2ab01a61 by lihua

元数据和数据质量功能迁入

1 parent aee6275a
This diff could not be displayed because it is too large.
1 import request from "@/utils/request";
2
3 /**
4 * 元数据-采集任务
5 **/
6 // 新增
7 export const addMetaDataTask = (params) => request({
8 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/add`,
9 method: 'post',
10 data: params
11 })
12 // 删除
13 export const deleteMetaDataTask = (params) => request({
14 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/del`,
15 method: 'delete',
16 data: params
17 })
18 // 分页查询
19 export const getMetaDataTask = (params) => request({
20 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/page-list`,
21 method: 'post',
22 data: params
23 })
24 // 修改
25 export const updateMetaDataTask = (params) => request({
26 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/update`,
27 method: 'put',
28 data: params
29 })
30 // 详情
31 export const getMetaDataTaskDetail = (params) => request({
32 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/detail/${params}`,
33 method: 'get'
34 })
35 // 上线下线
36 export const updateMetaDataState = (params) => request({
37 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/change-state`,
38 method: 'get',
39 params
40 })
41 // 名称唯一性验证
42 export const checkMetaDataTask = (params) => request({
43 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/check-exist`,
44 method: 'post',
45 data: {
46 collectTaskName: params,
47 }
48 })
49 // 执行元数据采集任务
50 export const executeMetaDataTask = (params) => request({
51 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/execute/${params}`,
52 method: 'get'
53 })
54 // 执行日志
55 export const getMetaDataTaskLog = (params) => request({
56 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-exec/page-list`,
57 method: 'post',
58 data: params
59 })
60 //
61 export const saveMetaReportAnalysis = (params) => request({
62 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage-analysis-report/add`,
63 method: 'post',
64 data: params
65 })
66 /**
67 * 元数据-元数据查询
68 **/
69 // 树形数据
70 export const getMetaTreeData = (params) => request({
71 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-tree-list`,
72 method: 'post',
73 data: params
74 })
75 // 数据库汇总信息
76 export const getMetaDatabaseCollect = (params) => request({
77 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-database-collect-list`,
78 method: 'post',
79 data: params
80 })
81 // 库分页查询
82 export const getMetaDataBase = (params) => request({
83 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-table-collect-list`,
84 method: 'post',
85 data: params
86 })
87 // 表分页查询
88 export const getMetaDataSheet = (params) => request({
89 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-table-detail-list`,
90 method: 'post',
91 data: params
92 })
93 // 表字段查询
94 export const getMetaSheetField = (params) => request({
95 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-table-field-list`,
96 method: 'post',
97 params
98 })
99 // 表索引查询
100 export const getMetaSheetKeys = (params) => request({
101 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-table-index-list`,
102 method: 'post',
103 params
104 })
105 // 变更查询
106 export const getMetaChange = (params) => request({
107 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-collect-change-list`,
108 method: 'post',
109 data: params
110 })
111 // 变更明细
112 export const getMetaChangeRecord = (params) => request({
113 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-change-record-list`,
114 method: 'post',
115 data: params
116 })
117 //
118 export const getMetacompareList = (params) => request({
119 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-change-compare-list/${params}`,
120 method: 'get',
121 //data: params
122 })
123
124 // 表信息详情
125 export const getMetaDetail = (params) => request({
126 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/detail/${params}`,
127 method: 'get',
128 })
129
130 /** 根据表获取血缘数据 */
131 export const getTableLineage = (params) => request({
132 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/lineage-query?guid=${params.guid}&lineageType=tb`,
133 method: 'get',
134 })
135
136 /** 根据字段获取血缘数据 */
137 export const getTableFieldLineage = (params) => request({
138 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/lineage-query?guid=${params.guid}&lineageType=co`,
139 method: 'get',
140 })
141
142 /** 获取表的所有字段血缘数据 */
143 export const getTableAllFieldLineage = (params) => request({
144 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/lineage-query-field?databaseName=${params.databaseName}&tableName=${params.tableName}`,
145 method: 'get',
146 })
147
148 // 查询列表
149 export const getMetaList = (params) => request({
150 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/list-meta-all`,
151 method: 'post',
152 data:params
153 })
154 // 元数据表字段查询
155 export const getMetaTableField = (params) => request({
156 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/meta-table-field-list`,
157 method: 'post',
158 params,
159 })
160 // 保存血缘字段节点
161 export const saveLineageField = (params) => request({
162 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/save-field`,
163 method: 'post',
164 data:params
165 })
166 // 保存血源节点
167 export const saveLineageTable = (params) => request({
168 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/save-table`,
169 method: 'post',
170 data:params
171 })
172 // 删除血源节点
173 export const delLineageTable = (params) => request({
174 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/del-vertex?vertexId=${params.vertexId}`,
175 method: 'delete',
176 //data:params
177 })
178 /** 获取同步任务变更记录 */
179 export const getTaskChangeList = (params) => request({
180 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/task-change-record/page-list`,
181 method: 'post',
182 data:params
183 })
184
185 /** 获取元数据变更记录 */
186 export const getMetaChangeList = (params) => request({
187 url: ``,
188 method: 'post',
189 data:params
190 })
191
192 /** 获取数据库选择列表 */
193 export const getDatabase = (params) => request({
194 url: `${import.meta.env.VITE_APP_API_BASEURL}/data-source/get-source-list`,
195 method: 'post',
196 data: params
197 })
198
199 /** 源数据分析报告 */
200 /**查询列表 */
201 export const getAnalysisReportList = (params) => request({
202 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage-analysis-report/list`,
203 method: 'post',
204 data: params
205 })
206
207 /** 根据guid删除 */
208 export const delAnalysisRepor = (params) => request({
209 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage-analysis-report/del`,
210 method: 'delete',
211 data: params
212 })
213 /** 根据guid更新 */
214 export const updateAnalysisRepor = (params) => request({
215 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage-analysis-report/update`,
216 method: 'put',
217 data: params
218 })
219 /** 删除边 */
220 export const delLineAge = (params) => request({
221 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/lineage/del-edge?euid=${params}`,
222 method: 'delete',
223 })
224 /** 判断是否有元数据数据 */
225 export const checkTableData = (params) => request({
226 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-table/check-table-data/${params}`,
227 method: 'get',
228 })
229 /**校验任务是否有数据库信息 */
230 export const checkDatabaseIsExist = (dataSourceGuid) => request({
231 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/meta-collect-task/check-database-is-exist/${dataSourceGuid}`,
232 method: 'get',
233 })
234 /**同步任务 变更详情展示 */
235
236 export const syncChangeDetail = (guid) => request({
237 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/task-change-record/sync-change-detail/${guid}`,
238 method: 'get',
239 })
...\ No newline at end of file ...\ No newline at end of file
1 import request from "@/utils/request";
2
3 /** 获取质量模型对应的所有分组表格数据 */
4 export const getQualityTreeData = (params) => request({
5 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/quality-model-tree` + (params ? `?guid=${params}` : ''),
6 method: 'get'
7 })
8
9 /** 获取数据源下,对应分组的表数据 */
10 export const getQualityTreeDataByDs = (params) => request({
11 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/quality-model-tree?guid=${params.guid}&dataSourceGuid=${params.dataSourceGuid}`,
12 method: 'get'
13 })
14
15 /** 获取质量模型对应的所有分组表格分页数据 */
16 export const getQualityGroupData = (params) => request({
17 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/page-list`,
18 method: 'post',
19 data: params
20 })
21
22 /** 删除质量模型的指定分组。 */
23 export const deleteGroup = (params) => request({
24 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/del`,
25 method: 'delete',
26 data: params
27 })
28
29 /** 添加分组 */
30 export const addQualityGroup = (params) => request({
31 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/add`,
32 method: 'post',
33 data: params
34 })
35
36 /** 修改分组 */
37 export const updateQualityGroup = (params) => request({
38 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/update`,
39 method: 'put',
40 data: params
41 })
42
43 /** 获取质量分组对应的表格分页数据 */
44 export const getQualityTable = (params) => request({
45 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/page-list`,
46 method: 'post',
47 data: params
48 })
49
50 /** 删除质检表 */
51 export const deleteQualityTable = (params) => request({
52 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/del`,
53 method: 'delete',
54 data: params
55 })
56
57 /** 获取质量表对应的所有规则数据 */
58 export const getQualityTableRule = (params) => request({
59 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/list/model-rule`,
60 method: 'post',
61 data: params
62 })
63
64 /** 删除质检表规则 */
65 export const deleteQualityTableRule = (params) => request({
66 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/conf/del?ruleConfGuid=${params}`,
67 method: 'delete',
68 data: params
69 })
70
71 /** 更新质检表规则的禁用和启用状态 */
72 export const updateRuleBizState = (params) => request({
73 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/conf/upate-bizstate?ruleConfGuid=${params.ruleConfGuid}&bizState=${params.bizState}`,
74 method: 'post',
75 })
76
77 /** 获取数据库表列表 */
78 export const getDatabase = (params) => request({
79 url: `${import.meta.env.VITE_APP_API_BASEURL}/data-source/get-source-list`,
80 method: 'post',
81 data: params
82 })
83
84 /** 新建质检表,获取主题域分层的主题表树结构 */
85 export const getSubjectTableTree = (params) => request({
86 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/data-catalog-directory/directory-tree-list`,
87 method: 'post',
88 data: params
89 })
90
91 /** 新建质检表,获取主题域分层的主题表树结构 */
92 export const getSubjectTableByDomain = (params) => request({
93 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/data-catalog-subject/list-by-domain-guid?domainGuid=${params}`,
94 method: 'get',
95 data: params
96 })
97
98 /** 获取主题表的字段列表 */
99 export const getSubjectFields = (params) => request({
100 url: `${import.meta.env.VITE_APP_PLAN_BASEURL}/data-catalog-subject/field/list?subjectGuid=${params}`,
101 method: 'get',
102 })
103
104 /** 表的逻辑条件和sql检验。 */
105 export const validateSubjectTableRule = (params) => request({
106 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/sql-operate/check-sql`,
107 method: 'post',
108 data: params
109 })
110
111 /** 自定义sql检验 */
112 export const validateCustomSql = (params) => request({
113 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/conf/check-custom-sql`,
114 method: 'post',
115 data: params
116 })
117
118 /** 批量验证过滤条件 */
119 export const batchValidateSubjectTableRule = (params) => request({
120 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/sql-operate/batch-check-sql`,
121 method: 'post',
122 data: params
123 })
124
125 /** 保存质检表 */
126 export const saveQualityTable = (params) => request({
127 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/add`,
128 method: 'post',
129 data: params
130 })
131
132 // 获取规则类型的接口
133 export const getRuleTypeList = () => request({
134 url:`${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-rule/list`,
135 method: 'post',
136 data: {}
137 })
138
139 // 获取规则大类的接口
140 export const getLargeCategoryList = () => request({
141 url:`${import.meta.env.VITE_APP_API_BASEURL}/data-dict/get-data-list`,
142 method: 'post',
143 data: { paramCode: "LARGE-CATEGORY" }
144 })
145
146 // 获取规则小类的接口
147 export const getSmallCategoryList = () => request({
148 url:`${import.meta.env.VITE_APP_API_BASEURL}/data-dict/get-data-list`,
149 method: 'post',
150 data: { paramCode: "SMALL-CATEGORY" }
151 })
152
153 // 根据规则guid获取规则的详情信息。
154 export const getRuleConfDetail = (param) => request({
155 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/conf/detail?ruleConfGuid=${param}`,
156 method: 'get'
157 })
158
159 // 根据质检模型guid获取详情信息。
160 export const getModelDetail = (param) => request({
161 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/detail/${param}`,
162 method: 'get'
163 })
164
165 // 更新规则信息。
166 export const updateModelRule = (params) => request({
167 url:`${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/conf/update`,
168 method: 'post',
169 data: params
170 })
...\ No newline at end of file ...\ No newline at end of file
1 import request from "@/utils/request";
2
3 /** 根据类型获取方案列表 */
4 export const getPlanList = (params) => request({
5 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/list`,
6 method: 'post',
7 data: params
8 })
9
10 /** 删除方案 */
11 export const deletePlan = (guids) => request({
12 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/del`,
13 method: 'delete',
14 data: guids
15 })
16
17 /** 更新指定方案的状态上线,下线。 */
18 export const updateQualityPlanState = (params) => request({
19 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/state-change?guid=${params.guid}&state=${params.state}`,
20 method: 'post'
21 })
22
23 /** 手动执行方案 */
24 export const executePlan = (params) => request({
25 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/exec-plan?planGuid=${params}`,
26 method: 'post'
27 })
28
29 /** 获取方案详情,用于编辑 */
30 export const getAssessPlanDetail = (params) => request({
31 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/detail/${params}`,
32 method: 'get'
33 })
34
35 /** 获取方案详情中的过滤条件,用于编辑 */
36 export const getPlanFilterDetail = (params) => request({
37 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/query-plan-filter?planGuid=${params}`,
38 method: 'get'
39 })
40
41 /** 获取方案查看详情列表数据。 */
42 export const getAssessDetailTableData = (params) => request({
43 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/query-exec-list`,
44 method: 'post',
45 data: params
46 })
47
48 /** 根据执行guid,获取方案执行详情。 */
49 export const getExecPlanDetailTableData = (params) => request({
50 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/query-exec-detail?planGuid=${params.planGuid}&planExecGuid=${params.planExecGuid}`,
51 method: 'get'
52 })
53
54 /** 获取方案详情中每个表的规则详细执行列表数据。 */
55 export const getAssessTableRulesData = (params) => request({
56 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/query-exec-table-detail?planExecGuid=${params.planExecGuid}&qualityModelGuid=${params.qualityModelGuid}`,
57 method: 'get'
58 })
59
60 /** 获取脏数据查询 */
61 export const getQueryDirtyData = (params) => request({
62 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/query-dirty-data`,
63 method: 'post',
64 data: params
65 })
66
67 /** 获取脏数据字段列表 */
68 export const getQueryDirtyFields = (params) => request({
69 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/query-dirty-data-column?qualityModelGuid=${params}`,
70 method: 'get'
71 })
72
73 /** 获取数据质量模型统计 */
74 export const getModelCountList = (params) => request({
75 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/list/model-count`,
76 method: 'post',
77 data: params
78 })
79
80 /** 获取规则大类统计 */
81 export const getModelRuleCount = (params) => request({
82 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/list/model-rule-category-count`,
83 method: 'post',
84 data: params
85 })
86
87 /** 根据modelGuid获取详细的规则统计 */
88 export const getModelRules = (params) => request({
89 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/list/model-rule`,
90 method: 'post',
91 data: params
92 })
93
94 /** 检查质检评估方案名称是否重复 */
95 export const checkPlanExist = (params) => request({
96 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/exist?planName=${params}`,
97 method: 'get'
98 })
99
100 /** 获取有效的数据分组,用于评估 表选择 */
101 export const getValidGroup = () => request({
102 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-group/valid-group-list`,
103 method: 'get'
104 })
105
106 /** 获取数据库列表 */
107 export const getModelDbGp = (dsGuid: any = null) => request({
108 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model/list/model-db-gp` + (!dsGuid ? '' : `?dataSourceGuid=${dsGuid}`),
109 method: 'get'
110 })
111
112 /** 保存质量方案 */
113 export const saveQualityPlan = (params) => request({
114 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/add`,
115 method: 'post',
116 data: params
117 })
118
119 /** 更新质量方案 */
120 export const updateQualityPlan = (params) => request({
121 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/update`,
122 method: 'put',
123 data: params
124 })
125
126 /** 下载脏数据 */
127 export const downloadDirtyData = (params) => request({
128 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-plan/down-dirty-data`,
129 method: 'post',
130 data: params,
131 responseType: 'blob'
132 })
133
134 /** 获取对应执行方案的规则详情 */
135 export const getRecordRuleConfDetail = (param) => request({
136 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-model-record/conf/detail?ruleConfGuid=${param.ruleConfGuid}&planExecGuid=${param.planExecGuid}`,
137 method: 'get'
138 });
...\ No newline at end of file ...\ No newline at end of file
1 /** 用于质量分析报告模块 */
2 import request from "@/utils/request";
3
4 /** 获取质量分析报告列表数据。 */
5 export const getQualityWordList = (params) => request({
6 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/page-list`,
7 method: 'post',
8 data: params
9 })
10
11 /** 删除质量分析报告 */
12 export const deleteQualityWord = (params) => request({
13 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/del`,
14 method: 'delete',
15 data: params
16 })
17
18 /** 添加质量报告 */
19 export const addQualityWord = (params) => request({
20 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/add`,
21 method: 'post',
22 data: params
23 })
24
25 /** 更新质量报告 */
26 export const updateQualityWord = (params) => request({
27 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/update`,
28 method: 'put',
29 data: params
30 })
31
32 /** 根据质量报告获取对应的执行日志。 */
33 export const getWordLogList = (params) => request({
34 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/page-exec-list`,
35 method: 'post',
36 data: params
37 });
38
39 /** 获取质量分析报告的详细内容,根绝报告guid。 */
40 export const getReportDetail = (params) => request({
41 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/get-report-data`,
42 method: 'post',
43 data: params
44 });
45
46 /** 获取数据质量一级指标得分统计 */
47 export const getLargeCategoryScore = (params) => request({
48 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/get-largeCategory-score?reportExecGuid=${params}`,
49 method: 'get'
50 });
51
52 /** 获取方案执行明细 */
53 export const getPlanDetail= (params) => request({
54 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/query-exec-table-detail?reportExecGuid=${params.reportExecGuid}&planGuid=${params.planGuid}`,
55 method: 'get'
56 });
57
58 /** 获取方案执行表规则查看 */
59 export const getTableRuleDetail= (params) => request({
60 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/query-exec-table-rule-detail?reportExecGuid=${params}`,
61 method: 'get'
62 });
63
64 /** 手动执行报告 */
65 export const executeReport = (params) => request({
66 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/exec?guid=${params}`,
67 method: 'get'
68 })
69
70 /** html转word接口 */
71 export const htmlToWord = (params) => request({
72 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/download/html-to-word`,
73 method: 'postJsonD',
74 data: params,
75 responseType: 'blob'
76 });
77
78 /**上下线 */
79
80 export const stateChange = (params) => request({
81 url: `${import.meta.env.VITE_APP_QUALITY_BASEURL}/quality-analysis-report/state-change?guid=${params.guid}&state=${params.state}`,
82 method: 'post',
83
84 });
...\ No newline at end of file ...\ No newline at end of file
1 /** 通用的分页配置 */
2 export const commonPageConfig = {
3 limit: 50,
4 curr: 1,
5 sizes: [
6 { label: "10", value: 10 },
7 { label: "50", value: 50 },
8 { label: "100", value: 100 },
9 { label: "150", value: 150 },
10 { label: "200", value: 200 },
11 ]
12 }
...\ No newline at end of file ...\ No newline at end of file
...@@ -36,6 +36,10 @@ const pageCount = computed(() => { ...@@ -36,6 +36,10 @@ const pageCount = computed(() => {
36 return Math.ceil(props.pageInfo.rows / props.pageInfo.limit) 36 return Math.ceil(props.pageInfo.rows / props.pageInfo.limit)
37 }) 37 })
38 38
39 const pagerCount = computed(() => {
40 return props.pageInfo.pagerCount ?? 7;
41 })
42
39 const pageType = computed({ 43 const pageType = computed({
40 get: () => { 44 get: () => {
41 return props.pageInfo.type 45 return props.pageInfo.type
...@@ -135,7 +139,7 @@ function inputPageJump(val) { ...@@ -135,7 +139,7 @@ function inputPageJump(val) {
135 条数据</span> 139 条数据</span>
136 </el-pagination> 140 </el-pagination>
137 <el-pagination small layout="prev, slot, next" :total="pageTotal" :default-page-size="pageLimit" 141 <el-pagination small layout="prev, slot, next" :total="pageTotal" :default-page-size="pageLimit"
138 :page-count="pageCount" :current-page="parseInt(pageNum)" @current-change="pageCurrentChange"> 142 :page-count="pageCount" :pager-count="pagerCount" :current-page="parseInt(pageNum)" @current-change="pageCurrentChange">
139 <div class="page_code"> 143 <div class="page_code">
140 <el-input class="pagnination" type="number" :min="1" :max="pageCount" v-model.trim="pageNumInput" size="small" 144 <el-input class="pagnination" type="number" :min="1" :max="pageCount" v-model.trim="pageNumInput" size="small"
141 style="width: 40px" @input="handleInput" @change="inputPageJump" :disabled="pageCount <= 1" /> 145 style="width: 40px" @input="handleInput" @change="inputPageJump" :disabled="pageCount <= 1" />
...@@ -162,7 +166,7 @@ function inputPageJump(val) { ...@@ -162,7 +166,7 @@ function inputPageJump(val) {
162 }}</span> 166 }}</span>
163 条数据</span> 167 条数据</span>
164 </el-pagination> 168 </el-pagination>
165 <el-pagination background small layout="prev, pager, next, slot" :total="pageTotal" :default-page-size="pageLimit" 169 <el-pagination background small layout="prev, pager, next, slot" :total="pageTotal" :default-page-size="pageLimit" :pager-count="pagerCount"
166 :page-count="pageCount" :current-page="pageCurr" @current-change="pageCurrentChange"> 170 :page-count="pageCount" :current-page="pageCurr" @current-change="pageCurrentChange">
167 <div class="page_jumper"> 171 <div class="page_jumper">
168 <el-input class="pagnination" type="number" :min="1" :max="pageCount" v-model.trim="pageNumInput" size="small" 172 <el-input class="pagnination" type="number" :min="1" :max="pageCount" v-model.trim="pageNumInput" size="small"
...@@ -237,4 +241,4 @@ function inputPageJump(val) { ...@@ -237,4 +241,4 @@ function inputPageJump(val) {
237 padding: 0 8px; 241 padding: 0 8px;
238 } 242 }
239 } 243 }
240 </style>@/utils/common
...\ No newline at end of file ...\ No newline at end of file
244 </style>
...\ No newline at end of file ...\ No newline at end of file
......
1 import type { RouteRecordRaw } from 'vue-router'
2
3 function Layout() {
4 return import('@/layouts/index.vue')
5 }
6
7 const routes: RouteRecordRaw[] = [
8 {
9 path: '/data-meta/collect-task',
10 component: Layout,
11 meta: {
12 title: '采集任务',
13 icon: 'sidebar-videos',
14 },
15 children: [
16 {
17 path: '',
18 name: 'collectorTask',
19 component: () => import('@/views/data_meta/collectorTask.vue'),
20 meta: {
21 title: '采集任务',
22 sidebar: false,
23 breadcrumb: false,
24 cache: true
25 },
26 },
27 {
28 path: 'excution-log',
29 name: 'excutionLog',
30 component: () => import('@/views/data_meta/executionLog.vue'),
31 meta: {
32 title: '采集日志',
33 sidebar: false,
34 breadcrumb: false,
35 },
36 beforeEnter: (to, from) => {
37 if (to.query.name) {
38 to.meta.title = `采集日志-${to.query.name}`;
39 }
40 }
41 },
42 ],
43 },
44 {
45 path: '/data-meta/metadata-query',
46 component: Layout,
47 meta: {
48 title: '元数据查询',
49 icon: 'sidebar-videos',
50 },
51 children: [
52 {
53 path: '',
54 name: 'metadataQuery',
55 component: () => import('@/views/data_meta/metadataQuery.vue'),
56 meta: {
57 title: '元数据查询',
58 sidebar: false,
59 breadcrumb: false,
60 cache: true
61 },
62 },
63 {
64 path: 'meta-sheet',
65 name: 'metaSheet',
66 component: () => import('@/views/data_meta/metaSheet.vue'),
67 meta: {
68 title: '元数据详情-',
69 sidebar: false,
70 breadcrumb: false,
71 cache: true,
72 reuse: true
73 },
74 beforeEnter: (to, from) => {
75 if (to.query.name) {
76 to.meta.title = `元数据详情-${to.query.name}`;
77 }
78 }
79 },
80 ],
81 },
82 {
83 path: '/data-meta/metadata-lineage',
84 component: Layout,
85 meta: {
86 title: '元数据血缘',
87 icon: 'ep:grid',
88 },
89 children: [
90 {
91 path: 'analysis-view',
92 name: 'analysisView',
93 component: () => import('@/views/data_meta/analysisView.vue'),
94 meta: {
95 title: '查看血缘',
96 breadcrumb: false,
97 cache: true
98 },
99 },
100 {
101 path: 'change-detection',
102 name: 'changeDetection',
103 component: () => import('@/views/data_meta/changeDetection.vue'),
104 meta: {
105 title: '血缘变更检测',
106 breadcrumb: false,
107 cache: true
108 },
109 },
110 {
111 path: 'analysis-reports',
112 name: 'analysisReports',
113 component: () => import('@/views/data_meta/analysisReports.vue'),
114 meta: {
115 title: '血缘关系解析',
116 breadcrumb: false,
117 cache: true
118 },
119 },
120 ],
121 },
122 ]
123
124 export default routes
1 import type { RouteRecordRaw } from 'vue-router'
2
3 function Layout() {
4 return import('@/layouts/index.vue')
5 }
6
7 const routes: RouteRecordRaw[] = [
8 {
9 path: '/data-quality/quality-rules',
10 component: Layout,
11 meta: {
12 title: '质量规则管理',
13 icon: 'sidebar-videos',
14 },
15 children: [
16 {
17 path: '',
18 name: 'qualityRules',
19 component: () => import('@/views/data_quality/qualityRules.vue'),
20 meta: {
21 title: '质量规则管理',
22 sidebar: false,
23 breadcrumb: false,
24 cache: true
25 },
26 },
27 {
28 path: 'rule-model',
29 name: 'ruleModel',
30 component: () => import('@/views/data_quality/ruleModel.vue'),
31 meta: {
32 title: '新建质检表',
33 sidebar: false,
34 breadcrumb: false,
35 cache: true,
36 reuse: true
37 },
38 beforeEnter: (to, from) => {
39 if (to.query.groupGuid) {
40 to.meta.title = `新建质检表(${to.query.name})`;
41 to.meta.editPage = true;
42 }
43 }
44 },
45 {
46 path: 'rule-template',
47 name: 'ruleTemplate',
48 component: () => import('@/views/data_quality/ruleTemplate.vue'),
49 meta: {
50 title: '新建规则',
51 sidebar: false,
52 breadcrumb: false,
53 cache: true,
54 reuse: true
55 },
56 beforeEnter: (to, from) => {
57 if (to.query.modelGuid) {
58 to.meta.title = `新建规则(${to.query.name})`;
59 to.meta.editPage = true;
60 }
61 }
62 },
63 {
64 path: 'rule-model-edit',
65 name: 'ruleModelEdit',
66 component: () => import('@/views/data_quality/ruleModelEdit.vue'),
67 meta: {
68 title: '编辑',
69 sidebar: false,
70 breadcrumb: false,
71 cache: true,
72 reuse: true,
73 editPage: true
74 },
75 beforeEnter: (to, from) => {
76 if (to.query.guid) {
77 to.meta.title = `编辑-`;
78 }
79 }
80 },
81 {
82 path: 'import-file',
83 name: 'importFiles',
84 component: () => import('@/views/importFile.vue'),
85 meta: {
86 title: '文件导入',
87 sidebar: false,
88 breadcrumb: false,
89 cache: true,
90 reuse: true
91 },
92 }
93 ],
94 },
95 {
96 path: '/data-quality/quality-assess',
97 component: Layout,
98 meta: {
99 title: '质量评估方案',
100 icon: 'sidebar-videos',
101 },
102 children: [
103 {
104 path: '',
105 name: 'qualityAssess',
106 component: () => import('@/views/data_quality/qualityAssess.vue'),
107 meta: {
108 title: '质量评估方案',
109 sidebar: false,
110 breadcrumb: false,
111 cache: true
112 },
113 },
114 {
115 path: 'assess-template',
116 name: 'assessTemplate',
117 component: () => import('@/views/data_quality/assessTemplate.vue'),
118 meta: {
119 title: '新建质量评估方案',
120 sidebar: false,
121 breadcrumb: false,
122 cache: true,
123 reuse: true,
124 editPage: true
125 },
126 beforeEnter: (to, from) => {
127 if (to.query.detail) {
128 to.meta.title = `方案详情-${to.query.planName}`;
129 to.meta.editPage = false;
130 } else if (to.query.planName) {
131 to.meta.title = `方案编辑-${to.query.planName}`;
132 }
133 }
134 },
135 {
136 path: 'assess-detail',
137 name: 'assessDetail',
138 component: () => import('@/views/data_quality/assessDetail.vue'),
139 meta: {
140 title: '查看结果',
141 sidebar: false,
142 breadcrumb: false,
143 cache: true,
144 reuse: true
145 },
146 beforeEnter: (to, from) => {
147 if (to.query.name) {
148 to.meta.title = `查看结果-${to.query.name}`;
149 }
150 }
151 },
152 {
153 path: 'assess-dirty',
154 name: 'assessDirty',
155 component: () => import('@/views/data_quality/assessDirty.vue'),
156 meta: {
157 title: '脏数据',
158 sidebar: false,
159 breadcrumb: false,
160 cache: true,
161 reuse: true
162 },
163 beforeEnter: (to, from) => {
164 if (to.query.name) {
165 to.meta.title = `脏数据-${to.query.name}`;
166 }
167 }
168 },
169 {
170 path: 'assess-log',
171 name: 'assessLog',
172 component: () => import('@/views/data_quality/assessLog.vue'),
173 meta: {
174 title: '执行日志',
175 sidebar: false,
176 breadcrumb: false,
177 cache: true,
178 reuse: true
179 },
180 beforeEnter: (to, from) => {
181 if (to.query.guid) {
182 to.meta.title = `日志-${to.query.name}`;
183 }
184 }
185 },
186 ],
187 },
188 {
189 path: '/data-quality/quality-analysis',
190 component: Layout,
191 meta: {
192 title: '质量分析报告',
193 icon: 'ep:grid',
194 },
195 children: [
196 {
197 path: '',
198 name: 'qualityAnalysis',
199 component: () => import('@/views/data_quality/qualityAnalysis.vue'),
200 meta: {
201 title: '质量分析报告',
202 sidebar: false,
203 breadcrumb: false,
204 cache: true
205 },
206 },
207 {
208 path: 'analysis-log',
209 name: 'analysisLog',
210 component: () => import('@/views/data_quality/analysisLog.vue'),
211 meta: {
212 title: '执行日志',
213 sidebar: false,
214 breadcrumb: false,
215 cache: true,
216 reuse: true
217 },
218 beforeEnter: (to, from) => {
219 if (to.query.name) {
220 to.meta.title = `日志-${to.query.name}`;
221 }
222 }
223 },
224 {
225 path: 'analysis-report',
226 name: 'analysisReport',
227 component: () => import('@/views/data_quality/analysisReport.vue'),
228 meta: {
229 title: '分析报告',
230 sidebar: false,
231 breadcrumb: false,
232 cache: true,
233 reuse: true
234 },
235 beforeEnter: (to, from) => {
236 if (to.query.name) {
237 to.meta.title = `分析报告-${to.query.name}`;
238 }
239 }
240 },
241 ],
242 }
243 ]
244
245 export default routes
...@@ -2,6 +2,8 @@ import { setupLayouts } from 'virtual:meta-layouts' ...@@ -2,6 +2,8 @@ import { setupLayouts } from 'virtual:meta-layouts'
2 import generatedRoutes from 'virtual:generated-pages' 2 import generatedRoutes from 'virtual:generated-pages'
3 import type { RouteRecordRaw } from 'vue-router' 3 import type { RouteRecordRaw } from 'vue-router'
4 import DataAssess from './modules/dataAsset'; 4 import DataAssess from './modules/dataAsset';
5 import DataMeta from './modules/dataMeta';
6 import DataQuality from './modules/dataQuality';
5 7
6 import type { Route } from '#/global' 8 import type { Route } from '#/global'
7 import useSettingsStore from '@/store/modules/settings' 9 import useSettingsStore from '@/store/modules/settings'
...@@ -105,6 +107,22 @@ const asyncRoutes: Route.recordMainRaw[] = [ ...@@ -105,6 +107,22 @@ const asyncRoutes: Route.recordMainRaw[] = [
105 ...DataAssess, 107 ...DataAssess,
106 ], 108 ],
107 }, 109 },
110 {
111 meta: {
112 title: '元数据',
113 },
114 children: [
115 ...DataMeta,
116 ],
117 },
118 {
119 meta: {
120 title: '数据质量',
121 },
122 children: [
123 ...DataQuality,
124 ],
125 }
108 ] 126 ]
109 127
110 const constantRoutesByFilesystem = generatedRoutes.filter((item) => { 128 const constantRoutesByFilesystem = generatedRoutes.filter((item) => {
......
1 const useDataQualityStore = defineStore( 1 const useDataQualityStore = defineStore(
2 // 质检表分组guid 2 // 质检表分组guid
3 'modelGroupGuid', 3 'modelGroupGuid',
4 4
5 () => { 5 () => {
6 const modelGroupGuid = ref<string>("") 6 const modelGroupGuid = ref<string>("")
7 7
8 function set(guid: any) { 8 function set(guid: any) {
9 modelGroupGuid.value = guid; 9 modelGroupGuid.value = guid;
10 } 10 }
11 11
12 const modelGuid = ref<string>("") 12 const modelGuid = ref<string>("")
13 13
14 function setModelGuid(guid: any) { 14 function setModelGuid(guid: any) {
15 modelGuid.value = guid; 15 modelGuid.value = guid;
16 } 16 }
17 17
18 const planType = ref<number>(0); 18 const planType = ref<number>(0);
19 19
20 function setPlanType(type: any) { 20 function setPlanType(type: any) {
21 planType.value = type; 21 planType.value = type;
22 } 22 }
23 23
24 const defaultPlanType = ref<number>(1); 24 const defaultPlanType = ref<number>(1);
25 25
26 function setDefaultPlanType(type: any) { 26 function setDefaultPlanType(type: any) {
27 defaultPlanType.value = type; 27 defaultPlanType.value = type;
28 } 28 }
29 29
30 const isUpdate = ref(false); 30 const isUpdate = ref(false);
31 function setIsUpdate(update: boolean) { 31 function setIsUpdate(update: boolean) {
32 isUpdate.value = update; 32 isUpdate.value = update;
33 } 33 }
34 34
35 35
36 return { 36 return {
37 modelGroupGuid, 37 modelGroupGuid,
38 set, 38 set,
39 setModelGuid, 39 setModelGuid,
40 modelGuid, 40 modelGuid,
41 planType, 41 planType,
42 isUpdate, 42 isUpdate,
43 setIsUpdate, 43 setIsUpdate,
44 setPlanType, 44 setPlanType,
45 setDefaultPlanType, 45 setDefaultPlanType,
46 defaultPlanType 46 defaultPlanType
47 } 47 }
48 }, 48 },
49 ) 49 )
50 50
51 export default useDataQualityStore 51 export default useDataQualityStore
...\ No newline at end of file ...\ No newline at end of file
......
1 <route lang="yaml">
2 name: analysisReports
3 </route>
4
5 <script lang="ts" setup name="analysisReports">
6 import { ref } from 'vue'
7 import { ElMessage, ElMessageBox } from "element-plus";
8 import Table from '@/components/Table/index.vue'
9 import TableTools from '@/components/Tools/table_tools.vue'
10 import {getAnalysisReportList,delAnalysisRepor,updateAnalysisRepor} from "@/api/modules/dataMetaService"
11 import { getImageContent } from "@/api/modules/queryService";
12 import Dialog from '@/components/Dialog/index.vue'
13 import { getDownloadUrl, download } from "@/utils/common";
14 import { useRouter } from 'vue-router';
15 const router = useRouter()
16 const page = ref({
17 limit: 50,
18 curr: 1,
19 sizes: [
20 { label: "10", value: 10 },
21 { label: "50", value: 50 },
22 { label: "100", value: 100 },
23 { label: "150", value: 150 },
24 { label: "200", value: 200 },
25 ],
26 analysisReportName: ''
27 });
28 const { proxy } = getCurrentInstance() as any;
29 const tableInfo = ref({
30 id: 'analysis-reports-table',
31 fields: [
32 { label: "序号", type: "index", width: 56, align: "center" },
33 { label: "血缘关系名称", field: "analysisReportName", width: 150 },
34 { label: "数据源中文名称", field: "databaseChName", width: 150 },
35 { label: "表名称", field: "table", width: 150 },
36 { label: "创建人", field: "updateUserName", width: 140 },
37 { label: "创建时间", field: "updateTime", width: 180 }
38 ],
39 data: [],
40 page: {
41 type: "normal",
42 rows: 0,
43 ...page.value,
44 },
45 actionInfo: {
46 label: "操作",
47 type: "btn",
48 width: 185,
49 fixed: 'right',
50 btns: (scope) => {
51 let btnsArr: any = [
52 { label: "查看", value: "view" },
53 { label: "下载", value: "export" },
54 { label: "重命名", value: "rename" },
55 { label: "删除", value: "delete" },
56 ]
57 return btnsArr;
58 },
59 },
60 loading: false
61 })
62
63 const currTableData: any = ref({});
64 const formItems: any = ref([
65 {
66 label: '血缘关系名称',
67 type: 'input',
68 placeholder: '请输入',
69 field: 'analysisReportName',
70 default: '',
71 clearable: true,
72 required: true
73 },
74 ])
75 const formRules: any = ref({
76 analysisReportName: [
77 {
78 required: true,
79 message: "请填写血缘关系名称",
80 trigger: "blur",
81 },
82 ],
83 })
84 const formInfo = ref({
85 type: 'form',
86 title: '',
87 formInfo: {
88 id: 'add-dict-form',
89 items: formItems.value,
90 rules: formRules.value
91 }
92 })
93 const reportDialogRef = ref();
94 const dialogInfo = ref({
95 visible: false,
96 size: 500,
97 direction: "column",
98 header: {
99 title: "重命名",
100 },
101 type: '',
102 contents: [
103 formInfo.value,
104 ],
105 footer: {
106 btns: [
107 { type: "default", label: "取消", value: "cancel" },
108 { type: "primary", label: "保存", value: "submit" },
109 ],
110 },
111 });
112 const rowData:any = ref({})
113 const tableSearchItemList: any = ref([{
114 type: 'input',
115 label: '',
116 field: 'analysisReportName',
117 default: '',
118 maxlength: 50,
119 placeholder: '血缘关系名称',
120 clearable: true
121 }]);
122 const tableBtnClick = (scope, btn) => {
123 const type = btn.value;
124 let row = scope.row;
125 rowData.value = row
126 currTableData.value = row;
127 if (type == 'view') {
128 getImageContent(row.analysisReportUrl).then((res: any) => {
129 if (res && !res.msg) {
130 let name = row.analysisReportUrl;
131 var fileSuffix = name ? name.substring(name.lastIndexOf('.') + 1) : '';
132 if (fileSuffix === 'png') { //浏览器可以支持图片和pdf预览
133 let fileUrl = getDownloadUrl(res, name, fileSuffix);
134 let win = window.open(fileUrl, name);
135 win && (win.document.title = name);
136 } else {
137 download(res, row.analysisReportName, fileSuffix);
138 }
139 } else {
140 res?.msg && ElMessage.error(res?.msg);
141 }
142 });
143 } else if (type == 'export') {
144 getImageContent(row.analysisReportUrl).then((res: any) => {
145 if (res && !res.msg) {
146 let name = row.analysisReportUrl;
147 var fileSuffix = name ? name.substring(name.lastIndexOf('.') + 1) : '';
148 download(res, row.analysisReportName, fileSuffix);
149 } else {
150 res?.msg && ElMessage.error(res?.msg);
151 }
152 });
153 } else if (type == "rename") {
154 dialogInfo.value.visible = true
155 formItems.value[0].default = row.analysisReportName
156 } else if (type == "delete") {
157 open("此操作将永久删除, 是否继续?", "warning");
158 }
159 };
160
161 const dialogBtnClick = (btn,scope)=>{
162 if(btn.value==="cancel") {
163 dialogInfo.value.visible = false
164 } else {
165 const analysisReportName = reportDialogRef.value.dialogFormRef[0].formInline.analysisReportName;
166 if(analysisReportName){
167 console.log(analysisReportName)
168 updateAnalysisRepor({guid:rowData.value.guid,analysisReportName}).then((res:any)=>{
169 if(res.code===proxy.$passCode){
170 ElMessage.success("血缘关系名称修改成功!")
171 getTableData()
172 dialogInfo.value.visible = false
173 } else {
174 ElMessage.error(res.msg)
175 }
176 }).catch((res:any)=>{
177 ElMessage.error(res.msg)
178 })
179 }
180 }
181
182 }
183
184 const open = (msg, type) => {
185 ElMessageBox.confirm(msg, "提示", {
186 confirmButtonText: "确定",
187 cancelButtonText: "取消",
188 type: type,
189 }).then(() => {
190 let guid = currTableData.value.guid;
191 delAnalysisRepor([guid]).then((res:any)=>{
192 if(res.code===proxy.$passCode){
193 getTableData()
194 ElMessage.success("删除成功")
195 } else {
196 ElMessage.error(res.msg)
197 }
198 }).catch((res)=>{
199 ElMessage.error(res.msg)
200 })
201 });
202 };
203
204 const tablePageChange = (info) => {
205 page.value.curr = Number(info.curr);
206 page.value.limit = Number(info.limit);
207 getTableData()
208 };
209
210
211 const getTableData = ()=>{
212 tableInfo.value.loading = true
213 getAnalysisReportList({pageIndex:page.value.curr,pageSize:page.value.limit, analysisReportName: page.value.analysisReportName}).then((res:any)=>{
214
215 if(res.code===proxy.$passCode){
216 tableInfo.value.data = res.data.records || []
217 tableInfo.value.page.rows = res.data.totalRows
218 tableInfo.value.page.curr = res.data.pageIndex
219 tableInfo.value.page.limit = res.data.pageSize
220 tableInfo.value.loading = false
221 } else {
222 ElMessage.error(res.msg)
223 tableInfo.value.loading = false
224 }
225
226 })
227 }
228 const toSearch = (val: any, clear: boolean = false) => {
229 if (clear) {
230 page.value.analysisReportName = '';
231 } else {
232 page.value.analysisReportName = val.analysisReportName;
233 }
234 page.value.curr = 1;
235 getTableData();
236 };
237 onActivated(() => {
238 getTableData()
239 });
240
241 </script>
242
243 <template>
244 <div class="container_wrap">
245 <div class="table_tool_wrap">
246 <TableTools :searchItems="tableSearchItemList" :init="false" searchId="report-table-search" @search="toSearch" />
247 </div>
248 <div class="table_panel_wrap full">
249 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tablePageChange="tablePageChange" />
250 </div>
251 <Dialog ref="reportDialogRef" :dialogInfo="dialogInfo" @btnClick="dialogBtnClick" />
252 </div>
253 </template>
254
255 <style scoped lang="scss">
256 :deep(.el-overlay .el-form .el-form-item) {
257 width: calc(100%);
258 }
259 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: analysisView
3 </route>
4
5 <script lang="ts" setup name="analysisView">
6 import { ref } from 'vue';
7 import { ElMessage, ElMessageBox } from "element-plus";
8 import Tree from '@/components/Tree/index.vue';
9 import LineageGraph from '@/components/LineageGraph/index.vue';
10 import Dialog from "@/components/Dialog/index.vue"
11 import {
12 getMetaTreeData,
13 getTableLineage,
14 getTableFieldLineage,
15 getTableAllFieldLineage,
16 getMetaList,
17 getMetaTableField,
18 saveLineageTable,
19 saveLineageField,
20 delLineageTable,
21 saveMetaReportAnalysis,
22 delLineAge,
23 checkTableData
24 } from '@/api/modules/dataMetaService';
25 import { getFileUrl } from "@/api/modules/queryService"
26 import { useRouter, useRoute } from "vue-router";
27 import useDataMetaStore from "@/store/modules/dataMeta"
28 import { cloneDeep } from 'lodash-es'
29
30 const router = useRouter();
31 const route = useRoute()
32 const emunData = {
33 "1": "数据库表"
34 }
35 const { proxy } = getCurrentInstance() as any;
36
37 /** 添加上下游节点弹框时第一个下拉框下拉第一级内容。 */
38 const metaTree = ref([])
39 /** 血缘关系数据 */
40 const lineageData = ref([]);
41 /** 血缘关系页面加载 */
42 const lineageLoading = ref(false);
43 /** 当前切换字段血缘关系的开关 */
44 const switchFieldLineage = ref(false);
45 /** 字段血缘关系 */
46 const isFieldLineage = ref(false);
47 /** 添加上下游节点弹框表单规则配置。 */
48 const formRules = ref({
49 databaseGuid: [
50 {
51 required: true,
52 message: "选择元数据名称",
53 trigger: "change",
54 },
55 ],
56 tableName: [
57 {
58 required: true,
59 message: "请添加节点",
60 trigger: "change",
61 },
62 ],
63 tableField: [
64 {
65 required: true,
66 message: "请添加节点",
67 trigger: "change",
68 },
69 ]
70 });
71 const formItems = ref([{
72 label: '选择元数据名称',
73 type: 'tree-select',
74 placeholder: '请选择',
75 field: 'databaseGuid',
76 default: "",
77 props: {
78 label: "name",
79 children: "children",
80 value: 'guid',
81 isLeaf: 'isLeaf'
82 },
83 teleported: false,
84 filterable: true,
85 checkStrictly: false,
86 options: [],
87 required: true,
88 block: true
89 },
90 {
91 label: '添加节点',
92 type: 'select',
93 placeholder: '请选择',
94 field: 'tableName',
95 options: [],
96 clearable: true,
97 required: true,
98 block: true,
99 filterable: true,
100 visible: true,
101 readonly: true,
102 teleported: false,
103 },
104 {
105 label: '添加节点',
106 type: 'tree-select',
107 placeholder: '请选择',
108 field: 'tableField',
109 default: "",
110 props: {
111 label: "label",
112 children: "children",
113 value: 'value',
114 isLeaf: 'isLeaf'
115 },
116 teleported: false,
117 filterable: true,
118 checkStrictly: false,
119 options: [],
120 visible: false,
121 required: true,
122 block: true
123 },
124 ])
125 /** 添加上下游节点时记录的当前选择的添加节点信息,以及当前操作的节点信息。 */
126 const formLine: any = ref({})
127 const dialogInfo = ref({
128 visible: false,
129 size: 500,
130 direction: "column",
131 showClose: true,
132 modalClass: "createNode",
133 header: {
134 title: "添加上游节点",
135 },
136 type: '',
137 addType: 'table', //分为table和field两种节点。
138 contents: [
139 {
140 type: "form",
141 title: "",
142 formInfo: {
143 id: "timed-management-form",
144 items: formItems.value,
145 rules: formRules.value
146 },
147 }
148 ],
149 footer: {
150 visible: true,
151 btns: [
152 { type: "default", label: "取消", value: "cancel" },
153 { type: "primary", label: "确定", value: "submit" },
154 ],
155 },
156 });
157
158 const treeInfo = ref({
159 id: "data-pickup-tree",
160 filter: true,
161 queryValue: "",
162 queryPlaceholder: "输入库/表名称搜索",
163 props: {
164 label: "name",
165 value: "guid",
166 isLeaf: "isLeaf",
167 },
168 nodeKey: 'guid',
169 lazy: true,
170 expandedKey: [],
171 currentNodeKey: '',
172 expandOnNodeClick: false,
173 data: <any>[],
174 customFilter: true,
175 loading: false
176 });
177 const currtableGuid = ref("")
178
179 /** 记录原始获取的所有树形结构数据,用于处理搜索. */
180 const allTreeData: any = ref([]);
181
182 /** 获取左侧树数据. */
183 const getTreeData = async () => {
184 treeInfo.value.loading = true
185 let params = {}
186 const res: any = await getMetaTreeData(params)
187 if (res.code == proxy.$passCode) {
188 const data = res.data || [];
189 let treeData: any = Object.keys(data).length ? [data] : [];
190 treeInfo.value.data = treeData;
191 allTreeData.value = treeData;
192 if (treeData.length) {
193 treeInfo.value.currentNodeKey = treeData[0].guid;
194 treeInfo.value.expandedKey = <any>[treeData[0].guid];
195 }
196 } else {
197 ElMessage.error(res.msg);
198 }
199 treeInfo.value.loading = false
200 }
201 /** 左侧树的的组件引用. */
202 const treeInfoRef = ref();
203
204 /** 处理左侧树过滤方法.默认的树懒加载模式未展开过的不会过滤出结果,但是现在已经有了两级数据. */
205 const handleFilterTree = (query) => {
206 if (!allTreeData.value.length) {
207 return [];
208 }
209 if (!query) {
210 treeInfo.value.data = allTreeData.value;
211 nextTick(() => {
212 treeInfoRef.value.treeRef.store.setData(treeInfo.value.data);
213 });
214 return;
215 }
216 let expandKeys: any = [allTreeData.value[0].guid];
217 const filterChildren = (children) => {
218 let nodes: any = [];
219 children.forEach(child => {
220 if (!child.children?.length) {
221 if (child[treeInfo.value.props.label].includes(query)) {
222 nodes.push(child);
223 }
224 } else {
225 let filterNodes = filterChildren(child.children);
226 if (filterNodes.length) {
227 child.children = filterNodes;
228 nodes.push(child);
229 expandKeys.push(child.guid);
230 }
231 }
232 });
233 return nodes;
234 }
235 treeInfo.value.data = [Object.assign({}, treeInfo.value.data[0], {
236 children: filterChildren(cloneDeep(allTreeData.value[0].children))
237 })];
238 treeInfo.value.expandedKey = expandKeys;
239 nextTick(() => {
240 treeInfoRef.value.treeRef.store.setData(treeInfo.value.data);
241 });
242 }
243
244 /** 根据表获取元数据字段 */
245 const getMetaTableFieldPromises: any = ref({});
246
247 /** 处理树形懒加载,一级级展开. */
248 const loadTreeNode = (node, resolve) => {
249 if (node.isLeaf) {
250 return resolve([]);
251 }
252 if (node.level === 0) {
253 resolve(treeInfo.value.data)
254 } else if (node.level === 1) {
255 resolve(node.data.children)
256 } else if (node.level === 2) {
257 resolve(node.data.children)
258 } else if (node.level === 3) {
259 let params = {
260 tableGuid: node.data.guid
261 }
262 if (getMetaTableFieldPromises.value[node.data.guid]) {
263 getMetaTableFieldPromises.value[node.data.guid].then((res: any) => {
264 delete getMetaTableFieldPromises.value[node.data.guid];
265 if (res.code == proxy.$passCode) {
266 const data = res.data ?? [];
267 let isCurrentNodeField = false;
268 let currentNodeKey = treeInfo.value.currentNodeKey;
269 data.map(item => {
270 if (currentNodeKey == item.guid) {
271 isCurrentNodeField = true;
272 }
273 item.enName = item.enName;
274 item.name = item.chName || item.enName;
275 item.databaseName = node.data.databaseName;
276 item.databaseChName = node.data.databaseChName;
277 item.tableName = node.data.tableName;
278 item.type = 4
279 item.dataTypeCode = item.fieldType
280 item.isLeaf = true
281 item.businessDefDesc = item.comment,
282 item.tableGuid = node.data.guid;
283 item.databaseGuid = node.parent.data.guid;
284 })
285 node.data.children = data;
286 nextTick(() => {
287 treeInfo.value.currentNodeKey && treeInfoRef.value.setCurrentKey(treeInfo.value.currentNodeKey);
288 treeInfo.value.currentNodeKey && isCurrentNodeField && scrollToNode(treeInfo.value.currentNodeKey);
289 let node1 = treeInfoRef.value.treeRef.store.nodesMap[treeInfo.value.currentNodeKey];
290 if (node1) {
291 node1.loaded = true;
292 node1.loading = false;
293 }
294 });
295 resolve(data)
296 } else {
297 ElMessage({
298 type: 'info',
299 message: res.msg,
300 })
301 }
302 });
303 } else {
304 getMetaTableFieldPromises.value[node.data.guid] = getMetaTableField(params).then((res: any) => {
305 delete getMetaTableFieldPromises.value[node.data.guid];
306 if (res.code == proxy.$passCode) {
307 const data = res.data ?? []
308 let isCurrentNodeField = false;
309 let currentNodeKey = treeInfo.value.currentNodeKey;
310 data.map(item => {
311 if (currentNodeKey == item.guid) {
312 isCurrentNodeField = true;
313 }
314 item.enName = item.enName;
315 item.name = item.chName || item.enName;
316 item.databaseName = node.data.databaseName;
317 item.databaseChName = node.data.databaseChName;
318 item.tableName = node.data.tableName;
319 item.type = 4
320 item.dataTypeCode = item.fieldType
321 item.isLeaf = true
322 item.businessDefDesc = item.comment,
323 item.tableGuid = node.data.guid;
324 item.databaseGuid = node.parent.data.guid;
325 })
326 node.data.children = data;
327 nextTick(() => {
328 treeInfo.value.currentNodeKey && treeInfoRef.value.setCurrentKey(treeInfo.value.currentNodeKey);
329 treeInfo.value.currentNodeKey && isCurrentNodeField && scrollToNode(treeInfo.value.currentNodeKey);
330 let node1 = treeInfoRef.value.treeRef.store.nodesMap[treeInfo.value.currentNodeKey];
331 if (node1) {
332 node1.loaded = true;
333 node1.loading = false;
334 }
335 });
336 resolve(data)
337 } else {
338 ElMessage({
339 type: 'info',
340 message: res.msg,
341 })
342 }
343 })
344 }
345 }
346 }
347 /** 当前选中的树节点数据data */
348 const lastClickNode: any = ref({ type: 1 });
349
350 const keyValue = ref("")
351 /** 数据血缘关系图组件 */
352 const lineageGraph: any = ref();
353 const diaLogRef = ref()
354 /** 当前操作的血缘节点的相邻节点. */
355 const neighbors = ref<{ tableName: string, databaseName: string, fieldName?: string }[]>([])
356
357 /** 点击左侧树节点,更新对应的血缘关系图. */
358 const nodeClick = (data) => {
359 console.log(data);
360 const ele = <HTMLElement>document.querySelector(".g6-component-contextmenu")
361 if (ele) {
362 ele.style.display = "none"
363 }
364 nextTick(() => {
365 lineageGraph.value?.tooltip1.hide()
366 })
367 treeInfo.value.currentNodeKey = data.guid;
368 treeInfo.value.expandedKey = <any>[data.guid];
369 let lastClickType = lastClickNode.value.type;
370 lastClickNode.value = data;
371 if (data.type === 3) {//点击的是表,显示血缘关系。
372 lastClickType != 3 && (switchFieldLineage.value = false);
373 if (switchFieldLineage.value) {
374 isToggle.value = true;
375 getAllTableFieldLineageMap();
376 } else {
377 getTableLineageMap()
378 }
379 } else if (data.type === 4) {//点击的是字段。
380 lastClickType != 4 && (switchFieldLineage.value = true);
381 if (!switchFieldLineage.value) {
382 getTableLineageMap();
383 } else {
384 isToggle.value = true;
385 getTableFieldLineageMap();
386 }
387 }
388 }
389
390 const getMetaTree = (params) => {
391 getMetaList(params).then((res) => {
392 if (params.dataType === "METADATA_MODEL") {
393 metaTree.value = res.data.map((item => {
394 item.name = emunData[item.metadataModel]
395 item.guid = emunData[item.metadataModel]
396 item.label = emunData[item.metadataModel]
397 item.value = emunData[item.metadataModel]
398 return item
399 })) || []
400 dialogInfo.value.contents[0].formInfo.items[0].options = metaTree.value
401 } //查源模型
402 })
403
404 }
405
406 /** 选中树节点后自动滚动到可视范围内. */
407 const scrollToNode = (nodeId) => {
408 nextTick(() => {
409 const nodeElement = treeInfoRef.value.treeRef.$el.querySelector(`[data-key="${nodeId}"]`);
410 if (nodeElement) {
411 nodeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
412 }
413 });
414 }
415
416 /** 处理从详情处跳转而来的默认展示. */
417 const processRouter = () => {
418 const { guid, databaseName, tableName, databaseChName, databaseGuid, fieldGuid, fieldEnName, set } = useDataMetaStore()
419 let isFL = useDataMetaStore().isFieldLineage;
420 if (fieldGuid) {//查看字段血缘的
421 nextTick(() => {
422 treeInfo.value.expandedKey = <any>[databaseGuid, guid];
423 treeInfo.value.currentNodeKey = fieldGuid as string;
424 scrollToNode(fieldGuid);
425 treeInfoRef.value.setCurrentKey(fieldGuid);
426 })
427 lastClickNode.value = { guid: fieldGuid, tableGuid: guid, databaseGuid: databaseGuid, enName: fieldEnName, tableName, databaseName, type: 4, isLeaf: true, databaseChName };
428 getTableFieldLineageMap();
429 set()
430 } else if (guid) {
431 treeInfo.value.currentNodeKey = guid as string;
432 treeInfo.value.expandedKey = <any>[databaseGuid, guid];
433 lastClickNode.value = { guid: guid, tableName, databaseName, type: 3, databaseChName };
434 scrollToNode(guid);
435 isFieldLineage.value = isFL;
436 switchFieldLineage.value = isFL;
437 if (isFL) {
438 getAllTableFieldLineageMap();
439 } else {
440 getTableLineageMap()
441 }
442 set()
443 }
444 }
445
446 onActivated(() => {
447 if (!allTreeData.value?.length) {
448 return;
449 }
450 processRouter();
451 });
452
453 onBeforeMount(async () => {
454 await getTreeData()
455 getMetaTree({ dataType: 'METADATA_MODEL', metadataModel: '1' })
456 processRouter();
457 })
458
459 /** 处理血缘节点的右键菜单事件. */
460 const handleTableContextMenu = (tableGuid, tableName, database, btnType, vertexId, databaseChName, chTable, neighbor, fieldGuid, fieldEnName, fieldChName) => {
461 // 当删除边的时候,第一个参数为euid 第二个是标识符 true
462 if (tableName === true) {
463 ElMessageBox.confirm('此操作将永久删除, 是否继续?', "提示", {
464 confirmButtonText: "确定",
465 cancelButtonText: "取消",
466 type: 'warning',
467 appendTo: lineageGraph.value.containerRef
468 }).then(() => {
469 delLineAge(tableGuid).then((res: any) => {
470 if (res.code == proxy.$passCode) {
471 ElMessage({
472 type: "success",
473 message: "删除成功",
474 appendTo: lineageGraph.value.containerRef
475 });
476 if (lastClickNode.value.type == 4) {
477 if (isFieldLineage.value) {
478 getTableFieldLineageMap();
479 } else {
480 getTableLineageMap();
481 }
482 } else {
483 if (isFieldLineage.value) {
484 getAllTableFieldLineageMap();
485 } else {
486 getTableLineageMap();
487 }
488 }
489 } else {
490 ElMessage({
491 type: "error",
492 message: res.msg,
493 appendTo: lineageGraph.value.containerRef
494 });
495 }
496 })
497 }).catch(() => {
498 ElMessage({
499 type: 'info',
500 message: '已取消删除',
501 appendTo: lineageGraph.value.containerRef
502 });
503 });
504 return
505 }
506
507 const databaseGuid = allTreeData.value[0].children?.find(child => child.name == databaseChName)?.guid;
508
509 neighbor.push({ tableName: tableName, databaseName: database, fieldName: fieldEnName });
510 neighbors.value = neighbor;
511 currtableGuid.value = tableGuid
512 if (btnType == 1) {//查看元数据详情
513 lineageGraph.value.handleEdit({});//先退出全屏再跳转,否则回来查看时就没有了。
514 checkTableData(tableGuid).then((res: any) => {
515 if (res.code === proxy.$passCode) {
516 if (res.data) {
517 router.push({
518 path: '/data-meta/metadata-query/meta-sheet',
519 query: {
520 id: tableGuid,
521 name: chTable || tableName
522 }
523 })
524 } else {
525 ElMessage.warning("元数据详情不存在")
526 }
527 }
528 })
529
530 } else if (btnType == 2) {//查看血缘。
531 if (!isFieldLineage.value) {
532 treeInfo.value.currentNodeKey = tableGuid;
533 let node = treeInfoRef.value.treeRef.store.nodesMap[databaseGuid];
534 node?.expand();
535 let node1 = treeInfoRef.value.treeRef.store.nodesMap[tableGuid];
536 node1?.expand();
537 treeInfo.value.expandedKey = <any>['0', databaseGuid, tableGuid];
538 nodeClick({ guid: tableGuid, tableName: tableName, databaseName: database, type: 3 })
539 nextTick(() => {
540 scrollToNode(tableGuid);
541 })
542 } else {
543 treeInfo.value.currentNodeKey = fieldGuid;
544 let node = treeInfoRef.value.treeRef.store.nodesMap[databaseGuid];
545 node?.expand();
546 let node1 = treeInfoRef.value.treeRef.store.nodesMap[tableGuid];
547 node1?.expand();
548 treeInfo.value.expandedKey = <any>['0', databaseGuid, tableGuid, fieldGuid];
549 nextTick(() => {
550 scrollToNode(fieldGuid);
551 })
552 nodeClick({ guid: fieldGuid, tableName: tableName, databaseName: database, type: 4, tableGuid: tableGuid, isLeaf: true, enName: fieldEnName, chName: fieldChName })
553 }
554 } else if (btnType == 3) {
555 dialogInfo.value.header.title = "添加上游节点"
556 dialogInfo.value.visible = true;
557 dialogInfo.value.addType = isFieldLineage.value ? 'field' : 'table';
558 if (isFieldLineage.value) {
559 dialogInfo.value.contents[0].formInfo.items[1].visible = false;
560 dialogInfo.value.contents[0].formInfo.items[2].visible = true;
561 } else {
562 dialogInfo.value.contents[0].formInfo.items[1].visible = true;
563 dialogInfo.value.contents[0].formInfo.items[2].visible = false;
564 }
565 dialogInfo.value.contents[0].formInfo.items[1].options = [];
566 dialogInfo.value.contents[0].formInfo.items[2].options = [];
567 formLine.value = { upOrDown: "up", vertexId, database, databaseName: database, databaseGuid, tableGuid, databaseChName, chTable, tableName: tableName, currEnName: fieldEnName, currChName: fieldChName, currDsChName: databaseChName }
568 nextTick(() => {
569 const ele = document.querySelector(".createNode")
570 if (ele) {
571 lineageGraph.value.containerRef.appendChild(ele)
572
573 }
574 })
575 } else if (btnType == 4) {
576 dialogInfo.value.header.title = "添加下游节点"
577 dialogInfo.value.visible = true
578 dialogInfo.value.addType = isFieldLineage.value ? 'field' : 'table';
579 if (isFieldLineage.value) {
580 dialogInfo.value.contents[0].formInfo.items[1].visible = false;
581 dialogInfo.value.contents[0].formInfo.items[2].visible = true;
582 } else {
583 dialogInfo.value.contents[0].formInfo.items[1].visible = true;
584 dialogInfo.value.contents[0].formInfo.items[2].visible = false;
585 }
586 dialogInfo.value.contents[0].formInfo.items[1].options = [];
587 dialogInfo.value.contents[0].formInfo.items[2].options = [];
588 formLine.value = { upOrDown: "down", vertexId, database, databaseName: database, databaseGuid, tableGuid, databaseChName, chTable, tableName, currEnName: fieldEnName, currChName: fieldChName, currDsChName: databaseChName }
589 nextTick(() => {
590 const ele = document.querySelector(".createNode")
591 if (ele) {
592 lineageGraph.value.containerRef.appendChild(ele)
593 }
594 })
595 } else if (btnType == 5) {
596 ElMessageBox.confirm('此操作将永久删除, 是否继续?', "提示", {
597 confirmButtonText: "确定",
598 cancelButtonText: "取消",
599 type: 'warning',
600 appendTo: lineageGraph.value.containerRef
601 }).then(() => {
602 delLineageTable({ vertexId: vertexId }).then((res: any) => {
603 if (res.code == proxy.$passCode) {
604 ElMessage({
605 type: "success",
606 message: "删除成功",
607 appendTo: lineageGraph.value.containerRef
608 });
609 if (lastClickNode.value.type == 4) {
610 if (isFieldLineage.value) {
611 getTableFieldLineageMap();
612 } else {
613 getTableLineageMap();
614 }
615 } else {
616 if (isFieldLineage.value) {
617 getAllTableFieldLineageMap();
618 } else {
619 getTableLineageMap();
620 }
621 }
622 } else {
623 ElMessage({
624 type: "error",
625 message: res.msg,
626 appendTo: lineageGraph.value.containerRef
627 });
628 }
629 })
630 }).catch(() => {
631 ElMessage({
632 type: 'info',
633 message: '已取消删除',
634 appendTo: lineageGraph.value.containerRef
635 });
636 });
637 }
638 }
639
640 /** 添加上下游节点对话框的确定取消事件处理. */
641 const dialogBtnClick = (btn, info) => {
642 if (btn.value == 'submit') {
643 let params = {}
644 params = {
645 ...formLine.value, guid: currtableGuid.value
646 }
647 if (lastClickNode.value.type == 4) {
648 Object.assign(params, {
649 currentNode: {
650 enName: lastClickNode.value.enName,
651 chName: lastClickNode.value.chName,
652 tableName: lastClickNode.value.tableName,
653 tableGuid: lastClickNode.value.tableGuid,
654 databaseGuid: lastClickNode.value.databaseGuid,
655 databaseChName: formLine.value.databaseChName,
656 databaseName: lastClickNode.value.databaseName,
657 tableChName: formLine.value.chTable
658 }
659 });
660 } else {
661 Object.assign(params, {
662 currentNode: {
663 enName: formLine.value.currEnName,
664 chName: formLine.value.currChName,
665 tableName: lastClickNode.value.tableName,
666 tableGuid: lastClickNode.value.guid,
667 databaseGuid: lastClickNode.value.parentGuid,
668 databaseChName: formLine.value.currDsChName,
669 databaseName: lastClickNode.value.databaseName,
670 tableChName: formLine.value.chTable
671 }
672 });
673 }
674 saveLineageField(params).then().then((res: any) => {
675 if (res.code === proxy.$passCode) {
676 ElMessage({
677 type: "success",
678 message: "添加节点成功!",
679 appendTo: lineageGraph.value.containerRef
680 })
681 dialogInfo.value.visible = false;
682 if (lastClickNode.value.type == 4) {
683 if (isFieldLineage.value) {
684 getTableFieldLineageMap();
685 } else {
686 getTableLineageMap();
687 }
688 } else {
689 if (isFieldLineage.value) {
690 getAllTableFieldLineageMap();
691 } else {
692 getTableLineageMap();
693 }
694 }
695 } else {
696 ElMessage({
697 type: 'error',
698 message: res.msg,
699 appendTo: lineageGraph.value.containerRef
700 })
701 }
702 }).catch((res: any) => {
703 ElMessage({
704 type: 'error',
705 message: res.msg,
706 appendTo: lineageGraph.value.containerRef
707 })
708 });
709 } else if (btn.value == 'cancel') {
710 dialogInfo.value.visible = false;
711 }
712 };
713
714 const metaTableList = ref([]);
715
716 const nodeLoad = (node, resolve, item) => {
717 const data = node.data;
718 if (item.field == 'databaseGuid') {
719 if (node.level === 0) {
720 return resolve(metaTree.value)
721 } else if (node.level === 1) {
722 getMetaList({ dataType: "DATABASE", metadataModel: data.metadataModel }).then((res: any) => {
723 if (res.code == proxy.$passCode) {
724 const children = res.data.map((item) => {
725 item.isLeaf = true;
726 item.name = item.databaseNameZh
727 return item
728 }) || []
729 resolve(children)
730 } else {
731 ElMessage.error(res.msg);
732 resolve([]);
733 }
734 })
735 } else if (node.level === 2) {
736 resolve([])
737 }
738 } else {
739 if (node.level === 0) {
740 return resolve(metaTableList.value)
741 } else if (node.level === 1) {
742 getMetaTableField({ tableGuid: data.guid }).then((res: any) => {
743 if (res.code == proxy.$passCode) {
744 const dataArray = res.data ?? []
745 dataArray.map(item => {
746 item.label = item.chName ? (item.chName + "(" + item.enName + ")") : item.enName;
747 item.value = item.enName;
748 item.name = item.chName || item.enName;
749 item.databaseName = data.databaseName;
750 item.databaseChName = data.databaseChName;
751 item.tableName = data.tableName;
752 item.tableChName = data.tableChName;
753 item.tableGuid = data.guid;
754 item.type = 4
755 item.dataTypeCode = item.fieldType
756 item.isLeaf = true
757 item.businessDefDesc = item.comment
758 if (neighbors.value.some(n => n.databaseName === item.databaseName && n.tableName === item.tableName && n.fieldName == item.enName)) {
759 item.disabled = true;
760 }
761 })
762 resolve(dataArray);
763 } else {
764 ElMessage.error(res.msg);
765 }
766 });
767 }
768 }
769 }
770 const treeSelectNodeChange = (node, item) => {
771 if (item.field == 'tableField' && node.isLeaf) {
772 formLine.value.enName = node.enName;
773 formLine.value.chName = node.chName;
774 formLine.value.tableName = node.tableName;
775 formLine.value.tableChName = node.tableChName;
776 formLine.value.tableGuid = node.tableGuid;
777 return;
778 }
779 if (!node.databaseNameEn || !node.isLeaf) {
780 return
781 }
782 console.log(diaLogRef.value.dialogFormRef[0].formInline)
783 diaLogRef.value.dialogFormRef[0].formInline.tableName = "";
784 diaLogRef.value.dialogFormRef[0].formInline.tableField = '';
785 dialogInfo.value.contents[0].formInfo.items[1].options = [];
786 dialogInfo.value.contents[0].formInfo.items[2].options = [];
787 formLine.value.databaseName = node.databaseNameEn || ""
788 formLine.value.databaseChName = node.databaseNameZh || "";
789 formLine.value.databaseGuid = node.guid;
790 getMetaList({ dataType: "TABLE", dataSourceGuid: node.guid }).then((res: any) => {
791 if (res.code == proxy.$passCode) {
792 if (dialogInfo.value.addType != 'table') {
793 dialogInfo.value.contents[0].formInfo.items[2].options = metaTableList.value = res.data.map((item) => {
794 item.label = item.tableChName + "(" + item.tableName + ")"
795 item.value = item.tableName;
796 item.isLeaf = false;
797 let nbor = neighbors.value.at(-1);
798 if (nbor?.databaseName == node.databaseNameEn && nbor?.tableName == item.tableName) {
799 item.isLeaf = true;
800 item.disabled = true;
801 } else {
802 item.disabled = false;
803 }
804 return item
805 })
806 } else {
807 dialogInfo.value.contents[0].formInfo.items[1].options = res.data.map((item) => {
808 item.label = item.tableChName + "(" + item.tableName + ")"
809 item.value = item.tableName
810
811 neighbors.value.forEach((item1) => {
812 if (item1.databaseName === item.databaseName && item1.tableName === item.tableName) {
813 item.disabled = true
814 }
815 })
816 return item
817 })
818 }
819 } else {
820 ElMessage.error(res.msg);
821 }
822
823 })
824 }
825
826 const selectChange = (val, row, info) => {
827 formLine.value.databaseGuid = info.databaseGuid;
828 if (dialogInfo.value.addType == 'table') {
829 formLine.value.tableName = info.tableName
830 if (row.field === "tableName") {
831 const option = row.options.find((item) => item.tableName === val)
832 formLine.value.tableChName = option.tableChName
833 }
834 } else {
835 // formLine.value.enName = info.tableName
836 }
837 }
838 const file: any = ref({})
839 const handleSave = (file1, fullRef) => {
840 file.value = file1
841 dialogInfo1.value.visible = true
842 nextTick(() => {
843 const ele = document.querySelector(".saveDialog")
844 if (ele) {
845 fullRef.appendChild(ele)
846 }
847 })
848 }
849 const pageSave = () => {
850 const analysisReportName = reportDialogRef.value.dialogFormRef[0].formInline.analysisReportName;
851 if (!analysisReportName) {
852 ElMessage({
853 type: "error",
854 message: "血缘关系名称不能为空!",
855 appendTo: lineageGraph.value.containerRef
856 })
857 return
858 }
859
860 let formData = new FormData();
861 formData.append('file', file.value);
862 formData.append('fileName', `${analysisReportName}.png`);
863 getFileUrl(formData).then((res) => {
864 saveMetaReportAnalysis({
865 table: lastClickNode.value.tableName,
866 database: lastClickNode.value.databaseName,
867 analysisReportUrl: res.data,
868 analysisReportName: analysisReportName,
869 databaseChName: lastClickNode.value.databaseChName
870 }).then((res: any) => {
871 if (res.code == proxy.$passCode) {
872 ElMessage({
873 type: "success",
874 message: "保存成功",
875 appendTo: lineageGraph.value.containerRef
876 })
877 dialogInfo1.value.visible = false
878 } else {
879 ElMessage({
880 type: "error",
881 message: res.msg,
882 appendTo: lineageGraph.value.containerRef
883 })
884 }
885 })
886 }).catch((res) => {
887 ElMessage.error(res.msg)
888 })
889 }
890
891 const formItems1: any = ref([
892 {
893 label: '血缘关系名称',
894 type: 'input',
895 placeholder: '请输入',
896 field: 'analysisReportName',
897 default: '',
898 clearable: true,
899 required: true,
900 },
901 ])
902 const formRules1: any = ref({
903 analysisReportName: [
904 {
905 required: true,
906 message: "请填写血缘关系名称",
907 trigger: "blur",
908 },
909 ],
910 })
911 const formInfo1 = ref({
912 type: 'form',
913 title: '',
914 formInfo: {
915 id: 'add-dict-form1',
916 items: formItems1.value,
917 rules: formRules1.value
918 }
919 })
920 const reportDialogRef = ref();
921 const dialogInfo1 = ref({
922 visible: false,
923 size: 500,
924 direction: "column",
925 modalClass: "saveDialog",
926 header: {
927 title: "保存为血缘关系图片",
928 },
929 type: '',
930 contents: [
931 formInfo1.value,
932 ],
933 footer: {
934 btns: [
935 { type: "default", label: "取消", value: "cancel" },
936 { type: "primary", label: "保存", value: "submit" },
937 ],
938 },
939 });
940
941 const dialogBtnClick1 = (btn, scope) => {
942 if (btn.value === "cancel") {
943 dialogInfo1.value.visible = false
944 } else {
945 pageSave()
946 }
947 }
948 /** 是否显示英文名称,默认不显示。 */
949 const isCh = ref(false);
950
951 /** 是否切换为纵向布局。 */
952 const isToggle = ref(true)
953
954 /** 获取对应表的血缘关系。 */
955 const getTableLineageMap = () => {
956 lineageLoading.value = true;
957 isFieldLineage.value = false;
958 getTableLineage({ guid: lastClickNode.value.isLeaf ? lastClickNode.value.tableGuid : lastClickNode.value.guid }).then((res: any) => {
959 if (res.code == proxy.$passCode) {
960 let data1 = res.data || [];
961 lineageData.value = data1;
962 nextTick(() => {
963 if (lineageData.value.length > 0) {
964 lineageGraph.value.updateLayout();
965 lineageLoading.value = false;
966 } else {
967 lineageLoading.value = false;
968 }
969
970 });
971 } else {
972 lineageLoading.value = false;
973 ElMessage({
974 type: 'error',
975 message: res.msg,
976 appendTo: lineageGraph.value.containerRef
977 })
978 }
979 })
980 }
981
982 /** 获取指定表字段的血缘关系 */
983 const getTableFieldLineageMap = () => {
984 lineageLoading.value = true;
985 isFieldLineage.value = true;
986 getTableFieldLineage({ guid: lastClickNode.value.guid }).then((res: any) => {
987 if (res.code == proxy.$passCode) {
988 let data1 = res.data || [];
989 lineageData.value = data1;
990 nextTick(() => {
991 if (lineageData.value.length > 0) {
992 lineageGraph.value.updateLayout();
993 lineageLoading.value = false;
994 } else {
995 lineageLoading.value = false;
996 }
997 });
998 } else {
999 lineageLoading.value = false;
1000 ElMessage({
1001 type: 'error',
1002 message: res.msg,
1003 appendTo: lineageGraph.value.containerRef
1004 })
1005 }
1006 })
1007 }
1008
1009 /** 获取指定表的所有字段血缘关系。 */
1010 const getAllTableFieldLineageMap = () => {
1011 lineageLoading.value = true;
1012 isFieldLineage.value = true;
1013 getTableAllFieldLineage({ databaseName: lastClickNode.value.databaseName, tableName: lastClickNode.value.tableName }).then((res: any) => {
1014 if (res.code == proxy.$passCode) {
1015 let data1 = res.data || [];
1016 lineageData.value = data1;
1017 isFieldLineage.value = true;
1018 nextTick(() => {
1019 if (lineageData.value.length > 0) {
1020 lineageGraph.value.updateLayout();
1021 lineageLoading.value = false;
1022 } else {
1023 lineageLoading.value = false;
1024 }
1025 });
1026 } else {
1027 lineageLoading.value = false;
1028 ElMessage({
1029 type: 'error',
1030 message: res.msg,
1031 })
1032 }
1033 })
1034 }
1035
1036 /** 处理刷新按钮。 */
1037 const handleRefres = () => {
1038 if (lastClickNode.value.type === 3) {//点击的是表,显示血缘关系。
1039 if (switchFieldLineage.value) {
1040 getAllTableFieldLineageMap();
1041 } else {
1042 getTableLineageMap()
1043 }
1044 } else if (lastClickNode.value.type === 4) {//点击的是字段。
1045 getTableFieldLineageMap();
1046 }
1047 }
1048 /** 处理中英文切换。 */
1049 const handleChOrEn = (flag) => {
1050 isCh.value = flag
1051 }
1052
1053 /** 处理上下布局切换。 */
1054 const handleToggle = () => {
1055 isToggle.value = !isToggle.value
1056 }
1057
1058 /** 处理字段血缘开关开启关闭。 */
1059 const handleLineageSwitchChange = (val, type) => {
1060 switchFieldLineage.value = val;
1061 if (val) {
1062 isToggle.value = true;
1063 getAllTableFieldLineageMap();
1064 } else {
1065 getTableLineageMap();
1066 }
1067 }
1068 </script>
1069
1070 <template>
1071 <div class="container_wrap full flex">
1072 <div class="aside_wrap">
1073 <div class="aside_title">数据库目录列表</div>
1074 <Tree ref="treeInfoRef" :treeInfo="treeInfo" @nodeClick="nodeClick" :key="keyValue" @loadNode="loadTreeNode"
1075 @filterTree="handleFilterTree" />
1076 </div>
1077 <div class="main_wrap">
1078 <LineageGraph v-show="lastClickNode?.type == 3 || lastClickNode?.type == 4" ref="lineageGraph" layout="vertical"
1079 :lineageData="lineageData" :isFieldLineage="isFieldLineage" v-if="lineageData.length > 0"
1080 v-loading="lineageLoading" :primary-table="lastClickNode?.tableName"
1081 :primary-field="lastClickNode?.type == 4 ? lastClickNode?.enName : ''" :isViewTable="lastClickNode?.type == 3"
1082 :is-detail="false" :primaryDatabase="lastClickNode?.databaseName" @tableContextMenu="handleTableContextMenu"
1083 @handleSave="handleSave" @handleRefres="handleRefres" @handleLineageSwitchChange="handleLineageSwitchChange"
1084 @handleChOrEn=handleChOrEn :isCh="isCh" @handleToggle="handleToggle" :isToggle="isToggle" />
1085 <div v-show="lastClickNode && lastClickNode?.type !== 3 && lastClickNode?.type !== 4" class="main-placeholder">
1086 <img width="210" height="100" src="../../../public/swzl_logo.png">
1087 </div>
1088 <Dialog :dialogInfo="dialogInfo" ref="diaLogRef" class="timedDia" @btnClick="dialogBtnClick"
1089 @selectChange="selectChange" @treeSelectLoad="nodeLoad" @treeSelectNodeChange="treeSelectNodeChange" />
1090 </div>
1091 <Dialog ref="reportDialogRef" class="createNode" :dialogInfo="dialogInfo1" @btnClick="dialogBtnClick1" />
1092 </div>
1093 </template>
1094
1095 <style scoped lang="scss">
1096 .container_wrap {
1097
1098 .aside_wrap {
1099 width: 200px;
1100 margin-right: 1px;
1101 }
1102
1103 .main_wrap {
1104 :deep(.canvas-wrapper) {
1105 background-color: #f7f7f9;
1106 }
1107
1108 .main-placeholder {
1109 height: 100%;
1110 display: flex;
1111 justify-content: center;
1112 align-items: center;
1113 }
1114 }
1115
1116 }
1117
1118 .container_wrap.flex .main_wrap {
1119 padding: 0px;
1120 }
1121
1122 .tree_panel {
1123 height: calc(100% - 36px);
1124 padding-top: 0;
1125
1126 :deep(.el-tree) {
1127 margin: 0;
1128 overflow: hidden auto;
1129 }
1130 }
1131
1132 .card-noData {
1133 height: 100%;
1134 width: 100%;
1135 background: #fafafa;
1136 display: flex;
1137 flex-direction: column;
1138 justify-content: center;
1139 align-items: center;
1140 color: #909399;
1141 font-size: 14px;
1142 }
1143
1144 :deep(.el-form .el-form-item) {
1145 width: calc(100%);
1146 // margin-right: 8px;
1147 }
1148
1149 :deep(.el-message) {
1150 position: fixed;
1151 /* 使用fixed或absolute定位 */
1152 z-index: 10000;
1153 /* 设置一个较高的z-index值确保在最上层显示 */
1154 }
1155 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: changeDetection
3 </route>
4
5 <script lang="ts" setup name="changeDetection">
6
7 import { ref } from 'vue';
8 import { ElMessage } from "element-plus";
9 import Tree from '@/components/Tree/index.vue';
10 import {
11 getMetaTreeData,
12 getTaskChangeList,
13 getMetaChangeList,
14 getMetaChangeRecord,
15 getMetacompareList,
16 checkTableData,
17 syncChangeDetail
18 } from '@/api/modules/dataMetaService';
19 import TableTools from '@/components/Tools/table_tools.vue';
20 import { useRouter, useRoute } from "vue-router";
21
22 const { proxy } = getCurrentInstance() as any;
23
24 const router = useRouter();
25
26 const treeInfo:any = ref({
27 id: "data-pickup-tree",
28 filter: true,
29 queryValue: "",
30 queryPlaceholder: "输入库/表名称搜索",
31 props: {
32 label: "name",
33 value: "guid",
34 },
35 nodeKey: 'guid',
36 expandedKey: [],
37 currentNodeKey: '',
38 expandOnNodeClick: false,
39 data: [],
40 loading: false
41 });
42 const treeData = ref([])
43 const getTreeData = () => {
44 treeInfo.value.loading = true
45 let params = {}
46 getMetaTreeData(params).then((res: any) => {
47 if (res.code == proxy.$passCode) {
48 // let treeData: any = res.data?.children || [];
49 const data = res.data || {}
50 let treeData: any = Object.keys(data).length ? [data] : [];
51 treeInfo.value.data = treeData;
52 if (treeData.length) {
53 tableSearchValue.value.taskGuid = treeData[0].guid
54 treeInfo.value.currentNodeKey = treeData[0].guid;
55 treeInfo.value.expandedKey = treeInfo.value.expandedKey.length == 0 ? [treeData[0].guid] : treeInfo.value.expandedKey
56 }
57 } else {
58 ElMessage.error(res.msg);
59 }
60 treeInfo.value.loading = false
61 }).catch(() => {
62 treeInfo.value.loading = false
63 })
64 }
65
66 const nodeClick = (data) => {
67 metaTableValue.value.dataSourceGuid = ""
68 tableSearchValue.value.dataSourceName = ""
69 tableSearchValue.value.tableGuid = ""
70 metaTableValue.value.tableGuid = ""
71 if(data.type === 1){
72 if(activeTabName.value==="task"){
73 getTaskChangeTableData()
74 } else {
75 getMetaChangeTableData()
76 }
77 } else if (data.type === 3) {//点击的是表,显示血缘关系。
78 metaTableValue.value.dataSourceGuid = ""
79 tableSearchValue.value.dataSourceName = ""
80 tableSearchValue.value.tableGuid = data.guid
81 metaTableValue.value.tableGuid = data.guid
82 if(activeTabName.value==="task"){
83
84 getTaskChangeTableData()
85 } else {
86 getMetaChangeTableData()
87 }
88
89 } else {//点击的不是表。
90 tableSearchValue.value.tableGuid = ""
91 metaTableValue.value.tableGuid = ""
92 metaTableValue.value.dataSourceGuid = data.guid
93 tableSearchValue.value.dataSourceName = data.name
94 if(activeTabName.value==="task"){
95
96 getTaskChangeTableData()
97 } else {
98 getMetaChangeTableData()
99 }
100 }
101 }
102
103 const getTaskChangeTableData = () => {
104 taskChangeTableInfo.value.loading = true;
105 getTaskChangeList(Object.assign(tableSearchValue.value,{pageIndex:taskChangePage.value.curr,pageSize:taskChangePage.value.limit})).then((res: any) => {
106 taskChangeTableInfo.value.loading = false;
107 if (res?.code == proxy.$passCode) {
108 const data = res.data || {};
109 taskChangeTableInfo.value.data = data.records || [];
110 taskChangeTableInfo.value.page.curr = data.pageIndex;
111 taskChangeTableInfo.value.page.rows = data.totalRows;
112 taskChangeTableInfo.value.page.limit = data.pageSize;
113 taskChangePage.value.curr = data.pageIndex;
114 taskChangePage.value.limit = data.pageSize;
115 } else {
116 ElMessage({
117 type: 'error',
118 message: res.msg,
119 })
120 taskChangeTableInfo.value.loading = false
121 }
122 });
123 }
124
125 const getMetaChangeTableData = () => {
126 metaChangeTableInfo.value.loading = true;
127 getMetaChangeRecord(Object.assign(metaTableValue.value,{pageIndex:metaChangePage.value.curr,pageSize:metaChangePage.value.limit,metaType:["TABLE","FIELD"]})).then((res: any) => {
128 metaChangeTableInfo.value.loading = false;
129 if (res.code == proxy.$passCode) {
130 const data = res.data || {};
131 metaChangeTableInfo.value.data = data.records || [];
132 metaChangeTableInfo.value.page.curr = data.pageIndex;
133 metaChangeTableInfo.value.page.rows = data.totalRows;
134 taskChangePage.value.curr = data.pageIndex
135 taskChangePage.value.limit = data.pageSize
136 } else {
137 ElMessage({
138 type: 'error',
139 message: res.msg,
140 })
141 metaChangeTableInfo.value.loading = false;
142 }
143 });
144 }
145
146
147 const activeTabName = ref('task');
148
149 watch(() => activeTabName.value, (val) => {
150 if(val==="task"){
151 getTaskChangeTableData()
152 } else {
153 getMetaChangeTableData()
154 }
155 })
156
157 onMounted(()=>{
158 getTaskChangeTableData()
159 })
160
161 const tableSearchValue:any = ref({})
162 const tableSearchItemList: any = ref(
163 [
164 {
165 type: 'input',
166 label: '',
167 field: 'taskName',
168 default: '',
169 placeholder: '同步任务名称',
170 maxlength: 50,
171 clearable: true
172 }, {
173 type: 'select',
174 label: '',
175 field: 'changeType',
176 default: '',
177 placeholder: '变更类型',
178 options: [
179 { label: '新增', value: 'I' },
180 { label: '删除', value: 'D' },
181 { label: '修改', value: 'U' },
182 { label: '上线', value: 'Y' },
183 { label: '下线', value: 'S' }
184 ],
185 clearable: true
186 },
187 ]);
188
189 const toTableSearch = (val, clear) => {
190 if (clear) {
191 tableSearchItemList.value.map(item => item.default = '')
192
193 }
194 tableSearchValue.value = Object.keys(val).length ? { ...val,dataSourceName:tableSearchValue.value.dataSourceName,tableGuid:tableSearchValue.value.tableGuid} : {}
195 getTaskChangeTableData();
196 }
197
198 const taskChangePage: any = ref({
199 limit: 50,
200 curr: 1,
201 sizes: [
202 { label: "10", value: 10 },
203 { label: "50", value: 50 },
204 { label: "100", value: 100 },
205 { label: "150", value: 150 },
206 { label: "200", value: 200 },
207 ],
208 taskName: '',
209 changeState: null,
210 changeType: ''
211 });
212
213 const taskChangeTableInfo = ref({
214 id: 'task-change-table',
215 rowKey: 'guid',
216 loading: false,
217 fields: [
218 { label: "序号", type: "index", width: 56, align: "center" },
219 { label: "同步任务名称", field: "taskName", width: 150, align: "left" },
220 { label: "目标端数据源", field: "targetDataSource", width: 160, align: "left" },
221 { label: "目标端表英文名称", field: "targetTable", width: 150, align: "left",type: 'text_btn', value: 'targetTable',columClass:"text_btn" },
222 { label: "目标端表中文名称", field: "targetTableZhName", width: 150, align: "left",type: 'text_btn', value: 'targetTableZhName',columClass:"text_btn" },
223 { label: "源端数据源", field: "sourceDataSource", width: 160, align: "left" },
224 { label: "源端表英文名称", field: "sourceTable", width: 150, align: "left" },
225 { label: "源端表中文名称", field: "sourceTableZhName", width: 150, align: "left" },
226 { label: "变更类型", field: "changeType", type :'popover',value:"changeType",checkName:(scope)=>{
227 return scope.row['changeType']==="U"
228 },width: 96, menus:{I:"新增",U:"修改",D:"删除", Y:'上线', S: '下线'},getName:(scope)=>{
229 const menus = {I:"新增",U:"修改",D:"删除", Y:'上线', S: '下线'}
230 return menus[scope.row['changeType']]
231 },column:[{field:"changeInfo",label:"变化信息",width:"150"},{field:"oldValue",label:"原值",width:"150"},{field:"newValue",label:"现值",width:"150"}] },
232 { label: "任务变更时间", field: "changeTime", width: 180 },
233 ],
234 data: [{
235 guid: 1
236 }],
237 popoverTitle:"",
238 popoverloading:false,
239 popoverData:[],
240 arraySpanMethod:({row,column,rowIndex,columnIndex})=>{
241
242 // if (rowIndex === 0 && columnIndex === 1) {
243
244 // return {
245 // rowspan: 1,
246 // colspan: 2
247 // };
248 // }
249 if (rowIndex === 0) {
250 if (columnIndex === 1) {
251 return [1,2]
252 } else if (columnIndex === 2) {
253 return [0, 0]
254 }
255 }
256 },
257 page: {
258 type: "normal",
259 rows: 0,
260 ...taskChangePage.value,
261 },
262 actionInfo: {
263 show: false,
264 label: "操作",
265 type: "btn",
266 width: 100,
267 btns: (scope) => {
268 let row = scope.row;
269 return [{ label: '影响分析', value: 'analysis', disabled: row['changeState'] == 1 }];
270 }
271 }
272 });
273
274 const taskChangeTablePageChange = (info) => {
275 taskChangePage.value.curr = Number(info.curr);
276 taskChangePage.value.limit = Number(info.limit);
277 taskChangeTableInfo.value.page.limit = taskChangePage.value.limit;
278 taskChangeTableInfo.value.page.curr = taskChangePage.value.curr;
279 getTaskChangeTableData();
280 };
281
282 const currTaskChangeTableData: any = ref<Object>({});
283 const taskChangeTableBtnClick = (scope, btn) => {
284 const type = btn.value;
285 const row = scope.row;
286 currTaskChangeTableData.value = row;
287 if (type === 'analysis') { // 详情
288 router.push({
289 name: 'impactAnalysis',
290 query: { guid: row.approvalGuid }
291 });
292 } else if(type ==='targetTable' ){
293 checkTableData(row.targetTableGuid).then((res:any)=>{
294 if(res.code===proxy.$passCode) {
295 if(res.data){
296 router.push({
297 path: '/data-meta/metadata-query/meta-sheet',
298 query: {
299 id: row.targetTableGuid,
300 name: row.targetTableZhName || row.targetTable
301 }
302 })
303 } else {
304 ElMessage.warning("元数据详情不存在")
305 }
306 }
307 })
308
309 } else if(type === "targetTableZhName"){
310 checkTableData(row.targetTableGuid).then((res:any)=>{
311 if(res.code===proxy.$passCode) {
312 if(res.data){
313 router.push({
314 path: '/data-meta/metadata-query/meta-sheet',
315 query: {
316 id: row.targetTableGuid,
317 name: row.targetTableZhName || row.targetTable
318 }
319 })
320 } else {
321 ElMessage.warning("元数据详情不存在")
322 }
323 }
324 })
325 } else if(type==="changeType"){
326 taskChangeTableInfo.value.popoverloading = true
327 syncChangeDetail(row.guid).then((res:any)=>{
328 taskChangeTableInfo.value.popoverloading = false
329 if(res.code===proxy.$passCode){
330 taskChangeTableInfo.value.popoverData = res.data
331 taskChangeTableInfo.value.popoverTitle = row.targetTableZhName
332 }
333
334 })
335 }
336 };
337
338 const metaTableValue:any = ref({})
339
340 const metaTableSearchItemList: any = ref(
341 [
342 {
343 type: 'input',
344 label: '',
345 field: 'collectTaskName',
346 default: '',
347 placeholder: '元数据采集任务',
348 maxlength: 50,
349 clearable: true
350 }, {
351 type: 'select',
352 label: '',
353 field: 'metaChangeType',
354 default: '',
355 placeholder: '变更类型',
356 options: [
357 { label: '新增', value: 'ADD' },
358 { label: '删除', value: 'DELETE' },
359 { label: '修改', value: 'UPDATE' }
360 ],
361 clearable: true
362 },
363 ]);
364
365 const toMetaTableSearch = (val, clear) => {
366 if (clear) {
367 metaTableSearchItemList.value.map(item => item.default = '')
368
369 }
370 metaTableValue.value = Object.keys(val).length ? { ...val,dataSourceGuid:metaTableValue.value.dataSourceGuid,tableGuid:metaTableValue.value.tableGuid} : {}
371 getMetaChangeTableData()
372 }
373
374 const metaChangePage: any = ref({
375 limit: 50,
376 curr: 1,
377 sizes: [
378 { label: "10", value: 10 },
379 { label: "50", value: 50 },
380 { label: "100", value: 100 },
381 { label: "150", value: 150 },
382 { label: "200", value: 200 },
383 ],
384 metaTaskName: '',
385 changeState: null,
386 changeType: ''
387 });
388
389 const metaChangeTableInfo = ref({
390 id: 'meta-change-table',
391 rowKey: 'guid',
392 loading: false,
393 fields: [
394 { label: "序号", type: "index", width: 56, align: "center", fixed: "left" },
395 { label: "元数据采集任务", field: "collectTaskName", width: 180, },
396 { label: "数据源名称", field: "dataSourceName", width: 180, },
397 { label: "表中文名称", field: "tableChName", width: 180, },
398 { label: "表英文名称", field: "tableName", width: 180, },
399 { label: "元数据名称", field: "metaName", width: 180, },
400 {
401 label: "元数据类型", field: "metaType", width: 100, getName: (scope) => {
402 if (!scope.row.metaType) {
403 return '--';
404 }
405 return scope.row.metaType == 'TABLE' ? '表' : (scope.row.metaType == 'INDEX' ? '索引' : (scope.row.metaType == 'PRI' ? '主键' : '字段'));
406 }
407 },
408 {
409 label: "变更类型", field: "metaChangeType", width: 100, getName: (scope) => {
410 if (!scope.row.metaChangeType) {
411 return '--';
412 }
413 return scope.row.metaChangeType == 'ADD' ? '新增' : (scope.row.metaChangeType == 'DELETE' ? '删除' : '修改');
414 }
415 },
416 { label: "变化", field: "metaChangeType", width: 120,type: 'popover',value:'metaChangeType',column:[{field:"item",label:"变化信息",width:"150"},{field:"oldValue",label:"原值",width:"150"},{field:"newValue",label:"现值",width:"150"}] },
417 // { label: "原值", field: "metaOldValue", width: 120 },
418 { label: "变更时间", field: "changeTime", width: 180, },
419 { label: "元数据采集时间", field: "changeTime", width: 180, },
420 // { label: "状态", field: "changeTime", width: 180, },
421 // { label: "操作时间", field: "changeTime", width: 180, },
422 ],
423 data: [{
424 guid: 1
425 }],
426 page: {
427 type: "normal",
428 rows: 0,
429 ...metaChangePage.value,
430 },
431 popoverloading:false,
432 popoverData:[],
433 actionInfo: {
434 show:false,
435 label: "操作",
436 type: "btn",
437 width: 100,
438 btns: (scope) => {
439 let row = scope.row;
440 return [{ label: '影响分析', value: 'analysis', disabled: row['changeState'] == 1 }];
441 }
442 }
443 });
444
445 const metaChangeTablePageChange = (info) => {
446
447 metaChangePage.value.curr = Number(info.curr);
448 metaChangePage.value.limit = Number(info.limit);
449 metaChangeTableInfo.value.page.limit = metaChangePage.value.limit;
450 metaChangeTableInfo.value.page.curr = metaChangePage.value.curr;
451 getMetaChangeTableData();
452 };
453
454 const currMetaChangeTableData: any = ref<Object>({});
455 const metaChangeTableBtnClick = (scope, btn) => {
456 const type = btn.value;
457 const row = scope.row;
458 currMetaChangeTableData.value = row;
459 if (type === 'analysis') { // 详情
460 router.push({
461 name: 'impactAnalysis',
462 query: { guid: row.approvalGuid }
463 });
464 } else if(type==="metaChangeType") {
465 metaChangeTableInfo.value.popoverloading = true
466 getMetacompareList(scope.row.guid).then((res:any)=>{
467 metaChangeTableInfo.value.popoverloading = false
468 metaChangeTableInfo.value.popoverData = res.data
469
470 })
471 console.log(scope,btn)
472 }
473 };
474
475 onBeforeMount(() => {
476 getTreeData()
477 })
478
479 </script>
480
481 <template>
482 <div class="container_wrap full flex">
483 <div class="aside_wrap">
484 <div class="aside_title">数据库目录列表</div>
485 <Tree :treeInfo="treeInfo" @nodeClick="nodeClick" />
486 </div>
487 <div class="main_wrap">
488 <el-tabs v-model="activeTabName">
489 <el-tab-pane label="同步任务变更记录" name="task">
490 <div class="table_tool_wrap">
491 <TableTools :searchItems="tableSearchItemList" :init="false" searchId="detect-table-search"
492 @search="toTableSearch" />
493 </div>
494 <div class="table_panel_wrap">
495 <Table :tableInfo="taskChangeTableInfo" @tableBtnClick="taskChangeTableBtnClick"
496 @tablePageChange="taskChangeTablePageChange" />
497 </div>
498 </el-tab-pane>
499 <el-tab-pane label="元数据变更记录" name="meta">
500 <div class="table_tool_wrap">
501 <TableTools :searchItems="metaTableSearchItemList" :init="false" searchId="meta-detect-table-search"
502 @search="toMetaTableSearch" />
503 </div>
504 <div class="table_panel_wrap">
505 <Table :tableInfo="metaChangeTableInfo" @tableBtnClick="metaChangeTableBtnClick"
506 @tablePageChange="metaChangeTablePageChange" />
507 </div>
508 </el-tab-pane>
509 </el-tabs>
510 </div>
511 </div>
512 </template>
513
514 <style scoped lang="scss">
515 .container_wrap {
516
517 .aside_wrap {
518 width: 200px;
519 }
520
521 .main_wrap {
522 padding: 0px;
523
524
525 :deep(.el-tabs) {
526 height: 100%;
527
528 .el-tabs__header {
529 margin-bottom: 0;
530 }
531
532 .el-tabs__item {
533 height: 32px;
534 padding: 0px;
535 width: 144px;
536
537 &:last-child {
538 width: 130px;
539 }
540 }
541
542 .el-tabs__content {
543 height: calc(100% - 32px);
544 }
545
546 .el-tab-pane {
547 padding: 0px 16px;
548 height: 100%;
549 }
550 }
551 }
552
553 }
554
555 .tree_panel {
556 height: calc(100% - 36px);
557 padding-top: 0;
558
559 :deep(.el-tree) {
560 margin: 0;
561 overflow: hidden auto;
562 }
563 }
564
565 .table_tool_wrap {
566 display: flex;
567 flex-direction: column;
568
569 .el-input {
570 width: 230px;
571 height: 32px;
572 }
573
574 :deep(.el-input) {
575 .el-input__suffix-inner {
576 flex-direction: row-reverse;
577 -webkit-flex-direction: row-reverse;
578 display: flex;
579 }
580 }
581 }
582
583 .table_panel_wrap {
584 height: calc(100% - 44px);
585 }
586 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: collectorTask
3 </route>
4
5 <script lang="ts" setup name="collectorTask">
6 import { ref } from 'vue'
7 import TableTools from "@/components/Tools/table_tools.vue";
8 import {
9 addMetaDataTask,
10 deleteMetaDataTask,
11 getMetaDataTask,
12 updateMetaDataTask,
13 updateMetaDataState,
14 checkMetaDataTask,
15 executeMetaDataTask,
16 getDatabase,
17 checkDatabaseIsExist
18 } from '@/api/modules/dataMetaService';
19 import {
20 getCronExecTime
21 } from '@/api/modules/queryService';
22 import { TableColumnWidth } from '@/utils/enum';
23 import { commonPageConfig } from '@/components/PageNav/index';
24 import { useValidator } from '@/hooks/useValidator';
25
26 const { required, checkExistName } = useValidator();
27 const { proxy } = getCurrentInstance() as any;
28
29 const router = useRouter();
30
31 /** 数据源选择列表数据。 */
32 const dataSourceList = ref([])
33
34 /** 头部搜索框配置 */
35 const searchItemList = ref([
36 {
37 type: "input",
38 label: "",
39 field: "collectTaskName",
40 default: "",
41 placeholder: "采集任务名称",
42 clearable: true,
43 },
44 {
45 type: "select",
46 label: "",
47 placeholder: '数据源名称',
48 field: 'dataSourceGuid',
49 default: '',
50 options: dataSourceList.value,
51 props: {
52 label: 'databaseNameZh',
53 value: 'guid'
54 },
55 clearable: true,
56 },
57 {
58 type: "select",
59 label: "",
60 field: "taskState",
61 default: "",
62 placeholder: "任务状态",
63 options: [
64 { label: "上线", value: "1" },
65 { label: "下线", value: "0" },
66 ],
67 clearable: true,
68 }
69 ]);
70
71 const currTableData: any = ref<Object>({});
72 /** 分页及搜索传参信息配置。 */
73 const page = ref({
74 ...commonPageConfig,
75 collectTaskName: '',
76 dataSourceGuid: '',
77 taskState: null
78 });
79
80 const tableInfo = ref({
81 id: 'data-source-table',
82 // multiple:true,
83 fields: [
84 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
85 { label: "采集任务名称", field: "collectTaskName", width: 140 },
86 { label: "元模型", field: "metadataModelChName", width: 140 },
87 { label: "数据源名称", field: "datasourceName", width: 160,type: 'text_btn', value: 'datasourceName',columClass:"text_btn" },
88 { label: '任务状态', field: 'taskState', type: 'switch', activeText: '上线', inactiveText: '下线', activeValue: 1, inactiveValue: 0, switchWidth: 56, width: 100, align: 'center' },
89 { label: "执行计划", field: "collectType", type: 'filter', width: 90 },
90 { label: "同步策略", field: "collectMode", type: 'filter', width: 90 },
91 { label: "执行周期", field: "execCycle", width: TableColumnWidth.EXECCYCLE },
92 { label: "执行状态", field: "runingState", width: TableColumnWidth.STATE, align: 'center', type: "tag" },
93 { label: "下次执行时间", field: "nextExecuteTime", width: TableColumnWidth.DATETIME },
94 { label: "最后执行时间", field: "lastRuningTime", width: TableColumnWidth.DATETIME, },
95 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
96 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
97 ],
98 data: [],
99 page: {
100 type: "normal",
101 rows: 0,
102 ...page.value,
103 },
104 actionInfo: {
105 label: "操作",
106 type: "btn",
107 width: 230,
108 fixed: 'right',
109 btns: (scope) => {
110 const row = scope.row
111 let btnsArr: any = [];
112 if (row.isCarry) {
113 btnsArr.splice(0, 0, { label: "执行中...", value: "carry", disabled: true })
114 } else {
115 btnsArr.splice(0, 0, { label: "立即执行", value: "carry", disabled: row.taskState === 0 || row.execState == 1 })
116 }
117 btnsArr.push({ label: "编辑", value: "edit", disabled: row.taskState === 1 || row.isCarry || row.execState == 1 });
118 btnsArr.push({ label: "删除", value: "delete", disabled: row.isCarry || row.taskState === 1 || row.execState == 1 });
119 btnsArr.push({ label: "执行日志", value: "log" });
120 return btnsArr
121 },
122 },
123 loading: false
124 })
125
126 const collectType = ref(1)
127
128 const formItems: any = ref([
129 {
130 label: '元模型',
131 type: 'select',
132 placeholder: '请选择',
133 field: 'metadataModel',
134 default: '1',
135 options:[
136 { label:"数据库表", value:'1' },
137 // {label:"BI报表",value:2,},
138 // {label:"数据服务",value:3,},
139 { label:"ETL工具",value:'4' },
140 // {label:"数据处理",value:5,}
141 ],
142 clearable: true,
143 required: true
144 }, {
145 label: '采集任务名称',
146 type: 'input',
147 placeholder: '请输入',
148 field: 'collectTaskName',
149 default: '',
150 maxlength: 50,
151 clearable: true,
152 required: true
153 }, {
154 label: '数据源名称',
155 type: 'select',
156 placeholder: '请选择',
157 field: 'dataSourceGuid',
158 default: '',
159 options: dataSourceList.value,
160 props: {
161 label: 'databaseNameZh',
162 value: 'guid'
163 },
164 clearable: true,
165 required: true
166 }, {
167 label: "执行计划",
168 type: "radio-group",
169 placeholder: "",
170 field: "collectType",
171 default: 1,
172 options: [
173 {
174 label: "离线",
175 value: 1,
176 },
177 // {
178 // label: "实时",
179 // value: 2,
180 // },
181 ],
182 required: true,
183 }, {
184 label: '同步策略',
185 type: 'select',
186 placeholder: '请选择',
187 field: 'collectMode',
188 default: 2,
189 options: [
190 // { label: '增量', value: 1 },
191 { label: '全量', value: 2 },
192 ],
193 clearable: true,
194 required: true,
195 visible: true
196 }, {
197 label: '执行周期',
198 type: 'input-popover-panel',
199 placeholder: '',
200 field: 'execCycle',
201 default: '',
202 append: {
203 btn: { label: '执行时间', value: 'cron' }
204 },
205 teleported: true,
206 clearable: true,
207 required: true,
208 block: true,
209 visible: true,
210 }, {
211 label: '接下来五次执行时间',
212 type: 'textarea',
213 placeholder: '执行时间',
214 field: 'zxzq',
215 clearable: true,
216 required: false,
217 rows: 5,
218 block: true,
219 visible: true,
220 readonly: true
221 }, {
222 label: "",
223 type: "checkbox",
224 placeholder: "抽取存量数据",
225 field: "isInit",
226 default: 'Y',
227 trueValue: 'Y',
228 falseValue: 'N',
229 required: false,
230 visible: false,
231 col: 'margin-top-21'
232 },
233 ])
234
235 /** 记录已校验过的信息。 */
236 const checkedInfo: any = ref({});
237
238 const formRules: any = ref({
239 metadataModel: [required('请选择元模型')],
240 collectTaskName: [required('请填写采集任务名称'), checkExistName(checkedInfo.value, checkMetaDataTask, currTableData.value, 'collectTaskName')],
241 dataSourceGuid: [required('请选择数据源')],
242 execCycle: [required('请设置执行周期')],
243 collectMode: [required('请选择同步策略')],
244 })
245 const formInfo = ref({
246 type: 'form',
247 title: '',
248 formInfo: {
249 id: 'add-dict-form',
250 items: formItems.value,
251 rules: formRules.value
252 }
253 })
254 /** 新建编辑采集任务对话框配置 */
255 const dialogInfo = ref({
256 visible: false,
257 size: 700,
258 direction: "column",
259 header: {
260 title: "新建",
261 },
262 type: '',
263 contents: [
264 formInfo.value,
265 ],
266 footer: {
267 btns: [
268 { type: "default", label: "取消", value: "cancel" },
269 { type: "primary", label: "确定", value: "submit" },
270 ],
271 },
272 });
273
274 const toSearch = (val: any, clear: boolean = false) => {
275 if (clear) {
276 searchItemList.value.map((item) => (item.default = ""));
277 page.value.collectTaskName = '';
278 page.value.dataSourceGuid = "";
279 page.value.taskState = null;
280 } else {
281 page.value.collectTaskName = val.collectTaskName;
282 page.value.dataSourceGuid = val.dataSourceGuid;
283 page.value.taskState = val.taskState;
284 }
285 getTableData();
286 };
287
288 const getTableData = () => {
289 tableInfo.value.loading = true
290 getMetaDataTask({
291 pageIndex: page.value.curr,
292 pageSize: page.value.limit,
293 collectTaskName: page.value.collectTaskName,
294 dataSourceGuid: page.value.dataSourceGuid,
295 taskState: page.value.taskState
296 }).then((res: any) => {
297 if (res.code == proxy.$passCode) {
298 const data = res.data || {}
299 tableInfo.value.data = data.records || []
300 tableInfo.value.page.limit = data.pageSize
301 tableInfo.value.page.curr = data.pageIndex
302 tableInfo.value.page.rows = data.totalRows
303 } else {
304 proxy.$ElMessage({
305 type: 'error',
306 message: res.msg,
307 })
308 }
309 tableInfo.value.loading = false
310 })
311 };
312
313 const tableSwitchBeforeChange = (scope, field, callback) => {
314 const msg = `确定【${scope.row[field] == 1 ? '下线' : '上线'}${scope.row.collectTaskName}?`
315 proxy.$openMessageBox(msg, () => {
316 const state = scope.row[field] == 1 ? 0 : 1
317 const result = tableSwitchChange(state, scope, field)
318 callback(result)
319 }, () => {
320 callback(false)
321 });
322 }
323
324 const tableSwitchChange = (val, scope, field) => {
325 return new Promise((resolve, reject) => {
326 let params = {
327 guid: scope.row.guid,
328 taskState: val
329 }
330 updateMetaDataState(params).then((res: any) => {
331 if (res.code == proxy.$passCode && res.data) {
332 getTableData();
333 proxy.$ElMessage({
334 type: "success",
335 message: `${scope.row.collectTaskName}${val == 0 ? '下线' : '上线'}】成功`,
336 });
337 resolve(true)
338 } else {
339 proxy.$ElMessage({
340 type: "error",
341 message: res.msg,
342 });
343 reject(false)
344 }
345 }).catch(() => {
346 reject(false)
347 })
348 })
349 }
350
351 const tablePageChange = (info) => {
352 page.value.curr = Number(info.curr);
353 page.value.limit = Number(info.limit);
354 getTableData();
355 };
356
357 const oldOriginValue = ref({});
358
359 const setFormItems = (row: any = null) => {
360 let val = oldOriginValue.value = Object.assign({ collectType: collectType.value, collectMode: 2, isInit: 'Y', metadataModel: '1' }, oldOriginValue.value, row || {});
361 formItems.value.map(item => {
362 if (item.field == 'dataSourceGuid') {
363 item.options = dataSourceList.value
364 }
365 item.default = val[item.field] || "";
366 })
367 }
368
369 const tableBtnClick = (scope, btn) => {
370 const type = btn.value;
371 let row = scope.row;
372 currTableData.value = row;
373 if (type == 'carry') {
374 row.isCarry = true
375 const guid = row.guid
376 executeMetaDataTask(guid).then((res: any) => {
377 if (res.code == proxy.$passCode) {
378 getTableData();
379 proxy.$ElMessage({
380 type: "success",
381 message: "立即执行成功",
382 });
383 } else {
384 proxy.$ElMessage({
385 type: "error",
386 message: res.msg,
387 });
388 getTableData();
389 }
390 row.isCarry = false
391 }).catch(() => {
392 row.isCarry = false
393 })
394 } else if (type == 'log') {
395 router.push({
396 path: '/data-meta/collect-task/excution-log',
397 query: {
398 guid: row.guid,
399 name: row.collectTaskName,
400 type: row.collectType
401 }
402 })
403 } else if (type == "edit") {
404 dialogInfo.value.header.title = '编辑采集任务';
405 dialogInfo.value.type = type
406 setDetailInfo(scope.row);
407 formInfo.value.formInfo.items[6].default = '';
408 formRules.value.collectTaskName = [required('请填写采集任务名称'), checkExistName(checkedInfo.value, checkMetaDataTask, currTableData.value, 'collectTaskName')];
409 dialogInfo.value.contents[0].formInfo.rules = formRules.value;
410 } else if (type == "delete") {
411 proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => {
412 let guids = [currTableData.value.guid]
413 deleteMetaDataTask(guids).then((res: any) => {
414 if (res.code == proxy.$passCode) {
415 getTableData();
416 proxy.$ElMessage({
417 type: "success",
418 message: "删除成功",
419 });
420 } else {
421 proxy.$ElMessage({
422 type: "error",
423 message: res.msg,
424 });
425 }
426 });
427 })
428 } else if(type=="datasourceName"){
429 if (row.metadataModel == '4') {
430 proxy.$ElMessage.warning("元模型为ETL工具的采集任务无元数据详情!")
431 return;
432 }
433 checkDatabaseIsExist(row.dataSourceGuid).then((res:any)=>{
434 if(res.code===proxy.$passCode){
435 if(res.data){
436 router.push({
437 name:"metadataQuery",
438 query:{
439 dataSourceGuid:row.dataSourceGuid,
440 datasourceName:row.datasourceName,
441 }
442 })
443 } else {
444 proxy.$ElMessage.warning("该任务暂未执行成功过!")
445 }
446 }
447 })
448 }
449 };
450
451 const loadDrawer = () => {
452 collectType.value = 1
453 setFormItems()
454 setGroup()
455 formInfo.value.formInfo.items[5].default = '0 00 00 01 * ? '
456 dialogInfo.value.header.title = '新建采集任务'
457 dialogInfo.value.type = 'add'
458 formInfo.value.formInfo.items = formItems.value
459 dialogInfo.value.visible = true;
460 checkedInfo.value = {};
461 formItems.value[0].default = '1';
462 formItems.value[0].disabled = false;
463 };
464
465 const setDetailInfo = (row) => {
466 collectType.value = row.collectType
467 setFormItems(row)
468 setGroup()
469 formItems.value[0].disabled = true;
470 dialogInfo.value.visible = true;
471 checkedInfo.value = {};
472 }
473
474 const getDataSourceList = () => {
475 getDatabase({ connectStatus: 1 }).then((res: any) => {
476 if (res.code == proxy.$passCode) {
477 dataSourceList.value = res.data || [];
478 formItems.value[1].options = dataSourceList.value;
479 searchItemList.value[1].options = dataSourceList.value;
480 } else {
481 proxy.$ElMessage({
482 type: "error",
483 message: res.msg,
484 });
485 }
486 })
487 }
488
489 const radioGroupChange = (val, info) => {
490 collectType.value = val
491 setFormItems(info)
492 setGroup()
493 }
494
495 const scheduleChange = (val, rowValue) => {
496 setFormItems(rowValue)
497 formItems.value.at(-3).default = val
498 formInfo.value.formInfo.items = formItems.value
499 }
500
501 // 切换结构类型 设置选项显隐
502 const setGroup = () => {
503 if (collectType.value == 2) {
504 formItems.value.at(-1).visible = true
505 formItems.value.at(-2).visible = false
506 formItems.value.at(-3).visible = false
507 formItems.value.at(-4).visible = false
508 } else {
509 formItems.value.at(-1).visible = false
510 formItems.value.at(-2).visible = true
511 formItems.value.at(-3).visible = true
512 formItems.value.at(-4).visible = true
513 }
514 formInfo.value.formInfo.items = formItems.value
515 }
516
517 const taskCreateDialogRef = ref();
518
519 const dialogBtnClick = (btn, info) => {
520 if (btn.value == 'submit') {
521 let params = { ...info ,}
522 // params.taskState = 1;
523 if (dialogInfo.value.type == 'add') {
524 params.taskState = 1;
525 addMetaDataTask(params).then((res: any) => {
526 if (res.code == proxy.$passCode) {
527 page.value.curr = 1;
528 getTableData();
529 proxy.$ElMessage({
530 type: 'success',
531 message: '添加采集任务成功'
532 })
533 dialogInfo.value.visible = false;
534 } else {
535 proxy.$ElMessage.error(res.msg);
536 }
537 })
538 } else {
539 params.guid = currTableData.value.guid
540 updateMetaDataTask(params).then((res: any) => {
541 if (res.code == proxy.$passCode) {
542 getTableData();
543 proxy.$ElMessage({
544 type: 'success',
545 message: '修改采集任务成功'
546 })
547 dialogInfo.value.visible = false;
548 } else {
549 proxy.$ElMessage.error(res.msg);
550 }
551 })
552 }
553 } else if (btn.value == 'cancel') {
554 dialogInfo.value.visible = false;
555 } else if (btn.value = 'cron') {
556 let vInfo = taskCreateDialogRef.value.dialogFormRef[0].formInline;
557 if (!vInfo.execCycle) {
558 return;
559 }
560 getCronExecTime(vInfo.execCycle).then((res: any) => {
561 if (res?.length) {
562 vInfo.zxzq = res.join('\n');
563 setFormItems(vInfo);
564 } else {
565 proxy.$ElMessage({
566 type: 'error',
567 message: res.msg,
568 })
569 }
570 })
571 }
572 };
573
574 onActivated(() => {
575 getDataSourceList()
576 })
577
578 onBeforeMount(() => {
579 toSearch({})
580 })
581 </script>
582
583 <template>
584 <div class="container_wrap">
585 <div class="table_tool_wrap">
586 <!-- 头部搜索 -->
587 <TableTools :searchItems="searchItemList" :searchId="'data-source-search'" @search="toSearch" :init="false" />
588 <div class="tools_btns">
589 <el-button type="primary" @click="loadDrawer" v-preReClick>新建</el-button>
590 </div>
591 </div>
592 <div class="table_panel_wrap">
593 <!-- 右侧采集任务表格 -->
594 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick"
595 @tablePageChange="tablePageChange" @tableSwitchBeforeChange="tableSwitchBeforeChange" />
596 </div>
597 <!-- 新建编辑采集任务对话框 -->
598 <Dialog ref="taskCreateDialogRef" :dialogInfo="dialogInfo" @btnClick="dialogBtnClick" @radioGroupChange="radioGroupChange"
599 @scheduleChange="scheduleChange" />
600 </div>
601 </template>
602
603 <style scoped lang="scss">
604
605 .table_tool_wrap {
606 width: 100%;
607 height: 84px !important;
608 padding: 0 8px;
609
610 .tools_btns {
611 padding: 0px 0 0;
612 }
613 }
614
615 .table_panel_wrap {
616 width: 100%;
617 height: calc(100% - 84px);
618 padding: 0px 8px 0;
619 }
620 </style>
1 <route lang="yaml">
2 name: executionLog
3 </route>
4
5 <script lang="ts" setup name="excutionLog">
6 import { ref } from "vue";
7 import { ElMessage } from "element-plus";
8 import Table from "@/components/Table/index.vue";
9 import Drawer from '@/components/Drawer/index.vue'
10
11 import {
12 getMetaDataTaskLog,getMetacompareList,getMetaChangeRecord
13 } from "@/api/modules/dataMetaService"
14 import { useRouter, useRoute } from "vue-router";
15 import Moment from 'moment';
16 import { changeNum } from '@/utils/common'
17 import { TableColumnWidth } from "@/utils/enum";
18
19 const { proxy } = getCurrentInstance() as any;
20
21 const router = useRouter();
22 const route = useRoute();
23
24 const collectTaskGuid: any = ref(route.query.guid);
25 const collectTaskType: any = ref(route.query.type);
26 const page = ref({
27 limit: 50,
28 curr: 1,
29 sizes: [
30 { label: "10", value: 10 },
31 { label: "50", value: 50 },
32 { label: "100", value: 100 },
33 { label: "150", value: 150 },
34 { label: "200", value: 200 },
35 ],
36 });
37
38 const originFields: any = ref([
39 { label: "采集任务名称", field: "collectTaskName", width: 140 },
40 { label: "数据源名称", field: "datasourceName", width: 160 },
41 { label: "执行计划", field: "collectType", type: 'filter', width: 90 },
42 { label: "执行状态", field: "execResult", width: 100, type: "tag", align: 'center' },
43 {
44 label: "执行时间", field: "execTime", width: TableColumnWidth.DATETIME, getName: (scope) => {
45 let row = scope.row;
46 return row.execTime && Moment(row.execTime).format('YYYY-MM-DD HH:mm:ss');
47 }
48 },
49 {
50 label: "耗时(秒)", field: "execDuration", width: TableColumnWidth.EXECDURATION, align: "right", getName: (scope) => {
51 return scope.row.execDuration != null ? changeNum(scope.row.execDuration ?? 0) : '--';
52 }
53 },
54 ]);
55
56 const tableInfo = ref({
57 id: "user-authority-table",
58 fields: [],
59 data: [],
60 page: {
61 type: "normal",
62 rows: 0,
63 ...page.value,
64 },
65 actionInfo: {
66 label: "操作",
67 type: "btn",
68 width: 150,
69 fixed: 'right',
70 btns: (scope)=>{
71
72 return [
73 { label: "元数据变更记录", value: "log_detail",disabled:scope.row['execResult']==='N' || scope.row['execResult']==='R'},
74 ]
75 }
76 },
77 loading: false
78 });
79 const metaChangePage: any = ref({
80 limit: 50,
81 curr: 1,
82 sizes: [
83 { label: "10", value: 10 },
84 { label: "50", value: 50 },
85 { label: "100", value: 100 },
86 { label: "150", value: 150 },
87 { label: "200", value: 200 },
88 ],
89 metaTaskName: '',
90 changeState: null,
91 changeType: ''
92 });
93 const formTable = ref({
94 type: "table",
95 title: "",
96 col: 'no-margin',
97
98 tableInfo: {
99 id: "task-detail-table",
100 minHeight: 'unset',
101 fields: [
102 { label: "序号", type: "index", width: 56, align: "center", fixed: "left" },
103 { label: "表中文名称", field: "tableChName", width: 180, },
104 { label: "表英文名称", field: "tableName", width: 180, },
105 { label: "元数据名称", field: "metaName", width: 180, },
106 {
107 label: "元数据类型", field: "metaType", width: 100, getName: (scope) => {
108 if (!scope.row.metaType) {
109 return '--';
110 }
111 return scope.row.metaType == 'TABLE' ? '表' : (scope.row.metaType == 'INDEX' ? '索引' : (scope.row.metaType == 'PRI' ? '主键' : '字段'));
112 }
113 },
114 {
115 label: "变更类型", field: "metaChangeType", width: 100, getName: (scope) => {
116 if (!scope.row.metaChangeType) {
117 return '--';
118 }
119 return scope.row.metaChangeType == 'ADD' ? '新增' : (scope.row.metaChangeType == 'DELETE' ? '删除' : '修改');
120 }
121 },
122 { label: "变化", field: "metaChangeType", width: 100,type: 'popover',value:'metaCurrValue',column:[{field:"item",label:"变化信息",width:"150"},{field:"oldValue",label:"原值",width:"150"},{field:"newValue",label:"现值",width:"150"}] },
123 // { label: "原值", field: "metaOldValue", width: 120 },
124 { label: "元数据采集时间", field: "changeTime", width: 180, },
125 // { label: "状态", field: "changeTime", width: 180, },
126 // { label: "操作时间", field: "changeTime", width: 180, },
127 ],
128 data: [],
129 loading: false,
130 page:{
131 rows:0,
132 ...metaChangePage.value
133 },
134 popoverData:[],
135 popoverloading:false,
136 showPage: true,
137 actionInfo: {
138 show: false
139 },
140 },
141 })
142
143 const drawerInfo: any = ref({
144 visible: false,
145 direction: "rtl",
146 modalClass: "wrap_width_auto",
147 size: 700,
148 modalClose:true,
149 header: {
150 title: "",
151 },
152 type: '',
153 container: {
154 contents: [
155 formTable.value,
156 ],
157 },
158 footer: {
159 visible: false,
160 btns: [
161 { type: 'default', label: '取消', value: 'cancel' },
162 { type: 'primary', label: '确认 ', value: 'save' },
163 ]
164 },
165 })
166 const currRow:any = ref({})
167 const getFirstPageData = () => {
168 page.value.curr = 1
169 toSearch({})
170 }
171
172 const toSearch = (val: any, clear: boolean = false) => {
173 let params: any = Object.keys(val).length ? { ...val } : {}
174 params.pageIndex = page.value.curr;
175 params.pageSize = page.value.limit;
176 params.collectTaskGuid = collectTaskGuid.value
177 getTableData(params);
178 };
179
180 const getTableData = (params) => {
181 tableInfo.value.loading = true
182 getMetaDataTaskLog(params).then((res: any) => {
183 if (res.code == proxy.$passCode) {
184 const data = res.data || {}
185 tableInfo.value.data = data.records || []
186 tableInfo.value.page.limit = data.pageSize
187 tableInfo.value.page.curr = data.pageIndex
188 tableInfo.value.page.rows = data.totalRows
189 } else {
190 ElMessage({
191 type: 'error',
192 message: res.msg,
193 })
194 }
195 tableInfo.value.loading = false
196 }).catch(xhr => {
197 tableInfo.value.loading = false
198 })
199 };
200
201 const tablePageChange = (info) => {
202 page.value.curr = Number(info.curr);
203 page.value.limit = Number(info.limit);
204 toSearch({})
205 };
206 const getMetaChangeTableData = ()=>{
207 formTable.value.tableInfo.loading = true
208 getMetaChangeRecord(Object.assign({execGuid:currRow.value.guid},{pageIndex:metaChangePage.value.curr,pageSize:metaChangePage.value.limit})).then((res: any) => {
209 formTable.value.tableInfo.loading = false
210 if (res.code == proxy.$passCode && res.data) {
211 let data = res.data || {}
212 formTable.value.tableInfo.data = data.records || []
213 formTable.value.tableInfo.page.rows = data.totalRows
214 const contents = [formTable.value]
215 drawerInfo.value.container.contents = contents
216 drawerInfo.value.footer.visible = false
217 drawerInfo.value.visible = true
218 } else {
219 ElMessage({
220 type: "error",
221 message: '未获取到对应人员信息',
222 });
223 }
224 })
225 }
226 const drawerTableClick = (scope,btn)=>{
227 drawerInfo.value.container.contents[0].tableInfo.popoverloading = true
228 getMetacompareList(scope.row.guid).then((res:any)=>{
229 drawerInfo.value.container.contents[0].tableInfo.popoverloading = false
230 drawerInfo.value.container.contents[0].tableInfo.popoverData = res.data
231
232 })
233 }
234 const tableBtnClick = (scope, btn) => {
235 const type = btn.value;
236 const row = scope.row;
237 currRow.value = row
238 if (type == 'log_detail') {
239 drawerInfo.value.header.title = row.collectTaskName;
240 drawerInfo.value.type = type
241 formTable.value.tableInfo.data = []
242 const contents = [formTable.value]
243 drawerInfo.value.container.contents = contents
244 drawerInfo.value.footer.visible = false
245 drawerInfo.value.visible = true
246 getMetaChangeTableData()
247 }
248 };
249
250 const drawerBtnClick = (btn, info) => {
251 if (btn.value == 'cancel') {
252 drawerInfo.value.visible = false
253 }
254 }
255 const metaChangeTablePageChange = (info) => {
256
257 metaChangePage.value.curr = Number(info.curr);
258 metaChangePage.value.limit = Number(info.limit);
259 formTable.value.tableInfo.page.limit = metaChangePage.value.limit;
260 formTable.value.tableInfo.page.curr = metaChangePage.value.curr;
261 getMetaChangeTableData();
262 };
263 onBeforeMount(() => {
264 if (collectTaskType.value == '1') {
265 originFields.value.splice(2, 0, { label: "同步策略", field: "collectMode", type: 'filter', width: 90 },)
266 }
267 tableInfo.value.fields = originFields.value;
268 getFirstPageData();
269 });
270 </script>
271
272 <template>
273 <div class="container_wrap">
274 <div class="table_panel_wrap">
275 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tablePageChange="tablePageChange" />
276 </div>
277 <Drawer :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick" @drawerTableBtnClick="drawerTableClick" @drawerTablePageChange="metaChangeTablePageChange" />
278 </div>
279 </template>
280
281 <style lang="scss" scoped>
282 .container_wrap {
283 padding: 0;
284
285 .table_panel_wrap {
286 height: 100%;
287 padding: 16px 16px 0;
288 }
289 }
290
291 :deep(.el-drawer) {
292
293 .el-drawer__body {
294 padding: 10px 10px 0px 10px;
295 }
296
297 .drawer_panel {
298 height: 100%;
299
300 .table_panel_wrap {
301 height: 100%;
302 }
303 }
304
305
306 }
307 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: metaSheet
3 </route>
4
5 <script lang="ts" setup name="metaSheet">
6 import { ref } from 'vue'
7 import { useRouter, useRoute } from "vue-router";
8 import { ElMessage } from "element-plus";
9 import { CaretRight } from '@element-plus/icons-vue'
10 import Table from '@/components/Table/index.vue'
11 import Tabs from '@/components/Tabs/index.vue'
12 import Drawer from '@/components/Drawer/index.vue'
13 import { changeNum } from '@/utils/common'
14 import useUserStore from "@/store/modules/user";
15 import LineageGraph from '@/components/LineageGraph/index.vue';
16 import EllipsisTooltip from '@/components/EllipsisTooltip.vue';
17 import {
18 getMetaDetail,
19 getMetaSheetField,
20 getMetaSheetKeys,
21 getMetaChange,
22 getMetaChangeRecord,
23 getTableLineage,
24 getTableAllFieldLineage,
25 getMetacompareList,
26 saveMetaReportAnalysis,
27 checkTableData
28 } from '@/api/modules/dataMetaService';
29 import { getFileUrl } from "@/api/modules/queryService"
30 import useDataMetaStore from "@/store/modules/dataMeta"
31 import { TableColumnWidth } from '@/utils/enum';
32
33 const { proxy } = getCurrentInstance() as any;
34 const router = useRouter();
35 const { set } = useDataMetaStore()
36 const lineageData = ref([]);
37
38 const userStore = useUserStore();
39 const detailGuid: any = ref('')
40 const sheetInfo: any = ref({})
41 const listLoading = ref(false)
42 const sheetList = ref([
43 { label: '创建时间', field: 'metaCreateTime', value: '--' },
44 { label: '修改时间', field: 'metaUpdateTime', value: '--' }
45 ])
46 const fieldList = ref([
47 { label: '存储量(约)(MB)', field: 'storageCapacities', value: 0.00, type: 'chnum', fixedNum: 2 },
48 { label: '总字段数', field: 'columns', value: 0, type: 'chnum' },
49 { label: '总行数(约)', field: 'tableRows', value: 0, type: 'chnum' },
50 { label: '数据总量(个)', field: 'dataCount', value: 0, type: 'chnum' },
51 { label: '是否分区表', field: 'isPartition', value: '--', type: 'filter' },
52 { label: '索引数', field: 'indexes', value: 0, type: 'chnum' },
53 { label: '储存引擎', field: 'tableEngine', value: '--' },
54 { label: '字符集', field: 'characterSet', value: '--' },
55 { label: '外部表信息', field: 'wbb', value: '--' },
56 ])
57
58 const tabsActiveName = ref('first')
59 const tabsInfo = ref({
60 activeName: '',
61 tabs: [
62 { label: '基础信息', name: 'first' },
63 { label: '数据血缘', name: 'second' },
64 { label: '变更记录', name: 'third' }
65 ]
66 })
67 /** 切换布局 */
68 const isToggle = ref(true);
69 /** 是否显示英文名称,默认不显示。 */
70 const isCh = ref(false);
71 /** 是否是字段血缘关系 */
72 const isFieldLineage = ref(false);
73 const fieldTableInfo = ref({
74 id: 'field-table',
75 minHeight: 'unset',
76 maxHeight: '100%',
77 fields: [
78 { label: "序号", type: "index", width: 56, align: "center", fixed: "left" },
79 { label: "字段英文名称", field: "enName", width: 150 },
80 { label: "字段中文名称", field: "chName", width: 150 },
81 { label: "注释", field: "comment", width: TableColumnWidth.TABLENAME },
82 { label: "字段类型", field: "fieldTypeChName", width: 100 },
83 { label: "长度", field: "fieldLength", width: 100, align: 'right', type: 'chnum', isNaN: true },
84 { label: "精度", field: "fieldPrecision", width: 100, align: 'right' },
85 { label: "是否主键", field: "isPrimary", type: 'filter', width: 100 },
86 // { label: "是否外键", field: "isFk", type: 'filter', width: 100 },
87 { label: "是否必填 ", field: "notNull", type: 'filter', width: 100 },
88 // { label: "是否分区", field: "isPartition", type: 'filter', width: 100 },
89 ],
90 data: [],
91 showPage: false,
92 actionInfo: {
93 label: "操作",
94 type: "btn",
95 width: 90,
96 fixed: 'right',
97 btns: [
98 {
99 label: "查看血缘", value: "view", click: (scope, btn) => {
100 //跳转倒查看血缘页面。
101 let row = scope.row;
102 set({
103 tableGuid: sheetInfo.value.guid,
104 table: sheetInfo.value.tableName,
105 databas: sheetInfo.value.databaseName,
106 databaseCh: sheetInfo.value.databaseChName,
107 dsGuid: sheetInfo.value.databaseGuid,
108 fGuid: row.guid,
109 fEnName: row.enName
110 })
111 router.push({
112 name: "analysisView",
113 })
114 }
115 },
116 ],
117 },
118 loading: false
119 })
120
121 const indexTableInfo = ref({
122 id: 'index-table',
123 minHeight: 'unset',
124 maxHeight: '100%',
125 fields: [
126 { label: "序号", type: "index", width: 56, align: "center", fixed: "left" },
127 { label: "索引名", field: "indexName", width: 160 },
128 { label: "索引类型", field: "indexType", width: 100 },
129 { label: "索引字段", field: "indexColumn", width: 160 },
130 { label: "唯一性", field: "isUnique", type: 'filter', width: 120, },
131 { label: "索引大小(MB)", field: "indexLength", width: 150, align: 'right' },
132 // { label: "创建时间", field: "createTime", width: 180, },
133 ],
134 data: [],
135 showPage: false,
136 actionInfo: {
137 show: false
138 },
139 loading: false
140 })
141
142 const logPage = ref({
143 limit: 50,
144 curr: 1,
145 sizes: [
146 { label: "10", value: 10 },
147 { label: "50", value: 50 },
148 { label: "100", value: 100 },
149 { label: "150", value: 150 },
150 { label: "200", value: 200 },
151 ],
152 })
153 const currLogTableData: any = ref({})
154 const logTableInfo = ref({
155 id: 'log-table',
156 fields: [
157 { label: "序号", type: "index", width: 56, align: "center", fixed: "left" },
158 { label: "采集任务名称", field: "collectTaskName", width: 140, type: 'text_btn', class: 'drawer-detail-cell', value: 'task-detail' },
159 // { label: "采集时间", field: "createTime", width: 180, },
160 { label: "同步策略", field: "collectMode", type: 'filter', width: 120 },
161 { label: "新增元数据", field: "addChangeRecordNum", type: 'chnum', width: 120, align: 'right' },
162 { label: "修改元数据", field: "updateChangeRecordNum", type: 'chnum', width: 120, align: 'right' },
163 { label: "删除元数据", field: "deleteChangeRecordNum", type: 'chnum', width: 120, align: 'right' },
164 ],
165 data: [
166 {
167 taskName: '采集任务1',
168 updateTime: '2022-12-22 10:30:02',
169 addnum: 1350,
170 updatenum: 239,
171 deletenum: 20,
172 operator: ''
173 }
174 ],
175 page: {
176 type: "normal",
177 rows: 0,
178 ...logPage.value
179 },
180 actionInfo: {
181 show: false
182 },
183 loading: false
184 })
185
186 const tabsPane: any = ref([])
187 const tabsPaneMap = ref({
188 first: [
189 {
190 title: '字段信息',
191 type: 'table',
192 tableInfo: fieldTableInfo.value,
193 show: true
194 }, {
195 title: '索引信息',
196 type: 'table',
197 isIndex: true,
198 tableInfo: indexTableInfo.value,
199 show: true
200 },
201 ],
202 second: [
203 {
204 title: '',
205 type: 'X6'
206 },
207 ],
208 third: [
209 {
210 title: '',
211 type: 'table',
212 tableInfo: logTableInfo.value
213 },
214 ]
215 })
216
217 const page = ref({
218 limit: 50,
219 curr: 1,
220 sizes: [
221 { label: "10", value: 10 },
222 { label: "50", value: 50 },
223 { label: "100", value: 100 },
224 { label: "150", value: 150 },
225 { label: "200", value: 200 },
226 ],
227 });
228 const formInline1 = ref({ pageName: "" })
229 const formTable = ref({
230 type: "table",
231 title: "",
232 col: 'no-margin',
233 tableInfo: {
234 loading: false,
235 id: "task-detail-table",
236 minHeight: 'unset',
237 fields: [
238 { label: "序号", type: "index", width: 56, align: "center", fixed: "left" },
239 { label: "元数据名称", field: "metaName", width: 150, align: "center", fixed: "left" },
240 {
241 label: "元数据类型", field: "metaType", width: 100, getName: (scope) => {
242 if (!scope.row.metaType) {
243 return '--';
244 }
245 return scope.row.metaType == 'TABLE' ? '表' : (scope.row.metaType == 'INDEX' ? '索引' : (scope.row.metaType == 'PRI' ? '主键' : '字段'));
246 }
247 },
248
249 {
250 label: "变更类型", field: "metaChangeType", width: 100, getName: (scope) => {
251 if (!scope.row.metaChangeType) {
252 return '--';
253 }
254 return scope.row.metaChangeType == 'ADD' ? '新增' : (scope.row.metaChangeType == 'DELETE' ? '删除' : '修改');
255 }
256 },
257 { label: "变化", field: "metaChangeType", width: 100, type: 'popover', value: 'metaCurrValue', column: [{ field: "item", label: "变化信息", width: "150" }, { field: "oldValue", label: "原值", width: "150" }, { field: "newValue", label: "现值", width: "150" }] },
258 // { label: "原值", field: "metaOldValue", width: 120 },
259 { label: "采集时间", field: "changeTime", width: 180, },
260 ],
261 data: [],
262 popoverData: [],
263 popoverloading: false,
264 page: {
265 type: "normal",
266 rows: 0,
267 ...page.value,
268 },
269 actionInfo: {
270 show: false
271 },
272 },
273 })
274 const drawerInfo: any = ref({
275 visible: false,
276 direction: "rtl",
277 modalClass: "wrap_width_auto",
278 modalClose: false,
279 modal: true,
280 size: 600,
281 header: {
282 title: "",
283 },
284 type: '',
285 container: {
286 contents: [
287 formTable.value,
288 ],
289 },
290 footer: {
291 visible: false,
292 btns: [
293 { type: 'default', label: '取消', value: 'cancel' },
294 { type: 'primary', label: '确认 ', value: 'save' },
295 ]
296 },
297 })
298 const drawerTableClick = (scope, btn) => {
299 drawerInfo.value.container.contents[0].tableInfo.popoverloading = true
300 getMetacompareList(scope.row.guid).then((res: any) => {
301 drawerInfo.value.container.contents[0].tableInfo.popoverloading = false
302 drawerInfo.value.container.contents[0].tableInfo.popoverData = res.data
303
304 })
305 }
306 /** 数据血缘加载状态。 */
307 const lineageDataLoading = ref(true);
308
309 const getDetailInfo = () => {
310 getSheetDetail()
311 getSheetField()
312 getSheetKeys()
313 logPage.value.curr = 1
314 getSheetLog();
315 getTableLineageMap();
316 }
317
318 const getSheetDetail = () => {
319 listLoading.value = true
320 getMetaDetail(detailGuid.value).then((res: any) => {
321 listLoading.value = false
322 if (res.code == proxy.$passCode) {
323 const data = res.data || {}
324 sheetInfo.value = data
325 let tab: any = userStore.tabbar.find((tab: any) => tab.fullPath === router.currentRoute.value.fullPath);
326 if (tab) {
327 tab.meta.title = `元数据详情-${data.tableChName || data.tableName}`;
328 }
329 fieldList.value.map((item: any) => {
330 if (item.type && item.type == 'chnum') {
331 item.value = data[item.field] ?? 0
332 } else {
333 item.value = data[item.field] ?? '--'
334 }
335 })
336 sheetList.value.map((item: any) => {
337 item.value = data[item.field] ?? '--'
338 })
339 } else {
340 ElMessage({
341 type: 'error',
342 message: res.msg,
343 })
344 }
345 }).catch(() => {
346 listLoading.value = false
347 })
348 }
349
350 const getSheetField = () => {
351 let params = {
352 tableGuid: detailGuid.value
353 }
354 fieldTableInfo.value.loading = true
355 getMetaSheetField(params).then((res: any) => {
356 if (res.code == proxy.$passCode) {
357 const data = res.data || []
358 fieldTableInfo.value.data = data
359 } else {
360 ElMessage({
361 type: 'error',
362 message: res.msg,
363 })
364 }
365 fieldTableInfo.value.loading = false
366 }).catch(() => {
367 fieldTableInfo.value.loading = false
368 })
369 }
370
371 const getSheetKeys = () => {
372 let params = {
373 tableGuid: detailGuid.value
374 }
375 indexTableInfo.value.loading = true
376 getMetaSheetKeys(params).then((res: any) => {
377 if (res.code == proxy.$passCode) {
378 const data = res.data || []
379 indexTableInfo.value.data = data
380 } else {
381 ElMessage({
382 type: 'error',
383 message: res.msg,
384 })
385 }
386 indexTableInfo.value.loading = false
387 }).catch(() => {
388 indexTableInfo.value.loading = false
389 })
390 }
391
392 const getSheetLog = () => {
393 let params = {
394 tableGuid: detailGuid.value,
395 pageIndex: logPage.value.curr,
396 pageSize: logPage.value.limit
397 }
398 logTableInfo.value.loading = true
399 getMetaChange(params).then((res: any) => {
400 if (res.code == proxy.$passCode) {
401 const data = res.data || {}
402 logTableInfo.value.data = data.records ?? []
403 logTableInfo.value.page.limit = data.pageSize
404 logTableInfo.value.page.curr = data.pageIndex
405 logTableInfo.value.page.rows = data.totalRows
406 } else {
407 ElMessage({
408 type: 'error',
409 message: res.msg,
410 })
411 }
412 logTableInfo.value.loading = false
413 }).catch(() => {
414 logTableInfo.value.loading = false
415 })
416 }
417
418
419 const tablePageChange = (info) => {
420 logPage.value.curr = Number(info.curr);
421 logPage.value.limit = Number(info.limit);
422 getSheetLog()
423 };
424
425 const tableBtnClick = (scope, btn) => {
426 const type = btn.value;
427 const row = scope.row;
428 currLogTableData.value = row;
429 if (type == "task-detail") {
430 page.value.limit = 50;
431 page.value.curr = 1;
432 drawerInfo.value.header.title = row.collectTaskName;
433 drawerInfo.value.type = type
434 let params = {
435 collectTaskGuid: row.guid,
436 tableGuid: detailGuid.value,
437 pageSize: page.value.limit,
438 pageIndex: page.value.curr
439 }
440 getMetaChangeRecordData(params);
441 }
442 };
443
444 const getMetaChangeRecordData = (params) => {
445 formTable.value.tableInfo.loading = true;
446 getMetaChangeRecord(params).then((res: any) => {
447 formTable.value.tableInfo.loading = false;
448 if (res.code == proxy.$passCode && res.data) {
449 let data = res.data || {}
450 formTable.value.tableInfo.data = data.records || []
451 formTable.value.tableInfo.page.limit = data.pageSize;
452 formTable.value.tableInfo.page.curr = data.pageIndex;
453 formTable.value.tableInfo.page.rows = data.totalRows;
454 const contents = [formTable.value]
455 drawerInfo.value.container.contents = contents
456 drawerInfo.value.footer.visible = false
457 drawerInfo.value.visible = true
458 } else {
459 ElMessage({
460 type: "error",
461 message: res.msg,
462 });
463 }
464 })
465 }
466
467 const filterValue = (row) => {
468 let val: any = row.value || '--'
469 if (row.type && row.type == 'chnum') {
470 val = changeNum(row.value, row.fixedNum ?? 0)
471 } else if (row.type && row.type == 'filter') {
472 if (row.field == 'isPartition') {
473 switch (row.value) {
474 case "Y":
475 val = '是'
476 break;
477 case "N":
478 val = '否'
479 break;
480 default:
481 val = '--'
482 break;
483 }
484 }
485 }
486 return val
487 }
488
489 const tabChange = (tab) => {
490 tabsActiveName.value = tab
491 tabsPane.value = tabsPaneMap.value[tabsActiveName.value]
492 }
493
494 const drawerBtnClick = (btn, info) => {
495 console.log(btn)
496 if (btn.value == 'cancel') {
497 drawerInfo.value.visible = false
498 } else if (btn.value = "metaCurrValue") {
499 console.log((1233))
500 }
501 }
502
503 const drawerTablePageChange = (info) => {
504 page.value.curr = Number(info.curr);
505 page.value.limit = Number(info.limit);
506 if (!currLogTableData.value?.guid) {
507 return;
508 }
509 getMetaChangeRecordData({
510 collectTaskGuid: currLogTableData.value?.guid,
511 tableGuid: detailGuid.value,
512 pageSize: page.value.limit,
513 pageIndex: page.value.curr
514 });
515 }
516
517 onBeforeMount(() => {
518 detailGuid.value = router.currentRoute.value.query?.id || ''
519 tabsInfo.value.activeName = tabsActiveName.value
520 tabsPane.value = tabsPaneMap.value[tabsActiveName.value]
521 getDetailInfo()
522 })
523
524 onMounted(() => {
525 let dom = document.getElementById('main-app');
526 if (dom) {
527 dom.addEventListener('click', (event: any) => {
528 if (!event.target?.classList?.contains('drawer-detail-cell')) {
529 if (drawerInfo.value.visible) {
530 drawerInfo.value.visible = false;
531 }
532 }
533 });
534 }
535 });
536 const lineageGraph = ref()
537 const file: any = ref({})
538 const handleSave = (file1, fullRef) => {
539 file.value = file1
540 dialogInfo1.value.visible = true
541 nextTick(() => {
542 const ele = document.querySelector(".saveDialog1")
543 if (ele) {
544 fullRef.appendChild(ele)
545 }
546 })
547 }
548 const pageSave = () => {
549 const analysisReportName = reportDialogRef.value.dialogFormRef[0].formInline.analysisReportName;
550 if (!analysisReportName) {
551 ElMessage({
552 type: "error",
553 message: "血缘关系名称不能为空!",
554 appendTo: lineageGraph.value[0].containerRef
555 })
556 return
557 }
558 let formData = new FormData();
559 formData.append('file', file.value);
560 formData.append('fileName', `${analysisReportName}.png`);
561 console.log(formInline1.value.pageName)
562 getFileUrl(formData).then((res) => {
563 saveMetaReportAnalysis({
564 table: sheetInfo.value.tableName,
565 database: sheetInfo.value.databaseName,
566 analysisReportUrl: res.data,
567 analysisReportName: analysisReportName,
568 databaseChName: sheetInfo.value.databaseChName
569 }).then((res: any) => {
570 if (res.code == proxy.$passCode) {
571 // ElMessage.success("保存成功")
572 ElMessage({
573 type: "success",
574 message: "保存成功",
575 appendTo: lineageGraph.value[0].containerRef
576 })
577 dialogInfo1.value.visible = false
578 } else {
579 ElMessage({
580 type: "error",
581 message: res.msg,
582 appendTo: lineageGraph.value[0].containerRef
583 })
584 }
585 })
586 }).catch((res) => {
587 ElMessage({
588 type: "error",
589 message: res.msg,
590 appendTo: lineageGraph.value[0].containerRef
591 })
592 })
593 }
594
595 const formItems1: any = ref([
596 {
597 label: '血缘关系名称',
598 type: 'input',
599 placeholder: '请输入',
600 field: 'analysisReportName',
601 default: '',
602 clearable: true,
603 required: true
604 },
605 ])
606 const formRules1: any = ref({
607 analysisReportName: [
608 {
609 required: true,
610 message: "请填写血缘关系名称",
611 trigger: "blur",
612 },
613 ],
614 })
615 const formInfo1 = ref({
616 type: 'form',
617 title: '',
618 formInfo: {
619 id: 'add-dict-form',
620 items: formItems1.value,
621 rules: formRules1.value
622 }
623 })
624 const reportDialogRef = ref();
625 const dialogInfo1 = ref({
626 visible: false,
627 size: 500,
628 direction: "column",
629 modalClass: "saveDialog1",
630 header: {
631 title: "保存为血缘关系图片",
632 },
633 type: '',
634 contents: [
635 formInfo1.value,
636 ],
637 footer: {
638 btns: [
639 { type: "default", label: "取消", value: "cancel" },
640 { type: "primary", label: "保存", value: "submit" },
641 ],
642 },
643 });
644 const dialogBtnClick1 = (btn, scope) => {
645 if (btn.value === "cancel") {
646 dialogInfo1.value.visible = false
647 } else {
648 pageSave()
649 }
650
651 }
652 const handleTableContextMenu = (tableGuid, tableName, database, btnType, vertexId, databaseChName, chTable, neighbor, fieldGuid) => {
653 if (btnType == 1) {//查看元数据详情
654 lineageGraph.value[0].handleEdit({});//先退出全屏再跳转,否则回来查看时就没有了。
655 checkTableData(tableGuid).then((res: any) => {
656 if (res.code === proxy.$passCode) {
657 if (res.data) {
658 router.push({
659 path: '/data-meta/metadata-query/meta-sheet',
660 query: {
661 id: tableGuid,
662 name: chTable
663 }
664 })
665 } else {
666 ElMessage.warning("元数据详情不存在")
667 }
668 }
669 })
670 }
671 }
672 const handleRefres = () => {
673 if (isFieldLineage.value) {
674 getAllTableFieldLineageMap();
675 } else {
676 getTableLineageMap();
677 }
678 }
679 const handleEdit = () => {
680 lineageGraph.value[0].handleEdit({});//先退出全屏再跳转,否则回来查看时就没有了。
681 set({
682 tableGuid: sheetInfo.value.guid,
683 table: sheetInfo.value.tableName,
684 databas: sheetInfo.value.databaseName,
685 databaseCh: sheetInfo.value.databaseChName,
686 dsGuid: sheetInfo.value.databaseGuid,
687 isFieldLine: isFieldLineage.value
688 })
689 router.push({
690 name: "analysisView",
691 })
692 }
693 const handleToggle = () => {
694 isToggle.value = !isToggle.value
695 }
696
697 /** 处理中英文切换。 */
698 const handleChOrEn = (flag) => {
699 isCh.value = flag
700 }
701
702 const getTableLineageMap = () => {
703 isFieldLineage.value = false;
704 lineageDataLoading.value = true;
705 getTableLineage({
706 guid: detailGuid.value
707 }).then((res: any) => {
708 lineageDataLoading.value = false;
709 if (res.code == proxy.$passCode) {
710 const data = res.data || []
711 lineageData.value = data;
712 } else {
713 ElMessage({
714 type: 'error',
715 message: res.msg,
716 })
717 }
718 });
719 }
720
721 const getAllTableFieldLineageMap = () => {
722 lineageDataLoading.value = true;
723 isFieldLineage.value = true;
724 getTableAllFieldLineage({ databaseName: sheetInfo.value.databaseName, tableName: sheetInfo.value.tableName }).then((res: any) => {
725 if (res.code == proxy.$passCode) {
726 let data1 = res.data || [];
727 lineageData.value = data1;
728 isFieldLineage.value = true;
729 nextTick(() => {
730 if (lineageData.value.length > 0) {
731 lineageGraph.value[0].updateLayout();
732 lineageDataLoading.value = false;
733 } else {
734 lineageDataLoading.value = false;
735 }
736 });
737 } else {
738 lineageDataLoading.value = false;
739 ElMessage({
740 type: 'error',
741 message: res.msg,
742 })
743 }
744 })
745 }
746
747 const handleLineageSwitchChange = (val) => {
748 if (val) {
749 isToggle.value = true;
750 getAllTableFieldLineageMap();
751 } else {
752 getTableLineageMap();
753 }
754 }
755 </script>
756
757 <template>
758 <div class="container_wrap full flex">
759 <div class="aside_wrap">
760 <div class="sheet_panel" v-loading="listLoading">
761 <div class="panel_header">
762 <h4>{{ sheetInfo.tableChName ?? '--' }}</h4>
763 <p>{{ sheetInfo.tableName ?? '--' }}</p>
764 </div>
765 <div class="panel_body">
766 <div class="sheet_list">
767 <div class="list_item" v-for="sheet in sheetList">
768 <p class="item_text">{{ sheet.label }}</p>
769 <p class="item_value">{{ sheet.value }}</p>
770 </div>
771 </div>
772 <div class="field_list">
773 <div class="list_item" v-for="field in fieldList">
774 <span class="item_text">{{ field.label }}</span>
775 <span class="item_value">{{ filterValue(field) }}</span>
776 </div>
777 </div>
778 </div>
779 </div>
780 </div>
781 <div class="main_wrap">
782 <Tabs :tabs-info="tabsInfo" @tab-change="tabChange" />
783 <div class="tabs_pane_panel">
784 <div class="tabs_pane" :class="{ full: !pane.title }" v-for="pane in tabsPane">
785 <template v-if="pane.type == 'table'">
786 <div v-if="pane.title" class="pane_header">
787 <span class="header_title" :class="{ active: pane.show }" @click="pane.show = !pane.show" v-preReClick>
788 <el-icon>
789 <CaretRight />
790 </el-icon>
791 <span>{{ pane.title }}</span>
792 </span>
793 </div>
794 <div class="pane_body" :class="{ active: pane.show ?? true, indexInfo: pane.show ? pane.isIndex : false }"
795 :style="{ paddingTop: pane.title ? '' : '16px' }">
796 <Table :tableInfo="pane.tableInfo" @tableBtnClick="tableBtnClick" @tablePageChange="tablePageChange" />
797 </div>
798 </template>
799 <template v-else>
800 <div class="lineage-content" v-loading="lineageDataLoading">
801 <LineageGraph ref="lineageGraph" v-if="lineageData.length" layout="vertical" :lineageData="lineageData"
802 :is-field-lineage="isFieldLineage" :primary-table="sheetInfo.tableName" :is-detail="true"
803 :primaryDatabase="sheetInfo.databaseName" :isEdit="true" @handleSave="handleSave"
804 @tableContextMenu="handleTableContextMenu" @handleToggle="handleToggle" :isToggle="isToggle" @handleChOrEn=handleChOrEn :isCh="isCh"
805 @handleRefres="handleRefres" @handleEdit="handleEdit"
806 @handleLineageSwitchChange="handleLineageSwitchChange" />
807
808 </div>
809 </template>
810 </div>
811 </div>
812 </div>
813 <Drawer :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick" @drawerTableBtnClick="drawerTableClick"
814 @drawerTablePageChange="drawerTablePageChange" />
815 <Dialog ref="reportDialogRef" :dialogInfo="dialogInfo1" @btnClick="dialogBtnClick1" />
816 </div>
817 </template>
818
819 <style lang="scss" scoped>
820 .container_wrap {
821 .aside_wrap {
822 width: 199px;
823 border-right: 1px solid #d9d9d9;
824 box-shadow: none;
825
826 .sheet_panel {
827 .panel_header {
828 padding: 12px 0 0;
829 word-break: break-all;
830
831 h4,
832 p {
833 margin: 0;
834 padding: 0 12px;
835 }
836
837 h4 {
838 font-size: 16px;
839 color: var(--el-color-regular);
840 line-height: 24px;
841 }
842
843 p {
844 font-size: 14px;
845 color: var(--el-text-color-regular);
846 line-height: 21px;
847 }
848 }
849
850 .panel_body {
851 padding: 0 12px;
852
853 .sheet_list {
854 margin-bottom: 16px;
855
856 .list_item {
857 &.list_item {
858 margin-top: 8px;
859 }
860
861 p {
862 margin: 0;
863 font-size: 12px;
864 color: #999;
865 line-height: 18px;
866
867 &.item_value {
868 color: var(--el-color-regular);
869 }
870 }
871 }
872 }
873
874 .field_list {
875 padding: 8px 0;
876
877 .list_item {
878 font-size: 12px;
879 color: var(--el-text-color-regular);
880
881 &.list_item {
882 margin-top: 4px;
883 }
884
885 span.item_text {
886 color: var(--el-color-regular);
887 // margin-right: 8px;
888 }
889 }
890 }
891 }
892 }
893 }
894
895 .main_wrap {
896 padding: 0;
897 height: auto;
898 flex: 1;
899
900 :deep(.el-tabs) {
901
902 .el-tabs__header {
903 margin-bottom: 0;
904 }
905
906 .el-tabs__item {
907 height: 32px;
908
909 &:nth-child(2) {
910 padding-left: 16px;
911 }
912
913 &:last-child {
914 padding-right: 16px;
915 }
916
917 &::after {
918 content: '';
919 width: 100%;
920 height: 2px;
921 background-color: transparent;
922 position: absolute;
923 left: 0;
924 bottom: 0;
925 }
926
927 &.is-active {
928 &::after {
929 background-color: var(--el-color-primary);
930 }
931 }
932 }
933
934 .el-tabs__active-bar {
935 display: none;
936 }
937 }
938
939 .tabs_pane_panel {
940 height: calc(100% - 32px);
941 background-color: #f7f7f9;
942 overflow: hidden auto;
943
944 .tabs_pane {
945 background-color: #fff;
946
947 .lineage-content {
948 height: 100%;
949 background-color: #f7f7f9;
950
951 .card-noData {
952 height: 100%;
953 width: 100%;
954 background: #fafafa;
955 display: flex;
956 flex-direction: column;
957 justify-content: center;
958 align-items: center;
959 color: #909399;
960 font-size: 14px;
961 }
962
963 }
964
965 &+.tabs_pane {
966 margin-top: 4px;
967 }
968
969 .pane_header {
970 height: 40px;
971 padding: 0 16px;
972
973 .header_title {
974 width: 100px;
975 height: 100%;
976 display: flex;
977 align-items: center;
978 color: var(--el-color-regular);
979 cursor: pointer;
980
981 .el-icon {
982 margin-right: 8px;
983 transition: all .3s;
984 }
985
986 &.active {
987 .el-icon {
988 transform: rotateZ(90deg);
989 }
990 }
991 }
992 }
993
994 .pane_body {
995 padding: 0 16px;
996 height: 0;
997 transition: all .3s;
998 overflow: hidden;
999
1000 &.active {
1001 height: 410px;
1002 }
1003
1004 &.indexInfo {
1005 height: 250px;
1006 }
1007
1008 .table_panel {
1009 min-height: unset;
1010 padding-bottom: 16px;
1011 }
1012 }
1013
1014 &.full {
1015 height: 100%;
1016 padding: 0px;
1017 overflow: hidden;
1018
1019 .pane_body {
1020 min-height: 394px;
1021 height: 100%;
1022
1023 .table_panel {
1024 min-height: unset;
1025 padding-bottom: 0;
1026 }
1027 }
1028 }
1029 }
1030 }
1031 }
1032 }
1033
1034 :deep(.el-drawer) {
1035
1036 .el-drawer__body {
1037 padding: 10px 10px 0px 10px;
1038 }
1039
1040 .drawer_panel {
1041 height: 100%;
1042
1043 .table_panel_wrap {
1044 height: 100%;
1045 }
1046 }
1047
1048
1049 }
1050
1051 :deep(.el-form .el-form-item) {
1052 width: calc(100%);
1053 }
1054 </style>
1 <router lang="yaml">
2 name: metadataQuery
3 </router>
4
5 <script lang="ts" setup name="metadataQuery">
6 import { ref } from 'vue'
7 import { ElMessage, ElMessageBox } from "element-plus";
8 import Tree from '@/components/Tree/index.vue'
9 import Table from '@/components/Table/index.vue'
10 import { changeNum } from '@/utils/common'
11 import { useRouter ,useRoute} from "vue-router";
12 import {
13 getMetaTreeData,
14 getMetaDatabaseCollect,
15 getMetaDataBase,
16 getMetaDataSheet,
17 } from '@/api/modules/dataMetaService';
18 import { TableColumnWidth } from '@/utils/enum';
19
20 const { proxy } = getCurrentInstance() as any;
21
22 const router = useRouter();
23 const route = useRoute()
24 let dataSourceGuid = route.query.dataSourceGuid
25 let datasourceName = route.query.datasourceName
26 const currNodeInfo: any = ref({})
27 const expandedKey: any = ref([])
28 const currentNodeKey = ref('')
29 const treeData: any = ref([])
30 const databaseCount = ref(0) //数据库总数
31 const treeInfo = ref({
32 id: "data-pickup-tree",
33 filter: true,
34 queryValue: "",
35 queryPlaceholder: "输入库/表名称搜索",
36 props: {
37 label: "name",
38 value: "guid",
39 },
40 nodeKey: 'guid',
41 expandedKey: [],
42 currentNodeKey: '',
43 expandOnNodeClick: false,
44 data: [],
45 loading: false
46 });
47
48 const cardMap = ref({
49 source: {
50 id: 'database-table',
51 count: [
52 {
53 label: '总表数',
54 field: 'tableCount',
55 value: 0,
56 type: 'chnum',
57 unit: '个',
58 icon: new URL('@/assets/images/icon2.png', import.meta.url).href
59 }, {
60 label: '存储量(约)',
61 field: 'storageCapacities',
62 value: 0.00,
63 type: 'chnum',
64 fixedNum: 2,
65 unit: 'GB',
66 icon: new URL('@/assets/images/icon3.png', import.meta.url).href
67 }, {
68 label: '总字段数',
69 field: 'columns',
70 value: 0,
71 type: 'chnum',
72 unit: '个',
73 icon: new URL('@/assets/images/icon4.png', import.meta.url).href
74 }, {
75 label: '总行数(约)',
76 field: 'tableRows',
77 value: 0,
78 type: 'chnum',
79 unit: '行',
80 icon: new URL('@/assets/images/icon5.png', import.meta.url).href
81 },
82 {
83 label: '数据总量',
84 field: 'dataCount',
85 value: 0,
86 type: 'chnum',
87 unit: '万个',
88 icon: new URL('@/assets/images/dataCount.png', import.meta.url).href
89 }
90 ],
91 fields: [
92 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center", fixed: "left" },
93 { label: "数据库名称", field: "databaseNameZh", width: 160 },
94 { label: "数据库名", field: "databaseName", width: 160 },
95 // { label: "存储索引", field: "operator", width: 120 },
96 { label: "总表数", field: "tableCount", type: 'chnum', width: 100, align: 'right' },
97 { label: "存储量(约)(MB)", field: "storageCapacities", type: 'chnum', fixedNum: 2, width: 140, align: 'right' },
98 { label: "总字段数", field: "columns", type: 'chnum', width: 100, align: 'right' },
99 { label: "总行数(约)", field: "tableRows", type: 'chnum', width: 100, align: 'right' },
100 { label: "数据采集时间", field: "collectTime", width: TableColumnWidth.DATETIME },
101 ]
102 },
103 database: {
104 id: 'datasheet-table',
105 count: [
106 {
107 label: '总表数',
108 field: 'tableCount',
109 value: 0,
110 type: 'chnum',
111 unit: '个',
112 icon: new URL('@/assets/images/icon2.png', import.meta.url).href
113 }, {
114 label: '存储量(约)',
115 field: 'storageCapacities',
116 value: 0.00,
117 type: 'chnum',
118 fixedNum: 2,
119 unit: 'GB',
120 icon: new URL('@/assets/images/icon3.png', import.meta.url).href
121 }, {
122 label: '总字段数',
123 field: 'columns',
124 value: 0,
125 type: 'chnum',
126 unit: '个',
127 icon: new URL('@/assets/images/icon4.png', import.meta.url).href
128 }, {
129 label: '总行数(约)',
130 field: 'tableRows',
131 value: 0,
132 type: 'chnum',
133 unit: '行',
134 icon: new URL('@/assets/images/icon5.png', import.meta.url).href
135 },
136 {
137 label: '数据总量',
138 field: 'dataCount',
139 value: 0,
140 type: 'chnum',
141 unit: '万个',
142 icon: new URL('@/assets/images/dataCount.png', import.meta.url).href
143 }
144 ],
145 fields: [
146 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center", fixed: "left" },
147 { label: "表中文名称", field: "tableChName", width: 140 },
148 { label: "表英文名称", field: "tableName", width: TableColumnWidth.TABLENAME },
149 { label: "存储量(约)(MB)", field: "storageCapacities", type: 'chnum', fixedNum: 2, width: 140, align: 'right' },
150 { label: "总字段数", field: "columns", type: 'chnum', width: 120, align: 'right' },
151 { label: "总行数(约)", field: "tableRows", type: 'chnum', width: 120, align: 'right' },
152 { label: "索引数", field: "indexes", type: 'chnum', width: 120, align: 'right' },
153 { label: "是否分区表", field: "isPartition", type: 'filter', width: 120 },
154 { label: "表创建时间", field: "createTime", width: TableColumnWidth.DATETIME },
155 { label: "数据采集时间", field: "collectTime", width: TableColumnWidth.DATETIME },
156 ]
157 },
158 })
159
160 const cardLoading = ref(false)
161 const cardList: any = ref([])
162 const treeActive = ref('source')
163
164 const currentTitle = ref("数据库总览")
165 const currTableData: any = ref<Object>({});
166 const page = ref({
167 limit: 50,
168 curr: 1,
169 sizes: [
170 { label: "10", value: 10 },
171 { label: "50", value: 50 },
172 { label: "100", value: 100 },
173 { label: "150", value: 150 },
174 { label: "200", value: 200 },
175 ],
176 });
177 const selectRowData = ref([])
178 const tableFields: any = ref([])
179 const tableData: any = ref([])
180 const tableInfo: any = ref({
181 id: 'data-table',
182 fields: cardMap.value[treeActive.value].fields,
183 data: [],
184 page: {
185 type: "normal",
186 rows: 0,
187 ...page.value,
188 },
189 actionInfo: {
190 label: "操作",
191 type: "btn",
192 width: 60,
193 fixed: 'right',
194 btns: [
195 { label: "详情", value: "detail" },
196 ],
197 },
198 loading: false
199 })
200
201 const getTreeData = () => {
202 treeInfo.value.loading = true
203 let params = {}
204 getMetaTreeData(params).then((res: any) => {
205 treeInfo.value.loading = false
206 if (res.code == proxy.$passCode) {
207 const data = res.data || [];
208 const treeList = Object.keys(data).length ? [data] : [];
209 treeData.value = treeList;
210 if (treeList.length) {
211 expandedKey.value = expandedKey.value.length == 0 ? [treeList[0].guid] : expandedKey.value
212 currentNodeKey.value = currentNodeKey.value == '' ? treeList[0].guid : currentNodeKey.value
213 currNodeInfo.value = treeList[0];
214 }
215 treeInfo.value.data = treeData.value
216 nextTick(() => {
217 treeInfo.value.currentNodeKey = currentNodeKey.value
218 treeInfo.value.expandedKey = expandedKey.value
219 })
220 getFirstPageData()
221 getDatabaseCount({ databaseGuid: currentNodeKey.value === "0" ? "" : currentNodeKey.value })
222 } else {
223 ElMessage.error(res.msg);
224 }
225 }).catch(() => {
226 treeInfo.value.loading = false
227 })
228 }
229
230 const getDatabaseCount = (params) => {
231 cardLoading.value = true
232 getMetaDatabaseCollect(params).then((res: any) => {
233 if (res.code == proxy.$passCode) {
234 const data = res.data || {}
235 databaseCount.value = data.databaseCount
236 cardMap.value[treeActive.value].count.map(item => {
237 item.value = data[item.field]
238 })
239 cardList.value = cardMap.value[treeActive.value].count
240 } else {
241 ElMessage.error(res.msg);
242 }
243 cardLoading.value = false
244 }).catch(() => {
245 cardLoading.value = false
246 })
247 }
248
249 const getFirstPageData = () => {
250 page.value.curr = 1
251 toSearch({})
252 }
253
254 const toSearch = (val: any, clear: boolean = false) => {
255 let params: any = Object.keys(val).length ? { ...val } : {}
256 params.pageIndex = page.value.curr;
257 params.pageSize = page.value.limit;
258 if (treeActive.value == 'database') {
259 params.databaseGuid = currNodeInfo.value.guid
260 }
261 getTableData(params);
262 };
263
264 const getTableData = (params) => {
265 let method: any = null
266 tableInfo.value.loading = true
267 if (treeActive.value == 'source') {
268 method = getMetaDataBase(params)
269 } else {
270 method = getMetaDataSheet(params)
271 }
272 method.then((res: any) => {
273 if (res.code == proxy.$passCode) {
274 const data = res.data || {}
275 tableData.value = data.records || []
276 tableFields.value = cardMap.value[treeActive.value].fields
277 tableInfo.value.id = cardMap.value[treeActive.value].id
278 tableInfo.value.fields = tableFields.value
279 tableInfo.value.data = tableData.value
280 tableInfo.value.page.limit = data.pageSize
281 tableInfo.value.page.curr = data.pageIndex
282 tableInfo.value.page.rows = data.totalRows
283 } else {
284 ElMessage({
285 type: 'error',
286 message: res.msg,
287 })
288 }
289 tableInfo.value.loading = false
290 }).catch(xhr => {
291 tableInfo.value.loading = false
292 })
293 };
294
295 const tableSelectionChange = (val) => {
296 selectRowData.value = val.map((item) => item.guid);
297 };
298
299 const tablePageChange = (info) => {
300 page.value.curr = Number(info.curr);
301 page.value.limit = Number(info.limit);
302 toSearch({});
303 };
304
305 const tableBtnClick = (scope, btn) => {
306 const type = btn.value;
307 const row = scope.row;
308 currTableData.value = row;
309 if (type == "detail") {
310
311 if (treeActive.value == 'source') {
312 let params: any = {}
313 currentTitle.value = row.databaseNameZh
314 params.databaseGuid = row.databaseGuid
315 currNodeInfo.value.guid = row.databaseGuid
316 getDatabaseCount(params)
317 treeActive.value = 'database'
318 getFirstPageData();
319 currentNodeKey.value = row.databaseGuid;
320 expandedKey.value = [row.databaseGuid]
321 nextTick(() => {
322 treeInfo.value.currentNodeKey = currentNodeKey.value;
323 treeInfo.value.expandedKey = expandedKey.value;
324 })
325 } else {
326 treeInfo.value.currentNodeKey = row.guid;
327 router.push({
328 path: '/data-meta/metadata-query/meta-sheet',
329 query: {
330 id: row.guid,
331 name: row.tableChName || row.tableName
332 }
333 })
334 }
335 }
336 };
337
338 const filterValue = (row,num=1) => {
339 let val: any = null
340 if (row.type == 'chnum') {
341 val = changeNum(row.value ? row.value/num : 0, row.fixedNum ?? 0)
342 }
343 return val
344 }
345
346 const nodeClick = (data) => {
347 if (data.type == 1 || data.type == 2) {
348 currNodeInfo.value = data;
349 currentTitle.value = data.name
350 treeActive.value = data.type == 1 ? 'source' : 'database'
351 let params: any = {}
352 if (treeActive.value == 'source') {
353 params.dataSourceGuid = data.guid === "0" ? "" : data.guid
354 } else {
355 params.databaseGuid = data.guid
356 }
357 getDatabaseCount(params)
358 getFirstPageData()
359 } else {
360 router.push({
361 path: '/data-meta/metadata-query/meta-sheet',
362 query: {
363 id: data.guid,
364 name: data.name
365 }
366 })
367 }
368 }
369 const getQueryTreeData = ()=>{
370 treeInfo.value.loading = true
371 let params = {}
372 getMetaTreeData(params).then((res: any) => {
373 treeInfo.value.loading = false
374 if (res.code == proxy.$passCode) {
375 const data = res.data || [];
376 const treeList = Object.keys(data).length ? [data] : [];
377 treeData.value = treeList;
378 treeInfo.value.data = treeData.value
379 if (treeList.length) {
380 expandedKey.value = [dataSourceGuid]
381 currentNodeKey.value = dataSourceGuid as string
382 currNodeInfo.value = {type:2,guid:dataSourceGuid,name:datasourceName}
383 }
384 nextTick(() => {
385 treeInfo.value.currentNodeKey = currentNodeKey.value
386 treeInfo.value.expandedKey = expandedKey.value
387 })
388 nodeClick({type:2,guid:dataSourceGuid,name:datasourceName})
389 dataSourceGuid = ""
390 }
391 }).catch(()=>{
392 treeInfo.value.loading = false
393 })
394 }
395 onBeforeMount(() => {
396 cardList.value = cardMap.value[treeActive.value].count;
397 if(dataSourceGuid){
398 getQueryTreeData()
399 } else {
400 getTreeData()
401 }
402
403 })
404
405 </script>
406
407 <template>
408 <div class="container_wrap full flex">
409 <div class="aside_wrap">
410 <div class="aside_title" >数据库目录列表</div>
411 <Tree :treeInfo="treeInfo" @nodeClick="nodeClick" />
412 </div>
413 <div class="pane-trigger-con"></div>
414 <div class="main_wrap">
415 <div class="table_tool_wrap">
416 <div class="aside_title" style="padding: 0;">
417 <template v-if="currentTitle !=='数据库总览'">
418 <span>数据库:</span>{{ currentTitle }}
419 </template>
420 <template v-else>
421 {{ currentTitle }} <span >({{ databaseCount }})</span>
422 </template>
423 </div>
424 <div class="card_panel">
425 <div class="img_tags_card" v-for="item in cardList" :key="item.label">
426 <img :src="item.icon" alt="">
427 <div class="tags_item">
428 <p class="tag_text">{{ item.label }}</p>
429 <template v-if="item.field==='dataCount'">
430 <el-tooltip
431 placement="top-start"
432 trigger="hover"
433 effect="light"
434 :show-after="400"
435 :content="filterValue(item)+'个'"
436 >
437 <p class="tag_num"><span>{{ filterValue(item,10000)}}</span><span class="unit">{{ ' ' + item.unit }}</span></p>
438 </el-tooltip>
439
440 </template>
441 <template v-else>
442 <p class="tag_num"><span>{{ filterValue(item) }}</span><span class="unit">{{ ' ' + item.unit }}</span></p>
443 </template>
444
445 </div>
446 </div>
447 </div>
448 </div>
449 <div class="table_panel_wrap full">
450 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tableSelectionChange="tableSelectionChange"
451 @tablePageChange="tablePageChange" />
452 </div>
453 </div>
454 </div>
455 </template>
456
457 <style lang="scss" scoped>
458 .container_wrap {
459 .aside_wrap {
460 width: 200px;
461 }
462
463 .main_wrap .table_tool_wrap {
464 height: auto;
465
466 .card_panel {
467 height: 48px;
468 // padding: 24px 0;
469 margin-bottom: 20px;
470 padding-top:10px ;
471 display: flex;
472 // justify-content: space-between;
473 align-items: center;
474
475 .img_tags_card {
476 width: 208px;
477 display: flex;
478 align-items: center;
479
480 img {
481 width: 48px;
482 height: 48px;
483 }
484
485 .tags_item {
486 height: 50px;
487 margin-left: 12px;
488 display: flex;
489 flex-direction: column;
490 justify-content: space-between;
491
492 p {
493 font-size: 14px;
494 color: var(--el-text-color-regular);
495 margin: 0;
496 line-height: 21px;
497
498 &.tag_num {
499 color: var(--el-color-regular);
500 font-size: 16px;
501 font-weight: 600;
502 line-height: 24px;
503
504 .unit {
505 font-size: 14px;
506 }
507 }
508 }
509 }
510 }
511 }
512 }
513
514 .table_panel_wrap.full {
515 height: calc(100% - 104px);
516 }
517
518 }
519
520 .tree_panel {
521 height: calc(100% - 36px);
522 padding-top: 0;
523
524 :deep(.el-tree) {
525 margin: 0;
526 overflow: hidden auto;
527 }
528 }
529 .aside_title {
530 padding: 0 8px;
531 font-size: 14px;
532 color: var(--el-color-regular);
533 height: 36px;
534 line-height: 36px;
535 font-weight: 600;
536 }
537 </style>
1 <route lang="yaml">
2 name: analysisLog
3 </route>
4
5 <script lang="ts" setup name="analysisLog">
6 import { ref } from "vue";
7 import { useRouter, useRoute } from "vue-router";
8 import Table from "@/components/Table/index.vue";
9 import { getWordLogList } from "@/api/modules/dataQualityWord";
10 import { ElMessage } from "element-plus";
11
12 const { proxy } = getCurrentInstance() as any;
13
14 const router = useRouter();
15 const route = useRoute();
16 const guid = route.query.guid;
17 const wordName = route.query.name;
18
19 const page = ref({
20 limit: 50,
21 curr: 1,
22 sizes: [
23 { label: "10", value: 10 },
24 { label: "50", value: 50 },
25 { label: "100", value: 100 },
26 { label: "150", value: 150 },
27 { label: "200", value: 200 },
28 ],
29 });
30 const tableInfo = ref({
31 id: "word-log-table",
32 loading: false,
33 fields: [
34 { label: "报告名称", field: "analysisReportName", width: 230 },
35 {
36 label: "方案类型", field: "analysisReportType", width: 100, getName: (scope) => {
37 let planType = scope.row.analysisReportType;
38 return planType == 1 ? '表' : (planType == 2 ? '数据库' : (planType == 4 ? '数据同步' : '分组'));
39 }
40 },
41 { label: "报告对象", field: "qualityModelName", width: 180, },
42 { label: "质量评分", field: "qualityScore", width: 100, align: "right" },
43 { label: "最后执行时间", field: "execTime", width: 180, },
44 { label: "执行状态", field: "execResult", type: "tag", width: 120, align: "center" },
45 ],
46 data: [],
47 page: {
48 type: "normal",
49 rows: 0,
50 ...page.value,
51 },
52 actionInfo: {
53 label: "操作",
54 type: "btn",
55 width: 100,
56 fixed: 'right',
57 btns: (scope) => {
58 return [{ label: "查看报告", value: "reportView", disabled: scope.row['execResult'] != 'Y' }];
59 }
60 }
61 });
62
63 const getTableData = () => {
64 tableInfo.value.loading = true;
65 getWordLogList({ pageIndex: page.value.curr, pageSize: page.value.limit, reportGuid: guid }).then((res: any) => {
66 tableInfo.value.loading = false;
67 if (res.code == proxy.$passCode) {
68 const data = res.data || {}
69 tableInfo.value.data = data.records || []
70 tableInfo.value.page.limit = data.pageSize
71 tableInfo.value.page.curr = data.pageIndex
72 tableInfo.value.page.rows = data.totalRows
73 } else {
74 ElMessage.error(res.msg);
75 }
76 })
77 };
78
79 const tableBtnClick = (scope, btn) => {
80 const type = btn.value;
81 const row = scope.row;
82 if (type == 'reportView') {
83 router.push({
84 name: 'analysisReport',
85 query: {
86 planGuid: row.planGuid,
87 reportExecGuid: row.guid,
88 name: wordName
89 }
90 });
91 }
92 };
93
94 onBeforeMount(() => {
95 getTableData();
96 });
97
98 </script>
99
100 <template>
101 <div class="container_wrap">
102 <div class="table_panel_wrap">
103 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" />
104 </div>
105 </div>
106 </template>
107
108 <style lang="scss" scoped>
109 .container_wrap {
110 padding: 0;
111
112 .table_panel_wrap {
113 height: 100%;
114 padding: 16px 16px 0;
115 }
116 }
117 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: analysisReport
3 </route>
4
5 <script lang="ts" setup name="analysisReport">
6 import { ref } from "vue";
7 import { useRouter, useRoute } from "vue-router";
8 import Table from "@/components/Table/index.vue";
9 import {
10 getReportDetail,
11 getLargeCategoryScore,
12 getPlanDetail,
13 getTableRuleDetail,
14 htmlToWord
15 } from '@/api/modules/dataQualityWord';
16 import * as echarts from 'echarts';
17 import { QuestionFilled } from "@element-plus/icons-vue";
18 import { ElMessage, ElMessageBox } from "element-plus";
19 import { changeNum, tagMethod, tagType } from '@/utils/common';
20 //报告导出word
21 import html2canvas from 'html2canvas';
22
23 const router = useRouter();
24 const route = useRoute();
25 const reportExecGuid = route.query.reportExecGuid;
26 const planGuid = route.query.planGuid;
27 const wordName = ref(route.query.name);
28
29 const { proxy } = getCurrentInstance() as any;
30
31 const detailInfo: any = ref({});
32 const fullscreenLoading = ref(false);
33 const loadingText = ref('报告图表生成中,请勿关闭浏览器...');
34
35 const getReportDetailInfo = () => {
36 let ps: any = [];
37 fullscreenLoading.value = true;
38 loadingText.value = '报告图表生成中,请勿关闭浏览器...';
39 ps.push(getReportDetail({ reportExecGuid: reportExecGuid, planGuid: planGuid }).then((res: any) => {
40 if (res.code == proxy.$passCode) {
41 let data = res.data || {};
42 detailInfo.value = data;
43 barChartData.value = data.qualityScoreLine?.slice(0, 12) || [];
44 planDetailTableInfo.value.data = [{
45 guid: '1',
46 planName: data.planName,
47 planType: data.analysisReportType == 1 ? '表' : (data.analysisReportType == 2 ? '数据库' : (data.analysisReportType == 4 ? '数据同步' : '分组')),
48 execDuration: data.planExecDuration != null ? changeNum(data.planExecDuration ?? 0) : '--',
49 execTime: data.execTime,
50 qualityTableNum: data.qualityTableNum != null ? changeNum(data.qualityTableNum ?? 0) : '--',
51 ruleCount: data.ruleCount != null ? changeNum(data.ruleCount ?? 0) : '--',
52 totalNum: data.totalNum != null ? changeNum(data.totalNum ?? 0) : '--',
53 qualifiedNum: data.qualifiedNum != null ? changeNum(data.qualifiedNum ?? 0) : '--',
54 unqualifiedNum: data.unqualifiedNum != null ? changeNum(data.unqualifiedNum ?? 0) : '--',
55 qualityScore: data.qualityScore ?? '--',
56 qualifiedRate: data.qualifiedRate != null ? (changeNum((data.qualifiedRate ?? 0) * 100, 2, true) + '%') : '--'
57 }]
58 } else {
59 ElMessage.error(res.msg);
60 }
61 }))
62 ps.push(getLargeCategoryScore(reportExecGuid).then((res: any) => {
63 if (res.code == proxy.$passCode) {
64 let data = res.data || {};
65 qualityRuleDetailTableInfo.value.data = data.largeCategoryScoreList || [];
66 if (!qualityRuleDetailTableInfo.value.data.length) {
67 qualityRuleDetailTableInfo.value.footerHtml = '';
68 } else {
69 qualityRuleDetailTableInfo.value.footerHtml = `<span>质量得分</span><span>∑规则得分*权重</span>`;
70 }
71 radarChartData.value = data.largeCategoryScore || {};
72 } else {
73 ElMessage.error(res.msg);
74 }
75 }))
76 /** 表明细,单表时没有。 */
77 ps.push(getPlanDetail({ reportExecGuid: reportExecGuid, planGuid: planGuid }).then((res: any) => {
78 if (res.code == proxy.$passCode) {
79 let data = res.data || [];
80 modelDetailTableInfo.value.data = data;
81 } else {
82 ElMessage.error(res.msg);
83 }
84 }))
85 ps.push(getTableRuleDetail(reportExecGuid).then((res: any) => {
86 if (res.code == proxy.$passCode) {
87 let data = res.data || [];
88 modelRuleDetailTableInfo.value.data = data;
89 } else {
90 ElMessage.error(res.msg);
91 }
92 }))
93 Promise.all(ps).then(() => {
94 fullscreenLoading.value = false;
95 });
96 }
97
98 /** 方案明细表。 */
99 const planDetailTableInfo: any = ref({
100 id: "plan-detail-table",
101 loading: false,
102 height: 'auto',
103 minPanelHeight: '60px',
104 minHeight: '60px',
105 fields: [
106 { label: "方案名称", field: "planName", width: 150 },
107 { label: "方案类型", field: "planType", width: 100 },
108 { label: "评估时间", field: "execTime", width: 180, },
109 {
110 label: "耗时(秒)", field: "execDuration", width: 100, align: 'right'
111 },
112 { label: "评估表数", field: "qualityTableNum", width: 100, align: 'right' },
113 { label: "规则数", field: "ruleCount", width: 100, align: 'right' },
114 { label: "评估总数", field: "totalNum", width: 100, align: 'right' },
115 { label: "合格条数", field: "qualifiedNum", width: 100, align: 'right' },
116 { label: "不合格条数", field: "unqualifiedNum", width: 100, align: 'right' },
117 { label: "合格率", field: "qualifiedRate", width: 100, align: 'right' },
118 { label: "质量评分", field: "qualityScore", width: 100, align: "right" }
119 ],
120 data: [],
121 showPage: false,
122 actionInfo: {
123 show: false
124 }
125 });
126
127 /** 数据质量一级指标得分明细 */
128 const qualityRuleDetailTableInfo = ref({
129 id: "quality-rule-detail-table",
130 loading: false,
131 minHeight: '60px',
132 footerClass: 'last-row',
133 footerHtml: `<span>质量得分</span><span></span>`,
134 fields: [
135 { label: "规则大类", field: "largeCategoryName", width: 100 },
136 {
137 label: "规则数", field: "ruleCount", width: 100, align: 'right', getName: (scope) => {
138 return scope.row.ruleCount != null ? changeNum(scope.row.ruleCount ?? 0) : '--';
139 }
140 },
141 {
142 label: "规则得分", field: "largeCategoryScore", width: 100, align: "right", getName: (scope) => {
143 return scope.row.largeCategoryScore != null ? changeNum(scope.row.largeCategoryScore ?? 0, 2, true) : '--';
144 }
145 },
146 {
147 label: "权重", field: "ruleLargeWeight", width: 100, align: "right", getName: (scope) => {
148 return scope.row.ruleLargeWeight != null ? scope.row.ruleLargeWeight.toFixed(2) : '--';
149 }
150 },
151 {
152 label: "质量评分", field: "qualityScore", width: 100, align: "right", getName: (scope) => {
153 return scope.row.qualityScore != null ? changeNum(scope.row.qualityScore ?? 0, 2, true) : '--';
154 }
155 }
156 ],
157 data: [],
158 showPage: false,
159 actionInfo: {
160 show: false
161 }
162 });
163
164 /** 表明细。 */
165 const modelDetailTableInfo = ref({
166 id: "model-detail-table",
167 loading: false,
168 height: 'auto',
169 minPanelHeight: '60px',
170 minHeight: '60px',
171 fields: [
172 { label: "序号", type: "index", width: 56, align: "center" },
173 { label: "表名", field: "qualityModelName", width: 140 },
174 { label: "执行结果", field: "execResult", type: "tag", width: 100, align: "center" },
175 { label: "评估时间", field: "execTime", width: 180, },
176 {
177 label: "耗时(秒)", field: "execDuration", width: 100, align: "right", getName: (scope) => {
178 return scope.row.execDuration != null ? changeNum(scope.row.execDuration ?? 0) : '--';
179 }
180 },
181 {
182 label: "规则数", field: "ruleNum", width: 100, align: "right", getName: (scope) => {
183 return scope.row.ruleNum != null ? changeNum(scope.row.ruleNum ?? 0) : '--';
184 }
185 },
186 {
187 label: "评估总数", field: "totalNum", width: 100, align: "right", getName: (scope) => {
188 return scope.row.totalNum != null ? changeNum(scope.row.totalNum ?? 0) : '--';
189 }
190 },
191 {
192 label: "合格条数", field: "qualifiedNum", width: 100, align: "right", getName: (scope) => {
193 return scope.row.qualifiedNum != null ? changeNum(scope.row.qualifiedNum ?? 0) : '--';
194 }
195 },
196 {
197 label: "不合格条数", field: "unqualifiedNum", width: 100, align: "right", getName: (scope) => {
198 return scope.row.unqualifiedNum != null ? changeNum(scope.row.unqualifiedNum ?? 0) : '--';
199 }
200 },
201 {
202 label: "合格率", field: "qualifiedRate", width: 100, align: "right", getName: (scope) => {
203 return scope.row.qualifiedRate != null ? ((scope.row.qualifiedRate ?? 0).toFixed(2) + '%') : '--';
204 }
205 },
206 { label: "评估范围", field: "dataRange", width: 180 },
207 ],
208 data: [{
209 guid: '1',
210 executeState: 'N'
211 }],
212 showPage: false,
213 actionInfo: {
214 show: false
215 }
216 });
217
218 /** 规则明细。 */
219 const modelRuleDetailTableInfo = ref({
220 id: "model-rule-detail-table",
221 loading: false,
222 height: 'auto',
223 minPanelHeight: '60px',
224 minHeight: '60px',
225 fields: [
226 { label: "序号", type: "index", width: 56, align: "center" },
227 { label: "表名", field: "qualityModelName", width: 140 },
228 { label: "规则类型", field: "ruleName", width: 140 },
229 { label: "规则名称", field: "ruleConfName", width: 140 },
230 { label: "规则大类", field: "largeCategory", width: 100 },
231 { label: "规则小类", field: "smallCategory", width: 140 },
232 { label: "规则字段", field: "ruleField", width: 140 },
233 { label: "执行结果", field: "execResult", type: "tag", width: 100, align: "center" },
234 {
235 label: "评估总数", field: "totalNum", width: 100, align: "right", getName: (scope) => {
236 return scope.row.totalNum != null ? changeNum(scope.row.totalNum ?? 0) : '--';
237 }
238 },
239 {
240 label: "合格条数", field: "qualifiedNum", width: 100, align: "right", getName: (scope) => {
241 return scope.row.qualifiedNum != null ? changeNum(scope.row.qualifiedNum ?? 0) : '--';
242 }
243 },
244 {
245 label: "不合格条数", field: "unqualifiedNum", width: 100, align: "right", getName: (scope) => {
246 return scope.row.unqualifiedNum != null ? changeNum(scope.row.unqualifiedNum ?? 0) : '--';
247 }
248 },
249 {
250 label: "合格率", field: "qualifiedRate", width: 100, align: "right", getName: (scope) => {
251 return scope.row.qualifiedRate != null ? ((scope.row.qualifiedRate ?? 0).toFixed(2) + '%') : '--';
252 }
253 }
254 ],
255 data: [],
256 showPage: false,
257 actionInfo: {
258 show: false
259 }
260 });
261
262 /** 柱形图数据数组 */
263 const barChartData: any = ref([]);
264 /** 雷达图数据数组 */
265 const radarChartData: any = ref([]);
266
267 let barChart: any = null;
268 let radarChart: any = null;
269
270 let barChartWord: any = null;
271 let radarChartWord: any = null;
272
273 /** 设置柱形图option,默认只 展示12次的 */
274 const setBarChartOption = (barChart, isWord = false) => {
275 return new Promise((resolve, reject) => {
276 if (!barChartData.value.length) {
277 let option1 = {
278 title: [
279 {
280 text: "",
281 left: "30px",
282 top: "30px",
283 },
284 {
285 text: "暂无数据",
286 left: "center",
287 top: "center",
288 textStyle: {
289 fontStyle: "normal",
290 fontWeight: "400",
291 fontSize: 18,
292 },
293 },
294 ],
295 };
296 barChart.setOption(option1, true);
297 window.addEventListener("resize", () => {
298 barChart.resize();
299 });
300 return;
301 }
302 let itemXAxisData: any = [];
303 let itemYAxisData1: any = [];
304 let itemYAxisData2: any = [];
305 barChartData.value.forEach(d => {
306 itemXAxisData.push(d['execTime']);
307 itemYAxisData1.push(d['qualityScore']);
308 itemYAxisData2.push((d['qualifiedRate'] ?? 0) * 100);
309 })
310 let option = {
311 textStyle: {
312 fontFamily: 'SimSun'
313 },
314 color: ['#5B8FF9', '#FF4E00', '#867EEC', '#FDBC3E', '#F48A64', '#276FF5', '#46D0B5'],
315 title: {
316 show: false,
317 text: '评分趋势',
318 left: '30px',
319 top: '30px',
320 },
321 tooltip: {
322 trigger: 'axis',
323 axisPointer: {
324 type: 'cross',
325 crossStyle: {
326 color: '#999'
327 }
328 },
329 textStyle: {
330 align: 'left'
331 },
332 },
333 legend: {
334 right: 0,
335 textStyle: {
336 fontSize: 14
337 },
338 data: ['评分', '合格率']
339 },
340 grid: {
341 left: 30,
342 right: 60,
343 bottom: 5,
344 containLabel: true
345 },
346 xAxis: {
347 type: 'category',
348 boundaryGap: false,
349 nameTextStyle: {
350 color: '#000000'
351 },
352 axisLabel: {
353 interval: isWord ? 'auto' : 0,
354 textStyle: {
355 color: '#000000'
356 },
357 formatter: function (value) {
358 if (!value) {
359 return value;
360 }
361 let v = value.split(' ');
362 return v.join('\n');
363 }
364 },
365 axisTick: {
366 show: false,
367 },
368 axisLine: {
369 lineStyle: {
370 color: '#d9d9d9'
371 }
372 },
373 data: itemXAxisData
374 },
375 yAxis: {
376 type: 'value',
377 name: '',
378 min: 0,
379 max: 100,
380 nameTextStyle: {
381 color: '#000000'
382 },
383 axisLabel: {
384 textStyle: {
385 color: '#000000'
386 }
387 },
388 axisLine: {
389 //y轴
390 show: false
391 }
392 },
393 series: [{
394 name: '评分',
395 type: 'line',
396 data: itemYAxisData1,
397 label: {
398 show: false,
399 },
400 yAxisIndex: 0
401 }, {
402 name: '合格率',
403 type: 'line',
404 data: itemYAxisData2,
405 label: {
406 show: false,
407 },
408 tooltip: {
409 valueFormatter: function (value) {
410 return changeNum(value, 2, true) + '%';
411 }
412 },
413 yAxisIndex: 0
414 }]
415 };
416 option && barChart.setOption(option, true);
417 barChart.on('finished', () => {
418 resolve(true);
419 });
420 window.addEventListener('resize', () => {
421 barChart.resize();
422 });
423 });
424 }
425
426 /** 设置雷达图option. */
427 const setRadarChartOption = (radarChart) => {
428 return new Promise((resolve, reject) => {
429 let indicator = [{ name: '规范性', max: 100, min: 0, color: '#000' },
430 { name: '完整性', max: 100, min: 0, color: '#000' },
431 { name: '一致性', max: 100, min: 0, color: '#000' },
432 { name: '准确性', max: 100, min: 0, color: '#000' },
433 { name: '时效性', max: 100, min: 0, color: '#000' },
434 { name: '可访问性', max: 100, min: 0, color: '#000' }]
435 let data: any = [];
436 for (const key in radarChartData.value) {
437 let index = indicator.findIndex(i => i.name == key);
438 index > -1 && (data[index] = changeNum(radarChartData.value[key] ?? 0, 2));
439 }
440 let option = {
441 textStyle: {
442 fontFamily: 'SimSun'
443 },
444 color: ['#5B8FF9', '#6DD18E', '#867EEC', '#FDBC3E', '#F48A64', '#276FF5', '#46D0B5'],
445 title: {
446 show: false,
447 text: '数据质量一级指标得分',
448 left: '30px',
449 top: '30px',
450 },
451 tooltip: {
452 axisPointer: {
453 type: 'cross',
454 crossStyle: {
455 color: '#999'
456 }
457 },
458 textStyle: {
459 align: 'left'
460 },
461 },
462 legend: {
463 show: false
464 },
465 grid: {
466 left: 0,
467 bottom: 0,
468 containLabel: true
469 },
470 radar: {
471 indicator: indicator,
472 axisLabel: {
473 show: false
474 },
475 axisLine: {
476 lineStyle: {
477 color: '#d9d9d9'
478 }
479 },
480 axisTick: {
481 lineStyle: {
482 color: '#d9d9d9'
483 }
484 },
485 splitLine: {
486 lineStyle: {
487 color: '#d9d9d9'
488 }
489 }
490 },
491 series: [
492 {
493 type: 'radar',
494 data: [{
495 name: '数据质量一级指标得分',
496 value: data
497 }],
498 label: {
499 show: true,
500 position: 'bottom',
501 distance: 1,
502 color: '#000'
503 },
504 areaStyle: {
505 color: '#e8eefc'
506 }
507 }
508 ]
509 };
510 option && radarChart.setOption(option, true);
511 radarChart.on('finished', () => {
512 resolve(true);
513 });
514 window.addEventListener('resize', () => {
515 radarChart.resize();
516 });
517 });
518 }
519
520 watch(() => barChartData.value, (val) => {
521 setBarChartOption(barChart);
522 barChartWord && setBarChartOption(barChartWord, true);
523 })
524
525 watch(() => radarChartData.value, (val) => {
526 setRadarChartOption(radarChart);
527 radarChartWord && setRadarChartOption(radarChartWord);
528 })
529
530 onBeforeMount(() => {
531 getReportDetailInfo();
532 })
533
534 onMounted(() => {
535 barChart = echarts.init(document.getElementById('bar'));
536 radarChart = echarts.init(document.getElementById('radar'));
537 setBarChartOption(barChart);
538 setRadarChartOption(radarChart);
539 })
540
541 const domClone: any = ref(null);
542
543 const convertChartsToBase64 = (contentDocument) => {
544 // 找到所有的图表 (echart)
545 let canvases = contentDocument.querySelectorAll('#bar-word');
546 // 遍历图表,转换为 base64 静态图片
547 canvases.forEach((canvas, i) => {
548 let url = barChartWord.getDataURL();
549 let img = document.createElement('img');
550 if (url) {
551 // img.src = url.split(',')[1];
552 img.src = url;
553 img.width = 620;
554 img.height = 400;
555 img.crossOrigin = 'Anonymous';
556 }
557 canvas.parentNode.replaceChild(img, canvas);
558 });
559 let canvases1 = contentDocument.querySelectorAll('#radar-word');
560 canvases1.forEach((canvas, i) => {
561 let url = radarChartWord.getDataURL();
562 let img = document.createElement('img');
563 if (url) {
564 // img.src = url.split(',')[1];
565 img.src = url;
566 img.width = 620;
567 img.height = 400;
568 img.crossOrigin = 'Anonymous';
569 }
570 canvas.parentNode.replaceChild(img, canvas);
571 });
572 }
573
574 const convertHtml2Img = (dom, domClone) => {
575 const element = <HTMLElement>dom.querySelector('.kpi-content')
576 if (!element) {
577 return Promise.resolve();
578 }
579 return html2canvas(element, {
580 allowTaint: true,
581 useCORS: true,
582 scale: 1,
583 }).then((canvas: any) => {
584 document.documentElement.scrollTop = 0;
585 document.body.scrollTop = 0;
586 element.parentNode && ((<HTMLElement>element.parentNode).scrollTop = 0);
587 let url = canvas.toDataURL('image/jpeg');
588 let img = document.createElement('img');
589 if (url) {
590 // img.src = url.split(',')[1];
591 img.src = url;
592 img.width = 620;
593 img.height = 80;
594 img.crossOrigin = 'Anonymous';
595 }
596 const copyElement = <HTMLElement>domClone.querySelector('.kpi-content')
597 copyElement.parentNode?.replaceChild(img, copyElement);
598 })
599 }
600
601 const report = ref();
602
603 const isWordStyle = ref(false);
604
605 const isTransfered = ref(false);
606
607 const downloadBtnDisable = ref(false);
608
609 const transfer = () => {
610 isWordStyle.value = true;
611 nextTick(() => {
612 if (!isTransfered.value) {
613 barChartWord = echarts.init(document.getElementById('bar-word'));
614 downloadBtnDisable.value = true;
615 let ps1 = setBarChartOption(barChartWord, true);
616 radarChartWord = echarts.init(document.getElementById('radar-word'));
617 let ps2 = setRadarChartOption(radarChartWord);
618 Promise.all([ps1, ps2]).then(res => {
619 downloadBtnDisable.value = false;
620 isTransfered.value = true;
621 })
622 }
623 });
624 };
625
626 const getHTML = (reportResultContent) => {
627 let html = reportResultContent;
628 html = html.replace(/"/g, "'");
629 return html;
630 };
631
632 const download = () => {
633 let dom = domClone.value || (domClone.value = document.createElement('div'));
634 dom.innerHTML = report.value.innerHTML;
635 convertChartsToBase64(dom);
636 fullscreenLoading.value = true;
637 loadingText.value = '报告正在下载中,请勿关闭浏览器...';
638 convertHtml2Img(report.value, dom).then(() => {
639 htmlToWord({ html: getHTML(dom.innerHTML) }).then((res: any) => {
640 fullscreenLoading.value = false;
641 // let blob = new Blob([res.data], { type: 'application/msword' });
642 let objectUrl = URL.createObjectURL(res);
643 const link = document.createElement('a')
644 link.download = wordName.value + '.docx';
645 link.href = objectUrl
646 link.click()
647 })
648 // // 下载word 需要接口。
649 // const converted = htmlDocx.asBlob(`
650 // <html xmlns:o=\'urn:schemas-microsoft-com:office:office\' xmlns:w=\'urn:schemas-microsoft-com:office:word\' xmlns=\'http://www.w3.org/TR/REC-html40\'><head><style>
651 // ${document.head.outerHTML}
652 // </head>
653 // <body>
654 // ${dom.outerHTML}
655 // </body>
656 // </html>`)
657 // converted.then((res: any) => {
658 // // 导出无样式。
659 // console.log(res);
660 // let objectUrl = URL.createObjectURL(res);
661 // const link = document.createElement('a')
662 // link.download = detailInfo.value.analysisReportName + '.docx';
663 // link.href = objectUrl
664 // link.click()
665 // })
666 })
667 }
668
669 </script>
670
671 <template>
672 <div class="container_wrap" v-loading="fullscreenLoading" :element-loading-text="loadingText">
673 <div v-show="!isWordStyle" class="header-title">
674 <div>
675 <span>{{ `【${(detailInfo.analysisReportType == 1 ? detailInfo.qualityModelName :
676 (detailInfo.analysisReportType
677 == 3 ? detailInfo.qualityModelGroupName : detailInfo.dataSourceName)) ?? '--'}】` + '数据质量评估报告' }}</span>
678 <span class="time-detail">{{ '生成时间:' + detailInfo.execTime }}</span>
679 </div>
680 <el-button type="primary" @click="transfer">生成Word报告</el-button>
681 <!-- <el-button @click="download">下载 Word</el-button> -->
682 </div>
683 <div v-show="!isWordStyle" class="header-detail">
684 <template v-if="detailInfo.analysisReportType == 1">
685 <span>{{ '表名称:' + (detailInfo.qualityModelName ?? '--') }}</span>
686 <span style="margin-left: 40px;">{{ '所属主题:' + (detailInfo.subjectDomainName ?? '--') }}</span>
687 <span style="margin-left: 40px;">{{ '所属数据源:' + (detailInfo.dataSourceName ?? '--') }}</span>
688 </template>
689 <span v-else-if="detailInfo.analysisReportType == 3">{{ '分组名称:' + (detailInfo.qualityModelGroupName ?? '--')
690 }}</span>
691 <span v-else>{{ '数据库名称:' + (detailInfo.dataSourceName ?? '--') }}</span>
692 </div>
693 <div v-show="!isWordStyle" class="content-main">
694 <div class="title">数据汇总</div>
695 <div class="kpi-content">
696 <div class="border-content">
697 <span class="number score-color">{{ detailInfo.qualityScore ?? '--' }}</span>
698 <div class="text">质量评分<el-tooltip placement="top" effect="light" popper-class="table_tooltip">
699 <template #content>
700 <div style="max-width: 236px;">
701 质量一级指标得分*权重
702 </div>
703 </template>
704 <el-icon style="margin-left: 2px;">
705 <QuestionFilled />
706 </el-icon>
707 </el-tooltip></div>
708 </div>
709 <div v-if="detailInfo.analysisReportType != 1" class="border-content ml16">
710 <span class="number">{{ detailInfo.qualityTableNum != null ? changeNum(detailInfo.qualityTableNum ?? 0) :
711 '--'
712 }}</span>
713 <span class="text">评估表数</span>
714 </div>
715 <div class="border-content ml16">
716 <span class="number num-color">{{ detailInfo.ruleCount != null ? changeNum(detailInfo.ruleCount ?? 0) : '--'
717 }}</span>
718 <span class="text">质量规则数</span>
719 </div>
720 <div class="border-content ml16">
721 <span class="number num-color">{{ detailInfo.totalNum != null ? changeNum(detailInfo.totalNum ?? 0) : '--'
722 }}</span>
723 <span class="text">评估总数</span>
724 </div>
725 <div class="border-content ml16">
726 <span class="number pass-color">{{ detailInfo.qualifiedNum != null ? changeNum(detailInfo.qualifiedNum ?? 0)
727 :
728 '--' }}</span>
729 <span class="text">合格条数</span>
730 </div>
731 <div class="border-content ml16">
732 <span class="number no-pass-color">{{ detailInfo.unqualifiedNum != null ?
733 changeNum(detailInfo.unqualifiedNum ??
734 0) : '--' }}</span>
735 <span class="text">不合格条数</span>
736 </div>
737 <div class="border-content ml16">
738 <span class="number rate-color">{{ detailInfo.qualifiedRate != null ? (changeNum((detailInfo.qualifiedRate
739 ?? 0) *
740 100, 2, true) + '%') : '--' }}</span>
741 <span class="text">合格率</span>
742 </div>
743 </div>
744 <div class="title">评分趋势</div>
745 <div class="content-chart-bar" id="bar">
746 </div>
747 <div class="content-radar-table">
748 <div class="content-two">
749 <div class="title">数据质量一级指标得分</div>
750 <div class="content-chart-radar" id="radar">
751 </div>
752 </div>
753 <div class="content-two">
754 <div class="title">数据质量一级指标得分明细</div>
755 <Table :tableInfo="qualityRuleDetailTableInfo" />
756 </div>
757 </div>
758 <div class="title">评估方案明细</div>
759 <Table :tableInfo="planDetailTableInfo" />
760 <template v-if="detailInfo.analysisReportType != 1">
761 <div class="title">表明细</div>
762 <Table :tableInfo="modelDetailTableInfo" />
763 </template>
764 <div class="title">规则明细</div>
765 <Table :tableInfo="modelRuleDetailTableInfo" />
766 </div>
767 <div v-show="isWordStyle" class="word-report">
768 <div class="word-btn">
769 <span>{{ `【${(detailInfo.analysisReportType == 1 ? detailInfo.qualityModelName :
770 (detailInfo.analysisReportType
771 == 3 ? detailInfo.qualityModelGroupName : detailInfo.dataSourceName)) ?? '--'}】` + '数据质量评估报告' }}</span>
772 <div>
773 <el-button @click="isWordStyle = false">返回</el-button>
774 <el-button type="primary" :disabled="downloadBtnDisable" @click="download">下载 Word</el-button>
775 </div>
776 </div>
777 <div class="word-report-main" ref="report">
778 <div style="width: 625px;">
779 <div style="text-align: center;padding: 8px 0px;">
780 <div
781 style="height: 40px;display: block;line-height: 32px;font-size: 18px;color: #212121;font-weight: 600;text-align: center;">
782 {{ `【${(detailInfo.analysisReportType == 1 ? detailInfo.qualityModelName :
783 (detailInfo.analysisReportType
784 == 3 ? detailInfo.qualityModelGroupName : detailInfo.dataSourceName)) ?? '--'}】` + '数据质量评估报告' }}</div>
785 <div style="font-size: 14px;color: #666666;font-weight: 400;">{{ '生成时间:' + detailInfo.execTime }}</div>
786 </div>
787 <div style=" height: 40px;line-height: 40px;font-size: 14px;color: #666666;">
788 <template v-if="detailInfo.analysisReportType == 1">
789 <span>{{ '表名称:' + (detailInfo.qualityModelName ?? '--') }}</span>
790 <span style="margin-left: 40px;">{{ '所属主题:' + (detailInfo.subjectDomainName ?? '--') }}</span>
791 <span style="margin-left: 40px;">{{ '所属数据源:' + (detailInfo.dataSourceName ?? '--') }}</span>
792 </template>
793 <span v-else-if="detailInfo.analysisReportType == 3">{{ '分组名称:' + (detailInfo.qualityModelGroupName ?? '--')
794 }}</span>
795 <span v-else>{{ '数据库名称:' + (detailInfo.dataSourceName ?? '--') }}</span>
796 </div>
797 <div
798 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
799 数据汇总</div>
800 <div class="kpi-content">
801 <div class="border-content" style="padding-left: 8px;min-width: 70px;height: 72px">
802 <span class="number score-color">{{ detailInfo.qualityScore ?? '--' }}</span>
803 <div class="text">质量评分</div>
804 </div>
805 <div v-if="detailInfo.analysisReportType != 1" class="border-content"
806 style="padding-left: 8px;margin-left: 8px;min-width: 70px;height: 72px">
807 <span class="number">{{ detailInfo.qualityTableNum != null ? changeNum(detailInfo.qualityTableNum ?? 0)
808 :
809 '--'
810 }}</span>
811 <span class="text">评估表数</span>
812 </div>
813 <div class="border-content" style="padding-left: 8px;margin-left: 8px;min-width: 84px;height: 72px">
814 <span class="number num-color">{{ detailInfo.ruleCount != null ? changeNum(detailInfo.ruleCount ?? 0) :
815 '--'
816 }}</span>
817 <span class="text">质量规则数</span>
818 </div>
819 <div class="border-content" style="padding-left: 8px;margin-left: 8px;min-width: 70px;height: 72px">
820 <span class="number num-color">{{ detailInfo.totalNum != null ? changeNum(detailInfo.totalNum ?? 0) :
821 '--'
822 }}</span>
823 <span class="text">评估总数</span>
824 </div>
825 <div class="border-content" style="padding-left: 8px;margin-left: 8px;min-width: 70px;height: 72px">
826 <span class="number pass-color">{{ detailInfo.qualifiedNum != null ? changeNum(detailInfo.qualifiedNum
827 ?? 0)
828 :
829 '--' }}</span>
830 <span class="text">合格条数</span>
831 </div>
832 <div class="border-content" style="padding-left: 8px;margin-left: 8px;min-width: 84px;height: 72px">
833 <span class="number no-pass-color">{{ detailInfo.unqualifiedNum != null ?
834 changeNum(detailInfo.unqualifiedNum ??
835 0) : '--' }}</span>
836 <span class="text">不合格条数</span>
837 </div>
838 <div class="border-content" style="padding-left: 8px;margin-left: 8px;min-width: 70px;height: 72px">
839 <span class="number rate-color">{{ detailInfo.qualifiedRate != null ?
840 (changeNum((detailInfo.qualifiedRate
841 ?? 0) *
842 100, 2, true) + '%') : '--' }}</span>
843 <span class="text">合格率</span>
844 </div>
845 </div>
846 <div
847 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
848 评分趋势</div>
849 <div class="content-chart-bar" style="border:none;padding: 8px 0px;" id="bar-word">
850 </div>
851 <div
852 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
853 数据质量一级指标得分</div>
854 <div class="content-chart-bar" style="border:none;height: 380px;padding: 8px 0px;" id="radar-word"></div>
855 <div
856 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
857 数据质量一级指标得分明细</div>
858 <table border="1" cellspacing="0"
859 style="width: 100%;table-layout: fixed;word-break: break-all;margin: 0 auto;text-align: center;border-collapse: collapse;">
860 <thead>
861 <tr>
862 <th v-for="(item, index) in qualityRuleDetailTableInfo.fields.map(f => f.label)" :key="index">
863 <span>{{ item }}</span>
864 </th>
865 </tr>
866 </thead>
867 <tbody>
868 <tr v-for="(recordItem, j) in qualityRuleDetailTableInfo.data" :key="j">
869 <td v-for="(columnItem, i) in qualityRuleDetailTableInfo.fields" :key="i"
870 :style="{ 'text-align': <any>(columnItem.align ?? 'left') }">
871 <span :style="{ 'word-break': 'break-all' }">
872 {{ columnItem.getName ? columnItem.getName({ row: recordItem }) : ((columnItem?.field &&
873 recordItem[columnItem.field]) ?? '--') }}
874 </span>
875 </td>
876 </tr>
877 </tbody>
878 </table>
879 <div style="
880 background: #FFF1D4;
881 /* border-bottom: 1px solid #000;
882 border-right: 1px solid #000;
883 border-left: 1px solid #000; */
884 "><span >质量得分:</span><span>∑规则得分*权重</span></div>
885 <div
886 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
887 评估方案明细</div>
888 <table border="1" cellspacing="0"
889 style="width: 100%;table-layout: fixed;word-break: break-all;margin: 0 auto;text-align: center;border-collapse: collapse;">
890 <thead>
891 <tr>
892 <th v-for="(item, index) in planDetailTableInfo.fields.map(f => f.label)" :key="index">
893 <span>{{ item }}</span>
894 </th>
895 </tr>
896 </thead>
897 <tbody>
898 <tr v-for="(recordItem, j) in planDetailTableInfo.data" :key="j">
899 <td v-for="(columnItem, i) in planDetailTableInfo.fields" :key="i"
900 :style="{ 'text-align': <any>(columnItem.align ?? 'left'), width: columnItem.field == 'qualifiedRate' ? '53px' : '' }">
901 <span :style="{ 'word-break': 'break-all' }">
902 {{ columnItem.getName ? columnItem.getName({ row: recordItem }) : ((columnItem?.field &&
903 recordItem[columnItem.field]) ?? '--') }}
904 </span>
905 </td>
906 </tr>
907 </tbody>
908 </table>
909 <template v-if="detailInfo.analysisReportType != 1">
910 <div
911 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
912 表明细</div>
913 <table border="1" cellspacing="0"
914 style="width: 100%;table-layout: fixed;word-break: break-all;margin: 0 auto;text-align: center;border-collapse: collapse;">
915 <thead>
916 <tr>
917 <th v-for="(item, index) in modelDetailTableInfo.fields.slice(1).map(f => f.label)" :key="index">
918 <span>{{ item }}</span>
919 </th>
920 </tr>
921 </thead>
922 <tbody>
923 <tr v-for="(recordItem, j) in modelDetailTableInfo.data" :key="j">
924 <td v-for="(columnItem, i) in modelDetailTableInfo.fields.slice(1)" :key="i"
925 :style="{ 'text-align': <any>(columnItem.align ?? 'left'), width: columnItem.field == 'qualifiedRate' ? '53px' : '' }">
926 <span v-if="columnItem.type != 'tag'">
927 {{ (columnItem.getName ? columnItem.getName({ row: recordItem }) : ((columnItem?.field &&
928 recordItem[columnItem.field]) ?? '--')) }}
929 </span>
930 <span v-else :style="{
931 'white-space': 'nowrap', 'font-size': '12px', color: tagType(recordItem, columnItem.field) == 'success' ? '#1BA854' : (tagType(recordItem, columnItem.field) == 'warning' ? '#e6a23c' : (tagType(recordItem, columnItem.field) == 'danger' ? '#FB2323' : '#999999')),
932 background: tagType(recordItem, columnItem.field) == 'success' ? '#F2FFF5' : (tagType(recordItem, columnItem.field) == 'warning' ? '#fdf6ec' : (tagType(recordItem, columnItem.field) == 'danger' ? '#FFF2F4' : '#F5F5F5'))
933 }">
934 {{ (columnItem.field && recordItem[columnItem.field]) ? tagMethod(recordItem, columnItem.field) :
935 '--' }}
936 </span>
937 </td>
938 </tr>
939 </tbody>
940 </table>
941 </template>
942 <div
943 style="line-height: 24px;font-size: 16px;color: #212121;font-weight: 600;margin-top: 12px;margin-bottom: 8px;">
944 规则明细</div>
945 <table border="1" cellspacing="0"
946 style="width: 100%;table-layout: fixed;word-break: break-all;margin: 0 auto;text-align: center;border-collapse: collapse;">
947 <thead>
948 <tr>
949 <th v-for="(item, index) in modelRuleDetailTableInfo.fields.slice(1).map(f => f.label)" :key="index">
950 <span>{{ item }}</span>
951 </th>
952 </tr>
953 </thead>
954 <tbody>
955 <tr v-for="(recordItem, j) in modelRuleDetailTableInfo.data" :key="j">
956 <td v-for="(columnItem, i) in modelRuleDetailTableInfo.fields.slice(1)" :key="i"
957 :style="{ 'text-align': <any>(columnItem.align ?? 'left'), width: columnItem.field == 'qualifiedRate' ? '53px' : '' }">
958 <span v-if="columnItem.type != 'tag'">
959 {{ (columnItem.getName ? columnItem.getName({ row: recordItem }) : ((columnItem?.field &&
960 recordItem[columnItem.field]) ?? '--')) }}
961 </span>
962 <span v-else
963 :style="{
964 'white-space': 'nowrap', 'font-size': '12px', color: tagType(recordItem, columnItem.field) == 'success' ? '#1BA854' : (tagType(recordItem, columnItem.field) == 'warning' ? '#e6a23c' : (tagType(recordItem, columnItem.field) == 'danger' ? '#FB2323' : '#999999')),
965 background: tagType(recordItem, columnItem.field) == 'success' ? '#F2FFF5' : (tagType(recordItem, columnItem.field) == 'warning' ? '#fdf6ec' : (tagType(recordItem, columnItem.field) == 'danger' ? '#FFF2F4' : '#F5F5F5')) }">
966 {{ (columnItem.field && recordItem[columnItem.field]) ? tagMethod(recordItem, columnItem.field) :
967 '--'
968 }}
969 </span>
970 </td>
971 </tr>
972 </tbody>
973 </table>
974 </div>
975 </div>
976 </div>
977 </div>
978 </template>
979
980 <style lang="scss" scoped>
981 .container_wrap {
982 padding: 0;
983 }
984
985 .content-main {
986 height: calc(100% - 106px);
987 overflow-y: auto;
988 overflow-x: hidden;
989 padding: 0px 16px 16px;
990 }
991
992 .header-title {
993 margin: 16px 16px 0px;
994 height: 40px;
995 line-height: 32px;
996 display: flex;
997 flex-direction: row;
998 justify-content: space-between;
999 align-items: center;
1000 font-size: 18px;
1001 color: #212121;
1002 font-weight: 600;
1003 }
1004
1005 .time-detail {
1006 font-size: 14px;
1007 color: #666666;
1008 font-weight: 400;
1009 margin-left: 16px;
1010 }
1011
1012 .header-detail {
1013 height: 40px;
1014 line-height: 40px;
1015 font-size: 14px;
1016 color: #666666;
1017 padding: 0 16px;
1018 border-bottom: 1px solid #d9d9d9;
1019 }
1020
1021 .kpi-content {
1022 display: flex;
1023 flex-direction: row;
1024 }
1025
1026 .border-content {
1027 height: 76px;
1028 display: flex;
1029 flex-direction: column;
1030 align-items: left;
1031 padding-left: 16px;
1032 justify-content: center;
1033 border: 1px solid #d9d9d9;
1034 width: 160px;
1035 min-width: 100px;
1036 border-radius: 2px;
1037
1038 .number {
1039 line-height: 30px;
1040 font-weight: 700;
1041 font-size: 20px;
1042 }
1043
1044 .num-color {
1045 color: #212121;
1046 }
1047
1048 .score-color {
1049 color: #FF5F1F;
1050 }
1051
1052 .pass-color {
1053 color: #1BA854;
1054 }
1055
1056 .no-pass-color {
1057 color: #FB2323;
1058 }
1059
1060 .rate-color {
1061 color: #5B8FF9;
1062 }
1063
1064 .text {
1065 font-size: 14px;
1066 color: #666666;
1067 display: flex;
1068
1069 .el-icon {
1070 color: #b2b2b2;
1071 }
1072 }
1073 }
1074
1075 .ml16 {
1076 margin-left: 16px;
1077 }
1078
1079 .ml32 {
1080 margin-left: 32px;
1081 }
1082
1083 .title {
1084 line-height: 24px;
1085 font-size: 16px;
1086 color: #212121;
1087 font-weight: 600;
1088 margin-top: 12px;
1089 margin-bottom: 8px;
1090 }
1091
1092 .content-radar-table {
1093 display: flex;
1094 flex-direction: row;
1095 height: 380px;
1096 justify-content: space-between;
1097
1098 .content-two {
1099 width: calc(50% - 16px);
1100
1101 .table_panel {
1102 min-height: 200px !important;
1103 height: calc(100% - 44px) !important;
1104 }
1105 }
1106
1107 .content-chart-radar {
1108 height: calc(100% - 44px);
1109 width: 100%;
1110 border: 1px solid #d9d9d9;
1111 padding: 8px;
1112 }
1113 }
1114
1115 .content-chart-bar {
1116 height: 300px;
1117 border: 1px solid #d9d9d9;
1118 padding: 8px;
1119 }
1120
1121 .word-report {
1122 height: 100%;
1123 widows: 100%;
1124 background-color: #f1f1f1;
1125
1126 .word-btn {
1127 background-color: #fff;
1128 margin: 8px 0px;
1129 padding: 0px 8px 8px;
1130 height: 48px;
1131 line-height: 32px;
1132 display: flex;
1133 flex-direction: row;
1134 justify-content: space-between;
1135 align-items: center;
1136 font-size: 18px;
1137 color: #212121;
1138 font-weight: 600;
1139 }
1140
1141 }
1142
1143 .word-report-main {
1144 height: calc(100% - 56px);
1145 overflow-y: auto;
1146 overflow-x: hidden;
1147 display: flex;
1148 flex-direction: column;
1149 align-items: center;
1150 background-color: #fff;
1151 padding-bottom: 12px;
1152 }
1153 </style>
1 <route lang="yaml">
2 name: assessDetail
3 </route>
4
5 <script lang="ts" setup name="assessDetail">
6 import { ref } from "vue";
7 import { useRouter, useRoute } from "vue-router";
8 import Table from "@/components/Table/index.vue";
9 import {
10 getRecordRuleConfDetail,
11 getExecPlanDetailTableData,
12 getAssessTableRulesData,
13 downloadDirtyData
14 } from '@/api/modules/dataQualityAssess';
15 import { ElMessage } from "element-plus";
16 import { changeNum, download } from '@/utils/common';
17 import ruleForm from "../data_quality/ruleForm.vue";
18
19 const { proxy } = getCurrentInstance() as any;
20
21 const router = useRouter();
22 const route = useRoute();
23
24 const planGuid = route.query.planGuid;
25 const planExecGuid = route.query.planExecGuid;
26
27 const page = ref({
28 limit: 50,
29 curr: 1,
30 sizes: [
31 { label: "10", value: 10 },
32 { label: "50", value: 50 },
33 { label: "100", value: 100 },
34 { label: "150", value: 150 },
35 { label: "200", value: 200 },
36 ],
37 });
38
39 const tableInfo = ref({
40 id: "quality-table",
41 loading: false,
42 fields: [
43 { label: "表名", field: "qualityModelName", width: 140 },
44 { label: "执行结果", field: "execResult", type: "tag", width: 100, align: "center" },
45 { label: "评估时间", field: "execTime", width: 180, },
46 {
47 label: "耗时(秒)", field: "execDuration", width: 100, align: "right", getName: (scope) => {
48 return scope.row.execDuration != null ? changeNum(scope.row.execDuration ?? 0) : '--';
49 }
50 },
51 {
52 label: "规则数", field: "ruleNum", width: 100, align: "right", getName: (scope) => {
53 return scope.row.ruleNum != null ? changeNum(scope.row.ruleNum ?? 0) : '--';
54 }
55 },
56 {
57 label: "评估总数", field: "totalNum", width: 100, align: "right", getName: (scope) => {
58 return scope.row.totalNum != null ? changeNum(scope.row.totalNum ?? 0) : '--';
59 }
60 },
61 {
62 label: "合格条数", field: "qualifiedNum", width: 100, align: "right", getName: (scope) => {
63 return scope.row.qualifiedNum != null ? changeNum(scope.row.qualifiedNum ?? 0) : '--';
64 }
65 },
66 {
67 label: "不合格条数", field: "unqualifiedNum", width: 100, align: "right", getName: (scope) => {
68 return scope.row.unqualifiedNum != null ? changeNum(scope.row.unqualifiedNum ?? 0) : '--';
69 }
70 },
71 {
72 label: "合格率", field: "qualifiedRate", width: 100, align: "right", getName: (scope) => {
73 return scope.row.qualifiedRate != null ? ((scope.row.qualifiedRate ?? 0).toFixed(2) + '%') : '--';
74 }
75 },
76 { label: "评估范围", field: "dataRange", width: 180 },
77 ],
78 data: [],
79 showPage: false,
80 actionInfo: {
81 label: "操作",
82 type: "btn",
83 width: 220,
84 fixed: 'right',
85 btns: (scope) => {
86 let unqualifiedNum = scope.row.unqualifiedNum ?? 0;
87 return [
88 { label: "查看规则", value: "rule" },
89 { label: "脏数据", value: "path_dirty", disabled: unqualifiedNum == 0 || scope.row.isTable == 'Y' },
90 { label: "脏数据下载", value: "download", disabled: unqualifiedNum == 0 || scope.row.isTable == 'Y' },
91 ]
92 },
93 }
94 });
95
96 /** 获取方案详情列表数据。 */
97 const getAssessDetail = () => {
98 tableInfo.value.loading = true;
99 getExecPlanDetailTableData({ planGuid: planGuid, planExecGuid: planExecGuid }).then((res: any) => {
100 tableInfo.value.loading = false;
101 if (res.code == proxy.$passCode) {
102 tableInfo.value.data = res.data || []
103 } else {
104 ElMessage.error(res.msg);
105 }
106 });
107 }
108
109 /** 获取每个表的规则详情。 */
110 const getModelRulesDetail = (qualityModelGuid: string) => {
111 rulesDetailTableInfo.value.loading = true;
112 getAssessTableRulesData({ planExecGuid: planExecGuid, qualityModelGuid: qualityModelGuid }).then((res: any) => {
113 rulesDetailTableInfo.value.loading = false;
114 if (res.code == proxy.$passCode) {
115 rulesDetailTableInfo.value.data = res.data || [];
116 } else {
117 ElMessage.error(res.msg);
118 }
119 });
120 }
121
122 /** 下载脏数据。 */
123 const exportDirtyData = (row) => {
124 downloadDirtyData({
125 planExecGuid: row.planExecGuid,
126 qualityModelGuid: row.qualityModelGuid
127 }).then((res: any) => {
128 if (res && !res.msg) {
129 download(res, `脏数据-${row.qualityModelName}.xlsx`, 'excel');
130 } else {
131 res?.msg && ElMessage.error(res?.msg);
132 }
133 })
134 }
135
136 const tableBtnClick = (scope, btn) => {
137 const type = btn.value;
138 const row = scope.row;
139 if (type == 'rule') {
140 rulesDetailDialogVisible.value = true;
141 getModelRulesDetail(row.qualityModelGuid);
142 } else if (type == 'path_dirty') {
143 router.push({
144 name: 'assessDirty',
145 query: {
146 planExecGuid: row.planExecGuid,
147 name: row.qualityModelName,
148 qualityModelGuid: row.qualityModelGuid
149 }
150 });
151 } else if (type == 'download') {
152 exportDirtyData(row);
153 }
154 };
155
156 onActivated(() => {
157 getAssessDetail();
158 });
159
160 /** 查看表规则详情的对话框显示隐藏。 */
161 const rulesDetailDialogVisible = ref(false);
162
163 const rulesDetailTableInfo: any = ref({
164 id: "rules-detail-table",
165 loading: false,
166 fields: [
167 { label: "序号", type: "index", width: 56, align: "center" },
168 { label: "表名", field: "qualityModelName", width: 140 },
169 { label: "规则类型", field: "ruleName", width: 140 },
170 { label: "规则名称", field: "ruleConfName", width: 140 },
171 { label: "规则大类", field: "largeCategory", width: 100 },
172 { label: "规则小类", field: "smallCategory", width: 140 },
173 { label: "规则字段", field: "ruleField", width: 140 },
174 { label: "执行结果", field: "execResult", type: "tag", width: 100, align: "center" },
175 // { label: "规则权重", field: "weight", width: 100, align: 'right' },
176 {
177 label: "评估总数", field: "totalNum", width: 100, align: "right", getName: (scope) => {
178 return scope.row.totalNum != null ? changeNum(scope.row.totalNum ?? 0) : '--';
179 }
180 },
181 {
182 label: "合格条数", field: "qualifiedNum", width: 100, align: "right", getName: (scope) => {
183 return scope.row.qualifiedNum != null ? changeNum(scope.row.qualifiedNum ?? 0) : '--';
184 }
185 },
186 {
187 label: "不合格条数", field: "unqualifiedNum", width: 100, align: "right", getName: (scope) => {
188 return scope.row.unqualifiedNum != null ? changeNum(scope.row.unqualifiedNum ?? 0) : '--';
189 }
190 },
191 {
192 label: "合格率", field: "qualifiedRate", width: 100, align: "right", getName: (scope) => {
193 return scope.row.qualifiedRate != null ? ((scope.row.qualifiedRate ?? 0).toFixed(2) + '%') : '--';
194 }
195 },
196 { label: "失败原因", field: "errorLog", width: 140 },
197 ],
198 data: [],
199 showPage: false,
200 actionInfo: {
201 label: "操作",
202 type: "btn",
203 width: 90,
204 fixed: 'right',
205 btns: [
206 { label: "规则详情 ", value: "ruleDetail" },
207 ],
208 }
209 });
210
211 /** 单个规则详情对话框 */
212 const oneRulesDetailDialogVisible = ref(false);
213
214 const toSubjectTables: any = ref([]);
215 const ruleType = ref('');
216 const detailInfo: any = ref({});
217 const detailLoading = ref(false);
218 const ruleTypeList: any = ref([]);
219 const smallCategoryList: any = ref([]);
220 const largeCategoryList: any = ref([]);
221 const detailJson: any = ref({});
222
223 const rulesDetailTableBtnClick = (scope, btn) => {
224 const type = btn.value;
225 const row = scope.row;
226 if (type == 'ruleDetail') {
227 detailLoading.value = true;
228 if (detailJson.value[row.ruleConfGuid]) {
229 ruleType.value = detailInfo.value.ruleCode;
230 toSubjectTables.value = [{
231 guid: detailInfo.value.subjectGuid,
232 enName: detailInfo.value.subjectName,
233 chName: detailInfo.value.subjectZhName,
234 label: `${detailInfo.value.subjectName}(${detailInfo.value.subjectZhName})`
235 }]
236 ruleTypeList.value = [{
237 value: detailInfo.value.ruleCode,
238 label: row.ruleName
239 }];
240 smallCategoryList.value = [{
241 paramValue: detailInfo.value.smallCategory,
242 paramName: row.smallCategory
243 }];
244 largeCategoryList.value = [{
245 paramValue: detailInfo.value.largeCategory,
246 paramName: row.largeCategory
247 }];
248 oneRulesDetailDialogVisible.value = true;
249 } else {
250 detailJson.value[row.ruleConfGuid] = { isRequest: true };
251 getRecordRuleConfDetail({ruleConfGuid: row.ruleConfGuid, planExecGuid: planExecGuid }).then((res: any) => {
252 detailLoading.value = false;
253 oneRulesDetailDialogVisible.value = true;
254 if (res.code == proxy.$passCode) {
255 let data = res.data || {};
256 detailInfo.value = data;
257 detailJson.value[row.ruleConfGuid] = detailInfo.value;
258 detailJson.value[row.ruleConfGuid].isRequest = false;
259 ruleType.value = detailInfo.value.ruleCode;
260 toSubjectTables.value = [{
261 guid: detailInfo.value.subjectGuid,
262 enName: detailInfo.value.subjectName,
263 chName: detailInfo.value.subjectZhName,
264 label: `${detailInfo.value.subjectName}(${detailInfo.value.subjectZhName})`
265 }]
266 ruleTypeList.value = [{
267 value: detailInfo.value.ruleCode,
268 label: row.ruleName
269 }];
270 smallCategoryList.value = [{
271 paramValue: detailInfo.value.smallCategory,
272 paramName: row.smallCategory
273 }];
274 largeCategoryList.value = [{
275 paramValue: detailInfo.value.largeCategory,
276 paramName: row.largeCategory
277 }];
278 } else {
279 ElMessage.error(res.msg);
280 delete detailJson.value[row.ruleConfGuid];
281 }
282 })
283 }
284 }
285 };
286
287 </script>
288
289 <template>
290 <div class="container_wrap">
291 <div class="table_panel_wrap">
292 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" />
293 </div>
294 <el-dialog v-model="rulesDetailDialogVisible" title="查看规则" width="800" :modal="true" :close-on-click-modal="false"
295 destroy-on-close align-center>
296 <div class="rules-detail-dialog-content">
297 <Table class="long-tooltip-table" :tableInfo="rulesDetailTableInfo" @tableBtnClick="rulesDetailTableBtnClick" />
298 </div>
299 </el-dialog>
300 <el-dialog v-model="oneRulesDetailDialogVisible" title="规则详情" width="800" :modal="true"
301 :close-on-click-modal="false" destroy-on-close align-center>
302 <ruleForm ref="ruleFormRef" :readonly="true" :toSubjectTables="toSubjectTables" :ruleTypeValue="ruleType"
303 :value="detailInfo" :ruleTypeList="ruleTypeList" :largeCategoryList="largeCategoryList"
304 :smallCategoryList="smallCategoryList">
305 </ruleForm>
306 </el-dialog>
307 </div>
308 </template>
309
310 <style lang="scss" scoped>
311 .container_wrap {
312 padding: 16px;
313 }
314
315 .table_panel_wrap {
316 height: 100%;
317 }
318
319 .rules-detail-dialog-content {
320 height: 450px;
321 }
322
323 .long-tooltip-table {
324 :deep(.el-table) {
325 .table_cell_tooltip {
326 max-width: 500px;
327 // max-height: 100px;
328 // overflow: auto;
329 }
330 }
331 }
332 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: assessDirty
3 </route>
4
5 <script lang="ts" setup name="assessDirty">
6 import { ref } from "vue";
7 import { calcColumnWidth } from "@/utils/index";
8 import Moment from 'moment';
9 import {
10 getQueryDirtyData,
11 getQueryDirtyFields,
12 downloadDirtyData
13 } from '@/api/modules/dataQualityAssess';
14 import { download } from '@/utils/common'
15 import { ElMessage } from "element-plus";
16 import { TableColumnWidth } from "@/utils/enum";
17
18 const { proxy } = getCurrentInstance() as any;
19 const route: any = useRoute();
20 const planExecGuid = route.query.planExecGuid;
21 const qualityModelGuid = route.query.qualityModelGuid;
22 const name = route.query.name;
23
24 const dirtyDataLoading = ref(false);
25 const dirtyData = ref([{
26 guid: 1,
27 }, {
28 guid: '2',
29 isDirty: true
30 }, {
31 guid: 3
32 }]);
33
34 const dirtyDataFields: any = ref([{
35 enName: 'guid',
36 chName: '主键',
37 dataType: 'string'
38 }]);
39
40 /** 脏数据下载 */
41 const exportData = () => {
42 if (!dirtyData.value.length || !dirtyDataFields.value.length) {
43 ElMessage.error('当没有可下载的脏数据');
44 return;
45 }
46 downloadDirtyData({
47 planExecGuid: planExecGuid,
48 qualityModelGuid: qualityModelGuid
49 }).then((res: any) => {
50 if (res && !res.msg) {
51 download(res, `脏数据-${name}.xlsx`, 'excel');
52 } else {
53 res?.msg && ElMessage.error(res?.msg);
54 }
55 })
56 }
57
58 const getTableData = () => {
59 dirtyData.value = [];
60 dirtyDataLoading.value = true;
61 let ps1 = getQueryDirtyData({
62 pageSize: pageInfo.value.limit,
63 pageIndex: pageInfo.value.curr,
64 qualityModelGuid: qualityModelGuid,
65 planExecGuid: planExecGuid,
66 }).then((res: any) => {
67 if (res.code == proxy.$passCode) {
68 let data = res.data || {};
69 dirtyData.value = data.records || [];
70 pageInfo.value.curr = data.pageIndex;
71 pageInfo.value.rows = data.totalRows || 0;
72 } else {
73 ElMessage.error(res.msg);
74 }
75 });
76 let ps2 = getQueryDirtyFields(qualityModelGuid).then((res: any) => {
77 if (res.code == proxy.$passCode) {
78 dirtyDataFields.value = res.data.fields || [];
79 } else {
80 ElMessage.error(res.msg);
81 }
82 })
83 Promise.all([ps1, ps2]).then(res => {
84 dirtyDataLoading.value = false;
85 });
86 };
87
88 onBeforeMount(() => {
89 getTableData();
90 });
91
92 const pageInfo = ref({
93 limit: 50,
94 curr: 1,
95 sizes: [
96 { label: "10", value: 10 },
97 { label: "50", value: 50 },
98 { label: "100", value: 100 },
99 { label: "150", value: 150 },
100 { label: "200", value: 200 },
101 ],
102 type: "normal",
103 rows: 0,
104 })
105
106 const pageChange = (info) => {
107 pageInfo.value.curr = Number(info.curr);
108 pageInfo.value.limit = Number(info.limit);
109 getTableData();
110 }
111
112 const formatterPreviewDate = (row, info) => {
113 let enName = info.enName;
114 let v = row[enName]?.value;
115 if (v === "" || v === 0) {
116 return v;
117 }
118 if (v == null) {
119 return '--';
120 }
121 if (info.dataType === 'datetime') {
122 return Moment(v).format('YYYY-MM-DD HH:mm:ss');
123 }
124 if (info.dataType === 'date') {
125 if (isNaN(<any>(new Date(v)))) {
126 return Moment(parseInt(v)).format('YYYY-MM-DD');
127 } else {
128 return Moment(v).format('YYYY-MM-DD');
129 }
130 }
131 return v;
132 };
133
134 const getTextAlign = (field) => {
135 if (field.dataType === 'decimal' || field.dataType === 'int' || field.dataType == 'bit' || field.dataType == 'tinyint') {
136 return 'right';
137 }
138 return 'left'
139 }
140
141 /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */
142 const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => {
143 let d: any[] = [];
144 data.forEach((dt) => d.push(dt[prop]));
145 return calcColumnWidth(
146 d,
147 title,
148 {
149 fontSize: 14,
150 fontFamily: "SimSun",
151 },
152 {
153 fontSize: 14,
154 fontFamily: "SimSun",
155 },
156 otherWidth
157 );
158 };
159
160 /** 每列字段对应的列宽计算结果。 */
161 const originTableFieldColumn: any = ref({
162 guid: 140
163 });
164
165 watch(
166 () => dirtyData.value,
167 (val: any[], oldVal) => {
168 if (!dirtyDataFields.value?.length) {
169 originTableFieldColumn.value = {};
170 return;
171 }
172 originTableFieldColumn.value = {};
173 dirtyDataFields.value.forEach((field, index) => {
174 originTableFieldColumn.value[field.enName] = calcTableColumnWidth(
175 val?.map(v => {
176 let json = {};
177 for (const k in v) {
178 json[k] = v[k]?.value
179 }
180 return json;
181 }) || [],
182 field.enName,
183 field.chName,
184 24
185 );
186 });
187 }
188 );
189
190 const handleDityCellClass = ({ row, column, rowIndex, columnIndex }) => {
191 let v = dirtyData.value[rowIndex][column.property];
192 if (v?.checkInfo?.length) {
193 return 'dirty-cell-bg';
194 }
195 }
196
197 </script>
198
199 <template>
200 <div class="container_wrap">
201 <div class="table_tool_wrap">
202 <div class="tools_btns">
203 <el-button type="primary" @click="exportData" v-preReClick>脏数据下载</el-button>
204 </div>
205 </div>
206 <div class="table_panel_wrap">
207 <el-table key="guid" v-loading="dirtyDataLoading" :data="dirtyData" border tooltip-effect="light" style="
208 width: 100%;
209 min-width: 200px;
210 max-width: 100%;
211 height: calc(100% - 44px);
212 display: inline-block;
213 " stripe :cell-class-name="handleDityCellClass">
214 <el-table-column v-for="(field, index) in dirtyDataFields" :key="field.enName" :prop="field.enName"
215 :label="field.chName" :width="field.dataType === 'datetime'
216 ? TableColumnWidth.DATETIME
217 : field.dataType === 'date'
218 ? TableColumnWidth.DATE
219 : originTableFieldColumn[field.enName]
220 " :align="getTextAlign(field)" :header-align="getTextAlign(field)" :show-overflow-tooltip="true"
221 >
222 <template #default="scope">
223 <el-tooltip v-if="scope.row[field.enName].checkInfo?.length" placement="bottom-start" effect="light"
224 popper-class="table_tooltip" trigger="click">
225 <template #content>
226 <div style="width: 236px; text-align: justify">
227 <p class="tips_title">不符合规则</p>
228 <p v-for="(item) in (scope.row[field.enName].checkInfo || [])">{{ item }}</p>
229 </div>
230 </template>
231 <span class="dirty-cell-tooltip">{{ formatterPreviewDate(scope.row, field) }}</span>
232 </el-tooltip>
233 <span v-else>{{ formatterPreviewDate(scope.row, field) }}</span>
234 </template>
235 </el-table-column>
236 </el-table>
237 <PageNav :class="[pageInfo.type]" :pageInfo="pageInfo" @pageChange="pageChange" />
238 </div>
239 </div>
240 </template>
241
242 <style lang="scss" scoped>
243 .table_tool_wrap {
244 width: 100%;
245 height: 44px;
246 padding: 8px;
247
248 .tools_btns {
249 padding: 0;
250 }
251 }
252
253 .table_panel_wrap {
254 width: 100%;
255 height: calc(100% - 44px);
256 padding: 0 8px;
257 }
258
259 :deep(.el-table) {
260 .dirty-cell-bg {
261 background: #FFF1D4 !important;
262
263 .cell.el-tooltip {
264 padding: 0px;
265 }
266 }
267
268 .dirty-cell-tooltip {
269 width: 100%;
270 min-height: 24px;
271 display: block;
272 overflow: hidden;
273 text-overflow: ellipsis;
274 padding: 0 12px;
275 }
276 }
277 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: assessLog
3 </route>
4
5 <script lang="ts" setup name="assessLog">
6 import { ref } from "vue";
7 import { useRouter, useRoute } from "vue-router";
8 import Table from "@/components/Table/index.vue";
9 import Drawer from "@/components/Drawer/index.vue";
10 import {
11 getAssessDetailTableData,
12 } from '@/api/modules/dataQualityAssess';
13 import { ElMessage } from "element-plus";
14 import { changeNum } from '@/utils/common';
15 import {TableColumnWidth} from "@/utils/enum"
16 const { proxy } = getCurrentInstance() as any;
17
18 const router = useRouter();
19 const route = useRoute();
20 /** 方案guid */
21 const planGuid = route.query.guid;
22 const planName = route.query.name;
23 const execType = route.query.type;
24 const page = ref({
25 limit: 50,
26 curr: 1,
27 sizes: [
28 { label: "10", value: 10 },
29 { label: "50", value: 50 },
30 { label: "100", value: 100 },
31 { label: "150", value: 150 },
32 { label: "200", value: 200 },
33 ],
34 });
35 const tableInfo = ref({
36 id: "user-authority-table",
37 fields: [
38 { type:"index", width: TableColumnWidth.INDEX, align:"center",label: "序号" },
39 { label: "质检执行结果", field: "execResult", type: "tag", width: 120, align: "center" },
40 { label: "评估时间", field: "execTime", width: 180, },
41 {
42 label: "耗时(秒)", field: "execDuration", align: "right", width: 100, getName: (scope) => {
43 return scope.row.execDuration != null ? changeNum(scope.row.execDuration ?? 0) : '--';
44 }
45 },
46 {
47 label: "规则数", field: "ruleNum", width: 100, align: "right", getName: (scope) => {
48 return scope.row.ruleNum != null ? changeNum(scope.row.ruleNum ?? 0) : '--';
49 }
50 },
51 {
52 label: "评估总数", field: "totalNum", width: 100, align: "right", getName: (scope) => {
53 return scope.row.totalNum != null ? changeNum(scope.row.totalNum ?? 0) : '--';
54 }
55 },
56 {
57 label: "合格条数", field: "qualifiedNum", width: 100, align: "right", getName: (scope) => {
58 return scope.row.qualifiedNum != null ? changeNum(scope.row.qualifiedNum ?? 0) : '--';
59 }
60 },
61 {
62 label: "不合格条数", field: "unqualifiedNum", width: 100, align: "right", getName: (scope) => {
63 return scope.row.unqualifiedNum != null ? changeNum(scope.row.unqualifiedNum ?? 0) : '--';
64 }
65 },
66 {
67 label: "合格率", field: "qualifiedRate", width: 100, align: "right", getName: (scope) => {
68 return scope.row.qualifiedRate != null ? ((scope.row.qualifiedRate ?? 0).toFixed(2) + '%') : '--';
69 }
70 },
71 ],
72 loading: false,
73 data: [],
74 page: {
75 type: "normal",
76 rows: 0,
77 ...page.value,
78 },
79 actionInfo: {
80 label: "操作",
81 type: "btn",
82 width: 140,
83 fixed: 'right',
84 btns: [
85 { label: "查看结果", value: "resultView" },
86 { label: "日志", value: "log" }
87 ],
88 }
89 });
90
91 const formTable = ref({
92 type: "table",
93 title: "",
94 col: 'no-margin',
95 tableInfo: {
96 id: "log-detail-table",
97 loading: false,
98 fields: [
99 { label: "执行时间", field: "changeTime", width: 140, },
100 { label: "日志类型", field: "metaCurrValue", width: 120 },
101 { label: "日志级别", field: "collectTaskName", width: 110 },
102 { label: "执行步骤", field: "updateType", width: 240 },
103 ],
104 data: [],
105 showPage: false,
106 actionInfo: {
107 show: false
108 },
109 },
110 })
111
112 const drawerInfo: any = ref({
113 visible: false,
114 direction: "rtl",
115 modalClass: "wrap_width_auto",
116 size: 650,
117 header: {
118 title: "日志详情",
119 },
120 type: '',
121 container: {
122 contents: [
123 formTable.value,
124 ],
125 },
126 footer: {
127 visible: false,
128 },
129 })
130
131 const tablePageChange = (info) => {
132 page.value.curr = Number(info.curr);
133 page.value.limit = Number(info.limit);
134 getTableData();
135 };
136
137 const getTableData = () => {
138 tableInfo.value.loading = true;
139 getAssessDetailTableData({ pageSize: page.value.limit, pageIndex: page.value.curr, planGuid: planGuid }).then((res: any) => {
140 tableInfo.value.loading = false;
141 if (res.code == proxy.$passCode) {
142 const data = res.data || {}
143 tableInfo.value.data = data.records || []
144 tableInfo.value.page.limit = data.pageSize ?? 50;
145 tableInfo.value.page.curr = data.pageIndex
146 tableInfo.value.page.rows = data.totalRows ?? 0;
147 } else {
148 ElMessage.error(res.msg);
149 }
150 });
151 };
152
153 const tableBtnClick = (scope, btn) => {
154 const type = btn.value;
155 const row = scope.row;
156 if (type == 'resultView') {
157 if(!!execType) {
158 router.push({
159 name: 'syncAssessDetail',
160 query: {
161 name: planName,
162 planGuid: row.planGuid,
163 planExecGuid: row.planExecGuid
164 }
165 });
166 } else {
167 router.push({
168 name: 'assessDetail',
169 query: {
170 name: planName,
171 planGuid: row.planGuid,
172 planExecGuid: row.planExecGuid
173 }
174 });
175 }
176
177 } else if (type == 'log') {
178 const params = {
179 logGuid: row.guid
180 }
181 drawerInfo.value.visible = true
182 // formTable.value.tableInfo.loading = true;
183 // getLogDetail(params).then((res: any) => {
184 // formTable.value.tableInfo.loading = false;
185 // if (res.code == proxy.$passCode && res.data) {
186 // const data = res.data
187 // formTable.value.tableInfo.data = data
188 // drawerInfo.value.container.contents[0].listInfo.data = data
189 // drawerInfo.value.visible = true
190 // } else {
191 // ElMessage({
192 // type: "info",
193 // message: res.msg,
194 // });
195 // }
196 // })
197 }
198 };
199
200 const drawerBtnClick = (btn) => {
201 drawerInfo.value.visible = false;
202 };
203
204 onBeforeMount(() => {
205 getTableData();
206 });
207
208 </script>
209
210 <template>
211 <div class="container_wrap">
212 <div class="table_panel_wrap">
213 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tablePageChange="tablePageChange" />
214 </div>
215
216 <Drawer :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick" />
217 </div>
218 </template>
219
220 <style lang="scss" scoped>
221 .container_wrap {
222 padding: 0;
223
224 .table_panel_wrap {
225 height: 100%;
226 padding: 16px 16px 0;
227 }
228 }
229
230 :deep(.el-drawer) {
231 .drawer_panel {
232 height: 100%;
233
234 .table_panel_wrap {
235 height: 100%;
236 }
237 }
238 }
239 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: assessTemplate
3 </route>
4
5 <script lang="ts" setup name="assessTemplate">
6 import { ref } from "vue";
7 import { useRouter } from "vue-router";
8 import { ElMessage, ElMessageBox } from "element-plus";
9 import Form from "@/components/Form/index.vue";
10 import StepBar from "@/components/StepBar/index.vue";
11 import Table from "@/components/Table/index.vue";
12 import TreeTransferChecked from "@/components/TreeTransferChecked/index.vue";
13 import TreeTransfer from "@/components/TreeTransfer/index.vue";
14 import filtersSettingBatch from "./filtersSettingBatch.vue";
15 import {
16 getModelCountList,
17 checkPlanExist,
18 getValidGroup,
19 getModelDbGp,
20 getModelRuleCount,
21 getModelRules,
22 saveQualityPlan,
23 updateQualityPlan,
24 getAssessPlanDetail,
25 getPlanFilterDetail
26 } from '@/api/modules/dataQualityAssess';
27 import {
28 getQualityTreeData,
29 getQualityTreeDataByDs
30 } from '@/api/modules/dataQuality';
31 import {
32 getCronExecTime
33 } from '@/api/modules/queryService';
34 import { changeNum } from '@/utils/common';
35 import useUserStore from "@/store/modules/user";
36 import useDataQualityStore from "@/store/modules/dataQuality";
37 import { cloneDeep } from "lodash-es";
38 import { TableColumnWidth } from '@/utils/enum';
39
40 const userStore = useUserStore();
41 const dataQualityStore = useDataQualityStore();
42
43 const { proxy } = getCurrentInstance() as any;
44
45 const router = useRouter();
46 const route = useRoute();
47
48 const fullPath = route.fullPath;
49 const planGuid = route.query.guid;//编辑方案时
50 const isDetail = ref(route.query.detail != null);// 查看方案详情
51 const modelGuid = ref(route.query.modelGuid); // 从质检表跳过来的评估方案,默认单表。
52 const groupGuid = ref(route.query.groupGuid); // 从质检表跳过来的评估方案,懒加载需要展开groupGuid
53
54 const step = ref(0);
55 const stepsInfo = ref({
56 step: step.value,
57 list: [
58 { title: '选择模型', value: 1 },
59 { title: '任务调度', value: 2 }
60 ]
61 })
62
63 const ruleModelTableInfo = ref({
64 id: 'rule-model-table',
65 loading: false,
66 minHeight: '200px',
67 nodeKey: 'modelGuid',
68 fields: [
69 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
70 { label: "表名", field: "modelName", width: 140, type: 'text_btn', value: 'detailView', columClass: 'text_btn' },
71 { label: "分组名称", field: "modelGroupName", width: 150 },
72 { label: "数据源", field: "dataSourceName", width: 140 },
73 { label: "规则数量", field: "ruleCount", width: 140, align: 'right' },
74 ],
75 data: [],
76 showPage: false,
77 actionInfo: {
78 label: "操作",
79 type: "btn",
80 width: 100,
81 fixed: 'right',
82 btns: isDetail.value ? [{ label: "规则", value: "ruleEdit", visible: true }] : [
83 { label: "规则", value: "ruleEdit", visible: true },
84 { label: "删除", value: "delete", visible: true },
85 ],
86 }
87 })
88
89 /** 规则设置对话框。 */
90 const modelRulesTableRef = ref();
91
92 /** 记录模型guid对应的guid, */
93 const modelRulesJson: any = ref({});
94
95 const currTableData: any = ref({});
96
97 const tableBtnClick = (scope, btn) => {
98 const type = btn.value;
99 const row = scope.row;
100 currTableData.value = row;
101 if (type == "ruleEdit") {
102 ruleSettingDialogVisible.value = true;
103 if (modelRulesJson.value[row.modelGuid]) {
104 modelRulesTableInfo.value.data = modelRulesJson.value[row.modelGuid];
105 nextTick(() => {
106 let confs: any[] = [];
107 currTableData.value.modelRuleConfGuids?.forEach((guid) => {
108 let index = modelRulesTableInfo.value.data.findIndex(d => d.guid == guid);
109 if (index > -1) {
110 let row = modelRulesTableInfo.value.data[index];
111 confs.push(row);
112 modelRulesTableRef.value.tableRef.toggleRowSelection(row, true);
113 }
114 })
115 currTableData.value.modelRuleConfs = confs;
116 });
117 } else {
118 getModelRulesDetail(row.modelGuid).then(() => {
119 nextTick(() => {
120 let confs: any[] = [];
121 currTableData.value.modelRuleConfGuids?.forEach((guid) => {
122 let index = modelRulesTableInfo.value.data.findIndex(d => d.guid == guid);
123 if (index > -1) {
124 let row = modelRulesTableInfo.value.data[index];
125 confs.push(row);
126 modelRulesTableRef.value.tableRef.toggleRowSelection(row, true);
127 }
128 })
129 currTableData.value.modelRuleConfs = confs;
130 })
131 })
132 }
133 } else if (type == "delete") {
134 ElMessageBox.confirm("此操作将永久删除,是否继续?", "提示", {
135 confirmButtonText: "确定",
136 cancelButtonText: "取消",
137 type: type,
138 }).then(() => {
139 // 只是内存删除。删除时也需要同步更新规则数量。
140 ruleModelTableInfo.value.data.splice(scope.$index, 1);
141 let v = modelFormRef.value?.formInline;
142 if (v.planType === 1) {
143 modelFormItems.value[2].default = '';
144 } else if (v.planType === 3) {//分组
145 let index = selectModelsByGroup.value.findIndex(s => s.guid == row.modelGuid);
146 selectModelsByGroup.value.splice(index, 1);
147 modelFormItems.value[3].default = selectModelsByGroup.value;
148 } else if (v.planType == 2) {
149 let index = selectModels.value.findIndex(s => s.guid == row.modelGuid);
150 selectModels.value.splice(index, 1);
151 modelFormItems.value[4].default = selectModels.value;
152 }
153 // http://test.csylcloud.com:8380/browse/WZL-511 修改了modelFormItems.value就需要更新form值,否则会导致上次修改的值消失。
154 let formInline = modelFormRef.value?.formInline || {};
155 let item1 = modelFormItems.value[0];
156 item1.default = formInline[item1.field];
157 let item2 = modelFormItems.value[1];
158 item2.default = formInline[item2.field];
159 let largeCategoryNum = row.largeCategoryNum || {};
160 for (const large in largeCategoryNum) {
161 let rowWeightIndex = ruleWeightData.value.findIndex(w => w.largeCategory == large);
162 if (rowWeightIndex > -1) {
163 let rowWeight = ruleWeightData.value[rowWeightIndex];
164 rowWeight.ruleCount = rowWeight.ruleCount - largeCategoryNum[large];
165 if (rowWeight.ruleCount < 1) {
166 ruleWeightData.value.splice(rowWeightIndex, 1);
167 }
168 }
169 }
170 //http://test.csylcloud.com:8380/browse/WZL-529
171 if (ruleWeightData.value.length == 1) {//只剩一条
172 ruleWeightData.value[0].ruleLargeWeight = changeNum(100, 2, true);
173 }
174 }).catch(() => {
175 ElMessage({
176 type: "info",
177 message: "已取消删除",
178 });
179 });
180 } else if (type == 'detailView') {
181 router.push({
182 name: 'qualityRules'
183 });
184 dataQualityStore.setModelGuid(row.modelGuid);
185 }
186 };
187
188 /** 选择的所有质检表对应的规则大类数量及权重占比。 */
189 const ruleWeightData: any = ref([]);
190 const ruleWeightDataLoading = ref(false);
191
192 const inputWeightChange = (val, row) => {
193 let strArr = val.split(".");
194 if (strArr.length > 1) {
195 let right = strArr[1];
196 if (right === "" || right.length < 2) {
197 row.ruleLargeWeight = val = parseFloat(val || 0).toFixed(2);
198 }
199 } else {
200 row.ruleLargeWeight = val = parseFloat(val || 0).toFixed(2);
201 }
202 };
203
204 /** 输入框输入触发事件 */
205 const inputEventWeightChange = (val, row) => {
206 row.ruleLargeWeight = row.ruleLargeWeight.toString().replace(/[^\d.]/g, "")
207 row.ruleLargeWeight = row.ruleLargeWeight.toString().replace(/\.{2,}/g, ".")
208 row.ruleLargeWeight = row.ruleLargeWeight.toString().replace(/^\D*(\d{0,3}(?:\.\d{0,2})?).*$/g, "$1")
209 if (row.ruleLargeWeight > 100) {
210 row.ruleLargeWeight = changeNum(100, 2, true);
211 }
212 }
213
214 /** 类型是表时,需要显示第一级为分组,第二级为表。 */
215 const qualityGroups: any = ref([]);
216
217 const getDataSourceListData = () => {
218 return getModelDbGp().then((res: any) => {
219 if (res.code == proxy.$passCode) {
220 const data = res.data || [];
221 databaseList.value = data;
222 console.log(data);
223 } else {
224 ElMessage.error(res.msg);
225 }
226 })
227 }
228
229 const planDetailInfo: any = ref({});
230
231 /** 全屏加载状态,保存时显示。 */
232 const fullscreenLoading = ref(false);
233
234 const getValidGroupPromise: any = ref(null);
235
236 const getPlanFilterDetailPromise: any = ref(null);
237
238 /** 记录是否是从分组跳转,若是,则需要默认值是该分组下所有模型表。 */
239 const initDefaultGroup = ref(false);
240
241 onBeforeMount(() => {
242 getValidGroupPromise.value = getValidGroup().then((res: any) => {
243 getValidGroupPromise.value = null;
244 if (res.code == proxy.$passCode) {
245 const data = qualityGroups.value = res.data || [];
246 return data;
247 } else {
248 ElMessage.error(res.msg);
249 return [];
250 }
251 })
252 if (planGuid) {
253 fullscreenLoading.value = true;
254 getAssessPlanDetail(planGuid).then((res: any) => {
255 if (res.code == proxy.$passCode) {
256 planDetailInfo.value = res.data || {};
257 switchInfo.value = planDetailInfo.value;
258 getPlanFilterDetailPromise.value = getPlanFilterDetail(planGuid).then((res: any) => {
259 getPlanFilterDetailPromise.value = null;
260 fullscreenLoading.value = false;
261 if (res.code == proxy.$passCode) {
262 console.log(res.data);
263 let data = res.data || [];
264 data.forEach(d => {
265 let tables: any = [];
266 for (const id in d.qualityModelInfo) {
267 tables.push({
268 guid: id,
269 subjectGuid: planDetailInfo.value.modelCount?.find(m => m.modelGuid == id)?.subjectGuid,
270 name: d.qualityModelInfo[id]
271 });
272 }
273 batchFiltersValue.value[d.dataFilterGroup] = {
274 filter: d.dataRange,
275 tables: tables,
276 checked: true
277 }
278 });
279 } else {
280 ElMessage.error(res.msg);
281 }
282 });
283 if (planDetailInfo.value.planType == 1) {
284 planDetailInfo.value.qualityModelGuid = planDetailInfo.value.modelCount?.[0]?.modelGuid;
285 modelFormItems.value[2].expandKeys = [planDetailInfo.value.modelCount?.[0]?.modelGroupGuid];
286 }
287 setModelFormItems(planDetailInfo.value);
288 checkedInfo.value[planDetailInfo.value.planName] = true;
289 ruleModelTableInfo.value.data = planDetailInfo.value.modelCount || [];
290 ruleWeightData.value = planDetailInfo.value.modelRuleWeights?.map(r => {
291 r.ruleLargeWeight = r.ruleLargeWeight.toFixed(2);
292 return r;
293 }) || [];
294 if (planDetailInfo.value.planType == 3) {//分组
295 dsSelectGroup.value = planDetailInfo.value.modelCount?.[0]?.modelGroupGuid;
296 modelFormItems.value[3].default = selectModelsByGroup.value = planDetailInfo.value.modelCount?.map((m: any) => {
297 m.guid = m.modelGuid;
298 m.name = m.modelName;
299 return m;
300 }) || [];
301 } else if (planDetailInfo.value.planType == 2) {//数据库
302 modelFormItems.value[4].default = selectModels.value = planDetailInfo.value.modelCount?.map(m => {
303 m.name = m.modelName;
304 m.guid = m.modelGuid;
305 return m;
306 }) || [];
307 databaseInfo.value = planDetailInfo.value.modelCount?.[0]?.dataSourceGuid;
308 }
309 setFormItems(planDetailInfo.value, true);
310 } else {
311 fullscreenLoading.value = false;
312 ElMessage.error(res.msg);
313 }
314 });
315 } else if (modelGuid.value) {
316 setModelFormItems({ planType: 1, qualityModelGuid: modelGuid.value });
317 modelFormItems.value[2].expandKeys = [groupGuid.value];
318 getModelCountListData([modelGuid.value]);
319 getModelRuleCountListData([modelGuid.value]);
320 } else if (groupGuid.value && !modelGuid.value) {
321 setModelFormItems({ planType: 3 });
322 dsSelectGroup.value = <string>groupGuid.value;
323 initDefaultGroup.value = true;
324 } else if (dataQualityStore.defaultPlanType) {
325 setModelFormItems({ planType: dataQualityStore.defaultPlanType });
326 dataQualityStore.setDefaultPlanType(null);
327 }
328 });
329
330 onActivated(() => {
331 if (modelGuid.value || groupGuid.value) {
332 return;
333 }
334 if (dataQualityStore.defaultPlanType) {
335 setModelFormItems({ planType: dataQualityStore.defaultPlanType });
336 dataQualityStore.setDefaultPlanType(null);
337 }
338 });
339
340 const modelFormItems: any = ref([
341 {
342 label: '质量方案名称',
343 type: 'input',
344 maxlength: 50,
345 placeholder: '请输入',
346 field: 'planName',
347 default: '',
348 clearable: true,
349 required: true
350 }, {
351 label: '方案类型',
352 type: 'select',
353 placeholder: '请选择',
354 field: 'planType',
355 default: 1,
356 options: [
357 {
358 label: '表',
359 value: 1
360 }, {
361 label: '分组',
362 value: 3
363 }, {
364 label: '数据库',
365 value: 2
366 }
367 ],
368 required: true,
369 visible: true
370 }, {
371 label: '选择质量规则集',
372 type: 'tree-select',
373 placeholder: '请选择',
374 field: 'qualityModelGuid',
375 default: '',
376 checkStrictly: false,
377 options: [],
378 props: {
379 label: 'name',
380 value: 'guid',
381 children: 'children',
382 isLeaf: 'isLeaf'
383 },
384 lazy: true,
385 expandKeys: [],
386 clearable: true,
387 required: true,
388 filterable: true,
389 visible: true
390 }, {
391 label: '选择质量规则集',
392 visible: false,
393 type: 'addBtn',
394 placeholder: '选择表',
395 field: 'qualityModelGuidsByGroup',
396 default: [],
397 required: true,
398 }, {
399 label: '选择质量规则集',
400 visible: false,
401 type: 'addBtn',
402 placeholder: '选择表',
403 field: 'qualityModelGuids',
404 default: [],
405 required: true,
406 }
407 ])
408
409 const checkPlanName = async (params) => {
410 return new Promise((resolve, reject) => {
411 checkPlanExist(params).then((res: any) => {
412 if (res.code == proxy.$passCode) {
413 if (res.data) {
414 resolve('该名称已存在,请填写其他名称')
415 } else {
416 resolve('')
417 }
418 } else {
419 resolve(res.msg)
420 }
421 }).catch((xhr) => {
422 reject(xhr.msg)
423 });
424 })
425 }
426
427 /** 记录已校验过的信息。 */
428 const checkedInfo: any = ref({});
429
430 const modelFormRules = ref({
431 planName: [
432 {
433 trigger: 'change', validator: (rule: any, value: any, callback: any) => {
434 if (!value) {
435 callback(new Error("请填写质量方案名称"));
436 return;
437 }
438 let checkedInfoValue = checkedInfo.value[value];
439 if (checkedInfoValue != null) {
440 if (checkedInfoValue) {
441 callback();
442 } else {
443 callback(new Error(checkedInfoValue));
444 }
445 return;
446 }
447 checkPlanName(value).then((res: any) => {
448 if (res) {
449 checkedInfo.value[value] = res;
450 callback(new Error(res));
451 } else {
452 checkedInfo.value[value] = true;
453 callback();
454 }
455 }).catch((xhr: any) => {
456 callback(new Error(xhr));
457 })
458 }
459 }
460 ],
461 qualityModelGuid: [{
462 validator: (rule: any, value: any, callback: any) => {
463 let formInline = modelFormRef.value?.formInline;
464 if (formInline && formInline.planType == 1) {
465 if (!value?.length) {
466 callback(new Error("请选择质量规则集"));
467 }
468 }
469 callback();
470 },
471 trigger: 'change'
472 }],
473 qualityModelGuidsByGroup: [{
474 validator: (rule: any, value: any, callback: any) => {
475 let formInline = modelFormRef.value?.formInline;
476 if (formInline && formInline.planType == 3) {
477 if (!formInline.qualityModelGuidsByGroup?.length) {
478 callback(new Error("请选择质量规则集"));
479 }
480 }
481 callback();
482 }
483 }],
484 qualityModelGuids: [{
485 validator: (rule: any, value: any, callback: any) => {
486 let formInline = modelFormRef.value?.formInline;
487 if (formInline && formInline.planType == 2) {
488 if (!formInline.qualityModelGuids?.length) {
489 callback(new Error("请选择质量规则集"));
490 }
491 }
492 callback();
493 }
494 }],
495 })
496
497 const cancelPlan = () => {
498 if (isDetail.value) {
499 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
500 toRouterPath('/data-quality/quality-assess');
501 return;
502 }
503 ElMessageBox.confirm(
504 "当前页面尚未保存,确定放弃修改吗?",
505 "提示",
506 {
507 confirmButtonText: "确定",
508 cancelButtonText: "取消",
509 type: "warning",
510 }
511 )
512 .then(() => {
513 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
514 toRouterPath('/data-quality/quality-assess');
515 })
516 .catch(() => {
517 ElMessage({
518 type: "info",
519 message: "已取消",
520 });
521 });
522 }
523
524 const modelFormRef = ref();
525
526 const changeStep = (val) => {
527 if (val == 2) {
528 modelFormRef.value?.ruleFormRef?.validate((valid) => {
529 if (valid) {
530 let sum = 0;
531 ruleWeightData.value.forEach(item => {
532 if (item.ruleLargeWeight != null) {
533 sum += parseFloat(item.ruleLargeWeight);
534 }
535 });
536 if (!(sum <= 100.02 && sum >= 99.98)) {// 允许浮动0.01误差
537 ElMessage.error('规则权重总分应为100分');
538 return;
539 }
540 if (!planGuid) {
541 let formInline = modelFormRef.value?.formInline;
542 if (formInline) {
543 taskFormItems.value.find(t => t.field == 'analysisReportName').default = formInline.planName;
544 }
545 }
546 step.value = val - 1;
547 stepsInfo.value.step = val - 1;
548 }
549 });
550 } else {
551 step.value = val - 1;
552 stepsInfo.value.step = val - 1;
553 }
554 };
555
556 /** 第二步任务调度的表单。 */
557 const taskFormItems: any = ref([
558 {
559 label: '脏数据保留次数(次)',
560 type: 'input',
561 inputType: 'integerNumber',
562 placeholder: '请输入',
563 max: 1000,
564 field: 'dirtyDataNum',
565 default: 365,
566 min: 1,
567 clearable: true,
568 required: false,
569 },
570 {
571 label: '是否创建调度',
572 type: 'switch',
573 field: 'isScheduler',
574 default: 'Y',
575 activeValue: 'Y',
576 inactiveValue: 'N'
577 },
578 {
579 label: '定时',
580 type: 'input-popover-panel',
581 placeholder: '',
582 field: 'execCycle',
583 teleported: true,
584 col: 'col2',
585 append: {
586 btn: { label: '执行时间', value: 'cron' }
587 },
588 clearable: true,
589 required: true,
590 visible: true
591 },
592 {
593 label: '接下来五次执行时间',
594 type: 'textarea',
595 placeholder: '执行时间',
596 field: 'zxzq',
597 clearable: true,
598 required: false,
599 block: true,
600 class: 'execute-time',
601 rows: 5,
602 visible: true,
603 readonly: true
604 },
605 {
606 label: '是否创建分析报告',
607 type: 'switch',
608 field: 'isCreateReport',
609 default: 'N',
610 activeValue: 'Y',
611 block: true,
612 inactiveValue: 'N'
613 },
614 {
615 label: '分析报告名称',
616 type: 'input',
617 maxlength: 50,
618 placeholder: '请输入',
619 field: 'analysisReportName',
620 default: '',
621 clearable: true,
622 visible: false,
623 required: true,
624 },
625 {
626 label: '报告保留期数(期)',
627 type: 'input',
628 inputType: 'integerNumber',
629 placeholder: '请输入',
630 field: 'reportReserveCycle',
631 default: 365,
632 max: 1000,
633 min: 1,
634 clearable: true,
635 required: false,
636 visible: false
637 },
638 ])
639
640 const taskFormRules = ref({
641 analysisReportName: [{
642 required: true,
643 message: '请填写报告名称',
644 trigger: 'blur'
645 }]
646 });
647
648 /** 任务调度表单组件ref。 */
649 const taskFormRef = ref();
650
651 const switchInfo: any = ref({});
652
653 /** 是否创建任务调度。 */
654 const handleTaskCreateChange = (val, formInfo, item) => {
655 if (item.field == 'isCreateReport') {
656 if (val == 'Y') {
657 formInfo.analysisReportName = formInfo.analysisReportName ?? switchInfo.value.analysisReportName;
658 formInfo.reportReserveCycle = formInfo.reportReserveCycle ?? switchInfo.value.reportReserveCycle;
659 }
660 }
661 else if (item.field == 'isScheduler') {
662 if (val == 'Y') {
663 formInfo.execCycle = formInfo.execCycle ?? switchInfo.value.execCycle;
664 }
665 }
666 setFormItems(formInfo);
667 }
668
669 const scheduleChange = (val, rowValue) => {
670 setFormItems(rowValue);
671 }
672
673 const taskFormBtnClick = (btn, type) => {
674 if (btn.value = 'cron') {
675 let vInfo = taskFormRef.value.formInline;
676 if (!vInfo.execCycle) {
677 return;
678 }
679 getCronExecTime(vInfo.execCycle).then((res: any) => {
680 if (res?.length) {
681 vInfo.zxzq = res.join('\n');
682 setFormItems(vInfo);
683 } else {
684 ElMessage({
685 type: 'error',
686 message: res.msg,
687 })
688 }
689 })
690 }
691 }
692
693 const setFormItems = (row, isDetail = false) => {
694 switchInfo.value = {
695 analysisReportName: row.analysisReportName,
696 reportReserveCycle: isDetail && row.isCreateReport == 'N' && row.reportReserveCycle == null ? 365 : row.reportReserveCycle,
697 execCycle: row.execCycle
698 };
699 taskFormItems.value.map(item => {
700 if (item.field == 'isScheduler') {
701 taskFormItems.value[1].default = row.isScheduler;
702 if (row.isScheduler == 'Y') {
703 taskFormItems.value[2].visible = true;
704 taskFormItems.value[3].visible = true;
705 } else {
706 taskFormItems.value[2].visible = false;
707 taskFormItems.value[3].visible = false;
708 }
709 } else if (item.field == 'isCreateReport') {
710 taskFormItems.value.at(-3).default = row.isCreateReport;
711 if (row.isCreateReport == 'Y') {
712 taskFormItems.value.at(-1).visible = true;
713 taskFormItems.value.at(-2).visible = true;
714 } else {
715 taskFormItems.value.at(-1).visible = false;
716 taskFormItems.value.at(-2).visible = false;
717 }
718 }
719 else if (item.field == 'reportReserveCycle' || item.field == 'dirtyDataNum') {
720 item.default = row[item.field] === undefined ? 365 : row[item.field];
721 } else if (item.field == 'analysisReportName') {
722 let formInline = modelFormRef.value?.formInline;
723 if (formInline) {
724 item.default = row[item.field] ?? formInline.planName;
725 }
726 } else {
727 item.default = row ? row[item.field] : ''
728 }
729 })
730 }
731
732 const toRouterPath = (url) => {
733 router.push({
734 path: url,
735 });
736 };
737
738 const save = () => {
739 taskFormRef.value?.ruleFormRef?.validate((valid) => {
740 if (valid) {
741 let modelValue = modelFormRef.value.formInline;
742 let models: any = [];
743 let ruleConfs: any = [];
744 let transferFilters: any = [];
745 for (const key in batchFiltersValue.value) {
746 let info = batchFiltersValue.value[key];
747 if (info.tables.length) {
748 info.tables.forEach(t => {
749 transferFilters.push(Object.assign({}, t, {
750 filter: info.filter,
751 dataFilterGroup: key
752 }));
753 });
754 }
755 }
756 ruleModelTableInfo.value.data.forEach((d: any) => {
757 let filterInfo = transferFilters.find(b => b.guid == d.modelGuid);
758 models.push({
759 qualityModelGuid: d.modelGuid,
760 subjectGuid: d.subjectGuid,
761 dataRange: filterInfo?.filter,
762 dataFilterGroup: filterInfo?.dataFilterGroup,
763 });
764 ruleConfs.push({
765 qualityModelGuid: d.modelGuid,
766 ruleConfGuid: d.modelRuleConfGuids || [],
767 dataSourceGuid: d.dataSourceGuid,
768 subjectGuid: d.subjectGuid
769 })
770 })
771 let params: any = Object.assign({}, taskFormRef.value.formInline, {
772 planName: modelValue.planName,
773 planType: modelValue.planType,
774 state: 1,
775 models: models,
776 ruleConfs: ruleConfs,
777 ruleWeights: ruleWeightData.value,
778 });
779 if (!params.reportReserveCycle && params.reportReserveCycle !== 0) {
780 params.reportReserveCycle = null;
781 }
782 if (!params.dirtyDataNum && params.dirtyDataNum !== 0) {
783 params.dirtyDataNum = null;
784 }
785 if (planGuid) {
786 params.guid = planGuid;
787 params.state = 0;
788 fullscreenLoading.value = true;
789 updateQualityPlan(params).then((res: any) => {
790 fullscreenLoading.value = false;
791 if (res.code == proxy.$passCode) {
792 ElMessage.success('方案更新成功');
793 router.push({
794 name: 'qualityAssess'
795 });
796 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
797 dataQualityStore.setPlanType(modelValue.planType);
798 dataQualityStore.setIsUpdate(true);
799 } else {
800 ElMessage.error(res.msg);
801 }
802 })
803 } else {
804 fullscreenLoading.value = true;
805 saveQualityPlan(params).then((res: any) => {
806 fullscreenLoading.value = false;
807 if (res.code == proxy.$passCode) {
808 ElMessage.success('新建质量评估方案保存成功');
809 router.push({
810 name: 'qualityAssess'
811 });
812 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
813 dataQualityStore.setPlanType(modelValue.planType);
814 } else {
815 ElMessage.error(res.msg);
816 }
817 })
818 }
819 }
820 });
821 }
822
823 /** 根据所选择的模型表,获取对应的规则数量信息。 */
824 const getModelCountListData = (modelGuids) => {
825 ruleModelTableInfo.value.loading = true;
826 getModelCountList({ modelGuids: modelGuids }).then((res: any) => {
827 ruleModelTableInfo.value.loading = false;
828 if (res.code == proxy.$passCode) {
829 ruleModelTableInfo.value.data = res.data || [];
830 } else {
831 ElMessage.error(res.msg);
832 }
833 });
834 }
835
836 /** 根据所选择的模型表,获取对应的权重信息。 */
837 const getModelRuleCountListData = (modelGuids) => {
838 ruleWeightDataLoading.value = true;
839 getModelRuleCount({ modelGuids: modelGuids }).then((res: any) => {
840 ruleWeightDataLoading.value = false;
841 if (res.code == proxy.$passCode) {
842 ruleWeightData.value = res.data || [];
843 if (ruleWeightData.value.length) {
844 let v = changeNum(100 / ruleWeightData.value.length, 2, true);
845 ruleWeightData.value.forEach(d => d.ruleLargeWeight = v);
846 }
847 } else {
848 ElMessage.error(res.msg);
849 }
850 });
851 }
852
853 /** 点击表格行中的规则按钮, 获取详细规则 */
854 const getModelRulesDetail = (modelGuid) => {
855 modelRulesTableInfo.value.loading = true;
856 return getModelRules({ modelGuid: modelGuid, bizState: 'Y' }).then((res: any) => {
857 modelRulesTableInfo.value.loading = false;
858 if (res.code == proxy.$passCode) {
859 modelRulesTableInfo.value.data = res.data || [];
860 modelRulesJson.value[modelGuid] = modelRulesTableInfo.value.data;
861 } else {
862 ElMessage.error(res.msg);
863 }
864 })
865 }
866
867 const batchFilterDialogRef = ref();
868
869 /** 批量配置过滤条件 */
870 const handleFiltersAdd = () => {
871 if (!selectSubjectTables.value.length) {
872 ElMessage.warning('请先选择质量规则集');
873 return;
874 }
875 batchFilterDialogRef.value?.openDialog(batchFiltersValue.value);
876 }
877
878 const setModelFormItems = (row) => {
879 let item1 = modelFormItems.value[0];
880 item1.default = row[item1.field];
881 let item2 = modelFormItems.value[1];
882 item2.default = row[item2.field];
883 if (item2.default === 1) {//表
884 modelFormItems.value[2].visible = true;
885 modelFormItems.value[3].visible = false;
886 modelFormItems.value[4].visible = false;
887 } else if (item2.default === 3) {//分组
888 modelFormItems.value[3].visible = true;
889 modelFormItems.value[2].visible = false;
890 modelFormItems.value[4].visible = false;
891 } else if (item2.default === 2) {//数据库
892 modelFormItems.value[2].visible = false;
893 modelFormItems.value[3].visible = false;
894 modelFormItems.value[4].visible = true;
895 }
896 let itemModel = modelFormItems.value.at(-3);
897 if (itemModel) {
898 itemModel.default = row[itemModel.field]
899 }
900 }
901
902 const selectChange = (val, item, row) => {
903 if (item.field === 'planType') {
904 //修改类型,则清空过滤条件
905 batchFiltersValue.value = {};
906 ruleModelTableInfo.value.data = [];
907 ruleWeightData.value = [];
908 if (val == 3) {
909 selectModels.value = [];
910 modelFormItems.value[3].default = [];
911 } else if (val == 2) {
912 selectModelsByGroup.value = [];
913 modelFormItems.value[4].default = [];
914 } else {
915 selectModelsByGroup.value = [];
916 selectModels.value = [];
917 }
918 } else if (item.field == 'qualityModelGuid') {
919 getModelCountListData([val]);
920 getModelRuleCountListData([val]);
921 }
922 setModelFormItems(row);
923 };
924
925 /** 分组+表结构懒加载。 */
926 const modelFormTreeSelectLoad = (node, resolve, item) => {
927 if (node.level === 0) {
928 if (getValidGroupPromise.value) {
929 getValidGroupPromise.value.then(res => {
930 resolve(res);
931 })
932 } else {
933 resolve(qualityGroups.value);
934 }
935 return;
936 } else if (node.level == 1) {
937 getQualityTreeData(node.data.guid).then((res: any) => {
938 if (res.code == proxy.$passCode) {
939 const data = res.data?.map(d => {
940 d.parentGuid = node.data.guid;
941 d.groupName = node.data.name;
942 d.isLeaf = true;
943 return d;
944 }) || [];
945 resolve(data);
946 } else {
947 resolve([]);
948 ElMessage.error(res.msg);
949 }
950 })
951 }
952 }
953
954 /** 方案类型为表时,选择的表json对象 */
955 const treeSelectNode: any = ref(null);
956
957 const modelFormTreeSelectNodeChange = (node, item) => {
958 treeSelectNode.value = node;
959 }
960
961 const btnFormClick = (btn, type) => {
962 console.log(btn, type);
963 if (btn.field === "qualityModelGuids") {
964 let v: any = modelFormItems.value[4].default ?? [];
965 if (!v.length) {
966 modelsDialogVisible.value = true;
967 if (!databaseList.value?.length) {
968 getDataSourceListData().then(() => {
969 if (databaseInfo.value == databaseList.value[0]?.guid ?? "") {
970 dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value));
971 } else {
972 databaseInfo.value = databaseList.value[0]?.guid ?? "";
973 }
974 })
975 } else {
976 if (databaseInfo.value == databaseList.value[0]?.guid ?? "") {
977 dsFromTreeData.value = JSON.parse(JSON.stringify(currentDsFromTreeData.value));
978 } else {
979 databaseInfo.value = databaseList.value[0]?.guid ?? "";
980 }
981 }
982 dsToTreeData.value = [];
983 } else {
984 let p: any = [];
985 v.forEach(vc => {
986 let pd = p.find(p => p.modelGroupGuid == vc.modelGroupGuid);
987 if (pd) {
988 pd.children.push({
989 guid: vc.modelGuid,
990 modelGuid: vc.modelGuid,
991 dataSourceGuid: vc.dataSourceGuid,
992 name: vc.modelName ?? vc.name,
993 isLeaf: true,
994 subjectGuid: vc.subjectGuid,
995 pid: vc.modelGroupGuid,
996 modelGroupName: vc.modelGroupName,
997 modelGroupGuid: vc.modelGroupGuid,
998 });
999 } else {
1000 p.push({
1001 guid: vc.modelGroupGuid,
1002 modelGroupGuid: vc.modelGroupGuid,
1003 modelGroupName: vc.modelGroupName,
1004 name: vc.modelGroupName,
1005 dataSourceGuid: vc.dataSourceGuid,
1006 pid: 0,
1007 children: [{
1008 guid: vc.modelGuid,
1009 modelGuid: vc.modelGuid,
1010 name: vc.modelName ?? vc.name,
1011 isLeaf: true,
1012 subjectGuid: vc.subjectGuid,
1013 pid: vc.modelGroupGuid,
1014 modelGroupGuid: vc.modelGroupGuid,
1015 modelGroupName: vc.modelGroupName,
1016 dataSourceGuid: vc.dataSourceGuid,
1017 }]
1018 });
1019 }
1020 });
1021 databaseInfo.value = v[0].dataSourceGuid;
1022 dsToTreeData.value = p;
1023 if (!databaseList.value?.length) { //编辑的时候第一次打开。
1024 getDataSourceListData().then(res => { //解决先出现guid,再变成中文的问题。
1025 if (getDsFromTreeDataPromise.value) {
1026 getDsFromTreeDataPromise.value.then(res => {
1027 Promise.all(dsToTreeData.value.map(pi => {
1028 return getTreeDataByDs(dsFromTreeData.value.find(d => d.guid == pi.guid))
1029 })).then(() => {
1030 dsFromTreeData.value = handlerFromFilterToTreeData(dsFromTreeData.value, dsToTreeData.value);
1031 modelsDialogVisible.value = true;
1032 })
1033 })
1034 } else {
1035 Promise.all(dsToTreeData.value.map(pi => {
1036 return getTreeDataByDs(dsFromTreeData.value.find(d => d.guid == pi.guid))
1037 })).then(() => {
1038 dsFromTreeData.value = handlerFromFilterToTreeData(dsFromTreeData.value, dsToTreeData.value);
1039 modelsDialogVisible.value = true;
1040 })
1041 }
1042 });
1043 } else {
1044 modelsDialogVisible.value = true;
1045 }
1046 }
1047 } else if (btn.field === 'qualityModelGuidsByGroup') {
1048 modelsByGroupDialogVisible.value = true;
1049 if (!dsSelectGroup.value) {
1050 if (getValidGroupPromise.value) {
1051 getValidGroupPromise.value.then((res: any) => {
1052 dsSelectGroup.value = res[0]?.guid ?? "";
1053 });
1054 } else {
1055 dsSelectGroup.value = qualityGroups.value[0]?.guid ?? "";
1056 }
1057 }
1058 dsByGroupToTreeData.value = cloneDeep(modelFormItems.value[3].default ?? []);
1059 }
1060 }
1061
1062 const handlerFromFilterToTreeData = (data, toData) => {
1063 for (let i = data.length - 1; i >= 0; i--) {
1064 for (let j = toData.length - 1; j >= 0; j--) {
1065 if (data[i] && data[i].guid === toData[j].guid) {
1066 // 当id相等可以删除的情况 即:没有子级可以删除;
1067 if (!data[i].children?.length && !toData[j].children?.length) {
1068 data.splice(i, 1);
1069 } else {
1070 handlerFromFilterToTreeData(data[i].children, toData[j].children);
1071 if (!data[i].children?.length) {
1072 data.splice(i, 1);
1073 }
1074 }
1075 }
1076 }
1077 }
1078 return data;
1079 }
1080
1081 const selectModels: any = ref([]);
1082
1083 /** 以数据库为粒度选择模型表。 */
1084 const modelsDialogVisible = ref(false);
1085
1086 const databaseList: any = ref([]);
1087
1088 const databaseInfo = ref('');
1089
1090 const dsFromTreeDataLoading = ref(false);
1091
1092 const dsFromTreeData: any = ref([]);
1093
1094 const currentDsFromTreeData = ref([]);
1095
1096 const dsToTreeData: any = ref([]);
1097
1098 let getDsFromTreeDataPromise: any = ref(null);
1099
1100 watch(() => databaseInfo.value, (val) => {
1101 if (val) {
1102 dsFromTreeDataLoading.value = true;
1103 getDsFromTreeDataPromise.value = getModelDbGp(val).then((res: any) => {
1104 getDsFromTreeDataPromise.value = null;
1105 dsFromTreeDataLoading.value = false;
1106 if (res.code == proxy.$passCode) {
1107 dsFromTreeData.value = res.data?.map(d => {
1108 d.pid = 0;
1109 return d;
1110 }) || [];
1111 currentDsFromTreeData.value = JSON.parse(JSON.stringify(dsFromTreeData.value)) || [];
1112 return dsFromTreeData.value;
1113 } else {
1114 dsFromTreeData.value = [];
1115 ElMessage.error(res.msg);
1116 }
1117 })
1118 } else {
1119 dsFromTreeData.value = [];
1120 }
1121 })
1122
1123 const cancelModelDialog = () => {
1124 modelsDialogVisible.value = false;
1125 }
1126
1127 const submitModels = () => {
1128 if (!dsToTreeData.value.length) {
1129 ElMessage.error('已选表不能为空');
1130 return;
1131 }
1132 let models: any = [];
1133 dsToTreeData.value.forEach(d => {
1134 models.push(...d.children);
1135 });
1136 let formInline = modelFormRef.value?.formInline || {};
1137 selectModels.value = models;
1138 modelFormItems.value[4].default = selectModels.value;
1139 // http://test.csylcloud.com:8380/browse/WZL-511
1140 let item1 = modelFormItems.value[0];
1141 item1.default = formInline[item1.field];
1142 let item2 = modelFormItems.value[1];
1143 item2.default = formInline[item2.field];
1144 modelFormRef.value?.ruleFormRef?.validateField('qualityModelGuids');
1145 let guids = selectModels.value.map((m: any) => m.guid);
1146 getModelCountListData(guids);
1147 getModelRuleCountListData(guids);
1148 modelsDialogVisible.value = false;
1149 }
1150
1151 const handleModelLeftCheckChange = (nodeObj, treeObj, checkAll, treeNode) => {
1152 if (!nodeObj.isLeaf && treeObj.checkedKeys?.includes(nodeObj.guid)) {
1153 treeNode?.expand();
1154 }
1155 }
1156
1157 /** 切换了数据库,清空表选择 */
1158 const handleSelectDsModelChange = (val) => {
1159 dsToTreeData.value = [];
1160 }
1161
1162 const loadNode = (node, resolve, from) => {
1163 if (node.level === 0) {
1164 if (node.data) {
1165 resolve(node.data);
1166 }
1167 return;
1168 }
1169 if (node.level === 1) {
1170 if (node.data.children?.length > 0) {
1171 resolve(node.data.children);
1172 return;
1173 }
1174 getTreeDataByDs(node.data).then((res) => {
1175 resolve(res);
1176 })
1177 }
1178 }
1179
1180 const getTreeDataByDs = (node) => {
1181 return getQualityTreeDataByDs({ guid: node.guid, dataSourceGuid: databaseInfo.value }).then((res: any) => {
1182 if (res.code == proxy.$passCode) {
1183 const data = res.data?.map(d => {
1184 d.parentGuid = node.guid;
1185 d.modelGroupGuid = node.guid;
1186 d.pid = node.guid;
1187 d.modelGuid = d.guid;
1188 d.modelGroupName = node.name;
1189 d.dataSourceGuid = databaseInfo.value;
1190 d.isLeaf = true;
1191 return d;
1192 }) || [];
1193 node.children = data;
1194 return data;
1195 } else {
1196 ElMessage.error(res.msg);
1197 return [];
1198 }
1199 })
1200 }
1201
1202 /** 以分组为粒度选择模型表。 */
1203 const selectModelsByGroup: any = ref([]);
1204
1205 const modelsByGroupDialogVisible = ref(false);
1206
1207 /** 以分组为粒度的对话框选择的当前分组 */
1208 const dsSelectGroup = ref('');
1209
1210 const dsByGroupFromTreeDataLoading = ref(false);
1211
1212 const dsByGroupFromTreeData = ref()
1213
1214 const dsByGroupToTreeData: any = ref([]);
1215
1216 watch(() => dsSelectGroup.value, (val) => {
1217 if (val) {
1218 dsByGroupFromTreeDataLoading.value = true;
1219 getQualityTreeData(val).then((res: any) => {
1220 dsByGroupFromTreeDataLoading.value = false;
1221 if (res.code == proxy.$passCode) {
1222 dsByGroupFromTreeData.value = res.data || [];
1223 if (initDefaultGroup.value == true) {
1224 initDefaultGroup.value = false;
1225 modelFormItems.value[3].default = selectModelsByGroup.value = dsByGroupFromTreeData.value;
1226 let guids = selectModelsByGroup.value.map((m: any) => m.guid);
1227 getModelCountListData(guids);
1228 getModelRuleCountListData(guids);
1229 }
1230 } else {
1231 dsByGroupFromTreeData.value = [];
1232 ElMessage.error(res.msg);
1233 }
1234 })
1235 } else {
1236 dsByGroupFromTreeData.value = [];
1237 }
1238 })
1239
1240 const cancelModelByGroupDialog = () => {
1241 modelsByGroupDialogVisible.value = false;
1242 }
1243
1244 const submitModelsByGroup = () => {
1245 if (!dsByGroupToTreeData.value.length) {
1246 ElMessage.error('已选表不能为空');
1247 return;
1248 }
1249 let formInline = modelFormRef.value?.formInline || {};
1250 selectModelsByGroup.value = dsByGroupToTreeData.value.slice(0);
1251 modelFormItems.value[3].default = selectModelsByGroup.value;
1252 // http://test.csylcloud.com:8380/browse/WZL-511
1253 let item1 = modelFormItems.value[0];
1254 item1.default = formInline[item1.field];
1255 let item2 = modelFormItems.value[1];
1256 item2.default = formInline[item2.field];
1257 modelFormRef.value?.ruleFormRef?.validateField('qualityModelGuidsByGroup');
1258 let guids = selectModelsByGroup.value.map((m: any) => m.guid);
1259 getModelCountListData(guids);
1260 getModelRuleCountListData(guids);
1261 modelsByGroupDialogVisible.value = false;
1262 }
1263
1264 const handleModelByGroupLeftCheckedChange = (nodeObj, fromCheckAll, checked, fromTree, filterFrom, leafChecked) => {
1265 if (nodeObj.children?.length && (nodeObj.children?.length) && !checked && leafChecked) {
1266 //全选变半选
1267 return;
1268 }
1269 if (filterFrom && nodeObj.label.indexOf(filterFrom) === -1) {
1270 return;
1271 }
1272 let nodeId = nodeObj.guid;
1273 let index = dsByGroupToTreeData.value.findIndex((t) => t.guid === nodeId);
1274 if (checked) {
1275 index === -1 && dsByGroupToTreeData.value.push(nodeObj);
1276 } else {
1277 if (index > -1) {
1278 dsByGroupToTreeData.value.splice(index, 1);
1279 }
1280 }
1281 }
1282
1283 /** 切换了分组,清空表选择 */
1284 const handleSelectGroupChange = (val) => {
1285 dsByGroupToTreeData.value = [];
1286 }
1287
1288 /** 规则设置对话框 */
1289 const ruleSettingDialogVisible = ref(false);
1290
1291 /** 记录根据模型表查出的表格数据。 */
1292 const modelRulesTableData = ref([]);
1293
1294 /** 指定模型表对应的规则设置表格。 */
1295 const modelRulesTableInfo: any = ref({
1296 id: 'rule-model-table',
1297 loading: false,
1298 minPanelHeight: '200px',
1299 minHeight: '200px',
1300 height: '100%',
1301 nodeKey: 'guid',
1302 multiple: true,
1303 selectable: !isDetail.value,
1304 fields: [
1305 { label: "序号", type: "index", width: 56, align: "center" },
1306 { label: "表名", field: "name", width: 140 },
1307 { label: "规则名称", field: "ruleConfName", width: 140 },
1308 { label: "规则大类", field: "largeCategoryName", width: 140 },
1309 { label: "规则小类", field: "smallCategoryName", width: 140 },
1310 { label: "规则类型", field: "ruleName", width: 140 },
1311 { label: "字段", field: "ruleField", width: 140 },
1312 ],
1313 data: modelRulesTableData.value,
1314 showPage: false,
1315 actionInfo: {
1316 show: false
1317 }
1318 });
1319
1320 const modelRuleSelectedRows: any = ref([]);
1321
1322 const handleModelRulesSelectionChange = (val) => {
1323 modelRuleSelectedRows.value = val;
1324 }
1325
1326 /** 取消规则设置对话框。 */
1327 const cancelRuleSettingDialog = () => {
1328 ruleSettingDialogVisible.value = false;
1329 }
1330
1331 /** 提交规则设置对话框。 */
1332 const submitRuleSetting = () => {
1333 if (!modelRuleSelectedRows.value?.length) {
1334 ElMessage.error('质量规则集至少选择一个规则');
1335 return;
1336 }
1337 let oldLen = ruleWeightData.value.length;
1338 // 跟原始规则对比,未勾选的规则,需要同步将规则数量减少。
1339 let selectRows = modelRuleSelectedRows.value.slice(0);
1340 currTableData.value.modelRuleConfs.forEach(rule => {
1341 let index = selectRows.findIndex(s => s.guid == rule.guid)
1342 if (index > -1) {
1343 selectRows.splice(index, 1);
1344 return;
1345 }
1346 // 取消选中了。总量要减1.
1347 let dIndex = ruleWeightData.value.findIndex(r => r.largeCategory == rule.largeCategory);
1348 if (dIndex > -1) {
1349 let d = ruleWeightData.value[dIndex];
1350 d.ruleCount--;
1351 if (d.ruleCount == 0) {
1352 ruleWeightData.value.splice(dIndex, 1);
1353 }
1354 currTableData.value.ruleCount--;
1355 currTableData.value.largeCategoryNum[rule.largeCategory]--;
1356 if (!currTableData.value.largeCategoryNum[rule.largeCategory]) {
1357 delete currTableData.value.largeCategoryNum[rule.largeCategory];
1358 }
1359 }
1360 });
1361 selectRows.forEach(s => {
1362 let d = ruleWeightData.value.find(r => r.largeCategory == s.largeCategory);
1363 currTableData.value.ruleCount++;
1364 if (!d) {
1365 let info = {
1366 largeCategory: s.largeCategory,
1367 largeCategoryName: s.largeCategoryName,
1368 ruleCount: 1,
1369 };
1370 ruleWeightData.value.push(info);
1371 } else {
1372 d.ruleCount++;
1373 }
1374 if (!currTableData.value.largeCategoryNum[s.largeCategory]) {
1375 currTableData.value.largeCategoryNum[s.largeCategory] = 1;
1376 } else {
1377 currTableData.value.largeCategoryNum[s.largeCategory]++;
1378 }
1379 });
1380 //http://test.csylcloud.com:8380/browse/WZL-529
1381 if (ruleWeightData.value.length == 1) {//只剩一条
1382 ruleWeightData.value[0].ruleLargeWeight = changeNum(100, 2, true);
1383 } else if (oldLen == 1 && ruleWeightData.value.length > 1) { //原始一条,现在多条
1384 let v = changeNum(100 / ruleWeightData.value.length, 2, true);
1385 ruleWeightData.value.forEach(d => d.ruleLargeWeight = v);
1386 }
1387 // 最后剩余的selectRows要加在总量中。
1388 currTableData.value.modelRuleConfGuids = modelRuleSelectedRows.value?.map(r => r.guid);
1389 currTableData.value.modelRuleConfs = modelRuleSelectedRows.value;
1390 ruleSettingDialogVisible.value = false;
1391 }
1392
1393 const selectSubjectTables = computed(() => {
1394 let v = modelFormRef.value?.formInline;
1395 if (!v) {
1396 return [];
1397 }
1398 if (v.planType === 1) {
1399 if (planGuid && !treeSelectNode.value) { // 编辑,可能没有treeSelectNode。解决WZL-545问题。
1400 let tInfo = planDetailInfo.value.modelCount?.[0];
1401 return (!v.qualityModelGuid || !tInfo) ? [] : [{
1402 name: tInfo.modelName,
1403 groupName: tInfo.modelGroupName,
1404 guid: tInfo.modelGuid,
1405 parentGuid: tInfo.modelGroupGuid,
1406 subjectGuid: tInfo.subjectGuid
1407 }];
1408 }
1409 return v.qualityModelGuid ? [treeSelectNode.value] : [];
1410 }
1411 if (v.planType === 3) {
1412 return selectModelsByGroup.value;
1413 }
1414 return selectModels.value;
1415 });
1416
1417 /** 记录分组json的过滤条件。最后提交时需要转化到table里。 */
1418 const batchFiltersValue: any = ref({});
1419
1420 const batchFiltersValueChange = (value) => {
1421 batchFiltersValue.value = value;
1422 }
1423
1424 </script>
1425
1426 <template>
1427 <div class="container_wrap full" v-loading="fullscreenLoading">
1428 <div class="content_main">
1429 <div class="top_tool_wrap">
1430 <StepBar :steps-info="stepsInfo" />
1431 </div>
1432 <div class="operator_panel_wrap" v-show="step == 0">
1433 <div class="operator_panel is-block">
1434 <div class="panel_title">
1435 <div class="title_text">
1436 <span>1.1选择模型</span>
1437 <span class="tips_text">选择质量规则集,会带出模型下所有启用状态下的规则</span>
1438 </div>
1439 </div>
1440 <div class="panel_content">
1441 <div class="form_panel">
1442 <Form ref="modelFormRef" :readonly="isDetail" :itemList="modelFormItems" formId="edit-standard-form"
1443 :rules="modelFormRules" col="col3" @btnClick="btnFormClick" @selectChange="selectChange"
1444 @treeSelectLoad="modelFormTreeSelectLoad" @treeSelectNodeChange="modelFormTreeSelectNodeChange" />
1445 </div>
1446 </div>
1447 </div>
1448 <div class="operator_panel is-block">
1449 <div class="panel_title">
1450 <div class="title_text">
1451 <span>1.2选择规则</span>
1452 <span class="tips_text">根据表选择需要执行的规则,不需要的执行规则选择删除</span>
1453 </div>
1454 </div>
1455 <div class="panel_content_rule">
1456 <div class="tools_btns">
1457 <el-button type="primary" @click="handleFiltersAdd">批量配置过滤条件</el-button>
1458 <span class="tips_text">批量给表添加质检数据范围,不需要写where关键字,支持函数。未填写默认质检全部数据。</span>
1459 </div>
1460 <Table :tableInfo="ruleModelTableInfo" @tableBtnClick="tableBtnClick" />
1461 </div>
1462 </div>
1463 <div class="operator_panel is-block">
1464 <div class="panel_title">
1465 <div class="title_text">
1466 <span>1.3设置权重</span>
1467 <span class="tips_text">选择需要执行的规则,不需要的执行规则选择删除</span>
1468 </div>
1469 </div>
1470 <div class="panel_content_weight">
1471 <el-table ref="ruleWeightTableRef" v-loading="ruleWeightDataLoading" :data="ruleWeightData" height="100%"
1472 :highlight-current-row="true" stripe tooltip-effect="light" border
1473 :style="{ height: '100%', width: 'auto', 'max-width': '100%', display: 'inline-block' }">
1474 <el-table-column prop="largeCategoryName" label="规则大类" width="150px" align="left" show-overflow-tooltip>
1475 </el-table-column>
1476 <el-table-column prop="ruleCount" label="规则数量" width="150px" header-align="right" align="right"
1477 show-overflow-tooltip>
1478 </el-table-column>
1479 <el-table-column prop="ruleLargeWeight" label="规则权重(总分100)" width="200px"
1480 :align="isDetail ? 'right' : 'left'" show-overflow-tooltip>
1481 <template #default="scope">
1482 <el-input v-if="!isDetail" v-model.trim="scope.row['ruleLargeWeight']" placeholder="请输入"
1483 @change="(val) => inputWeightChange(val, scope.row)"
1484 @input="(val) => inputEventWeightChange(val, scope.row)"></el-input>
1485 <span v-else>{{ scope.row['ruleLargeWeight'] }}</span>
1486 </template>
1487 </el-table-column>
1488 </el-table>
1489 </div>
1490 </div>
1491 </div>
1492 <div class="operator_panel_wrap" v-show="step == 1">
1493 <div class="operator_panel is-block">
1494 <div class="panel_title">
1495 <div class="title_text">
1496 <span>2.1任务调度</span>
1497 <!-- <span class="tips_text">选择质量规则集,会带出模型下所有启用状态下的规则</span> -->
1498 </div>
1499 </div>
1500 <div class="panel_content">
1501 <div class="form_panel form_task">
1502 <Form ref="taskFormRef" :readonly="isDetail" :itemList="taskFormItems" formId="edit-standard-form"
1503 :rules="taskFormRules" @switchChange="handleTaskCreateChange" @scheduleChange="scheduleChange"
1504 @btnClick="taskFormBtnClick" />
1505 </div>
1506 </div>
1507 </div>
1508 </div>
1509 </div>
1510 <div class="bottom_tool_wrap">
1511 <template v-if="step == 0">
1512 <el-button @click="cancelPlan">取消</el-button>
1513 <el-button type="primary" @click="changeStep(2)">下一步</el-button>
1514 </template>
1515 <template v-else>
1516 <el-button @click="cancelPlan">取消</el-button>
1517 <el-button type="primary" @click="changeStep(1)">上一步</el-button>
1518 <el-button v-if="!isDetail" type="primary" v-preReClick @click="save">保存</el-button>
1519 </template>
1520 </div>
1521
1522 <!-- 以数据库为粒度的选择表对话框 -->
1523 <el-dialog v-model="modelsDialogVisible" title="选择质量规则集" width="660" :modal="true" :close-on-click-modal="false"
1524 destroy-on-close align-center>
1525 <div style="height:450px">
1526 <TreeTransfer :readOnly="isDetail" mode="transfer" :title="['可选表', '已选表']" :defaultProps="{
1527 label: 'name',
1528 value: 'guid',
1529 isLeaf: 'isLeaf'
1530 }" :from-tree-data-loading="dsFromTreeDataLoading" :from_data="dsFromTreeData" :to_data="dsToTreeData"
1531 checkOnClickNode :from_checked_all="false" node_key="guid" :transferOpenNode="true" width="100%"
1532 @left-check-change="handleModelLeftCheckChange" lazy :lazyFn="loadNode" height="100%">
1533 <template v-slot:from>
1534 <el-select style="width: 100%;margin-bottom: 8px;" v-model="databaseInfo" placeholder="请选择数据库" filterable
1535 :disabled="isDetail" @change="handleSelectDsModelChange" value-key="guid">
1536 <el-option v-for="opt in databaseList" :key="opt['guid']" :label="opt['name']" :value="opt['guid']" />
1537 </el-select>
1538 </template>
1539 </TreeTransfer>
1540 </div>
1541 <template #footer v-if="!isDetail">
1542 <div class="dialog-footer">
1543 <el-button @click="cancelModelDialog" v-preReClick>取消</el-button>
1544 <el-button @click="submitModels" type="primary" v-preReClick>确定</el-button>
1545 </div>
1546 </template>
1547 </el-dialog>
1548 <!-- 以分组为粒度的选择表对话框 -->
1549 <el-dialog v-model="modelsByGroupDialogVisible" title="选择质量规则集" width="660" :modal="true"
1550 :close-on-click-modal="false" destroy-on-close align-center>
1551 <div style="height:450px">
1552 <TreeTransferChecked class="one-level" mode="transfer" :title="['可选表', '已选表']" :defaultProps="{
1553 label: 'name',
1554 value: 'guid'
1555 }" :from-tree-data-loading="dsByGroupFromTreeDataLoading" :from_data="dsByGroupFromTreeData" checkOnClickNode
1556 :to_data="dsByGroupToTreeData" node_key="guid" :transferOpenNode="true" width="100%"
1557 :defaultCheckedKeys="dsByGroupToTreeData.map(d => d.guid)" :rootPidValue="''"
1558 @left-check-changed="handleModelByGroupLeftCheckedChange" height="100%">
1559 <template v-slot:from>
1560 <el-select style="width: 100%;margin-bottom: 8px;" v-model="dsSelectGroup" placeholder="请选择分组" filterable
1561 :disabled="isDetail" @change="handleSelectGroupChange" value-key="guid">
1562 <el-option v-for="opt in qualityGroups" :key="opt['guid']" :label="opt['name']" :value="opt['guid']" />
1563 </el-select>
1564 </template>
1565 </TreeTransferChecked>
1566 </div>
1567 <template #footer v-if="!isDetail">
1568 <div class="dialog-footer">
1569 <el-button @click="cancelModelByGroupDialog">取消</el-button>
1570 <el-button @click="submitModelsByGroup" type="primary">确定</el-button>
1571 </div>
1572 </template>
1573 </el-dialog>
1574
1575 <!-- 规则设置对话框 -->
1576 <el-dialog v-model="ruleSettingDialogVisible" title="规则设置" width="800" :modal="true" :close-on-click-modal="false"
1577 destroy-on-close align-center>
1578 <div style="height:400px">
1579 <Table ref="modelRulesTableRef" :class="isDetail ? 'disable-table' : ''" :tableInfo="modelRulesTableInfo"
1580 @table-selection-change="handleModelRulesSelectionChange" />
1581 </div>
1582 <template #footer v-if="!isDetail">
1583 <div class="dialog-footer">
1584 <el-button @click="cancelRuleSettingDialog">取消</el-button>
1585 <el-button @click="submitRuleSetting" type="primary">确定</el-button>
1586 </div>
1587 </template>
1588 </el-dialog>
1589
1590 <filtersSettingBatch ref="batchFilterDialogRef" :subject-tables="selectSubjectTables" :readOnly="isDetail"
1591 @filtersValueChange="batchFiltersValueChange"></filtersSettingBatch>
1592 </div>
1593 </template>
1594
1595 <style lang="scss" scoped>
1596 .top_tool_wrap {
1597 width: 100%;
1598 height: 72px;
1599 margin: 8px 0 0px;
1600 display: flex;
1601 justify-content: center;
1602 align-items: center;
1603
1604 :deep(.el-steps) {
1605 width: 30%;
1606 }
1607 }
1608
1609 .content_main {
1610 height: calc(100% - 40px);
1611 padding: 0 16px;
1612 overflow: hidden auto;
1613
1614 :deep(.table_panel_wrap) {
1615 &.full {
1616 height: auto;
1617 }
1618
1619 .table_panel {
1620 width: 100%;
1621 min-height: unset;
1622 }
1623 }
1624
1625 .operator_panel_wrap {
1626 display: flex;
1627 justify-content: space-between;
1628 flex-wrap: wrap;
1629 height: auto;
1630
1631 :deep(.el-button) {
1632 &.is-text {
1633 height: auto;
1634 padding: 0;
1635 }
1636 }
1637
1638 .operator_panel {
1639 width: calc(50% - 5px);
1640 height: auto;
1641 border: 1px solid #d9d9d9;
1642 margin-bottom: 12px;
1643 overflow: hidden;
1644
1645 &.is-block {
1646 width: 100%;
1647 }
1648
1649 .panel_title {
1650 height: 44px;
1651 padding: 0 15px;
1652 display: flex;
1653 justify-content: space-between;
1654 align-items: center;
1655 color: var(--el-color-regular);
1656 font-weight: 600;
1657 border-bottom: 1px solid #d9d9d9;
1658 background-color: #fafafa;
1659
1660 .tips_text {
1661 font-size: 14px;
1662 color: #999;
1663 font-weight: normal;
1664 margin-left: 8px;
1665 }
1666 }
1667
1668 .panel_content {
1669 height: calc(100% - 42px);
1670
1671 >div {
1672 width: 100%;
1673 height: 100%;
1674 overflow: hidden;
1675 }
1676
1677 .form_panel {
1678 padding: 8px 16px;
1679 overflow: hidden auto;
1680 }
1681
1682 .form_task {
1683 width: 60%;
1684 }
1685
1686 .tree_search_input {
1687 margin-bottom: 10px;
1688 }
1689
1690 .list_panel {
1691 padding: 10px 0;
1692 overflow: hidden auto;
1693
1694 .list_item {
1695 height: 32px;
1696 padding: 0 10px;
1697 display: flex;
1698 justify-content: space-between;
1699 align-items: center;
1700
1701 &:hover {
1702 color: var(--g-sub-sidebar-menu-active-color);
1703 background-color: var(--g-sub-sidebar-menu-active-bg);
1704 }
1705 }
1706 }
1707
1708 .table_content_wrap {
1709 padding: 16px;
1710
1711 .tools_form {
1712 p {
1713 margin-top: 0;
1714 margin-bottom: 8px;
1715 }
1716 }
1717 }
1718 }
1719
1720 .panel_content_rule {
1721 height: 280px;
1722 padding: 12px 16px;
1723
1724 .table_panel {
1725 min-height: 200px;
1726 height: calc(100% - 36px) !important;
1727 }
1728
1729 .tools_btns {
1730 margin-bottom: 8px;
1731
1732 .tips_text {
1733 margin-left: 8px;
1734 font-size: 12px;
1735 color: #b2b2b2;
1736 }
1737 }
1738 }
1739
1740 .panel_content_weight {
1741 height: 230px;
1742 min-height: 230px;
1743 padding: 12px 16px;
1744
1745 :deep(.el-table) {
1746 & td.el-table__cell {
1747 padding: 2px 0;
1748 height: 36px;
1749 }
1750 }
1751 }
1752 }
1753
1754 .bottm_tools {
1755 width: 100%;
1756 height: 40px;
1757 display: flex;
1758 justify-content: center;
1759 align-items: center;
1760 background: #fafafa;
1761 color: #999;
1762 font-size: 14px;
1763 border: 1px dashed var(--el-border-color-regular);
1764 margin-bottom: 12px;
1765
1766 >span {
1767 margin-left: 8px;
1768 }
1769 }
1770 }
1771 }
1772
1773 .bottom_tool_wrap {
1774 height: 40px;
1775 padding: 0 16px;
1776 border-top: 1px solid #d9d9d9;
1777 display: flex;
1778 justify-content: flex-end;
1779 align-items: center;
1780 }
1781
1782 :deep(.disable-table) {
1783 .el-table {
1784 .el-table__header-wrapper .el-checkbox {
1785 display: none
1786 }
1787 }
1788 }
1789 </style>
1 <script lang="ts" setup name="filtersSettingBatch">
2 import { ref } from "vue";
3 import TreeTransferChecked from "@/components/TreeTransferChecked/index.vue";
4 import { ElMessage, ElMessageBox } from "element-plus";
5 import { Delete } from "@element-plus/icons-vue";
6 import {
7 batchValidateSubjectTableRule,
8 } from '@/api/modules/dataQuality';
9 import { cloneDeep } from 'lodash-es'
10
11 const { proxy } = getCurrentInstance() as any;
12
13 /** 记录每个分组对应的表信息和过滤条件 */
14 interface GroupFilterTableInfo {
15 tables?: Array<any>;
16 filter?: string;
17 checked?: boolean;
18 }
19
20 const props = defineProps({
21 subjectTables: {
22 type: Array<any>,
23 default: [],
24 },
25 readOnly: {
26 type: Boolean,
27 default: false,
28 }
29 });
30
31 const emits = defineEmits(["filtersValueChange"]);
32
33 /** 批量添加过滤条件对话框。 */
34 const filterDialogVisible = ref(false);
35
36 /** 打开对话框时过滤条件的值。 */
37 const filterPropsValue: Ref<GroupFilterTableInfo> = ref({});
38
39 /** 记录分组数组列表。 */
40 const groupData: any = ref([]);
41
42 /** 过滤条件初始默认值 */
43 let dialogInitValue: any = ref({ 1: { tables: [], filter: '', checked: false } });
44
45 /** json 分组对应的表信息过滤条件。 */
46 const groupFilterTables: any = computed(() => {
47 let keys = Object.keys(filterPropsValue.value);
48 return keys.length ? filterPropsValue.value : dialogInitValue.value;
49 });
50
51 const selectGroupIndex = ref(0);
52
53 const groupItemClick = (item, index) => {
54 selectGroupIndex.value = index;
55 }
56
57 /** 添加新的分组 */
58 const handleAddGroup = () => {
59 let len = groupData.value.length;
60 let guid = 1;
61 if (len) {
62 let lastId = parseInt(groupData.value[len - 1].guid);
63 guid = lastId + 1;
64 }
65 groupData.value.push({
66 guid: guid,
67 label: `分组${guid}`
68 });
69 selectGroupIndex.value = len;
70 groupFilterTables.value[guid] = { tables: [], filter: '' };
71 }
72
73 /** 给当前选中的分组选择对应的表 */
74 const handleSelectTables = () => {
75 selectTableDialogVisible.value = true;
76 dsFromTreeData.value = props.subjectTables || [];
77 let selectGroup = groupData.value[selectGroupIndex.value];
78 dsToTreeData.value = groupFilterTables.value[selectGroup.guid].tables?.slice(0) || [];
79 }
80
81 /** 删除选择表 */
82 const removeTable = (item, index) => {
83 groupFilterTables.value[groupData.value[selectGroupIndex.value].guid].tables.splice(index, 1);
84 }
85
86 /** 删除分组 */
87 const removeGroup = (item, index) => {
88 let info = groupFilterTables.value[item.guid];
89 if (!info?.tables?.length && !info.filter) {
90 if (selectGroupIndex.value == 0) {
91 selectGroupIndex.value = groupData.value.length == 1 ? -1 : 0;
92 } else if (selectGroupIndex.value >= index) {
93 selectGroupIndex.value--;
94 }
95 groupData.value.splice(index, 1);
96 delete groupFilterTables.value[item.guid];
97 return;
98 } else {
99 ElMessageBox.confirm("该分组下存在表或过滤条件,此操作将永久删除, 是否继续?", "提示", {
100 confirmButtonText: "确定",
101 cancelButtonText: "取消",
102 type: "warning",
103 })
104 .then(() => {
105 if (selectGroupIndex.value == 0) {
106 selectGroupIndex.value = groupData.value.length == 1 ? -1 : 0;
107 } else if (selectGroupIndex.value >= index) {
108 selectGroupIndex.value--;
109 }
110 groupData.value.splice(index, 1);
111 delete groupFilterTables.value[item.guid];
112 ElMessage({
113 type: "success",
114 message: "删除成功",
115 });
116 })
117 .catch(() => {
118 ElMessage({
119 type: "info",
120 message: "已取消删除",
121 });
122 });
123 }
124 }
125
126 const handleSqlInputChange = (val) => {
127 let selectGroup = groupData.value[selectGroupIndex.value];
128 groupFilterTables.value[selectGroup.guid].checked = false;
129 }
130
131 const validateSqlBtnDisable = ref(false);
132
133 /** 验证填写的过滤条件 */
134 const validateSql = () => {
135 validateSqlBtnDisable.value = true;
136 let selectGroup = groupData.value[selectGroupIndex.value]
137 let enName = selectGroup.label;
138 let tables = groupFilterTables.value[selectGroup.guid].tables;
139 let filter = groupFilterTables.value[selectGroup.guid].filter;
140 if (!filter) {
141 ElMessage.error('请先填写过滤条件再验证sql');
142 return;
143 }
144 if (!tables?.length) {
145 ElMessage.error('请先选择表再验证sql');
146 return;
147 }
148 batchValidateSubjectTableRule(
149 tables.map(t => {
150 return {
151 subjectGuid: t.subjectGuid,
152 condition: filter
153 }
154 })
155 ).then((res: any) => {
156 validateSqlBtnDisable.value = false;
157 if (res.code == proxy.$passCode) {
158 let isError = res.data.some(d => d.errInfo);
159 if (isError) {
160 let error = "";
161 res.data.forEach((d, index) => {
162 if (d.errInfo) {
163 error = error ? ';' : error;
164 error = error + '【' + tables[index].name + '】' + d.errInfo;
165 }
166 });
167 ElMessage.error('【' + enName + '】过滤条件验证失败:' + error);
168 } else {
169 groupFilterTables.value[selectGroup.guid].checked = true;
170 ElMessage.success('【' + enName + '】过滤条件验证通过');
171 }
172 } else {
173 ElMessage.error(res.msg);
174 }
175 });
176 }
177
178 const submitFilter = () => {
179 let index = 0;
180 for (const group of groupData.value) {
181 let filtersInfo = groupFilterTables.value[group.guid];
182 if (!filtersInfo?.tables.length) {
183 ElMessage.error('请先选择【' + group.label + '】对应的表');
184 selectGroupIndex.value = index;
185 return;
186 }
187 if (!filtersInfo?.filter) {
188 selectGroupIndex.value = index;
189 ElMessage.error('请填写【' + group.label + '】的过滤条件');
190 return;
191 }
192 if (!filtersInfo?.checked) {
193 selectGroupIndex.value = index;
194 ElMessage.error('请验证【' + group.label + '】的过滤条件');
195 return;
196 }
197 index++;
198 }
199 /** 验证有没有分组,验证每个分组的sql是否验证过,验证每个分组的表是否存在。 */
200 emits('filtersValueChange', groupFilterTables.value);
201 filterDialogVisible.value = false;
202 }
203
204 /** 打开对话框。*/
205 const openDialog = (v) => {
206 filterDialogVisible.value = true;
207 dialogInitValue.value = { 1: { tables: [], filter: '', checked: false } };
208 selectGroupIndex.value = 0;
209 filterPropsValue.value = cloneDeep(v);
210 let keys = Object.keys(filterPropsValue.value);
211 groupData.value = keys.length ? keys.map(k => {
212 return {
213 guid: k,
214 label: `分组${k}`
215 }
216 }): [{
217 guid: 1,
218 label: '分组1'
219 }];
220 if (props.subjectTables?.length == 1) {
221 dialogInitValue.value = { 1: { tables: props.subjectTables, filter: '', checked: false } };
222 }
223 }
224
225 /** 点击对话框取消按钮 */
226 const handleCloseFilterDialog = () => {
227 filterDialogVisible.value = false;
228 }
229
230
231 /** 选择表对话框处理 */
232 const selectTableDialogVisible = ref(false);
233
234 const dsFromTreeData: any = ref([]);
235
236 const dsToTreeData: any = ref([]);
237
238 const cancelSelectTableDialog = () => {
239 selectTableDialogVisible.value = false;
240 }
241
242 const submitSelectTableByGroup = () => {
243 if (!dsToTreeData.value.length) {
244 ElMessage.error('可选表不能为空');
245 return;
246 }
247 // 需要提示是否在别的分组中。
248 let selectGroup = groupData.value[selectGroupIndex.value];
249 let tables = dsToTreeData.value || [];
250 let str = '';
251 tables.forEach(t => {
252 for (const key in groupFilterTables.value) {
253 if (key == selectGroup.guid) {
254 continue
255 }
256 let keyTables = groupFilterTables.value[key].tables || [];
257 if (keyTables.some(kt => (kt.modelName && t.modelName && kt.modelName == t.modelName) || kt.name == t.name)) {
258 str = str + (str ? ';' : '') + `【${t.modelName ?? t.name}】从【分组${key}】中移入【${selectGroup.label}`;
259 }
260 }
261 });
262 if (!str) {
263 selectTableDialogVisible.value = false;
264 if (dsToTreeData.value.some(d => !groupFilterTables.value[selectGroup.guid].tables?.some(t => t.guid == d.guid))) {
265 groupFilterTables.value[selectGroup.guid].checked = false;
266 }
267 groupFilterTables.value[selectGroup.guid].tables = dsToTreeData.value;
268 } else {
269 ElMessageBox.confirm(
270 "确定将" + str + '?',
271 "提示",
272 {
273 confirmButtonText: "确定",
274 cancelButtonText: "取消",
275 type: "warning",
276 }
277 )
278 .then(() => {
279 tables.forEach(t => {
280 for (const key in groupFilterTables.value) {
281 if (key == selectGroup.guid) {
282 continue
283 }
284 let keyTables = groupFilterTables.value[key].tables || [];
285 let index = keyTables.findIndex(kt => (kt.modelName && t.modelName && kt.modelName == t.modelName) || kt.name == t.name);
286 if (index > -1) {
287 groupFilterTables.value[key].tables.splice(index, 1);
288 }
289 }
290 });
291 selectTableDialogVisible.value = false;
292 if (dsToTreeData.value.some(d => !groupFilterTables.value[selectGroup.guid].tables?.some(t => t.guid == d.guid))) {
293 groupFilterTables.value[selectGroup.guid].checked = false;
294 }
295 groupFilterTables.value[selectGroup.guid].tables = dsToTreeData.value;
296 })
297 .catch(() => {
298 ElMessage({
299 type: "info",
300 message: "已取消",
301 });
302 });
303 }
304 }
305
306 const handleModelLeftCheckedChange = (nodeObj, fromCheckAll, checked, fromTree, filterFrom, leafChecked) => {
307 if (filterFrom && nodeObj.label.indexOf(filterFrom) === -1) {
308 return;
309 }
310 let nodeId = nodeObj.guid;
311 let index = dsToTreeData.value.findIndex((t) => t.guid === nodeId);
312 if (checked) {
313 index === -1 && dsToTreeData.value.push(nodeObj);
314 } else {
315 if (index > -1) {
316 dsToTreeData.value.splice(index, 1);
317 }
318 }
319 }
320
321 defineExpose({
322 openDialog
323 });
324
325 </script>
326
327 <template>
328 <el-dialog v-model="filterDialogVisible" title="批量配置过滤条件" width="650" :modal="true" :close-on-click-modal="false"
329 destroy-on-close align-center @close="handleCloseFilterDialog">
330 <div class="filter-dialog-content">
331 <div class="group-list">
332 <div class="field-title">
333 <span>分组</span>
334 <el-button v-if="!props.readOnly" @click="handleAddGroup" link v-preReClick>新增</el-button>
335 </div>
336 <div class="list_unit">
337 <div class="list_item" :class="{ active: index == selectGroupIndex }" v-for="(item, index) in groupData"
338 @click="groupItemClick(item, index)">
339 <span>{{ item.label }}</span>
340 <!-- 删除按钮 -->
341 <el-button type="primary" text :icon="Delete" @click.stop="removeGroup(item, index)"
342 v-preReClick></el-button>
343 </div>
344 </div>
345 </div>
346 <div class="table-list" v-if="selectGroupIndex >= 0">
347 <div class="field-title">
348 <span></span>
349 <el-button v-if="!props.readOnly" @click="handleSelectTables" link>选择表</el-button>
350 </div>
351 <div class="list_unit">
352 <div class="list_item"
353 v-for="(item, index) in (groupFilterTables[groupData[selectGroupIndex].guid].tables || [])">
354 <span>{{ item.name }}</span>
355 <el-button type="primary" text :icon="Delete" @click.stop="removeTable(item, index)"
356 v-preReClick></el-button>
357 </div>
358 </div>
359 <div class="filter-edit">
360 <div class="field-title">
361 <span>过滤条件</span>
362 <el-button @click="validateSql" link v-preReClick>验证</el-button>
363 </div>
364 <el-input v-model="groupFilterTables[groupData[selectGroupIndex].guid].filter" ref="sqlInputRef"
365 :disabled="props.readOnly" type="textarea" :autosize="true" resize="none"
366 @change="handleSqlInputChange"></el-input>
367 </div>
368 </div>
369 </div>
370 <template #footer v-if="!props.readOnly">
371 <div class="dialog-footer">
372 <el-button @click="handleCloseFilterDialog">取消</el-button>
373 <el-button @click="submitFilter" type="primary">确定</el-button>
374 </div>
375 </template>
376 </el-dialog>
377 <!-- 选择表对话框 -->
378 <el-dialog v-model="selectTableDialogVisible" title="选择表" width="600" :modal="true" :close-on-click-modal="false"
379 destroy-on-close align-center>
380 <div style="height:400px">
381 <TreeTransferChecked class="one-level" mode="transfer" :title="['可选表', '已选表']" :from_data="dsFromTreeData" checkOnClickNode
382 :to_data="dsToTreeData" :default-checked-keys="dsToTreeData?.map(d => d.guid)" :defaultProps="{
383 label: 'name',
384 value: 'guid'
385 }" node_key="guid" :transferOpenNode="true" width="100%" height="100%" :rootPidValue="''"
386 @left-check-changed="handleModelLeftCheckedChange"></TreeTransferChecked>
387 </div>
388 <template #footer>
389 <div class="dialog-footer">
390 <el-button @click="cancelSelectTableDialog">取消</el-button>
391 <el-button @click="submitSelectTableByGroup" type="primary">确定</el-button>
392 </div>
393 </template>
394 </el-dialog>
395 </template>
396
397 <style lang="scss" scoped>
398 .filter-dialog-content {
399 display: flex;
400 flex-direction: row;
401 margin: -8px -24px;
402 height: 460px;
403
404 .group-list {
405 width: 200px;
406 border-right: 1px solid #d9d9d9;
407 }
408
409 .table-list {
410 width: calc(100% - 200px);
411 border-right: 1px solid #d9d9d9;
412 }
413
414 .list_unit {
415 height: 200px;
416 border-top: 1px solid #d9d9d9;
417 overflow-y: auto;
418
419 .list_item {
420 height: 32px;
421 font-size: 14px;
422 color: #212121;
423 line-height: 32px;
424 padding-left: 8px;
425 display: flex;
426 flex-direction: row;
427 justify-content: space-between;
428 align-items: center;
429 padding-right: 8px;
430 cursor: pointer;
431
432 &.active {
433 background: #EBF6F7;
434 }
435
436 .el-button {
437 display: none;
438 }
439
440 &:hover {
441 .el-button {
442 display: inline-flex;
443 }
444
445 .el-button.is-text:not(.is-disabled):hover,
446 .el-button.is-text:not(.is-disabled):focus {
447 background-color: transparent;
448 }
449 }
450 }
451 }
452
453 .filter-edit {
454 height: calc(100% - 232px);
455 border-top: 1px solid #d9d9d9;
456 }
457
458 .field-title {
459 height: 32px;
460 font-size: 14px;
461 color: #666;
462 line-height: 32px;
463 padding-left: 8px;
464 display: flex;
465 flex-direction: row;
466 justify-content: space-between;
467 align-items: center;
468 padding-right: 8px;
469 }
470
471 :deep(.el-textarea) {
472 height: calc(100% - 32px);
473 padding: 0px 8px 8px 8px;
474
475 .el-textarea__inner {
476 min-height: 100% !important;
477 border-radius: 0px;
478 }
479 }
480 }
481 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: qualityAnalysis
3 </route>
4
5 <script lang="ts" setup name="qualityAnalysis">
6 import { ref } from 'vue'
7 import { ElMessage, ElMessageBox } from "element-plus";
8 import TableTools from '@/components/Tools/table_tools.vue'
9 import Table from '@/components/Table/index.vue'
10 import Dialog from '@/components/Dialog/index.vue'
11 import { useRouter } from "vue-router";
12 import {
13 getQualityWordList,
14 deleteQualityWord,
15 addQualityWord,
16 updateQualityWord,
17 executeReport,
18 stateChange
19 } from '@/api/modules/dataQualityWord';
20 import {
21 getPlanList
22 } from '@/api/modules/dataQualityAssess';
23 import {
24 getCronExecTime
25 } from '@/api/modules/queryService';
26 import { TableColumnWidth } from '@/utils/enum';
27
28 const { proxy } = getCurrentInstance() as any;
29
30 const router = useRouter();
31 const searchItemList = ref([
32 {
33 type: 'input',
34 label: '',
35 maxlength: 50,
36 field: 'analysisReportName',
37 default: '',
38 placeholder: '报告名称',
39 clearable: true
40 }
41 ])
42
43 const currTableData: any = ref<Object>({});
44 const page = ref({
45 limit: 50,
46 curr: 1,
47 sizes: [
48 { label: "10", value: 10 },
49 { label: "50", value: 50 },
50 { label: "100", value: 100 },
51 { label: "150", value: 150 },
52 { label: "200", value: 200 },
53 ],
54 analysisReportName: '', //报告名称
55 });
56 const selectRowData = ref([])
57 const tableInfo = ref({
58 id: "word-manage-table",
59 // multiple: true,
60 loading: false,
61 fields: [
62 { label: "报告名称", field: "analysisReportName", width: 160 },
63 { label: "评估方案名称", field: "planName", width: 140 },
64 {
65 label: "方案类型", field: "planType", width: 90, getName: (scope) => {
66 let planType = scope.row.planType;
67 return planType == 1 ? '表' : (planType == 2 ? '数据库' : '分组');
68 }
69 },
70 { label: "质量规则集", field: "qualityModelName", width: 150 },
71 { label: "质量评分", field: "qualityScore", width: 100, align: 'right' },
72 {
73 label: '状态', field: 'state', type: 'switch', activeText: '上线', inactiveText: '下线', activeValue: 1, inactiveValue: 0, switchWidth: 56, width: 96, align: 'center', isDisabled: (scope) => {
74 return scope.row.isPlanGen == 'Y';
75 },toolTipContent:"质量评估方案生成的报告禁止上下线、编辑",columClass:"text_btn"
76 },
77 { label: "执行状态", field: "execState", type: 'tag', width: 100, align: 'center' },
78 {
79 label: "执行周期", field: "execCycle", width: TableColumnWidth.EXECCYCLE
80 },
81 { label: "下次执行时间", field: "nextExecTime", width: TableColumnWidth.DATETIME, },
82 { label: "最后执行时间", field: "lastExecTime", width: TableColumnWidth.DATETIME, },
83 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
84 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME, },
85 ],
86 data: [],
87 page: {
88 type: "normal",
89 rows: 0,
90 ...page.value,
91 },
92 actionInfo: {
93 label: "操作",
94 type: "btn",
95 width: 300,
96 fixed: 'right',
97 btns: (scope) => {
98 const row = scope.row
99 let btnsArr: any = [
100 { label: "查看报告", value: "reportView", disabled: row['execState'] != 2 },
101 { label: "编辑", value: "edit", disabled: row.execState === 1 || row.isExecute || scope.row.isPlanGen == 'Y' || scope.row.state == 1 }];
102 if (row.isExecute) {
103 btnsArr.splice(2, 0, { label: "执行中...", value: "execute", disabled: true })
104 } else {
105 btnsArr.splice(2, 0, { label: "手动执行", value: "execute", disabled: row.execState === 1 || scope.row.state == 0 })
106 }
107 btnsArr.push({ label: "执行日志", value: "path_log", disabled: !row['execState'] });
108 btnsArr.push({ label: "删除", value: "delete", disabled: row.isExecute || row.execState === 1 || scope.row.isPlanGen == 'Y' || scope.row.state == 1 });
109 return btnsArr
110 }
111 }
112 });
113
114 /** 评估方案选择列表。 */
115 const planList: any = ref([]);
116
117 const getAllPlanListPromise: any = ref(null);
118
119 onActivated(() => {
120 // 新建分析报告时应该只能选择上线的方案。
121 getAllPlanListPromise.value = getPlanList({ pageSize: -1, pageIndex: 1, state: 1 }).then((res: any) => {
122 getAllPlanListPromise.value = null;
123 if (res.code == proxy.$passCode) {
124 const data = res.data || {}
125 planList.value = data.records || [];
126 formItems.value[0].options = planList.value;
127 } else {
128 ElMessage({
129 type: 'error',
130 message: res.msg,
131 })
132 }
133 });
134 })
135
136 /** 新建编辑方案的对话框对象。 */
137 const wordDialogRef = ref();
138
139 const formItems = ref([
140 {
141 label: '评估方案名称',
142 type: 'select',
143 placeholder: '请选择',
144 field: 'planGuid',
145 default: '',
146 options: planList.value,
147 props: {
148 value: 'guid',
149 label: 'planName'
150 },
151 clearable: true,
152 filterable: true,
153 required: true,
154 disabled: false,
155 }, {
156 label: '报告名称',
157 type: 'input',
158 maxlength: 50,
159 placeholder: '请输入',
160 field: 'analysisReportName',
161 default: '',
162 clearable: true,
163 required: true
164 }, {
165 label: '报告保留期数(期)',
166 type: 'input',
167 inputType: 'integerNumber',
168 placeholder: '请输入',
169 field: 'reserveCycle',
170 default: 365,
171 max: 1000,
172 min: 1,
173 clearable: true,
174 required: false
175 },{
176 label: '是否上线',
177 type: 'switch',
178 field: 'state',
179 default: 0,
180 activeText:"上线",
181 inactiveText:"下线",
182 activeValue: 1,
183 inactiveValue: 0
184 },{
185 label: '执行周期',
186 type: 'input-popover-panel',
187 placeholder: '',
188 field: 'execCycle',
189 teleported: false,
190 default: "",
191 append: {
192 btn: { label: '执行时间', value: 'cron' }
193 },
194 required: true,
195 block: true
196 },
197 {
198 label: '接下来五次执行时间',
199 type: 'textarea',
200 placeholder: '执行时间',
201 field: 'zxzq',
202 clearable: true,
203 required: false,
204 rows: 5,
205 block: true,
206 visible: true,
207 readonly: true
208 },
209 ])
210
211 const formRules = ref({
212 planGuid: [{ required: true, trigger: 'change', message: "请填写评估方案" }],
213 analysisReportName: [{ required: true, trigger: 'blur', message: "请填写报告名称" }],
214 execCycle: [
215 {
216 required: true,
217 message: "请设置执行周期",
218 trigger: "blur",
219 },
220 ]
221 })
222
223 const dialogInfo = ref({
224 visible: false,
225 size: 700,
226 direction: "column",
227 header: {
228 title: "新建",
229 },
230 type: '',
231 contents: [
232 {
233 type: "form",
234 title: "",
235 formInfo: {
236 id: "word-edit-form",
237 items: formItems.value,
238 rules: formRules.value
239 },
240 }
241 ],
242 footer: {
243 visible: true,
244 btns: [
245 { type: "default", label: "取消", value: "cancel" },
246 { type: "primary", label: "确定", value: "submit" },
247 ],
248 },
249 });
250
251 const toSearch = (val: any, clear: boolean = false) => {
252 if (clear) {
253 page.value.analysisReportName = '';
254 } else {
255 page.value.analysisReportName = val.analysisReportName;
256 }
257 page.value.curr = 1;
258 getTableData();
259 };
260
261 const getTableData = () => {
262 tableInfo.value.loading = true;
263 getQualityWordList({ pageIndex: page.value.curr, pageSize: page.value.limit, analysisReportName: page.value.analysisReportName }).then((res: any) => {
264 tableInfo.value.loading = false;
265 if (res.code == proxy.$passCode) {
266 const data = res.data || {}
267 tableInfo.value.data = data.records || []
268 tableInfo.value.page.limit = data.pageSize
269 tableInfo.value.page.curr = data.pageIndex
270 tableInfo.value.page.rows = data.totalRows
271 } else {
272 ElMessage.error(res.msg);
273 }
274 })
275 };
276
277 const tableSelectionChange = (val) => {
278 selectRowData.value = val.map((item) => item.guid);
279 };
280
281 const tablePageChange = (info) => {
282 page.value.curr = Number(info.curr);
283 page.value.limit = Number(info.limit);
284 getTableData();
285 };
286
287 const tableBtnClick = (scope, btn) => {
288 const type = btn.value;
289 const row = scope.row;
290 currTableData.value = row;
291
292 if (type == "edit") {
293 dialogInfo.value.header.title = "编辑分析报告";
294 dialogInfo.value.type = type
295 dialogInfo.value.visible = true;
296 setFormItems(row);
297 formItems.value[0].disabled = true;
298 formItems.value[0].options = [{
299 guid: row.planGuid,
300 planName: row.planName
301 }]
302 } else if (type === 'reportView') {
303 router.push({
304 name: 'analysisReport',
305 query: {
306 planGuid: row.planGuid,
307 reportExecGuid: row.reportExecGuid,
308 name: row.analysisReportName,
309 }
310 });
311 } else if (type == 'path_log') {
312 router.push({
313 name: 'analysisLog',
314 query: {
315 guid: row.guid,
316 name: row.analysisReportName
317 }
318 });
319 } else if (type == "execute") {
320 row.isExecute = true
321 const guid = row.guid
322 executeReport(guid).then((res: any) => {
323 if (res.code == proxy.$passCode) {
324 getTableData();
325 ElMessage({
326 type: "success",
327 message: "手动执行提交成功",
328 });
329 } else {
330 ElMessage({
331 type: "error",
332 message: res.msg,
333 });
334 }
335 row.isExecute = false
336 }).catch(() => {
337 row.isExecute = false
338 })
339 } else if (type == 'delete') {
340 open("此操作将永久删除该分析报告,是否继续?", "warning");
341 } else if(type=='state') {
342
343 }
344 };
345 const tableSwitchBeforeChange = (scope, field, callback) => {
346 const row = scope.row
347 const msg = `确定【${scope.row[field] == 1 ? '下线' : '上线'}】该方案吗?`;
348 ElMessageBox.confirm(
349 msg,
350 '提示',
351 {
352 confirmButtonText: '确定',
353 cancelButtonText: '取消',
354 type: 'warning',
355 }
356 ).then(() => {
357 const guid = row.guid
358 const state1 = row.state
359 stateChange({guid,state:state1===1?0:1}).then((res:any)=>{
360 if(res.code===proxy.$passCode){
361 ElMessage.success(`${scope.row[field] == 1 ? '下线' : '上线'}成功`)
362 getTableData();
363 } else {
364 ElMessage.error(res.msg)
365 }
366 })
367 }).catch(() => {
368 callback(false)
369 })
370 }
371 const open = (msg, type, isBatch = false) => {
372 ElMessageBox.confirm(msg, "提示", {
373 confirmButtonText: "确定",
374 cancelButtonText: "取消",
375 type: type,
376 }).then(() => {
377 let guids = [currTableData.value.guid]
378 if (isBatch) {
379 guids = selectRowData.value
380 }
381 deleteQualityWord(guids).then((res: any) => {
382 if (res.code == proxy.$passCode) {
383 getTableData();
384 ElMessage({
385 type: "success",
386 message: "删除成功",
387 });
388 } else {
389 ElMessage({
390 type: "error",
391 message: res.msg,
392 });
393 }
394 });
395 });
396 };
397
398 /** 打开新建报告的编辑框 */
399 const handleCreateNewWord = () => {
400 if (getAllPlanListPromise.value) {
401 getAllPlanListPromise.value.then(() => {
402 setFormItems(null);
403 formItems.value[0].disabled = false;
404 formItems.value[0].options = planList.value;
405 dialogInfo.value.header.title = '新建分析报告'
406 dialogInfo.value.type = 'add'
407 dialogInfo.value.footer.visible = true
408 dialogInfo.value.visible = true
409 });
410 } else {
411 setFormItems(null);
412 formItems.value[0].disabled = false;
413 formItems.value[0].options = planList.value;
414 dialogInfo.value.header.title = '新建分析报告'
415 dialogInfo.value.type = 'add'
416 dialogInfo.value.footer.visible = true
417 dialogInfo.value.visible = true
418 }
419 };
420
421 const batching = (type) => {
422 if (type == 'delete') {
423 if (selectRowData.value.length == 0) {
424 ElMessage({
425 type: 'error',
426 message: '请选择需要删除的报告',
427 })
428 return
429 }
430 open("此操作将永久删除,是否继续?", "warning", true);
431 }
432 };
433
434 const dialogBtnClick = (btn, info) => {
435 if (btn.value == 'submit') {
436 if (!info.reserveCycle && info.reserveCycle !== 0) {
437 info.reserveCycle = null;
438 }
439 if (dialogInfo.value.type == 'add') {
440 addQualityWord(info).then((res: any) => {
441 if (res.code == proxy.$passCode) {
442 page.value.curr = 1;
443 getTableData();
444 ElMessage({
445 type: 'success',
446 message: '添加质量分析报告成功'
447 })
448 dialogInfo.value.visible = false;
449 } else {
450 ElMessage({
451 type: 'error',
452 message: res.msg,
453 })
454 }
455 })
456 } else {
457 const params = { ...info };
458 params.guid = currTableData.value.guid;
459 updateQualityWord(params).then((res: any) => {
460 if (res.code == proxy.$passCode) {
461 toSearch({analysisReportName:page.value.analysisReportName});
462 ElMessage({
463 type: 'success',
464 message: '修改成功'
465 })
466 dialogInfo.value.visible = false;
467 } else {
468 ElMessage({
469 type: 'error',
470 message: res.msg,
471 })
472 }
473 })
474 }
475 } else if (btn.value == 'cancel') {
476 dialogInfo.value.visible = false;
477 } else if (btn.value = 'cron') {
478 let vInfo = wordDialogRef.value.dialogFormRef[0].formInline;
479 if (!vInfo.execCycle) {
480 return;
481 }
482 getCronExecTime(vInfo.execCycle).then((res: any) => {
483 if (res?.length) {
484 vInfo.zxzq = res.join('\n');
485 setFormItems(vInfo);
486 } else {
487 ElMessage({
488 type: 'error',
489 message: res.msg,
490 })
491 }
492 })
493 }
494 };
495
496 const dialogSelectChange = (val, row, info) => {
497 if (row.field == 'planGuid') {
498 formItems.value[0].default = val;
499 formItems.value[1].default = planList.value.find(p => p.guid == val)?.planName || "";
500 }
501 }
502
503 const scheduleChange = (val, rowValue) => {
504 setFormItems(rowValue);
505 // formItems.value[3].default = val
506 }
507
508 const setFormItems = (row) => {
509 formItems.value.map(item => {
510 if (item.field == 'reserveCycle') {
511 item.default = row ? row[item.field] : 365;
512 } else {
513 item.default = row ? row[item.field] : ''
514 }
515 })
516 }
517
518 onBeforeMount(() => {
519 })
520
521 </script>
522
523 <template>
524 <div class="container_wrap">
525 <div class="table_tool_wrap has_search">
526 <TableTools :searchItems="searchItemList" searchId="word-search" @search="toSearch" />
527 <div class="tools_btns">
528 <el-button type="primary" @click="handleCreateNewWord">新建分析报告</el-button>
529 <!-- <el-button @click="batching('delete')" v-preReClick>批量删除</el-button> -->
530 </div>
531 </div>
532 <div class="table_panel_wrap">
533 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tableSelectionChange="tableSelectionChange"
534 @tablePageChange="tablePageChange" @tableSwitchBeforeChange="tableSwitchBeforeChange" />
535 </div>
536
537 <Dialog ref="wordDialogRef" :dialogInfo="dialogInfo" @btnClick="dialogBtnClick" @selectChange="dialogSelectChange"
538 @scheduleChange="scheduleChange" />
539 </div>
540 </template>
541
542 <style lang="scss" scoped>
543 .table_tool_wrap {
544 width: 100%;
545 height: 84px !important;
546 padding: 0 8px;
547
548 .tools_btns {
549 padding: 8px 0 0;
550 }
551 }
552
553 .table_panel_wrap {
554 width: 100%;
555 height: calc(100% - 84px);
556 padding: 8px 8px 0;
557 }
558 </style>
1 <route lang="yaml">
2 name: qualityAssess
3 </route>
4
5 <template>
6 <div class="container_wrap flex">
7 <div class="box_left aside_wrap">
8 <div class="aside_title">质量评估方案列表</div>
9 <Tree :treeInfo="treeInfo" @nodeClick="nodeClick" />
10 </div>
11 <div class="box_right">
12 <div class="table_tool_wrap">
13 <TableTools :searchItems="searchItemList" :searchId="'user-manage-search'" @search="toSearch" />
14 <div class="tools_btns">
15 <el-button type="primary" @click="newCreatePlan" :disabled="treeSelectItem.guid == 4"
16 v-preReClick>新建方案</el-button>
17 </div>
18 </div>
19 <div class="table_panel_wrap">
20 <Table :tableInfo="tableInfo" @tableBtnClick="tableBtnClick" @tableSelectionChange="tableSelectionChange"
21 @tableSwitchBeforeChange="tableSwitchBeforeChange" @tablePageChange="tablePageChange" />
22 </div>
23 </div>
24 </div>
25 </template>
26
27 <script lang="ts" setup name="qualityAssess">
28 import { ref } from 'vue'
29 import { ElMessage, ElMessageBox } from "element-plus";
30 import Tree from "@/components/Tree/index.vue";
31 import TableTools from '@/components/Tools/table_tools.vue'
32 import Table from "@/components/Table/index.vue";
33 import { useRouter } from "vue-router";
34 import { changeNum } from '@/utils/common';
35 import {
36 getPlanList,
37 deletePlan,
38 updateQualityPlanState,
39 executePlan
40 } from '@/api/modules/dataQualityAssess';
41 import { TableColumnWidth } from '@/utils/enum';
42 import useDataQualityStore from "@/store/modules/dataQuality";
43 import { commonPageConfig } from '@/components/PageNav/index';
44
45 /** 数据质量缓存内容 */
46 const dataQualityStore = useDataQualityStore();
47
48 const { proxy } = getCurrentInstance() as any;
49
50 const router = useRouter()
51
52 /** 当前左侧选中树的item,用于根据选中不同层级,右侧显示不同表格。 */
53 const treeSelectItem: any = ref({})
54
55 /** 左侧树形配置信息。 */
56 const treeInfo: any = ref({
57 id: "data-pickup-tree",
58 filter: false,
59 queryValue: "",
60 queryPlaceholder: "输入名称搜索",
61 nodeKey: 'guid',
62 expandedKey: [0],
63 currentNodeKey: 0,
64 props: {
65 value: 'guid',
66 label: 'label',
67 isLeaf: 'isLeaf'
68 },
69 data: [{
70 guid: 0,
71 label: '质量评估方案',
72 children: [{
73 guid: 1,
74 label: '表方案'
75 }, {
76 guid: 3,
77 label: '分组方案'
78 }, {
79 guid: 2,
80 label: '数据库方案'
81 }
82 // , {
83 // guid: 4,
84 // label: '数据同步方案'
85 // }
86 ]
87 }],
88 });
89
90 /** 上方搜索配置信息。 */
91 const searchItemList = ref([
92 {
93 type: 'input',
94 label: '',
95 field: 'planName',
96 default: '',
97 maxlength: 50,
98 placeholder: '方案名称',
99 clearable: true,
100 visible: true
101 }, {
102 type: 'select',
103 label: '',
104 field: 'state',
105 default: null,
106 placeholder: '状态',
107 options: [
108 { label: '上线', value: 1 },
109 { label: '下线', value: 0 },
110 ],
111 clearable: true,
112 visible: true
113 }
114 ])
115
116 /** 当前操作的方案表格行数据 */
117 const currTableData: any = ref<Object>({});
118 /** 分页及搜索传参信息配置。 */
119 const page = ref({
120 ...commonPageConfig, planName: '',
121 planType: null,
122 state: null
123 });
124 /** 方案表格已勾选选中的行。 */
125 const selectRowData = ref([])
126
127 /** 方案表格配置信息。 */
128 const tableInfo = ref({
129 id: 'user-manage-table',
130 // multiple: true,
131 loading: false,
132 nodeKey: 'guid',
133 fields: [
134 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center", fixed: "left" },
135 { label: "方案名称", field: "planName", width: 140, type: "text_btn", value: "detail", fixed: "left", columClass: 'text_btn' },
136 {
137 label: "方案类型", field: "planType", width: 90, getName: (scope) => {
138 let planType = scope.row.planType;
139 return planType == 1 ? '表' : (planType == 2 ? '数据库' : (planType == 4 ? '数据同步' : '分组'));
140 }
141 },
142 { label: "数据源", field: "dataSourceName", width: 150 },
143 {
144 label: "表数量", field: "tableNum", width: 80, align: 'right', getName: (scope) => {
145 return scope.row.tableNum != null ? changeNum(scope.row.tableNum ?? 0) : '--';
146 }
147 },
148 {
149 label: "规则数", field: "ruleNum", width: 80, align: 'right', getName: (scope) => {
150 return scope.row.ruleNum != null ? changeNum(scope.row.ruleNum ?? 0) : '--';
151 }
152 },
153 {
154 label: '状态', field: 'state', type: 'switch', activeText: '上线', inactiveText: '下线', activeValue: 1, inactiveValue: 0, switchWidth: 56, width: 96, align: 'center', isDisabled: (scope) => {
155 return scope.row.planType == 4;
156 }
157 },
158 { label: "执行状态", field: "execState", width: TableColumnWidth.STATE, align: 'center', type: "tag" },
159 {
160 label: "执行周期", field: "execCycle", width: TableColumnWidth.EXECCYCLE
161 },
162 { label: "下次执行时间", field: "nextExecTime", width: TableColumnWidth.DATETIME, },
163 { label: "最后执行时间", field: "lastExecTime", width: TableColumnWidth.DATETIME, },
164 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
165 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME, },
166 ],
167 data: [],
168 page: {
169 type: "normal",
170 rows: 0,
171 ...page.value,
172 },
173 actionInfo: {
174 label: "操作",
175 type: "btn",
176 width: 270,
177 btns: (scope) => {
178 const row = scope.row
179 let btnsArr: any = [{
180 label: "编辑", value: "edit", disabled: row.state === 1 || row.isExecute || row.execState == 1 || row.planType == 4, click: (scope) => {
181 router.push({
182 name: 'assessTemplate',
183 query: {
184 guid: row.guid,
185 planName: row.planName
186 }
187 });
188 }
189 },
190 {
191 label: "查看结果", value: "resultView", disabled: row.execState == 1 || row.isExecute || row.execState == 0, click: (scope) => {
192 router.push({
193 name: 'assessDetail',
194 query: {
195 planGuid: row.guid,
196 name: row.planName,
197 planExecGuid: row.planExecGuid
198 }
199 });
200 }
201 },
202 {
203 label: "日志", value: "path_log", disabled: row.execState == 0, click: (scope) => {
204 router.push({
205 name: 'assessLog',
206 query: {
207 guid: row.guid,
208 name: row.planName
209 }
210 });
211 }
212 }];
213 if (row.isExecute) {
214 btnsArr.splice(0, 0, { label: "执行中...", value: "execute", disabled: true })
215 } else {
216 btnsArr.splice(0, 0, { label: "手动执行", value: "execute", disabled: row.execState == 1 || row.state == 0 || row.planType == 4 })
217 }
218 btnsArr.push({ label: "删除", value: "delete", disabled: row.isExecute || row.execState == 1 || row.state == 1 });
219 return btnsArr
220 }
221 }
222 });
223
224 const toSearch = (val: any, clear: boolean = false) => {
225 page.value.curr = 1;
226 if (clear) {
227 searchItemList.value.map(item => item.default = '')
228 page.value.planName = '';
229 page.value.state = null;
230 getTableData();
231 return;
232 }
233 page.value.planName = val.planName;
234 page.value.state = val.state;
235 getTableData();
236 };
237
238 /** 调用接口刷新获取方案表格数据 */
239 const getTableData = () => {
240 tableInfo.value.loading = true;
241 getPlanList({
242 pageIndex: page.value.curr, pageSize: page.value.limit, planName: page.value.planName,
243 planType: page.value.planType,
244 state: page.value.state
245 }).then((res: any) => {
246 tableInfo.value.loading = false;
247 if (res === undefined) {
248 return;
249 }
250 if (res.code == proxy.$passCode) {
251 const data = res.data || {}
252 tableInfo.value.data = data.records || []
253 tableInfo.value.page.limit = data.pageSize
254 tableInfo.value.page.curr = data.pageIndex
255 tableInfo.value.page.rows = data.totalRows
256 } else {
257 ElMessage({
258 type: 'error',
259 message: res.msg,
260 })
261 }
262 })
263 };
264
265 /** 监听处理表格勾选事件。 */
266 const tableSelectionChange = (val) => {
267 selectRowData.value = val.map((item) => item.guid);
268 };
269
270 /** 监听处理分页事件。 */
271 const tablePageChange = (info) => {
272 page.value.curr = Number(info.curr);
273 page.value.limit = Number(info.limit);
274 getTableData();
275 };
276
277 const tableSwitchBeforeChange = (scope, field, callback) => {
278 if (scope.row.isExecute || scope.row.execState == 1) {
279 ElMessage.warning('该方案正在执行中,无法下线');
280 return;
281 }
282 const msg = `确定【${scope.row[field] == 1 ? '下线' : '上线'}】该方案吗?`;
283 ElMessageBox.confirm(
284 msg,
285 '提示',
286 {
287 confirmButtonText: '确定',
288 cancelButtonText: '取消',
289 type: 'warning',
290 }
291 ).then(() => {
292 const state = scope.row[field] == 1 ? 0 : 1
293 const result = tableSwitchChange(state, scope, field)
294 callback(result)
295 }).catch(() => {
296 callback(false)
297 })
298 }
299
300 const tableSwitchChange = (val, scope, field) => {
301 return new Promise((resolve, reject) => {
302 let params = {
303 guid: scope.row.guid,
304 state: val
305 }
306 updateQualityPlanState(params).then((res: any) => {
307 if (res.code == proxy.$passCode && res.data) {
308 page.value.curr = 1;
309 getTableData();
310 ElMessage({
311 type: "success",
312 message: `该方案${val == 1 ? '上线' : '下线'}成功`,
313 });
314 resolve(true)
315 } else {
316 ElMessage({
317 type: "error",
318 message: res.msg,
319 });
320 getTableData();
321 reject(false)
322 }
323 }).catch(() => {
324 getTableData();
325 reject(false)
326 })
327 })
328 }
329
330 const tableBtnClick = (scope, btn) => {
331 const type = btn.value;
332 const row = scope.row;
333 currTableData.value = row;
334 if (type == "execute") {
335 row.isExecute = true
336 const guid = row.guid
337 executePlan(guid).then((res: any) => {
338 if (res.code == proxy.$passCode) {
339 getTableData();
340 ElMessage({
341 type: "success",
342 message: "手动执行提交成功",
343 });
344 } else {
345 ElMessage({
346 type: "error",
347 message: res.msg,
348 });
349 }
350 row.isExecute = false
351 }).catch(() => {
352 row.isExecute = false
353 })
354 } else if (type === 'delete') {
355 open(`此操作将永久删除 ${row.planName}, 是否继续?`, "warning");
356 } else if (type == 'detail') {//查看详情
357 if (row.planType != 4) {
358 router.push({
359 name: 'assessTemplate',
360 query: {
361 guid: row.guid,
362 planName: row.planName,
363 detail: 1
364 }
365 });
366 }
367 }
368 };
369
370 /** 删除操作确认对话框。 */
371 const open = (msg, type, isBatch = false) => {
372 ElMessageBox.confirm(msg, "提示", {
373 confirmButtonText: "确定",
374 cancelButtonText: "取消",
375 type: type,
376 }).then(() => {
377 let guids = [currTableData.value.guid]
378 if (isBatch) {
379 guids = selectRowData.value
380 }
381 deletePlan(guids).then((res: any) => {
382 if (res.code == proxy.$passCode) {
383 page.value.curr = 1;
384 getTableData();
385 ElMessage({
386 type: "success",
387 message: "删除成功",
388 });
389 } else {
390 ElMessage({
391 type: "error",
392 message: res.msg,
393 });
394 }
395 });
396 });
397 };
398
399 /** 树形点击节点事件处理。 */
400 const nodeClick = (data) => {
401 treeSelectItem.value = data
402 if (data.children) {
403 page.value.planType = null;
404 } else {
405 page.value.planType = data.guid;
406 }
407 page.value.curr = 1;
408 searchItemList.value[0].default = '';
409 searchItemList.value[1].default = null;
410 getTableData();
411 }
412
413 onActivated(() => {
414 /** 若是新建评估方案跳转则默认选中对应的方案类型展示列表。 */
415 if (dataQualityStore.planType) {
416 /** 若是编辑方案之后跳转,则只需要重新获取当前的表格数据即可。 */
417 if (dataQualityStore.isUpdate) {
418 getTableData();
419 dataQualityStore.setIsUpdate(false);
420 } else {
421 /** 若树选中的与默认方案类型一致,则不需要再选中树,只更新表格数据。 */
422 if (treeSelectItem.value && treeSelectItem.value.guid == dataQualityStore.planType) {
423 treeInfo.value.currentNodeKey = dataQualityStore.planType;
424 getTableData();
425 } else {
426 treeInfo.value.currentNodeKey = dataQualityStore.planType;
427 nextTick(() => {
428 nodeClick(treeInfo.value.data[0].children.find(c => c.guid == treeInfo.value.currentNodeKey));
429 })
430 }
431 }
432 dataQualityStore.setPlanType(null);
433 }
434 })
435
436 const newCreatePlan = () => {
437 /** 新建评估方案时,若是左侧树选中的是方案类型,则新建时设置类型默认值与树选中值对应。 */
438 if (treeSelectItem.value.guid != null && treeSelectItem.value.guid !== 0) {
439 dataQualityStore.setDefaultPlanType(treeSelectItem.value.guid);
440 }
441 router.push({
442 name: 'assessTemplate'
443 });
444 }
445
446 </script>
447
448 <style lang="scss" scoped>
449 .container_wrap {
450 padding: 0;
451 display: flex;
452 justify-content: space-between;
453
454 .box_left {
455 width: 200px;
456 box-shadow: 1px 0 0 0 #d9d9d9;
457
458 .tree_panel {
459 height: 100%;
460 padding-top: 0;
461
462 :deep(.el-tree) {
463 margin: 0;
464 overflow: hidden auto;
465 }
466 }
467 }
468
469 .box_right {
470 width: calc(100% - 200px);
471 height: 100%;
472 padding: 0 16px;
473 overflow: hidden auto;
474 }
475
476 .panel_title {
477 line-height: 40px;
478 font-size: 16px;
479 font-weight: 600;
480 color: var(--el-color-regular);
481 }
482 }
483
484 .table_tool_wrap {
485 width: 100%;
486 height: 84px;
487
488 .tools_btns {
489 padding: 0;
490 }
491 }
492
493 .table_panel_wrap {
494 width: 100%;
495 height: calc(100% - 84px);
496 }
497 </style>
1 <route lang="yaml">
2 name: qualityRules
3 </route>
4
5 <script lang="ts" setup name="qualityRules">
6 import { ref } from 'vue'
7 import { ElMessage, ElMessageBox } from "element-plus";
8 import Tree from "@/components/Tree/index.vue";
9 import TableTools from '@/components/Tools/table_tools.vue'
10 import Table from "@/components/Table/index.vue";
11 import Dialog from '@/components/Dialog/index.vue'
12 import { useRouter } from "vue-router";
13 import useCatchStore from "@/store/modules/catch";
14 import {
15 getQualityTreeData,
16 getQualityGroupData,
17 deleteGroup,
18 updateQualityGroup,
19 addQualityGroup,
20 getQualityTable,
21 deleteQualityTable,
22 getQualityTableRule,
23 deleteQualityTableRule,
24 updateRuleBizState,
25 getDatabase,
26 getRuleTypeList
27 } from '@/api/modules/dataQuality';
28 import useDataQualityStore from "@/store/modules/dataQuality";
29 import { useValidator } from '@/hooks/useValidator';
30 import { TableColumnWidth } from '@/utils/enum';
31 import useDataCatalogStore from "@/store/modules/dataCatalog";
32
33 const dataQualityStore = useDataQualityStore();
34 const dataCatalogStore = useDataCatalogStore();
35
36 const { proxy } = getCurrentInstance() as any;
37 const { orderNum, description } = useValidator();
38
39 const router = useRouter();
40 const cacheStore = useCatchStore();
41 /** 可选择的质量规则列表。 */
42 const ruleTypeList: any = ref([]);
43
44 /** 质量规则集表对象。 */
45 const qualityModelTreeRef = ref();
46 /** 树选中不同层级的,代表的类型, model, group, table */
47 const treeType = ref('model')
48 const treeData = ref([
49 {
50 guid: '1',
51 name: "数据质量规则集",
52 type: 0,
53 children: [],
54 }
55 ])
56
57 const getQualityGroupTreePromise: any = ref(null);
58
59 /** 展示质量规则集,分组树形结构数据。 */
60 const getQualityGroupTreeData = (groupGuid?: string) => {
61 treeInfo.value.loading = true;
62 return getQualityGroupTreePromise.value = getQualityTreeData(groupGuid ? groupGuid : null).then((res: any) => {
63 treeInfo.value.loading = false;
64 getQualityGroupTreePromise.value = null;
65 if (res.code == proxy.$passCode) {
66 //return res.data || [];
67 treeData.value[0].children = res.data || []
68 } else {
69 ElMessage({
70 type: 'error',
71 message: res.msg,
72 })
73 }
74 })
75 }
76
77 /** 质量模型树形信息。 */
78 const treeInfo = ref({
79 id: "data-quality-tree",
80 filter: true,
81 loading: false,
82 // expandOnNodeClick: false, // 定位会由于未展开而失败。
83 queryValue: "",
84 queryPlaceholder: "输入名称搜索",
85 props: {
86 value: 'guid',
87 label: 'name',
88 isLeaf: 'isLeaf'
89 },
90 prefix: {
91 type: 'prefixIcon'
92 },
93 nodeKey: 'guid',
94 lazy: false, // 方便实现搜索,改为直接全部加载。
95 expandedKey: ['1'],
96 currentNodeKey: '1',
97 data: treeData.value
98 });
99
100 /** 指定分组下展示的质检表,表格搜索时下拉选择的数据源。 */
101 const databaseList: any = ref([]);
102
103 /** 指定分组下展示的质检表,上方搜索项配置。 */
104 const tableSearchItemList = ref([
105 {
106 type: 'select',
107 label: '',
108 field: 'dataSourceGuid',
109 default: '',
110 placeholder: '数据源',
111 props: {
112 label: 'databaseNameZh',
113 value: 'guid'
114 },
115 options: databaseList.value,
116 clearable: true,
117 visible: true
118 }, {
119 type: 'input',
120 label: '',
121 field: 'modelName',
122 maxlength: 50,
123 default: '',
124 placeholder: '表名',
125 clearable: true,
126 visible: true
127 }
128 ])
129
130 /** 指定质检表展示的规则列表,上方搜索项配置。 */
131 const searchItemList = ref([
132 {
133 type: 'input',
134 label: '',
135 field: 'ruleName',
136 default: '',
137 maxlength: 50,
138 placeholder: '规则名称',
139 clearable: true,
140 visible: true
141 }, {
142 type: 'select',
143 label: '',
144 field: 'ruleField',
145 default: '',
146 placeholder: '规则字段',
147 options: [
148 { label: '字段1', value: '1' },
149 { label: '字段2', value: '2' },
150 ],
151 clearable: true,
152 visible: false, //徐鹏接口未支持,数据量较少,没必要做。
153 }, {
154 type: 'select',
155 label: '',
156 field: 'ruleCode',
157 default: '',
158 placeholder: '规则类型',
159 options: ruleTypeList.value,
160 props: {
161 value: 'ruleCode',
162 label: 'ruleName'
163 },
164 clearable: true,
165 visible: true
166 }
167 ])
168
169 const page = ref({
170 limit: 50,
171 curr: 1,
172 sizes: [
173 { label: "10", value: 10 },
174 { label: "50", value: 50 },
175 { label: "100", value: 100 },
176 { label: "150", value: 150 },
177 { label: "200", value: 200 },
178 ],
179 modelGroupGuid: '',
180 dataSourceGuid: '',
181 modelName: ''
182 });
183
184 /** 当前分组列表数据勾选选中的行,用于批量删除。 */
185 const tableSelectRowData: any = ref([]);
186 const tableInfo = ref({
187 id: 'quality-table',
188 multiple: true,
189 loading: false,
190 fields: [
191 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
192 { label: "表名", field: "name", width: 150, type: 'text_btn', value: 'view', columClass: 'text_btn' },
193 { label: "表英文名", field: "subjectName", width: 160 },
194 { label: "数据源", field: "databaseName", width: 160 },
195 { label: "规则数量", field: "ruleNum", width: 90, align: 'right' },
196 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
197 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME, },
198 ],
199 data: [],
200 page: {
201 type: "normal",
202 rows: 0,
203 ...page.value,
204 },
205 actionInfo: {
206 label: "操作",
207 type: "btn",
208 width: 215,
209 btns: [
210 { label: "新建规则", value: "create" },
211 { label: "删除", value: "delete" },
212 { label: "查看表目录", value: "locateDataCatalog" },
213 ],
214 }
215 });
216
217 const groupPage = ref({
218 limit: 50,
219 curr: 1,
220 sizes: [
221 { label: "10", value: 10 },
222 { label: "50", value: 50 },
223 { label: "100", value: 100 },
224 { label: "150", value: 150 },
225 { label: "200", value: 200 },
226 ],
227 });
228 const groupSelectRowData = ref([]);
229 const groupTableInfo = ref({
230 id: 'group-table',
231 //multiple: true,
232 loading: false,
233 fields: [
234 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center", fixed: "left" },
235 { label: "分组名称", field: "name", width: 150, type: 'text_btn', value: 'locate', columClass: 'text_btn' },
236 { label: "排序", field: 'orderNum', width: TableColumnWidth.INDEX, align: "center" },
237 { label: "质检表数量", field: "qualityModelNum", width: 120, align: 'right' },
238 {
239 label: "生效/总规则数", field: "ruleNum", width: 120, align: 'right', getName: (scope) => {
240 let row = scope.row;
241 return `${row.effectRuleNum}/${row.ruleNum}`;
242 }
243 },
244 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
245 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME, },
246 { label: "描述", field: "description", width: TableColumnWidth.DESCRIPTION }
247 ],
248 data: [],
249 page: {
250 type: "normal",
251 rows: 0,
252 ...groupPage.value,
253 },
254 actionInfo: {
255 label: "操作",
256 type: "btn",
257 width: 92,
258 btns: [
259 { label: "编辑", value: "edit" },
260 { label: "删除", value: "delete" },
261 ],
262 }
263 });
264
265 const formItems: any = ref([
266 {
267 label: '分组名称',
268 type: 'input',
269 placeholder: '请输入',
270 field: 'name',
271 default: '',
272 maxlength: 50,
273 required: true
274 },
275 {
276 label: '排序',
277 type: 'input',
278 placeholder: '由数字组成',
279 field: 'orderNum',
280 inputType: 'integerNumber',
281 default: '',
282 maxlength: 6,
283 required: true
284 }, {
285 label: '描述',
286 type: 'textarea',
287 placeholder: '请输入',
288 field: 'description',
289 default: '',
290 block: true,
291 required: false,
292 },
293 ])
294
295 const formRules = ref({
296 name: [
297 { required: true, trigger: 'blur', message: "请填写分组名称" }
298 ],
299 orderNum: [orderNum()],
300 description: [description()]
301 });
302
303 /** 新建分组对话框。 */
304 const dialogInfo = ref({
305 visible: false,
306 size: 700,
307 direction: "column",
308 header: {
309 title: "",
310 },
311 type: '',
312 contents: [
313 {
314 type: 'form',
315 title: '',
316 formInfo: {
317 id: 'add-staff-form',
318 items: formItems.value,
319 rules: formRules.value
320 }
321 }
322 ],
323 footer: {
324 btns: [
325 { type: "default", label: "取消", value: "cancel" },
326 { type: "primary", label: "确定", value: "submit" },
327 ],
328 },
329 });
330
331 /** 根据树形选择的第一层级显示对应的分组数据,用表格分页展示。 */
332 const getGroupTableData = () => {
333 groupTableInfo.value.loading = true;
334 getQualityGroupData({ pageIndex: groupPage.value.curr, pageSize: groupPage.value.limit }).then((res: any) => {
335 groupTableInfo.value.loading = false;
336 if (res.code == proxy.$passCode) {
337 const data = res.data || {};
338 groupTableInfo.value.data = data.records ?? [];
339 groupTableInfo.value.page.curr = data.pageIndex;
340 groupTableInfo.value.page.rows = data.totalRows;
341 } else {
342 ElMessage({
343 type: 'error',
344 message: res.msg,
345 })
346 }
347 })
348 };
349
350 const getTableData = () => {
351 tableInfo.value.loading = true;
352 getQualityTable({ pageIndex: page.value.curr, pageSize: page.value.limit, modelGroupGuid: page.value.modelGroupGuid, dataSourceGuid: page.value.dataSourceGuid, name: page.value.modelName }).then((res: any) => {
353 tableInfo.value.loading = false;
354 if (res.code == proxy.$passCode) {
355 const data = res.data || {};
356 tableInfo.value.data = data.records ?? [];
357 tableInfo.value.page.curr = data.pageIndex;
358 tableInfo.value.page.rows = data.totalRows;
359 } else {
360 ElMessage.error(res.msg);
361 }
362 })
363 }
364
365 const getRuleTableData = () => {
366 ruleTableInfo.value.loading = true;
367 getQualityTableRule({ modelGuid: lastSelectNode.value.data.guid, ruleName: modelRulesSerchParams.value.ruleName, ruleCode: modelRulesSerchParams.value.ruleCode }).then((res: any) => {
368 ruleTableInfo.value.loading = false;
369 if (res.code == proxy.$passCode) {
370 ruleTableInfo.value.data = res.data || [];
371 } else {
372 ElMessage.error(res.msg);
373 }
374 })
375 }
376
377 const tableSelectionChange = (val) => {
378 tableSelectRowData.value = val;
379 };
380
381 const tablePageChange = (info) => {
382 page.value.curr = Number(info.curr);
383 page.value.limit = Number(info.limit);
384 tableInfo.value.page.limit = page.value.limit;
385 tableInfo.value.page.curr = page.value.curr;
386 getTableData();
387 };
388
389 /** 当前选中的规则行,用于操作列按钮。 */
390 const currTableData: any = ref({});
391 const tableBtnClick = (scope, btn) => {
392 const type = btn.value;
393 const row = scope.row;
394 currTableData.value = row;
395 if (type == "view") {
396 if (!qualityModelTreeRef.value) {
397 return;
398 }
399 qualityModelTreeRef.value.setCurrentKey(row.guid, true);
400 } else if (type == 'create') {
401 router.push({
402 name: 'ruleTemplate',
403 query: {
404 modelGuid: row.guid,
405 name: row.name
406 }
407 });
408 } else if (type == "delete") {
409 open("此操作将永久删除, 是否继续?", "warning");
410 } else if (type == 'locateDataCatalog') {
411 dataCatalogStore.setLocateSubjectName(row.name);
412 router.push({
413 name: 'dataWarehouse'
414 });
415 }
416 };
417
418 const open = (msg, type, isBatch = false) => {
419 ElMessageBox.confirm(msg, "提示", {
420 confirmButtonText: "确定",
421 cancelButtonText: "取消",
422 type: type,
423 }).then(() => {
424 let guids = [currTableData.value.guid]
425 if (isBatch) {
426 guids = tableSelectRowData.value.map(s => s.guid);
427 }
428 deleteQualityTable(guids).then((res: any) => {
429 if (res.code == proxy.$passCode) {
430 page.value.curr = 1;
431 getTableData();
432 let node = qualityModelTreeRef.value.treeRef.store.nodesMap[currTableData.value.modelGroupGuid];
433 node.loaded = false;
434 node.expand();
435 ElMessage.success('删除成功');
436 } else {
437 ElMessage.error(res.msg);
438 }
439 });
440 });
441 };
442
443 const batchingDelete = () => {
444 if (tableSelectRowData.value.length == 0) {
445 ElMessage({
446 type: 'error',
447 message: '请选择需要删除的数据',
448 })
449 return
450 }
451 open("此操作将永久删除, 是否继续?", "warning", true);
452 };
453
454 const groupTableSelectionChange = (val) => {
455 groupSelectRowData.value = val;
456 };
457
458 const groupTablePageChange = (info) => {
459 groupPage.value.curr = Number(info.curr);
460 groupPage.value.limit = Number(info.limit);
461 groupTableInfo.value.page.limit = groupPage.value.limit;
462 groupTableInfo.value.page.curr = groupPage.value.curr;
463 getGroupTableData();
464 };
465
466 const currGroupTableData: any = ref({});
467 const groupTableBtnClick = (scope, btn) => {
468 const type = btn.value;
469 const row = scope.row;
470 currGroupTableData.value = row;
471 if (type == "edit") {
472 dialogInfo.value.visible = true;
473 dialogInfo.value.header.title = "编辑分组";
474 dialogInfo.value.type = type
475 formItems.value.map(item => {
476 item.default = row[item.field];
477 });
478 } else if (type == "delete") {
479 ElMessageBox.confirm('此操作将永久删除该分组,确认删除吗?', "提示", {
480 confirmButtonText: "确定",
481 cancelButtonText: "取消",
482 type: type,
483 }).then(() => {
484 deleteGroup([row.guid]).then((res: any) => {
485 if (res.code == proxy.$passCode) {
486 groupPage.value.curr = 1;
487 getGroupTableData();
488 ElMessage({
489 type: "success",
490 message: "删除分组成功",
491 });
492 } else {
493 ElMessage({
494 type: "error",
495 message: res.msg,
496 });
497 }
498 });
499 });
500 } else if (type == 'locate') {
501 qualityModelTreeRef.value.setCurrentKey(row.guid, true);
502 let node = qualityModelTreeRef.value.treeRef.store.nodesMap[row.guid];
503 node && node.expand();
504 }
505 };
506
507 const ruleTableInfo = ref({
508 id: 'rule-table',
509 loading: false,
510 fields: [
511 { label: "规则名称", field: "ruleConfName", width: 150 },
512 { label: "规则大类", field: "largeCategoryName", width: 120 },
513 { label: "规则小类", field: "smallCategoryName", width: 140 },
514 { label: "规则类型", field: "ruleName", width: 120 },
515 { label: '状态', field: 'bizState', type: 'switch', activeText: '启用', inactiveText: '停用', activeValue: 'Y', inactiveValue: 'S', switchWidth: 56, width: 100, align: 'center' },
516 { label: "字段", field: "ruleField", width: 160 },
517 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
518 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME, },
519 ],
520 data: [],
521 showPage: false,
522 actionInfo: {
523 label: "操作",
524 type: "btn",
525 width: 100,
526 btns: (scope) => {
527 return [
528 { label: "编辑", value: "edit" },
529 { label: "删除", value: "delete" },
530 ]
531 }
532 }
533 });
534
535 const ruleTableSwitchBeforeChange = (scope, field, callback) => {
536 let stateName = scope.row[field] == 'Y' ? '停用' : '启用';
537 const msg = `确定【${stateName}】该规则吗?`
538 ElMessageBox.confirm(
539 msg,
540 '提示',
541 {
542 confirmButtonText: '确定',
543 cancelButtonText: '取消',
544 type: 'warning',
545 }
546 ).then(() => {
547 const state = scope.row[field] == 'Y' ? 'S' : 'Y';
548 const result = ruleTableSwitchChange(state, scope, field)
549 callback(result)
550 }).catch(() => {
551 callback(false)
552 })
553 }
554
555 const ruleTableSwitchChange = (val, scope, field) => {
556 return new Promise((resolve, reject) => {
557 let params = {
558 ruleConfGuid: scope.row.guid,
559 bizState: val
560 }
561 updateRuleBizState(params).then((res: any) => {
562 if (res.code == proxy.$passCode && res.data) {
563 getRuleTableData();
564 ElMessage({
565 type: "success",
566 message: `该规则 ${val == 'Y' ? '启用' : '停用'} 成功`,
567 });
568 resolve(true)
569 } else {
570 ElMessage({
571 type: "error",
572 message: res.msg,
573 });
574 getRuleTableData();
575 reject(false)
576 }
577 }).catch(() => {
578 getRuleTableData();
579 reject(false)
580 })
581 })
582 }
583
584 const currTableRuleData: any = ref({});
585 const ruleTableBtnClick = (scope, btn) => {
586 const type = btn.value;
587 const row = scope.row;
588 currTableRuleData.value = row;
589 if (type == "edit") {
590 router.push({
591 name: 'ruleModelEdit',
592 query: {
593 guid: row.guid
594 }
595 });
596 } else if (type == "delete") {
597 ruleOpen("此操作将永久删除该质检表, 是否继续?", "warning");
598 }
599 };
600
601 const ruleOpen = (msg, type, isBatch = false) => {
602 ElMessageBox.confirm(msg, "提示", {
603 confirmButtonText: "确定",
604 cancelButtonText: "取消",
605 type: type,
606 }).then(() => {
607 let guids = currTableRuleData.value.guid;
608 deleteQualityTableRule(guids).then((res: any) => {
609 if (res.code == proxy.$passCode) {
610 getRuleTableData();
611 let node = qualityModelTreeRef.value.treeRef.store.nodesMap[lastSelectNode.value.data.guid];
612 node.loaded = false;
613 node.expand();
614 ElMessage.success('删除质检表成功');
615 } else {
616 ElMessage.error(res.msg);
617 }
618 });
619 });
620 };
621
622 /** 新建分组 */
623 const clickCreateGroup = () => {
624 dialogInfo.value.visible = true;
625 dialogInfo.value.header.title = "新建分组";
626 dialogInfo.value.type = 'add';
627 formItems.value.map(item => {
628 if (item.field === 'orderNum') {
629 item.default = null;
630 } else {
631 item.default = "";
632 }
633 });
634 }
635
636 /** 新建质检表 */
637 const clickCreateTable = () => {
638 router.push({
639 name: 'ruleModel',
640 query: {
641 groupGuid: page.value.modelGroupGuid,
642 name: lastSelectNode.value.data.name
643 }
644 });
645 }
646
647 /** 新建规则 */
648 const clickCreateRule = () => {
649 router.push({
650 name: 'ruleTemplate',
651 query: {
652 modelGuid: lastSelectNode.value.data.guid,
653 name: lastSelectNode.value.data.name
654 }
655 });
656 }
657
658 /** 跳转配置质量评估方案页面。 */
659 const configPlan = () => {
660 if (lastSelectNode.value.data.type == 1) {//按照分组配置评估方案。
661 router.push({
662 name: 'assessTemplate',
663 query: {
664 groupGuid: lastSelectNode.value.data.guid
665 }
666 });
667 } else {
668 router.push({
669 name: 'assessTemplate',
670 query: {
671 modelGuid: lastSelectNode.value.data.guid,
672 groupGuid: lastSelectNode.value.parent.data.guid
673 }
674 });
675 }
676 }
677
678 const toPath = (url) => {
679 router.push({
680 path: url,
681 });
682 }
683
684 const lastSelectNode: any = ref({});
685
686 const modelRulesSerchParams = ref({
687 ruleName: '',
688 ruleCode: '',
689 ruleField: ''
690 });
691
692 const handleNodeSelectChange = (node) => {
693 console.log(node);
694 let data = node.data;
695 lastSelectNode.value = node;
696 if (node.level === 1) {
697 treeType.value = 'model';
698 groupPage.value.curr = 1;
699 getGroupTableData();
700 } else if (node.level === 2) {
701 treeType.value = 'group';
702 page.value.curr = 1;
703 page.value.modelGroupGuid = data.guid;
704 toTableSearch(groupTableTools.value?.toolSearch?.formInline || {}, false);
705 } else if (node.level === 3) {
706 treeType.value = 'table';
707 toRuleSearch(ruleTableTools.value?.toolSearch?.formInline || {}, false);
708 }
709
710 }
711
712 const groupTableTools: any = ref(null);
713
714 const toTableSearch = (val, clear) => {
715 page.value.curr = 1;
716 if (clear) {
717 page.value.modelName = '';
718 page.value.dataSourceGuid = '';
719 getTableData();
720 return;
721 }
722 page.value.modelName = val.modelName;
723 page.value.dataSourceGuid = val.dataSourceGuid;
724 getTableData();
725 }
726
727 const ruleTableTools: any = ref(null);
728 const toRuleSearch = (val, clear) => {
729 if (clear) {
730 modelRulesSerchParams.value = {
731 ruleName: '',
732 ruleCode: '',
733 ruleField: ''
734 };
735 } else {
736 modelRulesSerchParams.value = val;
737 }
738 getRuleTableData();
739 }
740
741 let submitPromise: any = ref(null);
742 let editSubmitPromise: any = ref(null);
743
744 /** 新建分组对话框确定。 */
745 const dialogBtnClick = (btn, info) => {
746 if (btn.value == 'submit') {
747 if (dialogInfo.value.type == 'add') {
748 if (submitPromise.value) {
749 return;
750 }
751 submitPromise.value = addQualityGroup(info).then((res: any) => {
752 submitPromise.value = null;
753 if (res.code == proxy.$passCode) {
754 groupPage.value.curr = 1;
755 getGroupTableData();
756 ElMessage({
757 type: 'success',
758 message: '新建分组成功'
759 })
760 dialogInfo.value.visible = false;
761 } else {
762 ElMessage({
763 type: 'error',
764 message: res.msg,
765 })
766 }
767 })
768 } else {
769 const params = { ...info };
770 params.guid = currGroupTableData.value.guid;
771 if (editSubmitPromise.value) {
772 return;
773 }
774 editSubmitPromise.value = updateQualityGroup(params).then((res: any) => {
775 editSubmitPromise.value = null;
776 if (res.code == proxy.$passCode) {
777 getGroupTableData();
778 ElMessage({
779 type: 'success',
780 message: '编辑分组成功'
781 })
782 dialogInfo.value.visible = false;
783 } else {
784 ElMessage({
785 type: 'error',
786 message: res.msg,
787 })
788 }
789 })
790 }
791 } else if (btn.value == 'cancel') {
792 dialogInfo.value.visible = false;
793 }
794 };
795
796 /** 导入按分组 */
797 const uploadDialog = () => {
798 const info = {
799 type: 'qualityModelGroup'
800 }
801 cacheStore.setCatch('uploadSetting', info)
802 nextTick(() => {
803 router.push({
804 name: 'importFiles',
805 });
806 })
807 }
808
809 /** 导出按照分组。 */
810 const exportData = () => {
811 if (groupSelectRowData.value.length == 0) {
812 // const fieldTemplate = "/files/set.xlsx";
813 // downFile(fieldTemplate, '标准集模板.xlsx')
814 } else {
815 // exportDataStandardSet(groupSelectRowData.value).then((res: any) => {
816 // download(res, '标准集.xls', 'excel')
817 // });
818 }
819 }
820
821 /** 导入质检规则 */
822 const ruleUploadDialog = () => {
823 const info = {
824 type: 'qualityRule'
825 }
826 cacheStore.setCatch('uploadSetting', info)
827 nextTick(() => {
828 router.push({
829 name: 'importFiles',
830 });
831 })
832 }
833
834 /** 导出质检规则。 */
835 const ruleExportData = () => {
836 if (groupSelectRowData.value.length == 0) {
837 // const fieldTemplate = "/files/set.xlsx";
838 // downFile(fieldTemplate, '标准集模板.xlsx')
839 } else {
840 // exportDataStandardSet(groupSelectRowData.value).then((res: any) => {
841 // download(res, '标准集.xls', 'excel')
842 // });
843 }
844 }
845
846 const getDatabaseList = () => {
847 getDatabase({ connectStatus: 1 }).then((res: any) => {
848 databaseList.value = [];
849 if (res.code == proxy.$passCode) {
850 databaseList.value = res.data || [];
851 tableSearchItemList.value[0].options = databaseList.value;
852 }
853 })
854 };
855
856 onActivated(async () => {
857 if (dataQualityStore.modelGroupGuid) {
858 await nextTick();
859 if (getQualityGroupTreePromise.value) {
860 getQualityGroupTreePromise.value.then(() => {
861 qualityModelTreeRef.value.setCurrentKey(dataQualityStore.modelGroupGuid);
862 if (lastSelectNode.value && lastSelectNode.value.data.guid == dataQualityStore.modelGroupGuid) {
863 getTableData();
864 }
865 let node = qualityModelTreeRef.value.treeRef.store.nodesMap[dataQualityStore.modelGroupGuid];
866 node?.expand();
867 dataQualityStore.set(null);
868 });
869 } else {
870 qualityModelTreeRef.value.setCurrentKey(dataQualityStore.modelGroupGuid);
871 if (lastSelectNode.value && lastSelectNode.value.data.guid == dataQualityStore.modelGroupGuid) {
872 getTableData();
873 }
874 let node = qualityModelTreeRef.value.treeRef.store.nodesMap[dataQualityStore.modelGroupGuid];
875 node.expand();
876 dataQualityStore.set(null);
877 }
878 }
879 if (dataQualityStore.modelGuid) {
880 if (lastSelectNode.value?.data && lastSelectNode.value.data.guid == dataQualityStore.modelGuid) {
881 qualityModelTreeRef.value.setCurrentKey(dataQualityStore.modelGuid);
882 getRuleTableData();
883 } else {
884 if (getQualityGroupTreePromise.value) {
885 getQualityGroupTreePromise.value.then(() => {
886 nextTick(() => {
887 qualityModelTreeRef.value.setCurrentKey(dataQualityStore.modelGuid);
888 dataQualityStore.setModelGuid(null);
889 })
890 })
891 } else {
892 nextTick(() => {
893 qualityModelTreeRef.value.setCurrentKey(dataQualityStore.modelGuid);
894 dataQualityStore.setModelGuid(null);
895 })
896 }
897 }
898 }
899 })
900
901 onBeforeMount(() => {
902 getQualityGroupTreeData();
903 getGroupTableData();
904 getDatabaseList();
905 getRuleTypeList().then((res: any) => {
906 if (res.code == proxy.$passCode) {
907 ruleTypeList.value = res.data?.map((d: any) => {
908 d.label = d.ruleName;
909 d.value = d.ruleCode;
910 return d;
911 }) || [];
912 searchItemList.value[2].options = ruleTypeList.value;
913 } else {
914 ElMessage.error(res.msg);
915 }
916 })
917 })
918
919 </script>
920
921 <template>
922 <div class="container_wrap flex">
923 <div class="box_left aside_wrap">
924 <div class="aside_title">质量规则集列表</div>
925 <Tree ref="qualityModelTreeRef" :treeInfo="treeInfo" @nodeSelectChange="handleNodeSelectChange" />
926 </div>
927 <div class="box_right">
928 <div class="table_tool_wrap">
929 <TableTools ref="groupTableTools" v-if="treeType == 'group'" :init="false" :searchItems="tableSearchItemList"
930 :searchId="'quality-table-search'" @search="toTableSearch" />
931 <TableTools ref="ruleTableTools" v-if="treeType == 'table'" :init="false" :searchItems="searchItemList"
932 :searchId="'table-rule-search'" @search="toRuleSearch" />
933 <div class="tools_btns" v-if="treeType == 'model'">
934 <el-button type="primary" @click="clickCreateGroup">新建分组</el-button>
935 <!-- <el-button @click="uploadDialog">导入</el-button>
936 <el-button @click="exportData">导出</el-button> -->
937 </div>
938 <div class="tools_btns" style="padding-bottom: 8px;" v-else-if="treeType == 'group'">
939 <el-button type="primary" @click="clickCreateTable">新建质检表</el-button>
940 <el-button @click="batchingDelete">批量删除</el-button>
941 <el-button v-if="lastSelectNode?.data?.isHaveModel" @click="configPlan">配置评估方案</el-button>
942 <!-- <el-button @click="ruleUploadDialog">导入</el-button>
943 <el-button @click="ruleExportData">导出</el-button> -->
944 </div>
945 <div class="tools_btns" style="padding-bottom: 8px;" v-else>
946 <el-button type="primary" @click="clickCreateRule">新建规则</el-button>
947 <el-button @click="configPlan">配置评估方案</el-button>
948 </div>
949 </div>
950 <div class="table_panel_wrap"
951 :style="{ height: treeType === 'model' ? 'calc(100% - 44px)' : (treeType === 'table' ? 'calc(100% - 100px)' : 'calc(100% - 84px)') }">
952 <Table v-if="treeType === 'model'" :tableInfo="groupTableInfo" @tableBtnClick="groupTableBtnClick"
953 @tableSelectionChange="groupTableSelectionChange" @tablePageChange="groupTablePageChange" />
954 <Table v-if="treeType === 'group'" :tableInfo="tableInfo" @tableBtnClick="tableBtnClick"
955 @tableSelectionChange="tableSelectionChange" @tablePageChange="tablePageChange" />
956 <Table v-if="treeType === 'table'" :tableInfo="ruleTableInfo" @tableBtnClick="ruleTableBtnClick"
957 @tableSwitchBeforeChange="ruleTableSwitchBeforeChange" />
958 </div>
959 </div>
960
961 <Dialog :dialogInfo="dialogInfo" @btnClick="dialogBtnClick" />
962 </div>
963 </template>
964
965 <style lang="scss" scoped>
966 .container_wrap {
967 padding: 0;
968 display: flex;
969 justify-content: space-between;
970
971 .box_left {
972 width: 200px;
973 box-shadow: 1px 0 0 0 #d9d9d9;
974
975 .tree_panel {
976 height: calc(100% - 36px);
977 padding-top: 0;
978
979 :deep(.el-tree) {
980 margin: 0;
981 overflow: hidden auto;
982 }
983 }
984 }
985
986 .box_right {
987 width: calc(100% - 200px);
988 height: 100%;
989 padding: 0 16px;
990 overflow: hidden auto;
991 }
992
993 .panel_title {
994 line-height: 40px;
995 font-size: 16px;
996 font-weight: 600;
997 color: var(--el-color-regular);
998 }
999 }
1000
1001 .table_tool_wrap {
1002 width: 100%;
1003 display: flex;
1004 align-items: flex-start;
1005 flex-direction: column;
1006 justify-content: center;
1007
1008 .tools_btns {
1009 padding: 0;
1010 }
1011 }
1012
1013 .table_panel_wrap {
1014 width: 100%;
1015 height: calc(100% - 84px);
1016 }
1017 </style>
1 <script lang="ts" setup name="ruleForm">
2 import { ref } from "vue";
3 import {
4 validateSubjectTableRule,
5 validateCustomSql,
6 getSubjectFields,
7 getSubjectTableTree,
8 getSubjectTableByDomain
9 } from '@/api/modules/dataQuality';
10 import Table from "@/components/Table/index.vue";
11 import Form from "@/components/Form/index.vue";
12 import { ElMessage } from "element-plus";
13 import { CirclePlus, Close } from '@element-plus/icons-vue';
14 import { cloneDeep } from 'lodash-es';
15
16 const { proxy } = getCurrentInstance() as any;
17
18 const props = defineProps({
19 toSubjectTables: {
20 type: Array<any>,
21 default: [],
22 },
23 ruleTypeValue: {
24 type: String,
25 default: '',
26 },
27 value: {
28 type: Object,
29 default: {}
30 },
31 ruleTypeList: {
32 type: Array<any>,
33 default: [],
34 },
35 smallCategoryList: {
36 type: Array<any>,
37 default: [],
38 },
39 largeCategoryList: {
40 type: Array<any>,
41 default: [],
42 },
43 isSingle: {
44 type: Boolean,
45 default: false,
46 },
47 readonly: {
48 type: Boolean,
49 default: false
50 }
51 });
52
53 watch(() => props.toSubjectTables, (val: any[]) => {
54 panelList.value[4].options = val;
55 panelList.value[4].default = val.map(s => s.guid);
56 setPanelListValue(Object.assign({ qualityModelGuids: panelList.value[4].default }, props.value), false, true);
57 formItems.value[0].children = panelList.value;
58 });
59
60 watch(() => props.value, (val) => {
61 if (Object.keys(val).length === 0) {
62 return;
63 }
64 setPanelListValue(val, false, true);
65 })
66
67 watch(() => props.ruleTypeValue, (val) => {
68 formItems.value[0].default = val;
69 if (val) {
70 let v = [props.ruleTypeList.find(option => option.value == props.ruleTypeValue)];
71 formItems.value[0].options = v;
72 formItems.value[0].default = val;
73 radioGroupChange(val, props.value, true);
74 } else {
75 let v = props.ruleTypeList;
76 formItems.value[0].options = v;
77 radioGroupChange(v[0].ruleCode, props.value, true);
78 }
79 })
80
81 watch(() => props.ruleTypeList, (val) => {
82 if (props.ruleTypeValue) {
83 let v = [val.find(option => option.value == props.ruleTypeValue)];
84 formItems.value[0].options = v;
85 formItems.value[0].default = props.ruleTypeValue;
86 radioGroupChange(props.ruleTypeValue, props.value, true);
87 } else {
88 let v = val;
89 formItems.value[0].options = v;
90 formItems.value[0].default = v[0]?.ruleCode;
91 radioGroupChange(v[0].ruleCode, props.value, true);
92 }
93 })
94
95 watch(() => props.smallCategoryList, (val) => {
96 if (!props.readonly) {
97 let largeV = props.value['largeCategory'] || getDefaultLargeCategory(formItems.value[0].default);
98 if (largeV == '1') {//规范性
99 panelList.value[2].options = val.slice(0, 6);
100 } else if (largeV == '2') {
101 panelList.value[2].options = val.slice(6, 8);
102 } else if (largeV == '3') {
103 panelList.value[2].options = val.slice(8, 13);
104 } else if (largeV == '4') {
105 panelList.value[2].options = val.slice(13, 15);
106 } else if (largeV == '5') {
107 panelList.value[2].options = val.slice(15, 18);
108 } else if (largeV == '6') {
109 panelList.value[2].options = val.slice(18, 20);
110 } else {
111 panelList.value[2].options = val.slice(0, 6);
112 panelList.value[2].default = getDefaultSmallCategory(formItems.value[0].default);;
113 }
114 }
115 if (Object.keys(props.value).length > 0) {
116 setPanelListValue(props.value, false, true);
117 }
118 })
119
120 watch(() => props.largeCategoryList, (val) => {
121 panelList.value[1].options = val;
122 panelList.value[1].default = getDefaultLargeCategory(formItems.value[0].default);
123 if (Object.keys(props.value).length > 0) {
124 setPanelListValue(props.value, false, true);
125 }
126 })
127
128 onBeforeMount(() => {
129 if (props.ruleTypeList?.length) {
130 if (props.ruleTypeValue) {
131 let v = [props.ruleTypeList.find(option => option.value == props.ruleTypeValue)];
132 formItems.value[0].options = v;
133 formItems.value[0].default = props.ruleTypeValue;
134 radioGroupChange(props.ruleTypeValue, props.value, true);
135 } else {
136 let v = props.ruleTypeList;
137 formItems.value[0].options = v;
138 formItems.value[0].default = v[0].ruleCode;
139 radioGroupChange(v[0].ruleCode, props.value, true);
140 }
141 }
142 if (props.largeCategoryList?.length) {
143 panelList.value[1].options = props.largeCategoryList;
144 panelList.value[1].default = getDefaultLargeCategory(formItems.value[0].default);
145 formItems.value[0].children = panelList.value;
146 }
147 if (props.smallCategoryList?.length) {
148 panelList.value[2].options = props.smallCategoryList.slice(0, 6);
149 panelList.value[2].default = getDefaultSmallCategory(formItems.value[0].default);
150 formItems.value[0].children = panelList.value;
151 }
152 if (props.isSingle) {
153 if (props.toSubjectTables.length) {
154 panelList.value[4].default = props.toSubjectTables.map(s => s.guid);
155 }
156 panelList.value[4].disabled = true;
157 formItems.value[0].children = panelList.value;
158 }
159 if (Object.keys(props.value).length > 0) {
160 setPanelListValue(props.value, false, true);
161 }
162 });
163
164 const getDefaultLargeCategory = (ruleCode) => {
165 if (ruleCode == 'repeate_data_check' || ruleCode == 'logic_check') {
166 return '3'; //准确性
167 } else if (ruleCode == 'volatility_check' || ruleCode == 'rows_check') {
168 return '5';
169 } else if (ruleCode == 'null_value_check') {
170 return '2';
171 } else {
172 return '1';
173 }
174 }
175
176 const getDefaultSmallCategory = (ruleCode) => {
177 if (ruleCode == 'repeate_data_check') {
178 return '11'; //准确性
179 } else if (ruleCode == 'logic_check') {
180 return '9';
181 } else if (ruleCode == 'volatility_check' || ruleCode == 'rows_check') {
182 return '17';
183 } else if (ruleCode == 'null_value_check') {
184 return '7';
185 } else {
186 return '1';
187 }
188 }
189
190 const ruleType = ref('empty')
191 const panelList: any = ref([
192 {
193 label: '规则名称',
194 type: 'input',
195 maxlength: 50,
196 placeholder: '请输入',
197 field: 'ruleConfName',
198 default: '',
199 required: true
200 }, {
201 label: '规则大类',
202 type: 'select',
203 placeholder: '请选择',
204 field: 'largeCategory',
205 default: '1',
206 options: props.largeCategoryList,
207 props: {
208 label: 'paramName',
209 value: 'paramValue'
210 },
211 required: true,
212 visible: true
213 }, {
214 label: '规则小类',
215 type: 'select',
216 placeholder: '请选择',
217 field: 'smallCategory',
218 props: {
219 label: 'paramName',
220 value: 'paramValue'
221 },
222 default: '',
223 options: props.smallCategoryList.slice(6),
224 required: true,
225 visible: true
226 }, {
227 label: '校验方式',
228 type: 'select',
229 placeholder: '请选择',
230 field: 'parity',
231 default: 1,
232 options: [
233 {
234 label: '1天周期比较',
235 value: 1
236 }, {
237 label: '7天周期比较',
238 value: 7
239 }, {
240 label: '上一周期比较',
241 value: -1
242 }
243 ],
244 clearable: true,
245 required: true,
246 visible: true
247 }, {
248 label: '质检表',
249 type: 'select',
250 placeholder: '请选择',
251 field: 'qualityModelGuids',
252 props: {
253 label: 'chName',
254 value: 'guid'
255 },
256 default: [],
257 options: props.toSubjectTables,
258 multiple: true,
259 tagsTooltip: true,
260 collapse: true,
261 filterable: true,
262 required: true,
263 visible: true,
264 }, {
265 label: '比较方式',
266 type: 'select',
267 placeholder: '请选择',
268 field: 'compareWay',
269 default: 1,
270 options: [
271 {
272 label: '绝对值',
273 value: 1
274 }, {
275 label: '上升',
276 value: 2
277 },
278 {
279 label: '下降',
280 value: 3
281 }
282 ],
283 clearable: true,
284 required: true,
285 visible: true
286 }, {
287 label: '波动值比较',
288 type: 'input-group',
289 placeholder: '请输入',
290 field: 'ruleFluctuations',
291 default: '',
292 children: [
293 {
294 label: '',
295 type: 'input',
296 inputType: 'scoreNumber',
297 max: 1,
298 placeholder: '橙色阈值',
299 field: 'orangeThreshold',
300 default: '',
301 clearable: true,
302 required: true
303 },
304 {
305 label: '',
306 type: 'input',
307 placeholder: '红色阈值',
308 inputType: 'scoreNumber',
309 max: 1,
310 field: 'redThreshold',
311 default: '',
312 clearable: true,
313 required: true
314 },
315 ],
316 col: 'col2',
317 clearable: true,
318 required: true
319 }, {
320 label: '规则设置',
321 type: 'input-dom',
322 placeholder: '请设置,可多选',
323 field: 'ruleSettings', //重复数据 7
324 default: '',//描述型的字段
325 defaultValue: {},//实际的json值
326 readonly: true,
327 required: true,
328 block: true,
329 visible: false
330 }, {
331 label: '规则设置',
332 type: 'input-dom',
333 placeholder: '请设置,可多选',
334 field: 'ruleSettings-logic', //8
335 default: '',//描述型的字段
336 defaultValue: {},//实际的json值, {ruleFields:,conditionSqls:}
337 readonly: true,
338 required: true,
339 block: true,
340 visible: false
341 }, {
342 label: '规则设置',
343 type: 'input-dom',
344 placeholder: '请设置,可多选',
345 field: 'ruleSettings-sql', //9
346 default: '',//描述型的字段
347 defaultValue: {},//实际的json值,key为表,value为sql
348 readonly: true,
349 required: true,
350 block: true,
351 visible: false
352 }, {
353 label: '规则设置',
354 type: 'input-dom',
355 placeholder: '请设置,可多选',
356 field: 'ruleSettings-null', //10
357 default: '',//描述型的字段
358 defaultValue: {},//实际的json值
359 readonly: true,
360 required: true,
361 block: true,
362 visible: false
363 }, {
364 label: '规则设置',
365 type: 'input-dom',
366 placeholder: '请设置,可多选',
367 field: 'ruleSettings-rows', //11
368 default: '',//描述型的字段
369 defaultValue: [],//实际的表格数据值数组
370 readonly: true,
371 required: true,
372 block: true,
373 visible: false
374 }, {
375 label: '描述',
376 type: 'textarea',
377 placeholder: '请输入',
378 field: 'description',
379 default: '',
380 clearable: true,
381 required: false,
382 block: true
383 }, {
384 label: '',
385 type: 'checkbox',
386 placeholder: '联合不为空',
387 desc: '勾选表示表的所有字段不能同时为空,不勾选表示每个字段都不为空',
388 field: 'jointly',
389 default: 'N',
390 trueValue: 'Y',
391 falseValue: 'N',
392 required: false,
393 visible: false,
394 block: true
395 }, {
396 label: '',
397 type: 'checkbox',
398 placeholder: '直接启用规则',
399 desc: '',
400 field: 'bizState',
401 default: "Y",
402 trueValue: 'Y',
403 falseValue: 'S',
404 required: false,
405 visible: !props.readonly,
406 block: true
407 }
408 ])
409
410 const formItems: any = ref([
411 {
412 label: '选择规则',
413 type: 'radio-panel',
414 placeholder: '',
415 field: 'ruleCode',
416 default: props.ruleTypeValue,
417 options: props.ruleTypeList,
418 children: panelList.value,
419 clearable: true,
420 required: false,
421 block: true,
422 col: 'no-wrap'
423 },
424 ])
425
426 const formRules = ref({
427 ruleConfName: [
428 { required: true, trigger: 'blur', message: "请填写规则名称" }
429 ],
430 largeCategory: [
431 { required: true, trigger: 'change', message: "请填写规则大类" }
432 ],
433 smallCategory: [
434 { required: true, trigger: 'change', message: "请填写规则小类" }
435 ],
436 parity: [
437 { required: true, trigger: 'change', message: "请填写检验方式" }
438 ],
439 compareWay: [
440 { required: true, trigger: 'change', message: "请填写比较方式" }
441 ],
442 ruleSettings: [
443 { required: true, trigger: 'change', message: "请设置规则" }
444 ],
445 'ruleSettings-logic': [
446 { required: true, trigger: 'change', message: "请设置规则" }
447 ],
448 'ruleSettings-sql': [
449 { required: true, trigger: 'change', message: "请设置规则" }
450 ],
451 'ruleSettings-null': [
452 { required: true, trigger: 'change', message: "请设置规则" }
453 ],
454 'ruleSettings-rows': [
455 { required: true, trigger: 'change', message: "请设置规则" }
456 ],
457 orangeThreshold: [
458 {
459 trigger: 'blur', validator: (rule: any, value: any, callback: any) => {
460 if (value === 0) {
461 callback();
462 return;
463 }
464 if (!value) {
465 callback(new Error('请填写橙色阈值'));
466 return;
467 }
468 if (parseFloat(value) > 1) {
469 callback(new Error('请填写小于1的小数'));
470 return;
471 }
472 let formInline = ruleFormRef.value?.formInline;
473 if (formInline && formInline.redThreshold && parseFloat(value) >= parseFloat(formInline.redThreshold)) {
474 callback(new Error("橙色阈值应小于红色阈值"));
475 }
476 callback();
477 }
478 }
479 ],
480 redThreshold: [
481 {
482 required: true, trigger: 'blur', validator: (rule: any, value: any, callback: any) => {
483 if (value === 0) {
484 callback();
485 return;
486 }
487 if (!value) {
488 callback(new Error('请填写红色阈值'));
489 return;
490 }
491 if (parseFloat(value) > 1) {
492 callback(new Error('请填写小于1的小数'));
493 return;
494 }
495 let formInline = ruleFormRef.value?.formInline;
496 if (formInline && formInline.orangeThreshold && parseFloat(value) <= parseFloat(formInline.orangeThreshold)) {
497 callback(new Error("红色阈值应大于橙色阈值"));
498 }
499 callback();
500 }
501 }
502 ],
503 qualityModelGuids: [
504 { type: 'array', required: true, message: '请至少选择一个质检表', trigger: 'change' }
505 ],
506 })
507
508 const ruleFormRef = ref();
509
510 const oldOriginValue: any = ref({});
511
512 const dialogVisible = ref(false);
513
514 const cancelDialog = () => {
515 dialogVisible.value = false;
516 }
517
518 const submit = () => {
519 if (Object.keys(nullSelectFields.value).length === 0) {
520 ElMessage.error('当前已选字段不能为空!');
521 return;
522 }
523 let index = ruleType.value == 'repeate_data_check' ? 7 : 10;
524 panelList.value[index].defaultValue = nullSelectFields.value;
525 let str = "";
526 for (const key in nullSelectFields.value) {
527 let fields = nullSelectFields.value[key];
528 str = str + (str ? ';' : '') + fields.map(f => f.enName).join(',');
529 }
530 let formInline = oldOriginValue.value = Object.assign({
531 qualityModelGuids: props.toSubjectTables.map(s => s.guid),
532 parity: 1,
533 compareWay: 1,
534 jointly: 'N',
535 bizState: 'Y'
536 }, oldOriginValue.value, ruleFormRef.value.formInline);
537 formInline[`${panelList.value[index].field}`] = str;
538 setPanelListValue(formInline);
539 dialogVisible.value = false;
540 }
541
542 const getSubjectTableTreeData = () => {
543 return getSubjectTableTree({}).then((res: any) => {
544 if (res.code == proxy.$passCode) {
545 contrastSubjects.value = res.data || [];
546 return res.data || [];
547 }
548 })
549 }
550
551 const formBtnClick = (btn) => {
552 if (ruleType.value == 'null_value_check' || ruleType.value == 'repeate_data_check') {
553 dialogVisible.value = true;
554 tableListInfo.value.data = props.toSubjectTables;
555 dialogSelectSubjectTable.value = props.toSubjectTables[0];
556 nullTableInfo.value.data = [];
557 let index = ruleType.value == 'repeate_data_check' ? 7 : 10;
558 nullSelectFields.value = cloneDeep(panelList.value[index].defaultValue);
559 if (props.toSubjectTables[0]?.guid) {
560 nullTableInfo.value.loading = true;
561 getSubjectFields(props.toSubjectTables[0]?.guid).then((res: any) => {
562 nullTableInfo.value.loading = false;
563 if (res.code == proxy.$passCode) {
564 nullTableInfo.value.data = res.data || [];
565 let tagData = nullSelectFields.value[props.toSubjectTables[0]?.chName];
566 if (tagData?.length) {
567 nextTick(() => {
568 tagData?.forEach((d) => {
569 let row = nullTableInfo.value.data.find((v: any) => v.guid == d.guid);
570 if (row) {
571 nullTableRef.value?.tableRef?.toggleRowSelection(row, true);
572 }
573 });
574 });
575 }
576 } else {
577 ElMessage.error(res.msg);
578 }
579 })
580 }
581 } else if (ruleType.value == 'logic_check') {
582 filterDialogVisible.value = true;
583 tableListInfo.value.data = props.toSubjectTables;
584 dialogSelectSubjectTable.value = props.toSubjectTables[0];
585 let defaultValue = panelList.value[8].defaultValue;
586 if (defaultValue.ruleFields) {
587 dialogSelectSubjectTableField.value = cloneDeep(defaultValue.ruleFields);
588 tableFilters.value = cloneDeep(defaultValue.conditionSqls);
589 tableFilterValidates.value = {};
590 for (const key in tableFilters.value) {
591 tableFilterValidates.value[key] = true;
592 }
593 } else {
594 dialogSelectSubjectTableField.value = {};
595 tableFilters.value = {};
596 tableFilterValidates.value = {};
597 }
598 tableInfo.value.data = [];
599 if (props.toSubjectTables[0]?.guid) {
600 tableInfo.value.loading = true;
601 getSubjectFields(props.toSubjectTables[0]?.guid).then((res: any) => {
602 tableInfo.value.loading = false;
603 if (res.code == proxy.$passCode) {
604 tableInfo.value.data = res.data || [];
605 } else {
606 ElMessage.error(res.msg);
607 }
608 })
609 }
610 } else if (ruleType.value == 'custom_sql') {
611 sqlDialogVisible.value = true;
612 tableListInfo.value.data = props.toSubjectTables;
613 dialogSelectSubjectTable.value = props.toSubjectTables[0];
614 let defaultValue = panelList.value[9].defaultValue || {};
615 sqlSelectField.value = cloneDeep(defaultValue.ruleFields || {});
616 sqlFieldsList.value = {};
617 for (const key in defaultValue.sqlFieldsList) {
618 sqlFieldsList.value[key] = defaultValue.sqlFieldsList?.[key]?.map(d => {
619 return {
620 value: d
621 }
622 }) || []
623 }
624 sqlTableFilters.value = cloneDeep(defaultValue.customSqls || {});
625 sqlFilterValidates.value = {};
626 for (const key in sqlTableFilters.value) {
627 sqlFilterValidates.value[key] = true;
628 }
629 sqlTableFieldInfo.value.data = [];
630 if (props.toSubjectTables[0]?.guid) {
631 sqlTableFieldInfo.value.loading = true;
632 getSubjectFields(props.toSubjectTables[0]?.guid).then((res: any) => {
633 sqlTableFieldInfo.value.loading = false;
634 if (res.code == proxy.$passCode) {
635 sqlTableFieldInfo.value.data = res.data || [];
636 } else {
637 ElMessage.error(res.msg);
638 }
639 })
640 }
641 } else if (ruleType.value == 'rows_check') {
642 if (!contrastSubjects.value?.length) {
643 getSubjectTableTreeData().then(res => {
644 return res;
645 })
646 }
647 tableRowDialogVisible.value = true;
648 tableRowRulesData.value = cloneDeep(panelList.value[11].defaultValue || []);
649 if (!tableRowRulesData.value.length) {
650 tableRowRulesData.value = [{
651 differenceRange: 0
652 }];
653 }
654 }
655 }
656
657 const selectChange = (val, item, info) => {
658 setPanelListValue(info, true);
659 };
660
661 /** 设置表单选项的值,防止largeCategory的options改变,重新赋值。 当radioGroupChange为true,init为false时,为用户手动切换,需要更新大类的默认值。 */
662 const setPanelListValue = (item, isSelectChange = false, init = false, radioGroupChange = false) => {
663 let val = oldOriginValue.value = Object.assign({
664 qualityModelGuids: props.toSubjectTables.map(s => s.guid),
665 parity: 1,
666 largeCategory: getDefaultLargeCategory(formItems.value[0].default),
667 compareWay: 1,
668 jointly: 'N',
669 bizState: 'Y'
670 }, oldOriginValue.value, item);
671 let ruleCode = val.ruleCode;
672 panelList.value.forEach(p => {
673 let field = p.field;
674 if (p.field == 'bizState') {
675 p.visible = !props.readonly;
676 } else if (field == 'ruleFluctuations') {
677 p.children?.forEach(c => {
678 c.default = val[c.field];
679 });
680 } else if (p.field === 'qualityModelGuids') {
681 if (val[p.field] && typeof val[p.field] == 'string') {
682 p.default = [val[p.field]];
683 } else {
684 p.default = val[p.field] || [];
685 }
686 p.disabled = true;
687 } else if (p.field == 'ruleFluctuations') {
688 p.children.forEach(c => {
689 c.default = val[c.field]
690 })
691 } else if ((ruleCode == 'repeate_data_check' && p.field == 'ruleSettings') || (ruleCode == 'null_value_check' && p.field == 'ruleSettings-null')) {
692 if (!init) {
693 p.default = val[field];
694 return;
695 }
696 if (val.ruleField) {
697 p.default = val.ruleField?.map(f => f.enName)?.join(';');
698 p.defaultValue[`${val.subjectZhName}`] = val.ruleField || [];
699 } else {
700 p.default = '';
701 p.defaultValue = {};
702 }
703 } else if (ruleCode == 'logic_check' && p.field == 'ruleSettings-logic') {
704 if (!init) {
705 p.default = val[field];
706 return;
707 }
708 if (val.ruleField) {
709 p.default = val.ruleField?.map(f => f.enName)?.join(';');
710 let ruleFields = {};
711 ruleFields[val.subjectName] = val.ruleField?.[0] || [];
712 let conditionSqls = {};
713 conditionSqls[val.subjectName] = val.conditionSql;
714 p.defaultValue = {
715 ruleFields: ruleFields,
716 conditionSqls: conditionSqls
717 }
718 } else {
719 p.default = '';
720 p.defaultValue = {};
721 }
722 } else if (ruleCode == 'custom_sql' && p.field == 'ruleSettings-sql') {
723 if (!init) {
724 p.default = val[field];
725 return;
726 }
727 if (val.ruleField) {
728 if (val.ruleField?.length) {
729 p.default = val.subjectName + ":" + val.ruleField?.map(f => f.enName)?.join(',');
730 }
731 let ruleFields = {};
732 ruleFields[val.subjectName] = val.ruleField?.map(f => f.enName) || [];
733 let sqls = {};
734 sqls[val.subjectName] = val.customSql;
735 let sqlFieldsList = {};
736 sqlFieldsList[val.subjectName] = val.fieldSelects || [];
737 p.defaultValue = {
738 ruleFields: ruleFields,
739 customSqls: sqls,
740 sqlFieldsList: sqlFieldsList
741 };
742 } else {
743 p.default = '';
744 p.defaultValue = {};
745 }
746 } else if (ruleCode == 'rows_check' && p.field == 'ruleSettings-rows') {
747 if (!init) {
748 p.default = val[field];
749 return;
750 }
751 p.default = val.subjectName;
752 if (val.subjectName) {
753 p.defaultValue = [{
754 mainTable: val.subjectGuid,
755 mainTableName: val.subjectName,
756 mainTableZhName: val.subjectZhName,
757 differenceRange: val.differenceRange || 0,
758 contrastSubjectGuid: val.contrastSubjectGuid,
759 contrastSubjectName: val.contrastSubjectName,
760 contrastSubjectZhName: val.contrastSubjectZhName
761 }]
762 } else {
763 p.defaultValue = [];
764 }
765 } else if (p.field == 'largeCategory') {
766 /** 此处有歧义,若是切换规则类型,修改默认值,可能会出现,用户先修改了规则大类,但是切换类型之后,被我还原了。 */
767 if (radioGroupChange && !init) {
768 p.default = getDefaultLargeCategory(ruleCode);
769 } else {
770 p.default = val[p.field];
771 }
772 if (!props.readonly) {
773 if (p.default == '1') {//规范性
774 panelList.value[2].options = props.smallCategoryList.slice(0, 6);
775 } else if (p.default == '2') {
776 panelList.value[2].options = props.smallCategoryList.slice(6, 8);
777 } else if (p.default == '3') {
778 panelList.value[2].options = props.smallCategoryList.slice(8, 13);
779 } else if (p.default == '4') {
780 panelList.value[2].options = props.smallCategoryList.slice(13, 15);
781 } else if (p.default == '5') {
782 panelList.value[2].options = props.smallCategoryList.slice(15, 18);
783 } else if (p.default == '6') {
784 panelList.value[2].options = props.smallCategoryList.slice(18, 20);
785 }
786 }
787 if (isSelectChange) {
788 val['smallCategory'] = panelList.value[2].options[0]?.paramValue;
789 } else if (!val['smallCategory']) {
790 val['smallCategory'] = getDefaultSmallCategory(formItems.value[0].default);
791 } else if (radioGroupChange && !init) {//切换规则类型。
792 val['smallCategory'] = getDefaultSmallCategory(ruleCode);
793 }
794 } else {
795 p.default = val[field];
796 }
797 });
798 formItems.value[0].children = panelList.value;
799 console.log(panelList.value);
800 }
801
802 const radioGroupChange = (val, inlineValue, init) => {
803 formItems.value[0].default = val;
804 let list: any = panelList.value
805 list.forEach((item, index) => {
806 if (val == 'volatility_check') {//表行数波动率
807 item.visible = true
808 if (index == 7 || index == 8 || index == 9 || index == 10 || index == 11 || index === 13) {// 7是规则设置,9是联合不为空 : 9+4
809 item.visible = false
810 }
811 } else if (val === 'null_value_check') {//空值检查
812 if (index === 3 || index === 4 || index === 5 || index === 6 || index == 7 || index == 8 || index == 9 || index == 11) {
813 item.visible = false
814 } else { //index为10的显示
815 item.visible = true;
816 }
817 } else if (val === 'repeate_data_check') { //重复数据检查
818 item.visible = false
819 if (index === 3 || index === 4 || index === 5 || index === 6 || index == 8 || index == 9 || index == 10 || index == 11 || index === 13) {
820 } else {
821 item.visible = true;
822 }
823 } else if (val === 'logic_check') { //逻辑检查
824 item.visible = false
825 if (index === 3 || index === 4 || index === 5 || index === 6 || index == 7 || index == 9 || index == 10 || index == 11 || index === 13) {
826 } else {
827 item.visible = true;
828 }
829 } else if (val === 'custom_sql') {//自定义sql
830 item.visible = false
831 if (index === 3 || index === 4 || index === 5 || index === 6 || index == 7 || index == 8 || index == 10 || index == 11 || index === 13) {
832 } else {
833 item.visible = true;
834 }
835 } else if (val === 'rows_check') { //表行数
836 item.visible = false
837 if (index === 3 || index === 4 || index === 5 || index === 6 || index == 7 || index == 8 || index == 9 || index == 10 || index === 13) {
838 } else {
839 item.visible = true;
840 }
841 }
842 })
843 ruleType.value = val;
844 panelList.value = list;
845 Object.keys(inlineValue).length > 0 && setPanelListValue(inlineValue, false, init, true);
846 formItems.value[0].children = panelList.value;
847 }
848
849 const filterDialogVisible = ref(false);
850 /** 质检表 */
851 const dialogSelectSubjectTable: any = ref({});
852 /** 记录表对应的质检字段 */
853 const dialogSelectSubjectTableField: any = ref({});
854
855 const listItemClick = (data) => {
856 if (dialogSelectSubjectTable.value == data) {
857 return;
858 }
859 dialogSelectSubjectTable.value = data;
860 if (ruleType.value === 'custom_sql') {
861 sqlTableFilters.value[dialogSelectSubjectTable.value.enName] = sqlTableFilters.value[dialogSelectSubjectTable.value.enName] || "";
862 sqlTableFieldInfo.value.loading = true;
863 getSubjectFields(data.guid).then((res: any) => {
864 sqlTableFieldInfo.value.loading = false;
865 if (res.code == proxy.$passCode) {
866 sqlTableFieldInfo.value.data = res.data || [];
867 } else {
868 ElMessage.error(res.msg);
869 }
870 })
871 } else if (ruleType.value === 'logic_check') {
872 tableFilters.value[dialogSelectSubjectTable.value.enName] = tableFilters.value[dialogSelectSubjectTable.value.enName] || "";
873 tableInfo.value.loading = true;
874 getSubjectFields(data.guid).then((res: any) => {
875 tableInfo.value.loading = false;
876 if (res.code == proxy.$passCode) {
877 tableInfo.value.data = res.data || [];
878 } else {
879 ElMessage.error(res.msg);
880 }
881 })
882 } else if (ruleType.value == 'null_value_check' || ruleType.value == 'repeate_data_check') {
883 nullTableInfo.value.loading = true;
884 getSubjectFields(data.guid).then((res: any) => {
885 nullTableInfo.value.loading = false;
886 if (res.code == proxy.$passCode) {
887 nullTableInfo.value.data = res.data || [];
888 nextTick(() => {
889 let tagData = nullSelectFields.value[dialogSelectSubjectTable.value.chName] || [];
890 tagData?.forEach((d) => {
891 let row = nullTableInfo.value.data.find((v: any) => v.guid == d.guid);
892 if (row) {
893 nullTableRef.value?.tableRef?.toggleRowSelection(row, true);
894 }
895 });
896 });
897 } else {
898 ElMessage.error(res.msg);
899 }
900 })
901 }
902 }
903
904 const tableListInfo = ref({
905 id: 'subject-list',
906 field: 'label',
907 getName: (item) => {
908 return `${item.enName}(${item.chName})`;
909 },
910 data: props.toSubjectTables
911 })
912
913 const tableInfo: any = ref({
914 id: 'subject-fields-table',
915 loading: false,
916 fields: [
917 { label: "字段名", field: "enName", width: 140, type: 'text_btn', value: 'enName', columClass: 'text_btn' },
918 { label: "注释", field: "chName", width: 120 },
919 { label: "数据类型", field: "dataTypeChName", width: 100 },
920 ],
921 data: [],
922 showPage: false,
923 actionInfo: {
924 show: !props.readonly,
925 label: "操作",
926 type: "btn",
927 width: 90,
928 btns: (scope) => {
929 if (scope.row.enName === dialogSelectSubjectTableField.value[dialogSelectSubjectTable.value.enName]?.enName) {
930 return [
931 { label: "取消选择", value: "delSelect" },
932 ]
933 }
934 return [
935 { label: "选择", value: "select" },
936 ]
937 },
938 },
939 })
940
941 /** 逻辑检查时,记录表对应的逻辑条件 */
942 const tableFilters: any = ref({});
943 /** 逻辑检查时,记录表对应的逻辑条件的检验结果。未检验通过的不能保存。 */
944 const tableFilterValidates: any = ref({});
945
946 const cancelFilterDialog = () => {
947 filterDialogVisible.value = false;
948 }
949
950 const filterFormListRef: any = ref({});
951
952 const submitFilter = () => {
953 let v: any = [];
954 for (const table in tableFilters.value) {
955 if (tableFilters.value[table]) {
956 if (!dialogSelectSubjectTableField.value[table]?.enName) {
957 if (dialogSelectSubjectTable.value.enName != table) {
958 filterFormListRef.value.setSelectList(table, 'enName');
959 }
960 ElMessage.error(`表【${table}】设置了质检条件,但未选择质检字段`);
961 return;
962 }
963 if (tableFilterValidates.value[table] !== true) {
964 if (dialogSelectSubjectTable.value.enName != table) {
965 filterFormListRef.value.setSelectList(table, 'enName');
966 }
967 ElMessage.error(`请先验证表【${table}】设置的质检条件`);
968 return;
969 }
970 v.push(table);
971 } else {
972 if (dialogSelectSubjectTableField.value[table]?.enName) {
973 if (dialogSelectSubjectTable.value.enName != table) {
974 filterFormListRef.value.setSelectList(table, 'enName');
975 }
976 ElMessage.error(`表【${table}】选择了质检字段,但未设置质检条件`);
977 return;
978 }
979 }
980 }
981 if (!v.length) {
982 ElMessage.error('当前未给表设置质检条件!');
983 return;
984 }
985 let index = 8;
986 panelList.value[index].defaultValue = {
987 ruleFields: dialogSelectSubjectTableField.value,
988 conditionSqls: tableFilters.value
989 };
990 let str = "";
991 for (const key in dialogSelectSubjectTableField.value) {
992 let field = dialogSelectSubjectTableField.value[key];
993 str = str + (str ? ';' : '') + field.enName;
994 }
995 let formInline = oldOriginValue.value = Object.assign({
996 qualityModelGuids: props.toSubjectTables.map(s => s.guid),
997 parity: 1,
998 compareWay: 1,
999 jointly: 'N',
1000 bizState: 'Y'
1001 }, oldOriginValue.value, ruleFormRef.value.formInline);
1002 formInline[`${panelList.value[index].field}`] = str;
1003 setPanelListValue(formInline);
1004 filterDialogVisible.value = false;
1005 }
1006
1007 const filterInputRef = ref();
1008
1009 const filterTableBtnClick = (scope, btn) => {
1010 if (btn.value === 'select') {
1011 dialogSelectSubjectTableField.value[dialogSelectSubjectTable.value.enName] = scope.row;
1012 } else if (btn.value === 'delSelect') {
1013 dialogSelectSubjectTableField.value[dialogSelectSubjectTable.value.enName] = {};
1014 } else {
1015 let selectField = scope.row.enName + ' ';
1016 let children = filterInputRef.value?.$el?.children?.[0];
1017 let selectionStart = children?.selectionStart;
1018 let filter = tableFilters.value[dialogSelectSubjectTable.value.enName] || "";
1019 if (filter) {
1020 tableFilters.value[dialogSelectSubjectTable.value.enName] = filter.slice(0, selectionStart) + selectField + filter.slice(selectionStart);
1021 nextTick(() => {
1022 children.focus();
1023 let index = selectionStart + selectField.length;
1024 children.setSelectionRange(index, index);
1025 })
1026 return;
1027 } else {
1028 tableFilters.value[dialogSelectSubjectTable.value.enName] = selectField;
1029 nextTick(() => {
1030 children.focus();
1031 filterInputRef.value.selectionStart = filter.length;
1032 filterInputRef.value.selectionEnd = filter.length;
1033 });
1034 }
1035 }
1036 }
1037
1038 const validateFilterBtnDisable = ref(false);
1039
1040 /** 检验逻辑条件是否合理。 */
1041 const validateFilter = () => {
1042 let enName = dialogSelectSubjectTable.value.enName;
1043 if (!tableFilters.value[enName]) {
1044 ElMessage.error('请先填写质检条件再验证sql');
1045 return;
1046 }
1047 validateFilterBtnDisable.value = true;
1048 validateSubjectTableRule({
1049 subjectGuid: dialogSelectSubjectTable.value.guid,
1050 condition: tableFilters.value[enName]
1051 }).then((res: any) => {
1052 validateFilterBtnDisable.value = false;
1053 if (res.code == proxy.$passCode) {
1054 tableFilterValidates.value[enName] = true;
1055 ElMessage.success('验证通过');
1056 } else {
1057 ElMessage.error(res.msg);
1058 }
1059 });
1060 }
1061
1062 const handleFilterChange = () => {
1063 tableFilterValidates.value[dialogSelectSubjectTable.value.enName] = false;
1064 }
1065
1066 const tableRowDialogVisible = ref(false);
1067 const tableRowRulesDataLoading = ref(false);
1068 const tableRowRulesData: any = ref([{
1069 differenceRange: 0
1070 }]);
1071
1072 const addRowRules = () => {
1073 tableRowRulesData.value.push({
1074 differenceRange: 0
1075 });
1076 }
1077
1078 const deleteRow = (scope) => {
1079 tableRowRulesData.value.splice(scope.$index, 1);
1080 }
1081
1082 const cancelTableRowDialog = () => {
1083 tableRowDialogVisible.value = false;
1084 }
1085
1086 const submitTableRow = () => {
1087 let v: any = [];
1088 if (!tableRowRulesData.value.length) {
1089 ElMessage.error('表行数检查规则不能为空');
1090 return;
1091 }
1092 let index: number = 0;
1093 for (const rule of tableRowRulesData.value) {
1094 if (!rule.mainTable) {
1095 ElMessage.error('主表不能为空');
1096 return;
1097 }
1098 if (!rule.contrastSubjectGuid) {
1099 ElMessage.error('对比表不能为空');
1100 return;
1101 }
1102 if (rule.mainTable == rule.contrastSubjectGuid) {
1103 ElMessage.error('主表和对比表不能选择同一个');
1104 return;
1105 }
1106 let label = props.toSubjectTables.find(s => s.guid === rule.mainTable).enName;
1107 if (v.includes(label)) {
1108 ElMessage.error('主表选择重复');
1109 return;
1110 }
1111 v.push(label);
1112 }
1113 let formInline = oldOriginValue.value = Object.assign({
1114 qualityModelGuids: props.toSubjectTables.map(s => s.guid),
1115 parity: 1,
1116 compareWay: 1,
1117 jointly: 'N',
1118 bizState: 'Y'
1119 }, oldOriginValue.value, ruleFormRef.value.formInline);
1120 panelList.value[11].defaultValue = tableRowRulesData.value;
1121 formInline[`${panelList.value[11].field}`] = v.join(';');
1122 setPanelListValue(formInline);
1123 tableRowDialogVisible.value = false;
1124 }
1125
1126 const sqlTableFieldInfo: any = ref({
1127 id: 'subject-fields-table',
1128 loading: false,
1129 fields: [
1130 { label: "字段名", field: "enName", width: 140, type: 'text_btn', value: 'enName', columClass: 'text_btn' },
1131 { label: "注释", field: "chName", width: 120 },
1132 { label: "数据类型", field: "dataTypeChName", width: 100 },
1133 ],
1134 data: [],
1135 showPage: false,
1136 actionInfo: {
1137 show: false
1138 },
1139 })
1140
1141 const sqlInputRef: any = ref(null);
1142
1143 const sqlTableBtnClick = (scope, btn) => {
1144 let selectField = scope.row.enName + ' ';
1145 let children = sqlInputRef.value?.$el?.children?.[0];
1146 let selectionStart = children?.selectionStart;
1147 let filter = sqlTableFilters.value[dialogSelectSubjectTable.value.enName] || "";
1148 if (filter) {
1149 sqlTableFilters.value[dialogSelectSubjectTable.value.enName] = filter.slice(0, selectionStart) + selectField + filter.slice(selectionStart);
1150 nextTick(() => {
1151 children.focus();
1152 let index = selectionStart + selectField.length;
1153 children.setSelectionRange(index, index);
1154 })
1155 return;
1156 } else {
1157 sqlTableFilters.value[dialogSelectSubjectTable.value.enName] = selectField;
1158 nextTick(() => {
1159 children.focus();
1160 sqlInputRef.value.selectionStart = filter.length;
1161 sqlInputRef.value.selectionEnd = filter.length;
1162 });
1163 }
1164 }
1165
1166 const sqlDialogVisible = ref(false);
1167 /** 记录表对应的自定义sql */
1168 const sqlTableFilters = ref({});
1169 /** 记录验证的sql */
1170 const sqlFilterValidates = ref({});
1171 /** 记录自定义sql选择的对应字段。 */
1172 const sqlSelectField = ref({});
1173
1174 /** 自定义sql的下拉列表。 */
1175 const sqlFieldsList = ref({});
1176
1177 const sqlFormListRef = ref();
1178
1179 const cancelSqlDialog = () => {
1180 sqlDialogVisible.value = false;
1181 };
1182
1183 const submitSql = () => {
1184 let v: any = [];
1185 for (const table in sqlTableFilters.value) {
1186 if (sqlTableFilters.value[table]) {
1187 if (sqlFilterValidates.value[table] !== true) {
1188 if (dialogSelectSubjectTable.value.enName != table) {
1189 sqlFormListRef.value.setSelectList(table, 'enName');
1190 }
1191 ElMessage.error(`请验证表【${table}】的sql`);
1192 return;
1193 }
1194 if (!sqlSelectField.value[table]?.length) {
1195 if (dialogSelectSubjectTable.value.enName != table) {
1196 sqlFormListRef.value.setSelectList(table, 'enName');
1197 }
1198 ElMessage.error(`表【${table}】设置了sql,但未选择质检字段`);
1199 return;
1200 }
1201 v.push(table);
1202 }
1203 }
1204 if (!v.length) {
1205 ElMessage.error('未给质检表设置sql!');
1206 return;
1207 }
1208
1209 let str = "";
1210 for (const key in sqlSelectField.value) {
1211 let field = sqlSelectField.value[key];
1212 str = str + (str ? ';' : '') + key + ':' + field.join(',');
1213 }
1214 let formInline = oldOriginValue.value = Object.assign({
1215 qualityModelGuids: props.toSubjectTables.map(s => s.guid),
1216 parity: 1,
1217 compareWay: 1,
1218 jointly: 'N',
1219 bizState: 'Y'
1220 }, oldOriginValue.value, ruleFormRef.value.formInline);
1221 let fList = {};
1222 for (const key in sqlFieldsList.value) {
1223 fList[key] = sqlFieldsList.value[key]?.map(s => s.value) || [];
1224 }
1225 panelList.value[9].defaultValue = {
1226 sqlFieldsList: fList,
1227 ruleFields: sqlSelectField.value,
1228 customSqls: sqlTableFilters.value
1229 };
1230 formInline[`${panelList.value[9].field}`] = str;
1231 setPanelListValue(formInline);
1232 sqlDialogVisible.value = false;
1233 }
1234
1235 const validateSqlBtnDisable = ref(false);
1236
1237 /** 检验自定义sql。 */
1238 const validateSql = () => {
1239 let enName = dialogSelectSubjectTable.value.enName;
1240 if (!sqlTableFilters.value[enName]) {
1241 ElMessage.error('请先填写自定义sql再验证');
1242 return;
1243 }
1244 validateSqlBtnDisable.value = true;
1245 validateCustomSql({
1246 subjectGuid: dialogSelectSubjectTable.value.guid,
1247 sqlScript: sqlTableFilters.value[enName]
1248 }).then((res: any) => {
1249 validateSqlBtnDisable.value = false;
1250 if (res?.code == proxy.$passCode) {
1251 sqlFilterValidates.value[enName] = true;
1252 sqlFieldsList.value[enName] = res.data?.map(d => {
1253 return {
1254 value: d
1255 }
1256 }) || [];
1257 sqlSelectField.value[dialogSelectSubjectTable.value.enName] = res.data || [];
1258 ElMessage.success('验证通过');
1259 } else {
1260 ElMessage.error(res.msg);
1261 }
1262 });
1263 }
1264
1265 const handleSqlChange = () => {
1266 sqlFilterValidates.value[dialogSelectSubjectTable.value.enName] = false;
1267 }
1268
1269 const nullTableInfo: any = ref({
1270 id: 'subject-fields-table',
1271 loading: false,
1272 multiple: !props.readonly,
1273 fields: [
1274 { label: "字段名", field: "enName", width: 140 },
1275 { label: "注释", field: "chName", width: 120 },
1276 { label: "数据类型", field: "dataTypeChName", width: 100 },
1277 ],
1278 data: [],
1279 showPage: false,
1280 actionInfo: {
1281 show: false
1282 },
1283 })
1284
1285 const nullSelectFields: any = ref({});
1286
1287 const nullTableRef = ref();
1288
1289 const emptySelectFields = () => {
1290 let tagData = nullSelectFields.value[dialogSelectSubjectTable.value.chName] || [];
1291 if (tagData.length) {
1292 nullTableRef.value?.tableRef?.clearSelection();
1293 }
1294 nullSelectFields.value = {};
1295 }
1296
1297 const uncheck = (tag, index, key) => {
1298 let data = nullSelectFields.value[key];
1299 data.splice(index, 1);
1300 let row = nullTableInfo.value.data.find(d => d.guid == tag.guid);
1301 nullTableRef.value?.tableRef?.toggleRowSelection(row, false);
1302 if (!data.length) {
1303 delete nullSelectFields.value[key];
1304 }
1305 }
1306
1307 const nullTableCheckboxSelectChange = (select, row) => {
1308 let data = nullSelectFields.value[dialogSelectSubjectTable.value.chName] || [];
1309 let isSelect = select.some(s => s.guid === row.guid);
1310 if (isSelect) {
1311 if (!data.length) {
1312 nullSelectFields.value[dialogSelectSubjectTable.value.chName] = data;
1313 }
1314 data.push(row);
1315 } else {
1316 let index = data.findIndex(d => d.guid === row.guid);
1317 if (index > -1) {
1318 data.splice(index, 1);
1319 if (!data.length) {
1320 delete nullSelectFields.value[dialogSelectSubjectTable.value.chName];
1321 }
1322 }
1323 }
1324 }
1325
1326 const nullTableCheckboxAllSelectChange = (select) => {
1327 let data = nullSelectFields.value[dialogSelectSubjectTable.value.chName] || [];
1328 if (!select.length) {
1329 let tableData = nullTableInfo.value.data;
1330 if (data.length) {
1331 tableData.forEach(d => {
1332 let index = data.findIndex(t => t.guid === d.guid);
1333 if (index > -1) {
1334 data.splice(index, 1);
1335 if (!data.length) {
1336 delete nullSelectFields.value[dialogSelectSubjectTable.value.chName];
1337 }
1338 }
1339 })
1340 }
1341 } else {
1342 let tableData = nullTableInfo.value.data;
1343 tableData.forEach(d => {
1344 let index = data.findIndex(t => t.guid === d.guid);
1345 if (index === -1) {
1346 if (!data.length) {
1347 nullSelectFields.value[dialogSelectSubjectTable.value.chName] = data;
1348 }
1349 data.push(d);
1350 }
1351 })
1352 }
1353 }
1354
1355 /** 对比的主题表可选择的数据 */
1356 const contrastSubjects: any = ref([]);
1357
1358 const getSubjectTableByDomainData = (guid) => {
1359 return getSubjectTableByDomain(guid).then((res: any) => {
1360 if (res.code == proxy.$passCode) {
1361 return res.data?.map(d => {
1362 d.isLeaf = true;
1363 d.name = `${d.enName}(${d.chName})`;
1364 d.parentGuid = guid;
1365 return d;
1366 }) || [];
1367 }
1368 })
1369 }
1370
1371 /** 主题表懒加载。 */
1372 const treeSelectLoad = (node, resolve) => {
1373 if (node.level == 0) {
1374 resolve(contrastSubjects.value);
1375 } else if (node.level === 1) {
1376 let guid = node.data.guid;
1377 resolve(contrastSubjects.value.find((t: any) => t.guid === guid)?.children || []);
1378 } else if (node.level === 2) {
1379 getSubjectTableByDomainData(node.data.guid).then((d) => {
1380 node.data.children = d;
1381 resolve(d);
1382 })
1383 }
1384 }
1385
1386 const defaultExpandedKeys = computed(() => {
1387 return props.value && contrastSubjects.value?.length ? [contrastSubjects.value.find(c => c.children?.some(cc => cc.guid == props.value.contrastSubjectDomainGuid)).guid, props.value.contrastSubjectDomainGuid] : [];
1388 })
1389
1390 const contrastSubjectInputFilterMethod = (v, data) => {
1391 if (!v) {
1392 return true;
1393 }
1394 return data.label?.includes(v) || data.name?.includes(v) ||
1395 data.chName?.includes(v) ||
1396 data.enName?.includes(v);
1397 };
1398
1399 const getFormInfo = () => {
1400 let formInline = ruleFormRef.value.formInline;
1401 let ruleName = props.ruleTypeList.find(option => option.ruleCode == formInline.ruleCode).ruleName;
1402 if (formInline.ruleCode == 'repeate_data_check') {
1403 let v = panelList.value[7].defaultValue;
1404 return Object.assign({}, formInline, {
1405 modelFields: v,
1406 ruleName: ruleName
1407 });
1408 } else if (formInline.ruleCode == 'null_value_check') {
1409 let v = panelList.value[10].defaultValue;
1410 return Object.assign({}, formInline, {
1411 modelFields: v,
1412 ruleName: ruleName
1413 });
1414 } else if (formInline.ruleCode == 'logic_check') {
1415 let v = panelList.value[8].defaultValue;
1416 return Object.assign({}, formInline, v, {
1417 ruleName: ruleName
1418 });
1419 } else if (formInline.ruleCode == 'custom_sql') {
1420 let v = panelList.value[9].defaultValue;
1421 return Object.assign({}, formInline, v, {
1422 ruleName: ruleName
1423 });
1424 } else if (formInline.ruleCode == 'volatility_check') {
1425 return formInline;
1426 } else if (formInline.ruleCode == 'rows_check') {
1427 let v = panelList.value[11].defaultValue;
1428 return Object.assign({}, formInline, {
1429 rows: v,
1430 ruleName: ruleName
1431 });
1432 }
1433 }
1434
1435 defineExpose({
1436 getFormInfo,
1437 ruleFormRef
1438 });
1439
1440 </script>
1441
1442 <template>
1443 <Form ref="ruleFormRef" :readonly="props.readonly" :itemList="formItems" formId="edit-standard-form"
1444 :rules="formRules" col="col3" @btnClick="formBtnClick" @selectChange="selectChange"
1445 @radioGroupChange="(val, inlineValue) => radioGroupChange(val, inlineValue, false)" />
1446
1447 <!-- 空值, 重复数据检查 -->
1448 <el-dialog v-model="dialogVisible" title="规则设置" width="850" :modal="true" :close-on-click-modal="false"
1449 destroy-on-close align-center>
1450 <div class="filter-dialog-content" :style="{ height: '500px' }">
1451 <div class="filter-table-list">
1452 <div class="left-title">质检表</div>
1453 <ListPanel class="list_unit" ref="formListRef" :listInfo="tableListInfo" @itemClick="listItemClick" />
1454 </div>
1455 <div class="empty-table-field">
1456 <div class="left-title">字段列表详情</div>
1457 <Table style="height: calc(100% - 32px)" ref="nullTableRef" :tableInfo="nullTableInfo"
1458 @table-checkbox-select-change="nullTableCheckboxSelectChange"
1459 @table-checkbox-all-select-change="nullTableCheckboxAllSelectChange"></Table>
1460 </div>
1461 <div class="empty-edit">
1462 <div class="header-title">
1463 <span class="left-title">已选</span>
1464 <span v-if="!props.readonly" class="text_btn" @click="emptySelectFields()">清空</span>
1465 </div>
1466 <div class="tags_list_panel">
1467 <div class="table_item" v-for="(tableFields, key) in nullSelectFields" :key="key">
1468 <div class="tag_title">{{ key }}</div>
1469 <div class="tag_item" v-for="(tag, index) in (tableFields || [])" :key="tag.guid">
1470 <span class="tag">
1471 <ellipsis-tooltip :content="`${tag.enName}(${tag.chName})`" class-name="tag-width"
1472 :refName="'tooltipOver' + tag.guid"></ellipsis-tooltip>
1473 <el-icon v-if="!props.readonly" @click="uncheck(tag, index, key)">
1474 <Close />
1475 </el-icon>
1476 </span>
1477 </div>
1478 </div>
1479 </div>
1480 </div>
1481 </div>
1482 <template #footer v-if="!props.readonly">
1483 <div class="dialog-footer">
1484 <el-button @click="cancelDialog">取消</el-button>
1485 <el-button @click="submit" type="primary" v-preReClick>确定</el-button>
1486 </div>
1487 </template>
1488 </el-dialog>
1489
1490 <!-- 自定义sql -->
1491 <el-dialog v-model="sqlDialogVisible" title="规则设置" width="800" :modal="true" :close-on-click-modal="false"
1492 destroy-on-close align-center>
1493 <div class="sql-dialog-content" :style="{ height: '500px' }">
1494 <div class="sql-table-list">
1495 <div class="left-title">质检表</div>
1496 <ListPanel class="list_unit" ref="sqlFormListRef" :listInfo="tableListInfo" @itemClick="listItemClick" />
1497 </div>
1498 <div class="table-field">
1499 <div class="left-title">字段列表详情</div>
1500 <Table style="height: calc(100% - 32px)" ref="sqlFormTableRef" :tableInfo="sqlTableFieldInfo"
1501 @tableBtnClick="sqlTableBtnClick"></Table>
1502 </div>
1503 <div class="sql-edit">
1504 <div class="field-title">
1505 <span>自定义sql</span>
1506 <el-button v-if="!props.readonly" @click="validateSql" link v-preReClick>验证</el-button>
1507 </div>
1508 <el-input v-model="sqlTableFilters[dialogSelectSubjectTable.enName]" ref="sqlInputRef" type="textarea"
1509 :disabled="props.readonly" :autosize="true" resize="none" @change="handleSqlChange"></el-input>
1510 <div class="left-title">质检字段<label style="color: red;">*</label></div>
1511 <el-select v-model="sqlSelectField[dialogSelectSubjectTable.enName]" placeholder="请选择" multiple filterable
1512 clearable :disabled="props.readonly">
1513 <el-option v-for="opt in sqlFieldsList[dialogSelectSubjectTable.enName]" :key="opt['value']"
1514 :label="opt['value']" :value="opt['value']" />
1515 </el-select>
1516 </div>
1517 </div>
1518
1519 <template #footer v-if="!props.readonly">
1520 <div class="dialog-footer">
1521 <el-button @click="cancelSqlDialog" v-preReClick>取消</el-button>
1522 <el-button @click="submitSql" type="primary" v-preReClick>确定</el-button>
1523 </div>
1524 </template>
1525 </el-dialog>
1526
1527 <!-- 逻辑检查 -->
1528 <el-dialog v-model="filterDialogVisible" title="规则设置" width="850" :modal="true" :close-on-click-modal="false"
1529 destroy-on-close align-center>
1530 <div class="filter-dialog-content" :style="{ height: '500px' }">
1531 <div class="filter-table-list">
1532 <div class="left-title">质检表</div>
1533 <ListPanel class="list_unit" ref="filterFormListRef" :listInfo="tableListInfo" @itemClick="listItemClick" />
1534 </div>
1535 <div class="table-field">
1536 <div class="left-title">字段列表详情</div>
1537 <Table style="height: calc(100% - 32px)" ref="formTableRef" :tableInfo="tableInfo"
1538 @tableBtnClick="filterTableBtnClick"></Table>
1539 </div>
1540 <div class="filter-edit">
1541 <div class="left-title">{{ '质检字段' }}<label style="color: red;">*</label></div>
1542 <el-input :model-value="dialogSelectSubjectTableField[dialogSelectSubjectTable.enName]?.enName || ''"
1543 :disabled="true" placeholder="请从左侧列表选择质检字段"></el-input>
1544 <div class="field-title">
1545 <span>质检条件</span>
1546 <el-button v-if="!props.readonly" @click="validateFilter" link v-preReClick>验证</el-button>
1547 </div>
1548 <el-input v-model="tableFilters[dialogSelectSubjectTable.enName]" @change="handleFilterChange"
1549 ref="filterInputRef" type="textarea" :autosize="true" resize="none"></el-input>
1550 </div>
1551 </div>
1552
1553 <template #footer v-if="!props.readonly">
1554 <div class="dialog-footer">
1555 <el-button @click="cancelFilterDialog" v-preReClick>取消</el-button>
1556 <el-button @click="submitFilter" type="primary" v-preReClick>确定</el-button>
1557 </div>
1558 </template>
1559 </el-dialog>
1560
1561 <!-- 表行数检查 -->
1562 <el-dialog v-model="tableRowDialogVisible" title="规则设置" width="750" :modal="true" :close-on-click-modal="false"
1563 destroy-on-close align-center>
1564 <div class="row-dialog-content">
1565 <el-table ref="rowTableRef" :data="tableRowRulesData" height="100%" :highlight-current-row="true" stripe
1566 v-loading="tableRowRulesDataLoading" tooltip-effect="light" border
1567 :style="{ height: 'calc(100% - 28px)', width: 'auto', 'max-width': '100%', display: 'inline-block' }">
1568 <el-table-column prop="mainTable" label="选择主表" width="200px" align="left" show-overflow-tooltip>
1569 <template #default="scope">
1570 <el-select v-if="!props.readonly" v-model="scope.row['mainTable']" placeholder="请选择">
1571 <el-option v-for="opt in toSubjectTables" :key="opt['guid']" :label="opt['label']" :value="opt['guid']" />
1572 </el-select>
1573 <span v-else>{{ scope.row['mainTableName'] + `(${scope.row['mainTableZhName']})` }}</span>
1574 </template>
1575 </el-table-column>
1576 <el-table-column prop="contrastSubjectGuid" label="选择对比表" width="200px" align="left" show-overflow-tooltip>
1577 <template #default="scope">
1578 <el-tree-select v-if="!props.readonly" ref="treeSelectRef" filterable clearable
1579 v-model="scope.row['contrastSubjectGuid']" node-key="guid" :data="contrastSubjects" placeholder="请选择" lazy
1580 :load="(node, resolve) => treeSelectLoad(node, resolve)" :default-expanded-keys="defaultExpandedKeys"
1581 :auto-expand-parent="true" :default-checked-keys="[props.value.contrastSubjectGuid]"
1582 :filter-node-method="contrastSubjectInputFilterMethod" :props="{
1583 value: 'guid',
1584 label: 'name',
1585 children: 'children',
1586 isLeaf: 'isLeaf'
1587 }">
1588 </el-tree-select>
1589 <span v-else>{{ scope.row['contrastSubjectName'] + `(${scope.row['contrastSubjectZhName']})` }}</span>
1590 </template>
1591 </el-table-column>
1592 <el-table-column prop="differenceRange" label="差值范围" width="200px" :align="props.readonly ? 'right' : 'left'"
1593 show-overflow-tooltip>
1594
1595 <template #default="scope">
1596 <el-input-number v-if="!props.readonly" v-model="scope.row['differenceRange']" :step="1" :min="0"
1597 step-strictly />
1598 <span v-else>{{ scope.row['differenceRange'] }}</span>
1599 </template>
1600 </el-table-column>
1601 <el-table-column label="操作" width="100px" align="left" fixed="right" v-if="!props.readonly">
1602
1603 <template #default="scope">
1604 <span class="text_btn" @click="deleteRow(scope)" v-preReClick>删除</span>
1605 </template>
1606 </el-table-column>
1607 </el-table>
1608 <div class="row-add-btn" v-if="!props.readonly">
1609 <el-button link @click="addRowRules" :disabled="toSubjectTables.length == 1 && tableRowRulesData.length == 1"
1610 :icon="CirclePlus" v-preReClick>添加规则</el-button>
1611 </div>
1612 </div>
1613
1614 <template #footer v-if="!props.readonly">
1615 <div class="dialog-footer">
1616 <el-button @click="cancelTableRowDialog" v-preReClick>取消</el-button>
1617 <el-button @click="submitTableRow" type="primary" v-preReClick>确定</el-button>
1618 </div>
1619 </template>
1620 </el-dialog>
1621 </template>
1622
1623 <style lang="scss" scoped>
1624 .filter-dialog-content {
1625 display: flex;
1626 flex-direction: row;
1627 margin: -8px -24px;
1628
1629 .left-title {
1630 height: 32px;
1631 font-size: 14px;
1632 color: #212121;
1633 line-height: 32px;
1634 padding-left: 8px;
1635 }
1636
1637 .el-input {
1638 margin-left: 8px;
1639 width: calc(100% - 16px);
1640 }
1641
1642 .filter-table-list {
1643 width: 200px;
1644
1645 .list_unit {
1646 height: calc(100% - 32px);
1647 overflow-y: auto;
1648 border-top: 1px solid #d9d9d9;
1649 }
1650 }
1651
1652 .table-field {
1653 width: 460px;
1654
1655 .left-title {
1656 border-left: 1px solid #d9d9d9;
1657 border-right: 1px solid #d9d9d9;
1658 }
1659 }
1660
1661 .empty-table-field {
1662 .left-title {
1663 border-left: 1px solid #d9d9d9;
1664 border-right: 1px solid #d9d9d9;
1665 }
1666 }
1667
1668 .filter-edit {
1669 width: calc(100% - 560px);
1670 }
1671
1672 .field-title {
1673 height: 32px;
1674 font-size: 14px;
1675 color: #212121;
1676 line-height: 32px;
1677 padding-left: 8px;
1678 padding-right: 8px;
1679 display: flex;
1680 flex-direction: row;
1681 justify-content: space-between;
1682 align-items: center;
1683 }
1684
1685 :deep(.el-textarea) {
1686 height: calc(100% - 96px);
1687 padding: 0px 8px 8px 8px;
1688
1689 .el-textarea__inner {
1690 min-height: 100% !important;
1691 border-radius: 0px;
1692 }
1693 }
1694
1695 .empty-edit {
1696 flex-grow: 1;
1697 min-width: 0;
1698
1699 .header-title {
1700 padding-right: 12px;
1701 display: flex;
1702 flex-direction: row;
1703 justify-content: space-between;
1704 align-items: center;
1705 }
1706
1707 .tags_list_panel {
1708 border-top: 1px solid #d9d9d9;
1709 height: calc(100% - 32px);
1710 overflow-y: auto;
1711
1712 .table_item {
1713 padding: 8px;
1714
1715 .tag_title {
1716 font-size: 12px;
1717 color: #666666;
1718 line-height: 18px;
1719 }
1720
1721 .tag_item {
1722 padding: 4px 0px;
1723
1724 .tag {
1725 display: inline-flex;
1726 font-size: 12px;
1727 color: var(--el-color-regular);
1728 line-height: 24px;
1729 padding-left: 8px;
1730 border: 1px solid transparent;
1731 cursor: pointer;
1732 align-items: center;
1733 width: 100%;
1734 position: relative;
1735
1736 :deep(.tag-width) {
1737 width: calc(100% - 20px);
1738 }
1739
1740 &>span {
1741 max-width: calc(100% - 20px);
1742 overflow: hidden;
1743 text-overflow: ellipsis;
1744 white-space: nowrap;
1745 }
1746
1747 .el-icon {
1748 margin-left: 8px;
1749 margin-right: 8px;
1750 color: var(--el-color-primary);
1751 opacity: 0;
1752 float: right;
1753 right: 0px;
1754 position: absolute;
1755 }
1756
1757 &:hover {
1758 border-color: var(--el-color-primary);
1759
1760 .el-icon {
1761 opacity: 1;
1762 }
1763 }
1764 }
1765 }
1766 }
1767 }
1768 }
1769
1770 }
1771
1772 .row-dialog-content {
1773 height: 300px;
1774
1775 :deep(.el-table) {
1776 & td.el-table__cell {
1777 padding: 2px 0;
1778 height: 36px;
1779 }
1780 }
1781
1782 .row-add-btn {
1783 .el-button--default {
1784 padding: 4px 0px;
1785 }
1786
1787 :deep(.el-icon) {
1788 width: 16px;
1789 height: 16px;
1790
1791 svg {
1792 width: 16px;
1793 height: 16px;
1794 }
1795 }
1796 }
1797 }
1798
1799 .sql-dialog-content {
1800 display: flex;
1801 flex-direction: row;
1802 margin: -8px -24px;
1803 height: 400px;
1804
1805 .left-title {
1806 height: 32px;
1807 font-size: 14px;
1808 color: #212121;
1809 line-height: 32px;
1810 padding-left: 12px;
1811 padding-right: 8px;
1812 }
1813
1814 .sql-table-list {
1815 width: 200px;
1816 border-right: 1px solid #d9d9d9;
1817
1818 .list_unit {
1819 height: calc(100% - 32px);
1820 overflow-y: auto;
1821 border-top: 1px solid #d9d9d9;
1822 }
1823 }
1824
1825 .sql-edit {
1826 width: calc(100% - 360px);
1827
1828 .el-select {
1829 margin-left: 12px;
1830 margin-right: 8px;
1831 width: calc(100% - 20px);
1832 }
1833 }
1834
1835 .field-title {
1836 height: 32px;
1837 font-size: 14px;
1838 color: #212121;
1839 line-height: 32px;
1840 padding-left: 8px;
1841 padding-right: 8px;
1842 display: flex;
1843 flex-direction: row;
1844 justify-content: space-between;
1845 align-items: center;
1846 }
1847
1848 :deep(.el-textarea) {
1849 height: calc(100% - 120px);
1850 padding: 0px 8px 0px 8px;
1851
1852 .el-textarea__inner {
1853 min-height: 100% !important;
1854 border-radius: 0px;
1855 }
1856 }
1857 }
1858
1859 .border-left {
1860 border-left: 1px solid #d9d9d9;
1861 }
1862 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: ruleModel
3 </route>
4
5 <script lang="ts" setup name="ruleModel">
6 import { ref } from "vue";
7 import { useRouter, useRoute } from "vue-router";
8 import { ElMessage, ElMessageBox } from "element-plus";
9 import StepBar from "@/components/StepBar/index.vue";
10 import TreeTransfer from "@/components/TreeTransfer/index.vue";
11 import {
12 getSubjectTableTree,
13 getSubjectTableByDomain,
14 saveQualityTable,
15 getRuleTypeList,
16 getSmallCategoryList,
17 getLargeCategoryList,
18 } from '@/api/modules/dataQuality';
19 import ruleForm from "../data_quality/ruleForm.vue";
20 import useUserStore from "@/store/modules/user";
21 import useDataQualityStore from "@/store/modules/dataQuality";
22
23 const userStore = useUserStore();
24 const dataQualityStore = useDataQualityStore();
25
26 const { proxy } = getCurrentInstance() as any;
27
28 const router = useRouter();
29 const route = useRoute();
30
31 const modelGroupGuid: any = ref(route.query.groupGuid);
32 const fullPath = route.fullPath;
33
34 const fullScreenLoading = ref(false);
35
36 const step = ref(0);
37 const stepsInfo = ref({
38 step: step.value,
39 list: [
40 {
41 title: '选择表、字段',
42 value: 1,
43 tooltip: {
44 content: '可多选表和字段,批量为表和字段定义规则提示文字提示文字',
45 className: 'step_title_tooltip',
46 effect: 'light',
47 }
48 },
49 { title: '定义规则', value: 2 }
50 ]
51 })
52
53 /** 一定要带上pid,否则穿梭在右边后不是树结构。 */
54 const dsFromTreeDataLoading = ref(false);
55 const dsFromTreeData: any = ref([])
56
57 const dsToTreeData: any = ref([]);
58
59 const toSubjectTables: any = ref([]);
60
61 const getSubjectTableTreeData = () => {
62 dsFromTreeDataLoading.value = true;
63 getSubjectTableTree({}).then((res: any) => {
64 dsFromTreeDataLoading.value = false;
65 if (res.code == proxy.$passCode) {
66 dsFromTreeData.value = res.data?.map(d => {
67 d.parentGuid = 0;
68 return d;
69 }) || [];
70 }
71 })
72 }
73
74 const getSubjectTableByDomainData = (guid) => {
75 return getSubjectTableByDomain(guid).then((res: any) => {
76 if (res.code == proxy.$passCode) {
77 return res.data?.map(d => {
78 d.isLeaf = true;
79 d.name = `${d.enName}(${d.chName})`;
80 d.parentGuid = guid;
81 return d;
82 }) || [];
83 }
84 })
85 }
86
87 const handleSubjectTableLazyFn = (node, resolve) => {
88 if (node.level === 0) {
89 return resolve(dsFromTreeData.value);
90 }
91 if (node.level === 1) {
92 let guid = node.data.guid;
93 resolve(dsFromTreeData.value.find((t: any) => t.guid === guid)?.children || []);
94 } else if (node.level === 2) {
95 getSubjectTableByDomainData(node.data.guid).then((d) => {
96 node.data.children = d;
97 resolve(d);
98 })
99 }
100 }
101
102 const handleSubjectCheckedChange = (nodeObj, treeObj, checkAll, treeNode) => {
103 if (treeObj.checkedKeys?.includes(nodeObj.guid)) {
104 if (nodeObj.parentGuids?.length) {
105 treeNode?.expand();
106 } else if (nodeObj.parentGuid === 0) {
107 treeNode?.expand();
108 nextTick(() => {
109 treeNode.childNodes?.forEach(n => n.expand());
110 })
111 }
112 }
113 }
114
115 const ruleTypeList = ref([]);
116 const smallCategoryList = ref([]);
117 const largeCategoryList = ref([]);
118
119 onBeforeMount(() => {
120 getSubjectTableTreeData();
121 getRuleTypeList().then((res: any) => {
122 if (res.code == proxy.$passCode) {
123 ruleTypeList.value = res.data?.map((d: any) => {
124 d.label = d.ruleName;
125 d.value = d.ruleCode;
126 return d;
127 }) || [];
128 } else {
129 ElMessage.error(res.msg);
130 }
131 })
132 getSmallCategoryList().then((res: any) => {
133 if (res.code == proxy.$passCode) {
134 smallCategoryList.value = res.data || [];
135 } else {
136 ElMessage.error(res.msg);
137 }
138 })
139 getLargeCategoryList().then((res: any) => {
140 if (res.code == proxy.$passCode) {
141 largeCategoryList.value = res.data || [];
142 } else {
143 ElMessage.error(res.msg);
144 }
145 })
146 })
147
148 const ruleFormRef = ref();
149
150 const changeStep = (val, skip = false) => {
151 if (val === 2) {
152 if (!dsToTreeData.value.length) {
153 ElMessage.error('已选主题表不能为空');
154 return;
155 }
156 toSubjectTables.value = [];
157 dsToTreeData.value.forEach(d => {
158 d.children.forEach(c => {
159 c.children.forEach(child => {
160 child.label = `${child.enName}(${child.chName})`;
161 toSubjectTables.value.push(child);
162 })
163 })
164 });
165 step.value = val - 1;
166 stepsInfo.value.step = val - 1
167 } else {
168 step.value = val - 1;
169 stepsInfo.value.step = val - 1
170 }
171 };
172
173 const cancel = () => {
174 ElMessageBox.confirm(
175 "当前页面尚未保存,确定放弃修改吗?",
176 "提示",
177 {
178 confirmButtonText: "确定",
179 cancelButtonText: "取消",
180 type: "warning",
181 }
182 )
183 .then(() => {
184 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
185 router.push({
186 name: 'qualityRules',
187 });
188 })
189 .catch(() => {
190 ElMessage({
191 type: "info",
192 message: "已取消",
193 });
194 });
195 }
196
197 /** 将新建规则之后转化为对应质检表的规则。 */
198 const transformRulesInfo = (info: any) => {
199 let modelRules: any = []; //key为质检表,值为信息。
200 if (info.ruleCode == "volatility_check") {//表行数波动率。
201 let subjectTables = toSubjectTables.value;
202 info.qualityModelGuids?.forEach((modelGuid: string) => {
203 let tableInfo = subjectTables.find(t => t.guid === modelGuid);
204 modelRules.push(Object.assign({}, {
205 modelGroupGuid: modelGroupGuid.value,
206 name: tableInfo.chName,
207 subjectName: tableInfo.enName,
208 subjectGuid: tableInfo.guid,
209 dataSourceGuid: tableInfo.dataSourceGuid,
210 databaseName: tableInfo.dataServerName,
211 modelRuleConfList: [Object.assign({}, info, {
212 qualityModelGuid: modelGuid
213 })]
214 }));
215 })
216 }
217 else if (info.ruleCode == 'null_value_check' || info.ruleCode == 'repeate_data_check') {
218 let subjectTables = toSubjectTables.value;
219 for (const ds in info.modelFields) {
220 let fields = info.modelFields[ds];
221 let tableInfo = subjectTables.find(t => t.chName === ds);
222 modelRules.push(Object.assign({}, {
223 modelGroupGuid: modelGroupGuid.value,
224 name: tableInfo.chName,
225 subjectName: tableInfo.enName,
226 subjectGuid: tableInfo.guid,
227 dataSourceGuid: tableInfo.dataSourceGuid,
228 databaseName: tableInfo.dataServerName,
229 modelRuleConfList: [Object.assign({}, info, {
230 ruleField: fields?.map(f => {
231 return {
232 guid: f.guid,
233 enName: f.enName,
234 chName: f.chName
235 }
236 }) || [],
237 })]
238 }));
239 }
240 } else if (info.ruleCode == 'logic_check') {
241 let subjectTables = toSubjectTables.value;
242 for (const ds in info.ruleFields) {
243 let fields = info.ruleFields[ds];
244 let tableInfo = subjectTables.find(t => t.enName === ds);
245 modelRules.push(Object.assign({}, {
246 modelGroupGuid: modelGroupGuid.value,
247 name: tableInfo.chName,
248 subjectName: tableInfo.enName,
249 subjectGuid: tableInfo.guid,
250 dataSourceGuid: tableInfo.dataSourceGuid,
251 databaseName: tableInfo.dataServerName,
252 modelRuleConfList: [Object.assign({}, info, {
253 ruleField: [{
254 guid: fields.guid,
255 enName: fields.enName,
256 chName: fields.chName
257 }],
258 conditionSql: info.conditionSqls?.[ds],
259 conditionSqls: '',
260 ruleFields: ''
261 })]
262 }));
263 }
264 } else if (info.ruleCode == 'custom_sql') {
265 let subjectTables = toSubjectTables.value;
266 for (const ds in info.ruleFields) {
267 let fields = info.ruleFields[ds];
268 let tableInfo = subjectTables.find(t => t.enName === ds);
269 modelRules.push(Object.assign({}, {
270 modelGroupGuid: modelGroupGuid.value,
271 name: tableInfo.chName,
272 subjectName: tableInfo.enName,
273 subjectGuid: tableInfo.guid,
274 dataSourceGuid: tableInfo.dataSourceGuid,
275 databaseName: tableInfo.dataServerName,
276 modelRuleConfList: [Object.assign({}, info, {
277 ruleField: fields?.map(f => {
278 return {
279 enName: f
280 }
281 }) || [],
282 customSql: info.customSqls[ds],
283 fieldSelects: info.sqlFieldsList?.[ds] || [],
284 conditionSqls: '',
285 ruleFields: ''
286 })]
287 }));
288 }
289 } else if (info.ruleCode == 'rows_check') {
290 let subjectTables = toSubjectTables.value;
291 info.rows.forEach(row => {
292 let tableInfo = subjectTables.find(t => t.guid === row.mainTable);
293 modelRules.push(Object.assign({}, {
294 modelGroupGuid: modelGroupGuid.value,
295 name: tableInfo.chName,
296 subjectName: tableInfo.enName,
297 subjectGuid: tableInfo.guid,
298 dataSourceGuid: tableInfo.dataSourceGuid,
299 databaseName: tableInfo.dataServerName,
300 modelRuleConfList: [Object.assign({}, info, {
301 differenceRange: row.differenceRange,
302 rows: [],
303 contrastSubjectGuid: row.contrastSubjectGuid
304 })]
305 }));
306 })
307 }
308 return modelRules;
309 }
310
311 const save = () => {
312 ruleFormRef.value?.ruleFormRef?.ruleFormRef?.validate((valid) => {
313 if (valid) {
314 let v = ruleFormRef.value?.getFormInfo();
315 let params = transformRulesInfo(v);
316 fullScreenLoading.value = true;
317 saveQualityTable(params).then((res: any) => {
318 fullScreenLoading.value = false;
319 if (res.code == proxy.$passCode) {
320 ElMessage.success('新建质检表保存成功');
321 //跳到对应的分组下
322 router.push({
323 name: 'qualityRules'
324 });
325 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
326 dataQualityStore.set(modelGroupGuid.value);
327 } else {
328 ElMessage.error(res.msg);
329 }
330 })
331 }
332 });
333 }
334
335 </script>
336
337 <template>
338 <div class="container_wrap full" v-loading="fullScreenLoading">
339 <div class="content_main">
340 <div class="top_tool_wrap">
341 <StepBar :steps-info="stepsInfo" />
342 </div>
343 <div class="operator_panel_wrap first-step-content" v-show="step == 0">
344 <div class="operator_panel is-block">
345 <div class="panel_title">
346 <div class="title_text">
347 <span>选择主题表</span>
348 <span class="tips_text">选择需要添加质检规则的主题表</span>
349 </div>
350 </div>
351 <TreeTransfer mode="transfer" :title="['主题表', '已选表']" pid="parentGuid"
352 :from-tree-data-loading="dsFromTreeDataLoading" :lazy="true" :checkOnClickNode="true"
353 :from_checked_all="false" :from_data="dsFromTreeData" :to_data="dsToTreeData" node_key="guid"
354 :transferOpenNode="true" width="70%" :defaultProps="{
355 label: 'name',
356 value: 'guid'
357 }" :lazyFn="handleSubjectTableLazyFn" @left-check-change="handleSubjectCheckedChange" height="calc(100% - 64px)">
358 </TreeTransfer>
359 </div>
360 </div>
361 <div class="operator_panel_wrap" v-show="step == 1">
362 <div class="operator_panel is-block">
363 <div class="panel_title">
364 <div class="title_text">
365 <span>定义规则</span>
366 <span class="tips_text">可为多表配置表级规则、字段级规则</span>
367 </div>
368 </div>
369 <div class="panel_content">
370 <div class="form_panel">
371 <ruleForm ref="ruleFormRef" :toSubjectTables="toSubjectTables" :ruleTypeList="ruleTypeList"
372 :largeCategoryList="largeCategoryList" :smallCategoryList="smallCategoryList"></ruleForm>
373 </div>
374 </div>
375 </div>
376 </div>
377 </div>
378 <div class="bottom_tool_wrap">
379 <template v-if="step == 0">
380 <el-button @click="cancel">取消</el-button>
381 <el-button type="primary" @click="changeStep(2)">下一步</el-button>
382 </template>
383
384 <template v-else>
385 <el-button @click="cancel">取消</el-button>
386 <el-button @click="changeStep(1)">上一步</el-button>
387 <el-button type="primary" @click="save" v-preReClick>保存</el-button>
388 </template>
389 </div>
390 </div>
391 </template>
392
393 <style lang="scss" scoped>
394 .top_tool_wrap {
395 width: 100%;
396 height: 72px;
397 margin: 8px 0 0px;
398 display: flex;
399 justify-content: center;
400 align-items: center;
401
402 :deep(.el-steps) {
403 width: 30%;
404 }
405 }
406
407 .content_main {
408 height: calc(100% - 40px);
409 padding: 0 16px 16px;
410 overflow: hidden auto;
411
412 :deep(.transfer_panel_wrap) {
413 .transfer_panel {
414 box-shadow: 0 0 0 1px var(--el-border-color-regular);
415 }
416 }
417
418 .first-step-content {
419 height: calc(100% - 80px);
420
421 .wl-transfer {
422 margin: 8px 16px;
423 height: calc(100% - 64px);
424 width: 70%;
425 min-width: 600px;
426 }
427 }
428
429 .operator_panel_wrap {
430 height: calc(100% - 80px);
431 min-height: 400px;
432 display: flex;
433 justify-content: space-between;
434
435 :deep(.el-button) {
436 &.is-text {
437 height: auto;
438 padding: 0;
439 }
440 }
441
442 .operator_panel {
443 width: calc(50% - 5px);
444 height: 100%;
445 border: 1px solid #d9d9d9;
446 overflow: hidden;
447
448 &.is-block {
449 width: 100%;
450 }
451
452 .panel_title {
453 height: 48px;
454 padding: 0 15px;
455 display: flex;
456 justify-content: space-between;
457 align-items: center;
458 color: var(--el-color-regular);
459 font-weight: 600;
460 border-bottom: 1px solid #d9d9d9;
461 background-color: #f5f5f5;
462
463 .tips_text {
464 font-size: 14px;
465 color: #999;
466 font-weight: normal;
467 margin-left: 8px;
468 }
469 }
470
471 .panel_content {
472 height: calc(100% - 46px);
473
474 >div {
475 width: 100%;
476 height: 100%;
477 overflow: hidden;
478 }
479
480 .form_panel {
481 padding: 8px 16px 0;
482 overflow: hidden auto;
483 }
484
485 .tree_search_input {
486 margin-bottom: 10px;
487 }
488
489 .list_panel {
490 padding: 10px 0;
491 overflow: hidden auto;
492
493 .list_item {
494 height: 32px;
495 padding: 0 10px;
496 display: flex;
497 justify-content: space-between;
498 align-items: center;
499
500 &:hover {
501 color: var(--g-sub-sidebar-menu-active-color);
502 background-color: var(--g-sub-sidebar-menu-active-bg);
503 }
504 }
505 }
506 }
507 }
508 }
509
510 .transfer_btns {
511 display: flex;
512 flex-direction: column;
513 justify-content: center;
514 align-items: center;
515
516 .el-button {
517 margin: 0 8px;
518
519 &+.el-button {
520 margin-top: 8px;
521 }
522 }
523 }
524 }
525
526 .bottom_tool_wrap {
527 height: 40px;
528 padding: 0 16px;
529 border-top: 1px solid #d9d9d9;
530 display: flex;
531 justify-content: flex-end;
532 align-items: center;
533 }
534 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: ruleModelEdit
3 </route>
4
5 <script lang="ts" setup name="ruleModelEdit">
6 import { ref } from "vue";
7 import ruleForm from "../data_quality/ruleForm.vue";
8 import { useRouter, useRoute } from "vue-router";
9 import {
10 getRuleConfDetail,
11 getRuleTypeList,
12 getSmallCategoryList,
13 getLargeCategoryList,
14 updateModelRule
15 } from '@/api/modules/dataQuality';
16 import { ElMessage, ElMessageBox } from "element-plus";
17 import useUserStore from "@/store/modules/user";
18 import useDataQualityStore from "@/store/modules/dataQuality";
19
20 const userStore = useUserStore();
21 const dataQualityStore = useDataQualityStore();
22
23 const { proxy } = getCurrentInstance() as any;
24
25 const router = useRouter();
26 const route = useRoute();
27 const ruleGuid = route.query.guid;
28 const fullPath = route.fullPath;
29
30 const fullScreenLoading = ref(false);
31
32 const detailLoading = ref(false);
33
34 const detailInfo: any = ref({});
35
36 const toSubjectTables: any = ref([]);
37
38 const ruleType = ref('');
39
40 const ruleFormRef = ref();
41
42 const cancel = () => {
43 ElMessageBox.confirm(
44 "当前页面尚未保存,确定放弃修改吗?",
45 "提示",
46 {
47 confirmButtonText: "确定",
48 cancelButtonText: "取消",
49 type: "warning",
50 }
51 )
52 .then(() => {
53 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
54 router.push({
55 name: 'qualityRules',
56 });
57 })
58 .catch(() => {
59 ElMessage({
60 type: "info",
61 message: "已取消",
62 });
63 });
64 }
65
66 /** 将新建规则之后转化为对应质检表的规则。 */
67 const transformRulesInfo = (info: any) => {
68 if (info.ruleCode == "volatility_check") {//表行数波动率。
69 return Object.assign({}, info, {
70 guid: ruleGuid,
71 qualityModelGuid: detailInfo.value.qualityModelGuid,
72 ruleCode: detailInfo.value.ruleCode,
73 });
74 }
75 else if (info.ruleCode == 'null_value_check' || info.ruleCode == 'repeate_data_check') {
76 return Object.assign({}, info, {
77 guid: ruleGuid,
78 qualityModelGuid: detailInfo.value.qualityModelGuid,
79 ruleCode: detailInfo.value.ruleCode,
80 ruleField: info.modelFields[detailInfo.value.subjectZhName] || []
81 });
82 } else if (info.ruleCode === 'logic_check') {
83 let subjectName = detailInfo.value.subjectName;
84 let fields = info.ruleFields[subjectName];
85 return Object.assign({}, info, {
86 guid: ruleGuid,
87 qualityModelGuid: detailInfo.value.qualityModelGuid,
88 ruleCode: detailInfo.value.ruleCode,
89 ruleField: [{
90 guid: fields.guid,
91 enName: fields.enName,
92 chName: fields.chName
93 }],
94 conditionSql: info.conditionSqls?.[subjectName],
95 conditionSqls: '',
96 ruleFields: ''
97 });
98 } else if (info.ruleCode === 'custom_sql') {
99 let subjectGuid = detailInfo.value.subjectGuid;
100 return Object.assign({}, info, {
101 guid: ruleGuid,
102 qualityModelGuid: detailInfo.value.qualityModelGuid,
103 ruleCode: detailInfo.value.ruleCode,
104 customSql: info.customSqls?.[detailInfo.value.subjectName],
105 ruleField: info.ruleFields?.[detailInfo.value.subjectName]?.map(f => {
106 return {
107 enName: f
108 }
109 }) || [],
110 fieldSelects: info.sqlFieldsList?.[detailInfo.value.subjectName] || [],
111 customSqls: '',
112 ruleFields: ''
113 });
114 } else if (info.ruleCode == 'rows_check') {
115 return Object.assign({}, info, {
116 guid: ruleGuid,
117 qualityModelGuid: detailInfo.value.qualityModelGuid,
118 ruleCode: detailInfo.value.ruleCode,
119 contrastSubjectGuid: info.rows[0].contrastSubjectGuid,
120 differenceRange: info.rows[0].differenceRange,
121 rows: ''
122 });
123 }
124 }
125
126 const save = () => {
127 ruleFormRef.value?.ruleFormRef?.ruleFormRef?.validate((valid) => {
128 if (valid) {
129 let v = ruleFormRef.value?.getFormInfo();
130 let params = transformRulesInfo(v);
131 fullScreenLoading.value = true;
132 updateModelRule(params).then((res: any) => {
133 fullScreenLoading.value = false;
134 if (res.code == proxy.$passCode) {
135 ElMessage.success(`【${params.ruleConfName}】` + '质量规则编辑成功');
136 //跳到对应的分组下
137 router.push({
138 name: 'qualityRules'
139 });
140 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
141 dataQualityStore.setModelGuid(params.qualityModelGuid);
142 } else {
143 ElMessage.error(res.msg);
144 }
145 })
146 }
147 });
148 }
149
150 const getRuleDetailInfo = () => {
151 detailLoading.value = true;
152 getRuleConfDetail(ruleGuid).then((res: any) => {
153 detailLoading.value = false;
154 if (res.code == proxy.$passCode) {
155 let data = res.data || {};
156 detailInfo.value = data;
157 ruleType.value = detailInfo.value.ruleCode;
158 toSubjectTables.value = [{
159 guid: detailInfo.value.subjectGuid, //编辑的时候显示的是主题表
160 enName: detailInfo.value.subjectName,
161 chName: detailInfo.value.subjectZhName,
162 label: `${detailInfo.value.subjectName}(${detailInfo.value.subjectZhName})`
163 }]
164 detailInfo.value.qualityModelGuids = [detailInfo.value.subjectGuid];
165 if (fullPath === route.fullPath) {
166 document.title = `编辑-${data.ruleConfName}(${data.subjectZhName})`;
167 }
168 let tab: any = userStore.tabbar.find((tab: any) => tab.fullPath === fullPath);
169 if (tab) {
170 tab.meta.title = `编辑-${data.ruleConfName}(${data.subjectZhName})`;
171 }
172 } else {
173 ElMessage.error(res.msg);
174 }
175 })
176 }
177
178 const ruleTypeList = ref([]);
179 const smallCategoryList = ref([]);
180 const largeCategoryList = ref([]);
181
182 onBeforeMount(() => {
183 getRuleDetailInfo();
184 getRuleTypeList().then((res: any) => {
185 if (res.code == proxy.$passCode) {
186 ruleTypeList.value = res.data?.map((d: any) => {
187 d.label = d.ruleName;
188 d.value = d.ruleCode;
189 return d;
190 }) || [];
191 } else {
192 ElMessage.error(res.msg);
193 }
194 })
195 getSmallCategoryList().then((res: any) => {
196 if (res.code == proxy.$passCode) {
197 smallCategoryList.value = res.data || [];
198 } else {
199 ElMessage.error(res.msg);
200 }
201 })
202 getLargeCategoryList().then((res: any) => {
203 if (res.code == proxy.$passCode) {
204 largeCategoryList.value = res.data || [];
205 } else {
206 ElMessage.error(res.msg);
207 }
208 })
209 })
210
211 </script>
212
213 <template>
214 <div class="content_main" v-loading="fullScreenLoading">
215 <div class="operator_panel_wrap">
216 <div class="operator_panel is-block" v-loading="detailLoading">
217 <div class="panel_title">
218 <div class="title_text">
219 <span>规则</span>
220 </div>
221 </div>
222 <div class="panel_content">
223 <div class="form_panel">
224 <ruleForm ref="ruleFormRef" :toSubjectTables="toSubjectTables" :ruleTypeValue="ruleType" :value="detailInfo"
225 :ruleTypeList="ruleTypeList" :largeCategoryList="largeCategoryList"
226 :smallCategoryList="smallCategoryList"></ruleForm>
227 </div>
228 </div>
229 </div>
230 </div>
231 <div class="bottom_tool_wrap">
232 <el-button @click="cancel">取消</el-button>
233 <el-button type="primary" @click="save">保存</el-button>
234 </div>
235 </div>
236 </template>
237
238 <style lang="scss" scoped>
239 .content_main {
240 display: flex;
241 flex-direction: column;
242 height: 100%;
243 }
244
245 .operator_panel_wrap {
246 height: auto;
247 min-height: 200px;
248 display: flex;
249 justify-content: space-between;
250 margin: 16px;
251 max-height: calc(100% - 72px);
252
253 :deep(.el-button) {
254 &.is-text {
255 height: auto;
256 padding: 0;
257 }
258 }
259
260 .operator_panel {
261 width: calc(50% - 5px);
262 height: 100%;
263 border: 1px solid #d9d9d9;
264 overflow: hidden;
265
266 &.is-block {
267 width: 100%;
268 }
269
270 .panel_title {
271 height: 48px;
272 padding: 0 15px;
273 display: flex;
274 justify-content: space-between;
275 align-items: center;
276 color: var(--el-color-regular);
277 font-weight: 600;
278 border-bottom: 1px solid #d9d9d9;
279
280 .tips_text {
281 font-size: 14px;
282 color: #999;
283 font-weight: normal;
284 margin-left: 8px;
285 }
286 }
287
288 .panel_content {
289 height: calc(100% - 46px);
290
291 >div {
292 width: 100%;
293 height: 100%;
294 overflow: hidden;
295 }
296
297 .form_panel {
298 padding: 8px 16px 0;
299 overflow: hidden auto;
300 }
301 }
302 }
303 }
304
305 .bottom_tool_wrap {
306 height: 40px;
307 padding: 0 16px;
308 border-top: 1px solid #d9d9d9;
309 display: flex;
310 justify-content: flex-end;
311 align-items: center;
312 }
313 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <route lang="yaml">
2 name: ruleTemplate
3 </route>
4
5 <script lang="ts" setup name="ruleTemplate">
6 import { ref } from "vue";
7 import { useRouter, useRoute } from "vue-router";
8 import { ElMessage, ElMessageBox } from "element-plus";
9 import { CaretRight, CaretBottom, Delete, Plus } from '@element-plus/icons-vue'
10 import ruleForm from "../data_quality/ruleForm.vue";
11 import {
12 getRuleTypeList,
13 getSmallCategoryList,
14 getLargeCategoryList,
15 getModelDetail,
16 saveQualityTable
17 } from '@/api/modules/dataQuality';
18 import useUserStore from "@/store/modules/user";
19 import useDataQualityStore from "@/store/modules/dataQuality";
20
21 const userStore = useUserStore();
22 const dataQualityStore = useDataQualityStore();
23
24 const router = useRouter();
25 const route = useRoute();
26
27 const { proxy } = getCurrentInstance() as any;
28
29 const modelGuid = route.query.modelGuid;
30 const fullPath = route.fullPath;
31
32 const modelDetailInfo: any = ref({});
33
34 const toSubjectTables: any = ref([]);
35
36 const ruleList: any = ref([
37 {
38 open: true
39 }
40 ])
41
42 const addRule = () => {
43 ruleList.value.push({
44 open: true
45 });
46 };
47
48 const deleteRule = (item, index) => {
49 ElMessageBox.confirm(`确定删除【规则${index + 1}】吗?`, "提示", {
50 confirmButtonText: "确定",
51 cancelButtonText: "取消",
52 type: 'warning',
53 }).then(() => {
54 ruleList.value.splice(index, 1);
55 });
56 }
57
58 const toRouterPath = (url) => {
59 router.push({
60 path: url,
61 });
62 };
63
64 const fullScreenLoading = ref(false);
65
66 const ruleFormRef = ref();
67
68 const transformRulesInfo = (info) => {
69 if (info.ruleCode == "volatility_check") {//表行数波动率。
70 return Object.assign({}, info, {
71 qualityModelGuid: modelGuid,
72 ruleCode: info.ruleCode,
73 });
74 }
75 else if (info.ruleCode == 'null_value_check' || info.ruleCode == 'repeate_data_check') {
76 return Object.assign({}, info, {
77 qualityModelGuid: modelGuid,
78 ruleCode: info.ruleCode,
79 ruleField: info.modelFields[modelDetailInfo.value.name] || []
80 });
81 } else if (info.ruleCode === 'logic_check') {
82 let subjectName = modelDetailInfo.value.subjectName;
83 let fields = info.ruleFields[subjectName];
84 return Object.assign({}, info, {
85 qualityModelGuid: modelGuid,
86 ruleCode: info.ruleCode,
87 ruleField: [{
88 guid: fields.guid,
89 enName: fields.enName,
90 chName: fields.chName
91 }],
92 conditionSql: info.conditionSqls?.[subjectName],
93 conditionSqls: '',
94 ruleFields: ''
95 });
96 } else if (info.ruleCode === 'custom_sql') {
97 let subjectGuid = modelDetailInfo.value.subjectGuid;
98 return Object.assign({}, info, {
99 qualityModelGuid: modelGuid,
100 ruleCode: info.ruleCode,
101 customSql: info.customSqls?.[modelDetailInfo.value.subjectName],
102 ruleField: info.ruleFields?.[modelDetailInfo.value.subjectName]?.map(f => {
103 return {
104 enName: f
105 }
106 }) || [],
107 fieldSelects: info.sqlFieldsList?.[modelDetailInfo.value.subjectName] || [],
108 customSqls: '',
109 ruleFields: ''
110 });
111 } else if (info.ruleCode == 'rows_check') {
112 return Object.assign({}, info, {
113 qualityModelGuid: modelGuid,
114 ruleCode: info.ruleCode,
115 contrastSubjectGuid: info.rows[0].contrastSubjectGuid,
116 differenceRange: info.rows[0].differenceRange,
117 rows: ''
118 });
119 }
120 }
121
122 const save = () => {
123 if (!ruleList.value?.length) {
124 ElMessage.error('新增规则不能为空');
125 return;
126 }
127 let psList: Promise<void>[] = [];
128 ruleFormRef.value.forEach((form, index) => {
129 psList.push(new Promise((resolve, reject) => {
130 form.ruleFormRef.ruleFormRef.validate(valid => {
131 if (valid) {
132 resolve();
133 } else {
134 ruleList.value[index].open = true;
135 reject();
136 }
137 });
138 }));
139 })
140 Promise.all(psList).then((res: any) => {
141 let submitInfos: any = [];
142 ruleFormRef.value.forEach(form => {
143 let info = form.getFormInfo();
144 let params = transformRulesInfo(info);
145 submitInfos.push(params);
146 });
147 fullScreenLoading.value = true;
148 saveQualityTable([{
149 modelGroupGuid: modelDetailInfo.value.modelGroupGuid,
150 name: modelDetailInfo.value.name,
151 subjectGuid: modelDetailInfo.value.subjectGuid,
152 subjectName: modelDetailInfo.value.subjectName,
153 dataSourceGuid: modelDetailInfo.value.dataSourceGuid,
154 modelRuleConfList: submitInfos
155 }]).then((res: any) => {
156 fullScreenLoading.value = false;
157 if (res.code == proxy.$passCode) {
158 ElMessage.success('新建规则保存成功');
159 router.push({
160 name: 'qualityRules'
161 });
162 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
163 dataQualityStore.setModelGuid(modelGuid);
164 } else {
165 ElMessage.error(res.msg);
166 }
167 })
168 }).catch(() => {
169 });
170 }
171
172 const ruleTypeList = ref([]);
173 const smallCategoryList = ref([]);
174 const largeCategoryList = ref([]);
175
176 onBeforeMount(() => {
177 getModelDetail(modelGuid).then((res: any) => {
178 if (res.code == proxy.$passCode) {
179 let data = res.data || {};
180 modelDetailInfo.value = data;
181 if (data.subjectGuid) {
182 toSubjectTables.value = [{
183 guid: data.subjectGuid,
184 enName: data.subjectName,
185 chName: data.name,
186 label: `${data.subjectName}(${data.name})`
187 }];
188 }
189 } else {
190 ElMessage.error(res.msg);
191 }
192 })
193 getRuleTypeList().then((res: any) => {
194 if (res.code == proxy.$passCode) {
195 ruleTypeList.value = res.data?.map((d: any) => {
196 d.label = d.ruleName;
197 d.value = d.ruleCode;
198 return d;
199 }) || [];
200 } else {
201 ElMessage.error(res.msg);
202 }
203 })
204 getSmallCategoryList().then((res: any) => {
205 if (res.code == proxy.$passCode) {
206 smallCategoryList.value = res.data || [];
207 } else {
208 ElMessage.error(res.msg);
209 }
210 })
211 getLargeCategoryList().then((res: any) => {
212 if (res.code == proxy.$passCode) {
213 largeCategoryList.value = res.data || [];
214 } else {
215 ElMessage.error(res.msg);
216 }
217 })
218 })
219
220 const cancel = () => {
221 ElMessageBox.confirm(
222 "当前页面尚未保存,确定放弃修改吗?",
223 "提示",
224 {
225 confirmButtonText: "确定",
226 cancelButtonText: "取消",
227 type: "warning",
228 }
229 )
230 .then(() => {
231 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
232 router.push({
233 name: 'qualityRules',
234 });
235 })
236 .catch(() => {
237 ElMessage({
238 type: "info",
239 message: "已取消",
240 });
241 });
242 }
243
244 </script>
245
246 <template>
247 <div class="container_wrap full" v-loading="fullScreenLoading">
248 <div class="content_main">
249 <div class="operator_panel_wrap">
250 <div class="operator_panel is-block" v-for="(item, index) in ruleList">
251 <div class="panel_title" :style="{ 'border-bottom': item.open ? '1px solid #d9d9d9' : 'none' }">
252 <div class="title_text" @click="item.open = !item.open">
253 <el-icon class="text_icon">
254 <CaretRight v-show="!item.open" />
255 <CaretBottom v-show="item.open" />
256 </el-icon>
257 <span>规则{{ index + 1 }}</span>
258 </div>
259 <div class="title_tool" @click="deleteRule(item, index)">
260 <el-icon :size="20" color="#b2b2b2">
261 <Delete />
262 </el-icon>
263 </div>
264 </div>
265 <div class="panel_content" v-show="item.open">
266 <div class="form_panel">
267 <ruleForm ref="ruleFormRef" :toSubjectTables="toSubjectTables" :ruleTypeList="ruleTypeList"
268 :largeCategoryList="largeCategoryList" :smallCategoryList="smallCategoryList" :isSingle="true">
269 </ruleForm>
270 </div>
271 </div>
272 </div>
273 <div class="bottm_tools" @click="addRule">
274 <el-icon>
275 <Plus />
276 </el-icon>
277 <span>新增规则</span>
278 </div>
279 </div>
280 </div>
281 <div class="bottom_tool_wrap">
282 <el-button @click="cancel" v-preReClick>取消</el-button>
283 <el-button type="primary" @click="save" v-preReClick>保存</el-button>
284 </div>
285 </div>
286 </template>
287
288 <style lang="scss" scoped>
289 .top_tool_wrap {
290 width: 100%;
291 height: 72px;
292 margin: 8px 0 16px;
293 display: flex;
294 justify-content: center;
295 align-items: center;
296
297 :deep(.el-steps) {
298 width: 30%;
299 }
300 }
301
302 .content_main {
303 height: calc(100% - 40px);
304 padding: 16px 16px 0px;
305 overflow: hidden auto;
306
307 :deep(.table_panel_wrap) {
308 .table_panel {
309 width: 100%;
310 min-height: unset;
311 }
312 }
313
314 .operator_panel_wrap {
315 display: flex;
316 justify-content: space-between;
317 flex-wrap: wrap;
318
319 :deep(.el-button) {
320 &.is-text {
321 height: auto;
322 padding: 0;
323 }
324 }
325
326 .operator_panel {
327 width: calc(50% - 5px);
328 height: 100%;
329 border: 1px solid #d9d9d9;
330 margin-bottom: 12px;
331 overflow: hidden;
332
333 &.is-block {
334 width: 100%;
335 }
336
337 .panel_title {
338 height: 48px;
339 padding: 0 15px;
340 display: flex;
341 justify-content: space-between;
342 align-items: center;
343 color: var(--el-color-regular);
344 font-weight: 600;
345
346 .title_text {
347 display: flex;
348 cursor: pointer;
349 }
350
351 .tips_text {
352 font-size: 14px;
353 color: #999;
354 font-weight: normal;
355 margin-left: 8px;
356 }
357 }
358
359 .title_tool {
360 :deep(.el-icon) {
361 svg {
362 width: 1em;
363 height: 1em;
364 }
365 }
366 }
367
368 .panel_content {
369 height: calc(100% - 46px);
370
371 >div {
372 width: 100%;
373 height: 100%;
374 overflow: hidden;
375 }
376
377 .form_panel {
378 padding: 8px 16px 0;
379 overflow: hidden auto;
380 }
381
382 .tree_search_input {
383 margin-bottom: 10px;
384 }
385
386 .list_panel {
387 padding: 10px 0;
388 overflow: hidden auto;
389
390 .list_item {
391 height: 32px;
392 padding: 0 10px;
393 display: flex;
394 justify-content: space-between;
395 align-items: center;
396
397 &:hover {
398 color: var(--g-sub-sidebar-menu-active-color);
399 background-color: var(--g-sub-sidebar-menu-active-bg);
400 }
401 }
402 }
403
404 .table_content_wrap {
405 padding: 16px;
406
407 .tools_form {
408 p {
409 margin-top: 0;
410 margin-bottom: 8px;
411 }
412 }
413 }
414 }
415 }
416
417 .bottm_tools {
418 width: 100%;
419 height: 40px;
420 display: flex;
421 justify-content: center;
422 align-items: center;
423 background: #fafafa;
424 color: #999;
425 font-size: 14px;
426 border: 1px dashed var(--el-border-color-regular);
427 margin-bottom: 12px;
428
429 >span {
430 margin-left: 8px;
431 }
432
433 &:hover {
434 background: #EBF6F7;
435 border: 1px dashed var(--el-color-primary);
436 }
437 }
438 }
439
440 .transfer_btns {
441 display: flex;
442 flex-direction: column;
443 justify-content: center;
444 align-items: center;
445
446 .el-button {
447 margin: 0 8px;
448
449 &+.el-button {
450 margin-top: 8px;
451 }
452 }
453 }
454 }
455
456 .bottom_tool_wrap {
457 height: 40px;
458 padding: 0 16px;
459 border-top: 1px solid #d9d9d9;
460 display: flex;
461 justify-content: flex-end;
462 align-items: center;
463 }
464 </style>
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!