@qilitt-mickey/vue3-temp-skill 1.0.12 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +66 -214
  2. package/SKILL.md +165 -527
  3. package/package.json +2 -2
  4. package/references/api-check.md +187 -128
  5. package/references/base-code-dict.md +49 -48
  6. package/references/chart-echarts.md +61 -0
  7. package/references/code-quality.md +44 -15
  8. package/references/core-kernel.md +305 -0
  9. package/references/crud-pages.md +87 -261
  10. package/references/data-compare.md +502 -501
  11. package/references/data-mapping.md +218 -213
  12. package/references/data-screen.md +94 -79
  13. package/references/data-writeback.md +104 -104
  14. package/references/detail-page.md +100 -99
  15. package/references/directives-advanced.md +27 -4
  16. package/references/download-export.md +70 -68
  17. package/references/feedback-loading.md +62 -60
  18. package/references/feedback-ui.md +125 -111
  19. package/references/file-management.md +28 -7
  20. package/references/flowchart-g6.md +249 -244
  21. package/references/form-advanced.md +264 -26
  22. package/references/graph-relation.md +12 -7
  23. package/references/http-api.md +138 -103
  24. package/references/icons.md +326 -0
  25. package/references/layout-theme.md +1 -1
  26. package/references/mobile-h5.md +20 -0
  27. package/references/particles.md +143 -0
  28. package/references/permission-auth.md +8 -31
  29. package/references/project-inventory.md +175 -120
  30. package/references/qrcode-barcode.md +107 -92
  31. package/references/rich-text.md +85 -73
  32. package/references/seamless-scroll.md +40 -38
  33. package/references/table-vxe.md +114 -0
  34. package/references/tree-table.md +2 -1
  35. package/references/ui-components.md +5 -1
  36. package/references/verify-captcha.md +110 -96
  37. package/references/websocket-realtime.md +2 -1
  38. package/references/wechat-js.md +58 -0
  39. package/references/workflow-bpmn.md +207 -206
  40. package/references/advanced-ui.md +0 -302
  41. package/references/build-optim.md +0 -282
  42. package/references/vue-core.md +0 -209
@@ -1,110 +1,130 @@
1
1
  ---
2
2
  skill: http-api
3
- description: 规范 HTTP 请求封装、API 接口文件组织、RSA+AES 加解密调用、文件下载等网络层开发模式。在编写或修改 API 接口调用时调用。
3
+ description: 规范 HTTP 请求封装、API 接口文件组织、加解密与文件下载。编写或修改 API 时加载。
4
4
  scope: project
5
5
  tags: [axios, http, api, encryption, rsa, aes, download, request]
6
6
  ---
7
7
 
8
8
  # HTTP 请求与 API 接口规范
9
9
 
10
+ > 实现落点:`src/utils/http.ts`、`src/api/**`、全局 `Result<T>`。
11
+
10
12
  ## Http 封装架构
11
13
 
12
- 项目使用自定义 `Http` 类封装 Axios,位于 `src/utils/http/`。核心特性:
14
+ 项目使用自定义 `Http` 类封装 Axios,位于 `src/utils/http.ts`。核心特性:
15
+
16
+ - 请求/响应拦截器统一处理 Token(`token` / `userCode` 请求头)、错误弹窗、NProgress
17
+ - RSA + AES 混合加解密(由 `VITE_ENCODE_SWITCH` 全局控制,非单请求开关)
18
+ - 请求去重(相同请求默认取消前一个,可用 `repeatRequest: true` 放行)
19
+ - 统一响应类型 `Result<T>`(定义在 `types/global.d.ts`)
13
20
 
14
- - 请求/响应拦截器统一处理 Token、错误码、Loading 状态
15
- - RSA + AES 混合加密(通过 `@CryptoAPI` 注解配合后端)
16
- - 请求去重(相同请求自动取消前一个)
17
- - 统一响应类型 `Result<T>`
21
+ ```typescript
22
+ import { http } from "@/utils/http";
23
+ ```
18
24
 
19
25
  ## 统一响应类型
20
26
 
21
27
  ```typescript
22
- interface Result<T = any> {
23
- success: boolean;
24
- data: T;
25
- message: string;
26
- code: number;
28
+ /** types/global.d.ts — 权威定义 */
29
+ interface Result<T = unknown> {
30
+ att?: unknown[];
31
+ data?: T | null;
32
+ exceptionName?: null;
33
+ status?: number;
34
+ statusText?: string;
35
+ name?: string;
27
36
  }
28
37
  ```
29
38
 
30
- 所有 API 返回值必须包裹在 `Result<T>` 中,通过 `data` 字段获取业务数据。
39
+ - 成功判断:`res.status === 200`
40
+ - 业务数据:`res.data`
41
+ - 错误文案:`res.statusText`
42
+ - **不要**使用 `success` / `code` / `message` 这类非本项目字段
43
+
44
+ 分页数据结构:
45
+
46
+ ```typescript
47
+ interface Data {
48
+ pageNo?: number;
49
+ pageSize?: number;
50
+ pageCount?: number;
51
+ totalCount?: number;
52
+ content?: Content[];
53
+ }
54
+
55
+ interface QueryData {
56
+ pageNo: number;
57
+ pageSize: number;
58
+ }
59
+ ```
31
60
 
32
61
  ## API 调用标准模式
33
62
 
34
63
  ### 接口文件组织
35
64
 
36
- API 文件按业务模块拆分,放在 `src/api/` 目录下:
65
+ API 按业务模块放在 `src/api/`(多为 `模块/index.ts`):
37
66
 
38
67
  ```
39
68
  src/api/
40
- ├── system.ts # 系统管理接口
41
- ├── order.ts # 订单模块接口
42
- ├── report.ts # 报表模块接口
43
- └── modules/
44
- └── auth.ts # 认证相关接口
69
+ ├── login/index.ts
70
+ ├── common/index.ts
71
+ ├── table/index.ts
72
+ ├── home/index.ts
73
+ ├── graph/index.ts
74
+ └── ...
45
75
  ```
46
76
 
47
77
  ### 接口定义格式
48
78
 
49
79
  ```typescript
50
- import http from "@/utils/http";
51
- import type { Result } from "@/utils/http/types";
52
- import type { UserListParams, UserListResult, UserInfo } from "./types/system";
53
-
54
- /** 获取用户列表 */
55
- export function getUserListApi(data: UserListParams) {
56
- return http.request<Result<UserListResult>>(
57
- "post",
58
- "/api/system/user/list",
59
- { data }
60
- );
61
- }
80
+ import { http } from "@/utils/http";
62
81
 
63
- /** 获取用户详情 */
64
- export function getUserDetailApi(id: string) {
65
- return http.request<Result<UserInfo>>(
82
+ /** 真实业务接口:环境变量 + 后端 path */
83
+ export function getUserListApi(data: CombinedQueryData) {
84
+ return http.request<Result<Data>>(
66
85
  "post",
67
- "/api/system/user/detail",
68
- { data: { id } }
86
+ `${import.meta.env.VITE_API_BASE_URL}/console/user/list`,
87
+ { data },
69
88
  );
70
89
  }
71
90
 
72
- /** 删除用户 */
73
- export function deleteUserApi(id: string) {
74
- return http.request<Result<null>>(
75
- "post",
76
- "/api/system/user/delete",
77
- { data: { id } }
78
- );
79
- }
91
+ /** 本地 Mock:仅开发 Fake Server 使用,不是前后端 URL 规范 */
92
+ export const getLogin = (data: LoginParams) => {
93
+ return http.request<Result<UserData>>("post", "/mock/login", { data });
94
+ };
80
95
  ```
81
96
 
82
97
  ### 接口约定
83
98
 
84
- 1. **所有接口统一使用 POST 方法**,参数通过 `data` 传递。
85
- 2. 接口函数命名:`[动作] + [模块] + Api`,如 `getUserListApi`、`deleteOrderApi`。
86
- 3. 接口函数必须声明完整的参数类型和返回类型。
87
- 4. 接口类型定义放在对应的 `types/` 文件或同目录 `type.ts` 中。
88
- 5. 不要在组件中直接写 `http.request`,必须抽取到 `api/` 目录。
99
+ 1. **请求方法以后端文档为准,允许 GET / POST**。常见列表/提交多用 POST + `data`;下载模板、签名、部分查询等后端要求 GET 时用 GET + `params`(见 `getDownLoadTemplate`、微信签名等)。
100
+ 2. **参数位置与方法匹配**:POST → `data`;GET → `params`。不要为了“统一 POST”强行改写后端已定义的 GET。
101
+ 3. **真实地址**用 `import.meta.env.VITE_API_BASE_URL` 拼接后端 path;**禁止硬编码域名**。
102
+ 4. **`/mock/...` 等路径仅用于本地 Fake Server**(`mock/` + `vite-plugin-fake-server`),联调真实后端时必须换成真实 path。
103
+ 5. **不要**虚构统一前缀 `/api/...` 当作项目标准。
104
+ 6. 字段命名前后端统一 **驼峰 camelCase**(如 `userName`、`userCode`)。
105
+ 7. Java `Long` / `BigDecimal` → 前端 `string`。
106
+ 8. 接口函数命名语义化,如 `getLogin`、`getTableList`、`deleteAttachmentById`。
107
+ 9. 不要在组件中直接写 `http.request`,必须抽到 `src/api/`。
108
+ 10. `Result<T>` 为全局类型,一般无需从 http 目录再 import。
89
109
 
90
110
  ## 在组件中使用 API
91
111
 
92
112
  ```vue
93
113
  <script setup lang="ts">
94
- import { ref } from "vue";
95
- import { getUserListApi } from "@/api/system";
96
- import type { UserListResult } from "@/api/types/system";
114
+ import { getUserListApi } from "@/api/xxx";
97
115
 
98
116
  const loading = ref(false);
99
- const tableData = ref<UserListResult["list"]>([]);
117
+ const tableData = ref<Content[]>([]);
100
118
  const total = ref(0);
101
119
 
102
- async function fetchData(params: UserListParams) {
120
+ async function fetchData(params: CombinedQueryData) {
103
121
  loading.value = true;
104
122
  try {
105
- const { data } = await getUserListApi(params);
106
- tableData.value = data.list;
107
- total.value = data.total;
123
+ const res = await getUserListApi(params);
124
+ if (res.status === 200) {
125
+ tableData.value = res.data?.content ?? [];
126
+ total.value = res.data?.totalCount ?? 0;
127
+ }
108
128
  } finally {
109
129
  loading.value = false;
110
130
  }
@@ -112,77 +132,92 @@ async function fetchData(params: UserListParams) {
112
132
  </script>
113
133
  ```
114
134
 
135
+ 列表页优先使用 `useTableSearch`(已按 `pageNo` / `pageSize` / `content` / `totalCount` / `status === 200` 对接)。
136
+
115
137
  ## 加密请求
116
138
 
117
- 项目支持 RSA + AES 混合加密,用于敏感数据传输(密码、身份证号等)。
139
+ 加解密由环境变量控制,实现见 `src/utils/http.ts`、`src/utils/encryptUtils.ts`:
118
140
 
119
- ### 使用方式
141
+ | 变量 | 作用 |
142
+ |------|------|
143
+ | `VITE_ENCODE_SWITCH` | 是否开启加解密 |
144
+ | `VITE_ENCODE_PUBKEY` | 前端加密用公钥 |
145
+ | `VITE_ENCODE_PRIKEY` | 前端解密用私钥 |
120
146
 
121
147
  ```typescript
122
- // 普通请求(不加密)
148
+ // ✅ 正确:开关开着时拦截器自动处理,业务侧照常传 data
123
149
  http.request<Result<T>>("post", url, { data: params });
124
150
 
125
- // 加密请求(通过配置项开启)
126
- http.request<Result<T>>("post", url, {
127
- data: params,
128
- crypto: true, // 开启 RSA+AES 加密
129
- });
151
+ // ❌ 错误:项目中不存在单请求 crypto: true 配置
152
+ http.request<Result<T>>("post", url, { data: params, crypto: true });
130
153
  ```
131
154
 
132
- ### 加密流程
155
+ ### 加密流程(开关开启时)
133
156
 
134
- 1. 前端生成随机 AES 密钥
135
- 2. 用 AES 密钥加密请求体
136
- 3. 用后端公钥(RSA)加密 AES 密钥
137
- 4. 将加密后的 AES 密钥 + 密文一起发送
138
- 5. 后端用私钥解密 AES 密钥,再解密请求体
139
- 6. 响应数据同样使用 AES 加密返回
157
+ 1. 前端生成会话 AES 密钥,RSA 公钥封装后放入 `accessToken` 字段
158
+ 2. 请求体 AES 加密;请求头带 `sign`
159
+ 3. 后端解密后处理业务
160
+ 4. 响应若带 `accessToken`,前端 RSA 解开密钥再 AES 解密得到业务 `Result`
140
161
 
141
- ## 文件下载
162
+ 注意:GET、FormData、白名单路径不走加解密。
142
163
 
143
- ### 标准下载模式
164
+ ## 文件上传 / 下载
144
165
 
145
- ```typescript
146
- import { blobDown } from "@/utils/download";
166
+ ### 上传
147
167
 
148
- /** 导出 Excel */
149
- export function exportUserExcelApi(data: ExportParams) {
150
- return http.request<Blob>(
168
+ ```typescript
169
+ export function getUploadUrl(path: string, data: FormData) {
170
+ const url = path || "/documentInfo/uploadFile";
171
+ return http.request<Result<UploadFileResult>>(
151
172
  "post",
152
- "/api/system/user/export",
153
- { data, responseType: "blob" }
173
+ `${import.meta.env.VITE_API_BASE_URL}${url}`,
174
+ {
175
+ data,
176
+ headers: { "Content-Type": "multipart/form-data" },
177
+ },
178
+ { repeatRequest: true },
154
179
  );
155
180
  }
181
+ ```
156
182
 
157
- // 在组件中调用
158
- async function handleExport() {
159
- const res = await exportUserExcelApi(queryParams);
160
- blobDown(res, "用户列表.xlsx");
183
+ ### 下载
184
+
185
+ ```typescript
186
+ import { blobDown } from "@/utils/utils";
187
+
188
+ export function downloadFile(data: Recordable) {
189
+ return http.request<Result<Blob>>(
190
+ "post",
191
+ `${import.meta.env.VITE_API_BASE_URL}/documentInfo/downloadFile`,
192
+ { data },
193
+ { responseType: "blob" },
194
+ );
161
195
  }
162
- ```
163
196
 
164
- ### 下载工具函数
197
+ // 组件中:拦截器对 Blob 可能返回 { data, name }
198
+ const res = await downloadFile(params);
199
+ blobDown(res.data, res.name || "文件.xlsx", res);
200
+ ```
165
201
 
166
- - `blobDown(data, filename)` — 将 Blob 数据下载为文件
167
- - `download(url, filename)` — 通过 URL 下载文件
168
- - `downloadAttachment(id)` — 根据附件 ID 下载附件
202
+ 相关工具:
169
203
 
170
- ## 请求取消
204
+ - `blobDown(data, name, response)` — Blob 下载(`src/utils/utils.ts`)
205
+ - `getDownLoadTemplate(name)` — 模板下载(GET + `params`,属现有特例)
171
206
 
172
- 使用 `AbortController` 实现请求取消,页面切换时自动取消未完成的请求:
207
+ ## 请求取消 / 重复请求
173
208
 
174
- ```typescript
175
- // http 封装内部自动处理,组件中无需手动取消
176
- // 但如果需要手动取消:
177
- const controller = new AbortController();
178
- http.request("post", url, { data, signal: controller.signal });
179
- controller.abort(); // 取消请求
180
- ```
209
+ - 默认 `repeatRequest: false`:相同请求会取消前一个。
210
+ - 需要允许并发时传 `{ repeatRequest: true }`(如上传)。
211
+ - 也可传 `signal: AbortController.signal` 手动取消。
181
212
 
182
213
  ## 常见反例
183
214
 
184
- - 在组件中直接写 `axios.post()` 而不通过 http 封装。
185
- - 接口函数不声明返回类型,使用 `any`。
186
- - 使用 GET 方法传 query 参数(本项目统一用 POST + data)。
187
- - 下载文件时忘记设置 `responseType: "blob"`。
188
- - 把接口 URL 硬编码在组件中而不抽取到 `api/` 目录。
215
+ - 在组件中直接 `axios.post()` / `fetch()`,绕过 `@/utils/http`。
216
+ - 使用虚构的 `/api/...` 前缀,或把 `/mock/...` 当成生产约定。
217
+ - 硬编码域名,不用 `VITE_API_BASE_URL`。
218
+ - 后端文档是 GET,却强行改成 POST(或反过来);POST 用 `params`、GET 用 `data` 等位置错配。
219
+ - 按 `success` / `message` / `code` 或 `list` / `total` 解析响应。
220
+ - 字段写成 `username` 等非驼峰,或与文档不一致。
221
+ - 使用不存在的 `crypto: true`;或忘记配置 `VITE_ENCODE_*`。
222
+ - 下载忘记 `responseType: "blob"`。
223
+ - `Long` / 金额用 `number` 导致精度丢失。
@@ -0,0 +1,326 @@
1
+ ---
2
+ skill: icons
3
+ description: 规范项目图标体系(本地 SVG 雪碧图、Iconify ep/ri、全局 svg-icon、ReIconPicker、菜单 meta.icon、UnoCSS presetIcons、svg?component)。在新增/选用图标、改菜单图标、图标选择器或内网离线图标时调用。
4
+ scope: project
5
+ tags: [vue3, icon, svg-icon, ReSvgIcon, ReIconPicker, iconify, ep, ri, unocss, menu-icon]
6
+ ---
7
+
8
+ # 图标体系规范
9
+
10
+ > 页面/菜单/按钮里要用图标时加载本模块。
11
+ > **真相源**:`src/components/ReSvgIcon`、`src/components/ReIconPicker`、`src/icons/svg`、`main.ts`、`uno.config.ts`、`build/plugins/SvgSpriteLoader.ts` / `SvgLoader.ts`。
12
+ > 选型表见 `project-inventory`「图标」节;勿另起 IconFont / 未约定的图标库。
13
+
14
+ ---
15
+
16
+ ## 依赖(core,模板已装)
17
+
18
+ | 包 | 版本 | 用途 |
19
+ |---|---|---|
20
+ | `@iconify/vue` | `5.0.1` | Iconify 运行时渲染 |
21
+ | `@iconify-json/ep` | `1.2.4` | Element Plus 图标集(dev) |
22
+ | `@iconify-json/ri` | `1.2.10` | Remix Icon 集(dev) |
23
+ | `vite-plugin-svg-icons` | `2.0.1` | 本地 SVG → Symbol 雪碧图 |
24
+ | `vite-svg-loader` | `5.1.1` | `*.svg?component` 组件化 |
25
+
26
+ 缺包时按精确版本安装(征得确认):
27
+
28
+ ```bash
29
+ pnpm add @iconify/vue@5.0.1
30
+ pnpm add -D @iconify-json/ep@1.2.4 @iconify-json/ri@1.2.10 vite-plugin-svg-icons@2.0.1 vite-svg-loader@5.1.1
31
+ ```
32
+
33
+ 构建接线(模板已有,**勿重复改 Vite**,除非用户明确要求):
34
+
35
+ - `build/plugins/SvgSpriteLoader.ts` → 扫描 `src/icons/svg`
36
+ - `build/plugins/SvgLoader.ts` → `vite-svg-loader`
37
+ - `main.ts`:`import 'virtual:svg-icons-register'` + `app.component('svg-icon', SvgIcon)`
38
+
39
+ ---
40
+
41
+ ## 一、选型决策(先选通道)
42
+
43
+ | 场景 | 用法 | 说明 |
44
+ |------|------|------|
45
+ | 菜单 / 标签 / 业务页通用展示 | **`<svg-icon name="..." />`** | 全局组件,统一入口 |
46
+ | 表单里选图标并回写字符串 | **`ReIconPicker`** | 选中值再交给 `svg-icon` |
47
+ | 模板里一次性装饰图标(搜索框前缀等) | **UnoCSS** `i-ep-*` / `i-ri:*` | 不经 `svg-icon` |
48
+ | 需当 Vue 组件传给 EP(如 `el-icon`、`:icon`) | **`import X from '...svg?component'`** | `vite-svg-loader` |
49
+ | 大插画 / 错误页整图 | `assets/svg/*.svg?component` | 非菜单雪碧图 |
50
+
51
+ **默认优先 `svg-icon`**。禁止:页面散落 `@iconify/vue` 的 `<Icon>`、引入未约定的 `@iconify-json/*` 全集、硬编码外链 Icon CDN(内网见第七节)。
52
+
53
+ ---
54
+
55
+ ## 二、命名与解析规则(`ReSvgIcon`)
56
+
57
+ 全局标签:`<svg-icon>`(实现:`src/components/ReSvgIcon/src/svgIcon.vue`)。
58
+
59
+ | `name` 形态 | 判定 | 渲染 |
60
+ |-------------|------|------|
61
+ | 含 `:`(如 `ep:user`、`ri:download-line`) | Iconify | `@iconify/vue` 的 `<Icon>` |
62
+ | 以 `i-` 开头且含 `:`(如 `i-ep:home`) | Iconify | 自动剥掉 `i-` 再渲染(兼容 Picker) |
63
+ | 无冒号(如 `home`、`hot`) | 本地雪碧图 | `<use xlink:href="#icon-{name}" />` |
64
+
65
+ Props:
66
+
67
+ | Prop | 类型 | 默认 | 说明 |
68
+ |------|------|------|------|
69
+ | `name` | `string` | 必填 | 见上表 |
70
+ | `size` | `number \| string` | `18` | 宽高与 fontSize |
71
+ | `color` | `string` | `''` | 颜色;本地 SVG 宜 `fill="currentColor"` |
72
+
73
+ ```vue
74
+ <template>
75
+ <!-- Iconify -->
76
+ <svg-icon name="ri:file-list-3-line" :size="18" color="var(--el-color-primary)" />
77
+ <svg-icon name="ep:user" :size="18" />
78
+
79
+ <!-- 本地:对应 src/icons/svg/home.svg -->
80
+ <svg-icon name="home" :size="18" />
81
+
82
+ <!-- Picker 回写值也可直接用 -->
83
+ <svg-icon name="i-ep:home" :size="18" />
84
+ </template>
85
+ ```
86
+
87
+ ---
88
+
89
+ ## 三、本地 SVG 雪碧图
90
+
91
+ ### 目录与注册
92
+
93
+ - 文件放在 **`src/icons/svg/*.svg`**(仅此目录进雪碧图)。
94
+ - 插件:`vite-plugin-svg-icons`,`symbolId: 'icon-[dir]-[name]'`。
95
+ - 运行时:`virtual:svg-icons-register`;`<svg-icon name="xxx">` → `#icon-xxx`(文件名无扩展名)。
96
+
97
+ ### 新增本地图标步骤
98
+
99
+ 1. 将 SVG 放入 `src/icons/svg/`,文件名用 **kebab-case 或已有风格**(如 `arrow-right-double.svg`、`circleClose.svg`),**不要空格**。
100
+ 2. path 使用 **`fill="currentColor"`**(或可继承 currentColor),便于跟主题色 / 菜单色。
101
+ 3. 业务里:`<svg-icon name="文件名不含扩展名" />`。
102
+ 4. 菜单:`meta: { icon: '文件名不含扩展名' }`。
103
+ 5. 无需改 `main.ts` / Vite;热更新后即可。
104
+
105
+ ### 现有本地名(摘要)
106
+
107
+ `home`、`hot`、`iframe`、`graph`、`lollipop`、`logo`、`notice`、`arrowRight`、`collapse`、`expand`、`unexpand`、`refresh`、`select`、`close`、`fullscreen-fill`、`fullscreen-exit-fill`、`dataScreen`、`form`、`function`、`tools`、`component`、`grid`、`multiple`、`new`、`drag`、`menuoverflow`、`day`、`dark`、`check-line`、`semi-select`、`circleClose`、`bottom-left`、`bottom-right`、`arrow-right-double` 等。以仓库 `src/icons/svg` 为准。
108
+
109
+ ### 约定
110
+
111
+ - **菜单高频、品牌、项目定制图形** → 本地 SVG。
112
+ - **通用线性图标** → `ep:` / `ri:`,不必每个都落本地文件。
113
+ - 勿把营销大图塞进 `icons/svg`;插画放 `src/assets/svg`。
114
+
115
+ ---
116
+
117
+ ## 四、菜单与路由图标
118
+
119
+ 侧栏 / 顶栏 / TagsView 均通过 **`meta.icon`(string)** → `<svg-icon :name="..." />`。
120
+
121
+ ```typescript
122
+ // src/router/modules/xxx.ts
123
+ export default {
124
+ path: "/customer",
125
+ redirect: "/customer/list",
126
+ meta: {
127
+ title: "客户管理",
128
+ icon: "ep:user", // 或本地 "home"
129
+ rank: 10,
130
+ },
131
+ children: [
132
+ {
133
+ path: "/customer/list",
134
+ name: "CustomerList",
135
+ component: () => import("@/views/customer/list/index.vue"),
136
+ meta: {
137
+ title: "客户列表",
138
+ icon: "ep:user",
139
+ // 可选:菜单右侧角标类小图标
140
+ extraIcon: "hot",
141
+ },
142
+ },
143
+ ],
144
+ } satisfies RouteConfigsTable;
145
+ ```
146
+
147
+ 规则:
148
+
149
+ 1. `meta.icon` / `meta.extraIcon` **必须是 string**(本地名或 `ep:` / `ri:` / `i-ep:`…),不要塞组件对象。
150
+ 2. `extraIcon` 用于「热门」等角标(如本地 `hot`),侧栏在非折叠时展示。
151
+ 3. 业务路由模块**禁止**再挂 `Layout`(见 `core-kernel`)。
152
+
153
+ ---
154
+
155
+ ## 五、ReIconPicker 图标选择器
156
+
157
+ 路径:`src/components/ReIconPicker`。演示页:`src/views/form/iconPicker`(`/form/icon-picker`)。
158
+
159
+ ### 默认图标源
160
+
161
+ | key | label | 说明 |
162
+ |-----|-------|------|
163
+ | `local` | 本地图标 | `import.meta.glob('/src/icons/svg/*.svg')`,值为**无冒号**文件名 |
164
+ | `ep` | Element Plus | `@iconify-json/ep/icons.json`,选中值形如 **`i-ep:xxx`** |
165
+ | `ri` | Remix Icon | `@iconify-json/ri/icons.json`,选中值形如 **`i-ri:xxx`** |
166
+
167
+ ### 基础用法
168
+
169
+ ```vue
170
+ <script setup lang="ts">
171
+ import { ReIconPicker } from "@/components/ReIconPicker";
172
+
173
+ const icon = ref("");
174
+ </script>
175
+
176
+ <template>
177
+ <ReIconPicker v-model="icon" clearable placeholder="请选择图标" />
178
+ <!-- 展示:与 Picker 值同一套 name 规则 -->
179
+ <svg-icon v-if="icon" :name="icon" :size="18" />
180
+ </template>
181
+ ```
182
+
183
+ ### 限制来源 / 仅本地
184
+
185
+ ```vue
186
+ <script setup lang="ts">
187
+ import type { IconSourceConfig } from "@/components/ReIconPicker/src/type";
188
+ import epIcons from "@iconify-json/ep/icons.json";
189
+ import { ReIconPicker } from "@/components/ReIconPicker";
190
+
191
+ const onlyEp: IconSourceConfig[] = [
192
+ { key: "ep", label: "Element Plus", iconData: epIcons },
193
+ ];
194
+ const onlyLocal: IconSourceConfig[] = [
195
+ { key: "local", label: "本地图标", isLocal: true },
196
+ ];
197
+
198
+ const iconEp = ref("i-ep:home");
199
+ const iconLocal = ref("home");
200
+ </script>
201
+
202
+ <template>
203
+ <ReIconPicker v-model="iconEp" :sources="onlyEp" />
204
+ <ReIconPicker v-model="iconLocal" :sources="onlyLocal" />
205
+ </template>
206
+ ```
207
+
208
+ ### 表单校验
209
+
210
+ ```vue
211
+ <script setup lang="ts">
212
+ import type { FormInstance, FormRules } from "element-plus";
213
+ import { ReIconPicker } from "@/components/ReIconPicker";
214
+
215
+ const formRef = ref<FormInstance>();
216
+ const form = reactive({ icon: "" });
217
+ const rules: FormRules = {
218
+ icon: [{ required: true, message: "请选择图标", trigger: "change" }],
219
+ };
220
+ </script>
221
+
222
+ <template>
223
+ <el-form ref="formRef" :model="form" :rules="rules">
224
+ <el-form-item label="图标" prop="icon">
225
+ <ReIconPicker v-model="form.icon" required />
226
+ </el-form-item>
227
+ </el-form>
228
+ </template>
229
+ ```
230
+
231
+ Props 摘要:`modelValue`、`placeholder`、`disabled`、`clearable`、`size`、`sources`、`required`。
232
+ Emits:`update:modelValue`、`change`、`blur`、`focus`。
233
+
234
+ ---
235
+
236
+ ## 六、UnoCSS 图标类
237
+
238
+ `uno.config.ts` 已启用 `presetIcons({ scale: 1.2 })`,并支持 `i-` 标签变体。
239
+
240
+ ```vue
241
+ <template>
242
+ <!-- class 写法 -->
243
+ <i class="i-ep-search" />
244
+ <i class="i-ep:chat-dot-round" />
245
+
246
+ <!-- 属性化 / 组件标签(Uno 变体) -->
247
+ <i-ep-edit />
248
+ </template>
249
+ ```
250
+
251
+ 约定:
252
+
253
+ 1. **动态绑定的图标 class** 若构建后丢失,加入 `uno.config.ts` 的 `safelist`(已有示例 `'i-ep:chat-dot-round'`)。
254
+ 2. 菜单、可配置图标、要随主题变色的业务图标 → 仍用 **`svg-icon`**,不要只用 Uno 类。
255
+ 3. 颜色用 `color-[var(--el-text-color-placeholder)]` 等变量,避免硬编码。
256
+
257
+ ---
258
+
259
+ ## 七、内网 / 离线 Iconify
260
+
261
+ 默认 `@iconify/vue` 会在首次渲染时请求 `https://api.iconify.design`。
262
+ 内网或离线需在 `main.ts` **开启集合打入**(约 +3MB,注释已预留):
263
+
264
+ ```typescript
265
+ import { addCollection } from "@iconify/vue";
266
+ import epIcons from "@iconify-json/ep/icons.json";
267
+ import riIcons from "@iconify-json/ri/icons.json";
268
+
269
+ addCollection(epIcons as any);
270
+ addCollection(riIcons as any);
271
+ ```
272
+
273
+ 仅外网可访问时可保持注释关闭,按需拉取。
274
+
275
+ ---
276
+
277
+ ## 八、`svg?component` 组件化
278
+
279
+ 用于必须传入 **Vue 组件** 的场景(非字符串 name):
280
+
281
+ ```vue
282
+ <script setup lang="ts">
283
+ import ExpandIcon from "@/icons/svg/expand.svg?component";
284
+ import BackTopIcon from "@/assets/svg/back_top.svg?component";
285
+ </script>
286
+
287
+ <template>
288
+ <el-button :icon="ExpandIcon" />
289
+ <BackTopIcon />
290
+ </template>
291
+ ```
292
+
293
+ - 菜单雪碧图仍走 `src/icons/svg` + `svg-icon`。
294
+ - `assets/svg`、`assets/login` 等静态图用 `?component` 或 `<img>`,不要混进菜单 `meta.icon`。
295
+
296
+ ---
297
+
298
+ ## 九、移动端
299
+
300
+ Vant Tabbar 等使用 **Vant 自带图标名**(如 `home-o`),见 `mobile-h5`,**不要**把 `ep:` / `ri:` 字符串塞进 `van-tabbar-item` 的 `icon`。PC 与 H5 图标通道分离。
301
+
302
+ ---
303
+
304
+ ## 十、常见反例
305
+
306
+ | 错误 | 正确 |
307
+ |------|------|
308
+ | 业务页直接 `<Icon icon="ep:xxx" />` | `<svg-icon name="ep:xxx" />` |
309
+ | 菜单 `icon: () => import('...')` 组件 | `icon: "ep:user"` 或 `"home"` |
310
+ | 硬编码 `#409EFF` 上色 | `color="var(--el-color-primary)"` 或 CSS `currentColor` |
311
+ | 新图标丢进 `assets` 却当菜单名用 | 放入 `src/icons/svg` 或改用 `ep:`/`ri:` |
312
+ | 未确认就 `pnpm add @iconify-json/mdi` | 优先 ep/ri/本地;新前缀须「待确认」 |
313
+ | 内网环境不 `addCollection` 却大量用 Iconify | 开启第七节或改本地 SVG |
314
+ | 通知栏等已支持本地名却写死外链 | `left-icon="notice"`(本地名) |
315
+
316
+ ---
317
+
318
+ ## 十一、生成后自检
319
+
320
+ - [ ] 菜单 / 展示统一 `svg-icon` 或路由 `meta.icon` 字符串
321
+ - [ ] `name` 符合「本地无冒号 / Iconify 含冒号」规则;Picker 的 `i-ep:` 可直接展示
322
+ - [ ] 新增本地文件在 `src/icons/svg`,且 `currentColor`
323
+ - [ ] 未擅自引入新图标集;未改 Vite 插件接线(除非用户要求)
324
+ - [ ] 内网场景已说明是否需要 `addCollection`
325
+ - [ ] 动态 Uno 图标类已考虑 `safelist`
326
+ - [ ] 颜色走 CSS 变量 / `currentColor`,暗黑与侧栏主题下可读
@@ -297,7 +297,7 @@ src/layout/
297
297
  presetRemToPx({ baseFontSize: 4 }) // 所有 rem 值 × 4 = px(mt-1 → 1px)
298
298
  presetWind3() // Tailwind Wind 兼容
299
299
  presetAttributify() // 属性化模式
300
- presetIcons({ scale: 1.2 }) // 图标自动引入
300
+ presetIcons({ scale: 1.2 }) // 图标自动引入(`i-ep-*` / `i-ri:*`;完整约定见 `icons`)
301
301
  ```
302
302
 
303
303
  ### 主题色桥接