├── .nvmrc ├── .husky ├── .gitignore └── pre-commit ├── src ├── hooks │ └── user.ts ├── polyfill │ └── polyfill.ts ├── assets │ ├── scss │ │ ├── variables.scss │ │ ├── reset.scss │ │ └── index.scss │ ├── images │ │ ├── 首页.png │ │ ├── 个人中心.png │ │ ├── 版本介绍.png │ │ ├── loginbg.jpg │ │ └── mypic.jpg │ ├── iconfont │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ ├── iconfont.woff2 │ │ ├── font │ │ │ ├── d9eH2sUIxqPl.woff │ │ │ ├── d9eH2sUIxqPl.woff2 │ │ │ └── font.css │ │ ├── iconfont.css │ │ ├── iconfont.json │ │ ├── demo.css │ │ ├── iconfont.js │ │ └── demo_index.html │ └── vue.svg ├── store │ ├── modules │ │ ├── index.ts │ │ └── user.ts │ └── index.ts ├── utils │ ├── vconsole.ts │ └── commonTools.ts ├── typings │ └── index.ts ├── views │ ├── shopcart │ │ └── index.vue │ ├── account.vue │ ├── category │ │ └── index.vue │ ├── mycenter │ │ └── index.vue │ ├── home │ │ └── index.vue │ ├── login │ │ └── index.vue │ └── Home.vue ├── service │ ├── apiList.ts │ ├── handleError.ts │ ├── request.ts │ ├── error.ts │ ├── webRequest.ts │ └── requestList.ts ├── vite-env.d.ts ├── layout │ └── index.vue ├── main.ts ├── components │ └── TabBar.vue ├── global │ └── env.ts ├── App.vue └── router │ └── index.ts ├── .vscode └── extensions.json ├── .eslintignore ├── tailwind.config.js ├── .prettierignore ├── tsconfig.node.json ├── .gitignore ├── .eslintrc.js ├── .prettierrc.js ├── index.html ├── tsconfig.json ├── public └── vite.svg ├── components.d.ts ├── package.json ├── vite.config.ts └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /src/hooks/user.ts: -------------------------------------------------------------------------------- 1 | // 存放自定义hooks函数 2 | -------------------------------------------------------------------------------- /src/polyfill/polyfill.ts: -------------------------------------------------------------------------------- 1 | import "core-js/stable"; 2 | -------------------------------------------------------------------------------- /src/assets/scss/variables.scss: -------------------------------------------------------------------------------- 1 | // 主题色 2 | $theme-color: #af1d36; -------------------------------------------------------------------------------- /src/store/modules/index.ts: -------------------------------------------------------------------------------- 1 | // 将所有子store统一导出 2 | export * from './user' 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/images/首页.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/images/首页.png -------------------------------------------------------------------------------- /src/assets/images/个人中心.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/images/个人中心.png -------------------------------------------------------------------------------- /src/assets/images/版本介绍.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/images/版本介绍.png -------------------------------------------------------------------------------- /src/assets/images/loginbg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/images/loginbg.jpg -------------------------------------------------------------------------------- /src/assets/images/mypic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/images/mypic.jpg -------------------------------------------------------------------------------- /src/utils/vconsole.ts: -------------------------------------------------------------------------------- 1 | // 这是移动端控制台调试工具,需要调试就打开,不用就注释 2 | import vConsole from 'vconsole' 3 | const vconsole = new vConsole() 4 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/iconfont/font/d9eH2sUIxqPl.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/iconfont/font/d9eH2sUIxqPl.woff -------------------------------------------------------------------------------- /src/assets/iconfont/font/d9eH2sUIxqPl.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HSg666/vue3.3-Mobile-template/HEAD/src/assets/iconfont/font/d9eH2sUIxqPl.woff2 -------------------------------------------------------------------------------- /src/typings/index.ts: -------------------------------------------------------------------------------- 1 | // 存放接口或者类型 2 | export interface Person { 3 | userName: string 4 | password: string 5 | } 6 | 7 | export type loginForm = Person 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | tsconfig.json 4 | *.svg 5 | *.png 6 | *.jpg 7 | *.jpeg 8 | *.scss 9 | *.gif 10 | *.webp 11 | *.ttf 12 | index.html 13 | *.md -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], theme: { extend: {} }, plugins: [] } 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | /deploy 4 | *.yml 5 | *.yaml 6 | tsconfig.json 7 | *.svg 8 | *.png 9 | *.jpg 10 | *.jpeg 11 | *.gif 12 | *.webp 13 | *.ttf 14 | index.html 15 | *.md -------------------------------------------------------------------------------- /src/assets/iconfont/font/font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: '思源黑体 ExtraLight-subset'; 3 | font-weight: 400; 4 | src: 5 | url('d9eH2sUIxqPl.woff?t=1704938355835') format('woff'), 6 | url('d9eH2sUIxqPl.woff2?t=1704938355835') format('woff2'); 7 | font-display: swap; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/commonTools.ts: -------------------------------------------------------------------------------- 1 | // 复制功能 2 | export const copyText = (text: string) => { 3 | const input = document.createElement('input') 4 | input.value = text 5 | document.body.appendChild(input) 6 | input.select() 7 | document.execCommand('copy') 8 | document.body.removeChild(input) 9 | } 10 | -------------------------------------------------------------------------------- /src/views/shopcart/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ES2020", 6 | "moduleResolution": "node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts", "src/**/*.ts", "global/*.ts", "vite-env.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/service/apiList.ts: -------------------------------------------------------------------------------- 1 | // 接口列表,存放系统中所有请求的接口 2 | export const APIs = { 3 | GET_SHOPLIST: '/api/info', // 测试获取商品列表 4 | GET_SHOPLIST_PROD: '/api/h5/getShopList', // 线上获取商品列表 5 | POST_LOGIN: '/api/login', // 登录 6 | POST_UPLOADIMG: '/api/uploadimg', // 上传图片 7 | POST_EDITADDRESS: '/h5/postEditAddress', // 编辑地址 8 | } 9 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' 3 | 4 | // 实例化pinia 5 | export const pinia = createPinia() 6 | // 使用持久化存储插件 7 | pinia.use(piniaPluginPersistedstate) 8 | 9 | // 统一导出modules下的所有store 10 | export * from './modules' 11 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | 7 | const component: DefineComponent<{}, {}, any> 8 | 9 | export default component 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .eslintcache -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "vue-eslint-parser", 4 | parserOptions: { 5 | parser: "@typescript-eslint/parser", 6 | }, 7 | extends: ["plugin:vue/vue3-recommended", "plugin:prettier/recommended"], 8 | rules: { 9 | "vue/no-v-html": 0, 10 | "vue/v-on-event-hyphenation": 0, 11 | "vue/no-template-shadow": 0, 12 | "vue/multi-word-component-names": 0, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, // 使用单引号代替双引号 3 | printWidth: 200, // 超过最大值换行 4 | semi: false, // 结尾不用分号 5 | useTabs: true, // 缩进使用tab, 不使用空格 6 | tabWidth: 4, // tab 样式宽度 7 | bracketSpacing: true, // 对象数组, 文字间加空格 {a: 1} => { a: 1 } 8 | arrowParens: "avoid", // 如果可以, 自动去除括号 (x) => x 变为 x => x 9 | proseWrap: "preserve", 10 | htmlWhitespaceSensitivity: "ignore", 11 | trailingComma: "all", 12 | overrides: [ 13 | { 14 | files: '*.scss', 15 | options: { 16 | parser: 'scss', 17 | }, 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/service/handleError.ts: -------------------------------------------------------------------------------- 1 | // 在 handleError.ts 中调用 AxiosRequestError 类,将 axios 默认的 AxiosError 转成我们处理过的 AxiosRequestError 类 2 | import { AxiosError } from 'axios' 3 | import AxiosRequestError, { ErrorResponse } from './error' 4 | 5 | // 把 axios 的 错误 转成 我们已经封装的 AxiosRequestError 类,统一处理 6 | export function handleError(error: AxiosError | AxiosRequestError): AxiosRequestError { 7 | const err = error instanceof AxiosRequestError ? error : new AxiosRequestError(error.response?.status || 1, error.message, error, error.response?.data as ErrorResponse) 8 | return err 9 | } 10 | -------------------------------------------------------------------------------- /src/service/request.ts: -------------------------------------------------------------------------------- 1 | // 封装axios 2 | import axios, { AxiosInstance } from 'axios' 3 | import { handleError } from './handleError' 4 | 5 | function createRequestInstance(getServerUrl: () => string): AxiosInstance { 6 | const instance = axios.create({ 7 | timeout: 1000 * 60 * 5, // 超时时间 8 | withCredentials: true, // 允许跨域携带cookie 9 | baseURL: `${getServerUrl()}`, // 请求地址 10 | }) 11 | 12 | instance.interceptors.response.use( 13 | async res => { 14 | return res 15 | }, 16 | async err => { 17 | // 应用自定义错误处理函数 18 | err = await handleError(err) 19 | return Promise.reject(err) 20 | }, 21 | ) 22 | 23 | return instance 24 | } 25 | 26 | export default createRequestInstance 27 | -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore, acceptHMRUpdate } from 'pinia' 2 | 3 | export const userStore = defineStore({ 4 | id: 'user', 5 | state: () => ({ 6 | name: '很老很老的值', 7 | }), 8 | getters: { 9 | myName: state => { 10 | return `getters ${state.name}` 11 | }, 12 | }, 13 | actions: { 14 | changeName(name: string) { 15 | this.name = name 16 | }, 17 | }, 18 | persist: { 19 | storage: localStorage, //default localStorage 20 | //设置['phone'] -->只会将phone 这个key进行缓存 21 | paths: ['name'], 22 | }, 23 | }) 24 | 25 | // 这行代码是用于支持热模块替换(HMR)的。在Pinia中,它允许接受热更新并应用到使用了userStore的地方。 26 | if (import.meta.hot) { 27 | import.meta.hot.accept(acceptHMRUpdate(userStore, import.meta.hot)) 28 | } 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Vue3.3+TS移动端脚手架 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /src/views/account.vue: -------------------------------------------------------------------------------- 1 | 24 | 30 | 31 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import '@/polyfill/polyfill' // 安装web兼容低版本浏览器插件 2 | 3 | // 引入全局样式 4 | import '@/assets/scss/index.scss' 5 | 6 | // 引入阿里云字体图标css 7 | import '@/assets/iconfont/iconfont.css' 8 | import '@/assets/iconfont/iconfont.js' 9 | 10 | // 引入阿里巴巴普惠体 2.0字体 11 | import '@/assets/iconfont/font/font.css' 12 | 13 | import 'tailwindcss/tailwind.css' // 引入 Tailwind CSS 样式 14 | // 引入 Font Awesome 的 CSS 文件 15 | import '@fortawesome/fontawesome-free/css/all.css' 16 | 17 | // 这是移动端控制台调试工具,需要调试就打开,不用就注释 18 | // import '@/utils/vconsole.ts' 19 | 20 | import { createApp } from 'vue' 21 | import App from '@/App.vue' 22 | import router from './router' // 封装的路由 23 | import { pinia } from './store' // 封装的模块化pinia store 24 | import 'vant/es/toast/style' 25 | import 'vant/es/dialog/style' 26 | import { Toast, Dialog } from 'vant' 27 | 28 | const app = createApp(App) 29 | app.use(Toast) 30 | app.use(Dialog) 31 | 32 | app.use(pinia).use(router).mount('#app') 33 | -------------------------------------------------------------------------------- /src/components/TabBar.vue: -------------------------------------------------------------------------------- 1 | 6 | 43 | 44 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/global/env.ts: -------------------------------------------------------------------------------- 1 | // 全局环境变量 请求的服务器地址在此配置 2 | 3 | // 静态图片前缀 4 | export const fileServerAddress = 'http://192.168.1.159:8085' // 客户端地址(某后端接口) 5 | // export const fileServerAddress = 'http://192.168.1.159:8085' // 客户端地址(线上生产环境地址) 6 | 7 | // 正式环境 8 | export const PROD_ENV = { 9 | SERVER_URL: 'http://192.168.1.193:8090/', // 线上生产环境地址 10 | IS_DEV: 'false', // 是否为开发环境 11 | } 12 | 13 | // 开发环境 (一般本地服务器地址跟图片静态资源都是一样的,所以这里直接取静态资源的地址就行) 14 | export const DEV_ENV = { 15 | SERVER_URL: fileServerAddress, 16 | IS_DEV: 'true', 17 | } 18 | 19 | let isDEV = true // 默认为开发环境,但会根据当前环境动态更换开发或生产 20 | if (typeof window !== 'undefined') { 21 | isDEV = process.env.NODE_ENV === 'development' || [fileServerAddress].includes(window.location.host) 22 | } 23 | 24 | export type EnvKey = keyof typeof PROD_ENV 25 | 26 | // 调用这个函数获取当前的环境变量 27 | export function getProcessEnv(key: EnvKey): string | void { 28 | if (isDEV) { 29 | if (DEV_ENV[key] !== undefined) { 30 | return DEV_ENV[key] 31 | } 32 | return '' 33 | } 34 | if (PROD_ENV[key] !== undefined) { 35 | return PROD_ENV[key] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "allowJs": true, 5 | "sourceMap": true, 6 | "paths": { 7 | "@/*": ["src/*"], 8 | "@/assets/*": ["src/assets/*"], 9 | }, 10 | 11 | "target": "ES2020", 12 | "useDefineForClassFields": true, 13 | "module": "ES2020", 14 | "lib": [ 15 | "es2020", 16 | "es5", 17 | "es6", 18 | "DOM", 19 | "DOM.Iterable" 20 | ], 21 | "skipLibCheck": true, 22 | "esModuleInterop": true, 23 | /* Bundler mode */ 24 | "moduleResolution": "node", 25 | "allowImportingTsExtensions": false, 26 | "resolveJsonModule": true, 27 | "isolatedModules": true, 28 | "noEmit": true, 29 | "jsx": "preserve", 30 | /* Linting */ 31 | "strict": false, 32 | "noUnusedLocals": true, 33 | "noUnusedParameters": true, 34 | "noFallthroughCasesInSwitch": true 35 | }, 36 | "include": [ 37 | "src/**/*.ts", 38 | "src/**/*.d.ts", 39 | "src/**/*.tsx", 40 | "src/**/*.vue", 41 | "./components.d.ts", 42 | 43 | ], 44 | "references": [ 45 | { 46 | "path": "./tsconfig.node.json" 47 | } 48 | ], 49 | "exclude": [ 50 | "node_modules", 51 | "dist" 52 | ] 53 | } -------------------------------------------------------------------------------- /src/views/category/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; /* Project id 3804767 */ 3 | src: 4 | url('iconfont.woff?t=1545807318834') format('woff'), 5 | url('iconfont.woff2?t=1545807318834') format('woff2'), 6 | url('iconfont.ttf?t=1545807318834#iconfont') format('truetype'); 7 | } 8 | 9 | .iconfont { 10 | font-family: 'iconfont' !important; 11 | font-size: 16px; 12 | font-style: normal; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | } 16 | 17 | .icon-user:before { 18 | content: '\e64b'; 19 | } 20 | 21 | .icon-quanyi:before { 22 | content: '\e617'; 23 | } 24 | 25 | .icon-qianbao1:before { 26 | content: '\e672'; 27 | } 28 | 29 | .icon-dingdandingdanmingxishouzhimingxi:before { 30 | content: '\e78a'; 31 | } 32 | 33 | .icon-yiwancheng-yiban-02:before { 34 | content: '\e7a5'; 35 | } 36 | 37 | .icon-jifen:before { 38 | content: '\e600'; 39 | } 40 | 41 | .icon-wenti:before { 42 | content: '\e60e'; 43 | } 44 | 45 | .icon-hongbao:before { 46 | content: '\e784'; 47 | } 48 | 49 | .icon-shouhuodizhi:before { 50 | content: '\e64e'; 51 | } 52 | 53 | .icon-fenlei:before { 54 | content: '\e658'; 55 | } 56 | 57 | .icon-qiandao:before { 58 | content: '\e641'; 59 | } 60 | 61 | .icon-xiaoxi:before { 62 | content: '\e622'; 63 | } 64 | 65 | .icon-huoche:before { 66 | content: '\e6f0'; 67 | } 68 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, createWebHashHistory, RouteRecordRaw } from 'vue-router' 2 | 3 | // 主要tabbar 4 | export const layoutRoutes: Array = [ 5 | { path: '/', name: 'home', meta: { title: 'home', keepAlive: true }, component: () => import('@/views/home/index.vue') }, 6 | { 7 | path: '/category', 8 | name: 'category', 9 | meta: { 10 | title: 'category', 11 | // keepAlive: true, 12 | }, 13 | component: () => import('@/views/category/index.vue'), 14 | }, 15 | { path: '/mycenter', name: 'mycenter', meta: { title: 'mycenter' }, component: () => import('@/views/mycenter/index.vue') }, 16 | { path: '/shopcart', name: 'shopcart', meta: { title: 'shopcart' }, component: () => import('@/views/shopcart/index.vue') }, 17 | ] 18 | 19 | export const routes: Array = [ 20 | { 21 | path: '/', 22 | component: () => import('@/layout/index.vue'), 23 | redirect: '/index', 24 | // 需要layout的页面 25 | children: layoutRoutes, 26 | }, 27 | 28 | { path: '/login', name: 'login', component: () => import('@/views/login/index.vue') }, 29 | { path: '/account', name: 'account', component: () => import('@/views/account.vue') }, 30 | 31 | // 替代vue2中的'*'通配符路径 32 | { path: '/:pathMatch(.*)*', redirect: '/' }, 33 | ] 34 | 35 | const router = createRouter({ scrollBehavior: () => ({ left: 0, top: 0 }), history: createWebHashHistory(), routes }) 36 | 37 | router.beforeEach((_to, _from, next) => { 38 | next() 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /src/views/mycenter/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 30 | 31 | 62 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | RouterLink: typeof import('vue-router')['RouterLink'] 11 | RouterView: typeof import('vue-router')['RouterView'] 12 | TabBar: typeof import('./src/components/TabBar.vue')['default'] 13 | VanButton: typeof import('vant/es')['Button'] 14 | VanCard: typeof import('vant/es')['Card'] 15 | VanCell: typeof import('vant/es')['Cell'] 16 | VanCollapse: typeof import('vant/es')['Collapse'] 17 | VanCollapseItem: typeof import('vant/es')['CollapseItem'] 18 | VanDropdownItem: typeof import('vant/es')['DropdownItem'] 19 | VanDropdownMenu: typeof import('vant/es')['DropdownMenu'] 20 | VanGrid: typeof import('vant/es')['Grid'] 21 | VanGridItem: typeof import('vant/es')['GridItem'] 22 | VanIcon: typeof import('vant/es')['Icon'] 23 | VanImage: typeof import('vant/es')['Image'] 24 | VanList: typeof import('vant/es')['List'] 25 | VanNavBar: typeof import('vant/es')['NavBar'] 26 | VanPopup: typeof import('vant/es')['Popup'] 27 | VanSearch: typeof import('vant/es')['Search'] 28 | VanSwipe: typeof import('vant/es')['Swipe'] 29 | VanSwipeItem: typeof import('vant/es')['SwipeItem'] 30 | VanTab: typeof import('vant/es')['Tab'] 31 | VanTabbar: typeof import('vant/es')['Tabbar'] 32 | VanTabbarItem: typeof import('vant/es')['TabbarItem'] 33 | VanTabs: typeof import('vant/es')['Tabs'] 34 | VanTag: typeof import('vant/es')['Tag'] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3.3-mobile-template", 3 | "private": true, 4 | "version": "0.0.1", 5 | "scripts": { 6 | "start": "vite", 7 | "testMobile": "vite --host 192.168.1.193", 8 | "build": "vite build", 9 | "preview": "vite preview --port 4173", 10 | "prepare": "husky install" 11 | }, 12 | "dependencies": { 13 | "@fortawesome/fontawesome-free": "^6.7.2", 14 | "@nutui/nutui": "3", 15 | "axios": "^1.6.4", 16 | "core-js": "^3.35.0", 17 | "lodash-es": "^4.17.21", 18 | "pinia": "^2.1.7", 19 | "pinia-plugin-persistedstate": "2.3.0", 20 | "sourcemap-codec": "1.4.8", 21 | "vant": "^4.8.2", 22 | "vconsole": "^3.15.1", 23 | "vue": "^3.3.11", 24 | "vue-router": "^4.2.5" 25 | }, 26 | "devDependencies": { 27 | "@types/lodash-es": "^4.17.12", 28 | "@types/node": "^20.10.6", 29 | "@typescript-eslint/eslint-plugin": "^6.17.0", 30 | "@typescript-eslint/parser": "^6.17.0", 31 | "@vitejs/plugin-legacy": "^5.2.0", 32 | "@vitejs/plugin-vue": "^4.5.2", 33 | "autoprefixer": "^10.4.21", 34 | "eslint": "^8.56.0", 35 | "eslint-config-prettier": "^9.1.0", 36 | "eslint-plugin-prettier": "^5.1.2", 37 | "eslint-plugin-vue": "^9.19.2", 38 | "husky": "^8.0.3", 39 | "lint-staged": "^15.2.0", 40 | "postcss": "^8.5.3", 41 | "postcss-px-to-viewport": "^1.1.1", 42 | "prettier": "^3.1.1", 43 | "sass": "^1.86.3", 44 | "tailwindcss": "^3.4.17", 45 | "typescript": "^5.2.2", 46 | "unplugin-vue-components": "^0.26.0", 47 | "vite": "^5.0.8", 48 | "vite-plugin-imagemin": "^0.6.1", 49 | "vite-plugin-style-import": "^2.0.0", 50 | "vue-tsc": "^1.8.25" 51 | }, 52 | "lint-staged": { 53 | "*.js": "eslint --cache --fix", 54 | "*.vue": "eslint --cache --fix" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/service/error.ts: -------------------------------------------------------------------------------- 1 | // 作用:专门处理axios请求的错误信息 2 | import { AxiosError } from 'axios' 3 | import { showFailToast } from 'vant' 4 | 5 | // 服务器报错返回 Error 的时候的数据结构,可以和后端商量定义,但是所有接口的格式要统一 6 | export type ErrorResponse = { 7 | status: number // http 状态码,这个是必须的 8 | // 其他自定义类型类型 9 | } 10 | 11 | class AxiosRequestError extends Error { 12 | data: ErrorResponse | undefined 13 | 14 | raw: AxiosError 15 | 16 | isUnAuthorized = false // 权限错误 401 17 | 18 | isServerError = false // 服务器错误 500 等 19 | 20 | constructor(status: number, message: string, raw: AxiosError, data?: ErrorResponse) { 21 | // 调用父类「Error」的构造函数 22 | super(message) 23 | this.data = data // 后端返回的 data 24 | this.raw = raw // axios 返回的原始数据 25 | this.isUnAuthorized = status === 401 26 | this.isServerError = status >= 500 27 | 28 | switch (status) { 29 | case 400: 30 | showFailToast('请求错误(400)') 31 | break 32 | case 401: 33 | showFailToast('未授权,请重新登录(401)') 34 | break 35 | case 403: 36 | showFailToast('拒绝访问(403)') 37 | break 38 | case 404: 39 | showFailToast('请求出错(404)') 40 | break 41 | case 408: 42 | showFailToast('请求超时(408)') 43 | break 44 | case 500: 45 | showFailToast('服务器错误(500)') 46 | break 47 | case 501: 48 | showFailToast('服务未实现(501)') 49 | break 50 | case 502: 51 | showFailToast('网络错误(502)') 52 | break 53 | case 503: 54 | showFailToast('服务不可用(503)') 55 | break 56 | case 504: 57 | showFailToast('网络超时(504)') 58 | break 59 | case 505: 60 | showFailToast('HTTP版本不受支持(505)') 61 | break 62 | default: 63 | showFailToast(`连接出错(${this.message})!`) 64 | break 65 | } 66 | 67 | this.message = this.message || '' //给用户展示的错误消息,后续可以自定义 68 | } 69 | } 70 | 71 | export default AxiosRequestError 72 | -------------------------------------------------------------------------------- /src/service/webRequest.ts: -------------------------------------------------------------------------------- 1 | // 封装请求函数 2 | import API from './requestList' 3 | import AxiosRequestError from './error' 4 | import { handleError } from './handleError' 5 | import { getProcessEnv } from '../global/env' 6 | import { showLoadingToast, closeToast } from 'vant' 7 | import { useRouter } from 'vue-router' 8 | 9 | const $api = new API({ 10 | // 这个是请求的后台的服务的地址 11 | getServerUrl: () => { 12 | return `${getProcessEnv('SERVER_URL') || ''}` 13 | }, 14 | }) 15 | 16 | // 请求的拦截器 17 | $api.request.interceptors.request.use((config: any) => { 18 | const headers = config.headers || {} 19 | // 这个地方可以自定义请求头 20 | /** 21 | * 请求拦截器 22 | * 客户端发送请求 -> [请求拦截器] -> 服务器 23 | * token校验(JWT) : 接受服务器返回的token,存储到pinia或本地储存当中 24 | */ 25 | 26 | // 获取缓存中的token,如果没有则去登录页 27 | // const token = localStorage.getItem('longsheng_token') || '' 28 | // if (!token) { 29 | // // 跳转到登录页面 30 | // window.location.href = '/login' 31 | // } 32 | 33 | // 防止接口缓存 34 | config.headers = { 35 | 'Cache-Control': 'no-cache', 36 | Pragma: 'no-cache', 37 | Expires: '0', 38 | 'If-Modified-Since': '0', 39 | // Authorization: token, // 这个是自定义的请求头,还可以加 token 等 40 | ...headers, 41 | } 42 | if (config.loading) { 43 | showLoadingToast({ 44 | message: '加载中...', 45 | forbidClick: true, 46 | }) 47 | } 48 | 49 | return config 50 | }) 51 | 52 | // 响应的拦截器 53 | $api.request.interceptors.response.use( 54 | response => { 55 | const router = useRouter() 56 | // 如果用户没有登录或者token已失效,那么跳转到登录页。 同时清空当前已失效的免登token 57 | if (response.data.code === 401) { 58 | // window.localStorage.removeItem('XXXX_token') 59 | // router.push('login') 60 | // return Promise.reject(new Error('未授权,请重新登录')) 61 | } 62 | 63 | return response 64 | }, 65 | async (err: AxiosRequestError) => { 66 | closeToast() 67 | 68 | err = handleError(err) // 调用我们自定义的 错误处理方法 69 | 70 | return Promise.reject(err) 71 | }, 72 | ) 73 | 74 | // 在 page 页面就可以直接调用这个 $api 请求接口 75 | export default $api 76 | -------------------------------------------------------------------------------- /src/assets/scss/reset.scss: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v4.0 | 20180602 3 | License: none (public domain) 4 | */ 5 | 6 | html, 7 | body, 8 | div, 9 | span, 10 | applet, 11 | object, 12 | iframe, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | blockquote, 21 | pre, 22 | a, 23 | abbr, 24 | acronym, 25 | address, 26 | big, 27 | cite, 28 | code, 29 | del, 30 | dfn, 31 | em, 32 | img, 33 | ins, 34 | kbd, 35 | q, 36 | s, 37 | samp, 38 | small, 39 | strike, 40 | strong, 41 | sub, 42 | sup, 43 | tt, 44 | var, 45 | b, 46 | u, 47 | i, 48 | center, 49 | dl, 50 | dt, 51 | dd, 52 | menu, 53 | ol, 54 | ul, 55 | li, 56 | fieldset, 57 | form, 58 | label, 59 | legend, 60 | table, 61 | caption, 62 | tbody, 63 | tfoot, 64 | thead, 65 | tr, 66 | th, 67 | td, 68 | article, 69 | aside, 70 | canvas, 71 | details, 72 | embed, 73 | figure, 74 | figcaption, 75 | footer, 76 | header, 77 | hgroup, 78 | main, 79 | menu, 80 | nav, 81 | output, 82 | ruby, 83 | section, 84 | summary, 85 | time, 86 | mark, 87 | audio, 88 | video { 89 | margin: 0; 90 | padding: 0; 91 | border: 0; 92 | font-size: 100%; 93 | font: inherit; 94 | vertical-align: baseline; 95 | } 96 | /* HTML5 display-role reset for older browsers */ 97 | article, 98 | aside, 99 | details, 100 | figcaption, 101 | figure, 102 | footer, 103 | header, 104 | hgroup, 105 | main, 106 | menu, 107 | nav, 108 | section { 109 | display: block; 110 | } 111 | /* HTML5 hidden-attribute fix for newer browsers */ 112 | *[hidden] { 113 | display: none; 114 | } 115 | body { 116 | line-height: 1; 117 | } 118 | menu, 119 | ol, 120 | ul { 121 | list-style: none; 122 | } 123 | blockquote, 124 | q { 125 | quotes: none; 126 | } 127 | blockquote:before, 128 | blockquote:after, 129 | q:before, 130 | q:after { 131 | content: ''; 132 | content: none; 133 | } 134 | table { 135 | border-collapse: collapse; 136 | border-spacing: 0; 137 | } 138 | img { 139 | border: none; 140 | } 141 | 142 | /* 去除iPhone中默认的input样式 */ 143 | input[type='submit'], 144 | input[type='reset'], 145 | input[type='button'], 146 | input:focus, 147 | button:focus, 148 | select:focus, 149 | textarea:focus { 150 | outline: none; 151 | } 152 | input { 153 | -webkit-appearance: none; 154 | resize: none; 155 | border-radius: 0; 156 | } 157 | -------------------------------------------------------------------------------- /src/assets/scss/index.scss: -------------------------------------------------------------------------------- 1 | @use '@/assets/scss/reset.scss' as *; 2 | @use '@/assets/scss/variables.scss' as *; 3 | 4 | :root { 5 | font-family: '思源黑体 ExtraLight-subset' !important; 6 | color: black; 7 | background-color: white; 8 | } 9 | 10 | #app { 11 | width: 100%; 12 | min-height: 100vh; 13 | font-family: '思源黑体 ExtraLight-subset' !important; 14 | overflow-x: hidden; 15 | font-weight: normal; 16 | 17 | // 当用户试图选择页面上的文本时,这个属性可以防止文本被选中。 18 | -webkit-touch-callout: none; 19 | -webkit-user-select: none; 20 | -khtml-user-select: none; 21 | -moz-user-select: none; 22 | -ms-user-select: none; 23 | user-select: none; 24 | 25 | /* 这个属性是用于WebKit浏览器(例如Safari和Chrome)的。它控制文本的平滑显示方式。 26 | antialiased值表示文本将使用抗锯齿技术来平滑显示,从而使文本看起来更加清晰和光滑。 */ 27 | -webkit-font-smoothing: antialiased; 28 | /* 这个属性是用于Mozilla浏览器(例如Firefox)在OS X系统上的。它也控制文本的平滑显示方式。 29 | grayscale值表示文本将使用灰度平滑技术来显示,这通常会使文本看起来更加清晰,特别是在高分辨率屏幕上。 */ 30 | -moz-osx-font-smoothing: grayscale; 31 | } 32 | 33 | .flex-shrink-0 { 34 | flex-shrink: 0; 35 | } 36 | .flex-grow-0 { 37 | flex-grow: 0; 38 | } 39 | .flex-grow-1 { 40 | flex-grow: 1; 41 | } 42 | .flex-column { 43 | flex-direction: column; 44 | } 45 | .flex-wrap { 46 | flex-wrap: wrap; 47 | } 48 | .object-fit-cover { 49 | object-fit: cover; 50 | } 51 | .border-box { 52 | box-sizing: border-box; 53 | } 54 | .fw-bold { 55 | font-weight: bold; 56 | } 57 | .w-0 { 58 | width: 0; 59 | } 60 | .w-100 { 61 | width: 100%; 62 | } 63 | .w-50 { 64 | width: 50%; 65 | } 66 | .h-100 { 67 | height: 100%; 68 | } 69 | .text-ellipsis { 70 | overflow: hidden; 71 | white-space: nowrap; 72 | text-overflow: ellipsis; 73 | } 74 | .text-again-center { 75 | text-align: center; 76 | } 77 | .flex-column { 78 | flex-direction: column; 79 | } 80 | 81 | @mixin placeholderColor($color) { 82 | &::input-placeholder { 83 | /*WebKit browsers*/ 84 | color: $color; 85 | } 86 | &::-webkit-input-placeholder { 87 | /*WebKit browsers*/ 88 | color: $color; 89 | } 90 | &::-moz-input-placeholder { 91 | /*Mozilla Firefox*/ 92 | color: $color; 93 | } 94 | &::-ms-input-placeholder { 95 | /*Internet Explorer*/ 96 | color: $color; 97 | } 98 | } 99 | 100 | /* 适配iphonex */ 101 | 102 | @supports (bottom: env(safe-area-inset-bottom)) { 103 | .layout-container { 104 | padding-bottom: calc(env(safe-area-inset-bottom) + 50px); // 这里是重点 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/service/requestList.ts: -------------------------------------------------------------------------------- 1 | // 作用:封装api请求类 2 | import { AxiosRequestConfig, AxiosResponse } from 'axios' 3 | import createRequestInstance from './request' 4 | import { APIs } from './apiList' 5 | 6 | // 图片接口 7 | interface File { 8 | name: string 9 | type: string 10 | size: number 11 | lastModified: number 12 | } 13 | 14 | class API { 15 | request!: ReturnType 16 | 17 | get!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise 18 | 19 | delete!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise 20 | 21 | head!: >(url: string, config?: AxiosRequestConfig) => Promise 22 | 23 | options!: >(url: string, config?: AxiosRequestConfig) => Promise 24 | 25 | post!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise 26 | 27 | put!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise 28 | 29 | patch!: >(url: string, data?: any, config?: AxiosRequestConfig) => Promise 30 | 31 | constructor(options: { getServerUrl: () => string }) { 32 | const request = createRequestInstance(options.getServerUrl) 33 | this.request = request 34 | this.post = request.post.bind(this) 35 | this.put = request.put.bind(this) 36 | this.get = request.get.bind(this) 37 | this.delete = request.delete.bind(this) 38 | this.head = request.head.bind(this) 39 | this.options = request.options.bind(this) 40 | this.patch = request.patch.bind(this) 41 | } 42 | 43 | // 上传图片方法 44 | async uploadImage(file: any) { 45 | if (!file) return 46 | const formData = new FormData() 47 | formData.append('file', file) 48 | return this.request.post(APIs.POST_UPLOADIMG, formData, { 49 | headers: { 50 | 'Content-Type': 'multipart/form-data', 51 | }, 52 | }) 53 | } 54 | 55 | // 获取单个id数据 56 | async getOnlyId(key, id) { 57 | return this.get(`${key}?id=${id}`) 58 | } 59 | // 获取商品列表 以及搜索 60 | async getShopListFN(key, data) { 61 | // 写法1 传统 62 | // return this.get(`${key}?id=${data.id}&name=${data.name}&price=${data.price}&sale=${data.sale}&xp=${data.xp}`) 63 | // 写法2 解构 64 | const { id, name, price, sale, xp } = data 65 | return this.get(`${key}?id=${id}&name=${name}&price=${price}&sale=${sale}&xp=${xp}`) 66 | } 67 | // 传递单个id数据 68 | async postOnlyId(key, data) { 69 | return this.post(`${key}?goodsId=${data.goodsId}`) 70 | } 71 | } 72 | 73 | export default API 74 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "3804767", 3 | "name": "个人商城项目", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "icon-", 6 | "description": "", 7 | "glyphs": [ 8 | { 9 | "icon_id": "5297134", 10 | "name": "user", 11 | "font_class": "user", 12 | "unicode": "e64b", 13 | "unicode_decimal": 58955 14 | }, 15 | { 16 | "icon_id": "15112801", 17 | "name": "权益", 18 | "font_class": "quanyi", 19 | "unicode": "e617", 20 | "unicode_decimal": 58903 21 | }, 22 | { 23 | "icon_id": "2742650", 24 | "name": "钱包", 25 | "font_class": "qianbao1", 26 | "unicode": "e672", 27 | "unicode_decimal": 58994 28 | }, 29 | { 30 | "icon_id": "6129157", 31 | "name": "116订单、订单明细、收支明细", 32 | "font_class": "dingdandingdanmingxishouzhimingxi", 33 | "unicode": "e78a", 34 | "unicode_decimal": 59274 35 | }, 36 | { 37 | "icon_id": "24286299", 38 | "name": "已完成-已办-02", 39 | "font_class": "yiwancheng-yiban-02", 40 | "unicode": "e7a5", 41 | "unicode_decimal": 59301 42 | }, 43 | { 44 | "icon_id": "135132", 45 | "name": "积分", 46 | "font_class": "jifen", 47 | "unicode": "e600", 48 | "unicode_decimal": 58880 49 | }, 50 | { 51 | "icon_id": "425601", 52 | "name": "问题", 53 | "font_class": "wenti", 54 | "unicode": "e60e", 55 | "unicode_decimal": 58894 56 | }, 57 | { 58 | "icon_id": "579598", 59 | "name": "红包", 60 | "font_class": "hongbao", 61 | "unicode": "e784", 62 | "unicode_decimal": 59268 63 | }, 64 | { 65 | "icon_id": "629340", 66 | "name": "收货地址", 67 | "font_class": "shouhuodizhi", 68 | "unicode": "e64e", 69 | "unicode_decimal": 58958 70 | }, 71 | { 72 | "icon_id": "809965", 73 | "name": "分类", 74 | "font_class": "fenlei", 75 | "unicode": "e658", 76 | "unicode_decimal": 58968 77 | }, 78 | { 79 | "icon_id": "2811150", 80 | "name": "签到", 81 | "font_class": "qiandao", 82 | "unicode": "e641", 83 | "unicode_decimal": 58945 84 | }, 85 | { 86 | "icon_id": "5037312", 87 | "name": "消息", 88 | "font_class": "xiaoxi", 89 | "unicode": "e622", 90 | "unicode_decimal": 58914 91 | }, 92 | { 93 | "icon_id": "7723426", 94 | "name": "货车", 95 | "font_class": "huoche", 96 | "unicode": "e6f0", 97 | "unicode_decimal": 59120 98 | } 99 | ] 100 | } 101 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 64 | 76 | 77 | 92 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 56 | 76 | 136 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import autoprefixer from 'autoprefixer' // css自动添加兼容性前缀 4 | import path from 'path' 5 | import legacy from '@vitejs/plugin-legacy' // 兼容web低版本浏览器插件 6 | import { getProcessEnv } from './src/global/env' // 获取项目请求地址 7 | 8 | const timeStamp = new Date().getTime() // 为每次打包的文件新增当前的时间戳,防止页面缓存的问题 9 | // 全局自动注册components中的组件,需要使用到其中的组件无需import导入,直接使用即可 10 | import Components from 'unplugin-vue-components/vite' 11 | import { VantResolver } from 'unplugin-vue-components/resolvers' 12 | import postcsspxtoviewport from 'postcss-px-to-viewport' 13 | import viteImagemin from 'vite-plugin-imagemin' // 压缩png和jpg 14 | import tailwindcss from 'tailwindcss' // 引入 tailwindcss 15 | 16 | export default defineConfig({ 17 | // 如果是线上则用 ./ 否则本地用 / ,如果不配置这个上线后静态资源会访问不到 18 | base: process.env.NODE_ENV === 'production' ? './' : '/', 19 | server: { 20 | host: '0.0.0.0', 21 | port: 5173, 22 | proxy: { 23 | '^/api': { 24 | target: `${getProcessEnv('SERVER_URL') || ''}`, //目标源,目标服务器,真实请求地址 25 | changeOrigin: true, //支持跨域 26 | rewrite: path => path.replace(/^\/api/, '/api'), //重写真实路径,替换/api 27 | }, 28 | }, 29 | }, 30 | css: { 31 | postcss: { 32 | plugins: [ 33 | tailwindcss, // 添加 tailwindcss 插件 34 | // 浏览器兼容性 35 | autoprefixer({ overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8'] }), 36 | postcsspxtoviewport({ 37 | unitToConvert: 'px', // 要转化的单位 38 | viewportWidth: 750, // UI设计稿的宽度,如果你的设计稿是375就改成375 39 | // viewportHeight: 1334, // (Number) The height of the viewport. IOS6/7/8 40 | unitPrecision: 6, // 转换后的精度,即小数点位数 41 | propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换 42 | viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw 43 | fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw 44 | selectorBlackList: ['ignore-'], // 指定不转换为视窗单位的类名, 45 | minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换 46 | mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false 47 | replace: true, // 是否转换后直接更换属性值 48 | exclude: [/node_modules\/vant/], // 设置忽略文件,用正则做目录名匹配 49 | landscape: false, // 是否处理横屏情况 50 | }), 51 | ], 52 | }, 53 | // css预处理器 54 | preprocessorOptions: { 55 | scss: { 56 | // 引入 variables.scss 这样就可以在全局中使用 variables.scss中预定义的变量了 57 | // 给导入的路径最后加上 ; 58 | additionalData: '@use "@/assets/scss/variables.scss" as *;', 59 | api: 'modern-compiler', // or "modern" modern-compiler 最新的sass编译方式 60 | }, 61 | }, 62 | }, 63 | plugins: [ 64 | vue(), 65 | // 兼容web低版本浏览器插件 1 66 | legacy({ targets: ['cover 99.5%'] }), 67 | // 压缩png和jpg 68 | viteImagemin({ 69 | gifsicle: { 70 | // 降低优化级别 71 | optimizationLevel: 3, 72 | interlaced: false, 73 | }, 74 | optipng: { 75 | // 降低优化级别 76 | optimizationLevel: 3, 77 | }, 78 | mozjpeg: { 79 | // 提高图片质量 80 | quality: 80, 81 | }, 82 | pngquant: { 83 | // 提高图片质量范围 84 | quality: [0.9, 1], 85 | speed: 3, 86 | }, 87 | svgo: { plugins: [{ name: 'removeViewBox' }, { name: 'removeEmptyAttrs', active: false }] }, 88 | }), 89 | 90 | // 全局自动注册components中的组件,需要使用到其中的组件无需import导入,直接使用即可 91 | Components({ dts: true, resolvers: [VantResolver()] }), 92 | ], 93 | // 兼容web低版本浏览器插件 94 | optimizeDeps: { include: ['core-js', 'vant', 'axios', 'pinia', 'vue', 'vue-router'] }, 95 | //路径别名 alias 96 | resolve: { 97 | alias: { 98 | '@': path.resolve(__dirname, './src'), //把 src 的别名设置为 @ 99 | '@/assets': path.resolve(__dirname, './src/assets'), 100 | }, 101 | extensions: ['.mjs', '.ejs', '.tsx', '.jsx', '.js', '.json', '.ts'], // 这些类型的文件后缀的不需要写 102 | }, 103 | build: { 104 | outDir: path.resolve(__dirname, 'dist'), // 打包输出文件夹 105 | sourcemap: false, 106 | assetsInlineLimit: 4096, //小于此阈值 kb 的导入或引用资源将内联为 base64 编码 107 | emptyOutDir: true, // 每次构建时清除dist目录 108 | rollupOptions: { 109 | output: { 110 | entryFileNames: `js/[name]-[hash].${timeStamp}.js`, // 入口文件输出的文件夹名称 111 | chunkFileNames: `js/[name]-[hash].${timeStamp}.js`, //chunk包输出的文件夹名称 112 | assetFileNames: `[ext]/[name]-[hash].${timeStamp}.[ext]`, //静态文件输出的文件夹名称 113 | manualChunks(id) { 114 | if (id.includes('node_modules')) { 115 | return 'vendor' 116 | } 117 | }, 118 | }, 119 | }, 120 | chunkSizeWarningLimit: 1300, 121 | minify: 'terser', 122 | cssCodeSplit: true, 123 | }, 124 | }) 125 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 125 | 126 | 132 | -------------------------------------------------------------------------------- /src/assets/iconfont/demo.css: -------------------------------------------------------------------------------- 1 | /* Logo 字体 */ 2 | @font-face { 3 | font-family: "iconfont logo"; 4 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); 5 | src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), 6 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), 7 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), 8 | url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); 9 | } 10 | 11 | .logo { 12 | font-family: "iconfont logo"; 13 | font-size: 160px; 14 | font-style: normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | /* tabs */ 20 | .nav-tabs { 21 | position: relative; 22 | } 23 | 24 | .nav-tabs .nav-more { 25 | position: absolute; 26 | right: 0; 27 | bottom: 0; 28 | height: 42px; 29 | line-height: 42px; 30 | color: #666; 31 | } 32 | 33 | #tabs { 34 | border-bottom: 1px solid #eee; 35 | } 36 | 37 | #tabs li { 38 | cursor: pointer; 39 | width: 100px; 40 | height: 40px; 41 | line-height: 40px; 42 | text-align: center; 43 | font-size: 16px; 44 | border-bottom: 2px solid transparent; 45 | position: relative; 46 | z-index: 1; 47 | margin-bottom: -1px; 48 | color: #666; 49 | } 50 | 51 | 52 | #tabs .active { 53 | border-bottom-color: #f00; 54 | color: #222; 55 | } 56 | 57 | .tab-container .content { 58 | display: none; 59 | } 60 | 61 | /* 页面布局 */ 62 | .main { 63 | padding: 30px 100px; 64 | width: 960px; 65 | margin: 0 auto; 66 | } 67 | 68 | .main .logo { 69 | color: #333; 70 | text-align: left; 71 | margin-bottom: 30px; 72 | line-height: 1; 73 | height: 110px; 74 | margin-top: -50px; 75 | overflow: hidden; 76 | *zoom: 1; 77 | } 78 | 79 | .main .logo a { 80 | font-size: 160px; 81 | color: #333; 82 | } 83 | 84 | .helps { 85 | margin-top: 40px; 86 | } 87 | 88 | .helps pre { 89 | padding: 20px; 90 | margin: 10px 0; 91 | border: solid 1px #e7e1cd; 92 | background-color: #fffdef; 93 | overflow: auto; 94 | } 95 | 96 | .icon_lists { 97 | width: 100% !important; 98 | overflow: hidden; 99 | *zoom: 1; 100 | } 101 | 102 | .icon_lists li { 103 | width: 100px; 104 | margin-bottom: 10px; 105 | margin-right: 20px; 106 | text-align: center; 107 | list-style: none !important; 108 | cursor: default; 109 | } 110 | 111 | .icon_lists li .code-name { 112 | line-height: 1.2; 113 | } 114 | 115 | .icon_lists .icon { 116 | display: block; 117 | height: 100px; 118 | line-height: 100px; 119 | font-size: 42px; 120 | margin: 10px auto; 121 | color: #333; 122 | -webkit-transition: font-size 0.25s linear, width 0.25s linear; 123 | -moz-transition: font-size 0.25s linear, width 0.25s linear; 124 | transition: font-size 0.25s linear, width 0.25s linear; 125 | } 126 | 127 | .icon_lists .icon:hover { 128 | font-size: 100px; 129 | } 130 | 131 | .icon_lists .svg-icon { 132 | /* 通过设置 font-size 来改变图标大小 */ 133 | width: 1em; 134 | /* 图标和文字相邻时,垂直对齐 */ 135 | vertical-align: -0.15em; 136 | /* 通过设置 color 来改变 SVG 的颜色/fill */ 137 | fill: currentColor; 138 | /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 139 | normalize.css 中也包含这行 */ 140 | overflow: hidden; 141 | } 142 | 143 | .icon_lists li .name, 144 | .icon_lists li .code-name { 145 | color: #666; 146 | } 147 | 148 | /* markdown 样式 */ 149 | .markdown { 150 | color: #666; 151 | font-size: 14px; 152 | line-height: 1.8; 153 | } 154 | 155 | .highlight { 156 | line-height: 1.5; 157 | } 158 | 159 | .markdown img { 160 | vertical-align: middle; 161 | max-width: 100%; 162 | } 163 | 164 | .markdown h1 { 165 | color: #404040; 166 | font-weight: 500; 167 | line-height: 40px; 168 | margin-bottom: 24px; 169 | } 170 | 171 | .markdown h2, 172 | .markdown h3, 173 | .markdown h4, 174 | .markdown h5, 175 | .markdown h6 { 176 | color: #404040; 177 | margin: 1.6em 0 0.6em 0; 178 | font-weight: 500; 179 | clear: both; 180 | } 181 | 182 | .markdown h1 { 183 | font-size: 28px; 184 | } 185 | 186 | .markdown h2 { 187 | font-size: 22px; 188 | } 189 | 190 | .markdown h3 { 191 | font-size: 16px; 192 | } 193 | 194 | .markdown h4 { 195 | font-size: 14px; 196 | } 197 | 198 | .markdown h5 { 199 | font-size: 12px; 200 | } 201 | 202 | .markdown h6 { 203 | font-size: 12px; 204 | } 205 | 206 | .markdown hr { 207 | height: 1px; 208 | border: 0; 209 | background: #e9e9e9; 210 | margin: 16px 0; 211 | clear: both; 212 | } 213 | 214 | .markdown p { 215 | margin: 1em 0; 216 | } 217 | 218 | .markdown>p, 219 | .markdown>blockquote, 220 | .markdown>.highlight, 221 | .markdown>ol, 222 | .markdown>ul { 223 | width: 80%; 224 | } 225 | 226 | .markdown ul>li { 227 | list-style: circle; 228 | } 229 | 230 | .markdown>ul li, 231 | .markdown blockquote ul>li { 232 | margin-left: 20px; 233 | padding-left: 4px; 234 | } 235 | 236 | .markdown>ul li p, 237 | .markdown>ol li p { 238 | margin: 0.6em 0; 239 | } 240 | 241 | .markdown ol>li { 242 | list-style: decimal; 243 | } 244 | 245 | .markdown>ol li, 246 | .markdown blockquote ol>li { 247 | margin-left: 20px; 248 | padding-left: 4px; 249 | } 250 | 251 | .markdown code { 252 | margin: 0 3px; 253 | padding: 0 5px; 254 | background: #eee; 255 | border-radius: 3px; 256 | } 257 | 258 | .markdown strong, 259 | .markdown b { 260 | font-weight: 600; 261 | } 262 | 263 | .markdown>table { 264 | border-collapse: collapse; 265 | border-spacing: 0px; 266 | empty-cells: show; 267 | border: 1px solid #e9e9e9; 268 | width: 95%; 269 | margin-bottom: 24px; 270 | } 271 | 272 | .markdown>table th { 273 | white-space: nowrap; 274 | color: #333; 275 | font-weight: 600; 276 | } 277 | 278 | .markdown>table th, 279 | .markdown>table td { 280 | border: 1px solid #e9e9e9; 281 | padding: 8px 16px; 282 | text-align: left; 283 | } 284 | 285 | .markdown>table th { 286 | background: #F7F7F7; 287 | } 288 | 289 | .markdown blockquote { 290 | font-size: 90%; 291 | color: #999; 292 | border-left: 4px solid #e9e9e9; 293 | padding-left: 0.8em; 294 | margin: 1em 0; 295 | } 296 | 297 | .markdown blockquote p { 298 | margin: 0; 299 | } 300 | 301 | .markdown .anchor { 302 | opacity: 0; 303 | transition: opacity 0.3s ease; 304 | margin-left: 8px; 305 | } 306 | 307 | .markdown .waiting { 308 | color: #ccc; 309 | } 310 | 311 | .markdown h1:hover .anchor, 312 | .markdown h2:hover .anchor, 313 | .markdown h3:hover .anchor, 314 | .markdown h4:hover .anchor, 315 | .markdown h5:hover .anchor, 316 | .markdown h6:hover .anchor { 317 | opacity: 1; 318 | display: inline-block; 319 | } 320 | 321 | .markdown>br, 322 | .markdown>p>br { 323 | clear: both; 324 | } 325 | 326 | 327 | .hljs { 328 | display: block; 329 | background: white; 330 | padding: 0.5em; 331 | color: #333333; 332 | overflow-x: auto; 333 | } 334 | 335 | .hljs-comment, 336 | .hljs-meta { 337 | color: #969896; 338 | } 339 | 340 | .hljs-string, 341 | .hljs-variable, 342 | .hljs-template-variable, 343 | .hljs-strong, 344 | .hljs-emphasis, 345 | .hljs-quote { 346 | color: #df5000; 347 | } 348 | 349 | .hljs-keyword, 350 | .hljs-selector-tag, 351 | .hljs-type { 352 | color: #a71d5d; 353 | } 354 | 355 | .hljs-literal, 356 | .hljs-symbol, 357 | .hljs-bullet, 358 | .hljs-attribute { 359 | color: #0086b3; 360 | } 361 | 362 | .hljs-section, 363 | .hljs-name { 364 | color: #63a35c; 365 | } 366 | 367 | .hljs-tag { 368 | color: #333333; 369 | } 370 | 371 | .hljs-title, 372 | .hljs-attr, 373 | .hljs-selector-id, 374 | .hljs-selector-class, 375 | .hljs-selector-attr, 376 | .hljs-selector-pseudo { 377 | color: #795da3; 378 | } 379 | 380 | .hljs-addition { 381 | color: #55a532; 382 | background-color: #eaffea; 383 | } 384 | 385 | .hljs-deletion { 386 | color: #bd2c00; 387 | background-color: #ffecec; 388 | } 389 | 390 | .hljs-link { 391 | text-decoration: underline; 392 | } 393 | 394 | /* 代码高亮 */ 395 | /* PrismJS 1.15.0 396 | https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ 397 | /** 398 | * prism.js default theme for JavaScript, CSS and HTML 399 | * Based on dabblet (http://dabblet.com) 400 | * @author Lea Verou 401 | */ 402 | code[class*="language-"], 403 | pre[class*="language-"] { 404 | color: black; 405 | background: none; 406 | text-shadow: 0 1px white; 407 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 408 | text-align: left; 409 | white-space: pre; 410 | word-spacing: normal; 411 | word-break: normal; 412 | word-wrap: normal; 413 | line-height: 1.5; 414 | 415 | -moz-tab-size: 4; 416 | -o-tab-size: 4; 417 | tab-size: 4; 418 | 419 | -webkit-hyphens: none; 420 | -moz-hyphens: none; 421 | -ms-hyphens: none; 422 | hyphens: none; 423 | } 424 | 425 | pre[class*="language-"]::-moz-selection, 426 | pre[class*="language-"] ::-moz-selection, 427 | code[class*="language-"]::-moz-selection, 428 | code[class*="language-"] ::-moz-selection { 429 | text-shadow: none; 430 | background: #b3d4fc; 431 | } 432 | 433 | pre[class*="language-"]::selection, 434 | pre[class*="language-"] ::selection, 435 | code[class*="language-"]::selection, 436 | code[class*="language-"] ::selection { 437 | text-shadow: none; 438 | background: #b3d4fc; 439 | } 440 | 441 | @media print { 442 | 443 | code[class*="language-"], 444 | pre[class*="language-"] { 445 | text-shadow: none; 446 | } 447 | } 448 | 449 | /* Code blocks */ 450 | pre[class*="language-"] { 451 | padding: 1em; 452 | margin: .5em 0; 453 | overflow: auto; 454 | } 455 | 456 | :not(pre)>code[class*="language-"], 457 | pre[class*="language-"] { 458 | background: #f5f2f0; 459 | } 460 | 461 | /* Inline code */ 462 | :not(pre)>code[class*="language-"] { 463 | padding: .1em; 464 | border-radius: .3em; 465 | white-space: normal; 466 | } 467 | 468 | .token.comment, 469 | .token.prolog, 470 | .token.doctype, 471 | .token.cdata { 472 | color: slategray; 473 | } 474 | 475 | .token.punctuation { 476 | color: #999; 477 | } 478 | 479 | .namespace { 480 | opacity: .7; 481 | } 482 | 483 | .token.property, 484 | .token.tag, 485 | .token.boolean, 486 | .token.number, 487 | .token.constant, 488 | .token.symbol, 489 | .token.deleted { 490 | color: #905; 491 | } 492 | 493 | .token.selector, 494 | .token.attr-name, 495 | .token.string, 496 | .token.char, 497 | .token.builtin, 498 | .token.inserted { 499 | color: #690; 500 | } 501 | 502 | .token.operator, 503 | .token.entity, 504 | .token.url, 505 | .language-css .token.string, 506 | .style .token.string { 507 | color: #9a6e3a; 508 | background: hsla(0, 0%, 100%, .5); 509 | } 510 | 511 | .token.atrule, 512 | .token.attr-value, 513 | .token.keyword { 514 | color: #07a; 515 | } 516 | 517 | .token.function, 518 | .token.class-name { 519 | color: #DD4A68; 520 | } 521 | 522 | .token.regex, 523 | .token.important, 524 | .token.variable { 525 | color: #e90; 526 | } 527 | 528 | .token.important, 529 | .token.bold { 530 | font-weight: bold; 531 | } 532 | 533 | .token.italic { 534 | font-style: italic; 535 | } 536 | 537 | .token.entity { 538 | cursor: help; 539 | } 540 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.js: -------------------------------------------------------------------------------- 1 | ;(window._iconfont_svg_string_3804767 = 2 | ''), 3 | (function (e) { 4 | var c = (c = document.getElementsByTagName('script'))[c.length - 1], 5 | t = c.getAttribute('data-injectcss'), 6 | c = c.getAttribute('data-disable-injectsvg') 7 | if (!c) { 8 | var l, 9 | i, 10 | o, 11 | a, 12 | n, 13 | h = function (c, t) { 14 | t.parentNode.insertBefore(c, t) 15 | } 16 | if (t && !e.__iconfont__svg__cssinject__) { 17 | e.__iconfont__svg__cssinject__ = !0 18 | try { 19 | document.write('') 20 | } catch (c) { 21 | console && console.log(c) 22 | } 23 | } 24 | ;(l = function () { 25 | var c, 26 | t = document.createElement('div') 27 | ;(t.innerHTML = e._iconfont_svg_string_3804767), 28 | (t = t.getElementsByTagName('svg')[0]) && 29 | (t.setAttribute('aria-hidden', 'true'), 30 | (t.style.position = 'absolute'), 31 | (t.style.width = 0), 32 | (t.style.height = 0), 33 | (t.style.overflow = 'hidden'), 34 | (t = t), 35 | (c = document.body).firstChild ? h(t, c.firstChild) : c.appendChild(t)) 36 | }), 37 | document.addEventListener 38 | ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) 39 | ? setTimeout(l, 0) 40 | : ((i = function () { 41 | document.removeEventListener('DOMContentLoaded', i, !1), l() 42 | }), 43 | document.addEventListener('DOMContentLoaded', i, !1)) 44 | : document.attachEvent && 45 | ((o = l), 46 | (a = e.document), 47 | (n = !1), 48 | s(), 49 | (a.onreadystatechange = function () { 50 | 'complete' == a.readyState && ((a.onreadystatechange = null), d()) 51 | })) 52 | } 53 | function d() { 54 | n || ((n = !0), o()) 55 | } 56 | function s() { 57 | try { 58 | a.documentElement.doScroll('left') 59 | } catch (c) { 60 | return void setTimeout(s, 50) 61 | } 62 | d() 63 | } 64 | })(window) 65 | -------------------------------------------------------------------------------- /src/assets/iconfont/demo_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iconfont Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 36 | 37 | 38 |
39 |

40 | 41 | 42 |

43 | 53 |
54 |
55 |
    56 | 57 |
  • 58 | 59 |
    user
    60 |
    &#xe64b;
    61 |
  • 62 | 63 |
  • 64 | 65 |
    权益
    66 |
    &#xe617;
    67 |
  • 68 | 69 |
  • 70 | 71 |
    钱包
    72 |
    &#xe672;
    73 |
  • 74 | 75 |
  • 76 | 77 |
    116订单、订单明细、收支明细
    78 |
    &#xe78a;
    79 |
  • 80 | 81 |
  • 82 | 83 |
    已完成-已办-02
    84 |
    &#xe7a5;
    85 |
  • 86 | 87 |
  • 88 | 89 |
    积分
    90 |
    &#xe600;
    91 |
  • 92 | 93 |
  • 94 | 95 |
    问题
    96 |
    &#xe60e;
    97 |
  • 98 | 99 |
  • 100 | 101 |
    红包
    102 |
    &#xe784;
    103 |
  • 104 | 105 |
  • 106 | 107 |
    收货地址
    108 |
    &#xe64e;
    109 |
  • 110 | 111 |
  • 112 | 113 |
    分类
    114 |
    &#xe658;
    115 |
  • 116 | 117 |
  • 118 | 119 |
    签到
    120 |
    &#xe641;
    121 |
  • 122 | 123 |
  • 124 | 125 |
    消息
    126 |
    &#xe622;
    127 |
  • 128 | 129 |
  • 130 | 131 |
    货车
    132 |
    &#xe6f0;
    133 |
  • 134 | 135 |
136 |
137 |

Unicode 引用

138 |
139 | 140 |

Unicode 是字体在网页端最原始的应用方式,特点是:

141 |
    142 |
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • 143 |
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • 144 |
145 |
146 |

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

147 |
148 |

Unicode 使用步骤如下:

149 |

第一步:拷贝项目下面生成的 @font-face

150 |
@font-face {
152 |   font-family: 'iconfont';
153 |   src: url('iconfont.woff2?t=1669963366750') format('woff2'),
154 |        url('iconfont.woff?t=1669963366750') format('woff'),
155 |        url('iconfont.ttf?t=1669963366750') format('truetype');
156 | }
157 | 
158 |

第二步:定义使用 iconfont 的样式

159 |
.iconfont {
161 |   font-family: "iconfont" !important;
162 |   font-size: 16px;
163 |   font-style: normal;
164 |   -webkit-font-smoothing: antialiased;
165 |   -moz-osx-font-smoothing: grayscale;
166 | }
167 | 
168 |

第三步:挑选相应图标并获取字体编码,应用于页面

169 |
170 | <span class="iconfont">&#x33;</span>
172 | 
173 |
174 |

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

175 |
176 |
177 |
178 |
179 |
    180 | 181 |
  • 182 | 183 |
    184 | user 185 |
    186 |
    .icon-user 187 |
    188 |
  • 189 | 190 |
  • 191 | 192 |
    193 | 权益 194 |
    195 |
    .icon-quanyi 196 |
    197 |
  • 198 | 199 |
  • 200 | 201 |
    202 | 钱包 203 |
    204 |
    .icon-qianbao1 205 |
    206 |
  • 207 | 208 |
  • 209 | 210 |
    211 | 116订单、订单明细、收支明细 212 |
    213 |
    .icon-dingdandingdanmingxishouzhimingxi 214 |
    215 |
  • 216 | 217 |
  • 218 | 219 |
    220 | 已完成-已办-02 221 |
    222 |
    .icon-yiwancheng-yiban-02 223 |
    224 |
  • 225 | 226 |
  • 227 | 228 |
    229 | 积分 230 |
    231 |
    .icon-jifen 232 |
    233 |
  • 234 | 235 |
  • 236 | 237 |
    238 | 问题 239 |
    240 |
    .icon-wenti 241 |
    242 |
  • 243 | 244 |
  • 245 | 246 |
    247 | 红包 248 |
    249 |
    .icon-hongbao 250 |
    251 |
  • 252 | 253 |
  • 254 | 255 |
    256 | 收货地址 257 |
    258 |
    .icon-shouhuodizhi 259 |
    260 |
  • 261 | 262 |
  • 263 | 264 |
    265 | 分类 266 |
    267 |
    .icon-fenlei 268 |
    269 |
  • 270 | 271 |
  • 272 | 273 |
    274 | 签到 275 |
    276 |
    .icon-qiandao 277 |
    278 |
  • 279 | 280 |
  • 281 | 282 |
    283 | 消息 284 |
    285 |
    .icon-xiaoxi 286 |
    287 |
  • 288 | 289 |
  • 290 | 291 |
    292 | 货车 293 |
    294 |
    .icon-huoche 295 |
    296 |
  • 297 | 298 |
299 |
300 |

font-class 引用

301 |
302 | 303 |

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

304 |

与 Unicode 使用方式相比,具有如下特点:

305 |
    306 |
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • 307 |
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • 308 |
309 |

使用步骤如下:

310 |

第一步:引入项目下面生成的 fontclass 代码:

311 |
<link rel="stylesheet" href="./iconfont.css">
312 | 
313 |

第二步:挑选相应图标并获取类名,应用于页面:

314 |
<span class="iconfont icon-xxx"></span>
315 | 
316 |
317 |

" 318 | iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

319 |
320 |
321 |
322 |
323 |
    324 | 325 |
  • 326 | 329 |
    user
    330 |
    #icon-user
    331 |
  • 332 | 333 |
  • 334 | 337 |
    权益
    338 |
    #icon-quanyi
    339 |
  • 340 | 341 |
  • 342 | 345 |
    钱包
    346 |
    #icon-qianbao1
    347 |
  • 348 | 349 |
  • 350 | 353 |
    116订单、订单明细、收支明细
    354 |
    #icon-dingdandingdanmingxishouzhimingxi
    355 |
  • 356 | 357 |
  • 358 | 361 |
    已完成-已办-02
    362 |
    #icon-yiwancheng-yiban-02
    363 |
  • 364 | 365 |
  • 366 | 369 |
    积分
    370 |
    #icon-jifen
    371 |
  • 372 | 373 |
  • 374 | 377 |
    问题
    378 |
    #icon-wenti
    379 |
  • 380 | 381 |
  • 382 | 385 |
    红包
    386 |
    #icon-hongbao
    387 |
  • 388 | 389 |
  • 390 | 393 |
    收货地址
    394 |
    #icon-shouhuodizhi
    395 |
  • 396 | 397 |
  • 398 | 401 |
    分类
    402 |
    #icon-fenlei
    403 |
  • 404 | 405 |
  • 406 | 409 |
    签到
    410 |
    #icon-qiandao
    411 |
  • 412 | 413 |
  • 414 | 417 |
    消息
    418 |
    #icon-xiaoxi
    419 |
  • 420 | 421 |
  • 422 | 425 |
    货车
    426 |
    #icon-huoche
    427 |
  • 428 | 429 |
430 |
431 |

Symbol 引用

432 |
433 | 434 |

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 435 | 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

436 |
    437 |
  • 支持多色图标了,不再受单色限制。
  • 438 |
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • 439 |
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • 440 |
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • 441 |
442 |

使用步骤如下:

443 |

第一步:引入项目下面生成的 symbol 代码:

444 |
<script src="./iconfont.js"></script>
445 | 
446 |

第二步:加入通用 CSS 代码(引入一次就行):

447 |
<style>
448 | .icon {
449 |   width: 1em;
450 |   height: 1em;
451 |   vertical-align: -0.15em;
452 |   fill: currentColor;
453 |   overflow: hidden;
454 | }
455 | </style>
456 | 
457 |

第三步:挑选相应图标并获取类名,应用于页面:

458 |
<svg class="icon" aria-hidden="true">
459 |   <use xlink:href="#icon-xxx"></use>
460 | </svg>
461 | 
462 |
463 |
464 | 465 |
466 |
467 | 486 | 487 | 488 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **[vue3.3-Mobile-template](https://github.com/HSg666/vue3.3-Mobile-template)** 2 | 3 | 基于Vue3.3 + TS + Vant4 + Vite5 + Pinia + tailwindcss + ViewPort适配 + Sass + Axios封装 + vconsole调试工具,搭建的H5移动端开发模板,开箱即用的。 4 | 5 | ### 环境要求: 6 | 7 | Node:18.20.2 pnpm:10.5.2 8 | 9 | 必须装上安装pnpm,没装的看这篇文章 https://blog.csdn.net/Steven_Son/article/details/135151622 10 | 11 | 代码管理工具推荐用:sourceTree 12 | 13 | ### 项目预览 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ### 项目结构 23 | 24 | ```js 25 | learn-vite -- UI 主目录 26 | ├── dist 打包后自动生成的文件夹 27 | ├── public -- 静态资源 28 | ├ ├── favicon.ico -- 图标 29 | ├── src -- 源码目录 30 | ├ ├── assets -- 全局静态资源 31 | ├ ├ ├── iconfont -- 字体和字体图标 32 | ├ ├ ├── images -- 图片存放路径 33 | ├ ├ ├── json -- 静态json 34 | ├ ├ └── scss -- index.scss 全局样式,reset.scss初始化样式 35 | ├ ├── components -- 封装的组件 36 | ├ ├── global 配置全局URL环境变量 37 | ├ ├── hooks -- vue3 Hooks 38 | ├ ├── layout -- 全局Tabbar配置、keep-alive可配置需长缓存的路由 39 | ├ ├── polyfill 解决浏览器兼容性的文件 40 | ├ ├── router -- VUE 路由 41 | ├ ├ ├── index -- 路由入口 42 | ├ ├── service 43 | ├ ├ ├── apiList.ts -- 接口列表 44 | ├ ├ ├── error.ts -- 封装的接口错误提示 45 | ├ ├ ├── handleError.ts -- 处理接口请求错误 46 | ├ ├ ├── requestList.ts -- 请求函数列表 47 | ├ ├ └── webRequest.ts -- 封装Axios请求函数 48 | ├ ├── store -- Pinia 49 | ├ ├ ├── index -- 统一导出整个pinia和store 50 | ├ ├ └── modules.ts store模块化 51 | ├ ├── typings -- 存储TS类型别名 52 | ├ ├── utils -- 工具包 53 | ├ ├── views -- 业务上的 vue 页面 54 | ├ ├── App.vue -- 根组件 55 | ├ └── main.ts -- 入口 ts 56 | ├── components.d.ts -- 自动注册组件文件 57 | ├── .eslintrc.js -- ESLint 配置 58 | ├── .gitignore -- git 忽略 59 | ├── tsconfig.json -- vscode 路径引入配置 60 | ├── tailwind.config.js -- tailwindcss 配置文件 61 | ├── index.html -- 首页 62 | ├── package.json -- 依赖管理 63 | ├── vite.config.ts -- vite5的相关配置 64 | └── windi.config.ts -- WindiCSS的配置文件 65 | ``` 66 | 67 | ## 命令 68 | 69 | ```js 70 | git clone https://github.com/HSg666/vue3.3-Mobile-template 71 | // 或 git clone git@github.com:HSg666/vue3.3-Mobile-template 72 | pnpm i // 装依赖 73 | pnpm start // 启动 74 | pnpm run build // 打包 75 | rm -rf node_modules // 强行删除依赖包 76 | ``` 77 | 准备打包上线时请看 配置全局URL环境变量,检查完配置后再执行pnpm run build 打包 78 | 79 | 部署上线后如果出现页面刷新报Nginx404,请看这篇文章并对照检查你的router/index.ts中的mode模式,更改配置后再试试就OK了。 80 | 81 | https://blog.csdn.net/Steven_Son/article/details/135414494 82 | 83 | ## 目录 84 | 85 | - [1、封装Router](#router) 86 | - [2、Vant4自动按需导入](#vant4) 87 | - [3、封装Axios请求函数、接口列表、请求错误处理](#axios) 88 | - [4、配置全局URL环境变量](#globalUrl) 89 | - [5、配置alias路径别名](#alias) 90 | - [6、封装Pinia、模块化、长缓存](#pinia) 91 | - [7、postcss-px-to-viewport移动端适配](#postcss-px-to-viewport) 92 | - [8、自动导入组件](#unplugin-vue-components) 93 | - [9、封装TabBar布局容器](#tabbar) 94 | - [10、tailwindcss样式库](#tailwindcss) 95 | - [11、fontawesome-free字体图标库](#fontawesome-free) 96 | - [12、初始化全局CSS和防止页面文本被用户选中](#resetcss) 97 | - [13、字体与字体图标](#iconfont) 98 | - [14、性能优化](#xnyh) 99 | - [15、代码规范](#pretter) 100 | - [16、配置兼容性](#jrx) 101 | - [17、已配置第三方工具库](#threeTool) 102 | - [18、拓展](#tuozhan) 103 | 104 | ## 1、封装Router 105 | 106 | 路径:src/router/index.ts 107 | 108 | ```js 109 | // 需要Tabbar的组件在layoutRoutes中添加路由,Tabbar就是页面底部的 精选、分类、购物车、我的 110 | export const layoutRoutes: Array = [ 111 | { 112 | path: '/', 113 | name: 'home', 114 | meta: { 115 | title: 'home', 116 | keepAlive: true, 117 | }, 118 | component: () => import('@/views/home/index.vue'), 119 | }, 120 | { 121 | path: '/category', 122 | name: 'category', 123 | meta: { 124 | title: 'category', 125 | // keepAlive: true, 126 | }, 127 | component: () => import('@/views/category/index.vue'), 128 | }, 129 | ] 130 | ``` 131 | 132 | ```js 133 | // 不需要Tabbar的组件在routes中添加路由,即页面底部空空如也的组件。 134 | export const routes: Array = [ 135 | // 这个是布局,不用改 136 | { 137 | path: '/', 138 | component: () => import('@/layout/index.vue'), 139 | redirect: '/index', 140 | // 需要layout的页面 141 | children: layoutRoutes, 142 | }, 143 | // 注册的路由类似登录页 144 | { 145 | path: '/login', 146 | name: 'login', 147 | component: () => import('@/views/login/index.vue'), 148 | }, 149 | ] 150 | 151 | ``` 152 | ## 2、移动端UI库采用Vant4 153 | 项目已经配置好按需导入和组件自动注册了,页面直接使用即可,无需手动注册。 154 | 155 | 除了Toast轻提示使用时需要手动引入,其他都无需手动引入。示例如下: 156 | 157 | 在vant4 Toast的函数名都改了,大家看官方文档就知道。 158 | 159 | ```js 160 | // 示例: 161 | 168 | 171 | ``` 172 | 173 | 自动注册的组件都保存在项目根目录的 components.d.ts中,可自行查看。 174 | 175 | 配置详情:https://blog.csdn.net/Steven_Son/article/details/135544198?spm=1001.2014.3001.5501 176 | 177 | UI库官网地址:https://vant-ui.github.io/vant/#/zh-CN/button 178 | 179 | ## 3、封装Axios 180 | 文件路径:/src/service 181 | 182 | ### 1、新增axios并封装,还新增了自定义请求错误处理函数,请求类。 183 | 184 | 这是二次封装的axios响应拦截器,如果要做全局接口状态码判断就打开下面代码,接口返回401时就自动跳转到登录页,同时清空已有但已失效的token,没有失效的token也是清理,不会报错的,程序执行正常的。 185 | 186 | ```js 187 | // 路径:/src/service/webRequest.ts 188 | // 响应的拦截器 189 | $api.request.interceptors.response.use( 190 | response => { 191 | const router = useRouter() 192 | // 如果用户没有登录或者token已失效,那么跳转到登录页。 同时清空当前已失效的免登token 193 | if (response.data.code === 401) { 194 | // window.localStorage.removeItem('XXXX_token') 195 | // router.push('login') 196 | // return Promise.reject(new Error('未授权,请重新登录')) 197 | } 198 | 199 | return response 200 | }, 201 | async (err: AxiosRequestError) => { 202 | closeToast() 203 | 204 | err = handleError(err) // 调用我们自定义的 错误处理方法 205 | 206 | return Promise.reject(err) 207 | }, 208 | ) 209 | ``` 210 | 211 | ### 2、封装api列表 apiList 212 | 213 | 封装的axios配合api接口使用模板 214 | 215 | (1)、先把接口添加进接口列表 216 | ```js 217 | // 路径:/src/service/requestList.ts 218 | export const APIs = { 219 | GET_SHOPLIST: '/h5/getShopList', // 获取商品列表 220 | POST_EDITADDRESS: '/h5/postEditAddress' // 编辑地址 221 | } 222 | ``` 223 | (2)、页面使用 224 | ```js 225 | // account.vue 226 | import AxiosRequestError from '@/service/error' // 引入自定义错误处理函数 227 | import $api from '@/service/webRequest' // 封装好的axios请求函数 228 | import { APIs } from '@/service/apiList' // 接口列表 229 | 230 | // 二选一即可 231 | 232 | // async await 写法 我一般用这种写法,原生promise太繁琐 233 | // ---- get请求方式 234 | const getShop = async () => { 235 | try { 236 | const res = await $api.getShopList() 237 | if(res.code == 200){ 238 | console.log(res,'res') 239 | }else{ 240 | showToast(res.msg) 241 | } 242 | } catch (error: AxiosRequestError) { 243 | console.dir(error, 'error') 244 | } 245 | } 246 | 247 | // ---- post请求方式 248 | const EditAddress = async () => { 249 | let params = { 250 | id: 1, 251 | name: '张三', 252 | phone: '13888888888', 253 | address: '北京市海淀区', 254 | } 255 | try { 256 | const res = await $api.post(APIs.POST_EDITADDRESS, params) 257 | if(res.code == 200){ 258 | console.log(res,'res') 259 | }else{ 260 | showToast(res.msg) 261 | } 262 | } catch (error: AxiosRequestError) { 263 | console.dir(error, 'error') 264 | } 265 | } 266 | 267 | // 原生Primise .then .catch ---- get请求方式 268 | const params = { user:'', password:'' } // 传参将需要传的值放入即可,跟vue2一样 269 | $api.get(APIs.GET_SHOPLIST, params) 270 | .then(() => {}) 271 | .catch((err: AxiosRequestError) => { 272 | console.dir(err, 'err') 273 | }) 274 | 275 | // 原生Primise .then .catch ---- post请求方式 276 | 277 | 278 | ``` 279 | (3)、用console.dir可以捕获到详细的错误信息,还能看到我们封装的错误处理函数 280 | 281 | data: undefined, // 接口返回值为undefined 282 | 283 | isServerError: false, // 是否为服务器出错 284 | 285 | isUnAuthorized: false, // 是否已通过鉴权,也就是常见的登录状态 286 | 287 | (4)、如果要添加或使用自定义请求函数,请在src/service/requestList.ts中添加,类似于已经存在的上传图片接口 288 | ### 3、自定义封装请求函数 289 | 1、先到service/apiList.ts中添加接口 290 | ```js 291 | export const APIs = { 292 | GET_PRDDETAIL: '/m/CcbLifeGoods/getyById', // 获取商品详情 293 | } 294 | ``` 295 | 2、在service/requestList.ts中添加,用模板字符串自行拼接 296 | 注:由于get如果传多个参数需要自己拼接的,不像post直接传整个json对象给后端,所以你打算用get传多个参数,请看一下案例(采用模板字符串和&拼接方式),见getOnlyId和getShopListFN;想用post单独传参也是ok的,见postOnlyId。 297 | ```js 298 | import { APIs } from './apiList' 299 | class API { 300 | // 获取单个id数据 301 | async getOnlyId(key, id) { 302 | return this.get(`${key}?id=${id}`) 303 | } 304 | // 获取商品列表 以及搜索 305 | // key:接口名称 data: 所有参数组成的对象 306 | async getShopListFN(key, data) { 307 | // 写法1 传统 308 | // return this.get(`${key}?id=${data.id}&name=${data.name}&price=${data.price}&sale=${data.sale}&xp=${data.xp}`) 309 | // 写法2 解构 310 | const { id,name,price,sale,xp } = data 311 | // return this.get(`${key}?id=${id}&name=${name}&price=${price}&sale=${sale}&xp=${xp}`) 312 | } 313 | 314 | 315 | } 316 | ``` 317 | 3、页面使用 318 | 注:自定义的方法都是绑定在$api this上的,所以直接在它身上取就可以 319 | ```js 320 | import $api from '@/service/webRequest' // 封装好的axios请求函数 321 | import { APIs } from '@/service/apiList' // 接口列表 322 | 323 | const getData = async () => { 324 | // 单一传 325 | let id = route.query.id 326 | const { data: res } = await $api.getOnlyId(APIs.GET_PRDDETAIL, id) 327 | 328 | // 多个参数组装为一个对象传入 329 | let params = { 330 | id:1, 331 | name:'HSg', 332 | price:99, 333 | sale:3, 334 | xp:11 335 | } 336 | const { data: res } = await $api.getShopListFN(APIs.GET_PRDDETAIL, params) 337 | } 338 | ``` 339 | ## 4、配置全局URL环境变量 340 | 开发和正式环境地址在 global/env.ts 中配置 341 | ```js 342 | // 静态图片前缀 343 | export const fileServerAddress = 'http://192.168.1.179:8081/' // 客户端地址(某后端接口地址) 344 | // const fileServerAddress = 'http://192.168.1.179:8081/' ; // 客户端地址(线上生产环境地址) 345 | 346 | // 正式环境 347 | export const PROD_ENV = { 348 | SERVER_URL: 'http://192.168.1.193:8090/', // 线上生产环境地址 349 | IS_DEV: 'false', // 是否为开发环境 350 | } 351 | 352 | // 开发环境 (一般本地服务器地址跟图片静态资源都是一样的,所以这里直接取静态资源的地址就行) 353 | export const DEV_ENV = { 354 | SERVER_URL: fileServerAddress, 355 | IS_DEV: 'true', 356 | } 357 | 358 | 359 | 360 | let isDEV = true // 默认为开发环境,但会根据当前环境动态更换开发或生产 361 | if (typeof window !== 'undefined') { 362 | isDEV = process.env.NODE_ENV === 'development' || [fileServerAddress].includes(window.location.host) 363 | } 364 | ``` 365 | 366 | 静态图片前缀页面使用案例 367 | ```js 368 | home.vue 369 | // 图片前缀 370 | import { fileServerAddress } from '@/global/env' 371 | // 1、标签中 372 | 373 | 374 | // 2、函数中 375 | 直接使用fileServerAddress 376 | ``` 377 | 378 | 379 | 380 | 381 | ## 5、配置路径别名 alias 382 | 示例:@/store 只要在src下的都能这样简写 383 | 384 | 总共分为4步: 385 | 386 | 1、vite.config.ts 387 | 388 | ```js 389 | import path from 'path' 390 | export default defineConfig({ 391 | //新增 392 | resolve: { 393 | alias: { 394 | '@/assets': path.resolve(__dirname, './src/assets'), 395 | }, 396 | }, 397 | }) 398 | ``` 399 | 2、tsconfig.json 400 | ```js 401 | "paths": { 402 | "@/assets/*": ["src/assets/*"], 403 | } 404 | ``` 405 | 3、配置好全局使用 406 | 例如main.ts引入 407 | ```js 408 | // 引入全局样式 409 | import '@/assets/scss/index.scss' 410 | ``` 411 | 412 | 4、更改完vite.config.ts和tsconfig.json记得重启项目。 413 | 414 | ## 6、封装Pinia、模块化、长缓存 415 | 416 | 使用方式: 417 | 418 | 1、在store/modules下创建user.ts 419 | 420 | ````js 421 | import { defineStore, acceptHMRUpdate } from 'pinia' 422 | 423 | // 1、声明导出store名称 424 | export const userStore = defineStore({ 425 | id: 'user', // 2、声明store名称 426 | state: () => ({ 427 | name: '很老很老的值', 428 | }), 429 | getters: { 430 | myName: state => { 431 | return `getters ${state.name}` 432 | }, 433 | }, 434 | actions: { 435 | changeName(name: string) { 436 | this.name = name 437 | }, 438 | }, 439 | 440 | }) 441 | 442 | // 这行代码是用于支持热模块替换(HMR)的。在Pinia中,它允许接受热更新并应用到使用了userStore的地方。 443 | // 3、为了让当前store接收热更新为它配置一下 444 | if (import.meta.hot) { 445 | import.meta.hot.accept(acceptHMRUpdate(userStore, import.meta.hot)) 446 | } 447 | ```` 448 | 449 | 2、导出user.ts中整个userStore给其他组件使用 450 | 451 | store/modules/index.ts 452 | 453 | ```js 454 | export * from './user' 455 | ``` 456 | 457 | 3、store在组件中的使用方式 458 | 459 | ```js 460 | // 1、引入 461 | import { userStore } from '@/store' // 由于项目已配置路径别名,所以就用@/,它代表的是src 462 | 463 | // 2、实例化 464 | const useUserStore = userStore() 465 | 466 | // 3、如何使用userStore中的变量和函数 看下面template中的p标签就知道,解不解构2选1 467 | // 3.1.1 变量可用解构 例如取出name后直接使用即可 468 | const { name } = useUserStore 469 | // 3.1.2 变量不解构 需要加上useUserStore.name 470 | console.log(useUserStore.name) 471 | 472 | // 3.2 使用userStore中的函数 473 | const handleLogin = () => { 474 | useUserStore.changeName('张三') 475 | } 476 | 477 | // 页面 478 | 482 | ``` 483 | 484 | 完整代码 485 | 486 | ```js 487 | import { userStore } from '@/store' // 1、引入 488 | const useUserStore = userStore() // 2、实例化 489 | 490 | const { name } = useUserStore // 3、解构变量 491 | 492 | // 4、使用 493 | const handleLogin = () => { 494 | useUserStore.changeName('张三') 495 | } 496 | 497 | 500 | ``` 501 | 502 | 503 | 504 | 4、引入的store存储的数据默认是没有响应式的,可以用 storeToRefs 将其变为响应式。 505 | 506 | ```js 507 | // 引入 508 | import { storeToRefs } from "pinia"; 509 | 510 | // 将我们实例化的useAppstore放进去然后解构,解构出的state数据即为响应式 511 | const { name } = storeToRefs(useAppstore); 512 | ``` 513 | 514 | 需要storeToRefs的完整代码 515 | 516 | ```js 517 | import { userStore } from '@/store' // 引入userStore 518 | import { storeToRefs } from "pinia"; // 取出响应式方法 519 | 520 | const useUserStore = userStore() // 实例化 521 | const { name } = storeToRefs(useUserStore); // 将实例化对象的数据更改为响应式并解构出来 522 | 523 | // 使用userStore中的函数 524 | const handleLogin = () => { 525 | useUserStore.changeName('张三') 526 | } 527 | 528 | // 页面 529 | 532 | ``` 533 | 534 | 如何证明数据是否为响应式,请看这篇文章 https://blog.csdn.net/Steven_Son/article/details/128440811 535 | 536 | 封装+模块化:https://blog.csdn.net/Steven_Son/article/details/135553816?spm=1001.2014.3001.5501 537 | 538 | 长缓存:https://blog.csdn.net/Steven_Son/article/details/135551314?spm=1001.2014.3001.5501 539 | 540 | Pinia官网文章:https://pinia.web3doc.top/introduction.html 541 | 542 | ## 7、自适应采用的是postcss-px-to-viewport 543 | 544 | 详细配置说明看这篇文章:https://blog.csdn.net/Steven_Son/article/details/135554296?spm=1001.2014.3001.5501 545 | 546 | ## 8、自动导入组件 547 | 548 | 使用components下的组件时自动注册的插件 unplugin-vue-components 549 | 550 | 作用:哪个页面要用到components下的组件无需import手动导入,直接使用即可。 551 | 552 | 所用的组件都自动保存在项目根目录的 components.d.ts 中。 553 | 554 | ## 9、封装TabBar布局容器 555 | 556 | 1、路径:src/layout/index.vue 557 | 558 | 2、作用:页面整体的布局结构,如需增加/减少tabbar数量,增加时记得给二次封装的Tabbar组件配置正确的路由,才能正常跳转。 559 | 路径: src/components/Tabbar.vue 560 | 561 | ## 10、tailwindcss库的用法 562 | 563 | 库已经配置好了,你直接使用即可。 564 | 565 | 566 | 567 | ```html 568 |

橙色

569 | ``` 570 | ## 11、fontawesome-free字体图标库 571 | 572 | 免费可商用的,这个库主要是配合tailwindcss的。因为用莫高生成的静态代码用的icon就是这个,为了方便直接使用莫高的生成静态代码到页面演示完美展示,所以直接帮你引入了。 你就无需再次引入。 573 | ```html 574 | 575 | ``` 576 | 577 | 578 | 579 | ## 12、初始化全局CSS和防止页面文本被用户选中 580 | 581 | src/assets/scss/reset.scss 和 src/assets/scss/index.scss 582 | 583 | ## 13、字体和字体图标 584 | 585 | 项目使用的字体和字体图标是阿里巴巴免费可商用的iconfont,无需担心是否侵权的问题。 586 | 587 | 路径:src/assets/iconfont 588 | 589 | 1、iconfont 阿里巴巴字体图标 590 | 591 | 配置文章链接: https://blog.csdn.net/Steven_Son/article/details/128149868?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22128149868%22%2C%22source%22%3A%22Steven_Son%22%7D 592 | 593 | 2、引入免费的阿里巴巴思源黑体字体 594 | 595 | 配置文章链接:https://www.iconfont.cn/fonts/detail?spm=a313x.fonts_index.i1.d9df05512.7ccd3a81uTg3IB&cnid=nsKKStjV4gdI 596 | 597 | ## 14、性能优化 598 | 599 | ### 1、需要keep-alive长缓存的组件在此配置 600 | 601 | 1、路由设置keepAlive属性 602 | 603 | src/router/index.ts 604 | 605 | ```js 606 | { 607 | path: '/category', 608 | name: 'category', 609 | meta: { 610 | title: 'category', 611 | keepAlive: true, // 加这一行 612 | }, 613 | component: () => import('@/views/category/index.vue'), 614 | }, 615 | ``` 616 | 617 | 2、到布局结构页面手动添加要keep-alive的组件名称 618 | 619 | src/layout/index.vue 620 | 621 | ```js 622 | const routerStrArr = ['home'] 623 | ``` 624 | 625 | 浏览器可以搭配插件vue.js Devtools 查看以及控制台网络降速测试 626 | 627 | 注意:最多缓存10个,缓存太多影响性能。 628 | 629 | ### 2、为每次打包的文件后缀添加打包时的时间戳,防止打包上线页面缓存的问题 630 | 631 | vite.config.ts timeStamp 632 | 633 | ### 3、为index.html增加防盗链,解决图片403 634 | 635 | ### 4、PC端时自动生成iframe框架嵌套项目并网页自动居中
636 | 637 | 具体代码逻辑在 src/App.vue onMounted中 638 | 639 | ### 5、vite.config.ts已配置诸多优化,具体请自行查看。 640 | 641 | ### 6、index.html 新增了清缓存的配置,防止网页静态资源缓存的问题。 642 | 643 | ## 15、代码规范 644 | 645 | ### 1、prettier + eslint 配置了代码规范插件 646 | 647 | ### 2、husky + lint-staged git提交规范 648 | 649 | - feat:新功能(feature) 650 | - fix/to:修复 bug,可以是 QA 发现的 BUG,也可以是研发自己发现的 651 | - fix:产生 diff 并自动修复此问题。适合于一次提交直接修复问题 652 | - to:只产生 diff 不自动修复此问题。适合于多次提交。最终修复问题提交时使用 fix 653 | - docs:文档(documentation)。 654 | - style:格式(不影响代码运行的变动)【比如说加注释就是这个?】 655 | - refactor:重构(即不是新增功能,也不是修改 bug 的代码变动)。 656 | - perf:优化相关,比如提升性能、体验。 657 | - test:增加测试。 658 | - chore:构建过程或辅助工具的变动。 659 | - revert:回滚到上一个版本。 660 | - merge:代码合并。 661 | - sync:同步主线或分支的 Bug。 662 | 663 | ## 16、配置兼容性 664 | 665 | ### 1、browserslist 配置了浏览器兼容性 666 | 667 | ### 2、polyfill web项目兼容低版本浏览器插件 668 | 669 | core-js 和 @vitejs/plugin-legacy 670 | 671 | ## 17、已配置第三方工具库 672 | 673 | ### 1、lodash 674 | 675 | 防抖和节流的使用方法,节流用到时再去查 676 | 677 | ```js 678 | import { debounce,throttle } from 'lodash-es' 679 | 680 | // 它返回一个带防抖的新函数 681 | const debounceLogin = debounce(toLogin, 500) 682 | function toLogin() { 683 | console.log(111) 684 | } 685 | ``` 686 | 687 | ### 2、vConsole移动端调试工具 688 | 689 | 详细文章看这篇:https://blog.csdn.net/Steven_Son/article/details/135555570?spm=1001.2014.3001.5501 690 | 691 | 692 | ## 18、拓展: 693 | 694 | 695 | 696 | ### 1、如果不知道怎么用Nginx部署前端打包后的dist,可以看这篇文章 697 | 698 | https://blog.csdn.net/Steven_Son/article/details/135414494?spm=1001.2014.3001.5501 699 | 700 | ### 2、如果要做JWT免登,请根据你的需求对以下几个文件进行更改 701 | 702 | 1、src/service/webRequest.ts 设置token的地方 703 | 2、src/service/error.ts 错误报错页 704 | 3、src/login/index.vue 登录页,登录后可能就要保存token了 705 | 706 | ### 3、本地开发的项目到手机端演示 707 | 708 | 1、修改package.json配置,更改为你电脑的IP地址,同时电脑和手机要在同个网络。 709 | 说明:连的同个WIFI、同个网线。 710 | 711 | ```js 712 | "scripts": { 713 | "testMobile": "vite --host 192.168.1.193" 714 | } 715 | ``` 716 | 717 | 2、电脑(windows)关闭防火墙,这三个都要关闭:域网络、专用网络、公用网络。 718 | 位置:安全中心 —— 防火墙和网络保护 719 | 720 | 3、pnpm testMobild 启动项目,手机访问启动后的项目链接。 721 | 722 | ### 4、解决main.ts 文件引入路径的问题 723 | 724 | 1、如果引入路径正确,但是提示找不到文件,则删除'XX',重新引入 725 | 726 | 2、检查vite.config.ts的路径别名配置是否正确,正确代码如下 727 | 728 | ```js 729 | //新增 730 | resolve: { 731 | alias: { 732 | '@': path.resolve(__dirname, './src'), //把 src 的别名设置为 @ 733 | }, 734 | extensions: ['.js', '.json', '.ts'], // 这些类型的文件后缀的不需要写 735 | }, 736 | ``` 737 | 738 | 3、检查tsconfig.json的部分属性配置 739 | 740 | ```js 741 | "baseUrl": ".", 742 | "paths": {"@/*": ["src/*"]}, 743 | "target": "ES2020", 744 | "module": "ES2020", 745 | "lib": [ 746 | "es2020", 747 | "es5", 748 | "es6", 749 | "DOM", 750 | "DOM.Iterable" 751 | ], 752 | "moduleResolution": "node", 753 | "include": [ 754 | "src/**/*.ts", 755 | "src/**/*.d.ts", 756 | "src/**/*.tsx", 757 | "src/**/*.vue", 758 | ], 759 | ``` 760 | 761 | 4、检查tsconfig.node.json的部分属性配置 762 | 763 | ```js 764 | "compilerOptions": { 765 | "module": "ES2020", 766 | "moduleResolution": "node", 767 | "allowSyntheticDefaultImports": true 768 | }, 769 | "include": ["vite.config.ts", "src/**/*.ts", "global/*.ts"] 770 | ``` 771 | 772 | 5、在src下新建vite-env.d.ts ,解决ts无法识别引入.vue后缀的文件夹 773 | 774 | ```js 775 | /// 776 | declare module '*.vue' { 777 | import type { DefineComponent } from 'vue' 778 | 779 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 780 | 781 | const component: DefineComponent<{}, {}, any> 782 | 783 | export default component 784 | } 785 | 786 | ``` 787 | 788 | 每次修改完都要重启项目,或者关闭项目重启VSCode、重启项目。 789 | 790 | ### 5、使用van-nav-bar时看这里 791 | 792 | 直接使用,但由于它会盖住外部包裹层,所以你使用van-nav-bar时需要给外层container添加padding-top:92px; 也就是vant-nav-bar的2倍高度(1倍是46),因为我们设计稿是750的。 793 | 794 | 这样在van-nav-bar下的内容就不会被它盖住了。 795 | 796 | 不需要van-nav-bar的无需加此样式。 797 | 798 | ```html 799 | 804 | 809 | ``` 810 | ### 6、如果打开某些组件正确引入vue的api了,但还是报未找到vue文件,此时项目有缓存,关闭整个VScode重启项目即可解决。 811 | 812 | ```js 813 | import { ref } from 'vue' // 正确引了,但提示报未找到文件 814 | ``` 815 | ### 7、souceTree 如果提交代码失败请检查当前node 版本是否为16 816 | ```js 817 | node -v 如果不是则切换为16 818 | nvm use 16 切换后souceTree重新提交代码 819 | ``` 820 | ### 8、如何深层次修改vant-ui组件样式 821 | 822 | ```js 823 | ::v-deep(.van-search) { 824 | background-color: #f5f5f5; 825 | } 826 | ``` 827 | ### 9、如何定义SCSS全局公共样式并使用 828 | 1、在src/assets/scss/variables.scss中定义 829 | ```js 830 | // 主题色 831 | $theme-color: #af1d36; 832 | ``` 833 | 2、记得在vite.config.ts配置一下这个文件路径,否则页面用了找不着 834 | vite.config.ts 835 | ```js 836 | export default defineConfig({ 837 | css:{ 838 | // css预处理器 839 | preprocessorOptions: { 840 | scss: { 841 | // 引入 variables.scss 这样就可以在全局中使用 variables.scss中预定义的变量了 842 | // 给导入的路径最后加上 ; 843 | additionalData: '@use "@/assets/scss/variables.scss" as *;', 844 | api: 'modern-compiler', // or "modern" modern-compiler 最新的sass编译方式 845 | }, 846 | }, 847 | } 848 | }) 849 | ``` 850 | 3、页面如何使用 851 | views/home/index.vue 852 | ```html 853 | 856 | 857 | 862 | ``` 863 | 864 | 865 | ### 10、assets/images的图片可以用变量形式引入并使用 866 | 1、以home.vue为例 867 | 868 | 项目中已为assets配置路径别名,所以无需../ 869 | ```js 870 | import prodImg from '@/assets/images/icons/商品1.png' 871 | 872 |