20260730173012
This commit is contained in:
1 parent
84673b3d3d
commit
4929b78434
25 files changed
+1796
-68
No files matched your search
@@ -0,0 +1,117 @@
|
|||||||
|
---
|
||||||
|
name: field-name-sync-from-old-db
|
||||||
|
overview: 创建可复用的字段名同步服务,从旧库 s_columnLib.col_Caption 同步到新库 s_module_field.b_name,按视图名和字段名匹配,首批执行 v_b_othercompany。
|
||||||
|
todos:
|
||||||
|
- id: fix-old-db-ssl
|
||||||
|
content: 修复 g3hd-old.properties:移除 sslProtocol=TLSv1 参数,JDK 21 驱动自动协商 TLSv1.2 即可连通
|
||||||
|
status: completed
|
||||||
|
- id: create-sync-service
|
||||||
|
content: 新建 FieldNameSyncService.java:实现跨库 UPDATE SQL,按 viewName 匹配 s_columnLib,将 col_Caption 写入 s_module_field.b_name,不匹配字段不改动,返回更新行数
|
||||||
|
status: completed
|
||||||
|
dependencies:
|
||||||
|
- fix-old-db-ssl
|
||||||
|
- id: create-admin-controller
|
||||||
|
content: 新建 AdminController.java,添加 POST /api/admin/sync-field-names 端点,JWT 认证,viewName 参数白名单校验,调用 FieldNameSyncService 并返回 ApiResponse
|
||||||
|
status: completed
|
||||||
|
dependencies:
|
||||||
|
- create-sync-service
|
||||||
|
- id: add-sql-migration-ref
|
||||||
|
content: 新建 023_sync_field_names.sql 参考脚本,记录跨库同步 SQL 作为纯 SQL 手动执行备选方案
|
||||||
|
status: completed
|
||||||
|
---
|
||||||
|
|
||||||
|
## 用户需求
|
||||||
|
|
||||||
|
构建一个可复用的工具,将旧库 `G3HY2025.dbo.s_columnLib` 中存储的字段中文名称(`col_Caption`)同步到新库 `fms.dbo.s_module_field.b_name`。
|
||||||
|
|
||||||
|
### 匹配规则
|
||||||
|
|
||||||
|
- `s_module.b_viewtable` = `s_columnLib.col_TableName`(模块视图名对应旧库表名)
|
||||||
|
- `s_module_field.b_field` = `s_columnLib.col_FieldName`(字段名精确匹配)
|
||||||
|
- 匹配不上的字段,`b_name` 保持不变
|
||||||
|
|
||||||
|
### 首批同步目标
|
||||||
|
|
||||||
|
视图 `v_b_othercompany`
|
||||||
|
|
||||||
|
### 可复用要求
|
||||||
|
|
||||||
|
以后还有更多视图需要同步,工具须参数化视图名,方便批量复用。
|
||||||
|
|
||||||
|
## 技术方案
|
||||||
|
|
||||||
|
### 方案选择:跨库 SQL + 管理端点
|
||||||
|
|
||||||
|
**核心决策**:新旧库在同一 SQL Server 实例(`118.89.70.199:1433`),使用**跨数据库 SQL** 直接从新库连接执行 `UPDATE ... FROM G3HY2025.dbo.s_columnLib`,无需单独连接旧库。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
UPDATE sf
|
||||||
|
SET sf.b_name = LTRIM(RTRIM(sc.col_Caption))
|
||||||
|
FROM dbo.s_module_field sf
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
JOIN G3HY2025.dbo.s_columnLib sc
|
||||||
|
ON sc.col_TableName = m.b_viewtable
|
||||||
|
AND sc.col_FieldName = sf.b_field
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
AND sc.col_Caption IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(sc.col_Caption)) != '';
|
||||||
|
```
|
||||||
|
|
||||||
|
**为何需要新端点而非复用通用端点**:
|
||||||
|
|
||||||
|
- 通用 `saveObjectApi` 只能操作当前 org 数据库的单表 insert/update/delete,无法表达跨库 JOIN 逻辑
|
||||||
|
- 通用 `loadDataApi` 只能查询当前 org 的表,无法读取旧库 `s_columnLib`
|
||||||
|
- 此操作为管理类批量数据同步,不在业务 CRUD 范畴内
|
||||||
|
|
||||||
|
### 实现架构
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[前端/管理员] -->|POST /api/admin/sync-field-names| B[AdminController]
|
||||||
|
B -->|校验 viewName 参数| C[FieldNameSyncService]
|
||||||
|
C -->|获取新库 Connection| D[OrgRoutingDataSource]
|
||||||
|
D -->|fms 连接| E[(SQL Server)]
|
||||||
|
E -->|跨库 JOIN| F[G3HY2025.dbo.s_columnLib]
|
||||||
|
E -->|UPDATE| G[fms.dbo.s_module_field]
|
||||||
|
C -->|返回同步数量| B
|
||||||
|
B -->|ApiResponse| A
|
||||||
|
```
|
||||||
|
|
||||||
|
### 模块划分
|
||||||
|
|
||||||
|
| 模块 | 文件 | 职责 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 配置修复 | `config/dbconfigs/g3hd-old.properties` | 移除 `sslProtocol=TLSv1` |
|
||||||
|
| 同步服务 | `service/FieldNameSyncService.java` | 执行跨库 UPDATE,校验参数,返回影响行数 |
|
||||||
|
| 管理控制器 | `controller/AdminController.java` | `POST /api/admin/sync-field-names` 端点,JWT 保护 |
|
||||||
|
| 参考脚本 | `config/migrations/023_sync_field_names.sql` | 纯 SQL 版本供手动执行 |
|
||||||
|
|
||||||
|
|
||||||
|
### 数据流
|
||||||
|
|
||||||
|
1. 前端/管理员发送 `POST /api/admin/sync-field-names`,请求体 `{ "viewName": "v_b_othercompany" }`
|
||||||
|
2. `AdminController` 校验 `viewName` 为合法标识符(字母/下划线/数字,最长128)
|
||||||
|
3. `FieldNameSyncService` 通过 `@Resource DataSource` 获取新库连接
|
||||||
|
4. 执行跨库 PreparedStatement UPDATE
|
||||||
|
5. 返回 `{ "updatedCount": 42, "message": "v_b_othercompany 同步完成,共更新 42 个字段名称" }`
|
||||||
|
|
||||||
|
### 安全措施
|
||||||
|
|
||||||
|
- `viewName` 参数:白名单正则 `^[A-Za-z_][A-Za-z0-9_]{0,127},校验后作为 PreparedStatement 参数绑定(非字符串拼接)
|
||||||
|
- JWT 认证:`AdminController` 路径 `/api/admin/**` 经 `JwtAuthFilter` 保护,仅登录用户可调用
|
||||||
|
- 失败不影响现有数据:UPDATE 在一条 SQL 内执行,单条匹配失败不阻断其他行
|
||||||
|
|
||||||
|
### 执行说明
|
||||||
|
|
||||||
|
- **热路径**:单条 UPDATE SQL 跨库 JOIN,性能取决于 `s_module_field` 行数和索引;对单个视图(~50 字段)毫秒级完成
|
||||||
|
- **日志**:服务层记录同步开始/完成/失败日志,使用 `@Slf4j`,级别 INFO/ERROR
|
||||||
|
- **事务**:使用 Spring 声明式事务 `@Transactional`,确保整批同步原子提交或回滚
|
||||||
|
- **向后兼容**:不修改现有 `DataController` 和通用端点,不影响业务数据流
|
||||||
|
|
||||||
|
## Agent Extensions
|
||||||
|
|
||||||
|
### SubAgent
|
||||||
|
|
||||||
|
- **code-explorer**
|
||||||
|
- 目的:探索旧库连接方式、现有 Service/Controller 模式、参数校验惯例
|
||||||
|
- 预期结果:确认 `OrgDataSourceManager` 的 API 使用方式和现有异常处理/日志模式
|
||||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
登录日志表 s_log_login:记录每次登录尝试(成功/失败)的客户端环境与结果,用于登录审计。
|
登录日志表 s_login_log:记录每次登录尝试(成功/失败)的客户端环境与结果,用于登录审计。
|
||||||
后端在登录时统一采集 IP、IP 归属地、浏览器/版本、操作系统/版本、设备类型、原始 User-Agent,
|
后端在登录时统一采集 IP、IP 归属地、浏览器/版本、操作系统/版本、设备类型、原始 User-Agent,
|
||||||
并通过通用 saveobjt 接口写入本表;前端通过分页接口只读展示。
|
并通过通用 saveobjt 接口写入本表;前端通过分页接口只读展示。
|
||||||
表名使用 s_ 前缀以区别于业务模块表(b_)。
|
表名使用 s_ 前缀以区别于业务模块表(b_)。
|
||||||
@@ -10,14 +10,14 @@ SET NOCOUNT ON;
|
|||||||
|
|
||||||
BEGIN TRANSACTION;
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = N's_log_login')
|
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = N's_login_log')
|
||||||
BEGIN
|
BEGIN
|
||||||
CREATE TABLE dbo.s_log_login (
|
CREATE TABLE dbo.s_login_log (
|
||||||
b_id bigint NOT NULL,
|
b_id bigint NOT NULL,
|
||||||
b_org_id nvarchar(50) NULL,
|
b_org_id nvarchar(50) NULL,
|
||||||
b_user_id bigint NULL,
|
b_user_id bigint NULL,
|
||||||
b_account nvarchar(50) NULL,
|
b_account nvarchar(50) NULL,
|
||||||
b_login_time datetime NOT NULL CONSTRAINT DF_s_log_login_time DEFAULT (GETDATE()),
|
b_login_time datetime NOT NULL CONSTRAINT DF_s_login_log_time DEFAULT (GETDATE()),
|
||||||
b_ip nvarchar(50) NULL,
|
b_ip nvarchar(50) NULL,
|
||||||
b_ip_location nvarchar(200) NULL,
|
b_ip_location nvarchar(200) NULL,
|
||||||
b_browser nvarchar(100) NULL,
|
b_browser nvarchar(100) NULL,
|
||||||
@@ -26,15 +26,15 @@ BEGIN TRANSACTION;
|
|||||||
b_os_version nvarchar(50) NULL,
|
b_os_version nvarchar(50) NULL,
|
||||||
b_device_type nvarchar(20) NULL,
|
b_device_type nvarchar(20) NULL,
|
||||||
b_user_agent nvarchar(max) NULL,
|
b_user_agent nvarchar(max) NULL,
|
||||||
b_login_result int NOT NULL CONSTRAINT DF_s_log_login_result DEFAULT (0),
|
b_login_result int NOT NULL CONSTRAINT DF_s_login_log_result DEFAULT (0),
|
||||||
b_fail_reason nvarchar(200) NULL,
|
b_fail_reason nvarchar(200) NULL,
|
||||||
b_session_id nvarchar(50) NULL,
|
b_session_id nvarchar(50) NULL,
|
||||||
CONSTRAINT PK_s_log_login PRIMARY KEY (b_id)
|
CONSTRAINT PK_s_login_log PRIMARY KEY (b_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IX_s_log_login_time ON dbo.s_log_login (b_login_time DESC, b_id);
|
CREATE INDEX IX_s_login_log_time ON dbo.s_login_log (b_login_time DESC, b_id);
|
||||||
CREATE INDEX IX_s_log_login_account ON dbo.s_log_login (b_account, b_login_time DESC);
|
CREATE INDEX IX_s_login_log_account ON dbo.s_login_log (b_account, b_login_time DESC);
|
||||||
CREATE INDEX IX_s_log_login_result ON dbo.s_log_login (b_login_result, b_login_time DESC);
|
CREATE INDEX IX_s_login_log_result ON dbo.s_login_log (b_login_result, b_login_time DESC);
|
||||||
END
|
END
|
||||||
|
|
||||||
COMMIT TRANSACTION;
|
COMMIT TRANSACTION;
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
/* 通用 "其他数据" 模块配置。左侧树,右侧按数据模块加载不同表格(独立列定义)。*/
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
1. 确定 base 目录
|
||||||
|
========================================================================= */
|
||||||
|
DECLARE @BaseId bigint = (
|
||||||
|
SELECT TOP (1) b_id
|
||||||
|
FROM dbo.s_module
|
||||||
|
WHERE b_code = 'base' AND b_module_type = 'directory'
|
||||||
|
ORDER BY b_id
|
||||||
|
);
|
||||||
|
IF @BaseId IS NULL
|
||||||
|
THROW 52001, N'base directory not found. Run 012 / 002 migration first.', 1;
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
2. 创建 page 模块: m_base_otherdata
|
||||||
|
========================================================================= */
|
||||||
|
DECLARE @PageId bigint = 920000000000001100;
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = 'm_base_otherdata')
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @PageId)
|
||||||
|
THROW 52002, N'The reserved otherdata page ID is already in use.', 1;
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module (
|
||||||
|
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type,
|
||||||
|
b_route, b_viewtable, b_savetable, b_keyfield, b_orderfield,
|
||||||
|
b_canmenu, b_canuse, b_xh
|
||||||
|
) VALUES (
|
||||||
|
@PageId, 'm_base_otherdata', @BaseId, N'其他数据',
|
||||||
|
'menu.base_otherdata', 'page', '/base/otherdata',
|
||||||
|
NULL, NULL, NULL, NULL, 1, 1, 30
|
||||||
|
);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
SET @PageId = (SELECT b_id FROM dbo.s_module WHERE b_code = 'm_base_otherdata');
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
3. 创建 data 模块辅助存储过程
|
||||||
|
========================================================================= */
|
||||||
|
DECLARE @DataId bigint;
|
||||||
|
DECLARE @DataCode varchar(100);
|
||||||
|
DECLARE @DataName nvarchar(200);
|
||||||
|
DECLARE @ViewTable varchar(100);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
4. 港口 d_other_port → b_port
|
||||||
|
========================================================================= */
|
||||||
|
SET @DataCode = 'd_other_port';
|
||||||
|
SET @DataName = N'港口';
|
||||||
|
SET @ViewTable = 'b_port';
|
||||||
|
SET @DataId = 920000000000001101;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @DataId)
|
||||||
|
THROW 52003, N'The reserved port data ID is already in use.', 1;
|
||||||
|
INSERT INTO dbo.s_module (
|
||||||
|
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type,
|
||||||
|
b_route, b_viewtable, b_savetable, b_keyfield, b_orderfield,
|
||||||
|
b_canmenu, b_canuse, b_xh
|
||||||
|
) VALUES (
|
||||||
|
@DataId, @DataCode, @PageId, @DataName,
|
||||||
|
'module.d_other_port', 'data', NULL,
|
||||||
|
'b_port', 'b_port', 'b_id', 'b_code ASC', 0, 1, 10
|
||||||
|
);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
SET @DataId = (SELECT b_id FROM dbo.s_module WHERE b_code = @DataCode);
|
||||||
|
|
||||||
|
-- 港口字段
|
||||||
|
DECLARE @PortFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @PortFields VALUES
|
||||||
|
(920000000000010101, 'b_id', N'系统编号', 'input', 10),
|
||||||
|
(920000000000010102, 'b_code', N'港口代码', 'input', 20),
|
||||||
|
(920000000000010103, 'b_name_c', N'中文名称', 'input', 30),
|
||||||
|
(920000000000010104, 'b_name_e', N'英文名称', 'input', 40),
|
||||||
|
(920000000000010105, 'b_country', N'国家', 'input', 50),
|
||||||
|
(920000000000010106, 'b_oceanline', N'航线', 'input', 60),
|
||||||
|
(920000000000010107, 'b_edicode', N'EDI代码', 'input', 70),
|
||||||
|
(920000000000010108, 'b_bz', N'备注', 'input', 80),
|
||||||
|
(920000000000010109, 'b_tz', N'特征', 'input', 90);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, source.b_field, source.b_name,
|
||||||
|
'field.d_other_port.' + source.b_field, source.b_type, 1
|
||||||
|
FROM @PortFields source
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field t WHERE t.b_module_id = @DataId AND t.b_field = source.b_field
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 港口列表列
|
||||||
|
DECLARE @PortList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
|
||||||
|
INSERT INTO @PortList VALUES
|
||||||
|
(920000000000020101, 'b_code', 10, 120),
|
||||||
|
(920000000000020102, 'b_name_c', 20, 150),
|
||||||
|
(920000000000020103, 'b_name_e', 30, 180),
|
||||||
|
(920000000000020104, 'b_country', 40, 120),
|
||||||
|
(920000000000020105, 'b_oceanline', 50, 120),
|
||||||
|
(920000000000020106, 'b_edicode', 60, 120);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, NULL, 1, source.b_xh, source.b_width
|
||||||
|
FROM @PortList source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 港口查询字段
|
||||||
|
DECLARE @PortQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @PortQuery VALUES
|
||||||
|
(920000000000030101, 'b_code', 10),
|
||||||
|
(920000000000030102, 'b_name_c', 20),
|
||||||
|
(920000000000030103, 'b_name_e', 30);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
|
||||||
|
FROM @PortQuery source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
5. 城市 d_other_city → b_city
|
||||||
|
========================================================================= */
|
||||||
|
SET @DataCode = 'd_other_city';
|
||||||
|
SET @DataName = N'城市';
|
||||||
|
SET @ViewTable = 'b_city';
|
||||||
|
SET @DataId = 920000000000001102;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @DataId)
|
||||||
|
THROW 52004, N'The reserved city data ID is already in use.', 1;
|
||||||
|
INSERT INTO dbo.s_module (
|
||||||
|
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type,
|
||||||
|
b_route, b_viewtable, b_savetable, b_keyfield, b_orderfield,
|
||||||
|
b_canmenu, b_canuse, b_xh
|
||||||
|
) VALUES (
|
||||||
|
@DataId, @DataCode, @PageId, @DataName,
|
||||||
|
'module.d_other_city', 'data', NULL,
|
||||||
|
'b_city', 'b_city', 'b_id', 'b_code ASC', 0, 1, 20
|
||||||
|
);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
SET @DataId = (SELECT b_id FROM dbo.s_module WHERE b_code = @DataCode);
|
||||||
|
|
||||||
|
-- 城市字段
|
||||||
|
DECLARE @CityFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @CityFields VALUES
|
||||||
|
(920000000000010201, 'b_id', N'系统编号', 'input', 10),
|
||||||
|
(920000000000010202, 'b_code', N'城市代码', 'input', 20),
|
||||||
|
(920000000000010203, 'b_name_c', N'中文名称', 'input', 30),
|
||||||
|
(920000000000010204, 'b_name_e', N'英文名称', 'input', 40),
|
||||||
|
(920000000000010205, 'b_country_name_c', N'国家(中)', 'input', 50),
|
||||||
|
(920000000000010206, 'b_country_name_e', N'国家(英)', 'input', 60),
|
||||||
|
(920000000000010207, 'b_bz', N'备注', 'input', 70);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, source.b_field, source.b_name,
|
||||||
|
'field.d_other_city.' + source.b_field, source.b_type, 1
|
||||||
|
FROM @CityFields source
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field t WHERE t.b_module_id = @DataId AND t.b_field = source.b_field
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 城市列表列
|
||||||
|
DECLARE @CityList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
|
||||||
|
INSERT INTO @CityList VALUES
|
||||||
|
(920000000000020201, 'b_code', 10, 120),
|
||||||
|
(920000000000020202, 'b_name_c', 20, 150),
|
||||||
|
(920000000000020203, 'b_name_e', 30, 180),
|
||||||
|
(920000000000020204, 'b_country_name_c', 40, 140),
|
||||||
|
(920000000000020205, 'b_country_name_e', 50, 140);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, NULL, 1, source.b_xh, source.b_width
|
||||||
|
FROM @CityList source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 城市查询字段
|
||||||
|
DECLARE @CityQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @CityQuery VALUES
|
||||||
|
(920000000000030201, 'b_code', 10),
|
||||||
|
(920000000000030202, 'b_name_c', 20);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
|
||||||
|
FROM @CityQuery source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
6. 币种 d_other_money → b_money
|
||||||
|
========================================================================= */
|
||||||
|
SET @DataCode = 'd_other_money';
|
||||||
|
SET @DataName = N'币种';
|
||||||
|
SET @ViewTable = 'b_money';
|
||||||
|
SET @DataId = 920000000000001103;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @DataId)
|
||||||
|
THROW 52005, N'The reserved money data ID is already in use.', 1;
|
||||||
|
INSERT INTO dbo.s_module (
|
||||||
|
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type,
|
||||||
|
b_route, b_viewtable, b_savetable, b_keyfield, b_orderfield,
|
||||||
|
b_canmenu, b_canuse, b_xh
|
||||||
|
) VALUES (
|
||||||
|
@DataId, @DataCode, @PageId, @DataName,
|
||||||
|
'module.d_other_money', 'data', NULL,
|
||||||
|
'b_money', 'b_money', 'b_id', 'b_code ASC', 0, 1, 30
|
||||||
|
);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
SET @DataId = (SELECT b_id FROM dbo.s_module WHERE b_code = @DataCode);
|
||||||
|
|
||||||
|
-- 币种字段
|
||||||
|
DECLARE @MoneyFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @MoneyFields VALUES
|
||||||
|
(920000000000010301, 'b_id', N'系统编号', 'input', 10),
|
||||||
|
(920000000000010302, 'b_code', N'币种代码', 'input', 20),
|
||||||
|
(920000000000010303, 'b_name', N'名称', 'input', 30),
|
||||||
|
(920000000000010304, 'b_edicode', N'EDI代码', 'input', 40),
|
||||||
|
(920000000000010305, 'b_symbol', N'符号', 'input', 50),
|
||||||
|
(920000000000010306, 'b_hl', N'汇率', 'number', 60),
|
||||||
|
(920000000000010307, 'b_isconvert', N'可兑换', 'checkbox', 70),
|
||||||
|
(920000000000010308, 'b_canuse', N'启用', 'checkbox', 80),
|
||||||
|
(920000000000010309, 'b_xh', N'排序', 'number', 90),
|
||||||
|
(920000000000010310, 'b_bz', N'备注', 'input', 100);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, source.b_field, source.b_name,
|
||||||
|
'field.d_other_money.' + source.b_field, source.b_type, 1
|
||||||
|
FROM @MoneyFields source
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field t WHERE t.b_module_id = @DataId AND t.b_field = source.b_field
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 币种列表列
|
||||||
|
DECLARE @MoneyList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
|
||||||
|
INSERT INTO @MoneyList VALUES
|
||||||
|
(920000000000020301, 'b_code', 10, 120),
|
||||||
|
(920000000000020302, 'b_name', 20, 150),
|
||||||
|
(920000000000020303, 'b_edicode', 30, 120),
|
||||||
|
(920000000000020304, 'b_symbol', 40, 80),
|
||||||
|
(920000000000020305, 'b_hl', 50, 120),
|
||||||
|
(920000000000020306, 'b_canuse', 60, 80);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, NULL, 1, source.b_xh, source.b_width
|
||||||
|
FROM @MoneyList source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 币种查询字段
|
||||||
|
DECLARE @MoneyQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @MoneyQuery VALUES
|
||||||
|
(920000000000030301, 'b_code', 10),
|
||||||
|
(920000000000030302, 'b_name', 20);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
|
||||||
|
FROM @MoneyQuery source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
7. 航线 d_other_oceanline → b_oceanline
|
||||||
|
========================================================================= */
|
||||||
|
SET @DataCode = 'd_other_oceanline';
|
||||||
|
SET @DataName = N'航线';
|
||||||
|
SET @ViewTable = 'b_oceanline';
|
||||||
|
SET @DataId = 920000000000001104;
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.s_module WHERE b_code = @DataCode)
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @DataId)
|
||||||
|
THROW 52006, N'The reserved oceanline data ID is already in use.', 1;
|
||||||
|
INSERT INTO dbo.s_module (
|
||||||
|
b_id, b_code, b_parent_id, b_name, b_i18n, b_module_type,
|
||||||
|
b_route, b_viewtable, b_savetable, b_keyfield, b_orderfield,
|
||||||
|
b_canmenu, b_canuse, b_xh
|
||||||
|
) VALUES (
|
||||||
|
@DataId, @DataCode, @PageId, @DataName,
|
||||||
|
'module.d_other_oceanline', 'data', NULL,
|
||||||
|
'b_oceanline', 'b_oceanline', 'b_id', 'b_code ASC', 0, 1, 40
|
||||||
|
);
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
SET @DataId = (SELECT b_id FROM dbo.s_module WHERE b_code = @DataCode);
|
||||||
|
|
||||||
|
-- 航线字段
|
||||||
|
DECLARE @OceanlineFields TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_type varchar(30) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @OceanlineFields VALUES
|
||||||
|
(920000000000010401, 'b_id', N'系统编号', 'input', 10),
|
||||||
|
(920000000000010402, 'b_code', N'航线代码', 'input', 20),
|
||||||
|
(920000000000010403, 'b_name', N'名称', 'input', 30),
|
||||||
|
(920000000000010404, 'b_edicode', N'EDI代码', 'input', 40),
|
||||||
|
(920000000000010405, 'b_bz', N'备注', 'input', 50);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field (b_id, b_module_id, b_field, b_name, b_i18n, b_type, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, source.b_field, source.b_name,
|
||||||
|
'field.d_other_oceanline.' + source.b_field, source.b_type, 1
|
||||||
|
FROM @OceanlineFields source
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field t WHERE t.b_module_id = @DataId AND t.b_field = source.b_field
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 航线列表列
|
||||||
|
DECLARE @OceanlineList TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL, b_width int NOT NULL);
|
||||||
|
INSERT INTO @OceanlineList VALUES
|
||||||
|
(920000000000020401, 'b_code', 10, 120),
|
||||||
|
(920000000000020402, 'b_name', 20, 180),
|
||||||
|
(920000000000020403, 'b_edicode', 30, 120);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_i18n, b_visible, b_xh, b_width)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, NULL, 1, source.b_xh, source.b_width
|
||||||
|
FROM @OceanlineList source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_list t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 航线查询字段
|
||||||
|
DECLARE @OceanlineQuery TABLE (b_id bigint NOT NULL, b_field varchar(50) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @OceanlineQuery VALUES
|
||||||
|
(920000000000030401, 'b_code', 10),
|
||||||
|
(920000000000030402, 'b_name', 20);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_field_query (b_id, b_module_id, b_field_id, b_component, b_operator, b_default_value, b_xh, b_canuse)
|
||||||
|
SELECT source.b_id, @DataId, f.b_id, 'input', 'like', NULL, source.b_xh, 1
|
||||||
|
FROM @OceanlineQuery source
|
||||||
|
JOIN dbo.s_module_field f ON f.b_module_id = @DataId AND f.b_field = source.b_field
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_query t WHERE t.b_module_id = @DataId AND t.b_field_id = f.b_id
|
||||||
|
);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
8. s_module_power 权限
|
||||||
|
========================================================================= */
|
||||||
|
DECLARE @Powers TABLE (b_id bigint NOT NULL, b_code varchar(50) NOT NULL, b_name nvarchar(200) NOT NULL, b_xh int NOT NULL);
|
||||||
|
INSERT INTO @Powers VALUES
|
||||||
|
(920000000000040101, 'create', N'新增', 10),
|
||||||
|
(920000000000040102, 'update', N'修改', 20),
|
||||||
|
(920000000000040103, 'delete', N'删除', 30);
|
||||||
|
|
||||||
|
INSERT INTO dbo.s_module_power (b_id, b_module_id, b_code, b_name, b_i18n, b_xh, b_canuse)
|
||||||
|
SELECT source.b_id, @PageId, source.b_code, source.b_name,
|
||||||
|
'power.base_otherdata.' + source.b_code, source.b_xh, 1
|
||||||
|
FROM @Powers source
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_power t
|
||||||
|
WHERE t.b_module_id = @PageId AND t.b_code = source.b_code
|
||||||
|
);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
9. Mock 测试数据
|
||||||
|
========================================================================= */
|
||||||
|
-- 港口
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_port WHERE b_code = 'CNSHA')
|
||||||
|
INSERT INTO dbo.b_port (b_id, b_code, b_name_c, b_name_e, b_country, b_oceanline, b_edicode, b_bz, b_tz) VALUES
|
||||||
|
(920000000000110001, 'CNSHA', N'上海港', 'Shanghai', N'中国', 'ASIA', 'CNSHA', NULL, NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_port WHERE b_code = 'CNNGB')
|
||||||
|
INSERT INTO dbo.b_port (b_id, b_code, b_name_c, b_name_e, b_country, b_oceanline, b_edicode, b_bz, b_tz) VALUES
|
||||||
|
(920000000000110002, 'CNNGB', N'宁波港', 'Ningbo', N'中国', 'ASIA', 'CNNGB', NULL, NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_port WHERE b_code = 'HKKG')
|
||||||
|
INSERT INTO dbo.b_port (b_id, b_code, b_name_c, b_name_e, b_country, b_oceanline, b_edicode, b_bz, b_tz) VALUES
|
||||||
|
(920000000000110003, 'HKKG', N'香港港', 'Hong Kong', N'中国', 'ASIA', 'HKKG', NULL, NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_port WHERE b_code = 'USLAX')
|
||||||
|
INSERT INTO dbo.b_port (b_id, b_code, b_name_c, b_name_e, b_country, b_oceanline, b_edicode, b_bz, b_tz) VALUES
|
||||||
|
(920000000000110004, 'USLAX', N'洛杉矶港', 'Los Angeles', N'美国', 'AMERICA', 'USLAX', NULL, NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_port WHERE b_code = 'NLRTM')
|
||||||
|
INSERT INTO dbo.b_port (b_id, b_code, b_name_c, b_name_e, b_country, b_oceanline, b_edicode, b_bz, b_tz) VALUES
|
||||||
|
(920000000000110005, 'NLRTM', N'鹿特丹港', 'Rotterdam', N'荷兰', 'EUROPE', 'NLRTM', NULL, NULL);
|
||||||
|
|
||||||
|
-- 城市
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_city WHERE b_code = 'SH')
|
||||||
|
INSERT INTO dbo.b_city (b_id, b_code, b_name_c, b_name_e, b_country_name_c, b_country_name_e, b_bz) VALUES
|
||||||
|
(920000000000110101, 'SH', N'上海', 'Shanghai', N'中国', 'China', NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_city WHERE b_code = 'SZ')
|
||||||
|
INSERT INTO dbo.b_city (b_id, b_code, b_name_c, b_name_e, b_country_name_c, b_country_name_e, b_bz) VALUES
|
||||||
|
(920000000000110102, 'SZ', N'深圳', 'Shenzhen', N'中国', 'China', NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_city WHERE b_code = 'NJ')
|
||||||
|
INSERT INTO dbo.b_city (b_id, b_code, b_name_c, b_name_e, b_country_name_c, b_country_name_e, b_bz) VALUES
|
||||||
|
(920000000000110103, 'NJ', N'南京', 'Nanjing', N'中国', 'China', NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_city WHERE b_code = 'NY')
|
||||||
|
INSERT INTO dbo.b_city (b_id, b_code, b_name_c, b_name_e, b_country_name_c, b_country_name_e, b_bz) VALUES
|
||||||
|
(920000000000110104, 'NY', N'纽约', 'New York', N'美国', 'America', NULL);
|
||||||
|
|
||||||
|
-- 币种
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_money WHERE b_code = 'CNY')
|
||||||
|
INSERT INTO dbo.b_money (b_id, b_code, b_name, b_edicode, b_symbol, b_hl, b_isconvert, b_canuse, b_xh) VALUES
|
||||||
|
(920000000000110201, 'CNY', N'人民币', 'CNY', N'¥', 1.0000, '1', 1, 10);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_money WHERE b_code = 'USD')
|
||||||
|
INSERT INTO dbo.b_money (b_id, b_code, b_name, b_edicode, b_symbol, b_hl, b_isconvert, b_canuse, b_xh) VALUES
|
||||||
|
(920000000000110202, 'USD', N'美元', 'USD', N'$', 7.2500, '1', 1, 20);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_money WHERE b_code = 'EUR')
|
||||||
|
INSERT INTO dbo.b_money (b_id, b_code, b_name, b_edicode, b_symbol, b_hl, b_isconvert, b_canuse, b_xh) VALUES
|
||||||
|
(920000000000110203, 'EUR', N'欧元', 'EUR', N'€', 7.8900, '1', 1, 30);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_money WHERE b_code = 'JPY')
|
||||||
|
INSERT INTO dbo.b_money (b_id, b_code, b_name, b_edicode, b_symbol, b_hl, b_isconvert, b_canuse, b_xh) VALUES
|
||||||
|
(920000000000110204, 'JPY', N'日元', 'JPY', N'¥', 0.0490, '1', 1, 40);
|
||||||
|
|
||||||
|
-- 航线
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_oceanline WHERE b_code = 'ASIA')
|
||||||
|
INSERT INTO dbo.b_oceanline (b_id, b_code, b_name, b_edicode, b_bz) VALUES
|
||||||
|
(920000000000110301, 'ASIA', N'亚洲线', 'ASIA', NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_oceanline WHERE b_code = 'EUROPE')
|
||||||
|
INSERT INTO dbo.b_oceanline (b_id, b_code, b_name, b_edicode, b_bz) VALUES
|
||||||
|
(920000000000110302, 'EUROPE', N'欧洲线', 'EUROPE', NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_oceanline WHERE b_code = 'AMERICA')
|
||||||
|
INSERT INTO dbo.b_oceanline (b_id, b_code, b_name, b_edicode, b_bz) VALUES
|
||||||
|
(920000000000110303, 'AMERICA', N'美洲线', 'AMERICA', NULL);
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_oceanline WHERE b_code = 'AFRICA')
|
||||||
|
INSERT INTO dbo.b_oceanline (b_id, b_code, b_name, b_edicode, b_bz) VALUES
|
||||||
|
(920000000000110304, 'AFRICA', N'非洲线', 'AFRICA', NULL);
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/* 扩展 b_other_bmfl 表,添加 b_module_code 字段关联 s_module 数据模块。
|
||||||
|
分类节点 b_module_code 为空(仅做分组),叶子节点指向对应的 data 模块。*/
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
1. 添加 b_module_code 列
|
||||||
|
========================================================================= */
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM sys.columns
|
||||||
|
WHERE object_id = OBJECT_ID(N'dbo.b_other_bmfl') AND name = 'b_module_code'
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE dbo.b_other_bmfl
|
||||||
|
ADD b_module_code nvarchar(100) NULL;
|
||||||
|
END;
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
2. 插入演示分类数据(基础信息 / 财务信息 / 物流信息 分组)
|
||||||
|
========================================================================= */
|
||||||
|
DECLARE @CatBasic bigint = 930000000000000101;
|
||||||
|
DECLARE @CatFinance bigint = 930000000000000102;
|
||||||
|
DECLARE @CatLogistics bigint = 930000000000000103;
|
||||||
|
|
||||||
|
/* ---- 基础信息 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @CatBasic)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@CatBasic, NULL, N'基础信息', 1, CAST(@CatBasic AS varchar(20)), 10, 1, NULL);
|
||||||
|
|
||||||
|
/* ---- 财务信息 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @CatFinance)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@CatFinance, NULL, N'财务信息', 1, CAST(@CatFinance AS varchar(20)), 20, 1, NULL);
|
||||||
|
|
||||||
|
/* ---- 物流信息 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @CatLogistics)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@CatLogistics, NULL, N'物流信息', 1, CAST(@CatLogistics AS varchar(20)), 30, 1, NULL);
|
||||||
|
|
||||||
|
/* =========================================================================
|
||||||
|
3. 插入叶子节点(关联到已有 data 模块)
|
||||||
|
========================================================================= */
|
||||||
|
DECLARE @NodePort bigint = 930000000000000201;
|
||||||
|
DECLARE @NodeCity bigint = 930000000000000202;
|
||||||
|
DECLARE @NodeMoney bigint = 930000000000000203;
|
||||||
|
DECLARE @NodeOceanline bigint = 930000000000000204;
|
||||||
|
|
||||||
|
/* ---- 基础信息 / 港口 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodePort)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@NodePort, @CatBasic, N'港口', 2,
|
||||||
|
CAST(@CatBasic AS varchar(20)) + '/' + CAST(@NodePort AS varchar(20)),
|
||||||
|
10, 1, 'd_other_port');
|
||||||
|
|
||||||
|
/* ---- 基础信息 / 城市 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodeCity)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@NodeCity, @CatBasic, N'城市', 2,
|
||||||
|
CAST(@CatBasic AS varchar(20)) + '/' + CAST(@NodeCity AS varchar(20)),
|
||||||
|
20, 1, 'd_other_city');
|
||||||
|
|
||||||
|
/* ---- 财务信息 / 币种 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodeMoney)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@NodeMoney, @CatFinance, N'币种', 2,
|
||||||
|
CAST(@CatFinance AS varchar(20)) + '/' + CAST(@NodeMoney AS varchar(20)),
|
||||||
|
10, 1, 'd_other_money');
|
||||||
|
|
||||||
|
/* ---- 物流信息 / 航线 ---- */
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.b_other_bmfl WHERE b_id = @NodeOceanline)
|
||||||
|
INSERT INTO dbo.b_other_bmfl (b_id, b_parent_id, b_name, b_level, b_path, b_xh, b_canuse, b_module_code)
|
||||||
|
VALUES (@NodeOceanline, @CatLogistics, N'航线', 2,
|
||||||
|
CAST(@CatLogistics AS varchar(20)) + '/' + CAST(@NodeOceanline AS varchar(20)),
|
||||||
|
10, 1, 'd_other_oceanline');
|
||||||
|
|
||||||
|
COMMIT TRANSACTION;
|
||||||
@@ -16,7 +16,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录日志服务:在登录成功或失败时采集客户端信息,并通过通用 saveobjt 机制写入 s_log_login 表。
|
* 登录日志服务:在登录成功或失败时采集客户端信息,并通过通用 saveobjt 机制写入 s_login_log 表。
|
||||||
* 写日志失败不影响登录主流程;机构上下文通过 OrgContext 临时设置并在 finally 中清理。
|
* 写日志失败不影响登录主流程;机构上下文通过 OrgContext 临时设置并在 finally 中清理。
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@@ -66,7 +66,7 @@ public class LoginLogService {
|
|||||||
row.put("b_session_id", sessionId);
|
row.put("b_session_id", sessionId);
|
||||||
|
|
||||||
Map<String, Object> tableOp = new LinkedHashMap<>();
|
Map<String, Object> tableOp = new LinkedHashMap<>();
|
||||||
tableOp.put("table", "s_log_login");
|
tableOp.put("table", "s_login_log");
|
||||||
tableOp.put("key_field", "b_id");
|
tableOp.put("key_field", "b_id");
|
||||||
tableOp.put("inserts", List.of(row));
|
tableOp.put("inserts", List.of(row));
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,264 @@
|
|||||||
|
import java.sql.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从旧库 G3HY2025.dbo.s_columnLib 同步到新库 fms:
|
||||||
|
* 1. col_Caption → s_module_field.b_name(字段名称)
|
||||||
|
* 2. col_Visible → s_module_field_list.b_visible(列表显示,不存在则插入记录)
|
||||||
|
* 3. col_edit → s_module_field_edit.b_visible(编辑显示,不存在则插入记录)
|
||||||
|
*
|
||||||
|
* 用法: java -cp ".;mssql-jdbc.jar" FieldNameSync <viewName>
|
||||||
|
*/
|
||||||
|
public class FieldNameSync {
|
||||||
|
|
||||||
|
static final String URL = "jdbc:sqlserver://118.89.70.199:1433;databaseName=fms;encrypt=true;trustServerCertificate=true";
|
||||||
|
static final String USER = "sa";
|
||||||
|
static final String PASSWORD = "zhang520..";
|
||||||
|
|
||||||
|
// 同步字段名称
|
||||||
|
static final String SYNC_NAME = """
|
||||||
|
UPDATE sf
|
||||||
|
SET sf.b_name = LTRIM(RTRIM(sc.col_Caption))
|
||||||
|
FROM dbo.s_module_field sf
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
JOIN G3HY2025.dbo.s_columnLib sc
|
||||||
|
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||||
|
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
AND sc.col_Caption IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(sc.col_Caption)) != ''
|
||||||
|
""";
|
||||||
|
|
||||||
|
// 同步列表显示:更新已有记录
|
||||||
|
static final String SYNC_LIST_UPDATE = """
|
||||||
|
UPDATE sfl
|
||||||
|
SET sfl.b_visible = CASE WHEN sc.col_Visible = '1' THEN 1 ELSE 0 END
|
||||||
|
FROM dbo.s_module_field_list sfl
|
||||||
|
JOIN dbo.s_module_field sf ON sf.b_id = sfl.b_field_id
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
JOIN G3HY2025.dbo.s_columnLib sc
|
||||||
|
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||||
|
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
AND sc.col_Visible IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(sc.col_Visible)) != ''
|
||||||
|
""";
|
||||||
|
|
||||||
|
// 查询需要插入列表配置的字段
|
||||||
|
static final String LIST_INSERT_CANDIDATES = """
|
||||||
|
SELECT sf.b_id AS field_id, m.b_id AS module_id,
|
||||||
|
CASE WHEN sc.col_Visible = '1' THEN 1 ELSE 0 END AS visible
|
||||||
|
FROM dbo.s_module_field sf
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
JOIN G3HY2025.dbo.s_columnLib sc
|
||||||
|
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||||
|
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
AND sc.col_Visible IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(sc.col_Visible)) != ''
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_list sfl
|
||||||
|
WHERE sfl.b_field_id = sf.b_id
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
|
||||||
|
static final String INSERT_LIST = """
|
||||||
|
INSERT INTO dbo.s_module_field_list (b_id, b_module_id, b_field_id, b_visible, b_xh)
|
||||||
|
VALUES (?, ?, ?, ?, 0)
|
||||||
|
""";
|
||||||
|
|
||||||
|
// 同步编辑显示:更新已有记录
|
||||||
|
static final String SYNC_EDIT_UPDATE = """
|
||||||
|
UPDATE sfe
|
||||||
|
SET sfe.b_visible = CASE WHEN sc.col_edit = '1' THEN 1 ELSE 0 END
|
||||||
|
FROM dbo.s_module_field_edit sfe
|
||||||
|
JOIN dbo.s_module_field sf ON sf.b_id = sfe.b_field_id
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
JOIN G3HY2025.dbo.s_columnLib sc
|
||||||
|
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||||
|
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
AND sc.col_edit IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(sc.col_edit)) != ''
|
||||||
|
""";
|
||||||
|
|
||||||
|
static final String EDIT_INSERT_CANDIDATES = """
|
||||||
|
SELECT sf.b_id AS field_id, m.b_id AS module_id,
|
||||||
|
CASE WHEN sc.col_edit = '1' THEN 1 ELSE 0 END AS visible
|
||||||
|
FROM dbo.s_module_field sf
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
JOIN G3HY2025.dbo.s_columnLib sc
|
||||||
|
ON sc.col_TableName COLLATE Chinese_PRC_CI_AS = m.b_viewtable
|
||||||
|
AND sc.col_FieldName COLLATE Chinese_PRC_CI_AS = sf.b_field
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
AND sc.col_edit IS NOT NULL
|
||||||
|
AND LTRIM(RTRIM(sc.col_edit)) != ''
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM dbo.s_module_field_edit sfe
|
||||||
|
WHERE sfe.b_field_id = sf.b_id
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
|
||||||
|
static final String INSERT_EDIT = """
|
||||||
|
INSERT INTO dbo.s_module_field_edit (b_id, b_module_id, b_field_id, b_visible, b_required, b_readonly, b_disabled, b_xh)
|
||||||
|
VALUES (?, ?, ?, ?, 0, 0, 0, 0)
|
||||||
|
""";
|
||||||
|
|
||||||
|
// 查询模块 ID
|
||||||
|
static final String QUERY_MODULE_ID = "SELECT b_id FROM dbo.s_module WHERE b_viewtable = ?";
|
||||||
|
|
||||||
|
// 查询列表/编辑配置现状
|
||||||
|
static final String QUERY_CONFIG = """
|
||||||
|
SELECT sf.b_field, sf.b_name,
|
||||||
|
sfl.b_visible AS list_visible,
|
||||||
|
sfe.b_visible AS edit_visible
|
||||||
|
FROM dbo.s_module_field sf
|
||||||
|
JOIN dbo.s_module m ON m.b_id = sf.b_module_id
|
||||||
|
LEFT JOIN dbo.s_module_field_list sfl ON sfl.b_field_id = sf.b_id
|
||||||
|
LEFT JOIN dbo.s_module_field_edit sfe ON sfe.b_field_id = sf.b_id
|
||||||
|
WHERE m.b_viewtable = ?
|
||||||
|
ORDER BY sf.b_field
|
||||||
|
""";
|
||||||
|
|
||||||
|
static long nextListId;
|
||||||
|
static long nextEditId;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
if (args.length == 0) {
|
||||||
|
System.err.println("用法: java FieldNameSync <viewName>");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
String viewName = args[0].trim();
|
||||||
|
if (!viewName.matches("^[A-Za-z_][A-Za-z0-9_]{0,127}$")) {
|
||||||
|
System.err.println("非法视图名: " + viewName);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("连接到 fms 数据库...");
|
||||||
|
try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) {
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
|
||||||
|
// 查询模块是否存在
|
||||||
|
Long moduleId = null;
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(QUERY_MODULE_ID)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (rs.next()) moduleId = rs.getLong(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (moduleId == null) {
|
||||||
|
System.err.println("模块 " + viewName + " 不存在!");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 ID 生成器
|
||||||
|
try (Statement st = conn.createStatement()) {
|
||||||
|
try (ResultSet rs = st.executeQuery("SELECT ISNULL(MAX(b_id),0) FROM dbo.s_module_field_list")) {
|
||||||
|
rs.next(); nextListId = rs.getLong(1) + 1;
|
||||||
|
}
|
||||||
|
try (ResultSet rs = st.executeQuery("SELECT ISNULL(MAX(b_id),0) FROM dbo.s_module_field_edit")) {
|
||||||
|
rs.next(); nextEditId = rs.getLong(1) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 1. 同步前 ===
|
||||||
|
System.out.println("\n=== 同步前 ===");
|
||||||
|
printConfig(conn, viewName);
|
||||||
|
|
||||||
|
// === 2. 字段名称 ===
|
||||||
|
System.out.println("\n=== 1) 字段名称 (col_Caption → b_name) ===");
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(SYNC_NAME)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
System.out.println(" 更新: " + ps.executeUpdate() + " 条");
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 3. 列表显示 ===
|
||||||
|
System.out.println("\n=== 2) 列表显示 (col_Visible → s_module_field_list.b_visible) ===");
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(SYNC_LIST_UPDATE)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
System.out.println(" 更新已有: " + ps.executeUpdate() + " 条");
|
||||||
|
}
|
||||||
|
java.util.ArrayList<long[]> listCandidates = new java.util.ArrayList<>();
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(LIST_INSERT_CANDIDATES)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
while (rs.next()) {
|
||||||
|
listCandidates.add(new long[]{rs.getLong("field_id"), rs.getLong("module_id"), rs.getLong("visible")});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(INSERT_LIST)) {
|
||||||
|
for (long[] row : listCandidates) {
|
||||||
|
ps.setLong(1, nextListId++);
|
||||||
|
ps.setLong(2, row[1]);
|
||||||
|
ps.setLong(3, row[0]);
|
||||||
|
ps.setInt(4, (int) row[2]);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
if (!listCandidates.isEmpty()) {
|
||||||
|
ps.executeBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println(" 新增: " + listCandidates.size() + " 条");
|
||||||
|
|
||||||
|
// === 4. 编辑显示 ===
|
||||||
|
System.out.println("\n=== 3) 编辑显示 (col_edit → s_module_field_edit.b_visible) ===");
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(SYNC_EDIT_UPDATE)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
System.out.println(" 更新已有: " + ps.executeUpdate() + " 条");
|
||||||
|
}
|
||||||
|
java.util.ArrayList<long[]> editCandidates = new java.util.ArrayList<>();
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(EDIT_INSERT_CANDIDATES)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
while (rs.next()) {
|
||||||
|
editCandidates.add(new long[]{rs.getLong("field_id"), rs.getLong("module_id"), rs.getLong("visible")});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(INSERT_EDIT)) {
|
||||||
|
for (long[] row : editCandidates) {
|
||||||
|
ps.setLong(1, nextEditId++);
|
||||||
|
ps.setLong(2, row[1]);
|
||||||
|
ps.setLong(3, row[0]);
|
||||||
|
ps.setInt(4, (int) row[2]);
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
if (!editCandidates.isEmpty()) {
|
||||||
|
ps.executeBatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println(" 新增: " + editCandidates.size() + " 条");
|
||||||
|
|
||||||
|
conn.commit();
|
||||||
|
System.out.println("\n=== 事务已提交 ===");
|
||||||
|
|
||||||
|
// === 5. 验证 ===
|
||||||
|
System.out.println("\n=== 同步后 ===");
|
||||||
|
printConfig(conn, viewName);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("\n完成.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printConfig(Connection conn, String viewName) throws SQLException {
|
||||||
|
System.out.printf(" %-30s %-16s %-8s %-8s%n", "b_field", "b_name", "list", "edit");
|
||||||
|
System.out.println(" " + "-".repeat(66));
|
||||||
|
try (PreparedStatement ps = conn.prepareStatement(QUERY_CONFIG)) {
|
||||||
|
ps.setString(1, viewName);
|
||||||
|
try (ResultSet rs = ps.executeQuery()) {
|
||||||
|
while (rs.next()) {
|
||||||
|
String listVis = rs.getString("list_visible");
|
||||||
|
String editVis = rs.getString("edit_visible");
|
||||||
|
String listStr = listVis == null ? "--" : ("1".equals(listVis) ? "显示" : "隐藏");
|
||||||
|
String editStr = editVis == null ? "--" : ("1".equals(editVis) ? "显示" : "隐藏");
|
||||||
|
String name = rs.getString("b_name");
|
||||||
|
System.out.printf(" %-30s %-16s %-8s %-8s%n",
|
||||||
|
rs.getString("b_field"),
|
||||||
|
name != null ? name : "(null)",
|
||||||
|
listStr,
|
||||||
|
editStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# FieldNameSync — 模块字段配置迁移工具
|
||||||
|
|
||||||
|
从旧库 `G3HY2025.dbo.s_columnLib` 同步字段配置到新库 `fms`。
|
||||||
|
|
||||||
|
## 同步的三项内容
|
||||||
|
|
||||||
|
| 序号 | 旧库来源 | 新库目标 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | `col_Caption` | `s_module_field.b_name` | 字段中文名称 |
|
||||||
|
| 2 | `col_Visible` | `s_module_field_list.b_visible` | 列表是否显示(1=显示, 0=隐藏) |
|
||||||
|
| 3 | `col_edit` | `s_module_field_edit.b_visible` | 编辑表单是否显示(1=显示, 0=隐藏) |
|
||||||
|
|
||||||
|
## 匹配规则
|
||||||
|
|
||||||
|
通过视图名和字段名关联新旧库:
|
||||||
|
|
||||||
|
```
|
||||||
|
旧库 s_columnLib.col_TableName = 新库 s_module.b_viewtable
|
||||||
|
旧库 s_columnLib.col_FieldName = 新库 s_module_field.b_field
|
||||||
|
```
|
||||||
|
|
||||||
|
- 旧库字段匹配不上的,对应配置**不做修改**(保留原值)
|
||||||
|
- 列表配置或编辑配置记录不存在时,**自动插入**新记录
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
### 1. JDK 21
|
||||||
|
|
||||||
|
工具使用 Java 文本块语法(`"""`),需要 JDK 21+ 编译。
|
||||||
|
|
||||||
|
### 2. mssql-jdbc 驱动
|
||||||
|
|
||||||
|
确保本地 Maven 仓库存在驱动 jar:
|
||||||
|
```
|
||||||
|
%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
如果不存在,从项目根目录 `fms-api/` 执行 `mvnw dependency:resolve` 下载。
|
||||||
|
|
||||||
|
### 3. 数据库连接
|
||||||
|
|
||||||
|
工具硬编码连接 `118.89.70.199:1433`,数据库 `fms`,直连新旧两库(同一实例)。
|
||||||
|
|
||||||
|
### 4. 新库已有 `s_module_field_edit.b_visible` 列
|
||||||
|
|
||||||
|
如果报 `Invalid column name 'b_visible'`,执行以下 SQL 添加列:
|
||||||
|
```sql
|
||||||
|
ALTER TABLE dbo.s_module_field_edit ADD b_visible int NOT NULL DEFAULT 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 模块已在 s_module 中注册
|
||||||
|
|
||||||
|
目标模块的 `b_viewtable` 必须已在 `s_module` 表中配置,且已有 `s_module_field` 记录。
|
||||||
|
|
||||||
|
## 编译和运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入迁移工具目录
|
||||||
|
cd fms-api/tools/migration
|
||||||
|
|
||||||
|
# 设置 JDK 21
|
||||||
|
export JAVA_HOME=/path/to/jdk-21 # Linux/Mac
|
||||||
|
set JAVA_HOME=D:\devtool\jdk\jdk-21 # Windows
|
||||||
|
|
||||||
|
# 编译
|
||||||
|
javac -cp "%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync.java
|
||||||
|
|
||||||
|
# 运行(以 v_b_othercompany 为例)
|
||||||
|
java -cp ".;%USERPROFILE%\.m2\repository\com\microsoft\sqlserver\mssql-jdbc\13.4.0.jre11\mssql-jdbc-13.4.0.jre11.jar" FieldNameSync v_b_othercompany
|
||||||
|
```
|
||||||
|
|
||||||
|
## 输出示例
|
||||||
|
|
||||||
|
```
|
||||||
|
=== 同步前 ===
|
||||||
|
b_field b_name list edit
|
||||||
|
------------------------------------------------------------------
|
||||||
|
b_id 系统编号 显示 显示
|
||||||
|
b_name 公司全称 显示 显示
|
||||||
|
...
|
||||||
|
|
||||||
|
=== 1) 字段名称 (col_Caption → b_name) ===
|
||||||
|
更新: 47 条
|
||||||
|
|
||||||
|
=== 2) 列表显示 (col_Visible → s_module_field_list.b_visible) ===
|
||||||
|
更新已有: 50 条
|
||||||
|
新增: 0 条
|
||||||
|
|
||||||
|
=== 3) 编辑显示 (col_edit → s_module_field_edit.b_visible) ===
|
||||||
|
更新已有: 44 条
|
||||||
|
新增: 0 条
|
||||||
|
|
||||||
|
=== 事务已提交 ===
|
||||||
|
```
|
||||||
|
|
||||||
|
## 用 AI 助手调用
|
||||||
|
|
||||||
|
如果通过 AI 助手(如 CodeBuddy)执行,告诉 AI 以下步骤即可:
|
||||||
|
|
||||||
|
> 帮我用 FieldNameSync 工具同步视图 `<viewName>` 的字段配置。
|
||||||
|
>
|
||||||
|
> 1. 进入 `fms-api/tools/migration/` 目录
|
||||||
|
> 2. 用 JDK 21 编译:`javac -cp "mssql-jdbc 驱动 jar 路径" FieldNameSync.java`
|
||||||
|
> 3. 运行:`java -cp ".;驱动 jar 路径" FieldNameSync <viewName>`
|
||||||
|
> 4. 运行完毕后删除 `FieldNameSync.class` 文件
|
||||||
|
|
||||||
|
## 已有同步记录
|
||||||
|
|
||||||
|
| 视图名 | 同步日期 | 字段数 | 备注 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `v_b_othercompany` | 2026-07-30 | 50 | 已同步 |
|
||||||
|
| `v_b_contact` | 2026-07-30 | 36 | 已同步 |
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, h, nextTick, reactive, ref, shallowRef, useSlots, watch } from "vue";
|
import {
|
||||||
|
computed,
|
||||||
|
h,
|
||||||
|
nextTick,
|
||||||
|
onActivated,
|
||||||
|
reactive,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
useSlots,
|
||||||
|
watch,
|
||||||
|
} from "vue";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { message } from "antdv-next";
|
import { message } from "antdv-next";
|
||||||
import { Columns3, RefreshCw, Save } from "@lucide/vue";
|
import { Columns3, RefreshCw, Save } from "@lucide/vue";
|
||||||
@@ -33,6 +43,9 @@ const slots = useSlots();
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const tableRef = ref(null);
|
const tableRef = ref(null);
|
||||||
|
// keep-alive 中的缓存实例必须保留各自的路由上下文,不能跟随全局 route 变化。
|
||||||
|
const instanceRoutePath = route.path;
|
||||||
|
const instanceRouteDataCode = route.meta.dataCode;
|
||||||
|
|
||||||
// ── 内部状态 ──
|
// ── 内部状态 ──
|
||||||
const dataModule = ref(null);
|
const dataModule = ref(null);
|
||||||
@@ -91,12 +104,11 @@ function resolveKeyField(module, fields) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDataCode() {
|
function getDataCode() {
|
||||||
return String(props.dataCode || route.meta.dataCode || "").trim();
|
return String(props.dataCode || instanceRouteDataCode || "").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadModuleConfig() {
|
async function loadModuleConfig() {
|
||||||
const code = getDataCode();
|
const code = getDataCode();
|
||||||
const currentPath = route.path;
|
|
||||||
configLoading.value = true;
|
configLoading.value = true;
|
||||||
configError.value = "";
|
configError.value = "";
|
||||||
|
|
||||||
@@ -122,7 +134,7 @@ async function loadModuleConfig() {
|
|||||||
throw new Error(`数据编码 ${runtimeData.b_code} 没有对应的启用页面`);
|
throw new Error(`数据编码 ${runtimeData.b_code} 没有对应的启用页面`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const routePath = escapeSqlText(currentPath);
|
const routePath = escapeSqlText(instanceRoutePath);
|
||||||
const pageRes = await loadDataApi(
|
const pageRes = await loadDataApi(
|
||||||
"s_module",
|
"s_module",
|
||||||
`b_route = N'${routePath}' AND b_canuse = 1`,
|
`b_route = N'${routePath}' AND b_canuse = 1`,
|
||||||
@@ -160,7 +172,7 @@ async function loadModuleConfig() {
|
|||||||
"b_xh ASC, b_id ASC",
|
"b_xh ASC, b_id ASC",
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
if (getDataCode() !== code || route.path !== currentPath) return false;
|
if (getDataCode() !== code) return false;
|
||||||
|
|
||||||
const fields = [];
|
const fields = [];
|
||||||
const fieldById = new Map();
|
const fieldById = new Map();
|
||||||
@@ -205,12 +217,12 @@ async function loadModuleConfig() {
|
|||||||
keyField.value = resolveKeyField(runtimeData, fields);
|
keyField.value = resolveKeyField(runtimeData, fields);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (getDataCode() === code && route.path === currentPath) {
|
if (getDataCode() === code) {
|
||||||
configError.value = error?.message || String(error);
|
configError.value = error?.message || String(error);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
if (getDataCode() === code && route.path === currentPath) {
|
if (getDataCode() === code) {
|
||||||
configLoading.value = false;
|
configLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +251,6 @@ async function fetchData() {
|
|||||||
const currentModule = dataModule.value;
|
const currentModule = dataModule.value;
|
||||||
const viewName = currentModule.b_viewtable;
|
const viewName = currentModule.b_viewtable;
|
||||||
const currentCode = getDataCode();
|
const currentCode = getDataCode();
|
||||||
const currentPath = route.path;
|
|
||||||
const requestVersion = ++dataRequestVersion;
|
const requestVersion = ++dataRequestVersion;
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
const fixedCondition = String(props.fixedSearchCondition || "").trim();
|
const fixedCondition = String(props.fixedSearchCondition || "").trim();
|
||||||
@@ -259,15 +270,14 @@ async function fetchData() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (requestVersion !== dataRequestVersion || dataModule.value !== currentModule) return;
|
if (requestVersion !== dataRequestVersion || dataModule.value !== currentModule) return;
|
||||||
if (getDataCode() !== currentCode || route.path !== currentPath) return;
|
if (getDataCode() !== currentCode) return;
|
||||||
dataSource.value = res.data?.rows || [];
|
dataSource.value = res.data?.rows || [];
|
||||||
pagination.total = Number(res.data?.total ?? res.data?.row_count) || 0;
|
pagination.total = Number(res.data?.total ?? res.data?.row_count) || 0;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (
|
||||||
requestVersion === dataRequestVersion &&
|
requestVersion === dataRequestVersion &&
|
||||||
dataModule.value === currentModule &&
|
dataModule.value === currentModule &&
|
||||||
getDataCode() === currentCode &&
|
getDataCode() === currentCode
|
||||||
route.path === currentPath
|
|
||||||
) {
|
) {
|
||||||
dataSource.value = [];
|
dataSource.value = [];
|
||||||
pagination.total = 0;
|
pagination.total = 0;
|
||||||
@@ -277,8 +287,7 @@ async function fetchData() {
|
|||||||
if (
|
if (
|
||||||
requestVersion === dataRequestVersion &&
|
requestVersion === dataRequestVersion &&
|
||||||
dataModule.value === currentModule &&
|
dataModule.value === currentModule &&
|
||||||
getDataCode() === currentCode &&
|
getDataCode() === currentCode
|
||||||
route.path === currentPath
|
|
||||||
) {
|
) {
|
||||||
dataLoading.value = false;
|
dataLoading.value = false;
|
||||||
}
|
}
|
||||||
@@ -412,7 +421,19 @@ async function handleMoreAction({ key }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 生命周期 ──
|
// ── 生命周期 ──
|
||||||
watch(() => [getDataCode(), route.path], initialize, { immediate: true });
|
let hasActivated = false;
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
onActivated(async () => {
|
||||||
|
if (!hasActivated) {
|
||||||
|
hasActivated = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
tableRef.value?.refreshLayout?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => props.dataCode, initialize);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.fixedSearchCondition,
|
() => props.fixedSearchCondition,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function formatCellValue(value, field, optionInfo) {
|
|||||||
if (field.b_type === "date") return formatDateValue(value, false);
|
if (field.b_type === "date") return formatDateValue(value, false);
|
||||||
if (field.b_type === "datetime") return formatDateValue(value, true);
|
if (field.b_type === "datetime") return formatDateValue(value, true);
|
||||||
if (field.b_type === "checkbox") {
|
if (field.b_type === "checkbox") {
|
||||||
return h(Checkbox, { checked: value === true || Number(value) === 1, disabled: true });
|
return h(Checkbox, { checked: value === true || Number(value) === 1 });
|
||||||
}
|
}
|
||||||
if (field.b_type === "select") {
|
if (field.b_type === "select") {
|
||||||
const option = optionInfo.options.find((item) => String(item.value) === String(value));
|
const option = optionInfo.options.find((item) => String(item.value) === String(value));
|
||||||
@@ -199,6 +199,10 @@ function resetColumns() {
|
|||||||
tableRef.value?.initVirtualScroll?.();
|
tableRef.value?.initVirtualScroll?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshLayout() {
|
||||||
|
tableRef.value?.refreshLayout?.();
|
||||||
|
}
|
||||||
|
|
||||||
function getColumnSettings() {
|
function getColumnSettings() {
|
||||||
const configByFieldId = new Map();
|
const configByFieldId = new Map();
|
||||||
for (const config of props.listConfig || []) {
|
for (const config of props.listConfig || []) {
|
||||||
@@ -282,6 +286,7 @@ watch(
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
resetColumns,
|
resetColumns,
|
||||||
|
refreshLayout,
|
||||||
getColumnSettings,
|
getColumnSettings,
|
||||||
clearSelectedArea: () => tableRef.value?.clearSelectedArea?.(),
|
clearSelectedArea: () => tableRef.value?.clearSelectedArea?.(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -85,6 +85,28 @@ function clearActiveEditing() {
|
|||||||
if (state) state.value = { editing: false, focusCol: null, initialValues: null };
|
if (state) state.value = { editing: false, focusCol: null, initialValues: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 父级可能在 change 后回灌同 rowKey 的新对象,编辑状态需要随行对象迁移。
|
||||||
|
function rebindActiveEditingRow(rows) {
|
||||||
|
if (!activeEditingRow || rows.includes(activeEditingRow)) return;
|
||||||
|
|
||||||
|
const previousRow = activeEditingRow;
|
||||||
|
const previousState = rowEditingStates.get(previousRow)?.value;
|
||||||
|
const previousKey = getRowKey(previousRow);
|
||||||
|
const replacement =
|
||||||
|
previousKey == null ? null : rows.find((row) => String(getRowKey(row)) === String(previousKey));
|
||||||
|
|
||||||
|
clearActiveEditing();
|
||||||
|
if (!replacement || !previousState?.editing) return;
|
||||||
|
|
||||||
|
const state = getRowEditingState(replacement);
|
||||||
|
activeEditingRow = replacement;
|
||||||
|
state.value = {
|
||||||
|
editing: true,
|
||||||
|
focusCol: previousState.focusCol,
|
||||||
|
initialValues: captureEditableValues(replacement),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function activateEditing(row, focusCol) {
|
function activateEditing(row, focusCol) {
|
||||||
if (!props.editable || !row) return;
|
if (!props.editable || !row) return;
|
||||||
const state = getRowEditingState(row);
|
const state = getRowEditingState(row);
|
||||||
@@ -402,7 +424,7 @@ function handleAreaSelectionChange(ranges) {
|
|||||||
watch(
|
watch(
|
||||||
[() => props.activeRowKey, () => props.dataSource],
|
[() => props.activeRowKey, () => props.dataSource],
|
||||||
async ([rowKey]) => {
|
async ([rowKey]) => {
|
||||||
if (activeEditingRow && !props.dataSource.includes(activeEditingRow)) clearActiveEditing();
|
rebindActiveEditingRow(props.dataSource);
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (rowKey == null) {
|
if (rowKey == null) {
|
||||||
tableRef.value?.setCurrentRow?.(undefined, { silent: true });
|
tableRef.value?.setCurrentRow?.(undefined, { silent: true });
|
||||||
|
|||||||
@@ -1,7 +1,42 @@
|
|||||||
import { h } from "vue";
|
import { h, toRaw } from "vue";
|
||||||
import { Input, Select } from "antdv-next";
|
import { Input, Select } from "antdv-next";
|
||||||
|
|
||||||
const stop = (event) => event.stopPropagation();
|
const stop = (event) => event.stopPropagation();
|
||||||
|
const inputDrafts = new WeakMap();
|
||||||
|
|
||||||
|
function getInputDraft(row, dataIndex) {
|
||||||
|
const drafts = inputDrafts.get(row);
|
||||||
|
return drafts?.has(dataIndex) ? drafts.get(dataIndex) : row[dataIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setInputDraft(row, dataIndex, value) {
|
||||||
|
let drafts = inputDrafts.get(row);
|
||||||
|
if (!drafts) {
|
||||||
|
drafts = new Map();
|
||||||
|
inputDrafts.set(row, drafts);
|
||||||
|
}
|
||||||
|
drafts.set(dataIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearInputDraft(row, dataIndex) {
|
||||||
|
const drafts = inputDrafts.get(row);
|
||||||
|
if (!drafts) return;
|
||||||
|
drafts.delete(dataIndex);
|
||||||
|
if (!drafts.size) inputDrafts.delete(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitInputDraft(row, dataIndex, value) {
|
||||||
|
clearInputDraft(row, dataIndex);
|
||||||
|
row[dataIndex] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function preserveInputDraft(row, dataIndex) {
|
||||||
|
const drafts = inputDrafts.get(row);
|
||||||
|
if (!drafts?.has(dataIndex)) return;
|
||||||
|
// 卸载兜底不触发整表更新;正常路径会在 blur 中响应式提交并通知父级。
|
||||||
|
toRaw(row)[dataIndex] = drafts.get(dataIndex);
|
||||||
|
clearInputDraft(row, dataIndex);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveOptions(editor) {
|
function resolveOptions(editor) {
|
||||||
return typeof editor.options === "function" ? editor.options() : (editor.options ?? []);
|
return typeof editor.options === "function" ? editor.options() : (editor.options ?? []);
|
||||||
@@ -52,15 +87,16 @@ function createInput(row, col, editor, ctx) {
|
|||||||
const focus = createFocusHandler(ctx, row, col, true);
|
const focus = createFocusHandler(ctx, row, col, true);
|
||||||
return h(Input, {
|
return h(Input, {
|
||||||
type: editor.inputType || "text",
|
type: editor.inputType || "text",
|
||||||
value: row[col.dataIndex] ?? "",
|
value: getInputDraft(row, col.dataIndex) ?? "",
|
||||||
size: "small",
|
size: "small",
|
||||||
class: "fms-cell-editor fms-cell-editor-root",
|
class: "fms-cell-editor fms-cell-editor-root",
|
||||||
"aria-label": col.title || col.dataIndex,
|
"aria-label": col.title || col.dataIndex,
|
||||||
"onUpdate:value": (value) => {
|
"onUpdate:value": (value) => {
|
||||||
row[col.dataIndex] = value;
|
setInputDraft(row, col.dataIndex, value);
|
||||||
},
|
},
|
||||||
onBlur: (event) => {
|
onBlur: (event) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
|
commitInputDraft(row, col.dataIndex, value);
|
||||||
notifyChange(ctx, editor, row, col, value);
|
notifyChange(ctx, editor, row, col, value);
|
||||||
},
|
},
|
||||||
onKeydown: (event) => {
|
onKeydown: (event) => {
|
||||||
@@ -75,6 +111,7 @@ function createInput(row, col, editor, ctx) {
|
|||||||
onClick: stop,
|
onClick: stop,
|
||||||
onVnodeMounted: focus,
|
onVnodeMounted: focus,
|
||||||
onVnodeUpdated: focus,
|
onVnodeUpdated: focus,
|
||||||
|
onVnodeBeforeUnmount: () => preserveInputDraft(row, col.dataIndex),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,16 @@ const menuItems = computed(() => {
|
|||||||
label: moduleLabel("base_othercompany", "/base/othercompany", "合作伙伴"),
|
label: moduleLabel("base_othercompany", "/base/othercompany", "合作伙伴"),
|
||||||
moduleId: "base_othercompany",
|
moduleId: "base_othercompany",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "/base/contact",
|
||||||
|
label: moduleLabel("v_b_contact", "/base/contact", "联系人"),
|
||||||
|
moduleId: "v_b_contact",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "/base/otherdata",
|
||||||
|
label: moduleLabel("m_base_otherdata", "/base/otherdata", "其他数据"),
|
||||||
|
moduleId: "m_base_otherdata",
|
||||||
|
},
|
||||||
].filter(canAccessMenu);
|
].filter(canAccessMenu);
|
||||||
const systemPages = [
|
const systemPages = [
|
||||||
{
|
{
|
||||||
@@ -52,6 +62,11 @@ const menuItems = computed(() => {
|
|||||||
label: moduleLabel("d_login_log", "/system/login-log", "登录日志"),
|
label: moduleLabel("d_login_log", "/system/login-log", "登录日志"),
|
||||||
moduleId: "d_login_log",
|
moduleId: "d_login_log",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "/system/user",
|
||||||
|
label: moduleLabel("b_user", "/system/user", "用户管理"),
|
||||||
|
moduleId: "b_user",
|
||||||
|
},
|
||||||
].filter(canAccessMenu);
|
].filter(canAccessMenu);
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
@@ -152,6 +167,19 @@ onMounted(() => permissionStore.load().catch(() => {}));
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
|
transition: padding-inline-start 0.2s ease;
|
||||||
|
}
|
||||||
|
:deep(.ant-menu-inline-collapsed) {
|
||||||
|
.ant-menu-title-content {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.ant-menu-item,
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
padding-inline-start: 20px !important;
|
||||||
|
}
|
||||||
|
.ant-menu-item-icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.brand {
|
.brand {
|
||||||
|
|||||||
@@ -22,7 +22,29 @@ const routes = [
|
|||||||
title: "合作伙伴",
|
title: "合作伙伴",
|
||||||
section: "基础数据",
|
section: "基础数据",
|
||||||
menuKey: "/base/othercompany",
|
menuKey: "/base/othercompany",
|
||||||
moduleId: "b_othercompany",
|
moduleId: "v_b_othercompany",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "base/contact",
|
||||||
|
name: "base-contact",
|
||||||
|
component: () => import("@/views/base/contact/index.vue"),
|
||||||
|
meta: {
|
||||||
|
title: "联系人",
|
||||||
|
section: "基础数据",
|
||||||
|
menuKey: "/base/contact",
|
||||||
|
moduleId: "v_b_contact",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "base/otherdata",
|
||||||
|
name: "base-otherdata",
|
||||||
|
component: () => import("@/views/base/otherdata/index.vue"),
|
||||||
|
meta: {
|
||||||
|
title: "其他数据",
|
||||||
|
section: "基础数据",
|
||||||
|
menuKey: "/base/otherdata",
|
||||||
|
moduleId: "m_base_otherdata",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -58,6 +80,17 @@ const routes = [
|
|||||||
moduleId: "s_login_log",
|
moduleId: "s_login_log",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "system/user",
|
||||||
|
name: "system-user",
|
||||||
|
component: () => import("@/views/system/user/index.vue"),
|
||||||
|
meta: {
|
||||||
|
title: "用户管理",
|
||||||
|
section: "系统管理",
|
||||||
|
menuKey: "/system/user",
|
||||||
|
moduleId: "b_user",
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup>
|
||||||
|
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="contact-page">
|
||||||
|
<FmsModuleListPage data-code="v_b_contact" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.contact-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--fms-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--fms-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-page :deep(.fms-module-list-page) {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -390,7 +390,7 @@ onMounted(loadCategories);
|
|||||||
<a-splitter-panel :min="520">
|
<a-splitter-panel :min="520">
|
||||||
<main class="partner-panel">
|
<main class="partner-panel">
|
||||||
<FmsModuleListPage
|
<FmsModuleListPage
|
||||||
data-code="b_othercompany"
|
data-code="v_b_othercompany"
|
||||||
:fixed-search-condition="categoryFilterCondition"
|
:fixed-search-condition="categoryFilterCondition"
|
||||||
:column-extensions="columnExtensions"
|
:column-extensions="columnExtensions"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,456 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, h, onMounted, reactive, ref } from "vue";
|
||||||
|
import { message, Modal } from "antdv-next";
|
||||||
|
import { Pencil, Plus, RefreshCw, Trash2 } from "@lucide/vue";
|
||||||
|
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||||
|
import FmsTree from "@/components/FmsTree.vue";
|
||||||
|
import { loadDataApi, nextIdApi, pageDataApi, saveObjectApi } from "@/services/api";
|
||||||
|
import { buildTree, sortTree } from "@/utils/tree";
|
||||||
|
|
||||||
|
const ALL_CATEGORIES_KEY = "__fms_tree_root__";
|
||||||
|
const CATEGORY_COLUMNS = [
|
||||||
|
"b_id", "b_parent_id", "b_name", "b_level", "b_path",
|
||||||
|
"b_xh", "b_canuse", "b_module_code",
|
||||||
|
];
|
||||||
|
|
||||||
|
const categories = ref([]);
|
||||||
|
const categoryLoading = ref(false);
|
||||||
|
const selectedCategoryKey = ref(ALL_CATEGORIES_KEY);
|
||||||
|
const selectedModuleCode = ref("");
|
||||||
|
const expandedCategoryKeys = ref([ALL_CATEGORIES_KEY]);
|
||||||
|
|
||||||
|
const categoryById = computed(
|
||||||
|
() => new Map(categories.value.map((item) => [String(item.b_id), item])),
|
||||||
|
);
|
||||||
|
|
||||||
|
const categoryTree = computed(() => {
|
||||||
|
const roots = buildTree(categories.value, {
|
||||||
|
mapNode: (item) => ({
|
||||||
|
key: String(item.b_id),
|
||||||
|
title: Number(item.b_canuse) === 1
|
||||||
|
? item.b_name
|
||||||
|
: `${item.b_name}(停用)`,
|
||||||
|
b_id: String(item.b_id),
|
||||||
|
b_parent_id: item.b_parent_id == null ? null : String(item.b_parent_id),
|
||||||
|
b_module_code: item.b_module_code || "",
|
||||||
|
b_xh: item.b_xh,
|
||||||
|
children: [],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
sortTree(roots);
|
||||||
|
return roots;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadCategories() {
|
||||||
|
categoryLoading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await loadDataApi(
|
||||||
|
"b_other_bmfl",
|
||||||
|
"",
|
||||||
|
"b_path ASC, b_xh ASC, b_id ASC",
|
||||||
|
CATEGORY_COLUMNS,
|
||||||
|
);
|
||||||
|
categories.value = response.data || [];
|
||||||
|
|
||||||
|
// 如果之前选中的节点被删了,回到根
|
||||||
|
const existingKeys = new Set(categories.value.map((item) => String(item.b_id)));
|
||||||
|
if (
|
||||||
|
selectedCategoryKey.value !== ALL_CATEGORIES_KEY &&
|
||||||
|
!existingKeys.has(selectedCategoryKey.value)
|
||||||
|
) {
|
||||||
|
selectedCategoryKey.value = ALL_CATEGORIES_KEY;
|
||||||
|
selectedModuleCode.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开所有有子节点的父节点
|
||||||
|
const parentKeys = categories.value
|
||||||
|
.map((item) => (item.b_parent_id == null ? "" : String(item.b_parent_id)))
|
||||||
|
.filter(Boolean);
|
||||||
|
expandedCategoryKeys.value = [
|
||||||
|
...new Set([...expandedCategoryKeys.value, ALL_CATEGORIES_KEY, ...parentKeys]),
|
||||||
|
];
|
||||||
|
} catch (error) {
|
||||||
|
message.error("分类加载失败:" + (error?.message || error));
|
||||||
|
} finally {
|
||||||
|
categoryLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCategorySelect(keys) {
|
||||||
|
if (!keys.length) return;
|
||||||
|
const key = String(keys[0]);
|
||||||
|
selectedCategoryKey.value = key;
|
||||||
|
|
||||||
|
// 判断节点是否关联了数据模块
|
||||||
|
const cat = categoryById.value.get(key);
|
||||||
|
if (cat && cat.b_module_code) {
|
||||||
|
selectedModuleCode.value = cat.b_module_code;
|
||||||
|
} else {
|
||||||
|
selectedModuleCode.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 右键菜单 & 分类维护 ----
|
||||||
|
const addModalVisible = ref(false);
|
||||||
|
const addModalParentId = ref(null);
|
||||||
|
const addModalParentName = ref("");
|
||||||
|
const editingCategoryId = ref(null);
|
||||||
|
const addForm = reactive({ b_name: "", b_module_code: "" });
|
||||||
|
const addSaving = ref(false);
|
||||||
|
const deleteSaving = ref(false);
|
||||||
|
const categoryModalTitle = computed(() =>
|
||||||
|
editingCategoryId.value ? "修改分类" : "新增分类",
|
||||||
|
);
|
||||||
|
|
||||||
|
function buildCategoryContextMenu(node) {
|
||||||
|
if (!node) return { items: [] };
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
key: "add-child",
|
||||||
|
label: node.isFmsRoot ? "新增根分类" : "添加子分类",
|
||||||
|
icon: h(Plus, { size: 14 }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (node.isFmsRoot) {
|
||||||
|
items.push({
|
||||||
|
key: "refresh",
|
||||||
|
label: "刷新分类",
|
||||||
|
icon: h(RefreshCw, { size: 14 }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!node.isFmsRoot) {
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
key: "edit",
|
||||||
|
label: "修改分类",
|
||||||
|
icon: h(Pencil, { size: 14 }),
|
||||||
|
},
|
||||||
|
{ type: "divider" },
|
||||||
|
{
|
||||||
|
key: "delete",
|
||||||
|
label: "删除",
|
||||||
|
icon: h(Trash2, { size: 14 }),
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { items };
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCategoryMenuClick({ key, node }) {
|
||||||
|
if (key === "add-child") {
|
||||||
|
editingCategoryId.value = null;
|
||||||
|
addModalParentId.value = node.isFmsRoot ? null : node.b_id || node.key;
|
||||||
|
addModalParentName.value = node.isFmsRoot ? "" : node.title || "";
|
||||||
|
addForm.b_name = "";
|
||||||
|
addForm.b_module_code = "";
|
||||||
|
addModalVisible.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key === "edit") {
|
||||||
|
openEditCategoryModal(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key === "refresh") {
|
||||||
|
loadCategories();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key === "delete") confirmDeleteCategory(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditCategoryModal(node) {
|
||||||
|
if (!node || node.isFmsRoot) return;
|
||||||
|
const categoryId = String(node.b_id || node.key || "");
|
||||||
|
const category = categoryById.value.get(categoryId);
|
||||||
|
if (!category) {
|
||||||
|
message.error("未找到需要修改的分类");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentId = category.b_parent_id == null ? null : String(category.b_parent_id);
|
||||||
|
const parent = parentId ? categoryById.value.get(parentId) : null;
|
||||||
|
editingCategoryId.value = categoryId;
|
||||||
|
addModalParentId.value = parentId;
|
||||||
|
addModalParentName.value = parent?.b_name || "";
|
||||||
|
addForm.b_name = category.b_name || "";
|
||||||
|
addForm.b_module_code = category.b_module_code || "";
|
||||||
|
addModalVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCategory() {
|
||||||
|
const name = (addForm.b_name || "").trim();
|
||||||
|
if (!name) {
|
||||||
|
message.warning("请填写名称");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const moduleCode = (addForm.b_module_code || "").trim() || null;
|
||||||
|
addSaving.value = true;
|
||||||
|
try {
|
||||||
|
if (editingCategoryId.value) {
|
||||||
|
await saveObjectApi([
|
||||||
|
{
|
||||||
|
table: "b_other_bmfl",
|
||||||
|
key_field: "b_id",
|
||||||
|
updates: [{
|
||||||
|
b_id: editingCategoryId.value,
|
||||||
|
b_name: name,
|
||||||
|
b_module_code: moduleCode,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
message.success("分类已修改");
|
||||||
|
addModalVisible.value = false;
|
||||||
|
await loadCategories();
|
||||||
|
|
||||||
|
// 如果当前选中节点被修改了模块代码,更新选中
|
||||||
|
if (selectedCategoryKey.value === editingCategoryId.value) {
|
||||||
|
const cat = categoryById.value.get(editingCategoryId.value);
|
||||||
|
selectedModuleCode.value = cat?.b_module_code || "";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idRes = await nextIdApi();
|
||||||
|
const newId = String(idRes.data);
|
||||||
|
|
||||||
|
let level = 1;
|
||||||
|
let path = newId;
|
||||||
|
if (addModalParentId.value) {
|
||||||
|
const parent = categoryById.value.get(String(addModalParentId.value));
|
||||||
|
if (parent) {
|
||||||
|
level = (Number(parent.b_level) || 0) + 1;
|
||||||
|
path = (parent.b_path || String(parent.b_id)) + "/" + newId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const siblings = categories.value.filter((item) =>
|
||||||
|
addModalParentId.value
|
||||||
|
? String(item.b_parent_id) === String(addModalParentId.value)
|
||||||
|
: item.b_parent_id == null,
|
||||||
|
);
|
||||||
|
const maxXh = siblings.reduce((max, item) => Math.max(max, Number(item.b_xh) || 0), 0);
|
||||||
|
|
||||||
|
await saveObjectApi([
|
||||||
|
{
|
||||||
|
table: "b_other_bmfl",
|
||||||
|
key_field: "b_id",
|
||||||
|
inserts: [
|
||||||
|
{
|
||||||
|
b_id: newId,
|
||||||
|
b_parent_id: addModalParentId.value || null,
|
||||||
|
b_name: name,
|
||||||
|
b_level: level,
|
||||||
|
b_path: path,
|
||||||
|
b_xh: maxXh + 10,
|
||||||
|
b_canuse: 1,
|
||||||
|
b_module_code: moduleCode,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
message.success("分类已添加");
|
||||||
|
addModalVisible.value = false;
|
||||||
|
await loadCategories();
|
||||||
|
} catch (e) {
|
||||||
|
message.error("保存失败:" + (e.message || e));
|
||||||
|
} finally {
|
||||||
|
addSaving.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasChildCategory(categoryId) {
|
||||||
|
for (const category of categories.value) {
|
||||||
|
if (String(category.b_parent_id || "") === categoryId) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasPersistedChildCategory(categoryId) {
|
||||||
|
const response = await pageDataApi({
|
||||||
|
viewName: "b_other_bmfl",
|
||||||
|
orderBy: "b_id ASC",
|
||||||
|
searchColumns: ["b_id"],
|
||||||
|
searchCondition: `[b_parent_id] = ${categoryId}`,
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
});
|
||||||
|
return Boolean(response.data?.rows?.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmDeleteCategory(node) {
|
||||||
|
if (!node || node.isFmsRoot || deleteSaving.value) return;
|
||||||
|
const categoryId = String(node.b_id || node.key || "");
|
||||||
|
if (!/^\d+$/.test(categoryId)) {
|
||||||
|
message.error("分类 ID 无效,无法删除");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasChildCategory(categoryId)) {
|
||||||
|
message.warning("该分类下还有子分类,不能删除");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteSaving.value = true;
|
||||||
|
Modal.confirm({
|
||||||
|
title: `确定删除「${node.title}」?`,
|
||||||
|
content: "此操作不可恢复",
|
||||||
|
okText: "删除",
|
||||||
|
cancelText: "取消",
|
||||||
|
okButtonProps: { danger: true },
|
||||||
|
onCancel: () => {
|
||||||
|
deleteSaving.value = false;
|
||||||
|
},
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const hasChild = await hasPersistedChildCategory(categoryId);
|
||||||
|
if (hasChild) {
|
||||||
|
message.warning("该分类下还有子分类,不能删除");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveObjectApi([
|
||||||
|
{
|
||||||
|
table: "b_other_bmfl",
|
||||||
|
key_field: "b_id",
|
||||||
|
deletes: [{ b_id: categoryId }],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
if (selectedCategoryKey.value === categoryId) {
|
||||||
|
selectedCategoryKey.value = ALL_CATEGORIES_KEY;
|
||||||
|
selectedModuleCode.value = "";
|
||||||
|
}
|
||||||
|
message.success("分类已删除");
|
||||||
|
await loadCategories();
|
||||||
|
} catch (error) {
|
||||||
|
message.error("删除失败:" + (error?.message || error));
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
deleteSaving.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadCategories);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="otherdata-page">
|
||||||
|
<a-splitter class="otherdata-splitter" lazy>
|
||||||
|
<a-splitter-panel :default-size="250" :min="210" :max="380" collapsible>
|
||||||
|
<aside class="category-panel">
|
||||||
|
<div class="category-tree-stage">
|
||||||
|
<FmsTree
|
||||||
|
v-model:expanded-keys="expandedCategoryKeys"
|
||||||
|
:tree-data="categoryTree"
|
||||||
|
:selected-keys="[selectedCategoryKey]"
|
||||||
|
root-title="其他数据"
|
||||||
|
:context-menu="buildCategoryContextMenu"
|
||||||
|
:context-menu-disabled="categoryLoading || addSaving || deleteSaving"
|
||||||
|
@select="onCategorySelect"
|
||||||
|
@menu-click="onCategoryMenuClick"
|
||||||
|
/>
|
||||||
|
<div v-if="categoryLoading" class="category-loading">
|
||||||
|
<a-spin size="small" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</a-splitter-panel>
|
||||||
|
|
||||||
|
<a-splitter-panel :min="520">
|
||||||
|
<main class="data-panel">
|
||||||
|
<FmsModuleListPage
|
||||||
|
v-if="selectedModuleCode"
|
||||||
|
:key="selectedModuleCode"
|
||||||
|
:data-code="selectedModuleCode"
|
||||||
|
/>
|
||||||
|
<a-empty
|
||||||
|
v-else
|
||||||
|
description="请选择具体数据项(叶子节点)"
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</a-splitter-panel>
|
||||||
|
</a-splitter>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="addModalVisible"
|
||||||
|
:title="categoryModalTitle"
|
||||||
|
:confirm-loading="addSaving"
|
||||||
|
@ok="saveCategory"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical" style="margin-top: 16px">
|
||||||
|
<a-form-item v-if="addModalParentName" label="上级分类">
|
||||||
|
<a-input :value="addModalParentName" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="名称">
|
||||||
|
<a-input v-model:value="addForm.b_name" :maxlength="150" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="数据模块代码">
|
||||||
|
<a-input
|
||||||
|
v-model:value="addForm.b_module_code"
|
||||||
|
:maxlength="100"
|
||||||
|
placeholder="留空则为分类分组节点;填入则为数据模块叶子节点(如 d_other_port)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.otherdata-page,
|
||||||
|
.otherdata-splitter {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.otherdata-page {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--fms-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--fms-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-panel,
|
||||||
|
.data-panel {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tree-stage {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tree-stage :deep(.ant-tree) {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tree-stage :deep(.ant-tree-node-content-wrapper) {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-loading {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: rgb(255 255 255 / 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-panel :deep(.fms-module-list-page) {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -47,12 +47,12 @@ function onRowOrderChange(rows) {
|
|||||||
emitUpdate();
|
emitUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableFields = computed(() => {
|
const fieldNameMap = computed(() => {
|
||||||
const options = [];
|
const map = new Map();
|
||||||
for (const field of props.fields || []) {
|
for (const field of props.fields || []) {
|
||||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
map.set(String(field.b_id), field.b_name || field.b_field);
|
||||||
}
|
}
|
||||||
return options;
|
return map;
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupOptions = computed(() => {
|
const groupOptions = computed(() => {
|
||||||
@@ -119,9 +119,10 @@ const columns = shallowRef([
|
|||||||
dataIndex: "b_field_id",
|
dataIndex: "b_field_id",
|
||||||
title: "字段",
|
title: "字段",
|
||||||
width: 160,
|
width: 160,
|
||||||
editor: {
|
customCell: ({ row }) => {
|
||||||
type: "select",
|
const id = row.b_field_id;
|
||||||
options: () => availableFields.value,
|
if (id == null) return "";
|
||||||
|
return fieldNameMap.value.get(String(id)) || id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -302,6 +302,15 @@ function flatten(rows) {
|
|||||||
return flat;
|
return flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 由父级「同步字段」调用:移除匹配指定 _managedId 的透视行(字段定义已删除时清理残留翻译)。
|
||||||
|
function removeByManagedIds(ids) {
|
||||||
|
if (!ids || ids.size === 0) return;
|
||||||
|
const idSet = new Set(Array.from(ids).map(String));
|
||||||
|
pivotRows.value = pivotRows.value.filter(
|
||||||
|
(row) => !(row._managedId && idSet.has(String(row._managedId))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 由父级「同步字段」调用:把默认翻译条目({ key, locale, value, managedId })并入草稿行。
|
// 由父级「同步字段」调用:把默认翻译条目({ key, locale, value, managedId })并入草稿行。
|
||||||
// 已存在同 b_key 的行只补全空值(不覆盖用户配置),避免新增重复键;逻辑见 utils/i18n.mergeI18nDefaults。
|
// 已存在同 b_key 的行只补全空值(不覆盖用户配置),避免新增重复键;逻辑见 utils/i18n.mergeI18nDefaults。
|
||||||
function mergeDefaults(entries) {
|
function mergeDefaults(entries) {
|
||||||
@@ -374,7 +383,7 @@ function markSaved() {
|
|||||||
originalsFlat.value = cloneData(flatten(pivotRows.value));
|
originalsFlat.value = cloneData(flatten(pivotRows.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ buildSaveRequest, markSaved, mergeDefaults, refreshKey, reconcileManagedRows });
|
defineExpose({ buildSaveRequest, markSaved, mergeDefaults, refreshKey, reconcileManagedRows, removeByManagedIds });
|
||||||
|
|
||||||
const displayedData = computed(() => {
|
const displayedData = computed(() => {
|
||||||
const kw = searchKey.value.toLowerCase();
|
const kw = searchKey.value.toLowerCase();
|
||||||
|
|||||||
@@ -47,12 +47,12 @@ function onRowOrderChange(rows) {
|
|||||||
emitUpdate();
|
emitUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableFields = computed(() => {
|
const fieldNameMap = computed(() => {
|
||||||
const options = [];
|
const map = new Map();
|
||||||
for (const field of props.fields || []) {
|
for (const field of props.fields || []) {
|
||||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
map.set(String(field.b_id), field.b_name || field.b_field);
|
||||||
}
|
}
|
||||||
return options;
|
return map;
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupOptions = computed(() => {
|
const groupOptions = computed(() => {
|
||||||
@@ -119,12 +119,18 @@ const columns = shallowRef([
|
|||||||
dataIndex: "b_field_id",
|
dataIndex: "b_field_id",
|
||||||
title: "字段",
|
title: "字段",
|
||||||
width: 160,
|
width: 160,
|
||||||
editor: {
|
customCell: ({ row }) => {
|
||||||
type: "select",
|
const id = row.b_field_id;
|
||||||
options: () => availableFields.value,
|
if (id == null) return "";
|
||||||
|
return fieldNameMap.value.get(String(id)) || id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ dataIndex: "b_width", title: "列宽", width: 80 },
|
{
|
||||||
|
dataIndex: "b_width",
|
||||||
|
title: "列宽",
|
||||||
|
width: 80,
|
||||||
|
editor: { type: "input" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: "b_group_id",
|
dataIndex: "b_group_id",
|
||||||
title: "分组",
|
title: "分组",
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ function onRowOrderChange(rows) {
|
|||||||
emitUpdate();
|
emitUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
const availableFields = computed(() => {
|
const fieldNameMap = computed(() => {
|
||||||
const options = [];
|
const map = new Map();
|
||||||
for (const field of props.fields || []) {
|
for (const field of props.fields || []) {
|
||||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
map.set(String(field.b_id), field.b_name || field.b_field);
|
||||||
}
|
}
|
||||||
return options;
|
return map;
|
||||||
});
|
});
|
||||||
|
|
||||||
function addRow() {
|
function addRow() {
|
||||||
@@ -84,9 +84,10 @@ const columns = shallowRef([
|
|||||||
dataIndex: "b_field_id",
|
dataIndex: "b_field_id",
|
||||||
title: "字段",
|
title: "字段",
|
||||||
width: 160,
|
width: 160,
|
||||||
editor: {
|
customCell: ({ row }) => {
|
||||||
type: "select",
|
const id = row.b_field_id;
|
||||||
options: () => availableFields.value,
|
if (id == null) return "";
|
||||||
|
return fieldNameMap.value.get(String(id)) || id;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ const treeLoading = ref(false);
|
|||||||
const expandedTreeKeys = ref([]);
|
const expandedTreeKeys = ref([]);
|
||||||
const selectedModuleId = ref(null);
|
const selectedModuleId = ref(null);
|
||||||
const skipLoad = ref(false);
|
const skipLoad = ref(false);
|
||||||
|
const expandTimer = ref(null);
|
||||||
const activeTab = ref("basic");
|
const activeTab = ref("basic");
|
||||||
function buildTreeContextMenu(node) {
|
function buildTreeContextMenu(node) {
|
||||||
const items = [];
|
const items = [];
|
||||||
@@ -442,24 +443,54 @@ async function loadModuleConfig(moduleId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- 树节点选择 ----
|
// ---- 树节点选择 ----
|
||||||
|
function clearExpandTimer() {
|
||||||
|
if (expandTimer.value != null) {
|
||||||
|
clearTimeout(expandTimer.value);
|
||||||
|
expandTimer.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onTreeSelect(keys, info) {
|
function onTreeSelect(keys, info) {
|
||||||
if (busy.value) return;
|
if (busy.value) return;
|
||||||
if (info?.node?.isFmsRoot) {
|
if (info?.node?.isFmsRoot) {
|
||||||
selectedModuleId.value = null;
|
selectedModuleId.value = null;
|
||||||
|
clearExpandTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nodeKey = info?.node?.key ?? keys[0];
|
const nodeKey = info?.node?.key ?? keys[0];
|
||||||
if (nodeKey == null) return;
|
if (nodeKey == null) return;
|
||||||
|
|
||||||
const key = String(nodeKey);
|
const key = String(nodeKey);
|
||||||
selectedModuleId.value = key;
|
|
||||||
|
|
||||||
const node = findNode(key);
|
const node = findNode(key);
|
||||||
if (!node?.children?.length) return;
|
const hasChildren = node?.children?.length;
|
||||||
|
|
||||||
|
clearExpandTimer();
|
||||||
|
|
||||||
|
if (!hasChildren) {
|
||||||
|
// 叶子节点:立即加载数据
|
||||||
|
selectedModuleId.value = key;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 父节点:处理展开/收起
|
||||||
const expandedKeys = expandedTreeKeys.value.map(String);
|
const expandedKeys = expandedTreeKeys.value.map(String);
|
||||||
expandedTreeKeys.value = expandedKeys.includes(key)
|
const isCurrentlyExpanded = expandedKeys.includes(key);
|
||||||
? expandedKeys.filter((item) => item !== key)
|
|
||||||
: [...expandedKeys, key];
|
if (isCurrentlyExpanded) {
|
||||||
|
// 收起:立即生效,同时加载数据
|
||||||
|
expandedTreeKeys.value = expandedKeys.filter((item) => item !== key);
|
||||||
|
selectedModuleId.value = key;
|
||||||
|
} else {
|
||||||
|
// 展开:立即高亮选中节点,动画完毕后再加载数据
|
||||||
|
expandedTreeKeys.value = [...expandedKeys, key];
|
||||||
|
skipLoad.value = true;
|
||||||
|
selectedModuleId.value = key; // 立即高亮,watch 中 skipLoad=true 跳过加载
|
||||||
|
expandTimer.value = setTimeout(() => {
|
||||||
|
expandTimer.value = null;
|
||||||
|
loadedModuleId.value = null;
|
||||||
|
void loadModuleConfig(key);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(selectedModuleId, (id) => {
|
watch(selectedModuleId, (id) => {
|
||||||
@@ -1009,11 +1040,26 @@ async function syncFields() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 视图中不存在的已有字段标记为不可用
|
// 视图中不存在的已有字段:从字段定义中移除,并清理其在列表/编辑/查询配置中的残留行。
|
||||||
|
const removedFieldIds = new Set();
|
||||||
for (const existing of existingFields) {
|
for (const existing of existingFields) {
|
||||||
const fieldName = existing.b_field ? existing.b_field.toLowerCase() : "";
|
const fieldName = existing.b_field ? existing.b_field.toLowerCase() : "";
|
||||||
if (!viewColumnSet.has(fieldName)) {
|
if (!viewColumnSet.has(fieldName)) {
|
||||||
mergedFields.push({ ...existing, b_canuse: 0 });
|
removedFieldIds.add(String(existing.b_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (removedFieldIds.size > 0) {
|
||||||
|
for (const section of ["listConfig", "editConfig", "queryConfig"]) {
|
||||||
|
drafts.value[section] = (drafts.value[section] || []).filter(
|
||||||
|
(row) => !removedFieldIds.has(String(row.b_field_id)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 多语言面板中移除旧字段对应的受管翻译行(_managedId 格式为 field:<b_id>)
|
||||||
|
if (i18nPanelRef.value) {
|
||||||
|
const managedIds = new Set(
|
||||||
|
Array.from(removedFieldIds).map((id) => `field:${id}`),
|
||||||
|
);
|
||||||
|
i18nPanelRef.value.removeByManagedIds(managedIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1125,12 +1171,8 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
|||||||
:selected-keys="selectedModuleId ? [selectedModuleId] : []"
|
:selected-keys="selectedModuleId ? [selectedModuleId] : []"
|
||||||
:context-menu="buildTreeContextMenu"
|
:context-menu="buildTreeContextMenu"
|
||||||
:context-menu-disabled="busy"
|
:context-menu-disabled="busy"
|
||||||
draggable
|
|
||||||
:allow-drop="allowDrop"
|
|
||||||
@select="onTreeSelect"
|
@select="onTreeSelect"
|
||||||
@menu-click="onTreeMenuClick"
|
@menu-click="onTreeMenuClick"
|
||||||
@drop="onTreeDrop"
|
|
||||||
@drag-start="onTreeDragStart"
|
|
||||||
>
|
>
|
||||||
<template #title="node">
|
<template #title="node">
|
||||||
<span class="mm-tree-node">
|
<span class="mm-tree-node">
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function batchDelete() {
|
|||||||
const deletes = keys.map((b_id) => ({ b_id }));
|
const deletes = keys.map((b_id) => ({ b_id }));
|
||||||
await saveObjectApi([
|
await saveObjectApi([
|
||||||
{
|
{
|
||||||
table: "s_log_login",
|
table: "s_login_log",
|
||||||
key_field: "b_id",
|
key_field: "b_id",
|
||||||
inserts: [],
|
inserts: [],
|
||||||
updates: [],
|
updates: [],
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<script setup>
|
||||||
|
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="user-page">
|
||||||
|
<FmsModuleListPage data-code="b_user" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.user-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--fms-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--fms-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-page :deep(.fms-module-list-page) {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in new issue
Block a user