当前位置: 首页 > news >正文

个人建站前端篇(七)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校验和自动格式化

  1. eslint 运行代码前就可以发现一些语法错误和潜在bug,保证代码一致性。
  2. prettier 是代码格式化工具,用于检查代码中的格式问题。
  3. 区别联系:eslint保证代码质量,prettier保证代码风格,eslint有小部分格式化功能,通常和prettier结合使用。
1. 安装eslint和prettier
  1. eslint: ESLint的核心代码库
  2. prettier:prettier格式化代码的核心库
  3. eslint-config-airbnb-base: airbnb的代码规范 (依赖plugin-import)
  4. eslint-config-prettier:eslint结合prettier的格式化
  5. eslint-plugin-vue:eslint在vue里的代码规范
  6. eslint-plugin-import:项目里面支持eslint
  7. 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
  1. 在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
  1. 修改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. 修改添加常见配置

项目根目录创建以下配置文件

  1. .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
  1. .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
}
  1. .prettierignore 忽略格式化文件
# prettierignore
/dist/*
.local
.output.js
/node_modules/**
src/.DS_Store**/*.svg
**/*.sh
/public/*
components.d.ts

在package.json添加格式化命令

  1. “lint”: “eslint “src/**/*.{js,ts,vue}” --fix”, 既可以检查又可以修复部分语法问题
  2. “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给我们自动引入
:::

  1. 安装依赖
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
  1. 在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},};
});
  1. 在src目录下自动生成auto-import.d.ts文件,用于存放全局指令

  2. 和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",// 不允许一元操作符++、--}
}
  1. tsconfig.json文件中添加如下配置:

include加入"src/auto-imports.d.ts"

  1. 安装 element-plus
npm install element-plus -S

在.eslintrc-auto-import.json文件中添加需要引入的组件:
“ElMessage”: true

  1. 安装saas
npm install sass -D

七、添加路由

  1. pnpm install vue-router 安装路由依赖;
  2. 在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' }//     }//   ]// }
]
  1. 在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

  1. 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>
  1. 在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>
  1. 在components文件夹下新建index.ts文件,内容如下:
export { default as AppMain } from './AppMain'
export { default as NavBar } from './NavBar'
export { default as SideBar } from './SideBar'
  1. 在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)

  1. 安装依赖pnpm install axios --save
  2. 新建api文件夹,utils文件夹,及创建request.ts文件

十、pinia状态管理器

  1. 安装依赖:pnpm install pinia --save
  2. 在src目录下新建文件夹store,在store文件下新建文件index.ts和modules文件夹
  3. 在index.ts文件中引入pinia,创建pinia实例,挂载到根组件上
  4. 在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根据提示选择模板&#xff0c;选择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、直接参考官方&#xff1a; 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&#xff1a;对称的二叉树 题目&#xff1a; 请设计一个函数判断一棵二叉树是否 轴对称 。 示例 1&#xff1a; 输入&#xff1a;root [6,7,7,8,9,9,8] 输出&#xff1a;true 解释&#xff1a;从图中可看出树是轴对称的。示例 2&#xff1a; 输入&#xff1a;root …...

JS:原型与原型链(附带图解与代码)

一、原型 写在前面&#xff1a; 任何对象都有原型。 函数也是对象&#xff0c;所以函数也有原型。 1.什么是原型 在 JavaScript 中&#xff0c;对象有一个特殊的隐藏属性 [[Prototype]]&#xff0c;它要么为 null&#xff0c;要么就是对另一个对象的引用&#xff0c;该对象…...

电子电器架构新趋势 —— 最佳着力点:域控制器

电子电器架构新趋势 —— 最佳着力点&#xff1a;域控制器 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师&#xff08;Wechat&#xff1a;gongkenan2013&#xff09;。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师…...

C++记录

常用快捷键&#xff1a; CTRL -向后定位 CTRL SHIFT -向前定位 1.注释&#xff1a;CTRLKC 2.取消注释&#xff1a;CTRLKU 11.调试(启动)&#xff1a;F5 20.查找&#xff1a;CTRLF 21.替换&#xff1a;CTRLH 31.跳转到指定的某一行 1)方法1&#xff1a;组合键“CtrlG…...

ConcurrentModificationException并发修改异常

ConcurrentModificationException并发修改异常 原因分析 可以通过遍历索引也可以通过迭代器进行遍历。在我们使用迭代器进行遍历集合的时候&#xff0c;会获取到当前集合的迭代对象。在里面有封装了迭代器的remove方法与集合自带的remove方法&#xff0c;如果我们调用迭代器对…...

小程序事件处理

事件处理 一个应用仅仅只有界面展示是不够的&#xff0c;还需要和用户做交互&#xff0c;例如&#xff1a;响应用户的点击、获取用户输入的值等等&#xff0c;在小程序里边&#xff0c;我们就通过编写 JS 脚本文件来处理用户的操作 1. 事件绑定和事件对象 小程序中绑定事件与…...

蓝桥杯-单片机组基础6——定时计数器与外部中断混合使用(附小蜜蜂课程代码)

蓝桥杯单片机组备赛指南请查看这篇文章&#xff1a;戳此跳转蓝桥杯备赛指南文章 本文章针对蓝桥杯-单片机组比赛开发板所写&#xff0c;代码可直接在比赛开发板上使用。 型号&#xff1a;国信天长4T开发板&#xff08;绿板&#xff09;&#xff0c;芯片&#xff1a;IAP15F2K6…...

交友社交软件开发-php交友聊天系统-

为了开发一个高效的交友系统&#xff0c;需要一个完善的信息管理和筛选机制。这个系统应该能够根据用户的个人信息、兴趣爱好、价值观等标准进行筛选&#xff0c;并向用户提供符合他们要求心仪的人的信息。为了实现这个目标&#xff0c;系统可以利用人工智能技术&#xff0c;分…...

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)|并发基础

前言 大家好&#xff0c;这里是白泽。**《Go语言的100个错误以及如何避免》**是最近朋友推荐我阅读的书籍&#xff0c;我初步浏览之后&#xff0c;大为惊喜。就像这书中第一章的标题说到的&#xff1a;“Go: Simple to learn but hard to master”&#xff0c;整本书通过分析1…...

钉钉机器人发送折线图卡片 工具类代码

钉钉机器人 “创建并投放卡片 接口 ” 可以 发送折线图、柱状图 官方文档&#xff1a;创建并投放卡片 - 钉钉开放平台 0依赖、1模板、2机器人放到内部应用、3放开这个权限 、4工具类、5调用工具类 拼接入参 卡片模板 自己看文档创建&#xff0c;卡片模板的id 有用 0、依赖…...

基于springboot的大型商场应急预案管理系统论文

大型商场应急预案管理系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了大型商场应急预案管理系统的开发全过程。通过分析大型商场应急预案管理系统管理的不足&#xff0c;创建了一个计算机管理大型商场应急…...

强化学习嵌入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…...

决定西弗吉尼亚州地区版图的关键历史事件

决定西弗吉尼亚州地区版图的关键历史事件&#xff1a; 1. 内部分裂与美国内战&#xff1a; - 在1861年美国内战爆发时&#xff0c;弗吉尼亚州作为南方邦联的一员宣布退出美利坚合众国。然而&#xff0c;弗吉尼亚州西部的一些县由于经济结构&#xff08;主要是农业非依赖奴隶制…...

LeetCode_22_中等_括号生成

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 暴力法2.2 回溯法 1. 题目 数字 n n n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a; n 3 n 3 …...

Verilog(未完待续)

Verilog教程 这个教程写的很好&#xff0c;可以多看看。本篇还没整理完。 一、Verilog简介 什么是FPGA&#xff1f;一种可通过编程来修改其逻辑功能的数字集成电路&#xff08;芯片&#xff09; 与单片机的区别&#xff1f;对单片机编程并不改变其地电路的内部结构&#xff0…...

Vim 调用外部命令学习笔记

Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

ssc377d修改flash分区大小

1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...

HTML前端开发:JavaScript 获取元素方法详解

作为前端开发者&#xff0c;高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法&#xff0c;分为两大系列&#xff1a; 一、getElementBy... 系列 传统方法&#xff0c;直接通过 DOM 接口访问&#xff0c;返回动态集合&#xff08;元素变化会实时更新&#xff09;。…...

EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势

一、WebRTC与智能硬件整合趋势​ 随着物联网和实时通信需求的爆发式增长&#xff0c;WebRTC作为开源实时通信技术&#xff0c;为浏览器与移动应用提供免插件的音视频通信能力&#xff0c;在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能&#xff0c;对实时…...

统计学(第8版)——统计抽样学习笔记(考试用)

一、统计抽样的核心内容与问题 研究内容 从总体中科学抽取样本的方法利用样本数据推断总体特征&#xff08;均值、比率、总量&#xff09;控制抽样误差与非抽样误差 解决的核心问题 在成本约束下&#xff0c;用少量样本准确推断总体特征量化估计结果的可靠性&#xff08;置…...

PLC入门【4】基本指令2(SET RST)

04 基本指令2 PLC编程第四课基本指令(2) 1、运用上接课所学的基本指令完成个简单的实例编程。 2、学习SET--置位指令 3、RST--复位指令 打开软件(FX-TRN-BEG-C)&#xff0c;从 文件 - 主画面&#xff0c;“B: 让我们学习基本的”- “B-3.控制优先程序”。 点击“梯形图编辑”…...