typescript|Vue.js 3 + Vite + TypeScript 实战项目开发

演示 Demo
地址: https://shop.fed.lagou.com/admin/
测试账号: lagou / 123456 zce wanglei
一、使用 Vite 创建项目 参考 Vite 官方指南

npm init vite@latest√ Project name: ... lagou-shop-admin √ Select a framework: ? vue √ Select a variant: ? vue-tsScaffolding project in C:\Users\lpz\Projects\lagou-shop-admin...Done. Now run:cd lagou-shop-admin npm install npm run dev

初始目录结构说明
. ├── public │└── favicon.ico ├── src │├── assets ││└── logo.png │├── components ││└── HelloWorld.vue │├── App.vue │├── main.ts │├── shims-vue.d.ts │└── vite-env.d.ts ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── tsconfig.json └── vite.config.ts

在安装了 Vite 的项目中,可以在 npm scripts 中使用 vite 可执行文件,或者直接使用 npx vite 运行它。下面是通过脚手架创建的 Vite 项目中默认的 npm scripts:
{ "scripts": { "dev": "vite", // 启动开发服务器 "build": "vite build", // 为生产环境构建产物 "serve": "vite preview" // 本地预览生产构建产物 } }

可以指定额外的命令行选项,如 --port 或 --https。运行 npx vite --help 获得完整的命令行选项列表
二、代码规范和 ESLint 基础配置
1、安装 ESLint 到项目中
npm install eslint --save-dev

2、初始化 ESLint 配置
npx eslint --init? How would you like to use ESLint? ... To check syntax only To check syntax and find problems > To check syntax, find problems, and enforce code style? What type of modules does your project use? ... > JavaScript modules (import/export) CommonJS (require/exports) None of these ? Which framework does your project use? ... React > Vue.js None of these? Does your project use TypeScript? ? No / Yes? Where does your code run? ...(Pressto select,to toggle all, to invert selection) √ Browser √ Node? How would you like to define a style for your project? ... > Use a popular style guide Answer questions about your style Inspect your JavaScript file(s)? Which style guide do you want to follow? ... Airbnb: https://github.com/airbnb/javascript > Standard: https://github.com/standard/standard Google: https://github.com/google/eslint-config-google XO: https://github.com/xojs/eslint-config-xo ? What format do you want your config file to be in? ... > JavaScript YAML JSON Checking peerDependencies of eslint-config-standard@latest The config that you've selected requires the following dependencies:eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest eslint-config-standard@latest eslint@^7.12.1 eslint-plugin-import@^2.22.1 eslint-plugin-node@^11.1.0 eslint-plugin-promise@^4.2.1 || ^5.0.0 @typescript-eslint/parser@latest ? Would you like to install them now with npm?+ eslint-plugin-import@2.23.4 + eslint-plugin-node@11.1.0 + eslint-config-standard@16.0.3 + eslint-plugin-vue@7.11.1 + eslint@7.29.0 + @typescript-eslint/parser@4.27.0 + @typescript-eslint/eslint-plugin@4.27.0 + eslint-plugin-promise@5.1.0

3、ESLint 配置文件
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

这里改成 vue3-strongly-recommended
// .eslintrc.js module.exports = { env: { browser: true, es2021: true }, extends: [ // 'plugin:vue/essential',// 使用 Vue 3 规则 // https://eslint.vuejs.org/user-guide/#bundle-configurations 'plugin:vue/vue3-strongly-recommended', 'standard' ], parserOptions: { ecmaVersion: 12, parser: '@typescript-eslint/parser', sourceType: 'module' }, plugins: [ 'vue', '@typescript-eslint' ], rules: {} }

4、在 npm scripts 中添加验证脚本
"scripts": { ... "lint": "eslint src/**/*.{js,jsx,vue,ts,tsx} --fix", }

注意:eslint 后面的路径最好加上引号,否则在类 Unix 系统(比如 macOS)中会报错说找不到资源。
vue-eslint-plugin
https://eslint.vuejs.org/
编译器宏和 defineProps、defineEmits、no-undef 规则警告
您需要定义全局变量 (打开新窗口)在您的 ESLint 配置文件中。
如果您不想定义全局变量,请使用 import { defineProps, defineEmits } from 'vue'
示例 .eslintrc.js:
module.exports = { globals: { defineProps: "readonly", defineEmits: "readonly", defineExpose: "readonly", withDefaults: "readonly" } }

另请参阅 ESLint - 指定全局变量 > 使用配置文件。
三、编辑器集成 ● 禁用 Vetur
● 安装 eslint 插件
● 安装 volar 插件
使用dbaeumer.vscode-eslint (打开新窗口)微软官方提供的扩展。
您必须配置eslint.validate扩展的选项来检查.vue文件,因为扩展默认只针对*.js或*.jsx文件。
示例**.vscode/settings.json:**
{ "eslint.validate": [ "javascript", "javascriptreact", "vue" ] }

如果您使用该 Vetur 插件,请设置 “vetur.validation.template”: false 为避免默认 Vetur 模板验证。查看vetur 文档 (打开新窗口)了解更多信息
1、在 vscode 中使用 ESLint 规则格式化代码
1)安装 vscode 扩展 ESLint
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

2)在 vscode 配置文件中找到 ESLint 启用该选项
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

3)重启 vscode
4)打开带有 ESLint 配置文件的项目中任意的 .js 或是 .vue 文件
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

右键选择 文档格式设置方式
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

选择 配置默认格式化程序
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

选择 ESLint
6)如果你喜欢保存文件的时候自动格式化代码,也可以开启这个功能
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

7) 如果你修改了项目中 ESLint 的校验规则,一定要重启 vscode 才能生效。
四、配置 git commit hook ● https://github.com/okonet/lint-staged
安装:
npx mrm@2 lint-staged

// package.json { "version": "0.0.0", "scripts": { "dev": "vite", "build": "vue-tsc --noEmit && vite build", "serve": "vite preview", "tsc": "vue-tsc --noEmit", "lint": "eslint ./src/**/*.ts ./src/**/*.vue --cache --fix", "prepare": "husky install" }, "dependencies": { "@form-create/element-ui": "^2.5.7", "axios": "^0.21.1", "element-plus": "^1.0.2-beta.48", "nprogress": "^0.2.0", "path-to-regexp": "^6.2.0", "utility-types": "^3.10.0", "vue": "^3.1.1", "vue-router": "^4.0.8", "vuex": "^4.0.1", "vxe-table": "^4.0.22", "xe-utils": "^3.3.0" }, "devDependencies": { "@types/node": "^15.12.2", "@types/nprogress": "^0.2.0", "@typescript-eslint/eslint-plugin": "^4.27.0", "@typescript-eslint/parser": "^4.27.0", "@vitejs/plugin-vue": "^1.2.3", "@vue/compiler-sfc": "^3.1.1", "eslint": "^7.29.0", "eslint-config-standard": "^16.0.3", "eslint-plugin-import": "^2.23.4", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^5.1.0", "eslint-plugin-vue": "^7.11.1", "husky": "^6.0.0", "lint-staged": "^11.0.0", "sass": "^1.34.1", "typescript": "^4.1.3", "vite": "^2.3.5", "vue-tsc": "^0.0.24" }, "lint-staged": { "*.{js,jsx,vue,ts,tsx}": [ "npm run lint", // "git add" 之前的版本需要手动把 lint 过程中修改的代码手动 add,新版本不需要了 ] } }

1、在开发和构建中进行代码规范校验
● https://github.com/vitejs/awesome-vite#plugins
● https://github.com/gxmari007/vite-plugin-eslint
npm install vite-plugin-eslint --save-dev
vite.config.ts里面做下配置
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

效果:
typescript|Vue.js 3 + Vite + TypeScript 实战项目开发
文章图片

五、Git commit 提交规范 ● Commit message 和 Change log 编写指南
● Git 使用规范流程
● Git 工作流程
统一团队 Git commit 日志标准,便于后续代码 review,版本发布以及日志自动化生成等等。
● commitlint:验证 git commit 日志是否符合规范
● Commitizen:辅助编写符合 git commit 规范的工具
六、Vite中得TS环境说明 ● TS 环境说明
● shimes-vue.d.ts 文件的作用
● vite-env.d.ts 文件的作用
● vue-tsc 和 tsc
○ tsc 只能验证 ts 代码类型
○ vue-tsc 可以验证 ts + Vue Template 中的类型(基于 Volar)
建议在 package.json 中新增一个 scripts 脚本用来单独执行 TS 类型验证:
"scripts": { ... "build": "npm run tsc && vite build", "tsc": "vue-tsc -noEmit" },

-noEmit 表示只验证类型,不输出编译结果。
跳过第三方包类型检查
{ "compilerOptions": { ... "baseUrl": "./", "skipLibCheck": true } }

1、Vue 3 中的 TS 支持
建议参考:
● https://v3.cn.vuejs.org/guide/typescript-support.html
Vue 3 中的
Vue 3 支持三种写法:
● Option API
● Composition API
● .el-container { height: 100vh; }.el-header { background-color: #fff; color: #333; display: flex; justify-content: space-between; align-items: center; }.el-aside { background-color: #304156; color: #333; }.el-main { background-color: #E9EEF3; color: #333; }
菜单栏
.el-menu { border-right: none; }

头部
i { font-size: 19px; cursor: pointer; }

ToggleSidebar

Breadcrumb

MenuSearch

FullScreen

Notification

UserInfo

十五、配置基础路由页面
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' import AppLayout from '@/layout/AppLayout.vue' import productRoutes from './modules/product' import orderRoutes from './modules/order' import permissionRoutes from './modules/permission' import mediaRoutes from './modules/media'const routes: RouteRecordRaw[] = [ { path: '/', component: AppLayout, children: [ { path: '', // 默认子路由 name: 'home', component: () => import('../views/home/index.vue') }, productRoutes, orderRoutes, permissionRoutes, mediaRoutes ] }, { path: '/login', name: 'login', component: () => import('../views/login/index.vue') } ]const router = createRouter({ history: createWebHashHistory(), // 路由模式 routes // 路由规则 })export default router

// src\router\modules\order.tsimport { RouteRecordRaw, RouterView } from 'vue-router'const routes: RouteRecordRaw = { path: '/order', name: 'order', component: RouterView, children: [ { path: 'list', name: 'order_list', component: () => import('@/views/order/list/index.vue') }, { path: 'offline', name: 'order-offline', component: () => import('@/views/order/offline/index.vue') } ] }export default routes

十六、页面加载进度条 知识点:
● 路由拦截器
● 加载进度条
安装 nprogress
npm i nprogress# 如果是 TS 需要补充安装它的类型补充包 npm i -D @types/nprogress

配置
// src\router\index.ts import nprogress from 'nprogress' import 'nprogress/nprogress.css'// 进度条的配置 nprogress.configure({})// VueRouter 4 中可以不写 next 了,默认就是通过状态 router.beforeEach((to, from) => { nprogress.start() })router.afterEach(() => { nprogress.done() })

十七、页面标题处理 ● https://github.com/nuxt/vue-meta
● https://github.com/nuxt/vue-meta/tree/next
npm install vue-meta@next --save

十八、面包屑导航

十九、全屏切换

二十、侧边栏展开/收起

二十一、用户登录和身份认证 1、登录页面布局
.login-container { min-width: 400px; height: 100vh; display: flex; justify-content: center; align-items: center; background-color: #2d3a4b; }.login-form { padding: 30px; border-radius: 6px; background: #fff; min-width: 350px; .login-form__header { display: flex; justify-content: center; align-items: center; padding-bottom: 30px; }.el-form-item:last-child { margin-bottom: 0; }.login__form-title { display: flex; justify-content: center; color: #fff; }.submit-button { width: 100%; }.login-logo { width: 271px; height: 74px; } .imgcode-wrap { display: flex; align-items: center; .imgcode { height: 37px; } } }

2、处理图片验证码
const captchaSrc = https://www.it610.com/article/ref('')onMounted(() => { loadCaptcha() })const loadCaptcha = async () => { const data = https://www.it610.com/article/await getCaptcha() captchaSrc.value = URL.createObjectURL(data) }

export const getCaptcha = () => { return request({ method: 'GET', url: '/captcha_pro', params: { stamp: Date.now() }, responseType: 'blob' // 请求获取图片数据 }) }

3、处理登录逻辑
const handleSubmit = async () => { // 表单验证 const valid = await form.value?.validate() if (!valid) { return false }// 验证通过,展示 loading loading.value = https://www.it610.com/article/true// 请求登录 const data = await login(user).finally(() => { loading.value = https://www.it610.com/article/false })// 存储登录用户信息 store.commit('setUser', { ...data.user_info, token: data.token })// 跳转回原来页面 let redirect = route.query.redirect || '/' if (typeof redirect !== 'string') { redirect = '/' } router.replace(redirect)// 路由跳转不想被记录 }

4、统一处理接口请求失败
request.interceptors.response.use( response => { const { status } = response.data// 请求成功 if (!status || status === 200) { return response }// 处理 Token 过期// 其它错误给出提示即可,比如 400 参数错误之类的 ElMessage({ type: 'error', message: response.data.msg, duration: 5 * 1000 }) return Promise.reject(response) }, err => { ElMessage({ type: 'error', message: err.message, duration: 5 * 1000 }) return Promise.reject(err) } )

5、封装 element-plus 类型
// src\types\element-plus.tsimport { ElForm } from 'element-plus' import { FormItemRule } from 'element-plus/packages/form/src/form.type'export type IElForm = InstanceTypeexport type IFormRule = Record

// src\utils\storage.ts export const getItem = (key: string) => { const data = https://www.it610.com/article/window.localStorage.getItem(key) if (!data) return null try { return JSON.parse(data) as T } catch (err) { return null } }export const setItem = (key: string, value: object | string | null) => { if (typeof value =https://www.it610.com/article/=='object') { value = https://www.it610.com/article/JSON.stringify(value) } window.localStorage.setItem(key, value) }export const removeItem = (key: string) => { window.localStorage.removeItem(key) }

6、统一设置用户 Token
request.interceptors.request.use( config => { // 容错:防止请求地址中有空格 config.url = config.url?.trim()// 统一设置用户 token const { user } = store.state if (user && user.token) { config.headers.Authorization = `Bearer ${user.token}` } return config }, error => { return Promise.reject(error) } )

7、未登录不允许访问
router.beforeEach((to, from) => { nprogress.start() // 开始加载进度条 if (to.meta.requiresAuth && !store.state.user) { // 此路由需要授权,请检查是否已登录 // 如果没有,则重定向到登录页面 return { path: '/login', // 保存我们所在的位置,以便以后再来 query: { redirect: to.fullPath } } } })

8、统一处理 Token 失效
// 控制登录过期的锁 let isRefreshing = false request.interceptors.response.use( response => { const { status } = response.data// 请求成功 if (status === 200 || response.config.responseType === 'blob') { return response }// 登录过期 if (status === 410000) { if (isRefreshing) return Promise.reject(response) isRefreshing = true ElMessageBox.confirm('您的登录已过期,您可以取消停留在此页面,或确认重新登录', '登录过期', { confirmButtonText: '确认', cancelButtonText: '取消' }).then( () => { // 清除登录状态并跳转到登录页 store.commit('setUser', null) router.push({ name: 'login', query: { redirect: router.currentRoute.value.fullPath } }) } ).finally(() => { isRefreshing = false })return Promise.reject(response) }// 其它错误给出提示即可,比如 400 参数错误之类的 ElMessage({ type: 'error', message: response.data.msg, duration: 5 * 1000 }) return Promise.reject(response) }, err => { ElMessage({ type: 'error', message: err.message, duration: 5 * 1000 }) return Promise.reject(err) } )

二十二、权限管理 1、管理员
开启 关闭


element 组件库的表格树有性能问题,这里推荐另一个第三方表格组件。
● https://github.com/x-extends/vxe-table
二十三、Excel 导出 npm install xlsx
import XLSX from 'xlsx'

【typescript|Vue.js 3 + Vite + TypeScript 实战项目开发】性能优化 import异步加载,没必要一开始就加载xlsx资源
const handleExportExcel = async () => { if (!selectionItems.value.length) { return ElMessage.warning('请选择商品') } exportExcelLoading.value = https://www.it610.com/article/true try { const { jsonToExcel } = await import('@/utils/export-to-excel') jsonToExcel({ data: selectionItems.value, header: { id: '编号', store_name: '商品名称', price: '价格' }, fileName: '测试.xlsx', bookType: 'xlsx' }) } catch (err) { console.error(err) } exportExcelLoading.value = https://www.it610.com/article/false }

二十四、富文本编辑器

二十五、拖拽
:deep(.el-tag) { margin-right: 5px; }

:deep(.el-tag) { margin-right: 5px; }

    推荐阅读