个人建站前端篇(七)vite + vue3企业级项目模板
一、vite命令行创建项目
npm create vite@latest
根据提示选择模板,选择vite + vue3 + ts即可。
二、项目连接远程仓库
git init
git remote add origin https://gitee.com/niech_project/vite-vue3-template.git
git pull origin master
git checkout -b dev
三、项目加入eslint校验和自动格式化
- eslint 运行代码前就可以发现一些语法错误和潜在bug,保证代码一致性。
- prettier 是代码格式化工具,用于检查代码中的格式问题。
- 区别联系:eslint保证代码质量,prettier保证代码风格,eslint有小部分格式化功能,通常和prettier结合使用。
1. 安装eslint和prettier
- eslint: ESLint的核心代码库
- prettier:prettier格式化代码的核心库
- eslint-config-airbnb-base: airbnb的代码规范 (依赖plugin-import)
- eslint-config-prettier:eslint结合prettier的格式化
- eslint-plugin-vue:eslint在vue里的代码规范
- eslint-plugin-import:项目里面支持eslint
- eslint-plugin-prettier:将prettier结合进入eslint的插件
pnpm install eslint eslint-plugin-vue eslint-config-prettier prettier eslint-plugin-import eslint-plugin-prettier eslint-config-airbnb-base -D
:::tip
npm i module_name -D
-D 表示安装模块到开发依赖管理devDependencies中,如果你安装的库是用来打包的、解析代码的,比如vite、webpack、babel,就可以用 -d 来安装,项目上线了,这些库就没用了。也比如saas
-S 表示安装模块到生产依赖管理dependencies中,这个是项目运行需要用到的依赖,比如vue、eslint、vuex、axios,就用 -s 来安装。
:::
npm安装模块
【npm install xxx】利用 npm 安装xxx模块到当前命令行所在目录;
【npm install -g xxx】利用npm安装全局模块xxx;
【npm install xxx】安装但不写入package.json;
【npm install xxx –save】 安装并写入package.json的“dependencies”中;
【npm install xxx –save-dev】安装并写入package.json的“devDependencies”中。
npm 删除模块
【npm uninstall/remove xxx 】删除xxx模块;
【npm uninstall/remove -g xxx】删除全局模块xxx;
2. 配置eslint和prettier
- 在package.json文件scripts加入命令
"lint:create":"eslint --init"
执行npm run lint:create,自动创建.eslintrc.cjs文件

:::tip
补充一些依赖安装
@typescript-esTint/parser: ESLint的解析器,用于解析typescript,从而检查和规范Typescript代码;
@typescript-eslint/eslint-plugin: 这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规范
eslint-import-resolver-alias 让我们可以import的时候使用 @ 别名
:::
pnpm install typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-import-resolver-alias @types/eslint @types/node -D
- 修改vue.config.ts文件
pnpm install vite-plugin-eslint consola terser -D
import { defineConfig,loadEnv } from 'vite'
import path from "path";
import vue from '@vitejs/plugin-vue'
import eslintPlugin from "vite-plugin-eslint";export default defineConfig(({ mode }) => {return {plugins: [vue(),eslintPlugin()//代码校检],base: "./", // 在生产中服务时的基本公共路径publicDir: "public", // 静态资源服务的文件夹, 默认"public"resolve: {alias: {"@": path.resolve(__dirname, "./src"), // 相对路径别名配置,使用 @ 代替 src},extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],},// 打包配置build: {target: "modules", // 设置最终构建的浏览器兼容目标。modules:支持原生 ES 模块的浏览器outDir: "dist", // 指定输出路径assetsDir: "assets", // 指定生成静态资源的存放路径assetsInlineLimit: "4096", // 小于此阈值的导入或引用资源将内联为base64编码,设置为0可禁用此项。默认4096(4kb)cssCodeSplit: true, // 启用/禁用CSS代码拆分,如果禁用,整个项目的所有CSS将被提取到一个CSS文件中,默认truesourcemap: false, // 构建后是否生成 source map 文件minify: "terser", // 混淆器,terser构建后文件体积更小write: true, // 设置为 false 来禁用将构建后的文件写入磁盘emptyOutDir: true, // 默认情况下,若 outDir 在 root 目录下,则 Vite 会在构建时清空该目录。chunkSizeWarningLimit: 500, // chunk 大小警告的限制terserOptions: {compress: {drop_console: true,drop_debugger: true,},}, // 去除 console debugger}}
})
vite-plugin-eslint: vite的一个插件,让项目可以方便的得到eslint支持,完成eslint配置后,可以快速的将其集成进vite中,便于在代码不符合eslint规范的第一时间看到提示
3. 修改添加常见配置
项目根目录创建以下配置文件
- .eslintrcignore 忽略校验文件
# .eslintrcignore*.sh
node_modules
*.md
*.woff
*.ttf
dist
/pubilc
/docs
.husky
/bin
.eslintrc.js
perttier.config.js
/src/mock/*
/src/*
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
pnpm-debug.log*
lerna-debug.log*.DS_Store
dist-ssr
*.local/cypress/videos/
/cypress/screenshots/# Editor directories and files
.vscode
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?components.d.ts
- .prettierrc.cjs 配置格式化规则
module.exports = {"printWidth" : 80, // 一行最多100字符"tabWidth": 2, // 使用2个空格缩进"useTabs": false, // 不适用缩进符,使用空格"semi": false, // 行尾是否要有分号"singleQuote": true, // 使用单引号"quoteProps": 'as-needed', // 对象的key,仅仅在必要时使用引号"jsxSingleQuote": false, // jsx是否使用双引号"trailingComma": 'es5', // 尾随逗号"bracketSpacing": true, // 大括号内的首尾需要有空格"arrowParens": 'always', // 箭头函数,是有一个参数的时候,也需要小括号"rangeStart": 0, // 文件格式化的范围是全部内容"rangeEnd": Infinity,"requirePragma": false, // 不需要写文件开头的 @prettier"insertPragma": false, // 不需要在文件开头插入 @prettier"proseWrap": 'always', // 使用默认执行标准"htmlWhitespaceSensitivity": 'css', // 根据显示样式决定html是否执行"endOfLine": 'lf' // 换行符是有lf
}
- .prettierignore 忽略格式化文件
# prettierignore
/dist/*
.local
.output.js
/node_modules/**
src/.DS_Store**/*.svg
**/*.sh
/public/*
components.d.ts
在package.json添加格式化命令
- “lint”: “eslint “src/**/*.{js,ts,vue}” --fix”, 既可以检查又可以修复部分语法问题
- “prettier-format”: “prettier --config .prettierrc.cjs “src/**/*.{js,ts,vue}” --write”, 利用prettier手动格式化一些样式问题
四、修改tsconfig.json配置别名
"baseUrl": "",
"paths": {"@/*":["src/*"]
}
五、环境配置(开发,预发,生产环境)
开发环境:开发人员开发的环境
测试环境:测试人员测试的环境
预发环境:准备上线的环境,也可叫内测环境
生产环境:正式线上环境,投入生产的环境
这里我们配置两个环境,一个测试环境和生产环境,
开发人员和测试人员使用测试环境,修改package.json文件,添加两个命令
“build:dev”: “vue-tsc --noEmit && vite build --mode development”,
“build:pro”: “vue-tsc --noEmit && vite build --mode production”,
新建两个配置文件
.env.development:开发测试环境
# 页面标题
VITE_APP_TITLE = xxx
# 开发环境配置
VITE_APP_ENV = 'development'
# 开发环境
VITE_APP_BASE_API = '/stage-api'
# 是否启用代理
VITE_HTTP_PROXY = true
# 端口
VITE_PORT = 80
#本地环境接口地址
VITE_SERVE = 'http://192.168.xx.xx:8080'
.env.production:生产环境
# 页面标题
VITE_APP_TITLE = xxx
# 生产环境配置
VITE_APP_ENV = 'production'
# 开发环境
VITE_APP_BASE_API = '/stage-api'
# 是否启用代理
VITE_HTTP_PROXY = false
# 端口
VITE_PORT = 80
#本地环境接口地址
VITE_SERVE = 'http://xx.xx.xx.xx:8080'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
六、自动引入插件unplugin-auto-import和unplugin-vue-components的使用
:::tip
vue3日常项目中定义变量需要引入ref,reactive等等比较麻烦,可以通过unplugin-auto-import给我们自动引入
:::
- 安装依赖
npm install -D unplugin-vue-components unplugin-auto-import vite-plugin-style-import
npm install element-plus @element-plus/icons-vue ant-design-vue -S
- 在vite.config.ts文件中添加配置
import { defineConfig, loadEnv } from "vite";
import path from "path";
import vue from "@vitejs/plugin-vue";
import eslintPlugin from "vite-plugin-eslint";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import {createStyleImportPlugin,ElementPlusResolve
} from "vite-plugin-style-import";export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd());const { VITE_APP_BASE_API,VITE_SERVE,VITE_PORT } = envreturn {plugins: [vue(),eslintPlugin(),AutoImport({imports: ["vue", "vue-router"],dts: "src/auto-import.d.ts", // 路径下自动生成文件夹存放全局指令eslintrc: {enabled: true, // 1、改为true用于生成eslint配置。2、生成后改回false,避免重复生成消耗},resolvers: [ElementPlusResolver()]}),Components({dts: "./src/components.d.ts", // 创建ts文件extensions: ["vue"], // 指定文件的后缀dirs: ["src/components/"], // 指定路径,自动导入自定义组件resolvers: [ElementPlusResolver()], // 指定自动引入的组件库,也就是从插件中导出的那个}),// 配置自动导入element startcreateStyleImportPlugin({resolves: [ElementPlusResolve()],libs: [{libraryName: "element-plus",esModule: true,resolveStyle: (name: string) =>`element-plus/theme-chalk/${name}.css`,},],})],base: "./", // 在生产中服务时的基本公共路径publicDir: "public", // 静态资源服务的文件夹, 默认"public"resolve: {alias: {"@": path.resolve(__dirname, "./src"), // 相对路径别名配置,使用 @ 代替 src},extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],},// 本地运行配置server: {host: '0.0.0.0',port: VITE_PORT || 80,https: false,open: true,proxy: {[VITE_APP_BASE_API]: {target: VITE_SERVE,changeOrigin: true,rewrite: path => path.replace(RegExp(`^${VITE_APP_BASE_API}`), '')}},disableHostCheck: true},// 打包配置build: {target: "modules", // 设置最终构建的浏览器兼容目标。modules:支持原生 ES 模块的浏览器outDir: "dist", // 指定输出路径assetsDir: "assets", // 指定生成静态资源的存放路径assetsInlineLimit: "4096", // 小于此阈值的导入或引用资源将内联为base64编码,设置为0可禁用此项。默认4096(4kb)cssCodeSplit: true, // 启用/禁用CSS代码拆分,如果禁用,整个项目的所有CSS将被提取到一个CSS文件中,默认truesourcemap: false, // 构建后是否生成 source map 文件minify: "terser", // 混淆器,terser构建后文件体积更小write: true, // 设置为 false 来禁用将构建后的文件写入磁盘emptyOutDir: true, // 默认情况下,若 outDir 在 root 目录下,则 Vite 会在构建时清空该目录。chunkSizeWarningLimit: 500, // chunk 大小警告的限制terserOptions: {compress: {drop_console: true,drop_debugger: true,},}, // 去除 console debugger},};
});
-
在src目录下自动生成auto-import.d.ts文件,用于存放全局指令
-
和eslintrc不兼容时,加上
eslintrc: {enabled: true, // 1、改为true用于生成eslint配置。2、生成后改回false,避免重复生成消耗
}
自动生成.eslintrc-auto-import.json文件
5. 在.eslintrc.json文件中添加如下配置:
module.exports = {// 环境 浏览器,最新ES语法,node环境"env": {"browser": true,"commonjs": true,"es2021": true},/*** 扩展的eslint规范语法,可以被继承的规则,字符串数组,每个配置继承它之前的配置* 分别是eslint-config-vue 提供的* eslint-config-airbnb-base 提供的* eslint-config-prettier 提供的* eslint-config- 前缀可以简写*/"extends": ["eslint:recommended","plugin:@typescript-eslint/recommended","plugin:vue/vue3-essential","airbnb-base","prettier","./.eslintrc-auto-import.json"],// eslint 会对代码进行校验,parser是将代码转换为ESTree(AST),ESlint会对ESTree校验"parser": "vue-eslint-parser",// 解析器的配置项"parserOptions": {// eslint的版本号,或者年份都可以"ecmaVersion": "latest","parser": "@typescript-eslint/parser","sourceType": "module",// 额外的语言类型"ecmaFeatures": {"jsx": true,"tsx": true}},// 全局自定义宏,这样在源文件中使用全局变量不会报错或警告"globals": {"defineProps": "readonly","defineEmits": "readonly","defineExpose": "readonly","withDefaults": "readonly"},/*** 插件* eslint-plugin- 前缀可以简写* vue官方提供了一个eslint插件eslint-plugin-vue,它提供了parser和rules。* parser为vue-eslint-parser,放在前面的parser字段里,rules放在extends字段里*/"plugins": ["@typescript-eslint","vue"],"settings": {// 设置项目内的别名"import/resolver": {"alias": {"map": [["@","./src"]]}},"import/extensions": [".js",".jsx",".tsx",".ts",".mjs",".cjs"]},/*** rules: 自定义规则,覆盖extends继承的规则,对规则进行灵活配置** "off" 或 0 ==> 关闭规则* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)*/"rules": {// eslint(https://eslint.bootcss.com/docs/rules/)"no-var": "error", // 要求使用 let 或 const 而不是 var"no-multiple-empty-lines": ["warn", { "max": 2 }], // 不允许多个空行"no-console": "off","no-debugger": "error","no-unexpected-multiline": "error", // 禁止空余的多行"no-useless-escape": "off", // 禁止不必要的转义字符"import/no-unresolved": "off","import/extensions": "off","import/no-absolute-path": "off","import/no-extraneous-dependencies": "off","import/prefer-default-export": "off",// typeScript (https://typescript-eslint.io/rules)"@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量"@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore"@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型"@typescript-eslint/no-non-null-assertion": "off","@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间。"@typescript-eslint/semi": "off",// eslint-plugin-vue (https://eslint.vuejs.org/rules/)"vue/multi-word-component-names": "off", // 要求组件名称始终为 “-” 链接的单词"vue/script-setup-uses-vars": "error", // 防止<script setup>使用的变量<template>被标记为未使用"vue/no-mutating-props": "off", // 不允许组件 prop的改变"vue/attribute-hyphenation": "off",// 对模板中的自定义组件强制执行属性命名样式// self"no-param-reassign":"off",// 不允许参数重新赋值"no-useless-concat":"off","no-plusplus":"off",// 不允许一元操作符++、--}
}
- tsconfig.json文件中添加如下配置:
include加入"src/auto-imports.d.ts"
- 安装 element-plus
npm install element-plus -S
在.eslintrc-auto-import.json文件中添加需要引入的组件:
“ElMessage”: true
- 安装saas
npm install sass -D
七、添加路由
- pnpm install vue-router 安装路由依赖;
- 在src目录下新建router文件夹index.ts文件和routes.ts文件;
index.ts内容如下
// 通过vue-router插件实现模板路由配置
import { createRouter, createWebHistory } from "vue-router";
import { constantRoutes } from "./routes.ts";// 创建路由器
const router = createRouter({// 路由模式hashhistory: createWebHistory(),routes: constantRoutes,// 滚动行为scrollBehavior() {return {left: 0,top: 0,};},
});export default router;
routes.ts内容如下
import Layout from '@/layout'
/*** Note: 路由配置项** hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面* // 若你想不管路由下面的 children 声明的个数都显示你的根路由* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数* roles: ['admin', 'common'] // 访问路由的角色权限* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限* meta : {noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svgbreadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。}*/
export const constantRoutes = [{path: '/redirect',component: Layout,hidden: true,children: [{path: '/redirect/:path(.*)',component: () => import('@/views/redirect/index.vue')}]},{path: '',component: Layout,redirect: '/index',children: [{path: '/index',component: () => import('@/views/home/index.vue'),name: 'Index',meta: { title: '系统首页', icon: 'home', affix: true }}]},{path: '/login',component: () => import('@/views/login/index.vue'),hidden: true}// {// path: "/404",// component: () => import("@/views/404/index.vue"),// name: "404",// meta: {// title: "404",// },// },
];
// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [// {// path: '/system/user-auth',// component: Layout,// hidden: true,// permissions: ['system:user:edit'],// children: [// {// path: 'role/:userId(\\d+)',// component: () => import('@/views/system/user/authRole'),// name: 'AuthRole',// meta: { title: '分配角色', activeMenu: '/system/user' }// }// ]// },// {// path: '/system/role-auth',// component: Layout,// hidden: true,// permissions: ['system:role:edit'],// children: [// {// path: 'user/:roleId(\\d+)',// component: () => import('@/views/system/role/authUser'),// name: 'AuthUser',// meta: { title: '分配用户', activeMenu: '/system/role' }// }// ]// },// {// path: '/system/dict-data',// component: Layout,// hidden: true,// permissions: ['system:dict:list'],// children: [// {// path: 'index/:dictId(\\d+)',// component: () => import('@/views/system/dict/data'),// name: 'Data',// meta: { title: '字典数据', activeMenu: '/system/dict' }// }// ]// }
]
- 在main.ts文件中引入router
import { createApp } from "vue";
import "./style.scss";
import ElementPlus from 'element-plus'
import Antd from 'ant-design-vue';
import "ant-design-vue/dist/reset.css";
import App from "./App.vue";
// 引入路由
import router from "./router";
import store from "./store";
import './permission';// 权限处理import "virtual:svg-icons-register";
import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon'const app = createApp(App);
app.use(router);
app.use(store);
app.use(ElementPlus);
app.use(Antd);
app.use(elementIcons)
app.component('svg-icon', SvgIcon)
app.mount("#app");
补充layout
- src下新建layout文件夹,分别新建index.vue和components文件夹
index.vue内容如下:
<template><div class="app-wrapper"><side-bar class="sideBar-container" /><div class="main-container"><div class="fixed-header"><nav-bar class="navBar-container" /></div><app-main /></div></div>
</template><script setup lang="ts">
import { AppMain, NavBar, SideBar } from './components'
</script><style lang='scss' scoped>
@import "@/assets/styles/mixin.scss";
@import "@/assets/styles/variables.module.scss";.app-wrapper {@include clearfix;position: relative;height: 100vh;width: 100%;.sideBar-container {width: $base-sidebar-width;background-color: $base-menu-background;height: calc(100% - #{$base-navbar-height});position: fixed;top: $base-navbar-height;bottom: 0;left: 0;z-index: 1001;overflow: hidden;}.main-container {height: 100%;width: calc(100% - #{$base-sidebar-width});position: relative;left: $base-sidebar-width;.fixed-header {position: fixed;top: 0;right: 0;z-index: 9;width: 100%;height: $base-navbar-height;transition: width 0.28s;background-color: #fff;box-shadow: 0 4px 6px rgba(0, 0, 0, .05);.navBar-container {height: 100%;// max-width: 1200px;position: relative;margin: auto;will-change: transform;// padding: 0 24px;}}}
}
</style>
- 在components文件夹下新建AppMain.vue文件,内容如下:
<template><section class="app-main"><div class="app-main-container"><router-view v-slot="{ Component, route }"><transition name="fade-transform" mode="out-in"><keep-alive :include="['index','crm']"><component :is="Component" :key="route.path"/></keep-alive></transition></router-view></div></section>
</template><script setup lang="ts">
</script><style lang='scss' scoped>
@import "@/assets/styles/variables.module.scss";
.app-main {height: calc(100vh - #{$base-navbar-height});background-color: #fff;width: 100%;position: relative;overflow: auto; top:$base-navbar-height;.app-main-container {height: 100%;padding: 20px;}
}
</style>
<style lang='scss'>
::-webkit-scrollbar {width: 6px;height: 6px;
}::-webkit-scrollbar-track {background-color: #f1f1f1;
}::-webkit-scrollbar-thumb {background-color: #c0c0c0;border-radius: 3px;
}
</style>
- 在components文件夹下新建index.ts文件,内容如下:
export { default as AppMain } from './AppMain'
export { default as NavBar } from './NavBar'
export { default as SideBar } from './SideBar'
- 在components文件夹下新建SideBar/NavBar/InnerLink文件夹,内容如下:
SideBar新建index/SidebarItem/Link.vue文件
index.vue内容如下:
<template><div :style="{ backgroundColor: variables.menuLightBackground }"><el-scrollbar wrap-class="scrollbar-wrapper" style="padding: 20px;"><el-menu:default-active="activeMenu":background-color="variables.menuLightBackground":text-color="variables.menuLightColor":unique-opened="true"mode="vertical"><sidebar-itemv-for="(route, index) in sidebarRouters":key="route.path + index":item="route":base-path="route.path"/></el-menu></el-scrollbar></div>
</template><script setup>
import variables from '@/assets/styles/variables.module.scss'
import SidebarItem from './SidebarItem'
import usePermissionStore from '@/store/modules/permission'const route = useRoute();
const permissionStore = usePermissionStore()const sidebarRouters = computed(() => permissionStore.sidebarRouters);
console.log('sidebarRouters',sidebarRouters)
const activeMenu = computed(() => {const { meta, path } = route;if (meta.activeMenu) {return meta.activeMenu;}return path;
})</script>
<style>
.sidebar-container .el-scrollbar__wrap{height: calc(100vh - 155px);overflow-y: auto;
}
#app .sidebar-container .el-menu--vertical .nest-menu .el-sub-menu > .el-sub-menu__title:hover,
#app .sidebar-container .el-menu--vertical .el-menu-item:hover,
#app .sidebar-container .nest-menu .el-sub-menu > .el-sub-menu__title:hover,
#app .sidebar-container .el-sub-menu .el-menu-item:hover,
#app .sidebar-container .el-menu--vertical .nest-menu .el-sub-menu > .el-sub-menu__title .is-active,
#app .sidebar-container .el-menu--vertical .el-menu-item.is-active,
#app .sidebar-container .nest-menu .el-sub-menu > .el-sub-menu__title .is-active,
#app .sidebar-container .el-sub-menu .el-menu-item .is-active{background-color: rgb(79 110 247 / 5%) !important;
}
.el-menu{--el-menu-item-height:2.75rem;--el-menu-sub-item-height:2.75rem;border-right: none!important;
}</style>
SidebarItem.vue内容如下:
<template><div v-if="!item.hidden"><template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow"><app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)"><el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }" @click="handleClick(item.path)"><svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"/><template #title><span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span></template></el-menu-item></app-link></template><el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported><template v-if="item.meta" #title><svg-icon :icon-class="item.meta && item.meta.icon" /><span class="menu-title" :title="hasTitle(item.meta.title)">{{ item.meta.title }}</span></template><sidebar-itemv-for="child in item.children":key="child.path":is-nest="true":item="child":base-path="resolvePath(child.path)"class="nest-menu"/></el-sub-menu></div>
</template><script setup>
import { isExternal } from '@/utils/validate'
import AppLink from './Link'
import { getNormalPath } from '@/utils/mis'const props = defineProps({// route objectitem: {type: Object,required: true},isNest: {type: Boolean,default: false},basePath: {type: String,default: ''}
})const onlyOneChild = ref({});function hasOneShowingChild(children,parent) {if (!children) {children = [];}const showingChildren = children.filter(item => {if (item.hidden) {return false} // Temp set(will be used if only has one showing child)onlyOneChild.value = itemreturn true})// When there is only one child router, the child router is displayed by defaultif (showingChildren.length === 1) {return true}// Show parent if there are no child router to displayif (showingChildren.length === 0) {onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }return true}return false
};
function handleClick(routePath){localStorage.setItem('routePath', routePath)
}
function resolvePath(routePath, routeQuery) {if (isExternal(routePath)) {return routePath}if (isExternal(props.basePath)) {return props.basePath}if (routeQuery) {const query = JSON.parse(routeQuery);return { path: getNormalPath(`${props.basePath }/${ routePath}`), query }}return getNormalPath(`${props.basePath }/${ routePath}`)
}function hasTitle(title){if (title.length > 5) {return title;} return "";}
</script>
Link.vue内容如下:
<template><component :is="type" v-bind="linkProps()"><slot /></component>
</template><script setup>
import { isExternal } from '@/utils/validate'const props = defineProps({to: {type: [String, Object],required: true}
})const isExt = computed(() => isExternal(props.to))const type = computed(() => {if (isExt.value) {return 'a'}return 'router-link'
})function linkProps() {if (isExt.value) {return {href: props.to,target: '_blank',rel: 'noopener'}}return {to: props.to}
}
</script>
NavBar新建index.vue,内容如下
<template><div class="navbar">navbar<span @click="handleLoginOut()">退出</span></div>
</template><script setup lang="ts">
import useUserStore from '@/store/modules/user'const userStore = useUserStore()
const handleLoginOut = () => {userStore.logOut().then(() => {window.location.href = '/index';})
}</script><style lang='scss' scoped>
.navbar {background: #4F6EF7;
}
</style>
InnerLink/index.vue内容如下:
<template><div :style="'height:' + height"><iframe:id="iframeId"style="width: 100%; height: 100%":src="src"frameborder="no"></iframe></div>
</template><script setup>
const props = defineProps(['src', 'iframeId'])
const { src, iframeId } = toRefs(props);const height = ref(`${document.documentElement.clientHeight - 94.5 }px`);
</script>
八、添加类型说明文件
typescript 只能理解 .ts 文件,无法理解 .vue文件
因此需要给.vue文件加上类型说明文件
解决方法:在项目根目录或 src 文件夹下创建一个后缀为 .d.ts 的文件,并vite-env.d.ts写入以下内容:
declare module '*.vue' {import type { DefineComponent } from 'vue'// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-typesconst component: DefineComponent<{}, {}, any>export default component
}
九、添加api,封装请求(axios)
- 安装依赖pnpm install axios --save
- 新建api文件夹,utils文件夹,及创建request.ts文件
十、pinia状态管理器
- 安装依赖:pnpm install pinia --save
- 在src目录下新建文件夹store,在store文件下新建文件index.ts和modules文件夹
- 在index.ts文件中引入pinia,创建pinia实例,挂载到根组件上
- 在main.ts文件中引入store,并挂载到根组件上
十一、pinia状态持久化处理
pnpm install pinia-plugin-persistedstate --save
persist: true// 开启持久化存储
十二、滚动条美化处理
::-webkit-scrollbar {width: 6px;height: 6px;
}::-webkit-scrollbar-track {background-color: #f1f1f1;
}::-webkit-scrollbar-thumb {background-color: #c0c0c0;border-radius: 3px;
}
十三、vue3告别.value,ref要求我们访问变量时需要加上.value
let count = ref(1)
const addCount = () => {count.value += 1
}
尤大也提交了一份新的ref语法糖提案。
ref: count = 1
const addCount = () => {count += 1
}
官方后来出的一种方案,在ref前加上$,该功能默认关闭,需要手动开启, vite.config.ts内容配置如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({plugins: [vue({refTransform: true // 开启ref转换})]
})
开启之后的写法
let count = $ref(1)
const addCount = () => {count++
}
十四、svg-icon的应用
pnpm install vite-plugin-svg-icons -S
在vite.config.ts中配置:
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
createSvgIconsPlugin({iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')],symbolId: 'icon-[dir]-[name]',
})
assets文件夹新建icons文件夹,新建svg文件夹,新建svg文件夹下新建svg文件,文件名随意
在components文件夹下新建SvgIcon文件夹,新建index.vue内容如下:
<template><svg :class="svgClass" aria-hidden="true"><use :xlink:href="iconName" :fill="color" /></svg>
</template><script>
export default defineComponent({props: {iconClass: {type: String,required: true},className: {type: String,default: ''},color: {type: String,default: ''},},setup(props) {return {iconName: computed(() => `#icon-${props.iconClass}`),svgClass: computed(() => {if (props.className) {return `svg-icon ${props.className}`}return 'svg-icon'})}}
})
</script><style scope lang="scss">
.sub-el-icon,
.nav-icon {display: inline-block;font-size: 15px;margin-right: 12px;position: relative;
}.svg-icon {width: 1em!important;height: 1em!important;position: relative;fill: currentColor;vertical-align: -2px;
}
</style>
新建svgicon.ts文件
import * as components from '@element-plus/icons-vue'export default {install: (app) => {for (const key in components) {const componentConfig = components[key];app.component(componentConfig.name, componentConfig);}},
};
应用到vue文件
<svg-icon :icon-class="item.meta && item.meta.icon" />
渲染结果是home.svg
<svg class="svg-icon" aria-hidden="true"><use xlink:href="#icon-home" fill=""></use></svg>
在main.ts引入svg-icon
import "virtual:svg-icons-register";
import SvgIcon from '@/components/SvgIcon'
import elementIcons from '@/components/SvgIcon/svgicon'
app.use(elementIcons)
app.component('svg-icon', SvgIcon)
最终vite.config.ts内容如下:
/* eslint-disable import/no-extraneous-dependencies */
import { defineConfig, loadEnv } from "vite";
import path from "path";
import vue from "@vitejs/plugin-vue";
import eslintPlugin from "vite-plugin-eslint";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
import {createStyleImportPlugin,ElementPlusResolve
} from "vite-plugin-style-import";import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
import VueSetupExtend from 'vite-plugin-vue-setup-extend'export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd());const { VITE_APP_BASE_API,VITE_SERVE,VITE_PORT } = envreturn {plugins: [vue({refTransform: true // 开启ref转换,不用.value,直接$ref}),eslintPlugin(),AutoImport({imports: ["vue", "vue-router"],dts: "src/auto-import.d.ts", // 路径下自动生成文件夹存放全局指令eslintrc: {enabled: true, // 1、改为true用于生成eslint配置。2、生成后改回false,避免重复生成消耗},resolvers: [ElementPlusResolver()]}),Components({dts: "./src/components.d.ts", // 创建ts文件extensions: ["vue"], // 指定文件的后缀dirs: ["src/components/"], // 指定路径,自动导入自定义组件resolvers: [ElementPlusResolver()], // 指定自动引入的组件库,也就是从插件中导出的那个}),// 配置自动导入element startcreateStyleImportPlugin({resolves: [ElementPlusResolve()],libs: [{libraryName: "element-plus",esModule: true,resolveStyle: (name: string) =>`element-plus/theme-chalk/${name}.css`,},],}),createSvgIconsPlugin({iconDirs: [path.resolve(process.cwd(), 'src/assets/icons/svg')],symbolId: 'icon-[dir]-[name]',}),VueSetupExtend()],base: "./", // 在生产中服务时的基本公共路径publicDir: "public", // 静态资源服务的文件夹, 默认"public"resolve: {alias: {"@": path.resolve(__dirname, "./src"), // 相对路径别名配置,使用 @ 代替 src},extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"],},// 本地运行配置server: {host: '0.0.0.0',port: VITE_PORT || 80,https: false,open: true,proxy: {[VITE_APP_BASE_API]: {target: VITE_SERVE,changeOrigin: true,rewrite: path => path.replace(RegExp(`^${VITE_APP_BASE_API}`), '')}},disableHostCheck: true},// 打包配置build: {target: "modules", // 设置最终构建的浏览器兼容目标。modules:支持原生 ES 模块的浏览器outDir: "dist", // 指定输出路径assetsDir: "assets", // 指定生成静态资源的存放路径assetsInlineLimit: "4096", // 小于此阈值的导入或引用资源将内联为base64编码,设置为0可禁用此项。默认4096(4kb)cssCodeSplit: true, // 启用/禁用CSS代码拆分,如果禁用,整个项目的所有CSS将被提取到一个CSS文件中,默认truesourcemap: false, // 构建后是否生成 source map 文件minify: "terser", // 混淆器,terser构建后文件体积更小write: true, // 设置为 false 来禁用将构建后的文件写入磁盘emptyOutDir: true, // 默认情况下,若 outDir 在 root 目录下,则 Vite 会在构建时清空该目录。chunkSizeWarningLimit: 500, // chunk 大小警告的限制terserOptions: {compress: {drop_console: true,drop_debugger: true,},}, // 去除 console debugger},};
});相关文章:
个人建站前端篇(七)vite + vue3企业级项目模板
一、vite命令行创建项目 npm create vitelatest根据提示选择模板,选择vite vue3 ts即可。 二、项目连接远程仓库 git init git remote add origin https://gitee.com/niech_project/vite-vue3-template.git git pull origin master git checkout -b dev三、项目…...
centos7 安装 docker-compose
1、直接参考官方: Install Compose standalone | Docker Docs 1、安装命令 curl -SL https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose 2、修改 docker-compose 执行权限 不修改执行权…...
剑指offer面试题28:对称的二叉树
#试题28:对称的二叉树 题目: 请设计一个函数判断一棵二叉树是否 轴对称 。 示例 1: 输入:root [6,7,7,8,9,9,8] 输出:true 解释:从图中可看出树是轴对称的。示例 2: 输入:root …...
JS:原型与原型链(附带图解与代码)
一、原型 写在前面: 任何对象都有原型。 函数也是对象,所以函数也有原型。 1.什么是原型 在 JavaScript 中,对象有一个特殊的隐藏属性 [[Prototype]],它要么为 null,要么就是对另一个对象的引用,该对象…...
电子电器架构新趋势 —— 最佳着力点:域控制器
电子电器架构新趋势 —— 最佳着力点:域控制器 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师…...
C++记录
常用快捷键: CTRL -向后定位 CTRL SHIFT -向前定位 1.注释:CTRLKC 2.取消注释:CTRLKU 11.调试(启动):F5 20.查找:CTRLF 21.替换:CTRLH 31.跳转到指定的某一行 1)方法1:组合键“CtrlG…...
ConcurrentModificationException并发修改异常
ConcurrentModificationException并发修改异常 原因分析 可以通过遍历索引也可以通过迭代器进行遍历。在我们使用迭代器进行遍历集合的时候,会获取到当前集合的迭代对象。在里面有封装了迭代器的remove方法与集合自带的remove方法,如果我们调用迭代器对…...
小程序事件处理
事件处理 一个应用仅仅只有界面展示是不够的,还需要和用户做交互,例如:响应用户的点击、获取用户输入的值等等,在小程序里边,我们就通过编写 JS 脚本文件来处理用户的操作 1. 事件绑定和事件对象 小程序中绑定事件与…...
蓝桥杯-单片机组基础6——定时计数器与外部中断混合使用(附小蜜蜂课程代码)
蓝桥杯单片机组备赛指南请查看这篇文章:戳此跳转蓝桥杯备赛指南文章 本文章针对蓝桥杯-单片机组比赛开发板所写,代码可直接在比赛开发板上使用。 型号:国信天长4T开发板(绿板),芯片:IAP15F2K6…...
交友社交软件开发-php交友聊天系统-
为了开发一个高效的交友系统,需要一个完善的信息管理和筛选机制。这个系统应该能够根据用户的个人信息、兴趣爱好、价值观等标准进行筛选,并向用户提供符合他们要求心仪的人的信息。为了实现这个目标,系统可以利用人工智能技术,分…...
vue2 开发记录
el-select 如何修改选择项的样式/el-select-dropdown__item 文字上下显示 测试代码 <div stylemargin-left: 100px><!-- 测试代码--><el-select filterablesizemini><div classxxx-el-select><el-optionv-foritem in [{key:1,des:2,…...
QML中表格中数据获取
1.在生成的动态表格中获取某格数据的内容 import QtQuick 2.15 import QtQuick.Window 2.15import QtQuick.Controls 2.0 import Qt.labs.qmlmodels 1.0 import QtQuick.Layouts 1.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")TableMod…...
【mysql 数据库事务】开启事务操作数据库,写入失败后,不回滚,会有问题么? 这里隐藏着大坑,复试,面试时可以镇住面试老师!!!!
建表字段: CREATE TABLE user (id INT(11) NOT NULL AUTO_INCREMENT,nickname VARCHAR(32) NOT NULL COLLATE utf8mb4_general_ci,email VARCHAR(32) NOT NULL COLLATE utf8mb4_general_ci,status SMALLINT(6) UNSIGNED NULL DEFAULT NULL,password VARCHAR(256) NULL DEFAULT…...
Go语言的100个错误使用场景(55-60)|并发基础
前言 大家好,这里是白泽。**《Go语言的100个错误以及如何避免》**是最近朋友推荐我阅读的书籍,我初步浏览之后,大为惊喜。就像这书中第一章的标题说到的:“Go: Simple to learn but hard to master”,整本书通过分析1…...
钉钉机器人发送折线图卡片 工具类代码
钉钉机器人 “创建并投放卡片 接口 ” 可以 发送折线图、柱状图 官方文档:创建并投放卡片 - 钉钉开放平台 0依赖、1模板、2机器人放到内部应用、3放开这个权限 、4工具类、5调用工具类 拼接入参 卡片模板 自己看文档创建,卡片模板的id 有用 0、依赖…...
基于springboot的大型商场应急预案管理系统论文
大型商场应急预案管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了大型商场应急预案管理系统的开发全过程。通过分析大型商场应急预案管理系统管理的不足,创建了一个计算机管理大型商场应急…...
强化学习嵌入Transformer(代码实践)
这里写目录标题 ChatGPT的答案GPT4.0 ChatGPT的答案 # 定义Transformer模块 class Transformer(nn.Module):def __init__(self, input_dim, hidden_dim, num_heads, num_layers):super(Transformer, self).__init__()self.encoder_layer nn.TransformerEncoderLayer(d_modeli…...
决定西弗吉尼亚州地区版图的关键历史事件
决定西弗吉尼亚州地区版图的关键历史事件: 1. 内部分裂与美国内战: - 在1861年美国内战爆发时,弗吉尼亚州作为南方邦联的一员宣布退出美利坚合众国。然而,弗吉尼亚州西部的一些县由于经济结构(主要是农业非依赖奴隶制…...
LeetCode_22_中等_括号生成
文章目录 1. 题目2. 思路及代码实现(Python)2.1 暴力法2.2 回溯法 1. 题目 数字 n n n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 示例 1: 输入: n 3 n 3 …...
Verilog(未完待续)
Verilog教程 这个教程写的很好,可以多看看。本篇还没整理完。 一、Verilog简介 什么是FPGA?一种可通过编程来修改其逻辑功能的数字集成电路(芯片) 与单片机的区别?对单片机编程并不改变其地电路的内部结构࿰…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
