Commit 83ccb207dcb18793c50334124683c2a2d1589a68
1 parent
23ee3b2a
最新
Showing
937 changed files
with
82377 additions
and
846 deletions
Too many changes.
To preserve performance only 100 of 937 files are displayed.
5-26代码优化.md
0 → 100644
| 1 | +# 5-26 代码优化 | |
| 2 | + | |
| 3 | +本文档说明 **2026-05-26** 对美国版接口的变更。 | |
| 4 | + | |
| 5 | +1. **`/api/app/product-category`**:新增/编辑 **`categoryCode` 取消必填**(见 [product-category-categoryCode](#product-category-categorycode-可选))。 | |
| 6 | +2. **`/api/app/label-template`**:新增/编辑/列表/详情支持 **Region、Location 多选数组**;列表 Query 增加 **Region/Location 筛选**(见 [label-template-regionlocation](#label-template-regionlocation-多选))。 | |
| 7 | +3. **`/api/app/rbac-role`**:修复 **`accessPermissions` JSON 数组**(如 `manage_labels`)无法绑定菜单(见 [rbac-role-accesspermissions](#rbac-role-accesspermissions-修复))。 | |
| 8 | +4. **`/api/app/auth-scope`**:管理员(及按数据范围受限账号)登录后 **Company → Region → Location** 级联选店(见 [auth-scope-登录选店](#auth-scope-登录后-company--region--location-级联选店))。 | |
| 9 | +5. **`/api/app/us-app-auth`**:App 管理员 Token 专用 **Company / Region / 门店筛选** 接口(见 [us-app-auth-管理员选店](#us-app-auth-app-管理员级联选店))。 | |
| 10 | + | |
| 11 | +**应用服务**:`ProductCategoryAppService`、`LabelTemplateAppService`、`RbacRoleAppService` | |
| 12 | +**命名约定**(与 5-17 / 5-18 一致):UI **Region** = API **`regionIds` / `groupIds` / `groupId`**(`fl_group.Id`);UI **Location** = **`locationIds` / `locationId`**(`location.Id`)。 | |
| 13 | + | |
| 14 | +--- | |
| 15 | + | |
| 16 | +## product-category categoryCode 可选 | |
| 17 | + | |
| 18 | +**影响接口** | |
| 19 | + | |
| 20 | +| 方法 | 路径 | | |
| 21 | +|------|------| | |
| 22 | +| POST | `/api/app/product-category` | | |
| 23 | +| PUT | `/api/app/product-category/{id}` | | |
| 24 | + | |
| 25 | +### 变更说明 | |
| 26 | + | |
| 27 | +| 项 | 变更前 | 变更后 | | |
| 28 | +|----|--------|--------| | |
| 29 | +| **categoryCode** | 必填;空则报「类别编码和名称不能为空」 | **可选**;可不传、传 `null` 或 `""` | | |
| 30 | +| **categoryName** | 必填 | 仍必填 | | |
| 31 | +| **落库** | — | 未填编码时 `CategoryCode` 存 **空字符串** | | |
| 32 | +| **唯一性** | 编码或名称重复即报错 | 有编码:编码 **或** 名称重复报错;**无编码**:仅校验 **名称** 不重复 | | |
| 33 | + | |
| 34 | +### 入参(节选) | |
| 35 | + | |
| 36 | +| 字段 | 类型 | 必填 | 说明 | | |
| 37 | +|------|------|------|------| | |
| 38 | +| categoryCode | string | **否** | 类别编码 | | |
| 39 | +| categoryName | string | **是** | 类别名称 | | |
| 40 | +| regionIds / groupIds / locationIds | string[] | 否 | Region·Location 范围(规则见 `5-17接口优化.md`) | | |
| 41 | + | |
| 42 | +### 请求示例(无编码) | |
| 43 | + | |
| 44 | +```http | |
| 45 | +POST /api/app/product-category | |
| 46 | +Content-Type: application/json | |
| 47 | +Authorization: Bearer {token} | |
| 48 | +``` | |
| 49 | + | |
| 50 | +```json | |
| 51 | +{ | |
| 52 | + "categoryName": "Beverages", | |
| 53 | + "buttonAppearance": "TEXT", | |
| 54 | + "state": true, | |
| 55 | + "availabilityType": "ALL", | |
| 56 | + "orderNum": 0 | |
| 57 | +} | |
| 58 | +``` | |
| 59 | + | |
| 60 | +### 联调注意 | |
| 61 | + | |
| 62 | +| 现象 | 处理 | | |
| 63 | +|------|------| | |
| 64 | +| 仍报「类别编码和名称不能为空」 | 确认已部署含本变更的后端;仅需保证 **categoryName** 非空 | | |
| 65 | +| 无编码时名称重复 | 正常:仅按 **categoryName** 判重 | | |
| 66 | + | |
| 67 | +> Region/Location 多选、列表 `region`/`location` 展示等完整说明见 `5-17接口优化.md` → product-category 章节。 | |
| 68 | + | |
| 69 | +--- | |
| 70 | + | |
| 71 | +## label-template Region·Location 多选 | |
| 72 | + | |
| 73 | +**应用服务**:`LabelTemplateAppService` | |
| 74 | +**存储表**:`fl_label_template_location`(模板 ↔ 门店,**无新表**) | |
| 75 | +**主表字段**:`fl_label_template.AppliedLocationType` = `ALL` / `SPECIFIED` | |
| 76 | + | |
| 77 | +### 变更说明 | |
| 78 | + | |
| 79 | +| 项 | 变更前 | 变更后 | | |
| 80 | +|----|--------|--------| | |
| 81 | +| **新增/编辑 Body** | 仅 `appliedLocation` + `appliedLocationIds` | 增加 **`regionIds`**、**`groupIds`**、**`locationIds`**(与 `appliedLocationIds` 合并) | | |
| 82 | +| **列表 Query** | 仅 `locationId` | 增加 **`groupId`**(Region);`locationId` 优先于 `groupId`(与 product-category 一致) | | |
| 83 | +| **列表出参** | 仅 `locationText`(单条展示) | 增加 **`region`**、**`location`** 展示 + **`regionIds`**、**`locationIds`** 数组 | | |
| 84 | +| **详情出参** | `appliedLocationIds` | 同上,并保留 **`appliedLocationIds`**(与 `locationIds` 一致,兼容编辑器) | | |
| 85 | +| **范围解析** | 仅显式门店 Id | Region 展开为门店后与门店 Id **取并集** 落库 | | |
| 86 | + | |
| 87 | +### 影响接口 | |
| 88 | + | |
| 89 | +| 方法 | 路径 | 说明 | | |
| 90 | +|------|------|------| | |
| 91 | +| GET | `/api/app/label-template?SkipCount=1&MaxResultCount=10` | 列表支持 `groupId`/`locationId` 筛选;`items[]` 增加 `region`、`location`、`regionIds`、`locationIds` | | |
| 92 | +| GET | `/api/app/label-template/{id}` | 详情增加上述字段 | | |
| 93 | +| POST | `/api/app/label-template` | Body 支持 Region/Location 多选 | | |
| 94 | +| PUT | `/api/app/label-template/{id}` | 同新增 | | |
| 95 | + | |
| 96 | +路径参数 **`id`** 仍为模板编码 **`TemplateCode`**(与编辑器 JSON 的 `id` 一致)。 | |
| 97 | + | |
| 98 | +### 新增/编辑入参(Body:`LabelTemplateCreateInputVo`) | |
| 99 | + | |
| 100 | +| 字段 | JSON 名 | 类型 | 必填 | 说明 | | |
| 101 | +|------|---------|------|------|------| | |
| 102 | +| TemplateCode | `id` | string | 是 | 模板编码 | | |
| 103 | +| TemplateName | `name` | string | 是 | 模板名称 | | |
| 104 | +| AppliedLocationType | `appliedLocation` | string | 否 | `ALL` / `SPECIFIED`,默认 `ALL` | | |
| 105 | +| RegionIds | `regionIds` | string[] | 否 | Region 多选(`fl_group.Id`) | | |
| 106 | +| GroupIds | `groupIds` | string[] | 否 | 与 `regionIds` 等价,合并去重 | | |
| 107 | +| LocationIds | `locationIds` | string[] | 否 | 门店多选(`location.Id`) | | |
| 108 | +| AppliedLocationIds | `appliedLocationIds` | string[] | 否 | 兼容旧字段,与 `locationIds` 合并 | | |
| 109 | +| Elements | `elements` | array | 否 | 模板组件,全量重建 | | |
| 110 | +| TemplateProductDefaults | `templateProductDefaults` | array | 否 | 仅 **编辑** 时显式传入才重建 | | |
| 111 | + | |
| 112 | +**自动规则** | |
| 113 | + | |
| 114 | +| 入参 | 行为 | | |
| 115 | +|------|------| | |
| 116 | +| `regionIds` / `groupIds` / `locationIds` / `appliedLocationIds` 任一有有效 Id | `appliedLocation` 按 **`SPECIFIED`** 处理 | | |
| 117 | +| 仅传空数组 `[]` 且 `appliedLocation` 为 `ALL` | 不绑定门店(全部门店) | | |
| 118 | +| `appliedLocation: "SPECIFIED"` 且合并后无有效门店 | 报错:`指定适用区域或门店时,至少需要匹配到一个有效门店` | | |
| 119 | +| `appliedLocation` 非法值 | 报错:`适用门店范围不合法(ALL/SPECIFIED)` | | |
| 120 | + | |
| 121 | +**合并规则**:每个 `regionIds` 展开为该 Region 下全部门店,再与 `locationIds`、`appliedLocationIds` **取并集** → 写入 `fl_label_template_location`。 | |
| 122 | + | |
| 123 | +### 请求示例(Region + 门店多选) | |
| 124 | + | |
| 125 | +```http | |
| 126 | +POST /api/app/label-template | |
| 127 | +Content-Type: application/json | |
| 128 | +Authorization: Bearer {token} | |
| 129 | +``` | |
| 130 | + | |
| 131 | +```json | |
| 132 | +{ | |
| 133 | + "id": "TPL_TEST_001", | |
| 134 | + "name": "Price Tag 4x6", | |
| 135 | + "labelType": "PRICE", | |
| 136 | + "unit": "inch", | |
| 137 | + "width": 4, | |
| 138 | + "height": 6, | |
| 139 | + "appliedLocation": "SPECIFIED", | |
| 140 | + "regionIds": [ | |
| 141 | + "fl_group_id_east", | |
| 142 | + "fl_group_id_west" | |
| 143 | + ], | |
| 144 | + "locationIds": [ | |
| 145 | + "11111111-1111-1111-1111-111111111111" | |
| 146 | + ], | |
| 147 | + "showRuler": true, | |
| 148 | + "showGrid": true, | |
| 149 | + "state": true, | |
| 150 | + "elements": [] | |
| 151 | +} | |
| 152 | +``` | |
| 153 | + | |
| 154 | +### 请求示例(全部门店,兼容旧版) | |
| 155 | + | |
| 156 | +```json | |
| 157 | +{ | |
| 158 | + "id": "TPL_ALL", | |
| 159 | + "name": "Global Template", | |
| 160 | + "labelType": "PRICE", | |
| 161 | + "unit": "inch", | |
| 162 | + "width": 4, | |
| 163 | + "height": 6, | |
| 164 | + "appliedLocation": "ALL", | |
| 165 | + "appliedLocationIds": [], | |
| 166 | + "elements": [] | |
| 167 | +} | |
| 168 | +``` | |
| 169 | + | |
| 170 | +### 列表(`GET /api/app/label-template`) | |
| 171 | + | |
| 172 | +**Query 参数** | |
| 173 | + | |
| 174 | +| 字段 | 类型 | 说明 | | |
| 175 | +|------|------|------| | |
| 176 | +| SkipCount / MaxResultCount | int | 分页(项目约定 SkipCount 从 1 起) | | |
| 177 | +| keyword | string | 模板名称/编码模糊 | | |
| 178 | +| **groupId** | string | **按 Region 筛选**(`fl_group.Id`):命中 `appliedLocation=ALL` 的模板,或在 `fl_label_template_location` 中绑定了该 Region 下任一门门店的模板 | | |
| 179 | +| **locationId** | string | **按门店筛选**(`location.Id`);**优先于 groupId** | | |
| 180 | +| labelType | string | 如 `PRICE` | | |
| 181 | +| state | bool | 启用状态 | | |
| 182 | +| sorting | string | 排序(可选) | | |
| 183 | + | |
| 184 | +**筛选规则**(与 product-category / label-type 相同,内部 `LocationScopeBindingHelper.ResolveScopedLocationIdsAsync`) | |
| 185 | + | |
| 186 | +| 入参 | 行为 | | |
| 187 | +|------|------| | |
| 188 | +| 均未传 `groupId`、`locationId` | 不过滤适用范围 | | |
| 189 | +| 仅 `groupId` | 解析该 Region 下全部门店 Id,再筛模板 | | |
| 190 | +| 仅 `locationId` | 按该门店 Id 筛模板 | | |
| 191 | +| 同时传 | **以 `locationId` 为准**(忽略 `groupId`) | | |
| 192 | +| Region/门店无效或解析结果为空 | 仅返回 **`appliedLocation=ALL`** 的模板 | | |
| 193 | + | |
| 194 | +命中条件(满足其一即可出现在列表): | |
| 195 | + | |
| 196 | +- `fl_label_template.AppliedLocationType = 'ALL'` | |
| 197 | +- `SPECIFIED` 且 `fl_label_template_location` 中存在 `LocationId ∈` 解析得到的门店集合 | |
| 198 | + | |
| 199 | +**请求示例** | |
| 200 | + | |
| 201 | +```http | |
| 202 | +GET /api/app/label-template?SkipCount=1&MaxResultCount=10&groupId=fl_group_id_east HTTP/1.1 | |
| 203 | +Authorization: Bearer {token} | |
| 204 | +``` | |
| 205 | + | |
| 206 | +```http | |
| 207 | +GET /api/app/label-template?SkipCount=1&MaxResultCount=10&locationId=11111111-1111-1111-1111-111111111111 HTTP/1.1 | |
| 208 | +Authorization: Bearer {token} | |
| 209 | +``` | |
| 210 | + | |
| 211 | +**命名对照**:UI **Region** → Query **`groupId`**;UI **Location** → Query **`locationId`**。 | |
| 212 | + | |
| 213 | +### 列表出参 | |
| 214 | + | |
| 215 | +**`items[]` 新增/对齐字段** | |
| 216 | + | |
| 217 | +| 字段 | 类型 | 说明 | | |
| 218 | +|------|------|------| | |
| 219 | +| region | string | 适用 Region 展示文案 | | |
| 220 | +| location | string | 适用门店展示文案 | | |
| 221 | +| regionIds | string[] | Region Id 多选;`ALL` 时为 `[]` | | |
| 222 | +| locationIds | string[] | 门店 Id 多选;`ALL` 时为 `[]` | | |
| 223 | +| locationText | string | **兼容字段**,与 `location` 相同 | | |
| 224 | + | |
| 225 | +其它字段不变:`id`(= TemplateCode)、`templateName`、`contentsCount`、`sizeText`、`versionNo`、`lastEdited` 等。 | |
| 226 | + | |
| 227 | +**列表响应示例片段** | |
| 228 | + | |
| 229 | +```json | |
| 230 | +{ | |
| 231 | + "pageIndex": 1, | |
| 232 | + "pageSize": 10, | |
| 233 | + "totalCount": 2, | |
| 234 | + "items": [ | |
| 235 | + { | |
| 236 | + "id": "TPL_ALL", | |
| 237 | + "templateCode": "TPL_ALL", | |
| 238 | + "templateName": "Global Template", | |
| 239 | + "labelType": "PRICE", | |
| 240 | + "region": "All Regions", | |
| 241 | + "location": "All Locations", | |
| 242 | + "locationText": "All Locations", | |
| 243 | + "regionIds": [], | |
| 244 | + "locationIds": [], | |
| 245 | + "contentsCount": 5, | |
| 246 | + "sizeText": "4x6inch", | |
| 247 | + "versionNo": 1, | |
| 248 | + "lastEdited": "2026-05-26T10:00:00" | |
| 249 | + }, | |
| 250 | + { | |
| 251 | + "id": "TPL_TEST_001", | |
| 252 | + "templateName": "Price Tag 4x6", | |
| 253 | + "region": "East Region, West Region", | |
| 254 | + "location": "UNCC store, Central Park Store", | |
| 255 | + "locationText": "UNCC store, Central Park Store", | |
| 256 | + "regionIds": ["fl_group_id_east", "fl_group_id_west"], | |
| 257 | + "locationIds": [ | |
| 258 | + "11111111-1111-1111-1111-111111111111", | |
| 259 | + "22222222-2222-2222-2222-222222222222" | |
| 260 | + ], | |
| 261 | + "contentsCount": 3, | |
| 262 | + "sizeText": "4x6inch", | |
| 263 | + "versionNo": 2, | |
| 264 | + "lastEdited": "2026-05-26T11:30:00" | |
| 265 | + } | |
| 266 | + ] | |
| 267 | +} | |
| 268 | +``` | |
| 269 | + | |
| 270 | +### 详情出参(`GET /api/app/label-template/{id}`) | |
| 271 | + | |
| 272 | +在原有 `elements`、`templateProductDefaults`、`appliedLocationType` 等基础上增加: | |
| 273 | + | |
| 274 | +| 字段 | 类型 | 说明 | | |
| 275 | +|------|------|------| | |
| 276 | +| region | string | 展示文案 | | |
| 277 | +| location | string | 展示文案 | | |
| 278 | +| regionIds | string[] | Region Id 多选 | | |
| 279 | +| groupIds | string[] | 与 `regionIds` 相同(兼容) | | |
| 280 | +| locationIds | string[] | 门店 Id 多选 | | |
| 281 | +| appliedLocationIds | string[] | 与 `locationIds` 一致(编辑器回显) | | |
| 282 | + | |
| 283 | +### 展示规则 | |
| 284 | + | |
| 285 | +| appliedLocation | region | location | regionIds / locationIds | | |
| 286 | +|-----------------|--------|----------|-------------------------| | |
| 287 | +| **ALL** | `All Regions` | `All Locations` | 空数组 `[]` | | |
| 288 | +| **SPECIFIED** | 绑定门店 `location.GroupName` 去重后 `, ` 拼接 | 门店名(优先 `LocationName`,否则 `LocationCode`)拼接 | 由绑定门店反推 / 直接为绑定 Id | | |
| 289 | +| **SPECIFIED** 无绑定 | `无` | `无` | `[]` | | |
| 290 | + | |
| 291 | +`regionIds` 由 `locationIds` 反查 `fl_group` 得到(与 product-category、label-type 一致)。 | |
| 292 | + | |
| 293 | +### 编辑说明 | |
| 294 | + | |
| 295 | +- `PUT` Body 字段与 `POST` 相同;传 `regionIds` / `locationIds` 会 **全量替换** 模板适用门店(先删 `fl_label_template_location` 再插入)。 | |
| 296 | +- `elements` 仍为全量重建;`templateProductDefaults` 仅当 Body **显式包含** 该字段时才重建,避免普通保存误清空。 | |
| 297 | +- 编辑成功 **`versionNo` +1**。 | |
| 298 | + | |
| 299 | +### 联调注意 | |
| 300 | + | |
| 301 | +| 现象 | 处理 | | |
| 302 | +|------|------| | |
| 303 | +| 列表无 `regionIds` | 确认已部署含本变更的后端 | | |
| 304 | +| 传 `groupId` 列表仍很多 | 正常:`appliedLocation=ALL` 的模板始终可见 | | |
| 305 | +| 传 `groupId` 列表为空 | 检查 Region 是否存在、其下是否有门店;无效 Region 时仅剩 ALL 模板 | | |
| 306 | +| 传了 Region 仍显示 All Locations | 检查 Region Id 是否有效、是否能在库中展开到门店 | | |
| 307 | +| 仅 `appliedLocationIds` 不传 `locationIds` | 仍支持,与 `locationIds` 合并 | | |
| 308 | +| 前端编辑器仍传 `appliedLocation: "ALL"` | 管理端若需多选,须在 Body 增加 `regionIds` / `locationIds`(见 `labelTemplateService.ts`) | | |
| 309 | +| 指定范围但 0 门店 | 后端报错,需至少 1 个有效门店 | | |
| 310 | + | |
| 311 | +### 与 product-category / label-type 的关系 | |
| 312 | + | |
| 313 | +逻辑与 **`5-17接口优化.md`** 中 product-category、label-type 的 Region·Location 绑定一致,差异仅为: | |
| 314 | + | |
| 315 | +| 模块 | 范围字段名 | 关联表 | | |
| 316 | +|------|------------|--------| | |
| 317 | +| product-category | `availabilityType` | `fl_product_category_location` | | |
| 318 | +| label-type | `availabilityType` | `fl_label_type_location` | | |
| 319 | +| **label-template** | **`appliedLocation`** | **`fl_label_template_location`** | | |
| 320 | + | |
| 321 | +--- | |
| 322 | + | |
| 323 | +## rbac-role accessPermissions 修复 | |
| 324 | + | |
| 325 | +**应用服务**:`RbacRoleAppService` | |
| 326 | +**影响接口**:`POST` / `PUT /api/app/rbac-role/{id}`、`GET` 列表/详情回显 | |
| 327 | + | |
| 328 | +### 问题与根因 | |
| 329 | + | |
| 330 | +| 现象 | 根因 | | |
| 331 | +|------|------| | |
| 332 | +| 保存报 `accessPermissions 未匹配到任何菜单` | 前端提交 **JSON 数组字符串**(如 `["manage_labels",...]`),旧逻辑按逗号拆分,解析结果带 `["` 引号,无法匹配 | | |
| 333 | +| 传 `manage_labels` 等仍无菜单 | 表单权限码为 **UI 编码**(`manage_labels`),菜单侧为 **`menu.labels`**(由 `Menu.Router` 推导);二者未做映射 | | |
| 334 | +| 详情 `accessPermissionCodes` 为空 | 新增/编辑未写入 **`Role.AccessPermissionCodes`**(JSON 列),仅依赖 `RoleMenu` 反查 | | |
| 335 | + | |
| 336 | +### 变更说明 | |
| 337 | + | |
| 338 | +| 项 | 变更后 | | |
| 339 | +|----|--------| | |
| 340 | +| **入参解析** | `accessPermissions` 支持 **JSON 数组字符串**、逗号分隔、以及 Body 字段 **`accessPermissionCodes`** 数组 | | |
| 341 | +| **菜单绑定** | UI 权限码经 **`RoleAccessPermissionMenuMapping`** 映射到 `Menu.Router`,再写入 **`RoleMenu`** | | |
| 342 | +| **落库** | 同时将勾选的 UI 编码写入 **`Role.AccessPermissionCodes`**(JSON 数组),供 GET 回显 | | |
| 343 | +| **PermissionCode 为空** | 仍可按 **`Router`** 推导 `menu.xxx`(建议执行 `menu_backfill_permission_code.sql`) | | |
| 344 | + | |
| 345 | +### UI 权限码 → 菜单 Router 映射(当前库) | |
| 346 | + | |
| 347 | +| accessPermissions(UI) | 绑定菜单 Router | | |
| 348 | +|-------------------------|-----------------| | |
| 349 | +| `manage_labels` | `/labeling`、`/labels`、`/label-categories`、`/label-types`、`/label-templates` | | |
| 350 | +| `manage_people` | `/account-management` | | |
| 351 | +| `edit_settings` | `/menu-management`、`/multiple-options` | | |
| 352 | +| `view_reports` | `/reports` | | |
| 353 | +| `manage_products` | (当前 `Menu` 表无 Products 路由,勾选不绑定菜单,**不单独报错**) | | |
| 354 | +| `approve_batches` | (当前无对应菜单路由,同上) | | |
| 355 | + | |
| 356 | +> 至少 **1 个** 权限码能匹配到菜单即保存成功;若 **全部** 均无法匹配(例如只勾 `manage_products` 且库中无对应菜单),仍返回业务错误。 | |
| 357 | + | |
| 358 | +### 请求示例(与前端一致) | |
| 359 | + | |
| 360 | +```http | |
| 361 | +PUT /api/app/rbac-role/3a1f077b-3665-63f2-5fea-0fd7e7044b88 | |
| 362 | +Content-Type: application/json | |
| 363 | +Authorization: Bearer {token} | |
| 364 | +``` | |
| 365 | + | |
| 366 | +```json | |
| 367 | +{ | |
| 368 | + "roleName": "Partner Admin", | |
| 369 | + "roleCode": "admin", | |
| 370 | + "remark": "Admin", | |
| 371 | + "dataScope": 0, | |
| 372 | + "state": true, | |
| 373 | + "orderNum": 999, | |
| 374 | + "accessPermissions": "[\"manage_labels\",\"edit_settings\",\"view_reports\",\"manage_people\",\"manage_products\",\"approve_batches\"]" | |
| 375 | +} | |
| 376 | +``` | |
| 377 | + | |
| 378 | +也可使用逗号分隔(旧格式): | |
| 379 | + | |
| 380 | +```json | |
| 381 | +{ | |
| 382 | + "accessPermissions": "manage_labels, view_reports, manage_people" | |
| 383 | +} | |
| 384 | +``` | |
| 385 | + | |
| 386 | +或同时传数组字段(与 `accessPermissions` 合并去重): | |
| 387 | + | |
| 388 | +```json | |
| 389 | +{ | |
| 390 | + "accessPermissionCodes": ["manage_labels", "view_reports"] | |
| 391 | +} | |
| 392 | +``` | |
| 393 | + | |
| 394 | +### 入参优先级(与 5-18 一致) | |
| 395 | + | |
| 396 | +| menuIds | accessPermissions / accessPermissionCodes | 行为 | | |
| 397 | +|---------|-------------------------------------------|------| | |
| 398 | +| 非空数组 | 任意 | **以 menuIds 为准** | | |
| 399 | +| 不传 | 非空 | 按 UI 权限码映射菜单并覆盖 `RoleMenu` | | |
| 400 | +| 不传 | `""` 或空数组 | 清空 `RoleMenu` 与 `AccessPermissionCodes` | | |
| 401 | +| `[]` | 不传 | 清空绑定 | | |
| 402 | + | |
| 403 | +### 响应回显 | |
| 404 | + | |
| 405 | +| 字段 | 说明 | | |
| 406 | +|------|------| | |
| 407 | +| `accessPermissionCodes` | 来自 **`Role.AccessPermissionCodes`**,如 `["manage_labels","view_reports"]` | | |
| 408 | +| `accessPermissions` | 已绑定菜单的 **`menu.xxx`** 汇总(逗号拼接,只读展示) | | |
| 409 | +| `menuIds` | 已绑定菜单 Guid 列表(`RoleMenu`) | | |
| 410 | + | |
| 411 | +### 数据库准备(推荐) | |
| 412 | + | |
| 413 | +```bash | |
| 414 | +美国版/Food Labeling Management Code/Yi.Abp.Net8/module/food-labeling-us/scripts/menu_backfill_permission_code.sql | |
| 415 | +``` | |
| 416 | + | |
| 417 | +### 联调注意 | |
| 418 | + | |
| 419 | +| 现象 | 处理 | | |
| 420 | +|------|------| | |
| 421 | +| 仍报未匹配到菜单 | 确认已部署本修复;检查 `Menu` 是否存在上表 Router | | |
| 422 | +| 只勾 Products/Batches 报错 | 当前库无对应菜单属预期;请同时勾选 Labels/Reports 等 | | |
| 423 | +| 同时传 `menuIds: []` | **menuIds 优先**,会清空绑定并忽略 accessPermissions | | |
| 424 | + | |
| 425 | +> 更完整的 RBAC 说明见 **`5-18接口优化.md`** → rbac-role 章节。 | |
| 426 | + | |
| 427 | +--- | |
| 428 | + | |
| 429 | +## auth-scope 登录后 Company · Region · Location 级联选店 | |
| 430 | + | |
| 431 | +**应用服务**:`AuthScopeAppService` | |
| 432 | +**适用场景**:Web `POST /api/app/account/login` 或 App `POST /api/app/us-app-auth/login` 取得 Token 后,**管理员**无 `userlocation` 绑定时需先选工作门店;亦支持非管理员在数据范围内级联选择(须已绑定该门店)。 | |
| 433 | + | |
| 434 | +**命名约定**(与 5-17 一致):UI **Company** = **`partnerId`**(`fl_partner.Id`);UI **Region** = **`groupId`**(`fl_group.Id`);UI **Location** = **`locationId`**(`location.Id`,Guid 字符串)。 | |
| 435 | + | |
| 436 | +### 接口一览 | |
| 437 | + | |
| 438 | +| 步骤 | 方法 | 路径 | 说明 | | |
| 439 | +|------|------|------|------| | |
| 440 | +| 1 | GET | `/api/app/auth-scope/companies` | 可选公司列表 | | |
| 441 | +| 2 | GET | `/api/app/auth-scope/regions?partnerId={partnerId}` | 指定公司下 Region | | |
| 442 | +| 3 | GET | `/api/app/auth-scope/locations?partnerId={partnerId}&groupId={groupId}` | 指定公司+Region 下门店 | | |
| 443 | +| 4 | POST | `/api/app/auth-scope/select-location` | 确认当前工作门店 | | |
| 444 | +| — | GET | `/api/app/auth-scope/current-scope` | 查询已选工作门店(未选返回 `null`) | | |
| 445 | + | |
| 446 | +**鉴权**:均需 `Authorization: Bearer {token}`。 | |
| 447 | + | |
| 448 | +### 数据范围 | |
| 449 | + | |
| 450 | +| 角色 | Company | Region | Location | | |
| 451 | +|------|---------|--------|----------| | |
| 452 | +| **管理员**(`admin` / 用户名 `admin` / 权限 `*:*:*`) | 全部未删除公司 | 该公司下全部 Region | 该 Region 下全部门店(`location.Partner` + `location.GroupName` 与 `fl_group` 一致) | | |
| 453 | +| **非管理员** | `userlocation` 绑定门店所属公司 | 绑定门店对应 Region | 上述 Region 内且符合 `LocationRegionScopeHelper` 的门店;**选店时**须已绑定该 `locationId` | | |
| 454 | + | |
| 455 | +### 1)公司列表 | |
| 456 | + | |
| 457 | +```http | |
| 458 | +GET /api/app/auth-scope/companies HTTP/1.1 | |
| 459 | +Authorization: Bearer {token} | |
| 460 | +``` | |
| 461 | + | |
| 462 | +**响应**:`AuthScopeCompanyOptionDto[]` | |
| 463 | + | |
| 464 | +```json | |
| 465 | +[ | |
| 466 | + { "id": "fl_partner_id_1", "partnerName": "Acme Foods", "state": true } | |
| 467 | +] | |
| 468 | +``` | |
| 469 | + | |
| 470 | +### 2)Region 列表 | |
| 471 | + | |
| 472 | +```http | |
| 473 | +GET /api/app/auth-scope/regions?partnerId=fl_partner_id_1 HTTP/1.1 | |
| 474 | +Authorization: Bearer {token} | |
| 475 | +``` | |
| 476 | + | |
| 477 | +**响应**:`AuthScopeRegionOptionDto[]` | |
| 478 | + | |
| 479 | +```json | |
| 480 | +[ | |
| 481 | + { "id": "fl_group_id_east", "groupName": "East Region", "partnerId": "fl_partner_id_1", "state": true } | |
| 482 | +] | |
| 483 | +``` | |
| 484 | + | |
| 485 | +### 3)门店列表 | |
| 486 | + | |
| 487 | +```http | |
| 488 | +GET /api/app/auth-scope/locations?partnerId=fl_partner_id_1&groupId=fl_group_id_east HTTP/1.1 | |
| 489 | +Authorization: Bearer {token} | |
| 490 | +``` | |
| 491 | + | |
| 492 | +**响应**:`AuthScopeLocationOptionDto[]`(含 `fullAddress`、`groupName` 等) | |
| 493 | + | |
| 494 | +### 4)确认选店(与现有 App 逻辑对齐) | |
| 495 | + | |
| 496 | +```http | |
| 497 | +POST /api/app/auth-scope/select-location HTTP/1.1 | |
| 498 | +Authorization: Bearer {token} | |
| 499 | +Content-Type: application/json | |
| 500 | +``` | |
| 501 | + | |
| 502 | +```json | |
| 503 | +{ | |
| 504 | + "partnerId": "fl_partner_id_1", | |
| 505 | + "groupId": "fl_group_id_east", | |
| 506 | + "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f" | |
| 507 | +} | |
| 508 | +``` | |
| 509 | + | |
| 510 | +**响应**:`AuthScopeSelectLocationOutputDto` | |
| 511 | + | |
| 512 | +| 字段 | 说明 | | |
| 513 | +|------|------| | |
| 514 | +| partnerId / partnerName | 所选公司 | | |
| 515 | +| groupId / groupName | 所选 Region | | |
| 516 | +| location | 与 **`UsAppBoundLocationDto`** 相同(`id`、`locationCode`、`locationName`、`fullAddress`、`state`) | | |
| 517 | + | |
| 518 | +**选店后的服务端行为**(无需改前端即可对接 App): | |
| 519 | + | |
| 520 | +| 能力 | 行为 | | |
| 521 | +|------|------| | |
| 522 | +| **工作范围缓存** | 写入分布式缓存(24h);退出 `POST /api/app/auth-session/logout` 时清除 | | |
| 523 | +| **`GET /api/app/us-app-auth/my-locations`** | 管理员在缓存选店后,列表 **合并** 该门店(与 `userlocation` 并集) | | |
| 524 | +| **`GET .../location-detail/{locationId}`** | 管理员可不依赖 `userlocation` 访问已选门店(`UsAppPrintLogScopeHelper.EnsureUserCanAccessLocationAsync`) | | |
| 525 | +| **App 打印/报表** | 仍传 `locationId`;权限规则不变(见 `5-18接口优化.md`) | | |
| 526 | + | |
| 527 | +### 5)当前工作范围 | |
| 528 | + | |
| 529 | +```http | |
| 530 | +GET /api/app/auth-scope/current-scope HTTP/1.1 | |
| 531 | +Authorization: Bearer {token} | |
| 532 | +``` | |
| 533 | + | |
| 534 | +未选店时响应体为 **`null`**(HTTP 200)。 | |
| 535 | + | |
| 536 | +### 联调注意 | |
| 537 | + | |
| 538 | +| 现象 | 处理 | | |
| 539 | +|------|------| | |
| 540 | +| `regions` 为空 | 公司下无 `fl_group` 或当前账号无 Region 数据范围 | | |
| 541 | +| `locations` 为空 | 门店 `Partner` / `GroupName` 未与 `fl_partner`、`fl_group` 对齐 | | |
| 542 | +| 选店报「门店与所选公司/区域不匹配」 | 检查 `location.Partner`、`location.GroupName` | | |
| 543 | +| 非管理员选店报未绑定 | 须在 **Team Member** 中为该账号绑定该门店 | | |
| 544 | +| 选店后 `my-locations` 仍为空 | 确认已调 `select-location` 且 Token 为管理员身份 | | |
| 545 | + | |
| 546 | +> Web 管理端报表等模块仍可按 Query 传 `partnerId` / `groupId` / `locationId` 收窄;本组接口主要解决 **登录后选工作门店** 与 **App 门店列表** 一致性问题。 | |
| 547 | + | |
| 548 | +--- | |
| 549 | + | |
| 550 | +## us-app-auth App 管理员级联选店 | |
| 551 | + | |
| 552 | +**应用服务**:`UsAppAuthAppService` | |
| 553 | +**适用场景**:App 使用 **`POST /api/app/us-app-auth/login`** 登录后,持 **管理员** 身份(`admin` 角色 / 用户名 `admin` / 权限 `*:*:*`)且 JWT 含 **`client_kind=us-app`**,按 Company → Region 筛选门店。 | |
| 554 | + | |
| 555 | +**与 `auth-scope` 关系**:查询逻辑共用 `AuthScopeQueryHelper`;App 侧路径统一在 **`us-app-auth`** 下,并 **强制 App Token + 管理员**,避免误用 Web Token。 | |
| 556 | + | |
| 557 | +### 接口一览 | |
| 558 | + | |
| 559 | +| 步骤 | 方法 | 路径 | 说明 | | |
| 560 | +|------|------|------|------| | |
| 561 | +| 0 | POST | `/api/app/us-app-auth/login` | 获取 App Token(须管理员账号) | | |
| 562 | +| 1 | GET | `/api/app/us-app-auth/admin-scope-companies` | 公司列表 → 取 `id` 作 `partnerId` | | |
| 563 | +| 2 | GET | `/api/app/us-app-auth/admin-scope-regions?partnerId={partnerId}` | Region 列表 → 取 `id` 作 `groupId` | | |
| 564 | +| 3 | GET | `/api/app/us-app-auth/admin-scope-locations?partnerId={partnerId}&groupId={groupId}` | **按公司与 Region Id 筛选门店** | | |
| 565 | +| 4 | POST | `/api/app/us-app-auth/select-admin-scope-location` | 确认工作门店 | | |
| 566 | +| — | GET | `/api/app/us-app-auth/my-locations` | 选店后刷新绑定门店(含缓存门店) | | |
| 567 | + | |
| 568 | +**鉴权**:步骤 1–4 须 Header `Authorization: Bearer {App登录返回的token}`。 | |
| 569 | + | |
| 570 | +### 前置条件 | |
| 571 | + | |
| 572 | +| 项 | 要求 | | |
| 573 | +|----|------| | |
| 574 | +| Token 来源 | 必须来自 **`/api/app/us-app-auth/login`**(非 Web `/api/app/account/login`) | | |
| 575 | +| JWT 声明 | `client_kind` = `us-app` | | |
| 576 | +| 角色 | 平台管理员(`ReportsRoleHelper.IsAdminRole`) | | |
| 577 | +| 违反时 | `请使用 App 登录令牌调用该接口` 或 `仅管理员可使用公司/区域/门店筛选接口` | | |
| 578 | + | |
| 579 | +### 1)公司列表 | |
| 580 | + | |
| 581 | +```http | |
| 582 | +GET /api/app/us-app-auth/admin-scope-companies HTTP/1.1 | |
| 583 | +Authorization: Bearer {app_token} | |
| 584 | +``` | |
| 585 | + | |
| 586 | +**响应**:`AuthScopeCompanyOptionDto[]`(与 auth-scope 相同) | |
| 587 | + | |
| 588 | +```json | |
| 589 | +[ | |
| 590 | + { "id": "fl_partner_id_1", "partnerName": "Acme Foods", "state": true } | |
| 591 | +] | |
| 592 | +``` | |
| 593 | + | |
| 594 | +### 2)Region 列表 | |
| 595 | + | |
| 596 | +```http | |
| 597 | +GET /api/app/us-app-auth/admin-scope-regions?partnerId=fl_partner_id_1 HTTP/1.1 | |
| 598 | +Authorization: Bearer {app_token} | |
| 599 | +``` | |
| 600 | + | |
| 601 | +**响应**:`AuthScopeRegionOptionDto[]` | |
| 602 | + | |
| 603 | +```json | |
| 604 | +[ | |
| 605 | + { "id": "fl_group_id_east", "groupName": "East Region", "partnerId": "fl_partner_id_1", "state": true } | |
| 606 | +] | |
| 607 | +``` | |
| 608 | + | |
| 609 | +### 3)门店列表(按 partnerId + groupId 筛选) | |
| 610 | + | |
| 611 | +```http | |
| 612 | +GET /api/app/us-app-auth/admin-scope-locations?partnerId=fl_partner_id_1&groupId=fl_group_id_east HTTP/1.1 | |
| 613 | +Authorization: Bearer {app_token} | |
| 614 | +``` | |
| 615 | + | |
| 616 | +**Query** | |
| 617 | + | |
| 618 | +| 参数 | 必填 | 说明 | | |
| 619 | +|------|------|------| | |
| 620 | +| partnerId | 是 | 公司 Id(`fl_partner.Id`) | | |
| 621 | +| groupId | 是 | Region Id(`fl_group.Id`) | | |
| 622 | + | |
| 623 | +**响应**:`AuthScopeLocationOptionDto[]` | |
| 624 | + | |
| 625 | +```json | |
| 626 | +[ | |
| 627 | + { | |
| 628 | + "id": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f", | |
| 629 | + "locationCode": "LOC-1", | |
| 630 | + "locationName": "Downtown Kitchen", | |
| 631 | + "fullAddress": "123 Main St, New York, NY 10001", | |
| 632 | + "state": true, | |
| 633 | + "partnerId": "fl_partner_id_1", | |
| 634 | + "groupId": "fl_group_id_east", | |
| 635 | + "groupName": "East Region" | |
| 636 | + } | |
| 637 | +] | |
| 638 | +``` | |
| 639 | + | |
| 640 | +筛选规则:`location.Partner` 匹配该公司(Id 或名称),且 `location.GroupName` 与所选 `fl_group.GroupName` 一致。 | |
| 641 | + | |
| 642 | +### 4)确认选店 | |
| 643 | + | |
| 644 | +```http | |
| 645 | +POST /api/app/us-app-auth/select-admin-scope-location HTTP/1.1 | |
| 646 | +Authorization: Bearer {app_token} | |
| 647 | +Content-Type: application/json | |
| 648 | +``` | |
| 649 | + | |
| 650 | +```json | |
| 651 | +{ | |
| 652 | + "partnerId": "fl_partner_id_1", | |
| 653 | + "groupId": "fl_group_id_east", | |
| 654 | + "locationId": "a2696b9e-2277-11f1-b4c6-00163e0c7c4f" | |
| 655 | +} | |
| 656 | +``` | |
| 657 | + | |
| 658 | +**响应**:`AuthScopeSelectLocationOutputDto`(含 `location` 节点,结构同 `UsAppBoundLocationDto`) | |
| 659 | + | |
| 660 | +### 推荐调用顺序(App) | |
| 661 | + | |
| 662 | +```text | |
| 663 | +POST /api/app/us-app-auth/login | |
| 664 | + → GET admin-scope-companies | |
| 665 | + → GET admin-scope-regions?partnerId=... | |
| 666 | + → GET admin-scope-locations?partnerId=...&groupId=... | |
| 667 | + → POST select-admin-scope-location | |
| 668 | + → GET my-locations | |
| 669 | + → 后续业务接口传 locationId(打印、报表等,规则不变) | |
| 670 | +``` | |
| 671 | + | |
| 672 | +### 联调注意 | |
| 673 | + | |
| 674 | +| 现象 | 处理 | | |
| 675 | +|------|------| | |
| 676 | +| 报「请使用 App 登录令牌」 | 勿用 Web `account/login` 的 Token;须重新 App 登录 | | |
| 677 | +| 报「仅管理员可使用」 | 换管理员账号或绑定 `admin` 角色 | | |
| 678 | +| `locations` 为空 | 核对门店 `Partner`、`GroupName` 与 `fl_partner`、`fl_group` | | |
| 679 | +| 与 auth-scope 重复 | App 端 **优先** 使用本节前缀;Web 端用 `auth-scope` | | |
| 680 | + | |
| 681 | +--- | |
| 682 | + | |
| 683 | +## 变更记录 | |
| 684 | + | |
| 685 | +| 日期 | 说明 | | |
| 686 | +|------|------| | |
| 687 | +| 2026-05-26 | us-app-auth:App 管理员 `admin-scope-companies/regions/locations`、`select-admin-scope-location` | | |
| 688 | +| 2026-05-26 | auth-scope:登录后 Company/Region/Location 级联选店;选店缓存;`my-locations` / 门店详情与管理员选店对齐 | | |
| 689 | +| 2026-05-26 | product-category:`categoryCode` 新增/编辑改为可选 | | |
| 690 | +| 2026-05-26 | label-template:新增/编辑/列表/详情支持 `regionIds`、`locationIds` 及 `region`、`location` 展示 | | |
| 691 | +| 2026-05-26 | label-template 列表 Query 增加 `groupId`(Region)、`locationId`(门店)筛选 | | |
| 692 | +| 2026-05-26 | rbac-role:支持 accessPermissions JSON 数组 + UI 权限码映射 Menu;落库 AccessPermissionCodes | | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.browserslistrc
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.changeset/README.md
0 → 100644
| 1 | +# Changesets | |
| 2 | + | |
| 3 | +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) | |
| 4 | + | |
| 5 | +We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.changeset/config.json
0 → 100644
| 1 | +{ | |
| 2 | + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", | |
| 3 | + "changelog": [ | |
| 4 | + "@changesets/changelog-github", | |
| 5 | + { "repo": "vbenjs/vue-vben-admin" } | |
| 6 | + ], | |
| 7 | + "commit": false, | |
| 8 | + "fixed": [["@vben-core/*", "@vben/*"]], | |
| 9 | + "snapshot": { | |
| 10 | + "prereleaseTemplate": "{tag}-{datetime}" | |
| 11 | + }, | |
| 12 | + "privatePackages": { "version": true, "tag": true }, | |
| 13 | + "linked": [], | |
| 14 | + "access": "public", | |
| 15 | + "baseBranch": "main", | |
| 16 | + "updateInternalDependencies": "patch", | |
| 17 | + "ignore": [] | |
| 18 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.commitlintrc.js
0 → 100644
| 1 | +export { default } from '@vben/commitlint-config'; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.dockerignore
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.editorconfig
0 → 100644
| 1 | +root = true | |
| 2 | + | |
| 3 | +[*] | |
| 4 | +charset=utf-8 | |
| 5 | +end_of_line=lf | |
| 6 | +insert_final_newline=true | |
| 7 | +indent_style=space | |
| 8 | +indent_size=2 | |
| 9 | +max_line_length = 100 | |
| 10 | +trim_trailing_whitespace = true | |
| 11 | +quote_type = single | |
| 12 | + | |
| 13 | +[*.{yml,yaml,json}] | |
| 14 | +indent_style = space | |
| 15 | +indent_size = 2 | |
| 16 | + | |
| 17 | +[*.md] | |
| 18 | +trim_trailing_whitespace = false | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.gitattributes
0 → 100644
| 1 | +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings | |
| 2 | + | |
| 3 | +# Automatically normalize line endings (to LF) for all text-based files. | |
| 4 | +* text=auto eol=lf | |
| 5 | + | |
| 6 | +# Declare files that will always have CRLF line endings on checkout. | |
| 7 | +*.{cmd,[cC][mM][dD]} text eol=crlf | |
| 8 | +*.{bat,[bB][aA][tT]} text eol=crlf | |
| 9 | + | |
| 10 | +# Denote all files that are truly binary and should not be modified. | |
| 11 | +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary | |
| 0 | 12 | \ No newline at end of file | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.gitconfig
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.gitignore
0 → 100644
| 1 | +node_modules | |
| 2 | +.DS_Store | |
| 3 | +dist | |
| 4 | +dist-ssr | |
| 5 | +dist.zip | |
| 6 | +dist.tar | |
| 7 | +dist.war | |
| 8 | +.nitro | |
| 9 | +.output | |
| 10 | +*-dist.zip | |
| 11 | +*-dist.tar | |
| 12 | +*-dist.war | |
| 13 | +coverage | |
| 14 | +*.local | |
| 15 | +**/.vitepress/cache | |
| 16 | +.cache | |
| 17 | +.turbo | |
| 18 | +.temp | |
| 19 | +dev-dist | |
| 20 | +.stylelintcache | |
| 21 | +yarn.lock | |
| 22 | +package-lock.json | |
| 23 | +pnpm-lock.yaml | |
| 24 | +.VSCodeCounter | |
| 25 | +**/backend-mock/data | |
| 26 | + | |
| 27 | +# local env files | |
| 28 | +.env.local | |
| 29 | +.env.*.local | |
| 30 | +.eslintcache | |
| 31 | + | |
| 32 | +logs | |
| 33 | +*.log | |
| 34 | +npm-debug.log* | |
| 35 | +yarn-debug.log* | |
| 36 | +yarn-error.log* | |
| 37 | +pnpm-debug.log* | |
| 38 | +lerna-debug.log* | |
| 39 | +vite.config.mts.* | |
| 40 | +vite.config.mjs.* | |
| 41 | +vite.config.js.* | |
| 42 | +vite.config.ts.* | |
| 43 | + | |
| 44 | +# Editor directories and files | |
| 45 | +.idea | |
| 46 | +# .vscode | |
| 47 | +*.suo | |
| 48 | +*.ntvs* | |
| 49 | +*.njsproj | |
| 50 | +*.sln | |
| 51 | +*.sw? | |
| 52 | +# 排除自动生成的类型文件 | |
| 53 | +apps/web-antd/types/components.d.ts | |
| 54 | +.history | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.gitpod.yml
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.node-version
0 → 100644
| 1 | +22.1.0 | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.npmrc
0 → 100644
| 1 | +registry = "https://registry.npmmirror.com" | |
| 2 | +public-hoist-pattern[]=lefthook | |
| 3 | +public-hoist-pattern[]=eslint | |
| 4 | +public-hoist-pattern[]=prettier | |
| 5 | +public-hoist-pattern[]=prettier-plugin-tailwindcss | |
| 6 | +public-hoist-pattern[]=stylelint | |
| 7 | +public-hoist-pattern[]=*postcss* | |
| 8 | +public-hoist-pattern[]=@commitlint/* | |
| 9 | +public-hoist-pattern[]=czg | |
| 10 | + | |
| 11 | +strict-peer-dependencies=false | |
| 12 | +auto-install-peers=true | |
| 13 | +dedupe-peer-dependents=true | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.prettierignore
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.prettierrc.mjs
0 → 100644
| 1 | +export { default } from '@vben/prettier-config'; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.stylelintignore
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/CHANGELOG.md
0 → 100644
| 1 | +# 1.4.1 | |
| 2 | + | |
| 3 | +**FEATURES** | |
| 4 | + | |
| 5 | +- Tinymce添加在antd原生表单/useVbenForm下的校验样式 | |
| 6 | +- useVbenForm 增加 Cascader(级联选择器) 组件 | |
| 7 | + | |
| 8 | +**BUG FIX** | |
| 9 | + | |
| 10 | +- 菜单管理 路由地址的必填项不生效 | |
| 11 | +- withDefaultPlaceholder中placeholder 在keepalive & 语言切换 & tab切换 显示不变的问题 | |
| 12 | + | |
| 13 | +**REFACTOR** | |
| 14 | + | |
| 15 | +- 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理 | |
| 16 | +- 代码生成 字典下拉加载 改为每次进入编辑页面都加载 | |
| 17 | +- ~~个人中心 账号绑定 样式/逻辑重构~~(回滚了 既要又要的问题) | |
| 18 | +- ~~个人中心 下拉卡片 昵称超长省略显示~~(回滚了 既要又要的问题) | |
| 19 | + | |
| 20 | +# 1.4.0 | |
| 21 | + | |
| 22 | +**FEATURES** | |
| 23 | + | |
| 24 | +- 菜单管理(通用方法) 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化) | |
| 25 | +- | |
| 26 | +- 菜单管理 级联删除 删除菜单和children | |
| 27 | + | |
| 28 | +**REFACTOR** | |
| 29 | + | |
| 30 | +- 除个人中心外所有本地路由改为从后端返回(需要执行更新sql) | |
| 31 | +- 流程图预览改为logicflow预览而非图片 ...然后后端又更新了 又改成iframe了 | |
| 32 | +- 菜单管理 新增角色校验(与后端权限保持一致) 只有superadmin可进行增删改 | |
| 33 | + | |
| 34 | +# 1.3.6 | |
| 35 | + | |
| 36 | +**BUG FIX** | |
| 37 | + | |
| 38 | +- oss配置switch切换 导致报错`存储类型找不到` | |
| 39 | +- 文件上传无法正确清除(innerList) | |
| 40 | + | |
| 41 | +# 1.3.5 | |
| 42 | + | |
| 43 | +**BUG FIX** | |
| 44 | + | |
| 45 | +- 某些带Vxe表格弹窗 关闭后没有正常清理表格数据的问题 | |
| 46 | + | |
| 47 | +# 1.3.4 | |
| 48 | + | |
| 49 | +**BUG FIX** | |
| 50 | + | |
| 51 | +- 文件上传多次触发导致数据不一致 https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC3BK6 | |
| 52 | + | |
| 53 | +**PREFORMANCE** | |
| 54 | + | |
| 55 | +- 浏览器返回按钮/手势操作时 弹窗不会被关闭(keepAlive导致) | |
| 56 | + | |
| 57 | +# 1.3.3 | |
| 58 | + | |
| 59 | +**BUG FIX** | |
| 60 | + | |
| 61 | +- 工作流list展示在开启缩放会有误差导致触底逻辑不会触发 | |
| 62 | + | |
| 63 | +**OTHER** | |
| 64 | + | |
| 65 | +- 代码生成预览对模板的提示...(下载都懒得点一下吗) | |
| 66 | + | |
| 67 | +# 1.3.2 | |
| 68 | + | |
| 69 | +**REFACTOR** | |
| 70 | + | |
| 71 | +- 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况) | |
| 72 | +- 菜单图标更新了一部分 sql同步更新 | |
| 73 | + | |
| 74 | +**OTHER** | |
| 75 | + | |
| 76 | +- 暂时锁死vite依赖 i18n会报错 | |
| 77 | + | |
| 78 | +# 1.3.1 | |
| 79 | + | |
| 80 | +**REFACTOR** | |
| 81 | + | |
| 82 | +- 所有Modal/Drawer表单关闭前会进行表单数据对比来弹出提示框 | |
| 83 | +- 字典项颜色选择从`原生input type=color`改为`vue3-colorpicker`组件 | |
| 84 | +- 全局Header: ClientID 更改大小写 [spring的问题导致](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS) | |
| 85 | + | |
| 86 | +**BUG FIX** | |
| 87 | + | |
| 88 | +- getVxePopupContainer逻辑调整 解决表格固定高度展开不全的问题 | |
| 89 | + | |
| 90 | +**FEATURES** | |
| 91 | + | |
| 92 | +- 字典渲染支持loading(length为0情况) | |
| 93 | + | |
| 94 | +**OTHERS** | |
| 95 | + | |
| 96 | +- useForm的组件改为异步导入(官方更新) bootstrap.js体积从2M降到600K 首屏加载速度提升 | |
| 97 | + | |
| 98 | +# 1.3.0 | |
| 99 | + | |
| 100 | +注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用 | |
| 101 | + | |
| 102 | +- `component: 'ImageUploadOld'` | |
| 103 | +- `component: 'FileUploadOld'` | |
| 104 | + | |
| 105 | +代替 **建议替换为新版本** | |
| 106 | + | |
| 107 | +大致变动: | |
| 108 | + | |
| 109 | +- `accept string[] -> string` | |
| 110 | +- `resultField 已经移除 统一使用ossId` | |
| 111 | +- `maxNumber -> maxCount` | |
| 112 | + | |
| 113 | +具体参数查看: `apps/web-antd/src/components/upload/src/props.d.ts` | |
| 114 | + | |
| 115 | +不再推荐使用useDescription, 这个版本会标记为@deprecated, 下个次版本将会移除 | |
| 116 | + | |
| 117 | +框架所有使用useDescription组件的会替换为原生(TODO) | |
| 118 | + | |
| 119 | +**REFACTOR** | |
| 120 | + | |
| 121 | +- **文件上传/图片上传重构(破坏性更新 不兼容之前的api)** | |
| 122 | +- **文件上传/图片上传不再支持url用法 强制使用ossId** | |
| 123 | +- TableSwitch组件重构 | |
| 124 | +- 管理员租户切换不再返回首页 直接刷新当前页(除特殊页面外会回到首页) | |
| 125 | +- 租户切换Select增加loading | |
| 126 | +- ~~modalLoading/drawerLoading改为调用内部的lock/unlock方法~~ 有待商榷暂时按老版本逻辑不变 | |
| 127 | +- 登录验证码 增加loading | |
| 128 | +- DictEnum使用const代替enum | |
| 129 | +- TinyMCE组件重构 移除冗余代码/功能 增加loading | |
| 130 | + | |
| 131 | +**ALPHA功能** | |
| 132 | + | |
| 133 | +- 弹窗表单数据更改关闭时的提示框(可能最终不会加入) 测试页面: 参数管理 | |
| 134 | + | |
| 135 | +**BUG FIX** | |
| 136 | + | |
| 137 | +- 重新登录 字典会unknown的情况[详细分析](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IBY27D) | |
| 138 | +- 测试菜单 请假申请 选中删除 需要根据状态判断 | |
| 139 | +- 修复文件/图片在Safari中无法上传 file-type库与Safari不兼容导致 | |
| 140 | +- 头像裁剪 图片加载失败一直处于loading无法上传 | |
| 141 | +- 头像裁剪 私有桶会拼接timestamp参数导致sign计算异常无法上传 感谢cropperjs作者 https://github.com/fengyuanchen/cropperjs/issues/1230 | |
| 142 | +- 租户选择下拉框会跟随body滚动(将下拉框样式的默认absolute改为fixed) | |
| 143 | + | |
| 144 | +**OTHER** | |
| 145 | + | |
| 146 | +- 字典管理 字典类型 表格选中行增加bold效果 | |
| 147 | +- 全局圆角修改 与antd保持一致 | |
| 148 | +- vditor(Markdown)升级3.10.9 | |
| 149 | +- 老版本的文件/图片上传将于下个版本移除 | |
| 150 | +- useDescription将于下个版本移除 | |
| 151 | +- getVxePopupContainer与新版Vxe不兼容 先返回body(会导致滚动不跟随)后续版本再优化 | |
| 152 | + | |
| 153 | +# 1.2.3 | |
| 154 | + | |
| 155 | +**BUG FIX** | |
| 156 | + | |
| 157 | +- `withDefaultPlaceholder`中将`placeholder`修改为computed, 解决后续使用`updateSchema`无法正常更新显示placeholder(响应式问题) | |
| 158 | + | |
| 159 | +- 流程定义 修改accept类型 解决无法拖拽上传 | |
| 160 | + | |
| 161 | +**FEATURES** | |
| 162 | + | |
| 163 | +- 增加`环境变量`打包配置demo -> build:antd:test | |
| 164 | +- 角色管理 勾选权限组件添加对错误用法的校验提示 | |
| 165 | + | |
| 166 | +**REFACTOR** | |
| 167 | + | |
| 168 | +- OAuth内部逻辑重构 增加新的默认OAuth登录方式 | |
| 169 | +- 重构部分setup组件为setup语法糖形式 | |
| 170 | + | |
| 171 | +# 1.2.2 | |
| 172 | + | |
| 173 | +**FEATURES** | |
| 174 | + | |
| 175 | +- 代码生成支持路径方式生成 | |
| 176 | +- 代码生成 支持选择表单生成类型(需要模板支持) | |
| 177 | +- 工作流 支持按钮权限 | |
| 178 | + | |
| 179 | +# 1.2.1 | |
| 180 | + | |
| 181 | +# BUG FIXES | |
| 182 | + | |
| 183 | +- 客户端管理 错误的status disabled | |
| 184 | +- modal/drawer升级后zIndex(2000)会遮挡Tinymce的下拉框zIndex(1300) | |
| 185 | + | |
| 186 | +# 1.2.0 | |
| 187 | + | |
| 188 | +**REFACTOR** | |
| 189 | + | |
| 190 | +- 菜单选择组件重构为Table形式 | |
| 191 | +- 字典相关功能重构 采用一个Map储存字典(之前为两个Map) | |
| 192 | +- 代码生成配置页面重构 去除步骤条 | |
| 193 | + | |
| 194 | +**Features** | |
| 195 | + | |
| 196 | +- 对接后端工作流 | |
| 197 | +- ~~通用的vxe-table排序事件(排序逻辑改为在排序事件中处理而非在api处理)~~ | |
| 198 | +- getDict/getDictOptions 提取公共逻辑 减少冗余代码 | |
| 199 | +- 字典新增对Number类型的支持 -> `getDictOptions('', true);`即可获取number类型的value | |
| 200 | +- 文件上传 增加上传进度条 下方上传提示 | |
| 201 | +- 图片上传 增加上传进度条 下方上传提示 | |
| 202 | +- oss下载进度提示 | |
| 203 | + | |
| 204 | +**BUG FIXES** | |
| 205 | + | |
| 206 | +- 字典项为空时getDict方法无限调用接口(无奈兼容 不给字典item本来就是错误用法) | |
| 207 | +- 表格排序翻页会丢失排序参数 | |
| 208 | +- 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况 | |
| 209 | +- requestClient缺失i18n内容 | |
| 210 | + | |
| 211 | +**OTHERS** | |
| 212 | + | |
| 213 | +- 用户管理 新增只获取一次(mounted)默认密码而非每次打开modal都获取 | |
| 214 | +- `apps/web-antd/src/utils/dict.ts` `getDict`方法将于下个版本删除 使用`getDictOptions`替代 | |
| 215 | +- VxeTable升级V4.10.0 | |
| 216 | +- 移除`@deprecated` `apps/web-antd/src/adapter/vxe-table.ts`的`tableCheckboxEvent`方法 | |
| 217 | +- 移除`由于更新方案弃用的` `apps/web-antd/src/adapter/vxe-table.ts`的`vxeSortEvent`方法 | |
| 218 | +- 移除apps下的ele和naive目录 | |
| 219 | + | |
| 220 | +# 1.1.3 | |
| 221 | + | |
| 222 | +**REFACTOR** | |
| 223 | + | |
| 224 | +- 重构: 判断vxe-table的复选框是否选中 | |
| 225 | + | |
| 226 | +**Bug Fixes** | |
| 227 | + | |
| 228 | +- 节点树在编辑 & 空数组(不勾选)情况 勾选节点会造成watch延迟触发 导致会带上父节点id造成id重复 | |
| 229 | +- 节点树在节点独立情况下的控制台warning: Invalid prop: type check failed for prop "value". Expected Array, got Object | |
| 230 | + | |
| 231 | +**Others** | |
| 232 | + | |
| 233 | +- 角色管理 优化Drawer布局 | |
| 234 | +- unplugin-vue-components插件(默认未开启) 需要排除Button组件 全局已经默认导入了 | |
| 235 | + | |
| 236 | +**BUG FIXES** | |
| 237 | + | |
| 238 | +- 操作日志详情 在description组件中json预览样式异常 | |
| 239 | +- 微服务版本 区间查询和中文搜索条件一起使用 无法正确查询 | |
| 240 | + | |
| 241 | +# 1.1.2 | |
| 242 | + | |
| 243 | +**Features** | |
| 244 | + | |
| 245 | +- Options转Enum工具函数 | |
| 246 | + | |
| 247 | +**OTHERS** | |
| 248 | + | |
| 249 | +- 菜单管理 改为虚拟滚动 | |
| 250 | +- 移除requestClient的一些冗余参数 | |
| 251 | +- 主动退出登录(右上角个人选项)不需要带跳转地址 | |
| 252 | + | |
| 253 | +**BUG FIXES** | |
| 254 | + | |
| 255 | +- 语言 漏加Content-Language请求头 | |
| 256 | +- 用户管理/岗位管理 左边部门树错误emit导致会调用两次列表api | |
| 257 | + | |
| 258 | +# 1.1.1 | |
| 259 | + | |
| 260 | +**REFACTOR** | |
| 261 | + | |
| 262 | +- 使用VxeTable重构OAuth账号绑定列表(替代antdv的Table) | |
| 263 | +- commonDownloadExcel方法 支持处理区间选择器字段导出excel | |
| 264 | + | |
| 265 | +**BUG FIXES** | |
| 266 | + | |
| 267 | +- 修复在Modal/Drawer中使用VxeTable时, 第二次打开表单参数依旧为第一次提交的参数 | |
| 268 | + | |
| 269 | +**OTHERS** | |
| 270 | + | |
| 271 | +- 废弃downloadExcel方法 统一使用commonDownloadExcel方法 | |
| 272 | + | |
| 273 | +# 1.1.0 | |
| 274 | + | |
| 275 | +**FEATURES** | |
| 276 | + | |
| 277 | +- 支持离线图标功能(全局可在内网环境中使用) | |
| 278 | + | |
| 279 | +**BUG FIXES** | |
| 280 | + | |
| 281 | +- 在VxeTable固定列时, getPopupContainer会导致宽度不够, 弹出层样式异常 解决办法(将弹窗元素挂载到VXe滚动容器上) | |
| 282 | + | |
| 283 | +**OTHERS** | |
| 284 | + | |
| 285 | +- 代码生成 - 字段信息修改 改为minWidth 防止在高分辨率屏幕出现空白 | |
| 286 | + | |
| 287 | +# 1.0.0 | |
| 288 | + | |
| 289 | +**FEATURES** | |
| 290 | + | |
| 291 | +- 用户管理 新增从参数取默认密码 | |
| 292 | +- 全局表格加上id 方便进行缓存列排序的操作 | |
| 293 | +- 支持菜单名称i18n | |
| 294 | +- 登录页 验证码登录 | |
| 295 | +- Markdown编辑/预览组件(基于vditor) | |
| 296 | +- VxeTable搜索表单 enter提交 | |
| 297 | + | |
| 298 | +**BUG FIXES** | |
| 299 | + | |
| 300 | +- 登录页面 关闭租户后下拉框没有正常隐藏 | |
| 301 | +- 字典管理 关闭租户不应显示`同步租户字典`按钮 | |
| 302 | +- 登录日志 漏掉了登录日志日期查询 | |
| 303 | +- 登出相关逻辑在并发(非await)情况下重复执行的问题 | |
| 304 | +- VxeTable在开启/关闭查询表单时 需要使用不同的padding | |
| 305 | +- VxeTable表格刷新 默认为reload 修改为在当前页刷新(query) | |
| 306 | +- 岗位管理 部门参数错误 | |
| 307 | +- 角色管理 菜单分配 节点独立下的回显及提交问题 | |
| 308 | +- 租户管理 套餐管理 回显时候`已选中节点`数量为0 | |
| 309 | +- 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息 | |
| 310 | + | |
| 311 | +**OTHERS** | |
| 312 | + | |
| 313 | +- 登录页 租户选择框浮层固定高度[256px] 超过高度自动滚动 | |
| 314 | +- 表单的Label默认方向改为`top` 支持\n换行 | |
| 315 | +- 所有表格的搜索加上allowClear属性 支持清除 | |
| 316 | +- vxe表格loading 只加载表格 不加载上面的表单 | |
| 317 | + | |
| 318 | +# 1.0.0-beta (2024-10-8) | |
| 319 | + | |
| 320 | +**FEATURES** | |
| 321 | + | |
| 322 | +- 基础功能已经开发完毕 | |
| 323 | +- 工作流相关模块等待后端重构后开发 | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/LICENSE
0 → 100644
| 1 | +MIT License | |
| 2 | + | |
| 3 | +Copyright (c) 2026 dubai | |
| 4 | + | |
| 5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 6 | +of this software and associated documentation files (the "Software"), to deal | |
| 7 | +in the Software without restriction, including without limitation the rights | |
| 8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| 9 | +copies of the Software, and to permit persons to whom the Software is | |
| 10 | +furnished to do so, subject to the following conditions: | |
| 11 | + | |
| 12 | +The above copyright notice and this permission notice shall be included in all | |
| 13 | +copies or substantial portions of the Software. | |
| 14 | + | |
| 15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| 20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| 21 | +SOFTWARE. | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/README.md
0 → 100644
| 1 | +## **简介** | |
| 2 | + | |
| 3 | +[](LICENSE) | |
| 4 | + | |
| 5 | +基于 [ruoyi-plus-vben & vben5 & ant-design-vue ](https://gitee.com/dapppp/ruoyi-plus-vben.git) 的 Yi 框架前端项目 | |
| 6 | + | |
| 7 | +完全兼容意框架[Yi.Admin](https://gitee.com/ccnetcore/Yi) rbac模块 | |
| 8 | + | |
| 9 | +| 组件/框架 | 版本 | | |
| 10 | +| :------------- | :----- | | |
| 11 | +| vben | 5.5.6 | | |
| 12 | +| ant-design-vue | 4.2.6 | | |
| 13 | +| vue | 3.5.13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | +## 提示 | |
| 18 | + | |
| 19 | +该仓库使用vben5开发,采用分包目录结构, 具体开发路径为: `根目录/apps/web-antd` | |
| 20 | + | |
| 21 | +**后端需要开启”furion格式的规范化api“**:路径在Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | +## 文档 | |
| 27 | + | |
| 28 | +[ruoyi-vben 框架文档](https://dapdap.top/) | |
| 29 | + | |
| 30 | +[VbenAdmin V5 文档地址](https://doc.vben.pro/) | |
| 31 | + | |
| 32 | + | |
| 33 | +## 安装使用 | |
| 34 | + | |
| 35 | +前置准备环境(只能用pnpm) | |
| 36 | + | |
| 37 | +```json | |
| 38 | +"packageManager": "pnpm", | |
| 39 | +"engines": { | |
| 40 | + "node": ">=20.15.0", | |
| 41 | + "pnpm": "latest" | |
| 42 | +}, | |
| 43 | +``` | |
| 44 | + | |
| 45 | +安装依赖 | |
| 46 | + | |
| 47 | +```bash | |
| 48 | +cd yiabp-mini | |
| 49 | + | |
| 50 | +pnpm install | |
| 51 | +``` | |
| 52 | + | |
| 53 | +运行 | |
| 54 | + | |
| 55 | +```bash | |
| 56 | +pnpm dev:antd | |
| 57 | +``` | |
| 58 | + | |
| 59 | +打包 | |
| 60 | + | |
| 61 | +```bash | |
| 62 | +pnpm build:antd | |
| 63 | +``` | |
| 64 | + | |
| 65 | +## 这是一个特性 而不是一个bug! | |
| 66 | + | |
| 67 | +1. 菜单管理可分配 但只有`admin`/`superadmin`角色能访问 其他角色访问会到403页面 | |
| 68 | +2. 租户相关菜单可分配 但只有`superadmin`角色能访问 其他角色访问会到403页面 | |
| 69 | +3. 分配的租户管理员无法修改自己的角色的菜单(即管理员角色的菜单) 防止自己把自己权限弄没了 | |
| 70 | + | |
| 71 | +## Git 贡献提交规范 | |
| 72 | + | |
| 73 | +参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) | |
| 74 | + | |
| 75 | +- `feat` 增加新功能 | |
| 76 | +- `fix` 修复问题/BUG | |
| 77 | +- `style` 代码风格相关无影响运行结果的 | |
| 78 | +- `perf` 优化/性能提升 | |
| 79 | +- `refactor` 重构 | |
| 80 | +- `revert` 撤销修改 | |
| 81 | +- `test` 测试相关 | |
| 82 | +- `docs` 文档/注释 | |
| 83 | +- `chore` 依赖更新/脚手架配置修改等 | |
| 84 | +- `workflow` 工作流改进 | |
| 85 | +- `ci` 持续集成 | |
| 86 | +- `types` 类型定义文件更改 | |
| 87 | +- `wip` 开发中 | |
| 88 | + | |
| 89 | +## 浏览器支持 | |
| 90 | + | |
| 91 | +最低适配应该为`Chrome 88+`以上浏览器 详见 [css - where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility) | |
| 92 | + | |
| 93 | +本地开发推荐使用`Chrome` 最新版本浏览器 | |
| 94 | + | |
| 95 | +支持现代浏览器,不支持 IE | |
| 96 | + | |
| 97 | +| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | | |
| 98 | +| :-: | :-: | :-: | :-: | :-: | | |
| 99 | +| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions | | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/.env
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/README.md
0 → 100644
| 1 | +# @vben/backend-mock | |
| 2 | + | |
| 3 | +## Description | |
| 4 | + | |
| 5 | +Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。 | |
| 6 | + | |
| 7 | +## Running the app | |
| 8 | + | |
| 9 | +```bash | |
| 10 | +# development | |
| 11 | +$ pnpm run start | |
| 12 | + | |
| 13 | +# production mode | |
| 14 | +$ pnpm run build | |
| 15 | +``` | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { unAuthorizedResponse } from '~/utils/response'; | |
| 3 | + | |
| 4 | +export default eventHandler((event) => { | |
| 5 | + const userinfo = verifyAccessToken(event); | |
| 6 | + if (!userinfo) { | |
| 7 | + return unAuthorizedResponse(event); | |
| 8 | + } | |
| 9 | + | |
| 10 | + const codes = | |
| 11 | + MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? []; | |
| 12 | + | |
| 13 | + return useResponseSuccess(codes); | |
| 14 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts
0 → 100644
| 1 | +import { | |
| 2 | + clearRefreshTokenCookie, | |
| 3 | + setRefreshTokenCookie, | |
| 4 | +} from '~/utils/cookie-utils'; | |
| 5 | +import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils'; | |
| 6 | +import { forbiddenResponse } from '~/utils/response'; | |
| 7 | + | |
| 8 | +export default defineEventHandler(async (event) => { | |
| 9 | + const { password, username } = await readBody(event); | |
| 10 | + if (!password || !username) { | |
| 11 | + setResponseStatus(event, 400); | |
| 12 | + return useResponseError( | |
| 13 | + 'BadRequestException', | |
| 14 | + 'Username and password are required', | |
| 15 | + ); | |
| 16 | + } | |
| 17 | + | |
| 18 | + const findUser = MOCK_USERS.find( | |
| 19 | + (item) => item.username === username && item.password === password, | |
| 20 | + ); | |
| 21 | + | |
| 22 | + if (!findUser) { | |
| 23 | + clearRefreshTokenCookie(event); | |
| 24 | + return forbiddenResponse(event, 'Username or password is incorrect.'); | |
| 25 | + } | |
| 26 | + | |
| 27 | + const accessToken = generateAccessToken(findUser); | |
| 28 | + const refreshToken = generateRefreshToken(findUser); | |
| 29 | + | |
| 30 | + setRefreshTokenCookie(event, refreshToken); | |
| 31 | + | |
| 32 | + return useResponseSuccess({ | |
| 33 | + ...findUser, | |
| 34 | + accessToken, | |
| 35 | + }); | |
| 36 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts
0 → 100644
| 1 | +import { | |
| 2 | + clearRefreshTokenCookie, | |
| 3 | + getRefreshTokenFromCookie, | |
| 4 | +} from '~/utils/cookie-utils'; | |
| 5 | + | |
| 6 | +export default defineEventHandler(async (event) => { | |
| 7 | + const refreshToken = getRefreshTokenFromCookie(event); | |
| 8 | + if (!refreshToken) { | |
| 9 | + return useResponseSuccess(''); | |
| 10 | + } | |
| 11 | + | |
| 12 | + clearRefreshTokenCookie(event); | |
| 13 | + | |
| 14 | + return useResponseSuccess(''); | |
| 15 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts
0 → 100644
| 1 | +import { | |
| 2 | + clearRefreshTokenCookie, | |
| 3 | + getRefreshTokenFromCookie, | |
| 4 | + setRefreshTokenCookie, | |
| 5 | +} from '~/utils/cookie-utils'; | |
| 6 | +import { verifyRefreshToken } from '~/utils/jwt-utils'; | |
| 7 | +import { forbiddenResponse } from '~/utils/response'; | |
| 8 | + | |
| 9 | +export default defineEventHandler(async (event) => { | |
| 10 | + const refreshToken = getRefreshTokenFromCookie(event); | |
| 11 | + if (!refreshToken) { | |
| 12 | + return forbiddenResponse(event); | |
| 13 | + } | |
| 14 | + | |
| 15 | + clearRefreshTokenCookie(event); | |
| 16 | + | |
| 17 | + const userinfo = verifyRefreshToken(refreshToken); | |
| 18 | + if (!userinfo) { | |
| 19 | + return forbiddenResponse(event); | |
| 20 | + } | |
| 21 | + | |
| 22 | + const findUser = MOCK_USERS.find( | |
| 23 | + (item) => item.username === userinfo.username, | |
| 24 | + ); | |
| 25 | + if (!findUser) { | |
| 26 | + return forbiddenResponse(event); | |
| 27 | + } | |
| 28 | + const accessToken = generateAccessToken(findUser); | |
| 29 | + | |
| 30 | + setRefreshTokenCookie(event, refreshToken); | |
| 31 | + | |
| 32 | + return accessToken; | |
| 33 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts
0 → 100644
| 1 | +export default eventHandler(async (event) => { | |
| 2 | + const userinfo = verifyAccessToken(event); | |
| 3 | + if (!userinfo) { | |
| 4 | + return unAuthorizedResponse(event); | |
| 5 | + } | |
| 6 | + const data = ` | |
| 7 | + { | |
| 8 | + "code": 0, | |
| 9 | + "message": "success", | |
| 10 | + "data": [ | |
| 11 | + { | |
| 12 | + "id": 123456789012345678901234567890123456789012345678901234567890, | |
| 13 | + "name": "John Doe", | |
| 14 | + "age": 30, | |
| 15 | + "email": "john-doe@demo.com" | |
| 16 | + }, | |
| 17 | + { | |
| 18 | + "id": 987654321098765432109876543210987654321098765432109876543210, | |
| 19 | + "name": "Jane Smith", | |
| 20 | + "age": 25, | |
| 21 | + "email": "jane@demo.com" | |
| 22 | + } | |
| 23 | + ] | |
| 24 | + } | |
| 25 | + `; | |
| 26 | + setHeader(event, 'Content-Type', 'application/json'); | |
| 27 | + return data; | |
| 28 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { unAuthorizedResponse } from '~/utils/response'; | |
| 3 | + | |
| 4 | +export default eventHandler(async (event) => { | |
| 5 | + const userinfo = verifyAccessToken(event); | |
| 6 | + if (!userinfo) { | |
| 7 | + return unAuthorizedResponse(event); | |
| 8 | + } | |
| 9 | + | |
| 10 | + const menus = | |
| 11 | + MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? []; | |
| 12 | + return useResponseSuccess(menus); | |
| 13 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/status.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { | |
| 3 | + sleep, | |
| 4 | + unAuthorizedResponse, | |
| 5 | + useResponseSuccess, | |
| 6 | +} from '~/utils/response'; | |
| 7 | + | |
| 8 | +export default eventHandler(async (event) => { | |
| 9 | + const userinfo = verifyAccessToken(event); | |
| 10 | + if (!userinfo) { | |
| 11 | + return unAuthorizedResponse(event); | |
| 12 | + } | |
| 13 | + await sleep(600); | |
| 14 | + return useResponseSuccess(null); | |
| 15 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].delete.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { | |
| 3 | + sleep, | |
| 4 | + unAuthorizedResponse, | |
| 5 | + useResponseSuccess, | |
| 6 | +} from '~/utils/response'; | |
| 7 | + | |
| 8 | +export default eventHandler(async (event) => { | |
| 9 | + const userinfo = verifyAccessToken(event); | |
| 10 | + if (!userinfo) { | |
| 11 | + return unAuthorizedResponse(event); | |
| 12 | + } | |
| 13 | + await sleep(1000); | |
| 14 | + return useResponseSuccess(null); | |
| 15 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { | |
| 3 | + sleep, | |
| 4 | + unAuthorizedResponse, | |
| 5 | + useResponseSuccess, | |
| 6 | +} from '~/utils/response'; | |
| 7 | + | |
| 8 | +export default eventHandler(async (event) => { | |
| 9 | + const userinfo = verifyAccessToken(event); | |
| 10 | + if (!userinfo) { | |
| 11 | + return unAuthorizedResponse(event); | |
| 12 | + } | |
| 13 | + await sleep(2000); | |
| 14 | + return useResponseSuccess(null); | |
| 15 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts
0 → 100644
| 1 | +import { faker } from '@faker-js/faker'; | |
| 2 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 3 | +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; | |
| 4 | + | |
| 5 | +const formatterCN = new Intl.DateTimeFormat('zh-CN', { | |
| 6 | + timeZone: 'Asia/Shanghai', | |
| 7 | + year: 'numeric', | |
| 8 | + month: '2-digit', | |
| 9 | + day: '2-digit', | |
| 10 | + hour: '2-digit', | |
| 11 | + minute: '2-digit', | |
| 12 | + second: '2-digit', | |
| 13 | +}); | |
| 14 | + | |
| 15 | +function generateMockDataList(count: number) { | |
| 16 | + const dataList = []; | |
| 17 | + | |
| 18 | + for (let i = 0; i < count; i++) { | |
| 19 | + const dataItem: Record<string, any> = { | |
| 20 | + id: faker.string.uuid(), | |
| 21 | + pid: 0, | |
| 22 | + name: faker.commerce.department(), | |
| 23 | + status: faker.helpers.arrayElement([0, 1]), | |
| 24 | + creationTime: formatterCN.format( | |
| 25 | + faker.date.between({ from: '2021-01-01', to: '2022-12-31' }), | |
| 26 | + ), | |
| 27 | + remark: faker.lorem.sentence(), | |
| 28 | + }; | |
| 29 | + if (faker.datatype.boolean()) { | |
| 30 | + dataItem.children = Array.from( | |
| 31 | + { length: faker.number.int({ min: 1, max: 5 }) }, | |
| 32 | + () => ({ | |
| 33 | + id: faker.string.uuid(), | |
| 34 | + pid: dataItem.id, | |
| 35 | + name: faker.commerce.department(), | |
| 36 | + status: faker.helpers.arrayElement([0, 1]), | |
| 37 | + creationTime: formatterCN.format( | |
| 38 | + faker.date.between({ from: '2023-01-01', to: '2023-12-31' }), | |
| 39 | + ), | |
| 40 | + remark: faker.lorem.sentence(), | |
| 41 | + }), | |
| 42 | + ); | |
| 43 | + } | |
| 44 | + dataList.push(dataItem); | |
| 45 | + } | |
| 46 | + | |
| 47 | + return dataList; | |
| 48 | +} | |
| 49 | + | |
| 50 | +const mockData = generateMockDataList(10); | |
| 51 | + | |
| 52 | +export default eventHandler(async (event) => { | |
| 53 | + const userinfo = verifyAccessToken(event); | |
| 54 | + if (!userinfo) { | |
| 55 | + return unAuthorizedResponse(event); | |
| 56 | + } | |
| 57 | + | |
| 58 | + const listData = structuredClone(mockData); | |
| 59 | + | |
| 60 | + return useResponseSuccess(listData); | |
| 61 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { MOCK_MENU_LIST } from '~/utils/mock-data'; | |
| 3 | +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; | |
| 4 | + | |
| 5 | +export default eventHandler(async (event) => { | |
| 6 | + const userinfo = verifyAccessToken(event); | |
| 7 | + if (!userinfo) { | |
| 8 | + return unAuthorizedResponse(event); | |
| 9 | + } | |
| 10 | + | |
| 11 | + return useResponseSuccess(MOCK_MENU_LIST); | |
| 12 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/name-exists.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { MOCK_MENU_LIST } from '~/utils/mock-data'; | |
| 3 | +import { unAuthorizedResponse } from '~/utils/response'; | |
| 4 | + | |
| 5 | +const namesMap: Record<string, any> = {}; | |
| 6 | + | |
| 7 | +function getNames(menus: any[]) { | |
| 8 | + menus.forEach((menu) => { | |
| 9 | + namesMap[menu.name] = String(menu.id); | |
| 10 | + if (menu.children) { | |
| 11 | + getNames(menu.children); | |
| 12 | + } | |
| 13 | + }); | |
| 14 | +} | |
| 15 | +getNames(MOCK_MENU_LIST); | |
| 16 | + | |
| 17 | +export default eventHandler(async (event) => { | |
| 18 | + const userinfo = verifyAccessToken(event); | |
| 19 | + if (!userinfo) { | |
| 20 | + return unAuthorizedResponse(event); | |
| 21 | + } | |
| 22 | + const { id, name } = getQuery(event); | |
| 23 | + | |
| 24 | + return (name as string) in namesMap && | |
| 25 | + (!id || namesMap[name as string] !== String(id)) | |
| 26 | + ? useResponseSuccess(true) | |
| 27 | + : useResponseSuccess(false); | |
| 28 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/path-exists.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { MOCK_MENU_LIST } from '~/utils/mock-data'; | |
| 3 | +import { unAuthorizedResponse } from '~/utils/response'; | |
| 4 | + | |
| 5 | +const pathMap: Record<string, any> = { '/': 0 }; | |
| 6 | + | |
| 7 | +function getPaths(menus: any[]) { | |
| 8 | + menus.forEach((menu) => { | |
| 9 | + pathMap[menu.path] = String(menu.id); | |
| 10 | + if (menu.children) { | |
| 11 | + getPaths(menu.children); | |
| 12 | + } | |
| 13 | + }); | |
| 14 | +} | |
| 15 | +getPaths(MOCK_MENU_LIST); | |
| 16 | + | |
| 17 | +export default eventHandler(async (event) => { | |
| 18 | + const userinfo = verifyAccessToken(event); | |
| 19 | + if (!userinfo) { | |
| 20 | + return unAuthorizedResponse(event); | |
| 21 | + } | |
| 22 | + const { id, path } = getQuery(event); | |
| 23 | + | |
| 24 | + return (path as string) in pathMap && | |
| 25 | + (!id || pathMap[path as string] !== String(id)) | |
| 26 | + ? useResponseSuccess(true) | |
| 27 | + : useResponseSuccess(false); | |
| 28 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts
0 → 100644
| 1 | +import { faker } from '@faker-js/faker'; | |
| 2 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 3 | +import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; | |
| 4 | +import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; | |
| 5 | + | |
| 6 | +const formatterCN = new Intl.DateTimeFormat('zh-CN', { | |
| 7 | + timeZone: 'Asia/Shanghai', | |
| 8 | + year: 'numeric', | |
| 9 | + month: '2-digit', | |
| 10 | + day: '2-digit', | |
| 11 | + hour: '2-digit', | |
| 12 | + minute: '2-digit', | |
| 13 | + second: '2-digit', | |
| 14 | +}); | |
| 15 | + | |
| 16 | +const menuIds = getMenuIds(MOCK_MENU_LIST); | |
| 17 | + | |
| 18 | +function generateMockDataList(count: number) { | |
| 19 | + const dataList = []; | |
| 20 | + | |
| 21 | + for (let i = 0; i < count; i++) { | |
| 22 | + const dataItem: Record<string, any> = { | |
| 23 | + id: faker.string.uuid(), | |
| 24 | + name: faker.commerce.product(), | |
| 25 | + status: faker.helpers.arrayElement([0, 1]), | |
| 26 | + creationTime: formatterCN.format( | |
| 27 | + faker.date.between({ from: '2022-01-01', to: '2025-01-01' }), | |
| 28 | + ), | |
| 29 | + permissions: faker.helpers.arrayElements(menuIds), | |
| 30 | + remark: faker.lorem.sentence(), | |
| 31 | + }; | |
| 32 | + | |
| 33 | + dataList.push(dataItem); | |
| 34 | + } | |
| 35 | + | |
| 36 | + return dataList; | |
| 37 | +} | |
| 38 | + | |
| 39 | +const mockData = generateMockDataList(100); | |
| 40 | + | |
| 41 | +export default eventHandler(async (event) => { | |
| 42 | + const userinfo = verifyAccessToken(event); | |
| 43 | + if (!userinfo) { | |
| 44 | + return unAuthorizedResponse(event); | |
| 45 | + } | |
| 46 | + | |
| 47 | + const { | |
| 48 | + page = 1, | |
| 49 | + MaxResultCount = 20, | |
| 50 | + name, | |
| 51 | + id, | |
| 52 | + remark, | |
| 53 | + startTime, | |
| 54 | + endTime, | |
| 55 | + status, | |
| 56 | + } = getQuery(event); | |
| 57 | + let listData = structuredClone(mockData); | |
| 58 | + if (name) { | |
| 59 | + listData = listData.filter((item) => | |
| 60 | + item.name.toLowerCase().includes(String(name).toLowerCase()), | |
| 61 | + ); | |
| 62 | + } | |
| 63 | + if (id) { | |
| 64 | + listData = listData.filter((item) => | |
| 65 | + item.id.toLowerCase().includes(String(id).toLowerCase()), | |
| 66 | + ); | |
| 67 | + } | |
| 68 | + if (remark) { | |
| 69 | + listData = listData.filter((item) => | |
| 70 | + item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()), | |
| 71 | + ); | |
| 72 | + } | |
| 73 | + if (startTime) { | |
| 74 | + listData = listData.filter((item) => item.creationTime >= startTime); | |
| 75 | + } | |
| 76 | + if (endTime) { | |
| 77 | + listData = listData.filter((item) => item.creationTime <= endTime); | |
| 78 | + } | |
| 79 | + if (['0', '1'].includes(status as string)) { | |
| 80 | + listData = listData.filter((item) => item.status === Number(status)); | |
| 81 | + } | |
| 82 | + return usePageResponseSuccess(page as string, MaxResultCount as string, listData); | |
| 83 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts
0 → 100644
| 1 | +import { faker } from '@faker-js/faker'; | |
| 2 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 3 | +import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; | |
| 4 | + | |
| 5 | +function generateMockDataList(count: number) { | |
| 6 | + const dataList = []; | |
| 7 | + | |
| 8 | + for (let i = 0; i < count; i++) { | |
| 9 | + const dataItem = { | |
| 10 | + id: faker.string.uuid(), | |
| 11 | + imageUrl: faker.image.avatar(), | |
| 12 | + imageUrl2: faker.image.avatar(), | |
| 13 | + open: faker.datatype.boolean(), | |
| 14 | + status: faker.helpers.arrayElement(['success', 'error', 'warning']), | |
| 15 | + productName: faker.commerce.productName(), | |
| 16 | + price: faker.commerce.price(), | |
| 17 | + currency: faker.finance.currencyCode(), | |
| 18 | + quantity: faker.number.int({ min: 1, max: 100 }), | |
| 19 | + available: faker.datatype.boolean(), | |
| 20 | + category: faker.commerce.department(), | |
| 21 | + releaseDate: faker.date.past(), | |
| 22 | + rating: faker.number.float({ min: 1, max: 5 }), | |
| 23 | + description: faker.commerce.productDescription(), | |
| 24 | + weight: faker.number.float({ min: 0.1, max: 10 }), | |
| 25 | + color: faker.color.human(), | |
| 26 | + inProduction: faker.datatype.boolean(), | |
| 27 | + tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()), | |
| 28 | + }; | |
| 29 | + | |
| 30 | + dataList.push(dataItem); | |
| 31 | + } | |
| 32 | + | |
| 33 | + return dataList; | |
| 34 | +} | |
| 35 | + | |
| 36 | +const mockData = generateMockDataList(100); | |
| 37 | + | |
| 38 | +export default eventHandler(async (event) => { | |
| 39 | + const userinfo = verifyAccessToken(event); | |
| 40 | + if (!userinfo) { | |
| 41 | + return unAuthorizedResponse(event); | |
| 42 | + } | |
| 43 | + | |
| 44 | + await sleep(600); | |
| 45 | + | |
| 46 | + const { page, MaxResultCount, sortBy, sortOrder } = getQuery(event); | |
| 47 | + const listData = structuredClone(mockData); | |
| 48 | + if (sortBy && Reflect.has(listData[0], sortBy as string)) { | |
| 49 | + listData.sort((a, b) => { | |
| 50 | + if (sortOrder === 'asc') { | |
| 51 | + if (sortBy === 'price') { | |
| 52 | + return ( | |
| 53 | + Number.parseFloat(a[sortBy as string]) - | |
| 54 | + Number.parseFloat(b[sortBy as string]) | |
| 55 | + ); | |
| 56 | + } else { | |
| 57 | + return a[sortBy as string] > b[sortBy as string] ? 1 : -1; | |
| 58 | + } | |
| 59 | + } else { | |
| 60 | + if (sortBy === 'price') { | |
| 61 | + return ( | |
| 62 | + Number.parseFloat(b[sortBy as string]) - | |
| 63 | + Number.parseFloat(a[sortBy as string]) | |
| 64 | + ); | |
| 65 | + } else { | |
| 66 | + return a[sortBy as string] < b[sortBy as string] ? 1 : -1; | |
| 67 | + } | |
| 68 | + } | |
| 69 | + }); | |
| 70 | + } | |
| 71 | + | |
| 72 | + return usePageResponseSuccess(page as string, MaxResultCount as string, listData); | |
| 73 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts
0 → 100644
| 1 | +export default defineEventHandler(() => 'Test get handler'); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts
0 → 100644
| 1 | +export default defineEventHandler(() => 'Test post handler'); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { unAuthorizedResponse } from '~/utils/response'; | |
| 3 | + | |
| 4 | +export default eventHandler((event) => { | |
| 5 | + const userinfo = verifyAccessToken(event); | |
| 6 | + if (!userinfo) { | |
| 7 | + return unAuthorizedResponse(event); | |
| 8 | + } | |
| 9 | + return useResponseSuccess({ | |
| 10 | + url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', | |
| 11 | + }); | |
| 12 | + // return useResponseError("test") | |
| 13 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts
0 → 100644
| 1 | +import { verifyAccessToken } from '~/utils/jwt-utils'; | |
| 2 | +import { unAuthorizedResponse } from '~/utils/response'; | |
| 3 | + | |
| 4 | +export default eventHandler((event) => { | |
| 5 | + const userinfo = verifyAccessToken(event); | |
| 6 | + if (!userinfo) { | |
| 7 | + return unAuthorizedResponse(event); | |
| 8 | + } | |
| 9 | + return useResponseSuccess(userinfo); | |
| 10 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/error.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts
0 → 100644
| 1 | +import { forbiddenResponse, sleep } from '~/utils/response'; | |
| 2 | + | |
| 3 | +export default defineEventHandler(async (event) => { | |
| 4 | + event.node.res.setHeader( | |
| 5 | + 'Access-Control-Allow-Origin', | |
| 6 | + event.headers.get('Origin') ?? '*', | |
| 7 | + ); | |
| 8 | + if (event.method === 'OPTIONS') { | |
| 9 | + event.node.res.statusCode = 204; | |
| 10 | + event.node.res.statusMessage = 'No Content.'; | |
| 11 | + return 'OK'; | |
| 12 | + } else if ( | |
| 13 | + ['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) && | |
| 14 | + event.path.startsWith('/api/') | |
| 15 | + ) { | |
| 16 | + await sleep(Math.floor(Math.random() * 2000)); | |
| 17 | + return forbiddenResponse(event, '演示环境,禁止修改'); | |
| 18 | + } | |
| 19 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts
0 → 100644
| 1 | +import errorHandler from './error'; | |
| 2 | + | |
| 3 | +process.env.COMPATIBILITY_DATE = new Date().toISOString(); | |
| 4 | +export default defineNitroConfig({ | |
| 5 | + devErrorHandler: errorHandler, | |
| 6 | + errorHandler: '~/error', | |
| 7 | + routeRules: { | |
| 8 | + '/api/**': { | |
| 9 | + cors: true, | |
| 10 | + headers: { | |
| 11 | + 'Access-Control-Allow-Credentials': 'true', | |
| 12 | + 'Access-Control-Allow-Headers': | |
| 13 | + 'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', | |
| 14 | + 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE', | |
| 15 | + 'Access-Control-Allow-Origin': '*', | |
| 16 | + 'Access-Control-Expose-Headers': '*', | |
| 17 | + }, | |
| 18 | + }, | |
| 19 | + }, | |
| 20 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/package.json
0 → 100644
| 1 | +{ | |
| 2 | + "name": "@vben/backend-mock", | |
| 3 | + "version": "0.0.1", | |
| 4 | + "description": "", | |
| 5 | + "private": true, | |
| 6 | + "license": "MIT", | |
| 7 | + "author": "", | |
| 8 | + "scripts": { | |
| 9 | + "build": "nitro build", | |
| 10 | + "start": "nitro dev" | |
| 11 | + }, | |
| 12 | + "dependencies": { | |
| 13 | + "@faker-js/faker": "catalog:", | |
| 14 | + "jsonwebtoken": "catalog:", | |
| 15 | + "nitropack": "catalog:" | |
| 16 | + }, | |
| 17 | + "devDependencies": { | |
| 18 | + "@types/jsonwebtoken": "catalog:", | |
| 19 | + "h3": "catalog:" | |
| 20 | + } | |
| 21 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts
0 → 100644
| 1 | +export default defineEventHandler(() => { | |
| 2 | + return ` | |
| 3 | +<h1>Hello Vben Admin</h1> | |
| 4 | +<h2>Mock service is starting</h2> | |
| 5 | +<ul> | |
| 6 | +<li><a href="/api/user">/api/user/info</a></li> | |
| 7 | +<li><a href="/api/menu">/api/menu/all</a></li> | |
| 8 | +<li><a href="/api/auth/codes">/api/auth/codes</a></li> | |
| 9 | +<li><a href="/api/auth/login">/api/auth/login</a></li> | |
| 10 | +<li><a href="/api/upload">/api/upload</a></li> | |
| 11 | +</ul> | |
| 12 | +`; | |
| 13 | +}); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts
0 → 100644
| 1 | +import type { EventHandlerRequest, H3Event } from 'h3'; | |
| 2 | + | |
| 3 | +export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) { | |
| 4 | + deleteCookie(event, 'jwt', { | |
| 5 | + httpOnly: true, | |
| 6 | + sameSite: 'none', | |
| 7 | + secure: true, | |
| 8 | + }); | |
| 9 | +} | |
| 10 | + | |
| 11 | +export function setRefreshTokenCookie( | |
| 12 | + event: H3Event<EventHandlerRequest>, | |
| 13 | + refreshToken: string, | |
| 14 | +) { | |
| 15 | + setCookie(event, 'jwt', refreshToken, { | |
| 16 | + httpOnly: true, | |
| 17 | + maxAge: 24 * 60 * 60, // unit: seconds | |
| 18 | + sameSite: 'none', | |
| 19 | + secure: true, | |
| 20 | + }); | |
| 21 | +} | |
| 22 | + | |
| 23 | +export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) { | |
| 24 | + const refreshToken = getCookie(event, 'jwt'); | |
| 25 | + return refreshToken; | |
| 26 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts
0 → 100644
| 1 | +import type { EventHandlerRequest, H3Event } from 'h3'; | |
| 2 | + | |
| 3 | +import jwt from 'jsonwebtoken'; | |
| 4 | + | |
| 5 | +import { UserInfo } from './mock-data'; | |
| 6 | + | |
| 7 | +// TODO: Replace with your own secret key | |
| 8 | +const ACCESS_TOKEN_SECRET = 'access_token_secret'; | |
| 9 | +const REFRESH_TOKEN_SECRET = 'refresh_token_secret'; | |
| 10 | + | |
| 11 | +export interface UserPayload extends UserInfo { | |
| 12 | + iat: number; | |
| 13 | + exp: number; | |
| 14 | +} | |
| 15 | + | |
| 16 | +export function generateAccessToken(user: UserInfo) { | |
| 17 | + return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' }); | |
| 18 | +} | |
| 19 | + | |
| 20 | +export function generateRefreshToken(user: UserInfo) { | |
| 21 | + return jwt.sign(user, REFRESH_TOKEN_SECRET, { | |
| 22 | + expiresIn: '30d', | |
| 23 | + }); | |
| 24 | +} | |
| 25 | + | |
| 26 | +export function verifyAccessToken( | |
| 27 | + event: H3Event<EventHandlerRequest>, | |
| 28 | +): null | Omit<UserInfo, 'password'> { | |
| 29 | + const authHeader = getHeader(event, 'Authorization'); | |
| 30 | + if (!authHeader?.startsWith('Bearer')) { | |
| 31 | + return null; | |
| 32 | + } | |
| 33 | + | |
| 34 | + const token = authHeader.split(' ')[1]; | |
| 35 | + try { | |
| 36 | + const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload; | |
| 37 | + | |
| 38 | + const username = decoded.username; | |
| 39 | + const user = MOCK_USERS.find((item) => item.username === username); | |
| 40 | + const { password: _pwd, ...userinfo } = user; | |
| 41 | + return userinfo; | |
| 42 | + } catch { | |
| 43 | + return null; | |
| 44 | + } | |
| 45 | +} | |
| 46 | + | |
| 47 | +export function verifyRefreshToken( | |
| 48 | + token: string, | |
| 49 | +): null | Omit<UserInfo, 'password'> { | |
| 50 | + try { | |
| 51 | + const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload; | |
| 52 | + const username = decoded.username; | |
| 53 | + const user = MOCK_USERS.find((item) => item.username === username); | |
| 54 | + const { password: _pwd, ...userinfo } = user; | |
| 55 | + return userinfo; | |
| 56 | + } catch { | |
| 57 | + return null; | |
| 58 | + } | |
| 59 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts
0 → 100644
| 1 | +export interface UserInfo { | |
| 2 | + id: number; | |
| 3 | + password: string; | |
| 4 | + realName: string; | |
| 5 | + roles: string[]; | |
| 6 | + username: string; | |
| 7 | + homePath?: string; | |
| 8 | +} | |
| 9 | + | |
| 10 | +export const MOCK_USERS: UserInfo[] = [ | |
| 11 | + { | |
| 12 | + id: 0, | |
| 13 | + password: '123456', | |
| 14 | + realName: 'Vben', | |
| 15 | + roles: ['super'], | |
| 16 | + username: 'vben', | |
| 17 | + }, | |
| 18 | + { | |
| 19 | + id: 1, | |
| 20 | + password: '123456', | |
| 21 | + realName: 'Admin', | |
| 22 | + roles: ['admin'], | |
| 23 | + username: 'admin', | |
| 24 | + homePath: '/workspace', | |
| 25 | + }, | |
| 26 | + { | |
| 27 | + id: 2, | |
| 28 | + password: '123456', | |
| 29 | + realName: 'Jack', | |
| 30 | + roles: ['user'], | |
| 31 | + username: 'jack', | |
| 32 | + homePath: '/analytics', | |
| 33 | + }, | |
| 34 | +]; | |
| 35 | + | |
| 36 | +export const MOCK_CODES = [ | |
| 37 | + // super | |
| 38 | + { | |
| 39 | + codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'], | |
| 40 | + username: 'vben', | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + // admin | |
| 44 | + codes: ['AC_100010', 'AC_100020', 'AC_100030'], | |
| 45 | + username: 'admin', | |
| 46 | + }, | |
| 47 | + { | |
| 48 | + // user | |
| 49 | + codes: ['AC_1000001', 'AC_1000002'], | |
| 50 | + username: 'jack', | |
| 51 | + }, | |
| 52 | +]; | |
| 53 | + | |
| 54 | +const dashboardMenus = [ | |
| 55 | + { | |
| 56 | + meta: { | |
| 57 | + order: -1, | |
| 58 | + title: 'page.dashboard.title', | |
| 59 | + }, | |
| 60 | + name: 'Dashboard', | |
| 61 | + path: '/dashboard', | |
| 62 | + redirect: '/analytics', | |
| 63 | + children: [ | |
| 64 | + { | |
| 65 | + name: 'Analytics', | |
| 66 | + path: '/analytics', | |
| 67 | + component: '/dashboard/analytics/index', | |
| 68 | + meta: { | |
| 69 | + affixTab: true, | |
| 70 | + title: 'page.dashboard.analytics', | |
| 71 | + }, | |
| 72 | + }, | |
| 73 | + { | |
| 74 | + name: 'Workspace', | |
| 75 | + path: '/workspace', | |
| 76 | + component: '/dashboard/workspace/index', | |
| 77 | + meta: { | |
| 78 | + title: 'page.dashboard.workspace', | |
| 79 | + }, | |
| 80 | + }, | |
| 81 | + ], | |
| 82 | + }, | |
| 83 | +]; | |
| 84 | + | |
| 85 | +const createDemosMenus = (role: 'admin' | 'super' | 'user') => { | |
| 86 | + const roleWithMenus = { | |
| 87 | + admin: { | |
| 88 | + component: '/demos/access/admin-visible', | |
| 89 | + meta: { | |
| 90 | + icon: 'mdi:button-cursor', | |
| 91 | + title: 'demos.access.adminVisible', | |
| 92 | + }, | |
| 93 | + name: 'AccessAdminVisibleDemo', | |
| 94 | + path: '/demos/access/admin-visible', | |
| 95 | + }, | |
| 96 | + super: { | |
| 97 | + component: '/demos/access/super-visible', | |
| 98 | + meta: { | |
| 99 | + icon: 'mdi:button-cursor', | |
| 100 | + title: 'demos.access.superVisible', | |
| 101 | + }, | |
| 102 | + name: 'AccessSuperVisibleDemo', | |
| 103 | + path: '/demos/access/super-visible', | |
| 104 | + }, | |
| 105 | + user: { | |
| 106 | + component: '/demos/access/user-visible', | |
| 107 | + meta: { | |
| 108 | + icon: 'mdi:button-cursor', | |
| 109 | + title: 'demos.access.userVisible', | |
| 110 | + }, | |
| 111 | + name: 'AccessUserVisibleDemo', | |
| 112 | + path: '/demos/access/user-visible', | |
| 113 | + }, | |
| 114 | + }; | |
| 115 | + | |
| 116 | + return [ | |
| 117 | + { | |
| 118 | + meta: { | |
| 119 | + icon: 'ic:baseline-view-in-ar', | |
| 120 | + keepAlive: true, | |
| 121 | + order: 1000, | |
| 122 | + title: 'demos.title', | |
| 123 | + }, | |
| 124 | + name: 'Demos', | |
| 125 | + path: '/demos', | |
| 126 | + redirect: '/demos/access', | |
| 127 | + children: [ | |
| 128 | + { | |
| 129 | + name: 'AccessDemos', | |
| 130 | + path: '/demosaccess', | |
| 131 | + meta: { | |
| 132 | + icon: 'mdi:cloud-key-outline', | |
| 133 | + title: 'demos.access.backendPermissions', | |
| 134 | + }, | |
| 135 | + redirect: '/demos/access/page-control', | |
| 136 | + children: [ | |
| 137 | + { | |
| 138 | + name: 'AccessPageControlDemo', | |
| 139 | + path: '/demos/access/page-control', | |
| 140 | + component: '/demos/access/index', | |
| 141 | + meta: { | |
| 142 | + icon: 'mdi:page-previous-outline', | |
| 143 | + title: 'demos.access.pageAccess', | |
| 144 | + }, | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + name: 'AccessButtonControlDemo', | |
| 148 | + path: '/demos/access/button-control', | |
| 149 | + component: '/demos/access/button-control', | |
| 150 | + meta: { | |
| 151 | + icon: 'mdi:button-cursor', | |
| 152 | + title: 'demos.access.buttonControl', | |
| 153 | + }, | |
| 154 | + }, | |
| 155 | + { | |
| 156 | + name: 'AccessMenuVisible403Demo', | |
| 157 | + path: '/demos/access/menu-visible-403', | |
| 158 | + component: '/demos/access/menu-visible-403', | |
| 159 | + meta: { | |
| 160 | + authority: ['no-body'], | |
| 161 | + icon: 'mdi:button-cursor', | |
| 162 | + menuVisibleWithForbidden: true, | |
| 163 | + title: 'demos.access.menuVisible403', | |
| 164 | + }, | |
| 165 | + }, | |
| 166 | + roleWithMenus[role], | |
| 167 | + ], | |
| 168 | + }, | |
| 169 | + ], | |
| 170 | + }, | |
| 171 | + ]; | |
| 172 | +}; | |
| 173 | + | |
| 174 | +export const MOCK_MENUS = [ | |
| 175 | + { | |
| 176 | + menus: [...dashboardMenus, ...createDemosMenus('super')], | |
| 177 | + username: 'vben', | |
| 178 | + }, | |
| 179 | + { | |
| 180 | + menus: [...dashboardMenus, ...createDemosMenus('admin')], | |
| 181 | + username: 'admin', | |
| 182 | + }, | |
| 183 | + { | |
| 184 | + menus: [...dashboardMenus, ...createDemosMenus('user')], | |
| 185 | + username: 'jack', | |
| 186 | + }, | |
| 187 | +]; | |
| 188 | + | |
| 189 | +export const MOCK_MENU_LIST = [ | |
| 190 | + { | |
| 191 | + id: 1, | |
| 192 | + name: 'Workspace', | |
| 193 | + status: 1, | |
| 194 | + type: 'menu', | |
| 195 | + icon: 'mdi:dashboard', | |
| 196 | + path: '/workspace', | |
| 197 | + component: '/dashboard/workspace/index', | |
| 198 | + meta: { | |
| 199 | + icon: 'carbon:workspace', | |
| 200 | + title: 'page.dashboard.workspace', | |
| 201 | + affixTab: true, | |
| 202 | + order: 0, | |
| 203 | + }, | |
| 204 | + }, | |
| 205 | + { | |
| 206 | + id: 2, | |
| 207 | + meta: { | |
| 208 | + icon: 'carbon:settings', | |
| 209 | + order: 9997, | |
| 210 | + title: 'system.title', | |
| 211 | + badge: 'new', | |
| 212 | + badgeType: 'normal', | |
| 213 | + badgeVariants: 'primary', | |
| 214 | + }, | |
| 215 | + status: 1, | |
| 216 | + type: 'catalog', | |
| 217 | + name: 'System', | |
| 218 | + path: '/system', | |
| 219 | + children: [ | |
| 220 | + { | |
| 221 | + id: 201, | |
| 222 | + pid: 2, | |
| 223 | + path: '/system/menu', | |
| 224 | + name: 'SystemMenu', | |
| 225 | + authCode: 'System:Menu:List', | |
| 226 | + status: 1, | |
| 227 | + type: 'menu', | |
| 228 | + meta: { | |
| 229 | + icon: 'carbon:menu', | |
| 230 | + title: 'system.menu.title', | |
| 231 | + }, | |
| 232 | + component: '/system/menu/list', | |
| 233 | + children: [ | |
| 234 | + { | |
| 235 | + id: 20_101, | |
| 236 | + pid: 201, | |
| 237 | + name: 'SystemMenuCreate', | |
| 238 | + status: 1, | |
| 239 | + type: 'button', | |
| 240 | + authCode: 'System:Menu:Create', | |
| 241 | + meta: { title: 'common.create' }, | |
| 242 | + }, | |
| 243 | + { | |
| 244 | + id: 20_102, | |
| 245 | + pid: 201, | |
| 246 | + name: 'SystemMenuEdit', | |
| 247 | + status: 1, | |
| 248 | + type: 'button', | |
| 249 | + authCode: 'System:Menu:Edit', | |
| 250 | + meta: { title: 'common.edit' }, | |
| 251 | + }, | |
| 252 | + { | |
| 253 | + id: 20_103, | |
| 254 | + pid: 201, | |
| 255 | + name: 'SystemMenuDelete', | |
| 256 | + status: 1, | |
| 257 | + type: 'button', | |
| 258 | + authCode: 'System:Menu:Delete', | |
| 259 | + meta: { title: 'common.delete' }, | |
| 260 | + }, | |
| 261 | + ], | |
| 262 | + }, | |
| 263 | + { | |
| 264 | + id: 202, | |
| 265 | + pid: 2, | |
| 266 | + path: '/system/dept', | |
| 267 | + name: 'SystemDept', | |
| 268 | + status: 1, | |
| 269 | + type: 'menu', | |
| 270 | + authCode: 'System:Dept:List', | |
| 271 | + meta: { | |
| 272 | + icon: 'carbon:container-services', | |
| 273 | + title: 'system.dept.title', | |
| 274 | + }, | |
| 275 | + component: '/system/dept/list', | |
| 276 | + children: [ | |
| 277 | + { | |
| 278 | + id: 20_401, | |
| 279 | + pid: 201, | |
| 280 | + name: 'SystemDeptCreate', | |
| 281 | + status: 1, | |
| 282 | + type: 'button', | |
| 283 | + authCode: 'System:Dept:Create', | |
| 284 | + meta: { title: 'common.create' }, | |
| 285 | + }, | |
| 286 | + { | |
| 287 | + id: 20_402, | |
| 288 | + pid: 201, | |
| 289 | + name: 'SystemDeptEdit', | |
| 290 | + status: 1, | |
| 291 | + type: 'button', | |
| 292 | + authCode: 'System:Dept:Edit', | |
| 293 | + meta: { title: 'common.edit' }, | |
| 294 | + }, | |
| 295 | + { | |
| 296 | + id: 20_403, | |
| 297 | + pid: 201, | |
| 298 | + name: 'SystemDeptDelete', | |
| 299 | + status: 1, | |
| 300 | + type: 'button', | |
| 301 | + authCode: 'System:Dept:Delete', | |
| 302 | + meta: { title: 'common.delete' }, | |
| 303 | + }, | |
| 304 | + ], | |
| 305 | + }, | |
| 306 | + ], | |
| 307 | + }, | |
| 308 | + { | |
| 309 | + id: 9, | |
| 310 | + meta: { | |
| 311 | + badgeType: 'dot', | |
| 312 | + order: 9998, | |
| 313 | + title: 'demos.vben.title', | |
| 314 | + icon: 'carbon:data-center', | |
| 315 | + }, | |
| 316 | + name: 'Project', | |
| 317 | + path: '/vben-admin', | |
| 318 | + type: 'catalog', | |
| 319 | + status: 1, | |
| 320 | + children: [ | |
| 321 | + { | |
| 322 | + id: 901, | |
| 323 | + pid: 9, | |
| 324 | + name: 'VbenDocument', | |
| 325 | + path: '/vben-admin/document', | |
| 326 | + component: 'IFrameView', | |
| 327 | + type: 'embedded', | |
| 328 | + status: 1, | |
| 329 | + meta: { | |
| 330 | + icon: 'carbon:book', | |
| 331 | + iframeSrc: 'https://doc.vben.pro', | |
| 332 | + title: 'demos.vben.document', | |
| 333 | + }, | |
| 334 | + }, | |
| 335 | + { | |
| 336 | + id: 902, | |
| 337 | + pid: 9, | |
| 338 | + name: 'VbenGithub', | |
| 339 | + path: '/vben-admin/github', | |
| 340 | + component: 'IFrameView', | |
| 341 | + type: 'link', | |
| 342 | + status: 1, | |
| 343 | + meta: { | |
| 344 | + icon: 'carbon:logo-github', | |
| 345 | + link: 'https://github.com/vbenjs/vue-vben-admin', | |
| 346 | + title: 'Github', | |
| 347 | + }, | |
| 348 | + }, | |
| 349 | + { | |
| 350 | + id: 903, | |
| 351 | + pid: 9, | |
| 352 | + name: 'VbenAntdv', | |
| 353 | + path: '/vben-admin/antdv', | |
| 354 | + component: 'IFrameView', | |
| 355 | + type: 'link', | |
| 356 | + status: 0, | |
| 357 | + meta: { | |
| 358 | + icon: 'carbon:hexagon-vertical-solid', | |
| 359 | + badgeType: 'dot', | |
| 360 | + link: 'https://ant.vben.pro', | |
| 361 | + title: 'demos.vben.antdv', | |
| 362 | + }, | |
| 363 | + }, | |
| 364 | + ], | |
| 365 | + }, | |
| 366 | + { | |
| 367 | + id: 10, | |
| 368 | + component: '_core/about/index', | |
| 369 | + type: 'menu', | |
| 370 | + status: 1, | |
| 371 | + meta: { | |
| 372 | + icon: 'lucide:copyright', | |
| 373 | + order: 9999, | |
| 374 | + title: 'demos.vben.about', | |
| 375 | + }, | |
| 376 | + name: 'About', | |
| 377 | + path: '/about', | |
| 378 | + }, | |
| 379 | +]; | |
| 380 | + | |
| 381 | +export function getMenuIds(menus: any[]) { | |
| 382 | + const ids: number[] = []; | |
| 383 | + menus.forEach((item) => { | |
| 384 | + ids.push(item.id); | |
| 385 | + if (item.children && item.children.length > 0) { | |
| 386 | + ids.push(...getMenuIds(item.children)); | |
| 387 | + } | |
| 388 | + }); | |
| 389 | + return ids; | |
| 390 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts
0 → 100644
| 1 | +import type { EventHandlerRequest, H3Event } from 'h3'; | |
| 2 | + | |
| 3 | +export function useResponseSuccess<T = any>(data: T) { | |
| 4 | + return { | |
| 5 | + code: 0, | |
| 6 | + data, | |
| 7 | + error: null, | |
| 8 | + message: 'ok', | |
| 9 | + }; | |
| 10 | +} | |
| 11 | + | |
| 12 | +export function usePageResponseSuccess<T = any>( | |
| 13 | + page: number | string, | |
| 14 | + MaxResultCount: number | string, | |
| 15 | + list: T[], | |
| 16 | + { message = 'ok' } = {}, | |
| 17 | +) { | |
| 18 | + const pageData = pagination( | |
| 19 | + Number.parseInt(`${page}`), | |
| 20 | + Number.parseInt(`${MaxResultCount}`), | |
| 21 | + list, | |
| 22 | + ); | |
| 23 | + | |
| 24 | + return { | |
| 25 | + ...useResponseSuccess({ | |
| 26 | + items: pageData, | |
| 27 | + total: list.length, | |
| 28 | + }), | |
| 29 | + message, | |
| 30 | + }; | |
| 31 | +} | |
| 32 | + | |
| 33 | +export function useResponseError(message: string, error: any = null) { | |
| 34 | + return { | |
| 35 | + code: -1, | |
| 36 | + data: null, | |
| 37 | + error, | |
| 38 | + message, | |
| 39 | + }; | |
| 40 | +} | |
| 41 | + | |
| 42 | +export function forbiddenResponse( | |
| 43 | + event: H3Event<EventHandlerRequest>, | |
| 44 | + message = 'Forbidden Exception', | |
| 45 | +) { | |
| 46 | + setResponseStatus(event, 403); | |
| 47 | + return useResponseError(message, message); | |
| 48 | +} | |
| 49 | + | |
| 50 | +export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) { | |
| 51 | + setResponseStatus(event, 401); | |
| 52 | + return useResponseError('Unauthorized Exception', 'Unauthorized Exception'); | |
| 53 | +} | |
| 54 | + | |
| 55 | +export function sleep(ms: number) { | |
| 56 | + return new Promise((resolve) => setTimeout(resolve, ms)); | |
| 57 | +} | |
| 58 | + | |
| 59 | +export function pagination<T = any>( | |
| 60 | + pageNo: number, | |
| 61 | + MaxResultCount: number, | |
| 62 | + array: T[], | |
| 63 | +): T[] { | |
| 64 | + const offset = (pageNo - 1) * Number(MaxResultCount); | |
| 65 | + return offset + Number(MaxResultCount) >= array.length | |
| 66 | + ? array.slice(offset) | |
| 67 | + : array.slice(offset, offset + Number(MaxResultCount)); | |
| 68 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/.env
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/.env.analyze
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/.env.development
0 → 100644
| 1 | +# 端口号 | |
| 2 | +VITE_PORT=17001 | |
| 3 | + | |
| 4 | +VITE_BASE=/ | |
| 5 | +# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 | |
| 6 | +VITE_NITRO_MOCK=false | |
| 7 | +# 是否打开 devtools,true 为打开,false 为关闭 | |
| 8 | +VITE_DEVTOOLS=false | |
| 9 | +# 是否注入全局loading | |
| 10 | +VITE_INJECT_APP_LOADING=true | |
| 11 | + | |
| 12 | +# 后台请求路径 具体在vite.config.mts配置代理 | |
| 13 | +VITE_GLOB_API_URL="/dev-api" | |
| 14 | +VITE_APP_URL="http://flus-test.3ffoodsafety.com/api/app" | |
| 15 | + | |
| 16 | +# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) | |
| 17 | +VITE_GLOB_ENABLE_ENCRYPT=false | |
| 18 | +# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 | |
| 19 | +VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== | |
| 20 | +# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 | |
| 21 | +VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= | |
| 22 | +# 客户端id | |
| 23 | +VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e | |
| 24 | + | |
| 25 | +VITE_GLOB_DEMO_MODE=false | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/.env.production
0 → 100644
| 1 | +VITE_BASE=/ | |
| 2 | + | |
| 3 | +# 是否开启压缩,可以设置为 none, brotli, gzip | |
| 4 | +VITE_COMPRESS=gzip | |
| 5 | + | |
| 6 | +# 是否开启 PWA | |
| 7 | +VITE_PWA=false | |
| 8 | + | |
| 9 | +# vue-router 的模式 | |
| 10 | +VITE_ROUTER_HISTORY=history | |
| 11 | + | |
| 12 | +# 是否注入全局loading | |
| 13 | +VITE_INJECT_APP_LOADING=true | |
| 14 | + | |
| 15 | +# 打包后是否生成dist.zip | |
| 16 | +VITE_ARCHIVER=true | |
| 17 | + | |
| 18 | +# 后端接口地址(ABP 动态 API:/api/app) | |
| 19 | +VITE_GLOB_API_URL=http://flus-test.3ffoodsafety.com/api/app | |
| 20 | + | |
| 21 | +# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) | |
| 22 | +VITE_GLOB_ENABLE_ENCRYPT=false | |
| 23 | +# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 | |
| 24 | +VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== | |
| 25 | +# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 | |
| 26 | +VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= | |
| 27 | +# 客户端id | |
| 28 | +VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e | |
| 29 | + | |
| 30 | +# 开启SSE | |
| 31 | +VITE_GLOB_SSE_ENABLE=false | |
| 32 | + | |
| 33 | +VITE_GLOB_DEMO_MODE=true | |
| 34 | + | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/.env.test
0 → 100644
| 1 | +# 该文件是为了给一个增加环境变量打包的例子 | |
| 2 | +# 对应在根目录package.json -> build:antd:test 命令 | |
| 3 | + | |
| 4 | +VITE_BASE=/ | |
| 5 | + | |
| 6 | +# 是否开启压缩,可以设置为 none, brotli, gzip | |
| 7 | +VITE_COMPRESS=gzip | |
| 8 | + | |
| 9 | +# 是否开启 PWA | |
| 10 | +VITE_PWA=false | |
| 11 | + | |
| 12 | +# vue-router 的模式 | |
| 13 | +VITE_ROUTER_HISTORY=history | |
| 14 | + | |
| 15 | +# 是否注入全局loading | |
| 16 | +VITE_INJECT_APP_LOADING=true | |
| 17 | + | |
| 18 | +# 打包后是否生成dist.zip | |
| 19 | +VITE_ARCHIVER=true | |
| 20 | + | |
| 21 | +# 后端接口地址 | |
| 22 | +VITE_GLOB_API_URL=/test-api | |
| 23 | + | |
| 24 | +# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) | |
| 25 | +VITE_GLOB_ENABLE_ENCRYPT=true | |
| 26 | +# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 | |
| 27 | +VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== | |
| 28 | +# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对 | |
| 29 | +VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= | |
| 30 | +# 客户端id | |
| 31 | +VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e | |
| 32 | + | |
| 33 | +# 开启SSE | |
| 34 | +VITE_GLOB_SSE_ENABLE=true | |
| 35 | + | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/index.html
0 → 100644
| 1 | +<!doctype html> | |
| 2 | +<html lang="zh"> | |
| 3 | + <head> | |
| 4 | + <meta charset="UTF-8" /> | |
| 5 | + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> | |
| 6 | + <meta name="renderer" content="webkit" /> | |
| 7 | + <meta name="description" content="A Modern Back-end Management System" /> | |
| 8 | + <meta name="keywords" content="Vben Admin Vue3 Vite" /> | |
| 9 | + <meta name="author" content="Vben" /> | |
| 10 | + <meta | |
| 11 | + name="viewport" | |
| 12 | + content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" | |
| 13 | + /> | |
| 14 | + <!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 --> | |
| 15 | + <title><%= VITE_APP_TITLE %></title> | |
| 16 | + <link rel="icon" href="/favicon.ico" /> | |
| 17 | + </head> | |
| 18 | + <body> | |
| 19 | + <div id="app"></div> | |
| 20 | + <script type="module" src="/src/main.ts"></script> | |
| 21 | + </body> | |
| 22 | +</html> | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/package.json
0 → 100644
| 1 | +{ | |
| 2 | + "name": "@vben/web-antd", | |
| 3 | + "version": "1.4.1", | |
| 4 | + "homepage": "https://vben.pro", | |
| 5 | + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | |
| 6 | + "repository": { | |
| 7 | + "type": "git", | |
| 8 | + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", | |
| 9 | + "directory": "apps/web-antd" | |
| 10 | + }, | |
| 11 | + "license": "MIT", | |
| 12 | + "author": { | |
| 13 | + "name": "vben", | |
| 14 | + "email": "ann.vben@gmail.com", | |
| 15 | + "url": "https://github.com/anncwb" | |
| 16 | + }, | |
| 17 | + "type": "module", | |
| 18 | + "scripts": { | |
| 19 | + "build": "pnpm vite build", | |
| 20 | + "build:analyze": "pnpm vite build --mode analyze", | |
| 21 | + "dev": "pnpm vite --mode development", | |
| 22 | + "preview": "vite preview", | |
| 23 | + "typecheck": "vue-tsc --noEmit --skipLibCheck" | |
| 24 | + }, | |
| 25 | + "imports": { | |
| 26 | + "#/*": "./src/*" | |
| 27 | + }, | |
| 28 | + "dependencies": { | |
| 29 | + "@ant-design/icons-vue": "^7.0.1", | |
| 30 | + "@tinymce/tinymce-vue": "^6.0.1", | |
| 31 | + "@vben/access": "workspace:*", | |
| 32 | + "@vben/common-ui": "workspace:*", | |
| 33 | + "@vben/constants": "workspace:*", | |
| 34 | + "@vben/hooks": "workspace:*", | |
| 35 | + "@vben/icons": "workspace:*", | |
| 36 | + "@vben/layouts": "workspace:*", | |
| 37 | + "@vben/locales": "workspace:*", | |
| 38 | + "@vben/plugins": "workspace:*", | |
| 39 | + "@vben/preferences": "workspace:*", | |
| 40 | + "@vben/request": "workspace:*", | |
| 41 | + "@vben/stores": "workspace:*", | |
| 42 | + "@vben/styles": "workspace:*", | |
| 43 | + "@vben/types": "workspace:*", | |
| 44 | + "@vben/utils": "workspace:*", | |
| 45 | + "@vueuse/core": "catalog:", | |
| 46 | + "ant-design-vue": "catalog:", | |
| 47 | + "cropperjs": "^1.6.2", | |
| 48 | + "crypto-js": "^4.2.0", | |
| 49 | + "dayjs": "catalog:", | |
| 50 | + "echarts": "^5.5.1", | |
| 51 | + "jsbarcode": "^3.12.3", | |
| 52 | + "jsencrypt": "^3.3.2", | |
| 53 | + "lodash-es": "^4.17.21", | |
| 54 | + "pinia": "catalog:", | |
| 55 | + "qrcode": "catalog:", | |
| 56 | + "tinymce": "^7.3.0", | |
| 57 | + "unplugin-vue-components": "^0.27.3", | |
| 58 | + "vue": "catalog:", | |
| 59 | + "vue-router": "catalog:", | |
| 60 | + "vue3-colorpicker": "^2.3.0" | |
| 61 | + }, | |
| 62 | + "devDependencies": { | |
| 63 | + "@types/crypto-js": "^4.2.2", | |
| 64 | + "@types/lodash-es": "^4.17.12" | |
| 65 | + } | |
| 66 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs
0 → 100644
| 1 | +export { default } from '@vben/tailwind-config/postcss'; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts
0 → 100644
| 1 | +/** | |
| 2 | + * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 | |
| 3 | + * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, | |
| 4 | + */ | |
| 5 | + | |
| 6 | +import type { Component } from 'vue'; | |
| 7 | + | |
| 8 | +import type { BaseFormComponentType } from '@vben/common-ui'; | |
| 9 | +import type { Recordable } from '@vben/types'; | |
| 10 | + | |
| 11 | +import { | |
| 12 | + computed, | |
| 13 | + defineAsyncComponent, | |
| 14 | + defineComponent, | |
| 15 | + getCurrentInstance, | |
| 16 | + h, | |
| 17 | + ref, | |
| 18 | +} from 'vue'; | |
| 19 | + | |
| 20 | +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; | |
| 21 | +import { $t } from '@vben/locales'; | |
| 22 | + | |
| 23 | +import { notification } from 'ant-design-vue'; | |
| 24 | + | |
| 25 | +import { FileUploadOld, ImageUploadOld } from '#/components/upload-old'; | |
| 26 | + | |
| 27 | +const RichTextarea = defineAsyncComponent(() => | |
| 28 | + import('#/components/tinymce/index').then((res) => res.Tinymce), | |
| 29 | +); | |
| 30 | + | |
| 31 | +const FileUpload = defineAsyncComponent(() => | |
| 32 | + import('#/components/upload').then((res) => res.FileUpload), | |
| 33 | +); | |
| 34 | + | |
| 35 | +const ImageUpload = defineAsyncComponent(() => | |
| 36 | + import('#/components/upload').then((res) => res.ImageUpload), | |
| 37 | +); | |
| 38 | + | |
| 39 | +const AutoComplete = defineAsyncComponent( | |
| 40 | + () => import('ant-design-vue/es/auto-complete'), | |
| 41 | +); | |
| 42 | +const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); | |
| 43 | +const Cascader = defineAsyncComponent( | |
| 44 | + () => import('ant-design-vue/es/cascader'), | |
| 45 | +); | |
| 46 | +const Checkbox = defineAsyncComponent( | |
| 47 | + () => import('ant-design-vue/es/checkbox'), | |
| 48 | +); | |
| 49 | +const CheckboxGroup = defineAsyncComponent(() => | |
| 50 | + import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), | |
| 51 | +); | |
| 52 | +const DatePicker = defineAsyncComponent( | |
| 53 | + () => import('ant-design-vue/es/date-picker'), | |
| 54 | +); | |
| 55 | +const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); | |
| 56 | +const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); | |
| 57 | +const InputNumber = defineAsyncComponent( | |
| 58 | + () => import('ant-design-vue/es/input-number'), | |
| 59 | +); | |
| 60 | +const InputPassword = defineAsyncComponent(() => | |
| 61 | + import('ant-design-vue/es/input').then((res) => res.InputPassword), | |
| 62 | +); | |
| 63 | +const Mentions = defineAsyncComponent( | |
| 64 | + () => import('ant-design-vue/es/mentions'), | |
| 65 | +); | |
| 66 | +const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio')); | |
| 67 | +const RadioGroup = defineAsyncComponent(() => | |
| 68 | + import('ant-design-vue/es/radio').then((res) => res.RadioGroup), | |
| 69 | +); | |
| 70 | +const RangePicker = defineAsyncComponent(() => | |
| 71 | + import('ant-design-vue/es/date-picker').then((res) => res.RangePicker), | |
| 72 | +); | |
| 73 | +const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate')); | |
| 74 | +const Select = defineAsyncComponent(() => import('ant-design-vue/es/select')); | |
| 75 | +const Space = defineAsyncComponent(() => import('ant-design-vue/es/space')); | |
| 76 | +const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch')); | |
| 77 | +const Textarea = defineAsyncComponent(() => | |
| 78 | + import('ant-design-vue/es/input').then((res) => res.Textarea), | |
| 79 | +); | |
| 80 | +const TimePicker = defineAsyncComponent( | |
| 81 | + () => import('ant-design-vue/es/time-picker'), | |
| 82 | +); | |
| 83 | +const TreeSelect = defineAsyncComponent( | |
| 84 | + () => import('ant-design-vue/es/tree-select'), | |
| 85 | +); | |
| 86 | +const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); | |
| 87 | + | |
| 88 | +const withDefaultPlaceholder = <T extends Component>( | |
| 89 | + component: T, | |
| 90 | + type: 'input' | 'select', | |
| 91 | + componentProps: Recordable<any> = {}, | |
| 92 | +) => { | |
| 93 | + return defineComponent({ | |
| 94 | + name: component.name, | |
| 95 | + inheritAttrs: false, | |
| 96 | + setup: (props: any, { attrs, expose, slots }) => { | |
| 97 | + // 改为placeholder 解决在keepalive & 语言切换 & tab切换 显示不变的问题 | |
| 98 | + const computedPlaceholder = computed( | |
| 99 | + () => | |
| 100 | + props?.placeholder || | |
| 101 | + attrs?.placeholder || | |
| 102 | + $t(`ui.placeholder.${type}`), | |
| 103 | + ); | |
| 104 | + | |
| 105 | + // 透传组件暴露的方法 | |
| 106 | + const innerRef = ref(); | |
| 107 | + const publicApi: Recordable<any> = {}; | |
| 108 | + expose(publicApi); | |
| 109 | + const instance = getCurrentInstance(); | |
| 110 | + instance?.proxy?.$nextTick(() => { | |
| 111 | + for (const key in innerRef.value) { | |
| 112 | + if (typeof innerRef.value[key] === 'function') { | |
| 113 | + publicApi[key] = innerRef.value[key]; | |
| 114 | + } | |
| 115 | + } | |
| 116 | + }); | |
| 117 | + return () => | |
| 118 | + h( | |
| 119 | + component, | |
| 120 | + { | |
| 121 | + ...componentProps, | |
| 122 | + placeholder: computedPlaceholder.value, | |
| 123 | + ...props, | |
| 124 | + ...attrs, | |
| 125 | + ref: innerRef, | |
| 126 | + }, | |
| 127 | + slots, | |
| 128 | + ); | |
| 129 | + }, | |
| 130 | + }); | |
| 131 | +}; | |
| 132 | + | |
| 133 | +// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 | |
| 134 | +export type ComponentType = | |
| 135 | + | 'ApiSelect' | |
| 136 | + | 'ApiTreeSelect' | |
| 137 | + | 'AutoComplete' | |
| 138 | + | 'Cascader' | |
| 139 | + | 'Checkbox' | |
| 140 | + | 'CheckboxGroup' | |
| 141 | + | 'DatePicker' | |
| 142 | + | 'DefaultButton' | |
| 143 | + | 'Divider' | |
| 144 | + | 'FileUpload' | |
| 145 | + | 'FileUploadOld' | |
| 146 | + | 'IconPicker' | |
| 147 | + | 'ImageUpload' | |
| 148 | + | 'ImageUploadOld' | |
| 149 | + | 'Input' | |
| 150 | + | 'InputNumber' | |
| 151 | + | 'InputPassword' | |
| 152 | + | 'Mentions' | |
| 153 | + | 'PrimaryButton' | |
| 154 | + | 'Radio' | |
| 155 | + | 'RadioGroup' | |
| 156 | + | 'RangePicker' | |
| 157 | + | 'Rate' | |
| 158 | + | 'RichTextarea' | |
| 159 | + | 'Select' | |
| 160 | + | 'Space' | |
| 161 | + | 'Switch' | |
| 162 | + | 'Textarea' | |
| 163 | + | 'TimePicker' | |
| 164 | + | 'TreeSelect' | |
| 165 | + | 'Upload' | |
| 166 | + | BaseFormComponentType; | |
| 167 | + | |
| 168 | +async function initComponentAdapter() { | |
| 169 | + const components: Partial<Record<ComponentType, Component>> = { | |
| 170 | + // 如果你的组件体积比较大,可以使用异步加载 | |
| 171 | + // Button: () => | |
| 172 | + // import('xxx').then((res) => res.Button), | |
| 173 | + ApiSelect: withDefaultPlaceholder( | |
| 174 | + { | |
| 175 | + ...ApiComponent, | |
| 176 | + name: 'ApiSelect', | |
| 177 | + }, | |
| 178 | + 'select', | |
| 179 | + { | |
| 180 | + component: Select, | |
| 181 | + loadingSlot: 'suffixIcon', | |
| 182 | + visibleEvent: 'onDropdownVisibleChange', | |
| 183 | + modelPropName: 'value', | |
| 184 | + }, | |
| 185 | + ), | |
| 186 | + ApiTreeSelect: withDefaultPlaceholder( | |
| 187 | + { | |
| 188 | + ...ApiComponent, | |
| 189 | + name: 'ApiTreeSelect', | |
| 190 | + }, | |
| 191 | + 'select', | |
| 192 | + { | |
| 193 | + component: TreeSelect, | |
| 194 | + fieldNames: { label: 'label', value: 'value', children: 'children' }, | |
| 195 | + loadingSlot: 'suffixIcon', | |
| 196 | + modelPropName: 'value', | |
| 197 | + optionsPropName: 'treeData', | |
| 198 | + visibleEvent: 'onVisibleChange', | |
| 199 | + }, | |
| 200 | + ), | |
| 201 | + AutoComplete, | |
| 202 | + Cascader: withDefaultPlaceholder(Cascader, 'select'), | |
| 203 | + Checkbox, | |
| 204 | + CheckboxGroup, | |
| 205 | + DatePicker, | |
| 206 | + // 自定义默认按钮 | |
| 207 | + DefaultButton: (props, { attrs, slots }) => { | |
| 208 | + return h(Button, { ...props, attrs, type: 'default' }, slots); | |
| 209 | + }, | |
| 210 | + Divider, | |
| 211 | + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { | |
| 212 | + iconSlot: 'addonAfter', | |
| 213 | + inputComponent: Input, | |
| 214 | + modelValueProp: 'value', | |
| 215 | + }), | |
| 216 | + Input: withDefaultPlaceholder(Input, 'input'), | |
| 217 | + InputNumber: withDefaultPlaceholder(InputNumber, 'input'), | |
| 218 | + InputPassword: withDefaultPlaceholder(InputPassword, 'input'), | |
| 219 | + Mentions: withDefaultPlaceholder(Mentions, 'input'), | |
| 220 | + // 自定义主要按钮 | |
| 221 | + PrimaryButton: (props, { attrs, slots }) => { | |
| 222 | + return h(Button, { ...props, attrs, type: 'primary' }, slots); | |
| 223 | + }, | |
| 224 | + Radio, | |
| 225 | + RadioGroup, | |
| 226 | + RangePicker, | |
| 227 | + Rate, | |
| 228 | + Select: withDefaultPlaceholder(Select, 'select'), | |
| 229 | + Space, | |
| 230 | + Switch, | |
| 231 | + Textarea: withDefaultPlaceholder(Textarea, 'input'), | |
| 232 | + TimePicker, | |
| 233 | + TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), | |
| 234 | + Upload, | |
| 235 | + ImageUpload, | |
| 236 | + FileUpload, | |
| 237 | + RichTextarea, | |
| 238 | + ImageUploadOld, | |
| 239 | + FileUploadOld, | |
| 240 | + }; | |
| 241 | + | |
| 242 | + // 将组件注册到全局共享状态中 | |
| 243 | + globalShareState.setComponents(components); | |
| 244 | + | |
| 245 | + // 定义全局共享状态中的消息提示 | |
| 246 | + globalShareState.defineMessage({ | |
| 247 | + // 复制成功消息提示 | |
| 248 | + copyPreferencesSuccess: (title, content) => { | |
| 249 | + notification.success({ | |
| 250 | + description: content, | |
| 251 | + message: title, | |
| 252 | + placement: 'bottomRight', | |
| 253 | + }); | |
| 254 | + }, | |
| 255 | + }); | |
| 256 | +} | |
| 257 | + | |
| 258 | +export { initComponentAdapter }; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts
0 → 100644
| 1 | +import type { | |
| 2 | + VbenFormSchema as FormSchema, | |
| 3 | + VbenFormProps, | |
| 4 | +} from '@vben/common-ui'; | |
| 5 | + | |
| 6 | +import type { ComponentType } from './component'; | |
| 7 | + | |
| 8 | +import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; | |
| 9 | +import { $t } from '@vben/locales'; | |
| 10 | + | |
| 11 | +import { isArray } from 'lodash-es'; | |
| 12 | + | |
| 13 | +async function initSetupVbenForm() { | |
| 14 | + setupVbenForm<ComponentType>({ | |
| 15 | + config: { | |
| 16 | + // ant design vue组件库默认都是 v-model:value | |
| 17 | + baseModelPropName: 'value', | |
| 18 | + | |
| 19 | + // 一些组件是 v-model:checked 或者 v-model:fileList | |
| 20 | + modelPropNameMap: { | |
| 21 | + Checkbox: 'checked', | |
| 22 | + Radio: 'checked', | |
| 23 | + RichTextarea: 'modelValue', | |
| 24 | + Switch: 'checked', | |
| 25 | + Upload: 'fileList', | |
| 26 | + }, | |
| 27 | + }, | |
| 28 | + defineRules: { | |
| 29 | + // 输入项目必填国际化适配 | |
| 30 | + required: (value, _params, ctx) => { | |
| 31 | + if (value === undefined || value === null || value.length === 0) { | |
| 32 | + return $t('ui.formRules.required', [ctx.label]); | |
| 33 | + } | |
| 34 | + return true; | |
| 35 | + }, | |
| 36 | + // 选择项目必填国际化适配 | |
| 37 | + selectRequired: (value, _params, ctx) => { | |
| 38 | + if ( | |
| 39 | + [false, null, undefined].includes(value) || | |
| 40 | + (isArray(value) && value.length === 0) | |
| 41 | + ) { | |
| 42 | + return $t('ui.formRules.selectRequired', [ctx.label]); | |
| 43 | + } | |
| 44 | + return true; | |
| 45 | + }, | |
| 46 | + }, | |
| 47 | + }); | |
| 48 | +} | |
| 49 | + | |
| 50 | +const useVbenForm = useForm<ComponentType>; | |
| 51 | + | |
| 52 | +export { initSetupVbenForm, useVbenForm, z }; | |
| 53 | + | |
| 54 | +export type VbenFormSchema = FormSchema<ComponentType>; | |
| 55 | +export type { VbenFormProps }; | |
| 56 | +export type FormSchemaGetter = () => VbenFormSchema[]; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts
0 → 100644
| 1 | +import type { VxeGridPropTypes } from '@vben/plugins/vxe-table'; | |
| 2 | + | |
| 3 | +import { h } from 'vue'; | |
| 4 | + | |
| 5 | +import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; | |
| 6 | + | |
| 7 | +import { Button, Image } from 'ant-design-vue'; | |
| 8 | + | |
| 9 | +import { useVbenForm } from './form'; | |
| 10 | + | |
| 11 | +setupVbenVxeTable({ | |
| 12 | + configVxeTable: (vxeUI) => { | |
| 13 | + vxeUI.setConfig({ | |
| 14 | + grid: { | |
| 15 | + align: 'center', | |
| 16 | + border: false, | |
| 17 | + minHeight: 180, | |
| 18 | + formConfig: { | |
| 19 | + // 全局禁用vxe-table的表单配置,使用formOptions | |
| 20 | + enabled: false, | |
| 21 | + }, | |
| 22 | + proxyConfig: { | |
| 23 | + autoLoad: true, | |
| 24 | + response: { | |
| 25 | + result: 'items', | |
| 26 | + total: 'totalCount', | |
| 27 | + list: 'items', | |
| 28 | + }, | |
| 29 | + showActiveMsg: true, | |
| 30 | + showResponseMsg: false, | |
| 31 | + }, | |
| 32 | + // 溢出展示形式 | |
| 33 | + showOverflow: true, | |
| 34 | + pagerConfig: { | |
| 35 | + // 默认条数 | |
| 36 | + pageSize: 10, | |
| 37 | + // 分页可选条数 | |
| 38 | + pageSizes: [10, 20, 30, 40, 50], | |
| 39 | + }, | |
| 40 | + rowConfig: { | |
| 41 | + // 鼠标移入行显示 hover 样式 | |
| 42 | + isHover: true, | |
| 43 | + // 点击行高亮 | |
| 44 | + isCurrent: false, | |
| 45 | + }, | |
| 46 | + columnConfig: { | |
| 47 | + // 可拖拽列宽 | |
| 48 | + resizable: true, | |
| 49 | + }, | |
| 50 | + // 右上角工具栏 | |
| 51 | + toolbarConfig: { | |
| 52 | + // 自定义列 | |
| 53 | + custom: true, | |
| 54 | + customOptions: { | |
| 55 | + icon: 'vxe-icon-setting', | |
| 56 | + }, | |
| 57 | + // 最大化 | |
| 58 | + zoom: true, | |
| 59 | + // 刷新 | |
| 60 | + refresh: true, | |
| 61 | + refreshOptions: { | |
| 62 | + // 默认为reload 修改为在当前页刷新 | |
| 63 | + code: 'query', | |
| 64 | + }, | |
| 65 | + }, | |
| 66 | + // 圆角按钮 | |
| 67 | + round: true, | |
| 68 | + // 表格尺寸 | |
| 69 | + size: 'medium', | |
| 70 | + customConfig: { | |
| 71 | + // 表格右上角自定义列配置 是否保存到localStorage | |
| 72 | + // 必须存在id参数才能使用 | |
| 73 | + storage: false, | |
| 74 | + }, | |
| 75 | + }, | |
| 76 | + }); | |
| 77 | + | |
| 78 | + // 表格配置项可以用 cellRender: { name: 'CellImage' }, | |
| 79 | + vxeUI.renderer.add('CellImage', { | |
| 80 | + renderTableDefault(_renderOpts, params) { | |
| 81 | + const { column, row } = params; | |
| 82 | + return h(Image, { src: row[column.field] }); | |
| 83 | + }, | |
| 84 | + }); | |
| 85 | + | |
| 86 | + // 表格配置项可以用 cellRender: { name: 'CellLink' }, | |
| 87 | + vxeUI.renderer.add('CellLink', { | |
| 88 | + renderTableDefault(renderOpts) { | |
| 89 | + const { props } = renderOpts; | |
| 90 | + return h( | |
| 91 | + Button, | |
| 92 | + { size: 'small', type: 'link' }, | |
| 93 | + { default: () => props?.text }, | |
| 94 | + ); | |
| 95 | + }, | |
| 96 | + }); | |
| 97 | + | |
| 98 | + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 | |
| 99 | + // vxeUI.formats.add | |
| 100 | + }, | |
| 101 | + useVbenForm, | |
| 102 | +}); | |
| 103 | + | |
| 104 | +export { useVbenVxeGrid }; | |
| 105 | + | |
| 106 | +export type * from '@vben/plugins/vxe-table'; | |
| 107 | + | |
| 108 | +/** | |
| 109 | + * 判断vxe-table的复选框是否选中 | |
| 110 | + * @param tableApi api | |
| 111 | + * @returns boolean | |
| 112 | + */ | |
| 113 | +export function vxeCheckboxChecked( | |
| 114 | + tableApi: ReturnType<typeof useVbenVxeGrid>[1], | |
| 115 | +) { | |
| 116 | + return tableApi?.grid?.getCheckboxRecords?.()?.length > 0; | |
| 117 | +} | |
| 118 | + | |
| 119 | +/** | |
| 120 | + * 通用的 排序参数添加到请求参数中 | |
| 121 | + * @param params 请求参数 | |
| 122 | + * @param sortList vxe-table的排序参数 | |
| 123 | + */ | |
| 124 | +export function addSortParams( | |
| 125 | + params: Record<string, any>, | |
| 126 | + sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[], | |
| 127 | +) { | |
| 128 | + // 这里是排序取消 length为0 就不添加参数了 | |
| 129 | + if (sortList.length === 0) { | |
| 130 | + return; | |
| 131 | + } | |
| 132 | + // 支持单/多字段排序 | |
| 133 | + const orderByColumn = sortList.map((item) => item.field).join(','); | |
| 134 | + const isAsc = sortList.map((item) => item.order).join(','); | |
| 135 | + params.orderByColumn = orderByColumn; | |
| 136 | + params.isAsc = isAsc; | |
| 137 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts
0 → 100644
| 1 | +export type ID = number | string; | |
| 2 | +export type IDS = (number | string)[]; | |
| 3 | + | |
| 4 | +export interface BaseEntity { | |
| 5 | + createBy?: string; | |
| 6 | + createDept?: string; | |
| 7 | + creationTime?: string; | |
| 8 | + updateBy?: string; | |
| 9 | + updateTime?: string; | |
| 10 | +} | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 分页信息 | |
| 14 | + * @param rows 结果集 | |
| 15 | + * @param total 总数 | |
| 16 | + */ | |
| 17 | +export interface PageResult<T = any> { | |
| 18 | + items: T[]; | |
| 19 | + totalCount: number; | |
| 20 | +} | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * 分页查询参数 | |
| 24 | + * | |
| 25 | + * 排序支持的用法如下: | |
| 26 | + * {isAsc:"asc",orderByColumn:"id"} order by id asc | |
| 27 | + * {isAsc:"asc",orderByColumn:"id,creationTime"} order by id asc,create_time asc | |
| 28 | + * {isAsc:"desc",orderByColumn:"id,creationTime"} order by id desc,create_time desc | |
| 29 | + * {isAsc:"asc,desc",orderByColumn:"id,creationTime"} order by id asc,create_time desc | |
| 30 | + * | |
| 31 | + * @param SkipCount 当前页 | |
| 32 | + * @param MaxResultCount 每页大小 | |
| 33 | + * @param orderByColumn 排序字段 | |
| 34 | + * @param isAsc 是否升序 | |
| 35 | + */ | |
| 36 | +export interface PageQuery { | |
| 37 | + isAsc?: string; | |
| 38 | + orderByColumn?: string; | |
| 39 | + SkipCount?: number; | |
| 40 | + MaxResultCount?: number; | |
| 41 | + [key: string]: any; | |
| 42 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts
0 → 100644
| 1 | +import type { GrantType } from '@vben/common-ui'; | |
| 2 | +import type { HttpResponse } from '@vben/request'; | |
| 3 | + | |
| 4 | +import { h } from 'vue'; | |
| 5 | + | |
| 6 | +import { useAppConfig } from '@vben/hooks'; | |
| 7 | + | |
| 8 | +import { Modal } from 'ant-design-vue'; | |
| 9 | + | |
| 10 | +import { requestClient } from '#/api/request'; | |
| 11 | + | |
| 12 | +const { clientId, sseEnable } = useAppConfig( | |
| 13 | + import.meta.env, | |
| 14 | + import.meta.env.PROD, | |
| 15 | +); | |
| 16 | + | |
| 17 | +export namespace AuthApi { | |
| 18 | + /** | |
| 19 | + * @description: 所有登录类型都需要用到的 | |
| 20 | + * @param clientId 客户端ID 这里为必填项 但是在loginApi内部处理了 所以为可选 | |
| 21 | + * @param grantType 授权/登录类型 | |
| 22 | + * @param tenantId 租户id | |
| 23 | + */ | |
| 24 | + export interface BaseLoginParams { | |
| 25 | + clientId?: string; | |
| 26 | + grantType: GrantType; | |
| 27 | + tenantId: string; | |
| 28 | + } | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * @description: oauth登录需要用到的参数 | |
| 32 | + * @param socialCode 第三方参数 | |
| 33 | + * @param socialState 第三方参数 | |
| 34 | + * @param source 与后端的 justauth.type.xxx的回调地址的source对应 | |
| 35 | + */ | |
| 36 | + export interface OAuthLoginParams extends BaseLoginParams { | |
| 37 | + socialCode: string; | |
| 38 | + socialState: string; | |
| 39 | + source: string; | |
| 40 | + } | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * @description: 验证码登录需要用到的参数 | |
| 44 | + * @param code 验证码 可选(未开启验证码情况) | |
| 45 | + * @param uuid 验证码ID 可选(未开启验证码情况) | |
| 46 | + * @param username 用户名 | |
| 47 | + * @param password 密码 | |
| 48 | + */ | |
| 49 | + export interface SimpleLoginParams extends BaseLoginParams { | |
| 50 | + code?: string; | |
| 51 | + uuid?: string; | |
| 52 | + username: string; | |
| 53 | + password: string; | |
| 54 | + } | |
| 55 | + | |
| 56 | + export type LoginParams = OAuthLoginParams | SimpleLoginParams; | |
| 57 | + | |
| 58 | + // /** 登录接口参数 */ | |
| 59 | + // export interface LoginParams { | |
| 60 | + // code?: string; | |
| 61 | + // grantType: string; | |
| 62 | + // password: string; | |
| 63 | + // tenantId: string; | |
| 64 | + // username: string; | |
| 65 | + // uuid?: string; | |
| 66 | + // } | |
| 67 | + | |
| 68 | + /** 登录接口返回值 */ | |
| 69 | + export interface LoginResult { | |
| 70 | + access_token: string; | |
| 71 | + client_id: string; | |
| 72 | + expire_in: number; | |
| 73 | + } | |
| 74 | + | |
| 75 | + export interface RefreshTokenResult { | |
| 76 | + data: string; | |
| 77 | + status: number; | |
| 78 | + } | |
| 79 | +} | |
| 80 | + | |
| 81 | +/** | |
| 82 | + * 登录 | |
| 83 | + */ | |
| 84 | +export async function loginApi(data: AuthApi.LoginParams) { | |
| 85 | + return requestClient.post<AuthApi.LoginResult>( | |
| 86 | + '/account/login', | |
| 87 | + { ...data, clientId }, | |
| 88 | + { | |
| 89 | + encrypt: true, | |
| 90 | + }, | |
| 91 | + ); | |
| 92 | +} | |
| 93 | + | |
| 94 | +/** | |
| 95 | + * 用户登出 | |
| 96 | + * @returns void | |
| 97 | + */ | |
| 98 | +export async function doLogout() { | |
| 99 | + const resp = await requestClient.post<HttpResponse<void>>( | |
| 100 | + 'account/logout', | |
| 101 | + null, | |
| 102 | + { | |
| 103 | + isTransformResponse: false, | |
| 104 | + }, | |
| 105 | + ); | |
| 106 | + // 无奈之举 对错误用法的提示 | |
| 107 | + if (resp.code === 401 && import.meta.env.DEV) { | |
| 108 | + Modal.destroyAll(); | |
| 109 | + Modal.warn({ | |
| 110 | + title: '后端配置出现错误', | |
| 111 | + centered: true, | |
| 112 | + content: h('div', { class: 'flex flex-col gap-2' }, [ | |
| 113 | + `检测到你的logout接口返回了401, 导致前端一直进入循环逻辑???`, | |
| 114 | + ...Array.from({ length: 3 }, () => | |
| 115 | + h( | |
| 116 | + 'span', | |
| 117 | + { class: 'font-bold text-red-500 text-[18px]' }, | |
| 118 | + '去检查你的后端配置!别盯着前端找问题了!这不是前端问题!', | |
| 119 | + ), | |
| 120 | + ), | |
| 121 | + ]), | |
| 122 | + }); | |
| 123 | + } | |
| 124 | +} | |
| 125 | + | |
| 126 | +/** | |
| 127 | + * 关闭sse连接 | |
| 128 | + * @returns void | |
| 129 | + */ | |
| 130 | +export function seeConnectionClose() { | |
| 131 | + /** | |
| 132 | + * 未开启sse 不需要处理 | |
| 133 | + */ | |
| 134 | + if (!sseEnable) { | |
| 135 | + return; | |
| 136 | + } | |
| 137 | + return requestClient.get<void>('/resource/sse/close'); | |
| 138 | +} | |
| 139 | + | |
| 140 | +/** | |
| 141 | + * @param companyName 租户/公司名称 | |
| 142 | + * @param domain 绑定域名(不带http(s)://) 可选 | |
| 143 | + * @param tenantId 租户id | |
| 144 | + */ | |
| 145 | +export interface TenantOption { | |
| 146 | + companyName: string; | |
| 147 | + domain?: string; | |
| 148 | + tenantId: string; | |
| 149 | +} | |
| 150 | + | |
| 151 | +/** | |
| 152 | + * @param tenantEnabled 是否启用租户 | |
| 153 | + * @param voList 租户列表 | |
| 154 | + */ | |
| 155 | +export interface TenantResp { | |
| 156 | + tenantEnabled: boolean; | |
| 157 | + voList: TenantOption[]; | |
| 158 | +} | |
| 159 | + | |
| 160 | +/** | |
| 161 | + * 获取租户列表 下拉框使用 | |
| 162 | + */ | |
| 163 | +export function tenantList() { | |
| 164 | + return requestClient.get<TenantResp>('/tenant/select'); | |
| 165 | +} | |
| 166 | + | |
| 167 | +/** | |
| 168 | + * vben的 先不删除 | |
| 169 | + * @returns string[] | |
| 170 | + */ | |
| 171 | +export async function getAccessCodesApi() { | |
| 172 | + return requestClient.get<string[]>('/auth/codes'); | |
| 173 | +} | |
| 174 | + | |
| 175 | +/** | |
| 176 | + * 绑定第三方账号 | |
| 177 | + * @param source 绑定的来源 | |
| 178 | + * @returns 跳转url | |
| 179 | + */ | |
| 180 | +export function authBinding(source: string, tenantId: string) { | |
| 181 | + return requestClient.get<string>(`/auth/binding/${source}`, { | |
| 182 | + params: { | |
| 183 | + domain: window.location.host, | |
| 184 | + tenantId, | |
| 185 | + }, | |
| 186 | + }); | |
| 187 | +} | |
| 188 | + | |
| 189 | +/** | |
| 190 | + * 取消绑定 | |
| 191 | + * @param id id | |
| 192 | + */ | |
| 193 | +export function authUnbinding(id: string) { | |
| 194 | + return requestClient.deleteWithMsg<void>(`/auth/unlock/${id}`); | |
| 195 | +} | |
| 196 | + | |
| 197 | +/** | |
| 198 | + * oauth授权回调 | |
| 199 | + * @param data oauth授权 | |
| 200 | + * @returns void | |
| 201 | + */ | |
| 202 | +export function authCallback(data: AuthApi.OAuthLoginParams) { | |
| 203 | + return requestClient.post<void>('/auth/social/callback', data); | |
| 204 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts
0 → 100644
| 1 | +import { requestClient } from '#/api/request'; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * 发送短信验证码 | |
| 5 | + * @param phone 手机号 | |
| 6 | + * @returns void | |
| 7 | + */ | |
| 8 | +export function sendSmsCode(phone: string) { | |
| 9 | + return requestClient.get<void>('/resource/sms/code', { | |
| 10 | + params: { phone }, | |
| 11 | + }); | |
| 12 | +} | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 发送邮件验证码 | |
| 16 | + * @param email 邮箱 | |
| 17 | + * @returns void | |
| 18 | + */ | |
| 19 | +export function sendEmailCode(email: string) { | |
| 20 | + return requestClient.get<void>('/resource/email/code', { | |
| 21 | + params: { email }, | |
| 22 | + }); | |
| 23 | +} | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * @param img 图片验证码 需要和base64拼接 | |
| 27 | + * @param isEnableCaptcha 是否开启 | |
| 28 | + * @param uuid 验证码ID | |
| 29 | + */ | |
| 30 | +export interface CaptchaResponse { | |
| 31 | + isEnableCaptcha: boolean; | |
| 32 | + img: string; | |
| 33 | + uuid: string; | |
| 34 | +} | |
| 35 | + | |
| 36 | +/** | |
| 37 | + * 图片验证码 | |
| 38 | + * @returns resp | |
| 39 | + */ | |
| 40 | +export function captchaImage() { | |
| 41 | + return requestClient.get<CaptchaResponse>('/account/captcha-image'); | |
| 42 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts
0 → 100644
| 1 | +import { requestClient } from '#/api/request'; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * @description: 菜单meta | |
| 5 | + * @param title 菜单名 | |
| 6 | + * @param icon 菜单图标 | |
| 7 | + * @param noCache 是否不缓存 | |
| 8 | + * @param link 外链链接 | |
| 9 | + */ | |
| 10 | +export interface MenuMeta { | |
| 11 | + icon: string; | |
| 12 | + link?: string; | |
| 13 | + noCache: boolean; | |
| 14 | + title: string; | |
| 15 | +} | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * @description: 菜单 | |
| 19 | + * @param name 菜单名 | |
| 20 | + * @param path 菜单路径 | |
| 21 | + * @param hidden 是否隐藏 | |
| 22 | + * @param component 组件名称 Layout | |
| 23 | + * @param alwaysShow 总是显示 | |
| 24 | + * @param query 路由参数(json形式) | |
| 25 | + * @param meta 路由信息 | |
| 26 | + * @param children 子路由信息 | |
| 27 | + */ | |
| 28 | +export interface Menu { | |
| 29 | + alwaysShow?: boolean; | |
| 30 | + children: Menu[]; | |
| 31 | + component: string; | |
| 32 | + hidden: boolean; | |
| 33 | + meta: MenuMeta; | |
| 34 | + name: string; | |
| 35 | + path: string; | |
| 36 | + query?: string; | |
| 37 | + redirect?: string; | |
| 38 | +} | |
| 39 | + | |
| 40 | +/** | |
| 41 | + * 获取用户所有菜单 | |
| 42 | + */ | |
| 43 | +export async function getAllMenusApi() { | |
| 44 | + return requestClient.get<Menu[]>('/account/Vue3Router/vben5'); | |
| 45 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts
0 → 100644
| 1 | +import type { AxiosRequestConfig } from '@vben/request'; | |
| 2 | + | |
| 3 | +import { requestClient } from '#/api/request'; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * Axios上传进度事件 | |
| 7 | + */ | |
| 8 | +export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress']; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 默认上传结果 | |
| 12 | + */ | |
| 13 | +export interface UploadResult { | |
| 14 | + url: string; | |
| 15 | + fileName: string; | |
| 16 | + ossId: string; | |
| 17 | +} | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * 通过单文件上传接口 | |
| 21 | + * @param file 上传的文件 | |
| 22 | + * @param options 一些配置项 | |
| 23 | + * @param options.onUploadProgress 上传进度事件 | |
| 24 | + * @param options.signal 上传取消信号 | |
| 25 | + * @param options.otherData 其他请求参数 后端拓展可能会用到 | |
| 26 | + * @returns 上传结果 | |
| 27 | + */ | |
| 28 | +export function uploadApi( | |
| 29 | + file: Blob | File, | |
| 30 | + options?: { | |
| 31 | + onUploadProgress?: AxiosProgressEvent; | |
| 32 | + otherData?: Record<string, any>; | |
| 33 | + signal?: AbortSignal; | |
| 34 | + }, | |
| 35 | +) { | |
| 36 | + const { onUploadProgress, signal, otherData = {} } = options ?? {}; | |
| 37 | + return requestClient.upload<UploadResult>( | |
| 38 | + '/resource/oss/upload', | |
| 39 | + { file, ...otherData }, | |
| 40 | + { onUploadProgress, signal, timeout: 60_000 }, | |
| 41 | + ); | |
| 42 | +} | |
| 43 | + | |
| 44 | +/** | |
| 45 | + * 上传api type | |
| 46 | + */ | |
| 47 | +export type UploadApi = typeof uploadApi; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts
0 → 100644
| 1 | +import { requestClient } from '#/api/request'; | |
| 2 | + | |
| 3 | +export interface Role { | |
| 4 | + dataScope: string; | |
| 5 | + flag: boolean; | |
| 6 | + roleId: number; | |
| 7 | + roleKey: string; | |
| 8 | + roleName: string; | |
| 9 | + roleSort: number; | |
| 10 | + status: string; | |
| 11 | + superAdmin: boolean; | |
| 12 | +} | |
| 13 | + | |
| 14 | +export interface User { | |
| 15 | + avatar: string; | |
| 16 | + creationTime: string; | |
| 17 | + deptId: number; | |
| 18 | + deptName: string; | |
| 19 | + email: string; | |
| 20 | + loginDate: string; | |
| 21 | + loginIp: string; | |
| 22 | + nick: string; | |
| 23 | + phone: string; | |
| 24 | + remark: string; | |
| 25 | + roles: Role[]; | |
| 26 | + sex: string; | |
| 27 | + status: string; | |
| 28 | + tenantId: string; | |
| 29 | + userId: number; | |
| 30 | + userName: string; | |
| 31 | + userType: string; | |
| 32 | +} | |
| 33 | + | |
| 34 | +export interface UserInfoResp { | |
| 35 | + permissionCodes: string[]; | |
| 36 | + roles: string[]; | |
| 37 | + roleCodes: string[]; | |
| 38 | + user: User; | |
| 39 | +} | |
| 40 | + | |
| 41 | +/** | |
| 42 | + * 获取用户信息 | |
| 43 | + * 存在返回null的情况(401) 不会抛出异常 需要手动抛异常 | |
| 44 | + */ | |
| 45 | +export async function getUserInfoApi() { | |
| 46 | + return requestClient.get<null | UserInfoResp>('account'); | |
| 47 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/dashboard-types.ts
0 → 100644
| 1 | +export interface DashboardMetricCardDto { | |
| 2 | + key: string; | |
| 3 | + title: string; | |
| 4 | + value: number; | |
| 5 | + previousValue: number; | |
| 6 | + changeValue: number; | |
| 7 | + changeRate: number; | |
| 8 | +} | |
| 9 | + | |
| 10 | +export interface DashboardWeeklyPointDto { | |
| 11 | + date: string; | |
| 12 | + value: number; | |
| 13 | +} | |
| 14 | + | |
| 15 | +export interface DashboardCategorySliceDto { | |
| 16 | + categoryId: string; | |
| 17 | + categoryName: string; | |
| 18 | + count: number; | |
| 19 | + ratio: number; | |
| 20 | +} | |
| 21 | + | |
| 22 | +export interface DashboardRecentLabelItemDto { | |
| 23 | + taskId: string; | |
| 24 | + labelCode: string; | |
| 25 | + displayName: string; | |
| 26 | + printedByUserId: string | null; | |
| 27 | + printedByName: string; | |
| 28 | + printedAt: string; | |
| 29 | + status: string; | |
| 30 | + labelTypeBadge: string; | |
| 31 | +} | |
| 32 | + | |
| 33 | +export interface DashboardOverviewDto { | |
| 34 | + labelsPrintedToday: DashboardMetricCardDto; | |
| 35 | + activeTemplates: DashboardMetricCardDto; | |
| 36 | + activeUsers: DashboardMetricCardDto; | |
| 37 | + locations: DashboardMetricCardDto; | |
| 38 | + people: DashboardMetricCardDto; | |
| 39 | + products: DashboardMetricCardDto; | |
| 40 | + weeklyPrintVolume: DashboardWeeklyPointDto[]; | |
| 41 | + byCategory: DashboardCategorySliceDto[]; | |
| 42 | + byCategoryTotal: number; | |
| 43 | + recentLabels: DashboardRecentLabelItemDto[]; | |
| 44 | + generatedAt?: string | null; | |
| 45 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/dashboard.ts
0 → 100644
| 1 | +import type { | |
| 2 | + DashboardCategorySliceDto, | |
| 3 | + DashboardMetricCardDto, | |
| 4 | + DashboardOverviewDto, | |
| 5 | + DashboardRecentLabelItemDto, | |
| 6 | + DashboardWeeklyPointDto, | |
| 7 | +} from './dashboard-types'; | |
| 8 | + | |
| 9 | +import { requestClient } from '#/api/request'; | |
| 10 | + | |
| 11 | +const PATH = '/dashboard/overview'; | |
| 12 | + | |
| 13 | +function num(x: unknown, fallback = 0): number { | |
| 14 | + const n = typeof x === 'number' ? x : Number(x); | |
| 15 | + return Number.isFinite(n) ? n : fallback; | |
| 16 | +} | |
| 17 | + | |
| 18 | +function str(o: Record<string, unknown>, camel: string, pascal: string, fallback = '') { | |
| 19 | + const v = o[camel] ?? o[pascal]; | |
| 20 | + if (v === null || v === undefined) { | |
| 21 | + return fallback; | |
| 22 | + } | |
| 23 | + return String(v); | |
| 24 | +} | |
| 25 | + | |
| 26 | +function normMetric(raw: unknown): DashboardMetricCardDto { | |
| 27 | + if (!raw || typeof raw !== 'object') { | |
| 28 | + return { | |
| 29 | + changeRate: 0, | |
| 30 | + changeValue: 0, | |
| 31 | + key: '', | |
| 32 | + previousValue: 0, | |
| 33 | + title: '', | |
| 34 | + value: 0, | |
| 35 | + }; | |
| 36 | + } | |
| 37 | + const o = raw as Record<string, unknown>; | |
| 38 | + return { | |
| 39 | + changeRate: num(o.changeRate ?? o.ChangeRate, 0), | |
| 40 | + changeValue: num(o.changeValue ?? o.ChangeValue, 0), | |
| 41 | + key: String(o.key ?? o.Key ?? ''), | |
| 42 | + previousValue: num(o.previousValue ?? o.PreviousValue, 0), | |
| 43 | + title: String(o.title ?? o.Title ?? ''), | |
| 44 | + value: num(o.value ?? o.Value, 0), | |
| 45 | + }; | |
| 46 | +} | |
| 47 | + | |
| 48 | +function normWeeklyPoint(raw: unknown): DashboardWeeklyPointDto | null { | |
| 49 | + if (!raw || typeof raw !== 'object') { | |
| 50 | + return null; | |
| 51 | + } | |
| 52 | + const o = raw as Record<string, unknown>; | |
| 53 | + const date = String(o.date ?? o.Date ?? ''); | |
| 54 | + if (!date) { | |
| 55 | + return null; | |
| 56 | + } | |
| 57 | + return { date, value: num(o.value ?? o.Value, 0) }; | |
| 58 | +} | |
| 59 | + | |
| 60 | +function normCategorySlice(raw: unknown): DashboardCategorySliceDto | null { | |
| 61 | + if (!raw || typeof raw !== 'object') { | |
| 62 | + return null; | |
| 63 | + } | |
| 64 | + const o = raw as Record<string, unknown>; | |
| 65 | + return { | |
| 66 | + categoryId: String(o.categoryId ?? o.CategoryId ?? ''), | |
| 67 | + categoryName: String(o.categoryName ?? o.CategoryName ?? ''), | |
| 68 | + count: num(o.count ?? o.Count, 0), | |
| 69 | + ratio: num(o.ratio ?? o.Ratio, 0), | |
| 70 | + }; | |
| 71 | +} | |
| 72 | + | |
| 73 | +function normRecentLabel(raw: unknown): DashboardRecentLabelItemDto | null { | |
| 74 | + if (!raw || typeof raw !== 'object') { | |
| 75 | + return null; | |
| 76 | + } | |
| 77 | + const o = raw as Record<string, unknown>; | |
| 78 | + const taskId = str(o, 'taskId', 'TaskId', ''); | |
| 79 | + const labelCode = str(o, 'labelCode', 'LabelCode', ''); | |
| 80 | + if (!taskId && !labelCode) { | |
| 81 | + return null; | |
| 82 | + } | |
| 83 | + const pbUid = o.printedByUserId ?? o.PrintedByUserId; | |
| 84 | + return { | |
| 85 | + displayName: str(o, 'displayName', 'DisplayName', '—') || '—', | |
| 86 | + labelCode: labelCode || '—', | |
| 87 | + labelTypeBadge: str(o, 'labelTypeBadge', 'LabelTypeBadge', '—') || '—', | |
| 88 | + printedAt: str(o, 'printedAt', 'PrintedAt', ''), | |
| 89 | + printedByName: str(o, 'printedByName', 'PrintedByName', '—') || '—', | |
| 90 | + printedByUserId: | |
| 91 | + pbUid === null || pbUid === undefined ? null : String(pbUid), | |
| 92 | + status: str(o, 'status', 'Status', 'active') || 'active', | |
| 93 | + taskId: taskId || labelCode, | |
| 94 | + }; | |
| 95 | +} | |
| 96 | + | |
| 97 | +function emptyMetric(key: string, title: string): DashboardMetricCardDto { | |
| 98 | + return { | |
| 99 | + changeRate: 0, | |
| 100 | + changeValue: 0, | |
| 101 | + key, | |
| 102 | + previousValue: 0, | |
| 103 | + title, | |
| 104 | + value: 0, | |
| 105 | + }; | |
| 106 | +} | |
| 107 | + | |
| 108 | +export function emptyDashboardOverview(): DashboardOverviewDto { | |
| 109 | + return { | |
| 110 | + activeTemplates: emptyMetric('activeTemplates', 'Active Templates'), | |
| 111 | + activeUsers: emptyMetric('activeUsers', 'Active Users'), | |
| 112 | + byCategory: [], | |
| 113 | + byCategoryTotal: 0, | |
| 114 | + generatedAt: null, | |
| 115 | + labelsPrintedToday: emptyMetric('labelsPrintedToday', 'Labels Printed Today'), | |
| 116 | + locations: emptyMetric('locations', 'Locations'), | |
| 117 | + people: emptyMetric('people', 'People'), | |
| 118 | + products: emptyMetric('products', 'Products'), | |
| 119 | + recentLabels: [], | |
| 120 | + weeklyPrintVolume: [], | |
| 121 | + }; | |
| 122 | +} | |
| 123 | + | |
| 124 | +function normalizeOverview(raw: unknown): DashboardOverviewDto { | |
| 125 | + if (!raw || typeof raw !== 'object') { | |
| 126 | + return emptyDashboardOverview(); | |
| 127 | + } | |
| 128 | + const o = raw as Record<string, unknown>; | |
| 129 | + | |
| 130 | + const weeklyRaw = (o.weeklyPrintVolume ?? o.WeeklyPrintVolume) as unknown; | |
| 131 | + const weeklyArr = Array.isArray(weeklyRaw) ? weeklyRaw : []; | |
| 132 | + | |
| 133 | + const byCat = (o.byCategory ?? o.ByCategory) as unknown; | |
| 134 | + const legacyCat = (o.categoryDistribution ?? o.CategoryDistribution) as unknown; | |
| 135 | + const catArr = | |
| 136 | + Array.isArray(byCat) && byCat.length | |
| 137 | + ? byCat | |
| 138 | + : Array.isArray(legacyCat) | |
| 139 | + ? legacyCat | |
| 140 | + : []; | |
| 141 | + | |
| 142 | + const totalRaw = | |
| 143 | + o.byCategoryTotal ?? | |
| 144 | + o.ByCategoryTotal ?? | |
| 145 | + o.categoryDistributionTotal ?? | |
| 146 | + o.CategoryDistributionTotal; | |
| 147 | + | |
| 148 | + const recentRaw = (o.recentLabels ?? o.RecentLabels) as unknown; | |
| 149 | + const recentArr = Array.isArray(recentRaw) ? recentRaw : []; | |
| 150 | + | |
| 151 | + return { | |
| 152 | + activeTemplates: normMetric(o.activeTemplates ?? o.ActiveTemplates), | |
| 153 | + activeUsers: normMetric(o.activeUsers ?? o.ActiveUsers), | |
| 154 | + byCategory: catArr | |
| 155 | + .map(normCategorySlice) | |
| 156 | + .filter((x): x is DashboardCategorySliceDto => x !== null), | |
| 157 | + byCategoryTotal: num(totalRaw, 0), | |
| 158 | + generatedAt: (o.generatedAt ?? o.GeneratedAt ?? null) as string | null, | |
| 159 | + labelsPrintedToday: normMetric(o.labelsPrintedToday ?? o.LabelsPrintedToday), | |
| 160 | + locations: normMetric(o.locations ?? o.Locations), | |
| 161 | + people: normMetric(o.people ?? o.People), | |
| 162 | + products: normMetric(o.products ?? o.Products), | |
| 163 | + recentLabels: recentArr | |
| 164 | + .map(normRecentLabel) | |
| 165 | + .filter((x): x is DashboardRecentLabelItemDto => x !== null), | |
| 166 | + weeklyPrintVolume: weeklyArr | |
| 167 | + .map(normWeeklyPoint) | |
| 168 | + .filter((x): x is DashboardWeeklyPointDto => x !== null), | |
| 169 | + }; | |
| 170 | +} | |
| 171 | + | |
| 172 | +export function dashboardOverview() { | |
| 173 | + return requestClient | |
| 174 | + .get<unknown>(`${PATH}`, { errorMessageMode: 'message' }) | |
| 175 | + .then((raw) => normalizeOverview(raw)); | |
| 176 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/index.ts
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/product-types.ts
| ... | ... | @@ -65,6 +65,10 @@ export interface ProductCategoryDto { |
| 65 | 65 | buttonStyleJson?: string | null; |
| 66 | 66 | availabilityType?: 'ALL' | 'SPECIFIED' | string | null; |
| 67 | 67 | locationIds?: string[] | null; |
| 68 | + region?: string | null; | |
| 69 | + location?: string | null; | |
| 70 | + regionIds?: string[] | null; | |
| 71 | + groupIds?: string[] | null; | |
| 68 | 72 | state?: boolean | null; |
| 69 | 73 | orderNum?: number | null; |
| 70 | 74 | lastEdited?: string | null; |
| ... | ... | @@ -88,8 +92,30 @@ export interface ProductCategoryCreateInput { |
| 88 | 92 | buttonStyleJson?: string | null; |
| 89 | 93 | availabilityType?: 'ALL' | 'SPECIFIED' | string | null; |
| 90 | 94 | locationIds?: string[] | null; |
| 95 | + regionIds?: string[] | null; | |
| 96 | + groupIds?: string[] | null; | |
| 91 | 97 | state?: boolean; |
| 92 | 98 | orderNum?: number | null; |
| 93 | 99 | } |
| 94 | 100 | |
| 101 | +export interface ProductBatchImportResultDto { | |
| 102 | + successCount: number; | |
| 103 | + failCount: number; | |
| 104 | + errors?: Array<{ | |
| 105 | + rowNumber?: number; | |
| 106 | + productName?: string; | |
| 107 | + message?: string; | |
| 108 | + }>; | |
| 109 | +} | |
| 110 | + | |
| 111 | +export interface ProductExportQuery { | |
| 112 | + Keyword?: string; | |
| 113 | + State?: boolean; | |
| 114 | + Sorting?: string; | |
| 115 | + PartnerId?: string; | |
| 116 | + GroupId?: string; | |
| 117 | + LocationId?: string; | |
| 118 | + CategoryId?: string; | |
| 119 | +} | |
| 120 | + | |
| 95 | 121 | export type ProductCategoryUpdateInput = ProductCategoryCreateInput; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/product.ts
| 1 | 1 | import type { |
| 2 | 2 | FlPagedResult, |
| 3 | + ProductBatchImportResultDto, | |
| 3 | 4 | ProductCreateInput, |
| 4 | 5 | ProductDto, |
| 6 | + ProductExportQuery, | |
| 5 | 7 | ProductGetListQuery, |
| 6 | 8 | ProductUpdateInput, |
| 7 | 9 | } from './product-types'; |
| 8 | 10 | |
| 11 | +import { ContentTypeEnum } from '#/api/helper'; | |
| 9 | 12 | import { requestClient } from '#/api/request'; |
| 10 | 13 | |
| 11 | 14 | const PATH = '/product'; |
| ... | ... | @@ -37,3 +40,37 @@ export function productUpdate(id: string, data: ProductUpdateInput) { |
| 37 | 40 | export function productRemove(id: string) { |
| 38 | 41 | return requestClient.deleteWithMsg<void>(`${PATH}/${encodeURIComponent(id)}`); |
| 39 | 42 | } |
| 43 | + | |
| 44 | +export function productDownloadImportTemplate() { | |
| 45 | + return requestClient.post<Blob>( | |
| 46 | + `${PATH}/download-product-import-template`, | |
| 47 | + {}, | |
| 48 | + { | |
| 49 | + isTransformResponse: false, | |
| 50 | + responseType: 'blob', | |
| 51 | + errorMessageMode: 'message', | |
| 52 | + }, | |
| 53 | + ); | |
| 54 | +} | |
| 55 | + | |
| 56 | +export function productExportExcel(params?: ProductExportQuery) { | |
| 57 | + return requestClient.get<Blob>(`${PATH}/export-products-excel`, { | |
| 58 | + params, | |
| 59 | + isTransformResponse: false, | |
| 60 | + responseType: 'blob', | |
| 61 | + errorMessageMode: 'message', | |
| 62 | + }); | |
| 63 | +} | |
| 64 | + | |
| 65 | +export function productImportBatch(file: File) { | |
| 66 | + const formData = new FormData(); | |
| 67 | + formData.append('file', file); | |
| 68 | + return requestClient.post<ProductBatchImportResultDto>( | |
| 69 | + `${PATH}/import-products-batch`, | |
| 70 | + formData, | |
| 71 | + { | |
| 72 | + headers: { 'Content-Type': ContentTypeEnum.FORM_DATA }, | |
| 73 | + errorMessageMode: 'message', | |
| 74 | + }, | |
| 75 | + ); | |
| 76 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/reports-types.ts
| ... | ... | @@ -60,6 +60,20 @@ export interface LabelReportDataDto { |
| 60 | 60 | mostUsedProducts: LabelReportTopProductDto[]; |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | +export interface ReportsTemplatePrintStatItemDto { | |
| 64 | + templateId?: string | null; | |
| 65 | + templateName?: string | null; | |
| 66 | + printedCount: number; | |
| 67 | +} | |
| 68 | + | |
| 69 | +export interface ReportsTemplatePrintStatQuery extends FlPageQuery { | |
| 70 | + PartnerId?: string; | |
| 71 | + GroupId?: string; | |
| 72 | + LocationId?: string; | |
| 73 | + StartDate?: string; | |
| 74 | + EndDate?: string; | |
| 75 | +} | |
| 76 | + | |
| 63 | 77 | export interface LabelReportQuery { |
| 64 | 78 | PartnerId?: string; |
| 65 | 79 | GroupId?: string; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/reports.ts
| ... | ... | @@ -4,6 +4,8 @@ import type { |
| 4 | 4 | LabelReportQuery, |
| 5 | 5 | ReportsPrintLogItemDto, |
| 6 | 6 | ReportsPrintLogQuery, |
| 7 | + ReportsTemplatePrintStatItemDto, | |
| 8 | + ReportsTemplatePrintStatQuery, | |
| 7 | 9 | } from './reports-types'; |
| 8 | 10 | |
| 9 | 11 | import { requestClient } from '#/api/request'; |
| ... | ... | @@ -24,6 +26,15 @@ export function reportsLabelReport(params?: LabelReportQuery) { |
| 24 | 26 | }); |
| 25 | 27 | } |
| 26 | 28 | |
| 29 | +export function reportsTemplatePrintStatList( | |
| 30 | + params?: ReportsTemplatePrintStatQuery, | |
| 31 | +) { | |
| 32 | + return requestClient.get<FlPagedResult<ReportsTemplatePrintStatItemDto>>( | |
| 33 | + `${PATH}/template-print-stat-list`, | |
| 34 | + { params, errorMessageMode: 'message' }, | |
| 35 | + ); | |
| 36 | +} | |
| 37 | + | |
| 27 | 38 | export function reportsExportPrintLogExcel(params?: ReportsPrintLogQuery) { |
| 28 | 39 | return requestClient.post<Blob>( |
| 29 | 40 | `${PATH}/export-print-log-excel`, | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts
0 → 100644
| 1 | +import { requestClient } from './request'; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * @description: contentType | |
| 5 | + */ | |
| 6 | +export const ContentTypeEnum = { | |
| 7 | + // form-data upload | |
| 8 | + FORM_DATA: 'multipart/form-data;charset=UTF-8', | |
| 9 | + // form-data qs | |
| 10 | + FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8', | |
| 11 | + // json | |
| 12 | + JSON: 'application/json;charset=UTF-8', | |
| 13 | +} as const; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 通用下载接口 封装一层 | |
| 17 | + * @param url 请求地址 | |
| 18 | + * @param data 请求参数 | |
| 19 | + * @returns blob二进制 | |
| 20 | + */ | |
| 21 | +export function commonExport(url: string, data: Record<string, any>) { | |
| 22 | + return requestClient.post<Blob>(url, data, { | |
| 23 | + data, | |
| 24 | + headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, | |
| 25 | + isTransformResponse: false, | |
| 26 | + responseType: 'blob', | |
| 27 | + }); | |
| 28 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts
0 → 100644
| 1 | +export * from './core'; | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts
0 → 100644
| 1 | +import { requestClient } from '#/api/request'; | |
| 2 | + | |
| 3 | +export interface CommandStats { | |
| 4 | + name: string; | |
| 5 | + value: string; | |
| 6 | +} | |
| 7 | + | |
| 8 | +export interface RedisInfo { | |
| 9 | + [key: string]: string; | |
| 10 | +} | |
| 11 | + | |
| 12 | +export interface CacheInfo { | |
| 13 | + commandStats: CommandStats[]; | |
| 14 | + dbSize: number; | |
| 15 | + info: RedisInfo; | |
| 16 | +} | |
| 17 | + | |
| 18 | +export interface CacheName { | |
| 19 | + cacheName: string; | |
| 20 | + remark: string; | |
| 21 | +} | |
| 22 | + | |
| 23 | +export interface CacheValue { | |
| 24 | + cacheName: string; | |
| 25 | + cacheKey: string; | |
| 26 | + cacheValue: string; | |
| 27 | +} | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * | |
| 31 | + * @returns redis信息 | |
| 32 | + */ | |
| 33 | +export function redisCacheInfo() { | |
| 34 | + return requestClient.get<CacheInfo>('/monitor/cache'); | |
| 35 | +} | |
| 36 | + | |
| 37 | +/** | |
| 38 | + * 查询缓存名称列表 | |
| 39 | + * @returns 缓存名称列表 | |
| 40 | + */ | |
| 41 | +export function listCacheName() { | |
| 42 | + return requestClient.get<CacheName[]>('/monitor-cache/name'); | |
| 43 | +} | |
| 44 | + | |
| 45 | +/** | |
| 46 | + * 查询缓存键名列表 | |
| 47 | + * @param cacheName 缓存名称 | |
| 48 | + * @returns 缓存键名列表 | |
| 49 | + */ | |
| 50 | +export function listCacheKey(cacheName: string) { | |
| 51 | + return requestClient.get<string[]>(`/monitor-cache/key/${cacheName}`); | |
| 52 | +} | |
| 53 | + | |
| 54 | +/** | |
| 55 | + * 查询缓存内容 | |
| 56 | + * @param cacheName 缓存名称 | |
| 57 | + * @param cacheKey 缓存键名 | |
| 58 | + * @returns 缓存内容 | |
| 59 | + */ | |
| 60 | +export function getCacheValue(cacheName: string, cacheKey: string) { | |
| 61 | + return requestClient.get<CacheValue>( | |
| 62 | + `/monitor-cache/value/${cacheName}/${cacheKey}`, | |
| 63 | + ); | |
| 64 | +} | |
| 65 | + | |
| 66 | +/** | |
| 67 | + * 清理指定名称缓存 | |
| 68 | + * @param cacheName 缓存名称 | |
| 69 | + */ | |
| 70 | +export function clearCacheName(cacheName: string) { | |
| 71 | + return requestClient.deleteWithMsg<void>(`/monitor-cache/key/${cacheName}`); | |
| 72 | +} | |
| 73 | + | |
| 74 | +/** | |
| 75 | + * 清理指定键名缓存 | |
| 76 | + * @param cacheName 缓存名称 | |
| 77 | + * @param cacheKey 缓存键名 | |
| 78 | + */ | |
| 79 | +export function clearCacheKey(cacheName: string, cacheKey: string) { | |
| 80 | + return requestClient.deleteWithMsg<void>( | |
| 81 | + `/monitor-cache/value/${cacheName}/${cacheKey}`, | |
| 82 | + ); | |
| 83 | +} | |
| 84 | + | |
| 85 | +/** | |
| 86 | + * 清理全部缓存 | |
| 87 | + */ | |
| 88 | +export function clearCacheAll() { | |
| 89 | + return requestClient.deleteWithMsg<void>('/monitor-cache/clear'); | |
| 90 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/index.ts
0 → 100644
| 1 | +import type { LoginLog } from './model'; | |
| 2 | + | |
| 3 | +import type { IDS, PageQuery, PageResult } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { commonExport } from '#/api/helper'; | |
| 6 | +import { requestClient } from '#/api/request'; | |
| 7 | + | |
| 8 | +enum Api { | |
| 9 | + loginInfoClean = '/login-log/clean', | |
| 10 | + loginInfoExport = '/login-log/export', | |
| 11 | + root = '/login-log', | |
| 12 | + userUnlock = '/login-log/unlock', | |
| 13 | +} | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 登录日志列表 | |
| 17 | + * @param params 查询参数 | |
| 18 | + * @returns list[] | |
| 19 | + */ | |
| 20 | +export function loginInfoList(params?: PageQuery) { | |
| 21 | + return requestClient.get<PageResult<LoginLog>>(Api.root, { params }); | |
| 22 | +} | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * 导出登录日志 | |
| 26 | + * @param data 表单参数 | |
| 27 | + * @returns excel | |
| 28 | + */ | |
| 29 | +export function loginInfoExport(data: any) { | |
| 30 | + return commonExport(Api.loginInfoExport, data); | |
| 31 | +} | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * 移除登录日志 | |
| 35 | + * @param infoIds 登录日志id数组 | |
| 36 | + * @returns void | |
| 37 | + */ | |
| 38 | +export function loginInfoRemove(infoIds: IDS) { | |
| 39 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 40 | + params: { ids: infoIds.join(',') }, | |
| 41 | + }); | |
| 42 | +} | |
| 43 | + | |
| 44 | +/** | |
| 45 | + * 账号解锁 | |
| 46 | + * @param username 用户名(账号) | |
| 47 | + * @returns void | |
| 48 | + */ | |
| 49 | +export function userUnlock(username: string) { | |
| 50 | + return requestClient.get<void>(`${Api.userUnlock}/${username}`, { | |
| 51 | + successMessageMode: 'message', | |
| 52 | + }); | |
| 53 | +} | |
| 54 | + | |
| 55 | +/** | |
| 56 | + * 清空全部登录日志 | |
| 57 | + * @returns void | |
| 58 | + */ | |
| 59 | +export function loginInfoClean() { | |
| 60 | + return requestClient.deleteWithMsg<void>(Api.loginInfoClean); | |
| 61 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts
0 → 100644
| 1 | +import type { OnlineUser } from './model'; | |
| 2 | + | |
| 3 | +import type { IDS, PageQuery, PageResult } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { requestClient } from '#/api/request'; | |
| 6 | + | |
| 7 | +enum Api { | |
| 8 | + root = '/online', | |
| 9 | +} | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * 当前账号的在线设备 个人中心使用 | |
| 13 | + * @returns OnlineUser[] | |
| 14 | + */ | |
| 15 | +export function onlineDeviceList() { | |
| 16 | + return requestClient.get<PageResult<OnlineUser>>(Api.root); | |
| 17 | +} | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * 这里的分页参数无效 返回的是全部的分页 | |
| 21 | + * @param params 请求参数 | |
| 22 | + * @returns 结果 | |
| 23 | + */ | |
| 24 | +export function onlineList(params?: PageQuery) { | |
| 25 | + return requestClient.get<PageResult<OnlineUser>>(Api.root, { params }); | |
| 26 | +} | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * 强制下线 | |
| 30 | + * @param tokenId 连接Id | |
| 31 | + * @returns void | |
| 32 | + */ | |
| 33 | +export function forceLogout(tokenId: IDS) { | |
| 34 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 35 | + params: { ids: tokenId.join(',') }, | |
| 36 | + }); | |
| 37 | +} | |
| 38 | + | |
| 39 | +/** | |
| 40 | + * 个人中心用的 跟上面的不同是用的Post | |
| 41 | + * @param tokenId 连接Id | |
| 42 | + * @returns void | |
| 43 | + */ | |
| 44 | +export function forceLogout2(tokenId: string) { | |
| 45 | + return requestClient.deleteWithMsg<void>(`${Api.root}/myself/${tokenId}`); | |
| 46 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts
0 → 100644
| 1 | +import type { OperationLog } from './model'; | |
| 2 | + | |
| 3 | +import type { IDS, PageQuery, PageResult } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { commonExport } from '#/api/helper'; | |
| 6 | +import { requestClient } from '#/api/request'; | |
| 7 | + | |
| 8 | +enum Api { | |
| 9 | + operLogClean = '/operation-log/clean', | |
| 10 | + operLogExport = '/operation-log/export', | |
| 11 | + root = '/operation-log', | |
| 12 | +} | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 操作日志分页 | |
| 16 | + * @param params 查询参数 | |
| 17 | + * @returns 分页结果 | |
| 18 | + */ | |
| 19 | +export function operLogList(params?: PageQuery) { | |
| 20 | + return requestClient.get<PageResult<OperationLog>>(Api.root, { | |
| 21 | + params, | |
| 22 | + }); | |
| 23 | +} | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * 删除操作日志 | |
| 27 | + * @param operIds id/ids | |
| 28 | + */ | |
| 29 | +export function operLogRemove(operIds: IDS) { | |
| 30 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 31 | + params: { ids: operIds.join(',') }, | |
| 32 | + }); | |
| 33 | +} | |
| 34 | + | |
| 35 | +/** | |
| 36 | + * 清空全部分页日志 | |
| 37 | + */ | |
| 38 | +export function operLogClean() { | |
| 39 | + return requestClient.deleteWithMsg<void>(Api.operLogClean); | |
| 40 | +} | |
| 41 | + | |
| 42 | +/** | |
| 43 | + * 导出操作日志 | |
| 44 | + * @param data 查询参数 | |
| 45 | + */ | |
| 46 | +export function operLogExport(data: Partial<OperationLog>) { | |
| 47 | + return commonExport(Api.operLogExport, data); | |
| 48 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts
0 → 100644
| 1 | +export interface OperationLog { | |
| 2 | + id: string; | |
| 3 | + title: string; | |
| 4 | + operType: string; | |
| 5 | + requestMethod: string; | |
| 6 | + operUser: string; | |
| 7 | + operIp: string; | |
| 8 | + operLocation: string; | |
| 9 | + method: string; | |
| 10 | + requestParam: string; | |
| 11 | + requestResult: string; | |
| 12 | + creationTime: string; | |
| 13 | + creatorId: string | null; | |
| 14 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts
0 → 100644
| 1 | +/** | |
| 2 | + * 该文件可自行根据业务逻辑进行调整 | |
| 3 | + */ | |
| 4 | + | |
| 5 | +import type { HttpResponse } from '@vben/request'; | |
| 6 | + | |
| 7 | +import { useAppConfig } from '@vben/hooks'; | |
| 8 | +import { $t } from '@vben/locales'; | |
| 9 | +import { preferences } from '@vben/preferences'; | |
| 10 | +import { | |
| 11 | + authenticateResponseInterceptor, | |
| 12 | + errorMessageResponseInterceptor, | |
| 13 | + RequestClient, | |
| 14 | + stringify, | |
| 15 | +} from '@vben/request'; | |
| 16 | +import { useAccessStore } from '@vben/stores'; | |
| 17 | + | |
| 18 | +import { message, Modal } from 'ant-design-vue'; | |
| 19 | + | |
| 20 | +import { useAuthStore } from '#/store'; | |
| 21 | +import { useThTenantStore } from '#/store/th-tenant'; | |
| 22 | +import { | |
| 23 | + decryptBase64, | |
| 24 | + decryptWithAes, | |
| 25 | + encryptBase64, | |
| 26 | + encryptWithAes, | |
| 27 | + generateAesKey, | |
| 28 | +} from '#/utils/encryption/crypto'; | |
| 29 | +import * as encryptUtil from '#/utils/encryption/jsencrypt'; | |
| 30 | + | |
| 31 | +const { apiURL, clientId, enableEncrypt, demoMode } = useAppConfig( | |
| 32 | + import.meta.env, | |
| 33 | + import.meta.env.PROD, | |
| 34 | +); | |
| 35 | + | |
| 36 | +/** | |
| 37 | + * 是否已经处在登出过程中了 一个标志位 | |
| 38 | + * 主要是防止一个页面会请求多个api 都401 会导致登出执行多次 | |
| 39 | + */ | |
| 40 | +let isLogoutProcessing = false; | |
| 41 | + | |
| 42 | +/** | |
| 43 | + * 定义一个401专用异常 用于可能会用到的区分场景? | |
| 44 | + */ | |
| 45 | +export class UnauthorizedException extends Error {} | |
| 46 | + | |
| 47 | +/** | |
| 48 | + * 演示模式错误,用于标识演示环境禁止修改的错误 | |
| 49 | + */ | |
| 50 | +export class DemoModeException extends Error { | |
| 51 | + constructor(message: string) { | |
| 52 | + super(message); | |
| 53 | + this.name = 'DemoModeException'; | |
| 54 | + // 添加标记,用于错误拦截器识别 | |
| 55 | + (this as any).__isDemoModeError = true; | |
| 56 | + } | |
| 57 | +} | |
| 58 | + | |
| 59 | +function createRequestClient(baseURL: string) { | |
| 60 | + const client = new RequestClient({ | |
| 61 | + // 后端地址 | |
| 62 | + baseURL, | |
| 63 | + // 消息提示类型 | |
| 64 | + errorMessageMode: 'message', | |
| 65 | + // 是否返回原生响应 比如:需要获取响应头时使用该属性 | |
| 66 | + isReturnNativeResponse: false, | |
| 67 | + // 需要对返回数据进行处理 | |
| 68 | + isTransformResponse: true, | |
| 69 | + }); | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * 重新认证逻辑 | |
| 73 | + */ | |
| 74 | + async function doReAuthenticate() { | |
| 75 | + console.warn('Access token or refresh token is invalid or expired. '); | |
| 76 | + const accessStore = useAccessStore(); | |
| 77 | + const authStore = useAuthStore(); | |
| 78 | + accessStore.setAccessToken(null); | |
| 79 | + if ( | |
| 80 | + preferences.app.loginExpiredMode === 'modal' && | |
| 81 | + accessStore.isAccessChecked | |
| 82 | + ) { | |
| 83 | + accessStore.setLoginExpired(true); | |
| 84 | + } else { | |
| 85 | + await authStore.logout(); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + /** | |
| 90 | + * 刷新token逻辑 | |
| 91 | + */ | |
| 92 | + async function doRefreshToken() { | |
| 93 | + // 不需要 | |
| 94 | + // 保留此方法只是为了合并方便 | |
| 95 | + return ''; | |
| 96 | + } | |
| 97 | + | |
| 98 | + function formatToken(token: null | string) { | |
| 99 | + return token ? `Bearer ${token}` : null; | |
| 100 | + } | |
| 101 | + | |
| 102 | + client.addRequestInterceptor({ | |
| 103 | + fulfilled: (config) => { | |
| 104 | + // 演示模式:拦截所有修改操作 | |
| 105 | + if (demoMode) { | |
| 106 | + const method = config.method?.toUpperCase() || ''; | |
| 107 | + const isModifyMethod = ['DELETE', 'PATCH', 'POST', 'PUT'].includes( | |
| 108 | + method, | |
| 109 | + ); | |
| 110 | + // 排除登录等认证接口,允许通过 | |
| 111 | + const isAuthPath = | |
| 112 | + config.url?.includes('/auth/') || | |
| 113 | + config.url?.includes('/login') || | |
| 114 | + config.url?.includes('/logout'); | |
| 115 | + if (isModifyMethod && !isAuthPath) { | |
| 116 | + // 显示错误提示 | |
| 117 | + message.error('演示环境,禁止修改'); | |
| 118 | + // 抛出演示模式错误,错误拦截器会识别并跳过处理 | |
| 119 | + throw new DemoModeException('演示环境,禁止修改'); | |
| 120 | + } | |
| 121 | + } | |
| 122 | + | |
| 123 | + const accessStore = useAccessStore(); | |
| 124 | + // 添加token | |
| 125 | + config.headers.Authorization = formatToken(accessStore.accessToken); | |
| 126 | + /** 泰额多租户:业务 API 携带 __tenant(JWT 亦含 TenantId,双保险) */ | |
| 127 | + const thTenantStore = useThTenantStore(); | |
| 128 | + if (thTenantStore.tenantId) { | |
| 129 | + config.headers.__tenant = thTenantStore.tenantId; | |
| 130 | + } | |
| 131 | + /** | |
| 132 | + * locale跟后台不一致 需要转换 | |
| 133 | + */ | |
| 134 | + const language = preferences.app.locale.replace('-', '_'); | |
| 135 | + config.headers['Accept-Language'] = language; | |
| 136 | + config.headers['Content-Language'] = language; | |
| 137 | + /** | |
| 138 | + * 添加全局clientId | |
| 139 | + * 关于header的clientId被错误绑定到实体类 | |
| 140 | + * https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS | |
| 141 | + */ | |
| 142 | + config.headers.ClientID = clientId; | |
| 143 | + /** | |
| 144 | + * 格式化get/delete参数 | |
| 145 | + * 如果包含自定义的paramsSerializer则不走此逻辑 | |
| 146 | + */ | |
| 147 | + if ( | |
| 148 | + ['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') && | |
| 149 | + config.params && | |
| 150 | + !config.paramsSerializer | |
| 151 | + ) { | |
| 152 | + /** | |
| 153 | + * 1. 格式化参数 微服务在传递区间时间选择(后端的params Map类型参数)需要格式化key 否则接收不到 | |
| 154 | + * 2. 数组参数需要格式化 后端才能正常接收 会变成arr=1&arr=2&arr=3的格式来接收 | |
| 155 | + */ | |
| 156 | + config.paramsSerializer = (params) => | |
| 157 | + stringify(params, { arrayFormat: 'repeat' }); | |
| 158 | + } | |
| 159 | + | |
| 160 | + const { encrypt } = config; | |
| 161 | + // 全局开启请求加密功能 && 该请求开启 && 是post/put请求 | |
| 162 | + if ( | |
| 163 | + enableEncrypt && | |
| 164 | + encrypt && | |
| 165 | + ['POST', 'PUT'].includes(config.method?.toUpperCase() || '') | |
| 166 | + ) { | |
| 167 | + const aesKey = generateAesKey(); | |
| 168 | + config.headers['encrypt-key'] = encryptUtil.encrypt( | |
| 169 | + encryptBase64(aesKey), | |
| 170 | + ); | |
| 171 | + | |
| 172 | + config.data = | |
| 173 | + typeof config.data === 'object' | |
| 174 | + ? encryptWithAes(JSON.stringify(config.data), aesKey) | |
| 175 | + : encryptWithAes(config.data, aesKey); | |
| 176 | + } | |
| 177 | + return config; | |
| 178 | + }, | |
| 179 | + }); | |
| 180 | + | |
| 181 | + // 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里 | |
| 182 | + // 主要处理http状态码不为200(如网络异常/离线)的情况 必须放在在下面的响应拦截器之前 | |
| 183 | + const errorInterceptor = errorMessageResponseInterceptor( | |
| 184 | + (msg: string, error: any) => { | |
| 185 | + // 如果是演示模式错误,已经在请求拦截器中提示过了,这里不再提示 | |
| 186 | + if (error?.__isDemoModeError || error?.name === 'DemoModeException') { | |
| 187 | + return; | |
| 188 | + } | |
| 189 | + message.error(msg); | |
| 190 | + }, | |
| 191 | + ); | |
| 192 | + client.addResponseInterceptor(errorInterceptor); | |
| 193 | + | |
| 194 | + client.addResponseInterceptor<HttpResponse>({ | |
| 195 | + fulfilled: async (response) => { | |
| 196 | + const encryptKey = (response.headers ?? {})['encrypt-key']; | |
| 197 | + if (encryptKey) { | |
| 198 | + /** RSA私钥解密 拿到解密秘钥的base64 */ | |
| 199 | + const base64Str = encryptUtil.decrypt(encryptKey); | |
| 200 | + /** base64 解码 得到请求头的 AES 秘钥 */ | |
| 201 | + const aesSecret = decryptBase64(base64Str.toString()); | |
| 202 | + /** 使用aesKey解密 responseData */ | |
| 203 | + const decryptData = decryptWithAes( | |
| 204 | + response.data as unknown as string, | |
| 205 | + aesSecret, | |
| 206 | + ); | |
| 207 | + /** 赋值 需要转为对象 */ | |
| 208 | + response.data = JSON.parse(decryptData); | |
| 209 | + } | |
| 210 | + | |
| 211 | + const { isReturnNativeResponse, isTransformResponse } = response.config; | |
| 212 | + // 是否返回原生响应 比如:需要获取响应时使用该属性 | |
| 213 | + if (isReturnNativeResponse) { | |
| 214 | + return response; | |
| 215 | + } | |
| 216 | + // 不进行任何处理,直接返回 | |
| 217 | + // 用于页面代码可能需要直接获取code,data,message这些信息时开启 | |
| 218 | + if (!isTransformResponse) { | |
| 219 | + /** | |
| 220 | + * 需要判断下载二进制的情况 正常是返回二进制 报错会返回json | |
| 221 | + * 当type为blob且content-type为application/json时 则判断已经下载出错 | |
| 222 | + */ | |
| 223 | + if ( | |
| 224 | + response.config.responseType === 'blob' && | |
| 225 | + response.headers['content-type']?.includes?.('application/json') | |
| 226 | + ) { | |
| 227 | + // 这时候的data为blob类型 | |
| 228 | + const blob = response.data as unknown as Blob; | |
| 229 | + // 拿到字符串转json对象 | |
| 230 | + response.data = JSON.parse(await blob.text()); | |
| 231 | + // 然后按正常逻辑执行下面的代码(判断业务状态码) | |
| 232 | + } else { | |
| 233 | + // 其他情况 直接返回 | |
| 234 | + return response.data; | |
| 235 | + } | |
| 236 | + } | |
| 237 | + | |
| 238 | + const axiosResponseData = response.data; | |
| 239 | + if (!axiosResponseData) { | |
| 240 | + throw new Error($t('http.apiRequestFailed')); | |
| 241 | + } | |
| 242 | + | |
| 243 | + console.log('axiosResponseData', axiosResponseData); | |
| 244 | + // 适配新的后端数据结构: { statusCode, data, succeeded, errors, extras, timestamp } | |
| 245 | + const { statusCode, data, succeeded, errors, extras, timestamp } = | |
| 246 | + axiosResponseData; | |
| 247 | + | |
| 248 | + // 业务状态码为200且succeeded为true则请求成功 | |
| 249 | + const hasSuccess = statusCode === 200 && succeeded === true; | |
| 250 | + if (hasSuccess) { | |
| 251 | + const successMsg = $t(`http.operationSuccess`); | |
| 252 | + | |
| 253 | + if (response.config.successMessageMode === 'modal') { | |
| 254 | + Modal.success({ | |
| 255 | + content: successMsg, | |
| 256 | + title: $t('http.successTip'), | |
| 257 | + }); | |
| 258 | + } else if (response.config.successMessageMode === 'message') { | |
| 259 | + message.success(successMsg); | |
| 260 | + } | |
| 261 | + | |
| 262 | + // 直接返回data字段 | |
| 263 | + return data; | |
| 264 | + } | |
| 265 | + | |
| 266 | + // 在此处根据自己项目的实际情况对不同的statusCode执行不同的操作 | |
| 267 | + // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 | |
| 268 | + let timeoutMsg = ''; | |
| 269 | + switch (statusCode) { | |
| 270 | + case 401: { | |
| 271 | + // 已经在登出过程中 不再执行 | |
| 272 | + if (isLogoutProcessing) { | |
| 273 | + throw new UnauthorizedException(timeoutMsg); | |
| 274 | + } | |
| 275 | + isLogoutProcessing = true; | |
| 276 | + const _msg = $t('http.loginTimeout'); | |
| 277 | + const userStore = useAuthStore(); | |
| 278 | + userStore.logout().finally(() => { | |
| 279 | + message.error(_msg); | |
| 280 | + isLogoutProcessing = false; | |
| 281 | + }); | |
| 282 | + // 不再执行下面逻辑 | |
| 283 | + throw new UnauthorizedException(_msg); | |
| 284 | + } | |
| 285 | + default: { | |
| 286 | + // 优先使用errors字段作为错误信息 | |
| 287 | + if (errors && Array.isArray(errors) && errors.length > 0) { | |
| 288 | + timeoutMsg = errors.join(', '); | |
| 289 | + } else if (typeof errors === 'string') { | |
| 290 | + timeoutMsg = errors; | |
| 291 | + } else { | |
| 292 | + timeoutMsg = $t('http.apiRequestFailed'); | |
| 293 | + } | |
| 294 | + } | |
| 295 | + } | |
| 296 | + | |
| 297 | + // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 | |
| 298 | + // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 | |
| 299 | + if (response.config.errorMessageMode === 'modal') { | |
| 300 | + Modal.error({ | |
| 301 | + content: timeoutMsg, | |
| 302 | + title: $t('http.errorTip'), | |
| 303 | + }); | |
| 304 | + } else if (response.config.errorMessageMode === 'message') { | |
| 305 | + message.error(timeoutMsg); | |
| 306 | + } | |
| 307 | + | |
| 308 | + throw new Error(timeoutMsg || $t('http.apiRequestFailed')); | |
| 309 | + }, | |
| 310 | + }); | |
| 311 | + | |
| 312 | + // token过期的处理 | |
| 313 | + client.addResponseInterceptor( | |
| 314 | + authenticateResponseInterceptor({ | |
| 315 | + client, | |
| 316 | + doReAuthenticate, | |
| 317 | + doRefreshToken, | |
| 318 | + enableRefreshToken: preferences.app.enableRefreshToken, | |
| 319 | + formatToken, | |
| 320 | + }), | |
| 321 | + ); | |
| 322 | + | |
| 323 | + return client; | |
| 324 | +} | |
| 325 | + | |
| 326 | +export const requestClient = createRequestClient(apiURL); | |
| 327 | + | |
| 328 | +export const baseRequestClient = new RequestClient({ baseURL: apiURL }); | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts
0 → 100644
| 1 | +export interface CpuInfo { | |
| 2 | + coreTotal: number; | |
| 3 | + logicalProcessors: number; | |
| 4 | + cpuRate: number; | |
| 5 | +} | |
| 6 | + | |
| 7 | +export interface MemoryInfo { | |
| 8 | + totalRAM: string; | |
| 9 | + usedRam: string; | |
| 10 | + freeRam: string; | |
| 11 | + ramRate: number; | |
| 12 | +} | |
| 13 | + | |
| 14 | +export interface SystemInfo { | |
| 15 | + computerName: string; | |
| 16 | + osName: string; | |
| 17 | + serverIP: string; | |
| 18 | + osArch: string; | |
| 19 | +} | |
| 20 | + | |
| 21 | +export interface AppInfo { | |
| 22 | + name: string; | |
| 23 | + version: string; | |
| 24 | + startTime: string; | |
| 25 | + runTime: string; | |
| 26 | + rootPath: string; | |
| 27 | + webRootPath: string; | |
| 28 | +} | |
| 29 | + | |
| 30 | +export interface DiskInfo { | |
| 31 | + diskName: string; | |
| 32 | + typeName: string; | |
| 33 | + totalSize: string; | |
| 34 | + availableFreeSpace: string; | |
| 35 | + used: string; | |
| 36 | + availablePercent: number; | |
| 37 | +} | |
| 38 | + | |
| 39 | +export interface ServerInfo { | |
| 40 | + cpu: CpuInfo; | |
| 41 | + memory: MemoryInfo; | |
| 42 | + sys: SystemInfo; | |
| 43 | + app: AppInfo; | |
| 44 | + disk: DiskInfo[]; | |
| 45 | +} | |
| 46 | + | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts
0 → 100644
| 1 | +import type { Client } from './model'; | |
| 2 | + | |
| 3 | +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { commonExport } from '#/api/helper'; | |
| 6 | +import { requestClient } from '#/api/request'; | |
| 7 | + | |
| 8 | +enum Api { | |
| 9 | + clientChangeStatus = '/client/changeStatus', | |
| 10 | + clientExport = '/client/export', | |
| 11 | + clientList = '/client/list', | |
| 12 | + root = '/client', | |
| 13 | +} | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 查询客户端分页列表 | |
| 17 | + * @param params 请求参数 | |
| 18 | + * @returns 列表 | |
| 19 | + */ | |
| 20 | +export function clientList(params?: PageQuery) { | |
| 21 | + return requestClient.get<PageResult<Client>>(Api.clientList, { params }); | |
| 22 | +} | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * 导出客户端excel | |
| 26 | + * @param data 请求参数 | |
| 27 | + */ | |
| 28 | +export function clientExport(data: Partial<Client>) { | |
| 29 | + return commonExport(Api.clientExport, data); | |
| 30 | +} | |
| 31 | + | |
| 32 | +/** | |
| 33 | + * 客户端详情 | |
| 34 | + * @param id id | |
| 35 | + * @returns 详情 | |
| 36 | + */ | |
| 37 | +export function clientInfo(id: ID) { | |
| 38 | + return requestClient.get<Client>(`${Api.root}/${id}`); | |
| 39 | +} | |
| 40 | + | |
| 41 | +/** | |
| 42 | + * 客户端新增 | |
| 43 | + * @param data 参数 | |
| 44 | + */ | |
| 45 | +export function clientAdd(data: Partial<Client>) { | |
| 46 | + return requestClient.postWithMsg<void>(Api.root, data); | |
| 47 | +} | |
| 48 | + | |
| 49 | +/** | |
| 50 | + * 客户端修改 | |
| 51 | + * @param data 参数 | |
| 52 | + */ | |
| 53 | +export function clientUpdate(data: Partial<Client>) { | |
| 54 | + return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data); | |
| 55 | +} | |
| 56 | + | |
| 57 | +/** | |
| 58 | + * 客户端状态修改 | |
| 59 | + * @param data 状态 | |
| 60 | + */ | |
| 61 | +export function clientChangeStatus(data: any) { | |
| 62 | + const requestData = { | |
| 63 | + clientId: data.clientId, | |
| 64 | + status: data.status, | |
| 65 | + }; | |
| 66 | + return requestClient.putWithMsg<void>(Api.clientChangeStatus, requestData); | |
| 67 | +} | |
| 68 | + | |
| 69 | +/** | |
| 70 | + * 客户端删除 | |
| 71 | + * @param ids id集合 | |
| 72 | + */ | |
| 73 | +export function clientRemove(ids: IDS) { | |
| 74 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 75 | + params: { ids: ids.join(',') }, | |
| 76 | + }); | |
| 77 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts
0 → 100644
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts
0 → 100644
| 1 | +import type { SysConfig } from './model'; | |
| 2 | + | |
| 3 | +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { commonExport } from '#/api/helper'; | |
| 6 | +import { requestClient } from '#/api/request'; | |
| 7 | + | |
| 8 | +enum Api { | |
| 9 | + configExport = '/config/export', | |
| 10 | + configInfoByKey = '/config/config-key', | |
| 11 | + configList = '/config/list', | |
| 12 | + configRefreshCache = '/config/refreshCache', | |
| 13 | + root = '/config', | |
| 14 | +} | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 系统参数分页列表 | |
| 18 | + * @param params 请求参数 | |
| 19 | + * @returns 列表 | |
| 20 | + */ | |
| 21 | +export function configList(params?: PageQuery) { | |
| 22 | + return requestClient.get<PageResult<SysConfig>>(Api.root, { params }); | |
| 23 | +} | |
| 24 | + | |
| 25 | +export function configInfo(configId: ID) { | |
| 26 | + return requestClient.get<SysConfig>(`${Api.root}/${configId}`); | |
| 27 | +} | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * 导出 | |
| 31 | + * @param data 参数 | |
| 32 | + */ | |
| 33 | +export function configExport(data: Partial<SysConfig>) { | |
| 34 | + return commonExport(Api.configExport, data); | |
| 35 | +} | |
| 36 | + | |
| 37 | +/** | |
| 38 | + * 刷新缓存 | |
| 39 | + * @returns void | |
| 40 | + */ | |
| 41 | +export function configRefreshCache() { | |
| 42 | + return requestClient.deleteWithMsg<void>(Api.configRefreshCache); | |
| 43 | +} | |
| 44 | + | |
| 45 | +/** | |
| 46 | + * 更新系统配置 | |
| 47 | + * @param data 参数 | |
| 48 | + */ | |
| 49 | +export function configUpdate(data: Partial<SysConfig>) { | |
| 50 | + return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data); | |
| 51 | +} | |
| 52 | + | |
| 53 | +/** | |
| 54 | + * 新增系统配置 | |
| 55 | + * @param data 参数 | |
| 56 | + */ | |
| 57 | +export function configAdd(data: Partial<SysConfig>) { | |
| 58 | + return requestClient.postWithMsg<void>(Api.root, data); | |
| 59 | +} | |
| 60 | + | |
| 61 | +/** | |
| 62 | + * 删除配置 | |
| 63 | + * @param configIds ids | |
| 64 | + */ | |
| 65 | +export function configRemove(configIds: IDS) { | |
| 66 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 67 | + params: { ids: configIds.join(',') }, | |
| 68 | + }); | |
| 69 | +} | |
| 70 | + | |
| 71 | +/** | |
| 72 | + * 获取配置信息 | |
| 73 | + * @param configKey configKey | |
| 74 | + * @returns value | |
| 75 | + */ | |
| 76 | +export function configInfoByKey(configKey: string) { | |
| 77 | + return requestClient.get<string>(`${Api.configInfoByKey}/${configKey}`); | |
| 78 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts
0 → 100644
| 1 | +export interface SysConfig { | |
| 2 | + id: string; | |
| 3 | + configName: string; | |
| 4 | + configKey: string; | |
| 5 | + configValue: string; | |
| 6 | + configType: string | null; | |
| 7 | + orderNum: number; | |
| 8 | + remark: string | null; | |
| 9 | + isDeleted: boolean; | |
| 10 | + creationTime: string; | |
| 11 | + creatorId: string | null; | |
| 12 | + lastModifierId: string | null; | |
| 13 | + lastModificationTime: string | null; | |
| 14 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts
0 → 100644
| 1 | +import type { Dept } from './model'; | |
| 2 | + | |
| 3 | +import type { ID } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { requestClient } from '#/api/request'; | |
| 6 | + | |
| 7 | +enum Api { | |
| 8 | + deptList = '/dept/list', | |
| 9 | + deptNodeInfo = '/dept/list/exclude', | |
| 10 | + root = '/dept', | |
| 11 | +} | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * 部门列表 | |
| 15 | + * @returns list | |
| 16 | + */ | |
| 17 | +export function deptList(params?: { deptName?: string; status?: string }) { | |
| 18 | + return requestClient.get<Dept[]>(Api.deptList, { params }); | |
| 19 | +} | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * 查询部门列表(排除节点) | |
| 23 | + * @param deptId 部门ID | |
| 24 | + * @returns void | |
| 25 | + */ | |
| 26 | +export function deptNodeList(deptId: ID) { | |
| 27 | + return requestClient.get<Dept[]>(`${Api.deptNodeInfo}/${deptId}`); | |
| 28 | +} | |
| 29 | + | |
| 30 | +/** | |
| 31 | + * 部门详情 | |
| 32 | + * @param deptId 部门id | |
| 33 | + * @returns 部门信息 | |
| 34 | + */ | |
| 35 | +export function deptInfo(deptId: ID) { | |
| 36 | + return requestClient.get<Dept>(`${Api.root}/${deptId}`); | |
| 37 | +} | |
| 38 | + | |
| 39 | +/** | |
| 40 | + * 部门新增 | |
| 41 | + * @param data 参数 | |
| 42 | + */ | |
| 43 | +export function deptAdd(data: Partial<Dept>) { | |
| 44 | + return requestClient.postWithMsg<void>(Api.root, data); | |
| 45 | +} | |
| 46 | + | |
| 47 | +/** | |
| 48 | + * 部门更新 | |
| 49 | + * @param data 参数 | |
| 50 | + */ | |
| 51 | +export function deptUpdate(data: Partial<Dept>) { | |
| 52 | + return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data); | |
| 53 | +} | |
| 54 | + | |
| 55 | +/** | |
| 56 | + * 注意这里只允许单删除 | |
| 57 | + * @param deptId ID | |
| 58 | + * @returns void | |
| 59 | + */ | |
| 60 | +export function deptRemove(deptId: ID) { | |
| 61 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 62 | + params: { ids: deptId }, | |
| 63 | + }); | |
| 64 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts
0 → 100644
| 1 | +export interface Dept { | |
| 2 | + creationTime: string; | |
| 3 | + creatorId?: string | null; | |
| 4 | + state: boolean; | |
| 5 | + deptName: string; | |
| 6 | + deptCode?: string; | |
| 7 | + leader?: string; | |
| 8 | + leaderName?: string; | |
| 9 | + parentId: string | null; | |
| 10 | + remark?: string; | |
| 11 | + orderNum: number; | |
| 12 | + id: string; | |
| 13 | + children?: Dept[]; | |
| 14 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts
0 → 100644
| 1 | +export interface DictData { | |
| 2 | + id: string; | |
| 3 | + isDeleted: boolean; | |
| 4 | + orderNum: number; | |
| 5 | + state: boolean; | |
| 6 | + remark: string | null; | |
| 7 | + listClass: string | null; | |
| 8 | + cssClass: string | null; | |
| 9 | + dictType: string; | |
| 10 | + dictLabel: string | null; | |
| 11 | + dictValue: string; | |
| 12 | + isDefault: boolean; | |
| 13 | + creationTime: string; | |
| 14 | + creatorId: string | null; | |
| 15 | + lastModifierId: string | null; | |
| 16 | + lastModificationTime: string | null; | |
| 17 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts
0 → 100644
| 1 | +import type { DictData } from './dict-data-model'; | |
| 2 | + | |
| 3 | +import type { ID, IDS, PageQuery } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { commonExport } from '#/api/helper'; | |
| 6 | +import { requestClient } from '#/api/request'; | |
| 7 | + | |
| 8 | +enum Api { | |
| 9 | + dictDataExport = '/dict/data/export', | |
| 10 | + dictDataInfo = '/dictionary/dic-type', | |
| 11 | + dictDataList = '/dict/data/list', | |
| 12 | + root = '/dictionary', | |
| 13 | +} | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 主要是DictTag组件使用 | |
| 17 | + * @param dictType 字典类型 | |
| 18 | + * @returns 字典数据 | |
| 19 | + */ | |
| 20 | +export function dictDataInfo(dictType: string) { | |
| 21 | + return requestClient.get<DictData[]>(`${Api.dictDataInfo}/${dictType}`); | |
| 22 | +} | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * 字典数据 | |
| 26 | + * @param params 查询参数 | |
| 27 | + * @returns 字典数据列表 | |
| 28 | + */ | |
| 29 | +export function dictDataList(params?: PageQuery) { | |
| 30 | + return requestClient.get<DictData[]>(Api.root, { params }); | |
| 31 | +} | |
| 32 | + | |
| 33 | +/** | |
| 34 | + * 导出字典数据 | |
| 35 | + * @param data 表单参数 | |
| 36 | + * @returns blob | |
| 37 | + */ | |
| 38 | +export function dictDataExport(data: Partial<DictData>) { | |
| 39 | + return commonExport(Api.dictDataExport, data); | |
| 40 | +} | |
| 41 | + | |
| 42 | +/** | |
| 43 | + * 删除 | |
| 44 | + * @param dictIds 字典ID Array | |
| 45 | + * @returns void | |
| 46 | + */ | |
| 47 | +export function dictDataRemove(dictIds: IDS) { | |
| 48 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 49 | + params: { ids: dictIds.join(',') }, | |
| 50 | + }); | |
| 51 | +} | |
| 52 | + | |
| 53 | +/** | |
| 54 | + * 新增 | |
| 55 | + * @param data 表单参数 | |
| 56 | + * @returns void | |
| 57 | + */ | |
| 58 | +export function dictDataAdd(data: Partial<DictData>) { | |
| 59 | + return requestClient.postWithMsg<void>(Api.root, data); | |
| 60 | +} | |
| 61 | + | |
| 62 | +/** | |
| 63 | + * 修改 | |
| 64 | + * @param data 表单参数 | |
| 65 | + * @returns void | |
| 66 | + */ | |
| 67 | +export function dictDataUpdate(data: Partial<DictData>) { | |
| 68 | + return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data); | |
| 69 | +} | |
| 70 | + | |
| 71 | +/** | |
| 72 | + * 查询字典数据详细 | |
| 73 | + * @param id 字典ID | |
| 74 | + * @returns 字典数据 | |
| 75 | + */ | |
| 76 | +export function dictDetailInfo(id: ID) { | |
| 77 | + return requestClient.get<DictData>(`${Api.root}/${id}`); | |
| 78 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts
0 → 100644
| 1 | +export interface DictType { | |
| 2 | + id: string; | |
| 3 | + isDeleted: boolean; | |
| 4 | + orderNum: number; | |
| 5 | + state: boolean | null; | |
| 6 | + dictName: string; | |
| 7 | + dictType: string; | |
| 8 | + remark: string | null; | |
| 9 | + creationTime: string; | |
| 10 | + creatorId: string | null; | |
| 11 | + lastModifierId: string | null; | |
| 12 | + lastModificationTime: string | null; | |
| 13 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts
0 → 100644
| 1 | +import type { DictType } from './dict-type-model'; | |
| 2 | + | |
| 3 | +import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { commonExport } from '#/api/helper'; | |
| 6 | +import { requestClient } from '#/api/request'; | |
| 7 | + | |
| 8 | +enum Api { | |
| 9 | + dictOptionSelectList = '/dictionary-type/select-data-list', | |
| 10 | + dictTypeExport = '/dictionary-type/export', | |
| 11 | + dictTypeList = '/dictionary-type/list', | |
| 12 | + dictTypeRefreshCache = '/dictionary-type/refreshCache', | |
| 13 | + root = '/dictionary-type', | |
| 14 | +} | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 获取字典类型列表 | |
| 18 | + * @param params 请求参数 | |
| 19 | + * @returns list | |
| 20 | + */ | |
| 21 | +export function dictTypeList(params?: PageQuery) { | |
| 22 | + return requestClient.get<PageResult<DictType>>(Api.root, { params }); | |
| 23 | +} | |
| 24 | + | |
| 25 | +/** | |
| 26 | + * 导出字典类型列表 | |
| 27 | + * @param data 表单参数 | |
| 28 | + * @returns blob | |
| 29 | + */ | |
| 30 | +export function dictTypeExport(data: Partial<DictType>) { | |
| 31 | + return commonExport(Api.dictTypeExport, data); | |
| 32 | +} | |
| 33 | + | |
| 34 | +/** | |
| 35 | + * 删除字典类型 | |
| 36 | + * @param dictIds 字典类型id数组 | |
| 37 | + * @returns void | |
| 38 | + */ | |
| 39 | +export function dictTypeRemove(dictIds: IDS) { | |
| 40 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 41 | + params: { ids: dictIds.join(',') }, | |
| 42 | + }); | |
| 43 | +} | |
| 44 | + | |
| 45 | +/** | |
| 46 | + * 刷新字典缓存 | |
| 47 | + * @returns void | |
| 48 | + */ | |
| 49 | +export function refreshDictTypeCache() { | |
| 50 | + return requestClient.deleteWithMsg<void>(Api.dictTypeRefreshCache); | |
| 51 | +} | |
| 52 | + | |
| 53 | +/** | |
| 54 | + * 新增 | |
| 55 | + * @param data 表单参数 | |
| 56 | + * @returns void | |
| 57 | + */ | |
| 58 | +export function dictTypeAdd(data: Partial<DictType>) { | |
| 59 | + return requestClient.postWithMsg<void>(Api.root, data); | |
| 60 | +} | |
| 61 | + | |
| 62 | +/** | |
| 63 | + * 修改 | |
| 64 | + * @param data 表单参数 | |
| 65 | + * @returns void | |
| 66 | + */ | |
| 67 | +export function dictTypeUpdate(data: Partial<DictType>) { | |
| 68 | + return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data); | |
| 69 | +} | |
| 70 | + | |
| 71 | +/** | |
| 72 | + * 查询详情 | |
| 73 | + * @param dictId 字典类型id | |
| 74 | + * @returns 信息 | |
| 75 | + */ | |
| 76 | +export function dictTypeInfo(dictId: ID) { | |
| 77 | + return requestClient.get<DictType>(`${Api.root}/${dictId}`); | |
| 78 | +} | |
| 79 | + | |
| 80 | +/** | |
| 81 | + * 这个在ele用到 v5用不上 | |
| 82 | + * 下拉框 返回值和list一样 | |
| 83 | + * @returns options | |
| 84 | + */ | |
| 85 | +export function dictOptionSelectList() { | |
| 86 | + return requestClient.get<DictType[]>(Api.dictOptionSelectList); | |
| 87 | +} | ... | ... |
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts
0 → 100644
| 1 | +import type { Menu, MenuOption, MenuQuery, MenuResp } from './model'; | |
| 2 | + | |
| 3 | +import type { ID, IDS } from '#/api/common'; | |
| 4 | + | |
| 5 | +import { requestClient } from '#/api/request'; | |
| 6 | + | |
| 7 | +enum Api { | |
| 8 | + menuList = '/menu/list', | |
| 9 | + menuTreeSelect = '/menu/tree', | |
| 10 | + root = '/menu', | |
| 11 | + tenantPackageMenuTreeselect = '/menu/tenantPackageMenuTreeselect', | |
| 12 | +} | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * 菜单列表 | |
| 16 | + * @param params 参数 | |
| 17 | + * @returns 列表 | |
| 18 | + */ | |
| 19 | +export function menuList(params?: MenuQuery) { | |
| 20 | + return requestClient.get<Menu[]>(Api.menuList, { params }); | |
| 21 | +} | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * 菜单详情 | |
| 25 | + * @param menuId 菜单id | |
| 26 | + * @returns 菜单详情 | |
| 27 | + */ | |
| 28 | +export function menuInfo(menuId: ID) { | |
| 29 | + return requestClient.get<Menu>(`${Api.root}/${menuId}`); | |
| 30 | +} | |
| 31 | + | |
| 32 | +/** | |
| 33 | + * 菜单新增 | |
| 34 | + * @param data 参数 | |
| 35 | + */ | |
| 36 | +export function menuAdd(data: Partial<Menu>) { | |
| 37 | + return requestClient.postWithMsg<void>(Api.root, data); | |
| 38 | +} | |
| 39 | + | |
| 40 | +/** | |
| 41 | + * 菜单更新 | |
| 42 | + * @param data 参数 | |
| 43 | + */ | |
| 44 | +export function menuUpdate(data: Partial<Menu>) { | |
| 45 | + return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data); | |
| 46 | +} | |
| 47 | + | |
| 48 | +/** | |
| 49 | + * 菜单删除 | |
| 50 | + * @param menuIds ids | |
| 51 | + */ | |
| 52 | +export function menuRemove(menuIds: IDS) { | |
| 53 | + return requestClient.deleteWithMsg<void>(Api.root, { | |
| 54 | + params: { ids: menuIds.join(',') }, | |
| 55 | + }); | |
| 56 | +} | |
| 57 | + | |
| 58 | +/** | |
| 59 | + * 下拉框使用 返回所有的菜单 | |
| 60 | + * @returns [] | |
| 61 | + */ | |
| 62 | +export function menuTreeSelect() { | |
| 63 | + return requestClient.get<MenuOption[]>(Api.menuTreeSelect); | |
| 64 | +} | |
| 65 | + | |
| 66 | +/** | |
| 67 | + * 租户套餐使用 | |
| 68 | + * @param packageId packageId | |
| 69 | + * @returns resp | |
| 70 | + */ | |
| 71 | +export function tenantPackageMenuTreeSelect(packageId: ID) { | |
| 72 | + return requestClient.get<MenuResp>( | |
| 73 | + `${Api.tenantPackageMenuTreeselect}/${packageId}`, | |
| 74 | + ); | |
| 75 | +} | |
| 76 | + | |
| 77 | +/** | |
| 78 | + * 批量删除菜单 | |
| 79 | + * @param menuIds 菜单ids | |
| 80 | + * @returns void | |
| 81 | + */ | |
| 82 | +export function menuCascadeRemove(menuIds: IDS) { | |
| 83 | + return requestClient.deleteWithMsg<void>(`${Api.root}/cascade/${menuIds}`); | |
| 84 | +} | ... | ... |