基于vite6创建项目

pnpm create vue

vite已自动配置@路径,在.config.js  jsconfig.json 中

按需引用element-plus  官网中有

pnpm install element-plus

npm install -D unplugin-vue-components unplugin-auto-import

// vite.config.ts
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

如果要自定义主题,方式如下

如果您的项目也使用了 SCSS,可以直接修改 Element Plus 的样式变量。 新建一个样式文件,例如 styles/element/index.scss

pnpm i sass -D

index.css 代码定制

/* 只需要重写你需要的即可 */
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
  $colors: (
    'primary': (
      // 主色
      'base': #27ba9b,
    ),
    'success': (
      // 成功色
      'base': #1dc779,
    ),
    'warning': (
      // 警告色
      'base': #ffb302,
    ),
    'danger': (
      // 危险色
      'base': #e26237,
    ),
    'error': (
      // 错误色
      'base': #cf4444,
    ),
  )
)

样式重置common.scss

// 重置样式
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  font-size: 14px;
}
body {
  height: 100%;
  color: #333;
  min-width: 1240px;
  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI',
    'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei',
    sans-serif;
}
body,
ul,
h1,
h3,
h4,
p,
dl,
dd {
  padding: 0;
  margin: 0;
}
a {
  text-decoration: none;
  color: #333;
  outline: none;
}
i {
  font-style: normal;
}
input[type='text'],
input[type='search'],
input[type='password'],
input[type='checkbox'] {
  padding: 0;
  outline: none;
  border: none;
  -webkit-appearance: none;
  &::placeholder {
    color: #ccc;
  }
}
img {
  max-width: 100%;
  max-height: 100%;
  vertical-align: middle;
  background: #ebebeb url('@/assets/images/200.png') no-repeat center / contain;
}
ul {
  list-style: none;
}

#app {
  background: #f5f5f5;
  user-select: none;
}

.container {
  width: 1240px;
  margin: 0 auto;
  position: relative;
}
.ellipsis {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.ellipsis-2 {
  word-break: break-all;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.fl {
  float: left;
}

.fr {
  float: right;
}

.clearfix:after {
  content: '.';
  display: block;
  visibility: hidden;
  height: 0;
  line-height: 0;
  clear: both;
}

// reset element
.el-breadcrumb__inner.is-link {
  font-weight: 400 !important;
}

var.scss 定义变量

$xtxColor: #27ba9b;
$helpColor: #e26237;
$sucColor: #1dc779;
$warnColor: #ffb302;
$priceColor: #cf4444;

最后是config.js 文件指定配色相关,全代码都有注释

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

// element-plus 按需引入
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [ElementPlusResolver()]
    }),
    Components({
      // 指定配色为sass
      resolvers: [ElementPlusResolver({ importStyle: 'sass' })]
    })
  ],
  resolve: {
    alias: {
      // @ 实际转换为 src
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  css: {
    // 主题配色
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/styles/element/index.scss" as *;`
      }
    }
  },
  server: {
    port: 5600,
    open: true
  }
})

重启项目测试主题是否有用

pnpm i axios

封装它 代码要修改

// import loadingPlugin from "./plugins/loading/index.js";

// 详细参考 todoproject ---web-coce 中
import axios from 'axios'
const TOKEN_KEY = '__TOKEN__'
const pendingMap = new Map()

function getTokenAUTH() {
  return localStorage.getItem(TOKEN_KEY)
}

/**
 * 生成唯一的每个请求的唯一key
 * @param {*} config
 * @returns
 */
function getPendingKey(config) {
  let { url, method, params, data } = config
  if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象
  return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&')
}

/**
 * 储存每个请求的唯一cancel回调, 以此为标识
 * @param {*} config
 */
function addPending(config) {
  const pendingKey = getPendingKey(config)
  config.cancelToken =
    config.cancelToken ||
    new axios.CancelToken((cancel) => {
      if (!pendingMap.has(pendingKey)) {
        pendingMap.set(pendingKey, cancel)
      }
    })
}

/**
 * 删除重复的请求
 * @param {*} config
 */
function removePending(config) {
  const pendingKey = getPendingKey(config)
  if (pendingMap.has(pendingKey)) {
    const cancelToken = pendingMap.get(pendingKey)
    cancelToken(pendingKey)
    pendingMap.delete(pendingKey)
  }
}

/**
 * 关闭Loading层实例
 * @param {*} _options
 */
function closeLoading(_options) {
  if (_options.loading && LoadingInstance._count > 0) LoadingInstance._count--
  if (LoadingInstance._count === 0) {
    if (LoadingInstance._target) {
      LoadingInstance._target.close()
    }
    LoadingInstance._target = null
  }
}

const LoadingInstance = {
  _target: null,
  _count: 0
}

// 添加loading
// const hideLoading = () => app.config.globalProperties.$smallLoading.hideLoading;
// const showLoading = () =>
//     app.config.globalProperties.$smallLoading.showLoading();
// let onProgress = () => app.config.globalProperties.$smallLoading.onProgress;

export function __MyAxios__(axiosConfig, customOptions) {
  const service = axios.create({
    // baseURL: "", // 设置统一的请求前缀
    timeout: 30000, // 设置统一的超时时长
    responseType: 'json' // default
  })

  // 自定义配置
  let custom_options = {
    repeat_request_cancel: true, // 是否开启取消重复请求, 默认为 true
    loading: false, // 是否开启loading层效果, 默认为false
    reduct_data_format: true, // 是否开启简洁的数据结构响应, 默认为true
    error_message_show: true, // 是否开启接口错误信息展示,默认为true
    code_message_show: false, // 是否开启code不为0时的信息提示, 默认为false
    scroll_to_top: false, // 请求成功后滑动到页面顶部
    ...customOptions
  }

  // 请求拦截
  service.interceptors.request.use(
    (config) => {
      removePending(config)
      custom_options.repeat_request_cancel && addPending(config)

      // if (custom_options.loading) {
      //     LoadingInstance._count++;
      //     LoadingInstance._target = loadingPlugin();
      // }

      // 自动携带token
      if (getTokenAUTH() && typeof window !== 'undefined') {
        config.headers.Authorization = getTokenAUTH()
      }

      // 添加loading
      // showLoading();
      // config.onDownloadProgress = (progressEvent) => {
      //     let percentCompleted = Math.floor(
      //         (progressEvent.loaded * 100) / progressEvent?.total
      //     );
      //     onProgress()(percentCompleted);
      // };

      return config
    },
    (error) => {
      // hideLoading()();
      return Promise.reject(error)
    }
  )

  // 响应拦截
  service.interceptors.response.use(
    (response) => {
      removePending(response.config)
      if (custom_options.scroll_to_top) {
        window.scrollTo(0, 0)
      }

      closeLoading(custom_options)
      // loading
      // hideLoading()();

      return custom_options.reduct_data_format ? response.data : response
    },
    (error) => {
      if (custom_options.scroll_to_top) {
        window.scrollTo(0, 0)
      }
      error.config && removePending(error.config)
      closeLoading(custom_options)
      // loading
      // hideLoading()();
      // TODO:401
      const { status = 200 } = error.response || {}
      if (status === 401) {
        return window.location.replace('/login')
      }
      return Promise.reject(error) // 错误继续返回给到具体页面
    }
  )

  return service(axiosConfig)
}

封装二 测试

//css

创建pinia 持久化存储

import { createPinia, defineStore } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { ref } from 'vue'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export const useStore = defineStore(
  'store',
  () => {
    const profile = ref()
    const setProfile = (val) => {
      profile.value = val
      return profile.value
    }

    const clearProfile = () => {
      profile.value = undefined
    }
    return { setProfile, clearProfile, profile }
  },
  {
    persist: {
// 默认为true  自定义key
      key: 'my--xtx-store'
    }
  }
)

export default pinia

测试代码

import { useStore } from './stores'
const { setProfile, clearProfile } = useStore()
import { ref } from 'vue'

const testProfile = ref('我是测试用户')

const onSet = () => {
  setProfile(testProfile.value)
  console.log('🚀 ~ onSet ~ onSet:', testProfile.value)
}

const onClear = () => {
  clearProfile()
}

vue-router

import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

import Home from '@/views/Home/index.vue'
import Layout from '@/views/Layout/index.vue'
import Login from '@/views/Login/index.vue'
import Category from '@/views/Category/index.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'layout',
      component: Layout,
      children: [
        {
          path: '',
          name: 'home',
          component: Home
        },
        {
          path: 'category',
          name: 'category',
          component: Category
        }
      ]
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('@/views/Login/index.vue')
    }
  ]
})

export default router

测试  在App.vue Layout.vue 中放入路由出口

<router-view />

切换不同路由  /category

导入main.js 中导入重置样式common.cscc

查看

index.html 

<link

rel="stylesheet"

href="//at.alicdn.com/t/font_2143783_iq6z4ey5vu.css"

/>

请求放在一起,目前请求还未封装完成  ToDo

吸顶效果

<script setup>
// 使用VueUse
import { useScroll } from '@vueuse/core'
const { y } = useScroll(window)
import { useHeaderStore } from '@/stores/Header.js'
const HeaderStore = useHeaderStore()
import HeaderCart from './HeaderCart.vue'
</script>

<template>
  <div class="app-header-sticky" :class="{ show: y > 78 }">
    <div class="container">
      <RouterLink class="logo" to="/" />
      <!-- 导航区域 -->
      <ul class="app-header-nav">
        <li class="home">
          <RouterLink to="/">首页</RouterLink>
        </li>
        <li class="home" v-for="item in HeaderStore.category" :key="item.key">
          <RouterLink :to="`/category/${item.id}`">{{ item.name }}</RouterLink>
        </li>
      </ul>

      <div class="right">
        <RouterLink to="/">品牌</RouterLink>
        <RouterLink to="/">专题</RouterLink>
      </div>
      <HeaderCart></HeaderCart>
    </div>
  </div>
</template>

<style scoped lang="scss">
.app-header-sticky {
  width: 100%;
  height: 80px;
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  background-color: #fff;
  border-bottom: 1px solid #e4e4e4;
  // 此处为关键样式!!!
  // 状态一:往上平移自身高度 + 完全透明
  transform: translateY(-100%);
  opacity: 0;

  // 状态二:移除平移 + 完全不透明
  &.show {
    transition: all 1s linear;
    transform: none;
    opacity: 1;
  }

  .container {
    display: flex;
    align-items: center;
  }

  .logo {
    width: 200px;
    height: 80px;
    background: url('@/assets/images/logo.png') no-repeat right 2px;
    background-size: 160px auto;
  }

  .right {
    width: 220px;
    display: flex;
    text-align: center;
    padding-left: 40px;
    border-left: 2px solid $xtxColor;

    a {
      width: 38px;
      margin-right: 40px;
      font-size: 16px;
      line-height: 1;

      &:hover {
        color: $xtxColor;
      }
    }
  }
}

.app-header-nav {
  width: 820px;
  display: flex;
  padding-left: 40px;
  position: relative;
  z-index: 998;

  li {
    margin-right: 40px;
    width: 38px;
    text-align: center;

    a {
      font-size: 16px;
      line-height: 32px;
      height: 32px;
      display: inline-block;

      &:hover {
        color: $xtxColor;
        border-bottom: 1px solid $xtxColor;
      }
    }

    .active {
      color: $xtxColor;
      border-bottom: 1px solid $xtxColor;
    }
  }
}
</style>

之前方式对比 判断滚动距离监听是否添加class

head.js  调用一次就可以直接在其他组件使用其数据

import { ref } from 'vue'
import { defineStore } from 'pinia'
import {getCategoryAPI} from '@/api/user.js'
export const useHeaderStore = defineStore('Header', () => {
  const category = ref([])
  const getCategory= async ()=>{
  const res = await getCategoryAPI()
  // console.log(res.data.result);
  category.value = res.data.result
}
  return {category,getCategory }
})

图片懒加载  当图片进入视口区就开始加载

// 定义懒加载插件
import { useIntersectionObserver } from '@vueuse/core'
export const lazyPlugin = {
  install(app) {
    // 定义全局指令
    app.directive('img-lazy', {
      mounted(el, binding) {
        // el:绑定的元素
        // binding:指令的参数 binding-value:指令等于号后面绑定的表达式的值 图片url
        // console.log(el,binding.value);
        const { stop } = useIntersectionObserver(el, ([{ isIntersecting }]) => {
          // console.log(isIntersecting);
          if (isIntersecting) {
            // 图片懒加载
            el.src = binding.value
            // 成功加载后停止监听
            stop()
          }
        })
      }
    })
  }
}

激活模块

<RouterLink active-class="active" :to="`/category/${item.id}`">{{
            item.name
          }}</RouterLink>

列表无限加载

const disabled = ref(false)
const load = async () => {
  console.log('++++++++')

  reqDate.value.page++
  const res = await getSubCategoryAPI(reqDate.value)
  goodList.value = [...goodList.value, ...res.data.result.items]
  // 加载完毕 停止监听
  if (res.data.result.items.length === 0) {
    disabled.value = true
  }
}

 <div
        class="body"
        :infinite-scroll-disabled="disabled"
        v-infinite-scroll="load"
        style="overflow: auto"
      >
        <GoodsItem
          v-for="item in goodList"
          :key="item.id"
          :good="item"
        ></GoodsItem>
      </div>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaodunmeng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值