feat(gateway): 新增智电改造网关台账功能(ICC消防物联设备同步) - 新建electric_gateway表(迁移20261024_icc_gateway.sql并同步db_seed:自增id、icc_imei/device_code双唯一、orgCode经customer.icc_code解析customer_id、equip_id可空、is_online/synced_at等);同步服务IccGatewaySyncService复用OAuth2真实鉴权分页拉取消防物联设备(POST /device/subsystem/page)并按imei幂等upsert写icc_sync_log;新增GET /api/electric-gateways列表/详情与POST /sync手动同步(RBAC device:list);注册icc_gateway_sync每10分钟定时任务(菜单62/role_menu/sys_job种子);前端网关台账页(搜索/筛选/同步按钮/在线状态tag/分页)与路由;server补装undici依赖
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
This commit is contained in:
1 parent
9d3bb13ecf
commit
cf0a0d4d31
21 files changed
+1439
-272
No files matched your search
@@ -0,0 +1,13 @@
|
|||||||
|
# Checklist
|
||||||
|
|
||||||
|
- [x] electric_gateway 表已建(迁移 20261024_icc_gateway.sql),字段集符合需求方确认:自增主键 id、icc_imei/device_code 双唯一、customer_id→customer、equip_id→customer_equipment 可空、org_code/org_name/manufacturer_code/manufacturer_name/type_code/type_name/product_model/is_online/synced_at/created_at;不含 companyId/companyName/dcpsId/softwareVersion/systemAddress/address/description;与 db_seed.sql 结构一致 —— 迁移已执行到远程生产库(信息模式校验 16 列),db_seed.sql 451-522 行同步
|
||||||
|
- [x] 菜单 62「智电改造网关台账」挂设备监控(4)(permission device:list)、ADMIN role_menu、sys_job 种子 icc_gateway_sync(cron `*/10 * * * *`)已写入迁移与 db_seed.sql 且幂等 —— 生产库已存在(menu62 查询命中、role_menu(1,62)、job 行 status=1)
|
||||||
|
- [x] ElectricGateway 实体与迁移结构一致(field 映射、索引)—— 实体含 @ForeignKey Customer/CustomerEquipment、@BelongsTo、3 索引
|
||||||
|
- [x] icc_gateway_mapper.ts 正确映射 DeviceInfoVO 字段(imei/deviceCode/deviceName/orgCode/orgName/manufacturerCode/manufacturerName/typeCode/typeName/productModel/isOnline)并解析分页响应(PageVO{nextPage,totalCount,pageData})—— 实测映射正确(typeName=组合式电气火灾监控探测器、manufacturerName=大华、productModel=DH-…)
|
||||||
|
- [x] IccGatewaySyncService 复用 getIccAccessToken/iccFetch 真实鉴权,POST 分页全量拉取(pageSize=50,循环至短页,最大页数保护),orgCode→customer.icc_code 解析 customer_id —— 实测 pulled=2,customerId 解析为 3(KH20261016001)
|
||||||
|
- [x] 同步按 icc_imei(缺省 device_code)幂等 upsert,跳过缺双键或匹配不到客户的行,更新 synced_at,写 icc_sync_log(sync_type=electric_gateway);失败仅记录不抛业务错误 —— 实测首次 inserted=2,重跑 updated=2 skipped=0;icc_sync_log 2 条 status=1 record_count=2
|
||||||
|
- [x] GET /api/electric-gateways 列表/详情、POST /api/electric-gateways/sync 已实现;RBAC `device:list` 已映射;非 ADMIN 按客户归属限定数据范围 —— 无 token 401、admin 列表 200
|
||||||
|
- [x] 定时任务 icc_gateway_sync 已注册(job_registry BUILTIN_JOBS + job_scheduler buildRuntime syncIccGateways + db_seed.sql 种子)—— 调度器启动日志"已加载 6 个任务"含 icc_gateway_sync cron=*/10 * * * *
|
||||||
|
- [x] server/.env 含 ICC_BASE_URL=https://222.243.55.92:1443、ICC_VERSION=1.0.0、ICC_CLIENT_ID/ICC_CLIENT_SECRET/ICC_USERNAME/ICC_PASSWORD —— 已确认齐全
|
||||||
|
- [x] 前端 views/device/gateway/index.vue(搜索/同步按钮/表格/在线状态 tag/分页)、路由 /device/gateway、api/device.ts electricGatewayApi 已实现 —— 浏览器实测渲染/同步/筛选/重置全部正常
|
||||||
|
- [x] 后端编译(tsc --noEmit)与前端类型检查(vue-tsc -b)通过;运行验证:401/200、sync 真实拉取成功不 500、菜单与页面渲染正常 —— 全部实测通过
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# 智电改造网关台账(ICC 消防物联设备同步)Spec
|
||||||
|
|
||||||
|
## Why
|
||||||
|
系统缺少"智电改造网关设备"台账。网关设备数据由 ICC 平台消防物联设备接口(`/evo-apigw/evo-fdbu/{version}/device/subsystem/page`)提供,需定时拉取并落库到新建的 `electric_gateway` 表,运营侧可按客户查看网关设备台账、手动触发同步。本表在设计文档中未定义,需依据接口文档新增表结构(已与需求方确认字段集)。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
- **新增** `electric_gateway` 表(数据库迁移 + db_seed.sql 同步):自增主键 `id`、`icc_imei`/`device_code` 双唯一键、`device_name`、`customer_id`(→ customer,由接口 orgCode→customer.icc_code 解析)、`equip_id`(→ customer_equipment,可空,同步不填充)、`org_code`/`org_name`、`manufacturer_code`/`manufacturer_name`、`type_code`/`type_name`、`product_model`、`is_online`、`synced_at`、`created_at`;**不含** companyId/companyName/dcpsId/softwareVersion/systemAddress/address/description(经需求方确认)
|
||||||
|
- **新增** ICC 网关设备同步:`IccGatewaySyncService` 复用真实 OAuth2 鉴权(`icc_customer_auth.ts` 的 `getIccAccessToken` + `iccFetch`,走 `.env` 已配置凭据),POST 分页拉取消防物联设备全量数据(pageNum/pageSize=50,循环至短页,全量不传 typeCode),按 imei(缺省时 deviceCode)幂等 upsert,写 `icc_sync_log`(sync_type=electric_gateway)
|
||||||
|
- **新增** 后端接口:`GET /api/electric-gateways` 列表、`GET /api/electric-gateways/:id` 详情、`POST /api/electric-gateways/sync` 手动同步
|
||||||
|
- **新增** 定时任务:注册 `icc_gateway_sync`(job_registry + job_scheduler buildRuntime + db_seed.sql sys_job 种子,默认 cron `*/10 * * * *`,与既有 ICC 同步任务口径一致)
|
||||||
|
- **新增** RBAC 映射:`/api/electric-gateways → device:list`
|
||||||
|
- **新增** 前端"智电改造网关台账"页面(`views/device/gateway/index.vue`),路由 `/device/gateway`,含"同步网关"手动按钮;菜单挂设备监控目录(parent=4)
|
||||||
|
- **新增** `.env` ICC 网关同步配置(ICC_BASE_URL=https://222.243.55.92:1443、ICC_VERSION=1.0.0、ICC_CLIENT_ID/ICC_CLIENT_SECRET/ICC_USERNAME/ICC_PASSWORD,取自接口文档)
|
||||||
|
- **安全**:全部 SQL 走 Sequelize 参数绑定;前端不用 v-html(防 XSS);接口返回统一 `code/message/data`
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Affected specs: 设备监控(设备台账、智电改造线路台账)、ICC 数据同步、RBAC 权限
|
||||||
|
- Affected code: server/src(entity/dto/service/controller/task/middleware)、client/src(api/router/views)、database(db_seed.sql、migrations/)
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
### Requirement: 网关台账查询(JWT+RBAC)
|
||||||
|
系统 SHALL 提供网关台账列表与详情查询,仅登录且有 `device:list` 权限的用户可访问;查询支持按 customer_id/equip_id/status(在线状态)/关键词(device_name/device_code/icc_imei)过滤分页;非 ADMIN 业务员按客户归属自动限定数据范围。
|
||||||
|
#### Scenario: 成功查询
|
||||||
|
- **WHEN** 已登录用户 GET `/api/electric-gateways?page=1&pageSize=10&customerId=1`
|
||||||
|
- **THEN** 返回 `code=0,data{list,total}`,含设备名称/编码/imei/客户/设备型号/主机类型/厂商/在线状态/同步时间;无 token 返回 401,无权限返回 403
|
||||||
|
|
||||||
|
### Requirement: ICC 网关设备拉取同步(真实鉴权 + 定时 + 手动)
|
||||||
|
系统 SHALL 提供 ICC 网关设备拉取同步能力:`IccGatewaySyncService` 读取 `.env` 的 ICC 配置(复用 `icc_customer_auth` 真实 OAuth2 密码模式鉴权),POST `/evo-apigw/evo-fdbu/{version}/device/subsystem/page` 分页拉取消防物联设备全量数据(不传 typeCode,pageSize 50,循环至短页,带最大页数保护),字段映射集中在 `icc_gateway_mapper`,按 icc_imei(缺省用 device_code)幂等 upsert 写入 `electric_gateway`,`customer_id` 由 orgCode→customer.icc_code 解析(匹配不到或缺 icc_imei 与 device_code 则跳过),更新 `synced_at`,写 `icc_sync_log`(sync_type=electric_gateway,status/record_count/error_message)。同步入口:① 定时任务(jobKey=`icc_gateway_sync`,默认 `*/10 * * * *`);② 手动 `POST /api/electric-gateways/sync`(JWT+RBAC `device:list`)。
|
||||||
|
#### Scenario: 定时/手动拉取成功
|
||||||
|
- **WHEN** 定时任务触发或运营点击"同步网关"
|
||||||
|
- **THEN** 拉取 ICC 数据并 upsert 对应网关行,`icc_sync_log` 记录成功;返回 `code=0,data{pulled,synced,inserted,updated,skipped}`
|
||||||
|
#### Scenario: 手动同步鉴权失败
|
||||||
|
- **WHEN** 未登录或无 `device:list` 权限调用 `/api/electric-gateways/sync`
|
||||||
|
- **THEN** 返回 401/403,不触发拉取
|
||||||
|
#### Scenario: ICC 拉取失败(不可达/鉴权失败/接口异常)
|
||||||
|
- **WHEN** ICC 地址不可达或返回异常
|
||||||
|
- **THEN** 同步日志记录失败状态与错误信息,业务接口不报错,仅返回已拉取计数
|
||||||
|
|
||||||
|
### Requirement: 前端网关台账页
|
||||||
|
系统 SHALL 提供 `/device/gateway` 页面:搜索栏(设备名称/编码/imei、客户、在线状态)、"同步网关"按钮(调 `/api/electric-gateways/sync`,成功后刷新)、表格(设备名称/编码/imei/客户/厂商/主机类型/设备型号/在线状态 tag/同步时间)、分页;样式与智电改造线路台账一致,全部文本经 Vue 插值转义。
|
||||||
|
#### Scenario: 打开页面
|
||||||
|
- **WHEN** 有 `device:list` 权限的用户访问设备监控→智电改造网关台账
|
||||||
|
- **THEN** 渲染列表并展示分页与筛选
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
(无既有功能被修改)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
- [x] Task 1: 数据库层:新建迁移 `database/migrations/20261024_icc_gateway.sql` 建 `electric_gateway`(字段集经需求方确认),并把建表/菜单/角色菜单/定时任务种子同步进 `db_seed.sql`;幂等(IF NOT EXISTS / ON CONFLICT)
|
||||||
|
- [x] SubTask 1.1: 核对生产库是否已有 electric_gateway,避免重复;确认 sys_menu 现有最大 id(迁移用 id=62 并 setval)—— 已核对:表无先例、菜单 id 62 空闲;迁移已执行到远程生产库
|
||||||
|
- [x] SubTask 1.2: 迁移内容:建表 electric_gateway(自增 id;icc_imei/device_code 唯一;customer_id→customer;equip_id→customer_equipment 可空;org_code/org_name/manufacturer_code/manufacturer_name/type_code/type_name/product_model/is_online/synced_at/created_at;索引 customer_id/equip_id/icc_imei);菜单 62「智电改造网关台账」挂设备监控(4)(permission device:list);ADMIN role_menu;sys_job 种子 icc_gateway_sync(cron `*/10 * * * *`)
|
||||||
|
- [x] SubTask 1.3: db_seed.sql 同步对应语句(保持幂等一致)—— 已追加段落(451-522 行)
|
||||||
|
- [x] Task 2: 后端实体与映射器:
|
||||||
|
- [x] SubTask 2.1: `server/src/entity/electric_gateway.entity.ts`(timestamps:false,结构对齐迁移文件)
|
||||||
|
- [x] SubTask 2.2: `server/src/task/icc_gateway_mapper.ts`:集中配置端点 `/evo-apigw/evo-fdbu/{version}/device/subsystem/page`、DeviceInfoVO 字段映射(id/imei/deviceCode/deviceName/orgCode/orgName/manufacturerCode/manufacturerName/typeCode/typeName/productModel/isOnline)、分页响应解析(PageVO{nextPage,totalCount,pageData})
|
||||||
|
- [x] Task 3: 后端同步服务 `server/src/service/icc_gateway_sync.service.ts`:
|
||||||
|
- [x] SubTask 3.1: 复用 `icc_customer_auth.ts` 的 `getIccAccessToken`/`iccFetch` 真实鉴权;读取 `.env`(ICC_BASE_URL/ICC_VERSION),配置缺失视为失败仅记日志
|
||||||
|
- [x] SubTask 3.2: POST 分页拉取(pageSize=50、不传 typeCode、循环至短页、最大页数保护),预取 orgCode→customer(id) 映射(customer.icc_code)
|
||||||
|
- [x] SubTask 3.3: 按 icc_imei(缺省 device_code)幂等 upsert;缺 imei 与 device_code 或匹配不到客户则跳过;更新 synced_at;写 icc_sync_log(sync_type=electric_gateway);拉取失败仅记录不抛业务错误 —— 实测同步成功(pulled=2),重跑 updated=2 幂等
|
||||||
|
- [x] Task 4: 后端查询服务/Controller/DTO + RBAC:
|
||||||
|
- [x] SubTask 4.1: `server/src/service/electric_gateway.service.ts`(list/detail,过滤:keyword=device_name/device_code/icc_imei、customer_id、equip_id、is_online;非 ADMIN 按 salesmanId 限定客户范围;decorate 补全客户/设备展示字段)
|
||||||
|
- [x] SubTask 4.2: `server/src/dto/electric_gateway.dto.ts`(查询/同步入参校验)
|
||||||
|
- [x] SubTask 4.3: `server/src/controller/electric_gateway.controller.ts`:GET `/api/electric-gateways`、GET `/:id`、POST `/sync`
|
||||||
|
- [x] SubTask 4.4: `rbac.middleware.ts` 追加 `/api/electric-gateways → device:list`
|
||||||
|
- [x] Task 5: 定时任务注册:
|
||||||
|
- [x] SubTask 5.1: `server/src/task/icc_gateway_sync.task.ts`(jobKey=`icc_gateway_sync`,默认 cron `*/10 * * * *`,handler 调 runtime.syncIccGateways)
|
||||||
|
- [x] SubTask 5.2: `job_registry.ts`:JobRuntime 增加 `syncIccGateways`,BUILTIN_JOBS 追加 iccGatewaySyncJob
|
||||||
|
- [x] SubTask 5.3: `job_scheduler.service.ts` buildRuntime 增加 `syncIccGateways`(匿名上下文注入 IccGatewaySyncService)
|
||||||
|
- [x] SubTask 5.4: `server/.env` 补全 ICC 网关同步凭据 —— 已确认 .env 中配置齐全(ICC_BASE_URL=https://222.243.55.92:1443、ICC_VERSION=1.0.0、ICC_CLIENT_ID=xtzdapp、ICC_CLIENT_SECRET、ICC_USERNAME=XTZD接口、ICC_PASSWORD)
|
||||||
|
- [x] Task 6: 前端:
|
||||||
|
- [x] SubTask 6.1: `client/src/api/device.ts` 新增 electricGatewayApi.list/get/sync
|
||||||
|
- [x] SubTask 6.2: `client/src/views/device/gateway/index.vue`(搜索栏+同步按钮+表格+在线状态 tag+分页,样式对齐 views/device/line/index.vue)
|
||||||
|
- [x] SubTask 6.3: `client/src/router/index.ts` 注册 `/device/gateway`(挂 device 目录)
|
||||||
|
- [x] Task 7: 验证:
|
||||||
|
- [x] SubTask 7.1: 后端 `tsc --noEmit`、前端 `vue-tsc -b` 类型检查通过 —— 均通过(exit 0)
|
||||||
|
- [x] SubTask 7.2: 启动服务:无 token 401(实测 401)、admin 列表 200(实测 total=2)、`POST /sync` 真实拉取成功(pulled=2→落库→重跑 updated=2)、定时任务 icc_gateway_sync 已注册(调度器加载 6 个任务,含 icc_gateway_sync cron */10 * * * *)
|
||||||
|
- [x] SubTask 7.3: 浏览器验证菜单出现、页面渲染、筛选/分页/同步按钮可用 —— 实测:菜单出现、9 列表格渲染 2 条、同步按钮提示"拉取 2 条,新增 0,更新 2"并刷新、关键词筛选/重置正常
|
||||||
|
|
||||||
|
# Task Dependencies
|
||||||
|
- Task 2/6 依赖 Task 1(表存在)
|
||||||
|
- Task 3 依赖 Task 2(映射器)
|
||||||
|
- Task 4 依赖 Task 1/2
|
||||||
|
- Task 5 依赖 Task 3
|
||||||
|
- Task 6 依赖 Task 4(接口可用)
|
||||||
|
- Task 7 依赖全部
|
||||||
@@ -203,3 +203,64 @@ export const electricLineApi = {
|
|||||||
sync: () =>
|
sync: () =>
|
||||||
request.post<IccLineSyncResult>('/electric-lines/sync'),
|
request.post<IccLineSyncResult>('/electric-lines/sync'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 智电改造网关台账 ====================
|
||||||
|
|
||||||
|
export interface ElectricGatewayListItem {
|
||||||
|
id: number;
|
||||||
|
iccImei?: string;
|
||||||
|
deviceCode?: string;
|
||||||
|
deviceName?: string;
|
||||||
|
customerId: number;
|
||||||
|
equipId?: number;
|
||||||
|
orgCode?: string;
|
||||||
|
orgName?: string;
|
||||||
|
manufacturerCode?: string;
|
||||||
|
manufacturerName?: string;
|
||||||
|
typeCode?: string;
|
||||||
|
typeName?: string;
|
||||||
|
productModel?: string;
|
||||||
|
isOnline: number;
|
||||||
|
syncedAt?: string;
|
||||||
|
createdAt: string;
|
||||||
|
// 联表补全字段
|
||||||
|
customerNo?: string;
|
||||||
|
customerName?: string;
|
||||||
|
equipCode?: string;
|
||||||
|
equipName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectricGatewayQuery {
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
keyword?: string;
|
||||||
|
customerId?: number;
|
||||||
|
equipId?: number;
|
||||||
|
isOnline?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElectricGatewayPageResult {
|
||||||
|
list: ElectricGatewayListItem[];
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IccGatewaySyncResult {
|
||||||
|
pulled: number;
|
||||||
|
synced: number;
|
||||||
|
inserted: number;
|
||||||
|
updated: number;
|
||||||
|
skipped: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const electricGatewayApi = {
|
||||||
|
getList: (params: ElectricGatewayQuery) =>
|
||||||
|
request.get<ElectricGatewayPageResult>('/electric-gateways', { params }),
|
||||||
|
|
||||||
|
getDetail: (id: number) =>
|
||||||
|
request.get<ElectricGatewayListItem>(`/electric-gateways/${id}`),
|
||||||
|
|
||||||
|
sync: () =>
|
||||||
|
request.post<IccGatewaySyncResult>('/electric-gateways/sync'),
|
||||||
|
};
|
||||||
@@ -193,6 +193,12 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import("@/views/device/line/index.vue"),
|
component: () => import("@/views/device/line/index.vue"),
|
||||||
meta: { requiresAuth: true, title: "智电改造线路台账", permission: "device:list" }
|
meta: { requiresAuth: true, title: "智电改造线路台账", permission: "device:list" }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "device/gateway",
|
||||||
|
name: "GatewayLedger",
|
||||||
|
component: () => import("@/views/device/gateway/index.vue"),
|
||||||
|
meta: { requiresAuth: true, title: "智电改造网关台账", permission: "device:list" }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "base",
|
path: "base",
|
||||||
name: "Base",
|
name: "Base",
|
||||||
|
|||||||
@@ -0,0 +1,510 @@
|
|||||||
|
<template>
|
||||||
|
<div class="device-gateway">
|
||||||
|
<!-- 顶部标题 -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<span>运营管控体系</span>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<span>设备监控</span>
|
||||||
|
<span class="sep">/</span>
|
||||||
|
<span class="cur">智电改造网关台账</span>
|
||||||
|
</div>
|
||||||
|
<div class="header-main">
|
||||||
|
<div class="header-left">
|
||||||
|
<div class="header-row">
|
||||||
|
<span class="title-bar"></span>
|
||||||
|
<span class="main-title">智电改造网关台账</span>
|
||||||
|
<span class="sub-title">ELECTRIC GATEWAY LEDGER</span>
|
||||||
|
</div>
|
||||||
|
<p class="desc">由 ICC 平台同步的智电改造网关台账,按客户、设备查看网关及在线状态。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 主体卡片 -->
|
||||||
|
<div class="content-card">
|
||||||
|
<!-- 筛选区 -->
|
||||||
|
<div class="filter-card">
|
||||||
|
<div class="filter-grid">
|
||||||
|
<div class="filter-item">
|
||||||
|
<label class="filter-label">所属客户</label>
|
||||||
|
<a-select
|
||||||
|
v-model:value="searchForm.customerId"
|
||||||
|
placeholder="全部客户"
|
||||||
|
allow-clear
|
||||||
|
show-search
|
||||||
|
option-filter-prop="label"
|
||||||
|
:options="customerOptions"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="filter-item">
|
||||||
|
<label class="filter-label">在线状态</label>
|
||||||
|
<a-select v-model:value="searchForm.isOnline" placeholder="全部状态" allow-clear>
|
||||||
|
<a-select-option :value="1">在线</a-select-option>
|
||||||
|
<a-select-option :value="0">离线</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-item">
|
||||||
|
<label class="filter-label">关键词检索</label>
|
||||||
|
<a-input
|
||||||
|
v-model:value="searchForm.keyword"
|
||||||
|
placeholder="设备名称 / 设备编码 / IMEI"
|
||||||
|
allow-clear
|
||||||
|
@pressEnter="handleSearch"
|
||||||
|
>
|
||||||
|
<template #prefix><SearchOutlined /></template>
|
||||||
|
</a-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-bottom">
|
||||||
|
<div class="filter-actions">
|
||||||
|
<a-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
查询
|
||||||
|
</a-button>
|
||||||
|
<a-button @click="handleReset">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
重置
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表区 -->
|
||||||
|
<div class="list-section">
|
||||||
|
<div class="list-header">
|
||||||
|
<div class="list-title">
|
||||||
|
<span class="dot"></span>
|
||||||
|
<span class="t">网关台账清单</span>
|
||||||
|
<span class="count">{{ total }} 条</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-actions">
|
||||||
|
<a-button type="primary" :loading="syncing" @click="handleSync">
|
||||||
|
<template #icon><SyncOutlined /></template>
|
||||||
|
同步网关
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrapper" ref="wrapperRef">
|
||||||
|
<a-table
|
||||||
|
:columns="columns"
|
||||||
|
:data-source="dataSource"
|
||||||
|
:loading="loading"
|
||||||
|
:pagination="pagination"
|
||||||
|
row-key="id"
|
||||||
|
:scroll="{ x: 'max-content', y: tableHeight }"
|
||||||
|
@change="handleTableChange"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'deviceCode'">
|
||||||
|
<span class="mono">{{ record.deviceCode || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'deviceName'">
|
||||||
|
<span class="device-name">{{ record.deviceName || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'iccImei'">
|
||||||
|
<span class="mono">{{ record.iccImei || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'customerId'">
|
||||||
|
{{ record.customerName ? `${record.customerName}(${record.customerNo})` : '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'manufacturerName'">
|
||||||
|
{{ record.manufacturerName || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'typeName'">
|
||||||
|
{{ record.typeName || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'productModel'">
|
||||||
|
{{ record.productModel || '-' }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'isOnline'">
|
||||||
|
<a-tag :color="record.isOnline === 1 ? 'success' : 'error'">
|
||||||
|
{{ record.isOnline === 1 ? '在线' : '离线' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="column.key === 'syncedAt'">
|
||||||
|
<span>{{ formatTime(record.syncedAt) }}</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #emptyText>
|
||||||
|
<a-empty description="暂无网关数据,可点击右上角「同步网关」从 ICC 拉取" />
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import {
|
||||||
|
SearchOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
SyncOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import {
|
||||||
|
electricGatewayApi,
|
||||||
|
type ElectricGatewayListItem,
|
||||||
|
type ElectricGatewayQuery,
|
||||||
|
} from '@/api/device';
|
||||||
|
import { customerApi, type CustomerInfo } from '@/api/customer';
|
||||||
|
import { useTableScroll } from '@/composables/useTableScroll';
|
||||||
|
|
||||||
|
const { wrapperRef, tableHeight } = useTableScroll();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const syncing = ref(false);
|
||||||
|
const dataSource = ref<ElectricGatewayListItem[]>([]);
|
||||||
|
const total = ref(0);
|
||||||
|
|
||||||
|
const searchForm = reactive<ElectricGatewayQuery>({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
keyword: '',
|
||||||
|
customerId: undefined as any,
|
||||||
|
isOnline: undefined as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
const customerOptions = ref<{ label: string; value: number }[]>([]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: '设备名称', dataIndex: 'deviceName', key: 'deviceName', ellipsis: true },
|
||||||
|
{ title: '设备编码', dataIndex: 'deviceCode', key: 'deviceCode', ellipsis: true, width: 170 },
|
||||||
|
{ title: '设备 IMEI', dataIndex: 'iccImei', key: 'iccImei', ellipsis: true, width: 170 },
|
||||||
|
{ title: '客户名称', dataIndex: 'customerId', key: 'customerId', ellipsis: true, width: 180 },
|
||||||
|
{ title: '厂商名称', dataIndex: 'manufacturerName', key: 'manufacturerName', ellipsis: true, width: 140 },
|
||||||
|
{ title: '主机类型', dataIndex: 'typeName', key: 'typeName', ellipsis: true, width: 120 },
|
||||||
|
{ title: '设备型号', dataIndex: 'productModel', key: 'productModel', ellipsis: true, width: 140 },
|
||||||
|
{ title: '在线状态', dataIndex: 'isOnline', key: 'isOnline', align: 'center' as const, width: 90 },
|
||||||
|
{ title: '同步时间', dataIndex: 'syncedAt', key: 'syncedAt', ellipsis: true, width: 170 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const pagination = computed(() => ({
|
||||||
|
current: searchForm.page,
|
||||||
|
pageSize: searchForm.pageSize,
|
||||||
|
total: total.value,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
showTotal: (t: number) => `共 ${t} 条数据`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchList();
|
||||||
|
fetchCustomerOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchList() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await electricGatewayApi.getList(searchForm);
|
||||||
|
if (res.code === 200) {
|
||||||
|
dataSource.value = res.data.list;
|
||||||
|
total.value = res.data.total;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('获取网关列表失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCustomerOptions() {
|
||||||
|
try {
|
||||||
|
const res: any = await customerApi.getCustomerOptions();
|
||||||
|
if (res.code === 200) {
|
||||||
|
const list: CustomerInfo[] = res.data;
|
||||||
|
customerOptions.value = list.map((item) => ({
|
||||||
|
label: `${item.customerNo} ${item.customerName}`,
|
||||||
|
value: item.id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取客户选项失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSearch() {
|
||||||
|
searchForm.page = 1;
|
||||||
|
fetchList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReset() {
|
||||||
|
searchForm.keyword = '';
|
||||||
|
searchForm.customerId = undefined as any;
|
||||||
|
searchForm.isOnline = undefined as any;
|
||||||
|
searchForm.page = 1;
|
||||||
|
fetchList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTableChange(pag: any) {
|
||||||
|
searchForm.page = pag.current;
|
||||||
|
searchForm.pageSize = pag.pageSize;
|
||||||
|
fetchList();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSync() {
|
||||||
|
syncing.value = true;
|
||||||
|
try {
|
||||||
|
const res: any = await electricGatewayApi.sync();
|
||||||
|
if (res.code === 200) {
|
||||||
|
const d = res.data || {};
|
||||||
|
message.success(`同步完成:拉取 ${d.pulled ?? 0} 条,新增 ${d.inserted ?? 0},更新 ${d.updated ?? 0}`);
|
||||||
|
fetchList();
|
||||||
|
} else {
|
||||||
|
message.error(res.message || '同步失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('同步失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
syncing.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(val?: string) {
|
||||||
|
if (!val) return '-';
|
||||||
|
const d = new Date(val);
|
||||||
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.device-gateway {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(180deg, #eef3f8 0%, #e8eefb 100%);
|
||||||
|
padding: 20px 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #5b6b7e;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb .sep {
|
||||||
|
color: #c2c6d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb .cur {
|
||||||
|
color: #0054cd;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-main {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
width: 5px;
|
||||||
|
height: 22px;
|
||||||
|
background: #0054cd;
|
||||||
|
border-radius: 3px;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0d1c2c;
|
||||||
|
line-height: 1.4;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0054cd;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin: 6px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #5b6b7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #dbe4ee;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.07);
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-card {
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #f8faff;
|
||||||
|
border: 1px solid #dbe4ee;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.filter-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.filter-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #5b6b7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bottom {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #dbe4ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-section {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 2px 4px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title .dot {
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background: #0054cd;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title .t {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0d1c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title .count {
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #eef4ff;
|
||||||
|
color: #0054cd;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: Consolas, Menlo, monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mono {
|
||||||
|
font-family: Consolas, Menlo, monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #0d1c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #0d1c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table) {
|
||||||
|
font-size: 14px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-table-thead > tr > th) {
|
||||||
|
background: #f4f7ff;
|
||||||
|
color: #5b6b7e;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-btn-primary) {
|
||||||
|
background: #0054cd;
|
||||||
|
border-color: #0054cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-btn-primary:not(:disabled):hover),
|
||||||
|
:deep(.ant-btn-primary:not(:disabled):focus) {
|
||||||
|
background: #136bfb;
|
||||||
|
border-color: #136bfb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+43
-266
@@ -41,7 +41,7 @@
|
|||||||
resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz"
|
resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.7.tgz"
|
||||||
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
|
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
|
||||||
|
|
||||||
"@babel/core@^7.23.0":
|
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.23.0":
|
||||||
version "7.29.7"
|
version "7.29.7"
|
||||||
resolved "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz"
|
resolved "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz"
|
||||||
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
|
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
|
||||||
@@ -275,6 +275,14 @@
|
|||||||
"@babel/types" "^7.29.7"
|
"@babel/types" "^7.29.7"
|
||||||
debug "^4.3.1"
|
debug "^4.3.1"
|
||||||
|
|
||||||
|
"@babel/types@^7.28.2", "@babel/types@^7.29.7":
|
||||||
|
version "7.29.7"
|
||||||
|
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz"
|
||||||
|
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-string-parser" "^7.29.7"
|
||||||
|
"@babel/helper-validator-identifier" "^7.29.7"
|
||||||
|
|
||||||
"@babel/types@7.23.0":
|
"@babel/types@7.23.0":
|
||||||
version "7.23.0"
|
version "7.23.0"
|
||||||
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.23.0.tgz"
|
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.23.0.tgz"
|
||||||
@@ -284,14 +292,6 @@
|
|||||||
"@babel/helper-validator-identifier" "^7.22.20"
|
"@babel/helper-validator-identifier" "^7.22.20"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@babel/types@^7.28.2", "@babel/types@^7.29.7":
|
|
||||||
version "7.29.7"
|
|
||||||
resolved "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz"
|
|
||||||
integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
|
|
||||||
dependencies:
|
|
||||||
"@babel/helper-string-parser" "^7.29.7"
|
|
||||||
"@babel/helper-validator-identifier" "^7.29.7"
|
|
||||||
|
|
||||||
"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.5.0":
|
"@ctrl/tinycolor@^3.4.0", "@ctrl/tinycolor@^3.5.0":
|
||||||
version "3.6.1"
|
version "3.6.1"
|
||||||
resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
|
resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
|
||||||
@@ -307,116 +307,6 @@
|
|||||||
resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz"
|
resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz"
|
||||||
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
|
integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
|
|
||||||
integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
|
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
|
|
||||||
integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
|
|
||||||
|
|
||||||
"@esbuild/android-arm@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
|
|
||||||
integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
|
|
||||||
|
|
||||||
"@esbuild/android-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
|
|
||||||
integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
|
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
|
|
||||||
integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
|
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
|
|
||||||
integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
|
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
|
|
||||||
integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
|
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
|
|
||||||
integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
|
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
|
|
||||||
integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
|
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
|
|
||||||
integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
|
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
|
|
||||||
integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
|
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
|
|
||||||
integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
|
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
|
|
||||||
integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
|
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
|
|
||||||
integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
|
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
|
|
||||||
integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
|
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
|
|
||||||
integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
|
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
|
|
||||||
integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
|
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
|
|
||||||
integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
|
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
|
|
||||||
integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
|
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
|
|
||||||
integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
|
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
|
|
||||||
integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
|
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.21.5":
|
|
||||||
version "0.21.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
|
|
||||||
integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
|
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.21.5":
|
"@esbuild/win32-x64@0.21.5":
|
||||||
version "0.21.5"
|
version "0.21.5"
|
||||||
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz"
|
resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz"
|
||||||
@@ -515,7 +405,7 @@
|
|||||||
"@nodelib/fs.stat" "2.0.5"
|
"@nodelib/fs.stat" "2.0.5"
|
||||||
run-parallel "^1.1.9"
|
run-parallel "^1.1.9"
|
||||||
|
|
||||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||||
@@ -528,121 +418,6 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.5"
|
"@nodelib/fs.scandir" "2.1.5"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz#3a04f01e9f01392bbef5920b94aa3b88794be7ab"
|
|
||||||
integrity sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==
|
|
||||||
|
|
||||||
"@rollup/rollup-android-arm64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz#e371b653ceabc900790ae73f5548a0fd7cd63a70"
|
|
||||||
integrity sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==
|
|
||||||
|
|
||||||
"@rollup/rollup-darwin-arm64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz#2a5aa70432e39816d666d79287a7324cfc3b4e72"
|
|
||||||
integrity sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==
|
|
||||||
|
|
||||||
"@rollup/rollup-darwin-x64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz#c3b5b49629379cd9cdc5d841bf00ed44ebf393dd"
|
|
||||||
integrity sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==
|
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-arm64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz#f929d8e0462fae6602fc960beeabd7287d859283"
|
|
||||||
integrity sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==
|
|
||||||
|
|
||||||
"@rollup/rollup-freebsd-x64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz#c01cb58031226f95d0900b1ec847f4fb32c6e809"
|
|
||||||
integrity sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-gnueabihf@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz#f29d890c4858c8e0d3be01677eef4f6a359eed9d"
|
|
||||||
integrity sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm-musleabihf@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz#1ebfc8eb9f66136ed2faae5f44995add5ca3c964"
|
|
||||||
integrity sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-gnu@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz#c1fa823c2c4ce46ba7f61de1a4c3fdadd4fb4e7b"
|
|
||||||
integrity sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-arm64-musl@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz#a7f18854d0471b78bda8ea38f0891a4e059b571d"
|
|
||||||
integrity sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-gnu@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz#83658a9a4576bcce8cef85b2c78b9b649d2200c4"
|
|
||||||
integrity sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-loong64-musl@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz#fd2af677ae3417bb58d57ae37dd0d84686e40244"
|
|
||||||
integrity sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-gnu@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz#6481647181c4cf8f1ddbd99f62c84cfc56c1a94a"
|
|
||||||
integrity sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-ppc64-musl@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz#18610a1a1550e28a5042ca916f898419540f17f4"
|
|
||||||
integrity sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-gnu@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz#597bb80465a2621dbe0de0a41c66394a8a7e9a6e"
|
|
||||||
integrity sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-riscv64-musl@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz#a2a919a9f927ef7f24a60af77e3cb55f1ad59e4d"
|
|
||||||
integrity sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-s390x-gnu@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz#3166f6ceae7df9bbfddf9f36be1937231e13e3c6"
|
|
||||||
integrity sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-gnu@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz#23c9bf79771d804fb87415eb0767569f273261e5"
|
|
||||||
integrity sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==
|
|
||||||
|
|
||||||
"@rollup/rollup-linux-x64-musl@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz#97941c6b94d67fe25cde0f027c10a19f2d1fdd39"
|
|
||||||
integrity sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==
|
|
||||||
|
|
||||||
"@rollup/rollup-openbsd-x64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz#7aeb7d92e2cd1d399f56daf75c39040b777b6c77"
|
|
||||||
integrity sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==
|
|
||||||
|
|
||||||
"@rollup/rollup-openharmony-arm64@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz#925de61ae83bf99aa636e8acea87432e8c0ffaab"
|
|
||||||
integrity sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-arm64-msvc@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz#888ab83842721491044c46a7407e1f38f3235bb4"
|
|
||||||
integrity sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-ia32-msvc@4.60.4":
|
|
||||||
version "4.60.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz#fa30ac24e3f0232139d2a47500560a28695764d4"
|
|
||||||
integrity sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==
|
|
||||||
|
|
||||||
"@rollup/rollup-win32-x64-gnu@4.60.4":
|
"@rollup/rollup-win32-x64-gnu@4.60.4":
|
||||||
version "4.60.4"
|
version "4.60.4"
|
||||||
resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz"
|
resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz"
|
||||||
@@ -666,7 +441,7 @@
|
|||||||
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz"
|
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz"
|
||||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||||
|
|
||||||
"@types/node@^22.15.30":
|
"@types/node@^18.0.0 || >=20.0.0", "@types/node@^22.15.30":
|
||||||
version "22.19.19"
|
version "22.19.19"
|
||||||
resolved "https://registry.npmmirror.com/@types/node/-/node-22.19.19.tgz"
|
resolved "https://registry.npmmirror.com/@types/node/-/node-22.19.19.tgz"
|
||||||
integrity sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==
|
integrity sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==
|
||||||
@@ -688,7 +463,7 @@
|
|||||||
natural-compare "^1.4.0"
|
natural-compare "^1.4.0"
|
||||||
ts-api-utils "^1.3.0"
|
ts-api-utils "^1.3.0"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^7.0.1":
|
"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.0.1":
|
||||||
version "7.18.0"
|
version "7.18.0"
|
||||||
resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-7.18.0.tgz"
|
resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-7.18.0.tgz"
|
||||||
integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==
|
integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==
|
||||||
@@ -764,14 +539,14 @@
|
|||||||
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz"
|
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz"
|
||||||
integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==
|
integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==
|
||||||
|
|
||||||
"@volar/language-core@1.11.1", "@volar/language-core@~1.11.1":
|
"@volar/language-core@~1.11.1", "@volar/language-core@1.11.1":
|
||||||
version "1.11.1"
|
version "1.11.1"
|
||||||
resolved "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz"
|
resolved "https://registry.npmmirror.com/@volar/language-core/-/language-core-1.11.1.tgz"
|
||||||
integrity sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==
|
integrity sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@volar/source-map" "1.11.1"
|
"@volar/source-map" "1.11.1"
|
||||||
|
|
||||||
"@volar/source-map@1.11.1", "@volar/source-map@~1.11.1":
|
"@volar/source-map@~1.11.1", "@volar/source-map@1.11.1":
|
||||||
version "1.11.1"
|
version "1.11.1"
|
||||||
resolved "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz"
|
resolved "https://registry.npmmirror.com/@volar/source-map/-/source-map-1.11.1.tgz"
|
||||||
integrity sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==
|
integrity sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==
|
||||||
@@ -828,7 +603,7 @@
|
|||||||
estree-walker "^2.0.2"
|
estree-walker "^2.0.2"
|
||||||
source-map-js "^1.2.1"
|
source-map-js "^1.2.1"
|
||||||
|
|
||||||
"@vue/compiler-dom@3.5.34", "@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@^3.3.4":
|
"@vue/compiler-dom@^3.3.0", "@vue/compiler-dom@^3.3.4", "@vue/compiler-dom@3.5.34":
|
||||||
version "3.5.34"
|
version "3.5.34"
|
||||||
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz"
|
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz"
|
||||||
integrity sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==
|
integrity sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==
|
||||||
@@ -836,7 +611,7 @@
|
|||||||
"@vue/compiler-core" "3.5.34"
|
"@vue/compiler-core" "3.5.34"
|
||||||
"@vue/shared" "3.5.34"
|
"@vue/shared" "3.5.34"
|
||||||
|
|
||||||
"@vue/compiler-sfc@3.5.34", "@vue/compiler-sfc@^3.5.18":
|
"@vue/compiler-sfc@^3.5.18", "@vue/compiler-sfc@3.5.34":
|
||||||
version "3.5.34"
|
version "3.5.34"
|
||||||
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz"
|
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz"
|
||||||
integrity sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==
|
integrity sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==
|
||||||
@@ -921,7 +696,7 @@
|
|||||||
"@vue/reactivity" "3.5.34"
|
"@vue/reactivity" "3.5.34"
|
||||||
"@vue/shared" "3.5.34"
|
"@vue/shared" "3.5.34"
|
||||||
|
|
||||||
"@vue/runtime-dom@3.5.34", "@vue/runtime-dom@^3.4.15":
|
"@vue/runtime-dom@^3.4.15", "@vue/runtime-dom@3.5.34":
|
||||||
version "3.5.34"
|
version "3.5.34"
|
||||||
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz"
|
resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz"
|
||||||
integrity sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==
|
integrity sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==
|
||||||
@@ -939,7 +714,7 @@
|
|||||||
"@vue/compiler-ssr" "3.5.34"
|
"@vue/compiler-ssr" "3.5.34"
|
||||||
"@vue/shared" "3.5.34"
|
"@vue/shared" "3.5.34"
|
||||||
|
|
||||||
"@vue/shared@3.5.34", "@vue/shared@^3.3.0", "@vue/shared@^3.5.18":
|
"@vue/shared@^3.3.0", "@vue/shared@^3.5.18", "@vue/shared@3.5.34":
|
||||||
version "3.5.34"
|
version "3.5.34"
|
||||||
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.34.tgz"
|
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.34.tgz"
|
||||||
integrity sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==
|
integrity sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==
|
||||||
@@ -954,7 +729,7 @@ acorn-jsx@^5.3.2:
|
|||||||
resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||||
|
|
||||||
acorn@^8.14.0, acorn@^8.9.0:
|
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0, acorn@^8.9.0:
|
||||||
version "8.16.0"
|
version "8.16.0"
|
||||||
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz"
|
resolved "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz"
|
||||||
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==
|
||||||
@@ -1127,7 +902,7 @@ braces@^3.0.3, braces@~3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.1.1"
|
fill-range "^7.1.1"
|
||||||
|
|
||||||
browserslist@^4.24.0, browserslist@^4.28.2:
|
browserslist@^4.24.0, browserslist@^4.28.2, "browserslist@>= 4.21.0":
|
||||||
version "4.28.2"
|
version "4.28.2"
|
||||||
resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz"
|
resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz"
|
||||||
integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
|
integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
|
||||||
@@ -1281,7 +1056,7 @@ de-indent@^1.0.2:
|
|||||||
resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz"
|
resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz"
|
||||||
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
|
||||||
|
|
||||||
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4:
|
||||||
version "4.4.3"
|
version "4.4.3"
|
||||||
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz"
|
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz"
|
||||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||||
@@ -1341,7 +1116,7 @@ dunder-proto@^1.0.1:
|
|||||||
es-errors "^1.3.0"
|
es-errors "^1.3.0"
|
||||||
gopd "^1.2.0"
|
gopd "^1.2.0"
|
||||||
|
|
||||||
echarts@^6.1.0:
|
echarts@^6.0.0, echarts@^6.1.0:
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz"
|
resolved "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz"
|
||||||
integrity sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==
|
integrity sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==
|
||||||
@@ -1459,7 +1234,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
|
|||||||
resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
|
resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
|
||||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||||
|
|
||||||
eslint@^8.56.0:
|
"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.56.0, eslint@>=6.0.0:
|
||||||
version "8.57.1"
|
version "8.57.1"
|
||||||
resolved "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz"
|
resolved "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz"
|
||||||
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
|
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
|
||||||
@@ -1641,11 +1416,6 @@ fs.realpath@^1.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||||
|
|
||||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
|
||||||
version "2.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
|
||||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
|
||||||
|
|
||||||
function-bind@^1.1.2:
|
function-bind@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz"
|
resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz"
|
||||||
@@ -1881,7 +1651,7 @@ isexe@^2.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz"
|
resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz"
|
||||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||||
|
|
||||||
jiti@^1.21.7:
|
jiti@^1.21.7, jiti@>=1.21.0:
|
||||||
version "1.21.7"
|
version "1.21.7"
|
||||||
resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz"
|
resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz"
|
||||||
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
|
integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
|
||||||
@@ -1935,7 +1705,7 @@ kolorist@^1.8.0:
|
|||||||
resolved "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz"
|
resolved "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz"
|
||||||
integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
|
integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
|
||||||
|
|
||||||
less@^4.6.4:
|
less@*, less@^4.6.4:
|
||||||
version "4.6.4"
|
version "4.6.4"
|
||||||
resolved "https://registry.npmmirror.com/less/-/less-4.6.4.tgz"
|
resolved "https://registry.npmmirror.com/less/-/less-4.6.4.tgz"
|
||||||
integrity sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==
|
integrity sha512-OJmO5+HxZLLw0RLzkqaNHzcgEAQG7C0y3aMbwtCzIUFZsLMNNq/1IdAdHEycQ58CwUO3jPTHmoN+tE5I7FQxNg==
|
||||||
@@ -2067,7 +1837,14 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimatch@^9.0.3, minimatch@^9.0.4:
|
minimatch@^9.0.3:
|
||||||
|
version "9.0.9"
|
||||||
|
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz"
|
||||||
|
integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^2.0.2"
|
||||||
|
|
||||||
|
minimatch@^9.0.4:
|
||||||
version "9.0.9"
|
version "9.0.9"
|
||||||
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz"
|
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz"
|
||||||
integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==
|
integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==
|
||||||
@@ -2238,7 +2015,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
|||||||
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz"
|
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz"
|
||||||
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
|
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
|
||||||
|
|
||||||
picomatch@^4.0.4:
|
"picomatch@^3 || ^4", picomatch@^4.0.4:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz"
|
resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz"
|
||||||
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
|
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
|
||||||
@@ -2308,7 +2085,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
|||||||
resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||||
|
|
||||||
postcss@^8.4.35, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.5.14:
|
postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.35, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.5.14, postcss@>=8.0.9:
|
||||||
version "8.5.15"
|
version "8.5.15"
|
||||||
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz"
|
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz"
|
||||||
integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==
|
integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==
|
||||||
@@ -2639,16 +2416,16 @@ ts-interface-checker@^0.1.9:
|
|||||||
resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
|
resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
|
||||||
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||||
|
|
||||||
tslib@2.3.0:
|
|
||||||
version "2.3.0"
|
|
||||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
|
|
||||||
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
|
||||||
|
|
||||||
tslib@^2.8.1:
|
tslib@^2.8.1:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
|
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz"
|
||||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||||
|
|
||||||
|
tslib@2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz"
|
||||||
|
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
|
||||||
|
|
||||||
type-check@^0.4.0, type-check@~0.4.0:
|
type-check@^0.4.0, type-check@~0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz"
|
resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz"
|
||||||
@@ -2661,7 +2438,7 @@ type-fest@^0.20.2:
|
|||||||
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz"
|
resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz"
|
||||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||||
|
|
||||||
typescript@~5.3.3:
|
typescript@*, typescript@>=4.2.0, typescript@>=4.5.0, typescript@~5.3.3, typescript@5.x:
|
||||||
version "5.3.3"
|
version "5.3.3"
|
||||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz"
|
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.3.3.tgz"
|
||||||
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
|
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
|
||||||
@@ -2731,7 +2508,7 @@ vite-plugin-vue-dev-locator@1.0.3:
|
|||||||
kolorist "^1.8.0"
|
kolorist "^1.8.0"
|
||||||
magic-string "^0.30.4"
|
magic-string "^0.30.4"
|
||||||
|
|
||||||
vite@^5.0.12:
|
"vite@^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0", "vite@^5.0.0 || ^6.0.0", vite@^5.0.12, vite@>=4.0.0:
|
||||||
version "5.4.21"
|
version "5.4.21"
|
||||||
resolved "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz"
|
resolved "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz"
|
||||||
integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==
|
integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==
|
||||||
@@ -2791,7 +2568,7 @@ vue-types@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-plain-object "3.0.1"
|
is-plain-object "3.0.1"
|
||||||
|
|
||||||
vue@^3.4.15:
|
vue@^3.0.0, vue@^3.2.25, vue@^3.3.0, vue@^3.4.0, vue@^3.4.15, vue@^3.5.0, vue@^3.5.11, vue@>=3.0.1, vue@>=3.0.3, vue@>=3.2.0, vue@3.5.34:
|
||||||
version "3.5.34"
|
version "3.5.34"
|
||||||
resolved "https://registry.npmmirror.com/vue/-/vue-3.5.34.tgz"
|
resolved "https://registry.npmmirror.com/vue/-/vue-3.5.34.tgz"
|
||||||
integrity sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==
|
integrity sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==
|
||||||
|
|||||||
@@ -448,6 +448,79 @@ INSERT INTO sys_notice_rule (rule_key, rule_name, event_key, trigger_mode, enabl
|
|||||||
'扫描派单后超过 advance_days 天仍未接单的工单,通知 ADMIN')
|
'扫描派单后超过 advance_days 天仍未接单的工单,通知 ADMIN')
|
||||||
ON CONFLICT (rule_key) DO NOTHING;
|
ON CONFLICT (rule_key) DO NOTHING;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- 11a. 智电改造网关台账(ICC 消防物联设备同步)
|
||||||
|
-- 建表 electric_gateway(与迁移 20261024_icc_gateway.sql 一致)
|
||||||
|
-- 设备监控(id=4)下新增菜单 62「智电改造网关台账」(sort 3,排在智电改造线路台账之后)
|
||||||
|
-- ADMIN 绑定新菜单;注册 icc_gateway_sync 定时任务(默认每 10 分钟)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS electric_gateway (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
icc_imei VARCHAR(64) UNIQUE,
|
||||||
|
device_code VARCHAR(64) UNIQUE,
|
||||||
|
device_name VARCHAR(128),
|
||||||
|
customer_id BIGINT NOT NULL REFERENCES customer(id),
|
||||||
|
equip_id BIGINT REFERENCES customer_equipment(id),
|
||||||
|
org_code VARCHAR(64),
|
||||||
|
org_name VARCHAR(128),
|
||||||
|
manufacturer_code VARCHAR(64),
|
||||||
|
manufacturer_name VARCHAR(128),
|
||||||
|
type_code VARCHAR(32),
|
||||||
|
type_name VARCHAR(64),
|
||||||
|
product_model VARCHAR(64),
|
||||||
|
is_online SMALLINT NOT NULL DEFAULT 1,
|
||||||
|
synced_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE electric_gateway IS '智电改造网关设备表(ICC 同步)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.id IS '自增主键';
|
||||||
|
COMMENT ON COLUMN electric_gateway.icc_imei IS 'ICC 设备 IMEI(可空唯一,ICC 数据可能缺失)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.device_code IS 'ICC 设备编码(可空唯一,ICC 数据可能缺失)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.device_name IS '设备名称(ICC 同步)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.customer_id IS '客户 ID(由 orgCode→customer.icc_code 解析,逻辑隔离键)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.equip_id IS '客户设备 ID(→ customer_equipment,同步不填充)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.org_code IS 'ICC 组织编码';
|
||||||
|
COMMENT ON COLUMN electric_gateway.org_name IS '组织名称(ICC 同步)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.manufacturer_code IS '厂商编码';
|
||||||
|
COMMENT ON COLUMN electric_gateway.manufacturer_name IS '厂商名称';
|
||||||
|
COMMENT ON COLUMN electric_gateway.type_code IS '设备类型编码';
|
||||||
|
COMMENT ON COLUMN electric_gateway.type_name IS '设备类型名称';
|
||||||
|
COMMENT ON COLUMN electric_gateway.product_model IS '产品型号';
|
||||||
|
COMMENT ON COLUMN electric_gateway.is_online IS '在线状态:1在线 0离线';
|
||||||
|
COMMENT ON COLUMN electric_gateway.synced_at IS 'ICC 同步时间';
|
||||||
|
COMMENT ON COLUMN electric_gateway.created_at IS '创建时间';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gateway_customer ON electric_gateway (customer_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gateway_equip ON electric_gateway (equip_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gateway_imei ON electric_gateway (icc_imei);
|
||||||
|
|
||||||
|
-- 菜单 62:设备监控下的智电改造网关台账菜单
|
||||||
|
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
|
||||||
|
(62, 4, '智电改造网关台账', '/device/gateway', 'device/gateway/index', 'ApiOutlined', 3, 2, 'device:list', 1)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
parent_id = EXCLUDED.parent_id,
|
||||||
|
menu_name = EXCLUDED.menu_name,
|
||||||
|
menu_path = EXCLUDED.menu_path,
|
||||||
|
component = EXCLUDED.component,
|
||||||
|
icon = EXCLUDED.icon,
|
||||||
|
sort_order = EXCLUDED.sort_order,
|
||||||
|
menu_type = EXCLUDED.menu_type,
|
||||||
|
permission = EXCLUDED.permission,
|
||||||
|
visible = EXCLUDED.visible;
|
||||||
|
|
||||||
|
-- 角色-菜单绑定:ADMIN(role_id=1)拥有 62
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
|
||||||
|
(1, 62)
|
||||||
|
ON CONFLICT (role_id, menu_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- 重置 sys_menu 自增序列(覆盖到 62)
|
||||||
|
SELECT setval('sys_menu_id_seq', GREATEST((SELECT MAX(id) FROM sys_menu), 62), true);
|
||||||
|
|
||||||
|
-- 智电网关同步任务种子(sys_job,按 job_key 幂等;默认每 10 分钟)
|
||||||
|
INSERT INTO sys_job (job_key, job_name, cron_expression, params, retry_times, retry_interval, status, remark) VALUES
|
||||||
|
('icc_gateway_sync', '智电网关同步', '*/10 * * * *', '{}'::jsonb, 1, 300, 1,
|
||||||
|
'从 ICC 平台拉取消防物联设备并按 icc_imei 幂等 upsert(默认每 10 分钟)')
|
||||||
|
ON CONFLICT (job_key) DO NOTHING;
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
-- 智电改造网关台账(ICC 消防物联设备同步):
|
||||||
|
-- 1) 幂等建表 electric_gateway(若 db_init.sql / db_seed.sql 已建则跳过)
|
||||||
|
-- 2) 设备监控(id=4)下新增菜单 62「智电改造网关台账」
|
||||||
|
-- 3) ADMIN 角色绑定新菜单
|
||||||
|
-- 4) 注册 icc_gateway_sync 定时任务(默认每 10 分钟)
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- 1) 幂等建表(与 db_seed.sql 定义保持一致)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
CREATE TABLE IF NOT EXISTS electric_gateway (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
icc_imei VARCHAR(64) UNIQUE,
|
||||||
|
device_code VARCHAR(64) UNIQUE,
|
||||||
|
device_name VARCHAR(128),
|
||||||
|
customer_id BIGINT NOT NULL REFERENCES customer(id),
|
||||||
|
equip_id BIGINT REFERENCES customer_equipment(id),
|
||||||
|
org_code VARCHAR(64),
|
||||||
|
org_name VARCHAR(128),
|
||||||
|
manufacturer_code VARCHAR(64),
|
||||||
|
manufacturer_name VARCHAR(128),
|
||||||
|
type_code VARCHAR(32),
|
||||||
|
type_name VARCHAR(64),
|
||||||
|
product_model VARCHAR(64),
|
||||||
|
is_online SMALLINT NOT NULL DEFAULT 1,
|
||||||
|
synced_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
COMMENT ON TABLE electric_gateway IS '智电改造网关设备表(ICC 同步)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.id IS '自增主键';
|
||||||
|
COMMENT ON COLUMN electric_gateway.icc_imei IS 'ICC 设备 IMEI(可空唯一,ICC 数据可能缺失)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.device_code IS 'ICC 设备编码(可空唯一,ICC 数据可能缺失)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.device_name IS '设备名称(ICC 同步)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.customer_id IS '客户 ID(由 orgCode→customer.icc_code 解析,逻辑隔离键)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.equip_id IS '客户设备 ID(→ customer_equipment,同步不填充)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.org_code IS 'ICC 组织编码';
|
||||||
|
COMMENT ON COLUMN electric_gateway.org_name IS '组织名称(ICC 同步)';
|
||||||
|
COMMENT ON COLUMN electric_gateway.manufacturer_code IS '厂商编码';
|
||||||
|
COMMENT ON COLUMN electric_gateway.manufacturer_name IS '厂商名称';
|
||||||
|
COMMENT ON COLUMN electric_gateway.type_code IS '设备类型编码';
|
||||||
|
COMMENT ON COLUMN electric_gateway.type_name IS '设备类型名称';
|
||||||
|
COMMENT ON COLUMN electric_gateway.product_model IS '产品型号';
|
||||||
|
COMMENT ON COLUMN electric_gateway.is_online IS '在线状态:1在线 0离线';
|
||||||
|
COMMENT ON COLUMN electric_gateway.synced_at IS 'ICC 同步时间';
|
||||||
|
COMMENT ON COLUMN electric_gateway.created_at IS '创建时间';
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gateway_customer ON electric_gateway (customer_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gateway_equip ON electric_gateway (equip_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_gateway_imei ON electric_gateway (icc_imei);
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- 2) 设备监控(id=4)下新增菜单 62「智电改造网关台账」(sort 3,排在智电改造线路台账之后)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
INSERT INTO sys_menu (id, parent_id, menu_name, menu_path, component, icon, sort_order, menu_type, permission, visible) VALUES
|
||||||
|
(62, 4, '智电改造网关台账', '/device/gateway', 'device/gateway/index', 'ApiOutlined', 3, 2, 'device:list', 1)
|
||||||
|
ON CONFLICT (id) DO UPDATE SET
|
||||||
|
parent_id = EXCLUDED.parent_id,
|
||||||
|
menu_name = EXCLUDED.menu_name,
|
||||||
|
menu_path = EXCLUDED.menu_path,
|
||||||
|
component = EXCLUDED.component,
|
||||||
|
icon = EXCLUDED.icon,
|
||||||
|
sort_order = EXCLUDED.sort_order,
|
||||||
|
menu_type = EXCLUDED.menu_type,
|
||||||
|
permission = EXCLUDED.permission,
|
||||||
|
visible = EXCLUDED.visible;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- 3) 角色-菜单绑定:ADMIN(role_id=1) 拥有 62
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
INSERT INTO sys_role_menu (role_id, menu_id) VALUES
|
||||||
|
(1, 62)
|
||||||
|
ON CONFLICT (role_id, menu_id) DO NOTHING;
|
||||||
|
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
-- 4) 定时任务种子:icc_gateway_sync(默认每 10 分钟,可按 job_key 幂等)
|
||||||
|
-- ------------------------------------------------------------
|
||||||
|
INSERT INTO sys_job (job_key, job_name, cron_expression, params, retry_times, retry_interval, status, remark) VALUES
|
||||||
|
('icc_gateway_sync', '智电网关同步', '*/10 * * * *', '{}'::jsonb, 1, 300, 1,
|
||||||
|
'从 ICC 平台拉取消防物联设备并按 icc_imei 幂等 upsert(默认每 10 分钟)')
|
||||||
|
ON CONFLICT (job_key) DO NOTHING;
|
||||||
|
|
||||||
|
-- 重置序列(覆盖到 62)
|
||||||
|
SELECT setval('sys_menu_id_seq', GREATEST((SELECT MAX(id) FROM sys_menu), 62), true);
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
+1
-1
@@ -38,7 +38,7 @@
|
|||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"sequelize": "^6.37.5",
|
"sequelize": "^6.37.5",
|
||||||
"sequelize-typescript": "^2.1.6",
|
"sequelize-typescript": "^2.1.6",
|
||||||
"undici": "^8.12.2",
|
"undici": "^8.12.3",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Controller, Get, Post, Inject, Query, Param } from "@midwayjs/core";
|
||||||
|
import { Context } from "@midwayjs/koa";
|
||||||
|
import { ElectricGatewayService } from "../service/electric_gateway.service";
|
||||||
|
import { IccGatewaySyncService } from "../service/icc_gateway_sync.service";
|
||||||
|
|
||||||
|
@Controller("/api/electric-gateways")
|
||||||
|
export class ElectricGatewayController {
|
||||||
|
@Inject() ctx!: Context;
|
||||||
|
@Inject() electricGatewayService!: ElectricGatewayService;
|
||||||
|
@Inject() iccGatewaySyncService!: IccGatewaySyncService;
|
||||||
|
|
||||||
|
@Get("/") async list(@Query() query: any) { return this.electricGatewayService.list(query, this.ctx); }
|
||||||
|
@Get("/:id") async detail(@Param("id") id: string) { return this.electricGatewayService.detail(Number(id), this.ctx); }
|
||||||
|
@Post("/sync") async sync() {
|
||||||
|
const result = await this.iccGatewaySyncService.syncFromIcc(this.ctx);
|
||||||
|
return { code: 200, message: "网关设备同步完成", data: result };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { IsOptional, IsString, IsInt, MaxLength, Min, Max } from "class-validator";
|
||||||
|
|
||||||
|
export class ElectricGatewayQueryDTO {
|
||||||
|
@IsOptional() @IsInt() @Min(1) page?: number = 1;
|
||||||
|
@IsOptional() @IsInt() @Min(1) @Max(200) pageSize?: number = 10;
|
||||||
|
@IsOptional() @IsString() @MaxLength(64) keyword?: string;
|
||||||
|
@IsOptional() @IsInt() customerId?: number;
|
||||||
|
@IsOptional() @IsInt() equipId?: number;
|
||||||
|
@IsOptional() @IsInt() @Min(0) @Max(1) isOnline?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
|
||||||
|
import Customer from "./customer.entity";
|
||||||
|
import CustomerEquipment from "./customer_equipment.entity";
|
||||||
|
|
||||||
|
@Table({
|
||||||
|
tableName: "electric_gateway",
|
||||||
|
comment: "智电改造网关设备表(ICC 同步)",
|
||||||
|
timestamps: false,
|
||||||
|
indexes: [
|
||||||
|
{ name: "idx_gateway_customer", fields: ["customer_id"] },
|
||||||
|
{ name: "idx_gateway_equip", fields: ["equip_id"] },
|
||||||
|
{ name: "idx_gateway_imei", fields: ["icc_imei"] }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export default class ElectricGateway extends Model {
|
||||||
|
@Column({ type: DataType.BIGINT, autoIncrement: true, primaryKey: true })
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(64), unique: true, field: "icc_imei", comment: "设备唯一标识码(imei)" })
|
||||||
|
iccImei!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(64), unique: true, field: "device_code", comment: "设备编码" })
|
||||||
|
deviceCode!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(128), field: "device_name", comment: "设备名称" })
|
||||||
|
deviceName!: string;
|
||||||
|
|
||||||
|
@ForeignKey(() => Customer)
|
||||||
|
@Column({ type: DataType.BIGINT, allowNull: false, field: "customer_id", comment: "客户 ID(由 orgCode→customer.icc_code 解析)" })
|
||||||
|
customerId!: number;
|
||||||
|
|
||||||
|
@ForeignKey(() => CustomerEquipment)
|
||||||
|
@Column({ type: DataType.BIGINT, field: "equip_id", comment: "客户设备 ID(→ customer_equipment,可手动关联)" })
|
||||||
|
equipId!: number;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(64), field: "org_code", comment: "ICC 组织编码" })
|
||||||
|
orgCode!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(128), field: "org_name", comment: "ICC 组织名称" })
|
||||||
|
orgName!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(64), field: "manufacturer_code", comment: "厂商编码" })
|
||||||
|
manufacturerCode!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(128), field: "manufacturer_name", comment: "厂商名称" })
|
||||||
|
manufacturerName!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(32), field: "type_code", comment: "设备类型编码" })
|
||||||
|
typeCode!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(64), field: "type_name", comment: "设备类型名称" })
|
||||||
|
typeName!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.STRING(64), field: "product_model", comment: "产品型号" })
|
||||||
|
productModel!: string;
|
||||||
|
|
||||||
|
@Column({ type: DataType.SMALLINT, allowNull: false, defaultValue: 1, field: "is_online", comment: "在线状态:1在线 0离线" })
|
||||||
|
isOnline!: number;
|
||||||
|
|
||||||
|
@Column({ type: DataType.DATE, field: "synced_at", comment: "ICC 同步时间" })
|
||||||
|
syncedAt!: Date;
|
||||||
|
|
||||||
|
@BelongsTo(() => Customer, { foreignKey: "customerId", as: "customer" })
|
||||||
|
customer!: Customer;
|
||||||
|
|
||||||
|
@BelongsTo(() => CustomerEquipment, { foreignKey: "equipId", as: "equipment" })
|
||||||
|
equipment!: CustomerEquipment;
|
||||||
|
|
||||||
|
@CreatedAt
|
||||||
|
created_at!: Date;
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ const PERMISSION_MAP: Record<string, string> = {
|
|||||||
"/api/customer-equipments": "device:list",
|
"/api/customer-equipments": "device:list",
|
||||||
"/api/smart-devices": "device:model",
|
"/api/smart-devices": "device:model",
|
||||||
"/api/electric-lines": "device:list",
|
"/api/electric-lines": "device:list",
|
||||||
|
"/api/electric-gateways": "device:list",
|
||||||
"/api/survey-orders": "project:survey",
|
"/api/survey-orders": "project:survey",
|
||||||
"/api/project-plans": "project:plan",
|
"/api/project-plans": "project:plan",
|
||||||
"/api/contracts": "project:contract",
|
"/api/contracts": "project:contract",
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import { Provide } from "@midwayjs/core";
|
||||||
|
import { InjectRepository } from "@midwayjs/sequelize";
|
||||||
|
import { Repository } from "sequelize-typescript";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
import { Context } from "@midwayjs/koa";
|
||||||
|
import ElectricGateway from "../entity/electric_gateway.entity";
|
||||||
|
import Customer from "../entity/customer.entity";
|
||||||
|
import CustomerEquipment from "../entity/customer_equipment.entity";
|
||||||
|
import SysUserRole from "../entity/sysUserRole.entity";
|
||||||
|
import SysRole from "../entity/sysRole.entity";
|
||||||
|
|
||||||
|
@Provide()
|
||||||
|
export class ElectricGatewayService {
|
||||||
|
@InjectRepository(ElectricGateway)
|
||||||
|
gatewayModel!: Repository<ElectricGateway>;
|
||||||
|
|
||||||
|
@InjectRepository(Customer)
|
||||||
|
customerModel!: Repository<Customer>;
|
||||||
|
|
||||||
|
@InjectRepository(CustomerEquipment)
|
||||||
|
equipmentModel!: Repository<CustomerEquipment>;
|
||||||
|
|
||||||
|
@InjectRepository(SysUserRole)
|
||||||
|
userRoleModel!: Repository<SysUserRole>;
|
||||||
|
|
||||||
|
@InjectRepository(SysRole)
|
||||||
|
roleModel!: Repository<SysRole>;
|
||||||
|
|
||||||
|
private async isAdmin(userId: number): Promise<boolean> {
|
||||||
|
const userRoles = await this.userRoleModel.findAll({ where: { userId }, include: [this.roleModel] });
|
||||||
|
return userRoles.some((ur) => (ur as any).role?.roleCode === "ADMIN");
|
||||||
|
}
|
||||||
|
|
||||||
|
async list(query: any, ctx: Context): Promise<any> {
|
||||||
|
const page = Number(query.page) || 1;
|
||||||
|
const pageSize = Number(query.pageSize) || 10;
|
||||||
|
const userId = ctx.state.user.userId;
|
||||||
|
const where: any = {};
|
||||||
|
|
||||||
|
if (query.keyword) {
|
||||||
|
where[Op.or] = [
|
||||||
|
{ deviceName: { [Op.like]: `%${query.keyword}%` } },
|
||||||
|
{ deviceCode: { [Op.like]: `%${query.keyword}%` } },
|
||||||
|
{ iccImei: { [Op.like]: `%${query.keyword}%` } }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (query.customerId !== undefined && query.customerId !== "") where.customerId = Number(query.customerId);
|
||||||
|
if (query.equipId !== undefined && query.equipId !== "") where.equipId = Number(query.equipId);
|
||||||
|
if (query.isOnline !== undefined && query.isOnline !== "" && query.isOnline !== null) where.isOnline = Number(query.isOnline);
|
||||||
|
|
||||||
|
// 非管理员按客户归属自动限定数据范围
|
||||||
|
if (!(await this.isAdmin(userId))) {
|
||||||
|
const myCustomers = await this.customerModel.findAll({ where: { salesmanId: userId }, attributes: ["id"] });
|
||||||
|
const ids = myCustomers.map((c) => c.id);
|
||||||
|
if (ids.length === 0) {
|
||||||
|
return { code: 200, message: "获取网关台账列表成功", data: { list: [], total: 0, page: Number(page), pageSize: Number(pageSize) } };
|
||||||
|
}
|
||||||
|
if (where.customerId !== undefined) {
|
||||||
|
if (!ids.includes(where.customerId)) {
|
||||||
|
return { code: 200, message: "获取网关台账列表成功", data: { list: [], total: 0, page: Number(page), pageSize: Number(pageSize) } };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
where.customerId = { [Op.in]: ids };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { count, rows } = await this.gatewayModel.findAndCountAll({
|
||||||
|
where,
|
||||||
|
limit: pageSize,
|
||||||
|
offset: (page - 1) * pageSize,
|
||||||
|
order: [["created_at", "DESC"], ["id", "DESC"]],
|
||||||
|
distinct: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = await this.decorate(rows);
|
||||||
|
return { code: 200, message: "获取网关台账列表成功", data: { list, total: count, page: Number(page), pageSize: Number(pageSize) } };
|
||||||
|
}
|
||||||
|
|
||||||
|
async detail(id: number, ctx: Context): Promise<any> {
|
||||||
|
const record = await this.gatewayModel.findByPk(id);
|
||||||
|
if (!record) return { code: 404, message: "网关设备不存在", data: null };
|
||||||
|
|
||||||
|
if (!(await this.isAdmin(ctx.state.user.userId))) {
|
||||||
|
const customer = await this.customerModel.findByPk(record.customerId);
|
||||||
|
if (!customer || customer.salesmanId !== ctx.state.user.userId) {
|
||||||
|
return { code: 403, message: "无权限访问该网关设备", data: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decorated = await this.decorate([record]);
|
||||||
|
return { code: 200, message: "获取网关设备台账详情成功", data: decorated[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 补全客户/设备展示信息,避免 N+1。 */
|
||||||
|
private async decorate(rows: ElectricGateway[]): Promise<any[]> {
|
||||||
|
if (rows.length === 0) return rows as any;
|
||||||
|
|
||||||
|
const customerIds = [...new Set(rows.map((r) => r.customerId).filter(Boolean))];
|
||||||
|
const equipIds = [...new Set(rows.map((r) => r.equipId).filter(Boolean))];
|
||||||
|
|
||||||
|
const [customers, equips] = await Promise.all([
|
||||||
|
customerIds.length
|
||||||
|
? this.customerModel.findAll({
|
||||||
|
where: { id: { [Op.in]: customerIds } },
|
||||||
|
attributes: ["id", "customerNo", "customerName"]
|
||||||
|
})
|
||||||
|
: Promise.resolve([]),
|
||||||
|
equipIds.length
|
||||||
|
? this.equipmentModel.findAll({
|
||||||
|
where: { id: { [Op.in]: equipIds } },
|
||||||
|
attributes: ["id", "equipCode", "equipName"]
|
||||||
|
})
|
||||||
|
: Promise.resolve([])
|
||||||
|
]);
|
||||||
|
|
||||||
|
const customerMap = new Map(customers.map((c) => [c.id, c]));
|
||||||
|
const equipMap = new Map(equips.map((e) => [e.id, e]));
|
||||||
|
|
||||||
|
return rows.map((row) => {
|
||||||
|
const json: any = row.toJSON();
|
||||||
|
const customer: any = customerMap.get(row.customerId);
|
||||||
|
const equip: any = row.equipId ? equipMap.get(row.equipId) : undefined;
|
||||||
|
json.customerNo = customer?.customerNo || "";
|
||||||
|
json.customerName = customer?.customerName || "";
|
||||||
|
json.equipCode = equip?.equipCode || "";
|
||||||
|
json.equipName = equip?.equipName || "";
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import { Provide } from "@midwayjs/core";
|
||||||
|
import { InjectRepository } from "@midwayjs/sequelize";
|
||||||
|
import { Repository } from "sequelize-typescript";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
import { Context } from "@midwayjs/koa";
|
||||||
|
import ElectricGateway from "../entity/electric_gateway.entity";
|
||||||
|
import IccSyncLog from "../entity/icc_sync_log.entity";
|
||||||
|
import Customer from "../entity/customer.entity";
|
||||||
|
import { getIccAccessToken, iccFetch } from "../task/icc_customer_auth";
|
||||||
|
import { ICC_GATEWAY_ENDPOINT, mapIccGatewayRecord, parseGatewayPage, IccGatewayRawRecord } from "../task/icc_gateway_mapper";
|
||||||
|
|
||||||
|
export interface IccGatewaySyncResult {
|
||||||
|
/** ICC 拉取到的原始设备条数 */
|
||||||
|
pulled: number;
|
||||||
|
/** 成功落库的设备条数(新增+更新) */
|
||||||
|
synced: number;
|
||||||
|
/** 新增的设备条数 */
|
||||||
|
inserted: number;
|
||||||
|
/** 更新的设备条数 */
|
||||||
|
updated: number;
|
||||||
|
/** 跳过的条数(缺 imei/deviceCode / orgCode 匹配不到客户) */
|
||||||
|
skipped: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYNC_TYPE = "electric_gateway";
|
||||||
|
/** 分页拉取参数(ICC 设备分页接口约定,短页判定阈值) */
|
||||||
|
const PAGE_SIZE = 50;
|
||||||
|
/** 最大分页数保护,防止异常接口导致无限拉取 */
|
||||||
|
const MAX_PAGES = 100;
|
||||||
|
/** ICC_VERSION 缺省值(与 icc_customer_auth 保持一致) */
|
||||||
|
const DEFAULT_VERSION = "1.0.0";
|
||||||
|
/** HTTP 超时 60s(项目约定) */
|
||||||
|
const HTTP_TIMEOUT_MS = 60000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ICC 智电改造网关设备拉取同步:
|
||||||
|
* 1) 复用 icc_customer_auth 的真实 OAuth2 密码模式鉴权(getIccAccessToken / iccFetch),
|
||||||
|
* 从 .env 读取 ICC_BASE_URL / ICC_VERSION
|
||||||
|
* 2) POST 分页请求 ICC 设备列表(/device/subsystem/page,pageNum/pageSize=50,全量拉取)
|
||||||
|
* 3) 预取 orgCode→customer.id 映射(customer.icc_code),避免 N+1 查询客户
|
||||||
|
* 4) 按 imei/deviceCode 幂等 upsert 写入 electric_gateway,更新 synced_at
|
||||||
|
* 5) 每次同步写一条 icc_sync_log;拉取失败仅记录 error,不抛业务错误
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
export class IccGatewaySyncService {
|
||||||
|
@InjectRepository(ElectricGateway)
|
||||||
|
gatewayModel!: Repository<ElectricGateway>;
|
||||||
|
|
||||||
|
@InjectRepository(IccSyncLog)
|
||||||
|
syncLogModel!: Repository<IccSyncLog>;
|
||||||
|
|
||||||
|
@InjectRepository(Customer)
|
||||||
|
customerModel!: Repository<Customer>;
|
||||||
|
|
||||||
|
async syncFromIcc(ctx?: Context): Promise<IccGatewaySyncResult> {
|
||||||
|
const startTime = new Date();
|
||||||
|
const baseUrl = process.env.ICC_BASE_URL?.trim();
|
||||||
|
|
||||||
|
const result: IccGatewaySyncResult = { pulled: 0, synced: 0, inserted: 0, updated: 0, skipped: 0 };
|
||||||
|
|
||||||
|
// 基础配置缺失视为拉取失败:记录日志,不抛业务错误
|
||||||
|
if (!baseUrl) {
|
||||||
|
await this.writeLog(startTime, 0, SYNC_TYPE, 0, "未配置 ICC_BASE_URL,请检查 server/.env");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await getIccAccessToken();
|
||||||
|
const url = `${baseUrl.replace(/\/$/, "")}${ICC_GATEWAY_ENDPOINT.replace("{version}", process.env.ICC_VERSION?.trim() || DEFAULT_VERSION)}`;
|
||||||
|
|
||||||
|
const allRaw: IccGatewayRawRecord[] = [];
|
||||||
|
let pageNum = 1;
|
||||||
|
|
||||||
|
while (pageNum <= MAX_PAGES) {
|
||||||
|
const res = await iccFetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
Authorization: `bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ pageNum, pageSize: PAGE_SIZE }), // 不传 typeCode,全量拉取
|
||||||
|
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`ICC 网关设备接口返回异常:HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: any = await res.json();
|
||||||
|
const page = parseGatewayPage(payload);
|
||||||
|
allRaw.push(...page.list);
|
||||||
|
result.pulled += page.list.length;
|
||||||
|
|
||||||
|
// 短页判定:本页不满 pageSize 视为无下一页;另有最大页数保护
|
||||||
|
if (page.list.length < PAGE_SIZE) break;
|
||||||
|
pageNum++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { inserted, updated, skipped } = await this.batchUpsert(allRaw);
|
||||||
|
result.inserted = inserted;
|
||||||
|
result.updated = updated;
|
||||||
|
result.synced = inserted + updated;
|
||||||
|
result.skipped = skipped;
|
||||||
|
|
||||||
|
await this.writeLog(startTime, 1, SYNC_TYPE, result.synced, null);
|
||||||
|
return result;
|
||||||
|
} catch (err: any) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
await this.writeLog(startTime, 0, SYNC_TYPE, result.pulled, message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按 imei/deviceCode 幂等 upsert(幂等),customer_id 解析失败的行跳过。 */
|
||||||
|
private async batchUpsert(records: IccGatewayRawRecord[]): Promise<{ inserted: number; updated: number; skipped: number }> {
|
||||||
|
let inserted = 0;
|
||||||
|
let updated = 0;
|
||||||
|
let skipped = 0;
|
||||||
|
|
||||||
|
if (records.length === 0) return { inserted, updated, skipped };
|
||||||
|
|
||||||
|
// 预取 orgCode → customer.id 映射,避免 N+1 查询客户
|
||||||
|
const orgCodes = [...new Set(records.map((r) => mapIccGatewayRecord(r).orgCode).filter(Boolean))];
|
||||||
|
const customers = orgCodes.length
|
||||||
|
? await this.customerModel.findAll({ where: { iccCode: { [Op.in]: orgCodes } }, attributes: ["id", "iccCode"] })
|
||||||
|
: [];
|
||||||
|
const customerIdByOrgCode = new Map(customers.map((c) => [c.iccCode, c.id]));
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
for (const raw of records) {
|
||||||
|
const mapped = mapIccGatewayRecord(raw);
|
||||||
|
// imei 与 deviceCode 都为空则无同步匹配键,跳过
|
||||||
|
if (!mapped.imei && !mapped.deviceCode) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// customer_id NOT NULL:orgCode 必须能解析到客户,否则跳过
|
||||||
|
if (!mapped.orgCode) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const customerId = customerIdByOrgCode.get(mapped.orgCode);
|
||||||
|
if (!customerId) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
iccImei: mapped.imei || null,
|
||||||
|
deviceCode: mapped.deviceCode || null,
|
||||||
|
deviceName: mapped.deviceName || null,
|
||||||
|
customerId,
|
||||||
|
orgCode: mapped.orgCode || null,
|
||||||
|
orgName: mapped.orgName || null,
|
||||||
|
manufacturerCode: mapped.manufacturerCode || null,
|
||||||
|
manufacturerName: mapped.manufacturerName || null,
|
||||||
|
typeCode: mapped.typeCode || null,
|
||||||
|
typeName: mapped.typeName || null,
|
||||||
|
productModel: mapped.productModel || null,
|
||||||
|
isOnline: mapped.isOnline,
|
||||||
|
syncedAt: now
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// 幂等 upsert:按非空键匹配(仅包含非空键)
|
||||||
|
const matchWhere: any[] = [];
|
||||||
|
if (mapped.imei) matchWhere.push({ iccImei: mapped.imei });
|
||||||
|
if (mapped.deviceCode) matchWhere.push({ deviceCode: mapped.deviceCode });
|
||||||
|
|
||||||
|
const existing = await this.gatewayModel.findOne({ where: { [Op.or]: matchWhere } });
|
||||||
|
if (existing) {
|
||||||
|
await existing.update({ ...payload, syncedAt: now });
|
||||||
|
updated++;
|
||||||
|
} else {
|
||||||
|
await this.gatewayModel.create(payload);
|
||||||
|
inserted++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { inserted, updated, skipped };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async writeLog(startTime: Date, status: number, syncType: string, recordCount: number, errorMessage: string | null): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.syncLogModel.create({
|
||||||
|
syncType,
|
||||||
|
startTime,
|
||||||
|
endTime: new Date(),
|
||||||
|
recordCount,
|
||||||
|
status,
|
||||||
|
errorMessage
|
||||||
|
} as any);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[ICC同步] 同步日志写入失败:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { LogService } from "./log.service";
|
|||||||
import { NoticeRuleService } from "./notice_rule.service";
|
import { NoticeRuleService } from "./notice_rule.service";
|
||||||
import { IccLineSyncService } from "./icc_line_sync.service";
|
import { IccLineSyncService } from "./icc_line_sync.service";
|
||||||
import { IccCustomerSyncService } from "./icc_customer_sync.service";
|
import { IccCustomerSyncService } from "./icc_customer_sync.service";
|
||||||
|
import { IccGatewaySyncService } from "./icc_gateway_sync.service";
|
||||||
import { BuiltinJob, JobHandlerContext, JobRuntime, getBuiltinJob } from "../task/job_registry";
|
import { BuiltinJob, JobHandlerContext, JobRuntime, getBuiltinJob } from "../task/job_registry";
|
||||||
|
|
||||||
/** 任务执行结果(一次尝试) */
|
/** 任务执行结果(一次尝试) */
|
||||||
@@ -459,6 +460,12 @@ export class JobSchedulerService {
|
|||||||
const iccCustomerService = (await ctx.requestContext.getAsync(IccCustomerSyncService)) as IccCustomerSyncService;
|
const iccCustomerService = (await ctx.requestContext.getAsync(IccCustomerSyncService)) as IccCustomerSyncService;
|
||||||
const result = await iccCustomerService.syncFromIcc();
|
const result = await iccCustomerService.syncFromIcc();
|
||||||
return `拉取 ${result.pulled} 条,新增 ${result.inserted},更新 ${result.updated},跳过 ${result.skipped}`;
|
return `拉取 ${result.pulled} 条,新增 ${result.inserted},更新 ${result.updated},跳过 ${result.skipped}`;
|
||||||
|
},
|
||||||
|
syncIccGateways: async () => {
|
||||||
|
const ctx = this.app.createAnonymousContext();
|
||||||
|
const iccGatewayService = (await ctx.requestContext.getAsync(IccGatewaySyncService)) as IccGatewaySyncService;
|
||||||
|
const result = await iccGatewayService.syncFromIcc();
|
||||||
|
return `拉取 ${result.pulled} 条,新增 ${result.inserted},更新 ${result.updated},跳过 ${result.skipped}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
* ICC 网关设备数据字段映射(集中配置,便于按 ICC 平台实际返回结构调整)。
|
||||||
|
*
|
||||||
|
* ICC 返回的原始字段名 → 本系统 electric_gateway 字段。接入真实 ICC 平台时
|
||||||
|
* 仅需调整本文件与 fetch 路径即可,业务层无需改动。纯映射逻辑,无 SQL 无 DOM。
|
||||||
|
*/
|
||||||
|
export interface IccGatewayRawRecord {
|
||||||
|
[field: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 设备分页列表接口路径({version} 由调用方按 ICC_VERSION 替换) */
|
||||||
|
export const ICC_GATEWAY_ENDPOINT = "/evo-apigw/evo-fdbu/{version}/device/subsystem/page";
|
||||||
|
|
||||||
|
/** 原始记录关键字段映射:本系统字段 → ICC 返回字段名 */
|
||||||
|
export const ICC_GATEWAY_FIELDS = {
|
||||||
|
imei: "imei", // 设备唯一标识码(imei)
|
||||||
|
deviceCode: "deviceCode", // 设备编码
|
||||||
|
deviceName: "deviceName", // 设备名称
|
||||||
|
orgCode: "orgCode", // 所属组织编码(→ customer.icc_code 反查客户)
|
||||||
|
orgName: "orgName", // 所属组织名称
|
||||||
|
manufacturerCode: "manufacturerCode", // 厂商编码
|
||||||
|
manufacturerName: "manufacturerName", // 厂商名称
|
||||||
|
typeCode: "typeCode", // 设备类型编码
|
||||||
|
typeName: "typeName", // 设备类型名称
|
||||||
|
productModel: "productModel", // 产品型号
|
||||||
|
isOnline: "isOnline" // 在线状态
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/** 离线标记集合:命中即归一化为离线(0),其余(含空)均视为在线(1) */
|
||||||
|
const OFFLINE_MARKERS = new Set(["0", "02", "false", "no", "off", "离线"]);
|
||||||
|
|
||||||
|
/** 字符串 trim;null / undefined / 空串统一归一为 undefined。 */
|
||||||
|
function toOptionalString(value: unknown): string | undefined {
|
||||||
|
if (value === undefined || value === null) return undefined;
|
||||||
|
const s = String(value).trim();
|
||||||
|
return s === "" ? undefined : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将 ICC 原始设备记录映射为本系统 electric_gateway 落库字段(不含联表外键)。 */
|
||||||
|
export function mapIccGatewayRecord(raw: IccGatewayRawRecord): {
|
||||||
|
imei: string;
|
||||||
|
deviceCode: string;
|
||||||
|
deviceName?: string;
|
||||||
|
orgCode?: string;
|
||||||
|
orgName?: string;
|
||||||
|
manufacturerCode?: string;
|
||||||
|
manufacturerName?: string;
|
||||||
|
typeCode?: string;
|
||||||
|
typeName?: string;
|
||||||
|
productModel?: string;
|
||||||
|
isOnline: number;
|
||||||
|
} {
|
||||||
|
const result: Record<string, any> = {
|
||||||
|
// 匹配键保持空串语义,由调用方判跳
|
||||||
|
imei: String(raw[ICC_GATEWAY_FIELDS.imei] ?? "").trim(),
|
||||||
|
deviceCode: String(raw[ICC_GATEWAY_FIELDS.deviceCode] ?? "").trim()
|
||||||
|
};
|
||||||
|
// 可选字符串字段
|
||||||
|
for (const key of ["deviceName", "orgCode", "orgName", "manufacturerCode", "manufacturerName", "typeCode", "typeName", "productModel"] as const) {
|
||||||
|
result[key] = toOptionalString(raw[ICC_GATEWAY_FIELDS[key]]);
|
||||||
|
}
|
||||||
|
// 在线状态归一化:离线标记集合 → 0,其余(含空)→ 1
|
||||||
|
const onlineRaw = String(raw[ICC_GATEWAY_FIELDS.isOnline] ?? "").trim();
|
||||||
|
result.isOnline = OFFLINE_MARKERS.has(onlineRaw) ? 0 : 1;
|
||||||
|
return result as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析设备分页响应(兼容三种形态):
|
||||||
|
* - payload 为数组 → 直接作为列表
|
||||||
|
* - payload.data.pageData 为数组 → 取 pageData
|
||||||
|
* - payload.data.list 为数组 → 取 list
|
||||||
|
* - 结构不匹配或解析异常 → 返回空列表
|
||||||
|
*/
|
||||||
|
export function parseGatewayPage(payload: any): {
|
||||||
|
list: IccGatewayRawRecord[];
|
||||||
|
nextPage?: number;
|
||||||
|
totalCount?: number;
|
||||||
|
} {
|
||||||
|
try {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
return { list: payload };
|
||||||
|
}
|
||||||
|
const page = payload?.data;
|
||||||
|
if (page && Array.isArray(page.pageData)) {
|
||||||
|
return {
|
||||||
|
list: page.pageData,
|
||||||
|
nextPage: Number(page.nextPage) > 0 ? Number(page.nextPage) : undefined,
|
||||||
|
totalCount: Number(page.totalCount) > 0 ? Number(page.totalCount) : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (page && Array.isArray(page.list)) {
|
||||||
|
return {
|
||||||
|
list: page.list,
|
||||||
|
nextPage: Number(page.nextPage) > 0 ? Number(page.nextPage) : undefined,
|
||||||
|
totalCount: Number(page.totalCount) > 0 ? Number(page.totalCount) : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { list: [] };
|
||||||
|
} catch {
|
||||||
|
return { list: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import type { BuiltinJob } from "./job_registry";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智电网关同步任务:从 ICC 平台拉取智电改造网关设备并按 imei/deviceCode 幂等 upsert,
|
||||||
|
* 写 icc_sync_log。默认每 10 分钟执行一次。
|
||||||
|
*/
|
||||||
|
export const iccGatewaySyncJob: BuiltinJob = {
|
||||||
|
jobKey: "icc_gateway_sync",
|
||||||
|
jobName: "智电网关同步",
|
||||||
|
description: "从 ICC 平台拉取智电改造网关设备并按 imei/deviceCode 幂等 upsert(写 icc_sync_log)",
|
||||||
|
defaultCron: "*/10 * * * *",
|
||||||
|
defaultParams: {},
|
||||||
|
paramFields: [],
|
||||||
|
handler: async (_params, ctx) => {
|
||||||
|
return ctx.runtime.syncIccGateways();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { logCleanJob } from "./log_clean.task";
|
|||||||
import { noticeScanJob } from "./notice_scan.task";
|
import { noticeScanJob } from "./notice_scan.task";
|
||||||
import { iccLineSyncJob } from "./icc_line_sync.task";
|
import { iccLineSyncJob } from "./icc_line_sync.task";
|
||||||
import { iccCustomerSyncJob } from "./icc_customer_sync.task";
|
import { iccCustomerSyncJob } from "./icc_customer_sync.task";
|
||||||
|
import { iccGatewaySyncJob } from "./icc_gateway_sync.task";
|
||||||
|
|
||||||
export type JobParamType = "number" | "string";
|
export type JobParamType = "number" | "string";
|
||||||
|
|
||||||
@@ -35,6 +36,8 @@ export interface JobRuntime {
|
|||||||
syncIccLines(): Promise<string>;
|
syncIccLines(): Promise<string>;
|
||||||
/** 从 ICC 平台拉取 org_base 组织并按 iccCode 幂等新增/变更客户,返回执行摘要 */
|
/** 从 ICC 平台拉取 org_base 组织并按 iccCode 幂等新增/变更客户,返回执行摘要 */
|
||||||
syncIccCustomers(): Promise<string>;
|
syncIccCustomers(): Promise<string>;
|
||||||
|
/** 从 ICC 平台拉取智电改造网关设备并按 imei/deviceCode 幂等 upsert,返回执行摘要 */
|
||||||
|
syncIccGateways(): Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JobHandlerContext {
|
export interface JobHandlerContext {
|
||||||
@@ -61,7 +64,7 @@ export interface BuiltinJob {
|
|||||||
handler: JobHandler;
|
handler: JobHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BUILTIN_JOBS: BuiltinJob[] = [demoHeartbeatJob, logCleanJob, noticeScanJob, iccLineSyncJob, iccCustomerSyncJob];
|
export const BUILTIN_JOBS: BuiltinJob[] = [demoHeartbeatJob, logCleanJob, noticeScanJob, iccLineSyncJob, iccCustomerSyncJob, iccGatewaySyncJob];
|
||||||
|
|
||||||
export function getBuiltinJob(jobKey: string): BuiltinJob | undefined {
|
export function getBuiltinJob(jobKey: string): BuiltinJob | undefined {
|
||||||
return BUILTIN_JOBS.find(job => job.jobKey === jobKey);
|
return BUILTIN_JOBS.find(job => job.jobKey === jobKey);
|
||||||
|
|||||||
+4
-4
@@ -4537,10 +4537,10 @@ undici-types@~6.21.0:
|
|||||||
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz"
|
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz"
|
||||||
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
|
||||||
|
|
||||||
undici@^8.12.2:
|
undici@^8.12.3:
|
||||||
version "8.12.2"
|
version "8.12.3"
|
||||||
resolved "https://registry.yarnpkg.com/undici/-/undici-8.12.2.tgz#e869237430adcc5de6bca798c94edd7db9dac5ac"
|
resolved "https://registry.yarnpkg.com/undici/-/undici-8.12.3.tgz#e76a91feee9c275ad0f43bfc0eb632b6124d3bb6"
|
||||||
integrity sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==
|
integrity sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^0.1.0:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
|
|||||||
Reference in new issue
Block a user