JutarryWu 5 months ago
parent
commit
c35362a1cb
100 changed files with 3653 additions and 8392 deletions
  1. 0 68
      .commitlintrc.js
  2. 0 9
      .editorconfig
  3. 9 12
      .env.development
  4. 8 16
      .env.production
  5. 0 17
      .env.test
  6. 27 3
      .gitignore
  7. 122 11
      .idea/workspace.xml
  8. 0 4
      .lintstagedrc
  9. 0 1
      .node-version
  10. 0 3
      .npmrc
  11. 1 1
      LICENSE
  12. 238 9
      README.md
  13. BIN
      docs/console.png
  14. BIN
      docs/home-en.png
  15. BIN
      docs/home-zh-dark.png
  16. BIN
      docs/home-zh.png
  17. 0 31
      eslint.config.js
  18. 161 10
      index.html
  19. 8 0
      jsconfig.json
  20. 31 0
      mock/index.js
  21. 31 103
      package.json
  22. 0 17
      plop-templates/component/index.hbs
  23. 0 66
      plop-templates/component/prompt.js
  24. 0 21
      plop-templates/page/index.hbs
  25. 0 54
      plop-templates/page/prompt.js
  26. 0 13
      plop-templates/store/index.hbs
  27. 0 28
      plop-templates/store/prompt.js
  28. 0 13
      plopfile.js
  29. 1808 5906
      pnpm-lock.yaml
  30. 0 14
      postcss.config.js
  31. 0 0
      public/.nojekyll
  32. BIN
      public/favicon-vue.ico
  33. BIN
      public/favicon.ico
  34. 8 118
      src/App.vue
  35. 0 105
      src/api/game.ts
  36. 0 73
      src/api/index.ts
  37. 18 0
      src/api/mock/index.js
  38. 0 16
      src/api/modules/user.ts
  39. 0 16
      src/api/typing.ts
  40. 0 0
      src/assets/icons/403.svg
  41. 0 0
      src/assets/icons/404.svg
  42. 0 1
      src/assets/icons/correct.svg
  43. 0 1
      src/assets/icons/example-crown.svg
  44. 0 1
      src/assets/icons/example-emotion-laugh-line.svg
  45. 0 1
      src/assets/icons/example-emotion-line.svg
  46. 0 1
      src/assets/icons/example-emotion-unhappy-line.svg
  47. 0 1
      src/assets/icons/example-star.svg
  48. 0 1
      src/assets/icons/example-vip.svg
  49. 0 0
      src/assets/icons/pentagram.svg
  50. 1 0
      src/assets/icons/svg/dark.svg
  51. 0 0
      src/assets/icons/svg/github.svg
  52. 0 0
      src/assets/icons/svg/i18n.svg
  53. 1 0
      src/assets/icons/svg/light.svg
  54. 1 0
      src/assets/icons/svg/link.svg
  55. 22 0
      src/assets/icons/svgo.yml
  56. 0 1
      src/assets/icons/wrong.svg
  57. BIN
      src/assets/images/404.png
  58. BIN
      src/assets/images/bg-main.png
  59. BIN
      src/assets/images/bg-soa.png
  60. BIN
      src/assets/images/logo.png
  61. 1 0
      src/assets/logo.svg
  62. BIN
      src/assets/logo/logo_melomini.png
  63. 0 35
      src/assets/styles/globals.css
  64. 34 0
      src/assets/styles/index.scss
  65. 370 0
      src/assets/styles/normalize.css
  66. 0 63
      src/assets/styles/nprogress.css
  67. 390 0
      src/assets/styles/reset.scss
  68. 0 55
      src/assets/styles/resources/utils.scss
  69. 0 1
      src/assets/styles/resources/variables.scss
  70. 13 0
      src/assets/styles/variables.scss
  71. 0 147
      src/components/AppSetting/index.vue
  72. 0 20
      src/components/Auth/index.vue
  73. 0 20
      src/components/AuthAll/index.vue
  74. 0 71
      src/components/CountDown/index.vue
  75. 0 49
      src/components/NotAllowed/index.vue
  76. 0 259
      src/components/PageLayout/index.vue
  77. 0 47
      src/components/PageMain/index.vue
  78. 0 229
      src/components/RoundSlider/index.vue
  79. 0 78
      src/components/SvgIcon/index.vue
  80. 0 38
      src/components/Trend/index.vue
  81. 0 65
      src/components/VanFieldCalendar/index.vue
  82. 0 58
      src/components/VanFieldDatePicker/index.vue
  83. 0 51
      src/components/VanFieldPicker/index.vue
  84. 0 71
      src/components/VoiceImp/index.vue
  85. 0 23
      src/components/WuIsCorrect/index.vue
  86. 22 0
      src/components/nav-bar/index.vue
  87. 55 0
      src/components/svg-icon/index.vue
  88. 35 0
      src/components/tabbar/index.vue
  89. 18 0
      src/layout/404.vue
  90. 33 0
      src/layout/index.vue
  91. 35 0
      src/locales/index.js
  92. 38 0
      src/locales/lang/en.json
  93. 38 0
      src/locales/lang/zh.json
  94. 9 0
      src/locales/locales.js
  95. 32 0
      src/main.js
  96. 0 32
      src/main.ts
  97. 0 47
      src/mock/user.ts
  98. 0 132
      src/router/guards.ts
  99. 35 0
      src/router/index.js
  100. 0 35
      src/router/index.ts

+ 0 - 68
.commitlintrc.js

@@ -1,68 +0,0 @@
-/** @type {import('cz-git').UserConfig} */
-export default {
-  rules: {
-    // @see: https://commitlint.js.org/#/reference-rules
-  },
-  prompt: {
-    alias: { fd: 'docs: fix typos' },
-    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: ':rewind:' },
-      { value: 'chore', name: 'chore:    🔨  其他修改 | Other changes that do not modify src or test files', emoji: ':hammer:' },
-    ],
-    useEmoji: false,
-    emojiAlign: 'center',
-    themeColorCode: '',
-    scopes: [],
-    allowCustomScopes: true,
-    allowEmptyScopes: true,
-    customScopesAlign: 'bottom',
-    customScopesAlias: 'custom',
-    emptyScopesAlias: 'empty',
-    upperCaseSubject: false,
-    markBreakingChangeMode: true,
-    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',
-    allowCustomIssuePrefixs: true,
-    allowEmptyIssuePrefixs: true,
-    confirmColorize: true,
-    maxHeaderLength: Number.POSITIVE_INFINITY,
-    maxSubjectLength: Number.POSITIVE_INFINITY,
-    minSubjectLength: 0,
-    scopeOverrides: undefined,
-    defaultBody: '',
-    defaultIssues: '',
-    defaultScope: '',
-    defaultSubject: '',
-  },
-}

+ 0 - 9
.editorconfig

@@ -1,9 +0,0 @@
-root = true
-
-[*]
-charset = utf-8
-indent_style = space
-indent_size = 2
-end_of_line = lf
-insert_final_newline = true
-trim_trailing_whitespace = true

+ 9 - 12
.env.development

@@ -1,13 +1,10 @@
-# 应用配置面板
-VITE_APP_SETTING = true
-# 页面标题
-VITE_APP_TITLE = Fantastic-mobile 基础版
-# 接口请求地址,会设置到 axios 的 baseURL 参数上
-VITE_APP_API_BASEURL = https://byly.jue-ming.com:8112/
-# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
-VITE_APP_DEBUG_TOOL =
+NODE_ENV = 'development'
 
-# 是否开启代理
-VITE_OPEN_PROXY = true
-# 是否开启开发者工具
-VITE_OPEN_DEVTOOLS = true
+# /dev-api 为本地 mock 地址
+VITE_BASE_API = '/dev-api'
+
+# 开发环境启用 VCONSOLE 调试工具。若不启用,将 true 修改为 false
+VITE_ENABLE_VCONSOLE = true
+
+# 打包发布路径
+VITE_BASE_URL = ''

+ 8 - 16
.env.production

@@ -1,17 +1,9 @@
-# 应用配置面板
-VITE_APP_SETTING = false
-# 页面标题
-VITE_APP_TITLE = Fantastic-mobile 基础版
-# 接口请求地址,会设置到 axios 的 baseURL 参数上
-VITE_APP_API_BASEURL = https://byly.jue-ming.com:8112/
-# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
-VITE_APP_DEBUG_TOOL =
+NODE_ENV = 'production'
+# baseUrl 
+VITE_BASE_API = "/"
 
-# 是否在打包时启用 Mock
-VITE_BUILD_MOCK = false
-# 是否在打包时生成 sourcemap
-VITE_BUILD_SOURCEMAP = false
-# 是否在打包时开启压缩,支持 gzip 和 brotli
-VITE_BUILD_COMPRESS = gzip,brotli
-# 是否在打包后生成存档,支持 zip 和 tar
-VITE_BUILD_ARCHIVE =
+# 生成环境不 VCONSOLE 调试工具。若启用,将 false 修改为 true
+VITE_ENABLE_VCONSOLE = false
+
+# 线上环境平台打包发布路径
+VITE_BASE_URL = "/vue3-h5-template"

+ 0 - 17
.env.test

@@ -1,17 +0,0 @@
-# 应用配置面板
-VITE_APP_SETTING = false
-# 页面标题
-VITE_APP_TITLE = Fantastic-mobile 基础版
-# 接口请求地址,会设置到 axios 的 baseURL 参数上
-VITE_APP_API_BASEURL = /
-# 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
-VITE_APP_DEBUG_TOOL =
-
-# 是否在打包时启用 Mock
-VITE_BUILD_MOCK = true
-# 是否在打包时生成 sourcemap
-VITE_BUILD_SOURCEMAP = true
-# 是否在打包时开启压缩,支持 gzip 和 brotli
-VITE_BUILD_COMPRESS =
-# 是否在打包后生成存档,支持 zip 和 tar
-VITE_BUILD_ARCHIVE =

+ 27 - 3
.gitignore

@@ -1,7 +1,31 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+pnpm-lock.yaml
+
 node_modules
 .DS_Store
-dist*
+dist
 dist-ssr
+coverage
 *.local
-.eslintcache
-.stylelintcache
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+# .vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo

+ 122 - 11
.idea/workspace.xml

@@ -5,17 +5,128 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="b9dc0b93-aea2-4509-84d8-c1e57bc059b1" name="更改" comment="修改后台请求接口路径">
-      <change afterPath="$PROJECT_DIR$/src/api/game.ts" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/api/typing.ts" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/store/mutation-type.ts" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/src/utils/request.ts" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.commitlintrc.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/.editorconfig" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/.env.development" beforeDir="false" afterPath="$PROJECT_DIR$/.env.development" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.env.production" beforeDir="false" afterPath="$PROJECT_DIR$/.env.production" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.env.test" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/.lintstagedrc" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/.node-version" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/.npmrc" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/LICENSE" beforeDir="false" afterPath="$PROJECT_DIR$/LICENSE" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/eslint.config.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/index.html" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/plop-templates/component/index.hbs" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/plop-templates/component/prompt.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/plop-templates/page/index.hbs" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/plop-templates/page/prompt.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/plop-templates/store/index.hbs" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/plop-templates/store/prompt.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/plopfile.js" beforeDir="false" />
       <change beforePath="$PROJECT_DIR$/pnpm-lock.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/pnpm-lock.yaml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/router/guards.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/router/guards.ts" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/types/components.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/types/components.d.ts" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/types/typed-router.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/types/typed-router.d.ts" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/main/index.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/cognitiveTasks/main/index.vue" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/postcss.config.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/public/.nojekyll" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/public/favicon.ico" beforeDir="false" afterPath="$PROJECT_DIR$/public/favicon.ico" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/App.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.vue" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/api/game.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/api/index.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/api/modules/user.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/api/typing.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/403.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/404.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/correct.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/example-crown.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/example-emotion-laugh-line.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/example-emotion-line.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/example-emotion-unhappy-line.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/example-star.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/example-vip.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/pentagram.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/icons/wrong.svg" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/images/bg-main.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/images/bg-soa.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/images/logo.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/styles/globals.css" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/styles/nprogress.css" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/styles/resources/utils.scss" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/assets/styles/resources/variables.scss" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/AppSetting/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/Auth/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/AuthAll/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/CountDown/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/NotAllowed/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/PageLayout/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/PageMain/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/RoundSlider/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/SvgIcon/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/Trend/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/VanFieldCalendar/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/VanFieldDatePicker/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/VanFieldPicker/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/VoiceImp/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/components/WuIsCorrect/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/main.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/mock/user.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/router/guards.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/router/index.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/settings.default.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/settings.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/store/index.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/store/modules/keepAlive.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/store/modules/settings.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/store/modules/user.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/store/mutation-type.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/types/auto-imports.d.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/types/components.d.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/types/global.d.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/types/route-meta.d.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/types/shims.d.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/types/typed-router.d.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HBadge.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HButton.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HDialog.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HInput.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HSlideover.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HTabList.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-kit/HToggle.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-provider/index.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/ui-provider/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/composables/useAuth.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/composables/useGlobalProperties.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/composables/usePage.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/dayjs.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/directive.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/eventBus.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/index.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/request.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/utils/system.copyright.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/[...all].vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/BreadthTraining/BTRandomPentagram.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/BreadthTraining/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/ContinueAddition/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/PictureNaming/Topics.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/PictureNaming/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/PicturePuzzle/Topics.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/PicturePuzzle/components/PPCountDown/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/PicturePuzzle/components/PicturePuzzleChild/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/PicturePuzzle/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/cocos/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/main/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/cognitiveTasks/spatialOrientationAbility/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/login.vue" beforeDir="false" afterPath="$PROJECT_DIR$/src/views/login.vue" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/reload.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/src/views/user/index.vue" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/stylelint.config.js" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/themes/index.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/tsconfig.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/tsconfig.node.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/uno.config.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/vite.config.ts" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/vite/plugins.ts" beforeDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -51,7 +162,7 @@
                 <list>
                   <RecentBranch>
                     <option name="branchName" value="20240919-Jutarry" />
-                    <option name="lastUsedInstant" value="1726733720" />
+                    <option name="lastUsedInstant" value="1726756299" />
                   </RecentBranch>
                   <RecentBranch>
                     <option name="branchName" value="dev" />
@@ -98,7 +209,7 @@
     "node.js.selected.package.tslint": "(autodetect)",
     "nodejs_package_manager_path": "pnpm",
     "settings.editor.selected.configurable": "preferences.pluginManager",
-    "ts.external.directory.path": "E:\\WorkSpace\\Web\\insomnia-cognition-h5\\node_modules\\typescript\\lib",
+    "ts.external.directory.path": "D:\\Program Files\\JetBrains\\WebStorm 2024.1.3\\plugins\\javascript-plugin\\jsLanguageServicesImpl\\external",
     "vue.rearranger.settings.migration": "true"
   },
   "keyToStringList": {

+ 0 - 4
.lintstagedrc

@@ -1,4 +0,0 @@
-{
-  "*.{ts,tsx,vue}": "eslint --cache --fix",
-  "*.{css,scss,vue}": "stylelint --cache --fix"
-}

+ 0 - 1
.node-version

@@ -1 +0,0 @@
-20

+ 0 - 3
.npmrc

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

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2024 fantastic-mobile
+Copyright (c) 2024 九月
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 238 - 9
README.md

@@ -1,9 +1,238 @@
-## 特点
-
-- 可自由替换 UI 组件库,默认使用 Vant
-- 提供页面整体布局组件,顶部导航栏、顶部标签栏、返回顶部、记录滚动位置等特性
-- 提供系统配置文件,轻松实现个性化定制
-- 基于文件系统的路由
-- 支持全方位权限验证
-- 内置最佳页面缓存方案
-- 轻松实现国际化多语言适配
+
+
+<div align="center">
+	<img src="src/assets/logo/logo_melomini.png" alt="logo_melomini" width=200 />
+</div>
+
+<div align="center">
+  <a href="https://gitee.com/limin04551/vue3-h5-template/blob/main/LICENSE">
+   <img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="MIT">
+  </a>
+  <a href="https://gitee.com/limin04551/vue3-h5-template/stargazers">
+    <img src="https://gitee.com/limin04551/vue3-h5-template/badge/star.svg" alt="Gitee star">
+  </a>
+  <a href="https://gitee.com/limin04551/vue3-h5-template">
+    <img src="https://gitee.com/limin04551/vue3-h5-template/badge/fork.svg?style=flat-square" alt="Gitee fork">
+  </a>
+</div>
+
+
+<h1 align="center">vue3 h5 template</h1>
+
+
+
+**🌱 基于 Vue3 全家桶、JavaScript-放弃为TS而TS🤪、Vite 构建工具,开箱即用的 H5 移动端项目基础模板。**
+
+- [x] ⚡ Vue3 + Vite4
+- [x] 🍕 JavaScript 无需类型定义
+- [x] ✨ Vant4 组件库
+- [x] 🎨 Unocss - 高性能且极具灵活性原子化CSS
+- [x] 🍍 Pinia 状态管理并进行持久化
+- [x] 🌓 支持深色模式
+- [x] 🌍 I18n 国际化开箱即用
+- [x] 🔥 Vue-router 4
+- [x] 📥 API及组件 自动加载 无需引入
+- [x] 🌠 支持 SVG 图标自动注册组件
+- [x] 🖼️ rem 适配
+- [x] 📩 Axios 封装
+- [x] 📨 开发环境支持 Mock 数据
+- [x] 📊 首屏加载动画
+- [x] 💻 VConsole 开发环境调试面板
+- [x] 📦 打包资源 gzip 压缩
+
+
+
+## 在线预览Preview
+
+👓 [点击这里](http://124.223.116.62:10000/vue3-h5-template/)(PC浏览器请切换手机端模式)
+
+
+
+## 截图
+<div style="flex justify-content: space-between;">
+<img alt="" src="docs/home-zh.png" width=200>
+<img alt="" src="docs/home-en.png" width=200>
+<img alt="" src="docs/home-zh-dark.png" width=200>
+</div>
+
+
+
+## 运行项目
+
+注意:要求 Node 版本 16+,可使用 [nvm](https://github.com/nvm-sh/nvm#installing-and-updating) 进行本地 Node 版本管理,同时建议使用 [pnpm](https://pnpm.io/zh/installation) 包管理器。
+
+```shell
+# 克隆项目
+git clone https://gitee.com/limin04551/vue3-h5-template.git
+
+# 进入项目目录
+cd vue3-h5-template
+
+# 安装依赖
+pnpm install
+
+# 启动服务
+pnpm dev
+```
+
+
+## 文档引导
+
+> - [按需引入 vant 组件](#vant)
+> - [SVG 图标使用](#svg)
+> - [路由缓存](#router)
+> - [调试面板 vconsole](#console)
+> - [动态设置页面标题](#page-title)
+> - [rem 视口适配](#viewport)
+> - [Unocss 原子类框架](#Unocss)
+> - [Git 提交信息规范](#git)
+
+
+
+### - <span id="vant">按需引入 vant 组件</span>
+
+全量引入组件库太过臃肿,项目中使用 `unplugin-vue-components` 插件进行按需自动引入组件,可通过[官方文档](https://vant-ui.github.io/vant/#/zh-CN/quickstart#2.-pei-zhi-cha-jian)了解更多。
+
+
+
+### - <span id="svg">SVG 图标使用</span>
+
+
+> 1. 将 svg 图标文件放在 `src/assets/icons/svg` 目录下
+> 2. 在项目中直接使用 `<svg-icon name="svg图标文件命名" />` 即可
+
+例如:
+
+本项目 `src/assets/icons/svg` 中放了个 `dark.svg` 的图标文件,然后在组件 `name` 属性中填入文件的命名即可,So easy~
+
+```Vue
+<svg-icon name="dark" />
+```
+
+> 项目中使用了 `unplugin-vue-components` 自动引入组件,所以 `main.ts` 中无需注册全局图标组件。
+
+
+
+### - <span id="router">路由缓存</span>
+
+组件默认不缓存,如某个组件需缓存,在对应路由 `meta` 内的 `isKeepAlive` 字段赋值为 `true` 即可。
+
+```javascript
+// src/router/routes.js
+ {
+        path: "about",
+        name: "about",
+        component: () => import('../views/about/index.vue'),
+        meta: {
+          title: "关于",
+          isKeepAlive: true // 进行路由缓存
+        }
+      }
+```
+
+
+### - <span id="console">调试面板 vconsole</span>
+<img alt="" src="docs/console.png" width=240>
+
+为了方便移动端查看 log 信息和调试,开发环境引入了 vconsole 调试面板。如果你的开发环境不需要的话请在 `main.js` 中注释
+
+```html
+# .env.development
+
+# 开发环境启用 VCONSOLE 调试工具。若不启用,将 true 修改为 false
+VITE_ENABLE_VCONSOLE = true
+```
+
+
+
+### - <span id="page-title">动态设置页面标题</span>
+
+在路由全局前置守卫中:
+
+```js
+// src/router/index.js
+// ...
+router.beforeEach((to, from, next) => {
+  NProgress.start();
+  useTitle(to.meta.title || pageDefaultTitle)// 设置页面标题
+  if (getToken()) {
+    next();
+  } else if (whiteList.includes(to.path)) {
+    next();
+  } else {
+    next('/login');
+  }
+});
+```
+
+
+
+### - <span id="mock">开发环境 Mock</span>
+
+> 本项目开发环境支持 mock 请求数据,在 `mock` 目录中可配置接口和数据,具体见[文档](http://mockjs.com)。
+
+
+
+### - <span id="viewport">rem 适配</span>
+
+使用 `postcss-pxtorem amfe-flexible` 进行视口适配,相关配置见项目根目录下 `vite.config.js`。
+
+```js
+// vite.config.js
+css: {
+    postcss: {
+      plugins: [
+        postCssPxToRem({
+          rootValue: 37.5, // 1rem,根据 设计稿宽度/10 进行设置
+          propList: ['*'], // 需要转换的属性,这里选择全部都进行转换
+          selectorBlackList: ["norem"], // 过滤掉norem-开头的class,不进行rem转换
+        })
+      ]
+    }
+  }
+```
+
+
+
+### - <span id="Unocss">Unocss 原子类框架</span>
+
+Tailwindcss 从 3.0 版本开始默认使用 `JIT` 模式,打包代码不再臃肿,结合 `vite` 使用非常香~ 如果你还没使用过类似的框架,Tailwindcss 首页的[示例](https://tailwindcss.com/)非常直观。
+
+官方文档:https://tailwindcss.com/docs/padding
+
+
+
+### - <span id="git">Git 提交信息规范</span>
+
+项目使用 `husky` 规范 Git 提交信息,遵循社区主流的 [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 开发中
+```
+
+```
+// 格式
+<type>(<scope>): <subject>
+// 示例
+feat(layout): 布局完成
+```
+
+## 鸣谢
+
+ [vue3-h5-template](https://github.com/yulimchen/vue3-h5-template) 
+
+## License
+
+[MIT license](https://gitee.com/limin04551/vue3-h5-template/blob/main/LICENSE).

BIN
docs/console.png


BIN
docs/home-en.png


BIN
docs/home-zh-dark.png


BIN
docs/home-zh.png


+ 0 - 31
eslint.config.js

@@ -1,31 +0,0 @@
-import antfu from '@antfu/eslint-config'
-
-export default antfu(
-  {
-    unocss: true,
-    ignores: [
-      'public',
-      'dist*',
-    ],
-  },
-  {
-    rules: {
-      'eslint-comments/no-unlimited-disable': 'off',
-      'curly': ['error', 'all'],
-      'ts/no-unused-expressions': ['error', {
-        allowShortCircuit: true,
-        allowTernary: true,
-      }],
-    },
-  },
-  {
-    files: [
-      'src/**/*.vue',
-    ],
-    rules: {
-      'vue/block-order': ['error', {
-        order: ['script', 'template', 'style'],
-      }],
-    },
-  },
-)

+ 161 - 10
index.html

@@ -1,12 +1,163 @@
 <!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" href="/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover"/>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.ts"></script>
-  </body>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <link rel="icon" href="/favicon.ico">
+  <meta name="viewport"
+    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
+  <title>H5基础开发模板</title>
+</head>
+
+<body>
+  <div id="app">
+    <div class="loader">
+      <div class="loader-inner">
+        <div class="loader-line-wrap">
+          <div class="loader-line"></div>
+        </div>
+        <div class="loader-line-wrap">
+          <div class="loader-line"></div>
+        </div>
+        <div class="loader-line-wrap">
+          <div class="loader-line"></div>
+        </div>
+        <div class="loader-line-wrap">
+          <div class="loader-line"></div>
+        </div>
+        <div class="loader-line-wrap">
+          <div class="loader-line"></div>
+        </div>
+      </div>
+    </div>
+
+  </div>
+  <script type="module" src="/src/main.js"></script>
+  <!-- 开启底部安全区适配 -->
+  <van-number-keyboard safe-area-inset-bottom />
+</body>
+
 </html>
+
+
+<style>
+  #app {
+    height: 100%;
+  }
+
+  .loader {
+    /* background: #000; */
+    /* background: radial-gradient(#777, #111); */
+    bottom: 0;
+    left: 0;
+    overflow: hidden;
+    position: fixed;
+    right: 0;
+    top: 0;
+    z-index: 99999;
+  }
+
+  .loader-inner {
+    bottom: 0;
+    height: 120px;
+    left: 0;
+    margin: auto;
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 200px;
+  }
+
+  .loader-line-wrap {
+    animation:
+      spin 2000ms cubic-bezier(.175, .885, .32, 1.275) infinite;
+    box-sizing: border-box;
+    height: 100px;
+    left: 0;
+    overflow: hidden;
+    position: absolute;
+    top: 0;
+    transform-origin: 50% 100%;
+    width: 200px;
+  }
+
+  .loader-line {
+    border: 8px solid transparent;
+    border-radius: 100%;
+    box-sizing: border-box;
+    height: 200px;
+    left: 0;
+    margin: 0 auto;
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 200px;
+  }
+
+  .loader-line-wrap:nth-child(1) {
+    animation-delay: -50ms;
+  }
+
+  .loader-line-wrap:nth-child(2) {
+    animation-delay: -100ms;
+  }
+
+  .loader-line-wrap:nth-child(3) {
+    animation-delay: -150ms;
+  }
+
+  .loader-line-wrap:nth-child(4) {
+    animation-delay: -200ms;
+  }
+
+  .loader-line-wrap:nth-child(5) {
+    animation-delay: -250ms;
+  }
+
+  .loader-line-wrap:nth-child(1) .loader-line {
+    border-color: hsl(0, 100%, 60%);
+    height: 180px;
+    width: 180px;
+    top: 14px;
+  }
+
+  .loader-line-wrap:nth-child(2) .loader-line {
+    border-color: hsl(60, 100%, 60%);
+    height: 152px;
+    width: 152px;
+    top: 28px;
+  }
+
+  .loader-line-wrap:nth-child(3) .loader-line {
+    border-color: hsl(120, 100%, 60%);
+    height: 124px;
+    width: 124px;
+    top: 42px;
+  }
+
+  .loader-line-wrap:nth-child(4) .loader-line {
+    border-color: hsl(180, 100%, 60%);
+    height: 96px;
+    width: 96px;
+    top: 56px;
+  }
+
+  .loader-line-wrap:nth-child(5) .loader-line {
+    border-color: hsl(240, 100%, 60%);
+    height: 68px;
+    width: 68px;
+    top: 70px;
+  }
+
+  @keyframes spin {
+
+    0%,
+    15% {
+      transform: rotate(0);
+    }
+
+    100% {
+      transform: rotate(360deg);
+    }
+  }
+</style>

+ 8 - 0
jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 31 - 0
mock/index.js

@@ -0,0 +1,31 @@
+import Mock from 'mockjs'
+
+const mock = [
+  {
+    url: "/dev-api/list/get",
+    delay: 1000,
+    method: 'get',
+    response: () => {
+      return {
+        code: 200,
+        msg: 'success',
+        data: '返回数据内容'
+      }
+    }
+  },
+  {
+    url: "/dev-api/list/error",
+    delay: 1000,
+    method: 'post',
+    response: () => {
+      return {
+        code: 400,
+        msg: '错误测试',
+        data: null
+      }
+    }
+  },
+
+]
+
+export default mock

+ 31 - 103
package.json

@@ -1,116 +1,44 @@
 {
+  "name": "vue3-h5-template",
+  "version": "0.0.0",
+  "private": true,
   "type": "module",
-  "version": "0.3.0",
-  "engines": {
-    "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
-  },
   "scripts": {
     "dev": "vite",
-    "build": "vue-tsc && vite build",
-    "build:test": "vue-tsc && vite build --mode test",
-    "serve": "http-server ./dist -o",
-    "serve:test": "http-server ./dist-test -o",
-    "svgo": "svgo -f src/assets/icons",
-    "new": "plop",
-    "lint": "npm-run-all -s lint:tsc lint:eslint lint:stylelint",
-    "lint:tsc": "vue-tsc --noEmit",
-    "lint:eslint": "eslint . --cache --fix",
-    "lint:stylelint": "stylelint \"src/**/*.{css,scss,vue}\" --cache --fix",
-    "postinstall": "simple-git-hooks",
-    "preinstall": "npx only-allow pnpm",
-    "commit": "git cz",
-    "release": "bumpp"
+    "build": "vite build --mode production",
+    "preview": "vite preview"
   },
   "dependencies": {
-    "@headlessui/vue": "^1.7.22",
-    "@vant/touch-emulator": "^1.4.0",
-    "@vueuse/components": "^11.0.3",
-    "@vueuse/core": "^11.0.3",
-    "@vueuse/integrations": "^11.0.3",
-    "animate.css": "^4.1.1",
-    "axios": "^1.7.7",
-    "dayjs": "^1.11.13",
-    "echarts": "^5.5.1",
-    "eruda": "^3.2.3",
-    "lodash-es": "^4.17.21",
-    "mitt": "^3.0.1",
-    "mockjs": "^1.1.0",
+    "@vueuse/core": "^10.9.0",
+    "amfe-flexible": "^2.2.1",
+    "axios": "^1.6.8",
+    "default-passive-events": "^2.0.0",
+    "js-cookie": "^3.0.5",
     "nprogress": "^0.2.0",
-    "overlayscrollbars-vue": "^0.5.9",
-    "path-browserify": "^1.0.1",
-    "pinia": "^2.2.2",
-    "qrcode": "^1.5.4",
-    "qs": "^6.13.0",
-    "swiper": "^11.1.11",
-    "vant": "^4.9.4",
+    "pinia": "^2.1.7",
+    "pinia-plugin-persistedstate": "^3.2.1",
+    "postcss-pxtorem": "^6.1.0",
+    "vant": "^4.8.11",
     "vconsole": "^3.15.1",
-    "vue": "^3.4.38",
-    "vue-esign": "^1.1.4",
-    "vue-m-message": "^4.0.2",
-    "vue-router": "^4.4.3"
+    "vite-plugin-vue-setup-extend": "^0.4.0",
+    "vue": "^3.4.21",
+    "vue-i18n": "^9.13.1",
+    "vue-router": "^4.3.0"
   },
   "devDependencies": {
-    "@antfu/eslint-config": "^2.25.1",
-    "@iconify/json": "^2.2.243",
-    "@iconify/vue": "^4.1.2",
-    "@stylistic/stylelint-config": "^2.0.0",
-    "@types/lodash-es": "^4.17.12",
-    "@types/mockjs": "^1.0.10",
-    "@types/nprogress": "^0.2.3",
-    "@types/path-browserify": "^1.0.3",
-    "@types/qrcode": "^1.5.5",
-    "@types/qs": "^6.9.15",
-    "@unocss/eslint-plugin": "^0.62.3",
-    "@unocss/preset-rem-to-px": "^0.62.3",
-    "@vitejs/plugin-legacy": "^5.4.2",
-    "@vitejs/plugin-vue": "^5.1.3",
-    "@vitejs/plugin-vue-jsx": "^4.0.1",
-    "@yeungkc/unocss-preset-safe-area": "^0.0.10",
-    "autoprefixer": "^10.4.20",
-    "boxen": "^8.0.1",
-    "bumpp": "^9.5.2",
-    "cz-git": "^1.9.4",
-    "eslint": "^9.9.1",
-    "http-server": "^14.1.1",
-    "less": "^4.2.0",
-    "lint-staged": "^15.2.9",
-    "npm-run-all2": "^6.2.2",
-    "picocolors": "^1.0.1",
-    "plop": "^4.0.1",
-    "postcss": "^8.4.42",
-    "postcss-mobile-forever": "^4.1.5",
-    "postcss-nested": "^6.2.0",
-    "sass": "^1.77.8",
-    "simple-git-hooks": "^2.11.1",
-    "stylelint": "^16.9.0",
-    "stylelint-config-recess-order": "^5.1.0",
-    "stylelint-config-standard-scss": "^13.1.0",
-    "stylelint-config-standard-vue": "^1.0.0",
-    "stylelint-scss": "^6.5.1",
-    "svgo": "^3.3.2",
-    "typescript": "^5.5.4",
-    "unocss": "^0.62.3",
-    "unplugin-auto-import": "^0.18.2",
-    "unplugin-turbo-console": "^1.10.1",
-    "unplugin-vue-components": "^0.27.4",
-    "unplugin-vue-router": "^0.10.7",
-    "vite": "^5.4.2",
-    "vite-plugin-archiver": "^0.1.1",
-    "vite-plugin-banner": "^0.7.1",
-    "vite-plugin-compression2": "^1.2.0",
-    "vite-plugin-fake-server": "^2.1.1",
-    "vite-plugin-pages": "^0.32.3",
+    "@vant/auto-import-resolver": "^1.1.0",
+    "@vitejs/plugin-vue": "^5.0.4",
+    "mockjs": "^1.1.0",
+    "sass": "^1.75.0",
+    "sass-loader": "^14.2.1",
+    "terser": "^5.30.4",
+    "unocss": "^0.59.4",
+    "unplugin-auto-import": "^0.17.5",
+    "unplugin-vue-components": "^0.26.0",
+    "vite": "^5.2.8",
+    "vite-plugin-compression": "^0.5.1",
+    "vite-plugin-mock": "^3.0.2",
     "vite-plugin-svg-icons": "^2.0.1",
-    "vite-plugin-vue-devtools": "^7.3.9",
-    "vue-tsc": "^2.1.4"
-  },
-  "simple-git-hooks": {
-    "pre-commit": "pnpm lint-staged",
-    "preserveUnused": true
-  },
-  "config": {
-    "commitizen": {
-      "path": "node_modules/cz-git"
-    }
+    "vite-plugin-vue-devtools": "^7.0.25"
   }
 }

+ 0 - 17
plop-templates/component/index.hbs

@@ -1,17 +0,0 @@
-<script setup lang="ts">
-{{#if isGlobal}}
-defineOptions({
-  name: '{{ properCase name }}',
-})
-{{/if}}
-</script>
-
-<template>
-  <div>
-    <!-- 布局 -->
-  </div>
-</template>
-
-<style scoped>
-/* 样式 */
-</style>

+ 0 - 66
plop-templates/component/prompt.js

@@ -1,66 +0,0 @@
-import fs from 'node:fs'
-
-function getFolder(path) {
-  const components = []
-  const files = fs.readdirSync(path)
-  files.forEach((item) => {
-    const stat = fs.lstatSync(`${path}/${item}`)
-    if (stat.isDirectory() === true && item !== 'components') {
-      components.push(`${path}/${item}`)
-      components.push(...getFolder(`${path}/${item}`))
-    }
-  })
-  return components
-}
-
-export default {
-  description: '创建组件',
-  prompts: [
-    {
-      type: 'confirm',
-      name: 'isGlobal',
-      message: '是否为全局组件',
-      default: false,
-    },
-    {
-      type: 'list',
-      name: 'path',
-      message: '请选择组件创建目录',
-      choices: getFolder('src/views'),
-      when: (answers) => {
-        return !answers.isGlobal
-      },
-    },
-    {
-      type: 'input',
-      name: 'name',
-      message: '请输入组件名称',
-      validate: (v) => {
-        if (!v || v.trim === '') {
-          return '组件名称不能为空'
-        }
-        else {
-          return true
-        }
-      },
-    },
-  ],
-  actions: (data) => {
-    let path = ''
-    if (data.isGlobal) {
-      path = 'src/components/{{properCase name}}/index.vue'
-    }
-    else {
-      path = `${data.path}/components/{{properCase name}}/index.vue`
-    }
-
-    const actions = [
-      {
-        type: 'add',
-        path,
-        templateFile: 'plop-templates/component/index.hbs',
-      },
-    ]
-    return actions
-  },
-}

+ 0 - 21
plop-templates/page/index.hbs

@@ -1,21 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: '{{ properCase componentName }}',
-})
-
-definePage({
-  meta: {
-    title: '新建页面',
-  },
-})
-</script>
-
-<template>
-  <PageLayout navbar>
-    <!-- 布局 -->
-  </PageLayout>
-</template>
-
-<style scoped>
-/* 样式 */
-</style>

+ 0 - 54
plop-templates/page/prompt.js

@@ -1,54 +0,0 @@
-import path from 'node:path'
-import fs from 'node:fs'
-
-function getFolder(path) {
-  const components = []
-  const files = fs.readdirSync(path)
-  files.forEach((item) => {
-    const stat = fs.lstatSync(`${path}/${item}`)
-    if (stat.isDirectory() === true && item !== 'components') {
-      components.push(`${path}/${item}`)
-      components.push(...getFolder(`${path}/${item}`))
-    }
-  })
-  return components
-}
-
-export default {
-  description: '创建页面',
-  prompts: [
-    {
-      type: 'list',
-      name: 'path',
-      message: '请选择页面创建目录',
-      choices: getFolder('src/views'),
-    },
-    {
-      type: 'input',
-      name: 'name',
-      message: '请输入文件名',
-      validate: (v) => {
-        if (!v || v.trim === '') {
-          return '文件名不能为空'
-        }
-        else {
-          return true
-        }
-      },
-    },
-  ],
-  actions: (data) => {
-    const relativePath = path.relative('src/views', data.path)
-    const actions = [
-      {
-        type: 'add',
-        path: `${data.path}/{{dotCase name}}.vue`,
-        templateFile: 'plop-templates/page/index.hbs',
-        data: {
-          componentName: `${relativePath} ${data.name}`,
-        },
-      },
-    ]
-    return actions
-  },
-}

+ 0 - 13
plop-templates/store/index.hbs

@@ -1,13 +0,0 @@
-const use{{ properCase name }}Store = defineStore(
-  // 唯一ID
-  '{{ camelCase name }}',
-  () => {
-    const someThing = ref(0)
-
-    return {
-      someThing,
-    }
-  },
-)
-
-export default use{{ properCase name }}Store

+ 0 - 28
plop-templates/store/prompt.js

@@ -1,28 +0,0 @@
-export default {
-  description: '创建全局状态',
-  prompts: [
-    {
-      type: 'input',
-      name: 'name',
-      message: '请输入模块名称',
-      validate: (v) => {
-        if (!v || v.trim === '') {
-          return '模块名称不能为空'
-        }
-        else {
-          return true
-        }
-      },
-    },
-  ],
-  actions: () => {
-    const actions = [
-      {
-        type: 'add',
-        path: 'src/store/modules/{{camelCase name}}.ts',
-        templateFile: 'plop-templates/store/index.hbs',
-      },
-    ]
-    return actions
-  },
-}

+ 0 - 13
plopfile.js

@@ -1,13 +0,0 @@
-import { promises as fs } from 'node:fs'
-
-export default async function (plop) {
-  plop.setWelcomeMessage('请选择需要创建的模式:')
-  const items = await fs.readdir('./plop-templates')
-  for (const item of items) {
-    const stat = await fs.lstat(`./plop-templates/${item}`)
-    if (stat.isDirectory()) {
-      const prompt = await import(`./plop-templates/${item}/prompt.js`)
-      plop.setGenerator(item, prompt.default)
-    }
-  }
-}

File diff suppressed because it is too large
+ 1808 - 5906
pnpm-lock.yaml


+ 0 - 14
postcss.config.js

@@ -1,14 +0,0 @@
-export default {
-  plugins: {
-    'autoprefixer': {},
-    'postcss-nested': {},
-    'postcss-mobile-forever': {
-      viewportWidth: 375,
-      maxDisplayWidth: 600,
-      border: true,
-      rootContainingBlockSelectorList: [
-        'van-popup',
-      ],
-    },
-  },
-}

+ 0 - 0
public/.nojekyll


BIN
public/favicon-vue.ico


BIN
public/favicon.ico


+ 8 - 118
src/App.vue

@@ -1,127 +1,17 @@
-<script setup lang="ts">
-import Provider from './ui-provider/index.vue'
-import eventBus from '@/utils/eventBus'
-import useSettingsStore from '@/store/modules/settings'
-import useKeepAliveStore from '@/store/modules/keepAlive'
+<script setup name="App">
+import router from './router';
 
-const route = useRoute()
 
-const settingsStore = useSettingsStore()
-const keepAliveStore = useKeepAliveStore()
-
-const { auth } = useAuth()
-
-const isAuth = computed(() => {
-  return route.matched.every((item) => {
-    return item.meta.auth ? (item.meta.auth === true ? true : auth(item.meta.auth)) : true
-  })
-})
-
-watch([
-  () => settingsStore.settings.app.enableDynamicTitle,
-  () => settingsStore.title,
-], () => {
-  nextTick(() => {
-    if (settingsStore.settings.app.enableDynamicTitle && settingsStore.title) {
-      document.title = settingsStore.title ?? import.meta.env.VITE_APP_TITLE
-    }
-    else {
-      document.title = import.meta.env.VITE_APP_TITLE
-    }
-  })
-}, {
-  immediate: true,
-  deep: true,
-})
-
-const enableAppSetting = import.meta.env.VITE_APP_SETTING === 'true'
 </script>
-
 <template>
-  <Provider>
-    <RouterView v-slot="{ Component }">
-      <Transition name="fade" mode="out-in" appear>
-        <KeepAlive :include="keepAliveStore.list">
-          <component :is="Component" v-if="isAuth" :key="route.fullPath" />
-          <NotAllowed v-else />
-        </KeepAlive>
-      </Transition>
-    </RouterView>
-    <template v-if="enableAppSetting">
-      <div class="app-setting" @click="eventBus.emit('global-app-setting-toggle')">
-        <SvgIcon name="i-uiw:setting-o" class="icon" />
-      </div>
-      <AppSetting />
-    </template>
-  </Provider>
+  <router-view v-slot="{ Component }">
+    <keep-alive>
+      <component :is="Component" :key="$route.fullPath" v-if="$route.meta.keepAlive" />
+    </keep-alive>
+    <component :is="Component" :key="$route.fullPath" v-if="!$route.meta.keepAlive" />
+  </router-view>
 </template>
 
 <style scoped>
-.app-setting {
-  --uno: text-white dark-text-dark bg-ui-primary;
-
-  position: fixed;
-  top: 70%;
-  right: 0;
-  z-index: 10;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 50px;
-  height: 50px;
-  font-size: 24px;
-  cursor: pointer;
-  border-radius: 5px 0 0 5px;
-
-  .icon {
-    animation: rotate 5s linear infinite;
-  }
-
-  @keyframes rotate {
-    from {
-      transform: rotate(0deg);
-    }
-
-    to {
-      transform: rotate(360deg);
-    }
-  }
-}
-
-.navbar-enter-active,
-.navbar-leave-active {
-  transition: transform 0.15s ease-in-out;
-}
-
-.navbar-enter-from,
-.navbar-leave-to {
-  transform: translateY(-100%);
-}
-
-.tabbar-enter-active,
-.tabbar-leave-active {
-  transition: transform 0.15s ease-in-out;
-}
-
-.tabbar-enter-from,
-.tabbar-leave-to {
-  transform: translateY(100%);
-}
-
-/* 主内容区动画 */
-.fade-enter-active {
-  transition: 0.2s;
-}
-
-.fade-leave-active {
-  transition: 0.15s;
-}
-
-.fade-enter-from {
-  opacity: 0;
-}
 
-.fade-leave-to {
-  opacity: 0;
-}
 </style>

+ 0 - 105
src/api/game.ts

@@ -1,105 +0,0 @@
-import request from '@/utils/request'
-import type { ResponseBody } from '@/api/typing'
-
-const GAME_BASE_URL = '/game'
-const GAME_BASE_URL_2 = '/record'
-
-class GameAPI {
-  /**
-   * 通过ID查询游戏信息
-   *
-   * @returns 游戏名称、编码等
-   */
-  static findById(id: string) {
-    return request<any, ResponseBody<GameVO>>({
-      url: `${GAME_BASE_URL}/findById`,
-      method: 'get',
-      params: { id },
-    })
-  }
-
-  /**
-   * 添加游戏结果数据
-   *
-   * @param data 游戏结果数据
-   */
-  static add(data: GameResultVO) {
-    return request({
-      url: `${GAME_BASE_URL_2}/save`,
-      method: 'post',
-      data,
-    })
-  }
-
-  /**
-   * 修改游戏
-   *
-   * @param data 游戏表单数据
-   */
-  // static update(data: GameVO) {
-  //   return request({
-  //     url: `${GAME_BASE_URL}/update`,
-  //     method: 'put',
-  //     data: data
-  //   })
-  // }
-
-  /**
-   * 删除游戏
-   *
-   * @param id 游戏Id
-   */
-  // static deleteById(id: string) {
-  //   return request({
-  //     url: `${GAME_BASE_URL}/delete/${id}`,
-  //     method: 'delete'
-  //   })
-  // }
-}
-
-export default GameAPI
-
-/** 游戏信息 */
-export interface GameVO {
-  /** 游戏ID */
-  id?: number | string
-  /** 游戏名称 */
-  name?: string
-  /** 游戏编码 */
-  code?: string
-  /** 游戏简介 */
-  intro?: string
-}
-
-/** 游戏记录信息 */
-export interface GameResultVO {
-  /** 游戏记录ID */
-  id?: string
-  /** 完成(0:未完成 1:已完成) */
-  finish?: string
-  /** 游戏ID */
-  gameId?: string | number
-  /** 游戏名称 */
-  gameName?: string
-  /** 游戏等级 */
-  gamelevel?: string | number
-  /** 游戏得分 */
-  score?: number
-  /** 用户ID */
-  userId?: string
-  /** 游戏结果参数 */
-  paramList?: Result[]
-  /** 游戏结果参数(关卡) */
-  levelList?: ResultLevel[]
-}
-
-export interface Result {
-  code?: string
-  name?: string
-  value?: string | number
-}
-
-export interface ResultLevel {
-  level?: string | number
-  levelParamList?: Result[]
-}

+ 0 - 73
src/api/index.ts

@@ -1,73 +0,0 @@
-import axios from 'axios'
-
-// import qs from 'qs'
-import Message from 'vue-m-message'
-import useUserStore from '@/store/modules/user'
-
-const api = axios.create({
-  baseURL: (import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true') ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
-  timeout: 1000 * 60,
-  responseType: 'json',
-})
-
-api.interceptors.request.use(
-  (request) => {
-    // 全局拦截请求发送前提交的参数
-    const userStore = useUserStore()
-    // 设置请求头
-    if (request.headers) {
-      if (userStore.isLogin) {
-        request.headers.Token = userStore.token
-      }
-    }
-    // 是否将 POST 请求参数进行字符串化处理
-    if (request.method === 'post') {
-      // request.data = qs.stringify(request.data, {
-      //   arrayFormat: 'brackets',
-      // })
-    }
-    return request
-  },
-)
-
-api.interceptors.response.use(
-  (response) => {
-    /**
-     * 全局拦截请求发送后返回的数据,如果数据有报错则在这做全局的错误提示
-     * 假设返回数据格式为:{ status: 1, error: '', data: '' }
-     * 规则是当 status 为 1 时表示请求成功,为 0 时表示接口需要登录或者登录状态失效,需要重新登录
-     * 请求出错时 error 会返回错误信息
-     */
-    if (response.data.status === 1) {
-      if (response.data.error !== '') {
-        // 错误提示
-        Message.error(response.data.error, {
-          zIndex: 2000,
-        })
-        return Promise.reject(response.data)
-      }
-    }
-    else {
-      useUserStore().logout()
-    }
-    return Promise.resolve(response.data)
-  },
-  (error) => {
-    let message = error.message
-    if (message === 'Network Error') {
-      message = '后端网络故障'
-    }
-    else if (message.includes('timeout')) {
-      message = '接口请求超时'
-    }
-    else if (message.includes('Request failed with status code')) {
-      message = `接口${message.substr(message.length - 3)}异常`
-    }
-    Message.error(message, {
-      zIndex: 2000,
-    })
-    return Promise.reject(error)
-  },
-)
-
-export default api

+ 18 - 0
src/api/mock/index.js

@@ -0,0 +1,18 @@
+import request from '@/utils/request'
+
+
+export function getListApi(params) {
+  return request({
+    url: "/list/get",
+    method: "get",
+    params
+  });
+}
+
+export function getListApiError(data) {
+  return request({
+    url: "/list/error",
+    method: "post",
+    data
+  });
+}

+ 0 - 16
src/api/modules/user.ts

@@ -1,16 +0,0 @@
-import api from '../index'
-
-export default {
-  // 登录
-  login: (data: {
-    account: string
-    password: string
-  }) => api.post('user/login', data, {
-    baseURL: '/mock/',
-  }),
-
-  // 获取权限
-  permission: () => api.get('user/permission', {
-    baseURL: '/mock/',
-  }),
-}

+ 0 - 16
src/api/typing.ts

@@ -1,16 +0,0 @@
-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
+ 0 - 0
src/assets/icons/403.svg


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


+ 0 - 1
src/assets/icons/correct.svg

@@ -1 +0,0 @@
-<svg 
 xmlns="http://www.w3.org/2000/svg"
 xmlns:xlink="http://www.w3.org/1999/xlink"
 width="49px" height="43px">
<defs>
<filter filterUnits="userSpaceOnUse" id="Filter_0" x="-1px" y="-1px" width="50px" height="44px"  >
                <feOffset in="SourceAlpha" dx="0" dy="2" />
                <feGaussianBlur result="blurOut" stdDeviation="0" />
                <feFlood flood-color="rgb(39, 48, 113)" result="floodOut" />
                <feComposite operator="atop" in="floodOut" in2="blurOut" />
                <feComponentTransfer><feFuncA type="linear" slope="0.4"/></feComponentTransfer>
                <feMerge>
    <feMergeNode/>
    <feMergeNode in="SourceGraphic"/>
  </feMerge>
            </filter>

</defs>
<g filter="url(#Filter_0)">
<path fill-rule="evenodd"  stroke="rgb(255, 255, 255)" stroke-width="2px" stroke-linecap="butt" stroke-linejoin="miter" fill="rgb(100, 197, 17)"
 d="M9.0,11.999 C9.0,11.999 11.499,11.59 13.0,11.0 C14.589,10.937 15.187,11.771 15.226,12.409 L19.199,16.375 C19.199,16.375 19.762,16.912 20.193,16.375 C20.624,15.838 32.115,3.486 32.115,3.486 C32.115,3.486 33.828,1.276 36.89,3.486 C38.350,5.696 44.37,11.418 44.37,11.418 C44.37,11.418 45.572,12.781 43.43,15.383 C40.515,17.985 22.180,36.203 22.180,36.203 C22.180,36.203 19.241,39.118 16.219,36.203 C13.197,33.288 3.303,23.314 3.303,23.314 C3.303,23.314 2.0,22.377 2.0,21.0 C2.0,19.942 3.303,18.358 3.303,18.358 L9.0,11.999 Z"/>
</g>
<path fill-rule="evenodd"  fill="rgb(255, 255, 255)"
 d="M5.293,19.535 L7.884,16.944 C8.274,16.553 8.907,16.553 9.298,16.944 C9.688,17.334 9.688,17.967 9.298,18.358 L6.706,20.949 C6.316,21.340 5.683,21.340 5.293,20.949 C4.902,20.559 4.902,19.925 5.293,19.535 Z"/>
<path fill-rule="evenodd"  fill="rgb(255, 255, 255)"
 d="M10.127,15.253 L11.255,14.125 C11.425,13.955 11.701,13.955 11.871,14.125 C12.41,14.295 12.41,14.571 11.871,14.740 L10.743,15.868 C10.572,16.39 10.297,16.39 10.127,15.868 C9.957,15.699 9.957,15.423 10.127,15.253 Z"/>
</svg>

+ 0 - 1
src/assets/icons/example-crown.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path fill="#DCA54F" d="m136.533 273.067 669.048 422.058c10.65 6.725 15.838 20.48 12.697 33.588-3.106 13.073-13.824 22.186-26.077 22.22H238.42c-27.306 0-50.346-22.425-53.726-52.326l-48.162-425.54z"/><path fill="#DCA54F" d="M834.219 698.607c-3.345 29.9-26.385 52.326-53.692 52.326H245.794c-12.288 0-41.984-9.113-45.124-22.186-3.175-13.073 2.048-26.863 12.697-33.588l668.98-422.092-48.128 425.506z"/><path fill="#E7B15C" d="m512 170.667 298.428 490.598a61.44 61.44 0 0 1 2.184 59.324c-9.489 18.705-27.818 30.344-47.718 30.344H512V170.667z"/><path fill="#F2D59C" d="M512 170.667 196.062 666.01a61.44 61.44 0 0 0-2.185 59.323c9.49 18.705 27.341 25.6 47.24 25.6H512V170.667z"/><path fill="#E7B15C" d="M459.776 153.327c0 18.193 9.967 34.987 26.112 44.1a53.35 53.35 0 0 0 52.224 0c16.145-9.113 26.112-25.941 26.112-44.1 0-28.126-23.381-50.927-52.224-50.927s-52.224 22.801-52.224 50.927zM851.319 255.18c-.41 18.432 9.455 35.67 25.771 45.022 16.316 9.318 36.523 9.318 52.873 0 16.315-9.353 26.18-26.59 25.77-45.056-.614-27.648-23.825-49.8-52.224-49.8-28.364 0-51.541 22.152-52.19 49.834zm-783.018 0c-.444 18.432 9.42 35.67 25.736 45.022 16.316 9.318 36.523 9.318 52.873 0 16.316-9.353 26.18-26.59 25.77-45.056-.614-27.648-23.825-49.8-52.223-49.8-28.365 0-51.542 22.152-52.19 49.834z"/><path fill="#DCA54F" d="M238.933 819.2h546.134q34.133 0 34.133 34.133 0 34.134-34.133 34.134H238.933q-34.133 0-34.133-34.134 0-34.133 34.133-34.133z"/></svg>

+ 0 - 1
src/assets/icons/example-emotion-laugh-line.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M512 85.333c235.648 0 426.667 191.019 426.667 426.667S747.648 938.667 512 938.667 85.333 747.648 85.333 512 276.352 85.333 512 85.333zm0 85.334a341.333 341.333 0 1 0 0 682.666 341.333 341.333 0 0 0 0-682.666zm0 298.666c85.333 0 156.459 14.208 213.333 42.667a213.333 213.333 0 0 1-426.666 0c56.874-28.459 128-42.667 213.333-42.667zM362.667 298.667A106.667 106.667 0 0 1 467.2 384H258.133a106.667 106.667 0 0 1 104.534-85.333zm298.666 0A106.667 106.667 0 0 1 765.867 384H556.8a106.667 106.667 0 0 1 104.533-85.333z"/></svg>

+ 0 - 1
src/assets/icons/example-emotion-line.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M512 938.667C276.352 938.667 85.333 747.648 85.333 512S276.352 85.333 512 85.333 938.667 276.352 938.667 512 747.648 938.667 512 938.667zm0-85.334a341.333 341.333 0 1 0 0-682.666 341.333 341.333 0 0 0 0 682.666zM341.333 554.667h341.334a170.667 170.667 0 1 1-341.334 0zm0-85.334a64 64 0 1 1 0-128 64 64 0 0 1 0 128zm341.334 0a64 64 0 1 1 0-128 64 64 0 0 1 0 128z"/></svg>

+ 0 - 1
src/assets/icons/example-emotion-unhappy-line.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path d="M512 938.667C276.352 938.667 85.333 747.648 85.333 512S276.352 85.333 512 85.333 938.667 276.352 938.667 512 747.648 938.667 512 938.667zm0-85.334a341.333 341.333 0 1 0 0-682.666 341.333 341.333 0 0 0 0 682.666zm-213.333-128a213.333 213.333 0 0 1 426.666 0H640a128 128 0 0 0-256 0h-85.333zm42.666-256a64 64 0 1 1 0-128 64 64 0 0 1 0 128zm341.334 0a64 64 0 1 1 0-128 64 64 0 0 1 0 128z"/></svg>

+ 0 - 1
src/assets/icons/example-star.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path fill="#FFC500" d="M512 768 262.059 899.413a28.444 28.444 0 0 1-41.245-30.009l47.702-278.3L66.332 394.012a28.444 28.444 0 0 1 15.759-48.526l279.438-40.59L486.485 51.684a28.444 28.444 0 0 1 51.03 0L662.47 304.896l279.438 40.59a28.444 28.444 0 0 1 15.759 48.526l-202.184 197.12 47.73 278.272a28.444 28.444 0 0 1-41.273 29.98L512 768z"/><path fill="#FED902" d="M512 768 262.059 899.413a28.444 28.444 0 0 1-41.245-30.009l47.702-278.3c36.124-190.805 67.128-286.208 93.013-286.208 38.827 0 393.955 261.774 393.955 286.208 0 16.299-81.18 75.264-243.484 176.896z"/></svg>

+ 0 - 1
src/assets/icons/example-vip.svg

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path fill="#FFA100" d="M270.219 121.212h483.474a29.257 29.257 0 0 1 23.347 11.645l188.416 249.885a29.257 29.257 0 0 1-1.843 37.42l-429.67 467.587a29.257 29.257 0 0 1-43.037.059L60.416 421.595a29.257 29.257 0 0 1-1.931-37.39l188.328-251.26a29.257 29.257 0 0 1 23.406-11.703z"/><path fill="#FFC663" d="m768.293 121.212 197.163 261.56a29.257 29.257 0 0 1-1.843 37.39L532.714 889.066a11.703 11.703 0 0 1-20.304-7.9L512 257.025l256.293-135.84z"/><path fill="#FFF" d="M721.598 386.34a29.257 29.257 0 0 1 .995 1.025l22.733 23.873a29.257 29.257 0 0 1 0 40.346l-189.411 198.89-22.733 23.874a29.257 29.257 0 0 1-1.726 1.668l1.755-1.668a29.491 29.491 0 0 1-19.456 9.011 28.935 28.935 0 0 1-18.08-4.915 30.193 30.193 0 0 1-4.857-4.096l1.96 1.872-.965-.877-.995-.995-22.733-23.874-189.41-198.89a29.257 29.257 0 0 1 0-40.375l22.732-23.844a29.257 29.257 0 0 1 42.364 0L512 563.96l168.229-176.596a29.257 29.257 0 0 1 41.37-1.024z"/></svg>

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


+ 1 - 0
src/assets/icons/svg/dark.svg

@@ -0,0 +1 @@
+<?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="1681314397470" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2282" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M448.22 48C704.358 48 912 255.74 912 512l-0.062 7.673C907.842 772.394 701.797 976 448.22 976c-110.645 0-198.866-26.169-264.663-78.506a32 32 0 0 1-4.325-4.159c-11.419-13.256-10.058-33.192 2.962-44.78l0.399-0.35 2.742-2.383c89.13-78.201 134.016-188.043 134.66-329.525L320 512c0.253-143.557-44.635-254.831-134.665-333.822l-2.742-2.384c-13.39-11.534-14.895-31.74-3.36-45.13a32 32 0 0 1 4.324-4.158C249.354 74.17 337.575 48 448.22 48z m0 64c-78.634 0-142.266 14.443-192.055 42.56l-1.809 1.03 0.175 0.18c85.695 88.653 128.775 207.05 129.462 351.494l0.007 4.825-0.007 4.499c-0.65 143.074-42.893 260.596-126.9 348.969l-2.737 2.852 1.809 1.031c49.174 27.77 111.852 42.201 189.149 42.56h2.906c216.135 0 392.63-171.775 399.568-386.834l0.154-6.358 0.057-6.946-0.053-6.477C844.452 289.716 670.368 115.55 454.83 112.054l-6.61-0.054z" fill="#000000" p-id="2283"></path></svg>

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


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


+ 1 - 0
src/assets/icons/svg/light.svg

@@ -0,0 +1 @@
+<?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="1681314392506" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2129" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 224c159.058 0 288 128.942 288 288S671.058 800 512 800 224 671.058 224 512s128.942-288 288-288z m0 64c-123.712 0-224 100.288-224 224s100.288 224 224 224 224-100.288 224-224-100.288-224-224-224z m0 576c17.673 0 32 14.327 32 32v64c0 17.673-14.327 32-32 32-17.673 0-32-14.327-32-32v-64c0-17.673 14.327-32 32-32zM263.098 760.902c12.497 12.496 12.497 32.758 0 45.254l-45.254 45.255c-12.497 12.497-32.758 12.497-45.255 0s-12.497-32.758 0-45.255l45.255-45.254c12.496-12.497 32.758-12.497 45.254 0z m543.058 0l45.255 45.254c12.497 12.497 12.497 32.758 0 45.255s-32.758 12.497-45.255 0l-45.254-45.255c-12.497-12.496-12.497-32.758 0-45.254 12.496-12.497 32.758-12.497 45.254 0zM128 480c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32H64c-17.673 0-32-14.327-32-32 0-17.673 14.327-32 32-32h64z m832 0c17.673 0 32 14.327 32 32 0 17.673-14.327 32-32 32h-64c-17.673 0-32-14.327-32-32 0-17.673 14.327-32 32-32h64zM217.844 172.589l45.254 45.255c12.497 12.496 12.497 32.758 0 45.254-12.496 12.497-32.758 12.497-45.254 0l-45.255-45.254c-12.497-12.497-12.497-32.758 0-45.255s32.758-12.497 45.255 0z m633.567 0c12.497 12.497 12.497 32.758 0 45.255l-45.255 45.254c-12.496 12.497-32.758 12.497-45.254 0-12.497-12.496-12.497-32.758 0-45.254l45.254-45.255c12.497-12.497 32.758-12.497 45.255 0zM512 32c17.673 0 32 14.327 32 32v64c0 17.673-14.327 32-32 32-17.673 0-32-14.327-32-32V64c0-17.673 14.327-32 32-32z" fill="#000000" p-id="2130"></path></svg>

+ 1 - 0
src/assets/icons/svg/link.svg

@@ -0,0 +1 @@
+<?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="1613816440554" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2265" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M883.459773 504.826645a46.043165 46.043165 0 0 1-60.708766-0.937916 41.609379 41.609379 0 0 1-3.49587-58.236078l77.591261-77.932321a163.538503 163.538503 0 0 0-16.96776-229.10738c-67.359446-67.274181-166.863842-73.328004-223.053557-16.711964L439.995878 340.264961a163.538503 163.538503 0 0 0 17.053024 229.107381 41.609379 41.609379 0 0 1 0 60.367706 45.61684 45.61684 0 0 1-63.43725 0 246.330935 246.330935 0 0 1-16.967759-347.284839L593.473096 65.37021C686.753139-28.080362 845.090469-20.406501 945.959107 82.423235a246.24567 246.24567 0 0 1 15.603518 343.618438l-78.102852 78.784972z m-742.318145 13.64242a46.043165 46.043165 0 0 1 60.708766 0.937916c16.797229 15.518252 18.417266 40.927258 3.49587 58.236078l-77.59126 77.932321a163.538503 163.538503 0 0 0 16.967759 229.10738c67.359446 67.274181 166.863842 73.328004 223.053557 16.711964L584.605524 683.030749a163.538503 163.538503 0 0 0-17.053025-229.107381 41.609379 41.609379 0 0 1 0-60.367706 45.531575 45.531575 0 0 1 63.437251 0 246.330935 246.330935 0 0 1 16.967759 347.284839l-216.829204 217.170264c-93.280043 93.365308-251.617373 85.691447-352.486011-17.053025a246.24567 246.24567 0 0 1-15.603517-343.618438l78.102851-78.784972z" p-id="2266"></path></svg>

+ 22 - 0
src/assets/icons/svgo.yml

@@ -0,0 +1,22 @@
+# replace default config
+
+# multipass: true
+# full: true
+
+plugins:
+
+  # - name
+  #
+  # or:
+  # - name: false
+  # - name: true
+  #
+  # or:
+  # - name:
+  #     param1: 1
+  #     param2: 2
+
+- removeAttrs:
+    attrs:
+      - 'fill'
+      - 'fill-rule'

+ 0 - 1
src/assets/icons/wrong.svg

@@ -1 +0,0 @@
-<svg 
 xmlns="http://www.w3.org/2000/svg"
 xmlns:xlink="http://www.w3.org/1999/xlink"
 width="43px" height="42px">
<defs>
<filter filterUnits="userSpaceOnUse" id="Filter_0" x="-1px" y="-1px" width="44px" height="43px"  >
                <feOffset in="SourceAlpha" dx="0" dy="2" />
                <feGaussianBlur result="blurOut" stdDeviation="0" />
                <feFlood flood-color="rgb(39, 48, 113)" result="floodOut" />
                <feComposite operator="atop" in="floodOut" in2="blurOut" />
                <feComponentTransfer><feFuncA type="linear" slope="0.4"/></feComponentTransfer>
                <feMerge>
    <feMergeNode/>
    <feMergeNode in="SourceGraphic"/>
  </feMerge>
            </filter>

</defs>
<g filter="url(#Filter_0)">
<path fill-rule="evenodd"  stroke="rgb(255, 255, 255)" stroke-width="2px" stroke-linecap="butt" stroke-linejoin="miter" fill="rgb(245, 50, 50)"
 d="M31.456,19.166 L37.486,24.825 C39.76,26.316 39.120,28.777 37.586,30.323 L32.366,35.582 C30.832,37.127 28.300,37.170 26.710,35.679 L20.376,29.736 L13.935,35.880 C12.355,37.387 9.838,37.343 8.313,35.782 L3.124,30.468 C1.599,28.906 1.644,26.418 3.224,24.911 L9.180,19.230 L3.727,14.113 C2.137,12.622 2.92,10.160 3.627,8.615 L8.847,3.356 C10.381,1.811 12.913,1.768 14.503,3.259 L20.259,8.660 L26.69,3.118 C27.649,1.611 30.166,1.656 31.690,3.217 L36.879,8.531 C38.404,10.92 38.360,12.580 36.780,14.87 L31.456,19.166 Z"/>
</g>
<path fill-rule="evenodd"  fill="rgb(255, 255, 255)"
 d="M5.292,10.535 L7.884,7.943 C8.274,7.553 8.908,7.553 9.298,7.943 C9.689,8.334 9.689,8.968 9.298,9.358 L6.707,11.949 C6.316,12.340 5.683,12.340 5.292,11.949 C4.902,11.559 4.902,10.926 5.292,10.535 Z"/>
<path fill-rule="evenodd"  fill="rgb(255, 255, 255)"
 d="M10.127,6.253 L11.255,5.125 C11.425,4.954 11.701,4.954 11.871,5.125 C12.41,5.295 12.41,5.571 11.871,5.741 L10.743,6.869 C10.573,7.39 10.297,7.39 10.127,6.869 C9.957,6.699 9.957,6.423 10.127,6.253 Z"/>
</svg>

BIN
src/assets/images/404.png


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


BIN
src/assets/images/bg-soa.png


BIN
src/assets/images/logo.png


+ 1 - 0
src/assets/logo.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

BIN
src/assets/logo/logo_melomini.png


+ 0 - 35
src/assets/styles/globals.css

@@ -1,35 +0,0 @@
-:root {
-  --g-navbar-height: 50px;
-  --g-tabbar-height: 60px;
-
-  color-scheme: light;
-
-  &.dark {
-    color-scheme: dark;
-  }
-}
-
-html {
-  overscroll-behavior: none;
-}
-
-body {
-  box-sizing: border-box;
-  margin: 0;
-}
-
-* {
-  box-sizing: inherit;
-}
-
-/* 全局样式 */
-#app {
-  overflow: hidden auto;
-  font-size: 14px;
-  background-color: var(--g-bg);
-}
-
-.medium-zoom-overlay,
-.medium-zoom-image--opened {
-  z-index: 1100;
-}

+ 34 - 0
src/assets/styles/index.scss

@@ -0,0 +1,34 @@
+// 引入清除默认样式
+@import "./normalize.css";
+
+// 引入变量
+@import "./variables.scss";
+
+// vant深色主题背景色
+.van-theme-dark body {
+  color: #f5f5f5;
+  background-color: black;
+}
+// 非移动端字体大小
+@media screen and (min-width: 750px) {
+  html {
+    font-size: 75px !important;
+  }
+}
+
+// 自动填充样式取消白色
+input:-webkit-autofill {
+  transition: background-color 50000s ease-in-out 0s;
+}
+input:-webkit-autofill, input:-webkit-autofill:active, input:-webkit-autofill:focus, input:-webkit-autofill:hover{
+  -webkit-box-shadow: 0 0 0 1000px #ffffff00 inset !important;
+}
+// input {
+//   -webkit-text-fill-color: var(--van-field-input-text-color);  //颜色是设置成你需要的颜色
+// }
+
+#app {
+  max-width: 750px;
+  margin: 0 auto;
+}
+

+ 370 - 0
src/assets/styles/normalize.css

@@ -0,0 +1,370 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/**
+* 解决图片之间空白问题
+*/ 
+
+img {
+  display: block;
+}
+
+
+/* Document
+   ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+ html {
+  line-height: 1.15; /* 1 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+  height: 100%;
+  font-family: PingFang SC, HarmonyOS_Medium, Helvetica Neue, Microsoft YaHei, sans-serif;
+}
+
+/* Sections
+   ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+  margin: 0;
+  height: 100%;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+  display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/* Grouping content
+   ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+  box-sizing: content-box; /* 1 */
+  height: 0; /* 1 */
+  overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+   ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+  border-bottom: none; /* 1 */
+  text-decoration: underline; /* 2 */
+  text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+  font-family: monospace, monospace; /* 1 */
+  font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+sup {
+  top: -0.5em;
+}
+
+/* Embedded content
+   ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+  border-style: none;
+}
+
+/* Forms
+   ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit; /* 1 */
+  font-size: 100%; /* 1 */
+  line-height: 1.15; /* 1 */
+  margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+  overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+  text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  border-style: none;
+  padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+  outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+  padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ *    `fieldset` elements in all browsers.
+ */
+
+legend {
+  box-sizing: border-box; /* 1 */
+  color: inherit; /* 2 */
+  display: table; /* 1 */
+  max-width: 100%; /* 1 */
+  padding: 0; /* 3 */
+  white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+  vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+  -webkit-appearance: button; /* 1 */
+  font: inherit; /* 2 */
+}
+
+/* Interactive
+   ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+  display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+  display: list-item;
+}
+
+/* Misc
+   ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+  display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+  display: none;
+}
+
+/**
+ * 解决padding问题
+ */
+*,
+:after,
+:before {
+  box-sizing: border-box;
+}

+ 0 - 63
src/assets/styles/nprogress.css

@@ -1,63 +0,0 @@
-#nprogress {
-  pointer-events: none;
-
-  .bar {
-    position: fixed;
-    top: 0;
-    left: 0;
-    z-index: 2000;
-    width: 100%;
-    height: 2px;
-    background: rgb(var(--ui-primary));
-  }
-
-  .peg {
-    position: absolute;
-    right: 0;
-    display: block;
-    width: 100px;
-    height: 100%;
-    box-shadow: 0 0 10px rgb(var(--ui-primary)), 0 0 5px rgb(var(--ui-primary));
-    opacity: 1;
-    transform: rotate(3deg) translate(0, -4px);
-  }
-
-  .spinner {
-    position: fixed;
-    top: 11px;
-    right: 14px;
-    z-index: 2000;
-    display: block;
-
-    .spinner-icon {
-      box-sizing: border-box;
-      width: 18px;
-      height: 18px;
-      border: solid 2px transparent;
-      border-top-color: rgb(var(--ui-primary));
-      border-left-color: rgb(var(--ui-primary));
-      border-radius: 50%;
-      animation: nprogress-spinner 400ms linear infinite;
-    }
-  }
-}
-
-.nprogress-custom-parent {
-  position: relative;
-  overflow: hidden;
-
-  #nprogress .spinner,
-  #nprogress .bar {
-    position: absolute;
-  }
-}
-
-@keyframes nprogress-spinner {
-  0% { transform: rotate(0deg); }
-  100% { transform: rotate(360deg); }
-}
-
-@keyframes nprogress-spinner {
-  0% { transform: rotate(0deg); }
-  100% { transform: rotate(360deg); }
-}

+ 390 - 0
src/assets/styles/reset.scss

@@ -0,0 +1,390 @@
+html {
+  -webkit-text-size-adjust: 100%;
+}
+html:focus-within {
+  scroll-behavior: smooth;
+}
+// 去除a链接的默认样式
+a:-webkit-any-link {
+  color: inherit;
+  text-decoration: none;
+}
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+  margin: 0;
+  font-weight: normal;
+}
+body {
+  -webkit-text-size-adjust: 100%;
+  -moz-text-size-adjust: 100%;
+  text-size-adjust: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  min-height: 100vh;
+  height: 100vh;
+  position: relative;
+  text-rendering: optimizeSpeed;
+  width: 100%;
+  font-family:
+    Inter,
+    -apple-system,
+    BlinkMacSystemFont,
+    "Segoe UI",
+    Roboto,
+    Oxygen,
+    Ubuntu,
+    Cantarell,
+    "Fira Sans",
+    "Droid Sans",
+    "Helvetica Neue",
+    sans-serif;
+}
+*,
+:after,
+:before {
+  box-sizing: border-box;
+}
+a:not([class]) {
+  -webkit-text-decoration-skip: ink;
+  text-decoration-skip-ink: auto;
+}
+a,
+abbr,
+acronym,
+address,
+applet,
+article,
+aside,
+audio,
+b,
+big,
+blockquote,
+body,
+canvas,
+caption,
+center,
+cite,
+code,
+dd,
+del,
+details,
+dfn,
+div,
+dl,
+dt,
+em,
+embed,
+fieldset,
+figcaption,
+figure,
+footer,
+form,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+header,
+hgroup,
+html,
+i,
+iframe,
+img,
+ins,
+kbd,
+label,
+legend,
+li,
+mark,
+menu,
+nav,
+object,
+ol,
+output,
+p,
+pre,
+q,
+ruby,
+s,
+samp,
+section,
+small,
+span,
+strike,
+strong,
+sub,
+summary,
+sup,
+table,
+tbody,
+td,
+tfoot,
+th,
+thead,
+time,
+tr,
+tt,
+u,
+ul,
+var,
+video {
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  margin: 0;
+  padding: 0;
+  vertical-align: baseline;
+}
+:focus {
+  outline: 0;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section {
+  display: block;
+}
+ol,
+ul {
+  list-style: none;
+}
+blockquote,
+q {
+  quotes: none;
+}
+blockquote:after,
+blockquote:before,
+q:after,
+q:before {
+  content: "";
+  content: none;
+}
+input,
+input:required {
+  box-shadow: none;
+}
+input:-webkit-autofill,
+input:-webkit-autofill:active,
+input:-webkit-autofill:focus,
+input:-webkit-autofill:hover {
+  -webkit-box-shadow: inset 0 0 0 30px #fff;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-results-button,
+input[type="search"]::-webkit-search-results-decoration {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+}
+input[type="search"] {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+}
+input:focus {
+  outline: none;
+}
+audio,
+canvas,
+video {
+  display: inline-block;
+  max-width: 100%;
+}
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+[hidden] {
+  display: none;
+}
+a:active,
+a:hover {
+  outline: none;
+}
+img {
+  height: auto;
+  // max-width: 100%;
+  vertical-align: middle;
+}
+img,
+picture {
+  display: inline-block;
+}
+button,
+input {
+  line-height: normal;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  background: transparent;
+  border: 0;
+  cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+[disabled] {
+  pointer-events: none;
+}
+input[type="checkbox"],
+input[type="radio"] {
+  padding: 0;
+}
+input[type="search"] {
+  -webkit-appearance: textfield;
+  box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+button {
+  background: transparent;
+  border: 0;
+}
+textarea {
+  overflow: auto;
+  resize: vertical;
+  vertical-align: top;
+}
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+  text-indent: 0;
+}
+hr {
+  background: #000;
+  border: 0;
+  box-sizing: content-box;
+  height: 1px;
+  line-height: 0;
+  margin: 0;
+  overflow: visible;
+  padding: 0;
+  page-break-after: always;
+  width: 100%;
+}
+pre {
+  font-family: monospace, monospace;
+  font-size: 100%;
+}
+a {
+  background-color: transparent;
+}
+abbr[title] {
+  border-bottom: none;
+  text-decoration: none;
+}
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+}
+small,
+sub,
+sup {
+  font-size: 75%;
+}
+sub,
+sup {
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+sub {
+  bottom: -5px;
+}
+sup {
+  top: -5px;
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+  font-family: inherit;
+  font-size: 100%;
+  line-height: 1;
+  margin: 0;
+  padding: 0;
+}
+button,
+input {
+  overflow: visible;
+}
+button,
+select {
+  text-transform: none;
+}
+[type="button"],
+[type="reset"],
+[type="submit"],
+button {
+  -webkit-appearance: button;
+}
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner,
+button::-moz-focus-inner {
+  border-style: none;
+  outline: 0;
+  padding: 0;
+}
+legend {
+  border: 0;
+  color: inherit;
+  display: block;
+  max-width: 100%;
+  white-space: normal;
+  width: 100%;
+}
+fieldset {
+  min-width: 0;
+}
+body:not(:-moz-handler-blocked) fieldset {
+  display: block;
+}
+progress {
+  vertical-align: baseline;
+}
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+[type="search"] {
+  -webkit-appearance: textfield;
+  outline-offset: -2px;
+}
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+::-webkit-file-upload-button {
+  -webkit-appearance: button;
+  font: inherit;
+}
+summary {
+  display: list-item;
+}
+template {
+  display: none;
+}

+ 0 - 55
src/assets/styles/resources/utils.scss

@@ -1,55 +0,0 @@
-// @mixin 通过 @include 调用使用
-// % 通过 @extend 调用使用
-
-// 文字超出隐藏,默认为单行超出隐藏,可设置多行
-@mixin text-overflow($line: 1, $fixed-width: true) {
-  @if $line == 1 and $fixed-width == true {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  } @else {
-    display: box;
-    overflow: hidden;
-    -webkit-box-orient: vertical;
-    -webkit-line-clamp: $line;
-  }
-}
-
-// 定位居中,默认水平居中,可选择垂直居中,或者水平垂直都居中
-@mixin position-center($type: x) {
-  position: absolute;
-
-  @if $type == x {
-    left: 50%;
-    transform: translateX(-50%);
-  }
-
-  @if $type == y {
-    top: 50%;
-    transform: translateY(-50%);
-  }
-
-  @if $type == xy {
-    top: 50%;
-    left: 50%;
-    transform: translateX(-50%) translateY(-50%);
-  }
-}
-
-// 文字两端对齐
-%justify-align {
-  text-align: justify;
-  text-align-last: justify;
-}
-
-// 清除浮动
-%clearfix {
-  zoom: 1;
-
-  &::before,
-  &::after {
-    display: block;
-    clear: both;
-    content: "";
-  }
-}

+ 0 - 1
src/assets/styles/resources/variables.scss

@@ -1 +0,0 @@
-// 全局变量

+ 13 - 0
src/assets/styles/variables.scss

@@ -0,0 +1,13 @@
+/* color palette from <https://github.com/vuejs/theme> */
+:root {
+  --color-text: #000;
+  --color-block-background: var(--van-border-color, #ebedf0);
+ 
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-text: #fff;
+    --color-block-background: var(--van-border-color, #3a3a3c);
+  }
+}

+ 0 - 147
src/components/AppSetting/index.vue

@@ -1,147 +0,0 @@
-<script setup lang="ts">
-import { useClipboard } from '@vueuse/core'
-import Message from 'vue-m-message'
-import settingsDefault from '@/settings.default'
-import { getTwoObjectDiff } from '@/utils'
-import eventBus from '@/utils/eventBus'
-import useSettingsStore from '@/store/modules/settings'
-
-defineOptions({
-  name: 'AppSetting',
-})
-
-const settingsStore = useSettingsStore()
-
-const isShow = ref(false)
-
-onMounted(() => {
-  eventBus.on('global-app-setting-toggle', () => {
-    isShow.value = !isShow.value
-  })
-})
-
-const { copy, copied, isSupported } = useClipboard()
-
-watch(copied, (val) => {
-  if (val) {
-    Message.success('复制成功,请粘贴到 src/settings.ts 文件中!', {
-      zIndex: 2000,
-    })
-  }
-})
-
-function handleCopy() {
-  copy(JSON.stringify(getTwoObjectDiff(settingsDefault, settingsStore.settings), null, 2))
-}
-</script>
-
-<template>
-  <HSlideover v-model="isShow" title="应用配置">
-    <div class="rounded-2 bg-rose/20 px-4 py-2 text-sm/6 c-rose">
-      <p class="my-1">
-        应用配置可实时预览效果,但只是临时生效,要想真正应用于项目,可以点击下方的「复制配置」按钮,并将配置粘贴到 src/settings.ts 文件中。
-      </p>
-      <p class="my-1">
-        注意:在生产环境中应关闭该模块。
-      </p>
-    </div>
-    <div>
-      <div class="my-4 flex items-center justify-between gap-4 whitespace-nowrap text-sm font-500 after:(h-[1px] w-full bg-stone-2 content-empty dark-bg-stone-6) before:(h-[1px] w-full bg-stone-2 content-empty dark-bg-stone-6)">
-        颜色主题风格
-      </div>
-      <div class="flex items-center justify-center pb-4">
-        <HTabList
-          v-model="settingsStore.settings.app.colorScheme"
-          :options="[
-            { icon: 'i-ri:sun-line', label: '明亮', value: 'light' },
-            { icon: 'i-ri:moon-line', label: '暗黑', value: 'dark' },
-            { icon: 'i-codicon:color-mode', label: '系统', value: '' },
-          ]"
-          class="w-60"
-        />
-      </div>
-    </div>
-    <div>
-      <div class="my-4 flex items-center justify-between gap-4 whitespace-nowrap text-sm font-500 after:(h-[1px] w-full bg-stone-2 content-empty dark-bg-stone-6) before:(h-[1px] w-full bg-stone-2 content-empty dark-bg-stone-6)">
-        底部版权
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          是否启用
-        </div>
-        <HToggle v-model="settingsStore.settings.copyright.enable" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          日期
-        </div>
-        <HInput v-model="settingsStore.settings.copyright.dates" :disabled="!settingsStore.settings.copyright.enable" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          公司
-        </div>
-        <HInput v-model="settingsStore.settings.copyright.company" :disabled="!settingsStore.settings.copyright.enable" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          网址
-        </div>
-        <HInput v-model="settingsStore.settings.copyright.website" :disabled="!settingsStore.settings.copyright.enable" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          备案
-        </div>
-        <HInput v-model="settingsStore.settings.copyright.beian" :disabled="!settingsStore.settings.copyright.enable" />
-      </div>
-    </div>
-    <div>
-      <div class="my-4 flex items-center justify-between gap-4 whitespace-nowrap text-sm font-500 after:(h-[1px] w-full bg-stone-2 content-empty dark-bg-stone-6) before:(h-[1px] w-full bg-stone-2 content-empty dark-bg-stone-6)">
-        其它
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          是否启用权限
-        </div>
-        <HToggle v-model="settingsStore.settings.app.enablePermission" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          载入进度条
-        </div>
-        <HToggle v-model="settingsStore.settings.app.enableProgress" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          哀悼模式
-        </div>
-        <HToggle v-model="settingsStore.settings.app.enableMournMode" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          色弱模式
-        </div>
-        <HToggle v-model="settingsStore.settings.app.enableColorAmblyopiaMode" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          返回顶部
-        </div>
-        <HToggle v-model="settingsStore.settings.app.enableBackTop" />
-      </div>
-      <div class="flex items-center justify-between gap-4 rounded-2 px-4 py-2">
-        <div class="flex flex-shrink-0 items-center gap-2 text-sm">
-          动态标题
-        </div>
-        <HToggle v-model="settingsStore.settings.app.enableDynamicTitle" />
-      </div>
-    </div>
-    <template v-if="isSupported" #footer>
-      <HButton block @click="handleCopy">
-        <SvgIcon name="i-ep:document-copy" />
-        复制配置
-      </HButton>
-    </template>
-  </HSlideover>
-</template>

+ 0 - 20
src/components/Auth/index.vue

@@ -1,20 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: 'Auth',
-})
-
-const props = defineProps<{
-  value: string | string[]
-}>()
-
-function check() {
-  return useAuth().auth(props.value)
-}
-</script>
-
-<template>
-  <div>
-    <slot v-if="check()" />
-    <slot v-else name="no-auth" />
-  </div>
-</template>

+ 0 - 20
src/components/AuthAll/index.vue

@@ -1,20 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: 'AuthAll',
-})
-
-const props = defineProps<{
-  value: string[]
-}>()
-
-function check() {
-  return useAuth().authAll(props.value)
-}
-</script>
-
-<template>
-  <div>
-    <slot v-if="check()" />
-    <slot v-else name="no-auth" />
-  </div>
-</template>

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

@@ -1,71 +0,0 @@
-<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 - 49
src/components/NotAllowed/index.vue

@@ -1,49 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: 'NotAllowed',
-})
-
-const router = useRouter()
-
-const data = ref({
-  inter: Number.NaN,
-  countdown: 5,
-})
-
-onUnmounted(() => {
-  data.value.inter && window.clearInterval(data.value.inter)
-})
-
-onMounted(() => {
-  data.value.inter = window.setInterval(() => {
-    data.value.countdown--
-    if (data.value.countdown === 0) {
-      data.value.inter && window.clearInterval(data.value.inter)
-      goBack()
-    }
-  }, 1000)
-})
-
-function goBack() {
-  router.push('/')
-}
-</script>
-
-<template>
-  <div class="min-h-screen flex flex-col items-center justify-center">
-    <SvgIcon name="403" class="text-[300px] -mt-9xl" />
-    <div class="flex flex-col items-center gap-4">
-      <h1 class="m-0 text-6xl font-sans">
-        403
-      </h1>
-      <div class="mx-0 text-xl text-stone-5">
-        抱歉,你无权访问该页面
-      </div>
-      <div>
-        <HButton @click="goBack">
-          {{ data.countdown }} 秒后,返回首页
-        </HButton>
-      </div>
-    </div>
-  </div>
-</template>

+ 0 - 259
src/components/PageLayout/index.vue

@@ -1,259 +0,0 @@
-<script setup lang="ts">
-import { useElementSize } from '@vueuse/core'
-import useSettingsStore from '@/store/modules/settings'
-
-defineOptions({
-  name: 'PageLayout',
-})
-
-withDefaults(
-  defineProps<{
-    /** 是否启用导航栏,默认使用应用配置 `navbar.enable` */
-    navbar?: boolean
-    /** 是否启用标签栏,默认使用应用配置 `tabbar.enable` */
-    tabbar?: boolean
-    /** 是否展示底部版权信息,默认使用应用配置 `copyright.enable` */
-    copyright?: boolean
-    /** 是否启用返回顶部按钮,默认使用应用配置 `app.enableBackTop` */
-    backTop?: boolean
-  }>(),
-  {
-    navbar: undefined,
-    tabbar: undefined,
-    copyright: undefined,
-    backTop: undefined,
-  },
-)
-
-const emits = defineEmits<{
-  scroll: [Event]
-  reachTop: []
-  reachBottom: []
-}>()
-
-const route = useRoute()
-const settingsStore = useSettingsStore()
-
-const layoutRef = ref()
-defineExpose({
-  ref: layoutRef,
-})
-function handleMainScroll(e: Event) {
-  handleNavbarScroll()
-  handleTabbarScroll()
-  handleBackTopScroll()
-  emits('scroll', e)
-  if ((e.target as HTMLElement).scrollTop === 0) {
-    emits('reachTop')
-  }
-  if (Math.ceil((e.target as HTMLElement).scrollTop + (e.target as HTMLElement).clientHeight) >= (e.target as HTMLElement).scrollHeight) {
-    emits('reachBottom')
-  }
-}
-onMounted(() => {
-  handleNavbarScroll()
-  handleTabbarScroll()
-  handleBackTopScroll()
-})
-onActivated(() => {
-  handleNavbarScroll()
-  handleTabbarScroll()
-  handleBackTopScroll()
-})
-
-// Navbar
-// 计算出左右两侧的最大宽度,让左右两侧的宽度保持一致
-const startSideRef = ref()
-const endSideRef = ref()
-const sideWidth = ref(0)
-onMounted(() => {
-  const { width: startWidth } = useElementSize(startSideRef, undefined, { box: 'border-box' })
-  const { width: endWidth } = useElementSize(endSideRef, undefined, { box: 'border-box' })
-  watch([startWidth, endWidth], (val) => {
-    sideWidth.value = Math.max(...val)
-  }, {
-    immediate: true,
-  })
-})
-const navbarScrollTop = ref(0)
-function handleNavbarScroll() {
-  navbarScrollTop.value = layoutRef.value.scrollTop
-}
-
-// Tabbar
-const showTabbarShadow = ref(false)
-function handleTabbarScroll() {
-  const scrollTop = layoutRef.value.scrollTop
-  const clientHeight = layoutRef.value.clientHeight
-  const scrollHeight = layoutRef.value.scrollHeight
-  showTabbarShadow.value = Math.ceil(scrollTop + clientHeight) < scrollHeight
-}
-const tabbarList = computed(() => {
-  if (settingsStore.settings.tabbar.list.length > 0) {
-    return settingsStore.settings.tabbar.list
-  }
-  return []
-})
-function getIcon(item: any) {
-  if (route.fullPath === item.path) {
-    return item.activeIcon ?? item.icon ?? undefined
-  }
-  else {
-    return item.icon ?? undefined
-  }
-}
-
-// 返回顶部
-const backTopScrollTop = ref(0)
-function handleBackTopScroll() {
-  backTopScrollTop.value = layoutRef.value.scrollTop
-}
-function handleBackTopClick() {
-  layoutRef.value.scrollTo({
-    top: 0,
-    behavior: 'smooth',
-  })
-}
-</script>
-
-<template>
-  <div ref="layoutRef" class="relative h-vh flex flex-col overflow-auto overscroll-none supports-[(height:100dvh)]:h-dvh" @scroll="handleMainScroll">
-    <!-- Navbar -->
-    <header
-      v-show="navbar ?? settingsStore.settings.navbar.enable" class="navbar w-full flex-center bg-[var(--g-navbar-bg)] text-[var(--g-navbar-color)] transition-all pt-safe h+safe-t-[var(--g-navbar-height)]" :class="{
-        'shadow-top': navbarScrollTop,
-      }"
-    >
-      <div
-        class="h-full flex items-center justify-start" :style="{
-          ...(sideWidth && { width: `${sideWidth}px` }),
-        }"
-      >
-        <div ref="startSideRef" class="h-full flex-center whitespace-nowrap">
-          <div class="h-full flex-center whitespace-nowrap px-2">
-            <slot name="navbar-start" />
-          </div>
-        </div>
-      </div>
-      <div class="min-w-0 flex-1 text-center text-sm">
-        <div class="truncate">
-          {{ settingsStore.title }}
-        </div>
-      </div>
-      <div
-        class="h-full flex items-center justify-end" :style="{
-          ...(sideWidth && { width: `${sideWidth}px` }),
-        }"
-      >
-        <div ref="endSideRef" class="h-full flex-center whitespace-nowrap">
-          <div class="h-full flex-center whitespace-nowrap px-2">
-            <slot name="navbar-end" />
-          </div>
-        </div>
-      </div>
-    </header>
-    <div
-      class="relative flex flex-1 flex-col transition-margin" :class="{
-        'mt+safe-[var(--g-navbar-height)]': navbar ?? settingsStore.settings.navbar.enable,
-        'mb+safe-[var(--g-tabbar-height)]': tabbar ?? settingsStore.settings.tabbar.enable,
-      }"
-    >
-      <slot />
-      <!-- 版权信息 -->
-      <Transition
-        v-bind="{
-          enterActiveClass: 'ease-out',
-          enterFromClass: 'opacity-0',
-          enterToClass: 'opacity-100',
-          leaveActiveClass: 'ease-in',
-          leaveFromClass: 'opacity-100',
-          leaveToClass: 'opacity-0',
-        }"
-      >
-        <div v-if="copyright ?? settingsStore.settings.copyright.enable" class="copyright relative flex flex-wrap items-center justify-center p-4 text-sm text-stone-5 mix-blend-difference">
-          <span class="px-1">Copyright</span>
-          <SvgIcon name="i-ri:copyright-line" class="text-lg" />
-          <span v-if="settingsStore.settings.copyright.dates" class="px-1">{{ settingsStore.settings.copyright.dates }}</span>
-          <template v-if="settingsStore.settings.copyright.company">
-            <a v-if="settingsStore.settings.copyright.website" :href="settingsStore.settings.copyright.website" target="_blank" rel="noopener" class="px-1 text-center text-stone-5 no-underline">{{ settingsStore.settings.copyright.company }}</a>
-            <span v-else class="px-1">{{ settingsStore.settings.copyright.company }}</span>
-          </template>
-          <a v-if="settingsStore.settings.copyright.beian" href="https://beian.miit.gov.cn/" target="_blank" rel="noopener" class="px-1 text-center text-stone-5 no-underline">{{ settingsStore.settings.copyright.beian }}</a>
-        </div>
-      </Transition>
-    </div>
-    <!-- Tabbar -->
-    <footer
-      v-show="tabbar ?? settingsStore.settings.tabbar.enable" class="tabbar w-full bg-[var(--g-tabbar-bg)] transition-all pb-safe h+safe-b-[calc(var(--g-tabbar-height))]" :class="{
-        'shadow-bottom': showTabbarShadow,
-      }"
-    >
-      <div class="h-full flex-center px-4">
-        <slot name="tabbar">
-          <RouterLink
-            v-for="item in tabbarList" :key="JSON.stringify(item)" class="flex flex-1 flex-col items-center gap-[2px] text-[var(--g-tabbar-color)] no-underline transition-all" :class="{
-              'text-[var(--g-tabbar-active-color)]!': route.fullPath === item.path,
-            }" :to="item.path" replace
-          >
-            <SvgIcon v-if="getIcon(item)" :name="getIcon(item) ?? ''" :class="item.text ? 'text-6' : 'text-8'" />
-            <div v-if="item.text" class="text-xs">
-              {{ item.text }}
-            </div>
-          </RouterLink>
-        </slot>
-      </div>
-    </footer>
-    <!-- 返回顶部 -->
-    <Transition
-      v-bind="{
-        enterActiveClass: 'ease-out duration-300',
-        enterFromClass: 'opacity-0 translate-y-4',
-        enterToClass: 'opacity-100 translate-y-0',
-        leaveActiveClass: 'ease-in duration-200',
-        leaveFromClass: 'opacity-100 scale-100',
-        leaveToClass: 'opacity-0 scale-50',
-      }"
-    >
-      <div
-        v-if="(backTop ?? settingsStore.settings.app.enableBackTop) && backTopScrollTop >= 200" class="backtop h-12 w-12 flex cursor-pointer items-center justify-center rounded-full bg-white shadow-lg ring-1 ring-stone-3 ring-inset active:bg-stone-1 dark-bg-dark dark-ring-stone-7 dark-active:bg-stone-9" :class="{
-          'bottom+safe-[calc(var(--g-tabbar-height)+16px)]!': tabbar ?? settingsStore.settings.tabbar.enable,
-        }" @click="handleBackTopClick"
-      >
-        <SvgIcon name="i-icon-park-outline:to-top-one" class="text-6" />
-      </div>
-    </Transition>
-  </div>
-</template>
-
-<style scoped>
-.navbar {
-  position: fixed;
-  top: 0;
-  left: 0;
-  z-index: 1000;
-  width: 100%;
-
-  &.shadow-top {
-    box-shadow: 0 10px 10px -10px var(--g-border-color);
-  }
-}
-
-.tabbar {
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  z-index: 1000;
-  width: 100%;
-
-  &.shadow-bottom {
-    box-shadow: 0 -10px 10px -10px var(--g-border-color);
-  }
-}
-
-.backtop {
-  position: fixed;
-  right: 16px;
-  bottom: 16px;
-  z-index: 1000;
-}
-</style>

+ 0 - 47
src/components/PageMain/index.vue

@@ -1,47 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: 'PageMain',
-})
-
-const props = withDefaults(
-  defineProps<{
-    title?: string
-    collaspe?: boolean
-    height?: string
-  }>(),
-  {
-    title: '',
-    collaspe: false,
-    height: '',
-  },
-)
-
-const titleSlot = !!useSlots().title
-
-const isCollaspe = ref(props.collaspe)
-function unCollaspe() {
-  isCollaspe.value = false
-}
-</script>
-
-<template>
-  <div
-    class="page-main relative m-4 flex flex-col bg-[var(--g-container-bg)] transition-background-color-300" :class="{
-      'of-hidden': isCollaspe,
-    }" :style="{
-      height: isCollaspe ? height : '',
-    }"
-  >
-    <div v-if="titleSlot || title" class="title-container border-b-1 border-b-[var(--g-bg)] border-b-solid px-4 py-3 transition-border-color-300">
-      <slot name="title">
-        {{ title }}
-      </slot>
-    </div>
-    <div class="main-container p-4">
-      <slot />
-    </div>
-    <div v-if="isCollaspe" class="collaspe absolute bottom-0 w-full cursor-pointer from-transparent to-[var(--g-container-bg)] bg-gradient-to-b pb-2 pt-10 text-center" @click="unCollaspe">
-      <SvgIcon name="i-ep:arrow-down" class="text-xl op-30 transition-opacity hover-op-100" />
-    </div>
-  </div>
-</template>

+ 0 - 229
src/components/RoundSlider/index.vue

@@ -1,229 +0,0 @@
-<script setup lang="ts">
-import { onMounted, ref } from 'vue'
-
-const props = defineProps({
-  centerName: {
-    type: String,
-    default: '中心点',
-  },
-  topName: {
-    type: String,
-    default: '',
-  },
-  moveName: {
-    type: String,
-    default: '3333',
-  },
-})
-const emits = defineEmits(['curValue'])
-
-// 定义角度值
-const angle = ref(0)
-const angleDiff = ref(0)
-
-// 获取滑块元素
-const sliderElement = ref<HTMLDivElement | null>(null)
-
-// 鼠标按下事件
-function onMouseDown(event: MouseEvent) {
-  startDrag(event)
-}
-
-// 触摸开始事件
-function onTouchStart(event: TouchEvent) {
-  startDrag(event.touches[0])
-}
-
-// 开始拖动
-function startDrag(event: MouseEvent | Touch) {
-  document.addEventListener('mousemove', onMouseMove)
-  document.addEventListener('mouseup', onMouseUp)
-  document.addEventListener('touchmove', onTouchMove)
-  document.addEventListener('touchend', onMouseUp)
-
-  // 计算初始角度
-  const rect = sliderElement.value!.getBoundingClientRect()
-  const x = event.clientX - (rect.left + rect.width / 2)
-  const y = event.clientY - (rect.top + rect.height / 2)
-  angle.value = calculateAngle(x, y) + 90
-  angleDiff.value = calculateAngleDiff(angle.value)
-  emits('curValue', angleDiff.value)
-}
-
-// 鼠标移动事件
-function onMouseMove(event: MouseEvent) {
-  updateAngle(event)
-}
-
-// 触摸移动事件
-function onTouchMove(event: TouchEvent) {
-  updateAngle(event.touches[0])
-}
-
-// 更新角度
-function updateAngle(event: MouseEvent | Touch) {
-  const rect = sliderElement.value!.getBoundingClientRect()
-  const x = event.clientX - (rect.left + rect.width / 2)
-  const y = event.clientY - (rect.top + rect.height / 2)
-  angle.value = calculateAngle(x, y) + 90
-  angleDiff.value = calculateAngleDiff(angle.value)
-  emits('curValue', angleDiff.value)
-}
-
-// 鼠标抬起事件
-function onMouseUp() {
-  document.removeEventListener('mousemove', onMouseMove)
-  document.removeEventListener('mouseup', onMouseUp)
-  document.removeEventListener('touchmove', onTouchMove)
-  document.removeEventListener('touchend', onMouseUp)
-}
-
-// 计算角度
-function calculateAngle(x: number, y: number): number {
-  const rad = Math.atan2(y, x)
-  const deg = (rad * 180) / Math.PI
-  return deg >= 0 ? deg : 360 + deg
-}
-
-// 计算角度差
-function calculateAngleDiff(currentAngle: number): number {
-  let diff = currentAngle
-  if (diff > 360 && diff <= 450) {
-    diff -= 360
-  }
-  else if (diff >= 180 && diff <= 360) {
-    diff = 360 - diff
-  }
-  return diff
-}
-
-onMounted(() => {
-  if (sliderElement.value) {
-    // 初始化角度
-    angle.value = calculateAngle(0, 0) // 假设初始位置在正上方
-    angleDiff.value = calculateAngleDiff(angle.value)
-  }
-
-  const touchArea = document.getElementById('touchArea')
-  // 监听触摸事件
-  touchArea.addEventListener('touchstart', (event) => {
-    // 记录开始触摸的位置
-    const xStart = event.touches[0].pageX
-    touchArea.addEventListener('touchmove', (event) => {
-      // 计算移动的距离
-      const xMove = event.touches[0].pageX - xStart
-      // 如果移动的距离大于0,则为左滑,小于0则为右滑
-      if (Math.abs(xMove) > 0) {
-        // 阻止页面滚动
-        event.preventDefault()
-        // 执行滑动处理
-        // console.log(`Swipe detected, distance: ${xMove}`)
-      }
-    })
-  }, false)
-})
-</script>
-
-<template>
-  <div class="slider-container">
-    <div
-      id="touchArea"
-      ref="sliderElement"
-      class="circle-slider"
-      @mousedown="onMouseDown"
-      @mousemove="onMouseMove"
-      @mouseup="onMouseUp"
-      @touchstart="onTouchStart"
-      @touchmove="onTouchMove"
-      @touchend="onMouseUp"
-    >
-      <div class="center-point" />
-      <div class="absolute-center z-399 w-[100px] text-center text-[16px] text-[#3A3A3A]">
-        {{ props.centerName }}
-      </div>
-      <div class="target-point flex-column flex-center text-[16px] text-[#3A3A3A]">
-        <van-icon name="play" class="rotate-90" />
-        <span>{{ props.topName }}</span>
-      </div>
-      <div class="move-point" :style="{ transform: `translateX(-50%) rotate(${angle}deg)` }">
-        <span class="absolute left-26px block w-[180px] text-[16px] text-[#F87171]">{{ props.moveName }}</span>
-      </div>
-      <img
-        src="@/assets/images/task/spatialOrientationAbility/icon_pointer.png"
-        class="absolute left-[50%] top-[38px] z-19 h-[150px] w-[20px] translate-x-[-50%]"
-        alt=""
-      >
-      <img
-        src="@/assets/images/task/spatialOrientationAbility/icon_pointer_red.png"
-        class="red-line absolute left-[50%] top-[40px] z-19 h-[130px] w-[4px] rounded-[2px]"
-        :style="{ transform: `translateX(-50%) rotate(${angle}deg)` }"
-        alt=""
-      >
-      <span class="absolute top-[295px] block text-[15px] text-[#3A3A3A]">{{ angleDiff.toFixed(5) }}°</span>
-    </div>
-  </div>
-</template>
-
-<style scoped>
-.slider-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  position: relative;
-}
-
-.circle-slider {
-  position: relative;
-  width: 280px; /* 直径 */
-  height: 280px; /* 直径 */
-  border-radius: 50%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  background-image: url('@/assets/images/task/spatialOrientationAbility/bg_circle.png');
-  background-size: cover;
-  background-repeat: no-repeat;
-  background-position: center; /* 可选,让图片居中对齐 */
-}
-
-.center-point {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  width: 48px;
-  height: 48px;
-  border-radius: 50%;
-  z-index: 99;
-  background-image: url('@/assets/images/task/spatialOrientationAbility/icon_center.png');
-  background-size: cover;
-  background-repeat: no-repeat;
-  background-position: center; /* 可选,让图片居中对齐 */
-}
-
-.target-point {
-  position: absolute;
-  top: -40px;
-  left: 50%;
-  transform: translateX(-50%);
-  width: 160px;
-  height: 40px;
-}
-
-.move-point {
-  position: absolute;
-  top: 20px;
-  left: 50%;
-  transform-origin: center 550%;
-  width: 22px;
-  height: 22px;
-  border-radius: 50%;
-  background-color: #f3f3f3 !important;
-  box-shadow: 0 0 4px 0 #000 !important;
-  cursor: move;
-}
-
-.red-line {
-  transform-origin: 50% 76%;
-}
-</style>

+ 0 - 78
src/components/SvgIcon/index.vue

@@ -1,78 +0,0 @@
-<script setup lang="ts">
-import { UseImage } from '@vueuse/components'
-import { Icon } from '@iconify/vue'
-
-defineOptions({
-  name: 'SvgIcon',
-})
-
-const props = defineProps<{
-  name: string
-  flip?: 'horizontal' | 'vertical' | 'both'
-  rotate?: number
-  color?: string
-  size?: string | number
-}>()
-
-const outputType = computed(() => {
-  const hasPathFeatures = (str: string) => {
-    return /^\.{1,2}\//.test(str) || str.startsWith('/') || str.includes('/')
-  }
-  if (/^https?:\/\//.test(props.name) || hasPathFeatures(props.name) || !props.name) {
-    return 'img'
-  }
-  else if (/i-[^:]+:[^:]+/.test(props.name)) {
-    return 'unocss'
-  }
-  else if (props.name.includes(':')) {
-    return 'iconify'
-  }
-  else {
-    return 'svg'
-  }
-})
-
-const style = computed(() => {
-  const transform = []
-  if (props.flip) {
-    switch (props.flip) {
-      case 'horizontal':
-        transform.push('rotateY(180deg)')
-        break
-      case 'vertical':
-        transform.push('rotateX(180deg)')
-        break
-      case 'both':
-        transform.push('rotateX(180deg)')
-        transform.push('rotateY(180deg)')
-        break
-    }
-  }
-  if (props.rotate) {
-    transform.push(`rotate(${props.rotate % 360}deg)`)
-  }
-  return {
-    ...(props.color && { color: props.color }),
-    ...(props.size && { fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size }),
-    ...(transform.length && { transform: transform.join(' ') }),
-  }
-})
-</script>
-
-<template>
-  <i class="relative h-[1em] w-[1em] flex-inline items-center justify-center fill-current leading-[1em]" :style="style">
-    <i v-if="outputType === 'unocss'" class="h-[1em] w-[1em]" :class="name" />
-    <Icon v-else-if="outputType === 'iconify'" :icon="name" />
-    <svg v-else-if="outputType === 'svg'" class="h-[1em] w-[1em]" aria-hidden="true">
-      <use :xlink:href="`#icon-${name}`" />
-    </svg>
-    <UseImage v-else-if="outputType === 'img'" :src="name" class="h-[1em] w-[1em]">
-      <template #loading>
-        <i class="i-line-md:loading-loop h-[1em] w-[1em]" />
-      </template>
-      <template #error>
-        <i class="i-tdesign:image-error h-[1em] w-[1em]" />
-      </template>
-    </UseImage>
-  </i>
-</template>

+ 0 - 38
src/components/Trend/index.vue

@@ -1,38 +0,0 @@
-<script setup lang="ts">
-defineOptions({
-  name: 'Trend',
-})
-
-const props = withDefaults(
-  defineProps<{
-    value: string
-    type?: 'up' | 'down'
-    prefix?: string
-    suffix?: string
-    reverse?: boolean
-  }>(),
-  {
-    type: 'up',
-    prefix: '',
-    suffix: '',
-    reverse: false,
-  },
-)
-
-const isUp = computed(() => {
-  let isUp = props.type === 'up'
-  if (props.reverse) {
-    isUp = !isUp
-  }
-  return isUp
-})
-</script>
-
-<template>
-  <div class="flex items-center transition" :class="`${isUp ? 'c-green' : 'c-red'}`">
-    <span v-if="prefix" class="prefix">{{ prefix }}</span>
-    <span class="text">{{ value }}</span>
-    <span v-if="suffix" class="suffix">{{ suffix }}</span>
-    <SvgIcon name="i-ep:caret-top" :rotate="isUp ? 0 : 180" class="ml-1 transition" />
-  </div>
-</template>

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

@@ -1,65 +0,0 @@
-<script setup lang="ts">
-import type { CalendarProps, FieldProps } from 'vant'
-import { pick } from 'lodash-es'
-import dayjs from '@/utils/dayjs'
-
-defineOptions({
-  name: 'VanFieldCalendar',
-})
-
-const props = withDefaults(
-  defineProps<{
-    // field
-    label?: FieldProps['label']
-    name?: FieldProps['name']
-    id?: FieldProps['id']
-    size?: FieldProps['size']
-    placeholder?: FieldProps['placeholder']
-    border?: FieldProps['border']
-    colon?: FieldProps['colon']
-    required?: FieldProps['required']
-    center?: FieldProps['center']
-    arrowDirection?: FieldProps['arrowDirection']
-    labelClass?: FieldProps['labelClass']
-    labelWidth?: FieldProps['labelWidth']
-    labelAlign?: FieldProps['labelAlign']
-    leftIcon?: FieldProps['leftIcon']
-    rightIcon?: FieldProps['rightIcon']
-    rules?: FieldProps['rules']
-    // calendar
-    color?: CalendarProps['color']
-    minDate?: CalendarProps['minDate']
-    maxDate?: CalendarProps['maxDate']
-    formatter?: CalendarProps['formatter']
-    showConfirm?: CalendarProps['showConfirm']
-    confirmText?: CalendarProps['confirmText']
-    firstDayOfWeek?: CalendarProps['firstDayOfWeek']
-    round?: CalendarProps['round']
-    // 自定义
-    format?: string
-    valueFormat?: string
-  }>(),
-  {
-    format: 'YYYY-MM-DD',
-    valueFormat: '',
-  },
-)
-
-const fieldProps = computed(() => pick(props, ['label', 'name', 'id', 'size', 'placeholder', 'border', 'colon', 'required', 'center', 'arrowDirection', 'labelClass', 'labelWidth', 'labelAlign', 'leftIcon', 'rightIcon', 'rules']))
-const calendarProps = computed(() => pick(props, ['color', 'minDate', 'maxDate', 'formatter', 'showConfirm', 'confirmText', 'firstDayOfWeek', 'round']))
-
-const value = defineModel<string>()
-const valueStr = computed(() => value.value && dayjs(value.value).format(props.format))
-const valueDate = computed(() => dayjs(value.value).toDate())
-const showCalendar = ref(false)
-
-function onConfirm(date: Date) {
-  value.value = dayjs(date).format(props.valueFormat)
-  showCalendar.value = false
-}
-</script>
-
-<template>
-  <van-field :model-value="valueStr" v-bind="fieldProps" is-link readonly @click="showCalendar = true" />
-  <van-calendar v-model:show="showCalendar" v-bind="calendarProps" :default-date="valueDate" teleport="body" @confirm="onConfirm" />
-</template>

+ 0 - 58
src/components/VanFieldDatePicker/index.vue

@@ -1,58 +0,0 @@
-<script setup lang="ts">
-import type { DatePickerProps, FieldProps, PopupProps } from 'vant'
-import { pick } from 'lodash-es'
-
-defineOptions({
-  name: 'VanFieldDatePicker',
-})
-
-const props = defineProps<{
-  // field
-  label?: FieldProps['label']
-  name?: FieldProps['name']
-  id?: FieldProps['id']
-  size?: FieldProps['size']
-  placeholder?: FieldProps['placeholder']
-  border?: FieldProps['border']
-  colon?: FieldProps['colon']
-  required?: FieldProps['required']
-  center?: FieldProps['center']
-  arrowDirection?: FieldProps['arrowDirection']
-  labelClass?: FieldProps['labelClass']
-  labelWidth?: FieldProps['labelWidth']
-  labelAlign?: FieldProps['labelAlign']
-  leftIcon?: FieldProps['leftIcon']
-  rightIcon?: FieldProps['rightIcon']
-  rules?: FieldProps['rules']
-  // popup
-  round?: PopupProps['round']
-  // date-picker
-  columnsType?: DatePickerProps['columnsType']
-  minDate?: DatePickerProps['minDate']
-  maxDate?: DatePickerProps['maxDate']
-  formatter?: DatePickerProps['formatter']
-}>()
-
-const fieldProps = computed(() => pick(props, ['label', 'name', 'id', 'size', 'placeholder', 'border', 'colon', 'required', 'center', 'arrowDirection', 'labelClass', 'labelWidth', 'labelAlign', 'leftIcon', 'rightIcon', 'rules']))
-const popupProps = computed(() => pick(props, ['round']))
-const datePickerProps = computed(() => pick(props, ['columnsType', 'minDate', 'maxDate', 'formatter']))
-
-const value = defineModel<string[]>()
-const valueDate = ref<string[]>(value.value ?? [])
-const valueStr = computed(() => {
-  return value.value ? value.value.join('-') : ''
-})
-
-const showPicker = ref(false)
-function onConfirm({ selectedValues }: { selectedValues: string[] }) {
-  value.value = selectedValues
-  showPicker.value = false
-}
-</script>
-
-<template>
-  <van-field :model-value="valueStr" v-bind="fieldProps" is-link readonly @click="showPicker = true" />
-  <van-popup v-model:show="showPicker" v-bind="popupProps" position="bottom" teleport="body">
-    <van-date-picker v-model="valueDate" v-bind="datePickerProps" @confirm="onConfirm" @cancel="showPicker = false" />
-  </van-popup>
-</template>

+ 0 - 51
src/components/VanFieldPicker/index.vue

@@ -1,51 +0,0 @@
-<script setup lang="ts">
-import type { FieldProps, PickerOption, PopupProps } from 'vant'
-import { pick } from 'lodash-es'
-
-defineOptions({
-  name: 'VanFieldPicker',
-})
-
-const props = defineProps<{
-  // field
-  label?: FieldProps['label']
-  name?: FieldProps['name']
-  id?: FieldProps['id']
-  type?: FieldProps['type']
-  size?: FieldProps['size']
-  placeholder?: FieldProps['placeholder']
-  border?: FieldProps['border']
-  colon?: FieldProps['colon']
-  required?: FieldProps['required']
-  center?: FieldProps['center']
-  arrowDirection?: FieldProps['arrowDirection']
-  labelClass?: FieldProps['labelClass']
-  labelWidth?: FieldProps['labelWidth']
-  labelAlign?: FieldProps['labelAlign']
-  autosize?: FieldProps['autosize']
-  leftIcon?: FieldProps['leftIcon']
-  rightIcon?: FieldProps['rightIcon']
-  rules?: FieldProps['rules']
-  // popup
-  round?: PopupProps['round']
-  // picker
-  columns?: PickerOption[]
-}>()
-
-const fieldProps = computed(() => pick(props, ['label', 'name', 'id', 'type', 'size', 'placeholder', 'border', 'colon', 'required', 'center', 'arrowDirection', 'labelClass', 'labelWidth', 'labelAlign', 'autosize', 'leftIcon', 'rightIcon', 'rules']))
-const popupProps = computed(() => pick(props, ['round']))
-const pickerProps = computed(() => pick(props, ['columns']))
-
-const value = defineModel<string | number>()
-const valuePicker = ref<any>([value.value])
-const valueStr = computed(() => props.columns?.find((item: any) => item.value === value.value)?.text)
-
-const showPicker = ref(false)
-</script>
-
-<template>
-  <van-field :model-value="valueStr" v-bind="fieldProps" is-link readonly @click="showPicker = true" />
-  <van-popup v-model:show="showPicker" v-bind="popupProps" position="bottom" teleport="body">
-    <van-picker :model-value="valuePicker" v-bind="pickerProps" @confirm="({ selectedOptions }) => { value = selectedOptions[0]?.value; showPicker = false }" @cancel="showPicker = false" />
-  </van-popup>
-</template>

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

@@ -1,71 +0,0 @@
-<script setup lang="ts">
-/*
- * 组件名: VoiceImp
- * 组件用途: 声音反馈组件
- * 创建日期: 2024/8/7
- * 编写者: JutarryWu
- */
-const VoiceUrlObj: Record<string, string> = {
-  click: '/static/voice/click.mp3',
-  right: '/static/voice/right.mp3',
-  error: '/static/voice/error.mp3',
-  bg: '/static/voice/bg-music.mp3',
-}
-const VideoRef_1 = ref<HTMLVideoElement>()
-const VideoRef_2 = ref<HTMLVideoElement>()
-const VideoRef_3 = ref<HTMLVideoElement>()
-const VideoRef_bg = ref<HTMLVideoElement>()
-
-function videoPlay(type: string = 'click') {
-  if (type === 'click') {
-    VideoRef_1.value?.play()
-  }
-  else if (type === 'right') {
-    VideoRef_2.value?.play()
-  }
-  else if (type === 'error') {
-    VideoRef_3.value?.play()
-  }
-  else if (type === 'bg') {
-    VideoRef_bg.value?.play()
-  }
-}
-
-function videoPause(type: string = 'bg') {
-  if (type === 'bg') {
-    VideoRef_bg.value?.pause()
-  }
-}
-
-defineExpose({
-  videoPlay,
-  videoPause,
-})
-</script>
-
-<template>
-  <section class="voice-container">
-    <video ref="VideoRef_1" :src="VoiceUrlObj.click" />
-    <video ref="VideoRef_2" :src="VoiceUrlObj.right" />
-    <video ref="VideoRef_3" :src="VoiceUrlObj.error" />
-    <video ref="VideoRef_bg" :src="VoiceUrlObj.bg" loop />
-  </section>
-</template>
-
-<style scoped lang="less">
-.voice-container {
-  width: 0;
-  height: 0;
-  overflow: hidden;
-
-  video {
-    position: absolute;
-    width: 0;
-    height: 0;
-    opacity: 0;
-    z-index: -1;
-    left: 1000vw;
-    top: 1000vh;
-  }
-}
-</style>

+ 0 - 23
src/components/WuIsCorrect/index.vue

@@ -1,23 +0,0 @@
-<script setup lang="ts">
-const props = defineProps({
-  correct: {
-    type: Boolean,
-    default: false,
-    required: true,
-    immediate: true,
-  },
-})
-</script>
-
-<template>
-  <div class="flex-center absolute bottom-[6px] right-[6px]">
-    <transition name="el-fade-in">
-      <SvgIcon v-if="props.correct" name="correct" class="text-[34px]" />
-    </transition>
-    <transition name="el-fade-in">
-      <SvgIcon v-if="!props.correct" name="wrong" class="text-[34px]" />
-    </transition>
-  </div>
-</template>
-
-<style scoped lang="less"></style>

+ 22 - 0
src/components/nav-bar/index.vue

@@ -0,0 +1,22 @@
+<script setup>
+import { useDark, useToggle } from '@vueuse/core'
+import { setLocale, locale } from '@/locales'
+const userStore = useUserStore()
+const isDark = useDark()
+const toggleDark = useToggle(isDark)
+const toggleLocale = () => {
+  setLocale(locale.value == 'en' ? 'zh-cn' : 'en')
+}
+</script>
+
+<template>
+  <van-nav-bar fixed placeholder >
+    <template #right>
+      <span>{{userStore.user.name}}</span>
+      <svg-icon @click="toggleLocale()" class="text-[18px] m-1" name="i18n" />
+      <svg-icon @click="toggleDark()" class="text-[18px]" :name="isDark ? 'light' : 'dark'" />
+    </template>
+  </van-nav-bar>
+</template>
+
+<style scoped></style>

+ 55 - 0
src/components/svg-icon/index.vue

@@ -0,0 +1,55 @@
+<script setup>
+import { isExternal } from "@/utils/validate";
+import { computed } from "vue";
+
+const props = defineProps({
+  name: {
+    type: String,
+    default: ""
+  },
+  className: {
+    type: String,
+    default: ""
+  }
+});
+
+const isExternalIcon = computed(() => isExternal(props.name));
+const iconName = computed(() => `#icon-${props.name}`);
+const svgClass = computed(() => {
+  if (props.className) {
+    return "svg-icon " + props.className;
+  } else {
+    return "svg-icon";
+  }
+});
+// 外链 icon
+const styleExternalIcon = computed(() => {
+  return {
+    mask: `url(${props.name}) no-repeat 50% 50%`,
+    "-webkit-mask": `url(${props.name}) no-repeat 50% 50%`
+  };
+});
+</script>
+
+<template>
+  <div v-if="isExternalIcon" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-bind="$attrs" />
+  <svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+
+.svg-external-icon {
+  background-color: currentColor;
+  mask-size: cover !important;
+  display: inline-block;
+}
+</style>

+ 35 - 0
src/components/tabbar/index.vue

@@ -0,0 +1,35 @@
+<template>
+  <van-tabbar v-model="active" :placeholder="true" :route="true" fixed>
+    <van-tabbar-item replace v-for="(item, index) in tabbarData" :key="index" :icon="item.icon" :to="item.to">
+      {{ item.title }}
+    </van-tabbar-item>
+  </van-tabbar>
+</template>
+
+<script setup>
+const { t } = useI18n();
+const active = ref(0);
+const tabbarData = reactive([
+  {
+    icon: "wap-home-o",
+    title: computed(() => t("home.tabbar.home")),
+    to: {
+      name: "home"
+    }
+  },
+  {
+    icon: "records-o",
+    title: computed(() => t("home.tabbar.tools")),
+    to: {
+      name: "tools"
+    }
+  },
+  {
+    icon: "user-o",
+    title: computed(() => t("home.tabbar.about")),
+    to: {
+      name: "about"
+    }
+  }
+]);
+</script>

+ 18 - 0
src/layout/404.vue

@@ -0,0 +1,18 @@
+<script setup>
+const router = useRouter()
+const { t } = useI18n()
+const goHome = () => {
+  router.replace({ name: 'home' })
+}
+</script>
+
+<template>
+  <div flex justify-center items-center class="h100%">
+    <div flex flex-col items-center>
+      <img w="full" src="@/assets/images/404.png" alt="404" />
+      <van-button type="primary" @click="goHome">{{ t('backHomeBtn') }}</van-button>
+    </div>
+
+
+  </div>
+</template>

+ 33 - 0
src/layout/index.vue

@@ -0,0 +1,33 @@
+<script setup>
+import tabbar from "@/components/tabbar/index.vue";
+import NavBar from "@/components/nav-bar/index.vue";
+import { useDark } from '@vueuse/core'
+
+const isDark = useDark();
+</script>
+
+<template>
+  <div class="app-wrapper">
+    <van-config-provider flex flex-col class="page h100%" :theme="isDark ? 'dark' : 'light'">
+      <nav-bar />
+      <div flex-1 overflow-y-auto overflow-x-hidden>
+        <!-- 路由缓存 -->
+        <router-view v-slot="{ Component, route }">
+          <keep-alive>
+            <component :is="Component" :key="route.fullPath" v-if="route.meta.keepAlive" />
+          </keep-alive>
+          <component :is="Component" :key="route.fullPath" v-if="!route.meta.keepAlive" />
+        </router-view>
+      </div>
+      <tabbar />
+    </van-config-provider>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.app-wrapper {
+  position: relative;
+  height: 100%;
+  width: 100%;
+}
+</style>

+ 35 - 0
src/locales/index.js

@@ -0,0 +1,35 @@
+import { createI18n } from 'vue-i18n'
+import messages from "./locales";
+import { getCookie, setCookie } from "@/utils/auth";
+
+const defaultLanguage = "zh";
+const i18n = createI18n({
+  // 默认语言
+  locale: getDefaultLanguage(),
+  legacy: false,
+  fallbackLocale: defaultLanguage, // 如果在message中找不到对应的key,就回退到这个语言
+  messages,
+  globalInjection: true, // 全局注册$t方法
+})
+export default i18n
+export const t = i18n.global.t;
+export const locale = i18n.global.locale;
+
+function getDefaultLanguage(){
+  const locales = Object.keys(messages);
+  // 1. 优先从 cookies 中取得语言
+  const chosenLanguage = getCookie("lang") ;
+  if (chosenLanguage && locales.includes(chosenLanguage)) return chosenLanguage;
+
+  // 2. 其次从浏览器中取得默认语言
+  const browserLanguage = navigator.language.toLowerCase();
+  if (locales.includes(browserLanguage)) return browserLanguage;
+
+  // 3. 最后默认语言
+  return defaultLanguage;
+}
+
+export function setLocale(locale) {
+  i18n.global.locale.value = locale;
+  setCookie("lang", locale);
+}

+ 38 - 0
src/locales/lang/en.json

@@ -0,0 +1,38 @@
+{
+  "home": {
+    "description": "Basic template for an H5 mobile project, based on Vue 3 ecosystem, JavaScript, and Vite build tool, ready to use.",
+    "tabbar": {
+      "home": "Home",
+      "tools": "Tools",
+      "about": "About"
+    },
+    "details": {
+      "vue3": "✔ ⚡ Vue3 + Vite4",
+      "javascript": "✔ 🍕 JavaScript no Type Definition Requir",
+      "vant": "✔ ✨ Vant4 - component library",
+      "unocss":"✔ 🎨 Unocss - the instant on-demand atomic CSS engine",
+      "pinia": "✔ 🍍 Pinia state management and persisted",
+      "darkmode": "✔ 🌓 Dark mode support",
+      "i18n": "✔ 🌍 Internationalization (i18n) support",
+      "vueuse": "✔ 🧀 VueUse collection of useful composition APIs",
+      "router": "✔ 🔥 Vue Router 4",
+      "autoImport": "✔ 📥 use Composition API and others directly",
+      "icon": "✔ 🌠 Automatic registration of SVG icon components",
+      "rem": "✔ 🖼️ rem adaptation",
+      "axios": "✔ 📩 Axios wrapper",
+      "mock": "✔ 📨 Mock data support in development environment",
+      "loading": "✔ 📊 First screen loading animation",
+      "vconsole": "✔ 💻 VConsole Development environment debugging panel",
+      "gzip": "✔ 📦 Gzip compression for bundled resources"
+      }
+  },
+  "tools": {
+    "successBtn": "Successful Request",
+    "errorBtn": "Failed Request",
+    "reqOK": "Request Successful",
+    "reqErr": "Request Failed"
+  },
+  "backHomeBtn": "Back to Home",
+  "loginBtn": "Login",
+  "logoutBtn": "Logout"
+}

+ 38 - 0
src/locales/lang/zh.json

@@ -0,0 +1,38 @@
+{
+  "home": {
+    "description": "基于 Vue3 全家桶、JavaScript-放弃为TS而TS🤪、Vite 构建工具,开箱即用的 H5 移动端项目基础模板。",
+    "tabbar": {
+      "home": "首页",
+      "tools": "工具",
+      "about": "关于"
+    },
+    "details": {
+      "vue3": "✔ ⚡ Vue3 + Vite4",
+      "javascript": "✔ 🍕 JavaScript 无需类型定义",
+      "vant": "✔ ✨ Vant4 - 组件库",
+      "unocss": "✔ 🎨 Unocss - 高性能且极具灵活性原子化CSS",
+      "pinia": "✔ 🍍 Pinia 状态管理并进行持久化",
+      "darkmode": "✔ 🌓 支持深色模式",
+      "i18n": "✔ 🌍 I18n 国际化开箱即用",
+      "vueuse": "✔ 🧀 VueUse - 实用的 Composition API 工具合集",
+      "router": "✔ 🔥 Vue-router 4",
+      "autoImport": "✔ 📥 API及组件 自动加载 无需引入",
+      "icon": "✔ 🌠 支持 SVG 图标自动注册组件",
+      "rem": "✔ 🖼️ rem 适配",
+      "axios": "✔ 📩 Axios 封装",
+      "mock": "✔ 📨 开发环境支持 Mock 数据",
+      "loading": "✔ 📊 首屏加载动画",
+      "vconsole": "✔ 💻 VConsole 开发环境调试面板",
+      "gzip": "✔ 📦 打包资源 gzip 压缩"
+    }
+  },
+  "tools": {
+    "successBtn": "成功请求",
+    "errorBtn": "失败请求",
+    "reqOK": "请求成功",
+    "reqErr": "请求失败"
+  },
+  "backHomeBtn": "返回首页",
+  "loginBtn": "登录",
+  "logoutBtn": "退出登录"
+}

+ 9 - 0
src/locales/locales.js

@@ -0,0 +1,9 @@
+import zhCNLocale from "./lang/zh";
+import enLocale from "./lang/en";
+
+const locales = {
+  "zh": zhCNLocale,
+  en: enLocale
+};
+
+export default locales;

+ 32 - 0
src/main.js

@@ -0,0 +1,32 @@
+import './assets/styles/index.scss' // 引入全局样式
+import 'amfe-flexible';
+import 'virtual:uno.css'
+import 'virtual:svg-icons-register'
+// import 'vant/es/toast/style' 
+// import 'vant/es/notify/style' 
+// import 'vant/es/dialog/style' 
+import 'vant/lib/index.css'; // 解决vant 提示样式问题
+import 'default-passive-events';
+import Vconsole from 'vconsole'
+// i18n
+import i18n from "./locales";
+
+import { createApp } from 'vue'
+import { pinia } from "./store";
+import { ConfigProvider } from 'vant';
+
+import App from './App.vue'
+import router from './router'
+
+const app = createApp(App)
+
+app.use(ConfigProvider);
+app.use(pinia);
+app.use(router)
+app.use(i18n);
+app.mount('#app')
+
+if (import.meta.env.VITE_ENABLE_VCONSOLE=='true') {
+  new Vconsole()
+}
+

+ 0 - 32
src/main.ts

@@ -1,32 +0,0 @@
-import './utils/system.copyright'
-
-import Message from 'vue-m-message'
-import 'vue-m-message/dist/style.css'
-
-import 'overlayscrollbars/overlayscrollbars.css'
-
-import App from './App.vue'
-import pinia from './store'
-import router from './router'
-import ui from './ui-provider'
-
-// 自定义指令
-import directive from '@/utils/directive'
-
-// 加载 svg 图标
-import 'virtual:svg-icons-register'
-
-import 'virtual:uno.css'
-
-// 全局样式
-import '@/assets/styles/globals.css'
-
-const app = createApp(App)
-
-app.use(Message)
-app.use(pinia)
-app.use(router)
-app.use(ui)
-directive(app)
-
-app.mount('#app')

+ 0 - 47
src/mock/user.ts

@@ -1,47 +0,0 @@
-import { defineFakeRoute } from 'vite-plugin-fake-server/client'
-import Mock from 'mockjs'
-
-export default defineFakeRoute([
-  {
-    url: '/mock/user/login',
-    method: 'post',
-    response: ({ body }) => {
-      return {
-        error: '',
-        status: 1,
-        data: Mock.mock({
-          account: body.account,
-          token: `${body.account}_@string`,
-          avatar: 'https://fantastic-mobile.hurui.me/logo.png',
-        }),
-      }
-    },
-  },
-  {
-    url: '/mock/user/permission',
-    method: 'get',
-    response: ({ headers }) => {
-      let permissions: string[] = []
-      if (headers.token?.indexOf('admin') === 0) {
-        permissions = [
-          'permission.browse',
-          'permission.create',
-          'permission.edit',
-          'permission.remove',
-        ]
-      }
-      else if (headers.token?.indexOf('test') === 0) {
-        permissions = [
-          'permission.browse',
-        ]
-      }
-      return {
-        error: '',
-        status: 1,
-        data: {
-          permissions,
-        },
-      }
-    },
-  },
-])

+ 0 - 132
src/router/guards.ts

@@ -1,132 +0,0 @@
-import type { Router } from 'vue-router/auto'
-import { useNProgress } from '@vueuse/integrations/useNProgress'
-import '@/assets/styles/nprogress.css'
-import useSettingsStore from '@/store/modules/settings'
-import useUserStore from '@/store/modules/user'
-import useKeepAliveStore from '@/store/modules/keepAlive'
-
-// 鉴权
-function setupAuth(router: Router) {
-  router.beforeEach(async (to, from, next) => {
-    const settingsStore = useSettingsStore()
-    const userStore = useUserStore()
-    if (to.meta.auth) {
-      if (userStore.isLogin) {
-        // 获取用户权限
-        if (settingsStore.settings.app.enablePermission) {
-          !userStore.isGetPermissions && await userStore.getPermissions()
-        }
-        next()
-      }
-      else {
-        next({
-          name: 'login',
-          query: {
-            redirect: to.fullPath,
-          },
-        })
-      }
-    }
-    else {
-      next()
-    }
-  })
-}
-
-// 进度条
-function setupProgress(router: Router) {
-  const { isLoading } = useNProgress(null, {
-    showSpinner: false,
-    parent: '#app',
-  })
-  router.beforeEach((to, from, next) => {
-    const settingsStore = useSettingsStore()
-    if (settingsStore.settings.app.enableProgress) {
-      isLoading.value = true
-    }
-    next()
-  })
-  router.afterEach(() => {
-    const settingsStore = useSettingsStore()
-    if (settingsStore.settings.app.enableProgress) {
-      isLoading.value = false
-    }
-  })
-}
-
-// 标题
-function setupTitle(router: Router) {
-  router.afterEach((to) => {
-    const settingsStore = useSettingsStore()
-    settingsStore.setTitle(to.meta.title ?? '')
-  })
-}
-
-// 页面缓存
-function setupKeepAlive(router: Router) {
-  router.afterEach((to, from) => {
-    const keepAliveStore = useKeepAliveStore()
-    if (to.fullPath !== from.fullPath) {
-      // 判断当前页面是否开启缓存,如果开启,则将当前页面的 name 信息存入 keep-alive 全局状态
-      if (to.meta.cache) {
-        const componentName = to.matched.at(-1)?.components?.default.name
-        if (componentName) {
-          keepAliveStore.add(componentName)
-        }
-        else {
-          // turbo-console-disable-next-line
-          console.warn('[Fantastic-mobile] 该页面组件未设置组件名,会导致缓存失效,请检查')
-        }
-      }
-      // 判断离开页面是否开启缓存,如果开启,则根据缓存规则判断是否需要清空 keep-alive 全局状态里离开页面的 name 信息
-      if (from.meta.cache) {
-        const componentName = from.matched.at(-1)?.components?.default.name
-        if (componentName) {
-        // 通过 meta.cache 判断针对哪些页面进行缓存
-          switch (typeof from.meta.cache) {
-            case 'string':
-              if (from.meta.cache !== to.name) {
-                keepAliveStore.remove(componentName)
-              }
-              break
-            case 'object':
-              if (!from.meta.cache.includes(to.name)) {
-                keepAliveStore.remove(componentName)
-              }
-              break
-          }
-          // 通过 meta.noCache 判断针对哪些页面不需要进行缓存
-          if (from.meta.noCache) {
-            switch (typeof from.meta.noCache) {
-              case 'string':
-                if (from.meta.noCache === to.name) {
-                  keepAliveStore.remove(componentName)
-                }
-                break
-              case 'object':
-                if (from.meta.noCache.includes(to.name)) {
-                  keepAliveStore.remove(componentName)
-                }
-                break
-            }
-          }
-        }
-      }
-    }
-  })
-}
-
-// 其他
-function setupOther(router: Router) {
-  router.afterEach(() => {
-    document.documentElement.scrollTop = 0
-  })
-}
-
-export default function setupGuards(router: Router) {
-  setupAuth(router)
-  setupProgress(router)
-  setupTitle(router)
-  setupKeepAlive(router)
-  setupOther(router)
-}

+ 35 - 0
src/router/index.js

@@ -0,0 +1,35 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import NProgress from "@/utils/progress";
+import routes from "./routes";
+import { useTitle } from '@vueuse/core'
+import { getToken } from '@/utils/auth'
+import { pageDefaultTitle } from '@/settings';
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.VITE_BASE_URL),
+  routes
+})
+
+const whiteList = ['/login', '/register', '/404', '/403'];
+
+router.beforeEach((to, from, next) => {
+  NProgress.start();
+  useTitle(to.meta.title || pageDefaultTitle)
+  if (getToken()) {
+    if (to.path === '/login') {
+      next({ path: '/' });
+    } else {
+      next();
+    }
+  } else if (whiteList.includes(to.path)) {
+    next();
+  } else {
+    next('/login');
+  }
+});
+
+router.afterEach(() => {
+  NProgress.done();
+});
+
+export default router

+ 0 - 35
src/router/index.ts

@@ -1,35 +0,0 @@
-import type { RouteRecordRaw } from 'vue-router/auto'
-import { createRouter, createWebHashHistory } from 'vue-router/auto'
-import { routes } from 'vue-router/auto-routes'
-import path from 'path-browserify'
-import setupGuards from './guards'
-
-function resolveRoutePath(basePath?: string, routePath?: string) {
-  return basePath ? path.resolve(basePath, routePath ?? '') : routePath ?? ''
-}
-
-// 将多层嵌套路由处理成一级
-function flatRoutesRecursive(routes: RouteRecordRaw[], baseUrl = '') {
-  const result: RouteRecordRaw[] = []
-  for (const route of routes) {
-    if (route.children) {
-      result.push(...flatRoutesRecursive(route.children, resolveRoutePath(baseUrl, route.path)))
-    }
-    else {
-      result.push({
-        ...route,
-        path: resolveRoutePath(baseUrl, route.path),
-      })
-    }
-  }
-  return result
-}
-
-const router = createRouter({
-  history: createWebHashHistory(),
-  routes: flatRoutesRecursive(routes),
-})
-
-setupGuards(router)
-
-export default router

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