20260729173317
This commit is contained in:
1 parent
f6b79ea09a
commit
1c839b707a
26 files changed
+1902
-29
No files matched your search
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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<String> codes = dataSaveService.nextCode(moduleId, count);
|
||||
if (codes.size() == 1) {
|
||||
return ApiResponse.success(codes.get(0));
|
||||
}
|
||||
return ApiResponse.success(codes);
|
||||
}
|
||||
|
||||
@PostMapping("/describe")
|
||||
public ApiResponse<List<Map<String, Object>>> describe(
|
||||
@RequestBody(required = false) Map<String, Object> params
|
||||
|
||||
@@ -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<String> 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<String> 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<String, Object> request,
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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?.(),
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -226,7 +226,7 @@ watch(() => props.queryItems, initializeDrafts, { immediate: true });
|
||||
<Search :size="15" />
|
||||
{{ appStore.t("common.search", "查询") }}
|
||||
</a-button>
|
||||
<a-button :disabled="loading" @click="handleAdvancedReset">
|
||||
<a-button type="text" :disabled="loading" @click="handleAdvancedReset">
|
||||
<RotateCcw :size="15" />
|
||||
{{ appStore.t("common.reset", "重置") }}
|
||||
</a-button>
|
||||
|
||||
@@ -15,8 +15,10 @@ const props = defineProps({
|
||||
noDataFull: { type: Boolean, default: true },
|
||||
showOverflow: { type: Boolean, default: true },
|
||||
columns: { type: Array, default: () => [] },
|
||||
// 序号列:false=关闭;true=默认;{ title, width }=自定义
|
||||
seq: { type: [Boolean, Object], default: false },
|
||||
// 序号列默认显示;false=关闭;{ title, width }=自定义
|
||||
seq: { type: [Boolean, Object], default: true },
|
||||
// 列头拖拽默认开启;false=关闭;{ mode, disabled }=自定义
|
||||
columnDraggable: { type: [Boolean, Object], default: true },
|
||||
// 行多选:{ selectedRowKeys, onChange },提供时在最前列插入复选框列
|
||||
rowSelection: { type: Object, default: null },
|
||||
dataSource: { type: Array, default: () => [] },
|
||||
@@ -137,6 +139,27 @@ const seqConfig = computed(() => {
|
||||
width: Number(cfg.width ?? 50),
|
||||
};
|
||||
});
|
||||
const columnDragConfig = computed(() => {
|
||||
if (!props.columnDraggable) return false;
|
||||
const configured = props.columnDraggable === true ? {} : props.columnDraggable;
|
||||
return {
|
||||
...configured,
|
||||
disabled: (column) => {
|
||||
if (
|
||||
isSelectionColumn(column) ||
|
||||
column.type === "seq" ||
|
||||
column.type === "dragRow" ||
|
||||
column.type === "expand" ||
|
||||
column.dataIndex === "" ||
|
||||
column.dataIndex === "__actions__"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (typeof configured.disabled === "function") return configured.disabled(column);
|
||||
return Boolean(configured.disabled);
|
||||
},
|
||||
};
|
||||
});
|
||||
const hasRowSelection = computed(() => Boolean(props.rowSelection));
|
||||
const dragRowColumn = {
|
||||
type: "dragRow",
|
||||
@@ -158,8 +181,6 @@ function getHeaderAlign(column) {
|
||||
|
||||
const displayColumns = computed(() => {
|
||||
const cols = props.columns.map((col) => resolveCustomCell(col, editingCtx));
|
||||
if (props.rowDraggable) cols.unshift(dragRowColumn);
|
||||
if (hasRowSelection.value) cols.unshift(selectionColumn);
|
||||
const seq = seqConfig.value;
|
||||
if (seq.enabled) {
|
||||
cols.unshift({
|
||||
@@ -170,6 +191,8 @@ const displayColumns = computed(() => {
|
||||
resizable: false,
|
||||
});
|
||||
}
|
||||
if (props.rowDraggable) cols.unshift(dragRowColumn);
|
||||
if (hasRowSelection.value) cols.unshift(selectionColumn);
|
||||
return cols.map((column) => ({
|
||||
...column,
|
||||
headerAlign: getHeaderAlign(column),
|
||||
@@ -435,6 +458,7 @@ defineExpose({
|
||||
:virtual-x="virtualX"
|
||||
:auto-resize="false"
|
||||
:col-resizable="colResizable"
|
||||
:header-drag="columnDragConfig"
|
||||
:columns="displayColumns"
|
||||
@update:columns="onColumnsUpdate"
|
||||
:data-source="dataSource"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, h, onMounted, ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { House, PanelLeftClose, PanelLeftOpen, Settings } from "@lucide/vue";
|
||||
import { Database, House, PanelLeftClose, PanelLeftOpen, Settings } from "@lucide/vue";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
import { usePermissionStore } from "@/stores/permissions";
|
||||
|
||||
@@ -10,7 +10,7 @@ const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
const menuIcon = (icon) => () => h(icon, { size: 16, strokeWidth: 1.8 });
|
||||
const openKeys = ref(["system"]);
|
||||
const openKeys = ref(["base", "system"]);
|
||||
const effectiveCollapsed = computed(() => appStore.sidebarCollapsed);
|
||||
function moduleLabel(moduleId, path, fallback) {
|
||||
const module = permissionStore.modules.find(
|
||||
@@ -29,6 +29,13 @@ function canAccessMenu(item) {
|
||||
return true;
|
||||
}
|
||||
const menuItems = computed(() => {
|
||||
const basePages = [
|
||||
{
|
||||
key: "/base/othercompany",
|
||||
label: moduleLabel("base_othercompany", "/base/othercompany", "合作伙伴"),
|
||||
moduleId: "base_othercompany",
|
||||
},
|
||||
].filter(canAccessMenu);
|
||||
const systemPages = [
|
||||
{
|
||||
key: "/system/module-management",
|
||||
@@ -53,6 +60,13 @@ const menuItems = computed(() => {
|
||||
label: appStore.t("common.dashboard", "工作台"),
|
||||
},
|
||||
];
|
||||
if (basePages.length)
|
||||
items.push({
|
||||
key: "base",
|
||||
icon: menuIcon(Database),
|
||||
label: directoryLabel("基础数据"),
|
||||
children: basePages,
|
||||
});
|
||||
if (systemPages.length)
|
||||
items.push({
|
||||
key: "system",
|
||||
@@ -66,6 +80,8 @@ const selectedKeys = computed(() => [route.meta.menuKey || route.path]);
|
||||
watch(
|
||||
() => route.path,
|
||||
(path) => {
|
||||
if (path.startsWith("/base") && !openKeys.value.includes("base"))
|
||||
openKeys.value = [...openKeys.value, "base"];
|
||||
if (path.startsWith("/system") && !openKeys.value.includes("system"))
|
||||
openKeys.value = [...openKeys.value, "system"];
|
||||
},
|
||||
|
||||
@@ -14,6 +14,18 @@ const routes = [
|
||||
component: () => import("@/views/dashboard/index.vue"),
|
||||
meta: { title: "工作台", menuKey: "/dashboard" },
|
||||
},
|
||||
{
|
||||
path: "base/othercompany",
|
||||
name: "base-othercompany",
|
||||
component: () => import("@/views/base/othercompany/index.vue"),
|
||||
meta: {
|
||||
title: "合作伙伴",
|
||||
section: "基础数据",
|
||||
menuKey: "/base/othercompany",
|
||||
moduleId: "base_othercompany",
|
||||
dataCode: "b_othercompany",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "system/module-management",
|
||||
name: "system-module-management",
|
||||
@@ -44,7 +56,7 @@ const routes = [
|
||||
title: "登录日志",
|
||||
section: "系统管理",
|
||||
menuKey: "/system/login-log",
|
||||
moduleId: "d_login_log",
|
||||
moduleId: "s_login_log",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -97,6 +97,16 @@ export function nextIdApi(count = 1) {
|
||||
return get("/data/nextid", { params: { count } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模块规则原子获取业务编号;count 大于 1 时一次返回多个编号。
|
||||
* @param {string} moduleId 数据模块 ID。
|
||||
* @param {number} [count=1] 需要获取的编号数量。
|
||||
* @returns {Promise<ApiResult<string|string[]>>} 业务编号或编号数组。
|
||||
*/
|
||||
export function nextCodeApi(moduleId, count = 1) {
|
||||
return get("/data/nextcode", { params: { moduleId: String(moduleId), count } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取表或视图的列结构,用于同步字段等场景。
|
||||
* @param {string} table 表名或视图名,必填,后端会校验标识符。
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { message } from "antdv-next";
|
||||
import { RefreshCw } from "@lucide/vue";
|
||||
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||
import FmsTree from "@/components/FmsTree.vue";
|
||||
import { escapeSqlText } from "@/components/fms-module-list/moduleListUtils";
|
||||
import { loadDataApi } from "@/services/api";
|
||||
import { buildTree, sortTree } from "@/utils/tree";
|
||||
|
||||
const ALL_CATEGORIES_KEY = "__all_categories__";
|
||||
const CATEGORY_COLUMNS = [
|
||||
"b_id",
|
||||
"b_parent_id",
|
||||
"b_code",
|
||||
"b_name",
|
||||
"b_level",
|
||||
"b_path",
|
||||
"b_xh",
|
||||
"b_canuse",
|
||||
];
|
||||
|
||||
const categories = ref([]);
|
||||
const categoryLoading = ref(false);
|
||||
const selectedCategoryKey = ref(ALL_CATEGORIES_KEY);
|
||||
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_xh: item.b_xh,
|
||||
children: [],
|
||||
}),
|
||||
});
|
||||
sortTree(roots);
|
||||
return [
|
||||
{
|
||||
key: ALL_CATEGORIES_KEY,
|
||||
title: "全部合作伙伴",
|
||||
b_xh: 0,
|
||||
children: roots,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const categoryFilterCondition = computed(() => {
|
||||
if (selectedCategoryKey.value === ALL_CATEGORIES_KEY) return "";
|
||||
const ids = collectDescendantIds(selectedCategoryKey.value);
|
||||
const literals = ids.map((id) => `N'${escapeSqlText(id)}'`);
|
||||
return literals.length ? `[b_cate_id] IN (${literals.join(", ")})` : "1 = 0";
|
||||
});
|
||||
|
||||
const columnExtensions = computed(() => ({
|
||||
b_cate_id: {
|
||||
formatter: ({ value }) => categoryById.value.get(String(value || ""))?.b_name || value || "",
|
||||
},
|
||||
}));
|
||||
|
||||
function collectDescendantIds(rootId) {
|
||||
const childrenByParent = new Map();
|
||||
for (const item of categories.value) {
|
||||
const parentId = item.b_parent_id == null ? "" : String(item.b_parent_id);
|
||||
if (!childrenByParent.has(parentId)) childrenByParent.set(parentId, []);
|
||||
childrenByParent.get(parentId).push(String(item.b_id));
|
||||
}
|
||||
|
||||
const result = [];
|
||||
const visited = new Set();
|
||||
const pending = [String(rootId)];
|
||||
while (pending.length) {
|
||||
const id = pending.pop();
|
||||
if (visited.has(id)) continue;
|
||||
visited.add(id);
|
||||
result.push(id);
|
||||
for (const childId of childrenByParent.get(id) || []) pending.push(childId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function loadCategories() {
|
||||
categoryLoading.value = true;
|
||||
try {
|
||||
const response = await loadDataApi(
|
||||
"b_othercompany_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;
|
||||
}
|
||||
|
||||
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) selectedCategoryKey.value = String(keys[0]);
|
||||
}
|
||||
|
||||
onMounted(loadCategories);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="othercompany-page">
|
||||
<a-splitter class="othercompany-splitter" lazy>
|
||||
<a-splitter-panel :default-size="250" :min="210" :max="380" collapsible>
|
||||
<aside class="category-panel">
|
||||
<header class="category-header">
|
||||
<div class="category-title">合作伙伴分类</div>
|
||||
<a-tooltip title="刷新分类">
|
||||
<a-button
|
||||
type="text"
|
||||
:disabled="categoryLoading"
|
||||
aria-label="刷新分类"
|
||||
@click="loadCategories"
|
||||
>
|
||||
<RefreshCw :size="15" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</header>
|
||||
<div class="category-tree-stage">
|
||||
<FmsTree
|
||||
v-model:expanded-keys="expandedCategoryKeys"
|
||||
:tree-data="categoryTree"
|
||||
:selected-keys="[selectedCategoryKey]"
|
||||
show-line
|
||||
@select="onCategorySelect"
|
||||
/>
|
||||
<div v-if="categoryLoading" class="category-loading"><a-spin size="small" /></div>
|
||||
</div>
|
||||
</aside>
|
||||
</a-splitter-panel>
|
||||
|
||||
<a-splitter-panel :min="520">
|
||||
<main class="partner-panel">
|
||||
<FmsModuleListPage
|
||||
data-code="b_othercompany"
|
||||
:fixed-search-condition="categoryFilterCondition"
|
||||
:column-extensions="columnExtensions"
|
||||
/>
|
||||
</main>
|
||||
</a-splitter-panel>
|
||||
</a-splitter>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.othercompany-page,
|
||||
.othercompany-splitter {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.othercompany-page {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--fms-border);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-surface);
|
||||
}
|
||||
|
||||
.category-panel,
|
||||
.partner-panel {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.category-header {
|
||||
display: flex;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 4px 6px 4px 12px;
|
||||
border-bottom: 1px solid var(--fms-border);
|
||||
}
|
||||
|
||||
.category-title {
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.category-header :deep(.ant-btn) {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.category-tree-stage {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.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%);
|
||||
}
|
||||
|
||||
.partner-panel :deep(.fms-module-list-page) {
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
import { createTempId } from "@/utils/tempId";
|
||||
|
||||
function defaultFieldId(fields) {
|
||||
const field = (fields || []).find(
|
||||
(item) =>
|
||||
Number(item.b_canuse) === 1 &&
|
||||
item.b_field !== "b_id" &&
|
||||
(item.b_type === "input" || item.b_type === "textarea"),
|
||||
);
|
||||
return field?.b_id != null ? String(field.b_id) : null;
|
||||
}
|
||||
|
||||
export function createAutoCodeDraft(moduleId, fields, enabled = false) {
|
||||
return {
|
||||
b_id: createTempId(),
|
||||
b_module_id: String(moduleId),
|
||||
b_field_id: defaultFieldId(fields),
|
||||
b_prefix: "",
|
||||
b_date_format: "yyyyMM",
|
||||
b_separator: "-",
|
||||
b_sequence_width: 5,
|
||||
b_reset_type: "month",
|
||||
b_start_value: 1,
|
||||
b_current_period: null,
|
||||
b_current_value: 0,
|
||||
b_canuse: enabled ? 1 : 0,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { createAutoCodeDraft } from "../autoCode";
|
||||
|
||||
const DATE_FORMAT_OPTIONS = [
|
||||
{ label: "不使用日期", value: "" },
|
||||
{ label: "yyyy", value: "yyyy" },
|
||||
{ label: "yy", value: "yy" },
|
||||
{ label: "yyyyMM", value: "yyyyMM" },
|
||||
{ label: "yyMM", value: "yyMM" },
|
||||
{ label: "yyyyMMdd", value: "yyyyMMdd" },
|
||||
{ label: "yyyy-MM", value: "yyyy-MM" },
|
||||
{ label: "yyyy-MM-dd", value: "yyyy-MM-dd" },
|
||||
{ label: "yyyy/MM", value: "yyyy/MM" },
|
||||
{ label: "yyyy/MM/dd", value: "yyyy/MM/dd" },
|
||||
];
|
||||
|
||||
const RESET_OPTIONS = [
|
||||
{ label: "不重置", value: "none" },
|
||||
{ label: "每年", value: "year" },
|
||||
{ label: "每月", value: "month" },
|
||||
{ label: "每日", value: "day" },
|
||||
];
|
||||
|
||||
const props = defineProps({
|
||||
moduleId: { type: String, required: true },
|
||||
autoCode: { type: Array, default: () => [] },
|
||||
fields: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const emit = defineEmits(["update"]);
|
||||
const localRow = ref(null);
|
||||
|
||||
const enabled = computed(() => Number(localRow.value?.b_canuse) === 1);
|
||||
|
||||
const fieldOptions = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (Number(field.b_canuse) !== 1) continue;
|
||||
if (field.b_field === "b_id") continue;
|
||||
if (field.b_type !== "input" && field.b_type !== "textarea") continue;
|
||||
options.push({
|
||||
label: `${field.b_name || field.b_field} (${field.b_field})`,
|
||||
value: String(field.b_id),
|
||||
});
|
||||
}
|
||||
return options;
|
||||
});
|
||||
|
||||
function notifyUpdate() {
|
||||
emit("update", localRow.value ? [{ ...localRow.value }] : []);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.autoCode,
|
||||
(rows) => {
|
||||
const row = rows && rows.length ? rows[0] : null;
|
||||
localRow.value = row ? { ...row } : createAutoCodeDraft(props.moduleId, props.fields, false);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function setRowValue(field, value) {
|
||||
if (!localRow.value) return;
|
||||
localRow.value[field] = value;
|
||||
notifyUpdate();
|
||||
}
|
||||
|
||||
function pad(value) {
|
||||
return String(value).padStart(2, "0");
|
||||
}
|
||||
|
||||
function formatDate(date, format) {
|
||||
const yyyy = String(date.getFullYear());
|
||||
const yy = yyyy.slice(-2);
|
||||
const MM = pad(date.getMonth() + 1);
|
||||
const dd = pad(date.getDate());
|
||||
return String(format || "")
|
||||
.replaceAll("yyyy", yyyy)
|
||||
.replaceAll("yy", yy)
|
||||
.replaceAll("MM", MM)
|
||||
.replaceAll("dd", dd);
|
||||
}
|
||||
|
||||
function currentPeriod(date, resetType) {
|
||||
const yyyy = String(date.getFullYear());
|
||||
const MM = pad(date.getMonth() + 1);
|
||||
const dd = pad(date.getDate());
|
||||
if (resetType === "none") return "*";
|
||||
if (resetType === "year") return yyyy;
|
||||
if (resetType === "day") return yyyy + MM + dd;
|
||||
return yyyy + MM;
|
||||
}
|
||||
|
||||
const preview = computed(() => {
|
||||
if (!localRow.value) return "";
|
||||
const today = new Date();
|
||||
const startValue = Number(localRow.value.b_start_value) || 1;
|
||||
const storedValue = Number(localRow.value.b_current_value) || 0;
|
||||
const activePeriod = currentPeriod(today, localRow.value.b_reset_type);
|
||||
const sequence = localRow.value.b_current_period === activePeriod ? storedValue + 1 : startValue;
|
||||
const parts = [];
|
||||
if (localRow.value.b_prefix) parts.push(localRow.value.b_prefix);
|
||||
const dateText = formatDate(today, localRow.value.b_date_format);
|
||||
if (dateText) parts.push(dateText);
|
||||
parts.push(String(sequence).padStart(Number(localRow.value.b_sequence_width) || 1, "0"));
|
||||
return parts.join(localRow.value.b_separator || "");
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="auto-code-panel">
|
||||
<div class="panel-body">
|
||||
<a-form layout="vertical" class="auto-code-form" :disabled="loading">
|
||||
<template v-if="localRow">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="业务编号字段" :required="enabled">
|
||||
<a-select
|
||||
:value="String(localRow.b_field_id || '')"
|
||||
:options="fieldOptions"
|
||||
:disabled="loading"
|
||||
@update:value="(value) => setRowValue('b_field_id', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="固定前缀">
|
||||
<a-input
|
||||
:value="localRow.b_prefix"
|
||||
:disabled="loading"
|
||||
:maxlength="100"
|
||||
@update:value="(value) => setRowValue('b_prefix', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="日期格式">
|
||||
<a-auto-complete
|
||||
:value="localRow.b_date_format"
|
||||
:options="DATE_FORMAT_OPTIONS"
|
||||
:disabled="loading"
|
||||
@update:value="(value) => setRowValue('b_date_format', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="分隔符">
|
||||
<a-input
|
||||
:value="localRow.b_separator"
|
||||
:disabled="loading"
|
||||
:maxlength="10"
|
||||
@update:value="(value) => setRowValue('b_separator', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="流水号位数" required>
|
||||
<a-input-number
|
||||
:value="localRow.b_sequence_width"
|
||||
:disabled="loading"
|
||||
:min="1"
|
||||
:max="18"
|
||||
:precision="0"
|
||||
@update:value="(value) => setRowValue('b_sequence_width', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="起始值" required>
|
||||
<a-input-number
|
||||
:value="localRow.b_start_value"
|
||||
:disabled="loading"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
@update:value="(value) => setRowValue('b_start_value', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="重置周期" required>
|
||||
<a-select
|
||||
:value="localRow.b_reset_type"
|
||||
:options="RESET_OPTIONS"
|
||||
:disabled="loading"
|
||||
@update:value="(value) => setRowValue('b_reset_type', value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-form-item label="编号预览">
|
||||
<div class="code-preview">{{ preview }}</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.auto-code-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.auto-code-form {
|
||||
max-width: 960px;
|
||||
}
|
||||
|
||||
.code-preview {
|
||||
min-height: 32px;
|
||||
padding: 4px 11px;
|
||||
border: 1px solid var(--fms-border, #d9d9d9);
|
||||
border-radius: 6px;
|
||||
background: var(--fms-bg, #f5f5f5);
|
||||
color: var(--fms-text, #1f2937);
|
||||
font-family: Consolas, monospace;
|
||||
line-height: 22px;
|
||||
}
|
||||
</style>
|
||||
@@ -47,7 +47,6 @@ function onRowOrderChange(rows) {
|
||||
const availableFields = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (field.b_canuse !== 1) continue;
|
||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
||||
}
|
||||
return options;
|
||||
@@ -78,6 +77,13 @@ const columns = shallowRef([
|
||||
options: () => availableFields.value,
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: "b_visible",
|
||||
title: "显示",
|
||||
width: 60,
|
||||
align: "center",
|
||||
editor: { type: "checkbox" },
|
||||
},
|
||||
{
|
||||
dataIndex: "b_required",
|
||||
title: "必填",
|
||||
|
||||
@@ -46,7 +46,11 @@ watch(
|
||||
);
|
||||
|
||||
function emitUpdate() {
|
||||
emit("update", localFields.value);
|
||||
const rows = [];
|
||||
for (const row of localFields.value) {
|
||||
rows.push({ ...row });
|
||||
}
|
||||
emit("update", rows);
|
||||
}
|
||||
|
||||
// 编辑器已直接更新当前行,这里只向父级同步草稿。
|
||||
|
||||
@@ -47,7 +47,6 @@ function onRowOrderChange(rows) {
|
||||
const availableFields = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (field.b_canuse !== 1) continue;
|
||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
||||
}
|
||||
return options;
|
||||
|
||||
@@ -59,7 +59,6 @@ function onRowOrderChange(rows) {
|
||||
const availableFields = computed(() => {
|
||||
const options = [];
|
||||
for (const field of props.fields || []) {
|
||||
if (field.b_canuse !== 1) continue;
|
||||
options.push({ label: field.b_name || field.b_field, value: String(field.b_id) });
|
||||
}
|
||||
return options;
|
||||
|
||||
@@ -27,8 +27,10 @@ import ModuleFieldsPanel from "./components/ModuleFieldsPanel.vue";
|
||||
import ModuleListConfigPanel from "./components/ModuleListConfigPanel.vue";
|
||||
import ModuleEditConfigPanel from "./components/ModuleEditConfigPanel.vue";
|
||||
import ModuleQueryConfigPanel from "./components/ModuleQueryConfigPanel.vue";
|
||||
import ModuleAutoCodePanel from "./components/ModuleAutoCodePanel.vue";
|
||||
import ModulePowersPanel from "./components/ModulePowersPanel.vue";
|
||||
import ModuleI18nPanel from "./components/ModuleI18nPanel.vue";
|
||||
import { createAutoCodeDraft } from "./autoCode";
|
||||
|
||||
// ---- 状态 ----
|
||||
const appStore = useAppStore();
|
||||
@@ -63,6 +65,7 @@ const originals = ref({
|
||||
listConfig: [],
|
||||
editConfig: [],
|
||||
queryConfig: [],
|
||||
autoCode: [],
|
||||
powers: [],
|
||||
});
|
||||
|
||||
@@ -74,6 +77,7 @@ const drafts = ref({
|
||||
listConfig: [],
|
||||
editConfig: [],
|
||||
queryConfig: [],
|
||||
autoCode: [],
|
||||
powers: [],
|
||||
});
|
||||
|
||||
@@ -90,6 +94,7 @@ function setI18nPanelRef(instance) {
|
||||
|
||||
// 当前选中模块对象(从 drafts.module 获取)
|
||||
const selectedModule = computed(() => drafts.value.module);
|
||||
const autoCodeEnabled = computed(() => Number(drafts.value.autoCode?.[0]?.b_canuse) === 1);
|
||||
const busy = computed(() => configLoading.value || configSaving.value || configSyncing.value);
|
||||
const syncDisabled = computed(
|
||||
() => busy.value || !String(selectedModule.value?.b_viewtable || "").trim(),
|
||||
@@ -185,6 +190,7 @@ const visibleTabs = computed(() => {
|
||||
{ key: "list", tab: "列表配置" },
|
||||
{ key: "edit", tab: "编辑配置" },
|
||||
{ key: "query", tab: "查询配置" },
|
||||
{ key: "auto-code", tab: "自动编码" },
|
||||
);
|
||||
}
|
||||
if (type === "page") {
|
||||
@@ -252,6 +258,7 @@ async function loadModuleConfig(moduleId) {
|
||||
let listResponse;
|
||||
let editResponse;
|
||||
let queryResponse;
|
||||
let autoCodeResponse;
|
||||
let powersResponse;
|
||||
|
||||
if (moduleType === "data") {
|
||||
@@ -262,6 +269,7 @@ async function loadModuleConfig(moduleId) {
|
||||
loadDataApi("s_module_field_list", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
|
||||
loadDataApi("s_module_field_edit", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
|
||||
loadDataApi("s_module_field_query", `b_module_id = ${id}`, "b_xh ASC, b_id ASC"),
|
||||
loadDataApi("s_module_auto_code", `b_module_id = ${id}`, "b_id ASC"),
|
||||
]);
|
||||
moduleResponse = responses[0];
|
||||
fieldsResponse = responses[1];
|
||||
@@ -269,6 +277,7 @@ async function loadModuleConfig(moduleId) {
|
||||
listResponse = responses[3];
|
||||
editResponse = responses[4];
|
||||
queryResponse = responses[5];
|
||||
autoCodeResponse = responses[6];
|
||||
} else if (moduleType === "page") {
|
||||
const responses = await Promise.all([
|
||||
loadDataApi("s_module", `b_id = ${id}`),
|
||||
@@ -289,6 +298,7 @@ async function loadModuleConfig(moduleId) {
|
||||
listConfig: listResponse ? listResponse.data || [] : [],
|
||||
editConfig: editResponse ? editResponse.data || [] : [],
|
||||
queryConfig: queryResponse ? queryResponse.data || [] : [],
|
||||
autoCode: autoCodeResponse ? autoCodeResponse.data || [] : [],
|
||||
powers: powersResponse ? powersResponse.data || [] : [],
|
||||
};
|
||||
originals.value = original;
|
||||
@@ -309,11 +319,20 @@ async function loadModuleConfig(moduleId) {
|
||||
}
|
||||
|
||||
// ---- 树节点选择 ----
|
||||
function onTreeSelect(keys) {
|
||||
function onTreeSelect(keys, info) {
|
||||
if (busy.value) return;
|
||||
if (keys.length > 0) {
|
||||
selectedModuleId.value = String(keys[0]);
|
||||
}
|
||||
const nodeKey = info?.node?.key ?? keys[0];
|
||||
if (nodeKey == null) return;
|
||||
|
||||
const key = String(nodeKey);
|
||||
selectedModuleId.value = key;
|
||||
|
||||
const node = findNode(key);
|
||||
if (!node?.children?.length) return;
|
||||
const expandedKeys = expandedTreeKeys.value.map(String);
|
||||
expandedTreeKeys.value = expandedKeys.includes(key)
|
||||
? expandedKeys.filter((item) => item !== key)
|
||||
: [...expandedKeys, key];
|
||||
}
|
||||
|
||||
watch(selectedModuleId, (id) => {
|
||||
@@ -337,6 +356,128 @@ function onUpdate(section, newData) {
|
||||
drafts.value[section] = newData;
|
||||
}
|
||||
|
||||
function isFieldEnabled(field) {
|
||||
if (!field) return false;
|
||||
return Number(field.b_canuse) === 1;
|
||||
}
|
||||
|
||||
function applyDisabledFieldDefaults(previousFields, nextFields) {
|
||||
const previousEnabledStates = new Map();
|
||||
for (const field of previousFields || []) {
|
||||
if (field.b_id != null) {
|
||||
previousEnabledStates.set(String(field.b_id), isFieldEnabled(field));
|
||||
}
|
||||
}
|
||||
|
||||
const disabledFieldIds = new Set();
|
||||
for (const field of nextFields || []) {
|
||||
if (field.b_id == null) continue;
|
||||
const fieldId = String(field.b_id);
|
||||
if (previousEnabledStates.get(fieldId) && !isFieldEnabled(field)) {
|
||||
disabledFieldIds.add(fieldId);
|
||||
}
|
||||
}
|
||||
if (disabledFieldIds.size === 0) return;
|
||||
|
||||
for (const row of drafts.value.listConfig || []) {
|
||||
if (disabledFieldIds.has(String(row.b_field_id))) row.b_visible = 0;
|
||||
}
|
||||
for (const row of drafts.value.editConfig || []) {
|
||||
if (disabledFieldIds.has(String(row.b_field_id))) row.b_visible = 0;
|
||||
}
|
||||
for (const row of drafts.value.queryConfig || []) {
|
||||
if (disabledFieldIds.has(String(row.b_field_id))) row.b_canuse = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function onFieldsUpdate(newFields) {
|
||||
applyDisabledFieldDefaults(drafts.value.fields, newFields);
|
||||
drafts.value.fields = newFields;
|
||||
}
|
||||
|
||||
function onAutoCodeUpdate(rows) {
|
||||
const previousRule = drafts.value.autoCode?.[0];
|
||||
const previousFieldId =
|
||||
previousRule && Number(previousRule.b_canuse) === 1 ? previousRule.b_field_id : null;
|
||||
drafts.value.autoCode = rows;
|
||||
const rule = rows && rows.length ? rows[0] : null;
|
||||
const nextFieldId = rule && Number(rule.b_canuse) === 1 ? rule.b_field_id : null;
|
||||
if (previousFieldId != null && String(previousFieldId) !== String(nextFieldId)) {
|
||||
const original = (originals.value.editConfig || []).find(
|
||||
(config) => String(config.b_field_id) === String(previousFieldId),
|
||||
);
|
||||
for (const config of drafts.value.editConfig || []) {
|
||||
if (String(config.b_field_id) === String(previousFieldId)) {
|
||||
config.b_readonly = Number(original?.b_readonly) === 1 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextFieldId == null) return;
|
||||
for (const config of drafts.value.editConfig || []) {
|
||||
if (String(config.b_field_id) === String(nextFieldId)) {
|
||||
config.b_readonly = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setAutoCodeEnabled(enabled) {
|
||||
const current = drafts.value.autoCode?.[0];
|
||||
const rule = current
|
||||
? { ...current, b_canuse: enabled ? 1 : 0 }
|
||||
: createAutoCodeDraft(selectedModule.value?.b_id, drafts.value.fields, enabled);
|
||||
onAutoCodeUpdate([rule]);
|
||||
}
|
||||
|
||||
function validateAutoCode() {
|
||||
const rows = drafts.value.autoCode || [];
|
||||
const rule = rows.length ? rows[0] : null;
|
||||
if (!rule || Number(rule.b_canuse) !== 1) return "";
|
||||
if (!String(selectedModule.value?.b_savetable || "").trim()) {
|
||||
return "启用自动编码前请先配置保存表";
|
||||
}
|
||||
const field = (drafts.value.fields || []).find(
|
||||
(item) => String(item.b_id) === String(rule.b_field_id),
|
||||
);
|
||||
if (!field || Number(field.b_canuse) !== 1) return "请选择已启用的业务编号字段";
|
||||
if (field.b_type !== "input" && field.b_type !== "textarea") {
|
||||
return "业务编号字段必须使用文本控件类型";
|
||||
}
|
||||
if (!Number.isInteger(Number(rule.b_start_value)) || Number(rule.b_start_value) < 0) {
|
||||
return "起始值必须是大于或等于 0 的整数";
|
||||
}
|
||||
if (!["none", "year", "month", "day"].includes(rule.b_reset_type)) {
|
||||
return "请选择正确的重置周期";
|
||||
}
|
||||
const width = Number(rule.b_sequence_width);
|
||||
if (!Number.isInteger(width) || width < 1 || width > 18) {
|
||||
return "流水号位数必须是 1 到 18 之间的整数";
|
||||
}
|
||||
if (String(rule.b_prefix || "").length > 100) return "固定前缀长度不能超过 100 个字符";
|
||||
if (String(rule.b_separator || "").length > 10) return "分隔符长度不能超过 10 个字符";
|
||||
const dateFormat = String(rule.b_date_format || "").trim();
|
||||
if (dateFormat.length > 30) return "日期格式长度不能超过 30 个字符";
|
||||
const dateLiterals = dateFormat
|
||||
.replaceAll("yyyy", "")
|
||||
.replaceAll("yy", "")
|
||||
.replaceAll("MM", "")
|
||||
.replaceAll("dd", "");
|
||||
if (/[A-Za-z]/.test(dateLiterals)) return "日期格式只支持 yyyy、yy、MM 和 dd";
|
||||
const hasYear = dateFormat.includes("yyyy") || dateFormat.includes("yy");
|
||||
const hasMonth = dateFormat.includes("MM");
|
||||
const hasDay = dateFormat.includes("dd");
|
||||
if (rule.b_reset_type === "year" && !hasYear) return "按年重置时,日期格式必须包含年份";
|
||||
if (rule.b_reset_type === "month" && (!hasYear || !hasMonth)) {
|
||||
return "按月重置时,日期格式必须包含年份和月份";
|
||||
}
|
||||
if (rule.b_reset_type === "day" && (!hasYear || !hasMonth || !hasDay)) {
|
||||
return "按日重置时,日期格式必须包含年月日";
|
||||
}
|
||||
for (const config of drafts.value.editConfig || []) {
|
||||
if (String(config.b_field_id) === String(rule.b_field_id)) config.b_readonly = 1;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function onSetField(field, value) {
|
||||
if (drafts.value.module) {
|
||||
drafts.value.module[field] = value;
|
||||
@@ -388,6 +529,7 @@ async function onAddModule(parentId, moduleType = "directory") {
|
||||
listConfig: [],
|
||||
editConfig: [],
|
||||
queryConfig: [],
|
||||
autoCode: [],
|
||||
powers: [],
|
||||
};
|
||||
drafts.value = cloneData(originals.value);
|
||||
@@ -469,6 +611,7 @@ async function onDeleteNodeById(moduleId) {
|
||||
deleteByModuleId("s_module_field_list"),
|
||||
deleteByModuleId("s_module_field_edit"),
|
||||
deleteByModuleId("s_module_field_query"),
|
||||
deleteByModuleId("s_module_auto_code"),
|
||||
deleteByModuleId("s_module_power"),
|
||||
deleteByModuleId("s_module_field"),
|
||||
deleteByModuleId("s_module_field_group"),
|
||||
@@ -497,6 +640,12 @@ async function saveAll() {
|
||||
try {
|
||||
const saveReqs = [];
|
||||
|
||||
const autoCodeError = validateAutoCode();
|
||||
if (autoCodeError) {
|
||||
message.warning(autoCodeError);
|
||||
return;
|
||||
}
|
||||
|
||||
// s_module 主表
|
||||
const moduleData = drafts.value.module;
|
||||
const moduleOriginal = originals.value.module;
|
||||
@@ -543,18 +692,25 @@ async function saveAll() {
|
||||
saveReqs.push(moduleRequest);
|
||||
}
|
||||
|
||||
// 子表:fields, listConfig, editConfig, queryConfig, powers
|
||||
// 子表:fields, listConfig, editConfig, queryConfig, autoCode, powers
|
||||
const subTables = [
|
||||
["s_module_field", "fields"],
|
||||
["s_module_field_list", "listConfig"],
|
||||
["s_module_field_edit", "editConfig"],
|
||||
["s_module_field_query", "queryConfig"],
|
||||
["s_module_auto_code", "autoCode"],
|
||||
["s_module_power", "powers"],
|
||||
];
|
||||
for (const [table, section] of subTables) {
|
||||
const current = drafts.value[section] || [];
|
||||
const previous = originals.value[section] || [];
|
||||
const changes = diffRows(current, previous);
|
||||
if (table === "s_module_auto_code") {
|
||||
changes.updates = changes.updates.map((row) => {
|
||||
const { b_current_period: _period, b_current_value: _value, ...config } = row;
|
||||
return config;
|
||||
});
|
||||
}
|
||||
const req = tableChange(table, changes);
|
||||
if (hasChanges(req)) {
|
||||
await allocateTemporaryIds([req.inserts || [], req.updates || [], req.deletes || []]);
|
||||
@@ -654,6 +810,7 @@ function buildMissingDefaultConfigs(module, fields) {
|
||||
b_required: 0,
|
||||
b_readonly: 0,
|
||||
b_disabled: 0,
|
||||
b_visible: 1,
|
||||
b_group_id: null,
|
||||
b_xh: order,
|
||||
});
|
||||
@@ -787,6 +944,7 @@ async function syncFields() {
|
||||
}
|
||||
}
|
||||
|
||||
applyDisabledFieldDefaults(existingFields, mergedFields);
|
||||
const generated = buildMissingDefaultConfigs(module, mergedFields);
|
||||
const generatedSections = ["listConfig", "editConfig", "queryConfig"];
|
||||
let generatedCount = 0;
|
||||
@@ -898,6 +1056,15 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
||||
:disabled="busy || drafts.module?.b_module_type === 'data'"
|
||||
@update:checked="(v) => onSetField('b_canmenu', v ? 1 : 0)"
|
||||
/>
|
||||
<template v-if="drafts.module?.b_module_type === 'data'">
|
||||
<a-divider type="vertical" />
|
||||
<span>自动编码</span>
|
||||
<a-checkbox
|
||||
:checked="autoCodeEnabled"
|
||||
:disabled="busy"
|
||||
@update:checked="setAutoCodeEnabled"
|
||||
/>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
@@ -926,7 +1093,7 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
||||
:module-code="selectedModule?.b_code || ''"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('fields', v)"
|
||||
@update="onFieldsUpdate"
|
||||
@i18n-saved="onI18nSaved"
|
||||
/>
|
||||
<ModuleListConfigPanel
|
||||
@@ -959,6 +1126,14 @@ void loadTree().catch((e) => message.error("模块树加载失败:" + (e.messa
|
||||
:loading="busy"
|
||||
@update="(v) => onUpdate('queryConfig', v)"
|
||||
/>
|
||||
<ModuleAutoCodePanel
|
||||
v-else-if="tab.key === 'auto-code'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
:auto-code="drafts.autoCode"
|
||||
:fields="drafts.fields"
|
||||
:loading="busy"
|
||||
@update="onAutoCodeUpdate"
|
||||
/>
|
||||
<ModulePowersPanel
|
||||
v-else-if="tab.key === 'powers'"
|
||||
:module-id="String(selectedModule.b_id)"
|
||||
|
||||
@@ -1,7 +1,93 @@
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { message, Modal } from "antdv-next";
|
||||
import { Trash2 } from "@lucide/vue";
|
||||
import FmsModuleListPage from "@/components/fms-module-list/FmsModuleListPage.vue";
|
||||
import { saveObjectApi } from "@/services/api";
|
||||
import { useAppStore } from "@/stores/app";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const listRef = ref(null);
|
||||
const selectedRowKeys = ref([]);
|
||||
const saving = ref(false);
|
||||
|
||||
const rowSelection = computed(() => {
|
||||
return {
|
||||
selectedRowKeys: selectedRowKeys.value,
|
||||
onChange: (keys) => {
|
||||
selectedRowKeys.value = keys;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
async function reloadList() {
|
||||
selectedRowKeys.value = [];
|
||||
if (listRef.value) await listRef.value.reload();
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
if (saving.value || !selectedRowKeys.value.length) return;
|
||||
const keys = [...selectedRowKeys.value];
|
||||
Modal.confirm({
|
||||
title: `${appStore.t("common.confirmBatchDelete", "确定删除选中的记录?")} (${keys.length})`,
|
||||
okText: appStore.t("common.delete", "删除"),
|
||||
okType: "danger",
|
||||
cancelText: appStore.t("common.cancel", "取消"),
|
||||
onOk: async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
const deletes = keys.map((b_id) => ({ b_id }));
|
||||
await saveObjectApi([
|
||||
{
|
||||
table: "s_log_login",
|
||||
key_field: "b_id",
|
||||
inserts: [],
|
||||
updates: [],
|
||||
deletes,
|
||||
},
|
||||
]);
|
||||
message.success(appStore.t("common.deleteSuccess", "删除成功"));
|
||||
await reloadList();
|
||||
} catch (error) {
|
||||
message.error(
|
||||
appStore.t("common.deleteFailed", "删除失败") + ":" + (error?.message || error),
|
||||
);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FmsModuleListPage />
|
||||
<div class="login-log-page">
|
||||
<FmsModuleListPage
|
||||
ref="listRef"
|
||||
:row-selection="rowSelection"
|
||||
@loaded="selectedRowKeys = []"
|
||||
>
|
||||
<template #actions="{ loading }">
|
||||
<a-space>
|
||||
<a-button
|
||||
danger
|
||||
:disabled="loading || saving || !selectedRowKeys.length"
|
||||
@click="batchDelete"
|
||||
>
|
||||
<Trash2 :size="16" />
|
||||
{{ appStore.t("common.batchDelete", "批量删除")
|
||||
}}{{ selectedRowKeys.length ? `(${selectedRowKeys.length})` : "" }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</FmsModuleListPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.login-log-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
+24
-1
@@ -18,6 +18,7 @@
|
||||
s_module
|
||||
|-- parent_id --> s_module
|
||||
|-- 1:N --------> s_module_field
|
||||
|-- 0:1 --------> s_module_auto_code --> s_module_field
|
||||
|-- 1:N --------> s_module_field_group -- parent_id --> s_module_field_group
|
||||
|-- 1:N --------> s_module_power ---- 1:N ----> b_user_power
|
||||
`-- 1:N --------> b_user_module
|
||||
@@ -89,6 +90,27 @@ s_module / s_module_field / s_module_field_list / s_module_field_group / s_modul
|
||||
- `b_type` 是展示和编辑的唯一类型来源:Table 按它渲染默认显示器,Form 或可编辑 Table 按它选择控件。
|
||||
- `select` 是否允许自由输入、选项来源、值字段和显示字段均由 `b_options` 控制。
|
||||
|
||||
### `dbo.s_module_auto_code`
|
||||
|
||||
每个数据模块最多绑定一条自动业务编号规则。`b_id` 仍是数据库技术主键,自动编码写入模块绑定的业务字段;业务字段自身应在实际保存表上建立唯一索引。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `b_id` | `bigint` | 雪花 ID 主键 |
|
||||
| `b_module_id` | `bigint` | 所属数据模块 ID,唯一 |
|
||||
| `b_field_id` | `bigint` | 写入业务编号的字段定义 ID |
|
||||
| `b_prefix` | `nvarchar(100)` | 固定前缀,可为空 |
|
||||
| `b_date_format` | `varchar(30)` | 日期格式,可为空 |
|
||||
| `b_separator` | `nvarchar(10)` | 编码各部分之间的分隔符,可为空 |
|
||||
| `b_sequence_width` | `int` | 流水号补零位数 |
|
||||
| `b_reset_type` | `varchar(10)` | `none`、`year`、`month` 或 `day` |
|
||||
| `b_start_value` | `bigint` | 流水号起始值 |
|
||||
| `b_current_period` | `varchar(8)` | 当前计数周期 |
|
||||
| `b_current_value` | `bigint` | 当前已分配流水值 |
|
||||
| `b_canuse` | `int` | 是否启用 |
|
||||
|
||||
取号时锁定规则行并递增 `b_current_value`。编号生成后不允许业务用户修改;取号与业务保存分开时允许断号,但目标业务字段必须建立唯一索引。
|
||||
|
||||
### 视图同步规则
|
||||
|
||||
字段同步服务仅面向 `b_module_type = 'data'` 且配置了 `b_viewtable` 的模块执行:
|
||||
@@ -135,7 +157,7 @@ s_module / s_module_field / s_module_field_list / s_module_field_group / s_modul
|
||||
|
||||
### `dbo.s_module_field_edit`
|
||||
|
||||
控制字段的通用编辑属性与编辑分组。控件类型、显示规则和类型专属参数统一由 `s_module_field.b_type`、`b_options` 决定;FmsForm 与 FmsTable 自行决定何时使用本配置。
|
||||
控制字段的通用编辑属性、显示状态与编辑分组。控件类型和类型专属参数统一由 `s_module_field.b_type`、`b_options` 决定;FmsForm 与 FmsTable 自行决定何时使用本配置。
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
@@ -145,6 +167,7 @@ s_module / s_module_field / s_module_field_list / s_module_field_group / s_modul
|
||||
| `b_required` | `int` | 是否必填,默认 `0` |
|
||||
| `b_readonly` | `int` | 是否只读,默认 `0` |
|
||||
| `b_disabled` | `int` | 是否禁用,默认 `0` |
|
||||
| `b_visible` | `int` | 是否显示,默认 `1` |
|
||||
| `b_group_id` | `bigint` | 所属编辑分组 ID;不分组时为空 |
|
||||
| `b_xh` | `int` | 编辑场景中的显示顺序,默认 `0` |
|
||||
|
||||
|
||||
Reference in new issue
Block a user