瀏覽代碼

feat(认知任务): init

JutarryWu 6 月之前
父節點
當前提交
f215a71e8a
共有 100 個文件被更改,包括 2180 次插入2318 次删除
  1. 26 31
      .editorconfig
  2. 3 14
      .env
  3. 3 31
      .env.development
  4. 2 28
      .env.production
  5. 5 6
      .gitignore
  6. 1 0
      .husky/commit-msg
  7. 1 0
      .husky/pre-commit
  8. 3 0
      .npmrc
  9. 9 0
      .vscode/extensions.json
  10. 72 0
      .vscode/settings.json
  11. 1 1
      LICENSE
  12. 182 251
      README.md
  13. 229 0
      README.zh-CN.md
  14. 0 6
      build/constant.ts
  15. 0 9
      build/getConfigFileName.ts
  16. 0 45
      build/script/buildConf.ts
  17. 0 24
      build/script/postBuild.ts
  18. 0 75
      build/utils.ts
  19. 119 0
      build/vite/index.ts
  20. 24 0
      build/vite/optimize.ts
  21. 0 35
      build/vite/plugin/compress.ts
  22. 0 46
      build/vite/plugin/html.ts
  23. 0 78
      build/vite/plugin/index.ts
  24. 0 19
      build/vite/plugin/mock.ts
  25. 0 19
      build/vite/plugin/svgSprite.ts
  26. 0 18
      build/vite/plugin/visualizer.ts
  27. 0 52
      build/vite/proxy.ts
  28. 44 0
      build/vite/vconsole.ts
  29. 0 155
      commitlint.config.cjs
  30. 0 29
      components.d.ts
  31. 8 53
      eslint.config.js
  32. 19 120
      index.html
  33. 0 18
      mock/_createProductionServer.ts
  34. 0 77
      mock/_util.ts
  35. 20 0
      mock/data.ts
  36. 5 0
      mock/index.ts
  37. 13 0
      mock/modules/prose.mock.ts
  38. 0 95
      mock/user/user.ts
  39. 19 0
      mock/util.ts
  40. 12 0
      netlify.toml
  41. 71 94
      package.json
  42. 750 470
      pnpm-lock.yaml
  43. 0 52
      postcss.config.js
  44. 4 0
      public/favicon-dark.svg
  45. 4 0
      public/favicon.svg
  46. 0 38
      public/logo.svg
  47. 二進制
      public/pwa-192x192.png
  48. 二進制
      public/pwa-512x512.png
  49. 1 0
      public/safari-pinned-tab.svg
  50. 18 0
      scripts/verifyCommit.js
  51. 48 68
      src/App.vue
  52. 5 0
      src/api/index.ts
  53. 0 59
      src/api/system/user.ts
  54. 16 0
      src/api/typing.ts
  55. 1 0
      src/assets/icons/about/box.svg
  56. 5 0
      src/assets/icons/about/car.svg
  57. 3 0
      src/assets/icons/about/logout.svg
  58. 0 32
      src/assets/icons/exception/403.svg
  59. 0 32
      src/assets/icons/exception/404.svg
  60. 0 36
      src/assets/icons/exception/500.svg
  61. 0 38
      src/assets/icons/logo.svg
  62. 二進制
      src/assets/images/bg-main.png
  63. 二進制
      src/assets/images/bg-soa.png
  64. 二進制
      src/assets/images/task/1.png
  65. 0 0
      src/assets/images/task/2.png
  66. 0 0
      src/assets/images/task/3.png
  67. 0 0
      src/assets/images/task/4.png
  68. 0 0
      src/assets/images/task/5.png
  69. 二進制
      src/assets/images/task/spatialOrientationAbility/bg_circle.png
  70. 二進制
      src/assets/images/task/spatialOrientationAbility/bg_shape.png
  71. 二進制
      src/assets/images/task/spatialOrientationAbility/car.png
  72. 二進制
      src/assets/images/task/spatialOrientationAbility/cat.png
  73. 二進制
      src/assets/images/task/spatialOrientationAbility/factory.png
  74. 二進制
      src/assets/images/task/spatialOrientationAbility/flower.png
  75. 二進制
      src/assets/images/task/spatialOrientationAbility/forest.png
  76. 二進制
      src/assets/images/task/spatialOrientationAbility/hospital.png
  77. 二進制
      src/assets/images/task/spatialOrientationAbility/house.png
  78. 二進制
      src/assets/images/task/spatialOrientationAbility/icon_center.png
  79. 二進制
      src/assets/images/task/spatialOrientationAbility/icon_pointer.png
  80. 二進制
      src/assets/images/task/spatialOrientationAbility/icon_pointer_red.png
  81. 二進制
      src/assets/images/task/spatialOrientationAbility/lakes.png
  82. 二進制
      src/assets/images/task/spatialOrientationAbility/massif.png
  83. 二進制
      src/assets/images/task/spatialOrientationAbility/stop.png
  84. 二進制
      src/assets/images/task/spatialOrientationAbility/trafficLight.png
  85. 二進制
      src/assets/logo.png
  86. 二進制
      src/assets/tabbar/about.png
  87. 二進制
      src/assets/tabbar/about_n.png
  88. 二進制
      src/assets/tabbar/comp.png
  89. 二進制
      src/assets/tabbar/comp_n.png
  90. 二進制
      src/assets/tabbar/home.png
  91. 二進制
      src/assets/tabbar/home_n.png
  92. 29 13
      src/auto-imports.d.ts
  93. 34 0
      src/components.d.ts
  94. 179 0
      src/components/Chart/dark.ts
  95. 65 0
      src/components/Chart/index.vue
  96. 23 0
      src/components/Container.vue
  97. 71 0
      src/components/CountDown/index.vue
  98. 0 51
      src/components/Logo.vue
  99. 30 0
      src/components/NavBar.vue
  100. 3 0
      src/components/README.md

+ 26 - 31
.editorconfig

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

+ 3 - 14
.env

@@ -1,14 +1,3 @@
-# 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
+VITE_APP_PUBLIC_PATH=/
+VITE_APP_PREVIEW=true
+VITE_APP_API_BASE_URL=/api

+ 3 - 31
.env.development

@@ -1,31 +1,3 @@
-# 只在开发模式中被载入
-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
-
-
-
+NODE_ENV=development
+VITE_APP_PREVIEW=true
+VITE_APP_API_BASE_URL=/api

+ 2 - 28
.env.production

@@ -1,28 +1,2 @@
-# 是否开启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
+VITE_APP_PREVIEW=false
+VITE_APP_API_BASE_URL=https://easyapi.devv.zone/api

+ 5 - 6
.gitignore

@@ -8,19 +8,18 @@ pnpm-debug.log*
 lerna-debug.log*
 
 node_modules
-/dist
+dist
 dist-ssr
 *.local
+stats.html
 
 # Editor directories and files
-.vscode/*
-!.vscode/extensions.json
 .idea
-.DS_Store
 *.suo
 *.ntvs*
 *.njsproj
 *.sln
 *.sw?
-/components.d.ts
-/components.d.ts
+
+# Misc
+.DS_Store

+ 1 - 0
.husky/commit-msg

@@ -0,0 +1 @@
+node scripts/verifyCommit.js

+ 1 - 0
.husky/pre-commit

@@ -0,0 +1 @@
+pnpm lint

+ 3 - 0
.npmrc

@@ -0,0 +1,3 @@
+shamefully-hoist=true
+strict-peer-dependencies=false
+package-manager-strict=false

+ 9 - 0
.vscode/extensions.json

@@ -0,0 +1,9 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "antfu.unocss",
+    "simonhe.common-intellisense",
+    "lokalise.i18n-ally",
+    "antfu.iconify"
+  ]
+}

+ 72 - 0
.vscode/settings.json

@@ -0,0 +1,72 @@
+{
+  // Enable the ESlint flat config support
+  "eslint.useFlatConfig": true,
+
+  // Disable the default formatter, use eslint instead
+  "prettier.enable": false,
+  "editor.formatOnSave": false,
+
+  // Auto fix
+  "editor.codeActionsOnSave": {
+    "source.fixAll": "explicit",
+    "source.organizeImports": "never"
+  },
+
+  // Silent the stylistic rules in you IDE, but still auto fix them
+  "eslint.rules.customizations": [
+    { "rule": "style/*", "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",
+    "xml",
+    "gql",
+    "graphql",
+    "astro",
+    "css",
+    "less",
+    "scss",
+    "pcss",
+    "postcss"
+  ],
+
+  // Specify the UI library you need to prompt
+  "common-intellisense.showSlots": false,
+  "common-intellisense.ui": [
+    "vant4"
+  ],
+
+  // Configuration of i18n i18n-ally
+  "i18n-ally.enabledParsers": ["json"],
+  "i18n-ally.displayLanguage": "zh-CN",
+  "i18n-ally.localesPaths": [
+    "src/locales"
+  ],
+  "i18n-ally.keystyle": "nested",
+
+  // Markdownlint rules
+  "markdownlint.config": {
+    "default": true,
+    "MD033": false,
+    "MD041": false
+  }
+}

+ 1 - 1
LICENSE

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

+ 182 - 251
README.md

@@ -1,298 +1,229 @@
 <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" />
+<img src="https://cdn.jsdelivr.net/gh/easy-temps/easy-static/cover.png" alt="cover" />
+
+<h1 align="center">vue3-vant-mobile</h1>
+
+English / [简体中文](./README.zh-CN.md)
+
+An mobile web apps template based on the Vue 3 ecosystem.
+
+一个基于 Vue 3 生态系统的移动 web 应用模板,帮助你快速完成业务开发。
+
+<p>
+<img src="https://img.shields.io/github/license/easy-temps/vue3-vant-mobile" alt="license" />
+<img src="https://img.shields.io/github/package-json/v/easy-temps/vue3-vant-mobile" alt="version" />
+<img src="https://img.shields.io/github/repo-size/easy-temps/vue3-vant-mobile" alt="repo-size" />
+<img src="https://img.shields.io/github/languages/top/easy-temps/vue3-vant-mobile" alt="languages" />
+<img src="https://img.shields.io/github/issues-closed/easy-temps/vue3-vant-mobile" alt="issues" />
 </p>
 
-<h1 align="center">vue3-vant4-mobile</h1>
+[文档](https://easy-temps.github.io/easy-docs/vue3-vant-mobile/) / [交流](https://github.com/easy-temps/vue3-vant-mobile/issues/56) / [反馈](https://github.com/easy-temps/vue3-vant-mobile/issues)
 
-## Stargazers over time
+<a href="https://vue3-vant-mobile.netlify.app">Live Demo</a>
 
-[![Stargazers over time](https://starchart.cc/xiangshu233/vue3-vant4-mobile.svg?variant=adaptive)](https://starchart.cc/xiangshu233/vue3-vant4-mobile)
+[![Netlify Status](https://api.netlify.com/api/v1/badges/e6828bd2-2904-4c3e-a67c-b97d32aa1275/deploy-status)](https://app.netlify.com/sites/vue3-vant-mobile/deploys)
 
-## 介绍
+</div>
 
-👋👋👋 Vue3 Vant4 Mobile 使用了最新的 `Vue3.4`、`Vite5`、`Vant4`、`Pinia`、`TypeScript`、`UnoCSS` 等主流技术开发,集成 `Dark Mode`(暗黑)模式和系统主题色,并且持久化保存,集成 `Mock` 数据,顺便写了登录/注册/找回密码 页面(包括逻辑),只需替换你的 API 即可,另外页面均可以 `<keep-alive>`,随便写了个包含 `NavBar`、`TabBar` 的 Layout,集成了 `Axios`、`useECharts`、`IconSvg`。
+<br>
 
-项目使用了 [antfu](https://github.com/antfu) 大佬的 [antfu/eslint-config](https://github.com/antfu/eslint-config) 作为代码规范检查工具,摆脱繁琐无聊的 Eslint 配置,配合 `cz-git`、 `lint-staged`、`simple-git-hooks`可对暂存区代码提交校验,代码风格不合格可打断提交,保证多人协作开发时上游 Git 库的干净。
+## Features
 
-现在你可以在此之上直接开发你的业务代码!希望你能喜欢!
+- ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite 5](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/), [esbuild](https://github.com/evanw/esbuild) - born with fastness
 
-## 截图预览
+- 🗂 [File based routing](./src/router)
 
-<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>
+- 📦 [Components auto importing](./src/components)
 
-<details>
-<summary>展开预览暗黑模式下的界面截图</summary>
+- 🍍 [State Management via Pinia](https://pinia.vuejs.org)
 
-<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>
+- 📲 [PWA](https://github.com/antfu/vite-plugin-pwa)
 
-</details>
+- 🎨 [UnoCSS](https://github.com/antfu/unocss) - the instant on-demand atomic CSS engine
 
-## 线上预览
+- 🌍 [I18n ready](./src/locales)
 
-预览链接:_[https://vvmobile.xiangshu233.cn/](https://vvmobile.xiangshu233.cn/)_
+- 🔥 Use the [new `<script setup>` syntax](https://github.com/vuejs/rfcs/pull/227)
 
-账号:admin,密码:123456
+- 📥 [APIs auto importing](https://github.com/antfu/unplugin-auto-import) - use Composition API and others directly
 
-账号:test,密码:123456
+- 💪 TypeScript, of course
 
-或者扫描以下二维码进入手机演示
+- ⚙️ Unit Testing with [Vitest](https://github.com/vitest-dev/vitest)
 
-<p align="center">
-  <img src="https://fastly.jsdelivr.net/gh/xiangshu233/blogAssets/2022/07/vue3-vant4-mobile-QR-code.png" width="200" />
-</p>
+- 💾 [Mock](https://github.com/pengzhanbo/vite-plugin-mock-dev-server) server Support
 
-## 基础知识
+- 🌈 Git [hooks](./.husky) - lint and commit
 
-- [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` 基本语法
+- 🪶 [Vant](https://github.com/youzan/vant) - Vue UI library for mobile web apps
 
-## 关于 Icon 的使用
+- 🔭 [vConsole](https://github.com/vadxq/vite-plugin-vconsole) - the developer tool for mobile web page
 
-项目使用 `unocss` 的 `icon` [预设](https://unocss.dev/presets/icons) 作为系统 Icon
+- 📱 Browser adaptation - use viewport vw/vh units
 
-请遵循以下约定使用图标
+- 💻 [Desktop optimization](https://github.com/wswmsword/postcss-mobile-forever) - the mobile area
 
-- `<prefix><collection>-<icon>`
-- `<prefix><collection>:<icon>`
+- 🌓 Dark Mode Support
 
-```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" />
-```
+- 🛡️ Configure [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) as default
 
-点击这里获取所有[可用的图标](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 支持(自动修复)
-
-安装 [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"
-  ]
-}
-```
+- ☁️ Deploy on [Netlify](https://www.netlify.com), zero-config
+
+<br>
+
+## Pre-packed
+
+### UI Frameworks
+
+- [UnoCSS](https://github.com/antfu/unocss) - The instant on-demand atomic CSS engine
+  - [`@unocss/preset-rem-to-px`](https://github.com/unocss/unocss/tree/main/packages/preset-rem-to-px) - Converts rem to px for all utilities
+  - [`eslint-plugin-unocss`](https://github.com/devunt/eslint-plugin-unocss) - ESLint plugin for UnoCSS
+- [Vant](https://github.com/youzan/vant) - Vue UI library for mobile web apps
+  - [`vant-touch-emulator`](https://github.com/youzan/vant/tree/main/packages/vant-touch-emulator) - Simulate mobile touch events on the desktop
+  - [`vant-use`](https://github.com/youzan/vant/tree/main/packages/vant-use) - Built-in composition APIs of Vant
 
-## 使用
+### Plugins
+
+- [Vue Router](https://github.com/vuejs/router)
+  - [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) - file system based routing
+- [Pinia](https://pinia.vuejs.org) - Intuitive, type safe, light and flexible Store for Vue using the composition api
+  - [`pinia-plugin-persistedstate`](https://github.com/prazdevs/pinia-plugin-persistedstate) -  Configurable persistence and rehydration of Pinia stores
+- [Vue I18n](https://github.com/intlify/vue-i18n-next) - Internationalization
+  - [`unplugin-vue-i18n`](https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n) - unplugin for Vue I18n
+- [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) - components auto import
+- [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) - Directly use Vue Composition API and others without importing
+- [vite-plugin-vconsole](https://github.com/vadxq/vite-plugin-vconsole) - A lightweight, extendable front-end developer tool for mobile web page
+- [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server) - Vite Plugin for API mock dev server
+- [postcss-mobile-forever](https://github.com/wswmsword/postcss-mobile-forever) - To adapt different displays by one mobile viewport
+- [vite-plugin-vue-devtools](https://github.com/vuejs/devtools-next) - Designed to enhance the Vue developer experience
+- [vueuse](https://github.com/antfu/vueuse) - collection of useful composition APIs
+- [@unhead/vue](https://github.com/unjs/unhead) - manipulate document head reactively
+- [vite-plugin-pwa](https://github.com/antfu/vite-plugin-pwa) - PWA
+- [vite-plugin-sitemap](https://github.com/jbaubree/vite-plugin-sitemap) - sitemap and robots generator
+
+### Coding Style
+
+- Use Composition API with [`<script setup>` SFC syntax](https://github.com/vuejs/rfcs/pull/227)
+- [ESLint](https://eslint.org/) with [@antfu/eslint-config](https://github.com/antfu/eslint-config), single quotes, no semi
+
+### Dev tools
+
+- [TypeScript](https://www.typescriptlang.org/)
+- [Vitest](https://github.com/vitest-dev/vitest) - Unit testing powered by Vite
+- [pnpm](https://pnpm.js.org/) - fast, disk space efficient package manager
+- [Netlify](https://www.netlify.com/) - zero-config deployment
+- [VS Code Extensions](./.vscode/extensions.json)
+  - [Vite](https://marketplace.visualstudio.com/items?itemName=antfu.vite) - Fire up Vite server automatically
+  - [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 3 `<script setup>` IDE support
+  - [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - Icon inline display and autocomplete
+  - [i18n Ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) - All in one i18n support
+  - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
+
+## Try it now
+
+> vue3-vant-mobile requires Node 18+
+
+### GitHub Template
+
+[Create a repo from this template on GitHub](https://github.com/easy-temps/vue3-vant-mobile/generate)
+
+### Clone to local
+
+If you prefer to do it manually with the cleaner git history
 
 ```bash
-# 获取项目代码
-git clone https://github.com/xiangshu233/vue3-vant4-mobile.git
+npx tiged easy-temps/vue3-vant-mobile my-mobile-app
+cd my-mobile-app
+pnpm i # If you don't have pnpm installed, run: npm install -g pnpm
+```
+
+## Checklist
 
-# 安装依赖
-cd vue3-vant4-mobile
-pnpm install
+When you use this template, try follow the checklist to update your info properly
 
-# 运行
+- [ ] Change the author name in `LICENSE`
+- [ ] Change the title in `index.html`
+- [ ] Change the hostname in `vite.config.ts`
+- [ ] Change the favicon in `public`
+- [ ] Clean up the READMEs and remove routes
+
+And, enjoy :)
+
+## Usage
+
+### Development
+
+Just run and visit <http://localhost:3000>
+
+```bash
 pnpm dev
+```
 
-# 打包
+### Build
+
+To build the App, run
+
+```bash
 pnpm build
 ```
 
-## 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` 开发中
-
-### 提交校验
-
-> [!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"
-  }
-}
-```
+And you will see the generated file in `dist` that ready to be served.
+
+### Deploy on Netlify
+
+Go to [Netlify](https://app.netlify.com/start) and select your clone, `OK` along the way, and your App will be live in a minute.
 
-## 浏览器支持
+## Community
 
-本地开发推荐使用`Chrome 80+` 浏览器
+We recommend that [issue](https://github.com/easy-temps/vue3-vant-mobile/issues) be used for problem feedback, or Wechat group.
 
-支持现代浏览器, 不支持 IE
+<img style="width: 25%" src="https://cdn.jsdelivr.net/gh/easy-temps/easy-static/community.png" alt="community" />
 
-| [![ 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                                                                                                                                                |
+## Donation ☕
 
-## 维护者
+[Buy Me a Coffee](https://github.com/CharleeWa/sponsor)
 
-[@xiangshu233](https://github.com/xiangshu233)
+<h2 align="center">💝 Our Sponsors 💝</h2>
+
+<p align="center">
+Your sponsorship will help us continue to iterate on this exciting project! 🚀
+</p>
+
+<p align="center">
+<a href="https://github.com/keyFeng"><img src="https://avatars.githubusercontent.com/u/52267976?v=4" width="60px" alt="keyFeng" /></a>
+<a href="https://github.com/ljt990218"><img src="https://avatars.githubusercontent.com/u/50509815?v=4" width="60px" alt="ljt990218" /></a>
+<a href="https://github.com/heked"><img src="https://avatars.githubusercontent.com/u/14127731?v=4" width="60px" alt="heked" /></a>
+</p>
+
+<h2 align="center">
+💪 Contributors 💪
+</h2>
+
+<p align="center">
+Our contributors have made this project better. Thank you! 🙏
+</p>
+
+<p align="center">
+<a href="https://github.com/CharleeWa"><img src="https://avatars.githubusercontent.com/u/22477554?v=4" width="60px" alt="CharleeWa" /></a>
+<a href="https://github.com/ljt990218"><img src="https://avatars.githubusercontent.com/u/50509815?v=4" width="60px" alt="ljt990218" /></a>
+<a href="https://github.com/wswmsword"><img src="https://avatars.githubusercontent.com/u/26893092?v=4" width="60px" alt="wswmsword" /></a>
+<a href="https://github.com/weiq"><img src="https://avatars.githubusercontent.com/u/1697158?v=4" width="60px" alt="weiq" /></a>
+<a href="https://github.com/SublimeCT"><img src="https://avatars.githubusercontent.com/u/20380890?v=4" width="60px" alt="SublimeCT" /></a>
+<a href="https://github.com/ReginYuan"><img src="https://avatars.githubusercontent.com/u/49477488?v=4" width="60px" alt="ReginYuan" /></a>
+<a href="https://github.com/smartsf"><img src="https://avatars.githubusercontent.com/u/19995400?v=4" width="60px" alt="smartsf" /></a>
+<a href="https://github.com/Kysen777"><img src="https://avatars.githubusercontent.com/u/63892082?v=4" width="60px" alt="Kysen777" /></a>
+<a href="https://github.com/Leezon"><img src="https://avatars.githubusercontent.com/u/38120280?v=4" width="60px" alt="Leezon" /></a>
+<a href="https://github.com/AlphaYoung111"><img src="https://avatars.githubusercontent.com/u/54132313?v=4" width="60px" alt="AlphaYoung111" /></a>
+<a href="https://github.com/leo4developer"><img src="https://avatars.githubusercontent.com/u/15160478?v=4" width="60px" alt="leo4developer" /></a>
+<a href="https://github.com/InsHomePgup"><img src="https://avatars.githubusercontent.com/u/47906083?v=4" width="60px" alt="InsHomePgup" /></a>
+<a href="https://github.com/wowping"><img src="https://avatars.githubusercontent.com/u/137802961?v=4" width="60px" alt="wowping" /></a>
+<a href="https://github.com/ChunyuPCY"><img src="https://avatars.githubusercontent.com/u/21986942?v=4" width="60px" alt="ChunyuPCY" /></a>
+<a href="https://github.com/qiyue2015"><img src="https://avatars.githubusercontent.com/u/11554433?v=4" width="60px" alt="qiyue2015" /></a>
+<a href="https://github.com/pyQianYi"><img src="https://avatars.githubusercontent.com/u/57526688?v=4" width="60px" alt="pyQianYi" /></a>
+<a href="https://github.com/xyy94813"><img src="https://avatars.githubusercontent.com/u/17971352?v=4" width="60px" alt="xyy94813" /></a>
+</p>
 
-## LICENSE
+## License
 
-[MIT](https://en.wikipedia.org/wiki/MIT_License)
+[MIT](./LICENSE) License

+ 229 - 0
README.zh-CN.md

@@ -0,0 +1,229 @@
+<div align="center">
+
+<img src="https://cdn.jsdelivr.net/gh/easy-temps/easy-static/cover.png" alt="cover" />
+
+<h1 align="center">vue3-vant-mobile</h1>
+
+[English](./README.md) / 简体中文
+
+An mobile web apps template based on the Vue 3 ecosystem.
+
+一个基于 Vue 3 生态系统的移动 web 应用模板,帮助你快速完成业务开发。
+
+<p>
+<img src="https://img.shields.io/github/license/easy-temps/vue3-vant-mobile" alt="license" />
+<img src="https://img.shields.io/github/package-json/v/easy-temps/vue3-vant-mobile" alt="version" />
+<img src="https://img.shields.io/github/repo-size/easy-temps/vue3-vant-mobile" alt="repo-size" />
+<img src="https://img.shields.io/github/languages/top/easy-temps/vue3-vant-mobile" alt="languages" />
+<img src="https://img.shields.io/github/issues-closed/easy-temps/vue3-vant-mobile" alt="issues" />
+</p>
+
+[文档](https://easy-temps.github.io/easy-docs/vue3-vant-mobile/) / [交流](https://github.com/easy-temps/vue3-vant-mobile/issues/56) / [反馈](https://github.com/easy-temps/vue3-vant-mobile/issues)
+
+<a href="https://vue3-vant-mobile.netlify.app">在线 Demo</a>
+
+[![Netlify Status](https://api.netlify.com/api/v1/badges/e6828bd2-2904-4c3e-a67c-b97d32aa1275/deploy-status)](https://app.netlify.com/sites/vue3-vant-mobile/deploys)
+
+</div>
+
+<br>
+
+## Features
+
+- ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite 5](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/), [esbuild](https://github.com/evanw/esbuild) - 就是快!
+
+- 🗂 [基于文件的路由](./src/router)
+
+- 📦 [组件自动化加载](./src/components)
+
+- 🍍 [使用 Pinia 的状态管理](https://pinia.vuejs.org)
+
+- 📲 [PWA](https://github.com/antfu/vite-plugin-pwa)
+
+- 🎨 [UnoCSS](https://github.com/antfu/unocss) - 高性能且极具灵活性的即时原子化 CSS 引擎
+
+- 🌍 [I18n 国际化开箱即用](./src/locales)
+
+- 🔥 使用 [新的 `<script setup>` 语法](https://github.com/vuejs/rfcs/pull/227)
+
+- 📥 [API 自动加载](https://github.com/antfu/unplugin-auto-import) - 直接使用 Composition API 无需引入
+
+- 💪 TypeScript, 当然
+
+- ⚙️ 使用 [Vitest](https://github.com/vitest-dev/vitest) 进行单元测试
+
+- 💾 [本地数据模拟](https://github.com/pengzhanbo/vite-plugin-mock-dev-server)的支持
+
+- 🌈 Git [hooks](./.husky) - 提交代码 eslint 检测 和 提交规范检测
+
+- 🪶 [Vant](https://github.com/youzan/vant) - 移动端 Vue 组件库
+
+- 🔭 [vConsole](https://github.com/vadxq/vite-plugin-vconsole) - 移动端网页开发工具
+
+- 📱 浏览器适配 - 使用 viewport vw/vh 单位布局
+
+- 💻 [桌面端优化](https://github.com/wswmsword/postcss-mobile-forever) - 处理为移动端视图
+
+- 🌓 支持深色模式
+
+- 🛡️ 将 [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) 设为默认
+
+- ☁️ 零配置部署 [Netlify](https://www.netlify.com)
+
+<br>
+
+## 预配置
+
+### UI 框架
+
+- [UnoCSS](https://github.com/antfu/unocss) - 高性能且极具灵活性的即时原子化 CSS 引擎
+  - [`@unocss/preset-rem-to-px`](https://github.com/unocss/unocss/tree/main/packages/preset-rem-to-px) - 将所有实用程序的 rem 转换为 px
+  - [`eslint-plugin-unocss`](https://github.com/devunt/eslint-plugin-unocss) - Unocss 的 ESLint 插件
+- [Vant](https://github.com/youzan/vant) - 移动端 Vue 组件库
+  - [`vant-touch-emulator`](https://github.com/youzan/vant/tree/main/packages/vant-touch-emulator) - 在桌面端上模拟移动端 touch 事件
+  - [`vant-use`](https://github.com/youzan/vant/tree/main/packages/vant-use) - Vant 内置的组合式 API
+
+### 插件
+
+- [Vue Router](https://github.com/vuejs/router)
+  - [`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) - 以文件系统为基础的路由
+- [Pinia](https://pinia.vuejs.org) - 直接的, 类型安全的, 使用 Composition API 的轻便灵活的 Vue 状态管理库
+  - [`pinia-plugin-persistedstate`](https://github.com/prazdevs/pinia-plugin-persistedstate) -  适用于 Pinia 的持久化存储插件
+- [Vue I18n](https://github.com/intlify/vue-i18n-next) - 国际化
+  - [`unplugin-vue-i18n`](https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n) - Vue I18n 的 Vite 插件
+- [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) - 自动加载组件
+- [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import) - 直接使用 Composition API 等,无需导入
+- [vite-plugin-vconsole](https://github.com/vadxq/vite-plugin-vconsole) - vConsole 的 vite 插件
+- [vite-plugin-mock-dev-server](https://github.com/pengzhanbo/vite-plugin-mock-dev-server) - vite mock 开发服务(mock-dev-server)插件
+- [postcss-mobile-forever](https://github.com/wswmsword/postcss-mobile-forever) - 一款 PostCSS 插件,将固定尺寸的移动端视图转为具有最大宽度的可伸缩的移动端视图
+- [vite-plugin-vue-devtools](https://github.com/vuejs/devtools-next) - 旨在增强Vue开发者体验的Vite插件
+- [vueuse](https://github.com/antfu/vueuse) - 实用的 Composition API 工具合集
+- [@unhead/vue](https://github.com/unjs/unhead) - 响应式地操作文档头信息
+- [vite-plugin-pwa](https://github.com/antfu/vite-plugin-pwa) - PWA
+- [vite-plugin-sitemap](https://github.com/jbaubree/vite-plugin-sitemap) - sitemap 和 robots 生成器
+
+### 编码风格
+
+- 使用 Composition API 地 [`<script setup>` SFC 语法](https://github.com/vuejs/rfcs/pull/227)
+- [ESLint](https://eslint.org/) 配置为 [@antfu/eslint-config](https://github.com/antfu/eslint-config), 单引号, 无分号
+
+### 开发工具
+
+- [TypeScript](https://www.typescriptlang.org/)
+- [Vitest](https://github.com/vitest-dev/vitest) - 基于 Vite 的单元测试框架
+- [pnpm](https://pnpm.js.org/) - 快, 节省磁盘空间的包管理器
+- [Netlify](https://www.netlify.com/) - 零配置的部署
+- [VS Code Extensions](./.vscode/extensions.json)
+  - [Vite](https://marketplace.visualstudio.com/items?itemName=antfu.vite) - 自动启动 Vite 服务器
+  - [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) - Vue 3 `<script setup>` IDE 支持
+  - [Iconify IntelliSense](https://marketplace.visualstudio.com/items?itemName=antfu.iconify) - 图标内联显示和自动补全
+  - [i18n Ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) - 多合一的 I18n 支持
+  - [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
+
+## 现在可以试试
+
+> vue3-vant-mobile 需要 Node 版本 18+
+
+### GitHub 模板
+
+[使用这个模板创建仓库](https://github.com/easy-temps/vue3-vant-mobile/generate)
+
+### 克隆到本地
+
+如果您更喜欢使用更干净的 git 历史记录手动执行此操作
+
+```bash
+npx tiged easy-temps/vue3-vant-mobile my-mobile-app
+cd my-mobile-app
+pnpm i # 如果你没装过 pnpm, 可以先运行: npm install -g pnpm
+```
+
+## 清单
+
+使用此模板时,请尝试按照清单正确更新您自己的信息
+
+- [ ] 在 `LICENSE` 中改变作者名
+- [ ] 在 `index.html` 中改变标题
+- [ ] 在 `vite.config.ts` 更改主机名
+- [ ] 在 `public` 目录下改变favicon
+- [ ] 整理 README 并删除路由
+
+紧接着, 享受吧 :)
+
+## 使用
+
+### 开发
+
+只需要执行以下命令就可以在 <http://localhost:3000> 中看到
+
+```bash
+pnpm dev
+```
+
+### 构建
+
+构建该应用只需要执行以下命令
+
+```bash
+pnpm build
+```
+
+然后你会看到用于发布的 `dist` 文件夹被生成。
+
+### 部署到 Netlify
+
+前往 [Netlify](https://app.netlify.com/start) 并选择你的仓库, 一路 `OK` 下去,稍等一下后,你的应用将被创建。
+
+## 社区
+
+我们推荐使用 [议题](https://github.com/easy-temps/vue3-vant-mobile/issues) 来反馈问题, 或者您也可以通过微信群联系我们。
+
+<img style="width: 25%" src="https://cdn.jsdelivr.net/gh/easy-temps/easy-static/community.png" alt="community" />
+
+## 捐赠 ☕
+
+[请我喝一杯咖啡](https://github.com/CharleeWa/sponsor)
+
+<h2 align="center">💝 我们的赞助者 💝</h2>
+
+<p align="center">
+您的赞助将帮助我们继续迭代这个令人兴奋的项目! 🚀
+</p>
+
+<p align="center">
+<a href="https://github.com/keyFeng"><img src="https://avatars.githubusercontent.com/u/52267976?v=4" width="60px" alt="keyFeng" /></a>
+<a href="https://github.com/ljt990218"><img src="https://avatars.githubusercontent.com/u/50509815?v=4" width="60px" alt="ljt990218" /></a>
+<a href="https://github.com/heked"><img src="https://avatars.githubusercontent.com/u/14127731?v=4" width="60px" alt="heked" /></a>
+</p>
+
+<h2 align="center">
+💪 贡献者 💪
+</h2>
+
+<p align="center">
+我们的贡献者使这个项目变得更好。谢谢你! 🙏
+</p>
+
+<p align="center">
+<a href="https://github.com/CharleeWa"><img src="https://avatars.githubusercontent.com/u/22477554?v=4" width="60px" alt="CharleeWa" /></a>
+<a href="https://github.com/ljt990218"><img src="https://avatars.githubusercontent.com/u/50509815?v=4" width="60px" alt="ljt990218" /></a>
+<a href="https://github.com/wswmsword"><img src="https://avatars.githubusercontent.com/u/26893092?v=4" width="60px" alt="wswmsword" /></a>
+<a href="https://github.com/weiq"><img src="https://avatars.githubusercontent.com/u/1697158?v=4" width="60px" alt="weiq" /></a>
+<a href="https://github.com/SublimeCT"><img src="https://avatars.githubusercontent.com/u/20380890?v=4" width="60px" alt="SublimeCT" /></a>
+<a href="https://github.com/ReginYuan"><img src="https://avatars.githubusercontent.com/u/49477488?v=4" width="60px" alt="ReginYuan" /></a>
+<a href="https://github.com/smartsf"><img src="https://avatars.githubusercontent.com/u/19995400?v=4" width="60px" alt="smartsf" /></a>
+<a href="https://github.com/Kysen777"><img src="https://avatars.githubusercontent.com/u/63892082?v=4" width="60px" alt="Kysen777" /></a>
+<a href="https://github.com/Leezon"><img src="https://avatars.githubusercontent.com/u/38120280?v=4" width="60px" alt="Leezon" /></a>
+<a href="https://github.com/AlphaYoung111"><img src="https://avatars.githubusercontent.com/u/54132313?v=4" width="60px" alt="AlphaYoung111" /></a>
+<a href="https://github.com/leo4developer"><img src="https://avatars.githubusercontent.com/u/15160478?v=4" width="60px" alt="leo4developer" /></a>
+<a href="https://github.com/InsHomePgup"><img src="https://avatars.githubusercontent.com/u/47906083?v=4" width="60px" alt="InsHomePgup" /></a>
+<a href="https://github.com/wowping"><img src="https://avatars.githubusercontent.com/u/137802961?v=4" width="60px" alt="wowping" /></a>
+<a href="https://github.com/ChunyuPCY"><img src="https://avatars.githubusercontent.com/u/21986942?v=4" width="60px" alt="ChunyuPCY" /></a>
+<a href="https://github.com/qiyue2015"><img src="https://avatars.githubusercontent.com/u/11554433?v=4" width="60px" alt="qiyue2015" /></a>
+<a href="https://github.com/pyQianYi"><img src="https://avatars.githubusercontent.com/u/57526688?v=4" width="60px" alt="pyQianYi" /></a>
+<a href="https://github.com/xyy94813"><img src="https://avatars.githubusercontent.com/u/17971352?v=4" width="60px" alt="xyy94813" /></a>
+</p>
+
+## License
+
+[MIT](./LICENSE) License

+ 0 - 6
build/constant.ts

@@ -1,6 +0,0 @@
-/**
- * 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/vant-mobile'

+ 0 - 9
build/getConfigFileName.ts

@@ -1,9 +0,0 @@
-/**
- * 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, '')
-}

+ 0 - 45
build/script/buildConf.ts

@@ -1,45 +0,0 @@
-/**
- * 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 { getEnvConfig, getRootPath } from '../utils'
-import { getConfigFileName } from '../getConfigFileName'
-
-import pkg from '../../package.json'
-
-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
-    const configStr = `${windowConf}=${JSON.stringify(config)};
-      Object.freeze(${windowConf});
-      Object.defineProperty(window, "${configName}", {
-        configurable: false,
-        writable: false,
-      });
-    `.replace(/\s/g, '')
-    fs.mkdirp(getRootPath(OUTPUT_DIR))
-    fs.writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr)
-
-    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 })
-}

+ 0 - 24
build/script/postBuild.ts

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

+ 0 - 75
build/utils.ts

@@ -1,75 +0,0 @@
-import fs from 'node:fs'
-import path from 'node:path'
-import dotenv from 'dotenv'
-
-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
-
-    if (envName === 'VITE_PORT') {
-      realName = Number(realName)
-    }
-    if (envName === 'VITE_PROXY') {
-      try {
-        realName = JSON.parse(realName)
-      }
-      catch (error) {}
-    }
-    ret[envName] = realName
-    process.env[envName] = realName
-  }
-  return ret
-}
-
-/**
- * Get the environment variables starting with the specified prefix
- * @param match prefix
- * @param confFiles ext
- */
-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(), item)))
-      envConfig = { ...envConfig, ...env }
-    }
-    catch (error) {}
-  })
-
-  Object.keys(envConfig).forEach((key) => {
-    const reg = new RegExp(`^(${match})`)
-    if (!reg.test(key)) {
-      Reflect.deleteProperty(envConfig, key)
-    }
-  })
-  return envConfig
-}
-
-/**
- * Get user root directory
- * @param dir file path
- */
-export function getRootPath(...dir: string[]) {
-  return path.resolve(process.cwd(), ...dir)
-}

+ 119 - 0
build/vite/index.ts

@@ -0,0 +1,119 @@
+import { dirname, resolve } from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { unheadVueComposablesImports } from '@unhead/vue'
+import legacy from '@vitejs/plugin-legacy'
+import vue from '@vitejs/plugin-vue'
+import UnoCSS from 'unocss/vite'
+import AutoImport from 'unplugin-auto-import/vite'
+import { VantResolver } from 'unplugin-vue-components/resolvers'
+import Components from 'unplugin-vue-components/vite'
+import { VueRouterAutoImports } from 'unplugin-vue-router'
+import VueRouter from 'unplugin-vue-router/vite'
+import mockDevServerPlugin from 'vite-plugin-mock-dev-server'
+import { VitePWA } from 'vite-plugin-pwa'
+import Sitemap from 'vite-plugin-sitemap'
+import VueDevTools from 'vite-plugin-vue-devtools'
+import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
+import { createViteVConsole } from './vconsole'
+
+export function createVitePlugins() {
+  return [
+    // https://github.com/posva/unplugin-vue-router
+    VueRouter({
+      extensions: ['.vue'],
+      routesFolder: 'src/pages',
+      dts: 'src/typed-router.d.ts',
+    }),
+
+    vue(),
+
+    // https://github.com/jbaubree/vite-plugin-sitemap
+    Sitemap(),
+
+    // https://github.com/pengzhanbo/vite-plugin-mock-dev-server
+    mockDevServerPlugin(),
+
+    // https://github.com/antfu/unplugin-vue-components
+    Components({
+      extensions: ['vue'],
+      resolvers: [VantResolver()],
+      include: [/\.vue$/, /\.vue\?vue/],
+      dts: 'src/components.d.ts',
+    }),
+
+    // https://github.com/antfu/unplugin-auto-import
+    AutoImport({
+      include: [
+        /\.[tj]sx?$/,
+        /\.vue$/,
+        /\.vue\?vue/,
+      ],
+      imports: [
+        'vue',
+        'vitest',
+        '@vueuse/core',
+        VueRouterAutoImports,
+        {
+          'vue-router/auto': ['useLink'],
+          '@/utils/i18n': ['i18n', 'locale'],
+          'vue-i18n': ['useI18n'],
+        },
+        unheadVueComposablesImports,
+      ],
+      dts: 'src/auto-imports.d.ts',
+      dirs: [
+        'src/composables',
+      ],
+      viteOptimizeDeps: false,
+    }),
+
+    // https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
+    VueI18nPlugin({
+      // locale messages resource pre-compile option
+      include: resolve(dirname(fileURLToPath(import.meta.url)), '../../src/locales/**'),
+    }),
+
+    legacy({
+      targets: ['defaults', 'not IE 11'],
+    }),
+
+    // https://github.com/antfu/unocss
+    // see uno.config.ts for config
+    UnoCSS(),
+
+    // https://github.com/vadxq/vite-plugin-vconsole
+    createViteVConsole(),
+
+    // https://github.com/vuejs/devtools-next
+    VueDevTools(),
+
+    // https://github.com/antfu/vite-plugin-pwa
+    VitePWA({
+      registerType: 'autoUpdate',
+      includeAssets: ['favicon.svg', 'safari-pinned-tab.svg'],
+      manifest: {
+        name: 'vue3-vant-mobile',
+        short_name: 'vue3-vant-mobile',
+        theme_color: '#ffffff',
+        icons: [
+          {
+            src: '/pwa-192x192.png',
+            sizes: '192x192',
+            type: 'image/png',
+          },
+          {
+            src: '/pwa-512x512.png',
+            sizes: '512x512',
+            type: 'image/png',
+          },
+          {
+            src: '/pwa-512x512.png',
+            sizes: '512x512',
+            type: 'image/png',
+            purpose: 'any maskable',
+          },
+        ],
+      },
+    }),
+  ]
+}

+ 24 - 0
build/vite/optimize.ts

@@ -0,0 +1,24 @@
+const include = [
+  'axios',
+  'store',
+  'echarts',
+  'lodash-es',
+  'resize-detector',
+  'store/plugins/expire',
+  'vant/es/cell-group/style/index',
+  'vant/es/popup/style/index',
+  'vant/es/picker/style/index',
+  'vant/es/cell/style/index',
+  'vant/es/switch/style/index',
+  'vant/es/space/style/index',
+  'vant/es/button/style/index',
+  'vant/es/empty/style/index',
+  'vant/es/icon/style/index',
+  'vant/es/stepper/style/index',
+]
+
+const exclude = [
+  '@iconify-json/carbon',
+]
+
+export { include, exclude }

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

@@ -1,35 +0,0 @@
-/**
- * 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 - 46
build/vite/plugin/html.ts

@@ -1,46 +0,0 @@
-/**
- * Plugin to minimize and use ejs template syntax in index.html.
- * https://github.com/anncwb/vite-plugin-html
- */
-import type { PluginOption } from 'vite'
-import { createHtmlPlugin } from 'vite-plugin-html'
-import pkg from '../../../package.json'
-import { GLOB_CONFIG_FILE_NAME } from '../../constant'
-
-export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
-  const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env
-
-  const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`
-
-  const getAppConfigSrc = () => {
-    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,
-      },
-
-      // Embed the generated app.config.js file 需要注入的标签列表
-      tags: isBuild
-        ? [
-            {
-              tag: 'script',
-              attrs: {
-                src: getAppConfigSrc(),
-              },
-            },
-          ]
-        : [],
-    },
-  })
-  return htmlPlugin
-}

+ 0 - 78
build/vite/plugin/index.ts

@@ -1,78 +0,0 @@
-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 UnoCSS from 'unocss/vite'
-import AutoImport from 'unplugin-auto-import/vite'
-import { configHtmlPlugin } from './html'
-import { configMockPlugin } from './mock'
-import { configCompressPlugin } from './compress'
-import { configVisualizerConfig } from './visualizer'
-import { configSvgIconsPlugin } from './svgSprite'
-
-/**
- * 配置 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(),
-    // 按需引入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',
-    }),
-  ]
-
-  // 加载 html 插件 vite-plugin-html
-  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild))
-
-  // rollup-plugin-visualizer
-  vitePlugins.push(configVisualizerConfig())
-
-  // vite-plugin-mock
-  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock))
-
-  // vite-plugin-svg-icons
-  vitePlugins.push(configSvgIconsPlugin(isBuild))
-
-  if (isBuild) {
-    // rollup-plugin-gzip
-    // 加载 gzip 打包
-    vitePlugins.push(
-      configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
-    )
-  }
-
-  return vitePlugins
-}

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

@@ -1,19 +0,0 @@
-/**
- * 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();
-      `,
-  })
-}

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

@@ -1,19 +0,0 @@
-/**
- *  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 - 18
build/vite/plugin/visualizer.ts

@@ -1,18 +0,0 @@
-/**
- * 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 - 52
build/vite/proxy.ts

@@ -1,52 +0,0 @@
-/**
- * Used to parse the .env.development proxy configuration
- */
-import type { ProxyOptions } from 'vite'
-
-type ProxyItem = [string, string]
-
-type ProxyList = ProxyItem[]
-
-type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>
-
-const httpsRE = /^https:\/\//
-
-/**
- * Generate proxy
- * @param list
- */
-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,
-      changeOrigin: true,
-      ws: true,
-      rewrite: path => path.replace(new RegExp(`^${prefix}`), ''),
-      // https is require 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/), ''),
-  //   }
-  // }
-}

+ 44 - 0
build/vite/vconsole.ts

@@ -0,0 +1,44 @@
+import path from 'node:path'
+import { viteVConsole } from 'vite-plugin-vconsole'
+
+export function createViteVConsole() {
+  return viteVConsole({
+    entry: [path.resolve('src/main.ts')],
+    enabled: false,
+    config: {
+      maxLogNumber: 1000,
+      theme: 'light',
+    },
+    // https://github.com/vadxq/vite-plugin-vconsole/issues/21
+    dynamicConfig: {
+      theme: `document.documentElement.classList.contains('dark') ? 'dark' : 'light'`,
+    },
+    eventListener: `
+      const targetElement = document.querySelector('html'); // 择要监听的元素
+      const observerOptions = {
+        attributes: true, // 监听属性变化
+        attributeFilter: ['class'] // 只监听class属性变化
+      };
+
+      // 定义回调函数来处理观察到的变化
+      function handleAttributeChange(mutationsList) {
+        for(let mutation of mutationsList) {
+          if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
+            if (window && window.vConsole) {
+              window.vConsole.dynamicChange.value = new Date().getTime();
+            }
+          }
+        }
+      }
+
+      // 创建观察者实例并传入回调函数
+      const observer = new MutationObserver(handleAttributeChange);
+
+      // 开始观察目标元素
+      observer.observe(targetElement, observerOptions);
+
+      // 当不再需要观察时,停止观察
+      // observer.disconnect();
+    `,
+  })
+}

+ 0 - 155
commitlint.config.cjs

@@ -1,155 +0,0 @@
-// 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 - 29
components.d.ts

@@ -1,29 +0,0 @@
-/* eslint-disable */
-/* prettier-ignore */
-// @ts-nocheck
-// Generated by unplugin-vue-components
-// Read more: https://github.com/vuejs/core/pull/3399
-export {}
-
-declare module 'vue' {
-  export interface GlobalComponents {
-    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']
-    VanForm: typeof import('vant/es')['Form']
-    VanImage: typeof import('vant/es')['Image']
-    VanNavBar: typeof import('vant/es')['NavBar']
-    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']
-  }
-}

+ 8 - 53
eslint.config.js

@@ -1,61 +1,16 @@
-// eslint.config.js
 import antfu from '@antfu/eslint-config'
 
 export default antfu({
+  // enable UnoCSS support
+  // https://unocss.dev/integrations/vscode
   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'],
   },
+
+  ignores: [
+    '.github/**',
+    'scripts/**',
+  ],
 })

+ 19 - 120
index.html

@@ -1,127 +1,26 @@
-<!doctype html>
-<html lang="zh-cmn-Hans" id="htmlRoot">
+<!DOCTYPE html>
+<html lang="en">
   <head>
     <meta charset="UTF-8" />
-    <link rel="icon" type="image/svg+xml" href="/logo.svg" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title><%= title %></title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
+    <link rel="apple-touch-icon" href="/pwa-192x192.png" />
+    <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#f6d2d2">
+    <meta name="msapplication-TileColor" content="#f6d2d2">
+    <script>
+      ;(function () {
+        const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
+        const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
+        if (setting === 'dark' || (prefersDark && setting !== 'light'))
+          document.documentElement.classList.toggle('dark', true)
+      })()
+    </script>
   </head>
   <body>
-    <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>
+    <div id="app"></div>
     <script type="module" src="/src/main.ts"></script>
+    <noscript>
+      This website requires JavaScript to function properly.
+      Please enable JavaScript to continue.
+    </noscript>
   </body>
 </html>

+ 0 - 18
mock/_createProductionServer.ts

@@ -1,18 +0,0 @@
-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)
-}

+ 0 - 77
mock/_util.ts

@@ -1,77 +0,0 @@
-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
-}

+ 20 - 0
mock/data.ts

@@ -0,0 +1,20 @@
+import { defineMockData } from 'vite-plugin-mock-dev-server'
+
+// defineMockData,用于在 mock 文件中使用 data.ts 作为共享数据源。
+export default defineMockData('proses', [
+  '🔖 躲在某一时间,想念一段时光的掌纹;躲在某一地点,想念一个站在来路也站在去路的,让我牵挂的人。',
+  '🔖 天空一碧如洗,灿烂的阳光正从密密的松针的缝隙间射下来,形成一束束粗粗细细的光柱,把飘荡着轻纱般薄雾的林荫照得通亮。',
+  '🔖 这一次相遇,美得彻骨,美得震颤,美得孤绝,美得惊艳。',
+  '🔖 沉默的状态,能让我感觉到呼吸的自由和自己原来就处于的本色位置。',
+  '🔖 青春,是一包象征着阳光的向日葵种子,在现在洒下,就会在未来得到收获,那一株株饱含青春的花朵。',
+  '🔖 燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的日子为什么一去不复返呢?',
+  '🔖 毕业了,青春在无形之中离去,我们即将翻开人生的另一页。',
+  '🔖 成长,是每个孩子的权力,也是他们必经的征程,或平坦、或崎岖,有悲欢,有离合。',
+  '🔖 旧时光里的人和事,琐碎而零乱。我的记忆很模糊,好像大部分都成了一种温馨的符号,静静的沉在我心底。',
+  '🔖 生活是一部大百科全书,包罗万象;生活是一把六弦琴,弹奏出多重美妙的旋律:生活是一座飞马牌大钟,上紧发条,便会使人获得浓缩的生命。',
+  '🔖 毕业了,身边的朋友一个个各奔东西,开始学会自己撑起生命的暖色。',
+  '🔖 已经走到尽头的东西,重生也不过是再一次的消亡。就像所有的开始,其实都只是一个写好了的结局。',
+  '🔖 下午茶的芬香熏陶着房内的任何一个角落,午后的阳光透过窗帘的间隙洒在木制的桌面上,一份思念随着红茶顺滑至心中。',
+  '🔖 这里再不是我们的校园,当我们就此离开我们的青葱岁月。',
+  '🔖 很久找你,一直没有找到,微风吹过的时候,我深深的呼吸,才感觉到你也在陪伴着我呼吸。',
+])

+ 5 - 0
mock/index.ts

@@ -0,0 +1,5 @@
+import prose from './modules/prose.mock'
+
+export default {
+  ...prose,
+}

+ 13 - 0
mock/modules/prose.mock.ts

@@ -0,0 +1,13 @@
+import { defineMock } from 'vite-plugin-mock-dev-server'
+import { builder } from '../util'
+import proses from '../data'
+
+export default defineMock({
+  url: '/api/prose',
+  delay: 100,
+  body: () => {
+    const rand = Math.floor(Math.random() * proses.value.length)
+    const prose = proses.value[rand]
+    return builder(prose)
+  },
+})

+ 0 - 95
mock/user/user.ts

@@ -1,95 +0,0 @@
-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[]

+ 19 - 0
mock/util.ts

@@ -0,0 +1,19 @@
+const responseBody = {
+  message: '',
+  timestamp: 0,
+  result: null as unknown,
+  code: 0,
+}
+
+export function builder(data: unknown, message = 'success', code = 0) {
+  responseBody.result = data
+
+  if (message !== undefined && message !== null)
+    responseBody.message = message
+
+  if (code !== undefined && code !== 0)
+    responseBody.code = code
+
+  responseBody.timestamp = new Date().getTime()
+  return responseBody
+}

+ 12 - 0
netlify.toml

@@ -0,0 +1,12 @@
+[build]
+base = "/"
+publish = "dist"
+command = "pnpm run build"
+
+[build.environment]
+NODE_VERSION = "18"
+
+[[redirects]]
+from = "/*"
+to = "/index.html"
+status = 200

+ 71 - 94
package.json

@@ -1,115 +1,92 @@
 {
-  "name": "vue3-vant4-mobile",
+  "name": "vue3-vant-mobile",
   "type": "module",
-  "version": "2.1.0",
-  "private": true,
-  "packageManager": "pnpm@8.6.10",
-  "author": {
-    "name": "xiangshu233",
-    "email": "xiangshu233@outlook.com",
-    "url": "https://github.com/xiangshu233"
-  },
+  "version": "2.3.9",
+  "packageManager": "pnpm@9.6.0",
+  "description": "Vue + Vite H5 Starter Template",
   "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",
+    "dev": "cross-env MOCK_SERVER_PORT=8086 vite",
+    "build": "vue-tsc --noEmit && vite build",
+    "build:dev": "vue-tsc --noEmit && vite build --mode=development",
     "preview": "vite preview",
-    "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
-    "clean:lib": "rimraf node_modules",
     "lint": "eslint .",
     "lint:fix": "eslint . --fix",
-    "lint:lint-staged": "lint-staged"
+    "test": "vitest",
+    "release": "bumpp --commit --push --tag",
+    "prepare": "husky"
   },
   "dependencies": {
-    "@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",
+    "@unhead/vue": "^1.9.16",
+    "@vant/touch-emulator": "^1.4.0",
+    "@vant/use": "^1.6.0",
+    "@vueuse/core": "^10.11.0",
+    "axios": "^1.7.2",
+    "dayjs": "^1.11.12",
+    "decimal.js": "^10.4.3",
+    "echarts": "^5.5.1",
     "lodash-es": "^4.17.21",
-    "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "pinia": "^2.1.7",
-    "pinia-plugin-persist": "^1.0.0",
-    "qs": "^6.11.2",
-    "vant": "^4.8.1",
-    "vue": "^3.3.13",
-    "vue-router": "4.2.5"
+    "pinia-plugin-persistedstate": "^3.2.1",
+    "resize-detector": "^0.3.0",
+    "store": "^2.0.12",
+    "vant": "^4.9.2",
+    "vconsole": "^3.15.1",
+    "vue": "^3.4.33",
+    "vue-i18n": "^9.13.1",
+    "vue-router": "^4.4.0"
   },
   "devDependencies": {
-    "@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",
+    "@antfu/eslint-config": "2.23.0",
+    "@iconify-json/carbon": "^1.1.36",
+    "@intlify/unplugin-vue-i18n": "^4.0.0",
+    "@types/lodash-es": "^4.17.12",
+    "@types/node": "^20.14.11",
     "@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",
+    "@types/store": "^2.0.5",
+    "@unocss/eslint-plugin": "0.61.5",
+    "@unocss/preset-rem-to-px": "0.61.5",
+    "@vitejs/plugin-legacy": "^5.4.1",
+    "@vitejs/plugin-vue": "^5.0.5",
+    "autoprefixer": "^10.4.19",
+    "bumpp": "^9.4.1",
+    "consola": "^3.2.3",
     "cross-env": "^7.0.3",
-    "cz-git": "^1.8.0",
-    "dotenv": "^16.3.1",
-    "eslint": "^8.56.0",
-    "eslint-plugin-format": "^0.1.0",
-    "esno": "^0.16.3",
-    "fs-extra": "^11.2.0",
+    "eslint": "^9.8.0",
+    "eslint-plugin-format": "^0.1.2",
+    "husky": "^9.1.1",
     "less": "^4.2.0",
-    "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.8",
-    "vite-plugin-svg-icons": "^2.0.1",
-    "vue-tsc": "^1.8.27"
-  },
-  "simple-git-hooks": {
-    "pre-commit": "pnpm lint-staged",
-    "commit-msg": "npx --no-install commitlint --edit $1"
-  },
-  "lint-staged": {
-    "*": "eslint --fix"
+    "mockjs": "^1.1.0",
+    "postcss-mobile-forever": "^4.1.5",
+    "rollup": "^4.18.1",
+    "terser": "^5.31.3",
+    "typescript": "^5.5.3",
+    "unocss": "0.61.5",
+    "unplugin-auto-import": "^0.18.0",
+    "unplugin-vue-components": "^0.27.3",
+    "unplugin-vue-router": "^0.10.0",
+    "vite": "^5.3.4",
+    "vite-plugin-mock-dev-server": "^1.5.1",
+    "vite-plugin-pwa": "^0.20.0",
+    "vite-plugin-sitemap": "^0.6.2",
+    "vite-plugin-vconsole": "^2.1.1",
+    "vite-plugin-vue-devtools": "^7.3.6",
+    "vitest": "^2.0.3",
+    "vue-tsc": "^2.0.26"
   },
-  "config": {
-    "commitizen": {
-      "path": "node_modules/cz-git"
+  "pnpm": {
+    "peerDependencyRules": {
+      "ignoreMissing": [
+        "postcss",
+        "esbuild"
+      ],
+      "allowedVersions": {
+        "rollup": "^4.x"
+      },
+      "allowAny": [
+        "eslint"
+      ]
     }
   }
 }

File diff suppressed because it is too large
+ 750 - 470
pnpm-lock.yaml


+ 0 - 52
postcss.config.js

@@ -1,52 +0,0 @@
-/**
- * 由于在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替代
- */
-
-// 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),
-    }),
-  ],
-}

+ 4 - 0
public/favicon-dark.svg

@@ -0,0 +1,4 @@
+<svg t="1709866807903" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4913" width="32" height="32">
+  <path d="M512 598.528a111.232 111.232 0 0 0-111.232 111.2064V870.4h222.464v-160.6656c0-61.44-49.792-111.232-111.232-111.232z m0 58.0608c-30.72 0-55.6032 24.9088-55.6032 55.6288v105.0368h111.2064v-105.0368c0-30.72-24.8832-55.6288-55.6032-55.6288z" fill="#FB4D31" p-id="4914"></path>
+  <path d="M542.08 270.208l45.2608-78.5408a24.5248 24.5248 0 0 0 0-25.6A26.4448 26.4448 0 0 0 564.1472 153.6a26.3424 26.3424 0 0 0-22.5792 13.44L512 217.6256l-29.5424-50.5344c-4.5824-8.192-13.184-13.312-22.5536-13.4912a26.4448 26.4448 0 0 0-23.2448 12.4928 24.5248 24.5248 0 0 0 0 25.6l45.2352 78.5152L156.928 832.768a24.4736 24.4736 0 0 0 0.3328 25.088c4.8384 7.8336 13.3888 12.544 22.5792 12.5184h664.3456c9.3184 0 17.92-4.7616 22.6304-12.4672a24.4736 24.4736 0 0 0 0.3072-25.088l-325.0176-562.688v0.0512zM619.52 816.64h-33.3568c-12.3648 0-9.6768 0.1024-65.0496 0H512c-55.3728 0.1024-24.704 0-61.7984 0H225.28L512 322.1248 798.72 816.64h-179.2z" fill="#ffffff" p-id="4915"></path>
+</svg>

+ 4 - 0
public/favicon.svg

@@ -0,0 +1,4 @@
+<svg t="1709866807903" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4913" width="32" height="32">
+  <path d="M512 598.528a111.232 111.232 0 0 0-111.232 111.2064V870.4h222.464v-160.6656c0-61.44-49.792-111.232-111.232-111.232z m0 58.0608c-30.72 0-55.6032 24.9088-55.6032 55.6288v105.0368h111.2064v-105.0368c0-30.72-24.8832-55.6288-55.6032-55.6288z" fill="#FB4D31" p-id="4914"></path>
+  <path d="M542.08 270.208l45.2608-78.5408a24.5248 24.5248 0 0 0 0-25.6A26.4448 26.4448 0 0 0 564.1472 153.6a26.3424 26.3424 0 0 0-22.5792 13.44L512 217.6256l-29.5424-50.5344c-4.5824-8.192-13.184-13.312-22.5536-13.4912a26.4448 26.4448 0 0 0-23.2448 12.4928 24.5248 24.5248 0 0 0 0 25.6l45.2352 78.5152L156.928 832.768a24.4736 24.4736 0 0 0 0.3328 25.088c4.8384 7.8336 13.3888 12.544 22.5792 12.5184h664.3456c9.3184 0 17.92-4.7616 22.6304-12.4672a24.4736 24.4736 0 0 0 0.3072-25.088l-325.0176-562.688v0.0512zM619.52 816.64h-33.3568c-12.3648 0-9.6768 0.1024-65.0496 0H512c-55.3728 0.1024-24.704 0-61.7984 0H225.28L512 322.1248 798.72 816.64h-179.2z" fill="#000000" p-id="4915"></path>
+</svg>

+ 0 - 38
public/logo.svg

@@ -1,38 +0,0 @@
-<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>

二進制
public/pwa-192x192.png


二進制
public/pwa-512x512.png


File diff suppressed because it is too large
+ 1 - 0
public/safari-pinned-tab.svg


+ 18 - 0
scripts/verifyCommit.js

@@ -0,0 +1,18 @@
+import fs from 'node:fs'
+import process from 'node:process'
+
+const msg = fs.readFileSync('.git/COMMIT_EDITMSG', 'utf-8').trim()
+
+const commitRE = /^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|release)(\(.+\))?: .{1,50}/
+const mergeRe = /^(Merge pull request|Merge branch)/
+
+if (!commitRE.test(msg)) {
+  if (!mergeRe.test(msg)) {
+    console.log('git commit unpass')
+    console.error('git commit error, needs title(scope): desc')
+    process.exit(1)
+  }
+}
+else {
+  console.log('git commit pass')
+}

+ 48 - 68
src/App.vue

@@ -1,77 +1,57 @@
-<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 { darken, lighten } from '@/utils'
-import { useRouteStore } from '@/store/modules/route'
-import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
+import { storeToRefs } from 'pinia'
+import useAppStore from '@/stores/modules/app'
+import useRouteCache from '@/stores/modules/routeCache'
+import useRouteTransitionNameStore from '@/stores/modules/routeTransitionName'
+import useAutoThemeSwitcher from '@/hooks/useAutoThemeSwitcher'
 
-const routeStore = useRouteStore()
-const { getDarkMode, getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting()
+useHead({
+  title: 'Vue3 Vant Mobile',
+  meta: [
+    {
+      name: 'description',
+      content: 'Vue + Vite H5 Starter Template',
+    },
+    {
+      name: 'theme-color',
+      content: () => isDark.value ? '#00aba9' : '#ffffff',
+    },
+  ],
+  link: [
+    {
+      rel: 'icon',
+      type: 'image/svg+xml',
+      href: () => preferredDark.value ? '/favicon-dark.svg' : '/favicon.svg',
+    },
+  ],
+})
 
-// 需要缓存的路由组件
-const keepAliveComponents = computed(() => routeStore.keepAliveComponents)
+const appStore = useAppStore()
+const { mode } = storeToRefs(appStore)
 
-function getThemeVars() {
-  const appTheme = unref(getAppTheme)
-  const darkenStr = darken(appTheme, 25)
-  const lightenStr = lighten(appTheme, 10)
+const routeTransitionNameStore = useRouteTransitionNameStore()
+const { routeTransitionName } = storeToRefs(routeTransitionNameStore)
+const { initializeThemeSwitcher } = useAutoThemeSwitcher(appStore)
 
-  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,
-  }
-}
+const keepAliveRouteNames = computed(() => {
+  return useRouteCache().routeCaches as string[]
+})
 
-const getTransitionName = computed(() => {
-  return unref(getIsPageAnimate) ? unref(getPageAnimateType) : undefined
+onMounted(() => {
+  initializeThemeSwitcher()
 })
 </script>
 
-<style lang="less">
-  @import './styles/index.less';
-</style>
+<template>
+  <VanConfigProvider :theme="mode">
+    <NavBar />
+    <router-view v-slot="{ Component, route }">
+      <transition :name="routeTransitionName">
+        <keep-alive :include="keepAliveRouteNames">
+          <component :is="Component" :key="route.name" />
+        </keep-alive>
+      </transition>
+    </router-view>
+    <TabBar />
+  </VanConfigProvider>
+</template>

+ 5 - 0
src/api/index.ts

@@ -0,0 +1,5 @@
+import request from '@/utils/request'
+
+export async function queryProse(): Promise<any> {
+  return request('/prose')
+}

+ 0 - 59
src/api/system/user.ts

@@ -1,59 +0,0 @@
-import { http } from '@/utils/http/axios'
-
-export interface BasicResponseModel<T = any> {
-  code: number
-  message: string
-  result: T
-}
-
-/**
- * @description: 用户登录
- */
-export function login(params: any) {
-  return http.request<BasicResponseModel>(
-    {
-      url: '/login',
-      method: 'POST',
-      params,
-    },
-    {
-      isTransformResponse: false,
-    },
-  )
-}
-
-/**
- * @description: 获取用户信息
- */
-export function getUserInfo() {
-  return http.request({
-    url: '/getUserInfo',
-    method: 'get',
-  })
-}
-
-/**
- * @description: 用户登出
- */
-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,
-    },
-  )
-}

+ 16 - 0
src/api/typing.ts

@@ -0,0 +1,16 @@
+export interface ResponseBody<T = any> {
+  message?: string
+  code?: number
+  data?: T
+  success: boolean
+}
+
+/** 统一返回结构体 */
+
+export interface PageResult<T = any> {
+  data: T[]
+  current?: number
+  pageSize?: number
+  total?: number
+  success: boolean
+}

File diff suppressed because it is too large
+ 1 - 0
src/assets/icons/about/box.svg


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

@@ -0,0 +1,5 @@
+<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>

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

@@ -0,0 +1,3 @@
+<?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>

File diff suppressed because it is too large
+ 0 - 32
src/assets/icons/exception/403.svg


File diff suppressed because it is too large
+ 0 - 32
src/assets/icons/exception/404.svg


File diff suppressed because it is too large
+ 0 - 36
src/assets/icons/exception/500.svg


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

@@ -1,38 +0,0 @@
-<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>

二進制
src/assets/images/bg-main.png


二進制
src/assets/images/bg-soa.png


二進制
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


二進制
src/assets/images/task/spatialOrientationAbility/bg_circle.png


二進制
src/assets/images/task/spatialOrientationAbility/bg_shape.png


二進制
src/assets/images/task/spatialOrientationAbility/car.png


二進制
src/assets/images/task/spatialOrientationAbility/cat.png


二進制
src/assets/images/task/spatialOrientationAbility/factory.png


二進制
src/assets/images/task/spatialOrientationAbility/flower.png


二進制
src/assets/images/task/spatialOrientationAbility/forest.png


二進制
src/assets/images/task/spatialOrientationAbility/hospital.png


二進制
src/assets/images/task/spatialOrientationAbility/house.png


二進制
src/assets/images/task/spatialOrientationAbility/icon_center.png


二進制
src/assets/images/task/spatialOrientationAbility/icon_pointer.png


二進制
src/assets/images/task/spatialOrientationAbility/icon_pointer_red.png


二進制
src/assets/images/task/spatialOrientationAbility/lakes.png


二進制
src/assets/images/task/spatialOrientationAbility/massif.png


二進制
src/assets/images/task/spatialOrientationAbility/stop.png


二進制
src/assets/images/task/spatialOrientationAbility/trafficLight.png


二進制
src/assets/logo.png


二進制
src/assets/tabbar/about.png


二進制
src/assets/tabbar/about_n.png


二進制
src/assets/tabbar/comp.png


二進制
src/assets/tabbar/comp_n.png


二進制
src/assets/tabbar/home.png


二進制
src/assets/tabbar/home_n.png


+ 29 - 13
types/auto-imports.d.ts → src/auto-imports.d.ts

@@ -6,9 +6,14 @@
 export {}
 declare global {
   const EffectScope: typeof import('vue')['EffectScope']
-  const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
+  const afterAll: typeof import('vitest')['afterAll']
+  const afterEach: typeof import('vitest')['afterEach']
+  const assert: typeof import('vitest')['assert']
   const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
   const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
+  const beforeAll: typeof import('vitest')['beforeAll']
+  const beforeEach: typeof import('vitest')['beforeEach']
+  const chai: typeof import('vitest')['chai']
   const computed: typeof import('vue')['computed']
   const computedAsync: typeof import('@vueuse/core')['computedAsync']
   const computedEager: typeof import('@vueuse/core')['computedEager']
@@ -20,7 +25,6 @@ declare global {
   const createEventHook: typeof import('@vueuse/core')['createEventHook']
   const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
   const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
-  const createPinia: typeof import('pinia')['createPinia']
   const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
   const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
   const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
@@ -31,28 +35,30 @@ declare global {
   const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
   const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
   const defineComponent: typeof import('vue')['defineComponent']
-  const defineStore: typeof import('pinia')['defineStore']
+  const definePage: typeof import('unplugin-vue-router/runtime')['definePage']
+  const describe: typeof import('vitest')['describe']
   const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
   const effectScope: typeof import('vue')['effectScope']
+  const expect: typeof import('vitest')['expect']
   const extendRef: typeof import('@vueuse/core')['extendRef']
-  const getActivePinia: typeof import('pinia')['getActivePinia']
+  const getActiveHead: typeof import('@unhead/vue')['getActiveHead']
   const getCurrentInstance: typeof import('vue')['getCurrentInstance']
   const getCurrentScope: typeof import('vue')['getCurrentScope']
   const h: typeof import('vue')['h']
+  const i18n: typeof import('@/utils/i18n')['i18n']
   const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
   const inject: typeof import('vue')['inject']
+  const injectHead: typeof import('@unhead/vue')['injectHead']
   const injectLocal: typeof import('@vueuse/core')['injectLocal']
+  const isDark: typeof import('./composables/dark')['isDark']
   const isDefined: typeof import('@vueuse/core')['isDefined']
   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 it: typeof import('vitest')['it']
+  const locale: typeof import('@/utils/i18n')['locale']
   const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
-  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']
@@ -75,6 +81,7 @@ declare global {
   const onUnmounted: typeof import('vue')['onUnmounted']
   const onUpdated: typeof import('vue')['onUpdated']
   const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
+  const preferredDark: typeof import('./composables/dark')['preferredDark']
   const provide: typeof import('vue')['provide']
   const provideLocal: typeof import('@vueuse/core')['provideLocal']
   const reactify: typeof import('@vueuse/core')['reactify']
@@ -93,15 +100,14 @@ declare global {
   const resolveComponent: typeof import('vue')['resolveComponent']
   const resolveRef: typeof import('@vueuse/core')['resolveRef']
   const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
-  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 suite: typeof import('vitest')['suite']
   const syncRef: typeof import('@vueuse/core')['syncRef']
   const syncRefs: typeof import('@vueuse/core')['syncRefs']
   const templateRef: typeof import('@vueuse/core')['templateRef']
+  const test: typeof import('vitest')['test']
   const throttledRef: typeof import('@vueuse/core')['throttledRef']
   const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
   const toRaw: typeof import('vue')['toRaw']
@@ -109,6 +115,7 @@ declare global {
   const toRef: typeof import('vue')['toRef']
   const toRefs: typeof import('vue')['toRefs']
   const toValue: typeof import('vue')['toValue']
+  const toggleDark: typeof import('./composables/dark')['toggleDark']
   const triggerRef: typeof import('vue')['triggerRef']
   const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
   const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
@@ -185,6 +192,9 @@ declare global {
   const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
   const useGamepad: typeof import('@vueuse/core')['useGamepad']
   const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
+  const useHead: typeof import('@unhead/vue')['useHead']
+  const useHeadSafe: typeof import('@unhead/vue')['useHeadSafe']
+  const useI18n: typeof import('vue-i18n')['useI18n']
   const useIdle: typeof import('@vueuse/core')['useIdle']
   const useImage: typeof import('@vueuse/core')['useImage']
   const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
@@ -193,7 +203,7 @@ declare global {
   const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
   const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
   const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
-  const useLink: typeof import('vue-router')['useLink']
+  const useLink: typeof import('vue-router/auto')['useLink']
   const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
   const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
   const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
@@ -236,6 +246,10 @@ declare global {
   const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
   const useScroll: typeof import('@vueuse/core')['useScroll']
   const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
+  const useSeoMeta: typeof import('@unhead/vue')['useSeoMeta']
+  const useServerHead: typeof import('@unhead/vue')['useServerHead']
+  const useServerHeadSafe: typeof import('@unhead/vue')['useServerHeadSafe']
+  const useServerSeoMeta: typeof import('@unhead/vue')['useServerSeoMeta']
   const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
   const useShare: typeof import('@vueuse/core')['useShare']
   const useSlots: typeof import('vue')['useSlots']
@@ -279,6 +293,8 @@ declare global {
   const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
   const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
   const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
+  const vi: typeof import('vitest')['vi']
+  const vitest: typeof import('vitest')['vitest']
   const watch: typeof import('vue')['watch']
   const watchArray: typeof import('@vueuse/core')['watchArray']
   const watchAtMost: typeof import('@vueuse/core')['watchAtMost']

+ 34 - 0
src/components.d.ts

@@ -0,0 +1,34 @@
+/* eslint-disable */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+export {}
+
+/* prettier-ignore */
+declare module 'vue' {
+  export interface GlobalComponents {
+    Chart: typeof import('./components/Chart/index.vue')['default']
+    Container: typeof import('./components/Container.vue')['default']
+    CountDown: typeof import('./components/CountDown/index.vue')['default']
+    NavBar: typeof import('./components/NavBar.vue')['default']
+    RoundSlider: typeof import('./components/RoundSlider/index.vue')['default']
+    RouterLink: typeof import('vue-router')['RouterLink']
+    RouterView: typeof import('vue-router')['RouterView']
+    TabBar: typeof import('./components/TabBar.vue')['default']
+    VanButton: typeof import('vant/es')['Button']
+    VanCell: typeof import('vant/es')['Cell']
+    VanCellGroup: typeof import('vant/es')['CellGroup']
+    VanConfigProvider: typeof import('vant/es')['ConfigProvider']
+    VanEmpty: typeof import('vant/es')['Empty']
+    VanIcon: typeof import('vant/es')['Icon']
+    VanImage: typeof import('vant/es')['Image']
+    VanNavBar: typeof import('vant/es')['NavBar']
+    VanPicker: typeof import('vant/es')['Picker']
+    VanPopup: typeof import('vant/es')['Popup']
+    VanSpace: typeof import('vant/es')['Space']
+    VanStepper: typeof import('vant/es')['Stepper']
+    VanSwitch: typeof import('vant/es')['Switch']
+    VanTabbar: typeof import('vant/es')['Tabbar']
+    VanTabbarItem: typeof import('vant/es')['TabbarItem']
+  }
+}

+ 179 - 0
src/components/Chart/dark.ts

@@ -0,0 +1,179 @@
+const contrastColor = 'rgba(255, 255, 255, 0.65)'
+const backgroundColor = 'transparent'
+const axisCommon = function () {
+  return {
+    axisLine: {
+      lineStyle: {
+        color: contrastColor,
+      },
+    },
+    splitLine: {
+      lineStyle: {
+        color: '#484753',
+      },
+    },
+    splitArea: {
+      areaStyle: {
+        color: ['rgba(255,255,255,0.02)', 'rgba(255,255,255,0.05)'],
+      },
+    },
+    minorSplitLine: {
+      lineStyle: {
+        color: '#20203B',
+      },
+    },
+  }
+}
+
+const colorPalette = [
+  '#4992ff',
+  '#7cffb2',
+  '#fddd60',
+  '#ff6e76',
+  '#58d9f9',
+  '#05c091',
+  '#ff8a45',
+  '#8d48e3',
+  '#dd79ff',
+]
+const theme: any = {
+  color: colorPalette,
+  backgroundColor,
+  axisPointer: {
+    lineStyle: {
+      color: '#817f91',
+    },
+    crossStyle: {
+      color: '#817f91',
+    },
+    label: {
+      // TODO Contrast of label backgorundColor
+      color: '#fff',
+    },
+  },
+  legend: {
+    textStyle: {
+      color: contrastColor,
+    },
+  },
+  textStyle: {
+    color: contrastColor,
+  },
+  title: {
+    textStyle: {
+      color: 'red',
+    },
+    subtextStyle: {
+      color: 'rgba(255, 255, 255, 0.65)',
+    },
+  },
+  toolbox: {
+    iconStyle: {
+      borderColor: contrastColor,
+    },
+  },
+  dataZoom: {
+    borderColor: '#71708A',
+    textStyle: {
+      color: contrastColor,
+    },
+    brushStyle: {
+      color: 'rgba(135,163,206,0.3)',
+    },
+    handleStyle: {
+      color: '#353450',
+      borderColor: '#C5CBE3',
+    },
+    moveHandleStyle: {
+      color: '#B0B6C3',
+      opacity: 0.3,
+    },
+    fillerColor: 'rgba(135,163,206,0.2)',
+    emphasis: {
+      handleStyle: {
+        borderColor: '#91B7F2',
+        color: '#4D587D',
+      },
+      moveHandleStyle: {
+        color: '#636D9A',
+        opacity: 0.7,
+      },
+    },
+    dataBackground: {
+      lineStyle: {
+        color: '#71708A',
+        width: 1,
+      },
+      areaStyle: {
+        color: '#71708A',
+      },
+    },
+    selectedDataBackground: {
+      lineStyle: {
+        color: '#87A3CE',
+      },
+      areaStyle: {
+        color: '#87A3CE',
+      },
+    },
+  },
+  visualMap: {
+    textStyle: {
+      color: contrastColor,
+    },
+  },
+  timeline: {
+    lineStyle: {
+      color: contrastColor,
+    },
+    label: {
+      color: contrastColor,
+    },
+    controlStyle: {
+      color: contrastColor,
+      borderColor: contrastColor,
+    },
+  },
+  calendar: {
+    itemStyle: {
+      color: backgroundColor,
+    },
+    dayLabel: {
+      color: contrastColor,
+    },
+    monthLabel: {
+      color: contrastColor,
+    },
+    yearLabel: {
+      color: contrastColor,
+    },
+  },
+  timeAxis: axisCommon(),
+  logAxis: axisCommon(),
+  valueAxis: axisCommon(),
+  categoryAxis: axisCommon(),
+
+  line: {
+    symbol: 'circle',
+  },
+  graph: {
+    color: colorPalette,
+  },
+  gauge: {
+    title: {
+      color: contrastColor,
+    },
+  },
+  candlestick: {
+    itemStyle: {
+      color: '#FD1050',
+      color0: '#0CF49B',
+      borderColor: '#FD1050',
+      borderColor0: '#0CF49B',
+    },
+  },
+}
+
+theme.categoryAxis.splitLine.show = false
+
+export default theme

+ 65 - 0
src/components/Chart/index.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import type { ECharts } from 'echarts'
+import * as echarts from 'echarts'
+import { debounce } from 'lodash-es'
+import { addListener, removeListener } from 'resize-detector'
+import dark from './dark'
+
+const props = defineProps({
+  option: Object,
+})
+
+echarts.registerTheme('dark-chart', dark)
+
+const chartDom = ref<HTMLDivElement>()
+let chart: ECharts | null = null
+const isRealDark = ref(isDark.value)
+function resizeChart() {
+  chart?.resize()
+}
+
+const resize = debounce(resizeChart, 300)
+
+function disposeChart() {
+  if (chartDom.value)
+    removeListener(chartDom.value, resize)
+
+  chart?.dispose()
+  chart = null
+}
+
+function initChart() {
+  disposeChart()
+  if (chartDom.value) {
+    // init echarts
+    chart = echarts.init(chartDom.value, isRealDark.value ? 'dark-chart' : undefined)
+    chart.setOption(props.option)
+    addListener(chartDom.value, resize)
+  }
+}
+
+watch(isRealDark, () => {
+  initChart()
+}, {
+  flush: 'post',
+})
+
+onMounted(() => {
+  watch(() => props.option, () => {
+    chart?.setOption(props.option)
+  }, {
+    deep: true,
+    flush: 'post',
+  })
+
+  initChart()
+})
+
+onUnmounted(() => {
+  disposeChart()
+})
+</script>
+
+<template>
+  <div ref="chartDom" />
+</template>

+ 23 - 0
src/components/Container.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import vw from '@/utils/inline-px-to-vw'
+
+defineProps({
+  paddingT: {
+    type: Number,
+    default: 46,
+  },
+  paddingX: {
+    type: Number,
+    default: 16,
+  },
+})
+</script>
+
+<template>
+  <main
+    class="absolute left-0 h-full w-full overflow-y-auto"
+    :style="`padding-top: ${vw(paddingT)}; padding-left: ${vw(paddingX)}; padding-right: ${vw(paddingX)}`"
+  >
+    <slot />
+  </main>
+</template>

+ 71 - 0
src/components/CountDown/index.vue

@@ -0,0 +1,71 @@
+<script setup lang="ts">
+/*
+   * 组件名: CountDown
+   * 组件用途: 倒计时组件
+   * 创建日期: 2024/8/16
+   * 编写者: JutarryWu
+   */
+const props = defineProps({
+  time: {
+    type: Number,
+    default: 0,
+  },
+  text: {
+    type: String,
+    default: '测试训练即将开始!',
+  },
+  color: {
+    type: String,
+    default: 'black',
+  },
+  size: {
+    type: Number,
+    default: 36,
+  },
+})
+const emit = defineEmits(['endCountDown'])
+const 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>
+
+<template>
+  <section
+    class="van-count-down-container absolute-center h-[45px] w-[90%] translate-[-50%] text-center align-bottom"
+    :style="{ color, fontSize: `${size}px` }"
+  >
+    {{ countDownStr }}<span v-if="showSpan" class="ml-[4px] mt-[8px] text-[28px]">s</span>
+  </section>
+</template>
+
+<style scoped lang="scss"></style>

+ 0 - 51
src/components/Logo.vue

@@ -1,51 +0,0 @@
-<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>

+ 30 - 0
src/components/NavBar.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+const route = useRoute()
+const router = useRouter()
+
+function onBack() {
+  if (window.history.state.back)
+    history.back()
+  else
+    router.replace('/')
+}
+
+const { t } = useI18n()
+
+const title = computed(() => {
+  if (!route.meta)
+    return ''
+
+  return route.meta.i18n ? t(route.meta.i18n) : (route.meta.title || '')
+})
+</script>
+
+<template>
+  <VanNavBar
+    v-show="title"
+    :title="title"
+    :fixed="true"
+    clickable left-arrow
+    @click-left="onBack"
+  />
+</template>

+ 3 - 0
src/components/README.md

@@ -0,0 +1,3 @@
+# Components
+
+Components in this dir will be auto-registered and on-demand, powered by [`unplugin-vue-components`](https://github.com/antfu/unplugin-vue-components).

Some files were not shown because too many files changed in this diff