diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index f8be207..0000000 --- a/.eslintignore +++ /dev/null @@ -1,9 +0,0 @@ -/build/ -/config/ -/dist/ -/*.js -/src/components/verifition -/test/unit/coverage/ -/src/icons/iconfont.js -/components.d.ts -/src/auto-import diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json deleted file mode 100644 index 2186ddf..0000000 --- a/.eslintrc-auto-import.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "globals": { - "$t": true, - "Component": true, - "ComponentPublicInstance": true, - "ComputedRef": true, - "Debounce": true, - "EffectScope": true, - "InjectionKey": true, - "PropType": true, - "Ref": true, - "VNode": true, - "clearLoginInfo": true, - "computed": true, - "configDefInfo": true, - "createApp": true, - "customRef": true, - "defineAsyncComponent": true, - "defineComponent": true, - "effectScope": true, - "encrypt": true, - "flatten": true, - "checkFileUrl": true, - "formatConfigInfo": true, - "getCurrentInstance": true, - "getCurrentScope": true, - "getLevels": true, - "getUUID": true, - "h": true, - "http": true, - "idList": true, - "inject": true, - "isAuth": true, - "isEmail": true, - "isHtmlNull": true, - "isMobile": true, - "isPhone": true, - "isProxy": true, - "isQq": true, - "isReactive": true, - "isReadonly": true, - "isRef": true, - "isURL": true, - "markRaw": true, - "nextTick": true, - "onActivated": true, - "onBeforeMount": true, - "onBeforeRouteLeave": true, - "onBeforeRouteUpdate": true, - "onBeforeUnmount": true, - "onBeforeUpdate": true, - "onDeactivated": true, - "onErrorCaptured": true, - "onMounted": true, - "onRenderTracked": true, - "onRenderTriggered": true, - "onScopeDispose": true, - "onServerPrefetch": true, - "onUnmounted": true, - "onUpdated": true, - "provide": true, - "reactive": true, - "readonly": true, - "ref": true, - "resolveComponent": true, - "shallowReactive": true, - "shallowReadonly": true, - "shallowRef": true, - "toRaw": true, - "toRef": true, - "toRefs": true, - "treeDataTranslate": true, - "triggerRef": true, - "unref": true, - "uploadFile": true, - "useAttrs": true, - "useCommonStore": true, - "scoreProdStore":true, - "useCssModule": true, - "useCssVars": true, - "useLink": true, - "useRoute": true, - "useRouter": true, - "useSlots": true, - "useUserStore": true, - "useWebConfigStore": true, - "validHtmlLength": true, - "validNoEmptySpace": true, - "watch": true, - "watchEffect": true, - "watchPostEffect": true, - "watchSyncEffect": true - } -} \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 4d5678e..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,79 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - es2021: true - }, - globals: { - BMap: 'readonly' - }, - extends: [ - 'standard', - './.eslintrc-auto-import.json', - 'plugin:vue/vue3-recommended', - 'plugin:vue-scoped-css/vue3-recommended' - ], - overrides: [], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module' - }, - plugins: ['vue'], - rules: { - // Possible Errors - // 要求使用 let 或 const 而不是 var - 'no-var': 'error', - // 强制 "for" 循环中更新子句的计数器朝着正确的方向移动 - 'for-direction': 'error', - // 强制 getter 函数中出现 return 语句 - 'getter-return': 'error', - // 禁止在嵌套的块中出现变量声明或 function 声明 - 'no-inner-declarations': 'error', - // 禁止由于 await 或 yield的使用而可能导致出现竞态条件的赋值 - 'require-atomic-updates': 'error', - // console 警告 - 'no-console': 'warn', - // 禁止出现未使用过的变量 - 'no-unused-vars': [ - 'warn', - { - args: 'all', - caughtErrors: 'none', - ignoreRestSiblings: true, - vars: 'all' - } - ], - // 关闭名称校验 - 'vue/multi-word-component-names': 'off', - // 非生产环境启用 debugger - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', - - // Best Practices - eqeqeq: 'off', - - // Stylistic Issues - // 强制可嵌套的块的最大深度 - 'max-depth': ['error', 5], - // 强制函数最大代码行数 - 'max-lines-per-function': [ - 'error', - { - max: 150, - skipBlankLines: true - } - ], - // 强制回调函数最大嵌套深度 - 'max-nested-callbacks': ['error', { max: 10 }], - // 强制函数定义中最多允许的参数数量 - 'max-params': ['error', { max: 5 }], - // 强制每一行中所允许的最大语句数量 - 'max-statements-per-line': ['error', { max: 1 }], - // 三目运算符换行 - 'multiline-ternary': ['error', 'never'], - // 传值给组件时的使用 kebab-case - 'vue/v-on-event-hyphenation': ['warn', 'always', { - autofix: true, - ignore: [] - }] - } -} diff --git a/.oxfmtrc.jsonc b/.oxfmtrc.jsonc new file mode 100644 index 0000000..d48dca4 --- /dev/null +++ b/.oxfmtrc.jsonc @@ -0,0 +1,18 @@ +{ + // 指向本地 schema,方便编辑器识别 Oxfmt 的配置字段。 + "$schema": "./node_modules/oxfmt/configuration_schema.json", + // 与现有项目代码风格保持一致:不自动补分号。 + "semi": false, + // 与既有 JS / Vue 文件风格保持一致:优先使用单引号。 + "singleQuote": true, + // 单参数箭头函数也强制保留括号,避免保存时因默认值或编辑器差异出现风格漂移。 + "arrowParens": "always", + // HTML、Vue、JSX 标签中的属性强制一行一个,和原有 ESLint 风格保持一致。 + "singleAttributePerLine": true, + // 避免保存时自动补尾逗号,减少无意义 diff。 + "trailingComma": "none", + // package.json 保持人工控制字段顺序,避免依赖区块被自动重排。 + "sortPackageJson": false, + // 忽略生成物与公开静态资源,避免格式化覆盖工具产物。 + "ignorePatterns": ["dist/**", "public/**", "src/auto-import/**", "types/auto-import/**"] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..7c28617 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,90 @@ +{ + // 指向本地 schema,方便编辑器做字段提示和校验。 + "$schema": "./node_modules/oxlint/configuration_schema.json", + // 声明当前项目默认运行在浏览器环境,并启用 ES6 全局变量集。 + "env": { + "browser": true, + "es6": true + }, + // 注册项目里会直接访问的全局变量,避免被误判成未定义。 + "globals": {}, + // 显式声明迁移期真正需要的插件集合,避免继续吃到默认插件带来的额外噪音。 + // 这里先只保留最接近原 ESLint 8 方案的 eslint 核心规则和 vue 脚本规则。 + // 注意:设置 plugins 会覆盖 Oxlint 默认插件集合,所以需要的插件必须全部写全。 + "plugins": ["eslint", "vue"], + // 统一维护不参与 lint 的目录,减少本地与 CI 结果偏差。 + "ignorePatterns": [ + "build/**", + "config/**", + "dist/**", + "public/**", + "test/unit/coverage/**", + "src/icons/iconfont.js", + "src/auto-import/**", + "types/auto-import/**" + ], + // 先启用最有价值的规则分类,避免一次性引入过多历史噪音。 + "categories": { + "correctness": "error", + "suspicious": "warn" + }, + // 这里放项目级基础规则,优先覆盖原先 ESLint 中保留下来的核心校验。 + // 迁移初期先追求和旧项目体感接近,再逐步补回更严格的规则。 + "rules": { + // 禁止随手输出调试日志,但保留 console.error 作为异常排查和兜底告警的输出通道。 + "no-console": [ + "warn", + { + "allow": ["error"] + } + ], + // 允许在本地联调阶段临时使用 debugger,避免影响现有排查流程。 + "no-debugger": "off", + // 禁止继续使用 var,统一收敛到 let / const,减少变量提升带来的作用域问题。 + "no-var": "error", + // 防止 for 循环的自增自减方向写反,避免出现死循环或根本不执行的情况。 + "for-direction": "error", + // 要求 getter 必须显式返回值,避免读取属性时得到难以排查的 undefined。 + "getter-return": "error", + // 禁止在块级作用域中声明易产生歧义的函数或变量,降低兼容性风险。 + "no-inner-declarations": "error", + // 避免异步场景对同一变量的读写互相覆盖,减少竞态条件问题。 + "require-atomic-updates": "error", + // 旧项目里这类变量名遮蔽并没有被大量提示,迁移期先关闭避免噪音过高。 + "no-shadow": "off", + // 兼容现有代码里常见的“条件成立才执行”写法,避免把短路表达式都判成错误。 + "no-unused-expressions": [ + "error", + { + "allowShortCircuit": true, + "allowTernary": true + } + ], + // 旧项目并没有强制限制字符串拼接,这里先关闭避免产生额外迁移噪音。 + "no-useless-concat": "off" + }, + // 按文件类型细分规则,降低 JS 与 Vue 场景下的误报。 + "overrides": [ + { + // JS 文件继续保留较宽松的未使用变量策略,兼容现有代码风格。 + "files": ["src/**/*.js"], + "rules": { + "no-unused-vars": [ + "warn", + { + // 继续检查函数参数,帮助发现历史遗留的无效入参。 + "args": "all", + // 不强制要求 catch 参数必须被使用,兼容现有异常捕获写法。 + "caughtErrors": "none", + // 允许对象剩余解构时的兄弟字段暂时未使用,减少常见场景下的误报。 + "ignoreRestSiblings": true, + // 对局部变量仍然给出提示,便于逐步清理废弃状态和冗余逻辑。 + "vars": "all", + // 保留项目里常见的占位参数命名,避免批量改名造成无意义 diff。 + "argsIgnorePattern": "^[rule, value, i, to, from, binding]" + } + ] + } + } + ] +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..c20dfbc --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,18 @@ +{ + // 仅为编辑器与 Volar 提供路径别名和自动生成声明文件的索引能力。 + "compilerOptions": { + // 对齐 Vite 的 @ 别名,避免业务源码和自动生成声明文件中的路径在编辑器里报错。 + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + // 当前项目以 JavaScript 为主,不启用严格的 TS 检查。 + "allowJs": true, + "checkJs": false, + "skipLibCheck": true + }, + // 排除依赖目录与构建产物,减少编辑器项目分析开销。 + "exclude": ["node_modules", "dist"], + // 同时索引业务源码与自动生成声明文件,和 b2c 项目保持一致。 + "include": ["src/**/*.js", "src/**/*.vue", "types/**/*.d.ts"] +} diff --git a/package.json b/package.json index 3e58a5f..5d5b710 100644 --- a/package.json +++ b/package.json @@ -9,57 +9,59 @@ "dev:test": "vite --mode testing", "build": "vite build", "build:test": "vite build --mode testing", - "lint": "eslint --ext .js,.vue src", - "lint:fix": "eslint --fix --ext .js,.vue src", + "lint": "oxlint src", + "lint:fix": "oxlint --fix src", + "fmt": "oxfmt .", + "fmt:check": "oxfmt --check .", "preview": "vite preview", "lint:staged": "lint-staged" }, - "engines": { - "node": ">=16", - "pnpm": ">=6" - }, "dependencies": { - "@element-plus/icons-vue": "2.1.0", + "@element-plus/icons-vue": "2.3.1", "@smallwei/avue": "3.2.22", - "axios": "1.3.4", - "big.js": "6.2.1", - "crypto-js": "4.1.1", - "echarts": "5.4.1", - "element-plus": "2.3.6", + "axios": "1.7.9", + "big.js": "6.2.2", + "crypto-js": "4.2.0", + "echarts": "5.6.0", + "element-plus": "2.9.3", "element-resize-detector": "1.2.4", "js-base64": "3.7.5", "lodash": "4.17.21", - "moment": "2.29.4", - "pinia": "2.0.33", - "qs": "6.11.1", - "vue": "3.2.47", - "vue-cookies": "1.8.3", - "vue-draggable-next": "2.1.1", - "vue-router": "4.1.6" + "moment": "2.30.1", + "pinia": "2.3.0", + "qs": "6.14.0", + "vue": "3.5.13", + "vue-cookies": "1.8.5", + "vue-draggable-next": "2.2.1", + "vue-router": "4.5.0" }, "devDependencies": { - "@babel/eslint-parser": "7.21.3", - "@vitejs/plugin-vue": "4.1.0", - "eslint": "8.38.0", - "eslint-config-standard": "17.0.0", - "eslint-plugin-import": "2.27.5", - "eslint-plugin-n": "15.7.0", - "eslint-plugin-promise": "6.1.1", - "eslint-plugin-vue": "9.10.0", - "eslint-plugin-vue-scoped-css": "2.4.0", - "lint-staged": "13.2.2", - "sass": "1.59.3", - "unplugin-auto-import": "0.15.1", - "unplugin-vue-components": "0.24.1", - "vite": "4.3.9", - "vite-plugin-compression": "0.5.1", - "vite-plugin-eslint": "1.8.1", - "vite-plugin-svg-icons": "2.0.1", - "vue-eslint-parser": "9.1.1" + "@vitejs/plugin-vue": "6.0.5", + "lint-staged": "15.3.0", + "oxfmt": "0.41.0", + "oxlint": "1.56.0", + "sass": "1.83.4", + "sortablejs": "1.15.6", + "unplugin-auto-import": "19.0.0", + "unplugin-vue-components": "28.0.0", + "vite": "8.0.1", + "vite-plugin-compression2": "2.5.3", + "vite-plugin-svg-icons": "2.0.1" }, "lint-staged": { - "*.{js,vue}": [ - "eslint --fix" + "src/**/*.{js,vue}": [ + "oxlint --fix", + "oxfmt" + ], + "src/**/*.{css,scss}": [ + "oxfmt" + ], + "*.{json,md,html,yml,yaml}": [ + "oxfmt" ] + }, + "engines": { + "node": "^20.19.0 || >=22.12.0", + "pnpm": ">=7" } } diff --git a/src/assets/scss/_base.scss b/src/assets/scss/_base.scss index f2dde4d..136fa59 100644 --- a/src/assets/scss/_base.scss +++ b/src/assets/scss/_base.scss @@ -1,3 +1,6 @@ +@use "sass:color"; +@use "./variables" as *; + *, *:before, *:after { @@ -11,11 +14,11 @@ body { background-color: #fff; } a { - color: mix(#fff, $--color-primary, 20%); + color: color.mix(#fff, $color-primary, 20%); text-decoration: none; &:focus, &:hover { - color: $--color-primary; + color: $color-primary; text-decoration: underline; } } @@ -132,7 +135,7 @@ img { z-index: 1030; height: 50px; box-shadow: 0 2px 4px rgba(0, 0, 0, .08); - background-color: $navbar--background-color; + background-color: $navbar-background-color; &--inverse { .site-navbar__body { @@ -145,12 +148,12 @@ img { &:focus, &:hover { color: #fff; - background-color: mix(#000, $navbar--background-color, 15%); + background-color: color.mix(#000, $navbar-background-color, 15%); } } > .el-menu-item.is-active, > .el-submenu.is-active > .el-submenu__title { - border-bottom-color: mix(#fff, $navbar--background-color, 85%); + border-bottom-color: color.mix(#fff, $navbar-background-color, 85%); } .el-menu-item i, .el-submenu__title i, @@ -159,7 +162,7 @@ img { } } .el-menu--popup-bottom-start { - background-color: $navbar--background-color; + background-color: $navbar-background-color; } } @@ -266,26 +269,26 @@ img { &--dark, &--dark-popper { - background-color: $sidebar--background-color-dark; + background-color: $sidebar-background-color-dark; .site-sidebar__menu.el-menu, > .el-menu--popup { - background-color: $sidebar--background-color-dark; + background-color: $sidebar-background-color-dark; .el-menu-item, .el-submenu > .el-submenu__title { - color: $sidebar--color-text-dark; + color: $sidebar-color-text-dark; &:focus, &:hover { - color: mix(#fff, $sidebar--color-text-dark, 50%); - background-color: mix(#fff, $sidebar--background-color-dark, 2.5%); + color: color.mix(#fff, $sidebar-color-text-dark, 50%); + background-color: color.mix(#fff, $sidebar-background-color-dark, 2.5%); } } .el-menu, .el-submenu.is-opened { - background-color: mix(#000, $sidebar--background-color-dark, 15%); + background-color: color.mix(#000, $sidebar-background-color-dark, 15%); } .el-menu-item.is-active, .el-submenu.is-active > .el-submenu__title { - color: mix(#fff, $sidebar--color-text-dark, 80%); + color: color.mix(#fff, $sidebar-color-text-dark, 80%); } } } @@ -322,10 +325,7 @@ img { padding-top: 50px; margin-left: 230px; min-height: 100%; - background: $content--background-color; - } - &--tabs { - padding: 55px 0 0; + background: $content-background-color; } > .el-tabs { > .el-tabs__header { @@ -355,7 +355,7 @@ img { padding: 0 12px; font-size: 16px; line-height: 40px; - background-color: $content--background-color; + background-color: $content-background-color; cursor: pointer; .el-icon--right { margin-left: 0; diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss index b081e04..455f823 100644 --- a/src/assets/scss/_variables.scss +++ b/src/assets/scss/_variables.scss @@ -1,13 +1,13 @@ // 站点主色 -// tips: 要达到整站主题修改效果, 请确保[$--color-primary]站点主色与[/src/element-ui-theme/index.js]文件中[import './element-[#17B3A3]/index.css']当前主题色一致 -$--color-primary: #02A1E9; +// tips: 要达到整站主题修改效果, 请确保[$color-primary]站点主色与[/src/element-ui-theme/index.js]文件中[import './element-[#17B3A3]/index.css']当前主题色一致 +$color-primary: #02A1E9; // Navbar -$navbar--background-color: $--color-primary; +$navbar-background-color: $color-primary; // Sidebar -$sidebar--background-color-dark: #263238; -$sidebar--color-text-dark: #8a979e; +$sidebar-background-color-dark: #263238; +$sidebar-color-text-dark: #8a979e; // Content -$content--background-color: #f1f4f5; +$content-background-color: #f1f4f5; diff --git a/src/assets/scss/index.scss b/src/assets/scss/index.scss index c17d72c..a43982f 100644 --- a/src/assets/scss/index.scss +++ b/src/assets/scss/index.scss @@ -1,3 +1,2 @@ -@import "normalize"; // api: https://github.com/necolas/normalize.css/ -@import "variables"; // 站点变量 -@import "base"; +@use "./normalize"; // api: https://github.com/necolas/normalize.css/ +@use "./base"; diff --git a/src/main.js b/src/main.js index 7ec1935..c37b708 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,7 @@ import moment from 'moment' import 'element-plus/dist/index.css' import * as ElementPlusIconsVue from '@element-plus/icons-vue' import router from '@/router' -import locale from 'element-plus/lib/locale/lang/zh-cn' +import locale from 'element-plus/es/locale/lang/zh-cn' import Avue from '@smallwei/avue' import '@smallwei/avue/lib/index.css' diff --git a/src/router/index.js b/src/router/index.js index 013bf38..aea0687 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -24,7 +24,6 @@ const globalRoutes = [ export const mainRoutes = { path: '/', component: Layout, - name: 'home', redirect: '/home', children: [ { diff --git a/src/styles/_base.scss b/src/styles/_base.scss index 088920b..d839bfd 100644 --- a/src/styles/_base.scss +++ b/src/styles/_base.scss @@ -1,3 +1,6 @@ +@use "sass:color"; +@use "./variables" as *; + *, *:before, *:after { @@ -11,11 +14,11 @@ body { background-color: #fff; } a { - color: mix(#fff, $--color-primary, 20%); + color: color.mix(#fff, $color-primary, 20%); text-decoration: none; &:focus, &:hover { - color: $--color-primary; + color: $color-primary; text-decoration: underline; } } @@ -108,7 +111,7 @@ img { z-index: 1030; height: 50px; // box-shadow: 0 2px 4px rgba(0, 0, 0, .08); - background-color: $navbar--background-color; + background-color: $navbar-background-color; &__header { position: relative; @@ -199,7 +202,7 @@ img { &--dark, &--dark-popper { - background-color: $sidebar--background-color-dark; + background-color: $sidebar-background-color-dark; } &__inner { position: relative; @@ -235,10 +238,7 @@ img { padding-top: 50px; margin-left: 230px; min-height: 100%; - background: $content--background-color; - } - &--tabs { - padding: 55px 0 0; + background: $content-background-color; } } @@ -514,4 +514,4 @@ img { } .time-select-item { text-align: center; -} \ No newline at end of file +} diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index 4518fa2..9739643 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -1,14 +1,14 @@ // css 变量 -$--color-primary: #155BD4; +$color-primary: #155BD4; // Navbar -$navbar--background-color: $--color-primary; +$navbar-background-color: $color-primary; // Sidebar -$sidebar--background-color-dark: #263238; -$sidebar--color-text-dark: #8a979e; +$sidebar-background-color-dark: #263238; +$sidebar-color-text-dark: #8a979e; // Content -$content--background-color: #f1f4f5; +$content-background-color: #f1f4f5; diff --git a/src/styles/index.scss b/src/styles/index.scss index 526a674..a8b30b7 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -1,7 +1,6 @@ -@import "_normalize"; // api: https://github.com/necolas/normalize.css/ -@import "_variables"; -@import "_base"; -@import "@/styles/element-variables.scss"; +@use "./normalize"; // api: https://github.com/necolas/normalize.css/ +@use "./base"; +@use "./element-variables"; .element-error-message-zindex { z-index:3000 !important; diff --git a/src/views/modules/order/order/index.vue b/src/views/modules/order/order/index.vue index 49889d7..6826c82 100644 --- a/src/views/modules/order/order/index.vue +++ b/src/views/modules/order/order/index.vue @@ -555,5 +555,8 @@ const getSoldExcel = () => { margin: 50px 0; color: #999; } + .el-select { + width: 200px + } } diff --git a/src/views/modules/prod/prodTag/add-or-update.vue b/src/views/modules/prod/prodTag/add-or-update.vue index 0c01405..e150923 100644 --- a/src/views/modules/prod/prodTag/add-or-update.vue +++ b/src/views/modules/prod/prodTag/add-or-update.vue @@ -92,7 +92,7 @@ const dataForm = ref({ status: 1, isDefault: null, prodCount: null, - seq: null, + seq: 0, style: 0 }) @@ -120,6 +120,9 @@ const dataFormRef = ref(null) * 表单提交 */ const onSubmit = Debounce(() => { + if (!dataForm.value.seq) { + dataForm.value.seq = 0 + } dataFormRef.value?.validate((valid) => { if (valid) { http({ diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 4709f16..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "sourceMap": true, - "skipLibCheck": true - }, - "exclude": [ - "node_modules" - ], - "include": [ - "src/auto-import/components.d.ts", - "src/auto-import/imports.d.ts" - ] -} \ No newline at end of file diff --git a/types/auto-import/imports.d.ts b/types/auto-import/imports.d.ts new file mode 100644 index 0000000..1c17451 --- /dev/null +++ b/types/auto-import/imports.d.ts @@ -0,0 +1,96 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + const Debounce: typeof import('../../src/utils/debounce.js')['Debounce'] + const EffectScope: typeof import('vue')['EffectScope'] + const checkFileUrl: typeof import('../../src/utils/index.js')['checkFileUrl'] + const clearLoginInfo: typeof import('../../src/utils/index.js')['clearLoginInfo'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const effectScope: typeof import('vue')['effectScope'] + const encrypt: typeof import('../../src/utils/crypto.js')['encrypt'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const getUUID: typeof import('../../src/utils/index.js')['getUUID'] + const h: typeof import('vue')['h'] + const http: typeof import('../../src/utils/http.js')['default'] + const idList: typeof import('../../src/utils/index.js')['idList'] + const inject: typeof import('vue')['inject'] + const isAuth: typeof import('../../src/utils/index.js')['isAuth'] + const isEmail: typeof import('../../src/utils/validate.js')['isEmail'] + const isMobile: typeof import('../../src/utils/validate.js')['isMobile'] + const isPhone: typeof import('../../src/utils/validate.js')['isPhone'] + const isProxy: typeof import('vue')['isProxy'] + const isQq: typeof import('../../src/utils/validate.js')['isQq'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const isURL: typeof import('../../src/utils/validate.js')['isURL'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const scoreProdStore: typeof import('../../src/stores/prod.js')['scoreProdStore'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const treeDataTranslate: typeof import('../../src/utils/index.js')['treeDataTranslate'] + const triggerRef: typeof import('vue')['triggerRef'] + const unitOption: typeof import('../../src/utils/unitOption.js')['unitOption'] + const unref: typeof import('vue')['unref'] + const uploadFile: typeof import('../../src/utils/http.js')['uploadFile'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCommonStore: typeof import('../../src/stores/common.js')['useCommonStore'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useLink: typeof import('vue-router')['useLink'] + const useModel: typeof import('vue')['useModel'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const useUserStore: typeof import('../../src/stores/user.js')['useUserStore'] + const validNoEmptySpace: typeof import('../../src/utils/validate.js')['validNoEmptySpace'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] +} +// for type re-export +declare global { + // @ts-ignore + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + import('vue') +} diff --git a/vite.config.js b/vite.config.js index e219ab1..a1b4cc4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,57 +1,118 @@ -import { defineConfig, loadEnv } from 'vite' +import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' -import viteCompression from 'vite-plugin-compression' +import { compression } from 'vite-plugin-compression2' -// eslint -import eslintPlugin from 'vite-plugin-eslint' +// 开发态预扫描全部业务源码入口,尽量在冷启动阶段就发现懒路由里的第三方依赖。 +const optimizeDepEntries = [ + 'index.html', + 'src/main.js', + 'src/**/*.js', + 'src/**/*.vue', + '!dist/**', + '!types/**' +] +// 显式预构建插件动态注入的深层依赖,避免首次访问新页面时才触发依赖重优化并整页刷新。 +const optimizeDepIncludes = [ + 'element-plus/es/components/**', + 'element-plus/es/components/**/style/css' +] +// 自动组件扫描时,排除仅供容器组件内部手动引用的私有子组件目录。 +const componentGlobs = [ + 'src/components/**/*.vue', + '!src/components/decorate-dialog-select/components/**/*.vue' +] +// 预先记录 Element Plus 图标组件名,供自动按需解析使用。 +const elementPlusIconNames = new Set(Object.keys(ElementPlusIconsVue)) + +/** + * 解析第三方依赖的分包名称 + * @param {String} moduleId 模块路径 + * @returns {String|null} 分包名称 + */ +function resolveVendorChunkName(moduleId) { + // 统一路径分隔符,避免 Windows 反斜杠路径导致分包命名逻辑失效。 + const normalizedModuleId = moduleId.replace(/\\/g, '/') + + // 只对第三方依赖做分包命名,业务代码继续走 Vite 默认拆分策略。 + if (!normalizedModuleId.includes('node_modules')) { + return null + } + + // 兼容 pnpm 的物理目录结构,保持历史产物命名规则稳定。 + if (normalizedModuleId.includes('.pnpm/')) { + return normalizedModuleId.split('.pnpm/')[1].split('/')[0].toString() + } + + // 兼容普通 node_modules 目录结构,避免不同包管理方式下 chunk 名称漂移。 + if (normalizedModuleId.includes('node_modules/')) { + return normalizedModuleId.split('node_modules/')[1].split('/')[0].toString() + } + + return 'vendor' +} + +/** + * 解析 Element Plus 图标组件 + * @param {String} componentName 组件名称 + * @returns {{name: String, from: String}|null} 自动导入配置 + */ +function resolveElementPlusIcon(componentName) { + // 只有命中 Element Plus 图标导出名时,才按需导入对应图标组件。 + if (elementPlusIconNames.has(componentName)) { + return { + name: componentName, + from: '@element-plus/icons-vue' + } + } + + return null +} // https://vitejs.dev/config/ -export default defineConfig(({ command })=> { +export default defineConfig(() => { return { + optimizeDeps: { + // 扩大开发态依赖扫描范围,提前覆盖懒加载页面里的直接依赖导入。 + entries: optimizeDepEntries, + // 显式纳入插件运行时生成的 Element Plus 深层导入,避免访问新路由时补优化触发 reload。 + include: optimizeDepIncludes + }, plugins: [ vue(), createSvgIconsPlugin({ iconDirs: [path.resolve(process.cwd(), 'src/icons/svg')], symbolId: 'icon-[dir]-[name]' }), - // 自动引入内容 + // 自动引入组合式 API、路由 API 以及 Element Plus 指令/方法。 AutoImport({ - imports: [ - 'vue', - 'vue-router' - ], - dirs: [ - 'src/hooks/**', - 'src/stores/**', - 'src/utils/**' - ], - resolvers: command === 'build' ? [ElementPlusResolver()] : [], - dts: 'src/auto-import/imports.d.ts', - eslintrc: { - enabled: false - } + imports: ['vue', 'vue-router'], + dirs: ['src/hooks/**', 'src/stores/**', 'src/utils/**'], + // 开发态和生产态统一走同一套 Element Plus 解析链路,减少两边行为差异。 + resolvers: [ElementPlusResolver()], + // 将自动生成的声明文件移出 src,避免开发态写入声明时触发页面整刷。 + dts: 'types/auto-import/imports.d.ts' }), - // 自动引入组件 + // 自动引入业务组件与 Element Plus 组件。 Components({ - dirs: [ - 'src/components' - ], - resolvers: command === 'build' ? [ElementPlusResolver()] : [], - dts: 'src/auto-import/components.d.ts' + // 使用精确 globs 控制自动注册范围,避免内部私有子组件参与全局组件命名。 + globs: componentGlobs, + // 同时补齐 Element Plus 图标的按需注册,替代 main.js 里的整包全局注册。 + resolvers: [resolveElementPlusIcon, ElementPlusResolver()], + // 将自动生成的组件声明移出 src,减少首次访问新页面时的全量刷新概率。 + dts: 'types/auto-import/components.d.ts' }), - // 对大于 1k 的文件进行压缩 - viteCompression({ - threshold: 1000, + // 对大于 1 KB 的静态资源生成 gzip 预压缩文件。 + compression({ + threshold: 1024, + algorithms: ['gzip'] }) - ].concat( - // eslint - command !== 'build' ? [eslintPlugin({ include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue'] })] : [] - ), + ], server: { host: true, port: 9527, @@ -64,27 +125,32 @@ export default defineConfig(({ command })=> { } }, build: { - base: './', - rollupOptions: { - // 静态资源分类打包 + rolldownOptions: { + // 静态资源分类打包。 output: { chunkFileNames: 'static/js/[name]-[hash].js', entryFileNames: 'static/js/[name]-[hash].js', assetFileNames: 'static/[ext]/[name]-[hash].[ext]', - // 静态资源分拆打包 - manualChunks (id) { - if (id.includes('node_modules')) { - if (id.toString().indexOf('.pnpm/') !== -1) { - return id.toString().split('.pnpm/')[1].split('/')[0].toString(); - } else if (id.toString().indexOf('node_modules/') !== -1) { - return id.toString().split('node_modules/')[1].split('/')[0].toString(); + // 使用 Rolldown 的 codeSplitting 配置替代旧链路中的 manualChunks 写法。 + codeSplitting: { + groups: [ + { + // 动态计算第三方依赖的 chunk 名称,延续原有按包拆分的缓存策略。 + name(moduleId) { + return resolveVendorChunkName(moduleId) + }, + // 仅捕获第三方依赖模块,避免业务代码被强行归并到 vendor chunk。 + test: /node_modules[\\/]/ } - } + ] } } }, sourcemap: false, - target: 'es2015', + // 使用 Vite 8 默认的 Oxc 压缩链路,避免引入额外压缩依赖。 + minify: 'oxc', + // 保留对超大共享库的提醒,同时减少后台项目常规 chunk 的误报。 + chunkSizeWarningLimit: 700, reportCompressedSize: false } }