Jelajahi Sumber

feat: 完成医疗设备管理布局

windyeasy 1 bulan lalu
induk
melakukan
8059a14789

+ 3 - 0
package.json

@@ -26,6 +26,7 @@
     "uvm": "npx @dcloudio/uvm@latest",
     "uvm-rm": "node ./scripts/postupgrade.js",
     "postuvm": "echo upgrade uni-app success!",
+    "svgicon": "node ./src/uni_modules/zui-svg-icon/tools/generate-svg-icon.js",
     "dev:app": "uni -p app",
     "dev:app-android": "uni -p app-android",
     "dev:app-ios": "uni -p app-ios",
@@ -94,6 +95,7 @@
     "pinia": "2.0.36",
     "pinia-plugin-persistedstate": "3.2.1",
     "qs": "6.5.3",
+    "svgo": "^3.3.2",
     "vue": "3.4.21",
     "wot-design-uni": "^1.2.26",
     "z-paging": "^2.7.10"
@@ -114,6 +116,7 @@
     "@types/wechat-miniprogram": "^3.4.7",
     "@typescript-eslint/eslint-plugin": "^6.21.0",
     "@typescript-eslint/parser": "^6.21.0",
+    "@uni-helper/vite-plugin-uni-components": "^0.0.10",
     "@uni-helper/vite-plugin-uni-layouts": "^0.1.10",
     "@uni-helper/vite-plugin-uni-manifest": "^0.2.6",
     "@uni-helper/vite-plugin-uni-pages": "0.2.20",

+ 1 - 1
pages.config.ts

@@ -31,7 +31,7 @@ export default defineUniPages({
         iconPath: 'static/tabbar/home.png',
         selectedIconPath: 'static/tabbar/homeHL.png',
         pagePath: 'pages/index/index',
-        text: '首页',
+        text: '预诊大厅',
       },
       {
         iconPath: 'static/tabbar/example.png',

+ 104 - 0
pnpm-lock.yaml

@@ -44,6 +44,9 @@ importers:
       qs:
         specifier: 6.5.3
         version: 6.5.3
+      svgo:
+        specifier: ^3.3.2
+        version: 3.3.2
       vue:
         specifier: 3.4.21
         version: 3.4.21(typescript@4.9.5)
@@ -99,6 +102,9 @@ importers:
       '@typescript-eslint/parser':
         specifier: ^6.21.0
         version: 6.21.0(eslint@8.57.0)(typescript@4.9.5)
+      '@uni-helper/vite-plugin-uni-components':
+        specifier: ^0.0.10
+        version: 0.0.10(rollup@4.18.0)
       '@uni-helper/vite-plugin-uni-layouts':
         specifier: ^0.1.10
         version: 0.1.10(rollup@4.18.0)
@@ -1596,6 +1602,10 @@ packages:
     resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
     engines: {node: '>= 6'}
 
+  '@trysound/sax@0.2.0':
+    resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
+    engines: {node: '>=10.13.0'}
+
   '@types/babel__core@7.20.5':
     resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
 
@@ -1729,6 +1739,9 @@ packages:
   '@uni-helper/uni-env@0.1.2':
     resolution: {integrity: sha512-61dETqNHxlRSvdV0lDn9VknHzvt6uo8IhqoEvy8aWQVRoEyvFvGZKRXesUnq2WzyaTnQhG/g42P7aSb7YpZAzQ==}
 
+  '@uni-helper/vite-plugin-uni-components@0.0.10':
+    resolution: {integrity: sha512-DtScnAkLWRxVWRUGrCG0bQvdQzLDtrcMu0QQKUV7kbw/C9477dKyCRdLXe9vXVmG+HSlHiJFhAu6wSOSnHrStw==}
+
   '@uni-helper/vite-plugin-uni-layouts@0.1.10':
     resolution: {integrity: sha512-RJdGmJjZtpKNVfShiKYZrualMxdi+i8uh7zpPG+X3lzf6wyKSJgWwVAj3GUdqeE/QUEncNPmj2sqwuyeLXPxbA==}
 
@@ -2379,6 +2392,10 @@ packages:
   commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
 
+  commander@7.2.0:
+    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+    engines: {node: '>= 10'}
+
   commitlint@18.6.1:
     resolution: {integrity: sha512-I10mj1OmBCrPUHItRqeCEj0uxCdWxL15sCfS1Poq8av2FcX/KvRoiH8jTNG0cVDA2ns7IftugTAM+nLvOavLsw==}
     engines: {node: '>=v18'}
@@ -2497,18 +2514,33 @@ packages:
   css-list-helpers@2.0.0:
     resolution: {integrity: sha512-9Bj8tZ0jWbAM3u/U6m/boAzAwLPwtjzFvwivr2piSvyVa3K3rChJzQy4RIHkNkKiZCHrEMWDJWtTR8UyVhdDnQ==}
 
+  css-select@5.1.0:
+    resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==}
+
   css-system-font-keywords@1.0.0:
     resolution: {integrity: sha512-1umTtVd/fXS25ftfjB71eASCrYhilmEsvDEI6wG/QplnmlfmVM5HkZ/ZX46DT5K3eblFPgLUHt5BRCb0YXkSFA==}
 
+  css-tree@2.2.1:
+    resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
+    engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
   css-tree@2.3.1:
     resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
     engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
 
+  css-what@6.1.0:
+    resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+    engines: {node: '>= 6'}
+
   cssesc@3.0.0:
     resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
     engines: {node: '>=4'}
     hasBin: true
 
+  csso@5.0.5:
+    resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==}
+    engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
+
   cssom@0.3.8:
     resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
 
@@ -3766,6 +3798,10 @@ packages:
     resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==}
     engines: {node: '>= 12.13.0'}
 
+  local-pkg@0.4.3:
+    resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
+    engines: {node: '>=14'}
+
   local-pkg@0.5.0:
     resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
     engines: {node: '>=14'}
@@ -3863,6 +3899,9 @@ packages:
   mathml-tag-names@2.1.3:
     resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
 
+  mdn-data@2.0.28:
+    resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
+
   mdn-data@2.0.30:
     resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
 
@@ -3939,6 +3978,10 @@ packages:
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 
+  minimatch@8.0.4:
+    resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
+    engines: {node: '>=16 || 14 >=14.17'}
+
   minimatch@9.0.3:
     resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
     engines: {node: '>=16 || 14 >=14.17'}
@@ -4860,6 +4903,11 @@ packages:
   svg-tags@1.0.0:
     resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
 
+  svgo@3.3.2:
+    resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
   symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
 
@@ -7441,6 +7489,8 @@ snapshots:
 
   '@tootallnate/once@1.1.2': {}
 
+  '@trysound/sax@0.2.0': {}
+
   '@types/babel__core@7.20.5':
     dependencies:
       '@babel/parser': 7.24.7
@@ -7604,6 +7654,21 @@ snapshots:
     dependencies:
       std-env: 3.7.0
 
+  '@uni-helper/vite-plugin-uni-components@0.0.10(rollup@4.18.0)':
+    dependencies:
+      '@antfu/utils': 0.7.8
+      '@rollup/pluginutils': 5.1.0(rollup@4.18.0)
+      chokidar: 3.6.0
+      debug: 4.3.5
+      fast-glob: 3.3.2
+      local-pkg: 0.4.3
+      magic-string: 0.30.10
+      minimatch: 8.0.4
+      resolve: 1.22.8
+    transitivePeerDependencies:
+      - rollup
+      - supports-color
+
   '@uni-helper/vite-plugin-uni-layouts@0.1.10(rollup@4.18.0)':
     dependencies:
       '@babel/types': 7.24.7
@@ -8487,6 +8552,8 @@ snapshots:
 
   commander@2.20.3: {}
 
+  commander@7.2.0: {}
+
   commitlint@18.6.1(@types/node@20.14.2)(typescript@4.9.5):
     dependencies:
       '@commitlint/cli': 18.6.1(@types/node@20.14.2)(typescript@4.9.5)
@@ -8592,15 +8659,34 @@ snapshots:
 
   css-list-helpers@2.0.0: {}
 
+  css-select@5.1.0:
+    dependencies:
+      boolbase: 1.0.0
+      css-what: 6.1.0
+      domhandler: 5.0.3
+      domutils: 3.1.0
+      nth-check: 2.1.1
+
   css-system-font-keywords@1.0.0: {}
 
+  css-tree@2.2.1:
+    dependencies:
+      mdn-data: 2.0.28
+      source-map-js: 1.2.0
+
   css-tree@2.3.1:
     dependencies:
       mdn-data: 2.0.30
       source-map-js: 1.2.0
 
+  css-what@6.1.0: {}
+
   cssesc@3.0.0: {}
 
+  csso@5.0.5:
+    dependencies:
+      css-tree: 2.2.1
+
   cssom@0.3.8: {}
 
   cssom@0.4.4: {}
@@ -10207,6 +10293,8 @@ snapshots:
 
   loader-utils@3.3.1: {}
 
+  local-pkg@0.4.3: {}
+
   local-pkg@0.5.0:
     dependencies:
       mlly: 1.7.1
@@ -10290,6 +10378,8 @@ snapshots:
 
   mathml-tag-names@2.1.3: {}
 
+  mdn-data@2.0.28: {}
+
   mdn-data@2.0.30: {}
 
   media-typer@0.3.0: {}
@@ -10351,6 +10441,10 @@ snapshots:
     dependencies:
       brace-expansion: 1.1.11
 
+  minimatch@8.0.4:
+    dependencies:
+      brace-expansion: 2.0.1
+
   minimatch@9.0.3:
     dependencies:
       brace-expansion: 2.0.1
@@ -11304,6 +11398,16 @@ snapshots:
 
   svg-tags@1.0.0: {}
 
+  svgo@3.3.2:
+    dependencies:
+      '@trysound/sax': 0.2.0
+      commander: 7.2.0
+      css-select: 5.1.0
+      css-tree: 2.3.1
+      css-what: 6.1.0
+      csso: 5.0.5
+      picocolors: 1.0.1
+
   symbol-tree@3.2.4: {}
 
   synckit@0.8.8:

+ 1 - 0
src/assets/svg-icons/address-list.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724031018502" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4777" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M80 749.184h26.368a52.704 52.704 0 1 0 0-105.408H80v-79.04h26.368a52.704 52.704 0 1 0 0-105.44H80v-79.04h26.368a52.704 52.704 0 1 0 0-105.44H80V128a64 64 0 0 1 64-64h768a64 64 0 0 1 64 64v768a64 64 0 0 1-64 64H144a64 64 0 0 1-64-64v-146.816z m403.84-224.416c-72.736 23.424-127.36 79.04-163.84 166.976A32 32 0 0 0 349.536 736h441.6a32 32 0 0 0 29.568-44.256c-38.72-93.344-97.952-150.336-177.632-170.976a160 160 0 1 0-159.2 4z" fill="#13227a" p-id="4778"></path></svg>

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

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724031109892" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6996" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M930.6 66 223.4 66c-51.6 0-93.4 41.8-93.4 93.4L130 192 93.4 192C41.8 192 0 233.8 0 285.4l0 581.1C0 918.2 41.8 960 93.4 960l707.1 0c51.6 0 93.4-41.8 93.4-93.4L893.9 834l36.6 0c51.6 0 93.4-41.8 93.4-93.4L1023.9 159.4C1024 107.8 982.2 66 930.6 66zM830 866.6c0 16.2-13.2 29.4-29.4 29.4l-54.9 0C731 787.2 654.5 698.1 552.8 665c64.7-37.9 108.1-108.2 108.1-188.6 0-120.6-97.8-218.4-218.4-218.4s-218.4 97.8-218.4 218.4c0 80.4 43.5 150.6 108.1 188.6C230.5 698.1 154 787.2 139.3 896L93.4 896C77.2 896 64 882.8 64 866.6L64 285.4c0-16.2 13.2-29.4 29.4-29.4L130 256l670.6 0c13.7 0 25.2 9.4 28.5 22.1 0.6 2.3 0.9 4.8 0.9 7.3L830 834 830 866.6zM442.5 630.9c-41.2 0-80-16.1-109.2-45.2-29.2-29.2-45.2-67.9-45.2-109.2 0-41.2 16.1-80 45.2-109.2 29.2-29.2 67.9-45.2 109.2-45.2 41.2 0 80 16.1 109.2 45.2 29.2 29.2 45.2 67.9 45.2 109.2 0 41.2-16.1 80-45.2 109.2C522.5 614.8 483.7 630.9 442.5 630.9zM465.2 715.2c58.5 0 113.5 22.8 154.9 64.2 31.9 31.9 52.7 71.9 60.6 115.3L204.3 894.7c7.9-43.4 28.7-83.4 60.6-115.3 41.4-41.4 96.4-64.2 154.9-64.2L465.2 715.2zM960 740.6c0 16.2-13.2 29.4-29.4 29.4L894 770 894 285.4c0-51.6-41.8-93.4-93.4-93.4l0 0L194 192l0-32.6c0-16.2 13.2-29.4 29.4-29.4l707.1 0c16.2 0 29.4 13.2 29.4 29.4L959.9 740.6z" p-id="6997" fill="#1296db"></path></svg>

File diff ditekan karena terlalu besar
+ 0 - 0
src/assets/svg-icons/scheduling.svg


File diff ditekan karena terlalu besar
+ 0 - 0
src/assets/svg-icons/statistics.svg


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

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1724031383522" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15213" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M867.126867 516.953333l-416-416A52.986667 52.986667 0 0 0 413.413533 85.333333H53.333533a53.393333 53.393333 0 0 0-53.333333 53.333334v360.08a52.986667 52.986667 0 0 0 15.62 37.713333l416 416a53.333333 53.333333 0 0 0 75.426667 0l360.08-360.08a53.4 53.4 0 0 0 0-75.426667zM341.333533 341.333333a85.333333 85.333333 0 1 1-85.333333-85.333333 85.426667 85.426667 0 0 1 85.333333 85.333333z m653.793334 251.046667l-382.706667 382.706667a21.333333 21.333333 0 0 1-30.173333-30.173334l382.706666-382.706666a10.666667 10.666667 0 0 0 0-15.08L539.5802 121.753333a21.333333 21.333333 0 0 1 30.173333-30.173333l425.373334 425.373333a53.4 53.4 0 0 1 0 75.426667z" fill="#1296db" p-id="15214"></path></svg>

File diff ditekan karena terlalu besar
+ 0 - 0
src/assets/svg-icons/team-info.svg


+ 16 - 0
src/components/card-menu/CardMenu.vue

@@ -0,0 +1,16 @@
+<script lang="ts" setup></script>
+
+<template>
+  <view class="card-menu">
+    <slot></slot>
+  </view>
+</template>
+
+<style lang="scss" scoped>
+.card-menu {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 10rpx 0;
+  margin: 0 -5rpx;
+}
+</style>

+ 37 - 0
src/components/card-menu/CardMenuItem.vue

@@ -0,0 +1,37 @@
+<script lang="ts" setup>
+interface IProps {
+  width?: string
+}
+
+withDefaults(defineProps<IProps>(), {
+  width: '33.33%',
+})
+
+const emit = defineEmits(['itemClick'])
+function onClick() {
+  emit('itemClick')
+}
+</script>
+
+<template>
+  <div class="card-menu-item" :style="{ width }" @click="onClick">
+    <view class="item-icon">
+      <slot name="icon"></slot>
+    </view>
+    <view class="item-text">
+      <slot name="text"></slot>
+    </view>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.card-menu-item {
+  box-sizing: border-box;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 25%;
+  padding: 10rpx 5rpx;
+}
+</style>

+ 48 - 0
src/components/card-menu/CardMenuList.vue

@@ -0,0 +1,48 @@
+<script lang="ts" setup>
+import CardMenu from './CardMenu.vue'
+import CardMenuItem from './CardMenuItem.vue'
+interface IItem {
+  icon: string
+  text: string
+  link?: string
+  iconColor?: string
+  color?: string
+}
+interface IProps {
+  list: IItem[]
+}
+defineProps<IProps>()
+function handleItemClick(link: string) {
+  if (link) {
+    uni.navigateTo({
+      url: link,
+    })
+  }
+}
+</script>
+
+<template>
+  <div class="card-menu-list">
+    <CardMenu>
+      <template v-for="item in list" :key="item.text">
+        <CardMenuItem @itemClick="handleItemClick(item.link)">
+          <template #icon>
+            <zui-svg-icon :icon="item.icon" width="60rpx" :color="item.iconColor" />
+          </template>
+          <template #text>
+            <view class="text" :style="{ color: item.color }">{{ item.text }}</view>
+          </template>
+        </CardMenuItem>
+      </template>
+    </CardMenu>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.card-menu-list {
+  overflow: hidden;
+  background-color: #fff;
+  border-radius: 10rpx;
+  // box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
+}
+</style>

+ 11 - 0
src/components/user-card.vue

@@ -0,0 +1,11 @@
+<script lang="ts" setup></script>
+
+<template>
+  <div class="user-card">
+    <view class="card-cover"></view>
+    <view class="card-content"></view>
+    <view class="status"></view>
+  </div>
+</template>
+
+<style lang="scss" scoped></style>

+ 45 - 0
src/components/w-search/index.vue

@@ -0,0 +1,45 @@
+<template>
+  <view class="w-search">
+    <uni-easyinput
+      :inputBorder="false"
+      :style="{ color: '#333' }"
+      suffixIcon="search"
+      :value="modelValue"
+      :placeholder="placeholder"
+      @update:modelValue="changeValue"
+      @iconClick="iconClick"
+    ></uni-easyinput>
+  </view>
+</template>
+
+<script lang="ts" setup>
+interface IProps {
+  placeholder?: string
+  modelValue?: string
+  bgcolor?: string
+}
+const props = withDefaults(defineProps<IProps>(), {
+  bgcolor: '#f5f5f5',
+})
+const emit = defineEmits(['search', 'update:modelValue'])
+function iconClick() {
+  emit('search', props.modelValue)
+}
+function changeValue(value: string) {
+  emit('update:modelValue', value)
+}
+</script>
+
+<style lang="scss" scoped>
+.w-search {
+  width: 100%;
+  padding: 0 30rpx;
+  overflow: hidden;
+  background: v-bind(bgcolor);
+  border-radius: 34rpx;
+  /* stylelint-disable-next-line selector-class-pattern */
+  :deep(.uni-easyinput__content) {
+    background: v-bind(bgcolor) !important;
+  }
+}
+</style>

+ 9 - 1
src/pages.json

@@ -28,7 +28,7 @@
         "iconPath": "static/tabbar/home.png",
         "selectedIconPath": "static/tabbar/homeHL.png",
         "pagePath": "pages/index/index",
-        "text": "首页"
+        "text": "预诊大厅"
       },
       {
         "iconPath": "static/tabbar/example.png",
@@ -90,6 +90,14 @@
         "navigationStyle": "custom",
         "navigationBarTitleText": "注册"
       }
+    },
+    {
+      "path": "pages/team-managent/index",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "医疗团队管理"
+      }
     }
   ],
   "subPackages": []

+ 67 - 0
src/pages/team-managent/index.vue

@@ -0,0 +1,67 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: '医疗团队管理',
+  },
+}
+</route>
+
+<template>
+  <view class="team-management">
+    <CardMenuList :list="list" />
+  </view>
+</template>
+
+<script lang="ts" setup>
+import CardMenuList from '@/components/card-menu/CardMenuList.vue'
+
+//
+const list = ref([
+  {
+    icon: 'address-list',
+    text: '团队通讯录',
+    link: '',
+  },
+  {
+    icon: 'group',
+    text: '团队分组',
+    link: '',
+  },
+  {
+    icon: 'team-info',
+    text: '团队信息',
+    link: '',
+  },
+  {
+    icon: 'scheduling',
+    text: '团队排班',
+    link: '',
+  },
+  {
+    icon: 'statistics',
+    text: '团队统计',
+    link: '',
+  },
+  {
+    icon: 'tags',
+    text: '团队标签',
+    link: '',
+  },
+])
+</script>
+
+<style lang="scss" scoped>
+//
+.team-management {
+  box-sizing: border-box;
+  height: 100vh;
+  padding: 40rpx 24rpx 0;
+  background-color: #f5f5f5;
+}
+// #ifdef H5
+.team-management {
+  height: calc(100vh - 44px);
+}
+// #endif
+</style>

File diff ditekan karena terlalu besar
+ 25 - 0
src/static/svg-icons-lib.js


+ 24 - 0
src/svgo.config.js

@@ -0,0 +1,24 @@
+// svgo.config.js
+module.exports = {
+  multipass: true, // boolean. false by default
+  datauri: 'enc', // 'base64' (default), 'enc' or 'unenc'.
+  js2svg: {
+    indent: 2, // string with spaces or number of spaces. 4 by default
+    pretty: true, // boolean, false by default
+  },
+  plugins: [
+    // set of built-in plugins enabled by default
+    'preset-default',
+
+    // enable built-in plugins by name
+    'prefixIds',
+
+    // or by expanded notation which allows to configure plugin
+    {
+      name: 'sortAttrs',
+      params: {
+        xmlnsOrder: 'alphabetical',
+      },
+    },
+  ],
+}

+ 2 - 1
src/types/uni-pages.d.ts

@@ -9,7 +9,8 @@ interface NavigateToOptions {
        "/pages/agreement/index" |
        "/pages/forget-password/index" |
        "/pages/login/index" |
-       "/pages/register/index";
+       "/pages/register/index" |
+       "/pages/team-managent/index";
 }
 interface RedirectToOptions extends NavigateToOptions {}
 

+ 219 - 0
src/uni_modules/zui-svg-icon/changelog.md

@@ -0,0 +1,219 @@
+## 0.1.60(2024-06-05)
+
+1. 更新说明文档
+
+## 0.1.59(2024-06-05)
+
+1. 更新示例
+
+## 0.1.58(2024-06-05)
+
+1. 修改svg图标保存位置, 避免在进行微信小程序开发时将 svg 图标文件也打包在内, 以减小整体包体积
+
+## 0.1.57(2024-05-10)
+
+1. 更新图标大小设置说明
+
+## 0.1.56(2024-04-14)
+
+1. 更新示例, 增加图标大小调整示例
+
+## 0.1.55(2024-01-22)
+
+1. 回滚版本至: 0.1.51, 在未明确bug原因时盲目修改导致了新问题.
+
+## 0.1.51(2023-12-06)
+
+1. 重新编写使用文档
+
+## 0.1.50(2023-12-06)
+
+1. 当图标不支持换色, 却设置了颜色值时给出警告提示
+
+## 0.1.49(2023-11-28)
+
+1. 修复分组图标无法显示的 bug
+
+## 0.1.48(2023-11-23)
+
+1. 新增高度属性 height, 设置图标尺寸更直观
+2. 新增 rpx 单位支持
+
+## 0.1.46(2023-11-19)
+
+1. 修复宽高比计算错误的问题
+
+## 0.1.45(2023-11-15)
+
+1. 优化图标名称定义,除正反斜线(/ 和 \)会被替换为短横线(-)以外,其余字符保持和图标文件名称一致。
+
+## 0.1.44(2023-11-13)
+
+1. 修复上一版测试代码导致插件不工作的问题
+
+## 0.1.43(2023-11-13)
+
+1. 修复windows平台无法加载分组图标的 bug
+2. 增加图标无限级分组功能
+
+## 0.1.41(2023-11-04)
+
+1. 修复在 SVG 预处理时可能导致程序异常中止的 bug
+
+## 0.1.40(2023-11-03)
+
+1. 增加图标库功能,支持导入外部图标库
+
+## 0.1.39(2023-10-20)
+
+1. 修复示例文件静态图标路径不正确的问题
+
+## 0.1.38(2023-10-19)
+
+1. 修复启动微信小程序开发第一次运行时报错的问题
+
+## 0.1.37(2023-10-08)
+
+1. 增加图标分组功能。基于文件夹对图标进行分组管理
+
+## 0.1.36(2023-10-08)
+
+1. 补充 cli 项目使用说明
+
+## 0.1.35(2023-09-26)
+
+1. icon 参数增加对 svg 源码字符串的支持
+
+## 0.1.34(2023-09-11)
+
+1. 增加颜色锁定功能
+
+## 0.1.33(2023-09-10)
+
+1. 修复了当 svg 图片中没有颜色定义,但在使用中又指定了颜色时导致图片无法显示的问题
+
+## 0.1.32(2023-09-04)
+
+1.  增强组件的点击事件兼容处理,同时支持 @click 和 @tap 事件
+
+## 0.1.31(2023-09-01)
+
+1. 修复当 svg 文件使用 currentColor 时导致无法替换颜色的问题
+
+## 0.1.30(2023-08-30)
+
+1. 修复提取颜色时会漏掉一些颜色的问题
+2. 优化对纯黑色的处理
+3. 修复渲染时可能导致渲染失败的问题
+4. 优化示例
+
+## 0.1.26(2023-08-26)
+
+小更新
+
+## 0.1.25(2023-08-26)
+
+1. 优化组件结构
+2. 修复支付宝小程序无法点击的 bug
+3. 优化使用说明
+
+##### 示例页面
+
+1. 修复示例页面文案错误
+2. 修复圆角滑块抖动的 bug
+
+## 0.1.23(2023-08-22)
+
+1. 增加图标圆角设置
+2. 修复示例项目中点击 slider 不更新的问题
+
+## 0.1.22(2023-08-21)
+
+1. 优化未定义的图标处理方式。未定义的图标不会再阻断程序运行。
+
+## 0.1.21(2023-08-18)
+
+1. 修复了由于 svgo 将纯黑色优化掉后导致图标无法改色的 bug
+2. 优化对 svg 文件 class 定义识别处理
+3. 修复了 vue3 点击事件处理
+4. 其它修改
+
+## 0.1.18(2023-08-17)
+
+1. 修复微信小程序、飞书小程序和 QQ 小程序 click/tap 事件会重复触发的 bug
+2. 修复颜色识别 bug
+3. 增加 spin 效果
+4. 增加 rgba/hsl 颜色格式支持
+5. 更新演示图标
+
+## 0.1.15(2023-08-15)
+
+1. 优化事件处理
+2. 增加普通图片文件图标支持, 但不支持此类图标修改颜色
+3. 增加灰度参数设置
+
+## 0.1.14(2023-08-12)
+
+1. 更新示例
+
+## 0.1.13(2023-08-12)
+
+1. 添加渐变色图标示例
+
+## 0.1.12(2023-08-12)
+
+1. 修复生成工具不更新的bug;
+2. 增加点击事件响应;
+3. 更新示例;
+   PS: 升级插件后需要重新生成图标库;
+
+## 0.1.10(2023-08-11)
+
+1. 更新小程序兼容性列表
+
+## 0.1.9(2023-08-11)
+
+1. 添加支付宝小程序支持
+
+## 0.1.8(2023-08-10)
+
+1. 添加使用 class 定义颜色的支持
+2. 添加渐变颜色支持
+3. 优化颜色替换机制
+4. 清理不必要的文件
+
+## 0.1.7(2023-08-10)
+
+1. 修复demo地址错误
+
+## 0.1.6(2023-08-10)
+
+1. 添加小程序兼容性说明
+2. 增加demo页面
+
+## 0.1.5(2023-08-09)
+
+1. 修复在APP和小程序端图标高度显示不正确的bug
+2. 添加参数说明文档
+3. 其它修改
+
+## 0.1.4(2023-08-08)
+
+1. 添加小程序支持
+2. 修复多色填充bug
+3. 图标库生成工具只有在数据变化后才重新写入
+
+## 0.1.2(2023-08-07)
+
+1. 添加svgo配置
+2. 修复生成图标库时路径不正确的问题
+3. VUE3 支持
+
+## 0.1.1(2023-08-07)
+
+1. 更新 readme.md
+
+## 0.1.0(2023-08-07)
+
+1. 单色SVG图标显示与换色
+2. 多色SVG图标显示与换色

+ 358 - 0
src/uni_modules/zui-svg-icon/components/zui-svg-icon/zui-svg-icon.vue

@@ -0,0 +1,358 @@
+<template>
+  <view class="zui-svg-icon">
+    <view :class="clazz" :style="style">
+      <!-- #ifndef H5 -->
+      <view class="click-helper" @click="doClick" @tap="doTap"></view>
+      <!-- #endif -->
+
+      <image class="zui-svg-icon-image" :src="svgDataurl" mode="aspectFit"></image>
+    </view>
+  </view>
+</template>
+
+<script>
+import SvgIconLib from '@/static/svg-icons-lib.js'
+import { rpx2px } from '../../utils/utils'
+
+export default {
+  name: 'zui-svg-icon',
+
+  components: {},
+
+  props: {
+    icon: {
+      type: String,
+      required: true,
+    },
+
+    /**
+     * 图标颜色
+     *
+     * 单色图标: '#FFF'
+     *
+     * 多色图标: ['#FFF', '#FFF', '#FFF'], 颜色数量需要匹配
+     */
+    color: [String, Array],
+
+    width: {
+      type: [Number, String],
+      default: '1.2em',
+    },
+
+    height: {
+      type: [Number, String],
+      default: undefined,
+    },
+
+    /**
+     * 图标灰度系数
+     * boolean true =>  1
+     * number => [0, 1]
+     */
+    gray: {
+      type: [Boolean, Number],
+      default: false,
+    },
+
+    /**
+     * 图标旋转
+     *
+     * Boolean, true 旋转, 旋转一周时间 1s; false 不旋转, 默认值
+     * Number, 旋转一周所需要时间. > 0 顺时针旋转; < 0 逆时针旋转;
+     */
+    spin: {
+      type: [Number, Boolean],
+      default: false,
+    },
+
+    /**
+     * 圆角设置
+     */
+    borderRadius: [Number, String],
+
+    /**
+     * 图标集合
+     */
+    collection: {
+      type: String,
+      default: 'default',
+    },
+
+    /**
+     * 宽高比
+     * aspectRatio = width / height
+     *
+     * @deprecated  直接使用宽高设置尺寸
+     *
+     */
+    aspectRatio: {
+      type: Number,
+      default: undefined,
+    },
+  },
+
+  data() {
+    return {
+      isFilled: false,
+      colorMap: {},
+      colorPlaceholder: null,
+      isColorCountMatch: true,
+    }
+  },
+
+  computed: {
+    cWidth() {
+      const wid = /rpx$/i.test(this.width) ? rpx2px(this.width, true) : this.width
+      return typeof wid === 'number' ? `${wid}px` : wid
+    },
+
+    cHeight() {
+      if (!this.height) {
+        if (this.aspectRatio) {
+          const unit = `${this.cWidth}`.replace(/[\d.]+/g, '')
+          const hei = parseFloat(this.cWidth) / this.aspectRatio
+
+          return `${hei}${unit}`
+        } else {
+          return this.cWidth
+        }
+      }
+
+      const hei = /rpx$/i.test(this.height) ? rpx2px(this.height, true) : this.height
+      return typeof hei === 'number' ? `${hei}px` : hei
+    },
+
+    svgIconLib() {
+      return SvgIconLib.getCollection(this.collection || 'default')
+    },
+
+    /**
+     * 是否文件来源
+     *
+     * 包含 url, svg原始字符串, 未进行 base64 编码的 data:image/svg+xml uri
+     */
+    isFileSource() {
+      if (/^https?\:\/\//i.test(this.icon)) return true
+      if (/^data:image\//i.test(this.icon)) return true
+      if (/\.svg([?#].*)?$/i.test(this.icon)) return true
+      if (this.icon.indexOf('/') > -1) return true
+
+      return false
+    },
+
+    svgRaw() {
+      if (this.isFileSource) return this.icon
+
+      const iconId = this.icon.toLowerCase()
+      const iconPreset = this.svgIconLib.icons[iconId]
+      if (!iconPreset) {
+        console.warn(`Svg icon [${iconId}] not defined and no fallback icon set.`)
+        return
+      }
+      let svg = iconPreset[0]
+
+      if (this.color && this.isColorCountMatch) {
+        svg = svg.replace(this.colorPlaceholder, (_, a, b) => {
+          return this.colorMap[a.toLowerCase()] + b
+        })
+      }
+
+      return svg
+    },
+
+    svgDataurl() {
+      if (!this.isFileSource) {
+        return `data:image/svg+xml,${encodeURIComponent(this.svgRaw)}`
+      }
+
+      if (/^data:image\/svg\+xml,<svg/i.test(this.icon)) {
+        return `data:image/svg+xml,${encodeURIComponent(this.icon.substring(19))}`
+      }
+
+      if (/^<svg/i.test(this.icon)) {
+        return `data:image/svg+xml,${encodeURIComponent(this.icon)}`
+      }
+
+      return this.icon
+    },
+
+    clazz() {
+      const clazz = ['zui-svg-icon-wrapper']
+      if (this.spin && this.spin > 0) clazz.push('rotate-clockwise')
+      if (this.spin && this.spin < 0) clazz.push('rotate-counterclockwise')
+      // 必须转换成字符串, 不然 支付宝小程序 会以逗号连接类名导致错误
+      return clazz.join(' ')
+    },
+
+    style() {
+      const style = {
+        '--zui-svg-icon-width': this.cWidth,
+        '--zui-svg-icon-height': this.cHeight,
+      }
+
+      if (this.borderRadius) {
+        let br = this.borderRadius
+        if (typeof this.borderRadius === 'string') {
+          if (!/[^a-z%]/i.test(this.borderRadius)) {
+            const v = parseFloat(this.borderRadius)
+            if (v < 1) {
+              br = `${v * 100}%`
+            } else {
+              br = `${v}px`
+            }
+          }
+        } else {
+          if (this.borderRadius < 1) {
+            br = `${this.borderRadius * 100}%`
+          } else {
+            br = `${this.borderRadius}px`
+          }
+        }
+        style['--zui-svg-icon-border-radius'] = br
+      }
+
+      if (this.gray) {
+        if (typeof this.gray === 'number') {
+          style['filter'] = `grayscale(${this.gray})`
+        } else {
+          style['filter'] = 'grayscale(1)'
+        }
+      }
+
+      if (this.spin) {
+        const rotateDur = this.spin === true ? 5 : Math.abs(this.spin)
+        style['--zui-svg-icon-rotate-duration'] = `${rotateDur}s`
+      }
+
+      return Object.keys(style)
+        .map((key) => `${key}:${style[key]}`)
+        .join('; ')
+    },
+  },
+
+  watch: {
+    icon() {
+      this.initialIcon()
+    },
+    color() {
+      this.initialIconColor()
+    },
+  },
+
+  mounted() {
+    this.initialIcon()
+  },
+
+  methods: {
+    doClick(evt) {
+      // #ifdef MP-ALIPAY || MP-DINTTALK || MP-DINGDING
+      this.$emit('tap', evt)
+      // #endif
+      setTimeout(() => {
+        this.$emit('click', evt)
+      }, 1)
+    },
+
+    doTap(evt) {
+      setTimeout(() => {
+        this.$emit('click', evt)
+      }, 1)
+    },
+
+    initialIconColor() {
+      // if (this.isFileSource && !!this.color) {
+      //   console.warn(`<zui-svg-icon /> 使用了未经过预处理的图标格式, 将不支持更换颜色. 未经过预处理的图标格式包括: URI, base64 图片, 原始SVG代码`)
+      // }
+      // Initial color map
+      const oriColors = this.getOriginalColors()
+      if (this.color && oriColors.length) {
+        const newColors = typeof this.color === 'string' ? this.color.split(',') : this.color
+        this.colorPlaceholder = new RegExp(
+          `(${oriColors.map((item) => item.replace(/([\(\)])/g, '\\$1')).join('|')})([^\\w])`,
+          'gi',
+        )
+        this.colorMap = oriColors.reduce((a, b, idx) => {
+          return {
+            ...a,
+            [b.toLowerCase()]: newColors[idx] || oriColors[idx],
+          }
+        }, {})
+        this.isColorCountMatch = oriColors.length === newColors.length
+      } else {
+        this.colorPlaceholder = null
+        this.colorMap = null
+        this.isColorCountMatch = true
+      }
+    },
+
+    initialIcon() {
+      this.initialIconColor()
+    },
+
+    getOriginalColors() {
+      const iconPreset = this.svgIconLib.icons[this.icon]
+      return iconPreset ? iconPreset.slice(1).map((idx) => this.svgIconLib.$_colorPalette[idx]) : []
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@keyframes rotateClockwise {
+  0% {
+    transform: rotate(0);
+  }
+
+  100% {
+    transform: rotate(360deg);
+  }
+}
+@keyframes rotateCounterclockwise {
+  0% {
+    transform: rotate(360deg);
+  }
+
+  100% {
+    transform: rotate(0);
+  }
+}
+.zui-svg-icon {
+  position: relative;
+  display: inline-flex;
+}
+.zui-svg-icon-wrapper {
+  position: relative;
+  display: inline-flex;
+  justify-content: center;
+  align-items: center;
+  width: var(--zui-svg-icon-width);
+  height: var(--zui-svg-icon-height);
+  line-height: 1;
+  vertical-align: middle;
+  border-radius: var(--zui-svg-icon-border-radius, 0);
+  overflow: hidden;
+
+  .zui-svg-icon-image {
+    width: 100%;
+    height: 100%;
+    vertical-align: middle;
+  }
+
+  &.rotate-clockwise {
+    animation: rotateClockwise var(--zui-svg-icon-rotate-duration, 5s) linear infinite;
+  }
+  &.rotate-counterclockwise {
+    animation: rotateCounterclockwise var(--zui-svg-icon-rotate-duration, 5s) linear infinite;
+  }
+}
+
+.click-helper {
+  position: absolute;
+  z-index: 10;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  opacity: 0;
+}
+</style>

+ 84 - 0
src/uni_modules/zui-svg-icon/package.json

@@ -0,0 +1,84 @@
+{
+  "id": "zui-svg-icon",
+  "displayName": "多彩SVG图标,自由换色,全端适配",
+  "version": "0.1.60",
+  "description": "一款适用于 uni-app 的 SVG 图标组件。全端适配:APP-Plus、小程序、H5。支持多色SVG图标;支持换色;支持URI和Base64格式;支持spin动画;SVG优化",
+  "keywords": [
+    "svg",
+    "icon",
+    "图标",
+    "彩色图标",
+    "换色"
+  ],
+  "repository": "https://github.com/zivyuan/zui-svg-icon.git",
+  "engines": {},
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y",
+        "alipay": "n"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "y",
+          "vue3": "y"
+        },
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "n"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "n",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "y",
+          "钉钉": "y",
+          "快手": "u",
+          "飞书": "y",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 223 - 0
src/uni_modules/zui-svg-icon/readme.md

@@ -0,0 +1,223 @@
+# &lt;zui-svg-icon /&gt;
+
+一款适用于 uni-app 的 SVG 图标组件。
+
+**解决了 uni-app 在 APP 端中无法使用 SVG标签的问题**
+
+- 支持单色、多色 SVG 图标换色;
+- 支持图片 URI 地址
+- 支持 Base64 图片格式
+- 支持 spin 动画,可自定义旋转方向与速度
+- SVG 图片优化。
+- 图标库支持
+
+## 在线演示
+
+**[💻 点我在浏览器里预览 https://uni.imgozi.cn/zui-svg-icon/](https://uni.imgozi.cn/zui-svg-icon/?utm_source=uni-plugin-market&utm_medium=readme&utm_campaign=zui-svg-icon&utm_id=uni-plugin)**
+
+PS: 启动浏览器预览需要打开手机模器
+
+**[📱 扫码体验](https://uni.imgozi.cn/zui-svg-icon/?utm_source=uni-plugin-market&utm_medium=readme&utm_campaign=zui-svg-icon&utm_id=uni-plugin)**
+
+<img src="https://uni.imgozi.cn/zui-svg-icon/static/preview-qr.png" width="128" />
+
+## 快速上手:
+
+### 〇、生成图标库
+
+```shell
+npm rum svgicon
+```
+
+> 第一次使用请先参考下方 **图标库管理** 章节
+
+### 一、使用
+
+```vue
+<!-- 单色图标 -->
+<zui-svg-icon icon="doc" color="#FF0000" />
+<!-- 多色图标 -->
+<zui-svg-icon icon="doc" :color="['#FF0000', '#00FF00', '#0000FF']" />
+```
+
+#### 项目启动时出现错误
+
+当项目启动时出现如下错误时, 先随便编辑一个文件并保存,让编译器进行一次编译后即可正常运行。
+
+出现错误的原因是项目在启动时没有对图标库文件进行正确的转译造成的。
+
+```
+<!--  -->
+SyntaxError: Unexpected token 'export'
+<!--  -->
+Error: module 'static/svg-icons-lib.js' is not defined, require args is '../../../../static/svg-icons-lib.js'
+```
+
+## 图标库管理
+
+图标库依赖 [svgo](https://www.npmjs.com/package/svgo) 插件对 svg 图标进行优化压缩,所以在所有工作开始前应该先安装 svgo 依赖。
+
+> HBuilderX 项目和 cli 项目在路径上有所区别,请根据自己项目类型合理使用。
+
+### 〇、安装必要依赖
+
+```
+npm i svgo@latest
+```
+
+如果最新版本在使用中出现异常,可以将 svgo 回退到插件开发环境使用的版本 `svgo@^3.0.5`。
+
+### 一、配置 npm 脚本
+
+在项目 `package.json` 中添加脚本。
+
+HBuilderX 项目
+
+```json
+"svgicon": "node ./uni_modules/zui-svg-icon/tools/generate-svg-icon.js"
+```
+
+cli 项目
+
+```json
+"svgicon": "node ./src/uni_modules/zui-svg-icon/tools/generate-svg-icon.js"
+```
+
+### 二、准备图片
+
+将 svg 图片复制到指定的目录
+
+HBuilderX 项目
+
+```shell
+./assets/svg-icons/
+```
+
+cli 项目
+
+```shell
+./src/assets/svg-icons/
+```
+
+> 在 svg 目录中可以对图标按文件夹进行分组管理
+
+### 三、生成图标库
+
+运行图标库生成脚本
+
+```shell
+npm run svgicon
+```
+
+生成图标库时,图标名称会与svg 文件名保持一致。如果对图标库进行了分组管理,则图标名称前面会添加目录名称作为前缀。
+
+例如:`browser/chrome-muti-color.svg` 生成后的图标名称为:`browser-chrome-muti-color`
+
+建议图标名称只使用 **英文、数字和连接线** 的组合。
+
+对于多色图标,生成工具会在生成报告中输出颜色序列,便于在项目中动态替换以实现图标换色功能。
+
+生成报告示例:
+
+```shell
+Add    ppt
+       [ '#f2733d', '#f3b2a6', '#fff' ]
+Update xls
+       [ '#47b347', '#e0efdc' ]
+Total 2 svg icon(s) generated, 1 added, 0 deleted.
+```
+
+> **除了自定义图标,现在还可以使用现成的图标库,比如这个:** <span class="banner"> > <span class="surport"> > <a href="https://ext.dcloud.net.cn/plugin?id=15226" target="_blank" class="btn btn-support" style="border: 1px solid #ec4d4d;letter-spacing: 1px;">
+> Material图标集合 图标组件 SVG 图标库
+> </a> > </span> > </span>
+
+### 四、使用
+
+```vue
+<zui-svg-icon icon="my-icon" />
+```
+
+> 在新增 svg 图标或更新svg 图标后都需要按步骤三重新生成图标库
+
+<span class="banner">
+<span class="surport">
+<a class="btn btn-support " data-toggle="modal" data-target="#support_modal" style="border: 1px solid #ec4d4d;letter-spacing: 1px;">
+  🍓🍇🍉 好用就打赏一下  🍒🍑🥭
+</a>
+</span>
+</span>
+
+## 参数说明
+
+| 参数         | 类型                          | 说明                                                                                        |
+| ------------ | ----------------------------- | ------------------------------------------------------------------------------------------- |
+| icon         | string                        | 图标 id, 同图标文件名                                                                       |
+|              | string                        | svg 源码字符串 _(不支持改色)_                                                               |
+|              | string                        | 图片 URI _(不支持改色)_                                                                     |
+|              | string                        | Data URI _(不支持改色)_                                                                     |
+| color        | string                        | 单色图标颜色                                                                                |
+|              | string[]                      | 多色图标颜色, 颜色种类必须与图标中的种类一致<br/>[了解多色图标👇🏻](#❤️‍🔥-多色图标)             |
+| width        | string \| number              | 图标宽度. 默认 1.2em.                                                                       |
+| height       | undefined \| string \| number | 图标调试. 默认 undefined, 即表示图标宽高相同, 是正方形.                                     |
+| gray         | number                        | 灰度显示系数, [0, 1]. 默认为 0, 不开启                                                      |
+|              | boolean                       | 灰度显示系数. true => 1, false => 0                                                         |
+| spin         | boolean                       | 是否启用 spin 动画。默认 false                                                              |
+|              | number                        | 动画时间。默认 5s,顺时针旋转;指定为负数时逆时针旋转;为 0 时禁用旋转效果,即 spin=false。 |
+| borderRadius | number                        | 圆角数值,单位:像素。当输入值小于1当,作百分比处理                                         |
+|              | string                        | CSS 允许的值                                                                                |
+
+> 说明:
+> 当 icon 配置为 svg 源码字符串,图片URI,Data URI 时,图标不支持修改颜色
+
+## 事件
+
+#### @click=(evt: Event) => void
+
+#### @tap=(evt: Event) => void
+
+组件针对小程序运行环境做了兼容处理,下表中列表已经做过兼容的小程序及映射的事件类型。
+
+| 小程序 | @click | @tap  | @click & @tap |
+| ------ | ------ | ----- | ------------- |
+| H5     | Click  | Click | Click, Click  |
+| 飞书   | Tap    | Tap   | Tap, Click    |
+| QQ     | Tap    | Tap   | Tap, Click    |
+| 钉钉   | Tap    | Tap   | Tap, Click    |
+| 支付宝 | Tap    | Tap   | Tap, Click    |
+| 微信   | Tap    | Tap   | Tap, Click    |
+
+在 H5 环境下,如果同时指定 @click 和 @tap 事件,两个事件的触发顺序由书写顺序确定。
+
+比如:`<zui-svg-icon @click @tap />` 是先触发 @click,再触发 @tap。
+
+**PPPS:由于事件是兼容性处理,@click 和 @tap 事件里的 target 对象可能不一致,使用 target 时需要特别注意这个情况**
+
+<span class="banner">
+<span class="surport">
+<a class="btn btn-support " data-toggle="modal" data-target="#support_modal" style="border: 1px solid #ec4d4d;letter-spacing: 1px;">
+  🍓🍇🍉 好用就打赏一下  🍒🍑🥭
+</a>
+</span>
+</span>
+
+## 特色功能
+
+### 颜色锁定
+
+在配置 color 时,将对应位置的颜色设置为空时,将保留原来的颜色。
+
+『空值』是以下值中的任意一种:
+
+`空字符串, null, undefined, false, 0`
+
+## 🍓 支持
+
+如果组件对您有帮助,请不吝打赏。肥宅快乐水🥤是创作动力!🥤🥤🥤
+
+<span class="banner">
+<span class="surport">
+<a class="btn btn-support " data-toggle="modal" data-target="#support_modal" style="border: 1px solid #ec4d4d;letter-spacing: 1px;">
+  🍓🍇🍉 好用就打赏一下  🍒🍑🥭
+</a>
+</span>
+</span>

+ 1 - 0
src/uni_modules/zui-svg-icon/static/zui-svg-icon/zui-svg-icon-placeholder.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"/>

+ 270 - 0
src/uni_modules/zui-svg-icon/tools/generate-svg-icon.js

@@ -0,0 +1,270 @@
+#!/usr/bin/env node
+
+/**
+ * SVG 图标组件生成器
+ *
+ * 转换 SVG 图标为 inline 数据
+ *
+ */
+
+const fs = require('fs')
+const path = require('path')
+const readline = require('readline')
+const cliInput = prompt => {
+  return new Promise((resolve, reject) => {
+    const rl = readline.createInterface({
+      input: process.stdin,
+      output: process.stdout,
+    })
+    rl.question(prompt, ipt => {
+      resolve(ipt)
+      rl.close()
+    })
+  })
+}
+const { optimize } = require('svgo')
+
+const parseOptions = () => {
+  const argv = process.argv.slice(2)
+  const opts = {}
+  argv.forEach(arg => {
+    if (arg.indexOf('=') > -1) {
+      const o = arg.split('=')
+      opts[o[0]] = o[1]
+    } else {
+      opts[arg] = true
+    }
+  })
+  return opts
+}
+
+const options = parseOptions()
+
+const regColorFormat = /#([0-9A-F]{3}|[0-9A-F]{6}|[0-9A-F]{8})|(?:rgb|hsl|hwb|lab|lch|oklab|oklch)a?\([\d.,\/%]+\)/i
+const regCurrentColor = /([:"'] *)currentColor/g
+
+const root = path.resolve(__dirname + '/../../..')
+if (fs.existsSync(root + '/src')) {
+  root = root + '/src'
+}
+const svgo = root + '/svgo.config.js'
+if (!fs.existsSync(svgo)) {
+  fs.copyFileSync(__dirname + '/svgo.config.js', svgo)
+}
+
+// 需要处理的颜色属性
+let svgBase = root + '/assets'
+const svgFolder = options.source || svgBase + '/svg-icons'
+
+if (!fs.existsSync(svgFolder)) {
+  fs.mkdirSync(svgFolder, { recursive: true })
+}
+const svgLibFile = root + `/static/${options.lib || 'svg-icons-lib'}.js`
+
+const svgLibCurrent = (() => {
+  try {
+    let raw = fs.readFileSync(svgLibFile, { encoding: 'utf-8' })
+    const start = raw.indexOf('const collections = {') + 20
+    const end = raw.indexOf('// == collection end')
+    raw = raw.substring(start, end).trim().replace(/;$/, '')
+    return JSON.parse(raw).default
+  } catch (err) {}
+  return {}
+})()
+const svgPath = path.resolve(svgFolder)
+const svgLib = {}
+const svgList = (() => {
+  const regFile = /\.svg$/i
+  const fileList = []
+  const loadSvgList = searchPath => {
+    const files = fs.readdirSync(searchPath, { recursive: false })
+    for (const file of files) {
+      const filePath = path.posix.join(searchPath, file)
+      const stat = fs.statSync(filePath)
+      if (stat.isFile()) {
+        if (!regFile.test(filePath)) continue
+
+        const item = filePath.slice(filePath.lastIndexOf('svg-icons/') + 10)
+        // const name = item.slice(0, -4).replace(/[/!@#$%^&*()+=\[\]{};:'",.<>\?`]/g, '-').toLowerCase()
+        const name = item.slice(0, -4).replace(/[\/\\]/g, '-').toLowerCase()
+        const content = fs.readFileSync(filePath, {
+          encoding: 'utf-8',
+        })
+        fileList.push({
+          name,
+          content,
+          hasCurrentColor: regCurrentColor.test(content),
+          file: filePath,
+        })
+      }
+      //
+      else if (stat.isDirectory()) {
+        loadSvgList(filePath)
+      }
+    }
+    return fileList
+  }
+
+  return loadSvgList(svgPath).filter(item => !!item)
+})(svgPath)
+
+//
+const defaultColor = '#22ac38'
+let currentColor = svgLibCurrent.currentColor || ''
+let palette = []
+
+const generateIcon = svgRaw => {
+  // svgo 会过滤纯黑, 此处对纯黑做简单处理
+  svgRaw = svgRaw.replace(regCurrentColor, `$1${currentColor}`).replace(/#0{3,8}/g, '#ZZZZZZ')
+  const result = optimize(svgRaw, {
+    multipass: true,
+  })
+  result.data = result.data.replace(/#Z{3,8}/gi, '#000')
+
+  const regColor = /(fill|stroke|stop-color):([^;}]+)/g
+  const parseColor = colorStr => {
+    if (!regRef.test(colorStr)) {
+      return colorStr
+    }
+    // 从 Gradient 引用里获取颜色
+    const match = colorStr.match(regRef)
+    const ref = gradients.find(item => {
+      return item.id === match[1]
+    })
+    return ref ? ref.colors : []
+  }
+
+  // Step 1, find all Gradient define and make KV map
+  const regGradient = /<(\w+Gradient) id="([^"]+)" [^>]+>(.+?)<\/\1>/g
+  const regStopColors = /stop-color="([^"]+)"/g
+  const gradients = [...result.data.matchAll(regGradient)].map(item => {
+    const colors = [...item[3].matchAll(regStopColors)].map(item => item[1])
+    return {
+      id: item[2],
+      content: item[3],
+      colors,
+    }
+  })
+
+  // Step 2, find all class define and make KV map
+  const regClass = /\.(cls-\d+)\{([^}]+)\}/g
+  const regRef = /url\(#(.+)\)/
+  const classes = [...result.data.matchAll(regClass)].map(item => {
+    // Search colors from item[2]
+    // find fill, stroke, stop-color
+    const colors = [...item[2].matchAll(regColor)].map(item => parseColor(item[2]))
+
+    return {
+      id: item[1],
+      content: item[2],
+      colors: colors,
+    }
+  })
+
+  // Step 3, find all style, class, stroke property and search color in value
+  const regProps = /(fill|stroke|class|style)="([^"]+)"/g
+  const props = [...result.data.matchAll(regProps)].map(content => {
+    let colors = []
+    if (content[1] === 'class') {
+      const item = classes.find(item => item.id === content[2])
+      colors = item ? item.colors : []
+    } else if (content[1] === 'style') {
+      colors = [...content[2].matchAll(regColor)].map(item => parseColor(item[2]))
+    } else if (content[1] === 'fill') {
+      colors = parseColor(content[2])
+    } else {
+      colors = content[2]
+    }
+    return {
+      prop: content[1],
+      content: content[2],
+      // 定义里的颜色
+      colors: colors,
+    }
+  })
+
+  // Step 4, filter
+  let colors = props
+    .map(item => item.colors)
+    .flat(2)
+    .filter(item => item !== 'none' && !/^url/.test(item))
+    .map(item => (item === 'currentColor' ? currentColor : item))
+  colors = Array.from(new Set(colors))
+
+  // Append new colors to palette
+  palette = Array.from(new Set([...palette, ...colors]))
+
+  // Build color index
+  let colorMap = colors.map(c => palette.indexOf(c))
+  const colorTotal = colors.length
+
+  if (colorTotal === 0) {
+    const fixable = /<(path|circle|ellipse|polygon|polyline|rect) /g
+    if (fixable.test(result.data)) {
+      return generateIcon(result.data.replace(fixable, `<$1 fill="${currentColor || defaultColor}" `))
+    } else {
+      console.log('  SVG 图片没有配置颜色, 并且无法进行预处理。请联系作者修复此问题。https://ext.dcloud.net.cn/plugin?id=13964')
+    }
+  } else if (colorTotal > 0) {
+    console.log('    ', JSON.stringify(colors))
+  }
+
+  return [result.data, ...colorMap]
+}
+
+;(async () => {
+  // 检测是否存在 currentColor
+  const hasCurrentColor = svgList.find(item => item.hasCurrentColor)
+  if (!currentColor && hasCurrentColor) {
+    console.log('\n')
+    console.log('::>> 检测到 svg 文件中使用了 currentColor 变量,该变量在组件中不被支持。\n')
+    currentColor = defaultColor
+    console.log(`::>> 需要指定一个颜色替代,默认黑色为(${currentColor})。\n`)
+
+    do {
+      const color = await cliInput(`请输入颜色,直接回车(enter)使用默认值:`)
+      if (color && color.length && !regColorFormat.test(color)) {
+        console.log('\n::>> 颜色格式不正确,请输入以下格式的颜色值:\n')
+        console.log('::>>', ['#000', '#000000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)'].join('   '), '\n')
+      } else {
+        currentColor = color && color.length ? color.replace(/ /g, '') : defaultColor
+      }
+    } while (!currentColor)
+  }
+
+  svgList.forEach(item => {
+    console.log(item.name)
+    svgLib[item.name] = generateIcon(item.content)
+  })
+
+  const data = {
+    icons: JSON.parse(JSON.stringify(svgLib)),
+    currentColor,
+    $_colorPalette: palette,
+  }
+
+  const hasChange = JSON.stringify(svgLibCurrent) !== JSON.stringify(data)
+  if (hasChange) {
+    const scriptTpl = fs.readFileSync(__dirname + '/svg-icons-lib.tpl.js', {
+      encoding: 'utf-8',
+    })
+    const params = {
+      datetime: `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`,
+      default: JSON.stringify(data, null, 2).split('\n').join('\n  '),
+    }
+    const script = scriptTpl.replace(/__(\w+)__/g, (_, key) => {
+      return params[key] || _
+    })
+
+    fs.writeFileSync(svgLibFile, script)
+    console.log(`\nTotal ${Object.keys(svgLib).length} svg icon(s) generated.`)
+  } else {
+    console.log(`\nTotal ${Object.keys(svgLib).length} svg icon(s) generated, nochange.`)
+  }
+
+  if (hasCurrentColor) {
+    console.log('\n')
+    console.log('  当前有使用到 currentColor 变量,可通过文件 static/svg-icons-lib.js 里的 currentColor 属性进行修改。')
+    console.log('\n')
+  }
+})()

+ 40 - 0
src/uni_modules/zui-svg-icon/tools/svg-icons-lib.tpl.js

@@ -0,0 +1,40 @@
+/**
+ *
+ * Icon Library for <zui-svg-icon> usage
+ *
+ * Auto generated by /tools/generate-svg-icon.js
+ *
+ * !!! DO NOT MODIFY MANUALLY !!!
+ *
+ * @datetime __datetime__
+ *
+ */
+
+// == collection start
+const collections = {
+  default: __default__,
+}
+// == collection end
+
+const svglib = {}
+
+svglib.registerCollection = (key, lib) => {
+  if (collections[key]) {
+    return
+  }
+
+  if (typeof lib.registerCollection === 'function') {
+    collections[key] = lib.getCollection('default')
+  } else {
+    collections[key] = lib
+  }
+}
+
+svglib.getCollection = (key = 'default') => {
+  if (!collections[key]) throw new Error(`没有找到名为 ${key} 的图标库。`)
+
+  return collections[key]
+}
+
+export const SvgIconLib = svglib
+export default SvgIconLib

+ 24 - 0
src/uni_modules/zui-svg-icon/tools/svgo.config.js

@@ -0,0 +1,24 @@
+// svgo.config.js
+module.exports = {
+  multipass: true, // boolean. false by default
+  datauri: 'enc', // 'base64' (default), 'enc' or 'unenc'.
+  js2svg: {
+    indent: 2, // string with spaces or number of spaces. 4 by default
+    pretty: true, // boolean, false by default
+  },
+  plugins: [
+    // set of built-in plugins enabled by default
+    'preset-default',
+
+    // enable built-in plugins by name
+    'prefixIds',
+
+    // or by expanded notation which allows to configure plugin
+    {
+      name: 'sortAttrs',
+      params: {
+        xmlnsOrder: 'alphabetical',
+      },
+    },
+  ],
+}

+ 11 - 0
src/uni_modules/zui-svg-icon/utils/utils.js

@@ -0,0 +1,11 @@
+const _screenWidth = uni.getSystemInfoSync().screenWidth;
+/**
+ * rpx 单位转换为 px
+ *
+ * @param {number | string} rpx 待转换的数值
+ * @returns number
+ */
+export const rpx2px = (rpx, unit = false) => {
+  const px = (_screenWidth * Number.parseFloat(rpx)) / 750;
+  return unit ? `${px}px` : px;
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini