From 1c839b707a5cc3ac68a8185d3b2a6580b59b2da2 Mon Sep 17 00:00:00 2001 From: ZhangAo Date: Wed, 29 Jul 2026 17:33:21 +0800 Subject: [PATCH] 20260729173317 --- .../config/migrations/000_reset_metadata.sql | 19 + .../migrations/008_add_edit_visible.sql | 14 + .../migrations/009_add_module_auto_code.sql | 25 ++ .../010_normalize_auto_code_rule.sql | 52 +++ .../migrations/011_migrate_othercompany.sql | 156 +++++++++ .../012_add_othercompany_module.sql | 252 ++++++++++++++ .../fmsapi/controller/DataController.java | 12 + .../fmsapi/service/DataSaveService.java | 328 ++++++++++++++++++ .../fmsapi/service/DataSaveServiceTests.java | 40 +++ .../fms-module-list/FmsModuleListPage.vue | 94 ++++- .../fms-module-list/FmsModuleTable.vue | 37 +- .../fms-module-list/FmsQueryToolbar.vue | 2 +- .../src/components/fms-table/FmsTable.vue | 32 +- .../src/layouts/components/AppSidebar.vue | 20 +- code/fms/fms-vue/src/router/routes.js | 14 +- code/fms/fms-vue/src/services/api.js | 10 + .../src/views/base/othercompany/index.vue | 250 +++++++++++++ .../src/views/module-management/autoCode.js | 28 ++ .../components/ModuleAutoCodePanel.vue | 230 ++++++++++++ .../components/ModuleEditConfigPanel.vue | 8 +- .../components/ModuleFieldsPanel.vue | 6 +- .../components/ModuleListConfigPanel.vue | 1 - .../components/ModuleQueryConfigPanel.vue | 1 - .../src/views/module-management/index.vue | 187 +++++++++- .../src/views/system/login-log/index.vue | 88 ++++- code/fms/新数据库结构.md | 25 +- 26 files changed, 1902 insertions(+), 29 deletions(-) create mode 100644 code/fms/fms-api/config/migrations/008_add_edit_visible.sql create mode 100644 code/fms/fms-api/config/migrations/009_add_module_auto_code.sql create mode 100644 code/fms/fms-api/config/migrations/010_normalize_auto_code_rule.sql create mode 100644 code/fms/fms-api/config/migrations/011_migrate_othercompany.sql create mode 100644 code/fms/fms-api/config/migrations/012_add_othercompany_module.sql create mode 100644 code/fms/fms-vue/src/views/base/othercompany/index.vue create mode 100644 code/fms/fms-vue/src/views/module-management/autoCode.js create mode 100644 code/fms/fms-vue/src/views/module-management/components/ModuleAutoCodePanel.vue diff --git a/code/fms/fms-api/config/migrations/000_reset_metadata.sql b/code/fms/fms-api/config/migrations/000_reset_metadata.sql index 8aec291f..e6f16559 100644 --- a/code/fms/fms-api/config/migrations/000_reset_metadata.sql +++ b/code/fms/fms-api/config/migrations/000_reset_metadata.sql @@ -80,6 +80,24 @@ GO ); CREATE INDEX IX_s_module_field_module ON dbo.s_module_field (b_module_id, b_canuse, b_id); + CREATE TABLE dbo.s_module_auto_code ( + b_id bigint NOT NULL, + b_module_id bigint NOT NULL, + b_field_id bigint NOT NULL, + b_prefix nvarchar(100) NOT NULL CONSTRAINT DF_s_module_auto_code_prefix DEFAULT (N''), + b_date_format varchar(30) NOT NULL CONSTRAINT DF_s_module_auto_code_date_format DEFAULT ('yyyyMM'), + b_separator nvarchar(10) NOT NULL CONSTRAINT DF_s_module_auto_code_separator DEFAULT (N'-'), + b_sequence_width int NOT NULL CONSTRAINT DF_s_module_auto_code_sequence_width DEFAULT (5), + b_reset_type varchar(10) NOT NULL CONSTRAINT DF_s_module_auto_code_reset_type DEFAULT ('month'), + b_start_value bigint NOT NULL CONSTRAINT DF_s_module_auto_code_start_value DEFAULT (1), + b_current_period varchar(8) NULL, + b_current_value bigint NOT NULL CONSTRAINT DF_s_module_auto_code_current_value DEFAULT (0), + b_canuse int NOT NULL CONSTRAINT DF_s_module_auto_code_canuse DEFAULT (1), + CONSTRAINT PK_s_module_auto_code PRIMARY KEY (b_id), + CONSTRAINT UQ_s_module_auto_code_module UNIQUE (b_module_id) + ); + CREATE INDEX IX_s_module_auto_code_field ON dbo.s_module_auto_code (b_field_id); + CREATE TABLE dbo.s_module_field_group ( b_id bigint NOT NULL, b_module_id bigint NOT NULL, @@ -113,6 +131,7 @@ GO b_required int NOT NULL CONSTRAINT DF_s_module_field_edit_required DEFAULT (0), b_readonly int NOT NULL CONSTRAINT DF_s_module_field_edit_readonly DEFAULT (0), b_disabled int NOT NULL CONSTRAINT DF_s_module_field_edit_disabled DEFAULT (0), + b_visible int NOT NULL CONSTRAINT DF_s_module_field_edit_visible DEFAULT (1), b_group_id bigint NULL, b_xh int NOT NULL CONSTRAINT DF_s_module_field_edit_xh DEFAULT (0), CONSTRAINT PK_s_module_field_edit PRIMARY KEY (b_id), diff --git a/code/fms/fms-api/config/migrations/008_add_edit_visible.sql b/code/fms/fms-api/config/migrations/008_add_edit_visible.sql new file mode 100644 index 00000000..ca69efbe --- /dev/null +++ b/code/fms/fms-api/config/migrations/008_add_edit_visible.sql @@ -0,0 +1,14 @@ +/* 为编辑配置增加显示开关,存量配置默认显示。 */ +SET NOCOUNT ON; + +IF NOT EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = OBJECT_ID(N'dbo.s_module_field_edit') + AND name = N'b_visible' +) +BEGIN + ALTER TABLE dbo.s_module_field_edit + ADD b_visible int NOT NULL + CONSTRAINT DF_s_module_field_edit_visible DEFAULT (1) WITH VALUES; +END; diff --git a/code/fms/fms-api/config/migrations/009_add_module_auto_code.sql b/code/fms/fms-api/config/migrations/009_add_module_auto_code.sql new file mode 100644 index 00000000..a80de0f2 --- /dev/null +++ b/code/fms/fms-api/config/migrations/009_add_module_auto_code.sql @@ -0,0 +1,25 @@ +SET NOCOUNT ON; +SET XACT_ABORT ON; + +IF OBJECT_ID(N'dbo.s_module_auto_code', N'U') IS NULL +BEGIN + CREATE TABLE dbo.s_module_auto_code ( + b_id bigint NOT NULL, + b_module_id bigint NOT NULL, + b_field_id bigint NOT NULL, + b_prefix nvarchar(100) NOT NULL CONSTRAINT DF_s_module_auto_code_prefix DEFAULT (N''), + b_date_format varchar(30) NOT NULL CONSTRAINT DF_s_module_auto_code_date_format DEFAULT ('yyyyMM'), + b_separator nvarchar(10) NOT NULL CONSTRAINT DF_s_module_auto_code_separator DEFAULT (N'-'), + b_sequence_width int NOT NULL CONSTRAINT DF_s_module_auto_code_sequence_width DEFAULT (5), + b_reset_type varchar(10) NOT NULL CONSTRAINT DF_s_module_auto_code_reset_type DEFAULT ('month'), + b_start_value bigint NOT NULL CONSTRAINT DF_s_module_auto_code_start_value DEFAULT (1), + b_current_period varchar(8) NULL, + b_current_value bigint NOT NULL CONSTRAINT DF_s_module_auto_code_current_value DEFAULT (0), + b_canuse int NOT NULL CONSTRAINT DF_s_module_auto_code_canuse DEFAULT (1), + CONSTRAINT PK_s_module_auto_code PRIMARY KEY (b_id), + CONSTRAINT UQ_s_module_auto_code_module UNIQUE (b_module_id) + ); + + CREATE INDEX IX_s_module_auto_code_field + ON dbo.s_module_auto_code (b_field_id); +END; diff --git a/code/fms/fms-api/config/migrations/010_normalize_auto_code_rule.sql b/code/fms/fms-api/config/migrations/010_normalize_auto_code_rule.sql new file mode 100644 index 00000000..897b46e0 --- /dev/null +++ b/code/fms/fms-api/config/migrations/010_normalize_auto_code_rule.sql @@ -0,0 +1,52 @@ +SET NOCOUNT ON; +SET XACT_ABORT ON; + +IF OBJECT_ID(N'dbo.s_module_auto_code', N'U') IS NOT NULL +BEGIN + IF COL_LENGTH(N'dbo.s_module_auto_code', N'b_prefix') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD b_prefix nvarchar(100) NULL; + IF COL_LENGTH(N'dbo.s_module_auto_code', N'b_date_format') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD b_date_format varchar(30) NULL; + IF COL_LENGTH(N'dbo.s_module_auto_code', N'b_separator') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD b_separator nvarchar(10) NULL; + IF COL_LENGTH(N'dbo.s_module_auto_code', N'b_sequence_width') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD b_sequence_width int NULL; + + IF COL_LENGTH(N'dbo.s_module_auto_code', N'b_rule_config') IS NOT NULL + BEGIN + EXEC sys.sp_executesql N' + UPDATE dbo.s_module_auto_code + SET b_prefix = COALESCE(JSON_VALUE(b_rule_config, ''$.prefix''), N''''), + b_date_format = COALESCE(JSON_VALUE(b_rule_config, ''$.dateFormat''), ''''), + b_separator = COALESCE(JSON_VALUE(b_rule_config, ''$.separator''), N''''), + b_sequence_width = COALESCE( + TRY_CONVERT(int, JSON_VALUE(b_rule_config, ''$.sequenceWidth'')), + 5 + );'; + END; + + EXEC sys.sp_executesql N' + UPDATE dbo.s_module_auto_code + SET b_prefix = COALESCE(b_prefix, N''''), + b_date_format = COALESCE(b_date_format, ''''), + b_separator = COALESCE(b_separator, N''''), + b_sequence_width = COALESCE(b_sequence_width, 5);'; + + EXEC sys.sp_executesql N' + ALTER TABLE dbo.s_module_auto_code ALTER COLUMN b_prefix nvarchar(100) NOT NULL; + ALTER TABLE dbo.s_module_auto_code ALTER COLUMN b_date_format varchar(30) NOT NULL; + ALTER TABLE dbo.s_module_auto_code ALTER COLUMN b_separator nvarchar(10) NOT NULL; + ALTER TABLE dbo.s_module_auto_code ALTER COLUMN b_sequence_width int NOT NULL;'; + + IF OBJECT_ID(N'dbo.DF_s_module_auto_code_prefix', N'D') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD CONSTRAINT DF_s_module_auto_code_prefix DEFAULT (N'') FOR b_prefix; + IF OBJECT_ID(N'dbo.DF_s_module_auto_code_date_format', N'D') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD CONSTRAINT DF_s_module_auto_code_date_format DEFAULT ('yyyyMM') FOR b_date_format; + IF OBJECT_ID(N'dbo.DF_s_module_auto_code_separator', N'D') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD CONSTRAINT DF_s_module_auto_code_separator DEFAULT (N'-') FOR b_separator; + IF OBJECT_ID(N'dbo.DF_s_module_auto_code_sequence_width', N'D') IS NULL + ALTER TABLE dbo.s_module_auto_code ADD CONSTRAINT DF_s_module_auto_code_sequence_width DEFAULT (5) FOR b_sequence_width; + + IF COL_LENGTH(N'dbo.s_module_auto_code', N'b_rule_config') IS NOT NULL + EXEC sys.sp_executesql N'ALTER TABLE dbo.s_module_auto_code DROP COLUMN b_rule_config;'; +END; diff --git a/code/fms/fms-api/config/migrations/011_migrate_othercompany.sql b/code/fms/fms-api/config/migrations/011_migrate_othercompany.sql new file mode 100644 index 00000000..920fc63c --- /dev/null +++ b/code/fms/fms-api/config/migrations/011_migrate_othercompany.sql @@ -0,0 +1,156 @@ +SET NOCOUNT ON; +SET XACT_ABORT ON; + +BEGIN TRANSACTION; + + IF OBJECT_ID(N'dbo.b_othercompany', N'U') IS NULL + BEGIN + CREATE TABLE dbo.b_othercompany ( + b_id varchar(50) NOT NULL, + b_pycode nvarchar(50) NULL, + b_type varchar(200) NULL, + b_name nvarchar(500) NULL, + b_shortname nvarchar(50) NULL, + b_linkman nvarchar(200) NULL, + b_phone nvarchar(500) NULL, + b_addr nvarchar(500) NULL, + b_firstlinkdate datetime NULL, + b_htno nvarchar(50) NULL, + b_htdate date NULL, + b_htexpiredate date NULL, + b_black varchar(1) NULL, + b_canuse varchar(1) NULL, + b_zl int NULL, + b_tel nvarchar(50) NULL, + b_fax nvarchar(500) NULL, + b_www nvarchar(50) NULL, + b_email nvarchar(50) NULL, + b_bankacount nvarchar(500) NULL, + b_invaccount nvarchar(500) NULL, + b_cate_id nvarchar(500) NULL, + b_bz1 nvarchar(500) NULL, + b_bz2 nvarchar(500) NULL, + b_bz3 nvarchar(500) NULL, + b_bz4 nvarchar(500) NULL, + b_province nvarchar(50) NULL, + b_city nvarchar(50) NULL, + b_area nvarchar(50) NULL, + b_country nvarchar(50) NULL, + b_edicode nvarchar(50) NULL, + b_showcolor int NULL, + b_type_id nvarchar(200) NULL, + b_from_id nvarchar(50) NULL, + b_type1_id nvarchar(50) NULL, + b_type2_id nvarchar(50) NULL, + b_type3_id nvarchar(50) NULL, + b_type4_id nvarchar(50) NULL, + b_type5_id nvarchar(50) NULL, + b_owner_id varchar(50) NULL, + b_inputuser_id varchar(50) NULL, + b_inputdatetime datetime NULL, + b_updateuser_id varchar(50) NULL, + b_updatedatetime datetime NULL, + b_password varchar(50) NULL, + b_state nvarchar(50) NULL, + b_bankacount_e nvarchar(500) NULL, + b_addr_e nvarchar(500) NULL, + b_name_e nvarchar(500) NULL, + CONSTRAINT PK_b_othercompany PRIMARY KEY (b_id) + ); + + CREATE INDEX IX_b_othercompany_name + ON dbo.b_othercompany (b_name, b_id); + CREATE INDEX IX_b_othercompany_pycode + ON dbo.b_othercompany (b_pycode, b_id); + CREATE INDEX IX_b_othercompany_owner + ON dbo.b_othercompany (b_owner_id, b_id); + END; + + IF NOT EXISTS ( + SELECT 1 + FROM sys.indexes + WHERE object_id = OBJECT_ID(N'dbo.b_othercompany') + AND name = N'IX_b_othercompany_category' + ) + CREATE INDEX IX_b_othercompany_category + ON dbo.b_othercompany (b_cate_id, b_id); + + DECLARE @RebuildCategory bit = 0; + DECLARE @CategoryObjectId int = OBJECT_ID(N'dbo.b_othercompany_bmfl', N'U'); + + IF @CategoryObjectId IS NULL + SET @RebuildCategory = 1; + ELSE IF EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = @CategoryObjectId + AND name IN ( + N'mc_a', N'bm_b', N'mc_b', N'js', + N'bm_1_b', N'mc_1_b', N'bm_2_b', N'mc_2_b', + N'bm_3_b', N'mc_3_b', N'bm_4_b', N'mc_4_b', + N'bm_5_b', N'mc_5_b', N'bm_6_b', N'mc_6_b' + ) + ) + SET @RebuildCategory = 1; + ELSE IF NOT EXISTS ( + SELECT 1 + FROM sys.columns + WHERE object_id = @CategoryObjectId + AND name = N'b_id' + AND system_type_id = TYPE_ID(N'bigint') + ) + SET @RebuildCategory = 1; + ELSE IF EXISTS ( + SELECT required.name + FROM (VALUES + (N'b_id'), (N'b_parent_id'), (N'b_code'), (N'b_name'), + (N'b_level'), (N'b_path'), (N'b_xh'), (N'b_canuse'), + (N'b_inputuser_id'), (N'b_inputdatetime'), + (N'b_updateuser_id'), (N'b_updatedatetime') + ) AS required(name) + WHERE NOT EXISTS ( + SELECT 1 + FROM sys.columns AS actual + WHERE actual.object_id = @CategoryObjectId + AND actual.name = required.name + ) + ) + SET @RebuildCategory = 1; + + IF @RebuildCategory = 1 + BEGIN + DELETE FROM dbo.b_othercompany; + + IF @CategoryObjectId IS NOT NULL + DROP TABLE dbo.b_othercompany_bmfl; + + CREATE TABLE dbo.b_othercompany_bmfl ( + b_id bigint NOT NULL, + b_parent_id bigint NULL, + b_code varchar(50) NOT NULL, + b_name nvarchar(150) NOT NULL, + b_level int NOT NULL CONSTRAINT DF_b_othercompany_bmfl_level DEFAULT (1), + b_path varchar(1000) NOT NULL, + b_xh int NOT NULL CONSTRAINT DF_b_othercompany_bmfl_xh DEFAULT (0), + b_canuse int NOT NULL CONSTRAINT DF_b_othercompany_bmfl_canuse DEFAULT (1), + b_inputuser_id bigint NULL, + b_inputdatetime datetime NULL, + b_updateuser_id bigint NULL, + b_updatedatetime datetime NULL, + CONSTRAINT PK_b_othercompany_bmfl PRIMARY KEY (b_id), + CONSTRAINT UQ_b_othercompany_bmfl_code UNIQUE (b_code), + CONSTRAINT CK_b_othercompany_bmfl_level CHECK (b_level >= 1), + CONSTRAINT CK_b_othercompany_bmfl_parent CHECK ( + b_parent_id IS NULL OR b_parent_id <> b_id + ), + CONSTRAINT FK_b_othercompany_bmfl_parent FOREIGN KEY (b_parent_id) + REFERENCES dbo.b_othercompany_bmfl (b_id) + ); + + CREATE INDEX IX_b_othercompany_bmfl_parent + ON dbo.b_othercompany_bmfl (b_parent_id, b_xh, b_id); + CREATE INDEX IX_b_othercompany_bmfl_path + ON dbo.b_othercompany_bmfl (b_path, b_xh, b_id); + END; + +COMMIT TRANSACTION; diff --git a/code/fms/fms-api/config/migrations/012_add_othercompany_module.sql b/code/fms/fms-api/config/migrations/012_add_othercompany_module.sql new file mode 100644 index 00000000..0295a305 --- /dev/null +++ b/code/fms/fms-api/config/migrations/012_add_othercompany_module.sql @@ -0,0 +1,252 @@ +SET NOCOUNT ON; +SET XACT_ABORT ON; + +IF OBJECT_ID(N'dbo.b_othercompany', N'U') IS NULL + THROW 51210, N'dbo.b_othercompany does not exist. Run migration 011 first.', 1; + +BEGIN TRANSACTION; + + 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 + BEGIN + SET @BaseId = 920000000000001000; + IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @BaseId) + THROW 51211, N'The reserved base data directory 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 ( + @BaseId, 'base', NULL, N'基础数据', 'menu.base', 'directory', + NULL, NULL, NULL, NULL, NULL, 1, 1, 20 + ); + END; + + DECLARE @PageId bigint = ( + SELECT TOP (1) b_id FROM dbo.s_module WHERE b_code = 'base_othercompany' + ); + IF @PageId IS NULL + BEGIN + SET @PageId = 920000000000001001; + IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @PageId) + THROW 51212, N'The reserved other company 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, 'base_othercompany', @BaseId, N'合作伙伴', + 'menu.base_othercompany', 'page', '/base/othercompany', + NULL, NULL, NULL, NULL, 1, 1, 10 + ); + END; + + DECLARE @DataId bigint = ( + SELECT TOP (1) b_id FROM dbo.s_module WHERE b_code = 'b_othercompany' + ); + IF @DataId IS NULL + BEGIN + SET @DataId = 920000000000001002; + IF EXISTS (SELECT 1 FROM dbo.s_module WHERE b_id = @DataId) + THROW 51213, N'The reserved other company 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, 'b_othercompany', @PageId, N'合作伙伴列表', + 'module.b_othercompany', 'data', NULL, 'b_othercompany', + 'b_othercompany', 'b_id', 'b_name ASC, b_id ASC', 0, 1, 10 + ); + END; + + DECLARE @Fields 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 @Fields (b_id, b_field, b_name, b_type, b_xh) VALUES + (920000000000002001, 'b_id', N'系统编号', 'input', 10), + (920000000000002002, 'b_pycode', N'助记码', 'input', 20), + (920000000000002003, 'b_type', N'伙伴类型', 'input', 30), + (920000000000002004, 'b_name', N'公司全称', 'input', 40), + (920000000000002005, 'b_shortname', N'简称', 'input', 50), + (920000000000002006, 'b_linkman', N'主联系人', 'input', 60), + (920000000000002007, 'b_phone', N'联系电话', 'input', 70), + (920000000000002008, 'b_addr', N'详细地址', 'input', 80), + (920000000000002009, 'b_firstlinkdate', N'首次联系', 'datetime', 90), + (920000000000002010, 'b_htno', N'合同号', 'input', 100), + (920000000000002011, 'b_htdate', N'签订日期', 'date', 110), + (920000000000002012, 'b_htexpiredate', N'到期日期', 'date', 120), + (920000000000002013, 'b_black', N'黑名单', 'checkbox', 130), + (920000000000002014, 'b_canuse', N'启用', 'checkbox', 140), + (920000000000002015, 'b_zl', N'级别', 'number', 150), + (920000000000002016, 'b_tel', N'电话', 'input', 160), + (920000000000002017, 'b_fax', N'传真', 'input', 170), + (920000000000002018, 'b_www', N'网站', 'input', 180), + (920000000000002019, 'b_email', N'邮箱', 'input', 190), + (920000000000002020, 'b_bankacount', N'银行账号', 'input', 200), + (920000000000002021, 'b_invaccount', N'开票信息', 'input', 210), + (920000000000002022, 'b_cate_id', N'所属分类', 'input', 220), + (920000000000002023, 'b_bz1', N'备注1', 'input', 230), + (920000000000002024, 'b_bz2', N'备注2', 'input', 240), + (920000000000002025, 'b_bz3', N'备注3', 'input', 250), + (920000000000002026, 'b_bz4', N'备注4', 'input', 260), + (920000000000002027, 'b_province', N'省份', 'input', 270), + (920000000000002028, 'b_city', N'城市', 'input', 280), + (920000000000002029, 'b_area', N'区域', 'input', 290), + (920000000000002030, 'b_country', N'国家', 'input', 300), + (920000000000002031, 'b_edicode', N'EDI代码', 'input', 310), + (920000000000002032, 'b_showcolor', N'显示颜色', 'number', 320), + (920000000000002033, 'b_type_id', N'客户级别', 'input', 330), + (920000000000002034, 'b_from_id', N'客户来源', 'input', 340), + (920000000000002035, 'b_type1_id', N'分类1', 'input', 350), + (920000000000002036, 'b_type2_id', N'分类2', 'input', 360), + (920000000000002037, 'b_type3_id', N'分类3', 'input', 370), + (920000000000002038, 'b_type4_id', N'分类4', 'input', 380), + (920000000000002039, 'b_type5_id', N'分类5', 'input', 390), + (920000000000002040, 'b_owner_id', N'负责人', 'input', 400), + (920000000000002041, 'b_inputuser_id', N'录入账号', 'input', 410), + (920000000000002042, 'b_inputdatetime', N'录入时间', 'datetime', 420), + (920000000000002043, 'b_updateuser_id', N'修改账号', 'input', 430), + (920000000000002044, 'b_updatedatetime', N'修改时间', 'datetime', 440), + (920000000000002045, 'b_password', N'查询密码', 'input', 450), + (920000000000002046, 'b_state', N'审核状态', 'input', 460), + (920000000000002047, 'b_bankacount_e', N'英文银行账号', 'input', 470), + (920000000000002048, 'b_addr_e', N'英文地址', 'input', 480), + (920000000000002049, 'b_name_e', N'英文名称', 'input', 490); + + IF EXISTS ( + SELECT 1 + FROM @Fields source + JOIN dbo.s_module_field target ON target.b_id = source.b_id + WHERE target.b_module_id <> @DataId OR target.b_field <> source.b_field + ) + THROW 51214, N'A reserved other company field ID is already in use.', 1; + + 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.b_othercompany.' + source.b_field, source.b_type, 1 + FROM @Fields source + WHERE NOT EXISTS ( + SELECT 1 FROM dbo.s_module_field target + WHERE target.b_module_id = @DataId AND target.b_field = source.b_field + ); + + DECLARE @ListFields TABLE ( + b_id bigint NOT NULL, + b_field varchar(50) NOT NULL, + b_xh int NOT NULL, + b_width int NOT NULL + ); + INSERT INTO @ListFields (b_id, b_field, b_xh, b_width) VALUES + (920000000000003001, 'b_id', 10, 150), + (920000000000003002, 'b_name', 20, 220), + (920000000000003003, 'b_shortname', 30, 130), + (920000000000003004, 'b_pycode', 40, 100), + (920000000000003005, 'b_linkman', 50, 110), + (920000000000003006, 'b_phone', 60, 140), + (920000000000003007, 'b_type', 70, 120), + (920000000000003008, 'b_cate_id', 80, 140), + (920000000000003009, 'b_state', 90, 100), + (920000000000003010, 'b_canuse', 100, 76), + (920000000000003011, 'b_updatedatetime', 110, 150); + + IF EXISTS ( + SELECT 1 FROM @ListFields source + JOIN dbo.s_module_field_list target ON target.b_id = source.b_id + WHERE target.b_module_id <> @DataId + ) + THROW 51215, N'A reserved other company list ID is already in use.', 1; + + 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, field.b_id, NULL, 1, source.b_xh, source.b_width + FROM @ListFields source + JOIN dbo.s_module_field field + ON field.b_module_id = @DataId AND field.b_field = source.b_field + WHERE NOT EXISTS ( + SELECT 1 FROM dbo.s_module_field_list target + WHERE target.b_module_id = @DataId AND target.b_field_id = field.b_id + ); + + DECLARE @QueryFields TABLE ( + b_id bigint NOT NULL, + b_field varchar(50) NOT NULL, + b_xh int NOT NULL + ); + INSERT INTO @QueryFields (b_id, b_field, b_xh) VALUES + (920000000000004001, 'b_name', 10), + (920000000000004002, 'b_shortname', 20), + (920000000000004003, 'b_pycode', 30), + (920000000000004004, 'b_linkman', 40), + (920000000000004005, 'b_phone', 50); + + IF EXISTS ( + SELECT 1 FROM @QueryFields source + JOIN dbo.s_module_field_query target ON target.b_id = source.b_id + WHERE target.b_module_id <> @DataId + ) + THROW 51216, N'A reserved other company query ID is already in use.', 1; + + 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, field.b_id, 'input', 'like', NULL, source.b_xh, 1 + FROM @QueryFields source + JOIN dbo.s_module_field field + ON field.b_module_id = @DataId AND field.b_field = source.b_field + WHERE NOT EXISTS ( + SELECT 1 FROM dbo.s_module_field_query target + WHERE target.b_module_id = @DataId AND target.b_field_id = field.b_id + ); + + 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 (b_id, b_code, b_name, b_xh) VALUES + (920000000000005001, 'create', N'新增', 10), + (920000000000005002, 'update', N'修改', 20), + (920000000000005003, 'delete', N'删除', 30); + + IF EXISTS ( + SELECT 1 FROM @Powers source + JOIN dbo.s_module_power target ON target.b_id = source.b_id + WHERE target.b_module_id <> @PageId OR target.b_code <> source.b_code + ) + THROW 51217, N'A reserved other company power ID is already in use.', 1; + + 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_othercompany.' + source.b_code, source.b_xh, 1 + FROM @Powers source + WHERE NOT EXISTS ( + SELECT 1 FROM dbo.s_module_power target + WHERE target.b_module_id = @PageId AND target.b_code = source.b_code + ); + +COMMIT TRANSACTION; diff --git a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/controller/DataController.java b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/controller/DataController.java index 99b4130d..3d26d671 100644 --- a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/controller/DataController.java +++ b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/controller/DataController.java @@ -69,6 +69,18 @@ public class DataController { return ApiResponse.success(ids); } + @GetMapping("/nextcode") + public ApiResponse nextCode( + @RequestParam String moduleId, + @RequestParam(required = false, defaultValue = "1") int count + ) throws SQLException { + List codes = dataSaveService.nextCode(moduleId, count); + if (codes.size() == 1) { + return ApiResponse.success(codes.get(0)); + } + return ApiResponse.success(codes); + } + @PostMapping("/describe") public ApiResponse>> describe( @RequestBody(required = false) Map params diff --git a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/DataSaveService.java b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/DataSaveService.java index e512882e..855216f3 100644 --- a/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/DataSaveService.java +++ b/code/fms/fms-api/src/main/java/cn/g3soft/fmsapi/service/DataSaveService.java @@ -9,11 +9,18 @@ import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.StringJoiner; @Service public class DataSaveService { @@ -50,6 +57,327 @@ public class DataSaveService { } } + public List nextCode(String moduleId, int count) throws SQLException { + if (moduleId == null || moduleId.isBlank()) { + throw new BusinessException("moduleId 不能为空"); + } + if (count < 1 || count > 1000) { + throw new BusinessException("count 必须是 1 到 1000 之间的整数"); + } + + final long moduleIdValue; + try { + moduleIdValue = Long.parseLong(moduleId); + } catch (NumberFormatException exception) { + throw new BusinessException("moduleId 必须是有效的数字"); + } + + try (Connection connection = dataSource.getConnection()) { + boolean originalAutoCommit = connection.getAutoCommit(); + connection.setAutoCommit(false); + try { + AutoCodeRule rule = loadAutoCodeRule(connection, moduleIdValue); + DbUtils.TableMetadata targetTable = dbUtils.loadTableMetadata(connection, rule.saveTable()); + DbUtils.ColumnMetadata targetColumn = dbUtils.getColumn(targetTable, rule.fieldName()); + if (targetColumn == null) { + throw new BusinessException( + "自动编码字段不存在: " + rule.saveTable() + "." + rule.fieldName() + ); + } + if (!isTextColumn(targetColumn.typeName())) { + throw new BusinessException("自动编码字段必须是文本类型: " + rule.fieldName()); + } + ensureUniqueCodeIndex(connection, targetTable, targetColumn); + if (rule.startValue() < 0) { + throw new BusinessException("自动编码起始值不能小于 0"); + } + + AutoCodeFormat format = createAutoCodeFormat( + rule.prefix(), + rule.dateFormat(), + rule.separator(), + rule.sequenceWidth() + ); + validateResetFormat(rule.resetType(), format.dateFormat()); + LocalDate today = LocalDate.now(ZoneId.of("Asia/Shanghai")); + String period = buildPeriodKey(today, rule.resetType()); + long nextValue = shouldReset(rule.currentPeriod(), period) + ? rule.startValue() + : increment(rule.currentValue()); + List codes = new ArrayList<>(count); + for (int index = 0; index < count; index++) { + CodeCandidate candidate = nextAvailableCode( + connection, + targetTable, + targetColumn, + format, + today, + nextValue + ); + codes.add(candidate.code()); + nextValue = increment(candidate.value()); + } + + long lastValue = nextValue - 1; + try (PreparedStatement statement = connection.prepareStatement( + "UPDATE dbo.s_module_auto_code " + + "SET b_current_period = ?, b_current_value = ? " + + "WHERE b_id = ?" + )) { + statement.setString(1, period); + statement.setLong(2, lastValue); + statement.setLong(3, rule.id()); + statement.executeUpdate(); + } + connection.commit(); + return codes; + } catch (SQLException | RuntimeException exception) { + rollback(connection, exception); + throw exception; + } finally { + if (originalAutoCommit) { + connection.setAutoCommit(true); + } + } + } + } + + private AutoCodeRule loadAutoCodeRule(Connection connection, long moduleId) throws SQLException { + String sql = "SELECT TOP 1 r.b_id, r.b_prefix, r.b_date_format, " + + "r.b_separator, r.b_sequence_width, r.b_reset_type, " + + "r.b_start_value, r.b_current_period, r.b_current_value, " + + "m.b_savetable, f.b_field " + + "FROM dbo.s_module_auto_code r WITH (UPDLOCK, HOLDLOCK, ROWLOCK) " + + "INNER JOIN dbo.s_module m ON m.b_id = r.b_module_id " + + "INNER JOIN dbo.s_module_field f ON f.b_id = r.b_field_id " + + "WHERE r.b_module_id = ? AND r.b_canuse = 1 " + + "AND m.b_module_type = 'data' AND f.b_canuse = 1"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setLong(1, moduleId); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + throw new BusinessException("当前模块未配置启用的自动编码规则"); + } + String saveTable = resultSet.getString("b_savetable"); + String fieldName = resultSet.getString("b_field"); + if (saveTable == null || saveTable.isBlank()) { + throw new BusinessException("模块未配置保存表,无法生成自动编号"); + } + return new AutoCodeRule( + resultSet.getLong("b_id"), + resultSet.getString("b_prefix"), + resultSet.getString("b_date_format"), + resultSet.getString("b_separator"), + resultSet.getInt("b_sequence_width"), + resultSet.getString("b_reset_type"), + resultSet.getLong("b_start_value"), + resultSet.getString("b_current_period"), + resultSet.getLong("b_current_value"), + saveTable, + fieldName + ); + } + } + } + + private CodeCandidate nextAvailableCode( + Connection connection, + DbUtils.TableMetadata targetTable, + DbUtils.ColumnMetadata targetColumn, + AutoCodeFormat format, + LocalDate today, + long value + ) throws SQLException { + long candidate = value; + for (int attempt = 0; attempt < 1000; attempt++) { + String code = formatCode(format, today, candidate); + if (targetColumn.size() > 0 && code.length() > targetColumn.size()) { + throw new BusinessException("自动编号长度超过目标字段长度"); + } + if (!codeExists(connection, targetTable, targetColumn, code)) { + return new CodeCandidate(code, candidate); + } + candidate = increment(candidate); + } + throw new BusinessException("自动编号连续冲突,无法分配新的编号"); + } + + private boolean codeExists( + Connection connection, + DbUtils.TableMetadata targetTable, + DbUtils.ColumnMetadata targetColumn, + String code + ) throws SQLException { + String sql = "SELECT TOP 1 1 FROM " + targetTable.quotedName() + + " WHERE " + dbUtils.quoteQualifiedIdentifier(targetColumn.name()) + " = ?"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, code); + try (ResultSet resultSet = statement.executeQuery()) { + return resultSet.next(); + } + } + } + + private void ensureUniqueCodeIndex( + Connection connection, + DbUtils.TableMetadata targetTable, + DbUtils.ColumnMetadata targetColumn + ) throws SQLException { + String sql = "SELECT TOP 1 1 " + + "FROM sys.indexes i " + + "INNER JOIN sys.index_columns ic " + + "ON ic.object_id = i.object_id AND ic.index_id = i.index_id " + + "INNER JOIN sys.columns c " + + "ON c.object_id = ic.object_id AND c.column_id = ic.column_id " + + "WHERE i.object_id = OBJECT_ID(?) AND i.is_unique = 1 AND i.is_disabled = 0 " + + "AND ic.key_ordinal = 1 AND c.name = ? " + + "AND NOT EXISTS (" + + "SELECT 1 FROM sys.index_columns other " + + "WHERE other.object_id = i.object_id AND other.index_id = i.index_id " + + "AND other.key_ordinal > 1)"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, targetTable.requestedName()); + statement.setString(2, targetColumn.name()); + try (ResultSet resultSet = statement.executeQuery()) { + if (!resultSet.next()) { + throw new BusinessException( + "自动编码字段必须建立单字段唯一索引: " + + targetTable.requestedName() + "." + targetColumn.name() + ); + } + } + } + } + + private AutoCodeFormat createAutoCodeFormat( + String prefix, + String dateFormat, + String separator, + int sequenceWidth + ) { + String normalizedPrefix = prefix == null ? "" : prefix; + String normalizedDateFormat = dateFormat == null ? "" : dateFormat.trim(); + String normalizedSeparator = separator == null ? "" : separator; + if (sequenceWidth < 1 || sequenceWidth > 18) { + throw new BusinessException("流水号位数必须是 1 到 18 之间的整数"); + } + if (!normalizedDateFormat.isEmpty()) { + dateFormatter(normalizedDateFormat); + } + if (normalizedSeparator.length() > 10) { + throw new BusinessException("分隔符长度不能超过 10 个字符"); + } + if (normalizedPrefix.length() > 100) { + throw new BusinessException("固定前缀长度不能超过 100 个字符"); + } + return new AutoCodeFormat( + normalizedPrefix, + normalizedDateFormat, + normalizedSeparator, + sequenceWidth + ); + } + + private String formatCode(AutoCodeFormat format, LocalDate today, long sequence) { + String sequenceText = Long.toString(sequence); + if (sequenceText.length() > format.sequenceWidth()) { + throw new BusinessException("流水号超过配置位数"); + } + sequenceText = "0".repeat(format.sequenceWidth() - sequenceText.length()) + sequenceText; + StringJoiner parts = new StringJoiner(format.separator()); + if (!format.prefix().isEmpty()) parts.add(format.prefix()); + if (!format.dateFormat().isEmpty()) parts.add(today.format(dateFormatter(format.dateFormat()))); + parts.add(sequenceText); + return parts.toString(); + } + + private DateTimeFormatter dateFormatter(String format) { + if (format.length() > 30) { + throw new BusinessException("日期格式长度不能超过 30 个字符"); + } + String literals = format + .replace("yyyy", "") + .replace("yy", "") + .replace("MM", "") + .replace("dd", ""); + if (literals.matches(".*[A-Za-z].*")) { + throw new BusinessException("日期格式只支持 yyyy、yy、MM 和 dd"); + } + try { + return DateTimeFormatter.ofPattern(format); + } catch (IllegalArgumentException exception) { + throw new BusinessException("日期格式不正确: " + format); + } + } + + private String buildPeriodKey(LocalDate date, String resetType) { + return switch (resetType == null ? "month" : resetType) { + case "none" -> "*"; + case "year" -> date.format(DateTimeFormatter.ofPattern("yyyy")); + case "month" -> date.format(DateTimeFormatter.ofPattern("yyyyMM")); + case "day" -> date.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + default -> throw new BusinessException("不支持的重置周期: " + resetType); + }; + } + + private void validateResetFormat(String resetType, String dateFormat) { + String normalizedResetType = resetType == null ? "month" : resetType; + boolean hasYear = dateFormat.contains("yyyy") || dateFormat.contains("yy"); + boolean hasMonth = dateFormat.contains("MM"); + boolean hasDay = dateFormat.contains("dd"); + if ("year".equals(normalizedResetType) && !hasYear) { + throw new BusinessException("按年重置时,日期格式必须包含年份"); + } + if ("month".equals(normalizedResetType) && (!hasYear || !hasMonth)) { + throw new BusinessException("按月重置时,日期格式必须包含年份和月份"); + } + if ("day".equals(normalizedResetType) && (!hasYear || !hasMonth || !hasDay)) { + throw new BusinessException("按日重置时,日期格式必须包含年月日"); + } + } + + private boolean shouldReset(String previousPeriod, String currentPeriod) { + return previousPeriod == null || !previousPeriod.equals(currentPeriod); + } + + private long increment(long value) { + if (value == Long.MAX_VALUE) { + throw new BusinessException("自动编号流水值已达到最大值"); + } + return value + 1; + } + + private boolean isTextColumn(String typeName) { + String normalized = typeName == null ? "" : typeName.toLowerCase(Locale.ROOT); + return normalized.contains("char") || normalized.contains("text"); + } + + private record AutoCodeRule( + long id, + String prefix, + String dateFormat, + String separator, + int sequenceWidth, + String resetType, + long startValue, + String currentPeriod, + long currentValue, + String saveTable, + String fieldName + ) { + } + + private record AutoCodeFormat( + String prefix, + String dateFormat, + String separator, + int sequenceWidth + ) { + } + + private record CodeCandidate(String code, long value) { + } + private void saveTable( Connection connection, Map request, diff --git a/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/service/DataSaveServiceTests.java b/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/service/DataSaveServiceTests.java index b4f739b5..dcfbb5ce 100644 --- a/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/service/DataSaveServiceTests.java +++ b/code/fms/fms-api/src/test/java/cn/g3soft/fmsapi/service/DataSaveServiceTests.java @@ -1,6 +1,7 @@ package cn.g3soft.fmsapi.service; import cn.g3soft.fmsapi.exception.SaveObjectException; +import cn.g3soft.fmsapi.exception.BusinessException; import cn.g3soft.fmsapi.utils.DbUtils; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; @@ -12,6 +13,7 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.time.LocalDate; import java.util.List; import java.util.Map; @@ -24,6 +26,44 @@ import static org.mockito.Mockito.when; class DataSaveServiceTests { + @Test + void formatsConfigWithCustomDateAndOptionalSeparator() { + DataSaveService service = new DataSaveService(); + Object format = ReflectionTestUtils.invokeMethod( + service, + "createAutoCodeFormat", + "SO", + "yyyy.MM.dd", + "-", + 5 + ); + + String code = ReflectionTestUtils.invokeMethod( + service, + "formatCode", + format, + LocalDate.of(2026, 7, 29), + 1L + ); + + assertThat(code).isEqualTo("SO-2026.07.29-00001"); + } + + @Test + void rejectsUnsupportedDateTokens() { + DataSaveService service = new DataSaveService(); + + assertThatThrownBy(() -> ReflectionTestUtils.invokeMethod( + service, + "createAutoCodeFormat", + "SO", + "yyyyHH", + "", + 5 + )).isInstanceOf(BusinessException.class) + .hasMessageContaining("日期格式只支持"); + } + @Test void savesDirectArrayInOneTransactionAndKeepsEmptyString() throws Exception { JdbcFixture fixture = new JdbcFixture("b_id", "b_amount"); diff --git a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue index 90d48878..907dc7fa 100644 --- a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue +++ b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleListPage.vue @@ -2,8 +2,8 @@ import { computed, h, nextTick, reactive, ref, shallowRef, useSlots, watch } from "vue"; import { useRoute } from "vue-router"; import { message } from "antdv-next"; -import { Columns3, RefreshCw } from "@lucide/vue"; -import { loadDataApi, pageDataApi } from "@/services/api"; +import { Columns3, RefreshCw, Save } from "@lucide/vue"; +import { loadDataApi, pageDataApi, saveObjectApi } from "@/services/api"; import { useAppStore } from "@/stores/app"; import { identifierRe, @@ -18,6 +18,7 @@ import FmsQueryToolbar from "./FmsQueryToolbar.vue"; const props = defineProps({ dataCode: { type: String, default: "" }, + fixedSearchCondition: { type: String, default: "" }, columnExtensions: { type: Object, default: () => ({}) }, rowActions: { type: Array, default: () => [] }, rowSelection: { type: Object, default: null }, @@ -44,6 +45,7 @@ const searchConditions = shallowRef([]); const keyField = ref(""); const configLoading = ref(false); const dataLoading = ref(false); +const columnSaving = ref(false); const configError = ref(""); const dataError = ref(""); const pagination = reactive({ @@ -51,6 +53,7 @@ const pagination = reactive({ pageSize: Number(props.pageSize) || 20, total: 0, }); +let dataRequestVersion = 0; const loading = computed(() => configLoading.value || dataLoading.value); @@ -63,9 +66,15 @@ const moreMenuItems = computed(() => [ }, { key: "reset-columns", - label: appStore.t("common.resetColumns", "恢复配置列宽"), + label: appStore.t("common.resetColumnSettings", "恢复栏位设置"), icon: h(Columns3, { size: 14 }), }, + { + key: "save-column-settings", + label: appStore.t("common.saveColumnSettings", "保存栏位设置"), + icon: h(Save, { size: 14 }), + disabled: columnSaving.value, + }, ]); const cellSlotNames = computed(() => Object.keys(slots).filter((name) => name.startsWith("cell-"))); @@ -225,12 +234,17 @@ function resolveOrderBy() { } async function fetchData() { - if (!dataModule.value || !dataModule.value.b_viewtable || dataLoading.value) return; + if (!dataModule.value || !dataModule.value.b_viewtable) return; const currentModule = dataModule.value; const viewName = currentModule.b_viewtable; const currentCode = getDataCode(); const currentPath = route.path; + const requestVersion = ++dataRequestVersion; + const conditions = []; + const fixedCondition = String(props.fixedSearchCondition || "").trim(); + if (fixedCondition) conditions.push(`(${fixedCondition})`); + conditions.push(...searchConditions.value); dataLoading.value = true; dataError.value = ""; @@ -239,17 +253,18 @@ async function fetchData() { viewName, orderBy: resolveOrderBy(), searchColumns: [], - searchCondition: searchConditions.value.join(" AND "), + searchCondition: conditions.join(" AND "), pageNo: pagination.current, pageSize: pagination.pageSize, }); - if (dataModule.value !== currentModule) return; + if (requestVersion !== dataRequestVersion || dataModule.value !== currentModule) return; if (getDataCode() !== currentCode || route.path !== currentPath) return; dataSource.value = res.data?.rows || []; pagination.total = Number(res.data?.total ?? res.data?.row_count) || 0; } catch (error) { if ( + requestVersion === dataRequestVersion && dataModule.value === currentModule && getDataCode() === currentCode && route.path === currentPath @@ -260,6 +275,7 @@ async function fetchData() { } } finally { if ( + requestVersion === dataRequestVersion && dataModule.value === currentModule && getDataCode() === currentCode && route.path === currentPath @@ -271,6 +287,7 @@ async function fetchData() { // ── 公开方法 ── async function initialize() { + dataRequestVersion++; dataModule.value = null; activeFields.value = []; activeFieldById.value = new Map(); @@ -324,6 +341,56 @@ async function resetColumns() { if (tableRef.value) tableRef.value.resetColumns(); } +async function saveColumnSettings() { + if (loading.value || columnSaving.value || !tableRef.value) return; + if (typeof tableRef.value.getColumnSettings !== "function") return; + + const settings = tableRef.value.getColumnSettings(); + if (!settings.length) { + message.info("当前没有可保存的栏位设置"); + return; + } + + const updates = []; + for (const row of settings) { + updates.push({ + b_id: row.b_id, + b_xh: row.b_xh, + b_width: row.b_width, + }); + } + + columnSaving.value = true; + try { + await saveObjectApi([ + { + table: "s_module_field_list", + key_field: "b_id", + updates, + }, + ]); + + const currentById = new Map(); + for (const row of listConfig.value) { + currentById.set(String(row.b_id), row); + } + const savedListConfig = []; + for (const saved of settings) { + const current = currentById.get(String(saved.b_id)); + if (!current) continue; + savedListConfig.push({ ...current, b_xh: saved.b_xh, b_width: saved.b_width }); + } + listConfig.value = savedListConfig; + message.success("栏位设置已保存"); + } catch (error) { + let errorMessage = error; + if (error && error.message) errorMessage = error.message; + message.error("栏位设置保存失败:" + errorMessage); + } finally { + columnSaving.value = false; + } +} + function resolveRowKey(row) { if (keyField.value) return row[keyField.value]; return JSON.stringify(row); @@ -336,13 +403,26 @@ async function handleMoreAction({ key }) { } if (key === "reset-columns") { await resetColumns(); - message.success(appStore.t("common.columnsReset", "已恢复配置列宽")); + message.success(appStore.t("common.columnSettingsReset", "已恢复栏位设置")); + return; + } + if (key === "save-column-settings") { + await saveColumnSettings(); } } // ── 生命周期 ── watch(() => [getDataCode(), route.path], initialize, { immediate: true }); +watch( + () => props.fixedSearchCondition, + () => { + if (!dataModule.value) return; + pagination.current = 1; + fetchData(); + }, +); + watch( () => dataSource.value, (rows) => { diff --git a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue index 3cbe5546..6468f763 100644 --- a/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue +++ b/code/fms/fms-vue/src/components/fms-module-list/FmsModuleTable.vue @@ -37,7 +37,7 @@ const expansionOptions = computed(() => const expandConfig = computed(() => ({ height: Number(expansionOptions.value.height) || 180 })); function formatCellValue(value, field, optionInfo) { - if (value === null || value === undefined || value === "") return "-"; + if (value === null || value === undefined || value === "") return ""; if (field.b_type === "date") return formatDateValue(value, false); if (field.b_type === "datetime") return formatDateValue(value, true); if (field.b_type === "checkbox") { @@ -199,6 +199,35 @@ function resetColumns() { tableRef.value?.initVirtualScroll?.(); } +function getColumnSettings() { + const configByFieldId = new Map(); + for (const config of props.listConfig || []) { + configByFieldId.set(String(config.b_field_id), config); + } + + const settings = []; + let order = 10; + for (const column of columns.value) { + if (!column.dataIndex || String(column.dataIndex).startsWith("__")) continue; + const field = props.fields.find((item) => item.b_field === column.dataIndex); + if (!field) continue; + const config = configByFieldId.get(String(field.b_id)); + if (!config) continue; + const parsedWidth = Number.parseFloat(String(column.width)); + let columnWidth = config.b_width; + if (Number.isFinite(parsedWidth) && parsedWidth > 0) { + columnWidth = Math.round(parsedWidth); + } + settings.push({ + ...config, + b_xh: order, + b_width: columnWidth, + }); + order += 10; + } + return settings; +} + function handleCellClick(event, record, column, meta) { if (!column?.dataIndex || String(column.dataIndex).startsWith("__")) return; const field = props.fields.find((item) => item.b_field === column.dataIndex); @@ -251,7 +280,11 @@ watch( }, ); -defineExpose({ resetColumns, clearSelectedArea: () => tableRef.value?.clearSelectedArea?.() }); +defineExpose({ + resetColumns, + getColumnSettings, + clearSelectedArea: () => tableRef.value?.clearSelectedArea?.(), +});