├── .eslintignore ├── .stylelintignore ├── .env.development ├── .env ├── .postcssrc.js ├── .commitlintrc.js ├── .env.production ├── public └── favicon.ico ├── src ├── assets │ └── logo.png ├── App.vue ├── router │ ├── modules │ │ ├── index.js │ │ └── demo.modules.js │ ├── constants.js │ ├── guard │ │ ├── index.js │ │ └── permission.guard.js │ └── index.js ├── styles │ ├── global │ │ ├── variables.less │ │ └── utils.less │ ├── index.less │ ├── reset.css │ └── normalize.css ├── store │ ├── index.js │ └── modules │ │ └── demo.js ├── api │ └── demo.js ├── utils │ ├── env.js │ └── request.js ├── main.js ├── views │ └── demo │ │ ├── components │ │ └── StyleTxt.vue │ │ ├── pinia.vue │ │ ├── route.vue │ │ └── style.vue ├── hooks │ └── useRouter.js ├── components │ └── HelloWorld.vue └── layouts │ └── BaseLayout.vue ├── .husky ├── pre-commit └── commit-msg ├── docs ├── 2.全家桶 │ ├── 3. 组件库.md │ ├── 2. 全局状态管理.md │ └── 1. 路由.md ├── 3.更多功能 │ ├── 1. Axios 封装及接口管理.md │ ├── 2. Css 样式处理.md │ └── 3. Vite 基础配置.md └── 1.工程化实践 │ ├── 3. husky.md │ ├── 4. commitlint.md │ ├── 2. stylelint.md │ └── 1. eslint.md ├── .prettierrc.js ├── jsconfig.json ├── .vscode ├── extensions.json └── settings.json ├── .editorconfig ├── .gitignore ├── index.html ├── .eslintrc.js ├── .stylelintrc.js ├── package.json ├── vite.config.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | public 2 | dist -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | public 2 | dist -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # public path 2 | VITE_PUBLIC_PATH = / -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # prot 2 | VITE_PORT = 8000 3 | 4 | # output dir 5 | VITE_OUTPUT_DIR = dist -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | } 4 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # public path 2 | VITE_PUBLIC_PATH = / 3 | 4 | # 是否兼容传统浏览器 5 | VITE_LEGACY = false -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Only-Xc/vue2.7-vite-cli/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Only-Xc/vue2.7-vite-cli/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm commitlint --edit 5 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/router/modules/index.js: -------------------------------------------------------------------------------- 1 | import demoRouteModules from './demo.modules' 2 | 3 | export default [...demoRouteModules] 4 | -------------------------------------------------------------------------------- /docs/2.全家桶/3. 组件库.md: -------------------------------------------------------------------------------- 1 | # 组件库 2 | 3 | 关于组件库可选择的就有很多了,因为可能每个人需要的都不相同,这里就不集成到脚手架模板中了。 4 | 5 | 有一点需要注意的是,Vue2.7 还是只能使用 Vue2.x 相关的组件库。 -------------------------------------------------------------------------------- /src/styles/global/variables.less: -------------------------------------------------------------------------------- 1 | @primary: #409eff; 2 | @success: #67c23a; 3 | @warning: #e6a23c; 4 | @danger: #f56c6c; 5 | @info: #909399; 6 | -------------------------------------------------------------------------------- /src/styles/index.less: -------------------------------------------------------------------------------- 1 | @import './normalize.css'; 2 | @import './reset.css'; 3 | 4 | html, 5 | body, 6 | #app { 7 | height: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /src/router/constants.js: -------------------------------------------------------------------------------- 1 | import BaseLayout from '@/layouts/BaseLayout.vue' 2 | 3 | export const LAYOUT = BaseLayout 4 | 5 | export const HOME_PATH = '/demo' 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | printWidth: 80, 5 | trailingComma: 'none', 6 | arrowParens: 'avoid' 7 | } 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["./src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "octref.vetur", 4 | "editorconfig.editorconfig", 5 | "dbaeumer.vscode-eslint", 6 | "stylelint.vscode-stylelint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { createPinia, PiniaVuePlugin } from 'pinia' 3 | 4 | Vue.use(PiniaVuePlugin) 5 | 6 | export const store = createPinia() 7 | 8 | export * from './modules/demo' 9 | -------------------------------------------------------------------------------- /src/api/demo.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export const demo = { 4 | getList: params => { 5 | return request({ 6 | url: '/demo', 7 | method: 'get', 8 | params 9 | }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/store/modules/demo.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const useDemoStore = defineStore('demo', { 4 | state: () => ({ 5 | count: 1 6 | }), 7 | actions: { 8 | accumulate() { 9 | this.count++ 10 | } 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /src/router/guard/index.js: -------------------------------------------------------------------------------- 1 | import { setupPermissionGuard } from './permission.guard' 2 | 3 | /** 4 | * 设置路由守卫 5 | * @param {import('vue-router/types/router').VueRouter} router 6 | */ 7 | export function setupGuard(router) { 8 | setupPermissionGuard(router) // 权限守卫 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/env.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 是否为开发环境 3 | * @returns {Boolean} 4 | */ 5 | export function isDev() { 6 | return import.meta.env.DEV 7 | } 8 | 9 | /** 10 | * 是否为生产环境 11 | * @returns {Boolean} 12 | */ 13 | export function isProd() { 14 | return import.meta.env.PROD 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": true, 4 | "source.fixAll.eslint": true 5 | }, 6 | "vetur.format.enable": false, 7 | "stylelint.validate": ["vue"], // Add "vue" language. 8 | "editor.tabSize": 2, 9 | "files.eol": "\n", 10 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | import { router } from './router' 5 | import { setupGuard } from './router/guard' 6 | 7 | import { store } from './store' 8 | 9 | import './styles/index.less' 10 | 11 | setupGuard(router) 12 | 13 | new Vue({ 14 | router, 15 | pinia: store, 16 | render: h => h(App) 17 | }).$mount('#app') 18 | -------------------------------------------------------------------------------- /.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/settings.json 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/router/guard/permission.guard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 设置权限守卫 3 | * @param {import('vue-router/types/router').VueRouter} router 4 | */ 5 | export function setupPermissionGuard(router) { 6 | router.beforeEach((to, from, next) => { 7 | console.log('beforeEach', to, from) 8 | next() 9 | }) 10 | 11 | router.afterEach((to, from) => { 12 | console.log('afterEach', to, from) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/global/utils.less: -------------------------------------------------------------------------------- 1 | // 禁止换行, 文本溢出省略号显示 (一行) 2 | .ellipsis() { 3 | overflow: hidden; 4 | white-space: nowrap; 5 | text-overflow: ellipsis; 6 | } 7 | 8 | // 文本溢出省略号显示 (多行) 9 | // 兼容大部分主流浏览器 10 | .multi-ellipsis(@lines) { 11 | display: -webkit-box; 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | -webkit-line-clamp: @lines; 15 | 16 | /* autoprefixer: ignore next */ 17 | -webkit-box-orient: vertical; 18 | } 19 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | 4 | import routeModules from './modules' 5 | 6 | import { HOME_PATH } from './constants' 7 | 8 | Vue.use(VueRouter) 9 | 10 | /** 11 | * @type {import('vue-router').RouteConfig[]} 12 | */ 13 | const routes = [ 14 | { 15 | path: '/', 16 | redirect: HOME_PATH 17 | }, 18 | ...routeModules 19 | ] 20 | 21 | export const router = new VueRouter({ 22 | mode: 'hash', 23 | routes 24 | }) 25 | -------------------------------------------------------------------------------- /docs/3.更多功能/1. Axios 封装及接口管理.md: -------------------------------------------------------------------------------- 1 | # Axios 封装及接口管理 2 | 3 | 这里接口请求使用的是基于 Promise 封装的请求库 [Axios](https://axios-http.com/zh/),也是现在使用很广泛的一个请求库。 4 | 5 | ## 目录说明 6 | 7 | 有关请求库的功能都在以下的目录中。 8 | 9 | ```sh 10 | . 11 | ├── src # 主目录 12 | │ ├── api # api 方法配置 13 | │ │ └── demo.js # 演示方法 14 | │ └── utils # 公共方法 15 | │ └── request.js # axios 请求库二次封装 16 | ``` 17 | 18 | 在开发时,我们先在 `utils/requset.js` 中配置好适合自己业务的请求拦截和响应拦截。 19 | 20 | 之后在 `api` 文件夹中定义请求方法,这里我一般会以功能进行拆分,同一个功能的请求方法封装在一个文件,之后在需要的地方进行调用。 -------------------------------------------------------------------------------- /src/views/demo/components/StyleTxt.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 34 | -------------------------------------------------------------------------------- /src/router/modules/demo.modules.js: -------------------------------------------------------------------------------- 1 | import { LAYOUT } from '../constants' 2 | 3 | /** 4 | * @type {import('vue-router').RouteConfig[]} 5 | */ 6 | export default [ 7 | { 8 | path: '/demo', 9 | component: LAYOUT, 10 | redirect: '/demo/route', 11 | children: [ 12 | { 13 | path: 'route', 14 | component: () => import('@/views/demo/route.vue'), 15 | meta: { title: '路由使用' } 16 | }, 17 | { 18 | path: 'pinia', 19 | component: () => import('@/views/demo/pinia.vue'), 20 | meta: { title: 'Pinia使用' } 21 | }, 22 | { 23 | path: 'style', 24 | component: () => import('@/views/demo/style.vue'), 25 | meta: { title: '公共样式' } 26 | } 27 | ] 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /docs/2.全家桶/2. 全局状态管理.md: -------------------------------------------------------------------------------- 1 | # 全局状态管理 2 | 3 | 这里全局状态管理器选择了 [Pinia](https://pinia.vuejs.org/),之所以没有选择 Vuex,对于我来说最主要的原因有两点: 4 | 5 | 1. Vue3 官方文档中,已经将 Pinia 放入官方推荐的核心库位置中了。 6 | 2. 对 Composition-API 具有更好的支持。 7 | 8 | 接下来看一段官方的说明: 9 | 10 | > Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。 11 | > 12 | > 与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。 13 | 14 | ## 目录说明 15 | 16 | 有关全局状态管理的功能都在以下的目录中。 17 | 18 | ```sh 19 | . 20 | ├── src # 主目录 21 | │ ├── main.js # 主入口 22 | │ ├── store # store 配置 23 | │ │ ├── modules # store 模块 24 | │ │ └── index.js 25 | │ └── views # 页面 26 | ``` 27 | 28 | ## store modules 29 | 30 | 我们在开发中需要将不同功能所对应的状态,拆分到不同的 modules,好处在 route moduels 中已经描述过了。 -------------------------------------------------------------------------------- /src/hooks/useRouter.js: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance, shallowRef } from 'vue' 2 | import { router } from '@/router' 3 | 4 | export function useRouter() { 5 | const vm = getCurrentInstance() 6 | 7 | if (vm) { 8 | return router 9 | } 10 | 11 | console.warn('请在 setup 中调用。') 12 | 13 | return undefined 14 | } 15 | 16 | let currentRoute = shallowRef() 17 | 18 | export function useRoute() { 19 | if (!currentRoute.value) { 20 | const vm = getCurrentInstance() 21 | 22 | if (!vm) { 23 | console.warn('请在 setup 中调用。') 24 | return 25 | } 26 | 27 | currentRoute.value = vm.proxy.$route 28 | 29 | // 每次路由切换时,更新 route 参数 30 | const router = useRouter() 31 | router.afterEach(to => (currentRoute.value = to)) 32 | } 33 | 34 | return currentRoute 35 | } 36 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('eslint-define-config') 2 | 3 | module.exports = defineConfig({ 4 | root: true, 5 | env: { 6 | browser: true, 7 | node: true, 8 | es6: true 9 | }, 10 | parser: 'vue-eslint-parser', 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true 14 | } 15 | }, 16 | extends: [ 17 | 'eslint:recommended', 18 | /** 19 | * 继承 eslint-plugin-vue 插件的规则 20 | * @link https://eslint.vuejs.org/user-guide/#installation 21 | */ 22 | 'plugin:vue/recommended', 23 | /** 24 | * 继承 eslint-plugin-prettier 插件的规则 25 | * @link https://github.com/prettier/eslint-plugin-prettier 26 | */ 27 | 'plugin:prettier/recommended' 28 | ], 29 | rules: { 30 | 'vue/multi-word-component-names': 'off' 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'stylelint-config-standard', 5 | 'stylelint-config-rational-order', 6 | 'stylelint-config-prettier', 7 | 'stylelint-config-html/vue' // 需要放在最后一位 8 | ], 9 | defaultSeverity: 'warning', 10 | plugins: ['stylelint-order'], 11 | rules: { 12 | 'no-empty-source': null, 13 | 'selector-class-pattern': null, 14 | 'value-keyword-case': null, 15 | 'font-family-no-missing-generic-family-keyword': null 16 | }, 17 | overrides: [ 18 | { 19 | files: ['*.vue', '**/*.vue'], 20 | rules: { 21 | 'selector-pseudo-class-no-unknown': [ 22 | true, 23 | { 24 | ignorePseudoClasses: ['deep', 'global'] 25 | } 26 | ], 27 | 'selector-pseudo-element-no-unknown': [ 28 | true, 29 | { 30 | ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'] 31 | } 32 | ] 33 | } 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /docs/2.全家桶/1. 路由.md: -------------------------------------------------------------------------------- 1 | # VueRouter 路由 2 | 3 | Vue2.x 中只能使用 vue-router3 版本。 4 | 5 | ## 目录说明 6 | 7 | 有关路由的功能都在以下的目录中。 8 | 9 | ```sh 10 | . 11 | ├── src # 主目录 12 | │ ├── main.js # 主入口 13 | │ ├── router # 路由配置 14 | │ │ ├── guard # 路由守卫 15 | │ │ ├── modules # 路由模块 16 | │ │ └── index.js 17 | │ └── views # 页面 18 | ``` 19 | 20 | ## route modules 21 | 22 | 关于路由表,建议根据功能的不同来拆分到 modules 文件夹中。 23 | 24 | 这样做的好处是: 25 | 26 | 1. 方便后期维护 27 | 2. 减少 Git 合并代码时的冲突的可能 28 | 29 | ## componsition-api 中使用 30 | 31 | 在 hooks/useRouter 中封装了两个方法: 32 | 33 | 1. `useRouter` 34 | 2. `useRoute` 35 | 36 | ```javascript 37 | import { watch } from 'vue' 38 | import { useRoute, useRouter } from '@/hooks/useRouter' 39 | 40 | export default { 41 | setup() { 42 | const route = useRoute() 43 | const router = useRouter() 44 | 45 | watch(route, () => { 46 | console.log('route 变化', route.value) 47 | }) 48 | 49 | function routeChange() { 50 | router.push({ path: '/home', query: { key: Date.now() } }) 51 | } 52 | 53 | return { 54 | routeChange 55 | } 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /src/styles/reset.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | 28 | html { 29 | box-sizing: border-box; 30 | } 31 | 32 | *, *::before, *::after { 33 | box-sizing: inherit; 34 | } 35 | 36 | body { 37 | line-height: 1; 38 | } 39 | 40 | ol, ul { 41 | list-style: none; 42 | } 43 | 44 | img, 45 | video { 46 | height: auto; 47 | max-width: 100%; 48 | } 49 | 50 | table { 51 | border-collapse: collapse; 52 | border-spacing: 0; 53 | } -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const services = axios.create({ 4 | baseURL: '/api', 5 | timeout: 8000 6 | }) 7 | 8 | // 请求拦截 9 | services.interceptors.request.use( 10 | config => { 11 | /** 12 | * 在这里一般会携带前台的参数发送给后台,比如下面这段代码: 13 | * const token = getToken() 14 | * if (token) { 15 | * config.headers.token = token 16 | * } 17 | */ 18 | 19 | return config 20 | }, 21 | error => { 22 | return Promise.reject(error) 23 | } 24 | ) 25 | 26 | // 响应拦截 27 | services.interceptors.response.use( 28 | response => { 29 | const res = response.data 30 | 31 | /** 32 | * 这里使用的是自定义 Code 码来做统一的错误处理 33 | * code 等于 -1 则代表接口响应出错(可根据自己的业务来进行修改) 34 | */ 35 | if (res.code === -1) { 36 | const msg = res.message || '未知错误,请联系管理员查看' 37 | 38 | console.error('[api]', msg) 39 | 40 | return Promise.reject(msg) 41 | } 42 | 43 | return res.data 44 | }, 45 | error => { 46 | const { response } = error 47 | if (response && response.data) { 48 | return Promise.reject(error) 49 | } else { 50 | const { message } = error 51 | console.error('[api]', message) 52 | return Promise.reject(error) 53 | } 54 | } 55 | ) 56 | 57 | export default services 58 | -------------------------------------------------------------------------------- /src/views/demo/pinia.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 54 | 55 | 60 | -------------------------------------------------------------------------------- /src/views/demo/route.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 54 | 55 | 66 | -------------------------------------------------------------------------------- /docs/1.工程化实践/3. husky.md: -------------------------------------------------------------------------------- 1 | # Husky Git Hook 工具 2 | 3 | husky Git Hook 工具,为 git 提供一系列钩子函数,在提交前(pre-commit)、提交消息(commit-msg)等钩子触发时可以为我们执行一些脚本。 4 | 5 | 我们可以使用 husky 工具来进行代码提交前的自动格式化,以及 commit message 的校验。 6 | 7 | ## 提交前代码格式化 8 | 9 | 1. 首先安装 husky 10 | 11 | ```bash 12 | pnpm add husky -D 13 | ``` 14 | 15 | 2. 初始化 husky 16 | 17 | ```bash 18 | pnpm husky install 19 | ``` 20 | 21 | 并在 package.json 中添加如下内容 22 | 23 | ```json 24 | { 25 | "scripts": { 26 | //... 27 | "prepare": "husky install" 28 | } 29 | } 30 | ``` 31 | 32 | 3. 添加 git hook 33 | 34 | ```bash 35 | pnpm husky add .husky/pre-commit 36 | ``` 37 | 38 | 到这里之后我们还需要使用另外一个工具: [`lint-staged`](https://www.npmjs.com/package/lint-staged),它是对 git 暂存区文件进行 lint 检查的工具。 39 | 40 | 4. 安装 lint-staged 41 | 42 | ```bash 43 | pnpm add lint-staged -D 44 | ``` 45 | 46 | 5. 在 `package.json` 中添加如下配置 47 | 48 | ```json 49 | { 50 | //... 51 | "lint-staged": { 52 | "*.{js,ts,jsx,tsx}": [ 53 | "prettier --write", 54 | "eslint --fix" 55 | ], 56 | "*.vue": [ 57 | "stylelint --fix", 58 | "prettier --write", 59 | "eslint --fix" 60 | ], 61 | "*.{less,css}": [ 62 | "stylelint --fix", 63 | "prettier --write" 64 | ] 65 | } 66 | } 67 | ``` 68 | 69 | 6. 在 `.husky/pre-commit` 文件中写入以下内容 70 | 71 | ```shell 72 | #!/bin/sh 73 | . "$(dirname "$0")/_/husky.sh" 74 | 75 | pnpm lint-staged 76 | ``` 77 | 78 | 经过以上配置之后,我们就可以在每次提交之前对所有代码进行格式化,保证线上代码的规范性。 79 | 80 | 81 | > 在实际中如果遇见 `Use the --allow-empty option to continue, or check your task configuration` 这个问题。 82 | > 83 | > 我们可以修改 `pnpm lint-staged` 为 `pnpm lint-staged --allow-empty` 来暂时屏蔽这个问题。 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2.7", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "prepare": "husky install", 10 | "lint:eslint": "eslint \"src/**/*.{vue,js,jsx}\" --fix", 11 | "lint:stylelint": "stylelint \"src/**/*.{vue,css,less,scss}\" --fix" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.27.2", 15 | "pinia": "^2.0.14", 16 | "vue": "^2.7.5", 17 | "vue-router": "^3.5.4" 18 | }, 19 | "devDependencies": { 20 | "@commitlint/cli": "^17.0.3", 21 | "@commitlint/config-conventional": "^17.0.3", 22 | "@vitejs/plugin-legacy": "^1.8.2", 23 | "@vitejs/plugin-vue2": "^1.1.2", 24 | "autoprefixer": "^10.4.7", 25 | "eslint": "^8.18.0", 26 | "eslint-config-prettier": "^8.5.0", 27 | "eslint-define-config": "^1.5.1", 28 | "eslint-plugin-prettier": "^4.2.1", 29 | "eslint-plugin-vue": "^9.1.1", 30 | "husky": "^8.0.1", 31 | "less": "^4.1.3", 32 | "lint-staged": "^13.0.3", 33 | "postcss-html": "^1.4.1", 34 | "postcss-less": "^6.0.0", 35 | "prettier": "^2.7.1", 36 | "stylelint": "^14.9.1", 37 | "stylelint-config-html": "^1.0.0", 38 | "stylelint-config-prettier": "^9.0.3", 39 | "stylelint-config-rational-order": "^0.1.2", 40 | "stylelint-config-standard": "^26.0.0", 41 | "stylelint-order": "^5.0.0", 42 | "vite": "^2.9.9", 43 | "vue-eslint-parser": "^9.0.3" 44 | }, 45 | "browserslist": [ 46 | "defaults" 47 | ], 48 | "lint-staged": { 49 | "*.{js,ts,jsx,tsx}": [ 50 | "prettier --write", 51 | "eslint --fix" 52 | ], 53 | "*.vue": [ 54 | "stylelint --fix", 55 | "prettier --write", 56 | "eslint --fix" 57 | ], 58 | "*.{less,css}": [ 59 | "stylelint --fix", 60 | "prettier --write" 61 | ] 62 | } 63 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from '@vitejs/plugin-vue2' 2 | import legacy from '@vitejs/plugin-legacy' 3 | import serverProxy from './build/proxy' 4 | import { defineConfig, loadEnv } from 'vite' 5 | import { pathResolve, wrapperEnv } from './build/utils' 6 | 7 | const variablesLessPath = pathResolve('./src/styles/global/variables.less') 8 | const utilsLessPath = pathResolve('./src/styles/global/utils.less') 9 | 10 | // https://vitejs.dev/config/ 11 | export default ({ mode, command }) => { 12 | const root = process.cwd() 13 | const isBuild = command === 'build' 14 | 15 | // 在 vite 配置文件中使用环境变量,需要通过 loadEnv 来加载 16 | const env = loadEnv(mode, root) 17 | 18 | const { VITE_PUBLIC_PATH, VITE_OUTPUT_DIR, VITE_PORT, VITE_LEGACY } = 19 | wrapperEnv(env) 20 | 21 | const plugins = [vue()] 22 | 23 | /** 24 | * vite 默认打包文件带有 ES6 语法,在旧版浏览器中是不支持的。 25 | * 为了支持旧版浏览器,可以在 .env.production 中开启 VITE_LEGACY 设置 26 | */ 27 | if (isBuild && VITE_LEGACY) { 28 | plugins.push(legacy()) 29 | } 30 | 31 | return defineConfig({ 32 | root, 33 | base: VITE_PUBLIC_PATH, 34 | plugins, 35 | resolve: { 36 | alias: { 37 | // @/xxxx => src/xxxx 38 | '@': pathResolve('./src') 39 | } 40 | }, 41 | server: { 42 | host: true, 43 | port: VITE_PORT, 44 | proxy: serverProxy 45 | }, 46 | build: { 47 | /** 48 | * 最终构建的浏览器兼容目标 49 | * https://www.vitejs.net/config/#build-target 50 | */ 51 | target: 'es2015', 52 | outDir: VITE_OUTPUT_DIR, 53 | brotliSize: false, // 关闭 brotli 压缩大小报告,可提升构建速度 54 | chunkSizeWarningLimit: 2000 // chunk 大小警告的限制(以 kbs 为单位) 55 | }, 56 | css: { 57 | preprocessorOptions: { 58 | less: { 59 | modifyVars: { 60 | hask: ` 61 | true; 62 | @import (reference) "${variablesLessPath}"; 63 | @import (reference) "${utilsLessPath}"; 64 | ` 65 | }, 66 | javascriptEnabled: true 67 | } 68 | } 69 | } 70 | }) 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 2.7 + Vite 脚手架 2 | 3 | Vue2.7 已经发布正式版啦,不出意外的话这应该是 Vue2 的最后一个版本了,但是很多公司目前还没有升级 Vue3 的打算(比如我们)。 4 | 5 | 所以封装了这么一个开箱即用的脚手架模板,脚手架的功能可以看下面的功能列表小节,并且配备完整的技术文档。 6 | 7 | 可以直接使用,也可以当作学习源码。 8 | 9 | ## 教程目录 10 | 11 | 1. 工程化实践 12 | - [Eslint 格式化 JS 代码](./docs/1.%E5%B7%A5%E7%A8%8B%E5%8C%96%E5%AE%9E%E8%B7%B5/1.%20eslint.md) 13 | - [Stylelint 格式化 CSS 代码](./docs/1.%E5%B7%A5%E7%A8%8B%E5%8C%96%E5%AE%9E%E8%B7%B5/2.%20stylelint.md) 14 | - [Husky 提交时自动格式化代码](./docs/1.%E5%B7%A5%E7%A8%8B%E5%8C%96%E5%AE%9E%E8%B7%B5/3.%20husky.md) 15 | - [Commitlint 校验 Commit Message](./docs/1.%E5%B7%A5%E7%A8%8B%E5%8C%96%E5%AE%9E%E8%B7%B5/4.%20commitlint.md) 16 | 2. 全家桶 17 | - [路由 vue-router](./docs/2.%E5%85%A8%E5%AE%B6%E6%A1%B6/1.%20%E8%B7%AF%E7%94%B1.md) 18 | - [全局状态管理 pinia](./docs/2.%E5%85%A8%E5%AE%B6%E6%A1%B6/2.%20%E5%85%A8%E5%B1%80%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86.md) 19 | - [组件库](./docs/2.%E5%85%A8%E5%AE%B6%E6%A1%B6/3.%20%E7%BB%84%E4%BB%B6%E5%BA%93.md) 20 | 3. 更多功能 21 | - [Axios 封装及接口管理](./docs/3.%E6%9B%B4%E5%A4%9A%E5%8A%9F%E8%83%BD/1.%20Axios%20%E5%B0%81%E8%A3%85%E5%8F%8A%E6%8E%A5%E5%8F%A3%E7%AE%A1%E7%90%86.md) 22 | - [Css 样式处理](./docs/3.%E6%9B%B4%E5%A4%9A%E5%8A%9F%E8%83%BD/2.%20Css%20%E6%A0%B7%E5%BC%8F%E5%A4%84%E7%90%86.md) 23 | - [Vite 基础配置](./docs/3.%E6%9B%B4%E5%A4%9A%E5%8A%9F%E8%83%BD/3.%20Vite%20%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE.md) 24 | 25 | ## 基础搭建 26 | 27 | 在这里我们使用的是 [pnpm](https://pnpm.io/zh/) 来作为本项目的包管理工具。 28 | 29 | ```bash 30 | # 安装依赖 31 | pnpm i 32 | 33 | # 运行 34 | pnpm run dev 35 | 36 | # 打包 37 | pnpm run build 38 | 39 | # 打包文件预览 40 | pnpm run preview 41 | ``` 42 | 43 | > 本人开发环境 44 | > 45 | > node v16.13.1(大于 14 版本即可) 46 | > 47 | > pnpm v6.30.0 48 | 49 | 50 | ### 插件安装 51 | 52 | 此脚手架必须安以下依赖才能保证,代码自动格式化的有效运行: 53 | 54 | - Vetur 55 | - EditorConfig for VS Code 56 | - ESLint 57 | - Stylelint 58 | 59 | ## 功能列表 60 | 61 | - [x] Vue2.7 + Vite 62 | - [x] Eslint、Stylelint、Commitlint 统一开发规范 63 | - [x] husky + lint-staged (git commit 时自动格式化代码) 64 | - [x] Vue 全家桶集成 65 | - [x] Axios 封装及接口管理 66 | - [x] Css 样式处理 67 | - [x] vite.config.js 基础配置 68 | - [x] 跨域配置 69 | - [x] 多环境变量配置 70 | - [x] 浏览器构建兼容性 71 | -------------------------------------------------------------------------------- /src/views/demo/style.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 38 | 39 | 100 | -------------------------------------------------------------------------------- /docs/3.更多功能/2. Css 样式处理.md: -------------------------------------------------------------------------------- 1 | # Css 样式处理 2 | 3 | ## 统一浏览器默认样式 4 | 5 | 在 Web 开发时,因为浏览器内核的不同,所以会导致某些 Html 元素默认的渲染样式有所差别,虽然很细微,但是会影响我们在不同浏览器中的显示效果。 6 | 7 | 为了解决这个问题,聪明的开发者就将某个元素在各个浏览器中表现不同的样式,通过一段 CSS 进行重置,然后再进行开发,这样就可以保证每个浏览器显示效果的统一性。 8 | 9 | 目前常用的有两种解决方案: 10 | 11 | 1. Normalize - [normalize.css](https://necolas.github.io/normalize.css/) 偏向于修复浏览器的默认 BUG 和一致性,但是保留元素的默认样式。 12 | 2. Reset - [reset.css](https://meyerweb.com/eric/tools/css/reset/) 偏向于完全重置浏览器默认样式,可控性更高。 13 | 14 | 在本项目中,我们结合两种方案进行使用,代码在 `styles/normalize.css` 和 `styles/reset.css` 中。 15 | 16 | ## Less 预处理器 17 | 18 | 在 Vite 中使用 Less,我们只需要运行以下命令即可: 19 | 20 | ```bash 21 | pnpm add less -D 22 | ``` 23 | 24 | > 如果需要使用 sass 则把 less 换成 sass 就行。 25 | 26 | 安装完成之后,无需多余配置,vite 即可对 less 样式进行解析。 27 | 28 | 在 Vue 文件中,我们如下,即可使用 less 进行样式的编写: 29 | 30 | ```vue 31 | 39 | ``` 40 | 41 | > 这里 CSS 命名规范推荐 BEM 命名规范,具体可以自行搜索了解。 42 | 43 | ### Less 全局变量/方法 44 | 45 | 一般在项目中会有自己的设计规范,比如像 Element、Antd,它就有一套自己的样式规范。 46 | 47 | 其中像颜色、边框、边距等都有统一的规范,当我们改一处,所有地方都会改变,这就用到了 less 的变量和 mixin。 48 | 49 | 然而我们每处都 `@import` 就比较麻烦,这个时候我们可以使用在 vite 中进行 less 的全局样式配置。 50 | 51 | ```js 52 | import { defineConfig } from 'vite' 53 | import { resolve } from 'path' 54 | 55 | function pathResolve(dir) { 56 | return resolve(process.cwd(), '.', dir) 57 | } 58 | 59 | // 全局变量 60 | const variablesLessPath = pathResolve('./src/styles/global/variables.less') 61 | // 全局方法 62 | const utilsLessPath = pathResolve('./src/styles/global/utils.less') 63 | 64 | export default defineConfig({ 65 | css: { 66 | preprocessorOptions: { 67 | less: { 68 | modifyVars: { 69 | hask: ` 70 | true; 71 | @import (reference) "${variablesLessPath}"; 72 | @import (reference) "${utilsLessPath}"; 73 | ` 74 | }, 75 | javascriptEnabled: true 76 | } 77 | } 78 | } 79 | }) 80 | ``` 81 | 82 | ## Vue 样式穿透 83 | 84 | [官方文档](https://vuejs.org/api/sfc-css-features.html#scoped-css) 85 | 86 | 在 Vue2.7 中,改变了以往样式穿透的语法,如果继续使用 `::v-deep`、`/deep/`、`>>>` 等语法的话,会出现一个警告,下面是新的语法: 87 | 88 | ```css 89 | /* 深度选择器 */ 90 | :deep(selector) { 91 | /* ... */ 92 | } 93 | 94 | /* 插槽选择器 */ 95 | :slotted(selector) { 96 | /* ... */ 97 | } 98 | 99 | /* 全局选择器 */ 100 | :global(selector) { 101 | /* ... */ 102 | } 103 | ``` -------------------------------------------------------------------------------- /src/layouts/BaseLayout.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 57 | 58 | 107 | -------------------------------------------------------------------------------- /docs/1.工程化实践/4. commitlint.md: -------------------------------------------------------------------------------- 1 | # Commitlint 提交信息校验 2 | 3 | 我们在使用 `git commit` 时,git 会记录每一次的 `commit message`(提交信息)。 4 | 5 | 正确的描述 `commit message` 在多人协同开发一个项目时,显得尤其重要。 6 | 7 | 这里我们可以看一下 `angular` 的 [`commit message`](https://github.com/angular/angular/commits/main),会发现它的描述特别的清晰明了。 8 | 9 | 而 [`commitlint`](https://commitlint.js.org/#/) 就是对 `commit message` 进行的检查的一个工具,当不规范时会终止提交。 10 | 11 | 1. 安装依赖 12 | 13 | - `@commitlint/cli`- Commitlint 本体 14 | 15 | - `@commitlint/config-conventional`- 通用的提交规范 16 | 17 | ```bash 18 | pnpm add @commitlint/cli @commitlint/config-conventional -D 19 | ``` 20 | 21 | 1. 创建 commitlint 配置 22 | 23 | 在根目录添加一个 `.commitlintrc.js` 文件,内容如下: 24 | 25 | ```javascript 26 | module.exports = { 27 | extends: ['@commitlint/config-conventional'] 28 | } 29 | ``` 30 | 31 | 1. 在 git `commit-msg` 时进行检查 32 | 33 | 执行下面这条命令即可: 34 | 35 | ```bash 36 | pnpm husky add .husky/commit-msg "pnpm commitlint --edit $1" 37 | ``` 38 | 39 | ## Commit Message 格式 40 | 41 | 配置完成之后就可以在每次 `git commit` 时对 `commit message` 进行校验了,规范有两种格式:单行信息和多行信息。 42 | 43 | 1. 单行信息 44 | 45 | 用于业务代码提交时使用,业务代码一般来说更改比较多而且无法具体说明其信息,具体的还需要看产品文档,所以用单行信息即可。 46 | 47 | ```xml 48 | (): 49 | ``` 50 | 51 | 1. 多行信息 52 | 53 | 用于提交一些不经常更改的功能型代码时使用,如:某个功能函数的新增、修改或重构,目录结构的调整(工程化调整),架构的更改等,这些我们需要进行详细说明防止出现遗忘。 54 | 55 | ```xml 56 | (): 57 | // 空行 58 | 59 | 60 |