├── .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 |
8 |
9 |
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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/src/views/account.vue:
--------------------------------------------------------------------------------
1 |
24 |
25 | toLogin
26 | 过来啦
27 | {{ name }}
28 |
29 |
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 |
45 |
46 | {{ item.title }}
47 |
48 |
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 |
36 |
37 |
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 |
33 |
34 |
35 |
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 |
9 |
10 |
11 |
12 |
13 |
项目作者:HaushoLin
14 |
15 |
Github项目地址:
16 |
19 |
20 |
Gitee项目地址:
21 |
24 |
28 |
29 |
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 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
92 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
75 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |

26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
今日爆款
34 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
今日上新
47 |
48 |
49 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
76 |
77 | 限时特惠
78 |
79 |
80 |
81 | +1000金币
82 | 立即购买
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | 首页
93 | 权益
94 | 电商
95 | 积分
96 | 我的
97 |
98 |
99 |
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 |
43 |
44 |
45 | - Unicode
46 | - Font class
47 | - Symbol
48 |
49 |
50 |
查看项目
51 |
52 |
53 |
54 |
55 |
56 |
57 | -
58 |
59 |
user
60 | 
61 |
62 |
63 | -
64 |
65 |
权益
66 | 
67 |
68 |
69 | -
70 |
71 |
钱包
72 | 
73 |
74 |
75 | -
76 |
77 |
116订单、订单明细、收支明细
78 | 
79 |
80 |
81 | -
82 |
83 |
已完成-已办-02
84 | 
85 |
86 |
87 | -
88 |
89 |
积分
90 | 
91 |
92 |
93 | -
94 |
95 |
问题
96 | 
97 |
98 |
99 | -
100 |
101 |
红包
102 | 
103 |
104 |
105 | -
106 |
107 |
收货地址
108 | 
109 |
110 |
111 | -
112 |
113 |
分类
114 | 
115 |
116 |
117 | -
118 |
119 |
签到
120 | 
121 |
122 |
123 | -
124 |
125 |
消息
126 | 
127 |
128 |
129 | -
130 |
131 |
货车
132 | 
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">3</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 |
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 |
169 | 按钮
170 |
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 |
479 | {{ name }}
480 | {{ useUserStore.name }}
481 |
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 |
498 | {{ name }}
499 |
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 |
530 | {{ name }}
531 |
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 |
800 |
801 |
802 |
803 |
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 |
854 | 测试
855 |
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 |
873 |
874 |
875 | ```
876 |
877 | ### 11、如果底部tabbar的图标不满意,要换成自定义,则需这样写
878 | ```html
879 |
908 |
909 |
910 |
911 |
912 | {{ item.title }}
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 | ```
922 | ### 12、引入sass文件记得用@use XX * as *; 否则找不到文件
923 | 因为sass 2.0版本以后,@import被移除,改为@use,所以需要用@use XX * as *; 否则找不到文件或报错。
924 |
925 | Author: HaushoLin
926 |
927 | 博客:https://blog.csdn.net/Steven_Son
928 |
--------------------------------------------------------------------------------