5f0a75ec by lihua

提交泛化文件迁移

1 parent bb0d0c63
1 /**
2 * 匿名化管理的接口api文件
3 */
4 import request from "@/utils/request";
5
6 /** 字段类型 */
7 export const fieldTypeList = [{
8 value: '1',
9 label: '日期'
10 }, {
11 value: '2',
12 label: '字符串'
13 }, {
14 value: '3',
15 label: '数值'
16 }]
17
18 /** 获取泛化文件列表 */
19 export const getGeneralizeFileList = (params) => request({
20 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/page-list`,
21 method: 'post',
22 data: params
23 })
24
25 /** 获取泛化文件列表,包括名称和guid */
26 export const getGeneralizeFileNameList = () => request({
27 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/name-list`,
28 method: 'post'
29 })
30
31 export const saveGeneralizeFile = (data) => request({
32 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/save`,
33 method: 'post',
34 data
35 })
36
37 /** 删除泛化文件 */
38 export const deleteGeneralizeFile = (data) => request({
39 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/delete`,
40 method: 'delete',
41 data
42 })
43
44 /** 获取泛化文件详情 */
45 export const getGeneralizeFileDetail = (guid) => request({
46 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/detail?guid=${guid}`,
47 method: 'get'
48 })
49
50 export const updateGeneralizeFile = (data) => request({
51 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/update`,
52 method: 'put',
53 data
54 })
55
56 /** 获取泛化文件解析结果 */
57 export const parseGeneralizeFileData = (data) => request({
58 url: `${import.meta.env.VITE_APP_DIGITAL_CONTRACT_URL}/generalize-file/parse-file`,
59 method: 'post',
60 data
61 })
...\ No newline at end of file ...\ No newline at end of file
...@@ -217,6 +217,46 @@ const routes: RouteRecordRaw[] = [ ...@@ -217,6 +217,46 @@ const routes: RouteRecordRaw[] = [
217 }, 217 },
218 }] 218 }]
219 }, 219 },
220 {
221 path: '/data-smart-contract-common/generalize-file',
222 component: Layout,
223 meta: {
224 title: '泛化文件管理',
225 icon: 'sidebar-videos',
226 },
227 children: [
228 {
229 path: '',
230 name: 'generalizeFile',
231 component: () => import('@/views/data_smart_contract/generalizeFile.vue'),
232 meta: {
233 title: '',
234 sidebar: false,
235 breadcrumb: false,
236 cache: true
237 },
238 },
239 {
240 path: 'generalize-file-edit',
241 name: 'generalizeFileEdit',
242 component: () => import('@/views/data_smart_contract/generalizeFileEdit.vue'),
243 meta: {
244 title: '新建泛化文件',
245 sidebar: false,
246 breadcrumb: false,
247 cache: true,
248 reuse: true,
249 editPage: true,
250 activeMenu: '/data-smart-contract-common/generalize-file'
251 },
252 beforeEnter: (to, from) => {
253 if (to.query.fileName) {
254 to.meta.title = `编辑-${to.query.fileName}`;
255 }
256 }
257 },
258 ],
259 },
220 ] 260 ]
221 261
222 export default routes 262 export default routes
...\ No newline at end of file ...\ No newline at end of file
......
1 const useDataAnonymizationStore = defineStore(
2 // 资产目录guid
3 'anonymization',
4 () => {
5 const isRefresh = ref<boolean>(false);
6 function setIsRefresh(v: boolean) {
7 isRefresh.value = v;
8 }
9
10 const isAnonPageRefresh = ref<boolean>(false);
11 function setIsAnonPageRefresh(v: boolean) {
12 isAnonPageRefresh.value = v;
13 }
14
15 return {
16 isRefresh,
17 isAnonPageRefresh,
18 setIsRefresh,
19 setIsAnonPageRefresh,
20 };
21 }
22 );
23
24 export default useDataAnonymizationStore;
1 <route lang="yaml">
2 name: generalizeFile
3 </route>
4
5 <script lang="ts" setup name="generalizeFile">
6 import TableTools from "@/components/Tools/table_tools.vue";
7 import { commonPageConfig } from '@/components/PageNav/index';
8 import { TableColumnWidth } from "@/utils/enum";
9 import {
10 deleteGeneralizeFile,
11 getGeneralizeFileList,
12 fieldTypeList,
13 } from '@/api/modules/dataAnonymization';
14 import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
15
16 const anonymizationStore = useDataAnonymizationStore();
17 const router = useRouter()
18 const { proxy } = getCurrentInstance() as any;
19
20 const searchItemList = ref([{
21 type: "input",
22 label: "",
23 field: "generalizeFileName",
24 default: "",
25 placeholder: "泛化文件名称",
26 clearable: true,
27 }, {
28 type: "select",
29 label: "",
30 field: "fieldType",
31 default: null,
32 options: fieldTypeList,
33 placeholder: "字段类型",
34 clearable: true,
35 filterable: true,
36 }])
37
38 /** 分页及搜索传参信息配置。 */
39 const page = ref({
40 ...commonPageConfig,
41 generalizeFileName: '',
42 fieldType: ''
43 });
44
45 const tableInfo = ref({
46 id: 'data-file-table',
47 fields: [
48 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
49 { label: "泛化文件名称", field: "generalizeFileName", width: 160 },
50 { label: "字段类型", field: "fieldType", width: 120, getName: (scope) => {
51 return scope.row.fieldType && fieldTypeList.find(f => f.value == scope.row.fieldType)?.label || '--';
52 } },
53 { label: "泛化层级", field: "generalizeLevel", width: 120, align: 'right', type: 'chnum' },
54 { label: "修改人", field: "updateUserName", width: TableColumnWidth.USERNAME },
55 { label: "修改时间", field: "updateTime", width: TableColumnWidth.DATETIME },
56 ],
57 data: [],
58 page: {
59 type: "normal",
60 rows: 0,
61 ...page.value,
62 },
63 loading: false,
64 actionInfo: {
65 label: "操作",
66 type: "btn",
67 width: 120,
68 fixed: 'right',
69 btns: (scope) => {
70 return [{
71 label: "编辑", value: "edit", click: (scope) => {
72 router.push({
73 name: 'generalizeFileEdit',
74 query: {
75 guid: scope.row.guid,
76 fileName: scope.row.generalizeFileName
77 }
78 });
79 }
80 }, {
81 label: "删除", value: "delete", disabled: scope.row.bizState == 'Y', click: (scope) => {
82 proxy.$openMessageBox("此操作将永久删除, 是否继续?", () => {
83 let guids = [scope.row.guid];
84 deleteGeneralizeFile(guids).then((res: any) => {
85 if (res?.code == proxy.$passCode) {
86 getTableData();
87 proxy.$ElMessage({
88 type: "success",
89 message: "删除成功",
90 });
91 } else {
92 proxy.$ElMessage({
93 type: "error",
94 message: res.msg,
95 });
96 }
97 });
98 }, () => {
99 proxy.$ElMessage.info("已取消");
100 })
101 }
102 }]
103 }
104 }
105 })
106
107 const toSearch = (val: any, clear: boolean = false) => {
108 if (clear) {
109 searchItemList.value.map((item) => (item.default = ""));
110 page.value.generalizeFileName = '';
111 page.value.fieldType = '';
112 } else {
113 page.value.generalizeFileName = val.generalizeFileName;
114 page.value.fieldType = val.fieldType;
115 }
116 getTableData();
117 };
118
119 const getTableData = () => {
120 tableInfo.value.loading = true
121 getGeneralizeFileList({
122 pageIndex: page.value.curr,
123 pageSize: page.value.limit,
124 generalizeFileName: page.value.generalizeFileName,
125 fieldType: page.value.fieldType
126 }).then((res: any) => {
127 if (res?.code == proxy.$passCode) {
128 const data = res.data || {}
129 tableInfo.value.data = data.records || []
130 tableInfo.value.page.limit = data.pageSize
131 tableInfo.value.page.curr = data.pageIndex
132 tableInfo.value.page.rows = data.totalRows
133 } else {
134 proxy.$ElMessage({
135 type: 'error',
136 message: res.msg,
137 })
138 }
139 tableInfo.value.loading = false
140 })
141 };
142
143 const tablePageChange = (info) => {
144 page.value.curr = Number(info.curr);
145 page.value.limit = Number(info.limit);
146 getTableData();
147 };
148
149 onBeforeMount(() => {
150 toSearch({});
151 })
152
153 onActivated(() => {
154 if (anonymizationStore.isRefresh) {//如果是首次加载,则不需要调用
155 page.value.curr = 1;
156 getTableData();
157 anonymizationStore.setIsRefresh(false);
158 }
159 })
160
161 const handleCreate = () => {
162 router.push({
163 name: 'generalizeFileEdit'
164 });
165 }
166
167 </script>
168
169 <template>
170 <div class="container_wrap">
171 <div class="table_tool_wrap">
172 <!-- 头部搜索 -->
173 <TableTools :searchItems="searchItemList" :searchId="'data-label-search'" @search="toSearch" :init="false" />
174 <div class="tools_btns">
175 <el-button type="primary" @click="handleCreate">新建</el-button>
176 </div>
177 </div>
178 <div class="table_panel_wrap">
179 <!-- 右侧标签管理表格 -->
180 <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" />
181 </div>
182 </div>
183 </template>
184
185 <style lang="scss" scoped>
186 .table_tool_wrap {
187 width: 100%;
188 height: 84px !important;
189 padding: 0 8px;
190
191 .tools_btns {
192 padding: 0px 0 0;
193 }
194 }
195
196 .table_panel_wrap {
197 width: 100%;
198 height: calc(100% - 84px);
199 padding: 0px 8px 0;
200 }
201 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <script lang="ts" setup name="generalizeFileEdit">
2 import {
3 fieldTypeList,
4 saveGeneralizeFile,
5 getGeneralizeFileDetail,
6 updateGeneralizeFile,
7 deleteGeneralizeFile,
8 parseGeneralizeFileData,
9 } from '@/api/modules/dataAnonymization';
10 import {
11 parseAndDecodeUrl,
12 getDownFileSignByUrl,
13 obsDownloadRequest
14 } from "@/api/modules/obsService";
15 import useUserStore from "@/store/modules/user";
16 import { useValidator } from '@/hooks/useValidator';
17 import * as XLSX from 'xlsx';
18 import { TableColumnWidth } from '@/utils/enum';
19 import { calcColumnWidth } from "@/utils/index";
20 import Moment from 'moment';
21 import useDataAnonymizationStore from "@/store/modules/dataAnonymization";
22 import { ElMessage } from 'element-plus';
23
24 const anonymizationStore = useDataAnonymizationStore();
25 const { required } = useValidator();
26 const userStore = useUserStore();
27 const router = useRouter();
28 const route = useRoute();
29 const { proxy } = getCurrentInstance() as any;
30 const fullPath = route.fullPath;
31 const guid = ref(route.query.guid);
32
33 const baseInfoItems = ref([{
34 label: '泛化文件名称',
35 type: 'input',
36 maxlength: 15,
37 placeholder: '请输入',
38 field: 'generalizeFileName',
39 default: '',
40 block: false,
41 clearable: true,
42 required: true
43 }, {
44 type: "select",
45 label: "字段类型",
46 field: "fieldType",
47 default: '2',
48 options: fieldTypeList,
49 placeholder: "请选择",
50 clearable: true,
51 filterable: true,
52 required: true,
53 block: false,
54 }, {
55 label: '最小泛化层级',
56 field: 'generalizeLevel',
57 placeholder: "请输入",
58 type: 'input',
59 inputType: 'integerNumber',
60 maxlength: 6,
61 default: null,
62 clearable: true,
63 required: true,
64 }]);
65
66 const baseInfoFormRules = ref({
67 generalizeFileName: [required('请输入泛化文件名称')],
68 fieldType: [required('请选择字段类型')],
69 generalizeLevel: [required('请输入最小泛化层级')]
70 });
71
72 /** 文件上传表单配置。 */
73 const fileFormItems: any = ref([
74 {
75 label: '选择文件上传',
76 tip: '支持扩展名:xlsx、xls、csv,文件大小不超过10MB',
77 type: 'upload-file',
78 accept: '.xlsx, .xls, .csv',
79 limit: 1,
80 limitSize: 10,
81 isExcel: true,
82 required: true,
83 default: [],
84 block: false,
85 field: 'file',
86 }
87 ]);
88
89 const fileFormRules = ref({
90 file: [{
91 validator: (rule: any, value: any, callback: any) => {
92 if (!value?.length) {
93 callback(new Error('请上传文件'))
94 } else {
95 callback();
96 }
97 }, trigger: 'change'
98 }]
99 });
100
101 /** 上传文件之后解析出字段和数据 */
102 const fileFieldLoading = ref(false);
103
104 const fileTableFields: any = ref([]);
105
106 const fileTableData: any = ref([]);
107
108 const parseFileData = (fileRaw) => {
109 fileFieldLoading.value = true;
110 let file = fileFormItems.value[0].default;
111 parseGeneralizeFileData({
112 name: file[0]?.name,
113 url: file[0]?.url
114 }).then((res: any) => {
115 fileFieldLoading.value = false;
116 if (res?.code == proxy.$passCode) {
117 let result = res.data?.fileDataRQVOS || [];
118 let resultFields = res.data?.sheetHeader || [];
119 fileRaw.arrayBuffer().then((f) => {
120 const wb = XLSX.read(f, {
121 raw: false, cellDates: true
122 });
123 const sheet = wb.Sheets[wb.SheetNames[0]];
124 const json: any[] = XLSX.utils.sheet_to_json(sheet, { header: 1 });
125 if (json.length == 0) {
126 fileTableFields.value = [];
127 fileTableData.value = [];
128 } else {
129 fileTableFields.value = resultFields?.map((j, index) => {
130 return {
131 index: index,
132 enName: j.enName + '',
133 chName: j.chName + '',
134 dataType: 'varchar'
135 }
136 }) || [];
137 if (json.length > 1) {
138 json.slice(1, 20).map((info, row) => {
139 let object = {};
140 json[0].forEach((name, col) => {
141 if (info[col] === "" || info[col] == null) {
142 object[name] = info[col];
143 } else {
144 var cellRef = XLSX.utils.encode_cell({ r: row + 1, c: col });
145 var cell = sheet[cellRef];
146 object[name] = cell.w || info[col];
147 let isNum = cell.t == 'n';
148 if (isNum && fileTableFields.value[col].dataType != 'int') {
149 fileTableFields.value[col].dataType = 'int';
150 }
151 }
152 });
153 return object;
154 });
155 fileTableData.value = result.slice(0, 50)?.map(d => {
156 return d.fileData || {};
157 })
158 } else {
159 fileTableData.value = [];
160 }
161 }
162 fileFieldLoading.value = false;
163 });
164 } else {
165 proxy.$ElMessage.error(res.msg);
166 }
167 });
168 }
169
170 const uploadFileChange = (file) => {
171 fileTableFields.value = [];
172 fileTableData.value = [];
173 if (!file.length) {
174 fileTableFields.value = [];
175 fileTableData.value = [];
176 fileFormItems.value[0].default = file;
177 return;
178 }
179 let fileRaw = file[0].file;
180 fileFormItems.value[0].default = file;
181 parseFileData(fileRaw);
182 }
183
184 /** otherWidth表示使用标题宽度时添加标题排序图标等宽度 */
185 const calcTableColumnWidth = (data: any[], prop, title, otherWidth = 0) => {
186 let d: any[] = [];
187 data.forEach((dt) => d.push(dt[prop]));
188 return calcColumnWidth(
189 d,
190 title,
191 {
192 fontSize: 14,
193 fontFamily: "SimSun",
194 },
195 {
196 fontSize: 14,
197 fontFamily: "SimSun",
198 },
199 otherWidth
200 );
201 };
202
203 /** 每列字段对应的列宽计算结果。 */
204 const originTableFieldColumn = ref({});
205
206 const getTextAlign = (field) => {
207 if (field.dataType === 'decimal' || field.dataType === 'int') {
208 return 'right';
209 }
210 return 'left'
211 }
212
213 watch(
214 fileTableData,
215 (val: any[], oldVal) => {
216 if (!fileTableFields.value?.length) {
217 originTableFieldColumn.value = {};
218 return;
219 }
220 originTableFieldColumn.value = {};
221 fileTableFields.value.forEach((field, index) => {
222 originTableFieldColumn.value[field.enName] = calcTableColumnWidth(
223 val?.slice(0, 20) || [],
224 field.enName,
225 field.chName,
226 24
227 );
228 });
229 },
230 {
231 deep: true,
232 }
233 );
234
235 const formatterPreviewDate = (row, info) => {
236 let enName = info.enName;
237 let v = row[enName];
238 if (v === 0) {
239 return v;
240 }
241 if (!v) {
242 return v || '--';
243 }
244 if (info.dataType === 'datetime') {
245 return Moment(v).format('YYYY-MM-DD HH:mm:ss');
246 }
247 if (info.dataType === 'date') {
248 if (isNaN(<any>(new Date(v)))) {
249 return Moment(parseInt(v)).format('YYYY-MM-DD');
250 } else {
251 return Moment(v).format('YYYY-MM-DD');
252 }
253 }
254 return v;
255 };
256
257 const cancel = () => {
258 proxy.$openMessageBox("当前页面尚未保存,确定放弃修改吗?", () => {
259 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
260 router.push({
261 name: 'generalizeFile'
262 });
263 }, () => {
264 proxy.$ElMessage.info("已取消");
265 });
266 }
267
268 const formRef = ref();
269
270 /** 文件表单 */
271 const fileFormRef = ref();
272
273 const saveLoading = ref(false);
274 const saveUpdate = async () => {
275 formRef.value?.ruleFormRef?.validate((valid, errorItem) => {
276 fileFormRef.value?.ruleFormRef?.validate((fileValid, errorItem) => {
277 if (valid && fileValid) {
278 let fileJson = fileFormRef.value?.formInline.file || [];
279 let params = Object.assign({}, formRef.value.formInline, {
280 filePath: {
281 name: fileJson[0]?.name,
282 url: fileJson[0]?.url
283 }
284 })
285 if (!guid.value) {
286 saveLoading.value = true;
287 saveGeneralizeFile(params).then((res: any) => {
288 if (res?.code == proxy.$passCode) {
289 proxy.$ElMessage.success('泛化文件新建成功');
290 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
291 anonymizationStore.setIsRefresh(true);
292 router.push({
293 name: 'generalizeFile'
294 });
295 saveLoading.value = false;
296 } else {
297 saveLoading.value = false;
298 proxy.$ElMessage.error(res.msg);
299 }
300 })
301 } else {
302 params.guid = guid.value;
303 saveLoading.value = true;
304 updateGeneralizeFile(params).then((res: any) => {
305 if (res?.code == proxy.$passCode) {
306 proxy.$ElMessage.success('泛化文件编辑成功');
307 userStore.setTabbar(userStore.tabbar.filter((tab: any) => tab.fullPath !== fullPath));
308 anonymizationStore.setIsRefresh(true);
309 router.push({
310 name: 'generalizeFile'
311 });
312 saveLoading.value = false;
313 } else {
314 saveLoading.value = false;
315 proxy.$ElMessage.error(res.msg);
316 }
317 })
318 }
319 }
320 });
321 })
322 }
323
324 const fullscreenLoading = ref(false);
325
326 onBeforeMount(() => {
327 if (guid.value) {
328 fullscreenLoading.value = true;
329 getGeneralizeFileDetail(guid.value).then(async (res: any) => {
330 if (res?.code == proxy.$passCode) {
331 let detail = res.data || {};
332 baseInfoItems.value.forEach(item => {
333 item.default = detail[item.field];
334 });
335 fileFormItems.value.forEach(item => {
336 item.default = [{
337 name: detail.filePath.name,
338 url: detail.filePath.url
339 }]
340 });
341 let url = detail.filePath.url;
342 const refSignInfo: any = await getDownFileSignByUrl(parseAndDecodeUrl(url).fileName);
343 if (!refSignInfo?.data) {
344 fullscreenLoading.value = false;
345 refSignInfo?.msg && ElMessage.error(refSignInfo?.msg);
346 return;
347 }
348 obsDownloadRequest(refSignInfo?.data).then((res: any) => {
349 if (res && !res.msg) {
350 parseFileData(res);
351 fullscreenLoading.value = false;
352 } else {
353 fullscreenLoading.value = false;
354 res?.msg && ElMessage.error(res?.msg);
355 }
356 })
357 } else {
358 fullscreenLoading.value = false;
359 proxy.$ElMessage.error(res.msg);
360 }
361 });
362 }
363 })
364
365 onActivated(() => {
366 const fileName = route.query.fileName;
367 let tab: any = userStore.tabbar.find((tab: any) => tab.fullPath === fullPath);
368 if (tab) {
369 if (fileName) {
370 tab.meta.title = `编辑-${fileName}`;
371 }
372 if (fullPath === route.fullPath) {
373 document.title = tab.meta.title;
374 }
375 }
376 });
377
378 </script>
379
380 <template>
381 <div class="container_wrap" v-loading="fullscreenLoading">
382 <div class="content_main">
383 <ContentWrap id="id-baseInfo" title="基本信息" description="" style="margin-top: 8px;">
384 <Form ref="formRef" :itemList="baseInfoItems" :rules="baseInfoFormRules" formId="main-model-edit" col="col3" />
385 </ContentWrap>
386 <ContentWrap id="id-grade-info" title="上传文件" class="detail-content" description="" style="margin-top: 8px;">
387 <Form ref="fileFormRef" :itemList="fileFormItems" :noUpload="false" formId="file-form" :rules="fileFormRules"
388 @uploadFileChange="uploadFileChange" />
389 <div v-show="fileTableFields.length" class="preview-data-totals">
390 <span>该表仅显示</span>
391 <span class="fontC-4fa1a4"> 50 </span>
392 <span>条数据</span>
393 </div>
394 <el-table ref="tableRef" v-loading="fileFieldLoading" v-show="fileTableFields.length" :data="fileTableData"
395 :highlight-current-row="true" stripe border tooltip-effect="light" height="100%" row-key="guid"
396 :style="{ width: '100%', height: '280px' }">
397 <template v-for="(item, index) in (fileTableFields || [])">
398 <el-table-column :label="item.chName" :width="item.dataType === 'datetime'
399 ? TableColumnWidth.DATETIME
400 : item.dataType === 'date'
401 ? TableColumnWidth.DATE
402 : originTableFieldColumn[item.enName]
403 " :align="getTextAlign(item)" :header-align="getTextAlign(item)"
404 :formatter="(row) => formatterPreviewDate(row, item)" :show-overflow-tooltip="true">
405 </el-table-column>
406 </template>
407 </el-table>
408 </ContentWrap>
409 </div>
410 <div class="bottom_tool_wrap">
411 <el-button @click="cancel">取消</el-button>
412 <el-button type="primary" @click="saveUpdate" :loading="saveLoading">提交</el-button>
413 </div>
414 </div>
415 </template>
416
417 <style lang="scss" scoped>
418 .container_wrap {
419 padding: 0px;
420 }
421
422 .content_main {
423 height: calc(100% - 44px);
424 padding: 10px 16px;
425 overflow: auto;
426
427 .table-top-btns {
428 margin-bottom: 12px;
429 }
430 }
431
432 .bottom_tool_wrap {
433 height: 44px;
434 padding: 0 16px;
435 border-top: 1px solid #d9d9d9;
436 display: flex;
437 justify-content: center;
438 align-items: center;
439 }
440
441 .tools_btns {
442 position: relative;
443 margin-bottom: 16px;
444
445 .show-change-btn {
446 position: absolute;
447 right: 0px;
448 }
449 }
450
451 .detail-content {
452 position: relative;
453 }
454
455 .preview-data-totals {
456 position: absolute;
457 top: 150px;
458 right: 16px;
459 font-size: 14px;
460 color: #666666;
461 font-weight: 400;
462
463 .fontC-4fa1a4 {
464 color: var(--el-color-primary);
465 }
466 }
467 </style>
...\ No newline at end of file ...\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!