Commit 83ccb207dcb18793c50334124683c2a2d1589a68

Authored by 杨鑫
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
  1 +> 1%
  2 +last 2 versions
  3 +not dead
  4 +not ie 11
泰额版/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
  1 +node_modules
  2 +.git
  3 +.gitignore
  4 +*.md
  5 +dist
  6 +.turbo
  7 +dist.zip
泰额版/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 \ No newline at end of file 12 \ No newline at end of file
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/.gitconfig 0 → 100644
  1 +[core]
  2 + ignorecase = false
泰额版/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
  1 +ports:
  2 + - port: 5555
  3 + onOpen: open-preview
  4 +tasks:
  5 + - init: npm i -g corepack && pnpm install
  6 + command: pnpm run dev:play
泰额版/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
  1 +dist
  2 +dev-dist
  3 +.local
  4 +.output.js
  5 +node_modules
  6 +.nvmrc
  7 +coverage
  8 +CODEOWNERS
  9 +.nitro
  10 +.output
  11 +
  12 +
  13 +**/*.svg
  14 +**/*.sh
  15 +
  16 +public
  17 +.npmrc
  18 +*-lock.yaml
泰额版/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
  1 +dist
  2 +public
  3 +__tests__
  4 +coverage
泰额版/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](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](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
  1 +PORT=5320
  2 +ACCESS_TOKEN_SECRET=access_token_secret
  3 +REFRESH_TOKEN_SECRET=refresh_token_secret
泰额版/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
  1 +export default eventHandler((event) => {
  2 + const { status } = getQuery(event);
  3 + setResponseStatus(event, Number(status));
  4 + return useResponseError(`${status}`);
  5 +});
泰额版/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
  1 +import type { NitroErrorHandler } from 'nitropack';
  2 +
  3 +const errorHandler: NitroErrorHandler = function (error, event) {
  4 + event.node.res.end(`[Error Handler] ${error.stack}`);
  5 +};
  6 +
  7 +export default errorHandler;
泰额版/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
  1 +{
  2 + "extends": "./tsconfig.json",
  3 + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
  4 +}
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json 0 → 100644
  1 +{
  2 + "extends": "./.nitro/types/tsconfig.json"
  3 +}
泰额版/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
  1 +# 应用标题
  2 +VITE_APP_TITLE=Yi Admin
  3 +
  4 +# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
  5 +VITE_APP_NAMESPACE=vben-web-antd
  6 +
  7 +# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
  8 +VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/.env.analyze 0 → 100644
  1 +# public path
  2 +VITE_BASE=/
  3 +
  4 +# Basic interface address SPA
  5 +VITE_GLOB_API_URL=/api/app
  6 +
  7 +VITE_VISUALIZER=true
泰额版/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
  1 +export * from './auth';
  2 +export * from './menu';
  3 +export * from './upload';
  4 +export * from './user';
泰额版/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
1 export * from './account-types'; 1 export * from './account-types';
  2 +export * from './dashboard';
  3 +export * from './dashboard-types';
2 export { 4 export {
3 groupAdd, 5 groupAdd,
4 groupInfo, 6 groupInfo,
泰额版/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,6 +65,10 @@ export interface ProductCategoryDto {
65 buttonStyleJson?: string | null; 65 buttonStyleJson?: string | null;
66 availabilityType?: 'ALL' | 'SPECIFIED' | string | null; 66 availabilityType?: 'ALL' | 'SPECIFIED' | string | null;
67 locationIds?: string[] | null; 67 locationIds?: string[] | null;
  68 + region?: string | null;
  69 + location?: string | null;
  70 + regionIds?: string[] | null;
  71 + groupIds?: string[] | null;
68 state?: boolean | null; 72 state?: boolean | null;
69 orderNum?: number | null; 73 orderNum?: number | null;
70 lastEdited?: string | null; 74 lastEdited?: string | null;
@@ -88,8 +92,30 @@ export interface ProductCategoryCreateInput { @@ -88,8 +92,30 @@ export interface ProductCategoryCreateInput {
88 buttonStyleJson?: string | null; 92 buttonStyleJson?: string | null;
89 availabilityType?: 'ALL' | 'SPECIFIED' | string | null; 93 availabilityType?: 'ALL' | 'SPECIFIED' | string | null;
90 locationIds?: string[] | null; 94 locationIds?: string[] | null;
  95 + regionIds?: string[] | null;
  96 + groupIds?: string[] | null;
91 state?: boolean; 97 state?: boolean;
92 orderNum?: number | null; 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 export type ProductCategoryUpdateInput = ProductCategoryCreateInput; 121 export type ProductCategoryUpdateInput = ProductCategoryCreateInput;
泰额版/Food Labeling Management Code/Yi.Vben5.Vue3/apps/web-antd/src/api/food-labeling/product.ts
1 import type { 1 import type {
2 FlPagedResult, 2 FlPagedResult,
  3 + ProductBatchImportResultDto,
3 ProductCreateInput, 4 ProductCreateInput,
4 ProductDto, 5 ProductDto,
  6 + ProductExportQuery,
5 ProductGetListQuery, 7 ProductGetListQuery,
6 ProductUpdateInput, 8 ProductUpdateInput,
7 } from './product-types'; 9 } from './product-types';
8 10
  11 +import { ContentTypeEnum } from '#/api/helper';
9 import { requestClient } from '#/api/request'; 12 import { requestClient } from '#/api/request';
10 13
11 const PATH = '/product'; 14 const PATH = '/product';
@@ -37,3 +40,37 @@ export function productUpdate(id: string, data: ProductUpdateInput) { @@ -37,3 +40,37 @@ export function productUpdate(id: string, data: ProductUpdateInput) {
37 export function productRemove(id: string) { 40 export function productRemove(id: string) {
38 return requestClient.deleteWithMsg<void>(`${PATH}/${encodeURIComponent(id)}`); 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,6 +60,20 @@ export interface LabelReportDataDto {
60 mostUsedProducts: LabelReportTopProductDto[]; 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 export interface LabelReportQuery { 77 export interface LabelReportQuery {
64 PartnerId?: string; 78 PartnerId?: string;
65 GroupId?: string; 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,6 +4,8 @@ import type {
4 LabelReportQuery, 4 LabelReportQuery,
5 ReportsPrintLogItemDto, 5 ReportsPrintLogItemDto,
6 ReportsPrintLogQuery, 6 ReportsPrintLogQuery,
  7 + ReportsTemplatePrintStatItemDto,
  8 + ReportsTemplatePrintStatQuery,
7 } from './reports-types'; 9 } from './reports-types';
8 10
9 import { requestClient } from '#/api/request'; 11 import { requestClient } from '#/api/request';
@@ -24,6 +26,15 @@ export function reportsLabelReport(params?: LabelReportQuery) { @@ -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 export function reportsExportPrintLogExcel(params?: ReportsPrintLogQuery) { 38 export function reportsExportPrintLogExcel(params?: ReportsPrintLogQuery) {
28 return requestClient.post<Blob>( 39 return requestClient.post<Blob>(
29 `${PATH}/export-print-log-excel`, 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
  1 +export interface LoginLog {
  2 + id: string;
  3 + loginUser: string;
  4 + loginLocation: string;
  5 + loginIp: string;
  6 + browser: string;
  7 + os: string;
  8 + logMsg: string;
  9 + creationTime: string;
  10 + creatorId: string | null;
  11 +}
泰额版/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
  1 +export interface OnlineUser {
  2 + connnectionId?: string;
  3 + userId?: string;
  4 + userName?: string;
  5 + loginTime: number;
  6 + ipaddr?: string;
  7 + loginLocation?: string;
  8 + os?: string;
  9 + browser?: string;
  10 +}
泰额版/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
  1 +import type { ServerInfo } from './model';
  2 +
  3 +import { requestClient } from '#/api/request';
  4 +
  5 +/**
  6 + * 获取服务器信息
  7 + * @returns 服务器信息
  8 + */
  9 +export function getServerInfo() {
  10 + return requestClient.get<ServerInfo>('/monitor-server/info');
  11 +}
  12 +
泰额版/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
  1 +export interface Client {
  2 + id: number;
  3 + clientId: string;
  4 + clientKey: string;
  5 + clientSecret: string;
  6 + grantTypeList: string[];
  7 + grantType: string;
  8 + deviceType: string;
  9 + activeTimeout: number;
  10 + timeout: number;
  11 + status: string;
  12 +}
泰额版/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 +}