638335cc by lxs

入表功调整

1 parent 812516c7
...@@ -9,10 +9,9 @@ export const getAssetCatalog = (params= {}) => request({ ...@@ -9,10 +9,9 @@ export const getAssetCatalog = (params= {}) => request({
9 }) 9 })
10 10
11 /** 获取成本项列表 */ 11 /** 获取成本项列表 */
12 export const getCostList = (params) => request({ 12 export const getCostList = () => request({
13 url: `${import.meta.env.VITE_API_NEW_PORTAL}/tableentry-index-classify/page-list`, 13 url: `${import.meta.env.VITE_API_NEW_PORTAL}/tableentry-index-classify/list`,
14 method: 'post', 14 method: 'post',
15 data: params
16 }) 15 })
17 16
18 /** 获取成本项详情 */ 17 /** 获取成本项详情 */
......
...@@ -6,13 +6,9 @@ ...@@ -6,13 +6,9 @@
6 import { ref, inject } from "vue"; 6 import { ref, inject } from "vue";
7 import { ElMessage, ElMessageBox, translate } from "element-plus"; 7 import { ElMessage, ElMessageBox, translate } from "element-plus";
8 import { MoreFilled } from '@element-plus/icons-vue' 8 import { MoreFilled } from '@element-plus/icons-vue'
9 import StepBar from "@/components/StepBar/index.vue";
10 import TableTools from "@/components/Tools/table_tools.vue";
11 import Table from "@/components/Table/index.vue";
12 import Form from "@/components/Form/index.vue";
13 import useUserStore from "@/store/modules/user"; 9 import useUserStore from "@/store/modules/user";
14 import { changeNum } from "@/utils/common"; 10 import { changeNum } from "@/utils/common";
15 import { getAssetCatalog, getCostList, sendEntryMsg } from "@/api/modules/dataEntry"; 11 import { costDelete, getAssetCatalog, getCostList, sendEntryMsg } from "@/api/modules/dataEntry";
16 import * as XLSXS from 'xlsx-js-style'; 12 import * as XLSXS from 'xlsx-js-style';
17 import { saveAs } from 'file-saver'; 13 import { saveAs } from 'file-saver';
18 14
...@@ -20,36 +16,17 @@ const { proxy } = getCurrentInstance() as any; ...@@ -20,36 +16,17 @@ const { proxy } = getCurrentInstance() as any;
20 const userStore = useUserStore(); 16 const userStore = useUserStore();
21 const userData = JSON.parse(userStore.userData); 17 const userData = JSON.parse(userStore.userData);
22 18
23 const itemWidth = ref('56px'); 19 const reload: any = inject("reload");
24 const itemLeft = ref('-56px');
25 const reload = inject("reload");
26 const loading = ref(false); 20 const loading = ref(false);
27 const companyName = ref(''); 21 const companyName = ref('');
28 /** 默认显示步骤 */ 22 /** 默认显示步骤 */
29 const step = ref(0); 23 const step = ref(0);
30 /** 步骤条配置信息 */
31 const stepsInfo = ref({
32 step: step.value,
33 list: [
34 { title: '设置成本项', value: 1 },
35 { title: '填写成本项', value: 2 },
36 { title: '入表', value: 3 },
37 ]
38 })
39 24
40 const allMapList = ref([]); 25 const showAdd = ref(false);
41 const showLevel4 = ref(false);
42 const gridList = ref([
43 { name: '一级指标', code: '0', children: [] },
44 { name: '一级分类', code: '1', children: [] },
45 { name: '二级分类', code: '2', children: [] },
46 { name: '三级分类', code: '3', children: [] },
47 ]);
48 const checkedList = ref([]);
49 const productList = ref([]); 26 const productList = ref([]);
50 const costFormRef = ref(); 27 const costFormRef = ref();
51 const entryFormRef = ref(); 28 const entryFormRef = ref();
52 const costFormInfo = ref({}); 29 const costFormInfo: any = ref({});
53 const costForm = ref({ 30 const costForm = ref({
54 id: 'step-form-one', 31 id: 'step-form-one',
55 col: '', 32 col: '',
...@@ -77,7 +54,7 @@ const costForm = ref({ ...@@ -77,7 +54,7 @@ const costForm = ref({
77 placeholder: "请选择", 54 placeholder: "请选择",
78 clearable: true, 55 clearable: true,
79 required: true, 56 required: true,
80 visible: false, 57 visible: true,
81 popperClass: 'date-year-popper', 58 popperClass: 'date-year-popper',
82 disabledDate: (date) => { 59 disabledDate: (date) => {
83 const curr = new Date(); 60 const curr = new Date();
...@@ -92,7 +69,7 @@ const costForm = ref({ ...@@ -92,7 +69,7 @@ const costForm = ref({
92 placeholder: "请选择", 69 placeholder: "请选择",
93 clearable: true, 70 clearable: true,
94 required: true, 71 required: true,
95 visible: false, 72 visible: true,
96 style: { width: 'calc(33.33% - 70px)', 'margin-right': '8px' }, 73 style: { width: 'calc(33.33% - 70px)', 'margin-right': '8px' },
97 popperClass: 'date-month-popper', 74 popperClass: 'date-month-popper',
98 disabledDate: (date) => { 75 disabledDate: (date) => {
...@@ -106,7 +83,7 @@ const costForm = ref({ ...@@ -106,7 +83,7 @@ const costForm = ref({
106 options: [ 83 options: [
107 { label: '确认', value: 'besure', type: 'primary', style: { height: '32px' } }, 84 { label: '确认', value: 'besure', type: 'primary', style: { height: '32px' } },
108 ], 85 ],
109 visible: false, 86 visible: true,
110 style: { width: 'unset', 'align-self': 'end', 'margin-right': '0px' } 87 style: { width: 'unset', 'align-self': 'end', 'margin-right': '0px' }
111 }, 88 },
112 ], 89 ],
...@@ -135,7 +112,7 @@ const costForm = ref({ ...@@ -135,7 +112,7 @@ const costForm = ref({
135 ] 112 ]
136 } 113 }
137 }); 114 });
138 const entryForm = ref({ 115 const entryForm: any = ref({
139 id: 'step-form-two', 116 id: 'step-form-two',
140 col: '', 117 col: '',
141 items: [ 118 items: [
...@@ -208,15 +185,8 @@ const entryForm = ref({ ...@@ -208,15 +185,8 @@ const entryForm = ref({
208 }); 185 });
209 186
210 const amount = ref(0); 187 const amount = ref(0);
211 const sumAmount = ref([]); 188 const sumAmount: any = ref([]);
212 const initCheckedList = ref([]);
213 const checkedData = ref([]);
214 const costFileds = ref({}); 189 const costFileds = ref({});
215 const rowCount = {
216 level1: { index: 0, rowspan: [] },
217 level2: { index: 0, rowspan: [] },
218 level3: { index: 0, rowspan: [] },
219 }
220 const convertConfig = { 190 const convertConfig = {
221 intangible: { 191 intangible: {
222 originName: '一、账面原值', 192 originName: '一、账面原值',
...@@ -255,12 +225,24 @@ const convertConfig = { ...@@ -255,12 +225,24 @@ const convertConfig = {
255 } 225 }
256 } 226 }
257 const costTableRef = ref(null); 227 const costTableRef = ref(null);
258 const isInitField = ref(true); 228 const initField: any = ref([
259 const initField = ref([]); //第二步初始化的表头数据 229 { label: "一级指标", field: "name1", level: 1, width: 120, visible: true },
260 const tableField = ref([]); 230 { label: "一级分类", field: "name2", level: 2, width: 120, visible: true },
261 const tableData = ref([]) 231 { label: "二级分类", field: "name3", level: 3, width: 200, visible: true },
232 { label: "三级分类", field: "name4", level: 4, width: 200, visible: false },
233 {
234 label: "累计投入", field: "total", width: 120, align: 'right', visible: false, columClass: 'inverse_cell', getName: (scope) => {
235 return changeNum(scope.row.total, 2, true);
236 }
237 },
238 ]); //第二步初始化的表头数据
239 const tableField: any = ref([]);
240 const tableData: any = ref([])
262 const besure = ref(false); 241 const besure = ref(false);
263 const mergeRowCount = ref({}); 242 const mergeRowCount: any = ref({});
243 const checkedData: any = ref([]);
244 const checkedField: any = ref([]);
245
264 const entryItem = { 246 const entryItem = {
265 year: '', 247 year: '',
266 originName: '', 248 originName: '',
...@@ -289,10 +271,17 @@ const entryItem = { ...@@ -289,10 +271,17 @@ const entryItem = {
289 }; 271 };
290 272
291 const entryTableRef = ref(null); 273 const entryTableRef = ref(null);
292 const entryData = [] 274 const entryData: any = ref([]);
293 const headers = ref([]); 275 const bookHeaders: any = ref([
276 { label: '项目', field: 'title', width: 140, align: 'left' }
277 ]);
294 const transposedData = ref([]); 278 const transposedData = ref([]);
295 279
280 const popoverVisible = ref(false);
281 const popoverTriggerRef = ref(null)
282 const currentRow: any = ref({})
283
284 // 获取产品数据
296 const getProducts = () => { 285 const getProducts = () => {
297 getAssetCatalog().then((res: any) => { 286 getAssetCatalog().then((res: any) => {
298 if (res.code == proxy.$passCode) { 287 if (res.code == proxy.$passCode) {
...@@ -305,147 +294,106 @@ const getProducts = () => { ...@@ -305,147 +294,106 @@ const getProducts = () => {
305 }) 294 })
306 } 295 }
307 296
297 // 获取原始指标数据
308 const getCostData = () => { 298 const getCostData = () => {
309 loading.value = true; 299 loading.value = true;
310 getCostList({ 300 getCostList().then((res: any) => {
311 pageIndex: 1,
312 pageSize: -1,
313 }).then((res: any) => {
314 loading.value = false; 301 loading.value = false;
315 const data = res.data.records || []; 302 const data = res.data || [];
316 let levelList = { level1: [], level2: [], level3: [], level4: [] }; 303 tableData.value = data.map(item => {
317 data.map(item => { 304 item.checked1 = false;
318 item.checked = false; 305 item.checked2 = false;
319 item.children = []; 306 item.checked3 = false;
320 levelList['level' + item.level].push(item); 307 return item;
321 }) 308 })
322 levelList.level1.map(l => allMapList.value.push(l)); 309 getMergeRow();
323 setAllMapList(levelList, allMapList.value, 2);
324 }).catch((res) => { 310 }).catch((res) => {
325 loading.value = false; 311 loading.value = false;
326 }); 312 });
327 }; 313 };
328 314
329 // 整理成本项数据 315 // 单元格多选框选中监听
330 const setAllMapList = (mapObj, arr, level) => { 316 const cellCheckboxChange = (val, scope) => {
331 arr.map(item => { 317 const { rIndex, level } = scope;
332 const code = item.code; 318 let rowData = tableData.value[rIndex];
333 item.children = mapObj['level' + level].filter(a => a.parentId == code); 319 rowData[`checked${level}`] = val;
334 level < 3 && setAllMapList(mapObj, item.children, level + 1); 320 if (val) {
335 }) 321 // 选中上级
336 // console.log(allMapList.value); 322 for (let l = level - 1; l >= 1; l--) {
337 } 323 const tIndex = l;
338 324 const rCode = rowData[`code${l}`];
339 const setStep = async () => { 325 (() => {
340 const info = costFormInfo.value; 326 setChecked(tIndex, rCode, val);
341 await setFormItems(info, 'cost'); 327 })();
342 costForm.value.items[0].disabled = step.value > 0;
343 costForm.value.items[1].visible = step.value == 1;
344 costForm.value.items[2].visible = step.value == 1;
345 costForm.value.items[3].visible = step.value == 1;
346 if (step.value == 1) {
347 setSumRow();
348 } 328 }
349 stepsInfo.value.step = step.value; 329 } else {
350 nextTick(() => { 330 // 取消选中下级
351 const contentDom = document.getElementsByClassName('content_main'); 331 const rowList = tableData.value.filter(t => t[`code${level}`] == rowData[`code${level}`]);
352 if (contentDom[0]) { 332 rowList.forEach(rData => {
353 contentDom[0].scrollTo({ 333 for (let l = level; l <= 4; l++) {
354 behavior: 'smooth', 334 rData[`checked${l}`] = val;
355 top: 0,
356 });
357 } 335 }
358 }) 336 })
359 } 337 }
338 rowData.edit = rowData.code4 ? rowData.checked4 : rowData.checked3; // 数据是否可编辑
339 checkedData.value = tableData.value.filter(t => t.edit);
340 };
360 341
361 const checkboxChange = (val, scope, level) => { 342 // 设置单元格选中
362 const { item, grid, child, opt } = scope; 343 const setChecked = (level, code, check) => {
363 let row = {}; 344 const rowList = tableData.value.filter(t => t[`code${level}`] == code);
364 switch (level) { 345 rowList.forEach(rData => rData[`checked${level}`] = check)
365 case 1: 346 };
366 row = item;
367 break;
368 case 2:
369 row = grid;
370 break;
371 case 3:
372 row = child;
373 break;
374 case 4:
375 row = opt;
376 break;
377 }
378 let list = checkedList.value, tData = checkedData.value, tRow = { ...row, total: '0.00', memo: '', level1: item.name, level2: grid ? grid.name : '', level3: child ? child.name : '', level4: opt ? opt.name : '' };
379 const eIndex = list.findIndex(item => item.guid == row.guid);
380 const tIndex = tData.findIndex(t => t.guid == row.guid);
381 347
382 if (val) { 348 // 点击外部区域关闭处理
383 eIndex === -1 && list.push(row); 349 const handleClickOutside = (event) => {
384 if (tIndex === -1) { 350 const popoverEl = document.querySelector('.tree-item-edit-menu')
385 tRow[`level${row.level}`] = row.name; 351 const triggerEl: any = popoverTriggerRef.value
386 tData.push(tRow); 352
387 } 353 if (!popoverVisible.value || !popoverEl || !triggerEl) return
388 if (level > 1) { 354
389 item.checked = true; 355 const clickedInPopover = popoverEl.contains(event.target)
390 const x = list.findIndex(l => l.guid == item.guid); 356 const clickedOnTrigger = triggerEl === event.target || triggerEl.contains(event.target)
391 x === -1 && list.push(item); 357
392 const i = tData.findIndex(t => t.guid == item.guid); 358 if (!clickedInPopover && !clickedOnTrigger) {
393 i > -1 && tData.splice(i, 1); 359 popoverVisible.value = false
394 if (level > 2) {
395 grid.checked = true;
396 const y = list.findIndex(l => l.guid == grid.guid);
397 y === -1 && list.push(grid);
398 const g = tData.findIndex(t => t.guid == grid.guid);
399 g > -1 && tData.splice(g, 1);
400 if (level > 3) {
401 child.checked = true;
402 const z = list.findIndex(l => l.guid == child.guid);
403 z === -1 && list.push(child);
404 const c = tData.findIndex(t => t.guid == child.guid);
405 c > -1 && tData.splice(c, 1);
406 }
407 }
408 }
409 } else {
410 eIndex > -1 && list.splice(eIndex, 1);
411 tIndex > -1 && tData.splice(tIndex, 1);
412 if (level > 1) {
413 const targetObj = level === 2 ? item : level === 3 ? grid : child;
414 const hasChildItem = targetObj.children.find(r => r.checked);
415 const nIndex = tData.findIndex(t => t.guid == targetObj.guid);
416 if (!hasChildItem && nIndex === -1) {
417 let nRow = { ...targetObj, total: '0.00', memo: '', level1: item.name, level2: grid ? grid.name : '', level3: child ? child.name : '', level4: opt ? opt.name : '' };
418 nRow.level4 = '';
419 if (level === 2) {
420 nRow.level2 = '';
421 nRow.level3 = '';
422 } else if (level === 3) {
423 nRow.level3 = '';
424 }
425 tData.push(nRow);
426 }
427 }
428 level < 4 && setChecked(row.children);
429 } 360 }
430 }; 361 }
431 362
432 const setChecked = (arr) => { 363 // 切换Popover显示/隐藏
433 let list = checkedList.value, tData = checkedData.value; 364 const togglePopover = (row, event) => {
434 arr.forEach(a => { 365 // 如果点击的是当前已激活的触发器,则关闭popover
435 a.checked = false; 366 if (popoverVisible.value && popoverTriggerRef.value === event.currentTarget) {
436 const lIndex = list.findIndex(t => t.guid == a.guid); 367 popoverVisible.value = false
437 lIndex > -1 && list.splice(lIndex, 1); 368 return
438 const tIndex = tData.findIndex(t => t.guid == a.guid);
439 tIndex > -1 && tData.splice(tIndex, 1);
440 if (a.children.length) {
441 setChecked(a.children);
442 } 369 }
443 }); 370
444 }; 371 // 设置当前行和触发器引用
372 currentRow.value = row
373 popoverTriggerRef.value = event.currentTarget
374 const level = Number(row.field.split('name')[1]);
375 showAdd.value = level == 3 ? true : false;
376
377 // 打开popover
378 popoverVisible.value = true
379 }
380
381 // 处理菜单点击
382 const handleMenuClick = (action) => {
383 // 关闭popover
384 popoverVisible.value = false;
385 btnClick({ value: action, row: currentRow.value })
386 }
387
388 // Popover关闭后的处理
389 const handlePopoverClose = () => {
390 currentRow.value = null
391 popoverTriggerRef.value = null
392 }
445 393
446 const selectChange = async (val, row, info) => { 394 const selectChange = async (val, row, info) => {
447 if (row.field == 'entryType') { 395 if (row.field == 'entryType') {
448 await setFormItems(info); 396 await setFormItems(info, 'entry');
449 entryForm.value.items.at(0).default = val; 397 entryForm.value.items.at(0).default = val;
450 entryForm.value.items.at(1).visible = val == 'intangible' ? true : false; 398 entryForm.value.items.at(1).visible = val == 'intangible' ? true : false;
451 entryForm.value.items.at(2).visible = val == 'intangible' ? true : false; 399 entryForm.value.items.at(2).visible = val == 'intangible' ? true : false;
...@@ -485,13 +433,57 @@ const inputEventChange = (val, scope, field) => { ...@@ -485,13 +433,57 @@ const inputEventChange = (val, scope, field) => {
485 row[field] = row[field].toString().replace(/^\D*(\d{0,12}(?:\.\d{0,2})?).*$/g, "$1") 433 row[field] = row[field].toString().replace(/^\D*(\d{0,12}(?:\.\d{0,2})?).*$/g, "$1")
486 } 434 }
487 435
436 // 重命名
437 const setName = (name, level, rIndex) => {
438 let rowData = tableData.value[rIndex];
439 rowData[`name${level}`] = name;
440 }
441
442 // 新增本级
443 const addSameData = (rIndex, name, level, tData) => {
444 let rowData = JSON.parse(JSON.stringify(tData));
445 const lastCode = rowData[`code${level - 1}`];
446 const tCode = tData[`code${level}`].split(lastCode)[1];
447 const codeVal = parseInt(tCode, 10) < 10 ? `0${parseInt(tCode, 10) + 1}` : parseInt(tCode, 10) + 1;
448 for (var r in rowData) {
449 if (r.indexOf('name') == -1 && r.indexOf('code') == -1) rowData[r] = '';
450 }
451 rowData[`name${level}`] = name;
452 rowData[`code${level}`] = `${lastCode}${codeVal}`;
453 rowData[`checked${level}`] = false;
454 delete rowData.name4;
455 delete rowData.code4;
456 tableData.value.splice(rIndex + 1, 0, rowData);
457 getMergeRow();
458 }
459
460 // 新增下级
461 const addLowerData = (rIndex, name, level, len) => {
462 let rowData = JSON.parse(JSON.stringify(tableData.value[rIndex]));
463 const hasLowerItem = rowData[`code${level + 1}`] ? true : false;
464 const lastCode = rowData[`code${level}`];
465 const codeVal = (len + 1) < 10 ? '0' + (len + 1) : len + 1;
466 for (var r in rowData) {
467 if (r.indexOf('name') == -1 && r.indexOf('code') == -1) rowData[r] = '';
468 }
469 rowData[`name${level + 1}`] = name;
470 rowData[`code${level + 1}`] = `${lastCode}${codeVal}`;
471 rowData[`checked${level + 1}`] = false;
472 rowData.edit = false;
473
474 hasLowerItem ? tableData.value.splice(rIndex + 1, 0, rowData) : tableData.value.splice(rIndex, 1, rowData);
475 tableField.value[3].visible = true;
476 getMergeRow();
477 }
478
479 // 按钮点击事件
488 const btnClick = async (btn, bType = null) => { 480 const btnClick = async (btn, bType = null) => {
489 const type = btn.value; 481 const type = btn.value;
490 if (type == 'add-same' || type == 'add-lower' || type == 'edit') { 482 if (type == 'add-same' || type == 'add-lower' || type == 'edit') {
491 const row = btn.row; 483 const row = btn.row;
492 const parent = btn.parent; 484 const { rIndex, level } = row;
493 const inputVal = type == 'edit' ? row.name : ''; 485 const inputVal = type == 'edit' ? row[`name${level}`] : '';
494 let isChange = false, curGridList = type == 'add-lower' ? row.children : parent.children; 486 let isChange = false, rowList = type == 'add-same' ? tableData.value.filter(t => t[`code${level - 1}`] == row[`code${level - 1}`]) : tableData.value.filter(t => t[`code${level}`] == row[`code${level}`]);
495 ElMessageBox.prompt('', '节点名称', { 487 ElMessageBox.prompt('', '节点名称', {
496 confirmButtonText: '确定', 488 confirmButtonText: '确定',
497 cancelButtonText: '取消', 489 cancelButtonText: '取消',
...@@ -512,7 +504,7 @@ const btnClick = async (btn, bType = null) => { ...@@ -512,7 +504,7 @@ const btnClick = async (btn, bType = null) => {
512 if (name.length > 10) { 504 if (name.length > 10) {
513 return '节点名称长度不能超过10个字符' 505 return '节点名称长度不能超过10个字符'
514 } 506 }
515 const isExist = curGridList.find(a => a.name == name); 507 const isExist = rowList.find(a => type == 'add-same' ? a[`name${level}`] == name : a[`name${level + 1}`] == name);
516 if (isExist) { 508 if (isExist) {
517 return '节点名称已存在,请填写其他名称' 509 return '节点名称已存在,请填写其他名称'
518 } 510 }
...@@ -522,7 +514,7 @@ const btnClick = async (btn, bType = null) => { ...@@ -522,7 +514,7 @@ const btnClick = async (btn, bType = null) => {
522 beforeClose: (action, instance, done) => { 514 beforeClose: (action, instance, done) => {
523 if (action === 'confirm') { 515 if (action === 'confirm') {
524 if (!instance.inputValue) { 516 if (!instance.inputValue) {
525 const dom = document.querySelectorAll('.prompt_msg_box .el-message-box__errormsg'); 517 const dom: any = document.querySelectorAll('.prompt_msg_box .el-message-box__errormsg');
526 if (dom[0]) { 518 if (dom[0]) {
527 dom[0].innerHTML = '请填写节点名称'; 519 dom[0].innerHTML = '请填写节点名称';
528 dom[0].style.visibility = 'visible'; 520 dom[0].style.visibility = 'visible';
...@@ -540,61 +532,13 @@ const btnClick = async (btn, bType = null) => { ...@@ -540,61 +532,13 @@ const btnClick = async (btn, bType = null) => {
540 const name = value.trim(); 532 const name = value.trim();
541 if (type == 'edit') { 533 if (type == 'edit') {
542 if (value == inputVal) return 534 if (value == inputVal) return
543 row.name = name; 535 setName(name, level, rIndex);
544 let list = checkedList.value, tData = checkedData.value;
545 tData.forEach((t) => {
546 if (t.guid == row.guid) {
547 t['level' + row.level] = name;
548 t.name = name;
549 }
550 if (t.parentId == row.code) {
551 t['level' + row.level] = name;
552 }
553 })
554 list.forEach((l) => {
555 if (l.guid == row.guid) {
556 l.name = name;
557 }
558 })
559 } else if (type == 'add-same') { 536 } else if (type == 'add-same') {
560 let code = ''; 537 const tData = rowList.at(-1);
561 const sameCode = curGridList.filter(c => c.parentId == row.parentId); 538 const tIndex = tableData.value.findIndex(t => (tData.code4 ? t.code4 === tData.code4 : t.code3 === tData.code3));
562 sameCode.sort((a, b) => a.sortNum - b.sortNum); 539 addSameData(tIndex, name, level, tData);
563 const lastCode = sameCode.at(-1);
564 const codeVal = (sameCode.length + 1) < 10 ? '0' + (sameCode.length + 1) : sameCode.length + 1;
565 code = `${lastCode.parentId}${codeVal}`;
566 curGridList.push({
567 ...JSON.parse(JSON.stringify(row)),
568 code: code,
569 name: name,
570 guid: `new-${code}`,
571 sortNum: lastCode.sortNum + 1,
572 children: [],
573 checked: false
574 })
575 } else if (type == 'add-lower') { 540 } else if (type == 'add-lower') {
576 let code = '', sortNum = 1; 541 addLowerData(rIndex, name, level, rowList.length);
577 if (curGridList.length) {
578 curGridList.sort((a, b) => a.sortNum - b.sortNum);
579 const lastCode = curGridList.at(-1);
580 const codeVal = (curGridList.length + 1) < 10 ? '0' + (curGridList.length + 1) : curGridList.length + 1;
581 code = `${lastCode.parentId}${codeVal}`;
582 sortNum = lastCode.sortNum + 1;
583 } else {
584 code = `${row.code}01`;
585 }
586 curGridList.push({
587 ...JSON.parse(JSON.stringify(row)),
588 code: code,
589 name: name,
590 sortNum: sortNum,
591 level: 4,
592 guid: `new-${code}`,
593 parentId: row.code,
594 children: [],
595 checked: false
596 })
597 if (!showLevel4.value) showLevel4.value = true;
598 } 542 }
599 }).catch(() => { 543 }).catch(() => {
600 ElMessage({ 544 ElMessage({
...@@ -604,8 +548,9 @@ const btnClick = async (btn, bType = null) => { ...@@ -604,8 +548,9 @@ const btnClick = async (btn, bType = null) => {
604 }) 548 })
605 } else if (type == 'remove') { 549 } else if (type == 'remove') {
606 const row = btn.row; 550 const row = btn.row;
607 const hasChild = row.children; 551 const { rIndex, level } = row;
608 const msg = hasChild.length ? '删除该节点会同步删除其子节点,是否确定删除?' : '确定删除该节点吗?'; 552 const rowList = tableData.value.filter(t => t[`code${level}`] == row[`code${level}`]);
553 const msg = level == 3 && rowList[0].code4 ? '删除该节点会同步删除其子节点,是否确定删除?' : '确定删除该节点吗?';
609 ElMessageBox.confirm( 554 ElMessageBox.confirm(
610 msg, 555 msg,
611 '提示', 556 '提示',
...@@ -615,57 +560,26 @@ const btnClick = async (btn, bType = null) => { ...@@ -615,57 +560,26 @@ const btnClick = async (btn, bType = null) => {
615 type: 'warning', 560 type: 'warning',
616 } 561 }
617 ).then(() => { 562 ).then(() => {
618 let curGridList = btn.parent.children; 563 if (level == 3) {
619 const rIndex = curGridList.findIndex(c => c.guid == row.guid); 564 if (rowList.length > 1) {
620 rIndex > -1 && curGridList.splice(rIndex, 1); 565 // 获取所有code3不等于当前code3的节点
621 let list = checkedList.value, tData = checkedData.value; 566 tableData.value = tableData.value.filter(t => t.code3 !== row.code3);
622 let cRow = tData.find(c => c.guid == row.guid); 567 } else {
623 const hasChild = row.children.find(r => r.checked); 568 tableData.value.splice(rIndex, 1);
624 if (!cRow && hasChild) {
625 cRow = tData.find(c => c.guid == hasChild.guid);
626 }
627 const tIndex = tData.findIndex(c => c.guid == row.guid);
628 tIndex > -1 && tData.splice(tIndex, 1);
629 const lIndex = list.findIndex(c => c.guid == row.guid);
630 lIndex > -1 && list.splice(lIndex, 1);
631 const level = row.level;
632 if (level > 1 && cRow) {
633 const targetObj = btn.parent;
634 const hasChildItem = targetObj.children.find(r => r.checked);
635 const nIndex = tData.findIndex(t => t.guid == targetObj.guid);
636 if (!hasChildItem && nIndex === -1) {
637 let nRow = { ...cRow, ...targetObj, total: '0.00', level: targetObj.level };
638 nRow[`level${targetObj.level}`] = targetObj.name;
639 nRow.level4 = '';
640 if (level === 2) {
641 nRow.level2 = '';
642 nRow.level3 = '';
643 } else if (level === 3) {
644 nRow.level3 = '';
645 }
646 tData.push(nRow);
647 }
648 }
649 level < 4 && setChecked(row.children);
650 for (let c = 0; c < curGridList.length; c++) {
651 const item = curGridList[c];
652 if (item.sortNum > row.sortNum) {
653 const code = parseInt(item.code.split(item.parentId)[1], 10) - 1;
654 tData.forEach(t => {
655 if (t.guid == item.guid) {
656 t.code = `${t.parentId}${code < 10 ? '0' + code : code}`;
657 t.guid = t.guid.indexOf('new') > -1 ? `new-${t.code}` : t.guid;
658 t.sortNum = t.sortNum - 1;
659 } 569 }
660 })
661 item.code = `${item.parentId}${code < 10 ? '0' + code : code}`;
662 item.guid = item.guid.indexOf('new') > -1 ? `new-${item.code}` : item.guid;
663 item.sortNum = item.sortNum - 1;
664 } else { 570 } else {
665 continue; 571 const peerList = tableData.value.filter(t => t[`code${level - 1}`] == row[`code${level - 1}`]);
572 if (peerList.length > 1) {
573 tableData.value.splice(rIndex, 1);
574 } else {
575 let rowData = tableData.value[rIndex];
576 delete rowData[`name${level}`];
577 delete rowData[`code${level}`];
666 } 578 }
667 } 579 }
668 setShowLevel4(); 580 const level4 = tableData.value.find(t => t.code4);
581 !level4 && (tableField.value[3].visible = false);
582 getMergeRow();
669 }).catch(() => { 583 }).catch(() => {
670 ElMessage({ 584 ElMessage({
671 type: 'info', 585 type: 'info',
...@@ -678,80 +592,6 @@ const btnClick = async (btn, bType = null) => { ...@@ -678,80 +592,6 @@ const btnClick = async (btn, bType = null) => {
678 costFormInfo.value = { ...costFormInfo.value, ...JSON.parse(JSON.stringify(fInfo)) }; 592 costFormInfo.value = { ...costFormInfo.value, ...JSON.parse(JSON.stringify(fInfo)) };
679 setStep(); 593 setStep();
680 } else if (type == 'next') { 594 } else if (type == 'next') {
681 if (step.value == 0) {
682 const formEl = costFormRef.value.ruleFormRef;
683 const fInfo = costFormRef.value.formInline;
684 costFormInfo.value = { ...costFormInfo.value, ...JSON.parse(JSON.stringify(fInfo)) };
685 if (!formEl) return;
686 const valid = await submitForm(formEl);
687 if (valid) {
688 if (checkedList.value.length == 0) {
689 ElMessage.warning('请勾选成本项');
690 return
691 }
692
693 const nameArr = checkedList.value.map(c => c.name);
694 isInitField.value = initCheckedList.value.toString() != nameArr.toString();
695 const tGuids = tableData.value.map(d => d.guid);
696 if (isInitField.value) {
697 initCheckedList.value = nameArr;
698 let fields = [
699 {
700 label: "累计投入", field: "total", width: 120, align: 'right', getName: (scope) => {
701 return changeNum(scope.row.total, 2, true);
702 }
703 },
704 ]
705 const hasLevel1 = checkedList.value.find(c => c.level == 1);
706 if (hasLevel1) {
707 fields.splice(-1, 0, { label: "指标", field: "level1", width: 120 });
708 }
709 const hasLevel2 = checkedList.value.find(c => c.level == 2);
710 if (hasLevel2) {
711 fields.splice(-1, 0, { label: "一级分类", field: "level2", width: 120 });
712 }
713 const hasLevel3 = checkedList.value.find(c => c.level == 3);
714 if (hasLevel3) {
715 fields.splice(-1, 0, { label: "二级分类", field: "level3", width: 120 });
716 }
717 const hasLevel4 = checkedList.value.find(c => c.level == 4);
718 if (hasLevel4) {
719 fields.splice(-1, 0, { label: "三级分类", field: "level4", width: 120 });
720 }
721 tableField.value.splice(0);
722 tableField.value.push(...fields);
723 initField.value = JSON.parse(JSON.stringify(tableField.value));
724 const datas = JSON.parse(JSON.stringify(checkedData.value));
725 datas.sort((a, b) => a.code.localeCompare(b.code));
726 const dGuid = datas.map(d => d.guid);
727 let tDatas = tableData.value.filter(t => dGuid.indexOf(t.guid) > -1);
728 datas.forEach((td, t) => {
729 if (tGuids.indexOf(td.guid) == -1) {
730 tDatas.splice(t, 0, td)
731 } else {
732 tDatas.map(d => {
733 if (d.guid == td.guid) {
734 d.name = td.name;
735 d.level1 = td.level1;
736 d.level2 = td.level2;
737 d.level3 = td.level3;
738 d.level4 = td.level4;
739 }
740 })
741 }
742 })
743 tableData.value.splice(0);
744 tableData.value = tDatas;
745 if (tGuids.length > 0 && costFormInfo.value.baseDate && costFormInfo.value.investYear) {
746 setTableFields(costFormInfo.value);
747 besure.value = true;
748 }
749 getMergeRow();
750 }
751 step.value++;
752 setStep();
753 }
754 } else if (step.value == 1) {
755 const formEl1 = entryFormRef.value.ruleFormRef; 595 const formEl1 = entryFormRef.value.ruleFormRef;
756 const fInfo1 = entryFormRef.value.formInline; 596 const fInfo1 = entryFormRef.value.formInline;
757 if (!formEl1) return; 597 if (!formEl1) return;
...@@ -773,9 +613,14 @@ const btnClick = async (btn, bType = null) => { ...@@ -773,9 +613,14 @@ const btnClick = async (btn, bType = null) => {
773 } 613 }
774 await setEntryData(fInfo1, fInfo2); 614 await setEntryData(fInfo1, fInfo2);
775 await transposeData(fInfo1.entryType); 615 await transposeData(fInfo1.entryType);
776 step.value++; 616 costForm.value.items.map((cost, c) => {
777 setStep(); 617 if (c == 0) {
618 cost.disabled = true
619 } else {
620 cost.visible = false
778 } 621 }
622 })
623 step.value++;
779 } 624 }
780 } 625 }
781 } else if (type == 'refresh') { 626 } else if (type == 'refresh') {
...@@ -793,11 +638,13 @@ const btnClick = async (btn, bType = null) => { ...@@ -793,11 +638,13 @@ const btnClick = async (btn, bType = null) => {
793 } 638 }
794 }; 639 };
795 640
796 const setShowLevel4 = () => { 641 const setStep = async () => {
797 nextTick(() => { 642 const info = costFormInfo.value;
798 const box4 = document.querySelectorAll('.grid-panel-box.box3 .grid-items'); 643 await setFormItems(info, 'cost');
799 showLevel4.value = box4.length > 0 644 costForm.value.items[0].disabled = step.value > 0;
800 }) 645 costForm.value.items[1].visible = step.value == 0;
646 costForm.value.items[2].visible = step.value == 0;
647 costForm.value.items[3].visible = step.value == 0;
801 } 648 }
802 649
803 const setFormItems = (row: any = null, type) => { 650 const setFormItems = (row: any = null, type) => {
...@@ -839,10 +686,10 @@ const submitForm = (formEl) => { ...@@ -839,10 +686,10 @@ const submitForm = (formEl) => {
839 }; 686 };
840 687
841 const setEntryData = (fInfo1, fInfo2) => { 688 const setEntryData = (fInfo1, fInfo2) => {
842 let datas = [], lastCount = -1, lastYear = {}; 689 let datas: any = [], lastCount = -1, lastYear: any = {};
843 for (var f in costFileds.value) { 690 for (var f in costFileds.value) {
844 lastCount++; 691 lastCount++;
845 let row = { ...entryItem }; 692 let row: any = { ...entryItem };
846 if (f != 'baseNum') { 693 if (f != 'baseNum') {
847 row.year = `${f}年`; 694 row.year = `${f}年`;
848 } else { 695 } else {
...@@ -863,7 +710,7 @@ const setEntryData = (fInfo1, fInfo2) => { ...@@ -863,7 +710,7 @@ const setEntryData = (fInfo1, fInfo2) => {
863 } else { 710 } else {
864 let amortize1 = 0; 711 let amortize1 = 0;
865 for (var d = datas.length - 1; d > (lastCount - fInfo1.shareYears); d--) { 712 for (var d = datas.length - 1; d > (lastCount - fInfo1.shareYears); d--) {
866 const da = datas[d]; 713 const da: any = datas[d];
867 amortize1 += da.amortize2 714 amortize1 += da.amortize2
868 } 715 }
869 row.amortize1 = amortize1; 716 row.amortize1 = amortize1;
...@@ -889,25 +736,29 @@ const setTableFields = (info) => { ...@@ -889,25 +736,29 @@ const setTableFields = (info) => {
889 //结束年 736 //结束年
890 let nowYears = Number(info.baseDate.split('-')[0]); 737 let nowYears = Number(info.baseDate.split('-')[0]);
891 let Years = nowYears - smallYears 738 let Years = nowYears - smallYears
892 let arrYear = [], fields = [], row = { baseNum: '' }; 739 let arrYear: any = [], fields: any = [], row: any = { baseNum: '' };
893 for (let i = 0; i < Years; i++) { 740 for (let i = 0; i < Years; i++) {
894 arrYear.push(smallYears++) 741 arrYear.push(smallYears++)
895 } 742 }
896 arrYear.forEach(item => { 743 arrYear.forEach(item => {
897 const field = `${item}`; 744 const field = `${item}`;
898 fields.push({ label: `${item}年`, field: field, type: 'input', width: 100, columClass: 'edit_cell' },) 745 fields.push({ label: `${item}年`, field: field, type: 'input', width: 100, align: 'right', columClass: 'edit_cell' },)
899 row[field] = ''; 746 row[field] = '';
900 }) 747 })
901 costFileds.value = row; 748 costFileds.value = row;
902 const date = new Date(info.baseDate); 749 const date = new Date(info.baseDate);
903 const currDay = date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long' }); 750 const currDay = date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long' });
904 tableField.value = JSON.parse(JSON.stringify(initField.value)); 751 const modifiedStr = currDay.replace(/(\d+)(\d+)月/, (_, year, month) => {
905 tableField.value.splice(-1, 0, ...fields, { label: currDay, field: 'baseNum', type: 'input', width: 120, columClass: 'edit_cell' }); 752 return `${year}年1-${month}月`;
906 753 });
907 let datas = []; 754 tableField.value.at(-1).visible = true;
755 tableField.value = tableField.value.filter(item => item.field.indexOf('name') > -1 || item.field == 'total');
756 tableField.value.splice(-1, 0, ...fields, { label: modifiedStr, field: 'baseNum', type: 'input', width: 120, align: 'right', columClass: 'edit_cell' });
757 // 设置table数据
758 let datas: any = [];
908 tableData.value.map(t => { 759 tableData.value.map(t => {
909 let tRow = { ...row, ...t }; 760 let tRow = { ...row, ...t };
910 const tKey = Object.keys(t); 761 const tKey: any = Object.keys(t);
911 tKey.forEach(k => { 762 tKey.forEach(k => {
912 if ((k == 'baseNum' || !isNaN(k)) && row[k] === undefined) { 763 if ((k == 'baseNum' || !isNaN(k)) && row[k] === undefined) {
913 delete tRow[k]; 764 delete tRow[k];
...@@ -917,87 +768,107 @@ const setTableFields = (info) => { ...@@ -917,87 +768,107 @@ const setTableFields = (info) => {
917 }) 768 })
918 tableData.value.splice(0); 769 tableData.value.splice(0);
919 tableData.value = datas; 770 tableData.value = datas;
771 getMergeRow();
920 } 772 }
921 773
922 // 设置表格合并下标 774 // 设置表格合并下标
923 const getMergeRow = () => { 775 const getMergeRow = () => {
924 mergeRowCount.value = JSON.parse(JSON.stringify(rowCount)); 776 let list = tableData.value, info = {}
925 let list = tableData.value; 777 // 为每个层级初始化合并信息
926 for (var i = 0; i < list.length; i++) { 778 for (let level = 1; level <= 3; level++) {
927 if (i === 0) { 779 const nameKey = `name${level}`
928 //第一个数据 默认合并1行,开始位置下标默认为0 780 const codeKey = `code${level}`
929 for (var m in mergeRowCount.value) { 781 info[nameKey] = []
930 mergeRowCount.value[m].rowspan.push(1); 782
931 mergeRowCount.value[m].index = 0; 783 // 处理每行数据
932 } 784 list.forEach((row, index) => {
785 if (index === 0) {
786 // 第一行默认合并1行,开始位置0
787 info[nameKey][index] = 1
933 } else { 788 } else {
934 // 根据拥有子级数量进行合并 789 // 当前行与上一行的code和name比较
935 for (var m in mergeRowCount.value) { 790 const sameCode = row[codeKey] === list[index - 1][codeKey]
936 let mergeRow = mergeRowCount.value[m]; 791 const sameName = row[nameKey] === list[index - 1][nameKey]
937 const e = Number(m.split('level')[1]); 792
938 if (list[i][m] && list[i]['level' + (e - 1)] == list[i - 1]['level' + (e - 1)] && list[i][m] === list[i - 1][m]) { 793 if (sameCode && sameName) {
939 mergeRow.rowspan[mergeRow.index] += 1; 794 // 找到当前分组的起始位置
940 mergeRow.rowspan.push(0); 795 let groupStart = index - 1
796 while (groupStart > 0 && info[nameKey][groupStart] === 0) {
797 groupStart--
798 }
799 // 增加合并行数
800 info[nameKey][groupStart]++
801 info[nameKey][index] = 0
941 } else { 802 } else {
942 mergeRow.rowspan.push(1); 803 // 新分组,合并1行
943 mergeRow.index = i; 804 info[nameKey][index] = 1
944 }
945 } 805 }
946 } 806 }
807 })
947 } 808 }
809
810 mergeRowCount.value = info;
948 } 811 }
949 812
950 // 表格行合并 813 // 表格行合并
951 const tableSpanMethod = ({ row, column, rowIndex, columnIndex }) => { 814 const tableSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
952 if (column.property.indexOf('level') > -1 && column.property != 'level4') { 815 const colName = column.property
953 const rowspan = mergeRowCount.value[column.property].rowspan; 816 if (mergeRowCount.value[colName]) {
954 const _row = rowspan[rowIndex]; 817 const rowspan = mergeRowCount.value[colName][rowIndex]
955 const _col = _row == 0 ? 0 : 1;
956 return { 818 return {
957 rowspan: _row, 819 rowspan: rowspan,
958 colspan: _col 820 colspan: rowspan > 0 ? 1 : 0
959 } 821 }
960 } 822 }
823 return { rowspan: 1, colspan: 1 }
961 } 824 }
962 825
963 const isMergedCell = ({ rowIndex, columnIndex }) => { 826 const isMergedCell = ({ row, column, rowIndex, columnIndex }) => {
964 const property = tableField.value[columnIndex].field; 827 const property = column.property;
965 const rowspan = mergeRowCount.value[property]?.rowspan[rowIndex]; 828 // 只处理需要合并的列 (name1/name2/name3)
966 return rowspan > 1 ? 'merged-cell' : ''; 829 if (['name1', 'name2', 'name3'].includes(property)) {
830 // 检查当前单元格是否属于合并范围
831 const rowspan = mergeRowCount.value[property]?.[rowIndex];
832 // 无论是合并的起始单元格(rowspan>1)还是被合并的隐藏单元格(rowspan===0)
833 return rowspan !== 1 ? 'merged-cell' : '';
834 }
835 return '';
967 } 836 }
968 837
969 // 表格合计行 838 // 优化后的合计计算方法
970 const tableSummaryMethod = ({ columns, data }) => { 839 const tableSummaryMethod = ({ columns, data }) => {
971 let sums = []; 840 if (!besure.value) return;
841 let sums: any = [], numericColumns: any = []; // 记录需要计算的数字列
972 columns.forEach((column, index) => { 842 columns.forEach((column, index) => {
973 if (index === 0) { //需要显示'总金额'的列 坐标 :0 843 if (index === 0) {
974 sums[index] = '合计' 844 sums[index] = '合计';
975 return 845 return;
976 } else {
977 if (column.property.indexOf('level') == -1 && column.property != 'memo') {
978 const values = data.map(item => parseFloat(item[column.property] ? item[column.property].replace(/,/g, "") : 0));
979 if (!values.every(value => isNaN(value))) {
980 const sum = values.reduce((prev, curr) => {
981 const value = parseFloat(curr || 0)
982 if (!isNaN(value)) {
983 return prev + curr
984 } else {
985 return prev
986 }
987 }, 0)
988 sums[index] = changeNum(sum, 2, true)
989 if (column.property == 'total') {
990 amount.value = sum
991 } 846 }
992 sumAmount.value[column.property] = sum; 847 // 识别需要计算的数字列
848 if (!column.property.includes('name') && column.property !== 'memo') {
849 numericColumns.push({
850 index,
851 property: column.property
852 });
993 } else { 853 } else {
994 sums[index] = 'N/A' 854 sums[index] = ''; // 非数字列留空
995 }
996 }
997 } 855 }
998 }) 856 });
999 return sums 857 // 计算数字列合计
1000 } 858 numericColumns.forEach(({ index, property }) => {
859 const sum = data.reduce((total, item) => {
860 const value = parseFloat(String(item[property]).replace(/,/g, '')) || 0;
861 return total + value;
862 }, 0);
863 sums[index] = changeNum(sum, 2, true);
864 // 特殊字段处理
865 if (property === 'total') {
866 amount.value = sum;
867 }
868 sumAmount.value[property] = sum;
869 });
870 return sums;
871 };
1001 872
1002 const isExist = (newArr, name) => { 873 const isExist = (newArr, name) => {
1003 for (let i = 0; i < newArr.length; i++) { 874 for (let i = 0; i < newArr.length; i++) {
...@@ -1010,19 +881,23 @@ const isExist = (newArr, name) => { ...@@ -1010,19 +881,23 @@ const isExist = (newArr, name) => {
1010 881
1011 // 表格数据行列转置 882 // 表格数据行列转置
1012 const transposeData = (type) => { 883 const transposeData = (type) => {
884 bookHeaders.value.splice(1);
1013 // 提取原始数据的头部作为转置后数据的列 885 // 提取原始数据的头部作为转置后数据的列
1014 headers.value = entryData.value.map((t) => { 886 const list = entryData.value.map((t) => {
1015 return { 887 return {
1016 label: t.year, 888 label: t.year,
1017 value: t.guid || t.year 889 field: t.guid || t.year,
890 width: 120,
891 align: 'right'
1018 } 892 }
1019 }); 893 });
894 bookHeaders.value.push(...list);
1020 895
1021 /** 896 /**
1022 * 定义映射字段表(最好取全量字段) 897 * 定义映射字段表(最好取全量字段)
1023 * */ 898 * */
1024 const mapObj = convertConfig[type] 899 const mapObj = convertConfig[type]
1025 const newArr = []; 900 const newArr: any = [];
1026 for (const t in mapObj) { 901 for (const t in mapObj) {
1027 for (let i = 0; i < entryData.value.length; i++) { 902 for (let i = 0; i < entryData.value.length; i++) {
1028 const item = entryData.value[i] 903 const item = entryData.value[i]
...@@ -1030,7 +905,7 @@ const transposeData = (type) => { ...@@ -1030,7 +905,7 @@ const transposeData = (type) => {
1030 if (result) { 905 if (result) {
1031 result[item.year] = item[t] 906 result[item.year] = item[t]
1032 } else { 907 } else {
1033 const obj = {} 908 const obj: any = {}
1034 obj.title = mapObj[t] 909 obj.title = mapObj[t]
1035 obj[item.year] = item[t] 910 obj[item.year] = item[t]
1036 newArr.push(obj) 911 newArr.push(obj)
...@@ -1054,248 +929,360 @@ const setLabel = (val) => { ...@@ -1054,248 +929,360 @@ const setLabel = (val) => {
1054 } 929 }
1055 } 930 }
1056 931
1057 const getClass = (i, checked) => { 932 // 下载表格
1058 return checked ? `active${i + 1}` : ''; 933 const s2ab = (s) => {
934 const buf = new ArrayBuffer(s.length);
935 const view = new Uint8Array(buf);
936 for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
937 return buf;
1059 } 938 }
1060 939
1061 const gridPanel = ref(null); 940 // 定义边框样式 - 实线边框
1062 const gridItem = ref(null); 941 const borderStyle = {
1063 const setItemLine = () => { 942 top: { style: 'thin', color: { rgb: '888888' } },
1064 nextTick(() => { 943 bottom: { style: 'thin', color: { rgb: '888888' } },
1065 setTimeout(() => { 944 left: { style: 'thin', color: { rgb: '888888' } },
1066 if (gridItem.value) { 945 right: { style: 'thin', color: { rgb: '888888' } }
1067 const width1 = gridPanel.value[0].offsetWidth; 946 };
1068 const width2 = gridItem.value[0].offsetWidth;
1069 // 设置::before伪元素的宽度
1070 itemWidth.value = `${(width1 - width2) / 2 + (showLevel4.value ? 6 : 8)}px`;
1071 itemLeft.value = `-${(width1 - width2) / 2 + (showLevel4.value ? 6 : 8)}px`;
1072 document.documentElement.style.setProperty('--item-width', itemWidth.value);
1073 document.documentElement.style.setProperty('--item-left', itemLeft.value);
1074 }
1075 }, 100)
1076 })
1077 }
1078 947
1079 const spanHeight = (obj) => { 948 // 字体配置
1080 return `${obj.children.length ? obj.children.length * 48 : 48}px`; 949 const defaultFont = {
1081 } 950 name: '宋体', // 字体名称
951 sz: 14 * 0.75, // 字体大小(磅)
952 color: { rgb: '212121' } // 黑色
953 };
1082 954
1083 const showPanel = (arr, row) => { 955 // 设置行高(单位:磅,1px≈0.75磅)
1084 arr.forEach(a => { 956 const defaultRowHeightInPx = 36; // 默认行高36px
1085 if (a.level < 3) { 957 const headerRowHeightInPx = 32; // 表头行高32px
1086 a.children.length && showPanel(a.children, row) 958
959 // 生成成本设置表
960 const exportDetailsToExcel = async () => {
961 // 准备工作表数据
962 let wsData = [], fields: any = [];
963 tableField.value.map(f => {
964 if (f.visible || f.field.indexOf('name') == -1) {
965 if (f.field == 'name4') {
966 const exist4 = checkedData.value.find(c => c.code4)
967 exist4 && fields.push(f)
1087 } else { 968 } else {
1088 if (a.children.length) { 969 fields.push(f)
1089 row.show4 = true;
1090 return;
1091 } 970 }
1092 } 971 }
1093 }) 972 });
1094 return row.show4 || false;
1095 }
1096 973
1097 // 设置二级分类 item的margin-bottom值 974 // 表头
1098 const setItemStyle = (row, indexObj) => { 975 const headers = fields.map(item => item.label)
1099 if (indexObj) { 976 wsData.push(headers)
1100 const { o } = indexObj; 977
1101 setGroupStyle(); 978 // 判断哪些列是数值类型
1102 setItemsStyle(o); 979 const numField = fields.filter(item => item.field.indexOf('name') == -1);
1103 return { height: 'auto' } 980 const numericFields = numField.map(f => f.field);
981 const isNumericColumn = fields.map(field => numericFields.includes(field.field));
982
983 // 处理数据行
984 checkedData.value.forEach(item => {
985 const row = fields.map(f => {
986 if (f.field.indexOf('name') == -1) {
987 // 分类字段
988 return changeNum(item[f.field], 2, true)
1104 } else { 989 } else {
1105 setHeight(); 990 // 其他字段
1106 return { 'margin-bottom': row.children.length > 1 ? ((row.children.length - 1) * 48) + 8 + 'px' : '0px' } 991 return item[f.field] || ''
1107 } 992 }
1108 }
1109
1110 // 设置三级分类 group高度
1111 const setGroupStyle = () => {
1112 nextTick(() => {
1113 const box3 = document.querySelectorAll('.grid-panel-box.box2 .grid-panel');
1114 const box4 = document.querySelectorAll('.grid-panel-box.box3 .grid-group');
1115 box4.forEach((box, b) => {
1116 box.style.height = box3[b].offsetHeight + 'px';
1117 }) 993 })
994 wsData.push(row)
1118 }) 995 })
1119 }
1120 996
1121 // 设置三级分类 item高度 997 // 添加合计行
1122 const setItemsStyle = (i) => { 998 const totalRow = ['合计', '', '', ''];
1123 nextTick(() => { 999 let totalValues = {}
1124 const box3 = document.querySelectorAll('.grid-panel-box.box2 .grid-items'); 1000
1125 const box4 = document.querySelectorAll('.grid-panel-box.box3 .group-items'); 1001 // 初始化合计值
1126 box4.forEach((box, b) => { 1002 numericFields.forEach(field => {
1127 const bStyle = window.getComputedStyle(box3[b]); 1003 totalValues[field] = 0
1128 const h = parseFloat(bStyle.height) + parseFloat(bStyle.marginBottom); 1004 })
1129 box.style.height = h <= 40 ? (h + 8) + 'px' : h + 'px'; 1005
1006 // 计算合计值
1007 checkedData.value.forEach(item => {
1008 numericFields.forEach(field => {
1009 if (item[field]) {
1010 const num = parseFloat(item[field].replace(/,/g, '')) || 0
1011 totalValues[field] += num
1012 }
1130 }) 1013 })
1131 }) 1014 })
1132 }
1133 1015
1134 // 设置一级分类 panel高度 1016 // 将合计值添加到合计行
1135 const setHeight = () => { 1017 fields.forEach((field, index) => {
1136 nextTick(() => { 1018 if (index < 4) return // 前4列已经处理
1137 const boxArr = document.querySelectorAll('.grid-panel-box'); 1019 if (numericFields.includes(field.field)) {
1138 if (boxArr.length) { 1020 const value = totalValues[field.field]
1139 const box2 = boxArr[1].querySelectorAll('.grid-panel'); 1021 // 格式化数字,保留2位小数
1140 const box3 = boxArr[2].querySelectorAll('.grid-panel'); 1022 totalRow.push(value.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","))
1141 box2.forEach((box, b) => { 1023 } else {
1142 box.style.height = box3[b].offsetHeight + 'px'; 1024 totalRow.push('')
1025 }
1143 }) 1026 })
1027
1028 wsData.push(totalRow)
1029
1030 // 创建工作表
1031 const ws = XLSXS.utils.aoa_to_sheet(wsData)
1032
1033 // 自动调整列宽
1034 const colWidths = fields.map((field, index) => {
1035 const defaultWidth = field.width || 100 // 默认宽度100
1036 const colWidth = 100 * 0.75; // 留一些余地
1037
1038 // 取表头宽度和内容宽度的较大值
1039 return { wpx: colWidth } // 使用像素宽度
1040 })
1041
1042 ws['!cols'] = colWidths
1043
1044 // 转换为磅(Excel使用磅作为单位)
1045 const defaultRowHeight = defaultRowHeightInPx * 0.75;
1046 const headerRowHeight = headerRowHeightInPx * 0.75;
1047
1048 // 初始化行高设置
1049 ws['!rows'] = [];
1050
1051 // 设置表头行高
1052 ws['!rows'][0] = { hpt: headerRowHeight, customHeight: true };
1053
1054 // 设置数据行高
1055 for (let i = 1; i < wsData.length; i++) {
1056 ws['!rows'][i] = { hpt: defaultRowHeight, customHeight: true };
1144 } 1057 }
1058
1059 // 设置合并单元格
1060 const merges = []
1061
1062 // 按code1合并一级指标
1063 const level1Groups = groupBy(checkedData.value, 'code1')
1064 Object.values(level1Groups).forEach(group => {
1065 const startRow = checkedData.value.findIndex(item => item.code1 === group[0].code1) + 1 // +1因为第一行是表头
1066 const endRow = startRow + group.length - 1
1067 merges.push({ s: { r: startRow, c: 0 }, e: { r: endRow, c: 0 } })
1145 }) 1068 })
1146 }
1147 1069
1148 // 下载表格 1070 // 按code2合并一级分类
1149 const s2ab = (s) => { 1071 const level2Groups = groupBy(checkedData.value, 'code2')
1150 const buf = new ArrayBuffer(s.length); 1072 Object.values(level2Groups).forEach(group => {
1151 const view = new Uint8Array(buf); 1073 const startRow = checkedData.value.findIndex(item => item.code2 === group[0].code2) + 1
1152 for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; 1074 const endRow = startRow + group.length - 1
1153 return buf; 1075 merges.push({ s: { r: startRow, c: 1 }, e: { r: endRow, c: 1 } })
1154 } 1076 })
1155 1077
1156 // 获取所有单元格对象 1078 // 按code3合并二级分类
1157 function getAllCells(pRef, type) { 1079 const level3Groups = groupBy(checkedData.value, 'code3')
1158 const allCells = []; 1080 Object.values(level3Groups).forEach(group => {
1159 let columns = [], allColsNum = 0; 1081 const startRow = tableData.value.findIndex(item => item.code3 === group[0].code3) + 1
1160 if (type == 'cost') { 1082 const endRow = startRow + group.length - 1
1161 allColsNum = tableField.value.length; 1083 merges.push({ s: { r: startRow, c: 2 }, e: { r: endRow, c: 2 } })
1162 tableField.value.map((tc, c) => { 1084 })
1163 if (tc.field.indexOf('level') == -1 || tc.field == 'memo') { 1085
1164 columns.push({ ...tc, cIndex: c }) 1086 ws['!merges'] = merges
1087
1088 // 设置单元格样式
1089 const range = XLSXS.utils.decode_range(ws['!ref'])
1090
1091 for (let R = range.s.r; R <= range.e.r; ++R) {
1092 for (let C = range.s.c; C <= range.e.c; ++C) {
1093 const cellAddress = XLSXS.utils.encode_cell({ r: R, c: C })
1094
1095 // 默认样式
1096 const defaultStyle = {
1097 alignment: {
1098 vertical: 'center',
1099 horizontal: 'left'
1100 },
1101 font: defaultFont,
1102 border: borderStyle // 添加边框样式
1165 } 1103 }
1166 }); 1104
1167 } else { 1105 // 表头样式
1168 headers.value.map((th, h) => { 1106 if (R === 0) {
1169 columns.push({ ...th, cIndex: h + 1 }) 1107 // 如果该列是数值列,则表头也右对齐
1170 }); 1108 const isNumeric = isNumericColumn[C];
1109 ws[cellAddress].s = {
1110 ...defaultStyle,
1111 alignment: {
1112 vertical: 'center',
1113 horizontal: isNumeric ? 'right' : 'left'
1114 },
1115 fill: { fgColor: { rgb: 'F2F2F2' } } // 表头背景色(可选)
1116 }
1117 continue
1171 } 1118 }
1172 const colIndex = columns.map(c => c.cIndex);
1173 // 获取所有行
1174 const rows = pRef.$el.querySelectorAll('.el-table__row');
1175 1119
1176 rows.forEach((rowElement, rowIndex) => { 1120 // 合计行样式
1177 const cells = rowElement.querySelectorAll('.el-table__cell'); 1121 if (R === range.e.r) {
1122 ws[cellAddress].s = {
1123 ...defaultStyle,
1124 alignment: {
1125 vertical: 'center',
1126 horizontal: isNumericColumn[C] ? 'right' : 'left'
1127 },
1128 // fill: { fgColor: { rgb: 'FFF1D4' } } // 合计行背景色(可选)
1129 }
1130 continue
1131 }
1178 1132
1179 cells.forEach((cellElement, cellIndex) => { 1133 // 数据行样式
1180 const cell = { 1134 const fieldIndex = C
1181 rowIndex: (rowIndex + 1), 1135 const field = fields[fieldIndex]
1182 cellIndex: cellIndex, 1136
1183 value: cellElement.innerText.trim(), 1137 // 数值列右对齐
1184 style: window.getComputedStyle(cellElement) 1138 if (isNumericColumn[C]) {
1185 }; 1139 ws[cellAddress].s = {
1186 let format = 'General'; 1140 ...defaultStyle,
1187 if (type == 'entry') { 1141 alignment: {
1188 format = colIndex.indexOf(cellIndex) > -1 ? '#,##0.00' : ''; 1142 vertical: 'center',
1189 } else { 1143 horizontal: 'right'
1190 if (rowIndex == rows.length - 1) { 1144 }
1191 format = cell.value != '合计' && cell.value ? '#,##0.00' : ''; 1145 }
1192 } else { 1146 } else {
1193 format = colIndex.indexOf(cellIndex + (allColsNum - cells.length)) > -1 ? '#,##0.00' : ''; 1147 // 其他列左对齐
1194 cell.cellIndex = cellIndex + (allColsNum - cells.length) 1148 ws[cellAddress].s = defaultStyle
1149 }
1150 }
1195 } 1151 }
1152
1153 return ws;
1154 };
1155
1156 // 辅助函数:按属性分组
1157 const groupBy = (array, key) => {
1158 return array.reduce((acc, obj) => {
1159 const groupKey = obj[key]
1160 if (!acc[groupKey]) {
1161 acc[groupKey] = []
1162 }
1163 acc[groupKey].push(obj)
1164 return acc
1165 }, {})
1166 };
1167
1168 // 生成成本设置表
1169 const exportBookToExcel = async () => {
1170 // 准备工作表数据
1171 const wsData = []
1172
1173 // 表头
1174 const headers = bookHeaders.value.map(item => item.label)
1175 wsData.push(headers)
1176
1177 // 处理数据行
1178 transposedData.value.forEach(item => {
1179 const row = bookHeaders.value.map(f => {
1180 if (f.field.indexOf('title') == -1) {
1181 // 分类字段
1182 return changeNum(item[f.field], 2, true)
1183 } else {
1184 // 其他字段
1185 return item[f.field] || ''
1196 } 1186 }
1197 cell.format = format;
1198 allCells.push(cell);
1199 }) 1187 })
1188 wsData.push(row)
1200 }) 1189 })
1201 return allCells;
1202 }
1203 1190
1204 // 设置单元格样式 1191 // 创建工作表
1205 const applyStyles = (ws, pRef, type) => { 1192 const ws = XLSXS.utils.aoa_to_sheet(wsData)
1206 // 获取所有带有样式的单元格
1207 const styledCells = getAllCells(pRef, type)
1208 1193
1209 // 设置单元格样式 1194 // 自动调整列宽
1210 styledCells.forEach((cell) => { 1195 const colWidths = bookHeaders.value.map((field, index) => {
1211 const cellRef = XLSXS.utils.encode_cell({ r: cell.rowIndex, c: cell.cellIndex }); 1196 const defaultWidth = field.width || 100 // 默认宽度100
1212 const style = cell.style; 1197 const colWidth = defaultWidth * 0.75; // 留一些余地
1198
1199 // 取表头宽度和内容宽度的较大值
1200 return { wpx: colWidth } // 使用像素宽度
1201 })
1202
1203 ws['!cols'] = colWidths
1204
1205 // 转换为磅(Excel使用磅作为单位)
1206 const defaultRowHeight = defaultRowHeightInPx * 0.75;
1207 const headerRowHeight = headerRowHeightInPx * 0.75;
1208
1209 // 初始化行高设置
1210 ws['!rows'] = [];
1211
1212 // 设置表头行高
1213 ws['!rows'][0] = { hpt: headerRowHeight, customHeight: true };
1214
1215 // 设置数据行高
1216 for (let i = 1; i < wsData.length; i++) {
1217 ws['!rows'][i] = { hpt: defaultRowHeight, customHeight: true };
1218 }
1213 1219
1214 // 设置单元格样式 1220 // 设置单元格样式
1215 const cellStyle = { 1221 const range = XLSXS.utils.decode_range(ws['!ref'])
1216 alignment: { 1222 // 确定哪些列是数值列(field不包含'title'的列)
1217 horizontal: cell.format ? 'right' : style.textAlign, 1223 const isNumericColumn = bookHeaders.value.map(f => f.field.indexOf('title') === -1);
1218 vertical: 'center', // 垂直居中
1219 },
1220 };
1221 1224
1222 ws[cellRef] = ws[cellRef] || {}; 1225 for (let R = range.s.r; R <= range.e.r; ++R) {
1223 ws[cellRef].s = cellStyle; 1226 for (let C = range.s.c; C <= range.e.c; ++C) {
1224 if (cell.format) { 1227 const cellAddress = XLSXS.utils.encode_cell({ r: R, c: C })
1225 ws[cellRef].z = cell.format; 1228
1226 } else { 1229 // 确保单元格存在(有些空值单元格可能不存在)
1227 ws[cellRef].t = 's'; 1230 if (!ws[cellAddress]) {
1228 ws[cellRef].v = cell.value; 1231 ws[cellAddress] = {};
1229 } 1232 }
1230 }); 1233
1231 // 合并单元格 1234 // 默认样式(垂直居中)
1232 const mergedCells = document.querySelectorAll('.merged-cell'); 1235 const defaultStyle = {
1233 mergedCells.forEach((cell) => {
1234 // 设置合并单元格的样式
1235 const mergedCellStyle = {
1236 alignment: { 1236 alignment: {
1237 vertical: 'center', // 垂直居中 1237 vertical: 'center',
1238 horizontal: 'left'
1238 }, 1239 },
1239 }; 1240 font: defaultFont,
1241 border: borderStyle // 添加边框样式
1242 }
1240 1243
1241 const topLeftCellRef = XLSXS.utils.encode_cell({ r: cell.rowIndex, c: cell.cellIndex }); 1244 // 判断当前列是否为数值列
1242 ws[topLeftCellRef] = ws[topLeftCellRef] || {}; 1245 const isNumeric = isNumericColumn[C];
1243 ws[topLeftCellRef].s = mergedCellStyle; 1246 // 数值列的对齐方式
1244 }); 1247 const alignment = {
1245 } 1248 vertical: 'center',
1249 horizontal: isNumeric ? 'right' : 'left'
1250 };
1251 // 表头样式(第1行)
1252 if (R === 0) {
1253 ws[cellAddress].s = {
1254 ...defaultStyle,
1255 alignment: alignment, // 使用统一对齐方式
1256 fill: { fgColor: { rgb: 'F2F2F2' } } // 表头背景色(可选)
1257 }
1258 continue
1259 }
1246 1260
1247 // 创建一个隐藏的 div 用于测量文本宽度 1261 // 数据行样式
1248 const measureDiv = document.createElement('div'); 1262 const cellValue = ws[cellAddress].v;
1249 measureDiv.style.position = 'absolute';
1250 measureDiv.style.visibility = 'hidden';
1251 measureDiv.style.whiteSpace = 'nowrap';
1252 document.body.appendChild(measureDiv);
1253
1254 // 测量文本宽度的函数
1255 function measureTextWidth(text, fontSize, fontFamily) {
1256 measureDiv.style.fontSize = `${fontSize}px`;
1257 measureDiv.style.fontFamily = fontFamily;
1258 measureDiv.textContent = text;
1259 return measureDiv.offsetWidth;
1260 }
1261 1263
1262 const adjustColumnWidth = (ws) => { 1264 ws[cellAddress].s = {
1263 const range = XLSXS.utils.decode_range(ws['!ref']); 1265 ...defaultStyle,
1264 const defaultFontSize = 11; // 默认字体大小 1266 alignment: alignment // 使用统一对齐方式
1265 const defaultFontFamily = 'Arial'; // 默认字体族 1267 };
1266 1268
1267 for (let C = range.s.c; C <= range.e.c; ++C) { 1269 // 确保数值被正确识别为数字类型
1268 let maxWidth = 0; 1270 if (isNumeric && cellValue !== undefined && cellValue !== "" && !isNaN(cellValue)) {
1269 for (let R = range.s.r; R <= range.e.r; ++R) { 1271 ws[cellAddress].t = 'n'; // 数字类型
1270 const cellRef = XLSXS.utils.encode_cell({ r: R, c: C }); 1272 ws[cellAddress].z = '#,##0.00'; // 数字格式
1271 const cellValue = ws[cellRef]?.v?.toString() || ''; 1273 }
1272 const width = measureTextWidth(cellValue, defaultFontSize, defaultFontFamily);
1273 maxWidth = Math.max(maxWidth, width);
1274 } 1274 }
1275
1276 // 根据测量的宽度计算列宽
1277 const colWidth = maxWidth / defaultFontSize + 2; // 留一些余地
1278 ws['!cols'] = ws['!cols'] || [];
1279 ws['!cols'][C] = { wpx: colWidth * defaultFontSize }; // 使用像素宽度
1280 } 1275 }
1276
1277 return ws;
1281 }; 1278 };
1282 1279
1283 const exportClick = () => { 1280 const exportClick = async () => {
1284 // 获取第一个表格的数据 1281 // 获取第一个表格的数据
1285 const table1 = document.getElementById('cost-table'); 1282 const ws1 = await exportDetailsToExcel();
1286 const ws1 = XLSXS.utils.table_to_sheet(table1);
1287 1283
1288 // 获取第二个表格的数据 1284 // 获取第二个表格的数据
1289 const table2 = document.getElementById('entry-table'); 1285 const ws2 = await exportBookToExcel();
1290 const ws2 = XLSXS.utils.table_to_sheet(table2);
1291
1292 // 调整列宽
1293 adjustColumnWidth(ws1);
1294 adjustColumnWidth(ws2);
1295
1296 // 添加样式
1297 applyStyles(ws1, costTableRef.value, 'cost');
1298 applyStyles(ws2, entryTableRef.value, 'entry');
1299 1286
1300 // 创建工作簿并添加两个工作表 1287 // 创建工作簿并添加两个工作表
1301 const wb = XLSXS.utils.book_new(); 1288 const wb = XLSXS.utils.book_new();
...@@ -1342,220 +1329,129 @@ const setSumRow = () => { ...@@ -1342,220 +1329,129 @@ const setSumRow = () => {
1342 } 1329 }
1343 1330
1344 onActivated(() => { 1331 onActivated(() => {
1332 tableField.value = JSON.parse(JSON.stringify(initField.value));
1345 getProducts(); 1333 getProducts();
1346 getCostData(); 1334 getCostData();
1347 }); 1335 });
1348 1336
1349 1337
1350 onMounted(() => { 1338 onMounted(() => {
1351 setItemLine(); 1339 // 添加/移除全局点击监听
1352 window.addEventListener('resize', setItemLine); 1340 document.addEventListener('click', handleClickOutside)
1353 }); 1341 })
1354 1342
1355 onUpdated(() => { 1343 onBeforeUnmount(() => {
1356 setItemLine(); 1344 document.removeEventListener('click', handleClickOutside)
1357 }); 1345 })
1358 1346
1359 onUnmounted(() => { 1347 onUpdated(() => {
1360 window.removeEventListener('resize', setItemLine); 1348 // setItemLine();
1361 }); 1349 });
1362 1350
1363 watch(showLevel4, (newVal, oldVal) => {
1364 setItemLine();
1365 })
1366 </script> 1351 </script>
1367 1352
1368 <template> 1353 <template>
1369 <div class="container_wrap"> 1354 <div class="container_wrap">
1370 <div class="content_main"> 1355 <div class="content_main">
1371 <div class="top_tool_wrap"> 1356 <div class="operator_panel_wrap" :style="{ 'min-height': 'unset' }" v-loading="loading">
1372 <StepBar :steps-info="stepsInfo" />
1373 </div>
1374 <div class="operator_panel_wrap" :style="{ 'min-height': step == 0 ? 'calc(100% - 112px)' : 'unset' }"
1375 v-loading="loading">
1376 <ContentWrap title="入表类型" description="" :expandSwicth="false" v-show="step == 1">
1377 <Form ref="entryFormRef" :itemList="entryForm.items" formId="dam-base-form" :rules="entryForm.rules"
1378 col="col3" @selectChange="selectChange" />
1379 </ContentWrap>
1380 <div class="v-tip" v-show="step == 0"> 1357 <div class="v-tip" v-show="step == 0">
1381 <div class="tip-icon"></div> 1358 <div class="tip-icon"></div>
1382 <div class="tip-des"> 1359 <div class="tip-des">
1383 本工具提供的入表评估结果仅为初步测算参考,基于用户输入参数生成,不代表最终入表金额。实际入表需遵循《企业数据资源相关会计处理暂行规定》及会计准则要求,经专业审计机构确认后方可生效。 1360 本工具提供的入表评估结果仅为初步测算参考,基于用户输入参数生成,不代表最终入表金额。实际入表需遵循《企业数据资源相关会计处理暂行规定》及会计准则要求,经专业审计机构确认后方可生效。
1384 </div> 1361 </div>
1385 </div> 1362 </div>
1386 <ContentWrap :title="step == 0 ? '设置成本项' : step == 1 ? '填写成本明细' : '文件预览'" 1363 <ContentWrap :title="step == 0 ? '设置成本项' : '文件预览'" description="" :expandSwicth="false"
1387 description="" 1364 :style="step == 1 ? { 'margin-top': '16px' } : {}">
1388 :expandSwicth="false" :style="step == 1 ? { 'margin-top': '16px' } : {}">
1389 <div class="table_tool_wrap"> 1365 <div class="table_tool_wrap">
1390 <Form ref="costFormRef" :itemList="costForm.items" formId="dam-base-form" :rules="costForm.rules" col="col3" 1366 <Form ref="costFormRef" :itemList="costForm.items" formId="dam-base-form" :rules="costForm.rules" col="col3"
1391 @btnClick="btnClick" @selectChange="selectChange" /> 1367 @btnClick="btnClick" @selectChange="selectChange" />
1392 <div class="tool_btn" v-if="step == 2"> 1368 <div class="tool_btn" v-if="step == 1">
1393 <el-button type="primary" @click="exportClick">下载文件</el-button> 1369 <el-button type="primary" @click="exportClick">下载文件</el-button>
1394 <!-- <el-button type="primary" plain @click="senMessage">入表咨询</el-button> --> 1370 <!-- <el-button type="primary" plain @click="senMessage">入表咨询</el-button> -->
1395 </div> 1371 </div>
1396 </div> 1372 </div>
1397 <div class="grid-box-wrap" v-show="step == 0"> 1373 <div class="table_panel_wrap">
1398 <!-- 头部标题 --> 1374 <div class="amount_tool" v-if="besure">
1399 <div class="grid-panel-wrap header" :class="{ col4: showLevel4 }">
1400 <template v-for="(list, l) in gridList" :key="'panel'+l">
1401 <div class="panel-header" v-if="l < 3 || showLevel4">{{ list.name }}</div>
1402 </template>
1403 </div>
1404 <div class="grid-panel-wrap" :class="{ col4: showLevel4 }" v-for="(item, i) in allMapList"
1405 :key="'panel-' + i">
1406 <!-- 一级指标 -->
1407 <div class="grid-panel-box">
1408 <div class="grid-panel" ref="gridPanel">
1409 <div class="grid-items after" ref="gridItem" :class="getClass(i, item.checked)">
1410 <el-checkbox v-model="item.checked" @change="val => checkboxChange(val, { item }, 1)" />
1411 <span class="item-label">{{ item.name }}</span>
1412 <span class="circle"></span>
1413 </div>
1414 </div>
1415 </div>
1416 <!-- 一级分类 -->
1417 <div class="grid-panel-box box1">
1418 <template v-for="(grid, g) in item.children" :key="'grid' + i + '-' + g">
1419 <div class="grid-panel" :class="{ before: g < item.children.length - 1, left8: showLevel4 }">
1420 <div class="grid-items before after" :class="getClass(i, grid.checked)">
1421 <el-checkbox v-model="grid.checked" @change="val => checkboxChange(val, { item, grid }, 2)" />
1422 <span class="item-label">{{ grid.name }}</span>
1423 <span class="circle"></span>
1424 </div>
1425 </div>
1426 </template>
1427 </div>
1428 <!-- 二级分类 -->
1429 <div class="grid-panel-box box2 before" :class="{ after: showLevel4 }">
1430 <template v-for="(grid, g) in item.children" :key="'grid' + i + '-' + g">
1431 <div class="grid-panel height_auto" :class="{ left8: showLevel4 }" v-if="grid.children.length">
1432 <div class="grid-items before"
1433 :class="[getClass(i, child.checked), { after: child.children.length }]"
1434 v-for="(child, c) in grid.children" :key="'item' + i + '-' + g + '-' + c"
1435 :style="setItemStyle(child, null)">
1436 <el-checkbox v-model="child.checked"
1437 @change="val => checkboxChange(val, { item, grid, child }, 3)" />
1438 <span class="item-label">{{ child.name }}</span>
1439 <div class="item-tool">
1440 <el-popover placement="bottom-end" :width="108" trigger="click" popper-class="popover_btns">
1441 <template #reference>
1442 <el-icon>
1443 <MoreFilled />
1444 </el-icon>
1445 </template>
1446 <template #default>
1447 <div class="tool-btns">
1448 <div @click="btnClick({ value: 'add-same', row: child, parent: grid })">新增本级</div>
1449 <div @click="btnClick({ value: 'add-lower', row: child, parent: grid })">新增下级</div>
1450 <div @click="btnClick({ value: 'edit', row: child, parent: grid })">重命名</div>
1451 <div v-if="grid.children.length > 1"
1452 @click="btnClick({ value: 'remove', row: child, parent: grid })">删除</div>
1453 </div>
1454 </template>
1455 </el-popover>
1456 </div>
1457 <span class="circle" :style="{ height: spanHeight(child) }"></span>
1458 </div>
1459 </div>
1460 </template>
1461 </div>
1462 <!-- 三级分类 -->
1463 <div class="grid-panel-box box3" v-if="showLevel4">
1464 <div class="grid-panel no_padding" v-if="showPanel(item.children, item)">
1465 <template v-for="(grid, g) in item.children" :key="'grid' + i + '-' + g">
1466 <div class="grid-group">
1467 <template v-for="(child, c) in grid.children" :key="'item' + i + '-' + g + '-' + c">
1468 <div class="group-items before">
1469 <div class="grid-items before" :class="getClass(i, opt.checked)"
1470 v-for="(opt, o) in child.children" :key="'pis' + i + '-' + g + '-' + c + '-' + o"
1471 :style="setItemStyle(opt, { o })">
1472 <el-checkbox v-model="opt.checked"
1473 @change="val => checkboxChange(val, { item, grid, child, opt }, 4)" />
1474 <span class="item-label">{{ opt.name }}</span>
1475 <div class="item-tool">
1476 <el-popover placement="bottom-end" :width="108" trigger="click"
1477 popper-class="popover_btns">
1478 <template #reference>
1479 <el-icon>
1480 <MoreFilled />
1481 </el-icon>
1482 </template>
1483 <template #default>
1484 <div class="tool-btns">
1485 <div @click="btnClick({ value: 'edit', row: opt, parent: child })">重命名</div>
1486 <div @click="btnClick({ value: 'remove', row: opt, parent: child })">删除</div>
1487 </div>
1488 </template>
1489 </el-popover>
1490 </div>
1491 </div>
1492 </div>
1493 </template>
1494 </div>
1495 </template>
1496 </div>
1497 </div>
1498 </div>
1499 </div>
1500 <div class="table_panel_wrap" v-show="step > 0">
1501 <div class="amount_tool" v-if="step == 1">
1502 <span class="amount_text">累计投入:</span> 1375 <span class="amount_text">累计投入:</span>
1503 <span class="amount_num">{{ changeNum(amount, 2, true) }}</span> 1376 <span class="amount_num">{{ changeNum(amount, 2, true) }}</span>
1504 <span></span> 1377 <span></span>
1505 </div> 1378 </div>
1506 <div class="table_panel"> 1379 <div class="table_panel">
1507 <el-table id="cost-table" v-show="step == 1" ref="costTableRef" :data="tableData" 1380 <el-table id="cost-table" v-show="step == 0" ref="costTableRef" :data="tableData"
1508 :span-method="tableSpanMethod" :summary-method="tableSummaryMethod" show-summary border 1381 :span-method="tableSpanMethod" :show-summary="besure" :summary-method="tableSummaryMethod"
1509 :cell-class-name="isMergedCell"> 1382 :cell-class-name="isMergedCell" stripe border>
1510 <el-table-column v-for="(item, i) in tableField" :label="item.label" :width="item.width" 1383 <template v-for="(item, i) in tableField" :key="'tField' + i">
1511 :min-width="item.minWidth" :fixed="item.fixed" :align="item.align" :sortable="item.sortable ?? false" 1384 <el-table-column v-if="item.visible ?? true" :label="item.label" :width="item.width"
1512 :prop="item.field" :class-name="item.columClass" show-overflow-tooltip> 1385 :min-width="item.minWidth" :fixed="item.fixed" :align="item.align"
1386 :sortable="item.sortable ?? false" :prop="item.field" :class-name="item.columClass"
1387 show-overflow-tooltip>
1513 <template #default="scope"> 1388 <template #default="scope">
1514 <div class="input_cell" v-if="item.type == 'input'"> 1389 <div class="input_cell" v-if="item.type == 'input'">
1515 <el-input v-model.trim="scope.row[item.field]" placeholder="请输入" :maxlength="item.maxlength ?? ''" 1390 <el-input v-model.trim="scope.row[item.field]" :placeholder="scope.row.edit ? '' : '--'"
1516 @change="(val) => inputChange(val, scope, item.field)" 1391 :maxlength="item.maxlength ?? ''" @change="(val) => inputChange(val, scope, item.field)"
1517 @input="(val) => inputEventChange(val, scope, item.field)" clearable></el-input> 1392 @input="(val) => inputEventChange(val, scope, item.field)"
1393 :disabled="scope.row.edit ? false : true" clearable></el-input>
1518 <span>{{ scope.row[item.field] ? changeNum(scope.row[item.field], 2, true) : 1394 <span>{{ scope.row[item.field] ? changeNum(scope.row[item.field], 2, true) :
1519 Number(scope.row[item.field]) == 0 ? '0.00' : '' }}</span> 1395 Number(scope.row[item.field]) == 0 ? '0.00' : '' }}</span>
1520 </div> 1396 </div>
1521 <span v-else> 1397 <div class="cell-tool" v-else>
1522 {{ item.getName ? item.getName(scope) : scope.row[item.field] !== 0 && !scope.row[item.field] ? 1398 <span class="cell_text_group">
1399 <el-checkbox v-if="item.field != 'total' && scope.row[item.field]"
1400 v-model="scope.row['checked' + item.level]"
1401 @change="val => cellCheckboxChange(val, { ...item, ...scope.row, rIndex: scope.$index })" />
1402 <span class="cell_text">
1403 {{ item.getName ? item.getName(scope) : scope.row[item.field] !== 0 &&
1404 !scope.row[item.field]
1405 ?
1523 "--" : scope.row[item.field] }} 1406 "--" : scope.row[item.field] }}
1524 </span> 1407 </span>
1408 </span>
1409 <el-icon v-if="item.field == 'name3' || (item.field == 'name4' && scope.row[item.field])"
1410 class="list-more" color="#666"
1411 @click="e => togglePopover({ ...item, ...scope.row, rIndex: scope.$index }, e)">
1412 <MoreFilled />
1413 </el-icon>
1414 </div>
1525 </template> 1415 </template>
1526 </el-table-column> 1416 </el-table-column>
1527 </el-table>
1528 <el-table id="entry-table" ref="entryTableRef" :data="transposedData" stripe border v-show="step == 2">
1529 <el-table-column prop="title" label="项目" :width="140">
1530 <template v-slot="{ row }">
1531 <span>{{ setLabel(row.title) }}</span>
1532 </template>
1533 </el-table-column>
1534 <el-table-column v-for="(item, index) in headers" :key="index" :prop="item.value" :label="item.label"
1535 :width="120" align="right">
1536 <template v-slot:header>
1537 <span v-html="item.label"></span>
1538 </template> 1417 </template>
1539 <template v-slot="{ row }"> 1418 </el-table>
1540 <span v-if="typeof row[item.value] == 'number'">{{ changeNum(row[item.value], 2, true) }}</span> 1419 <el-table id="entry-table" ref="entryTableRef" :data="transposedData" stripe border v-show="step == 1">
1541 <span v-else></span> 1420 <el-table-column v-for="(item, index) in bookHeaders" :key="index" :prop="item.field"
1421 :label="item.label" :width="item.width" :align="item.align">
1422 <template #default="scope">
1423 <span v-if="item.field == 'title'">{{ setLabel(scope.row[item.field]) }}</span>
1424 <span v-else>{{ changeNum(scope.row[item.field], 2, true) }}</span>
1542 </template> 1425 </template>
1543 </el-table-column> 1426 </el-table-column>
1544 </el-table> 1427 </el-table>
1545 </div> 1428 </div>
1546 </div> 1429 </div>
1547 </ContentWrap> 1430 </ContentWrap>
1431 <ContentWrap title="入表类型" description="" :expandSwicth="false" v-show="step == 0">
1432 <Form ref="entryFormRef" :itemList="entryForm.items" formId="dam-base-form" :rules="entryForm.rules"
1433 col="col3" @selectChange="selectChange" />
1434 </ContentWrap>
1548 </div> 1435 </div>
1549 </div> 1436 </div>
1550 <div class="tool_btns"> 1437 <div class="tool_btns">
1551 <div class="btns"> 1438 <div class="btns">
1552 <el-button @click="btnClick({ value: 'refresh' })">重置</el-button> 1439 <el-button @click="btnClick({ value: 'refresh' })">重置</el-button>
1553 <el-button @click="btnClick({ value: 'prev' })" v-if="step == 1">上一步</el-button> 1440 <el-button type="primary" @click="btnClick({ value: 'next' })" v-if="step == 0">入表</el-button>
1554 <el-button type="primary" @click="btnClick({ value: 'prev' })" v-if="step == 2">上一步</el-button> 1441 <el-button type="primary" @click="btnClick({ value: 'prev' })" v-if="step == 1">上一步</el-button>
1555 <el-button type="primary" @click="btnClick({ value: 'next' })" v-if="step < 2">下一步</el-button> 1442 </div>
1556 </div> 1443 </div>
1557 </div> 1444 </div>
1445 <el-popover :visible="popoverVisible" placement="bottom-start" width="110" trigger="click"
1446 popper-class="tree-item-edit-menu" :virtual-ref="popoverTriggerRef" virtual-triggering :hide-after="0" :offset="8"
1447 @after-leave="handlePopoverClose">
1448 <div class="levitation-ul" @mousedown.stop>
1449 <span class="levitation-li" @click="handleMenuClick('add-same')" v-show="showAdd">新增本级分类</span>
1450 <span class="levitation-li" @click="handleMenuClick('add-lower')" v-show="showAdd">新增下级分类</span>
1451 <span class="levitation-li" @click="handleMenuClick('edit')">重命名</span>
1452 <span class="levitation-li" @click="handleMenuClick('remove')">删除</span>
1558 </div> 1453 </div>
1454 </el-popover>
1559 </template> 1455 </template>
1560 1456
1561 <style lang="scss" scoped> 1457 <style lang="scss" scoped>
...@@ -1848,7 +1744,7 @@ watch(showLevel4, (newVal, oldVal) => { ...@@ -1848,7 +1744,7 @@ watch(showLevel4, (newVal, oldVal) => {
1848 background: #FFFBF2; 1744 background: #FFFBF2;
1849 border: 1px solid rgba(255, 241, 212, 1); 1745 border: 1px solid rgba(255, 241, 212, 1);
1850 border-radius: 4px; 1746 border-radius: 4px;
1851 margin: 5px 0px 8px; 1747 margin: 16px 0px 8px;
1852 padding: 2px 0px; 1748 padding: 2px 0px;
1853 1749
1854 .tip-icon { 1750 .tip-icon {
...@@ -1902,6 +1798,30 @@ watch(showLevel4, (newVal, oldVal) => { ...@@ -1902,6 +1798,30 @@ watch(showLevel4, (newVal, oldVal) => {
1902 1798
1903 :deep(.el-table) { 1799 :deep(.el-table) {
1904 td.el-table__cell { 1800 td.el-table__cell {
1801 .cell-tool {
1802 display: flex;
1803 justify-content: space-between;
1804 align-items: center;
1805
1806 .cell_text_group {
1807 display: flex;
1808
1809 .el-checkbox {
1810 height: 24px;
1811 line-height: 24px;
1812 }
1813
1814 .cell_text {
1815 margin-left: 4px;
1816 line-height: 24px;
1817 }
1818 }
1819
1820 .list-more {
1821 cursor: pointer;
1822 }
1823 }
1824
1905 &.edit_cell { 1825 &.edit_cell {
1906 padding: 4px 0; 1826 padding: 4px 0;
1907 1827
...@@ -1911,6 +1831,11 @@ watch(showLevel4, (newVal, oldVal) => { ...@@ -1911,6 +1831,11 @@ watch(showLevel4, (newVal, oldVal) => {
1911 .input_cell { 1831 .input_cell {
1912 position: relative; 1832 position: relative;
1913 1833
1834 .el-input .el-input__inner {
1835 text-align: right;
1836 height: 24px;
1837 }
1838
1914 >span { 1839 >span {
1915 position: absolute; 1840 position: absolute;
1916 left: 0; 1841 left: 0;
...@@ -1919,6 +1844,13 @@ watch(showLevel4, (newVal, oldVal) => { ...@@ -1919,6 +1844,13 @@ watch(showLevel4, (newVal, oldVal) => {
1919 } 1844 }
1920 } 1845 }
1921 } 1846 }
1847
1848 &.inverse_cell {
1849 .cell_text_group {
1850 display: block;
1851 width: 100%;
1852 }
1853 }
1922 } 1854 }
1923 1855
1924 .el-table__footer-wrapper tr td { 1856 .el-table__footer-wrapper tr td {
...@@ -1926,5 +1858,15 @@ watch(showLevel4, (newVal, oldVal) => { ...@@ -1926,5 +1858,15 @@ watch(showLevel4, (newVal, oldVal) => {
1926 text-align: right; 1858 text-align: right;
1927 } 1859 }
1928 } 1860 }
1861
1862 .el-table__footer-wrapper tfoot td.el-table__cell {
1863 background: #FFF1D4;
1864 }
1865 }
1866
1867 :deep(.el-popper) {
1868 &.tree-item-edit-menu {
1869 transform: translateX(-10px);
1870 }
1929 } 1871 }
1930 </style> 1872 </style>
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!