6637b003 by lxs

入表功调整

1 parent f44693c3
......@@ -8,10 +8,9 @@ export const getAssetCatalog = (params= {}) => request({
})
/** 获取成本项列表 */
export const getCostList = (params) => request({
url: `${import.meta.env.VITE_API_NEW_PORTAL}/tableentry-index-classify/page-list`,
export const getCostList = () => request({
url: `${import.meta.env.VITE_API_NEW_PORTAL}/tableentry-index-classify/list`,
method: 'post',
data: params
})
/** 获取成本项详情 */
......
......@@ -6,13 +6,9 @@
import { ref, inject } from "vue";
import { ElMessage, ElMessageBox, translate } from "element-plus";
import { MoreFilled } from '@element-plus/icons-vue'
import StepBar from "@/components/StepBar/index.vue";
import TableTools from "@/components/Tools/table_tools.vue";
import Table from "@/components/Table/index.vue";
import Form from "@/components/Form/index.vue";
import useUserStore from "@/store/modules/user";
import { changeNum } from "@/utils/common";
import { getAssetCatalog, getCostList, sendEntryMsg } from "@/api/modules/dataEntry";
import { costDelete, getAssetCatalog, getCostList, sendEntryMsg } from "@/api/modules/dataEntry";
import * as XLSXS from 'xlsx-js-style';
import { saveAs } from 'file-saver';
......@@ -20,43 +16,17 @@ const { proxy } = getCurrentInstance() as any;
const userStore = useUserStore();
const userData = JSON.parse(userStore.userData);
const itemWidth = ref('56px');
const itemLeft = ref('-56px');
const reload = inject("reload");
const reload: any = inject("reload");
const loading = ref(false);
const companyName = ref('');
/** 默认显示步骤 */
const step = ref(0);
/** 步骤条配置信息 */
const stepsInfo = ref({
step: step.value,
list: [
{ title: '设置成本项', value: 1 },
{ title: '填写成本项', value: 2 },
{ title: '入表', value: 3 },
]
})
const allMapList = ref([]);
const showLevel4 = ref(false);
const gridList = ref([
{ name1: '获取成本', code1: '0', name2: '外购成本', code2: '01', name3: '外购成本', code3: '011' },
{ name1: '获取成本', code1: '0', name2: '外购成本', code2: '01', name3: '数据加工', code3: '012' },
{ name1: '获取成本', code1: '0', name2: '数据加工', code2: '02', name3: '数据采集', code3: '021' },
{ name1: '获取成本', code1: '0', name2: '数据加工', code2: '02', name3: '数据脱敏', code3: '022' },
{ name1: '获取成本', code1: '0', name2: '数据加工', code2: '02', name3: '数据清洗', code3: '023' },
{ name1: '获取成本', code1: '0', name2: '数据加工', code2: '02', name3: '数据分析', code3: '024' },
{ name1: '存储成本', code1: '1', name2: '数据存储', code2: '11', name3: '数据存储成本', code3: '111' },
{ name1: '管理成本', code1: '2', name2: '数据维护', code2: '21', name3: '数据质检成本', code3: '211' },
{ name1: '管理成本', code1: '2', name2: '数据维护', code2: '21', name3: '数据维护', code3: '212' },
{ name1: '管理成本', code1: '2', name2: '数据维护', code2: '21', name3: '采购维护', code3: '213' },
{ name1: '管理成本', code1: '2', name2: '数据维护', code2: '21', name3: '采购租赁', code3: '214' },
]);
const checkedList = ref([]);
const showAdd = ref(false);
const productList = ref([]);
const costFormRef = ref();
const entryFormRef = ref();
const costFormInfo = ref({});
const costFormInfo: any = ref({});
const costForm = ref({
id: 'step-form-one',
col: '',
......@@ -113,7 +83,7 @@ const costForm = ref({
options: [
{ label: '确认', value: 'besure', type: 'primary', style: { height: '32px' } },
],
visible: false,
visible: true,
style: { width: 'unset', 'align-self': 'end', 'margin-right': '0px' }
},
],
......@@ -142,7 +112,7 @@ const costForm = ref({
]
}
});
const entryForm = ref({
const entryForm: any = ref({
id: 'step-form-two',
col: '',
items: [
......@@ -215,15 +185,8 @@ const entryForm = ref({
});
const amount = ref(0);
const sumAmount = ref([]);
const initCheckedList = ref([]);
const checkedData = ref([]);
const sumAmount: any = ref([]);
const costFileds = ref({});
const rowCount = {
level1: { index: 0, rowspan: [] },
level2: { index: 0, rowspan: [] },
level3: { index: 0, rowspan: [] },
}
const convertConfig = {
intangible: {
originName: '一、账面原值',
......@@ -262,16 +225,23 @@ const convertConfig = {
}
}
const costTableRef = ref(null);
const isInitField = ref(true);
const initField = ref([]); //第二步初始化的表头数据
const tableField = ref([
{ label: "一级指标", field: "name1", width: 120 },
{ label: "一级分类", field: "name2", width: 120 },
{ label: "二级分类", field: "name3", width: 120 }
]);
const tableData = ref([])
const initField: any = ref([
{ label: "一级指标", field: "name1", level: 1, width: 120 },
{ label: "一级分类", field: "name2", level: 2, width: 120 },
{ label: "二级分类", field: "name3", level: 3, width: 200 },
{ label: "三级分类", field: "name4", level: 4, width: 200, visible: false },
{
label: "累计投入", field: "total", width: 120, align: 'right', visible: false, columClass: 'inverse_cell', getName: (scope) => {
return changeNum(scope.row.total, 2, true);
}
},
]); //第二步初始化的表头数据
const tableField: any = ref([]);
const tableData: any = ref([])
const besure = ref(false);
const mergeRowCount = ref({});
const mergeRowCount: any = ref({});
const checkedData: any = ref([]);
const entryItem = {
year: '',
originName: '',
......@@ -300,10 +270,17 @@ const entryItem = {
};
const entryTableRef = ref(null);
const entryData = []
const headers = ref([]);
const entryData: any = ref([]);
const bookHeaders: any = ref([
{ label: '项目', field: 'title', width: 140, align: 'left' }
]);
const transposedData = ref([]);
const popoverVisible = ref(false);
const popoverTriggerRef = ref(null)
const currentRow: any = ref({})
// 获取产品数据
const getProducts = () => {
getAssetCatalog().then((res: any) => {
if (res.code == proxy.$passCode) {
......@@ -316,148 +293,100 @@ const getProducts = () => {
})
}
// 获取原始指标数据
const getCostData = () => {
loading.value = true;
getCostList({
pageIndex: 1,
pageSize: -1,
}).then((res: any) => {
getCostList().then((res: any) => {
loading.value = false;
const data = res.data.records || [];
let levelList = { level1: [], level2: [], level3: [], level4: [] };
data.map(item => {
item.checked = false;
item.children = [];
levelList['level' + item.level].push(item);
})
levelList.level1.map(l => allMapList.value.push(l));
setAllMapList(levelList, allMapList.value, 2);
tableData.value = res.data || [];
getMergeRow();
}).catch((res) => {
loading.value = false;
});
};
// 整理成本项数据
const setAllMapList = (mapObj, arr, level) => {
arr.map(item => {
const code = item.code;
item.children = mapObj['level' + level].filter(a => a.parentId == code);
level < 3 && setAllMapList(mapObj, item.children, level + 1);
})
// console.log(allMapList.value);
}
const setStep = async () => {
const info = costFormInfo.value;
await setFormItems(info, 'cost');
// costForm.value.items[0].disabled = step.value > 0;
// costForm.value.items[1].visible = step.value == 1;
// costForm.value.items[2].visible = step.value == 1;
// costForm.value.items[3].visible = step.value == 1;
if (step.value == 1) {
setSumRow();
}
stepsInfo.value.step = step.value;
nextTick(() => {
const contentDom = document.getElementsByClassName('content_main');
if (contentDom[0]) {
contentDom[0].scrollTo({
behavior: 'smooth',
top: 0,
});
}
})
}
const checkboxChange = (val, scope, level) => {
const { item, grid, child, opt } = scope;
let row = {};
switch (level) {
case 1:
row = item;
break;
case 2:
row = grid;
break;
case 3:
row = child;
break;
case 4:
row = opt;
break;
}
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 : '' };
const eIndex = list.findIndex(item => item.guid == row.guid);
const tIndex = tData.findIndex(t => t.guid == row.guid);
// 单元格多选框选中监听
const cellCheckboxChange = (val, scope) => {
const { rIndex, level } = scope;
let rowData = tableData.value[rIndex];
rowData[`checked${level}`] = val;
if (val) {
eIndex === -1 && list.push(row);
if (tIndex === -1) {
tRow[`level${row.level}`] = row.name;
tData.push(tRow);
}
if (level > 1) {
item.checked = true;
const x = list.findIndex(l => l.guid == item.guid);
x === -1 && list.push(item);
const i = tData.findIndex(t => t.guid == item.guid);
i > -1 && tData.splice(i, 1);
if (level > 2) {
grid.checked = true;
const y = list.findIndex(l => l.guid == grid.guid);
y === -1 && list.push(grid);
const g = tData.findIndex(t => t.guid == grid.guid);
g > -1 && tData.splice(g, 1);
if (level > 3) {
child.checked = true;
const z = list.findIndex(l => l.guid == child.guid);
z === -1 && list.push(child);
const c = tData.findIndex(t => t.guid == child.guid);
c > -1 && tData.splice(c, 1);
}
}
// 选中上级
for (let l = level - 1; l >= 1; l--) {
const tIndex = l;
const rCode = rowData[`code${l}`];
(() => {
setChecked(tIndex, rCode, val);
})();
}
} else {
eIndex > -1 && list.splice(eIndex, 1);
tIndex > -1 && tData.splice(tIndex, 1);
if (level > 1) {
const targetObj = level === 2 ? item : level === 3 ? grid : child;
const hasChildItem = targetObj.children.find(r => r.checked);
const nIndex = tData.findIndex(t => t.guid == targetObj.guid);
if (!hasChildItem && nIndex === -1) {
let nRow = { ...targetObj, total: '0.00', memo: '', level1: item.name, level2: grid ? grid.name : '', level3: child ? child.name : '', level4: opt ? opt.name : '' };
nRow.level4 = '';
if (level === 2) {
nRow.level2 = '';
nRow.level3 = '';
} else if (level === 3) {
nRow.level3 = '';
}
tData.push(nRow);
}
}
level < 4 && setChecked(row.children);
// 取消选中下级
const rowList = tableData.value.filter(t => t[`code${level}`] == rowData[`code${level}`]);
rowList.forEach(rData => {
for (let l = level; l <= 4; l++) {
rData[`checked${l}`] = val;
}
})
}
rowData.edit = rowData.code4 ? rowData.checked4 : rowData.checked3; // 数据是否可编辑
checkedData.value = tableData.value.filter(t => t.edit);
};
const setChecked = (arr) => {
let list = checkedList.value, tData = checkedData.value;
arr.forEach(a => {
a.checked = false;
const lIndex = list.findIndex(t => t.guid == a.guid);
lIndex > -1 && list.splice(lIndex, 1);
const tIndex = tData.findIndex(t => t.guid == a.guid);
tIndex > -1 && tData.splice(tIndex, 1);
if (a.children.length) {
setChecked(a.children);
}
});
// 设置单元格选中
const setChecked = (level, code, check) => {
const rowList = tableData.value.filter(t => t[`code${level}`] == code);
rowList.forEach(rData => rData[`checked${level}`] = check)
};
// 点击外部区域关闭处理
const handleClickOutside = (event) => {
const popoverEl = document.querySelector('.tree-item-edit-menu')
const triggerEl: any = popoverTriggerRef.value
if (!popoverVisible.value || !popoverEl || !triggerEl) return
const clickedInPopover = popoverEl.contains(event.target)
const clickedOnTrigger = triggerEl === event.target || triggerEl.contains(event.target)
if (!clickedInPopover && !clickedOnTrigger) {
popoverVisible.value = false
}
}
// 切换Popover显示/隐藏
const togglePopover = (row, event) => {
// 如果点击的是当前已激活的触发器,则关闭popover
if (popoverVisible.value && popoverTriggerRef.value === event.currentTarget) {
popoverVisible.value = false
return
}
// 设置当前行和触发器引用
currentRow.value = row
popoverTriggerRef.value = event.currentTarget
const level = Number(row.field.split('name')[1]);
showAdd.value = level == 3 ? true : false;
// 打开popover
popoverVisible.value = true
}
// 处理菜单点击
const handleMenuClick = (action) => {
// 关闭popover
popoverVisible.value = false;
btnClick({ value: action, row: currentRow.value })
}
// Popover关闭后的处理
const handlePopoverClose = () => {
currentRow.value = null
popoverTriggerRef.value = null
}
const selectChange = async (val, row, info) => {
if (row.field == 'entryType') {
await setFormItems(info);
await setFormItems(info, 'entry');
entryForm.value.items.at(0).default = val;
entryForm.value.items.at(1).visible = val == 'intangible' ? true : false;
entryForm.value.items.at(2).visible = val == 'intangible' ? true : false;
......@@ -497,13 +426,48 @@ const inputEventChange = (val, scope, field) => {
row[field] = row[field].toString().replace(/^\D*(\d{0,12}(?:\.\d{0,2})?).*$/g, "$1")
}
// 重命名
const setName = (name, level) => {
const rIndex = currentRow.value.$index;
let rowData = tableData.value[rIndex];
rowData[`name${level}`] = name;
}
// 新增本级
const addSameData = (rIndex, name, level, tData) => {
let rowData = JSON.parse(JSON.stringify(tData));
const lastCode = rowData[`code${level - 1}`];
const tCode = tData[`code${level}`].split(lastCode)[1];
const codeVal = parseInt(tCode, 10) < 10 ? `0${parseInt(tCode, 10) + 1}` : parseInt(tCode, 10) + 1;
rowData[`name${level}`] = name;
rowData[`code${level}`] = `${lastCode}${codeVal}`;
delete rowData.name4;
delete rowData.code4;
tableData.value.splice(rIndex + 1, 0, rowData);
getMergeRow();
}
// 新增下级
const addLowerData = (rIndex, name, level, len) => {
let rowData = JSON.parse(JSON.stringify(tableData.value[rIndex]));
const hasLowerItem = rowData[`code${level + 1}`] ? true : false;
const lastCode = rowData[`code${level}`];
const codeVal = (len + 1) < 10 ? '0' + (len + 1) : len + 1;
rowData[`name${level + 1}`] = name;
rowData[`code${level + 1}`] = `${lastCode}${codeVal}`;
hasLowerItem ? tableData.value.splice(rIndex + 1, 0, rowData) : tableData.value.splice(rIndex, 1, rowData);
tableField.value[3].visible = true;
getMergeRow();
}
// 按钮点击事件
const btnClick = async (btn, bType = null) => {
const type = btn.value;
if (type == 'add-same' || type == 'add-lower' || type == 'edit') {
const row = btn.row;
const parent = btn.parent;
const inputVal = type == 'edit' ? row.name : '';
let isChange = false, curGridList = type == 'add-lower' ? row.children : parent.children;
const { rIndex, level } = row;
const inputVal = type == 'edit' ? row[`name${level}`] : '';
let isChange = false, rowList = tableData.value.filter(t => t[`code${level - 1}`] == row[`code${level - 1}`]);
ElMessageBox.prompt('', '节点名称', {
confirmButtonText: '确定',
cancelButtonText: '取消',
......@@ -524,7 +488,7 @@ const btnClick = async (btn, bType = null) => {
if (name.length > 10) {
return '节点名称长度不能超过10个字符'
}
const isExist = curGridList.find(a => a.name == name);
const isExist = rowList.find(a => a[`name${level}`] == name);
if (isExist) {
return '节点名称已存在,请填写其他名称'
}
......@@ -534,7 +498,7 @@ const btnClick = async (btn, bType = null) => {
beforeClose: (action, instance, done) => {
if (action === 'confirm') {
if (!instance.inputValue) {
const dom = document.querySelectorAll('.prompt_msg_box .el-message-box__errormsg');
const dom: any = document.querySelectorAll('.prompt_msg_box .el-message-box__errormsg');
if (dom[0]) {
dom[0].innerHTML = '请填写节点名称';
dom[0].style.visibility = 'visible';
......@@ -552,61 +516,13 @@ const btnClick = async (btn, bType = null) => {
const name = value.trim();
if (type == 'edit') {
if (value == inputVal) return
row.name = name;
let list = checkedList.value, tData = checkedData.value;
tData.forEach((t) => {
if (t.guid == row.guid) {
t['level' + row.level] = name;
t.name = name;
}
if (t.parentId == row.code) {
t['level' + row.level] = name;
}
})
list.forEach((l) => {
if (l.guid == row.guid) {
l.name = name;
}
})
setName(name, level);
} else if (type == 'add-same') {
let code = '';
const sameCode = curGridList.filter(c => c.parentId == row.parentId);
sameCode.sort((a, b) => a.sortNum - b.sortNum);
const lastCode = sameCode.at(-1);
const codeVal = (sameCode.length + 1) < 10 ? '0' + (sameCode.length + 1) : sameCode.length + 1;
code = `${lastCode.parentId}${codeVal}`;
curGridList.push({
...JSON.parse(JSON.stringify(row)),
code: code,
name: name,
guid: `new-${code}`,
sortNum: lastCode.sortNum + 1,
children: [],
checked: false
})
const tData = rowList.at(-1);
const tIndex = tableData.value.findIndex(t => (tData.code4 ? t.code4 === tData.code4 : t.code3 === tData.code3));
addSameData(tIndex, name, level, tData);
} else if (type == 'add-lower') {
let code = '', sortNum = 1;
if (curGridList.length) {
curGridList.sort((a, b) => a.sortNum - b.sortNum);
const lastCode = curGridList.at(-1);
const codeVal = (curGridList.length + 1) < 10 ? '0' + (curGridList.length + 1) : curGridList.length + 1;
code = `${lastCode.parentId}${codeVal}`;
sortNum = lastCode.sortNum + 1;
} else {
code = `${row.code}01`;
}
curGridList.push({
...JSON.parse(JSON.stringify(row)),
code: code,
name: name,
sortNum: sortNum,
level: 4,
guid: `new-${code}`,
parentId: row.code,
children: [],
checked: false
})
if (!showLevel4.value) showLevel4.value = true;
addLowerData(rIndex, name, level, rowList.length);
}
}).catch(() => {
ElMessage({
......@@ -616,8 +532,9 @@ const btnClick = async (btn, bType = null) => {
})
} else if (type == 'remove') {
const row = btn.row;
const hasChild = row.children;
const msg = hasChild.length ? '删除该节点会同步删除其子节点,是否确定删除?' : '确定删除该节点吗?';
const { rIndex, level } = row;
const rowList = tableData.value.filter(t => t[`code${level}`] == row[`code${level}`]);
const msg = level == 3 && rowList[0].code4 ? '删除该节点会同步删除其子节点,是否确定删除?' : '确定删除该节点吗?';
ElMessageBox.confirm(
msg,
'提示',
......@@ -627,57 +544,21 @@ const btnClick = async (btn, bType = null) => {
type: 'warning',
}
).then(() => {
let curGridList = btn.parent.children;
const rIndex = curGridList.findIndex(c => c.guid == row.guid);
rIndex > -1 && curGridList.splice(rIndex, 1);
let list = checkedList.value, tData = checkedData.value;
let cRow = tData.find(c => c.guid == row.guid);
const hasChild = row.children.find(r => r.checked);
if (!cRow && hasChild) {
cRow = tData.find(c => c.guid == hasChild.guid);
}
const tIndex = tData.findIndex(c => c.guid == row.guid);
tIndex > -1 && tData.splice(tIndex, 1);
const lIndex = list.findIndex(c => c.guid == row.guid);
lIndex > -1 && list.splice(lIndex, 1);
const level = row.level;
if (level > 1 && cRow) {
const targetObj = btn.parent;
const hasChildItem = targetObj.children.find(r => r.checked);
const nIndex = tData.findIndex(t => t.guid == targetObj.guid);
if (!hasChildItem && nIndex === -1) {
let nRow = { ...cRow, ...targetObj, total: '0.00', level: targetObj.level };
nRow[`level${targetObj.level}`] = targetObj.name;
nRow.level4 = '';
if (level === 2) {
nRow.level2 = '';
nRow.level3 = '';
} else if (level === 3) {
nRow.level3 = '';
}
tData.push(nRow);
}
}
level < 4 && setChecked(row.children);
for (let c = 0; c < curGridList.length; c++) {
const item = curGridList[c];
if (item.sortNum > row.sortNum) {
const code = parseInt(item.code.split(item.parentId)[1], 10) - 1;
tData.forEach(t => {
if (t.guid == item.guid) {
t.code = `${t.parentId}${code < 10 ? '0' + code : code}`;
t.guid = t.guid.indexOf('new') > -1 ? `new-${t.code}` : t.guid;
t.sortNum = t.sortNum - 1;
}
})
item.code = `${item.parentId}${code < 10 ? '0' + code : code}`;
item.guid = item.guid.indexOf('new') > -1 ? `new-${item.code}` : item.guid;
item.sortNum = item.sortNum - 1;
if (level == 3) {
if (rowList.length > 1) {
// 获取所有code3不等于当前code3的节点
tableData.value = tableData.value.filter(t => t.code3 !== row.code3);
} else {
continue;
tableData.value.splice(rIndex, 1);
}
} else {
let rowData = tableData.value[rIndex];
delete rowData[`name${level}`];
delete rowData[`code${level}`];
const level4 = tableData.value.find(t => t.code4);
!level4 && (tableField.value.at(-2).visible = false);
}
setShowLevel4();
getMergeRow();
}).catch(() => {
ElMessage({
type: 'info',
......@@ -690,80 +571,6 @@ const btnClick = async (btn, bType = null) => {
costFormInfo.value = { ...costFormInfo.value, ...JSON.parse(JSON.stringify(fInfo)) };
setStep();
} else if (type == 'next') {
if (step.value == 0) {
const formEl = costFormRef.value.ruleFormRef;
const fInfo = costFormRef.value.formInline;
costFormInfo.value = { ...costFormInfo.value, ...JSON.parse(JSON.stringify(fInfo)) };
if (!formEl) return;
const valid = await submitForm(formEl);
if (valid) {
if (checkedList.value.length == 0) {
ElMessage.warning('请勾选成本项');
return
}
const nameArr = checkedList.value.map(c => c.name);
isInitField.value = initCheckedList.value.toString() != nameArr.toString();
const tGuids = tableData.value.map(d => d.guid);
if (isInitField.value) {
initCheckedList.value = nameArr;
let fields = [
{
label: "累计投入", field: "total", width: 120, align: 'right', getName: (scope) => {
return changeNum(scope.row.total, 2, true);
}
},
]
const hasLevel1 = checkedList.value.find(c => c.level == 1);
if (hasLevel1) {
fields.splice(-1, 0, { label: "指标", field: "level1", width: 120 });
}
const hasLevel2 = checkedList.value.find(c => c.level == 2);
if (hasLevel2) {
fields.splice(-1, 0, { label: "一级分类", field: "level2", width: 120 });
}
const hasLevel3 = checkedList.value.find(c => c.level == 3);
if (hasLevel3) {
fields.splice(-1, 0, { label: "二级分类", field: "level3", width: 120 });
}
const hasLevel4 = checkedList.value.find(c => c.level == 4);
if (hasLevel4) {
fields.splice(-1, 0, { label: "三级分类", field: "level4", width: 120 });
}
tableField.value.splice(0);
tableField.value.push(...fields);
initField.value = JSON.parse(JSON.stringify(tableField.value));
const datas = JSON.parse(JSON.stringify(checkedData.value));
datas.sort((a, b) => a.code.localeCompare(b.code));
const dGuid = datas.map(d => d.guid);
let tDatas = tableData.value.filter(t => dGuid.indexOf(t.guid) > -1);
datas.forEach((td, t) => {
if (tGuids.indexOf(td.guid) == -1) {
tDatas.splice(t, 0, td)
} else {
tDatas.map(d => {
if (d.guid == td.guid) {
d.name = td.name;
d.level1 = td.level1;
d.level2 = td.level2;
d.level3 = td.level3;
d.level4 = td.level4;
}
})
}
})
tableData.value.splice(0);
tableData.value = tDatas;
if (tGuids.length > 0 && costFormInfo.value.baseDate && costFormInfo.value.investYear) {
setTableFields(costFormInfo.value);
besure.value = true;
}
getMergeRow();
}
step.value++;
setStep();
}
} else if (step.value == 1) {
const formEl1 = entryFormRef.value.ruleFormRef;
const fInfo1 = entryFormRef.value.formInline;
if (!formEl1) return;
......@@ -785,9 +592,14 @@ const btnClick = async (btn, bType = null) => {
}
await setEntryData(fInfo1, fInfo2);
await transposeData(fInfo1.entryType);
step.value++;
setStep();
costForm.value.items.map((cost, c) => {
if (c == 0) {
cost.disabled = true
} else {
cost.visible = false
}
})
step.value++;
}
}
} else if (type == 'refresh') {
......@@ -805,66 +617,13 @@ const btnClick = async (btn, bType = null) => {
}
};
const initTableFileds = () => {
let fields = [
{
label: "累计投入", field: "total", width: 120, align: 'right', getName: (scope) => {
return changeNum(scope.row.total, 2, true);
}
},
]
const hasLevel1 = checkedList.value.find(c => c.level == 1);
if (hasLevel1) {
fields.splice(-1, 0, { label: "指标", field: "level1", width: 120 });
}
const hasLevel2 = checkedList.value.find(c => c.level == 2);
if (hasLevel2) {
fields.splice(-1, 0, { label: "一级分类", field: "level2", width: 120 });
}
const hasLevel3 = checkedList.value.find(c => c.level == 3);
if (hasLevel3) {
fields.splice(-1, 0, { label: "二级分类", field: "level3", width: 120 });
}
const hasLevel4 = checkedList.value.find(c => c.level == 4);
if (hasLevel4) {
fields.splice(-1, 0, { label: "三级分类", field: "level4", width: 120 });
}
tableField.value.splice(0);
tableField.value.push(...fields);
initField.value = JSON.parse(JSON.stringify(tableField.value));
const datas = JSON.parse(JSON.stringify(checkedData.value));
datas.sort((a, b) => a.code.localeCompare(b.code));
const dGuid = datas.map(d => d.guid);
let tDatas = tableData.value.filter(t => dGuid.indexOf(t.guid) > -1);
datas.forEach((td, t) => {
if (tGuids.indexOf(td.guid) == -1) {
tDatas.splice(t, 0, td)
} else {
tDatas.map(d => {
if (d.guid == td.guid) {
d.name = td.name;
d.level1 = td.level1;
d.level2 = td.level2;
d.level3 = td.level3;
d.level4 = td.level4;
}
})
}
})
tableData.value.splice(0);
tableData.value = tDatas;
// if (tGuids.length > 0 && costFormInfo.value.baseDate && costFormInfo.value.investYear) {
// setTableFields(costFormInfo.value);
// besure.value = true;
// }
getMergeRow();
};
const setShowLevel4 = () => {
nextTick(() => {
const box4 = document.querySelectorAll('.grid-panel-box.box3 .grid-items');
showLevel4.value = box4.length > 0
})
const setStep = async () => {
const info = costFormInfo.value;
await setFormItems(info, 'cost');
costForm.value.items[0].disabled = step.value > 0;
costForm.value.items[1].visible = step.value == 0;
costForm.value.items[2].visible = step.value == 0;
costForm.value.items[3].visible = step.value == 0;
}
const setFormItems = (row: any = null, type) => {
......@@ -906,10 +665,10 @@ const submitForm = (formEl) => {
};
const setEntryData = (fInfo1, fInfo2) => {
let datas = [], lastCount = -1, lastYear = {};
let datas: any = [], lastCount = -1, lastYear: any = {};
for (var f in costFileds.value) {
lastCount++;
let row = { ...entryItem };
let row: any = { ...entryItem };
if (f != 'baseNum') {
row.year = `${f}年`;
} else {
......@@ -930,7 +689,7 @@ const setEntryData = (fInfo1, fInfo2) => {
} else {
let amortize1 = 0;
for (var d = datas.length - 1; d > (lastCount - fInfo1.shareYears); d--) {
const da = datas[d];
const da: any = datas[d];
amortize1 += da.amortize2
}
row.amortize1 = amortize1;
......@@ -956,7 +715,7 @@ const setTableFields = (info) => {
//结束年
let nowYears = Number(info.baseDate.split('-')[0]);
let Years = nowYears - smallYears
let arrYear = [], fields = [], row = { baseNum: '' };
let arrYear: any = [], fields: any = [], row: any = { baseNum: '' };
for (let i = 0; i < Years; i++) {
arrYear.push(smallYears++)
}
......@@ -968,13 +727,13 @@ const setTableFields = (info) => {
costFileds.value = row;
const date = new Date(info.baseDate);
const currDay = date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long' });
tableField.value = JSON.parse(JSON.stringify(initField.value));
tableField.value.at(-1).visible = true;
tableField.value.splice(-1, 0, ...fields, { label: currDay, field: 'baseNum', type: 'input', width: 120, columClass: 'edit_cell' });
let datas = [];
// 设置table数据
let datas: any = [];
tableData.value.map(t => {
let tRow = { ...row, ...t };
const tKey = Object.keys(t);
const tKey: any = Object.keys(t);
tKey.forEach(k => {
if ((k == 'baseNum' || !isNaN(k)) && row[k] === undefined) {
delete tRow[k];
......@@ -984,87 +743,107 @@ const setTableFields = (info) => {
})
tableData.value.splice(0);
tableData.value = datas;
getMergeRow();
}
// 设置表格合并下标
const getMergeRow = () => {
mergeRowCount.value = JSON.parse(JSON.stringify(rowCount));
let list = tableData.value;
for (var i = 0; i < list.length; i++) {
if (i === 0) {
//第一个数据 默认合并1行,开始位置下标默认为0
for (var m in mergeRowCount.value) {
mergeRowCount.value[m].rowspan.push(1);
mergeRowCount.value[m].index = 0;
}
let list = tableData.value, info = {}
// 为每个层级初始化合并信息
for (let level = 1; level <= 3; level++) {
const nameKey = `name${level}`
const codeKey = `code${level}`
info[nameKey] = []
// 处理每行数据
list.forEach((row, index) => {
if (index === 0) {
// 第一行默认合并1行,开始位置0
info[nameKey][index] = 1
} else {
// 根据拥有子级数量进行合并
for (var m in mergeRowCount.value) {
let mergeRow = mergeRowCount.value[m];
const e = Number(m.split('level')[1]);
if (list[i][m] && list[i]['level' + (e - 1)] == list[i - 1]['level' + (e - 1)] && list[i][m] === list[i - 1][m]) {
mergeRow.rowspan[mergeRow.index] += 1;
mergeRow.rowspan.push(0);
// 当前行与上一行的code和name比较
const sameCode = row[codeKey] === list[index - 1][codeKey]
const sameName = row[nameKey] === list[index - 1][nameKey]
if (sameCode && sameName) {
// 找到当前分组的起始位置
let groupStart = index - 1
while (groupStart > 0 && info[nameKey][groupStart] === 0) {
groupStart--
}
// 增加合并行数
info[nameKey][groupStart]++
info[nameKey][index] = 0
} else {
mergeRow.rowspan.push(1);
mergeRow.index = i;
}
// 新分组,合并1行
info[nameKey][index] = 1
}
}
})
}
mergeRowCount.value = info;
}
// 表格行合并
const tableSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
if (column.property.indexOf('level') > -1 && column.property != 'level4') {
const rowspan = mergeRowCount.value[column.property].rowspan;
const _row = rowspan[rowIndex];
const _col = _row == 0 ? 0 : 1;
const colName = column.property
if (mergeRowCount.value[colName]) {
const rowspan = mergeRowCount.value[colName][rowIndex]
return {
rowspan: _row,
colspan: _col
rowspan: rowspan,
colspan: rowspan > 0 ? 1 : 0
}
}
return { rowspan: 1, colspan: 1 }
}
const isMergedCell = ({ rowIndex, columnIndex }) => {
const property = tableField.value[columnIndex].field;
const rowspan = mergeRowCount.value[property]?.rowspan[rowIndex];
return rowspan > 1 ? 'merged-cell' : '';
const isMergedCell = ({ row, column, rowIndex, columnIndex }) => {
const property = column.property;
// 只处理需要合并的列 (name1/name2/name3)
if (['name1', 'name2', 'name3'].includes(property)) {
// 检查当前单元格是否属于合并范围
const rowspan = mergeRowCount.value[property]?.[rowIndex];
// 无论是合并的起始单元格(rowspan>1)还是被合并的隐藏单元格(rowspan===0)
return rowspan !== 1 ? 'merged-cell' : '';
}
return '';
}
// 表格合计行
// 优化后的合计计算方法
const tableSummaryMethod = ({ columns, data }) => {
let sums = [];
if (!besure.value) return;
let sums: any = [], numericColumns: any = []; // 记录需要计算的数字列
columns.forEach((column, index) => {
if (index === 0) { //需要显示'总金额'的列 坐标 :0
sums[index] = '合计'
return
} else {
if (column.property.indexOf('level') == -1 && column.property != 'memo') {
const values = data.map(item => parseFloat(item[column.property] ? item[column.property].replace(/,/g, "") : 0));
if (!values.every(value => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
const value = parseFloat(curr || 0)
if (!isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
sums[index] = changeNum(sum, 2, true)
if (column.property == 'total') {
amount.value = sum
if (index === 0) {
sums[index] = '合计';
return;
}
sumAmount.value[column.property] = sum;
// 识别需要计算的数字列
if (!column.property.includes('name') && column.property !== 'memo') {
numericColumns.push({
index,
property: column.property
});
} else {
sums[index] = 'N/A'
}
sums[index] = ''; // 非数字列留空
}
}
})
return sums
}
});
// 计算数字列合计
numericColumns.forEach(({ index, property }) => {
const sum = data.reduce((total, item) => {
const value = parseFloat(String(item[property]).replace(/,/g, '')) || 0;
return total + value;
}, 0);
sums[index] = changeNum(sum, 2, true);
// 特殊字段处理
if (property === 'total') {
amount.value = sum;
}
sumAmount.value[property] = sum;
});
return sums;
};
const isExist = (newArr, name) => {
for (let i = 0; i < newArr.length; i++) {
......@@ -1077,19 +856,23 @@ const isExist = (newArr, name) => {
// 表格数据行列转置
const transposeData = (type) => {
bookHeaders.value.splice(1);
// 提取原始数据的头部作为转置后数据的列
headers.value = entryData.value.map((t) => {
const list = entryData.value.map((t) => {
return {
label: t.year,
value: t.guid || t.year
field: t.guid || t.year,
width: 120,
align: 'right'
}
});
bookHeaders.value.push(...list);
/**
* 定义映射字段表(最好取全量字段)
* */
const mapObj = convertConfig[type]
const newArr = [];
const newArr: any = [];
for (const t in mapObj) {
for (let i = 0; i < entryData.value.length; i++) {
const item = entryData.value[i]
......@@ -1097,7 +880,7 @@ const transposeData = (type) => {
if (result) {
result[item.year] = item[t]
} else {
const obj = {}
const obj: any = {}
obj.title = mapObj[t]
obj[item.year] = item[t]
newArr.push(obj)
......@@ -1121,248 +904,290 @@ const setLabel = (val) => {
}
}
const getClass = (i, checked) => {
return checked ? `active${i + 1}` : '';
// 下载表格
const s2ab = (s) => {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
const gridPanel = ref(null);
const gridItem = ref(null);
const setItemLine = () => {
nextTick(() => {
setTimeout(() => {
if (gridItem.value) {
const width1 = gridPanel.value[0].offsetWidth;
const width2 = gridItem.value[0].offsetWidth;
// 设置::before伪元素的宽度
itemWidth.value = `${(width1 - width2) / 2 + (showLevel4.value ? 6 : 8)}px`;
itemLeft.value = `-${(width1 - width2) / 2 + (showLevel4.value ? 6 : 8)}px`;
document.documentElement.style.setProperty('--item-width', itemWidth.value);
document.documentElement.style.setProperty('--item-left', itemLeft.value);
}
}, 100)
// 生成成本设置表
const exportDetailsToExcel = async () => {
// 准备工作表数据
const wsData = []
// 表头
const headers = tableField.value.map(item => item.label)
wsData.push(headers)
// 处理数据行
checkedData.value.forEach(item => {
const row = tableField.value.map(f => {
if (f.field.indexOf('name') == -1) {
// 分类字段
return changeNum(item[f.field])
} else {
// 其他字段
return item[f.field] || ''
}
})
wsData.push(row)
})
}
const spanHeight = (obj) => {
return `${obj.children.length ? obj.children.length * 48 : 48}px`;
}
// 添加合计行
const totalRow = ['合计', '', '', ''];
const numField = tableField.value.filter(item => item.field.indexOf('name') == -1);
const numericFields = numField.map(f => f.field);
let totalValues = {}
const showPanel = (arr, row) => {
arr.forEach(a => {
if (a.level < 3) {
a.children.length && showPanel(a.children, row)
} else {
if (a.children.length) {
row.show4 = true;
return;
}
// 初始化合计值
numericFields.forEach(field => {
totalValues[field] = 0
})
// 计算合计值
checkedData.value.forEach(item => {
numericFields.forEach(field => {
if (item[field]) {
const num = parseFloat(item[field].replace(/,/g, '')) || 0
totalValues[field] += num
}
})
return row.show4 || false;
}
})
// 设置二级分类 item的margin-bottom值
const setItemStyle = (row, indexObj) => {
if (indexObj) {
const { o } = indexObj;
setGroupStyle();
setItemsStyle(o);
return { height: 'auto' }
// 将合计值添加到合计行
tableField.value.forEach((field, index) => {
if (index < 4) return // 前4列已经处理
if (numericFields.includes(field.field)) {
const value = totalValues[field.field]
// 格式化数字,保留2位小数
totalRow.push(value.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","))
} else {
setHeight();
return { 'margin-bottom': row.children.length > 1 ? ((row.children.length - 1) * 48) + 8 + 'px' : '0px' }
totalRow.push('')
}
}
// 设置三级分类 group高度
const setGroupStyle = () => {
nextTick(() => {
const box3 = document.querySelectorAll('.grid-panel-box.box2 .grid-panel');
const box4 = document.querySelectorAll('.grid-panel-box.box3 .grid-group');
box4.forEach((box, b) => {
box.style.height = box3[b].offsetHeight + 'px';
})
wsData.push(totalRow)
// 创建工作表
const ws = XLSXS.utils.aoa_to_sheet(wsData)
// 自动调整列宽
const colWidths = tableField.value.map((field, index) => {
const defaultWidth = field.width || 100 // 默认宽度100
const colWidth = defaultWidth / 14 + 2; // 留一些余地
// 取表头宽度和内容宽度的较大值
return { wpx: colWidth * 12 } // 使用像素宽度
})
}
// 设置三级分类 item高度
const setItemsStyle = (i) => {
nextTick(() => {
const box3 = document.querySelectorAll('.grid-panel-box.box2 .grid-items');
const box4 = document.querySelectorAll('.grid-panel-box.box3 .group-items');
box4.forEach((box, b) => {
const bStyle = window.getComputedStyle(box3[b]);
const h = parseFloat(bStyle.height) + parseFloat(bStyle.marginBottom);
box.style.height = h <= 40 ? (h + 8) + 'px' : h + 'px';
ws['!cols'] = colWidths
// 设置合并单元格
const merges = []
// 按code1合并一级指标
const level1Groups = groupBy(checkedData.value, 'code1')
Object.values(level1Groups).forEach(group => {
const startRow = checkedData.value.findIndex(item => item.code1 === group[0].code1) + 1 // +1因为第一行是表头
const endRow = startRow + group.length - 1
merges.push({ s: { r: startRow, c: 0 }, e: { r: endRow, c: 0 } })
})
// 按code2合并一级分类
const level2Groups = groupBy(checkedData.value, 'code2')
Object.values(level2Groups).forEach(group => {
const startRow = checkedData.value.findIndex(item => item.code2 === group[0].code2) + 1
const endRow = startRow + group.length - 1
merges.push({ s: { r: startRow, c: 1 }, e: { r: endRow, c: 1 } })
})
}
// 设置一级分类 panel高度
const setHeight = () => {
nextTick(() => {
const boxArr = document.querySelectorAll('.grid-panel-box');
if (boxArr.length) {
const box2 = boxArr[1].querySelectorAll('.grid-panel');
const box3 = boxArr[2].querySelectorAll('.grid-panel');
box2.forEach((box, b) => {
box.style.height = box3[b].offsetHeight + 'px';
// 按code3合并二级分类
const level3Groups = groupBy(checkedData.value, 'code3')
Object.values(level3Groups).forEach(group => {
const startRow = tableData.value.findIndex(item => item.code3 === group[0].code3) + 1
const endRow = startRow + group.length - 1
merges.push({ s: { r: startRow, c: 2 }, e: { r: endRow, c: 2 } })
})
ws['!merges'] = merges
// 设置单元格样式
const range = XLSXS.utils.decode_range(ws['!ref'])
for (let R = range.s.r; R <= range.e.r; ++R) {
for (let C = range.s.c; C <= range.e.c; ++C) {
const cellAddress = XLSXS.utils.encode_cell({ r: R, c: C })
// 默认样式
const defaultStyle = {
alignment: {
vertical: 'center',
horizontal: 'left'
}
}
})
}
// 下载表格
const s2ab = (s) => {
const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}
// 表头样式
if (R === 0) {
ws[cellAddress].s = {
...defaultStyle,
}
continue
}
// 获取所有单元格对象
function getAllCells(pRef, type) {
const allCells = [];
let columns = [], allColsNum = 0;
if (type == 'cost') {
allColsNum = tableField.value.length;
tableField.value.map((tc, c) => {
if (tc.field.indexOf('level') == -1 || tc.field == 'memo') {
columns.push({ ...tc, cIndex: c })
// 合计行样式
if (R === range.e.r) {
ws[cellAddress].s = {
...defaultStyle,
alignment: {
vertical: 'center',
horizontal: C > 3 ? 'right' : 'left'
}
});
} else {
headers.value.map((th, h) => {
columns.push({ ...th, cIndex: h + 1 })
});
}
const colIndex = columns.map(c => c.cIndex);
// 获取所有行
const rows = pRef.$el.querySelectorAll('.el-table__row');
continue
}
rows.forEach((rowElement, rowIndex) => {
const cells = rowElement.querySelectorAll('.el-table__cell');
// 数据行样式
const fieldIndex = C
const field = tableField.value[fieldIndex]
cells.forEach((cellElement, cellIndex) => {
const cell = {
rowIndex: (rowIndex + 1),
cellIndex: cellIndex,
value: cellElement.innerText.trim(),
style: window.getComputedStyle(cellElement)
};
let format = 'General';
if (type == 'entry') {
format = colIndex.indexOf(cellIndex) > -1 ? '#,##0.00' : '';
} else {
if (rowIndex == rows.length - 1) {
format = cell.value != '合计' && cell.value ? '#,##0.00' : '';
// 数值列右对齐
if (numericFields.includes(field.field)) {
ws[cellAddress].s = {
...defaultStyle,
alignment: {
vertical: 'center',
horizontal: 'right'
}
}
} else {
format = colIndex.indexOf(cellIndex + (allColsNum - cells.length)) > -1 ? '#,##0.00' : '';
cell.cellIndex = cellIndex + (allColsNum - cells.length)
// 其他列左对齐
ws[cellAddress].s = defaultStyle
}
}
}
cell.format = format;
allCells.push(cell);
return ws;
};
// 辅助函数:按属性分组
const groupBy = (array, key) => {
return array.reduce((acc, obj) => {
const groupKey = obj[key]
if (!acc[groupKey]) {
acc[groupKey] = []
}
acc[groupKey].push(obj)
return acc
}, {})
};
// 生成成本设置表
const exportBookToExcel = async () => {
// 准备工作表数据
const wsData = []
// 表头
const headers = bookHeaders.value.map(item => item.label)
wsData.push(headers)
// 处理数据行
transposedData.value.forEach(item => {
const row = bookHeaders.value.map(f => {
if (f.field.indexOf('title') == -1) {
// 分类字段
return changeNum(item[f.field])
} else {
// 其他字段
return item[f.field] || ''
}
})
wsData.push(row)
})
return allCells;
}
// 设置单元格样式
const applyStyles = (ws, pRef, type) => {
// 获取所有带有样式的单元格
const styledCells = getAllCells(pRef, type)
// 创建工作表
const ws = XLSXS.utils.aoa_to_sheet(wsData)
// 设置单元格样式
styledCells.forEach((cell) => {
const cellRef = XLSXS.utils.encode_cell({ r: cell.rowIndex, c: cell.cellIndex });
const style = cell.style;
// 自动调整列宽
const colWidths = bookHeaders.value.map((field, index) => {
const defaultWidth = field.width || 100 // 默认宽度100
const colWidth = defaultWidth / 14 + 2; // 留一些余地
// 取表头宽度和内容宽度的较大值
return { wpx: colWidth * 12 } // 使用像素宽度
})
ws['!cols'] = colWidths
// 设置单元格样式
const cellStyle = {
alignment: {
horizontal: cell.format ? 'right' : style.textAlign,
vertical: 'center', // 垂直居中
},
};
const range = XLSXS.utils.decode_range(ws['!ref'])
// 数值字段(除title外的所有字段)
const numericFields = bookHeaders.value.slice(1).map(f => f.field);
ws[cellRef] = ws[cellRef] || {};
ws[cellRef].s = cellStyle;
if (cell.format) {
ws[cellRef].z = cell.format;
} else {
ws[cellRef].t = 's';
ws[cellRef].v = cell.value;
for (let R = range.s.r; R <= range.e.r; ++R) {
for (let C = range.s.c; C <= range.e.c; ++C) {
const cellAddress = XLSXS.utils.encode_cell({ r: R, c: C })
// 确保单元格存在(有些空值单元格可能不存在)
if (!ws[cellAddress]) {
ws[cellAddress] = {};
}
});
// 合并单元格
const mergedCells = document.querySelectorAll('.merged-cell');
mergedCells.forEach((cell) => {
// 设置合并单元格的样式
const mergedCellStyle = {
// 默认样式(垂直居中)
const defaultStyle = {
alignment: {
vertical: 'center', // 垂直居中
},
};
vertical: 'center',
horizontal: 'left'
}
}
const topLeftCellRef = XLSXS.utils.encode_cell({ r: cell.rowIndex, c: cell.cellIndex });
ws[topLeftCellRef] = ws[topLeftCellRef] || {};
ws[topLeftCellRef].s = mergedCellStyle;
});
}
// 表头样式(第1行)
if (R === 0) {
ws[cellAddress].s = {
...defaultStyle,
// font: { bold: true } // 表头加粗
}
continue
}
// 创建一个隐藏的 div 用于测量文本宽度
const measureDiv = document.createElement('div');
measureDiv.style.position = 'absolute';
measureDiv.style.visibility = 'hidden';
measureDiv.style.whiteSpace = 'nowrap';
document.body.appendChild(measureDiv);
// 测量文本宽度的函数
function measureTextWidth(text, fontSize, fontFamily) {
measureDiv.style.fontSize = `${fontSize}px`;
measureDiv.style.fontFamily = fontFamily;
measureDiv.textContent = text;
return measureDiv.offsetWidth;
}
// 获取当前列对应的字段
const field = bookHeaders.value[C];
const adjustColumnWidth = (ws) => {
const range = XLSXS.utils.decode_range(ws['!ref']);
const defaultFontSize = 11; // 默认字体大小
const defaultFontFamily = 'Arial'; // 默认字体族
// 判断是否为数值列(包括最后一行)
const isNumeric = numericFields.includes(field?.field);
const cellValue = ws[cellAddress].v;
for (let C = range.s.c; C <= range.e.c; ++C) {
let maxWidth = 0;
for (let R = range.s.r; R <= range.e.r; ++R) {
const cellRef = XLSXS.utils.encode_cell({ r: R, c: C });
const cellValue = ws[cellRef]?.v?.toString() || '';
const width = measureTextWidth(cellValue, defaultFontSize, defaultFontFamily);
maxWidth = Math.max(maxWidth, width);
// 数值列特殊处理(无论是否最后一行)
if (isNumeric) {
ws[cellAddress].s = {
alignment: {
vertical: 'center',
horizontal: 'right'
}
};
// 根据测量的宽度计算列宽
const colWidth = maxWidth / defaultFontSize + 2; // 留一些余地
ws['!cols'] = ws['!cols'] || [];
ws['!cols'][C] = { wpx: colWidth * defaultFontSize }; // 使用像素宽度
// 确保数值被正确识别为数字类型
if (cellValue !== undefined && cellValue !== "" && !isNaN(cellValue)) {
ws[cellAddress].t = 'n'; // 数字类型
ws[cellAddress].z = '#,##0.00'; // 数字格式
}
} else {
// 文本列保持默认样式
ws[cellAddress].s = defaultStyle;
}
}
}
return ws;
};
const exportClick = () => {
const exportClick = async () => {
// 获取第一个表格的数据
const table1 = document.getElementById('cost-table');
const ws1 = XLSXS.utils.table_to_sheet(table1);
const ws1 = await exportDetailsToExcel();
// 获取第二个表格的数据
const table2 = document.getElementById('entry-table');
const ws2 = XLSXS.utils.table_to_sheet(table2);
// 调整列宽
adjustColumnWidth(ws1);
adjustColumnWidth(ws2);
// 添加样式
applyStyles(ws1, costTableRef.value, 'cost');
applyStyles(ws2, entryTableRef.value, 'entry');
const ws2 = await exportBookToExcel();
// 创建工作簿并添加两个工作表
const wb = XLSXS.utils.book_new();
......@@ -1409,35 +1234,30 @@ const setSumRow = () => {
}
onActivated(() => {
tableField.value = JSON.parse(JSON.stringify(initField.value));
getProducts();
getCostData();
});
onMounted(() => {
setItemLine();
window.addEventListener('resize', setItemLine);
});
// 添加/移除全局点击监听
document.addEventListener('click', handleClickOutside)
})
onUpdated(() => {
setItemLine();
});
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
})
onUnmounted(() => {
window.removeEventListener('resize', setItemLine);
onUpdated(() => {
// setItemLine();
});
watch(showLevel4, (newVal, oldVal) => {
setItemLine();
})
</script>
<template>
<div class="container_wrap">
<div class="content_main">
<!-- <div class="top_tool_wrap">
<StepBar :steps-info="stepsInfo" />
</div> -->
<div class="operator_panel_wrap" :style="{ 'min-height': 'unset' }" v-loading="loading">
<div class="v-tip" v-show="step == 0">
<div class="tip-icon"></div>
......@@ -1455,152 +1275,71 @@ watch(showLevel4, (newVal, oldVal) => {
<!-- <el-button type="primary" plain @click="senMessage">入表咨询</el-button> -->
</div>
</div>
<div class="grid-box-wrap" v-show="step == 3">
<!-- 头部标题 -->
<div class="grid-panel-wrap header" :class="{ col4: showLevel4 }">
<template v-for="(list, l) in gridList" :key="'panel'+l">
<div class="panel-header" v-if="l < 3 || showLevel4">{{ list.name }}</div>
</template>
</div>
<div class="grid-panel-wrap" :class="{ col4: showLevel4 }" v-for="(item, i) in allMapList"
:key="'panel-' + i">
<!-- 一级指标 -->
<div class="grid-panel-box">
<div class="grid-panel" ref="gridPanel">
<div class="grid-items after" ref="gridItem" :class="getClass(i, item.checked)">
<el-checkbox v-model="item.checked" @change="val => checkboxChange(val, { item }, 1)" />
<span class="item-label">{{ item.name }}</span>
<span class="circle"></span>
</div>
</div>
</div>
<!-- 一级分类 -->
<div class="grid-panel-box box1">
<template v-for="(grid, g) in item.children" :key="'grid' + i + '-' + g">
<div class="grid-panel" :class="{ before: g < item.children.length - 1, left8: showLevel4 }">
<div class="grid-items before after" :class="getClass(i, grid.checked)">
<el-checkbox v-model="grid.checked" @change="val => checkboxChange(val, { item, grid }, 2)" />
<span class="item-label">{{ grid.name }}</span>
<span class="circle"></span>
</div>
</div>
</template>
</div>
<!-- 二级分类 -->
<div class="grid-panel-box box2 before" :class="{ after: showLevel4 }">
<template v-for="(grid, g) in item.children" :key="'grid' + i + '-' + g">
<div class="grid-panel height_auto" :class="{ left8: showLevel4 }" v-if="grid.children.length">
<div class="grid-items before"
:class="[getClass(i, child.checked), { after: child.children.length }]"
v-for="(child, c) in grid.children" :key="'item' + i + '-' + g + '-' + c"
:style="setItemStyle(child, null)">
<el-checkbox v-model="child.checked"
@change="val => checkboxChange(val, { item, grid, child }, 3)" />
<span class="item-label">{{ child.name }}</span>
<div class="item-tool">
<el-popover placement="bottom-end" :width="108" trigger="click" popper-class="popover_btns">
<template #reference>
<el-icon>
<MoreFilled />
</el-icon>
</template>
<template #default>
<div class="tool-btns">
<div @click="btnClick({ value: 'add-same', row: child, parent: grid })">新增本级</div>
<div @click="btnClick({ value: 'add-lower', row: child, parent: grid })">新增下级</div>
<div @click="btnClick({ value: 'edit', row: child, parent: grid })">重命名</div>
<div v-if="grid.children.length > 1"
@click="btnClick({ value: 'remove', row: child, parent: grid })">删除</div>
</div>
</template>
</el-popover>
</div>
<span class="circle" :style="{ height: spanHeight(child) }"></span>
</div>
</div>
</template>
</div>
<!-- 三级分类 -->
<div class="grid-panel-box box3" v-if="showLevel4">
<div class="grid-panel no_padding" v-if="showPanel(item.children, item)">
<template v-for="(grid, g) in item.children" :key="'grid' + i + '-' + g">
<div class="grid-group">
<template v-for="(child, c) in grid.children" :key="'item' + i + '-' + g + '-' + c">
<div class="group-items before">
<div class="grid-items before" :class="getClass(i, opt.checked)"
v-for="(opt, o) in child.children" :key="'pis' + i + '-' + g + '-' + c + '-' + o"
:style="setItemStyle(opt, { o })">
<el-checkbox v-model="opt.checked"
@change="val => checkboxChange(val, { item, grid, child, opt }, 4)" />
<span class="item-label">{{ opt.name }}</span>
<div class="item-tool">
<el-popover placement="bottom-end" :width="108" trigger="click"
popper-class="popover_btns">
<template #reference>
<el-icon>
<MoreFilled />
</el-icon>
</template>
<template #default>
<div class="tool-btns">
<div @click="btnClick({ value: 'edit', row: opt, parent: child })">重命名</div>
<div @click="btnClick({ value: 'remove', row: opt, parent: child })">删除</div>
</div>
</template>
</el-popover>
</div>
</div>
</div>
</template>
</div>
</template>
</div>
</div>
</div>
</div>
<div class="table_panel_wrap">
<div class="amount_tool">
<div class="amount_tool" v-if="besure">
<span class="amount_text">累计投入:</span>
<span class="amount_num">{{ changeNum(amount, 2, true) }}</span>
<span></span>
</div>
<div class="table_panel">
<el-table id="cost-table" v-show="step == 0" ref="costTableRef" :data="tableData"
:span-method="tableSpanMethod" :summary-method="tableSummaryMethod" show-summary border
:cell-class-name="isMergedCell">
<el-table-column v-for="(item, i) in tableField" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align" :sortable="item.sortable ?? false"
:prop="item.field" :class-name="item.columClass" show-overflow-tooltip>
:span-method="tableSpanMethod" :show-summary="besure" :summary-method="tableSummaryMethod"
:cell-class-name="isMergedCell" border>
<template v-for="(item, i) in tableField" :key="'tField' + i">
<el-table-column v-if="item.visible ?? true" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align"
:sortable="item.sortable ?? false" :prop="item.field" :class-name="item.columClass"
show-overflow-tooltip>
<template #default="scope">
<div class="input_cell" v-if="item.type == 'input'">
<el-input v-model.trim="scope.row[item.field]" placeholder="请输入" :maxlength="item.maxlength ?? ''"
@change="(val) => inputChange(val, scope, item.field)"
@input="(val) => inputEventChange(val, scope, item.field)" clearable></el-input>
<el-input v-model.trim="scope.row[item.field]" placeholder="请输入"
:maxlength="item.maxlength ?? ''" @change="(val) => inputChange(val, scope, item.field)"
@input="(val) => inputEventChange(val, scope, item.field)"
:disabled="scope.row.edit ? false : true" clearable></el-input>
<span>{{ scope.row[item.field] ? changeNum(scope.row[item.field], 2, true) :
Number(scope.row[item.field]) == 0 ? '0.00' : '' }}</span>
</div>
<span v-else>
{{ item.getName ? item.getName(scope) : scope.row[item.field] !== 0 && !scope.row[item.field] ?
<div class="cell-tool" v-else>
<span class="cell_text_group">
<el-checkbox v-if="item.field != 'total' && scope.row[item.field]"
v-model="scope.row['checked' + item.level]"
@change="val => cellCheckboxChange(val, { ...item, ...scope.row, rIndex: scope.$index })" />
<span class="cell_text">
{{ item.getName ? item.getName(scope) : scope.row[item.field] !== 0 &&
!scope.row[item.field]
?
"--" : scope.row[item.field] }}
</span>
</span>
<el-icon v-if="item.field == 'name3' || (item.field == 'name4' && scope.row[item.field])"
class="list-more" color="#666"
@click="e => togglePopover({ ...item, ...scope.row, rIndex: scope.$index }, e)">
<MoreFilled />
</el-icon>
</div>
</template>
</el-table-column>
</template>
</el-table>
<el-table id="entry-table" ref="entryTableRef" :data="transposedData" stripe border v-show="step == 1">
<el-table-column prop="title" label="项目" :width="140">
<!-- <el-table-column prop="title" label="项目" :width="140">
<template v-slot="{ row }">
<span>{{ setLabel(row.title) }}</span>
</template>
</el-table-column>
<el-table-column v-for="(item, index) in headers" :key="index" :prop="item.value" :label="item.label"
:width="120" align="right">
<template v-slot:header>
</el-table-column> -->
<el-table-column v-for="(item, index) in bookHeaders" :key="index" :prop="item.field" :label="item.label"
:width="item.width" :align="item.align">
<template #default="scope">
<span v-if="item.field == 'title'">{{ setLabel(scope.row[item.field]) }}</span>
<span v-else>{{ changeNum(scope.row[item.field], 2) }}</span>
</template>
<!-- <template v-slot:header>
<span v-html="item.label"></span>
</template>
<template v-slot="{ row }">
<span v-if="typeof row[item.value] == 'number'">{{ changeNum(row[item.value], 2, true) }}</span>
<span v-if="typeof row[item.field] == 'number'">{{ changeNum(row[item.field], 2, true) }}</span>
<span v-else></span>
</template>
</template> -->
</el-table-column>
</el-table>
</div>
......@@ -1615,13 +1354,21 @@ watch(showLevel4, (newVal, oldVal) => {
<div class="tool_btns">
<div class="btns">
<el-button @click="btnClick({ value: 'refresh' })">重置</el-button>
<el-button @click="btnClick({ value: 'next' })" v-if="step == 0">入表</el-button>
<!-- <el-button @click="btnClick({ value: 'prev' })" v-if="step == 1">上一步</el-button>
<el-button type="primary" @click="btnClick({ value: 'prev' })" v-if="step == 2">上一步</el-button>
<el-button type="primary" @click="btnClick({ value: 'next' })" v-if="step < 2">下一步</el-button> -->
<el-button type="primary" @click="btnClick({ value: 'next' })" v-if="step == 0">入表</el-button>
<el-button type="primary" @click="btnClick({ value: 'prev' })" v-if="step == 1">上一步</el-button>
</div>
</div>
</div>
<el-popover :visible="popoverVisible" placement="bottom-start" width="110" trigger="click"
popper-class="tree-item-edit-menu" :virtual-ref="popoverTriggerRef" virtual-triggering :hide-after="0" :offset="8"
@after-leave="handlePopoverClose">
<div class="levitation-ul" @mousedown.stop>
<span class="levitation-li" @click="handleMenuClick('add-same')" v-show="showAdd">新增本级分类</span>
<span class="levitation-li" @click="handleMenuClick('add-lower')" v-show="showAdd">新增下级分类</span>
<span class="levitation-li" @click="handleMenuClick('edit')">重命名</span>
<span class="levitation-li" @click="handleMenuClick('remove')">删除</span>
</div>
</el-popover>
</template>
<style lang="scss" scoped>
......@@ -1968,6 +1715,25 @@ watch(showLevel4, (newVal, oldVal) => {
:deep(.el-table) {
td.el-table__cell {
.cell-tool {
display: flex;
justify-content: space-between;
align-items: center;
.cell_text_group {
display: flex;
.cell_text {
margin-left: 4px;
line-height: 32px;
}
}
.list-more {
cursor: pointer;
}
}
&.edit_cell {
padding: 4px 0;
......@@ -1985,6 +1751,13 @@ watch(showLevel4, (newVal, oldVal) => {
}
}
}
&.inverse_cell {
.cell_text_group {
display: block;
width: 100%;
}
}
}
.el-table__footer-wrapper tr td {
......@@ -1993,4 +1766,10 @@ watch(showLevel4, (newVal, oldVal) => {
}
}
}
:deep(.el-popper) {
&.tree-item-edit-menu {
transform: translateX(-10px);
}
}
</style>
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!