Explorar o código

feat(认知任务): init

JutarryWu hai 6 meses
pai
achega
b3bed8092b
Modificáronse 100 ficheiros con 2251 adicións e 1716 borrados
  1. 43 0
      .editorconfig
  2. 14 0
      .env
  3. 31 0
      .env.development
  4. 28 0
      .env.production
  5. 0 16
      .eslintignore
  6. 0 87
      .eslintrc-auto-import.json
  7. 0 82
      .eslintrc.js
  8. 4 5
      .gitignore
  9. 0 4
      .husky/commit-msg
  10. 0 4
      .husky/pre-commit
  11. 0 9
      .prettierignore
  12. 0 53
      .prettierrc.js
  13. 1 1
      LICENSE
  14. 286 45
      README.md
  15. 0 252
      auto-imports.d.ts
  16. 5 2
      build/constant.ts
  17. 0 4
      build/generate/generateModifyVars.ts
  18. 8 2
      build/getConfigFileName.ts
  19. 19 16
      build/script/buildConf.ts
  20. 8 7
      build/script/postBuild.ts
  21. 31 34
      build/utils.ts
  22. 0 51
      build/vite/plugin/autoImport.ts
  23. 0 14
      build/vite/plugin/autoVconsole.ts
  24. 0 27
      build/vite/plugin/componentResolver.ts
  25. 35 0
      build/vite/plugin/compress.ts
  26. 0 8
      build/vite/plugin/compression.ts
  27. 13 7
      build/vite/plugin/html.ts
  28. 61 25
      build/vite/plugin/index.ts
  29. 19 0
      build/vite/plugin/mock.ts
  30. 19 0
      build/vite/plugin/svgSprite.ts
  31. 0 8
      build/vite/plugin/unocss.ts
  32. 0 18
      build/vite/plugin/unpluginIcons.ts
  33. 18 0
      build/vite/plugin/visualizer.ts
  34. 0 16
      build/vite/plugin/viteMockServe.ts
  35. 23 5
      build/vite/proxy.ts
  36. 155 0
      commitlint.config.cjs
  37. 0 13
      commitlint.config.js
  38. 11 14
      components.d.ts
  39. 0 5
      env/.env
  40. 0 23
      env/.env.development
  41. 0 26
      env/.env.production
  42. 0 0
      env/.env.staging
  43. 61 0
      eslint.config.js
  44. 120 8
      index.html
  45. 18 0
      mock/_createProductionServer.ts
  46. 77 0
      mock/_util.ts
  47. 0 8
      mock/index.ts
  48. 0 36
      mock/modules/example.ts
  49. 0 45
      mock/modules/user.ts
  50. 95 0
      mock/user/user.ts
  51. 90 89
      package.json
  52. 518 425
      pnpm-lock.yaml
  53. 50 72
      postcss.config.js
  54. BIN=BIN
      public/favicon.ico
  55. 38 0
      public/logo.svg
  56. 71 20
      src/App.vue
  57. 0 12
      src/api/example/hooks.ts
  58. 0 6
      src/api/example/model/hooks.model.ts
  59. 0 9
      src/api/model/base.model.ts
  60. 0 22
      src/api/system/model/user.model.ts
  61. 44 12
      src/api/system/user.ts
  62. 0 1
      src/assets/icons/about/box.svg
  63. 0 5
      src/assets/icons/about/car.svg
  64. 0 3
      src/assets/icons/about/logout.svg
  65. 32 0
      src/assets/icons/exception/403.svg
  66. 32 0
      src/assets/icons/exception/404.svg
  67. 36 0
      src/assets/icons/exception/500.svg
  68. 38 0
      src/assets/icons/logo.svg
  69. BIN=BIN
      src/assets/images/bg-main.png
  70. BIN=BIN
      src/assets/images/task/1.png
  71. 0 0
      src/assets/images/task/2.png
  72. 0 0
      src/assets/images/task/3.png
  73. 0 0
      src/assets/images/task/4.png
  74. 0 0
      src/assets/images/task/5.png
  75. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/bg_circle.png
  76. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/bg_shape.png
  77. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/car.png
  78. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/cat.png
  79. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/factory.png
  80. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/flower.png
  81. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/forest.png
  82. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/hospital.png
  83. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/house.png
  84. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/icon_center.png
  85. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/icon_pointer.png
  86. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/icon_pointer_red.png
  87. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/lakes.png
  88. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/massif.png
  89. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/stop.png
  90. BIN=BIN
      src/assets/images/task/spatialOrientationAbility/trafficLight.png
  91. BIN=BIN
      src/assets/logo.png
  92. BIN=BIN
      src/assets/tabbar/about.png
  93. BIN=BIN
      src/assets/tabbar/about_n.png
  94. BIN=BIN
      src/assets/tabbar/comp.png
  95. BIN=BIN
      src/assets/tabbar/comp_n.png
  96. BIN=BIN
      src/assets/tabbar/home.png
  97. BIN=BIN
      src/assets/tabbar/home_n.png
  98. 51 0
      src/components/Logo.vue
  99. 48 0
      src/components/SvgIcon.vue
  100. 0 60
      src/components/resolver/CountDown/index.vue

+ 43 - 0
.editorconfig

@@ -0,0 +1,43 @@
+# 官网是这么介绍 EditorConfig 的:
+# EditorConfig帮助开发人员在不同的编辑器和IDE之间定义和维护一致的编码样式。
+# EditorConfig 项目由用于定义编码样式的文件格式和一组文本编辑器插件组成,这些插件使编辑器能够读取文件格式并遵循定义的样式。
+# EditorConfig 文件易于阅读,并且与版本控制系统配合使用。
+# 不同的开发人员,不同的编辑器,有不同的编码风格,而 EditorConfig 就是用来协同团队开发人员之间的代码的风格及样式规范化的一个工具,
+# 而.editorconfig正是它的默认配置文件
+
+#EditorConfig 的匹配规则是从上往下,即先定义的规则优先级比后定义的优先级要高。
+
+# 告诉 EditorConfig 插件,这是根文件,不用继续往上查找
+root = true
+
+# 匹配全部文件
+[*]
+
+# 设置字符集
+charset=utf-8
+
+# 结尾换行符,可选"lf"、"cr"、"crlf"
+end_of_line=LF
+
+# 在文件结尾插入新行
+insert_final_newline=true
+
+# 缩进风格,可选"space"、"tab"
+indent_style=space
+
+# 缩进的空格数
+indent_size=2
+
+max_line_length = 100
+
+# 匹配 yml 和 yaml、json 结尾的文件
+[*.{yml,yaml,json}]
+indent_style = space
+indent_size = 2
+
+[*.md]
+# 删除一行中的前后空格
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 14 - 0
.env

@@ -0,0 +1,14 @@
+# port
+VITE_PORT = 8000
+
+# spa-title
+VITE_GLOB_APP_TITLE = Vue3Vant4Mobile
+
+# 系统中文名称
+VITE_GLOB_APP_TITLE_CN = vue3-vant4-mobile
+
+# spa shortname 不可出现空格数字等特殊字符
+VITE_GLOB_APP_SHORT_NAME = vantMobile
+
+# 生产环境 开启mock
+VITE_GLOB_PROD_MOCK = true

+ 31 - 0
.env.development

@@ -0,0 +1,31 @@
+# 只在开发模式中被载入
+VITE_PORT = 9999
+
+# 网站根目录
+VITE_PUBLIC_PATH = /
+
+# 是否开启mock
+VITE_USE_MOCK = true
+
+# 是否删除console
+VITE_DROP_CONSOLE = true
+
+# 跨域代理,可以配置多个,请注意不要换行
+# VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
+# VITE_PROXY=[["/api","http://localhost:8001"]]
+
+# API 接口地址
+# 如果没有跨域问题,直接在这里配置即可
+VITE_GLOB_API_URL =
+
+# 图片上传地址
+VITE_GLOB_UPLOAD_URL =
+
+# 图片前缀地址
+VITE_GLOB_IMG_URL =
+
+# 接口前缀
+VITE_GLOB_API_URL_PREFIX = /api
+
+
+

+ 28 - 0
.env.production

@@ -0,0 +1,28 @@
+# 是否开启mock
+VITE_USE_MOCK = true
+
+# 网站根目录
+VITE_PUBLIC_PATH = /
+
+# 是否删除console
+VITE_DROP_CONSOLE = true
+
+# API
+VITE_GLOB_API_URL =
+
+# 图片上传地址
+VITE_GLOB_UPLOAD_URL =
+
+# 图片前缀地址
+VITE_GLOB_IMG_URL =
+
+# 接口 (api) 前缀
+VITE_GLOB_API_URL_PREFIX = /api
+
+# 是否启用gzip压缩或brotli压缩
+# 可选: gzip | brotli | none
+# 如果你需要多种形式,你可以用','来分隔
+VITE_BUILD_COMPRESS = 'none'
+
+# 使用压缩时是否删除原始文件,默认为false
+VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false

+ 0 - 16
.eslintignore

@@ -1,16 +0,0 @@
-
-*.sh
-node_modules
-*.md
-*.woff
-*.ttf
-.vscode
-.idea
-dist
-/public
-/docs
-.husky
-.local
-/bin
-Dockerfile
-.vscode

+ 0 - 87
.eslintrc-auto-import.json

@@ -1,87 +0,0 @@
-{
-  "globals": {
-    "Component": true,
-    "ComponentPublicInstance": true,
-    "ComputedRef": true,
-    "EffectScope": true,
-    "ExtractDefaultPropTypes": true,
-    "ExtractPropTypes": true,
-    "ExtractPublicPropTypes": true,
-    "InjectionKey": true,
-    "PropType": true,
-    "Ref": true,
-    "VNode": true,
-    "WritableComputedRef": true,
-    "acceptHMRUpdate": true,
-    "computed": true,
-    "createApp": true,
-    "createPinia": true,
-    "customRef": true,
-    "defineAsyncComponent": true,
-    "defineComponent": true,
-    "defineStore": true,
-    "effectScope": true,
-    "getActivePinia": true,
-    "getAssetsImageUrl": true,
-    "getCurrentInstance": true,
-    "getCurrentScope": true,
-    "getRelativeImageUrl": true,
-    "h": true,
-    "inject": true,
-    "isProxy": true,
-    "isReactive": true,
-    "isReadonly": true,
-    "isRef": true,
-    "mapActions": true,
-    "mapGetters": true,
-    "mapState": true,
-    "mapStores": true,
-    "mapWritableState": 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,
-    "setActivePinia": true,
-    "setMapStoreSuffix": true,
-    "shallowReactive": true,
-    "shallowReadonly": true,
-    "shallowRef": true,
-    "storeToRefs": true,
-    "toRaw": true,
-    "toRef": true,
-    "toRefs": true,
-    "toValue": true,
-    "triggerRef": true,
-    "unref": true,
-    "useAttrs": true,
-    "useCssModule": true,
-    "useCssVars": true,
-    "useLink": true,
-    "useMessage": true,
-    "useRoute": true,
-    "useRouter": true,
-    "useSlots": true,
-    "watch": true,
-    "watchEffect": true,
-    "watchPostEffect": true,
-    "watchSyncEffect": true
-  }
-}

+ 0 - 82
.eslintrc.js

@@ -1,82 +0,0 @@
-// @ts-check
-const { defineConfig } = require('eslint-define-config')
-module.exports = defineConfig({
-  root: true,
-  env: {
-    browser: true,
-    node: true,
-    es6: true
-  },
-  parser: 'vue-eslint-parser',
-  parserOptions: {
-    parser: '@typescript-eslint/parser',
-    ecmaVersion: 2020,
-    sourceType: 'module',
-    jsxPragma: 'React',
-    ecmaFeatures: {
-      jsx: true
-    }
-  },
-  globals: {
-    AMap: false,
-    AMapUI: false
-  },
-  extends: ['plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended', 'prettier', 'plugin:prettier/recommended', './.eslintrc-auto-import.json'],
-  rules: {
-    '@typescript-eslint/ban-ts-ignore': 'off',
-    '@typescript-eslint/explicit-function-return-type': 'off',
-    '@typescript-eslint/no-explicit-any': 'off',
-    '@typescript-eslint/no-var-requires': 'off',
-    '@typescript-eslint/no-empty-function': 'off',
-    'vue/custom-event-name-casing': 'off',
-    'no-use-before-define': 'off',
-    '@typescript-eslint/no-use-before-define': 'off',
-    '@typescript-eslint/ban-ts-comment': 'off',
-    '@typescript-eslint/ban-types': 'off',
-    '@typescript-eslint/no-non-null-assertion': 'off',
-    '@typescript-eslint/explicit-module-boundary-types': 'off',
-    '@typescript-eslint/no-unused-vars': [
-      'error',
-      {
-        argsIgnorePattern: '^_',
-        varsIgnorePattern: '^_'
-      }
-    ],
-    'no-unused-vars': [
-      'error',
-      {
-        argsIgnorePattern: '^_',
-        varsIgnorePattern: '^_'
-      }
-    ],
-    'space-before-function-paren': 'off',
-    // "vue/name-property-casing": ["error", "PascalCase"], // vue/component-definition-name-casing 对组件定义名称强制使用特定的大小
-    'vue/multi-word-component-names': [
-      'error',
-      {
-        ignores: ['index'] //需要忽略的组件名
-      }
-    ],
-    'vue/attributes-order': 'off',
-    'vue/one-component-per-file': 'off',
-    'vue/html-closing-bracket-newline': 'off',
-    'vue/max-attributes-per-line': 'off',
-    'vue/multiline-html-element-content-newline': 'off',
-    'vue/singleline-html-element-content-newline': 'off',
-    'vue/attribute-hyphenation': 'off',
-    'vue/require-default-prop': 'off',
-    'vue/script-setup-uses-vars': 'error', //防止<script setup>使用的变量<template>被标记为未使用
-    'vue/html-self-closing': [
-      'error',
-      {
-        html: {
-          void: 'always',
-          normal: 'never',
-          component: 'always'
-        },
-        svg: 'always',
-        math: 'always'
-      }
-    ]
-  }
-})

+ 4 - 5
.gitignore

@@ -8,15 +8,12 @@ pnpm-debug.log*
 lerna-debug.log*
 
 node_modules
-dist
+/dist
 dist-ssr
 *.local
-.npmrc
-.cache
-.eslintcache
 
 # Editor directories and files
-# .vscode/*
+.vscode/*
 !.vscode/extensions.json
 .idea
 .DS_Store
@@ -25,3 +22,5 @@ dist-ssr
 *.njsproj
 *.sln
 *.sw?
+/components.d.ts
+/components.d.ts

+ 0 - 4
.husky/commit-msg

@@ -1,4 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-npx --no-install commitlint --edit $1

+ 0 - 4
.husky/pre-commit

@@ -1,4 +0,0 @@
-#!/bin/sh
-. "$(dirname "$0")/_/husky.sh"
-
-npm run lint:lint-staged

+ 0 - 9
.prettierignore

@@ -1,9 +0,0 @@
-/dist/*
-.local
-.output.js
-/node_modules/**
-
-**/*.svg
-**/*.sh
-.vscode
-/public/*

+ 0 - 53
.prettierrc.js

@@ -1,53 +0,0 @@
-/*
- * @Description: prettierrc配置文件
- * @Date: 2022-04-06 16:19:54
- * @Author: chenjiaming
- * @LastEditors: chenjiaming
- * @LastEditTime: 2022-04-06 18:37:31
- */
-module.exports = {
-  // (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
-  arrowParens: 'always',
-  // 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
-  bracketSameLine: false,
-  // 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
-  bracketSpacing: true,
-  // 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
-  embeddedLanguageFormatting: 'auto',
-  // 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
-  htmlWhitespaceSensitivity: 'css',
-  // 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
-  insertPragma: false,
-  // 在 JSX 中使用单引号替代双引号,默认false
-  jsxSingleQuote: true,
-  // 每行最多字符数量,超出换行(默认80)
-  printWidth: 120,
-  // 超出打印宽度 (always | never | preserve )
-  proseWrap: 'preserve',
-  // 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
-  quoteProps: 'as-needed',
-  // 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
-  requirePragma: false,
-  // 结尾添加分号
-  semi: false,
-  // 使用单引号 (true:单引号;false:双引号)
-  singleQuote: true,
-  // 缩进空格数,默认2个空格
-  tabWidth: 2,
-  // 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
-  trailingComma: 'none',
-  // 指定缩进方式,空格或tab,默认false,即使用空格
-  useTabs: false,
-  // vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
-  vueIndentScriptAndStyle: false,
-
-  endOfLine: 'auto',
-  overrides: [
-    {
-      files: '*.html',
-      options: {
-        parser: 'html'
-      }
-    }
-  ]
-}

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2022
+Copyright (c) 2022 傲慢或香橙
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 286 - 45
README.md

@@ -1,57 +1,298 @@
-基于 Vue3.4 + Typescript + Vite5 + vant4 + pinia + vue-router + unoCss
+<div align="center">
+  <a href="https://github.com/xiangshu233/vue3-vant4-mobile">
+    <img alt="Vue3Vant4MobileLogo" width="200" height="200" src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2022/07/logo.svg">
+  </a>
+</div><br><br>
 
-## 步骤
+<p align="center">
+  <img src="https://img.shields.io/github/license/xiangshu233/vue3-vant4-mobile" alt="license" />
+  <img src="https://img.shields.io/github/package-json/v/xiangshu233/vue3-vant4-mobile" alt="version" />
+  <img src="https://img.shields.io/github/repo-size/xiangshu233/vue3-vant4-mobile" alt="repo-size" />
+  <img src="https://img.shields.io/github/languages/top/xiangshu233/vue3-vant4-mobile" alt="languages" />
+  <img src="https://img.shields.io/github/issues-closed/xiangshu233/vue3-vant4-mobile" alt="issues" />
+</p>
 
-1. 安装依赖
+<h1 align="center">vue3-vant4-mobile</h1>
 
-```shell
-pnpm install
+## Stargazers over time
+
+[![Stargazers over time](https://starchart.cc/xiangshu233/vue3-vant4-mobile.svg?variant=adaptive)](https://starchart.cc/xiangshu233/vue3-vant4-mobile)
+
+## 介绍
+
+👋👋👋 Vue3 Vant4 Mobile 使用了最新的 `Vue3.4`、`Vite5`、`Vant4`、`Pinia`、`TypeScript`、`UnoCSS` 等主流技术开发,集成 `Dark Mode`(暗黑)模式和系统主题色,并且持久化保存,集成 `Mock` 数据,顺便写了登录/注册/找回密码 页面(包括逻辑),只需替换你的 API 即可,另外页面均可以 `<keep-alive>`,随便写了个包含 `NavBar`、`TabBar` 的 Layout,集成了 `Axios`、`useECharts`、`IconSvg`。
+
+项目使用了 [antfu](https://github.com/antfu) 大佬的 [antfu/eslint-config](https://github.com/antfu/eslint-config) 作为代码规范检查工具,摆脱繁琐无聊的 Eslint 配置,配合 `cz-git`、 `lint-staged`、`simple-git-hooks`可对暂存区代码提交校验,代码风格不合格可打断提交,保证多人协作开发时上游 Git 库的干净。
+
+现在你可以在此之上直接开发你的业务代码!希望你能喜欢!
+
+## 截图预览
+
+<table>
+  <tr>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2022/10/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20221022091917.png" width="400" alt="登录页面" /></td>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172041924.png" width="400" alt="主控台页(首页)" /></td>
+  </tr>
+  <tr>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172042123.png" width="400" alt="消息页(图标页)" /></td>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172042858.png" width="400" alt="我的(我的信息页面)" /></td>
+  </tr>
+</table>
+
+<details>
+<summary>展开预览暗黑模式下的界面截图</summary>
+
+<table>
+  <tr>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172036432.png" width="400" alt="登录页面(暗黑模式)" /></td>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172038413.png" width="400" alt="主控台页(暗黑模式)" /></td>
+  </tr>
+  <tr>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172039913.png" width="400" alt="我的页面(暗黑模式)" /></td>
+    <td><img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2023/12/202403172040019.png" width="400" alt="主题设置页面(暗黑模式)" /></td>
+  </tr>
+</table>
+
+</details>
+
+## 线上预览
+
+预览链接:_[https://vvmobile.xiangshu233.cn/](https://vvmobile.xiangshu233.cn/)_
+
+账号:admin,密码:123456
+
+账号:test,密码:123456
+
+或者扫描以下二维码进入手机演示
+
+<p align="center">
+  <img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2022/07/vue3-vant4-mobile-QR-code.png" width="200" />
+</p>
+
+## 基础知识
+
+- [Vite](https://cn.vitejs.dev/) - 熟悉 `Vite` 特性
+- [Vue3](https://v3.vuejs.org/) - 熟悉 `Vue3` 基础语法
+- [Vant4](https://youzan.github.io/vant/v4/#/zh-CN) - 掌握 `vant4` 组件基本使用
+- [Pinia](https://pinia.vuejs.org/) - 熟悉 `Pinia` 特性
+- [TypeScript](https://www.typescriptlang.org/) - 熟悉 `TypeScript` 基本语法
+- [Vue-Router-Next](https://router.vuejs.org/) - 熟悉 `Vue-Router`基本使用
+- [ECharts5](https://echarts.apache.org/handbook/zh/get-started/) - 熟悉 `Echarts` 基本使用
+- [Iconify](https://icones.js.org/) - 本项目推荐图标库,当然你也可以使用 `IconSvg`
+- [Postcss-Mobile-Forever](https://github.com/wswmsword/postcss-mobile-forever) - 了解手机端 `px` 转 `viewport` 插件的作用
+- [Lodash-es](https://www.lodashjs.com/) - `JS`高性能工具库
+- [UnoCSS](https://unocss.dev/) - 原子化 `CSS`,熟悉 `UnoCSS` 基本使用
+- [Mock.js](https://github.com/nuysoft/Mock) - 了解 `Mockjs` 基本语法
+- [ES6+](http://es6.ruanyifeng.com/) - 熟悉 `ES6` 基本语法
+
+## 关于 Icon 的使用
+
+项目使用 `unocss` 的 `icon` [预设](https://unocss.dev/presets/icons) 作为系统 Icon
+
+请遵循以下约定使用图标
+
+- `<prefix><collection>-<icon>`
+- `<prefix><collection>:<icon>`
+
+```html
+<!-- A basic anchor icon from Phosphor icons -->
+<div class="i-ph-anchor-simple-thin" />
+<!-- An orange alarm from Material Design Icons -->
+<div class="i:mdi:alarm" />
 ```
 
-2. 启动
+点击这里获取所有[可用的图标](https://icones.js.org/),找到想要的图标后点击复制 icon name 到 class 里即可
+
+> [!WARNING]
+> 记得加 `i-xxx` 前缀,从 icones 复制的 icon name 是没有 `i-` 前缀的
+>
+> 更多详细使用请看 https://unocss.dev/presets/icons#icons-preset
+
+## 环境准备
+
+本地环境需要安装 [Pnpm](https://www.pnpm.cn/)、[Node.js](http://nodejs.org/) 和 [Git](https://git-scm.com/)
+
+- 必须使用 [pnpm>=8.6.10](https://www.pnpm.cn/),否则依赖可能安装不上。
+- [Node.js](http://nodejs.org/) 版本要求`18.x`以上,且不能为`13.x`版本,这里推荐 ` ^20.9.0 || >=21.1.0`。
+
+## VS Code 配套插件
+
+如果你使用的 IDE 是 [VS Code](https://code.visualstudio.com/)(推荐)的话,可以安装以下工具来提高开发效率及代码格式化
+
+- [UnoCSS](https://marketplace.visualstudio.com/items?itemName=antfu.unocss) - UnoCSS 提示插件
+- [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 开发必备
+- [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) - 用于 TypeScript 服务器的 Vue 插件
+- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) - 脚本代码检查
+- [DotENV](https://marketplace.visualstudio.com/items?itemName=mikestead.dotenv) - `.env` 文件 高亮
+- [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) - 更好的错误定位
+- [EditorConfig for VS Code](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) - 不同 IDE 维护一致的编码样式
+- [File Nesting Updater](https://marketplace.visualstudio.com/items?itemName=antfu.file-nesting) - 使用 VS Code 的文件嵌套功能使文件树更干净的配置
+- [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=antfu.file-nesting) - 使 VSCode 中的 TypeScript 错误更漂亮、更易于理解
+- [Todo Tree](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree) - 在树视图中显示 TODO、FIXME 等注释标签
+- [Trailing Spaces](https://marketplace.visualstudio.com/items?itemName=shardulm94.trailing-spaces) - 突出显示尾随空格并立即将其删除
+
+## VS Code Eslint 支持(自动修复)
 
-```shell
-# 开发环境
+安装 [VS Code ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
+
+将以下设置添加到您的 `.vscode/settings.json` 中:
+
+```jsonc
+{
+  // Enable the ESlint flat config support
+  "eslint.experimental.useFlatConfig": true,
+
+  // Disable the default formatter, use eslint instead
+  "prettier.enable": false,
+  "editor.formatOnSave": false,
+
+  // Auto fix
+  "editor.codeActionsOnSave": {
+    "source.fixAll.eslint": "explicit",
+    "source.organizeImports": "never"
+  },
+
+  // Silent the stylistic rules in you IDE, but still auto fix them
+  "eslint.rules.customizations": [
+    { "rule": "style/*", "severity": "off" },
+    { "rule": "format/*", "severity": "off" },
+    { "rule": "*-indent", "severity": "off" },
+    { "rule": "*-spacing", "severity": "off" },
+    { "rule": "*-spaces", "severity": "off" },
+    { "rule": "*-order", "severity": "off" },
+    { "rule": "*-dangle", "severity": "off" },
+    { "rule": "*-newline", "severity": "off" },
+    { "rule": "*quotes", "severity": "off" },
+    { "rule": "*semi", "severity": "off" }
+  ],
+
+  // Enable eslint for all supported languages
+  "eslint.validate": [
+    "javascript",
+    "javascriptreact",
+    "typescript",
+    "typescriptreact",
+    "vue",
+    "html",
+    "markdown",
+    "json",
+    "jsonc",
+    "yaml",
+    "toml"
+  ]
+}
+```
+
+## 使用
+
+```bash
+# 获取项目代码
+git clone https://github.com/xiangshu233/vue3-vant4-mobile.git
+
+# 安装依赖
+cd vue3-vant4-mobile
+pnpm install
+
+# 运行
 pnpm dev
-# 测试环境
-serve:staging
+
+# 打包
+pnpm build
 ```
 
-3. 构建
+## Git 提交规范
+
+### 提交规范
+
+参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
+
+- `feat` 增加新功能
+- `fix` 修复问题/BUG
+- `style` 代码风格相关无影响运行结果的
+- `perf` 优化/性能提升
+- `refactor` 重构
+- `revert` 撤销修改
+- `test` 测试相关
+- `docs` 文档/注释
+- `chore` 依赖更新/脚手架配置修改等
+- `workflow` 工作流改进
+- `ci` 持续集成
+- `types` 类型定义文件更改
+- `wip` 开发中
 
-```shell
-pnpm run build
+### 提交校验
+
+> [!TIP]
+> 关于前端工程化 **配置构建代码检查工作流** 不了解的可以看下面这两篇文章了解下
+>
+> [前端工程化配置(上) 构建代码检查工作流](https://xiangshu233.cn/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96%E9%85%8D%E7%BD%AE%EF%BC%88%E4%B8%8A%EF%BC%89%20%E6%9E%84%E5%BB%BA%E4%BB%A3%E7%A0%81%E6%A3%80%E6%9F%A5%E5%B7%A5%E4%BD%9C%E6%B5%81/)
+>
+> [前端工程化配置(下) 规范仓库提交记录 commitlint + commitizen + cz-git + 配置](https://xiangshu233.cn/%E5%89%8D%E7%AB%AF%E5%B7%A5%E7%A8%8B%E5%8C%96%E9%85%8D%E7%BD%AE%EF%BC%88%E4%B8%8B%EF%BC%89%20%E8%A7%84%E8%8C%83%E4%BB%93%E5%BA%93%E6%8F%90%E4%BA%A4%E8%AE%B0%E5%BD%95/)
+
+> [!IMPORTANT]
+> 首次 clone 代码 `pnpm install` 后 需要执行以下命令来更新`git hooks`
+>
+> ```shell
+> # Update ./git/hooks
+> npx simple-git-hooks
+> ```
+
+本项目提交规范校验使用 [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks) 作为 git hooks,使用 [cz-git](https://github.com/Zhengqbbb/cz-git) 作为 commitlint commitizen。
+
+> [!IMPORTANT]
+> 更改的代码若想要使用 Commitlint 规范提交需要将文件(放入暂存区) `git add` 后,控制台执行 `cz` 命令开启 cz-git CLI
+> 。若想直接执行 `git commit` 需要满足上面提交规范才能通过校验,否则将会被 Git Hook 打断提交
+
+simple-git-hooks 和 husky 都是用于管理 Git 钩子(Git hooks)的工具,但它们有一些区别:
+
+> simple-git-hooks:
+>
+> - 简介: simple-git-hooks 是一个轻量级的工具,用于管理和运行Git钩子。
+> - 特点:
+>   - 提供了简单的配置方式来定义和运行 Gi 钩子。
+>   - 适合于小型项目或对 Git 钩子需求不复杂的项目。
+>   - 相对较少的功能和配置选项。
+>   - 使用场景: 适用于简单的项目或对 Git 钩子管理需求不高的情况。
+>
+> husky:
+>
+> - 简介: husky 是一个功能强大的工具,用于管理 Git 钩子,并且在项目中被广泛使用。
+> - 特点:
+>   - 提供了丰富的配置选项和灵活性,可以精细地控制 Git 钩子的行为。
+>   - 支持在不同的 Git 钩子事件上运行自定义脚本。
+>   - 可以与其他工具(如linters、测试框架等)集成,实现更复杂的工作流。
+>   - 使用场景: 适用于需要灵活配置和管理 Git 钩子的项目,尤其是大型或复杂的项目。
+
+```json
+// package.json
+{
+  "simple-git-hooks": {
+    // 对暂存区执行 eslint --fix
+    "pre-commit": "pnpm lint-staged",
+    // 对提交信息进行校验
+    "commit-msg": "npx --no-install commitlint --edit $1"
+  },
+
+  "lint-staged": {
+    "*": "eslint --fix"
+  }
+}
 ```
 
-## 完成进度
-
-- [x] husky & .eslintrc - 代码规范
-- [x] unocss(使用样式预算、属性模式预设) & .prettierrc & postcss-px-to-viewport(多设计尺寸) - 样式相关
-- [x] unplugin-icons 图标功能
-- [x] unplugin-auto-import & unplugin-auto-vconsole 自动导入方法、组件(一些 vant 组件或者 vue3、pinia、router 等引入还是很方便的,但不可滥用)
-- [x] mock - 请求数据模拟
-- [x] pinia - store 全局状态管理 - 持久化等
-- [x] axios - http 请求
-- [x] router 路由权限等一系列基础用法
-- [x] 简单的登入登出
-- [x] tabbar
-- [x] hook (加载列表示例)
-- [ ] 组件
-
-## Git 贡献提交规范
-
-- 这里遵循 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
-
-  - `feat` 增加新功能
-  - `fix` 修复问题/BUG
-  - `style` 代码风格相关无影响运行结果的
-  - `perf` 优化/性能提升
-  - `refactor` 重构
-  - `revert` 撤销修改
-  - `test` 测试相关
-  - `docs` 文档/注释
-  - `chore` 依赖更新/脚手架配置修改等
-  - `workflow` 工作流改进
-  - `ci` 持续集成
-  - `types` 类型定义文件更改
-  - `wip` 开发中
+## 浏览器支持
+
+本地开发推荐使用`Chrome 80+` 浏览器
+
+支持现代浏览器, 不支持 IE
+
+| [![ Edge](https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png)](http://godban.github.io/browsers-support-badges/) IE | [![ Edge](https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png)](http://godban.github.io/browsers-support-badges/) Edge | [![Firefox](https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png)](http://godban.github.io/browsers-support-badges/) Firefox | [![Chrome](https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png)](http://godban.github.io/browsers-support-badges/) Chrome | [![Safari](https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png)](http://godban.github.io/browsers-support-badges/) Safari |
+| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| not support                                                                                                                                           | last 2 versions                                                                                                                                         | last 2 versions                                                                                                                                                    | last 2 versions                                                                                                                                                | last 2 versions                                                                                                                                                |
+
+## 维护者
+
+[@xiangshu233](https://github.com/xiangshu233)
+
+## LICENSE
+
+[MIT](https://en.wikipedia.org/wiki/MIT_License)

+ 0 - 252
auto-imports.d.ts

@@ -1,252 +0,0 @@
-/* eslint-disable */
-/* prettier-ignore */
-// @ts-nocheck
-// noinspection JSUnusedGlobalSymbols
-// Generated by unplugin-auto-import
-export {}
-declare global {
-  const EffectScope: typeof import('vue')['EffectScope']
-  const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
-  const computed: typeof import('vue')['computed']
-  const createApp: typeof import('vue')['createApp']
-  const createPinia: typeof import('pinia')['createPinia']
-  const customRef: typeof import('vue')['customRef']
-  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
-  const defineComponent: typeof import('vue')['defineComponent']
-  const defineStore: typeof import('pinia')['defineStore']
-  const effectScope: typeof import('vue')['effectScope']
-  const getActivePinia: typeof import('pinia')['getActivePinia']
-  const getAssetsImageUrl: typeof import('../../../@/utils/file/image')['getAssetsImageUrl']
-  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
-  const getCurrentScope: typeof import('vue')['getCurrentScope']
-  const getRelativeImageUrl: typeof import('../../../@/utils/file/image')['getRelativeImageUrl']
-  const h: typeof import('vue')['h']
-  const inject: typeof import('vue')['inject']
-  const isProxy: typeof import('vue')['isProxy']
-  const isReactive: typeof import('vue')['isReactive']
-  const isReadonly: typeof import('vue')['isReadonly']
-  const isRef: typeof import('vue')['isRef']
-  const mapActions: typeof import('pinia')['mapActions']
-  const mapGetters: typeof import('pinia')['mapGetters']
-  const mapState: typeof import('pinia')['mapState']
-  const mapStores: typeof import('pinia')['mapStores']
-  const mapWritableState: typeof import('pinia')['mapWritableState']
-  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 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 setActivePinia: typeof import('pinia')['setActivePinia']
-  const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
-  const shallowReactive: typeof import('vue')['shallowReactive']
-  const shallowReadonly: typeof import('vue')['shallowReadonly']
-  const shallowRef: typeof import('vue')['shallowRef']
-  const storeToRefs: typeof import('pinia')['storeToRefs']
-  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 triggerRef: typeof import('vue')['triggerRef']
-  const unref: typeof import('vue')['unref']
-  const useAttrs: typeof import('vue')['useAttrs']
-  const useCssModule: typeof import('vue')['useCssModule']
-  const useCssVars: typeof import('vue')['useCssVars']
-  const useLink: typeof import('vue-router')['useLink']
-  const useMessage: typeof import('../../../@/hooks/web/useMessage')['useMessage']
-  const useRoute: typeof import('vue-router')['useRoute']
-  const useRouter: typeof import('vue-router')['useRouter']
-  const useSlots: typeof import('vue')['useSlots']
-  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,
-    ExtractDefaultPropTypes,
-    ExtractPropTypes,
-    ExtractPublicPropTypes,
-    InjectionKey,
-    PropType,
-    Ref,
-    VNode,
-    WritableComputedRef
-  } from 'vue'
-  import('vue')
-}
-// for vue template auto import
-import { UnwrapRef } from 'vue'
-declare module 'vue' {
-  interface ComponentCustomProperties {
-    readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
-    readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
-    readonly computed: UnwrapRef<typeof import('vue')['computed']>
-    readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
-    readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
-    readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
-    readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
-    readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
-    readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
-    readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
-    readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
-    readonly getAssetsImageUrl: UnwrapRef<typeof import('../../../@/utils/file/image')['getAssetsImageUrl']>
-    readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
-    readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
-    readonly getRelativeImageUrl: UnwrapRef<typeof import('../../../@/utils/file/image')['getRelativeImageUrl']>
-    readonly h: UnwrapRef<typeof import('vue')['h']>
-    readonly inject: UnwrapRef<typeof import('vue')['inject']>
-    readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
-    readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
-    readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
-    readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
-    readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
-    readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
-    readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
-    readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
-    readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
-    readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
-    readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
-    readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
-    readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
-    readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
-    readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
-    readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
-    readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
-    readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
-    readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
-    readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
-    readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
-    readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
-    readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
-    readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
-    readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
-    readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
-    readonly provide: UnwrapRef<typeof import('vue')['provide']>
-    readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
-    readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
-    readonly ref: UnwrapRef<typeof import('vue')['ref']>
-    readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
-    readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
-    readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
-    readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
-    readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
-    readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
-    readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
-    readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
-    readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
-    readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
-    readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
-    readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
-    readonly unref: UnwrapRef<typeof import('vue')['unref']>
-    readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
-    readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
-    readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
-    readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
-    readonly useMessage: UnwrapRef<typeof import('../../../@/hooks/web/useMessage')['useMessage']>
-    readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
-    readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
-    readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
-    readonly watch: UnwrapRef<typeof import('vue')['watch']>
-    readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
-    readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
-    readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
-  }
-}
-declare module '@vue/runtime-core' {
-  interface ComponentCustomProperties {
-    readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
-    readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
-    readonly computed: UnwrapRef<typeof import('vue')['computed']>
-    readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
-    readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
-    readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
-    readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
-    readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
-    readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
-    readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
-    readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
-    readonly getAssetsImageUrl: UnwrapRef<typeof import('../../../@/utils/file/image')['getAssetsImageUrl']>
-    readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
-    readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
-    readonly getRelativeImageUrl: UnwrapRef<typeof import('../../../@/utils/file/image')['getRelativeImageUrl']>
-    readonly h: UnwrapRef<typeof import('vue')['h']>
-    readonly inject: UnwrapRef<typeof import('vue')['inject']>
-    readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
-    readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
-    readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
-    readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
-    readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
-    readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
-    readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
-    readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
-    readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
-    readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
-    readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
-    readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
-    readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
-    readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
-    readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
-    readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
-    readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
-    readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
-    readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
-    readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
-    readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
-    readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
-    readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
-    readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
-    readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
-    readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
-    readonly provide: UnwrapRef<typeof import('vue')['provide']>
-    readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
-    readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
-    readonly ref: UnwrapRef<typeof import('vue')['ref']>
-    readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
-    readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
-    readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
-    readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
-    readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
-    readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
-    readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
-    readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
-    readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
-    readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
-    readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
-    readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
-    readonly unref: UnwrapRef<typeof import('vue')['unref']>
-    readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
-    readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
-    readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
-    readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
-    readonly useMessage: UnwrapRef<typeof import('../../../@/hooks/web/useMessage')['useMessage']>
-    readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
-    readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
-    readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
-    readonly watch: UnwrapRef<typeof import('vue')['watch']>
-    readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
-    readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
-    readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
-  }
-}

+ 5 - 2
build/constant.ts

@@ -1,3 +1,6 @@
-export const GLOB_CONFIG_FILE_NAME = '_app.config.js'
+/**
+ * The name of the configuration file entered in the production environment
+ */
+export const GLOB_CONFIG_FILE_NAME = 'app.config.js'
 
-export const OUTPUT_DIR = 'dist'
+export const OUTPUT_DIR = 'dist/vant-mobile'

+ 0 - 4
build/generate/generateModifyVars.ts

@@ -1,4 +0,0 @@
-// 可定制主题
-export function generateModifyVars() {
-  return {}
-}

+ 8 - 2
build/getConfigFileName.ts

@@ -1,3 +1,9 @@
-export const getConfigFileName = (env: Record<string, any>) => {
-  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '')
+/**
+ * Get the configuration file variable name
+ * @param env
+ */
+export function getConfigFileName(env: Record<string, any>) {
+  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
+    .toUpperCase()
+    .replace(/\s/g, '')
 }

+ 19 - 16
build/script/buildConf.ts

@@ -1,20 +1,22 @@
+/**
+ * Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging
+ */
+import fs from 'fs-extra'
+import colors from 'picocolors'
 import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'
-import fs, { writeFileSync } from 'fs-extra'
-import chalk from 'chalk'
 
 import { getEnvConfig, getRootPath } from '../utils'
 import { getConfigFileName } from '../getConfigFileName'
 
 import pkg from '../../package.json'
 
-interface CreateConfigParams {
-  configName: string
-  config: any
-  configFileName?: string
-}
-
-function createConfig(params: CreateConfigParams) {
-  const { configName, config, configFileName } = params
+function createConfig(
+  {
+    configName,
+    config,
+    configFileName = GLOB_CONFIG_FILE_NAME,
+  }: { configName: string, config: any, configFileName?: string } = { configName: '', config: {} },
+) {
   try {
     const windowConf = `window.${configName}`
     // Ensure that the variable will not be modified
@@ -26,17 +28,18 @@ function createConfig(params: CreateConfigParams) {
       });
     `.replace(/\s/g, '')
     fs.mkdirp(getRootPath(OUTPUT_DIR))
-    writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
+    fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
 
-    console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`)
-    console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n')
-  } catch (error) {
-    console.log(chalk.red('configuration file configuration file failed to package:\n' + error))
+    console.log(`${colors.cyan(`✨ [${pkg.name}]`)} - configuration file is build successfully:`)
+    console.log(`${colors.gray(`${OUTPUT_DIR}/${colors.green(configFileName)}`)}\n`)
+  }
+  catch (error) {
+    console.log(colors.red(`configuration file configuration file failed to package:\n${error}`))
   }
 }
 
 export function runBuildConfig() {
   const config = getEnvConfig()
   const configFileName = getConfigFileName(config)
-  createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME })
+  createConfig({ config, configName: configFileName })
 }

+ 8 - 7
build/script/postBuild.ts

@@ -1,22 +1,23 @@
 // #!/usr/bin/env node
 
-import { runBuildConfig } from './buildConf'
-import chalk from 'chalk'
+import colors from 'picocolors'
 
 import pkg from '../../package.json'
+import { runBuildConfig } from './buildConf'
 
-export const runBuild = async () => {
+export async function runBuild() {
   try {
     const argvList = process.argv.splice(2)
 
     // Generate configuration file
     if (!argvList.includes('disabled-config')) {
-      runBuildConfig()
+      await runBuildConfig()
     }
 
-    console.log(`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!')
-  } catch (error) {
-    console.log(chalk.red('vite build error:\n' + error))
+    console.log(`✨ ${colors.cyan(`[${pkg.name}]`)} - build successfully!`)
+  }
+  catch (error) {
+    console.log(colors.red(`vite build error:\n${error}`))
     process.exit(1)
   }
 }

+ 31 - 34
build/utils.ts

@@ -1,67 +1,64 @@
-import fs from 'fs'
-import path from 'path'
+import fs from 'node:fs'
+import path from 'node:path'
 import dotenv from 'dotenv'
 
-export const wrapperEnv = (envConf: Recordable): ViteEnv => {
+export function isDevFn(mode: string): boolean {
+  return mode === 'development'
+}
+
+export function isProdFn(mode: string): boolean {
+  return mode === 'production'
+}
+
+/**
+ * Whether to generate package preview
+ */
+export function isReportMode(): boolean {
+  return process.env.REPORT === 'true'
+}
+
+// Read all environment variable configuration files to process.env
+// 读取并处理所有环境变量配置文件 .env
+export function wrapperEnv(envConf: Recordable): ViteEnv {
   const ret: any = {}
 
   for (const envName of Object.keys(envConf)) {
+    // 去除空格
     let realName = envConf[envName].replace(/\\n/g, '\n')
-    // 布尔类型
     realName = realName === 'true' ? true : realName === 'false' ? false : realName
-    // 端口号转为number类型
+
     if (envName === 'VITE_PORT') {
       realName = Number(realName)
     }
-    // 代理配置
-    if (envName === 'VITE_PROXY' && realName) {
+    if (envName === 'VITE_PROXY') {
       try {
-        realName = JSON.parse(realName.replace(/'/g, '"'))
-      } catch (error) {
-        realName = ''
+        realName = JSON.parse(realName)
       }
+      catch (error) {}
     }
     ret[envName] = realName
-    if (typeof realName === 'string') {
-      process.env[envName] = realName
-    } else if (typeof realName === 'object') {
-      process.env[envName] = JSON.stringify(realName)
-    }
+    process.env[envName] = realName
   }
   return ret
 }
 
-/**
- * 获取当前环境下生效的配置文件名
- */
-function getConfFiles() {
-  const script = process.env.npm_lifecycle_script
-  const reg = new RegExp('--mode ([a-z_\\d]+)')
-  const result = reg.exec(script as string) as any
-  if (result) {
-    const mode = result[1] as string
-    return ['.env', `.env.${mode}`]
-  }
-  return ['.env', '.env.production']
-}
-
 /**
  * Get the environment variables starting with the specified prefix
  * @param match prefix
  * @param confFiles ext
  */
-export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
+export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) {
   let envConfig = {}
   confFiles.forEach((item) => {
     try {
-      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), 'env', item)))
+      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)))
       envConfig = { ...envConfig, ...env }
-    } catch (e) {
-      console.error(`Error in parsing ${item}`, e)
     }
+    catch (error) {}
   })
-  const reg = new RegExp(`^(${match})`)
+
   Object.keys(envConfig).forEach((key) => {
+    const reg = new RegExp(`^(${match})`)
     if (!reg.test(key)) {
       Reflect.deleteProperty(envConfig, key)
     }

+ 0 - 51
build/vite/plugin/autoImport.ts

@@ -1,51 +0,0 @@
-import AutoImport from 'unplugin-auto-import/vite'
-
-export const autoImportPlugin = () => {
-  return AutoImport({
-    include: [
-      /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
-      /\.vue$/,
-      /\.vue\?vue/, // .vue
-      /\.md$/ // .md
-    ],
-    imports: [
-      // presets
-      'vue',
-      'vue-router',
-      'pinia',
-      // custom
-      {
-        '/@/hooks/web/useMessage': ['useMessage'],
-        '/@/utils/file/image': ['getAssetsImageUrl', 'getRelativeImageUrl']
-        // "@vueuse/core": [
-        //   // named imports
-        //   "useMouse", // import { useMouse } from '@vueuse/core',
-        //   // alias
-        //   ["useFetch", "useMyFetch"], // import { useFetch as useMyFetch } from '@vueuse/core',
-        // ],
-        // axios: [
-        //   // default imports
-        //   ["default", "axios"], // import { default as axios } from 'axios',
-        // ],
-        // "[package-name]": [
-        //   "[import-names]",
-        //   // alias
-        //   ["[from]", "[alias]"],
-        // ],
-      }
-      // example type import
-      // {
-      //   from: "vue-router",
-      //   imports: ["RouteLocationRaw"],
-      //   type: true,
-      // },
-    ],
-    vueTemplate: true,
-    eslintrc: {
-      enabled: true,
-      globalsPropValue: true
-    }
-    // Custom
-    // resolvers: [IconsResolver()],
-  })
-}

+ 0 - 14
build/vite/plugin/autoVconsole.ts

@@ -1,14 +0,0 @@
-import autoVconsole from 'unplugin-auto-vconsole/vite'
-export const autoVconsolePlugin = (isBuild) => {
-  function vconsoleEnabled() {
-    if (isBuild) {
-      return true
-    } else {
-      return false
-    }
-  }
-  return autoVconsole({
-    isBuild,
-    enabled: vconsoleEnabled()
-  })
-}

+ 0 - 27
build/vite/plugin/componentResolver.ts

@@ -1,27 +0,0 @@
-import { VantResolver } from 'unplugin-vue-components/resolvers'
-import IconsResolver from 'unplugin-icons/resolver'
-import Components from 'unplugin-vue-components/vite'
-
-export const componentResolverPlugin = () => {
-  return Components({
-    dirs: ['src/components/resolver'],
-    // globs: ["src/components/resolver/*/index.{vue}"],
-    // extensions: ["vue"],
-    // exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/, /[\\/]\.src[\\/]/],
-    deep: true,
-    // globalNamespaces: ["resolver"],
-    // directoryAsNamespace: true,
-    // collapseSamePrefixes: true,
-    // dts: "types/components.d.ts",
-    resolvers: [
-      VantResolver(),
-      IconsResolver({
-        prefix: 'Icon',
-        alias: {
-          spinners: 'svg-spinners'
-        },
-        customCollections: ['about']
-      })
-    ]
-  })
-}

+ 35 - 0
build/vite/plugin/compress.ts

@@ -0,0 +1,35 @@
+/**
+ * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
+ * https://github.com/anncwb/vite-plugin-compression
+ */
+import type { PluginOption } from 'vite'
+
+import compressPlugin from 'vite-plugin-compression'
+
+export function configCompressPlugin(
+  compress: 'gzip' | 'brotli' | 'none',
+  deleteOriginFile = false,
+): PluginOption | PluginOption[] {
+  const compressList = compress.split(',')
+
+  const plugins: PluginOption[] = []
+
+  if (compressList.includes('gzip')) {
+    plugins.push(
+      compressPlugin({
+        ext: '.gz',
+        deleteOriginFile,
+      }),
+    )
+  }
+  if (compressList.includes('brotli')) {
+    plugins.push(
+      compressPlugin({
+        ext: '.br',
+        algorithm: 'brotliCompress',
+        deleteOriginFile,
+      }),
+    )
+  }
+  return plugins
+}

+ 0 - 8
build/vite/plugin/compression.ts

@@ -1,8 +0,0 @@
-import viteCompression from 'vite-plugin-compression'
-
-export const compressionPlugin = (gzip: boolean) => {
-  return viteCompression({
-    disable: !gzip,
-    threshold: 20 * 1024
-  })
-}

+ 13 - 7
build/vite/plugin/html.ts

@@ -16,25 +16,31 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
     return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`
   }
 
+  // 当执行 yarn build 构建项目之后,会自动生成 _app.config.js 文件并插入 index.html
+  // _app.config.js 用于项目在打包后,需要动态修改配置的需求,如接口地址
+  // 不用重新进行打包,可在打包后修改 /dist/_app.config.js 内的变量,刷新即可更新代码内的局部变量
+
   const htmlPlugin: PluginOption[] = createHtmlPlugin({
     minify: isBuild,
     inject: {
       // Inject data into ejs template
+      // 需要注入 index.html ejs 模版的数据 使用在 html 中 :<div><%= title %></div>
       data: {
-        title: VITE_GLOB_APP_TITLE
+        title: VITE_GLOB_APP_TITLE,
       },
-      // Embed the generated app.config.js file
+
+      // Embed the generated app.config.js file 需要注入的标签列表
       tags: isBuild
         ? [
             {
               tag: 'script',
               attrs: {
-                src: getAppConfigSrc()
-              }
-            }
+                src: getAppConfigSrc(),
+              },
+            },
           ]
-        : []
-    }
+        : [],
+    },
   })
   return htmlPlugin
 }

+ 61 - 25
build/vite/plugin/index.ts

@@ -1,42 +1,78 @@
 import type { PluginOption } from 'vite'
+import Components from 'unplugin-vue-components/vite'
+import { VantResolver } from 'unplugin-vue-components/resolvers'
 import vue from '@vitejs/plugin-vue'
-import { configViteMockServePlugin } from './viteMockServe'
+import UnoCSS from 'unocss/vite'
+import AutoImport from 'unplugin-auto-import/vite'
 import { configHtmlPlugin } from './html'
-import { configUnocssPlugin } from './unocss'
-import { componentResolverPlugin } from './componentResolver'
-import { autoImportPlugin } from './autoImport'
-import { autoVconsolePlugin } from './autoVconsole'
-import { unpluginIcons } from './unpluginIcons'
-import { compressionPlugin } from './compression'
+import { configMockPlugin } from './mock'
+import { configCompressPlugin } from './compress'
+import { configVisualizerConfig } from './visualizer'
+import { configSvgIconsPlugin } from './svgSprite'
 
-export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
-  const { VITE_USE_MOCK, VITE_USE_GZIP } = viteEnv
+/**
+ * 配置 vite 插件
+ * @param viteEnv vite 环境变量配置文件键值队 object
+ * @param isBuild 是否是 build 环境 true/false
+ * @returns vitePlugins[]
+ */
+export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock: boolean) {
+  // VITE_BUILD_COMPRESS 是否启用 gzip 压缩或 brotli 压缩
+  // 可选: gzip | brotli | none,
+  // 如果你需要多种形式,你可以用','来分隔
+
+  // VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE 打包使用压缩时是否删除原始文件,默认为 false
+  const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv
 
   const vitePlugins: (PluginOption | PluginOption[])[] = [
+    // have to
     vue(),
-    componentResolverPlugin(),
-    autoImportPlugin()
-    // use vueJsx
-    // vueJsx(),
+    // 按需引入VantUi且自动创建组件声明
+    Components({
+      dts: true,
+      resolvers: [VantResolver()],
+      types: [],
+    }),
+    // UnoCSS
+    UnoCSS(),
+
+    AutoImport({
+      // targets to transform
+      include: [
+        /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
+        /\.vue$/,
+        /\.vue\?vue/, // .vue
+      ],
+      imports: [
+        // presets
+        'vue',
+        'vue-router',
+        'pinia',
+        '@vueuse/core',
+      ],
+      dts: 'types/auto-imports.d.ts',
+    }),
   ]
 
-  // vite-plugin-html
+  // 加载 html 插件 vite-plugin-html
   vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
 
-  // unocss https://uno.antfu.me/
-  vitePlugins.push(configUnocssPlugin())
-
-  // svg icons
-  vitePlugins.push(unpluginIcons())
+  // rollup-plugin-visualizer
+  vitePlugins.push(configVisualizerConfig())
 
-  // console
-  vitePlugins.push(autoVconsolePlugin(isBuild))
+  // vite-plugin-mock
+  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock))
 
-  // 压缩
-  vitePlugins.push(compressionPlugin(VITE_USE_GZIP))
+  // vite-plugin-svg-icons
+  vitePlugins.push(configSvgIconsPlugin(isBuild))
 
-  // mock
-  VITE_USE_MOCK && vitePlugins.push(configViteMockServePlugin(isBuild, VITE_USE_MOCK))
+  if (isBuild) {
+    // rollup-plugin-gzip
+    // 加载 gzip 打包
+    vitePlugins.push(
+      configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
+    )
+  }
 
   return vitePlugins
 }

+ 19 - 0
build/vite/plugin/mock.ts

@@ -0,0 +1,19 @@
+/**
+ * Mock plugin for development and production.
+ * https://github.com/anncwb/vite-plugin-mock
+ */
+import { viteMockServe } from 'vite-plugin-mock'
+
+export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
+  return viteMockServe({
+    ignore: /^\_/,
+    mockPath: 'mock',
+    localEnabled: !isBuild,
+    prodEnabled: isBuild && prodMock,
+    injectCode: `
+      import { setupProdMockServer } from '../mock/_createProductionServer';
+
+      setupProdMockServer();
+      `,
+  })
+}

+ 19 - 0
build/vite/plugin/svgSprite.ts

@@ -0,0 +1,19 @@
+/**
+ *  Vite Plugin for fast creating SVG sprites.
+ * https://github.com/anncwb/vite-plugin-svg-icons
+ */
+
+import path from 'node:path'
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
+
+export function configSvgIconsPlugin(isBuild: boolean) {
+  // 指定需要缓存的图标文件夹
+  const svgIconsPlugin = createSvgIconsPlugin({
+    iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
+    // 是否压缩
+    svgoOptions: isBuild,
+    // 指定symbolId格式
+    symbolId: 'icon-[dir]-[name]',
+  })
+  return svgIconsPlugin
+}

+ 0 - 8
build/vite/plugin/unocss.ts

@@ -1,8 +0,0 @@
-import Unocss from 'unocss/vite'
-import { presetUno, presetAttributify } from 'unocss'
-
-export const configUnocssPlugin = () => {
-  return Unocss({
-    presets: [presetUno(), presetAttributify()]
-  })
-}

+ 0 - 18
build/vite/plugin/unpluginIcons.ts

@@ -1,18 +0,0 @@
-import Icons from 'unplugin-icons/vite'
-import { FileSystemIconLoader } from 'unplugin-icons/loaders'
-
-export const unpluginIcons = () => {
-  return Icons({
-    compiler: 'vue3',
-    // 缩放比例
-    scale: 1,
-    // defaultClass: "w-1em h-1em",
-    autoInstall: true,
-    // https://github.com/antfu/unplugin-icons#custom-icons
-    customCollections: {
-      about: FileSystemIconLoader('src/assets/icons/about', (svg) => {
-        return svg.replace(/^<svg /, '<svg fill="currentColor" ')
-      })
-    }
-  })
-}

+ 18 - 0
build/vite/plugin/visualizer.ts

@@ -0,0 +1,18 @@
+/**
+ * Package file volume analysis
+ */
+import visualizer from 'rollup-plugin-visualizer'
+import type { PluginOption } from 'vite'
+import { isReportMode } from '../../utils'
+
+export function configVisualizerConfig() {
+  if (isReportMode()) {
+    return visualizer({
+      filename: './node_modules/.cache/visualizer/stats.html',
+      open: true,
+      gzipSize: true,
+      brotliSize: true,
+    }) as PluginOption
+  }
+  return []
+}

+ 0 - 16
build/vite/plugin/viteMockServe.ts

@@ -1,16 +0,0 @@
-import { viteMockServe } from 'vite-plugin-mock'
-// import { resolve } from "path";
-
-export const configViteMockServePlugin = (isBuild: boolean, prodBuild: boolean) => {
-  return viteMockServe({
-    mockPath: '/mock',
-    supportTs: true,
-    localEnabled: !isBuild, // 是否应用于本地
-    prodEnabled: isBuild && prodBuild, // 是否应用于生产-isBuild 并且生产环境配置了true
-    //  这样可以控制关闭mock的时候不让mock打包到最终代码内  生产用的
-    injectCode: `
-      import { setupProdMockServer } from '/mock/index';
-      setupProdMockServer();
-    `
-  })
-}

+ 23 - 5
build/vite/proxy.ts

@@ -7,7 +7,7 @@ type ProxyItem = [string, string]
 
 type ProxyList = ProxyItem[]
 
-type ProxyTargetList = Record<string, ProxyOptions>
+type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>
 
 const httpsRE = /^https:\/\//
 
@@ -15,20 +15,38 @@ const httpsRE = /^https:\/\//
  * Generate proxy
  * @param list
  */
-export const createProxy = (list: ProxyList = []) => {
+export function createProxy(list: ProxyList = []) {
   const ret: ProxyTargetList = {}
   for (const [prefix, target] of list) {
     const isHttps = httpsRE.test(target)
 
     // https://github.com/http-party/node-http-proxy#options
     ret[prefix] = {
-      target: target,
+      target,
       changeOrigin: true,
       ws: true,
-      rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
+      rewrite: path => path.replace(new RegExp(`^${prefix}`), ''),
       // https is require secure=false
-      ...(isHttps ? { secure: false } : {})
+      // 如果您secure="true"只允许来自 HTTPS 的请求,则secure="false"意味着允许来自 HTTP 和 HTTPS 的请求。
+      ...(isHttps ? { secure: false } : {}),
     }
   }
+
   return ret
+
+  // ret
+  // {
+  //   '/test/api': {
+  //     target: 'http://localhost:3080/test/api',
+  //     changeOrigin: true,
+  //     ws: true,
+  //     rewrite: (path) => path.replace(new RegExp(/^\/test/api/), ''),
+  //   },
+  //   '/upload': {
+  //     target: 'http://localhost:8001/upload',
+  //     changeOrigin: true,
+  //     ws: true,
+  //     rewrite: (path) => path.replace(new RegExp(/^\/upload/), ''),
+  //   }
+  // }
 }

+ 155 - 0
commitlint.config.cjs

@@ -0,0 +1,155 @@
+// commitlint.config.js
+const fs = require('node:fs')
+const path = require('node:path')
+const { execSync } = require('node:child_process')
+
+const scopes = fs
+  .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
+  .filter(dirent => dirent.isDirectory())
+  .map(dirent => dirent.name.replace(/s$/, ''))
+
+// precomputed scope
+const scopeComplete = execSync('git status --porcelain || true')
+  .toString()
+  .trim()
+  .split('\n')
+  .find(r => ~r.indexOf('M  src'))
+  ?.replace(/(\/)/g, '%%')
+  ?.match(/src%%((\w|-)*)/)?.[1]
+  ?.replace(/s$/, '')
+
+/** @type {import('cz-git').UserConfig} */
+module.exports = {
+  ignores: [commit => commit.includes('init')],
+  extends: ['@commitlint/config-conventional'],
+  rules: {
+    'body-leading-blank': [2, 'always'],
+    'footer-leading-blank': [1, 'always'],
+    'header-max-length': [2, 'always', 108],
+    'subject-empty': [2, 'never'],
+    'type-empty': [2, 'never'],
+    'subject-case': [0],
+    'type-enum': [
+      2,
+      'always',
+      [
+        'feat',
+        'fix',
+        'perf',
+        'style',
+        'docs',
+        'test',
+        'refactor',
+        'build',
+        'ci',
+        'chore',
+        'revert',
+        'wip',
+        'workflow',
+        'types',
+        'release',
+      ],
+    ],
+  },
+  prompt: {
+    /** @use `yarn commit :f` */
+    alias: {
+      f: 'docs: fix typos',
+      r: 'docs: update README',
+      s: 'style: update code format',
+      b: 'build: bump dependencies',
+      c: 'chore: update config',
+    },
+    customScopesAlign: !scopeComplete ? 'top' : 'bottom',
+    defaultScope: scopeComplete,
+    scopes: [...scopes, 'mock'],
+    allowEmptyIssuePrefixs: true,
+    allowCustomIssuePrefixs: true,
+    messages: {
+      type: '选择你要提交的类型 :',
+      scope: '选择一个提交范围(可选):',
+      customScope: '请输入自定义的提交范围 :',
+      subject: '填写简短精炼的变更描述 :\n',
+      body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
+      breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
+      footerPrefixsSelect: '选择关联issue前缀(可选):',
+      customFooterPrefixs: '输入自定义issue前缀 :',
+      footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
+      confirmCommit: '是否提交或修改commit ?',
+    },
+    types: [
+      { value: 'feat', name: 'feat:      ✨ 新增功能 | A new feature', emoji: ':sparkles:' },
+      { value: 'fix', name: 'fix:       🐛 修复缺陷 | A bug fix', emoji: ':bug:' },
+      {
+        value: 'docs',
+        name: 'docs:      📝 文档更新 | Documentation only changes',
+        emoji: ':memo:',
+      },
+      {
+        value: 'style',
+        name: 'style:     💄 代码格式 | Changes that do not affect the meaning of the code',
+        emoji: ':lipstick:',
+      },
+      {
+        value: 'refactor',
+        name: 'refactor:  ♻️  代码重构 | A code change that neither fixes a bug nor adds a feature',
+        emoji: ':recycle:',
+      },
+      {
+        value: 'perf',
+        name: 'perf:      ⚡️ 性能提升 | A code change that improves performance',
+        emoji: ':zap:',
+      },
+      {
+        value: 'test',
+        name: 'test:      ✅ 测试相关 | Adding missing tests or correcting existing tests',
+        emoji: ':white_check_mark:',
+      },
+      {
+        value: 'build',
+        name: 'build:     📦️ 构建相关 | Changes that affect the build system or external dependencies',
+        emoji: ':package:',
+      },
+      {
+        value: 'ci',
+        name: 'ci:        🎡 持续集成 | Changes to our CI configuration files and scripts',
+        emoji: ':ferris_wheel:',
+      },
+      { value: 'revert', name: 'revert:    🔨 回退代码 | Revert to a commit', emoji: ':hammer:' },
+      {
+        value: 'chore',
+        name: 'chore:     ⏪️ 其他修改 | Other changes that do not modify src or test files',
+        emoji: ':rewind:',
+      },
+    ],
+    useEmoji: true,
+    emojiAlign: 'center',
+    themeColorCode: '',
+    allowCustomScopes: true,
+    allowEmptyScopes: true,
+    customScopesAlias: 'custom',
+    emptyScopesAlias: 'empty',
+    upperCaseSubject: false,
+    markBreakingChangeMode: false,
+    allowBreakingChanges: ['feat', 'fix'],
+    breaklineNumber: 100,
+    breaklineChar: '|',
+    skipQuestions: [],
+    issuePrefixs: [
+      // 如果使用 gitee 作为开发管理
+      { value: 'link', name: 'link:     链接 ISSUES 进行中' },
+      { value: 'closed', name: 'closed:   标记 ISSUES 已完成' },
+    ],
+    customIssuePrefixsAlign: 'top',
+    emptyIssuePrefixsAlias: 'skip',
+    customIssuePrefixsAlias: 'custom',
+    confirmColorize: true,
+    maxHeaderLength: Number.POSITIVE_INFINITY,
+    maxSubjectLength: Number.POSITIVE_INFINITY,
+    minSubjectLength: 0,
+    scopeOverrides: undefined,
+    defaultBody: '',
+    defaultIssues: '',
+    defaultSubject: '',
+  },
+}

+ 0 - 13
commitlint.config.js

@@ -1,13 +0,0 @@
-module.exports = {
-  ignores: [(commit) => commit.includes('init')],
-  extends: ['@commitlint/config-conventional'],
-  rules: {
-    'body-leading-blank': [2, 'always'],
-    'footer-leading-blank': [1, 'always'],
-    'header-max-length': [2, 'always', 108],
-    'subject-empty': [2, 'never'],
-    'type-empty': [2, 'never'],
-    'subject-case': [0],
-    'type-enum': [2, 'always', ['feat', 'fix', 'perf', 'style', 'docs', 'test', 'refactor', 'build', 'ci', 'chore', 'revert', 'wip', 'workflow', 'types', 'release']]
-  }
-}

+ 11 - 14
components.d.ts

@@ -7,26 +7,23 @@ export {}
 
 declare module 'vue' {
   export interface GlobalComponents {
-    CountDown: typeof import('./src/components/resolver/CountDown/index.vue')['default']
-    IconAboutBox: typeof import('~icons/about/box')['default']
-    IconAboutCar: typeof import('~icons/about/car')['default']
-    IconAboutLogout: typeof import('~icons/about/logout')['default']
-    IconSpinnersBlocksShuffle2: typeof import('~icons/svg-spinners/blocks-shuffle2')['default']
-    ResolverElement: typeof import('./src/components/resolver/resolverElement/index.vue')['default']
-    ResolverImage: typeof import('./src/components/resolver/resolverImage/index.vue')['default']
-    RouterLink: typeof import('vue-router')['RouterLink']
-    RouterView: typeof import('vue-router')['RouterView']
+    Logo: typeof import('./src/components/Logo.vue')['default']
+    SvgIcon: typeof import('./src/components/SvgIcon.vue')['default']
+    VanActionSheet: typeof import('vant/es')['ActionSheet']
     VanButton: typeof import('vant/es')['Button']
     VanCell: typeof import('vant/es')['Cell']
+    VanCellGroup: typeof import('vant/es')['CellGroup']
+    VanCheckbox: typeof import('vant/es')['Checkbox']
     VanConfigProvider: typeof import('vant/es')['ConfigProvider']
     VanDivider: typeof import('vant/es')['Divider']
     VanField: typeof import('vant/es')['Field']
-    VanIcon: typeof import('vant/es')['Icon']
+    VanForm: typeof import('vant/es')['Form']
     VanImage: typeof import('vant/es')['Image']
-    VanList: typeof import('vant/es')['List']
     VanNavBar: typeof import('vant/es')['NavBar']
-    VanPullRefresh: typeof import('vant/es')['PullRefresh']
-    VanTab: typeof import('vant/es')['Tab']
-    VanTabs: typeof import('vant/es')['Tabs']
+    VanSwipe: typeof import('vant/es')['Swipe']
+    VanSwipeItem: typeof import('vant/es')['SwipeItem']
+    VanSwitch: typeof import('vant/es')['Switch']
+    VanTabbar: typeof import('vant/es')['Tabbar']
+    VanTabbarItem: typeof import('vant/es')['TabbarItem']
   }
 }

+ 0 - 5
env/.env

@@ -1,5 +0,0 @@
-# 端口号
-VITE_PORT = 9527
-
-# 应用名称
-VITE_GLOB_APP_TITLE = CNJM-H5

+ 0 - 23
env/.env.development

@@ -1,23 +0,0 @@
-# 公共路径 vite base
-VITE_PUBLIC_PATH = /insomnia-cognition/
-
-# 路由模式 history | hash
-VITE_ROUTER_MODE = history
-
-# 是否开启mock
-VITE_USE_MOCK = true
-
-# 基本接口地址
-VITE_GLOB_API_URL=
-
-# 接口前缀
-VITE_GLOB_API_URL_PREFIX=/api
-
-# 文件上传地址
-VITE_GLOB_UPLOAD_URL=https://upload-z2.qiniup.com
-
-# 本地调试的代理配置
-VITE_PROXY = [["/api","http://localhost:3000"],["/upload","https://upload-z2.qiniup.com"]]
-
-# 删除日志
-VITE_DROP_CONSOLE = false

+ 0 - 26
env/.env.production

@@ -1,26 +0,0 @@
-# 公共路径 vite base
-VITE_PUBLIC_PATH = /insomnia-cognition/
-
-# 路由模式 history | hash
-VITE_ROUTER_MODE = history
-
-# 是否开启mock
-VITE_USE_MOCK = true
-
-# 是否开启压缩
-VITE_USE_GZIP = true
-
-# 基本接口地址
-VITE_GLOB_API_URL=
-
-# 接口前缀
-VITE_GLOB_API_URL_PREFIX=/api
-
-# 文件上传地址
-VITE_GLOB_UPLOAD_URL=https://upload-z2.qiniup.com
-
-# 本地调试的代理配置
-VITE_PROXY = [["/api","http://localhost:3000"],["/upload","https://upload-z2.qiniup.com"]]
-
-# 删除日志
-VITE_DROP_CONSOLE = false

+ 0 - 0
env/.env.staging


+ 61 - 0
eslint.config.js

@@ -0,0 +1,61 @@
+// eslint.config.js
+import antfu from '@antfu/eslint-config'
+
+export default antfu({
+  unocss: true,
+  stylistic: {
+    indent: 2, // 4, or 'tab'
+    quotes: 'single', // or 'double'
+  },
+  // 使用外部格式化程序来格式化 ESLint 无法处理的文件( .css 、 .html 等)
+  formatters: {
+    css: true,
+    html: true,
+    markdown: 'prettier',
+  },
+  // https://alloyteam.github.io/eslint-config-alloy/?language=zh-CN&rule=base
+  // https://eslint.vuejs.org/rules/
+  rules: {
+    'no-console': 'off',
+    // 强制组件顶级元素的顺序
+    'vue/block-order': [
+      'error',
+      {
+        order: ['template', 'script', 'style'],
+      },
+    ],
+    'max-params': ['error', 4],
+    // 代码块嵌套的深度禁止超过 4 层
+    'max-depth': ['error', 4],
+    // 回调函数嵌套禁止超过 3 层,多了请用 async await 替代
+    'max-nested-callbacks': ['error', 4],
+    // 禁止使用 Array 构造函数时传入的参数超过一个
+    // 参数为多个时表示创建一个指定内容的数组,此时可以用数组字面量实现,不必使用构造函数
+    'no-array-constructor': 'error',
+    // 禁止 if else 的条件判断中出现重复的条件
+    'no-dupe-else-if': 'error',
+    // 禁止出现空代码块,允许 catch 为空代码块
+    'no-empty': [
+      'error',
+      {
+        allowEmptyCatch: true,
+      },
+    ],
+    // 禁止出现没必要的字符串连接
+    'no-useless-concat': 'error',
+    // 禁止使用 var
+    'no-var': 'error',
+    // 禁止变量申明时用逗号一次申明多个
+    'one-var': ['error', 'never'],
+    // 必须使用 ... 而不是 Object.assign,除非 Object.assign 的第一个参数是一个变量
+    'prefer-object-spread': 'error',
+    // 回调函数必须使用箭头函数
+    'prefer-arrow-callback': 'error',
+    // "stroustrup":强制一致的大括号风格,左括号必须与控制语句在同一行开始,右括号必须独占一行。
+    'brace-style': ['error', 'stroustrup'],
+    // 强制使用 node 全局变量 process 而不是 require("process") 。
+    'node/prefer-global/process': 'off',
+    // 对所有控制语句强制执行一致的大括号样式,(只有一行的时候eslint默认是不需要大括号的,这样会降低代码清晰度)
+    'curly': ['error', 'all'],
+  },
+})

+ 120 - 8
index.html

@@ -1,15 +1,127 @@
-<!DOCTYPE html>
-<html lang="zh">
+<!doctype html>
+<html lang="zh-cmn-Hans" id="htmlRoot">
   <head>
     <meta charset="UTF-8" />
-    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
-    <meta name="renderer" content="webkit" />
-    <link rel="icon" href="/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
-    <title><%- title %></title>
+    <link rel="icon" type="image/svg+xml" href="/logo.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title><%= title %></title>
   </head>
   <body>
-    <div id="app"></div>
+    <div id="app">
+      <script>
+        ;(() => {
+          const { darkMode = 'dark', appTheme = '#5d9dfe' } =
+            JSON.parse(window.localStorage.getItem('DESIGN-SETTING')) || {}
+
+          let htmlRoot = document.getElementById('htmlRoot')
+          if (htmlRoot) {
+            htmlRoot.classList.add(darkMode)
+          }
+
+          // 设置主题色变量
+          document.documentElement.style.setProperty(
+            '--app-theme-color',
+            appTheme,
+          )
+        })()
+      </script>
+      <style>
+        body {
+          margin: 0;
+        }
+        html.dark .first-loading-wrap {
+          background-color: #101014;
+        }
+        .first-loading-wrap {
+          display: flex;
+          width: 100%;
+          height: 100vh;
+          justify-content: center;
+          align-items: center;
+          flex-direction: column;
+        }
+        .first-loading-wrap > h1 {
+          font-size: 28px;
+        }
+        .first-loading-wrap .loading-wrap {
+          padding: 98px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+        }
+        .dot {
+          animation: antRotate 1.2s infinite linear;
+          transform: rotate(45deg);
+          position: relative;
+          display: inline-block;
+          font-size: 12px;
+          width: 30px;
+          height: 30px;
+          box-sizing: border-box;
+        }
+        .dot i {
+          width: 14px;
+          height: 14px;
+          position: absolute;
+          display: block;
+          background: var(--app-theme-color);
+          border-radius: 100%;
+          transform: scale(0.75);
+          transform-origin: 50% 50%;
+          opacity: 0.3;
+          animation: antSpinMove 1s infinite linear alternate;
+        }
+        .dot i:nth-child(1) {
+          top: 0;
+          left: 0;
+        }
+        .dot i:nth-child(2) {
+          top: 0;
+          right: 0;
+          -webkit-animation-delay: 0.4s;
+          animation-delay: 0.4s;
+        }
+        .dot i:nth-child(3) {
+          right: 0;
+          bottom: 0;
+          -webkit-animation-delay: 0.8s;
+          animation-delay: 0.8s;
+        }
+        .dot i:nth-child(4) {
+          bottom: 0;
+          left: 0;
+          -webkit-animation-delay: 1.2s;
+          animation-delay: 1.2s;
+        }
+        @keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+        @-webkit-keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+        @keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+        @-webkit-keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+      </style>
+      <div class="first-loading-wrap">
+        <div class="loading-wrap">
+          <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
+        </div>
+      </div>
+    </div>
     <script type="module" src="/src/main.ts"></script>
   </body>
 </html>

+ 18 - 0
mock/_createProductionServer.ts

@@ -0,0 +1,18 @@
+import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
+
+const modules = import.meta.glob('./**/*.ts', { eager: true }) as any
+
+const mockModules: any[] = []
+Object.keys(modules).forEach((key) => {
+  if (key.includes('/_')) {
+    return
+  }
+  mockModules.push(...modules[key].default)
+})
+
+/**
+ * Used in a production environment. Need to manually import all modules
+ */
+export function setupProdMockServer() {
+  createProdMockServer(mockModules)
+}

+ 77 - 0
mock/_util.ts

@@ -0,0 +1,77 @@
+import Mock from 'mockjs'
+import { ResultEnum } from '@/enums/httpEnum'
+
+export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) {
+  return Mock.mock({
+    code: ResultEnum.SUCCESS,
+    result,
+    message,
+    type: 'success',
+  })
+}
+
+export function resultPageSuccess<T = any>(
+  page: number,
+  pageSize: number,
+  list: T[],
+  { message = 'ok' } = {},
+) {
+  const pageData = pagination(page, pageSize, list)
+
+  return {
+    ...resultSuccess({
+      page,
+      pageSize,
+      pageCount: list.length,
+      list: pageData,
+    }),
+    message,
+  }
+}
+
+export function resultError(
+  message = 'Request failed',
+  { code = ResultEnum.ERROR, result = null } = {},
+) {
+  return {
+    code,
+    result,
+    message,
+    type: 'error',
+  }
+}
+
+export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
+  const offset = (pageNo - 1) * Number(pageSize)
+  const ret
+    = offset + Number(pageSize) >= array.length
+      ? array.slice(offset, array.length)
+      : array.slice(offset, offset + Number(pageSize))
+  return ret
+}
+
+/**
+ * @param {number} times 回调函数需要执行的次数
+ * @param {Function} callback 回调函数
+ */
+export function doCustomTimes(times: number, callback: any) {
+  let i = -1
+  while (++i < times) {
+    callback(i)
+  }
+}
+
+export interface requestParams {
+  method: string
+  body: any
+  headers?: { authorization?: string }
+  query: any
+}
+
+/**
+ * @description 本函数用于从request数据中获取token,请根据项目的实际情况修改
+ *
+ */
+export function getRequestToken({ headers }: requestParams): string | undefined {
+  return headers?.authorization
+}

+ 0 - 8
mock/index.ts

@@ -1,8 +0,0 @@
-import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
-
-import userMock from './modules/user'
-import exampleMock from './modules/example'
-
-export function setupProdMockServer() {
-  createProdMockServer([...userMock, ...exampleMock])
-}

+ 0 - 36
mock/modules/example.ts

@@ -1,36 +0,0 @@
-import { MockMethod } from 'vite-plugin-mock'
-const baseUrl = '/api/mock/example'
-export default [
-  {
-    url: `${baseUrl}/loadList`,
-    method: 'post',
-    timeout: 500,
-    response: (res) => {
-      const { pageNum, pageSize, name } = res.body
-      const total = name === '无数据' ? 0 : 85
-      const preNum = (pageNum - 1) * pageSize
-
-      let code = 20000
-
-      if (name === '错误') {
-        code = Math.floor(Math.random() * 10 + 1) > 7 ? 0 : 20000
-      }
-
-      let size = pageSize
-
-      if (preNum + pageSize > total) {
-        size = total - preNum
-      }
-
-      const result = {
-        items: [...new Array(size)].map((_x, n) => name + ':' + (n + preNum)),
-        total
-      }
-      return {
-        code,
-        result,
-        message: 'ok'
-      }
-    }
-  }
-] as MockMethod[]

+ 0 - 45
mock/modules/user.ts

@@ -1,45 +0,0 @@
-import { MockMethod } from 'vite-plugin-mock'
-const baseUrl = '/api/mock/user'
-export default [
-  {
-    url: `${baseUrl}/login`,
-    method: 'post',
-    response: () => {
-      return {
-        code: 20000,
-        result: { token: '123add' },
-        message: '登录成功'
-      }
-    }
-  },
-  {
-    url: `${baseUrl}/getUserInfo`,
-    method: 'get',
-    response: () => {
-      return {
-        code: 20000,
-        result: {
-          userId: 1,
-          userName: '微茫',
-          roles: [
-            { value: 's_admin', roleName: '超级管理员' },
-            { value: 'test', roleName: '测试角色' },
-            { value: 'test1', roleName: '测试角色1' }
-          ]
-        },
-        message: '获取用户信息成功'
-      }
-    }
-  },
-  {
-    url: `${baseUrl}/logout`,
-    method: 'get',
-    response: () => {
-      return {
-        code: 20000,
-        result: {},
-        message: '登出成功'
-      }
-    }
-  }
-] as MockMethod[]

+ 95 - 0
mock/user/user.ts

@@ -0,0 +1,95 @@
+import type { MockMethod } from 'vite-plugin-mock'
+import type { requestParams } from '../_util'
+import { getRequestToken, resultError, resultSuccess } from '../_util'
+import { ResultEnum } from '@/enums/httpEnum'
+
+const fakeUserList = [
+  {
+    userId: 1,
+    username: 'admin',
+    password: '123456',
+    nickname: '一条咸鱼',
+    realname: 'administrator',
+    avatar: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
+    cover: '',
+    sign: '一年精通三年熟练五年入门',
+    industry: 4,
+    gender: 0,
+    phone: '15758791450',
+    token: 'fakeToken1',
+  },
+  {
+    userId: 2,
+    username: 'test',
+    password: '123456',
+    nickname: '萝卜头',
+    realname: 'test user',
+    avatar:
+      'https://link.jscdn.cn/1drv/aHR0cHM6Ly8xZHJ2Lm1zL3UvcyFBaFhWN0U3bHBTaWtsbkNaWjYxY0lLczdEUGlpP2U9Yldkd0Fp.jpg',
+    cover: '',
+    sign: '这个家伙很懒,什么都没有写~',
+    industry: 7,
+    gender: 1,
+    phone: '18822137893',
+    token: 'fakeToken2',
+  },
+]
+
+export default [
+  {
+    url: '/api/login',
+    timeout: 1000,
+    method: 'post',
+    response: ({ body }) => {
+      const { username, password } = body
+      const checkUser = fakeUserList.find(
+        item => item.username === username && password === item.password,
+      )
+      if (!checkUser) {
+        return resultError('帐号或密码不正确')
+      }
+      const { userId, username: _username, token, realname, sign } = checkUser
+      return resultSuccess({
+        userId,
+        username: _username,
+        token,
+        realname,
+        sign,
+      })
+    },
+  },
+  {
+    url: '/api/getUserInfo',
+    timeout: 1000,
+    method: 'get',
+    response: (request: requestParams) => {
+      const token = getRequestToken(request)
+      if (!token) {
+        return resultError('无效令牌')
+      }
+      const checkUser = fakeUserList.find(item => item.token === token)
+      if (!checkUser) {
+        return resultError('没有获取到对应的用户信息', {
+          code: ResultEnum.TOKEN_EXPIRED,
+        })
+      }
+      return resultSuccess(checkUser)
+    },
+  },
+  {
+    url: '/api/logout',
+    timeout: 1000,
+    method: 'post',
+    response: (request: requestParams) => {
+      const token = getRequestToken(request)
+      if (!token) {
+        return resultError('无效令牌')
+      }
+      const checkUser = fakeUserList.find(item => item.token === token)
+      if (!checkUser) {
+        return resultError('无效令牌')
+      }
+      return resultSuccess(undefined, { message: '令牌已被销毁' })
+    },
+  },
+] as MockMethod[]

+ 90 - 89
package.json

@@ -1,114 +1,115 @@
 {
-  "name": "insommnia-cognition",
+  "name": "vue3-vant4-mobile",
+  "type": "module",
+  "version": "2.1.0",
   "private": true,
-  "version": "0.0.0",
+  "packageManager": "pnpm@8.6.10",
+  "author": {
+    "name": "xiangshu233",
+    "email": "xiangshu233@outlook.com",
+    "url": "https://github.com/xiangshu233"
+  },
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/xiangshu233/vue3-vant4-mobile.git"
+  },
+  "bugs": {
+    "url": "https://github.com/xiangshu233/vue3-vant4-mobile/issues"
+  },
+  "engines": {
+    "node": "^18.18.0 || ^20.9.0 || >=21.1.0",
+    "pnpm": ">=8.6.10"
+  },
   "scripts": {
+    "preinstall": "npx only-allow pnpm",
+    "bootstrap": "pnpm install",
+    "serve": "npm run dev",
+    "dev": "cross-env VITE_CJS_IGNORE_WARNING=true vite dev",
+    "dev:debugcjs": "cross-env VITE_CJS_TRACE=true vite dev",
+    "build": "cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts",
+    "build:no-cache": "pnpm clean:cache && npm run build",
+    "report": "cross-env REPORT=true npm run build",
+    "type:check": "vue-tsc --noEmit --skipLibCheck",
+    "preview": "vite preview",
     "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
     "clean:lib": "rimraf node_modules",
-    "clean:dist": "rimraf dist",
-    "dev": "vite",
-    "serve:staging": "vite --mode staging",
-    "build:staging": "vue-tsc --noEmit && vite build  --mode staging && esno ./build/script/postBuild.ts",
-    "build": "vue-tsc --noEmit && cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts",
-    "preview": "npm run build && vite preview",
-    "preview:dist": "vite preview",
-    "prepare": "husky install",
-    "lint:eslint": "vue-tsc --noEmit --skipLibCheck && eslint --cache --max-warnings 0  \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
-    "lint:prettier": "prettier --write  \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix",
     "lint:lint-staged": "lint-staged"
   },
   "dependencies": {
-    "@vueuse/core": "^10.9.0",
-    "axios": "^1.6.8",
-    "crypto-js": "4.2.0",
-    "dayjs": "^1.11.9",
+    "@types/lodash-es": "^4.17.12",
+    "@unocss/reset": "^0.58.5",
+    "@vueuse/core": "^10.7.0",
+    "axios": "^1.6.3",
+    "date-fns": "^3.0.6",
+    "echarts": "^5.4.3",
     "lodash-es": "^4.17.21",
+    "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
-    "pinia-plugin-persistedstate": "^3.2.1",
+    "pinia-plugin-persist": "^1.0.0",
     "qs": "^6.11.2",
-    "vant": "^4.8.11",
-    "vconsole": "^3.15.1",
-    "vue": "^3.4.23",
-    "vue-router": "^4.3.2"
+    "vant": "^4.8.1",
+    "vue": "^3.3.13",
+    "vue-router": "4.2.5"
   },
   "devDependencies": {
-    "@commitlint/cli": "^16.2.3",
-    "@commitlint/config-conventional": "^16.2.1",
-    "@iconify-json/svg-spinners": "^1.1.2",
-    "@types/crypto-js": "^4.1.1",
-    "@types/lodash-es": "^4.17.8",
-    "@types/nprogress": "^0.2.0",
-    "@types/qs": "^6.9.7",
-    "@typescript-eslint/eslint-plugin": "^5.18.0",
-    "@typescript-eslint/parser": "^5.18.0",
-    "@unocss/preset-attributify": "^0.53.4",
-    "@unocss/preset-uno": "^0.53.4",
-    "@vitejs/plugin-vue": "^4.5.1",
-    "autoprefixer": "^10.4.4",
-    "chalk": "^5.2.0",
-    "cnjm-postcss-px-to-viewport": "^1.0.1",
+    "@antfu/eslint-config": "^2.6.4",
+    "@commitlint/cli": "^18.4.3",
+    "@commitlint/config-conventional": "^18.4.3",
+    "@iconify/json": "^2.2.188",
+    "@types/fs-extra": "^11.0.4",
+    "@types/mockjs": "^1.0.10",
+    "@types/node": "^20.10.5",
+    "@types/nprogress": "^0.2.3",
+    "@types/qs": "^6.9.11",
+    "@unocss/eslint-plugin": "^0.58.5",
+    "@unocss/preset-icons": "^0.58.5",
+    "@unocss/preset-rem-to-px": "^0.58.5",
+    "@unocss/transformer-directives": "^0.58.5",
+    "@unocss/transformer-variant-group": "^0.58.5",
+    "@vitejs/plugin-vue": "^5.0.0",
+    "autoprefixer": "^10.4.16",
     "cross-env": "^7.0.3",
+    "cz-git": "^1.8.0",
     "dotenv": "^16.3.1",
-    "eslint": "^8.12.0",
-    "eslint-config-prettier": "^8.8.0",
-    "eslint-define-config": "^1.23.0",
-    "eslint-plugin-prettier": "^4.2.1",
-    "eslint-plugin-vue": "^9.15.1",
+    "eslint": "^8.56.0",
+    "eslint-plugin-format": "^0.1.0",
     "esno": "^0.16.3",
-    "fs-extra": "^11.1.1",
-    "husky": "^7.0.4",
+    "fs-extra": "^11.2.0",
     "less": "^4.2.0",
-    "lint-staged": "^12.3.7",
-    "mockjs": "^1.1.0",
-    "postcss": "8.4.31",
-    "postcss-less": "^6.0.0",
-    "prettier": "^2.6.2",
-    "rollup": "^3.28.0",
-    "typescript": "^4.6.3",
-    "unocss": "^0.58.0",
-    "unplugin-auto-import": "^0.16.6",
-    "unplugin-auto-vconsole": "^0.0.4",
-    "unplugin-icons": "^0.16.5",
-    "unplugin-vue-components": "^0.25.1",
-    "vite": "^5.2.10",
+    "lint-staged": "^15.2.0",
+    "only-allow": "^1.2.1",
+    "picocolors": "^1.0.0",
+    "postcss": "^8.4.32",
+    "postcss-mobile-forever": "^4.0.0",
+    "rimraf": "^3.0.2",
+    "rollup": "^4.9.1",
+    "rollup-plugin-visualizer": "^5.11.0",
+    "simple-git-hooks": "^2.9.0",
+    "typescript": "^5.3.3",
+    "unocss": "^0.58.5",
+    "unplugin-auto-import": "^0.17.5",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.0.10",
     "vite-plugin-compression": "^0.5.1",
     "vite-plugin-html": "^3.2.2",
-    "vite-plugin-mock": "^2.9.6",
-    "vue-tsc": "^1.8.8"
+    "vite-plugin-mock": "^2.9.8",
+    "vite-plugin-svg-icons": "^2.0.1",
+    "vue-tsc": "^1.8.27"
   },
-  "repository": {
-    "type": "git",
-    "url": "git+https://github.com/cnjm-cli-template/vue_h5.git"
+  "simple-git-hooks": {
+    "pre-commit": "pnpm lint-staged",
+    "commit-msg": "npx --no-install commitlint --edit $1"
   },
-  "license": "MIT",
-  "bugs": {
-    "url": "https://github.com/cnjm-cli-template/vue_h5/issues"
-  },
-  "engines": {
-    "node": "^12 || >=14"
-  },
-  "homepage": "https://github.com/cnjm-cli-template/vue_h5",
   "lint-staged": {
-    "*.{js,jsx,ts,tsx}": [
-      "eslint --fix",
-      "prettier --config .prettierrc.js --write"
-    ],
-    "{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
-      "prettier --config .prettierrc.js --write--parser json"
-    ],
-    "package.json": [
-      "prettier --config .prettierrc.js --write"
-    ],
-    "*.vue": [
-      "eslint --fix",
-      "prettier --config .prettierrc.js --write"
-    ],
-    "*.{scss,less,styl,html}": [
-      "prettier --config .prettierrc.js --write"
-    ],
-    "*.md": [
-      "prettier --config .prettierrc.js --write"
-    ]
+    "*": "eslint --fix"
+  },
+  "config": {
+    "commitizen": {
+      "path": "node_modules/cz-git"
+    }
   }
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 518 - 425
pnpm-lock.yaml


+ 50 - 72
postcss.config.js

@@ -1,74 +1,52 @@
-// 为处理vite vant375尺寸 但自身项目750等其他尺寸配置问题有如下两个方案  优点缺点都很明显,具体怎么用,见人见智了
-// 1.https://blog.csdn.net/weixin_42998707/article/details/124150578  修改postcss-px-to-viewport插件源码
-// 2.https://github.com/vitejs/vite/issues/4653  多个配置,这将导致插件处理两次,文件最后注释处
+/**
+ * 由于在vite中用 module.exports = (param) => {} 这种方式导出postcss配置时,param中没有文件相关信息,
+ * 同时postcss-px-to-viewport也没有提供类似postcss-pxtorem中 rootValue({ file }) {} 的方法,无法根据文件路径动态设置viewportWidth
+ * 所以只能通过多次px2viewport()处理不同文件的hack方式来设置viewportWidth
+ *
+ * postcss-px-to-viewport v1.1.1不支持include配置项,v1.2.0开始加入include,但是并没有发布到npm仓库
+ * 如在v1.1.1中使用include,无效果,并且执行多次导致转换混乱
+ *
+ * postcss-px-to-viewport 不支持 postcss 8.x,而vite内置postcss 8.x,所以使用postcss-px-to-viewport会抛出警告
+ * 改用postcss-px-to-viewport-8-plugin替代
+ */
 
-const path = require('path')
-module.exports = () => {
-  return {
-    plugins: {
-      autoprefixer: {
-        overrideBrowserslist: ['> 1%', 'last 2 versions']
-      },
-      'cnjm-postcss-px-to-viewport': {
-        unitToConvert: 'px', // 要转化的单位
-        viewportWidth: 750, // UI设计稿的宽度
-        unitPrecision: 6, // 转换后的精度,即小数点位数
-        propList: ['*'], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
-        viewportUnit: 'vw', // 指定需要转换成的视窗单位,默认vw
-        fontViewportUnit: 'vw', // 指定字体需要转换成的视窗单位,默认vw
-        selectorBlackList: ['ignore'], // 指定不转换为视窗单位的类名,
-        minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
-        mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
-        replace: true, // 是否转换后直接更换属性值
-        exclude: [], // 设置忽略文件,用正则做目录名匹配
-        landscape: false, // 是否处理横屏情况
-        // 如果没有使用其他的尺寸来设计,下面这个方法可以不需要,比如vant是375的
-        customFun: ({ file }) => {
-          // 这个自定义的方法是针对处理vant组件下的设计稿为375问题
-          const designWidth = path.join(file).includes(path.join('node_modules', 'vant')) ? 375 : 750
-          return designWidth
-        }
-      }
-    }
-  }
+// FIXME: 升级 vite5 后控制台警告:The CJS build of Vite's Node API is deprecated.
+// 将 "type": "module" 添加到 package.json 后,
+// 所有*.js文件现在都解释为 ESM,并且需要使用 ESM 语法。您可以使用扩展名重命名文件.cjs来继续使用 CJS。
+// require 是cjs 语法
+
+import autoprefixer from 'autoprefixer'
+import viewport from 'postcss-mobile-forever'
+
+const baseViewportOpts = {
+  appSelector: '#app', // 根元素选择器,用于设置桌面端和横屏时的居中样式
+  viewportWidth: 375, // 设计稿的视口宽度,可传递函数动态生成视图宽度
+  unitPrecision: 3, // 单位转换后保留的精度(很多时候无法整除)
+  maxDisplayWidth: 600, // 桌面端最大展示宽度
+  propList: [
+    '*',
+    // '!font-size'
+  ],
+  // 能转化为vw的属性列表,!font-size表示font-size后面的单位不会被转换
+  // 指定不转换为视口单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
+  // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
+  // 下面配置表示类名中含有'keep-px'以及'.ignore'类都不会被转换
+  selectorBlackList: ['.ignore', 'keep-px'],
+  // 下面配置表示属性值包含 '1px solid' 的内容不会转换
+  valueBlackList: ['1px solid'],
+  // exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件
+  // include: [/src/], // 如果设置了include,那将只有匹配到的文件才会被转换
+  mobileUnit: 'vw', // 指定需要转换成的视口单位,建议使用 vw
+  rootContainingBlockSelectorList: ['van-popup--bottom'], // 指定包含块是根包含块的选择器,这种选择器的定位通常是 `fixed`,但是选择器内没有 `position: fixed`
+}
+
+export default {
+  plugins: [
+    autoprefixer(),
+    viewport({
+      ...baseViewportOpts,
+      // 只将 vant 转为 375 设计稿的 viewport,其它样式的视图宽度为 750
+      // viewportWidth: file => (file.includes('node_modules/vant/') ? 375 : 750),
+    }),
+  ],
 }
-// const path = require("path");
-// const px2viewport = require("postcss-px-to-viewport");
-// const autoprefixer = require("autoprefixer");
-// module.exports = () => {
-//   return {
-//     plugins: [
-//       autoprefixer({
-//         overrideBrowserslist: ["Android 4.1", "iOS 7.1", "Chrome > 31", "ff > 31", "ie >= 8"],
-//       }),
-//       px2viewport({
-//         unitToConvert: "px", // 要转化的单位
-//         viewportWidth: 375, // UI设计稿的宽度
-//         unitPrecision: 6, // 转换后的精度,即小数点位数
-//         propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
-//         viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
-//         fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
-//         selectorBlackList: ["ignore"], // 指定不转换为视窗单位的类名,
-//         minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
-//         mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
-//         replace: true, // 是否转换后直接更换属性值
-//         exclude: [/^(?!.*node_modules\/vant)/], // 设置忽略文件,用正则做目录名匹配
-//         landscape: false, // 是否处理横屏情况
-//       }),
-//       px2viewport({
-//         unitToConvert: "px", // 要转化的单位
-//         viewportWidth: 750, // UI设计稿的宽度
-//         unitPrecision: 6, // 转换后的精度,即小数点位数
-//         propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
-//         viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
-//         fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw
-//         selectorBlackList: ["ignore"], // 指定不转换为视窗单位的类名,
-//         minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
-//         mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
-//         replace: true, // 是否转换后直接更换属性值
-//         exclude: [/node_modules\/vant/i], // 设置忽略文件,用正则做目录名匹配
-//         landscape: false, // 是否处理横屏情况
-//       }),
-//     ],
-//   };
-// };

BIN=BIN
public/favicon.ico


+ 38 - 0
public/logo.svg

@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  viewBox="0 0 87 100"
+>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient23" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#AAE8FF" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#BFDCFF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient23)" d="M 86.45 25.05Q 86.05 25.9 85.5 26.25L 44.9 49.7 44.9 51.75 41.9 51.75 41.9 49.7 1.25 26.25Q 0.9 26.05 0.65 25.55L 0.45 25.1 0.45 75.2 0.5 75.75Q 0.7 76.35 1.1 76.55L 41.75 100 41.85 59.85 44.95 59.85 45.05 100 85.65 76.55Q 86.15 76.2 86.35 75.7L 86.45 75.2 86.45 25.05 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient24" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#C2FFF8" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#AADAFF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient24)" d="M 86.6 25.1Q 86.6 24.4 85.8 23.95L 45.15 0.5Q 44.35 0 43.15 0 42 0 41.2 0.5L 0.85 23.95Q 0 24.4 0 25.1 0 25.75 0.85 26.25L 41.45 49.7Q 42.3 50.15 43.45 50.15 44.65 50.15 45.45 49.7L 85.8 26.25Q 86.6 25.75 86.6 25.1 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient25" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#4573FF" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#005FB9" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient25)" d="M 67.9 35.85Q 67.8 36.35 67.5 36.55L 43.25 50.2 43.25 79.3 67.6 65.3Q 67.95 65.1 67.9 64.5L 67.9 35.85 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient26" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient26)" d="M 19.2 36.55Q 19 36.4 18.9 36.1L 18.8 35.85 18.85 64.85Q 18.9 65.15 19.15 65.3L 43.45 79.3 43.45 50.2 19.2 36.55 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient27" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient27)" d="M 67.6 35.2L 44.4 21.8Q 43.9 21.55 43.25 21.55 42.55 21.55 42.1 21.8L 19.05 35.2Q 18.6 35.5 18.6 35.9 18.6 36.25 19.05 36.55L 42.25 49.95Q 42.7 50.2 43.4 50.2 44.1 50.2 44.55 49.95L 67.6 36.55Q 68.1 36.25 68.05 35.9 68.05 35.5 67.6 35.2 Z"></path>
+</svg>

+ 71 - 20
src/App.vue

@@ -1,26 +1,77 @@
+<template>
+  <vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()">
+    <routerView v-slot="{ Component }">
+      <div class="absolute bottom-0 top-0 w-full overflow-hidden">
+        <transition :name="getTransitionName" mode="out-in" appear>
+          <keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
+            <component :is="Component" />
+          </keep-alive>
+        </transition>
+      </div>
+    </routerView>
+  </vanConfigProvider>
+</template>
+
 <script setup lang="ts">
-  import { usePageTitle } from '/@/hooks/web/usePageTitle'
-  usePageTitle()
-  const themeVars = {
-    // tab的底部横线颜色
-    tabsBottomBarColor: 'rgb(33, 166, 117)',
-    rateIconFullColor: '#07c160',
-    sliderBarHeight: '4px',
-    sliderButtonWidth: '20px',
-    sliderButtonHeight: '20px',
-    sliderActiveBackgroundColor: '#07c160'
-    // toastBackground: "rgba(0, 0, 0,0.7)",
-    // buttonPrimaryBorderColor: "#07c160",
-    // buttonPrimaryBackgroundColor: "#07c160",
+import { darken, lighten } from '@/utils'
+import { useRouteStore } from '@/store/modules/route'
+import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
+
+const routeStore = useRouteStore()
+const { getDarkMode, getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting()
+
+// 需要缓存的路由组件
+const keepAliveComponents = computed(() => routeStore.keepAliveComponents)
+
+function getThemeVars() {
+  const appTheme = unref(getAppTheme)
+  const darkenStr = darken(appTheme, 25)
+  const lightenStr = lighten(appTheme, 10)
+
+  return {
+    actionSheetCancelTextColor: appTheme,
+    buttonPrimaryBackground: appTheme,
+    buttonPrimaryBorderColor: appTheme,
+    radioCheckedIconColor: appTheme,
+    sliderActiveBackground: appTheme,
+    cascaderActiveColor: appTheme,
+    checkboxCheckedIconColor: appTheme,
+    numberKeyboardButtonBackground: appTheme,
+    pickerLoadingIconColor: appTheme,
+    calendarRangeEdgeBackground: appTheme,
+    calendarRangeMiddleColor: appTheme,
+    calendarSelectedDayBackground: appTheme,
+    stepperButtonRoundThemeColor: appTheme,
+    switchOnBackground: appTheme,
+    dialogConfirmButtonTextColor: appTheme,
+    dropdownMenuOptionActiveColor: appTheme,
+    dropdownMenuTitleActiveTextColor: appTheme,
+    notifyPrimaryBackground: appTheme,
+    circleColor: appTheme,
+    noticeBarBackground: lightenStr,
+    noticeBarTextColor: darkenStr,
+    progressColor: appTheme,
+    progressPivotBackground: appTheme,
+    stepActiveColor: appTheme,
+    stepFinishLineColor: appTheme,
+    swipeIndicatorActiveBackground: appTheme,
+    tagPrimaryColor: appTheme,
+    navBarIconColor: appTheme,
+    navBarTextColor: appTheme,
+    paginationItemDefaultColor: appTheme,
+    sidebarSelectedBorderColor: appTheme,
+    tabsDefaultColor: appTheme,
+    tabsBottomBarColor: appTheme,
+    tabbarItemActiveColor: appTheme,
+    treeSelectItemActiveColor: appTheme,
   }
-</script>
+}
 
-<template>
-  <van-config-provider :theme-vars="themeVars"><RouterView /></van-config-provider>
-</template>
+const getTransitionName = computed(() => {
+  return unref(getIsPageAnimate) ? unref(getPageAnimateType) : undefined
+})
+</script>
 
 <style lang="less">
-  :root {
-    --van-button-primary-background-color: red;
-  }
+  @import './styles/index.less';
 </style>

+ 0 - 12
src/api/example/hooks.ts

@@ -1,12 +0,0 @@
-import { BasicFetchResult } from '../model/base.model'
-import { LoadListParams } from './model/hooks.model'
-import { defHttp } from '/@/utils/axios/index'
-
-enum Api {
-  LOAD = '/mock/example/loadList'
-}
-
-/**
- * @description: 加载列表hooks示例
- */
-export const loadList = (params: LoadListParams) => defHttp.post<BasicFetchResult<string>>({ url: Api.LOAD, params })

+ 0 - 6
src/api/example/model/hooks.model.ts

@@ -1,6 +0,0 @@
-import { BasicPageParams } from '../../model/base.model'
-
-export type LoadListFormState = {
-  name: string
-}
-export type LoadListParams = BasicPageParams & LoadListFormState

+ 0 - 9
src/api/model/base.model.ts

@@ -1,9 +0,0 @@
-export interface BasicPageParams {
-  pageNum: number
-  pageSize: number
-}
-
-export interface BasicFetchResult<T> {
-  items: T[]
-  total: number
-}

+ 0 - 22
src/api/system/model/user.model.ts

@@ -1,22 +0,0 @@
-export interface LoginParams {
-  account: string
-  password: string
-}
-
-export interface LoginResult {
-  userId: string | number
-  token: string
-}
-
-export interface RoleInfo {
-  roleName: string
-  value: string
-}
-export interface GetUserInfoModel {
-  // 用户id
-  userId: string | number
-  // 用户名
-  userName: string
-  // 角色列表
-  roles?: RoleInfo[]
-}

+ 44 - 12
src/api/system/user.ts

@@ -1,27 +1,59 @@
-import { GetUserInfoModel, LoginParams, LoginResult } from './model/user.model'
-import { ErrorMessageMode } from '/#/axios'
-import { defHttp } from '/@/utils/axios'
+import { http } from '@/utils/http/axios'
 
-enum Api {
-  Login = '/mock/user/login',
-  GetUserInfo = '/mock/user/getUserInfo',
-  Logout = '/mock/user/logout'
+export interface BasicResponseModel<T = any> {
+  code: number
+  message: string
+  result: T
 }
 
 /**
  * @description: 用户登录
  */
-export const loginApi = (params: LoginParams, mode: ErrorMessageMode = 'dialog') => defHttp.post<LoginResult>({ url: Api.Login, params }, { errorMessageMode: mode })
+export function login(params: any) {
+  return http.request<BasicResponseModel>(
+    {
+      url: '/login',
+      method: 'POST',
+      params,
+    },
+    {
+      isTransformResponse: false,
+    },
+  )
+}
 
 /**
  * @description: 获取用户信息
  */
-export const getUserInfo = () => {
-  return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' })
+export function getUserInfo() {
+  return http.request({
+    url: '/getUserInfo',
+    method: 'get',
+  })
 }
+
 /**
  * @description: 用户登出
  */
-export const doLogout = () => {
-  return defHttp.get({ url: Api.Logout }, {})
+export function doLogout() {
+  return http.request({
+    url: '/logout',
+    method: 'POST',
+  })
+}
+
+/**
+ * @description: 用户修改密码
+ */
+export function changePassword(params: any, uid: any) {
+  return http.request(
+    {
+      url: `/user/u${uid}/changepw`,
+      method: 'POST',
+      params,
+    },
+    {
+      isTransformResponse: false,
+    },
+  )
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 1
src/assets/icons/about/box.svg


+ 0 - 5
src/assets/icons/about/car.svg

@@ -1,5 +0,0 @@
-<svg fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"
-  stroke="currentColor">
-  <path
-    d="M17 10a4 4 0 0 0-4-4h-2a4 4 0 0 0-4 4m5-4v4M3 16h2m4 0h6m4 0h2v-3a3 3 0 0 0-3-3h-12a3 3 0 0 0-3 3v3M9 16a2 2 0 1 1-4 0a2 2 0 0 1 4 0zM19 16a2 2 0 1 1-4 0a2 2 0 0 1 4 0z" />
-</svg>

+ 0 - 3
src/assets/icons/about/logout.svg

@@ -1,3 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1653881144049" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4823" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css">@font-face { font-family: element-icons; src: url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.woff") format("woff"), url("chrome-extension://moombeodfomdpjnpocobemoiaemednkg/fonts/element-icons.ttf ") format("truetype"); }
-@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
-</style></defs><path d="M689.664 172.992l0 133.781333c89.28 58.176 148.394667 158.698667 148.394667 273.216 0 180.032-145.984 325.994667-326.058667 325.994667-180.032 0-326.016-145.962667-326.016-325.994667 0-101.781333 46.634667-192.618667 119.722667-252.437333L305.706667 186.773333C164.373333 261.098667 67.968 409.216 67.968 579.946667 67.968 825.173333 266.794667 1024 512.042667 1024c245.205333 0 443.989333-198.826667 443.989333-444.053333C956.010667 397.888 846.464 241.536 689.664 172.992z" p-id="4824" fill="#21a675"></path><path d="M577.344 459.989333c0 28.693333-29.248 51.989333-65.344 51.989333l0 0c-36.053333 0-65.322667-23.274667-65.322667-51.989333L446.677333 51.989333C446.677333 23.274667 475.946667 0 512 0l0 0c36.096 0 65.344 23.274667 65.344 51.989333L577.344 459.989333z" p-id="4825" fill="#21a675"></path></svg>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 32 - 0
src/assets/icons/exception/403.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 32 - 0
src/assets/icons/exception/404.svg


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 36 - 0
src/assets/icons/exception/500.svg


+ 38 - 0
src/assets/icons/logo.svg

@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  viewBox="0 0 87 100"
+>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient23" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#AAE8FF" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#BFDCFF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient23)" d="M 86.45 25.05Q 86.05 25.9 85.5 26.25L 44.9 49.7 44.9 51.75 41.9 51.75 41.9 49.7 1.25 26.25Q 0.9 26.05 0.65 25.55L 0.45 25.1 0.45 75.2 0.5 75.75Q 0.7 76.35 1.1 76.55L 41.75 100 41.85 59.85 44.95 59.85 45.05 100 85.65 76.55Q 86.15 76.2 86.35 75.7L 86.45 75.2 86.45 25.05 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient24" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#C2FFF8" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#AADAFF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient24)" d="M 86.6 25.1Q 86.6 24.4 85.8 23.95L 45.15 0.5Q 44.35 0 43.15 0 42 0 41.2 0.5L 0.85 23.95Q 0 24.4 0 25.1 0 25.75 0.85 26.25L 41.45 49.7Q 42.3 50.15 43.45 50.15 44.65 50.15 45.45 49.7L 85.8 26.25Q 86.6 25.75 86.6 25.1 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient25" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#4573FF" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#005FB9" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient25)" d="M 67.9 35.85Q 67.8 36.35 67.5 36.55L 43.25 50.2 43.25 79.3 67.6 65.3Q 67.95 65.1 67.9 64.5L 67.9 35.85 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient26" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient26)" d="M 19.2 36.55Q 19 36.4 18.9 36.1L 18.8 35.85 18.85 64.85Q 18.9 65.15 19.15 65.3L 43.45 79.3 43.45 50.2 19.2 36.55 Z"></path>
+	<defs>
+		<linearGradient x1="0" y1="0" x2="0" y2="1" id="gradient27" gradientTransform="rotate(-90 .5 .5)">
+			<stop offset="0" stop-color="#2EFFF8" stop-opacity="1"></stop>
+			<stop offset="1" stop-color="#4599FF" stop-opacity="1"></stop>
+		</linearGradient>
+	</defs>
+	<path fill="url(#gradient27)" d="M 67.6 35.2L 44.4 21.8Q 43.9 21.55 43.25 21.55 42.55 21.55 42.1 21.8L 19.05 35.2Q 18.6 35.5 18.6 35.9 18.6 36.25 19.05 36.55L 42.25 49.95Q 42.7 50.2 43.4 50.2 44.1 50.2 44.55 49.95L 67.6 36.55Q 68.1 36.25 68.05 35.9 68.05 35.5 67.6 35.2 Z"></path>
+</svg>

BIN=BIN
src/assets/images/bg-main.png


BIN=BIN
src/assets/images/task/1.png


+ 0 - 0
src/assets/images/task/2.png


+ 0 - 0
src/assets/images/task/3.png


+ 0 - 0
src/assets/images/task/4.png


+ 0 - 0
src/assets/images/task/5.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/bg_circle.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/bg_shape.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/car.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/cat.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/factory.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/flower.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/forest.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/hospital.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/house.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/icon_center.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/icon_pointer.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/icon_pointer_red.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/lakes.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/massif.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/stop.png


BIN=BIN
src/assets/images/task/spatialOrientationAbility/trafficLight.png


BIN=BIN
src/assets/logo.png


BIN=BIN
src/assets/tabbar/about.png


BIN=BIN
src/assets/tabbar/about_n.png


BIN=BIN
src/assets/tabbar/comp.png


BIN=BIN
src/assets/tabbar/comp_n.png


BIN=BIN
src/assets/tabbar/home.png


BIN=BIN
src/assets/tabbar/home_n.png


+ 51 - 0
src/components/Logo.vue

@@ -0,0 +1,51 @@
+<template>
+  <div>
+    <SvgIcon v-if="designStore.getAppTheme === defaultAppTheme" class="!h-30 !w-30" name="logo" />
+    <svg v-else xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 87 100">
+      <defs>
+        <linearGradient id="gradient23" x1="0" y1="0" x2="0" y2="1" gradientTransform="rotate(-90 .5 .5)">
+          <stop offset="0" :stop-color="hexToRgba(designStore.getAppTheme, 0.3)" stop-opacity="1" />
+          <stop offset="1" :stop-color="hexToRgba(designStore.getAppTheme, 0.8)" stop-opacity="1" />
+        </linearGradient>
+      </defs>
+      <path fill="url(#gradient23)" d="M 86.45 25.05Q 86.05 25.9 85.5 26.25L 44.9 49.7 44.9 51.75 41.9 51.75 41.9 49.7 1.25 26.25Q 0.9 26.05 0.65 25.55L 0.45 25.1 0.45 75.2 0.5 75.75Q 0.7 76.35 1.1 76.55L 41.75 100 41.85 59.85 44.95 59.85 45.05 100 85.65 76.55Q 86.15 76.2 86.35 75.7L 86.45 75.2 86.45 25.05 Z" />
+      <defs>
+        <linearGradient id="gradient24" x1="0" y1="0" x2="0" y2="1" gradientTransform="rotate(-90 .5 .5)">
+          <stop offset="0" :stop-color="hexToRgba(designStore.getAppTheme, 0.8)" stop-opacity="1" />
+          <stop offset="1" :stop-color="hexToRgba(designStore.getAppTheme, 0.2)" stop-opacity="1" />
+        </linearGradient>
+      </defs>
+      <path fill="url(#gradient24)" d="M 86.6 25.1Q 86.6 24.4 85.8 23.95L 45.15 0.5Q 44.35 0 43.15 0 42 0 41.2 0.5L 0.85 23.95Q 0 24.4 0 25.1 0 25.75 0.85 26.25L 41.45 49.7Q 42.3 50.15 43.45 50.15 44.65 50.15 45.45 49.7L 85.8 26.25Q 86.6 25.75 86.6 25.1 Z" />
+      <defs>
+        <linearGradient id="gradient25" x1="0" y1="0" x2="0" y2="1" gradientTransform="rotate(-90 .5 .5)">
+          <stop offset="0" :stop-color="hexToRgba(designStore.getAppTheme, 0.4)" stop-opacity="1" />
+          <stop offset="1" :stop-color="hexToRgba(designStore.getAppTheme, 0.9)" stop-opacity="1" />
+        </linearGradient>
+      </defs>
+      <path fill="url(#gradient25)" d="M 67.9 35.85Q 67.8 36.35 67.5 36.55L 43.25 50.2 43.25 79.3 67.6 65.3Q 67.95 65.1 67.9 64.5L 67.9 35.85 Z" />
+      <defs>
+        <linearGradient id="gradient26" x1="0" y1="0" x2="0" y2="1" gradientTransform="rotate(-90 .5 .5)">
+          <stop offset="0" :stop-color="hexToRgba(designStore.getAppTheme, 0.8)" stop-opacity="1" />
+          <stop offset="1" :stop-color="hexToRgba(designStore.getAppTheme, 0.4)" stop-opacity="1" />
+        </linearGradient>
+      </defs>
+      <path fill="url(#gradient26)" d="M 19.2 36.55Q 19 36.4 18.9 36.1L 18.8 35.85 18.85 64.85Q 18.9 65.15 19.15 65.3L 43.45 79.3 43.45 50.2 19.2 36.55 Z" />
+      <defs>
+        <linearGradient id="gradient27" x1="0" y1="0" x2="0" y2="1" gradientTransform="rotate(-90 .5 .5)">
+          <stop offset="0" :stop-color="hexToRgba(designStore.getAppTheme, 0.9)" stop-opacity="1" />
+          <stop offset="1" :stop-color="hexToRgba(designStore.getAppTheme, 0.3)" stop-opacity="1" />
+        </linearGradient>
+      </defs>
+      <path fill="url(#gradient27)" d="M 67.6 35.2L 44.4 21.8Q 43.9 21.55 43.25 21.55 42.55 21.55 42.1 21.8L 19.05 35.2Q 18.6 35.5 18.6 35.9 18.6 36.25 19.05 36.55L 42.25 49.95Q 42.7 50.2 43.4 50.2 44.1 50.2 44.55 49.95L 67.6 36.55Q 68.1 36.25 68.05 35.9 68.05 35.5 67.6 35.2 Z" />
+    </svg>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useDesignSettingStore } from '@/store/modules/designSetting'
+import { hexToRgba } from '@/utils'
+import { appThemeList } from '@/settings/designSetting'
+
+const defaultAppTheme = appThemeList[0]
+const designStore = useDesignSettingStore()
+</script>

+ 48 - 0
src/components/SvgIcon.vue

@@ -0,0 +1,48 @@
+<template>
+  <svg :style="getStyle" aria-hidden="true">
+    <use :xlink:href="symbolId" :fill="color" />
+  </svg>
+</template>
+
+<script lang="ts">
+import type { CSSProperties } from 'vue'
+
+export default defineComponent({
+  name: 'SvgIcon',
+  props: {
+    prefix: {
+      type: String,
+      default: 'icon',
+    },
+    name: {
+      type: String,
+      required: true,
+    },
+    size: {
+      type: [Number, String],
+      default: 16,
+    },
+    color: {
+      type: String,
+      default: '#333',
+    },
+  },
+  setup(props) {
+    const symbolId = computed(() => `#${props.prefix}-${props.name}`)
+
+    const getStyle = computed((): CSSProperties => {
+      const { size } = props
+      let s = `${size}`
+      s = `${s.replace('px', '')}px`
+      return {
+        width: s,
+        height: s,
+      }
+    })
+
+    return { symbolId, getStyle }
+  },
+})
+</script>
+
+<style scoped lang="less"></style>

+ 0 - 60
src/components/resolver/CountDown/index.vue

@@ -1,60 +0,0 @@
-<template>
-  <section class="van-count-down-container absolute top-[50%] left-[50%] translate-[-50%] w-[90%] h-[120px] flex-center text-[68px]">
-    {{ countDownStr }}<span v-if="showSpan" class="text-[42px] ml-[4px] mt-[20px]">s</span>
-  </section>
-</template>
-
-<script setup lang="ts">
-  /*
-   * 组件名: CountDown
-   * 组件用途: 倒计时组件
-   * 创建日期: 2024/8/16
-   * 编写者: JutarryWu
-   */
-  const props = defineProps({
-    time: {
-      type: Number,
-      default: 0
-    },
-    text: {
-      type: String,
-      default: '测试训练即将开始!'
-    }
-  })
-  const emit = defineEmits(['endCountDown'])
-  let countTimer = ref<ReturnType<typeof setInterval> | null>(null)
-
-  const secondNum = ref(0)
-  const countDownStr = ref('')
-  const showSpan = ref(false)
-
-  async function exec() {
-    secondNum.value = props.time
-    countDownStr.value = props.text
-
-    countTimer.value = setInterval(() => {
-      countDownStr.value = `${secondNum.value}`
-      showSpan.value = true
-      secondNum.value--
-      if (secondNum.value < 0) {
-        clearInterval(Number(countTimer.value))
-        countTimer.value = null
-        showSpan.value = false
-
-        emit('endCountDown')
-      }
-    }, 1000)
-  }
-
-  onMounted(() => {
-    exec()
-  })
-
-  onBeforeUnmount(() => {
-    clearInterval(Number(countTimer.value))
-    countTimer.value = null
-    showSpan.value = false
-  })
-</script>
-
-<style scoped lang="scss"></style>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio