shizhongqi 1 年間 前
コミット
0640ce01ea

+ 129 - 0
hdApp/CLAUDE.md

@@ -0,0 +1,129 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+This is **花掌柜 (huā zhǎng guì - Flower Shop Manager)**, a comprehensive flower shop management system built with UniApp. It's a cross-platform application targeting WeChat Mini Programs, H5 web apps, and native mobile apps (iOS/Android).
+
+## Key Features
+
+- **Inventory Management**: Flower materials, plants, bouquets, and greenery tracking
+- **Order Management**: Customer orders, procurement, sales, and refunds
+- **Customer Management**: Member system with levels and benefits
+- **Financial Management**: Billing, payments, revenue tracking, and financial reports
+- **Supply Chain**: Supplier management and procurement workflows
+- **Multi-store Support**: Multiple shop locations management
+- **Analytics**: Sales statistics, profit analysis, and inventory reports
+
+## Technology Stack
+
+- **Framework**: UniApp (Vue.js 2.x)
+- **Build Tool**: Vue CLI with uni-app plugin
+- **State Management**: Vuex
+- **UI Framework**: Vant WeApp components + custom components
+- **HTTP Client**: Flyio
+- **Charts**: ECharts 4.2.1
+- **Platforms**: WeChat Mini Program, H5, iOS, Android
+
+## Development Environment
+
+### Prerequisites
+- Node.js
+- HBuilderX or VS Code with uni-app plugin
+- WeChat Developer Tools (for mini program testing)
+
+### Available Scripts
+
+```bash
+# Development
+npm run dev:h5          # H5 development server (port 81)
+npm run dev:mp-weixin   # WeChat Mini Program
+npm run dev:app-plus    # App development
+
+# Production Build
+npm run build:h5        # H5 production build
+npm run build:mp-weixin # Mini program production build
+npm run build:app-plus  # App production build
+
+# Other platforms
+npm run dev:mp-alipay   # Alipay Mini Program
+npm run dev:mp-baidu    # Baidu Smart Program
+npm run dev:mp-qq       # QQ Mini Program
+npm run dev:mp-toutiao  # ByteDance Mini Program
+```
+
+### Environment Configuration
+
+- **Development API**: `http://api.shop.huaml.com`
+- **Production API**: `http://shop.huaml.com`
+- **Image Host**: `http://img.huaml.com`
+- **Current Environment**: `dev` (see src/config.js)
+
+## Project Structure
+
+```
+src/
+├── admin/                 # Admin management system
+│   ├── home/             # Main navigation pages (workbench, order, member, apply, me)
+│   ├── goods/            # Product management (flowers, plants, categories)
+│   ├── order/            # Order management system
+│   ├── billing/          # Billing and checkout flows
+│   ├── customer/         # Customer management
+│   ├── staff/            # Employee management
+│   ├── shop/             # Shop management
+│   ├── stat/             # Statistics and reports
+│   └── ...               # Other admin modules
+├── pagesClient/          # Client-facing pages
+├── pagesPurchase/        # Procurement system
+├── pagesStorehouse/      # Inventory management
+├── pagesOrder/           # Order processing
+├── pagesStatistics/      # Business analytics
+├── components/           # Reusable UI components
+├── api/                  # API service modules (organized by business domain)
+├── store/                # Vuex state management
+├── utils/                # Utility functions and helpers
+└── static/               # Static assets
+```
+
+## Key Configuration Files
+
+- **src/manifest.json**: App configuration (app name, permissions, SDKs)
+- **src/pages.json**: Page routing and navigation configuration
+- **src/config.js**: Environment-specific API endpoints
+- **vue.config.js**: Webpack configuration for fallback modules
+- **project.config.json**: WeChat Mini Program project settings
+
+## API Architecture
+
+The API layer is organized by business domain:
+- `/api/goods/` - Product management
+- `/api/order/` - Order processing
+- `/api/custom/` - Customer management
+- `/api/stat/` - Statistics and analytics
+- `/api/billing/` - Billing and payments
+- `/api/storehouse/` - Inventory management
+
+## Important Notes
+
+- The app supports **sub-package loading** for WeChat Mini Program optimization
+- **Lazy loading** is enabled for required components
+- **Multiple deployment targets**: H5 (port 81), WeChat Mini Program, iOS/Android apps
+- **Native plugins**: Includes Bluetooth printer support (佳博打印机) and custom dialog components
+- **Payment integration**: WeChat Pay and Alipay support
+- **Multi-language**: Chinese language support throughout
+
+## Common Development Tasks
+
+1. **Adding new pages**: Update `src/pages.json` and create corresponding Vue files
+2. **API integration**: Add new endpoints in appropriate `/api/` modules
+3. **State management**: Use Vuex modules in `/store/modules/`
+4. **Styling**: Use SCSS in `/static/css/` or component-scoped styles
+5. **Component creation**: Add reusable components to `/components/` directory
+
+## Platform-Specific Considerations
+
+- **WeChat Mini Program**: Follows WeChat's component and API standards
+- **H5**: Uses responsive design with port 81 for development
+- **iOS/Android**: Uses native capabilities like camera, Bluetooth, geolocation
+- **Cross-platform compatibility**: Uses UniApp's conditional compilation for platform-specific code

+ 1 - 1
hdApp/package.json

@@ -96,7 +96,7 @@
     "postcss-import": "^12.0.1",
     "postcss-loader": "~3.0.0",
     "qs": "^6.8.0",
-    "svgo": "^4.0.0",
+    "svgo": "^3.0.5",
     "sass": "1.32.13",
     "sass-migrator": "^1.7.3",
     "vue-template-compiler": "^2.6.10",

+ 1 - 0
hdApp/src/assets/svg-icons/general/big-img.svg

@@ -0,0 +1 @@
+<svg t="1753443798461" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3053" width="200" height="200"><path d="M896.698062 0.090615h-769.156499a124.25072 124.25072 0 0 0-122.254108 122.868451v645.059364a124.25072 124.25072 0 0 0 122.254108 122.868451h769.156499a124.25072 124.25072 0 0 0 122.254108-122.868451v-645.059364a117.748932 117.748932 0 0 0-122.254108-122.868451z m-769.156499 76.792782h769.156499a45.768498 45.768498 0 0 1 46.075669 46.075669v660.417921l-264.883901-271.334495-152.817635 102.390375-203.756847-204.78075-244.508216 245.7369s-13.157163-387.803546-0.409562-532.429951c-0.204781-38.857147 25.648789-46.075669 51.143993-46.075669z" p-id="3054"></path><path d="M768.095751 256.066554m-102.390376 0a102.390375 102.390375 0 1 0 204.780751 0 102.390375 102.390375 0 1 0-204.780751 0Z" p-id="3055"></path><path d="M983.115539 1023.994369h-941.991453a40.290613 40.290613 0 0 1-40.95615-40.956151 37.577268 37.577268 0 0 1 40.95615-40.95615h941.991453a40.290613 40.290613 0 0 1 40.95615 40.95615 40.290613 40.290613 0 0 1-40.95615 40.956151z" p-id="3056"></path></svg>

+ 1 - 0
hdApp/src/assets/svg-icons/general/bjwj.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="1753443835835" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3200" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M368.52 753.578l-219.58 53.904 61.831-217.995 428.855-441.538 156.956 164.09L368.52 753.578z m458.185-475.625l-157.749-164.09 79.271-81.649c26.159-26.952 68.173-26.952 94.332 0l63.417 65.795c26.159 26.952 26.159 71.344 0 98.296l-79.271 81.648zM18.143 893.887V1012h987.713V893.887H18.143z" p-id="3201"></path></svg>

+ 1 - 0
hdApp/src/assets/svg-icons/general/edit.svg

@@ -0,0 +1 @@
+<svg t="1753443835835" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3200" width="200" height="200"><path d="M368.52 753.578l-219.58 53.904 61.831-217.995 428.855-441.538 156.956 164.09L368.52 753.578z m458.185-475.625l-157.749-164.09 79.271-81.649c26.159-26.952 68.173-26.952 94.332 0l63.417 65.795c26.159 26.952 26.159 71.344 0 98.296l-79.271 81.648zM18.143 893.887V1012h987.713V893.887H18.143z" p-id="3201"></path></svg>

+ 1 - 0
hdApp/src/assets/svg-icons/general/small-img.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"/></svg>

+ 63 - 0
hdApp/src/static/svg-icons-lib.js

@@ -0,0 +1,63 @@
+/**
+ *
+ * Icon Library for <zui-svg-icon> usage
+ *
+ * Auto generated by /tools/generate-svg-icon.js
+ *
+ * !!! DO NOT MODIFY MANUALLY !!!
+ *
+ * @datetime 2025/7/25 22:57:06
+ *
+ */
+
+// == collection start
+const collections = {
+  default: {
+    "icons": {
+      "general-big-img": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"200\" class=\"icon\" viewBox=\"0 0 1024 1024\"><path fill=\"#22ac38\" d=\"M896.698.09H127.542A124.25 124.25 0 0 0 5.287 122.96v645.058a124.25 124.25 0 0 0 122.255 122.869h769.156a124.25 124.25 0 0 0 122.254-122.869V122.96A117.75 117.75 0 0 0 896.698.091M127.542 76.884h769.156a45.77 45.77 0 0 1 46.076 46.076v660.418L677.89 512.042l-152.818 102.39-203.757-204.78L76.807 655.39s-13.157-387.804-.41-532.43c-.204-38.857 25.65-46.076 51.145-46.076\"/><path fill=\"#22ac38\" d=\"M665.706 256.067a102.39 102.39 0 1 0 204.78 0 102.39 102.39 0 1 0-204.78 0m317.41 767.927H41.124A40.29 40.29 0 0 1 .168 983.038a37.577 37.577 0 0 1 40.956-40.956h941.992a40.29 40.29 0 0 1 40.956 40.956 40.29 40.29 0 0 1-40.956 40.956\"/></svg>",
+        0
+      ],
+      "general-bjwj": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"200\" class=\"icon\" viewBox=\"0 0 1024 1024\"><path fill=\"#22ac38\" d=\"m368.52 753.578-219.58 53.904 61.831-217.995 428.855-441.538 156.956 164.09zm458.185-475.625-157.749-164.09 79.271-81.649c26.159-26.952 68.173-26.952 94.332 0l63.417 65.795c26.159 26.952 26.159 71.344 0 98.296zM18.143 893.887V1012h987.713V893.887z\"/></svg>",
+        0
+      ],
+      "general-edit": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"200\" class=\"icon\" viewBox=\"0 0 1024 1024\"><path fill=\"#22ac38\" d=\"m368.52 753.578-219.58 53.904 61.831-217.995 428.855-441.538 156.956 164.09zm458.185-475.625-157.749-164.09 79.271-81.649c26.159-26.952 68.173-26.952 94.332 0l63.417 65.795c26.159 26.952 26.159 71.344 0 98.296zM18.143 893.887V1012h987.713V893.887z\"/></svg>",
+        0
+      ],
+      "general-small-img": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path fill=\"#22ac38\" d=\"M4 8h4V4H4zm6 12h4v-4h-4zm-6 0h4v-4H4zm0-6h4v-4H4zm6 0h4v-4h-4zm6-10v4h4V4zm-6 4h4V4h-4zm6 6h4v-4h-4zm0 6h4v-4h-4z\"/></svg>",
+        0
+      ]
+    },
+    "currentColor": "",
+    "$_colorPalette": [
+      "#22ac38"
+    ]
+  },
+}
+// == 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
hdApp/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',
+      },
+    },
+  ],
+}

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

@@ -0,0 +1,164 @@
+## 0.1.61(2024-09-25)
+1. 修复当 svg 中包含 use 节点导致递归超限制的问题
+## 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图标显示与换色

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

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

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

@@ -0,0 +1,86 @@
+{
+  "id": "zui-svg-icon",
+  "displayName": "多彩SVG图标,自由换色,全端适配",
+  "version": "0.1.61",
+  "description": "一款适用于 uni-app 的 SVG 图标组件。全端适配:APP-Plus、小程序、H5。支持多色SVG图标;支持换色;支持URI和Base64格式;支持spin动画;SVG优化",
+  "keywords": [
+    "svg",
+    "svg-icon",
+    "svg图标",
+    "彩色图标",
+    "换色"
+],
+  "repository": "https://github.com/zivyuan/zui-svg-icon.git",
+  "engines": {
+    "HBuilderX": "^3.4.15"
+  },
+  "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"
+        }
+      }
+    }
+  }
+}

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

@@ -0,0 +1,249 @@
+# &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 run 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
hdApp/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
hdApp/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|use) /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
hdApp/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
hdApp/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
hdApp/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;
+}