@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.
- package/README.md +66 -214
- package/SKILL.md +165 -527
- package/package.json +2 -2
- package/references/api-check.md +187 -128
- package/references/base-code-dict.md +49 -48
- package/references/chart-echarts.md +61 -0
- package/references/code-quality.md +44 -15
- package/references/core-kernel.md +305 -0
- package/references/crud-pages.md +87 -261
- package/references/data-compare.md +502 -501
- package/references/data-mapping.md +218 -213
- package/references/data-screen.md +94 -79
- package/references/data-writeback.md +104 -104
- package/references/detail-page.md +100 -99
- package/references/directives-advanced.md +27 -4
- package/references/download-export.md +70 -68
- package/references/feedback-loading.md +62 -60
- package/references/feedback-ui.md +125 -111
- package/references/file-management.md +28 -7
- package/references/flowchart-g6.md +249 -244
- package/references/form-advanced.md +264 -26
- package/references/graph-relation.md +12 -7
- package/references/http-api.md +138 -103
- package/references/icons.md +326 -0
- package/references/layout-theme.md +1 -1
- package/references/mobile-h5.md +20 -0
- package/references/particles.md +143 -0
- package/references/permission-auth.md +8 -31
- package/references/project-inventory.md +175 -120
- package/references/qrcode-barcode.md +107 -92
- package/references/rich-text.md +85 -73
- package/references/seamless-scroll.md +40 -38
- package/references/table-vxe.md +114 -0
- package/references/tree-table.md +2 -1
- package/references/ui-components.md +5 -1
- package/references/verify-captcha.md +110 -96
- package/references/websocket-realtime.md +2 -1
- package/references/wechat-js.md +58 -0
- package/references/workflow-bpmn.md +207 -206
- package/references/advanced-ui.md +0 -302
- package/references/build-optim.md +0 -282
- package/references/vue-core.md +0 -209
package/references/http-api.md
CHANGED
|
@@ -1,110 +1,130 @@
|
|
|
1
1
|
---
|
|
2
2
|
skill: http-api
|
|
3
|
-
description: 规范 HTTP 请求封装、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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- 统一响应类型 `Result<T>`
|
|
21
|
+
```typescript
|
|
22
|
+
import { http } from "@/utils/http";
|
|
23
|
+
```
|
|
18
24
|
|
|
19
25
|
## 统一响应类型
|
|
20
26
|
|
|
21
27
|
```typescript
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
|
65
|
+
API 按业务模块放在 `src/api/`(多为 `模块/index.ts`):
|
|
37
66
|
|
|
38
67
|
```
|
|
39
68
|
src/api/
|
|
40
|
-
├──
|
|
41
|
-
├──
|
|
42
|
-
├──
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
65
|
-
return http.request<Result<
|
|
82
|
+
/** 真实业务接口:环境变量 + 后端 path */
|
|
83
|
+
export function getUserListApi(data: CombinedQueryData) {
|
|
84
|
+
return http.request<Result<Data>>(
|
|
66
85
|
"post",
|
|
67
|
-
|
|
68
|
-
{ data
|
|
86
|
+
`${import.meta.env.VITE_API_BASE_URL}/console/user/list`,
|
|
87
|
+
{ data },
|
|
69
88
|
);
|
|
70
89
|
}
|
|
71
90
|
|
|
72
|
-
/**
|
|
73
|
-
export
|
|
74
|
-
return http.request<Result<
|
|
75
|
-
|
|
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.
|
|
85
|
-
2.
|
|
86
|
-
3.
|
|
87
|
-
4.
|
|
88
|
-
5.
|
|
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 {
|
|
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<
|
|
117
|
+
const tableData = ref<Content[]>([]);
|
|
100
118
|
const total = ref(0);
|
|
101
119
|
|
|
102
|
-
async function fetchData(params:
|
|
120
|
+
async function fetchData(params: CombinedQueryData) {
|
|
103
121
|
loading.value = true;
|
|
104
122
|
try {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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.
|
|
135
|
-
2.
|
|
136
|
-
3.
|
|
137
|
-
4.
|
|
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
|
-
|
|
146
|
-
import { blobDown } from "@/utils/download";
|
|
166
|
+
### 上传
|
|
147
167
|
|
|
148
|
-
|
|
149
|
-
export function
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
{
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
+
## 请求取消 / 重复请求
|
|
173
208
|
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
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
|
### 主题色桥接
|