Pārlūkot izejas kodu

初始化项目: 首页,我的,登录,打印助手管理

“shengjie.huang” 1 gadu atpakaļ
revīzija
eda1971755
100 mainītis faili ar 18911 papildinājumiem un 0 dzēšanām
  1. 106 0
      .commitlintrc.cjs
  2. 13 0
      .editorconfig
  3. 1 0
      .eslintignore
  4. 101 0
      .eslintrc-auto-import.json
  5. 97 0
      .eslintrc.cjs
  6. 42 0
      .gitignore
  7. 5 0
      .husky/commit-msg
  8. 5 0
      .husky/pre-commit
  9. 6 0
      .npmrc
  10. 9 0
      .prettierignore
  11. 19 0
      .prettierrc.cjs
  12. 1 0
      .stylelintignore
  13. 58 0
      .stylelintrc.cjs
  14. 18 0
      .vscode/extensions.json
  15. 67 0
      .vscode/settings.json
  16. 56 0
      .vscode/vue3.code-snippets
  17. 21 0
      LICENSE
  18. 70 0
      README.md
  19. 25 0
      env/.env
  20. 6 0
      env/.env.development
  21. 6 0
      env/.env.production
  22. 4 0
      env/.env.test
  23. BIN
      favicon.ico
  24. 26 0
      index.html
  25. 134 0
      manifest.config.ts
  26. 169 0
      package.json
  27. 43 0
      pages.config.ts
  28. 12339 0
      pnpm-lock.yaml
  29. 36 0
      scripts/postupgrade.js
  30. 58 0
      src/App.vue
  31. 0 0
      src/components/.gitkeep
  32. 32 0
      src/directives/permission.ts
  33. 31 0
      src/env.d.ts
  34. 0 0
      src/hooks/.gitkeep
  35. 44 0
      src/hooks/useRequest.ts
  36. 69 0
      src/hooks/useUpload.ts
  37. 3 0
      src/interceptors/index.ts
  38. 13 0
      src/interceptors/prototype.ts
  39. 68 0
      src/interceptors/request.ts
  40. 54 0
      src/interceptors/route.ts
  41. 17 0
      src/layouts/default.vue
  42. 17 0
      src/layouts/demo.vue
  43. 17 0
      src/main.ts
  44. 111 0
      src/manifest.json
  45. 20 0
      src/pages-sub/demo/index.vue
  46. 88 0
      src/pages.json
  47. 166 0
      src/pages/assistant/detail.vue
  48. 307 0
      src/pages/assistant/index.vue
  49. 234 0
      src/pages/index/index.vue
  50. 141 0
      src/pages/login/components/LoginInput.vue
  51. 181 0
      src/pages/login/index.vue
  52. 189 0
      src/pages/personal/index.vue
  53. 22 0
      src/pages/webview/index.vue
  54. 97 0
      src/service/api/index.ts
  55. 0 0
      src/static/images/.gitkeep
  56. BIN
      src/static/images/avatar.png
  57. BIN
      src/static/images/banner1.jpg
  58. BIN
      src/static/images/banner2.jpg
  59. BIN
      src/static/images/banner3.jpg
  60. BIN
      src/static/images/device.jpg
  61. BIN
      src/static/images/empty.png
  62. BIN
      src/static/images/logo_temp.png
  63. BIN
      src/static/images/picture.jpg
  64. BIN
      src/static/images/setting.png
  65. 33 0
      src/static/logo.svg
  66. BIN
      src/static/tabbar/home.png
  67. BIN
      src/static/tabbar/homeHL.png
  68. BIN
      src/static/tabbar/order.png
  69. BIN
      src/static/tabbar/orderHL.png
  70. BIN
      src/static/tabbar/personal.png
  71. BIN
      src/static/tabbar/personalHL.png
  72. 17 0
      src/store/index.ts
  73. 61 0
      src/store/user.ts
  74. 28 0
      src/style/iconfont.css
  75. 188 0
      src/style/index.scss
  76. 187 0
      src/types/auto-import.d.ts
  77. 23 0
      src/types/global.d.ts
  78. 8 0
      src/types/shims-uni.d.ts
  79. 27 0
      src/types/uni-pages.d.ts
  80. 29 0
      src/typings.d.ts
  81. 6 0
      src/typings.ts
  82. 81 0
      src/uni.scss
  83. 0 0
      src/uni_modules/.gitkeep
  84. 192 0
      src/uni_modules/pyh-nv/changelog.md
  85. 1291 0
      src/uni_modules/pyh-nv/components/pyh-nv/pyh-nv.vue
  86. 80 0
      src/uni_modules/pyh-nv/package.json
  87. 165 0
      src/uni_modules/pyh-nv/readme.md
  88. 7 0
      src/uni_modules/uv-load-more/changelog.md
  89. 95 0
      src/uni_modules/uv-load-more/components/uv-load-more/props.js
  90. 152 0
      src/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue
  91. 89 0
      src/uni_modules/uv-load-more/package.json
  92. 11 0
      src/uni_modules/uv-load-more/readme.md
  93. 9 0
      src/uni_modules/uv-loading-icon/changelog.md
  94. 67 0
      src/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js
  95. 347 0
      src/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue
  96. 87 0
      src/uni_modules/uv-loading-icon/package.json
  97. 19 0
      src/uni_modules/uv-loading-icon/readme.md
  98. 19 0
      src/uni_modules/uv-transition/changelog.md
  99. 131 0
      src/uni_modules/uv-transition/components/uv-transition/createAnimation.js
  100. 0 0
      src/uni_modules/uv-transition/components/uv-transition/props.js

+ 106 - 0
.commitlintrc.cjs

@@ -0,0 +1,106 @@
+const fs = require('fs')
+const path = require('path')
+const { execSync } = require('child_process')
+
+const scopes = fs
+  .readdirSync(path.resolve(__dirname, 'src'), { withFileTypes: true })
+  .filter((dirent) => dirent.isDirectory())
+  .map((dirent) => dirent.name.replace(/s$/, ''))
+
+// precomputed scope
+const scopeComplete = execSync('git status --porcelain || true')
+  .toString()
+  .trim()
+  .split('\n')
+  .find((r) => ~r.indexOf('M  src'))
+  ?.replace(/(\/)/g, '%%')
+  ?.match(/src%%((\w|-)*)/)?.[1]
+  ?.replace(/s$/, '')
+
+module.exports = {
+  ignores: [(commit) => commit.includes('init')],
+  extends: ['@commitlint/config-conventional'],
+  rules: {
+    'body-leading-blank': [2, 'always'],
+    'footer-leading-blank': [1, 'always'],
+    'header-max-length': [2, 'always', 108],
+    'subject-empty': [2, 'never'],
+    'type-empty': [2, 'never'],
+    'subject-case': [0],
+    'type-enum': [
+      2,
+      'always',
+      [
+        'feat',
+        'fix',
+        'perf',
+        'style',
+        'docs',
+        'test',
+        'refactor',
+        'build',
+        'ci',
+        'chore',
+        'revert',
+        'wip',
+        'workflow',
+        'types',
+        'release',
+      ],
+    ],
+  },
+  prompt: {
+    /** @use `pnpm commit :f` */
+    alias: {
+      f: 'docs: fix typos',
+      r: 'docs: update README',
+      s: 'style: update code format',
+      b: 'build: bump dependencies',
+      c: 'chore: update config',
+    },
+    customScopesAlign: !scopeComplete ? 'top' : 'bottom',
+    defaultScope: scopeComplete,
+    scopes: [...scopes, 'mock'],
+    allowEmptyIssuePrefixs: false,
+    allowCustomIssuePrefixs: false,
+
+    // English
+    typesAppend: [
+      { value: 'wip', name: 'wip:      work in process' },
+      { value: 'workflow', name: 'workflow: workflow improvements' },
+      { value: 'types', name: 'types:    type definition file changes' },
+    ],
+
+    // 中英文对照版
+    // 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:     新增功能' },
+    //   { value: 'fix', name: 'fix:      修复缺陷' },
+    //   { value: 'docs', name: 'docs:     文档变更' },
+    //   { value: 'style', name: 'style:    代码格式' },
+    //   { value: 'refactor', name: 'refactor: 代码重构' },
+    //   { value: 'perf', name: 'perf:     性能优化' },
+    //   { value: 'test', name: 'test:     添加疏漏测试或已有测试改动' },
+    //   { value: 'build', name: 'build:    构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' },
+    //   { value: 'ci', name: 'ci:       修改 CI 配置、脚本' },
+    //   { value: 'revert', name: 'revert:   回滚 commit' },
+    //   { value: 'chore', name: 'chore:    对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' },
+    //   { value: 'wip', name: 'wip:      正在开发中' },
+    //   { value: 'workflow', name: 'workflow: 工作流程改进' },
+    //   { value: 'types', name: 'types:    类型定义文件修改' },
+    // ],
+    // emptyScopesAlias: 'empty:      不填写',
+    // customScopesAlias: 'custom:     自定义',
+  },
+}

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+root = true
+
+[*] # 表示所有文件适用
+charset = utf-8 # 设置文件字符集为 utf-8
+indent_style = space # 缩进风格(tab | space)
+indent_size = 2 # 缩进大小
+end_of_line = lf # 控制换行类型(lf | cr | crlf)
+trim_trailing_whitespace = true # 去除行首的任意空白字符
+insert_final_newline = true # 始终在文件末尾插入一个新行
+
+[*.md] # 表示仅 md 文件适用以下规则
+max_line_length = off # 关闭最大行长度限制
+trim_trailing_whitespace = false # 关闭末尾空格修剪

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+src/uni_modules/

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

@@ -0,0 +1,101 @@
+{
+  "globals": {
+    "Component": true,
+    "ComponentPublicInstance": true,
+    "ComputedRef": true,
+    "EffectScope": true,
+    "ExtractDefaultPropTypes": true,
+    "ExtractPropTypes": true,
+    "ExtractPublicPropTypes": true,
+    "InjectionKey": true,
+    "PropType": true,
+    "Ref": true,
+    "VNode": true,
+    "WritableComputedRef": true,
+    "computed": true,
+    "createApp": true,
+    "customRef": true,
+    "defineAsyncComponent": true,
+    "defineComponent": true,
+    "effectScope": true,
+    "getCurrentInstance": true,
+    "getCurrentScope": true,
+    "h": true,
+    "inject": true,
+    "isProxy": true,
+    "isReactive": true,
+    "isReadonly": true,
+    "isRef": true,
+    "markRaw": true,
+    "nextTick": true,
+    "onActivated": true,
+    "onAddToFavorites": true,
+    "onBackPress": true,
+    "onBeforeMount": true,
+    "onBeforeUnmount": true,
+    "onBeforeUpdate": true,
+    "onDeactivated": true,
+    "onError": true,
+    "onErrorCaptured": true,
+    "onHide": true,
+    "onLaunch": true,
+    "onLoad": true,
+    "onMounted": true,
+    "onNavigationBarButtonTap": true,
+    "onNavigationBarSearchInputChanged": true,
+    "onNavigationBarSearchInputClicked": true,
+    "onNavigationBarSearchInputConfirmed": true,
+    "onNavigationBarSearchInputFocusChanged": true,
+    "onPageNotFound": true,
+    "onPageScroll": true,
+    "onPullDownRefresh": true,
+    "onReachBottom": true,
+    "onReady": true,
+    "onRenderTracked": true,
+    "onRenderTriggered": true,
+    "onResize": true,
+    "onScopeDispose": true,
+    "onServerPrefetch": true,
+    "onShareAppMessage": true,
+    "onShareTimeline": true,
+    "onShow": true,
+    "onTabItemTap": true,
+    "onThemeChange": true,
+    "onUnhandledRejection": true,
+    "onUnload": true,
+    "onUnmounted": true,
+    "onUpdated": true,
+    "provide": true,
+    "reactive": true,
+    "readonly": true,
+    "ref": true,
+    "resolveComponent": true,
+    "shallowReactive": true,
+    "shallowReadonly": true,
+    "shallowRef": true,
+    "toRaw": true,
+    "toRef": true,
+    "toRefs": true,
+    "toValue": true,
+    "triggerRef": true,
+    "unref": true,
+    "useAttrs": true,
+    "useCssModule": true,
+    "useCssVars": true,
+    "useRequest": true,
+    "useSlots": true,
+    "useUpload": true,
+    "useUpload2": true,
+    "watch": true,
+    "watchEffect": true,
+    "watchPostEffect": true,
+    "watchSyncEffect": true,
+    "DirectiveBinding": true,
+    "MaybeRef": true,
+    "MaybeRefOrGetter": true,
+    "onWatcherCleanup": true,
+    "useId": true,
+    "useModel": true,
+    "useTemplateRef": true
+  }
+}

+ 97 - 0
.eslintrc.cjs

@@ -0,0 +1,97 @@
+module.exports = {
+  env: {
+    browser: true,
+    es2021: true,
+    node: true,
+  },
+  extends: [
+    'eslint:recommended',
+    'plugin:@typescript-eslint/recommended',
+    'plugin:vue/vue3-essential',
+    // eslint-plugin-import 插件, @see https://www.npmjs.com/package/eslint-plugin-import
+    'plugin:import/recommended',
+    // eslint-config-airbnb-base 插件 已经改用 eslint-config-standard 插件
+    'standard',
+    // 1. 接入 prettier 的规则
+    'prettier',
+    'plugin:prettier/recommended',
+    './.eslintrc-auto-import.json',
+  ],
+  overrides: [
+    {
+      env: {
+        node: true,
+      },
+      files: ['.eslintrc.{js,cjs}'],
+      parserOptions: {
+        sourceType: 'script',
+      },
+    },
+  ],
+  parserOptions: {
+    ecmaVersion: 'latest',
+    parser: '@typescript-eslint/parser',
+    sourceType: 'module',
+  },
+  plugins: [
+    '@typescript-eslint',
+    'vue',
+    // 2. 加入 prettier 的 eslint 插件
+    'prettier',
+    // eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
+    'import',
+  ],
+  rules: {
+    // 3. 注意要加上这一句,开启 prettier 自动修复的功能
+    'prettier/prettier': 'error',
+    // turn on errors for missing imports
+    'import/no-unresolved': 'off',
+    // 对后缀的检测,否则 import 一个ts文件也会报错,需要手动添加'.ts', 增加了下面的配置后就不用了
+    'import/extensions': [
+      'error',
+      'ignorePackages',
+      { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' },
+    ],
+    // 只允许1个默认导出,关闭,否则不能随意export xxx
+    'import/prefer-default-export': ['off'],
+    'no-console': ['off'],
+    // 'no-unused-vars': ['off'],
+    // '@typescript-eslint/no-unused-vars': ['off'],
+    // 解决vite.config.ts报错问题
+    'import/no-extraneous-dependencies': 'off',
+    'no-plusplus': 'off',
+    'no-shadow': 'off',
+    'vue/multi-word-component-names': 'off',
+    '@typescript-eslint/no-explicit-any': 'off',
+    'no-underscore-dangle': 'off',
+    'no-use-before-define': 'off',
+    'no-undef': 'off',
+    'no-unused-vars': 'off',
+    'no-param-reassign': 'off',
+    '@typescript-eslint/no-unused-vars': 'off',
+    // 避免 `eslint` 对于 `typescript` 函数重载的误报
+    'no-redeclare': 'off',
+    '@typescript-eslint/no-redeclare': 'error',
+  },
+  // eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
+  settings: {
+    'import/parsers': {
+      '@typescript-eslint/parser': ['.ts', '.tsx'],
+    },
+    'import/resolver': {
+      typescript: {},
+    },
+  },
+  globals: {
+    $t: true,
+    uni: true,
+    UniApp: true,
+    wx: true,
+    WechatMiniprogram: true,
+    getCurrentPages: true,
+    UniHelper: true,
+    Page: true,
+    App: true,
+    NodeJS: true,
+  },
+}

+ 42 - 0
.gitignore

@@ -0,0 +1,42 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+*.local
+
+# Editor directories and files
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+.hbuilderx
+
+.stylelintcache
+.eslintcache
+
+docs/.vitepress/dist
+docs/.vitepress/cache
+
+# lock 文件还是不要了,我主要的版本写死就好了
+# pnpm-lock.yaml
+# package-lock.json
+
+# TIPS:如果某些文件已经加入了版本管理,现在重新加入 .gitignore 是不生效的,需要执行下面的操作
+# `git rm -r --cached .` 然后提交 commit 即可。
+
+# git rm -r --cached file1 file2  ## 针对某些文件
+# git rm -r --cached dir1 dir2  ## 针对某些文件夹
+# git rm -r --cached .  ## 针对所有文件
+
+# 更新 uni-app 官方版本
+# npx @dcloudio/uvm@latest

+ 5 - 0
.husky/commit-msg

@@ -0,0 +1,5 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+# Run the commit-msg hook
+npx --no-install commitlint --edit

+ 5 - 0
.husky/pre-commit

@@ -0,0 +1,5 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+# Run the pre-commit hook
+npx --no-install -- lint-staged

+ 6 - 0
.npmrc

@@ -0,0 +1,6 @@
+# registry = https://registry.npmjs.org
+registry = https://registry.npmmirror.com
+
+strict-peer-dependencies=false
+auto-install-peers=true
+shamefully-hoist=true

+ 9 - 0
.prettierignore

@@ -0,0 +1,9 @@
+# unplugin-auto-import 生成的类型文件,每次提交都改变,所以加入这里吧,与 .gitignore 配合使用
+auto-import.d.ts
+
+# vite-plugin-uni-pages 生成的类型文件,每次切换分支都一堆不同的,所以直接 .gitignore
+uni-pages.d.ts
+
+# 插件生成的文件
+src/pages.json
+src/manifest.json

+ 19 - 0
.prettierrc.cjs

@@ -0,0 +1,19 @@
+// @see https://prettier.io/docs/en/options
+module.exports = {
+  singleQuote: true,
+  printWidth: 100,
+  tabWidth: 2,
+  useTabs: false,
+  semi: false,
+  trailingComma: 'all',
+  endOfLine: 'auto',
+  htmlWhitespaceSensitivity: 'ignore',
+  overrides: [
+    {
+      files: '*.json',
+      options: {
+        trailingComma: 'none',
+      },
+    },
+  ],
+}

+ 1 - 0
.stylelintignore

@@ -0,0 +1 @@
+src/uni_modules/

+ 58 - 0
.stylelintrc.cjs

@@ -0,0 +1,58 @@
+// .stylelintrc.cjs
+
+module.exports = {
+  root: true,
+  extends: [
+    // stylelint-config-standard 替换成了更宽松的 stylelint-config-recommended
+    'stylelint-config-recommended',
+    // stylelint-config-standard-scss 替换成了更宽松的 stylelint-config-recommended-scss
+    'stylelint-config-recommended-scss',
+    'stylelint-config-recommended-vue/scss',
+    'stylelint-config-html/vue',
+    'stylelint-config-recess-order',
+  ],
+  plugins: ['stylelint-prettier'],
+  overrides: [
+    // 扫描 .vue/html 文件中的<style>标签内的样式
+    {
+      files: ['**/*.{vue,html}'],
+      customSyntax: 'postcss-html',
+    },
+    {
+      files: ['**/*.{css,scss}'],
+      customSyntax: 'postcss-scss',
+    },
+  ],
+  // 自定义规则
+  rules: {
+    'prettier/prettier': true,
+    // 允许 global 、export 、v-deep等伪类
+    'selector-pseudo-class-no-unknown': [
+      true,
+      {
+        ignorePseudoClasses: ['global', 'export', 'v-deep', 'deep'],
+      },
+    ],
+    'unit-no-unknown': [
+      true,
+      {
+        ignoreUnits: ['rpx'],
+      },
+    ],
+    // 处理小程序page标签不认识的问题
+    'selector-type-no-unknown': [
+      true,
+      {
+        ignoreTypes: ['page'],
+      },
+    ],
+    'comment-empty-line-before': 'never', // never|always|always-multi-line|never-multi-line
+    'custom-property-empty-line-before': 'never',
+    'no-empty-source': null,
+    'comment-no-empty': null,
+    'no-duplicate-selectors': null,
+    'scss/comment-no-empty': null,
+    'selector-class-pattern': null,
+    'font-family-no-missing-generic-family-keyword': null,
+  },
+}

+ 18 - 0
.vscode/extensions.json

@@ -0,0 +1,18 @@
+{
+  "recommendations": [
+    "vue.volar",
+    "stylelint.vscode-stylelint",
+    "esbenp.prettier-vscode",
+    "dbaeumer.vscode-eslint",
+    "antfu.unocss",
+    "antfu.iconify",
+    "evils.uniapp-vscode",
+    "uni-helper.uni-helper-vscode",
+    "uni-helper.uni-app-schemas-vscode",
+    "uni-helper.uni-highlight-vscode",
+    "uni-helper.uni-ui-snippets-vscode",
+    "uni-helper.uni-app-snippets-vscode",
+    "mrmlnc.vscode-json5",
+    "streetsidesoftware.code-spell-checker"
+  ]
+}

+ 67 - 0
.vscode/settings.json

@@ -0,0 +1,67 @@
+{
+  // 默认格式化工具选择prettier
+  "editor.defaultFormatter": "esbenp.prettier-vscode",
+  // 保存的时候自动格式化
+  "editor.formatOnSave": true,
+  //开启自动修复
+  "editor.codeActionsOnSave": {
+    "source.fixAll": "explicit",
+    "source.fixAll.eslint": "explicit",
+    "source.fixAll.stylelint": "explicit"
+  },
+  // 配置stylelint检查的文件类型范围
+  "stylelint.validate": ["css", "scss", "vue", "html"], // 与package.json的scripts对应
+  "stylelint.enable": true,
+  "css.validate": false,
+  "less.validate": false,
+  "scss.validate": false,
+  "[shellscript]": {
+    "editor.defaultFormatter": "foxundermoon.shell-format"
+  },
+  "[dotenv]": {
+    "editor.defaultFormatter": "foxundermoon.shell-format"
+  },
+  "[vue]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[typescript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[jsonc]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  // 配置语言的文件关联
+  "files.associations": {
+    "pages.json": "jsonc", // pages.json 可以写注释
+    "manifest.json": "jsonc" // manifest.json 可以写注释
+  },
+  "cSpell.words": [
+    "Aplipay",
+    "climblee",
+    "commitlint",
+    "dcloudio",
+    "iconfont",
+    "qrcode",
+    "refresherrefresh",
+    "scrolltolower",
+    "tabbar",
+    "Toutiao",
+    "unibest",
+    "uvui",
+    "Wechat",
+    "WechatMiniprogram",
+    "Weixin"
+  ],
+  "typescript.tsdk": "node_modules\\typescript\\lib",
+  // 控制相关文件嵌套展示
+  "explorer.fileNesting.enabled": true,
+  "explorer.fileNesting.expand": false,
+  "explorer.fileNesting.patterns": {
+    "*.ts": "$(capture).test.ts, $(capture).test.tsx",
+    "*.tsx": "$(capture).test.ts, $(capture).test.tsx",
+    // "*.env": "$(capture).env.*",
+    "CHANGELOG.md": "CHANGELOG*",
+    "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,LICENSE,.gitattributes,.gitignore,.gitpod.yml,CNAME,.npmrc,.browserslistrc",
+    ".eslintrc.cjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,.stylelintrc.*,.eslintrc-auto-import.json,.editorconfig,.commitlint.cjs"
+  }
+}

+ 56 - 0
.vscode/vue3.code-snippets

@@ -0,0 +1,56 @@
+{
+  // Place your unibest 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
+  // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
+  // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
+  // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
+  // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
+  // Placeholders with the same ids are connected.
+  // Example:
+  // "Print to console": {
+  // 	"scope": "javascript,typescript",
+  // 	"prefix": "log",
+  // 	"body": [
+  // 		"console.log('$1');",
+  // 		"$2"
+  // 	],
+  // 	"description": "Log output to console"
+  // }
+  "Print unibest Vue3 SFC": {
+    "scope": "vue",
+    "prefix": "v3",
+    "body": [
+      "<route lang=\"json5\" type=\"page\">",
+      "{",
+      "  layout: 'default',",
+      "  style: {",
+      "    navigationBarTitleText: '$1',",
+      "  },",
+      "}",
+      "</route>\n",
+      "<template>",
+      "  <view class=\"\">$2</view>",
+      "</template>\n",
+      "<script lang=\"ts\" setup>",
+      "//$3",
+      "</script>\n",
+      "<style lang=\"scss\" scoped>",
+      "//$4",
+      "</style>\n",
+    ],
+  },
+  "Print unibest style": {
+    "scope": "vue",
+    "prefix": "st",
+    "body": ["<style lang=\"scss\" scoped>", "//", "</style>\n"],
+  },
+  "Print unibest script": {
+    "scope": "vue",
+    "prefix": "sc",
+    "body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
+  },
+  "Print unibest template": {
+    "scope": "vue",
+    "prefix": "te",
+    "body": ["<template>", "  <view class=\"\">$1</view>", "</template>\n"],
+  },
+}

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 70 - 0
README.md

@@ -0,0 +1,70 @@
+<p align="center">
+  <a href="https://github.com/feige996/unibest">
+    <img width="160" src="./src/static/logo.svg">
+  </a>
+</p>
+
+<h1 align="center">
+  <a href="https://github.com/feige996/unibest" target="_blank">unibest - 最好的 uniapp 开发框架</a>
+</h1>
+
+<div align="center">
+
+[![GitHub Repo stars](https://img.shields.io/github/stars/feige996/unibest?style=flat&logo=github)](https://github.com/feige996/unibest)
+[![GitHub forks](https://img.shields.io/github/forks/feige996/unibest?style=flat&logo=github)](https://github.com/feige996/unibest)
+[![star](https://gitee.com/feige996/unibest/badge/star.svg?theme=dark)](https://gitee.com/feige996/unibest/stargazers)
+[![fork](https://gitee.com/feige996/unibest/badge/fork.svg?theme=dark)](https://gitee.com/feige996/unibest/members)
+![node version](https://img.shields.io/badge/node-%3E%3D18-green)
+![pnpm version](https://img.shields.io/badge/pnpm-%3E%3D7.30-green)
+![GitHub package.json version (subfolder of monorepo)](https://img.shields.io/github/package-json/v/feige996/unibest)
+![GitHub License](https://img.shields.io/github/license/feige996/unibest
+
+</div>
+
+`unibest` —— 最好的 `uniapp` 开发模板,由 `uniapp` + `Vue3` + `Ts` + `Vite5` + `UnoCss` + `wot-ui` + `z-paging` 构成,使用了最新的前端技术栈,无需依靠 `HBuilderX`,通过命令行方式运行 `web`、`小程序` 和 `App`(编辑器推荐 `VSCode`,可选 `webstorm`)。
+
+`unibest` 内置了 `约定式路由`、`layout布局`、`请求封装`、`请求拦截`、`登录拦截`、`UnoCSS`、`i18n多语言` 等基础功能,提供了 `代码提示`、`自动格式化`、`统一配置`、`代码片段` 等辅助功能,让你编写 `uniapp` 拥有 `best` 体验 ( `unibest 的由来`)。
+
+
+<p align="center">
+  <a href="https://unibest.tech/" target="_blank">📖 文档地址(new)</a>
+  <span style="margin:0 10px;">|</span>
+  <a href="https://feige996.github.io/hello-unibest/" target="_blank">📱 DEMO 地址</a>
+</p>
+
+---
+
+注意旧的地址 [codercup](https://github.com/codercup/unibest) 我进不去了,使用新的 [feige996](https://github.com/feige996/unibest)。PR和 issue 也请使用新地址,否则无法合并。
+
+## ⚙️ 环境
+
+- node>=18
+- pnpm>=7.30
+- Vue Official>=2.1.10
+- TypeScript>=5.0
+
+## &#x1F4C2; 快速开始
+
+执行 `pnpm create unibest` 创建项目
+
+执行 `pnpm i` 安装依赖
+
+执行 `pnpm dev` 运行 `H5`
+
+## 📦 运行(支持热更新)
+
+- web平台: `pnpm dev:h5`, 然后打开 [http://localhost:9000/](http://localhost:9000/)。
+- weixin平台:`pnpm dev:mp-weixin` 然后打开微信开发者工具,导入本地文件夹,选择本项目的`dist/dev/mp-weixin` 文件。
+- APP平台:`pnpm dev:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/dev/app` 文件夹,选择运行到模拟器(开发时优先使用),或者运行的安卓/ios基座。
+
+## 🔗 发布
+
+- web平台: `pnpm build:h5`,打包后的文件在 `dist/build/h5`,可以放到web服务器,如nginx运行。如果最终不是放在根目录,可以在 `manifest.config.ts` 文件的 `h5.router.base` 属性进行修改。
+- weixin平台:`pnpm build:mp-weixin`, 打包后的文件在 `dist/build/mp-weixin`,然后通过微信开发者工具导入,并点击右上角的“上传”按钮进行上传。
+- APP平台:`pnpm build:app`, 然后打开 `HBuilderX`,导入刚刚生成的`dist/build/app` 文件夹,选择发行 - APP云打包。
+
+## 📄 License
+
+[MIT](https://opensource.org/license/mit/)
+
+Copyright (c) 2024 菲鸽

+ 25 - 0
env/.env

@@ -0,0 +1,25 @@
+VITE_APP_TITLE = 'printer-miniapp'
+VITE_APP_PORT = 9000
+
+VITE_UNI_APPID = '__UNI__DD53B81'
+VITE_WX_APPID = 'wx46467fc3f5573a68'
+
+# h5部署网站的base,配置到 manifest.config.ts 里的 h5.router.base
+VITE_APP_PUBLIC_BASE = /printer-h5/
+
+VITE_SERVER_BASEURL = 'http://tnas-f2b3:8505'
+VITE_UPLOAD_BASEURL = 'http://tnas-f2b3:8505/upload'
+
+# 有些同学可能需要在微信小程序里面根据 develop、trial、release 分别设置上传地址,参考代码如下。
+# 下面的变量如果没有设置,会默认使用 VITE_SERVER_BASEURL or VITE_UPLOAD_BASEURL
+# VITE_SERVER_BASEURL__WEIXIN_DEVELOP = 'http://tnas-f2b3:8505'
+# VITE_SERVER_BASEURL__WEIXIN_TRIAL = 'http://tnas-f2b3:8505'
+# VITE_SERVER_BASEURL__WEIXIN_RELEASE = 'http://tnas-f2b3:8505'
+
+# VITE_UPLOAD_BASEURL__WEIXIN_DEVELOP = 'http://tnas-f2b3:8505/upload'
+# VITE_UPLOAD_BASEURL__WEIXIN_TRIAL = 'http://tnas-f2b3:8505/upload'
+# VITE_UPLOAD_BASEURL__WEIXIN_RELEASE = 'http://tnas-f2b3:8505/upload'
+
+# h5是否需要配置代理
+VITE_APP_PROXY = false
+VITE_APP_PROXY_PREFIX = '/api'

+ 6 - 0
env/.env.development

@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = false
+# 是否开启sourcemap
+VITE_SHOW_SOURCEMAP = true

+ 6 - 0
env/.env.production

@@ -0,0 +1,6 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = true
+# 是否开启sourcemap
+VITE_SHOW_SOURCEMAP = false

+ 4 - 0
env/.env.test

@@ -0,0 +1,4 @@
+# 变量必须以 VITE_ 为前缀才能暴露给外部读取
+NODE_ENV = 'development'
+# 是否去除console 和 debugger
+VITE_DELETE_CONSOLE = false

BIN
favicon.ico


+ 26 - 0
index.html

@@ -0,0 +1,26 @@
+<!doctype html>
+<html build-time="%BUILD_TIME%">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
+    <script>
+      var coverSupport =
+        'CSS' in window &&
+        typeof CSS.supports === 'function' &&
+        (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+          (coverSupport ? ', viewport-fit=cover' : '') +
+          '" />',
+      )
+    </script>
+    <title>猫耳云打印</title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 134 - 0
manifest.config.ts

@@ -0,0 +1,134 @@
+// manifest.config.ts
+import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
+import path from 'node:path'
+import { loadEnv } from 'vite'
+
+// 获取环境变量的范例
+const env = loadEnv(process.env.NODE_ENV!, path.resolve(process.cwd(), 'env'))
+const {
+  VITE_APP_TITLE,
+  VITE_UNI_APPID,
+  VITE_WX_APPID,
+  VITE_APP_PUBLIC_BASE,
+  VITE_FALLBACK_LOCALE,
+} = env
+
+export default defineManifestConfig({
+  name: VITE_APP_TITLE,
+  appid: VITE_UNI_APPID,
+  description: '',
+  versionName: '1.0.0',
+  versionCode: '100',
+  transformPx: false,
+  locale: VITE_FALLBACK_LOCALE, // 'zh-Hans'
+  h5: {
+    router: {
+      base: VITE_APP_PUBLIC_BASE,
+    },
+  },
+  /* 5+App特有相关 */
+  'app-plus': {
+    usingComponents: true,
+    nvueStyleCompiler: 'uni-app',
+    compilerVersion: 3,
+    compatible: {
+      ignoreVersion: true,
+    },
+    splashscreen: {
+      alwaysShowBeforeRender: true,
+      waiting: true,
+      autoclose: true,
+      delay: 0,
+    },
+    /* 模块配置 */
+    modules: {},
+    /* 应用发布信息 */
+    distribute: {
+      /* android打包配置 */
+      android: {
+        minSdkVersion: 30,
+        targetSdkVersion: 30,
+        abiFilters: ['armeabi-v7a', 'arm64-v8a'],
+        permissions: [
+          '<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
+          '<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
+          '<uses-permission android:name="android.permission.VIBRATE"/>',
+          '<uses-permission android:name="android.permission.READ_LOGS"/>',
+          '<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
+          '<uses-feature android:name="android.hardware.camera.autofocus"/>',
+          '<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
+          '<uses-permission android:name="android.permission.CAMERA"/>',
+          '<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
+          '<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
+          '<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
+          '<uses-permission android:name="android.permission.WAKE_LOCK"/>',
+          '<uses-permission android:name="android.permission.FLASHLIGHT"/>',
+          '<uses-feature android:name="android.hardware.camera"/>',
+          '<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
+        ],
+      },
+      /* ios打包配置 */
+      ios: {},
+      /* SDK配置 */
+      sdkConfigs: {},
+      /* 图标配置 */
+      icons: {
+        android: {
+          hdpi: 'static/images/logo.png',
+          xhdpi: 'static/images/logo.png',
+          xxhdpi: 'static/images/logo.png',
+          xxxhdpi: 'static/images/logo.png',
+        },
+        ios: {
+          appstore: 'static/images/logo.png',
+          ipad: {
+            app: 'static/images/logo.png',
+            'app@2x': 'static/images/logo.png',
+            notification: 'static/images/logo.png',
+            'notification@2x': 'static/images/logo.png',
+            'proapp@2x': 'static/images/logo.png',
+            settings: 'static/images/logo.png',
+            'settings@2x': 'static/images/logo.png',
+            spotlight: 'static/images/logo.png',
+            'spotlight@2x': 'static/images/logo.png',
+          },
+          iphone: {
+            'app@2x': 'static/images/logo.png',
+            'app@3x': 'static/images/logo.png',
+            'notification@2x': 'static/images/logo.png',
+            'notification@3x': 'static/images/logo.png',
+            'settings@2x': 'static/images/logo.png',
+            'settings@3x': 'static/images/logo.png',
+            'spotlight@2x': 'static/images/logo.png',
+            'spotlight@3x': 'static/images/logo.png',
+          },
+        },
+      },
+    },
+  },
+  /* 快应用特有相关 */
+  quickapp: {},
+  /* 小程序特有相关 */
+  'mp-weixin': {
+    appid: VITE_WX_APPID,
+    setting: {
+      urlCheck: false,
+    },
+    usingComponents: true,
+    // __usePrivacyCheck__: true,
+  },
+  'mp-alipay': {
+    usingComponents: true,
+    styleIsolation: 'shared',
+  },
+  'mp-baidu': {
+    usingComponents: true,
+  },
+  'mp-toutiao': {
+    usingComponents: true,
+  },
+  uniStatistics: {
+    enable: false,
+  },
+  vueVersion: '3',
+})

+ 169 - 0
package.json

@@ -0,0 +1,169 @@
+{
+  "name": "猫耳云打印",
+  "type": "commonjs",
+  "version": "0.5.0",
+  "description": "猫耳云打印",
+  "author": {
+    "name": "feige996",
+    "zhName": "菲鸽",
+    "email": "1020103647@qq.com",
+    "github": "https://github.com/feige996",
+    "gitee": "https://gitee.com/feige996"
+  },
+  "license": "MIT",
+  "repository": "https://github.com/feige996/unibest",
+  "repository-gitee": "https://gitee.com/feige996/unibest",
+  "repository-deprecated": "https://github.com/codercup/unibest",
+  "bugs": {
+    "url": "https://github.com/feige996/unibest/issues"
+  },
+  "homepage": "https://feige996.github.io/unibest/",
+  "engines": {
+    "node": ">=18",
+    "pnpm": ">=7.30"
+  },
+  "scripts": {
+    "preinstall": "npx only-allow pnpm",
+    "uvm": "npx @dcloudio/uvm@latest",
+    "uvm-rm": "node ./scripts/postupgrade.js",
+    "postuvm": "echo upgrade uni-app success!",
+    "dev:app": "uni -p app",
+    "dev:app-android": "uni -p app-android",
+    "dev:app-ios": "uni -p app-ios",
+    "dev:custom": "uni -p",
+    "dev": "uni",
+    "dev:h5": "uni",
+    "dev:h5:ssr": "uni --ssr",
+    "dev:mp": "uni -p mp-weixin",
+    "dev:mp-alipay": "uni -p mp-alipay",
+    "dev:mp-baidu": "uni -p mp-baidu",
+    "dev:mp-jd": "uni -p mp-jd",
+    "dev:mp-kuaishou": "uni -p mp-kuaishou",
+    "dev:mp-lark": "uni -p mp-lark",
+    "dev:mp-qq": "uni -p mp-qq",
+    "dev:mp-toutiao": "uni -p mp-toutiao",
+    "dev:mp-weixin": "uni -p mp-weixin",
+    "dev:mp-xhs": "uni -p mp-xhs",
+    "dev:quickapp-webview": "uni -p quickapp-webview",
+    "dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
+    "dev:quickapp-webview-union": "uni -p quickapp-webview-union",
+    "build:app": "uni build -p app",
+    "build:app-android": "uni build -p app-android",
+    "build:app-ios": "uni build -p app-ios",
+    "build:custom": "uni build -p",
+    "build:h5": "uni build",
+    "build": "uni build",
+    "build:h5:ssr": "uni build --ssr",
+    "build:mp-alipay": "uni build -p mp-alipay",
+    "build:mp": "uni build -p mp-weixin",
+    "build:mp-baidu": "uni build -p mp-baidu",
+    "build:mp-jd": "uni build -p mp-jd",
+    "build:mp-kuaishou": "uni build -p mp-kuaishou",
+    "build:mp-lark": "uni build -p mp-lark",
+    "build:mp-qq": "uni build -p mp-qq",
+    "build:mp-toutiao": "uni build -p mp-toutiao",
+    "build:mp-weixin": "uni build -p mp-weixin",
+    "build:mp-xhs": "uni build -p mp-xhs",
+    "build:quickapp-webview": "uni build -p quickapp-webview",
+    "build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
+    "build:quickapp-webview-union": "uni build -p quickapp-webview-union",
+    "prepare": "git init && husky install ",
+    "type-check": "vue-tsc --noEmit",
+    "cz": "czg"
+  },
+  "lint-staged": {
+    "**/*.{html,vue,ts,cjs,json,md}": [
+      "prettier --write"
+    ],
+    "**/*.{vue,js,ts,jsx,tsx}": [
+      "eslint --cache --fix"
+    ],
+    "**/*.{vue,css,scss,html}": [
+      "stylelint --fix"
+    ]
+  },
+  "resolutions": {
+    "bin-wrapper": "npm:bin-wrapper-china"
+  },
+  "dependencies": {
+    "@dcloudio/uni-app": "3.0.0-4020920240930001",
+    "@dcloudio/uni-app-harmony": "3.0.0-4020920240930001",
+    "@dcloudio/uni-app-plus": "3.0.0-4020920240930001",
+    "@dcloudio/uni-components": "3.0.0-4020920240930001",
+    "@dcloudio/uni-h5": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-alipay": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-baidu": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-jd": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-kuaishou": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-lark": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-qq": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-toutiao": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-weixin": "3.0.0-4020920240930001",
+    "@dcloudio/uni-mp-xhs": "3.0.0-4020920240930001",
+    "@dcloudio/uni-quickapp-webview": "3.0.0-4020920240930001",
+    "dayjs": "1.11.10",
+    "pinia": "2.0.36",
+    "pinia-plugin-persistedstate": "3.2.1",
+    "qs": "6.5.3",
+    "vue": "3.4.21",
+    "wot-design-uni": "^1.4.0",
+    "z-paging": "^2.8.4"
+  },
+  "devDependencies": {
+    "@commitlint/cli": "^18.6.1",
+    "@commitlint/config-conventional": "^18.6.3",
+    "@dcloudio/types": "^3.4.14",
+    "@dcloudio/uni-automator": "3.0.0-4020920240930001",
+    "@dcloudio/uni-cli-shared": "3.0.0-4020920240930001",
+    "@dcloudio/uni-stacktracey": "3.0.0-4020920240930001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-4020920240930001",
+    "@esbuild/darwin-arm64": "0.20.2",
+    "@esbuild/darwin-x64": "0.20.2",
+    "@iconify-json/carbon": "^1.2.4",
+    "@rollup/rollup-darwin-x64": "^4.28.0",
+    "@types/node": "^20.17.9",
+    "@types/wechat-miniprogram": "^3.4.8",
+    "@typescript-eslint/eslint-plugin": "^6.21.0",
+    "@typescript-eslint/parser": "^6.21.0",
+    "@uni-helper/uni-types": "1.0.0-alpha.3",
+    "@uni-helper/vite-plugin-uni-layouts": "^0.1.10",
+    "@uni-helper/vite-plugin-uni-manifest": "^0.2.7",
+    "@uni-helper/vite-plugin-uni-pages": "0.2.20",
+    "@uni-helper/vite-plugin-uni-platform": "^0.0.4",
+    "@unocss/preset-legacy-compat": "^0.59.4",
+    "@vue/runtime-core": "^3.5.13",
+    "@vue/tsconfig": "^0.1.3",
+    "autoprefixer": "^10.4.20",
+    "commitlint": "^18.6.1",
+    "czg": "^1.9.4",
+    "eslint": "^8.57.1",
+    "eslint-config-prettier": "^9.1.0",
+    "eslint-config-standard": "^17.1.0",
+    "eslint-import-resolver-typescript": "^3.7.0",
+    "eslint-plugin-import": "^2.31.0",
+    "eslint-plugin-prettier": "^5.2.1",
+    "eslint-plugin-vue": "^9.32.0",
+    "husky": "^8.0.3",
+    "lint-staged": "^15.2.10",
+    "postcss": "^8.4.49",
+    "postcss-html": "^1.7.0",
+    "postcss-scss": "^4.0.9",
+    "rollup-plugin-visualizer": "^5.12.0",
+    "sass": "1.77.8",
+    "stylelint": "^16.11.0",
+    "stylelint-config-html": "^1.1.0",
+    "stylelint-config-recess-order": "^4.6.0",
+    "stylelint-config-recommended": "^14.0.1",
+    "stylelint-config-recommended-scss": "^14.1.0",
+    "stylelint-config-recommended-vue": "^1.5.0",
+    "stylelint-prettier": "^5.0.2",
+    "terser": "^5.36.0",
+    "typescript": "^5.7.2",
+    "unocss": "^0.58.9",
+    "unocss-applet": "^0.7.8",
+    "unplugin-auto-import": "^0.17.8",
+    "vite": "5.2.8",
+    "vite-plugin-restart": "^0.4.2",
+    "vue-tsc": "^1.8.27"
+  }
+}

+ 43 - 0
pages.config.ts

@@ -0,0 +1,43 @@
+import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
+
+export default defineUniPages({
+  globalStyle: {
+    navigationStyle: 'default',
+    navigationBarTitleText: '猫耳云打印',
+    navigationBarBackgroundColor: '#f8f8f8',
+    navigationBarTextStyle: 'black',
+    backgroundColor: '#FFFFFF',
+  },
+  easycom: {
+    autoscan: true,
+    custom: {
+      '^wd-(.*)': 'wot-design-uni/components/wd-$1/wd-$1.vue',
+      '^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)':
+        'z-paging/components/z-paging$1/z-paging$1.vue',
+    },
+  },
+  tabBar: {
+    color: '#999999',
+    selectedColor: '#FF8D1A',
+    backgroundColor: '#F8F8F8',
+    borderStyle: 'black',
+    height: '50px',
+    fontSize: '10px',
+    iconWidth: '24px',
+    spacing: '3px',
+    list: [
+      {
+        iconPath: 'static/tabbar/home.png',
+        selectedIconPath: 'static/tabbar/homeHL.png',
+        pagePath: 'pages/index/index',
+        text: '首页',
+      },
+      {
+        iconPath: 'static/tabbar/personal.png',
+        selectedIconPath: 'static/tabbar/personalHL.png',
+        pagePath: 'pages/personal/index',
+        text: '我的',
+      },
+    ],
+  },
+})

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 12339 - 0
pnpm-lock.yaml


+ 36 - 0
scripts/postupgrade.js

@@ -0,0 +1,36 @@
+// # 执行 `pnpm upgrade` 后会升级 `uniapp` 相关依赖
+// # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积
+// # 只需要执行下面的命令即可
+
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const { exec } = require('child_process')
+
+// 定义要执行的命令
+const dependencies = [
+  '@dcloudio/uni-app-harmony',
+  // TODO: 如果需要某个平台的小程序,请手动删除或注释掉
+  '@dcloudio/uni-mp-alipay',
+  '@dcloudio/uni-mp-baidu',
+  '@dcloudio/uni-mp-jd',
+  '@dcloudio/uni-mp-kuaishou',
+  '@dcloudio/uni-mp-lark',
+  '@dcloudio/uni-mp-qq',
+  '@dcloudio/uni-mp-toutiao',
+  '@dcloudio/uni-mp-xhs',
+  '@dcloudio/uni-quickapp-webview',
+  // i18n模板要注释掉下面的
+  'vue-i18n',
+]
+
+// 使用exec执行命令
+exec(`pnpm un ${dependencies.join(' ')}`, (error, stdout, stderr) => {
+  if (error) {
+    // 如果有错误,打印错误信息
+    console.error(`执行出错: ${error}`)
+    return
+  }
+  // 打印正常输出
+  console.log(`stdout: ${stdout}`)
+  // 如果有错误输出,也打印出来
+  console.error(`stderr: ${stderr}`)
+})

+ 58 - 0
src/App.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
+
+onLaunch(() => {
+  console.log('App Launch')
+})
+onShow(() => {
+  console.log('App Show')
+})
+onHide(() => {
+  console.log('App Hide')
+})
+</script>
+
+<style lang="scss">
+/* stylelint-disable selector-type-no-unknown */
+button::after {
+  border: none;
+}
+
+swiper,
+scroll-view {
+  flex: 1;
+  height: 100%;
+  overflow: hidden;
+}
+
+image {
+  width: 100%;
+  height: 100%;
+  vertical-align: middle;
+}
+
+// 单行省略,优先使用 unocss: text-ellipsis
+.ellipsis {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+// 两行省略
+.ellipsis-2 {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+// 三行省略
+.ellipsis-3 {
+  display: -webkit-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 3;
+  -webkit-box-orient: vertical;
+}
+</style>

+ 0 - 0
src/components/.gitkeep


+ 32 - 0
src/directives/permission.ts

@@ -0,0 +1,32 @@
+import { useUserStore } from '@/store/user'
+import { App } from 'vue'
+
+export const hasPerm = (permission: string[]) => {
+  const { roles } = useUserStore().userInfo
+  // const perms = useUserStore().userButtons
+  const perms = []
+  if (!permission) {
+    throw new Error('指令必须有参数')
+  }
+  // 「超级管理员」拥有所有的按钮权限
+  const isExit = roles ? roles.find((v: any) => v.code.includes('admin')) : false
+  if (isExit) {
+    return true
+  }
+  // 「其他角色」按钮权限校验
+  const hasPerm = perms?.some((item: any) => {
+    return permission.includes(item.perm)
+  })
+  // 没有权限的情况
+  if (!hasPerm) {
+    return false
+  } else {
+    return true
+  }
+}
+
+export const hasPermPlugin = {
+  install(app: App) {
+    app.config.globalProperties.$hasPerm = hasPerm
+  },
+}

+ 31 - 0
src/env.d.ts

@@ -0,0 +1,31 @@
+/// <reference types="vite/client" />
+/// <reference types="vite-svg-loader" />
+
+declare module '*.vue' {
+  import { DefineComponent } from 'vue'
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}
+
+interface ImportMetaEnv {
+  /** 网站标题,应用名称 */
+  readonly VITE_APP_TITLE: string
+  /** 服务端口号 */
+  readonly VITE_SERVER_PORT: string
+  /** 后台接口地址 */
+  readonly VITE_SERVER_BASEURL: string
+  /** H5是否需要代理 */
+  readonly VITE_APP_PROXY: 'true' | 'false'
+  /** H5是否需要代理,需要的话有个前缀 */
+  readonly VITE_APP_PROXY_PREFIX: string // 一般是/api
+  /** 上传图片地址 */
+  readonly VITE_UPLOAD_BASEURL: string
+  /** 是否清除console */
+  readonly VITE_DELETE_CONSOLE: string
+  // 更多环境变量...
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv
+}

+ 0 - 0
src/hooks/.gitkeep


+ 44 - 0
src/hooks/useRequest.ts

@@ -0,0 +1,44 @@
+import { UnwrapRef } from 'vue'
+
+type IUseRequestOptions<T> = {
+  /** 是否立即执行 */
+  immediate?: boolean
+  /** 初始化数据 */
+  initialData?: T
+}
+
+/**
+ * useRequest是一个定制化的请求钩子,用于处理异步请求和响应。
+ * @param func 一个执行异步请求的函数,返回一个包含响应数据的Promise。
+ * @param options 包含请求选项的对象 {immediate, initialData}。
+ * @param options.immediate 是否立即执行请求,默认为false。
+ * @param options.initialData 初始化数据,默认为undefined。
+ * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
+ */
+export default function useRequest<T>(
+  func: () => Promise<IResData<T>>,
+  options: IUseRequestOptions<T> = { immediate: false },
+) {
+  const loading = ref(false)
+  const error = ref(false)
+  const data = ref<T>(options.initialData)
+  const run = async () => {
+    loading.value = true
+    return func()
+      .then((res) => {
+        data.value = res.data as UnwrapRef<T>
+        error.value = false
+        return data.value
+      })
+      .catch((err) => {
+        error.value = err
+        throw err
+      })
+      .finally(() => {
+        loading.value = false
+      })
+  }
+
+  options.immediate && run()
+  return { loading, error, data, run }
+}

+ 69 - 0
src/hooks/useUpload.ts

@@ -0,0 +1,69 @@
+// TODO: 别忘加更改环境变量的 VITE_UPLOAD_BASEURL 地址。
+import { getEnvBaseUploadUrl } from '@/utils'
+
+const VITE_UPLOAD_BASEURL = `${getEnvBaseUploadUrl()}`
+
+/**
+ * useUpload 是一个定制化的请求钩子,用于处理上传图片。
+ * @param formData 额外传递给后台的数据,如{name: '菲鸽'}。
+ * @returns 返回一个对象{loading, error, data, run},包含请求的加载状态、错误信息、响应数据和手动触发请求的函数。
+ */
+export default function useUpload<T = string>(formData: Record<string, any> = {}) {
+  const loading = ref(false)
+  const error = ref(false)
+  const data = ref<T>()
+  const run = () => {
+    // #ifdef MP-WEIXIN
+    // 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。
+    // 微信小程序在2023年10月17日之后,使用本API需要配置隐私协议
+    uni.chooseMedia({
+      count: 1,
+      mediaType: ['image'],
+      success: (res) => {
+        loading.value = true
+        const tempFilePath = res.tempFiles[0].tempFilePath
+        uploadFile<T>({ tempFilePath, formData, data, error, loading })
+      },
+      fail: (err) => {
+        console.error('uni.chooseMedia err->', err)
+        error.value = true
+      },
+    })
+    // #endif
+    // #ifndef MP-WEIXIN
+    uni.chooseImage({
+      count: 1,
+      success: (res) => {
+        loading.value = true
+        const tempFilePath = res.tempFilePaths[0]
+        uploadFile<T>({ tempFilePath, formData, data, error, loading })
+      },
+      fail: (err) => {
+        console.error('uni.chooseImage err->', err)
+        error.value = true
+      },
+    })
+    // #endif
+  }
+
+  return { loading, error, data, run }
+}
+
+function uploadFile<T>({ tempFilePath, formData, data, error, loading }) {
+  uni.uploadFile({
+    url: VITE_UPLOAD_BASEURL,
+    filePath: tempFilePath,
+    name: 'file',
+    formData,
+    success: (uploadFileRes) => {
+      data.value = uploadFileRes.data as T
+    },
+    fail: (err) => {
+      console.error('uni.uploadFile err->', err)
+      error.value = true
+    },
+    complete: () => {
+      loading.value = false
+    },
+  })
+}

+ 3 - 0
src/interceptors/index.ts

@@ -0,0 +1,3 @@
+export { routeInterceptor } from './route'
+export { requestInterceptor } from './request'
+export { prototypeInterceptor } from './prototype'

+ 13 - 0
src/interceptors/prototype.ts

@@ -0,0 +1,13 @@
+export const prototypeInterceptor = {
+  install() {
+    // 解决低版本手机不识别 array.at() 导致运行报错的问题
+    if (typeof Array.prototype.at !== 'function') {
+      // eslint-disable-next-line no-extend-native
+      Array.prototype.at = function (index: number) {
+        if (index < 0) return this[this.length + index]
+        if (index >= this.length) return undefined
+        return this[index]
+      }
+    }
+  },
+}

+ 68 - 0
src/interceptors/request.ts

@@ -0,0 +1,68 @@
+/* eslint-disable no-param-reassign */
+import qs from 'qs'
+import { useUserStore } from '@/store'
+import { platform } from '@/utils/platform'
+import { getEnvBaseUrl } from '@/utils'
+
+export type CustomRequestOptions = UniApp.RequestOptions & {
+  query?: Record<string, any>
+  /** 出错时是否隐藏错误提示 */
+  hideErrorToast?: boolean
+} & IUniUploadFileOptions // 添加uni.uploadFile参数类型
+
+// 请求基准地址
+const baseUrl = getEnvBaseUrl()
+
+// 拦截器配置
+const httpInterceptor = {
+  // 拦截前触发
+  invoke(options: CustomRequestOptions) {
+    // 接口请求支持通过 query 参数配置 queryString
+    if (options.query) {
+      const queryStr = qs.stringify(options.query)
+      if (options.url.includes('?')) {
+        options.url += `&${queryStr}`
+      } else {
+        options.url += `?${queryStr}`
+      }
+    }
+    // 非 http 开头需拼接地址
+    if (!options.url.startsWith('http')) {
+      // #ifdef H5
+      // console.log(__VITE_APP_PROXY__)
+      if (JSON.parse(__VITE_APP_PROXY__)) {
+        // 啥都不需要做
+      } else {
+        options.url = baseUrl + options.url
+      }
+      // #endif
+      // 非H5正常拼接
+      // #ifndef H5
+      options.url = baseUrl + options.url
+      // #endif
+      // TIPS: 如果需要对接多个后端服务,也可以在这里处理,拼接成所需要的地址
+    }
+    // 1. 请求超时
+    options.timeout = 10000 // 10s
+    // 2. (可选)添加小程序端请求头标识
+    options.header = {
+      platform, // 可选,与 uniapp 定义的平台一致,告诉后台来源
+      ...options.header,
+    }
+    // 3. 添加 token 请求头标识
+    const userStore = useUserStore()
+    const token = userStore.token
+    if (token) {
+      options.header['X-ACCESS-TOKEN'] = token
+    }
+  },
+}
+
+export const requestInterceptor = {
+  install() {
+    // 拦截 request 请求
+    uni.addInterceptor('request', httpInterceptor)
+    // 拦截 uploadFile 文件上传
+    uni.addInterceptor('uploadFile', httpInterceptor)
+  },
+}

+ 54 - 0
src/interceptors/route.ts

@@ -0,0 +1,54 @@
+/**
+ * by 菲鸽 on 2024-03-06
+ * 路由拦截,通常也是登录拦截
+ * 可以设置路由白名单,或者黑名单,看业务需要选哪一个
+ * 我这里应为大部分都可以随便进入,所以使用黑名单
+ */
+import { useUserStore } from '@/store'
+import { needLoginPages as _needLoginPages, getNeedLoginPages } from '@/utils'
+
+// TODO Check
+const loginRoute = '/pages/login/index'
+
+const isLogined = () => {
+  const userStore = useUserStore()
+  return userStore.isLogined
+}
+
+const isDev = import.meta.env.DEV
+
+// 黑名单登录拦截器 - (适用于大部分页面不需要登录,少部分页面需要登录)
+const navigateToInterceptor = {
+  // 注意,这里的url是 '/' 开头的,如 '/pages/index/index',跟 'pages.json' 里面的 path 不同
+  invoke({ url }: { url: string }) {
+    // console.log(url) // /pages/route-interceptor/index?name=feige&age=30
+    const path = url.split('?')[0]
+    let needLoginPages: string[] = []
+    // 为了防止开发时出现BUG,这里每次都获取一下。生产环境可以移到函数外,性能更好
+    if (isDev) {
+      needLoginPages = getNeedLoginPages()
+    } else {
+      needLoginPages = _needLoginPages
+    }
+    const isNeedLogin = needLoginPages.includes(path)
+    if (!isNeedLogin) {
+      return true
+    }
+    const hasLogin = isLogined()
+    if (hasLogin) {
+      return true
+    }
+    const redirectRoute = `${loginRoute}?redirect=${encodeURIComponent(url)}`
+    uni.navigateTo({ url: redirectRoute })
+    return false
+  },
+}
+
+export const routeInterceptor = {
+  install() {
+    uni.addInterceptor('navigateTo', navigateToInterceptor)
+    uni.addInterceptor('reLaunch', navigateToInterceptor)
+    uni.addInterceptor('redirectTo', navigateToInterceptor)
+    uni.addInterceptor('switchTab', navigateToInterceptor)
+  },
+}

+ 17 - 0
src/layouts/default.vue

@@ -0,0 +1,17 @@
+<template>
+  <wd-config-provider :themeVars="themeVars">
+    <slot />
+    <wd-toast />
+    <wd-message-box />
+  </wd-config-provider>
+</template>
+
+<script lang="ts" setup>
+import type { ConfigProviderThemeVars } from 'wot-design-uni'
+
+const themeVars: ConfigProviderThemeVars = {
+  // colorTheme: 'red',
+  // buttonPrimaryBgColor: '#07c160',
+  // buttonPrimaryColor: '#07c160',
+}
+</script>

+ 17 - 0
src/layouts/demo.vue

@@ -0,0 +1,17 @@
+<template>
+  <wd-config-provider :themeVars="themeVars">
+    <slot />
+    <wd-toast />
+    <wd-message-box />
+  </wd-config-provider>
+</template>
+
+<script lang="ts" setup>
+import type { ConfigProviderThemeVars } from 'wot-design-uni'
+
+const themeVars: ConfigProviderThemeVars = {
+  // colorTheme: 'red',
+  // buttonPrimaryBgColor: '#07c160',
+  // buttonPrimaryColor: '#07c160',
+}
+</script>

+ 17 - 0
src/main.ts

@@ -0,0 +1,17 @@
+import { createSSRApp } from 'vue'
+import App from './App.vue'
+import store from './store'
+import { routeInterceptor, requestInterceptor, prototypeInterceptor } from './interceptors'
+import 'virtual:uno.css'
+import '@/style/index.scss'
+
+export function createApp() {
+  const app = createSSRApp(App)
+  app.use(store)
+  app.use(routeInterceptor)
+  app.use(requestInterceptor)
+  app.use(prototypeInterceptor)
+  return {
+    app,
+  }
+}

+ 111 - 0
src/manifest.json

@@ -0,0 +1,111 @@
+{
+  "name": "printer-miniapp",
+  "appid": "__UNI__DD53B81",
+  "description": "",
+  "versionName": "1.0.0",
+  "versionCode": "100",
+  "transformPx": false,
+  "app-plus": {
+    "usingComponents": true,
+    "nvueStyleCompiler": "uni-app",
+    "compilerVersion": 3,
+    "splashscreen": {
+      "alwaysShowBeforeRender": true,
+      "waiting": true,
+      "autoclose": true,
+      "delay": 0
+    },
+    "modules": {},
+    "distribute": {
+      "android": {
+        "permissions": [
+          "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+          "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+          "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+          "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+          "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+          "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+          "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+          "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+          "<uses-feature android:name=\"android.hardware.camera\"/>",
+          "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+        ],
+        "minSdkVersion": 30,
+        "targetSdkVersion": 30,
+        "abiFilters": [
+          "armeabi-v7a",
+          "arm64-v8a"
+        ]
+      },
+      "ios": {},
+      "sdkConfigs": {},
+      "icons": {
+        "android": {
+          "hdpi": "static/images/logo.png",
+          "xhdpi": "static/images/logo.png",
+          "xxhdpi": "static/images/logo.png",
+          "xxxhdpi": "static/images/logo.png"
+        },
+        "ios": {
+          "appstore": "static/images/logo.png",
+          "ipad": {
+            "app": "static/images/logo.png",
+            "app@2x": "static/images/logo.png",
+            "notification": "static/images/logo.png",
+            "notification@2x": "static/images/logo.png",
+            "proapp@2x": "static/images/logo.png",
+            "settings": "static/images/logo.png",
+            "settings@2x": "static/images/logo.png",
+            "spotlight": "static/images/logo.png",
+            "spotlight@2x": "static/images/logo.png"
+          },
+          "iphone": {
+            "app@2x": "static/images/logo.png",
+            "app@3x": "static/images/logo.png",
+            "notification@2x": "static/images/logo.png",
+            "notification@3x": "static/images/logo.png",
+            "settings@2x": "static/images/logo.png",
+            "settings@3x": "static/images/logo.png",
+            "spotlight@2x": "static/images/logo.png",
+            "spotlight@3x": "static/images/logo.png"
+          }
+        }
+      }
+    },
+    "compatible": {
+      "ignoreVersion": true
+    }
+  },
+  "quickapp": {},
+  "mp-weixin": {
+    "appid": "wx46467fc3f5573a68",
+    "setting": {
+      "urlCheck": false
+    },
+    "usingComponents": true
+  },
+  "mp-alipay": {
+    "usingComponents": true,
+    "styleIsolation": "shared"
+  },
+  "mp-baidu": {
+    "usingComponents": true
+  },
+  "mp-toutiao": {
+    "usingComponents": true
+  },
+  "uniStatistics": {
+    "enable": false
+  },
+  "vueVersion": "3",
+  "h5": {
+    "router": {
+      "base": "/printer-h5/"
+    }
+  }
+}

+ 20 - 0
src/pages-sub/demo/index.vue

@@ -0,0 +1,20 @@
+<route lang="json5" type="page">
+{
+  style: { navigationBarTitleText: '分包页面 标题' },
+}
+</route>
+
+<template>
+  <view class="text-center">
+    <view class="m-8">http://localhost:9000/#/pages-sub/demo/index</view>
+    <view class="text-green-500">分包页面demo</view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+// code here
+</script>
+
+<style lang="scss" scoped>
+//
+</style>

+ 88 - 0
src/pages.json

@@ -0,0 +1,88 @@
+{
+  "globalStyle": {
+    "navigationStyle": "default",
+    "navigationBarTitleText": "猫耳云打印",
+    "navigationBarBackgroundColor": "#f8f8f8",
+    "navigationBarTextStyle": "black",
+    "backgroundColor": "#FFFFFF"
+  },
+  "easycom": {
+    "autoscan": true,
+    "custom": {
+      "^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue",
+      "^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
+    }
+  },
+  "tabBar": {
+    "color": "#999999",
+    "selectedColor": "#FF8D1A",
+    "backgroundColor": "#F8F8F8",
+    "borderStyle": "black",
+    "height": "50px",
+    "fontSize": "10px",
+    "iconWidth": "24px",
+    "spacing": "3px",
+    "list": [
+      {
+        "iconPath": "static/tabbar/home.png",
+        "selectedIconPath": "static/tabbar/homeHL.png",
+        "pagePath": "pages/index/index",
+        "text": "首页"
+      },
+      {
+        "iconPath": "static/tabbar/personal.png",
+        "selectedIconPath": "static/tabbar/personalHL.png",
+        "pagePath": "pages/personal/index",
+        "text": "我的"
+      }
+    ]
+  },
+  "pages": [
+    {
+      "path": "pages/index/index",
+      "type": "home",
+      "style": {
+        "navigationBarTitleText": "首页",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/assistant/detail",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "打印助手"
+      }
+    },
+    {
+      "path": "pages/assistant/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "打印助手列表"
+      }
+    },
+    {
+      "path": "pages/login/index",
+      "type": "page",
+      "style": {
+        "navigationBarTitleText": "登录"
+      }
+    },
+    {
+      "path": "pages/personal/index",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "我的"
+      }
+    },
+    {
+      "path": "pages/webview/index",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "webView"
+      }
+    }
+  ],
+  "subPackages": []
+}

+ 166 - 0
src/pages/assistant/detail.vue

@@ -0,0 +1,166 @@
+<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
+<route lang="json5">
+{
+  style: {
+    navigationBarTitleText: '打印助手',
+  },
+}
+</route>
+<template>
+  <view class="page-form">
+    <wd-form ref="form" :model="formData">
+      <view class="form-title">
+        <view>详细信息</view>
+        <!-- <view class="opt-item" @click="customRecognize">
+          <wd-icon name="list" size="30rpx"></wd-icon>
+          <text>右侧图标</text>
+        </view> -->
+      </view>
+      <wd-cell-group custom-class="form-group" border>
+        <wd-input
+          v-if="type == 'add'"
+          v-model="formData.hostname"
+          label="主机台"
+          label-width="100px"
+          prop="hostname"
+          placeholder="请输入"
+          clearable
+          suffix-icon="scan"
+          :readonly="type == 'view'"
+          :rules="[{ required: true, message: '请填写主机台' }]"
+          @clicksuffixicon="scanQRCode"
+        />
+        <wd-input
+          v-model="formData.asname"
+          label="设备名称"
+          label-width="100px"
+          prop="asname"
+          placeholder="请输入"
+          clearable
+          :readonly="type == 'view'"
+          :rules="[{ required: true, message: '请填写设备名称' }]"
+        />
+        <wd-select-picker
+          v-model="formData.addType"
+          label="添加方式"
+          label-width="100px"
+          prop="addType"
+          type="radio"
+          filterable
+          readonly
+          :columns="typeList"
+          :max="1"
+          :show-confirm="false"
+          :rules="[{ required: true, message: '请选择添加方式' }]"
+        />
+        <wd-cell 
+          title="设备状态"
+          title-width="100px"
+          :value="getLabel(formData.enabled, statusList)"
+        />
+        <wd-textarea
+          v-if="type != 'add'"
+          v-model="formData.remark"
+          label="备注"
+          label-width="100px"
+          prop="remark"
+          clearable
+          auto-height
+          :readonly="type == 'view'"
+          :placeholder="type == 'view' ? '' : '请输入'"
+        />
+      </wd-cell-group>
+
+      <view v-if="type != 'view'" class="form-footer">
+        <wd-button type="primary" size="large" @click="handleSubmit" block>提交</wd-button>
+      </view>
+    </wd-form>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { useMessage, useToast } from 'wot-design-uni'
+import { getUserHub, addUserHub, updateUserHub } from '@/service/api'
+import { getLabel } from '@/utils'
+
+const message = useMessage()
+const toast = useToast()
+const type = ref("view")
+const form = ref()
+const formData: any = ref({
+  hostname: "",
+  asname: "",
+  addType: 'qrcode',
+})
+const typeList: any = ref([{ label: '扫码添加', value: 'qrcode' }, { label: '内网发现', value: 'intranet' }])
+const statusList: any = ref([{ label: '停用', value: '0' }, { label: '正常', value: '1' }])
+
+defineOptions({
+  name: 'UserHubDetail',
+})
+
+function getDetailById(id: string) {
+  getUserHub(id).then((res: any) => {
+    if (res.code == 0 && res.body) {
+      formData.value = res.body || { }
+    }
+  })
+}
+
+function scanQRCode() {
+  uni.scanCode({
+    success: (res) => {
+      formData.value.hostname = res.result
+    },
+    fail: (err) => {
+      console.log('err: ', err);
+      toast.error('扫码失败')
+    },
+  })
+}
+
+function getFn() {
+  return type.value == "add" ? addUserHub : updateUserHub
+}
+
+function handleSubmit() {
+  form.value.validate().then(({ valid, errors }) => {
+    if (valid) {
+      getFn()(formData.value)
+        .then((res: any) => {
+          if (res.code == 0 && res.body) {
+            uni.$emit('refreshData')
+            message
+              .alert({
+                msg: '设备保存成功',
+                closeOnClickModal: false,
+              })
+              .then(() => {
+                uni.navigateBack()
+              })
+          }
+        })
+        .catch((error) => {
+          console.log(error)
+        })
+    }
+  })
+}
+
+onLoad((option) => {
+  type.value = option.type || "add"
+  if (option && option.id) getDetailById(option.id)
+})
+</script>
+
+<style lang="scss" scoped>
+:deep(.wd-radio-group) {
+  padding: 0 !important;
+  .wd-radio {
+    padding-top: 0 !important;
+  }
+}
+:deep(.wd-cell__value) {
+  text-align: left !important;
+}
+</style>

+ 307 - 0
src/pages/assistant/index.vue

@@ -0,0 +1,307 @@
+<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
+<route lang="json5">
+{
+  style: {
+    navigationBarTitleText: '打印助手列表',
+  },
+}
+</route>
+<template>
+  <view class="page-list">
+    <wd-sticky>
+      <view class="sticky-box">
+        <wd-search
+          placeholder="请输入打印助手名称"
+          placeholder-left
+          hide-cancel
+          v-model="queryParams.asname"
+          @change="onSearch"
+          @clear="onSearch"
+        >
+          <!-- <template v-slot:prefix>
+            <wd-drop-menu custom-class="search-menu">
+              <wd-drop-menu-item v-model="queryParams.addType" :options="typeList" @change="onSearch" />
+            </wd-drop-menu>
+          </template> -->
+        </wd-search>
+      </view>
+    </wd-sticky>
+
+    <view class="list-contain">
+      <template v-if="dataList.length > 0 || loadStatus == 'loading'">
+        <view v-for="item of dataList" :key="item.id" class="list-box" @click="toDetail('view', item.id)">
+          <view class="name">
+            <view class="main-text">
+              <view>{{ item.asname }}</view>
+              <wd-tag v-if="item.enabled == '0'" type="danger" mark custom-class="ml-2">
+                {{ getLabel('0', statusList) }}
+              </wd-tag>
+            </view>
+            <view class="sub-text">
+              {{ `添加方式: ${getLabel(item.addType, typeList)}` }}
+            </view>
+          </view>
+          <view class="opt" @click.stop="showActions(item)">
+            <wd-button type="icon" icon="more"></wd-button>
+          </view>
+        </view>
+        <!-- 加载更多 -->
+        <wd-loadmore custom-class="loadmore" :state="loadStatus" />
+      </template>
+      <wd-status-tip v-else image="content" tip="暂无内容" />
+    </view>
+
+    <!-- 悬浮按钮 -->
+    <wd-fab
+      :expandable="false"
+      @click="toDetail('add')"
+    ></wd-fab>
+
+    <!-- 动作面板 -->
+    <wd-action-sheet
+      v-model="isShowActions"
+      :actions="currentActions"
+      :title="actionsTitle"
+      @close="isShowActions = false"
+      @select="selectActions"
+    />
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { useMessage, useToast } from 'wot-design-uni'
+import { getUserHubPage, deleteUserHub } from '@/service/api'
+import { getLabel } from '@/utils'
+import { debounce } from 'wot-design-uni/components/common/util'
+
+const message = useMessage()
+const toast = useToast()
+const total = ref(0)
+const queryParams: any = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  addType: "all",
+  asname: '',
+})
+const typeList: any = ref([
+  { label: '全部', value: 'all' },
+  { label: '扫码添加', value: 'qrcode' },
+  { label: '内网发现', value: 'intranet' }
+])
+const statusList: any = ref([{ label: '停用', value: '0' }, { label: '正常', value: '1' }])
+const dataList = ref([])
+const currentData: any = ref({})
+// 加载中: loading; 没有数据: finished; 错误: error;
+const loadStatus: any = ref('loading')
+// 动作面板
+const isShowActions = ref(false)
+const actionsTitle = ref('')
+const currentActions: any = ref([{ loading: true }])
+const isToTop = ref(false)
+
+defineOptions({
+  name: 'UserHub',
+})
+
+// 根据状态 构造操作面板
+function buildCurrentActions() {
+  currentActions.value = [
+    { name: '查看' },
+    { name: '编辑' },
+    { name: '删除', color: '#fa4350' }
+  ]
+}
+
+function getDataList() {
+  loadStatus.value = 'loading'
+  const params = { ...queryParams }
+  if (params.asname) {
+    params.asname = '*' + params.asname + '*'
+  }
+  if (params.addType === '' || params.addType === 'all') {
+    delete params.addType
+  }
+  getUserHubPage(params)
+    .then((res: any) => {
+      if (res.code === 0 && res.body) {
+        total.value = res.body.total
+        if (queryParams.pageNo === 1) {
+          dataList.value = res.body.records || []
+        } else {
+          dataList.value.push(...(res.body.records || []))
+        }
+      }
+    })
+    .catch((e) => {})
+    .finally(() => {
+      loadStatus.value = 'finished'
+    })
+}
+
+const onSearch = debounce(() => {
+  queryParams.pageNo = 1
+  getDataList()
+}, 500)
+
+// 跳转到详情(新增add / 编辑edit / 查看view)
+function toDetail(type: string, id?: string) {
+  uni.navigateTo({
+    url: `/pages/assistant/detail?type=${type}&id=${id || ''}`,
+  })
+}
+
+function handleDelete() {
+  message
+    .confirm({
+      title: '提示',
+      msg: `是否删除打印助手: ${currentData.value.asname} ?`,
+    })
+    .then(() => {
+      deleteUserHub(currentData.value.id)
+        .then((res: any) => {
+          if (res.code === 0) {
+            toast.success('删除成功')
+            queryParams.pageNo = 1
+            getDataList()
+          }
+        })
+        .catch((error) => {
+          console.log(error)
+        })
+    })
+    .catch((error) => {
+      console.log(error)
+    })
+}
+
+function showActions(data: any) {
+  actionsTitle.value = data.asname ? `打印助手: ${data.asname}` : '操作'
+  currentData.value = data || {}
+  buildCurrentActions()
+  isShowActions.value = true
+}
+
+function selectActions(data: any) {
+  switch (data.item.name) {
+    case '查看':
+      toDetail('view', currentData.value.id)
+      break
+    case '编辑':
+      toDetail('edit', currentData.value.id)
+      break
+    case '删除':
+      handleDelete()
+      break
+  }
+}
+
+// 查询列表, 滚动到顶部, 返回第一页
+function resetDataList() {
+  uni.pageScrollTo({
+    scrollTop: 0,
+    duration: 300,
+  })
+  onSearch()
+}
+
+// 页面加载
+onLoad(() => {
+  getDataList()
+  // 监听刷新
+  uni.$on('refreshData', () => {
+    resetDataList()
+    isToTop.value = true
+  })
+})
+
+// 页面显示
+onShow(() => {
+  if (isToTop.value) {
+    uni.pageScrollTo({
+      scrollTop: 0,
+      duration: 300,
+    })
+    isToTop.value = false
+  }
+})
+
+// 滚动到底部
+onReachBottom(() => {
+  if (dataList.value.length < total.value) {
+    queryParams.pageNo++
+    getDataList()
+  } else {
+    loadStatus.value = 'finished'
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+.sticky-box {
+  width: 100vw;
+  height: 100rpx;
+  background-color: #ffffff;
+}
+.search-type {
+  position: relative;
+  height: 60rpx;
+  padding: 0 16rpx 0 32rpx;
+  line-height: 60rpx;
+}
+.search-type::after {
+  position: absolute;
+  top: 10rpx;
+  right: 0;
+  bottom: 10rpx;
+  width: 2rpx;
+  content: '';
+  background: rgba(0, 0, 0, 0.25);
+}
+.search-type {
+  :deep(.icon-arrow) {
+    display: inline-block;
+    font-size: 40rpx;
+    vertical-align: middle;
+  }
+}
+.list-contain {
+  padding: 10rpx 30rpx 30rpx;
+  overflow-y: auto;
+
+  .list-box {
+    display: flex;
+    align-items: center;
+    padding: 30rpx 0 30rpx 30rpx;
+    margin-bottom: 30rpx;
+    border: 1px solid #eee;
+
+    .name {
+      flex: 1;
+      .main-text {
+        display: flex;
+        align-items: center;
+      }
+      .sub-text {
+        margin-top: 20rpx;
+        font-size: 28rpx;
+        color: #00000073;
+      }
+    }
+    .opt {
+      width: 80rpx;
+      text-align: center;
+    }
+  }
+}
+//搜索下拉菜单
+:deep(.search-menu .wd-drop-menu__list) {
+  background: none;
+}
+:deep(.search-menu .wd-drop-menu__item) {
+  height: 62rpx;
+  line-height: 62rpx;
+}
+:deep(.search-menu .wd-drop-menu__item-title::after) {
+  content: none;
+}
+</style>

+ 234 - 0
src/pages/index/index.vue

@@ -0,0 +1,234 @@
+<!-- 使用 type="home" 属性设置首页,其他页面不需要设置,默认为page;推荐使用json5,更强大,且允许注释 -->
+<route lang="json5" type="home">
+{
+  style: {
+    navigationBarTitleText: '首页',
+    navigationStyle: 'custom',
+  },
+}
+</route>
+<template>
+  <view class="my-page">
+    <!-- 自定义导航栏 -->
+    <pyh-nv ref="nvRef" :config="nvConfig">
+      <wd-img :src="logoSrc" height="30" mode="heightFix"></wd-img>
+    </pyh-nv>
+
+    <view class="px-4">
+      <!-- 主图轮播 1:1 height="calc(100vw - 60rpx)" -->
+      <wd-swiper
+        v-model:current="current"
+        :list="swiperList"
+        :indicator="{ type: 'dots-bar' }"
+        value-key="url"
+        height="calc(66.6vw - 20rpx)"
+        imageMode="widthFix"
+        :duration="500"
+        custom-class="my-swiper shadow-class"
+      ></wd-swiper>
+      <!-- 功能区 -->
+      <view class="guide-box shadow-class">
+        <view class="guide-item" @click="toUserHub()">
+          <wd-img :src="deviceImgSrc" width="100" mode="widthFix"></wd-img>
+          <view class="item-title">打印助手</view>
+          <view class="item-desc">我的打印助手</view>
+        </view>
+        <view class="guide-item" @click="toPrint()">
+          <wd-img :src="picImgSrc" width="100" mode="widthFix"></wd-img>
+          <view class="item-title">远程打印</view>
+          <view class="item-desc">无需驱动 隔空打印</view>
+        </view>
+      </view>
+      <!-- 补充入口 -->
+      <view class="content">
+        <view class="section">
+          <view class="item">
+            <view class="title">
+              <image :src="settingSrc"></image>
+              <view>设备配网</view>
+            </view>
+            <view class="tips"></view>
+          </view>
+          <view class="item">
+            <view class="title">
+              <image :src="settingSrc"></image>
+              <view>预留入口</view>
+            </view>
+            <view class="tips">描述信息</view>
+          </view>
+          <view class="item">
+            <view class="title">
+              <image :src="settingSrc"></image>
+              <view>预留入口</view>
+            </view>
+            <view class="tips">描述信息</view>
+          </view>
+          <view class="item">
+            <view class="title">
+              <image :src="settingSrc"></image>
+              <view>预留入口</view>
+            </view>
+            <view class="tips">描述信息</view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+  name: 'Home',
+})
+
+// 获取屏幕边界到安全区域距离
+// const { safeAreaInsets } = uni.getSystemInfoSync()
+const nvRef: any = ref()
+const nvConfig = ref({
+  title: '首页',
+  type: 'slot', // default slot logo
+  back: {
+    hide: true,
+  },
+  bgColor: '#ffffff',
+  color: '#000000',
+  transparent: {
+    type: 'type',
+    initColor: '#000000',
+  },
+})
+const logoSrc = ref('/static/images/logo_temp.png')
+const picImgSrc = ref('/static/images/picture.jpg')
+const deviceImgSrc = ref('/static/images/device.jpg')
+const settingSrc = ref('/static/images/setting.png')
+const swiperList = ref([])
+const current = ref(0)
+
+// 初始化 swiperList 数组
+function initSwiperList() {
+  swiperList.value = [
+    { id: '3', url: '/static/images/banner3.jpg' },
+    { id: '2', url: '/static/images/banner2.jpg' },
+    { id: '1', url: '/static/images/banner1.jpg' },
+  ]
+}
+
+// 跳转
+// uni.reLaunch({
+//   url: `/pages/personal/index`,
+// })
+// uni.navigateTo({
+//   url: `/pages/map/index`,
+// })
+
+function toUserHub() {
+  uni.navigateTo({
+    url: `/pages/assistant/index`,
+  })
+}
+
+function toPrint() {
+
+}
+
+onLoad((option: any) => {
+  initSwiperList()
+})
+
+// 监听滚动
+onPageScroll((e) => {
+  nvRef.value.pageScroll(e)
+})
+</script>
+
+<style lang="scss" scoped>
+.my-page {
+  background-color: #eeeeee;
+  background-size: 100% auto;
+  background-position: top;
+  background-repeat: no-repeat;
+  width: 100%;
+  min-height: calc(100vh - 60rpx);
+  padding: 30rpx 0;
+  overflow-y: auto;
+}
+
+:deep(.my-swiper) {
+  border-radius: 40rpx !important;
+  swiper {
+    border-radius: 40rpx !important;
+  }
+}
+
+.guide-box {
+  display: flex;
+  padding: 20rpx 0 20rpx;
+  margin-top: 30rpx;
+  border-radius: 40rpx;
+  background-color: #fff;
+  .guide-item {
+    flex: 1;
+    text-align: center;
+    .item-title {
+      font-size: 32rpx;
+      font-weight: bold;
+      margin-top: 4rpx;
+    }
+    .item-desc {
+      font-size: 24rpx;
+      margin-top: 4rpx;
+      margin-bottom: 20rpx;
+    }
+  }
+  .guide-item + .guide-item {
+    border-left: 1px solid transparent; /* 设置透明边框以便于渐变可见 */
+    border-image: linear-gradient(to bottom, white, #b8b8b8, white) 10 10; /* 使用渐变作为边框图片 */
+  }
+}
+
+.content {
+  width: 100%;
+  position: relative;
+  margin-top: 30rpx;
+
+  .section {
+    display: flex;
+    flex-wrap: wrap;
+    justify-content: center;
+    align-items: center;
+    margin-left: -20rpx;
+    
+    .item {
+      flex: 1 1 calc(50% - 20px);
+      // background-color: #e1e1e1;
+      background-color: #ffffff;
+      padding: $uni-spacing-row-lg 0;
+      margin: 0 0 20rpx 20rpx;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      border-radius: 30rpx;
+      
+      .title {
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-size: $uni-font-size-lg;
+        
+        image {
+          width: 40rpx;
+          height: 40rpx;
+          margin-right: 10rpx;
+        }
+      }
+      
+      .tips {
+        color: $uni-text-color-grey;
+        font-size: $uni-font-size-base;
+      }
+    }
+  }
+}
+</style>

+ 141 - 0
src/pages/login/components/LoginInput.vue

@@ -0,0 +1,141 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: '登录-组件',
+  },
+}
+</route>
+<template>
+  <view class="login-input">
+    <input
+      type="text"
+      :value="modelValue"
+      @input="onInput"
+      @focus="onFocus"
+      @blur="onBlur"
+      :password="password && !showPassword"
+      :placeholder="focus ? '' : placeholder"
+      placeholder-class="login-input-placeholder"
+      :class="{ 'login-input-inp': 'true' }"
+    />
+    <view class="after-icon" v-if="password" @click="doShow">
+      <wd-icon :name="icon" size="16px"></wd-icon>
+    </view>
+    <view
+      :class="[password && modelValue.toString().length ? 'before-icon' : 'after-icon']"
+      v-if="clearable && modelValue.toString().length && focus"
+      @click="doClear"
+    >
+      <wd-icon name="error-fill" size="16px"></wd-icon>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+interface Props {
+  // 输入框值
+  modelValue: string | number
+  // 是否为密码类型
+  password: boolean
+  // 占位符
+  placeholder: string
+  // 是否显示清除按钮
+  clearable: boolean
+}
+
+// Props 赋予初始值
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: '',
+  password: false,
+  placeholder: '',
+  clearable: true
+})
+
+const focus = ref<boolean>(false) // 输入框是否聚焦
+const showPassword = ref<boolean>(false) // 是否显示密码
+
+// 隐藏按钮图标
+const icon = computed(() => {
+  return showPassword.value ? 'view' : 'eye-close'
+})
+
+// 事件
+// v-model
+const emit = defineEmits(['update:modelValue'])
+// 输入框输入
+function onInput(e) {
+  emit('update:modelValue', e.detail.value)
+}
+
+// 聚焦
+function onFocus() {
+  focus.value = true
+}
+
+// 失焦
+function onBlur() {
+  setTimeout(() => {
+    focus.value = false
+  }, 50)
+}
+
+// 清除按钮事件
+function doClear() {
+  showPassword.value = false
+  emit('update:modelValue', '')
+}
+
+// 显示密码按钮事件
+function doShow() {
+  focus.value = true
+  showPassword.value = !showPassword.value
+}
+</script>
+
+<style lang="scss" scoped>
+.login-input {
+  position: relative;
+  width: 100%;
+  height: 88rpx;
+  font-size: 28rpx;
+  font-weight: 500;
+  // color: $color-text-secondary;
+  border-radius: 44rpx;
+  overflow: hidden;
+  background: #f3f4f6;
+  box-sizing: border-box;
+  padding: 0 128rpx;
+  &-inp {
+    width: 100%;
+    height: 88rpx;
+    padding: 0;
+    margin: 0;
+    border: none;
+    background: #f3f4f6;
+    text-align: center;
+  }
+  &-placeholder {
+    font-size: 28rpx;
+    font-weight: 500;
+    line-height: 88rpx;
+    // color: $color-text-fourth;
+  }
+  .after-icon {
+    position: absolute;
+    right: 24rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    display: flex;
+    align-items: center;
+  }
+  .before-icon {
+    position: absolute;
+    right: 76rpx;
+    top: 50%;
+    transform: translateY(-50%);
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 181 - 0
src/pages/login/index.vue

@@ -0,0 +1,181 @@
+<route lang="json5" type="page">
+{
+  style: {
+    navigationBarTitleText: '登录',
+  },
+}
+</route>
+<template>
+  <wd-toast></wd-toast>
+  <view class="login">
+    <view class="login-main">
+      <text class="login-main-title">欢迎登录</text>
+      <view class="login-main-subtitle">使用猫耳云打印小程序</view>
+      <view class="login-main-body">
+        <!-- <login-input key="username" v-model="username" clearable placeholder="用户名" :maxlength="20"></login-input>
+        <view class="login-password">
+          <login-input key="password" v-model="password" password clearable placeholder="密码" :maxlength="20"></login-input>
+        </view> -->
+        <wd-button v-if="token" size="large" block type="primary" open-type="getPhoneNumber" :phone-number-no-quota-toast="false" @getphonenumber="onGetPhoneNumber">手机号快捷登录</wd-button>
+      </view>
+    </view>
+
+    <view class="login-footer">
+      <text>广东省中宝奥科技有限公司</text>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { useToast } from 'wot-design-uni'
+import { useUserStore } from '@/store'
+import { loginByCode, bingPhoneByCode } from '@/service/api/index'
+import { getLoginCode } from '@/utils'
+// import LoginInput from './components/LoginInput.vue'
+const toast = useToast()
+
+const username = ref<string>('') // 用户名
+const password = ref<string>('') // 密码
+const token = ref<string>('') // 令牌
+
+async function init() {
+  token.value = ""
+  const code = await getLoginCode()
+  loginByCode({ code }).then((res) => {
+    if (res.code == 0 ) {
+      switch (res.body.res) {
+        case "0":
+          handleToken(res.body.token || "")
+          break;
+        case "1":
+          token.value = res.body.token || ""
+          break;
+        default:
+          toast.error({ msg: '获取token异常', duration: 4000 })
+          break;
+      }
+    } else {
+      toast.error({ msg: '获取token异常', duration: 4000 })
+    }
+  }).catch((error) => {
+    toast.close()
+    toast.error({ msg: error!.data!.message || '获取token异常', duration: 4000 })
+  })
+}
+
+function handleToken(token) {
+  if (token) {
+    useUserStore().setToken(token)
+    // useUserStore().initUserInfo()
+    uni.reLaunch({
+      url: `/pages/index/index`,
+    })
+  } else {
+    handleTokenError()
+  }
+}
+
+function handleTokenError() {
+  toast.error({ msg: '登录失败, 请重新登录', duration: 4000 })
+  init()
+}
+
+/**
+ * 手机号实时验证组件: 获取并验证手机号
+ * e.detail.code -- 动态令牌;	e.detail.errMsg --- 回调信息(成功失败都会返回); e.detail.errno -- 错误码(失败时返回)
+ */
+function onGetPhoneNumber(detail) {
+  if(detail.errMsg == "getPhoneNumber:ok" && detail.code) {
+    handlePhoneNumberLogin(detail.code)
+  } else {
+    let tips = ""
+		if (detail.errno === 1400001) {
+			tips = "验证失败,请使用验证码方式"
+		} else if (detail.errMsg.includes('user deny') || detail.errMsg.includes('user cancel')) {
+			tips = "验证取消,请重新选择手机号"
+		} else if (detail.errMsg.includes('freq limit')) {
+			tips = "频率受限,请稍后重试"
+		} else {
+			tips = "验证失败,请稍后重试"
+		}
+    uni.showToast({
+      title: tips,
+      icon: "none",
+      duration: 2000
+    })
+  }
+}
+
+// 根据code换取手机号, 进行绑定 (必须绑定)
+function handlePhoneNumberLogin(code) {
+  let params = {
+    code,
+    token: token.value || ''
+  }
+  bingPhoneByCode(params).then((res) => {
+    if (res.code == 0 && res.body.res == 0) {
+      handleToken(res.body.token)
+    } else {
+      handleTokenError()
+    }
+  }).catch((error) => {
+    toast.error({ msg: error!.data!.message || '登录失败, 请重新登录', duration: 4000 })
+  })
+}
+
+onLoad(() => {
+  init()
+})
+</script>
+
+<style lang="scss" scoped>
+.login {
+  background-color: #ffffff;
+  height: calc(100vh - var(--window-top));
+  width: 100vw;
+  box-sizing: border-box;
+  &-main {
+    padding: 112rpx 75rpx 0 75rpx;
+    position: relative;
+    &-title {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      height: 64rpx;
+      font-size: 48rpx;
+      font-weight: 600;
+      line-height: 64rpx;
+      letter-spacing: 8rpx;
+    }
+    &-subtitle {
+      font-size: 28rpx;
+      line-height: 40rpx;
+      letter-spacing: 12rpx;
+      text-align: center;
+      margin: 8rpx 0 104rpx;
+    }
+    &-body {
+      position: relative;
+      box-sizing: border-box;
+      width: 600rpx;
+      height: 70vh;
+      // 仅用于 显示账号密码登录时
+      // .login-password {
+      //   position: relative;
+      //   padding: 40rpx 0 104rpx;
+      // }
+      padding-top: 20vh;
+    }
+  }
+  &-footer {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: 300rpx;
+    text-align: center;
+    line-height: 28rpx;
+    font-size: 24rpx;
+    font-weight: 500;
+  }
+}
+</style>

+ 189 - 0
src/pages/personal/index.vue

@@ -0,0 +1,189 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: '我的',
+  },
+}
+</route>
+
+<template>
+  <view class="main overflow-hidden pt-2 px4" :style="{ marginTop: safeAreaInsets?.top + 'px' }">
+    <view class="head">
+      <!-- <wd-upload
+        accept="image"
+        :file-list="fileList"
+        :max-count="1"
+        :upload-method="uploadImage"
+        @change="handleChange"
+      >
+        <wd-img
+          v-if="userInfo.avatar"
+          custom-class="img-btn"
+          :width="40"
+          :height="40"
+          :src="userInfo.avatar"
+        />
+        <wd-button v-else custom-class="gray-btn" type="icon" icon="photo"></wd-button>
+      </wd-upload> -->
+      <wd-img
+        v-if="userInfo.avatar"
+        custom-class="img-btn"
+        :width="40"
+        :height="40"
+        :src="userInfo.avatar"
+      />
+      <view class="head-name">{{ userInfo.name }}</view>
+    </view>
+    <view class="main-content mt6">
+      <view class="content-item line-b" @click="toAddUserHub">
+        <view>添加打印助手</view>
+        <wd-icon name="arrow-right" size="20px" color="#808080"></wd-icon>
+      </view>
+      <view class="content-item" @click="handleLogout">
+        <view>退出登录</view>
+        <wd-icon name="arrow-right" size="20px" color="#808080"></wd-icon>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { getEnvBaseUrl } from '@/utils'
+import { logout } from '@/service/api/index'
+import { useUserStore } from '@/store'
+import { useToast } from 'wot-design-uni'
+import type { UploadMethod, UploadFile } from 'wot-design-uni/components/wd-upload/types'
+
+// 请求基准地址
+const baseUrl = getEnvBaseUrl()
+
+const toast = useToast()
+// 获取屏幕边界到安全区域距离
+const { safeAreaInsets } = uni.getSystemInfoSync()
+const userInfo = reactive({
+  avatar: '',
+  name: '',
+})
+const fileList = ref([])
+
+const uploadImage: UploadMethod = (file, formData, options) => {
+  uni.uploadFile({
+    url: `${baseUrl}/sys/user/avatar`,
+    filePath: file.url,
+    name: 'file', // 这里根据后端需要的字段来定义
+    fileType: options.fileType,
+    formData, // 如果需要额外的 formData
+    success: async (res) => {
+      if (res.statusCode === options.statusCode) {
+        toast.success('头像上传成功')
+        await useUserStore().initUserInfo()
+        initFn()
+      } else {
+        const dataObj: any = JSON.parse(res.data)
+        toast.error({ msg: dataObj.message || '头像上传失败', duration: 4000 })
+      }
+    },
+    fail: (error) => {
+      console.error('上传失败', error)
+    },
+  })
+}
+
+function handleChange({ fileList }) {
+  fileList.value = fileList
+}
+
+function toAddUserHub() {
+  uni.navigateTo({
+    url: `/pages/assistant/detail?type=add`,
+  })
+}
+
+function handleLogout() {
+  // logout().then((res) => {
+  //   if (res.code === 0) {
+  //     useUserStore().setToken('')
+  //     useUserStore().setAvatar('')
+  //     useUserStore().setUserInfo({})
+  //     toast.success('退出登录成功')
+  //     toLogin()
+  //   }
+  // })
+
+  useUserStore().clearUserInfo()
+  toast.success('退出登录成功')
+  uni.reLaunch({
+    url: `/pages/login/index`
+  })
+}
+
+function initFn() {
+  // userInfo.name = useUserStore().userInfo.name
+  // userInfo.avatar = useUserStore().userInfo.avatar
+  userInfo.name = "微信用户"
+  userInfo.avatar = "/static/images/avatar.png"
+}
+
+onLoad(() => {
+  initFn()
+})
+</script>
+
+<style lang="scss" scoped>
+.head {
+  display: flex;
+  align-items: center;
+  .head-name {
+    @apply pl-3;
+  }
+}
+.main-content {
+  padding: 20rpx;
+  background-color: white;
+  border-radius: 16rpx;
+}
+.content-item {
+  display: flex;
+  align-items: start;
+  justify-content: space-between;
+  padding: 30rpx 0;
+}
+.line-b {
+  border-bottom: 1px solid #eee;
+}
+:deep(.head .wd-upload__evoke) {
+  width: 80rpx;
+  height: 80rpx;
+  margin-bottom: 0;
+  border-radius: 100%;
+}
+:deep(.wd-upload) {
+  display: flex;
+}
+:deep(.wd-upload__evoke-num) {
+  display: none;
+}
+:deep(.wd-icon) {
+  font-size: 50rpx;
+}
+:deep(.img-btn .wd-img__image) {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 40rpx;
+}
+:deep(.wd-upload__preview) {
+  display: none !important;
+}
+:deep(.gray-btn.wd-button) {
+  width: 80rpx;
+  height: 80rpx;
+  background-color: #ddd;
+  border-radius: 40rpx;
+}
+:deep(.form-ipt) {
+  padding-right: 0 !important;
+  padding-left: 0 !important;
+}
+</style>
+

+ 22 - 0
src/pages/webview/index.vue

@@ -0,0 +1,22 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: 'webView',
+  },
+}
+</route>
+
+<template>
+  <view class="">
+    <web-view :src="url"></web-view>
+  </view>
+</template>
+
+<script lang="ts" setup>
+const url = "http://192.168.9.104:8181/tos/#/"
+</script>
+
+<style lang="scss" scoped>
+//
+</style>

+ 97 - 0
src/service/api/index.ts

@@ -0,0 +1,97 @@
+import { http } from '@/utils/http'
+
+// ---------------------- 用户相关 ----------------------
+
+/** 小程序登录--静默自动登录 */
+export const loginByCode = (data: any) => {
+  return http<any>({
+    url: '/api/wx/mini/login',
+    method: 'POST',
+    hideErrorToast: true,
+    query: data,
+  })
+}
+
+/** 绑定手机号 */
+export const bingPhoneByCode = (data: any) => {
+  return http<any>({
+    url: '/api/wx/mini/bing-phone',
+    method: 'POST',
+    query: data,
+  })
+}
+
+/** 退出登录--暂未使用 */
+export const logout = () => {
+  return http<any>({
+    url: '/sys/user/logout',
+    method: 'GET',
+  })
+}
+
+/** 用户信息--暂未使用 */
+export const getUserInfo = () => {
+  return http<any>({
+    url: '/sys/user/info',
+    method: 'GET',
+  })
+}
+
+/** 上传头像--暂未使用 */
+export const uploadAvatar = (file: any) => {
+  const formData = new FormData()
+  formData.append('file', file)
+  return http<any>({
+    url: '/sys/user/avatar',
+    method: 'POST',
+    query: formData,
+    header: {
+      'Content-Type': 'multipart/form-data',
+    },
+  })
+}
+
+// ---------------------- 打印助手相关 ----------------------
+
+/** 新增打印助手 */
+export const addUserHub = (data: any) => {
+  return http<any>({
+    url: '/sys/wx/userHub/add',
+    method: 'POST',
+    data: data,
+  })
+}
+
+/** 查询打印助手 详细信息 */
+export const getUserHub = (id) => {
+  return http<any>({
+    url: `/sys/wx/userHub/get?id=${id}`,
+    method: 'GET',
+  })
+}
+
+/** 查询打印助手 分页信息 */
+export const getUserHubPage = (data) => {
+  return http<any>({
+    url: '/sys/wx/userHub/page',
+    method: 'GET',
+    data: data,
+  })
+}
+
+/** 删除打印助手 */
+export const deleteUserHub = (id: any) => {
+  return http<any>({
+    url: `/sys/wx/userHub/remove?id=${id}`,
+    method: 'POST',
+  })
+}
+
+/** 更新打印助手 */
+export const updateUserHub = (data: any) => {
+  return http<any>({
+    url: `/sys/wx/userHub/update`,
+    method: 'POST',
+    data: data,
+  })
+}

+ 0 - 0
src/static/images/.gitkeep


BIN
src/static/images/avatar.png


BIN
src/static/images/banner1.jpg


BIN
src/static/images/banner2.jpg


BIN
src/static/images/banner3.jpg


BIN
src/static/images/device.jpg


BIN
src/static/images/empty.png


BIN
src/static/images/logo_temp.png


BIN
src/static/images/picture.jpg


BIN
src/static/images/setting.png


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 33 - 0
src/static/logo.svg


BIN
src/static/tabbar/home.png


BIN
src/static/tabbar/homeHL.png


BIN
src/static/tabbar/order.png


BIN
src/static/tabbar/orderHL.png


BIN
src/static/tabbar/personal.png


BIN
src/static/tabbar/personalHL.png


+ 17 - 0
src/store/index.ts

@@ -0,0 +1,17 @@
+import { createPinia } from 'pinia'
+import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化
+
+const store = createPinia()
+store.use(
+  createPersistedState({
+    storage: {
+      getItem: uni.getStorageSync,
+      setItem: uni.setStorageSync,
+    },
+  }),
+)
+
+export default store
+
+// 模块统一导出
+export * from './user'

+ 61 - 0
src/store/user.ts

@@ -0,0 +1,61 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { getUserInfo } from '@/service/api/index'
+
+const initState = { nickname: '', avatar: '' }
+
+export const useUserStore = defineStore(
+  'user',
+  () => {
+    const userInfo = ref<IUserInfo>({ ...initState })
+    const userButtons: any = ref() // 按钮权限
+    const token = ref('')
+    const avatar = ref('')
+
+    const setUserInfo = (val: IUserInfo) => {
+      userInfo.value = val
+    }
+    const setToken = (val: string) => {
+      token.value = val
+    }
+    const setAvatar = (val: string) => {
+      avatar.value = val
+    }
+    const clearUserInfo = () => {
+      userInfo.value = { }
+      token.value = ""
+      avatar.value = ""
+    }
+    // 一般没有reset需求,不需要的可以删除
+    const reset = () => {
+      userInfo.value = { ...initState }
+    }
+    const isLogined = computed(() => !!token)
+
+    async function initUserInfo() {
+      // 查询个人信息
+      const res: any = await getUserInfo()
+      const { buttons, user, routes, avatarData } = res.body
+      userInfo.value = user
+      userButtons.value = buttons
+      avatar.value = avatarData
+    }
+
+    return {
+      userInfo,
+      token,
+      avatar,
+      userButtons,
+      isLogined,
+      setUserInfo,
+      clearUserInfo,
+      reset,
+      setToken,
+      setAvatar,
+      initUserInfo
+    }
+  },
+  {
+    persist: true,
+  },
+)

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 28 - 0
src/style/iconfont.css


+ 188 - 0
src/style/index.scss

@@ -0,0 +1,188 @@
+// @import './iconfont.css';
+
+.test {
+  // 可以通过 @apply 多个样式封装整体样式
+  @apply mt-4 ml-4;
+
+  padding-top: 4px;
+  color: red;
+}
+
+:root,
+page {
+  font-size: 32rpx;
+  background-color: #f1f1f1;
+
+  // 修改按主题色
+  --wot-color-theme: #FF8D1A;
+
+  // 修改按钮背景色
+  // --wot-button-primary-bg-color: #FF8D1A;
+
+  // 日历选中激活颜色
+  --wot-calendar-active-color: #FF8D1A;
+}
+
+// 全屏loading
+.loading-wrapper {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+}
+
+// 图片loading
+.loading-wrap {
+  width: 100%;
+  height: 30vh;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+// 图片error
+.error-wrap {
+  width: 100%;
+  height: 100%;
+  background-color: #f0f0f0;
+  color: #666666;
+  line-height: 30vh;
+  text-align: center;
+}
+
+// 图片样式 (根节点样式)
+.shadow-class {
+  box-shadow: 2px 2px 20px #0000001a;
+}
+
+.min-w-80 {
+  min-width: 80px !important;
+}
+
+.min-w-none {
+  min-width: unset !important;
+}
+
+
+// 文字加粗
+.text-bold {
+  font-weight: bold;
+}
+
+// 阴影样式
+.shadow-class {
+  box-shadow: 2px 2px 20px #0000001a;
+}
+
+.page-list {
+  min-height: 100vh;
+  background-color: white;
+}
+
+.page-form {
+  min-height: calc(100vh - 168rpx);
+  padding: 24rpx 24rpx 144rpx;
+
+  .form-title {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 48rpx;
+    margin-bottom: 24rpx;
+    font-size: 34rpx;
+
+    .opt-item {
+      display: inline-block;
+      margin-left: 34rpx;
+    }
+  }
+
+  .form-group {
+    overflow: hidden;
+    border-radius: 16rpx;
+  }
+
+  .form-footer {
+    position: fixed;
+    bottom: 20rpx;
+    z-index: 10;
+    width: calc(100vw - 48rpx);
+  }
+
+  .form-title:first-of-type {
+    margin-top: 0;
+  }
+}
+
+.fix-btn {
+  position: fixed;
+  bottom: 20rpx;
+  z-index: 2;
+  width: calc(100% - 48rpx);
+  height: 80rpx;
+  line-height: 80rpx;
+  color: white;
+  text-align: center;
+  background-color: #e87e15;
+  border-radius: 80rpx;
+}
+.fix-btn-box {
+  position: fixed;
+  bottom: 20rpx;
+  left: 10vw;
+  z-index: 2;
+  width: 80vw;
+}
+.main-color {
+  color: #e87e15;
+}
+.secondary-colors {
+  color: #808080;
+}
+.flex-center {
+  display: flex;
+  flex-wrap: wrap;
+  justify-items: center;
+}
+.flex-end {
+  display: flex;
+  justify-content: flex-end;
+}
+.flex-between {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.flex-around {
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+}
+.flex-items {
+  display: flex;
+  align-items: center;
+}
+.flex-2 {
+  flex: 2 1 0%;
+}
+
+.fixed-top {
+  position: fixed;
+  top: -1px;
+  left: 0;
+  z-index: 10; /* 确保搜索栏在页面内容之上 */
+  box-sizing: border-box;
+  width: 100%;
+  padding: 0 24rpx;
+  margin-bottom: 20rpx;
+}
+
+.sticky-search {
+  box-sizing: border-box;
+  width: 100vw !important;
+  background-color: #fff;
+}
+.sticky-search-nobg {
+  box-sizing: border-box;
+  width: 100vw !important;
+  background: none !important;
+}

+ 187 - 0
src/types/auto-import.d.ts

@@ -0,0 +1,187 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+  const EffectScope: typeof import('vue')['EffectScope']
+  const computed: typeof import('vue')['computed']
+  const createApp: typeof import('vue')['createApp']
+  const customRef: typeof import('vue')['customRef']
+  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
+  const defineComponent: typeof import('vue')['defineComponent']
+  const effectScope: typeof import('vue')['effectScope']
+  const getCurrentInstance: typeof import('vue')['getCurrentInstance']
+  const getCurrentScope: typeof import('vue')['getCurrentScope']
+  const h: typeof import('vue')['h']
+  const inject: typeof import('vue')['inject']
+  const isProxy: typeof import('vue')['isProxy']
+  const isReactive: typeof import('vue')['isReactive']
+  const isReadonly: typeof import('vue')['isReadonly']
+  const isRef: typeof import('vue')['isRef']
+  const markRaw: typeof import('vue')['markRaw']
+  const nextTick: typeof import('vue')['nextTick']
+  const onActivated: typeof import('vue')['onActivated']
+  const onAddToFavorites: typeof import('@dcloudio/uni-app')['onAddToFavorites']
+  const onBackPress: typeof import('@dcloudio/uni-app')['onBackPress']
+  const onBeforeMount: typeof import('vue')['onBeforeMount']
+  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
+  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
+  const onDeactivated: typeof import('vue')['onDeactivated']
+  const onError: typeof import('@dcloudio/uni-app')['onError']
+  const onErrorCaptured: typeof import('vue')['onErrorCaptured']
+  const onHide: typeof import('@dcloudio/uni-app')['onHide']
+  const onLaunch: typeof import('@dcloudio/uni-app')['onLaunch']
+  const onLoad: typeof import('@dcloudio/uni-app')['onLoad']
+  const onMounted: typeof import('vue')['onMounted']
+  const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']
+  const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']
+  const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']
+  const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']
+  const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']
+  const onPageNotFound: typeof import('@dcloudio/uni-app')['onPageNotFound']
+  const onPageScroll: typeof import('@dcloudio/uni-app')['onPageScroll']
+  const onPullDownRefresh: typeof import('@dcloudio/uni-app')['onPullDownRefresh']
+  const onReachBottom: typeof import('@dcloudio/uni-app')['onReachBottom']
+  const onReady: typeof import('@dcloudio/uni-app')['onReady']
+  const onRenderTracked: typeof import('vue')['onRenderTracked']
+  const onRenderTriggered: typeof import('vue')['onRenderTriggered']
+  const onResize: typeof import('@dcloudio/uni-app')['onResize']
+  const onScopeDispose: typeof import('vue')['onScopeDispose']
+  const onServerPrefetch: typeof import('vue')['onServerPrefetch']
+  const onShareAppMessage: typeof import('@dcloudio/uni-app')['onShareAppMessage']
+  const onShareTimeline: typeof import('@dcloudio/uni-app')['onShareTimeline']
+  const onShow: typeof import('@dcloudio/uni-app')['onShow']
+  const onTabItemTap: typeof import('@dcloudio/uni-app')['onTabItemTap']
+  const onThemeChange: typeof import('@dcloudio/uni-app')['onThemeChange']
+  const onUnhandledRejection: typeof import('@dcloudio/uni-app')['onUnhandledRejection']
+  const onUnload: typeof import('@dcloudio/uni-app')['onUnload']
+  const onUnmounted: typeof import('vue')['onUnmounted']
+  const onUpdated: typeof import('vue')['onUpdated']
+  const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
+  const provide: typeof import('vue')['provide']
+  const reactive: typeof import('vue')['reactive']
+  const readonly: typeof import('vue')['readonly']
+  const ref: typeof import('vue')['ref']
+  const resolveComponent: typeof import('vue')['resolveComponent']
+  const shallowReactive: typeof import('vue')['shallowReactive']
+  const shallowReadonly: typeof import('vue')['shallowReadonly']
+  const shallowRef: typeof import('vue')['shallowRef']
+  const toRaw: typeof import('vue')['toRaw']
+  const toRef: typeof import('vue')['toRef']
+  const toRefs: typeof import('vue')['toRefs']
+  const toValue: typeof import('vue')['toValue']
+  const triggerRef: typeof import('vue')['triggerRef']
+  const unref: typeof import('vue')['unref']
+  const useAttrs: typeof import('vue')['useAttrs']
+  const useCssModule: typeof import('vue')['useCssModule']
+  const useCssVars: typeof import('vue')['useCssVars']
+  const useId: typeof import('vue')['useId']
+  const useModel: typeof import('vue')['useModel']
+  const useNavbarWeixin: (typeof import('../hooks/useNavbarWeixin'))['default']
+  const useRequest: typeof import('../hooks/useRequest')['default']
+  const useSlots: typeof import('vue')['useSlots']
+  const useTemplateRef: typeof import('vue')['useTemplateRef']
+  const useUpload: typeof import('../hooks/useUpload')['default']
+  const useUpload2: typeof import('../hooks/useUpload2')['default']
+  const watch: typeof import('vue')['watch']
+  const watchEffect: typeof import('vue')['watchEffect']
+  const watchPostEffect: typeof import('vue')['watchPostEffect']
+  const watchSyncEffect: typeof import('vue')['watchSyncEffect']
+}
+// for type re-export
+declare global {
+  // @ts-ignore
+  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
+  import('vue')
+}
+// for vue template auto import
+import { UnwrapRef } from 'vue'
+declare module 'vue' {
+  interface GlobalComponents {}
+  interface ComponentCustomProperties {
+    readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
+    readonly computed: UnwrapRef<typeof import('vue')['computed']>
+    readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
+    readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
+    readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
+    readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
+    readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
+    readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
+    readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
+    readonly h: UnwrapRef<typeof import('vue')['h']>
+    readonly inject: UnwrapRef<typeof import('vue')['inject']>
+    readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
+    readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
+    readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
+    readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
+    readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
+    readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
+    readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
+    readonly onAddToFavorites: UnwrapRef<typeof import('@dcloudio/uni-app')['onAddToFavorites']>
+    readonly onBackPress: UnwrapRef<typeof import('@dcloudio/uni-app')['onBackPress']>
+    readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
+    readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
+    readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
+    readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
+    readonly onError: UnwrapRef<typeof import('@dcloudio/uni-app')['onError']>
+    readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
+    readonly onHide: UnwrapRef<typeof import('@dcloudio/uni-app')['onHide']>
+    readonly onLaunch: UnwrapRef<typeof import('@dcloudio/uni-app')['onLaunch']>
+    readonly onLoad: UnwrapRef<typeof import('@dcloudio/uni-app')['onLoad']>
+    readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
+    readonly onNavigationBarButtonTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarButtonTap']>
+    readonly onNavigationBarSearchInputChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputChanged']>
+    readonly onNavigationBarSearchInputClicked: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputClicked']>
+    readonly onNavigationBarSearchInputConfirmed: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputConfirmed']>
+    readonly onNavigationBarSearchInputFocusChanged: UnwrapRef<typeof import('@dcloudio/uni-app')['onNavigationBarSearchInputFocusChanged']>
+    readonly onPageNotFound: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageNotFound']>
+    readonly onPageScroll: UnwrapRef<typeof import('@dcloudio/uni-app')['onPageScroll']>
+    readonly onPullDownRefresh: UnwrapRef<typeof import('@dcloudio/uni-app')['onPullDownRefresh']>
+    readonly onReachBottom: UnwrapRef<typeof import('@dcloudio/uni-app')['onReachBottom']>
+    readonly onReady: UnwrapRef<typeof import('@dcloudio/uni-app')['onReady']>
+    readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
+    readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
+    readonly onResize: UnwrapRef<typeof import('@dcloudio/uni-app')['onResize']>
+    readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
+    readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
+    readonly onShareAppMessage: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareAppMessage']>
+    readonly onShareTimeline: UnwrapRef<typeof import('@dcloudio/uni-app')['onShareTimeline']>
+    readonly onShow: UnwrapRef<typeof import('@dcloudio/uni-app')['onShow']>
+    readonly onTabItemTap: UnwrapRef<typeof import('@dcloudio/uni-app')['onTabItemTap']>
+    readonly onThemeChange: UnwrapRef<typeof import('@dcloudio/uni-app')['onThemeChange']>
+    readonly onUnhandledRejection: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnhandledRejection']>
+    readonly onUnload: UnwrapRef<typeof import('@dcloudio/uni-app')['onUnload']>
+    readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
+    readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
+    readonly onWatcherCleanup: UnwrapRef<typeof import('vue')['onWatcherCleanup']>
+    readonly provide: UnwrapRef<typeof import('vue')['provide']>
+    readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
+    readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
+    readonly ref: UnwrapRef<typeof import('vue')['ref']>
+    readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
+    readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
+    readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
+    readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
+    readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
+    readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
+    readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
+    readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
+    readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
+    readonly unref: UnwrapRef<typeof import('vue')['unref']>
+    readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
+    readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
+    readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
+    readonly useId: UnwrapRef<typeof import('vue')['useId']>
+    readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
+    readonly useRequest: UnwrapRef<typeof import('../hooks/useRequest')['default']>
+    readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
+    readonly useTemplateRef: UnwrapRef<typeof import('vue')['useTemplateRef']>
+    readonly useUpload: UnwrapRef<typeof import('../hooks/useUpload')['default']>
+    readonly watch: UnwrapRef<typeof import('vue')['watch']>
+    readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
+    readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
+    readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
+  }
+}

+ 23 - 0
src/types/global.d.ts

@@ -0,0 +1,23 @@
+declare const __UNI_PLATFORM__:
+  | 'h5'
+  | 'app'
+  | 'mp-alipay'
+  | 'mp-baidu'
+  | 'mp-jd'
+  | 'mp-kuaishou'
+  | 'mp-lark'
+  | 'mp-qq'
+  | 'mp-toutiao'
+  | 'mp-weixin'
+  | 'quickapp-webview'
+  | 'quickapp-webview-huawei'
+  | 'quickapp-webview-union'
+
+declare const __VITE_APP_PROXY__: 'true' | 'false'
+
+declare namespace JSX {
+  interface IntrinsicElements {
+    template: any
+    block: any
+  }
+}

+ 8 - 0
src/types/shims-uni.d.ts

@@ -0,0 +1,8 @@
+/// <reference types='@dcloudio/types' />
+import 'vue'
+
+declare module '@vue/runtime-core' {
+  type Hooks = App.AppInstance & Page.PageInstance
+
+  interface ComponentCustomOptions extends Hooks {}
+}

+ 27 - 0
src/types/uni-pages.d.ts

@@ -0,0 +1,27 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by vite-plugin-uni-pages
+
+interface NavigateToOptions {
+  url: "/pages/index/index" |
+       "/pages/assistant/detail" |
+       "/pages/assistant/index" |
+       "/pages/login/index" |
+       "/pages/personal/index" |
+       "/pages/webview/index";
+}
+interface RedirectToOptions extends NavigateToOptions {}
+
+interface SwitchTabOptions {
+  url: "/pages/index/index" | "/pages/personal/index"
+}
+
+type ReLaunchOptions = NavigateToOptions | SwitchTabOptions;
+
+declare interface Uni {
+  navigateTo(options: UniNamespace.NavigateToOptions & NavigateToOptions): void;
+  redirectTo(options: UniNamespace.RedirectToOptions & RedirectToOptions): void;
+  switchTab(options: UniNamespace.SwitchTabOptions & SwitchTabOptions): void;
+  reLaunch(options: UniNamespace.ReLaunchOptions & ReLaunchOptions): void;
+}

+ 29 - 0
src/typings.d.ts

@@ -0,0 +1,29 @@
+// 全局要用的类型放到这里
+
+declare global {
+  type IResData<T> = {
+    code: number
+    msg: string
+    data: T
+  }
+
+  // uni.uploadFile文件上传参数
+  type IUniUploadFileOptions = {
+    file?: File
+    files?: UniApp.UploadFileOptionFiles[]
+    filePath?: string
+    name?: string
+    formData?: any
+  }
+
+  type IUserInfo = {
+    nickname?: string
+    avatar?: string
+    /** 微信的 openid,非微信没有这个字段 */
+    openid?: string
+    token?: string
+    roles?: string[]
+  }
+}
+
+export {} // 防止模块污染

+ 6 - 0
src/typings.ts

@@ -0,0 +1,6 @@
+// 枚举定义
+
+export enum TestEnum {
+  A = '1',
+  B = '2',
+}

+ 81 - 0
src/uni.scss

@@ -0,0 +1,81 @@
+/* stylelint-disable comment-empty-line-before */
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color: #333; // 基本色
+$uni-text-color-inverse: #fff; // 反色
+$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable: #c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color: #F8F8F8;
+$uni-bg-color-white: #ffffff;
+$uni-bg-color-grey: #f8f8f8;
+$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
+$uni-bg-color-mask: rgb(0 0 0 / 40%); // 遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color: #c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm: 12px;
+$uni-font-size-base: 14px;
+$uni-font-size-lg: 16;
+
+/* 图片尺寸 */
+$uni-img-size-sm: 20px;
+$uni-img-size-base: 26px;
+$uni-img-size-lg: 40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 阴影 */
+$uni-box-shadow: 0 20rpx 20rpx -20rpx rgba($color: #333, $alpha: 0.1);
+
+/* 文章场景相关 */
+$uni-color-title: #2c405a; // 文章标题颜色
+$uni-font-size-title: 20px;
+$uni-color-subtitle: #555; // 二级标题颜色
+$uni-font-size-subtitle: 18px;
+$uni-color-paragraph: #3f536e; // 文章段落颜色
+$uni-font-size-paragraph: 15px;

+ 0 - 0
src/uni_modules/.gitkeep


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 192 - 0
src/uni_modules/pyh-nv/changelog.md


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1291 - 0
src/uni_modules/pyh-nv/components/pyh-nv/pyh-nv.vue


+ 80 - 0
src/uni_modules/pyh-nv/package.json

@@ -0,0 +1,80 @@
+{
+    "id": "pyh-nv",
+    "name": "pyh-nv 全自定义、全兼容、全功能、多类型、可渐变导航栏",
+    "version": "1.6.0",
+    "description": "所有属性都可自定义,兼容各端包括nvue、vue3,所有类型导航栏都可渐变,还可设置状态栏字体色;1.1.6版本后续非uni_modules版本不再更新",
+    "keywords": [
+        "导航栏",
+        "自定义",
+        "渐变",
+        "状态栏",
+        "多端兼容"
+    ],
+    "displayName": "pyh-nv 全自定义、全兼容、全功能、多类型、可渐变导航栏",
+    "repository": "https://github.com/Ulovely/pyh-nv",
+    "dcloudext": {
+        "sale": {
+            "regular": {
+                "price": "0.00"
+            },
+            "sourcecode": {
+                "price": "0.00"
+            }
+        },
+        "contact": {
+            "qq": ""
+        },
+        "declaration": {
+            "ads": "无",
+            "data": "无",
+            "permissions": "无"
+        },
+        "npmurl": "",
+        "type": "component-vue"
+    },
+    "uni_modules": {
+        "platforms": {
+            "cloud": {
+                "tcb": "y",
+                "aliyun": "y"
+            },
+            "client": {
+                "App": {
+                    "app-vue": "y",
+                    "app-nvue": "y"
+                },
+                "H5-mobile": {
+                    "Safari": "y",
+                    "Android Browser": "y",
+                    "微信浏览器(Android)": "y",
+                    "QQ浏览器(Android)": "y"
+                },
+                "H5-pc": {
+                    "Chrome": "y",
+                    "IE": "y",
+                    "Edge": "y",
+                    "Firefox": "y",
+                    "Safari": "y"
+                },
+                "小程序": {
+                    "微信": "y",
+                    "阿里": "u",
+                    "百度": "u",
+                    "字节跳动": "u",
+                    "QQ": "u"
+                },
+                "快应用": {
+                    "华为": "u",
+                    "联盟": "u"
+                },
+                "Vue": {
+                    "vue2": "y",
+                    "vue3": "y"
+                }
+            }
+        }
+    },
+    "engines": {
+        "HBuilderX": "^3.1.0"
+    }
+}

+ 165 - 0
src/uni_modules/pyh-nv/readme.md

@@ -0,0 +1,165 @@
+### pyh-nv 全自定义、全兼容、全功能、多类型、可渐变导航栏
+
+pyh-nv 导航栏组件,组件名:``pyh-nv``,代码块: pyh-nv。
+
+**使用方式:**
+
+uni_modules:
+
+[uni_modules使用方法](https://uniapp.dcloud.io/uni_modules?id=%e4%bd%bf%e7%94%a8-uni_modules-%e6%8f%92%e4%bb%b6);
+
+非uni_modules:
+[如何从插件市场下载使用组件](https://ask.dcloud.net.cn/article/35409)
+uni-app插件市场的插件详情页,右上角uni_modules版本下载插件ZIP,解压/拉入components文件夹到 uni-app 根目录
+
+在 ``main.js`` 中引用组件 (示例项目main.js有注释,去掉对应注释可以直接使用)
+
+```javascript
+
+import nv from "@/components/pyh-nv/pyh-nv.vue";
+// #ifndef VUE3
+Vue.component("pyh-nv",nv)
+// #endif
+// #ifdef VUE3
+//在const app = createSSRApp(App)后添加
+app.component('pyh-nv',nv)
+// #endif
+
+```
+
+在 ``template`` 中使用组件
+
+```html
+
+<pyh-nv></pyh-nv>
+
+<pyh-nv :config="{back:{hide:true}}"></pyh-nv>
+
+<pyh-nv :config="config"></pyh-nv>
+
+```
+
+在 ``script`` 中 config 说明
+
+所有配置都为选填,无需要可以不配置,不复杂!!!</br>
+所有配置都为选填,无需要可以不配置,不复杂!!!</br>
+所有配置都为选填,无需要可以不配置,不复杂!!!</br>
+
+**config 属性说明:**
+
+|属性名				|类型	|默认值	    |说明			|
+|---				|----	|---	    |---			|
+|title				|String	|'pyh-nv' 	|标题,默认值为getApp().globalData.NAME或组件内title写死的字符串;标题可以使用config配置修改|
+|show				|Boolean|true   	|是否显示导航栏,可以用于多情形控制导航栏的显示,model模型不受影响固定为true|
+|sysncTitle			|Boolean|true   	|单页面h5端是否开启同步导航栏(比如微信导航栏)|
+|position			|Object	|{}			|定位属性,position->type为fixed和absoult都是固定定位,其它值为静态导航栏,随页面滚动,position->top目前仅model模式可用|
+|back				|Object	|		    |导航栏返回键配置,详细见下方back说明		|
+|model				|Boolean|false      |是否页面内独立使用模型,如果是固定定位,top为导航栏高度|
+|mainColor			|String	|''     	|导航的活动态颜色,可覆盖getApp().globalData.mainColor,仅部分导航栏有用到(搜索、tab切换)|
+|bgImage			|String	|''			|导航栏背景图,如果使用,则bgColor无效|
+|bgColor			|String	|'#ffffff'	|导航栏背景色,transparent值为透明底;如果使用渐变色,transparent渐变配置失效|
+|color				|String	|'#000000'	|导航栏和状态栏字体色,也用于渐变完成时字体色(状态栏字体只支持#000000或#ffffff)|
+|shadow				|String	|''			|导航栏阴影样式,同css的box-shadow用法|
+|componentBgColor	|String	|'#f8f8f8'	|导航栏组件背景色(可被覆盖),如果有设置,回到首页的返回键有背景色|
+|type				|String	|'default'	|导航栏类型(默认为通用),还有logo和search以及slot	|
+|componentsFlex		|String	|'center'   |type为search或image(只控制图片)时的组件内容flex布局align-items属性,内容居上(top)、中(center/middle/auto)、下(bottom)|
+|safeArea			|Number	|安全高度	|暂时只用于控制滚动显示,比如回到顶部		|
+|toTop				|Object |			|是否使用回到顶部,有该属性就是使用,详细见下方toTop说明|
+|windowInfo			|Object	|{}			|可填入width、height	、statusBarHeight来自定义窗口大小,如{width:667,height:375,statusBarHeight:0},单位为px,不传则为uni.getSystemInfoSync()的值|
+|logo				|Object	|		   	|导航栏logo的配置,仅type为logo或search时有效,详细见下方logo说明					|
+|search				|Object	|		    |导航栏含搜索框的配置,仅type为search时有效,详细见下方search说明					|
+|transparent		|Object	|		    |导航栏渐变配置,详细见下方transparent说明	|
+|fixedAssist		|Object	|    		|固定/绝对定位时辅助容器,{hide:false,bgColor:''}|
+|address			|Object	|		    |导航栏左地址配置,{province:'广东省',color:""},填入color会固定字体色				|
+|btn				|Array	|[]		    |导航栏右方按钮组,{text:'点击1',style:''},{icon:'',text:'',badge:{text:'1',style:''}}|
+|tabArr				|Array	|[]		    |导航栏中间tab切换,{title:'',active:true,hide:false}(说明:active为初始化活动态选项,hide为隐藏选项)|
+
+
+**pyh-nv.vue 内配置说明:**
+
+涉及到全局变量[getApp().globalData](https://uniapp.dcloud.net.cn/collocation/App.html#globaldata)
+
+|属性名				|类型	|默认值	                    |说明			|
+|---				|----	|---	                    |---			|
+|title				|String	|'pyh-nv' 					|标题默认值,getApp().globalData.NAME或自定义字符串	|
+|show				|Boolean|true   					|是否显示导航栏,可以通过getApp().globalData.nvShow来全局设置,model模型不受影响固定为true|
+|sysncTitle			|Boolean|true	 					|h5端是否开启同步导航栏(比如微信导航栏),为全局设置,也通过修改getApp().globalData.sysncTitle来设置|
+|mainColor			|String	|'#2b9939' 					|导航栏的活动态颜色,可以通过getApp().globalData.mainColor或自定义颜色,也可以单组件传mainColor设置,目前仅部分导航栏有用到(搜索、tab切换)|
+
+
+**config 内 back 配置说明:**
+
+|属性名				|类型	|默认值	                    |说明								|
+|---				|----	|---	                    |---								|
+|hide				|Boolean|false   					|是否隐藏返回按钮					|
+|iconForce			|Boolean|false   					|是否固定返回图标为返回键(不做home判断)|
+|customEvent		|Boolean|false     					|是否监听并自定义右上角返回键事件,如果为true,默认的返回事件失效,需要组件上写@nvBackTap来监听和自定义事件|
+|icon				|Object	|{path,homePath,style}   	|自定义图标配置,homePath和style可不传,如果存在icon和icon.path,则覆盖默认返回键|
+
+
+**config 内 toTop 配置说明:**
+
+|属性名				|类型	|默认值	                    |说明								|
+|---				|----	|---	                    |---								|
+|duration			|Number	|300   						|回到顶部的滚动动画时间(ms)			|
+|style				|String	|''	     					|样式配置							|
+
+**config 内 logo 配置说明:**
+
+|属性名				|类型	|默认值	                    |说明								|
+|---				|----	|---	                    |---								|
+|src				|String	|'/static/logo.png'   		|logo路径							|
+|url				|String	|''						    |如果传值,点击logo会reLaunch到该url	|
+|style				|String	|'width:60rpx'	     		|样式配置,图片默认为withFix,只传height为heightFix,宽高都传为aspectFill|
+
+**config 内 search 配置说明:**
+
+|属性名				|类型	|默认值	    |说明													|
+|---				|----	|---	    |---	|
+|value				|String	|''		    |input的初始值,如需动态赋值,必须初始化	|
+|bgColor			|String	|'#f8f8f8'  |组件背景色,覆盖	componentBgColor		|
+|color				|String	|''  		|字体以及图标颜色,不填使用默认图标颜色,和继承的字体颜色|
+|icon				|Object	|	    	|如果不传,默认显示搜索图标,{hide:false,color:"",size:18}|
+|input				|Boolean|false	    |输入框提示语样式						|
+|url				|String	|''		    |input为false时生效,点击跳转到url		|
+|linkType			|String	|''		    |input为false时生效,点击(uni[linkType])到url|
+|focus				|Boolean|false		|是否自动聚焦		|
+|border				|String |''			|输入框边框样式		|
+|placeholder		|String	|'搜索'	   	|输入框提示语		|
+|placeholderStyle	|String	|''		    |输入框提示语样式	|
+|btn				|Object	|		    |input为true时生效,搜索框提交按钮,{text:'搜索',style:''}	|
+|confirmType		|String	|'search'	|同官方input的confirm-type,设置回车键文字|
+
+**config 内 transparent 配置说明:**
+
+|属性名						|类型	|默认值	      	|说明													|
+|---						|----	|---	      	|---							|
+|type						|String	|'background' 	|渐变类型,content为全透明渐变	|
+|anchor						|Number	|当前导航栏高度	|最终渐变位置					|
+|initColor					|String	|'#ffffff'  	|导航栏与状态栏初始色,(状态栏字体只支持#000000或#ffffff)	|
+|reverse					|Boolean|false  		|是否反向渐变,为true时,渐变隐藏	|
+
+**组件pyh-nv 事件说明(详情请参考示例项目):**
+
+|事件名				|说明										|
+|---				|----										|
+|nvBackTap			|返回键点击自定义事件,会取消返回事件,back.customEvent为true时生效|
+|nvLogoTap			|点击logo,仅logo存在时生效					|
+|nvAddressTap		|点击地址,仅地址存在时生效					|
+|nvInput			|输入框input事件,仅search.input为true时生效	|
+|nvInputTap			|输入框点击自定义事件						|
+|nvFormSubmit		|输入框确认事件,仅search.input为true时生效	|
+|nvBtnTap			|右方按钮组点击事件,仅右方按钮存在时生效		|
+|nvTabTap			|中间tab组点击事件,仅中间tab按钮存在时生效	|
+
+
+**ref 事件说明(详情请参考示例项目):**
+
+|事件名				|参数类型	|参数默认值			|说明						|
+|---				|----		|----				|---						|
+|setStyle			|Object		|{}					|直接设置导航栏最外层元素样式	|
+|pageScroll			|Object		|{scrollTop:0}		|传递页面滚动信息			|
+
+**感谢:**
+
+> 有更多优化建议和需求,请联系作者 panyh 。谢谢!

+ 7 - 0
src/uni_modules/uv-load-more/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-21)
+1. 优化customStyle属性
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-load-more 加载更多

+ 95 - 0
src/uni_modules/uv-load-more/components/uv-load-more/props.js

@@ -0,0 +1,95 @@
+export default {
+	props: {
+		// 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
+		status: {
+			type: String,
+			default: 'loadmore'
+		},
+		// 组件背景色
+		bgColor: {
+			type: String,
+			default: 'transparent'
+		},
+		// 是否显示加载中的图标
+		icon: {
+			type: Boolean,
+			default: true
+		},
+		// 字体大小
+		fontSize: {
+			type: [String, Number],
+			default: 14
+		},
+		// 图标大小
+		iconSize: {
+			type: [String, Number],
+			default: 16
+		},
+		// 字体颜色
+		color: {
+			type: String,
+			default: '#606266'
+		},
+		// 加载中状态的图标,spinner-花朵状图标,circle-圆圈状,semicircle-半圆
+		loadingIcon: {
+			type: String,
+			default: 'spinner'
+		},
+		// 加载前的提示语
+		loadmoreText: {
+			type: String,
+			default: '加载更多'
+		},
+		// 加载中提示语
+		loadingText: {
+			type: String,
+			default: '正在加载...'
+		},
+		// 没有更多的提示语
+		nomoreText: {
+			type: String,
+			default: '没有更多了'
+		},
+		// 在“没有更多”状态下,是否显示粗点
+		isDot: {
+			type: Boolean,
+			default: false
+		},
+		// 加载中图标的颜色
+		iconColor: {
+			type: String,
+			default: '#b7b7b7'
+		},
+		// 上边距
+		marginTop: {
+			type: [String, Number],
+			default: 10
+		},
+		// 下边距
+		marginBottom: {
+			type: [String, Number],
+			default: 10
+		},
+		// 高度,单位px
+		height: {
+			type: [String, Number],
+			default: 'auto'
+		},
+		// 是否显示左边分割线
+		line: {
+			type: Boolean,
+			default: false
+		},
+		// 线条颜色
+		lineColor: {
+			type: String,
+			default: '#E6E8EB'
+		},
+		// 是否虚线,true-虚线,false-实线
+		dashed: {
+			type: Boolean,
+			default: false
+		},
+		...uni.$uv?.props?.loadmore
+	}
+}

+ 152 - 0
src/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue

@@ -0,0 +1,152 @@
+<template>
+	<view
+	    class="uv-loadmore"
+	    :style="[{
+				backgroundColor: bgColor,
+				marginBottom: $uv.addUnit(marginBottom),
+				marginTop: $uv.addUnit(marginTop),
+				height: $uv.addUnit(height),
+			},
+			$uv.addStyle(customStyle)]"
+	>
+		<uv-line
+		    length="140rpx"
+		    :color="lineColor"
+		    :hairline="false"
+			:dashed="dashed"
+			v-if="line"
+		></uv-line>
+		<!-- 加载中和没有更多的状态才显示两边的横线 -->
+		<view
+		    :class="status == 'loadmore' || status == 'nomore' ? 'uv-more' : ''"
+		    class="uv-loadmore__content"
+		>
+			<view
+			    class="uv-loadmore__content__icon-wrap"
+			    v-if="status === 'loading' && icon"
+			>
+				<uv-loading-icon
+				    :color="iconColor"
+				    :size="iconSize"
+				    :mode="loadingIcon"
+				></uv-loading-icon>
+			</view>
+			<!-- 如果没有更多的状态下,显示内容为dot(粗点),加载特定样式 -->
+			<text
+			    class="uv-line-1"
+			    :style="[loadTextStyle]"
+			    :class="[(status == 'nomore' && isDot == true) ? 'uv-loadmore__content__dot-text' : 'uv-loadmore__content__text']"
+			    @tap="loadMore"
+			>{{ showText }}</text>
+		</view>
+		<uv-line
+		    length="140rpx"
+		    :color="lineColor"
+			:hairline="false"
+			:dashed="dashed"
+			v-if="line"
+		></uv-line>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+
+	/**
+	 * loadmore 加载更多
+	 * @description 此组件一般用于标识页面底部加载数据时的状态。
+	 * @tutorial https://www.uvui.cn/components/loadMore.html
+	 * @property {String}			status			组件状态(默认 'loadmore' )
+	 * @property {String}			bgColor			组件背景颜色,在页面是非白色时会用到(默认 'transparent' )
+	 * @property {Boolean}			icon			加载中时是否显示图标(默认 true )
+	 * @property {String | Number}	fontSize		字体大小(默认 14 )
+	 * @property {String | Number}	iconSize		图标大小(默认 17 )
+	 * @property {String}			color			字体颜色(默认 '#606266' )
+	 * @property {String}			loadingIcon		加载图标(默认 'circle' )
+	 * @property {String}			loadmoreText	加载前的提示语(默认 '加载更多' )
+	 * @property {String}			loadingText		加载中提示语(默认 '正在加载...' )
+	 * @property {String}			nomoreText		没有更多的提示语(默认 '没有更多了' )
+	 * @property {Boolean}			isDot			到上一个相邻元素的距离 (默认 false )
+	 * @property {String}			iconColor		加载中图标的颜色 (默认 '#b7b7b7' )
+	 * @property {String}			lineColor		线条颜色(默认 #E6E8EB )
+	 * @property {String | Number}	marginTop		上边距 (默认 10 )
+	 * @property {String | Number}	marginBottom	下边距 (默认 10 )
+	 * @property {String | Number}	height			高度,单位px (默认 'auto' )
+	 * @property {Boolean}			line			是否显示左边分割线  (默认 false )
+	 * @property {Boolean}			dashed		// 是否虚线,true-虚线,false-实线  (默认 false )
+	 * @event {Function} loadmore status为loadmore时,点击组件会发出此事件
+	 * @example <uv-loadmore :status="status" icon-type="iconType" load-text="loadText" />
+	 */
+	export default {
+		name: "uv-loadmore",
+		mixins: [mpMixin, mixin,props],
+		data() {
+			return {
+				// 粗点
+				dotText: "●"
+			}
+		},
+		computed: {
+			// 加载的文字显示的样式
+			loadTextStyle() {
+				return {
+					color: this.color,
+					fontSize: this.$uv.addUnit(this.fontSize),
+					lineHeight: this.$uv.addUnit(this.fontSize),
+					backgroundColor: this.bgColor,
+				}
+			},
+			// 显示的提示文字
+			showText() {
+				let text = '';
+				if (this.status == 'loadmore') text = this.loadmoreText
+				else if (this.status == 'loading') text = this.loadingText
+				else if (this.status == 'nomore' && this.isDot) text = this.dotText;
+				else text = this.nomoreText;
+				return text;
+			}
+		},
+		methods: {
+			loadMore() {
+				// 只有在“加载更多”的状态下才发送点击事件,内容不满一屏时无法触发底部上拉事件,所以需要点击来触发
+				if (this.status == 'loadmore') this.$emit('loadmore');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	$show-lines: 1;
+	@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-loadmore {
+		@include flex(row);
+		align-items: center;
+		justify-content: center;
+		flex: 1;
+
+		&__content {
+			margin: 0 15px;
+			@include flex(row);
+			align-items: center;
+			justify-content: center;
+
+			&__icon-wrap {
+				margin-right: 8px;
+			}
+
+			&__text {
+				font-size: 14px;
+				color: $uv-content-color;
+			}
+
+			&__dot-text {
+				font-size: 15px;
+				color: $uv-tips-color;
+			}
+		}
+	}
+</style>

+ 89 - 0
src/uni_modules/uv-load-more/package.json

@@ -0,0 +1,89 @@
+{
+  "id": "uv-load-more",
+  "displayName": "uv-load-more 加载更多  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.2",
+  "description": "uv-load-more 此组件一般用于标识页面底部加载数据时的状态,共有三种状态:加载前、加载中、加载后。",
+  "keywords": [
+    "uv-load-more",
+    "uvui",
+    "uv-ui",
+    "more",
+    "加载更多"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-line",
+			"uv-loading-icon"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
src/uni_modules/uv-load-more/readme.md

@@ -0,0 +1,11 @@
+## LoadMore 加载更多
+
+> **组件名:uv-load-more**
+
+此组件一般用于标识页面底部加载数据时的状态,共有三种状态:加载前、加载中、加载后。
+
+### <a href="https://www.uvui.cn/components/loadMore.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 9 - 0
src/uni_modules/uv-loading-icon/changelog.md

@@ -0,0 +1,9 @@
+## 1.0.3(2023-08-14)
+1. 新增参数textStyle,自定义文本样式
+## 1.0.2(2023-06-27)
+优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-loading-icon组件

+ 67 - 0
src/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js

@@ -0,0 +1,67 @@
+export default {
+	props: {
+		// 是否显示组件
+		show: {
+			type: Boolean,
+			default: true
+		},
+		// 颜色
+		color: {
+			type: String,
+			default: '#909193'
+		},
+		// 提示文字颜色
+		textColor: {
+			type: String,
+			default: '#909193'
+		},
+		// 文字和图标是否垂直排列
+		vertical: {
+			type: Boolean,
+			default: false
+		},
+		// 模式选择,circle-圆形,spinner-花朵形,semicircle-半圆形
+		mode: {
+			type: String,
+			default: 'spinner'
+		},
+		// 图标大小,单位默认px
+		size: {
+			type: [String, Number],
+			default: 24
+		},
+		// 文字大小
+		textSize: {
+			type: [String, Number],
+			default: 15
+		},
+		// 文字样式
+		textStyle: {
+			type: Object,
+			default () {
+				return {}
+			}
+		},
+		// 文字内容
+		text: {
+			type: [String, Number],
+			default: ''
+		},
+		// 动画模式 https://www.runoob.com/cssref/css3-pr-animation-timing-function.html
+		timingFunction: {
+			type: String,
+			default: 'linear'
+		},
+		// 动画执行周期时间
+		duration: {
+			type: [String, Number],
+			default: 1200
+		},
+		// mode=circle时的暗边颜色
+		inactiveColor: {
+			type: String,
+			default: ''
+		},
+		...uni.$uv?.props?.loadingIcon
+	}
+}

+ 347 - 0
src/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue

@@ -0,0 +1,347 @@
+<template>
+	<view
+		class="uv-loading-icon"
+		:style="[$uv.addStyle(customStyle)]"
+		:class="[vertical && 'uv-loading-icon--vertical']"
+		v-if="show"
+	>
+		<view
+			v-if="!webviewHide"
+			class="uv-loading-icon__spinner"
+			:class="[`uv-loading-icon__spinner--${mode}`]"
+			ref="ani"
+			:style="{
+				color: color,
+				width: $uv.addUnit(size),
+				height: $uv.addUnit(size),
+				borderTopColor: color,
+				borderBottomColor: otherBorderColor,
+				borderLeftColor: otherBorderColor,
+				borderRightColor: otherBorderColor,
+				'animation-duration': `${duration}ms`,
+				'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
+			}"
+		>
+			<block v-if="mode === 'spinner'">
+				<!-- #ifndef APP-NVUE -->
+				<view
+					v-for="(item, index) in array12"
+					:key="index"
+					class="uv-loading-icon__dot"
+				>
+				</view>
+				<!-- #endif -->
+				<!-- #ifdef APP-NVUE -->
+				<!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 -->
+				<loading-indicator
+					v-if="!webviewHide"
+					class="uv-loading-indicator"
+					:animating="true"
+					:style="{
+						color: color,
+						width: $uv.addUnit(size),
+						height: $uv.addUnit(size)
+					}"
+				/>
+				<!-- #endif -->
+			</block>
+		</view>
+		<text
+			v-if="text"
+			class="uv-loading-icon__text"
+			:style="[{
+				fontSize: $uv.addUnit(textSize),
+				color: textColor,
+			},$uv.addStyle(textStyle)]"
+		>{{text}}</text>
+	</view>
+</template>
+
+<script>
+	import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = weex.requireModule('animation');
+	// #endif
+	/**
+	 * loading 加载动画
+	 * @description 警此组件为一个小动画,目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。
+	 * @tutorial https://www.uvui.cn/components/loading.html
+	 * @property {Boolean}			show			是否显示组件  (默认 true)
+	 * @property {String}			color			动画活动区域的颜色,只对 mode = flower 模式有效(默认#909193)
+	 * @property {String}			textColor		提示文本的颜色(默认#909193)
+	 * @property {Boolean}			vertical		文字和图标是否垂直排列 (默认 false )
+	 * @property {String}			mode			模式选择,见官网说明(默认 'circle' )
+	 * @property {String | Number}	size			加载图标的大小,单位px (默认 24 )
+	 * @property {String | Number}	textSize		文字大小(默认 15 )
+	 * @property {String | Number}	text			文字内容 
+	 * @property {Object}	textStyle 文字样式
+	 * @property {String}			timingFunction	动画模式 (默认 'ease-in-out' )
+	 * @property {String | Number}	duration		动画执行周期时间(默认 1200)
+	 * @property {String}			inactiveColor	mode=circle时的暗边颜色 
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * @example <uv-loading mode="circle"></uv-loading>
+	 */
+	export default {
+		name: 'uv-loading-icon',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				// Array.form可以通过一个伪数组对象创建指定长度的数组
+				// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
+				array12: Array.from({
+					length: 12
+				}),
+				// 这里需要设置默认值为360,否则在安卓nvue上,会延迟一个duration周期后才执行
+				// 在iOS nvue上,则会一开始默认执行两个周期的动画
+				aniAngel: 360, // 动画旋转角度
+				webviewHide: false, // 监听webview的状态,如果隐藏了页面,则停止动画,以免性能消耗
+				loading: false, // 是否运行中,针对nvue使用
+			}
+		},
+		computed: {
+			// 当为circle类型时,给其另外三边设置一个更轻一些的颜色
+			// 之所以需要这么做的原因是,比如父组件传了color为红色,那么需要另外的三个边为浅红色
+			// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
+			otherBorderColor() {
+				const lightColor = colorGradient(this.color, '#ffffff', 100)[80]
+				if (this.mode === 'circle') {
+					return this.inactiveColor ? this.inactiveColor : lightColor
+				} else {
+					return 'transparent'
+				}
+			}
+		},
+		watch: {
+			show(n) {
+				// nvue中,show为true,且为非loading状态,就重新执行动画模块
+				// #ifdef APP-NVUE
+				if (n && !this.loading) {
+					setTimeout(() => {
+						this.startAnimate()
+					}, 30)
+				}
+				// #endif
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				setTimeout(() => {
+					// #ifdef APP-NVUE
+					this.show && this.nvueAnimate()
+					// #endif
+					// #ifdef APP-PLUS 
+					this.show && this.addEventListenerToWebview()
+					// #endif
+				}, 20)
+			},
+			// 监听webview的显示与隐藏
+			addEventListenerToWebview() {
+				// webview的堆栈
+				const pages = getCurrentPages()
+				// 当前页面
+				const page = pages[pages.length - 1]
+				// 当前页面的webview实例
+				const currentWebview = page.$getAppWebview()
+				// 监听webview的显示与隐藏,从而停止或者开始动画(为了性能)
+				currentWebview.addEventListener('hide', () => {
+					this.webviewHide = true
+				})
+				currentWebview.addEventListener('show', () => {
+					this.webviewHide = false
+				})
+			},
+			// #ifdef APP-NVUE
+			nvueAnimate() {
+				// nvue下,非spinner类型时才需要旋转,因为nvue的spinner类型,使用了weex的
+				// loading-indicator组件,自带旋转功能
+				this.mode !== 'spinner' && this.startAnimate()
+			},
+			// 执行nvue的animate模块动画
+			startAnimate() {
+				this.loading = true
+				const ani = this.$refs.ani
+				if (!ani) return
+				animation.transition(ani, {
+					// 进行角度旋转
+					styles: {
+						transform: `rotate(${this.aniAngel}deg)`,
+						transformOrigin: 'center center'
+					},
+					duration: this.duration,
+					timingFunction: this.timingFunction,
+					// delay: 10
+				}, () => {
+					// 每次增加360deg,为了让其重新旋转一周
+					this.aniAngel += 360
+					// 动画结束后,继续循环执行动画,需要同时判断webviewHide变量
+					// nvue安卓,页面隐藏后依然会继续执行startAnimate方法
+					this.show && !this.webviewHide ? this.startAnimate() : this.loading = false
+				})
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-loading-icon-color: #c8c9cc !default;
+	$uv-loading-icon-text-margin-left:4px !default;
+	$uv-loading-icon-text-color:$uv-content-color !default;
+	$uv-loading-icon-text-font-size:14px !default;
+	$uv-loading-icon-text-line-height:20px !default;
+	$uv-loading-width:30px !default;
+	$uv-loading-height:30px !default;
+	$uv-loading-max-width:100% !default;
+	$uv-loading-max-height:100% !default;
+	$uv-loading-semicircle-border-width: 2px !default;
+	$uv-loading-semicircle-border-color:transparent !default;
+	$uv-loading-semicircle-border-top-right-radius: 100px !default;
+	$uv-loading-semicircle-border-top-left-radius: 100px !default;
+	$uv-loading-semicircle-border-bottom-left-radius: 100px !default;
+	$uv-loading-semicircle-border-bottom-right-radiu: 100px !default;
+	$uv-loading-semicircle-border-style: solid !default;
+	$uv-loading-circle-border-top-right-radius: 100px !default;
+	$uv-loading-circle-border-top-left-radius: 100px !default;
+	$uv-loading-circle-border-bottom-left-radius: 100px !default;
+	$uv-loading-circle-border-bottom-right-radiu: 100px !default;
+	$uv-loading-circle-border-width:2px !default;
+	$uv-loading-circle-border-top-color:#e5e5e5 !default;
+	$uv-loading-circle-border-right-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-bottom-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-left-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-style:solid !default;
+	$uv-loading-icon-host-font-size:0px !default;
+	$uv-loading-icon-host-line-height:1 !default;
+	$uv-loading-icon-vertical-margin:6px 0 0 !default;
+	$uv-loading-icon-dot-top:0 !default;
+	$uv-loading-icon-dot-left:0 !default;
+	$uv-loading-icon-dot-width:100% !default;
+	$uv-loading-icon-dot-height:100% !default;
+	$uv-loading-icon-dot-before-width:2px !default;
+	$uv-loading-icon-dot-before-height:25% !default;
+	$uv-loading-icon-dot-before-margin:0 auto !default;
+	$uv-loading-icon-dot-before-background-color:currentColor !default;
+	$uv-loading-icon-dot-before-border-radius:40% !default;
+
+	.uv-loading-icon {
+		/* #ifndef APP-NVUE */
+		// display: inline-flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		color: $uv-loading-icon-color;
+
+		&__text {
+			margin-left: $uv-loading-icon-text-margin-left;
+			color: $uv-loading-icon-text-color;
+			font-size: $uv-loading-icon-text-font-size;
+			line-height: $uv-loading-icon-text-line-height;
+		}
+
+		&__spinner {
+			width: $uv-loading-width;
+			height: $uv-loading-height;
+			position: relative;
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			max-width: $uv-loading-max-width;
+			max-height: $uv-loading-max-height;
+			animation: uv-rotate 1s linear infinite;
+			/* #endif */
+		}
+
+		&__spinner--semicircle {
+			border-width: $uv-loading-semicircle-border-width;
+			border-color: $uv-loading-semicircle-border-color;
+			border-top-right-radius: $uv-loading-semicircle-border-top-right-radius;
+			border-top-left-radius: $uv-loading-semicircle-border-top-left-radius;
+			border-bottom-left-radius: $uv-loading-semicircle-border-bottom-left-radius;
+			border-bottom-right-radius: $uv-loading-semicircle-border-bottom-right-radiu;
+			border-style: $uv-loading-semicircle-border-style;
+		}
+
+		&__spinner--circle {
+			border-top-right-radius: $uv-loading-circle-border-top-right-radius;
+			border-top-left-radius: $uv-loading-circle-border-top-left-radius;
+			border-bottom-left-radius: $uv-loading-circle-border-bottom-left-radius;
+			border-bottom-right-radius: $uv-loading-circle-border-bottom-right-radiu;
+			border-width: $uv-loading-circle-border-width;
+			border-top-color: $uv-loading-circle-border-top-color;
+			border-right-color: $uv-loading-circle-border-right-color;
+			border-bottom-color: $uv-loading-circle-border-bottom-color;
+			border-left-color: $uv-loading-circle-border-left-color;
+			border-style: $uv-loading-circle-border-style;
+		}
+
+		&--vertical {
+			flex-direction: column
+		}
+	}
+
+	/* #ifndef APP-NVUE */
+	:host {
+		font-size: $uv-loading-icon-host-font-size;
+		line-height: $uv-loading-icon-host-line-height;
+	}
+
+	.uv-loading-icon {
+		&__spinner--spinner {
+			animation-timing-function: steps(12)
+		}
+
+		&__text:empty {
+			display: none
+		}
+
+		&--vertical &__text {
+			margin: $uv-loading-icon-vertical-margin;
+			color: $uv-content-color;
+		}
+
+		&__dot {
+			position: absolute;
+			top: $uv-loading-icon-dot-top;
+			left: $uv-loading-icon-dot-left;
+			width: $uv-loading-icon-dot-width;
+			height: $uv-loading-icon-dot-height;
+
+			&:before {
+				display: block;
+				width: $uv-loading-icon-dot-before-width;
+				height: $uv-loading-icon-dot-before-height;
+				margin: $uv-loading-icon-dot-before-margin;
+				background-color: $uv-loading-icon-dot-before-background-color;
+				border-radius: $uv-loading-icon-dot-before-border-radius;
+				content: " "
+			}
+		}
+	}
+
+	@for $i from 1 through 12 {
+		.uv-loading-icon__dot:nth-of-type(#{$i}) {
+			transform: rotate($i * 30deg);
+			opacity: 1 - 0.0625 * ($i - 1);
+		}
+	}
+
+	@keyframes uv-rotate {
+		0% {
+			transform: rotate(0deg)
+		}
+
+		to {
+			transform: rotate(1turn)
+		}
+	}
+
+	/* #endif */
+</style>

+ 87 - 0
src/uni_modules/uv-loading-icon/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-loading-icon",
+  "displayName": "uv-loading-icon 加载动画 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.3",
+  "description": "此组件为一个小动画,目前用在uv-ui的uv-load-more加载更多等组件,还可以运用在项目中正在加载状态场景。",
+  "keywords": [
+    "uv-loading-icon",
+    "uvui",
+    "uv-ui",
+    "loading",
+    "加载动画"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 19 - 0
src/uni_modules/uv-loading-icon/readme.md

@@ -0,0 +1,19 @@
+## LoadingIcon 加载动画
+
+> **组件名:uv-loading-icon**
+
+此组件为一个小动画,目前用在 `uv-ui` 的 `uv-load-more` 加载更多等组件,还可以运用在项目中正在加载状态场景。
+
+# <a href="https://www.uvui.cn/components/loadingIcon.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 19 - 0
src/uni_modules/uv-transition/changelog.md

@@ -0,0 +1,19 @@
+## 1.0.8(2023-10-18)
+1. 修复在APP上不能正常显示的BUG
+## 1.0.7(2023-10-12)
+1. 修复部分情况,修改某属性自动关闭的BUG
+## 1.0.6(2023-07-24)
+1. 优化  nvue模式下增加cellChild参数,是否在list中cell节点下,nvue中cell下建议设置成true
+## 1.0.5(2023-07-02)
+修改VUE3模式下可能存在的BUG
+## 1.0.4(2023-07-02)
+uv-transition  动画组件,代码重构优化,性能更加友好,增加自定义动画功能。详情参考文档:https://www.uvui.cn/components/transition.html
+## 1.0.3(2023-06-12)
+1. 恢复this.$nextTick的使用,经过测试百度等平台无问题
+## 1.0.2(2023-05-23)
+1. 百度小程序等平台不支持this.$nextick,修改成延时
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增动画组件

+ 131 - 0
src/uni_modules/uv-transition/components/uv-transition/createAnimation.js

@@ -0,0 +1,131 @@
+// const defaultOption = {
+// 	duration: 300,
+// 	timingFunction: 'linear',
+// 	delay: 0,
+// 	transformOrigin: '50% 50% 0'
+// }
+// #ifdef APP-NVUE
+const nvueAnimation = uni.requireNativePlugin('animation')
+// #endif
+class MPAnimation {
+	constructor(options, _this) {
+		this.options = options
+		// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
+		this.animation = uni.createAnimation({
+			...options
+		})
+		this.currentStepAnimates = {}
+		this.next = 0
+		this.$ = _this
+
+	}
+
+	_nvuePushAnimates(type, args) {
+		let aniObj = this.currentStepAnimates[this.next]
+		let styles = {}
+		if (!aniObj) {
+			styles = {
+				styles: {},
+				config: {}
+			}
+		} else {
+			styles = aniObj
+		}
+		if (animateTypes1.includes(type)) {
+			if (!styles.styles.transform) {
+				styles.styles.transform = ''
+			}
+			let unit = ''
+			if(type === 'rotate'){
+				unit = 'deg'
+			}
+			styles.styles.transform += `${type}(${args+unit}) `
+		} else {
+			styles.styles[type] = `${args}`
+		}
+		this.currentStepAnimates[this.next] = styles
+	}
+	_animateRun(styles = {}, config = {}) {
+		let ref = this.$.$refs['ani'].ref
+		if (!ref) return
+		return new Promise((resolve, reject) => {
+			nvueAnimation.transition(ref, {
+				styles,
+				...config
+			}, res => {
+				resolve()
+			})
+		})
+	}
+
+	_nvueNextAnimate(animates, step = 0, fn) {
+		let obj = animates[step]
+		if (obj) {
+			let {
+				styles,
+				config
+			} = obj
+			this._animateRun(styles, config).then(() => {
+				step += 1
+				this._nvueNextAnimate(animates, step, fn)
+			})
+		} else {
+			this.currentStepAnimates = {}
+			typeof fn === 'function' && fn()
+			this.isEnd = true
+		}
+	}
+
+	step(config = {}) {
+		// #ifndef APP-NVUE
+		this.animation.step(config)
+		// #endif
+		// #ifdef APP-NVUE
+		this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
+		this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
+		this.next++
+		// #endif
+		return this
+	}
+
+	run(fn) {
+		// #ifndef APP-NVUE
+		this.$.animationData = this.animation.export()
+		this.$.timer = setTimeout(() => {
+			typeof fn === 'function' && fn()
+		}, this.$.durationTime)
+		// #endif
+		// #ifdef APP-NVUE
+		this.isEnd = false
+		let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
+		if(!ref) return
+		this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
+		this.next = 0
+		// #endif
+	}
+}
+
+
+const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
+	'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
+	'translateZ'
+]
+const animateTypes2 = ['opacity', 'backgroundColor']
+const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
+	MPAnimation.prototype[type] = function(...args) {
+		// #ifndef APP-NVUE
+		this.animation[type](...args)
+		// #endif
+		// #ifdef APP-NVUE
+		this._nvuePushAnimates(type, args)
+		// #endif
+		return this
+	}
+})
+
+export function createAnimation(option, _this) {
+	if(!_this) return
+	clearTimeout(_this.timer)
+	return new MPAnimation(option, _this)
+}

+ 0 - 0
src/uni_modules/uv-transition/components/uv-transition/props.js


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels