明树Git Lab

Commit 1e428006 authored by zhanghan's avatar zhanghan

成本管理开发

parent efe4b95d
Pipeline #107145 passed with stage
in 18 seconds
...@@ -37,19 +37,67 @@ ...@@ -37,19 +37,67 @@
<span v-else>&nbsp;</span> <span v-else>&nbsp;</span>
</template> </template>
</el-table-column> </el-table-column>
<!-- 动态时间列:改用【处理后的有效时间列表】,保留原条件渲染逻辑 -->
<!-- 🌟 新增:多级时间列表头渲染逻辑 -->
<!-- 有时间列分组 → 渲染多级表头 -->
<template v-if="hasTimeHeaderGroup">
<el-table-column
v-for="group in timeColumnGroups"
:key="`time-group-${group.key}`"
:label="group.label"
align="right"
>
<!-- 二级表头:时间列 -->
<el-table-column
v-for="timeItem in group.children"
:key="`time-col-${timeItem.prop}`"
:label="timeItem.label"
min-width="140"
align="center"
>
<template #default="{ row }">
<!-- 1. 文本行:多行文本输入框 -->
<el-input
v-if="row.isTextRow"
v-model="row[timeItem.prop]"
type="textarea"
:rows="2"
:disabled="isPreview"
placeholder="请输入说明"
style="width: 100%"
@input="() => handleTextInput(row)"
/>
<!-- 2. 数字行:金额输入框 -->
<el-input-number
v-else
v-model="row[timeItem.prop]"
:min="0"
:precision="2"
controls-position="right"
:disabled="isPreview"
placeholder="请输入金额"
style="width: 100%"
@change="() => handleFinancialChange(row)"
/>
</template>
</el-table-column>
</el-table-column>
</template>
<!-- 无时间列分组 → 保持原有单级表头逻辑 -->
<el-table-column <el-table-column
v-else
v-for="time in validDynamicTimeList" v-for="time in validDynamicTimeList"
:key="`time-col-${time}`" :key="`time-col-${time.prop || time}`"
:label="time" :label="time.label || time"
min-width="140" min-width="140"
align="center" align="center"
> >
<template #default="{ row }"> <template #default="{ row }">
<!-- 1. 文本行:多行文本输入框,支持字符串 → 绑定内部数据 --> <!-- 1. 文本行:多行文本输入框 -->
<el-input <el-input
v-if="row.isTextRow" v-if="row.isTextRow"
v-model="row[time]" v-model="row[time.prop || time]"
type="textarea" type="textarea"
:rows="2" :rows="2"
:disabled="isPreview" :disabled="isPreview"
...@@ -57,10 +105,10 @@ ...@@ -57,10 +105,10 @@
style="width: 100%" style="width: 100%"
@input="() => handleTextInput(row)" @input="() => handleTextInput(row)"
/> />
<!-- 2. 数字行:金额输入框(0+、2位小数)→ 绑定内部数据 --> <!-- 2. 数字行:金额输入框 -->
<el-input-number <el-input-number
v-else v-else
v-model="row[time]" v-model="row[time.prop || time]"
:min="0" :min="0"
:precision="2" :precision="2"
controls-position="right" controls-position="right"
...@@ -82,8 +130,7 @@ ...@@ -82,8 +130,7 @@
<script setup> <script setup>
import { ref, computed, watch, nextTick, defineProps, defineEmits } from "vue"; import { ref, computed, watch, nextTick, defineProps, defineEmits } from "vue";
// 1. 重构Props/Emit:对齐参考组件,改用defineProps+defineEmits,隔离v-model循环 // 1. 重构Props/Emit:兼容多级表头配置
// 替代原defineModel,避免对象式v-model的循环更新问题,和参考组件统一
const props = defineProps({ const props = defineProps({
// 核心配置对象:包含indicatorList/dynamicTimeList/tableData // 核心配置对象:包含indicatorList/dynamicTimeList/tableData
modelValue: { modelValue: {
...@@ -101,30 +148,95 @@ const props = defineProps({ ...@@ -101,30 +148,95 @@ const props = defineProps({
}, },
}); });
// 定义发射事件:双向绑定+业务事件,和参考组件一致 // 定义发射事件:双向绑定+业务事件
const emit = defineEmits([ const emit = defineEmits([
"update:modelValue", // v-model双向绑定更新 "update:modelValue", // v-model双向绑定更新
"handleTableChange", // 自定义业务变化事件(可让父组件监听) "handleTableChange", // 自定义业务变化事件
]); ]);
// 2. 核心:创建组件内部响应式数据,完全隔离外部Props → 参考组件核心设计 // 2. 核心:创建组件内部响应式数据
// 所有数据操作先改内部,再统一发射,避免直接修改Props导致的循环更新
const tableDataRef = ref([]); const tableDataRef = ref([]);
// 3. 处理动态时间列表:对齐参考组件,做【去重/去空/trim/兜底】→ 关键健壮性改造 // 3. 🌟 新增:处理动态时间列表(兼容单级/多级配置)
// 解决原组件直接用props中可能的无效时间列(空值/空格/重复)问题 // 支持两种格式:
const validDynamicTimeList = computed(() => { // - 单级:["一月", "二月"]
// - 多级:[{ label: "一月", prop: "一月", headerGroup: "一季度" }, ...]
const validDynamicTimeConfig = computed(() => {
const rawTimeList = props.modelValue.dynamicTimeList || []; const rawTimeList = props.modelValue.dynamicTimeList || [];
if (!Array.isArray(rawTimeList) || rawTimeList.length === 0) { if (!Array.isArray(rawTimeList) || rawTimeList.length === 0) {
// 兜底默认值:保证表格至少有列渲染,不会空白 // 兜底默认值
return ["2025", "2026", "2027"]; return [
{ label: "2025", prop: "2025", headerGroup: "" },
{ label: "2026", prop: "2026", headerGroup: "" },
{ label: "2027", prop: "2027", headerGroup: "" },
];
} }
return [...new Set(rawTimeList)]
.map((time) => (typeof time === "string" ? time.trim() : "")) // 去除隐形空白 // 标准化配置:统一转为对象格式(兼容字符串数组)
.filter((time) => !!time); // 过滤空值 return rawTimeList
.map((item) => {
if (typeof item === "string") {
return {
label: item.trim(),
prop: item.trim(),
headerGroup: "", // 无分组
};
}
return {
label: item.label?.trim() || "",
prop: item.prop?.trim() || item.label?.trim() || "",
headerGroup: item.headerGroup?.trim() || "",
};
})
.filter((item) => !!item.prop); // 过滤空值
});
// 🌟 新增:判断是否有时间列分组(多级表头开关)
const hasTimeHeaderGroup = computed(() => {
return validDynamicTimeConfig.value.some((item) => !!item.headerGroup);
});
// 🌟 新增:时间列分组计算(按headerGroup自动分组,保持原始顺序)
const timeColumnGroups = computed(() => {
const timeConfig = validDynamicTimeConfig.value;
if (!timeConfig.length) return [];
// 构建分组映射
const groupMap = {};
timeConfig.forEach((item) => {
// 无headerGroup用唯一标识,避免分组混乱
const groupKey = item.headerGroup || `single_${item.prop}`;
if (!groupMap[groupKey]) {
groupMap[groupKey] = {
label: item.headerGroup || item.label,
key: groupKey,
children: [],
};
}
groupMap[groupKey].children.push(item);
});
// 保持原始顺序,避免分组后列乱序
const result = [];
const addedKeys = new Set();
timeConfig.forEach((item) => {
const groupKey = item.headerGroup || `single_${item.prop}`;
if (!addedKeys.has(groupKey)) {
result.push(groupMap[groupKey]);
addedKeys.add(groupKey);
}
});
return result;
}); });
// 4. 配置合法性校验:基于【处理后的有效时间列表】重构,更严谨 // 兼容原有逻辑:提取纯时间prop列表(用于数据初始化/计算合计)
const validDynamicTimeList = computed(() => {
// 兼容多级/单级配置,只提取prop
return validDynamicTimeConfig.value.map((item) => item.prop);
});
// 4. 配置合法性校验:基于标准化后的时间配置
const validConfig = computed(() => { const validConfig = computed(() => {
const { indicatorList } = props.modelValue; const { indicatorList } = props.modelValue;
return ( return (
...@@ -134,45 +246,44 @@ const validConfig = computed(() => { ...@@ -134,45 +246,44 @@ const validConfig = computed(() => {
); );
}); });
// 5. 工具方法:初始化行的时间字段 → 对齐参考组件initRowTimeField // 5. 工具方法:初始化行的时间字段(适配新的时间配置)
// 针对【文本行/数字行】做差异化初始化,避免类型混乱
const initRowTimeField = (row) => { const initRowTimeField = (row) => {
if (!row || typeof row !== "object") return; if (!row || typeof row !== "object") return;
validDynamicTimeList.value.forEach((time) => { validDynamicTimeList.value.forEach((timeProp) => {
if (row.isTextRow) { if (row.isTextRow) {
// 文本行:无值则置空字符串,保留原有字符串 // 文本行:无值则置空字符串
row[time] = row[timeProp] =
row[time] === undefined || row[time] === null ? "" : String(row[time]); row[timeProp] === undefined || row[timeProp] === null
? ""
: String(row[timeProp]);
} else { } else {
// 数字行:无值/非数字则置0,确保数字类型 // 数字行:无值/非数字则置0
row[time] = row[timeProp] =
row[time] === undefined || row[timeProp] === undefined ||
row[time] === null || row[timeProp] === null ||
isNaN(Number(row[time])) isNaN(Number(row[timeProp]))
? 0 ? 0
: Number(row[time]); : Number(row[timeProp]);
} }
}); });
}; };
// 6. 工具方法:计算单行合计 → 对齐参考组件calcRowTotal // 6. 工具方法:计算单行合计(逻辑不变,适配prop列表)
// 仅数字行生效,自动过滤文本行/非数字,解决浮点精度问题
const calculateRowTotal = (row) => { const calculateRowTotal = (row) => {
if (!row || typeof row !== "object" || row.noTotal || row.isTextRow) return 0; if (!row || typeof row !== "object" || row.noTotal || row.isTextRow) return 0;
const total = validDynamicTimeList.value.reduce((sum, time) => { const total = validDynamicTimeList.value.reduce((sum, timeProp) => {
return sum + (Number(row[time]) || 0); return sum + (Number(row[timeProp]) || 0);
}, 0); }, 0);
return Number(total.toFixed(2)); // 保留2位小数,避免0.1+0.2=0.30000000000000004 return Number(total.toFixed(2)); // 解决浮点精度问题
}; };
// 7. 核心工具方法:深拷贝+数据处理 → 对齐参考组件handleTableData // 7. 核心工具方法:深拷贝+数据处理(逻辑不变)
// 强制保留所有原有字段,仅【初始化时间字段/计算合计】,避免字段丢失
const handleTableData = (sourceIndicatorList, sourceTableData) => { const handleTableData = (sourceIndicatorList, sourceTableData) => {
if (!validConfig.value) return []; if (!validConfig.value) return [];
const newData = []; const newData = [];
sourceIndicatorList.forEach((item, index) => { sourceIndicatorList.forEach((item, index) => {
const { name, isTextRow = false, noTotal = false } = item; const { name, isTextRow = false, noTotal = false } = item;
// 深拷贝源数据行:保留所有原有字段(serialNumber/level/indicatorName等) // 深拷贝源数据行
const originRow = const originRow =
sourceTableData.find((row) => row.indicatorName === name) || {}; sourceTableData.find((row) => row.indicatorName === name) || {};
const rowData = JSON.parse( const rowData = JSON.parse(
...@@ -183,56 +294,50 @@ const handleTableData = (sourceIndicatorList, sourceTableData) => { ...@@ -183,56 +294,50 @@ const handleTableData = (sourceIndicatorList, sourceTableData) => {
isTextRow, isTextRow,
noTotal, noTotal,
total: 0, total: 0,
...originRow, // 源数据行覆盖默认值,保留原有配置 ...originRow,
}), }),
); );
// 初始化时间字段:差异化处理文本/数字行 // 初始化时间字段
initRowTimeField(rowData); initRowTimeField(rowData);
// 计算合计:仅数字行/需要合计的行生效 // 计算合计
rowData.total = calculateRowTotal(rowData); rowData.total = calculateRowTotal(rowData);
newData.push(rowData); newData.push(rowData);
}); });
return newData; return newData;
}; };
// 8. 工具方法:数据变化后统一发射 → 对齐参考组件emitDataChange // 8. 工具方法:数据变化后统一发射(逻辑不变)
// 深拷贝后发射,保留所有字段,避免父组件修改影响内部数据,统一发射逻辑
const emitDataChange = (newInnerData) => { const emitDataChange = (newInnerData) => {
if (props.isPreview) return; // 预览模式不发射 if (props.isPreview) return;
// 深拷贝内部数据,避免引用传递
const emitData = JSON.parse(JSON.stringify(newInnerData)); const emitData = JSON.parse(JSON.stringify(newInnerData));
// 构造父组件需要的完整配置对象,保持原数据结构
const newModelValue = { const newModelValue = {
...props.modelValue, ...props.modelValue,
tableData: emitData, tableData: emitData,
}; };
// 统一发射:双向绑定更新 + 业务事件
emit("update:modelValue", newModelValue); emit("update:modelValue", newModelValue);
emit("handleTableChange", newModelValue); emit("handleTableChange", newModelValue);
}; };
// 9. 监听Props变化:同步到内部数据 → 对齐参考组件的watch逻辑 // 9. 监听Props变化:同步到内部数据(逻辑不变)
// 仅同步,不发射;nextTick确保有效时间列表先处理完成
watch( watch(
() => props.modelValue, () => props.modelValue,
async (newVal) => { async (newVal) => {
if (!validConfig.value) return; if (!validConfig.value) return;
await nextTick(); // 等待有效时间列表计算完成 await nextTick();
const newTableData = handleTableData( const newTableData = handleTableData(
newVal.indicatorList, newVal.indicatorList,
newVal.tableData, newVal.tableData,
); );
tableDataRef.value = newTableData; // 只更新内部数据,不直接修改Props tableDataRef.value = newTableData;
}, },
{ deep: true, immediate: true }, // 深度监听+立即执行,组件挂载即初始化 { deep: true, immediate: true },
); );
// 监听有效时间列表变化:更新内部数据后统一发射 → 对齐参考组件 // 监听时间配置变化:更新内部数据
watch( watch(
() => validDynamicTimeList.value, () => validDynamicTimeList.value,
() => { () => {
if (!validConfig.value || tableDataRef.value.length === 0) return; if (!validConfig.value || tableDataRef.value.length === 0) return;
// 重新初始化时间字段+计算合计
tableDataRef.value.forEach((row) => { tableDataRef.value.forEach((row) => {
initRowTimeField(row); initRowTimeField(row);
row.total = calculateRowTotal(row); row.total = calculateRowTotal(row);
...@@ -242,39 +347,37 @@ watch( ...@@ -242,39 +347,37 @@ watch(
{ deep: true, immediate: true }, { deep: true, immediate: true },
); );
// 10. 数字行输入回调 → 重构,仅更新内部数据,再统一发射 // 10. 数字行输入回调(逻辑不变)
const handleFinancialChange = (currentRow) => { const handleFinancialChange = (currentRow) => {
if (props.isPreview || !currentRow || currentRow.noTotal) return; if (props.isPreview || !currentRow || currentRow.noTotal) return;
currentRow.total = calculateRowTotal(currentRow); // 仅更新内部数据的合计 currentRow.total = calculateRowTotal(currentRow);
emitDataChange(tableDataRef.value); // 统一发射所有数据 emitDataChange(tableDataRef.value);
}; };
// 11. 文本行输入回调 → 重构,同步内部数据后统一发射(可扩展 // 11. 文本行输入回调(逻辑不变
const handleTextInput = (currentRow) => { const handleTextInput = (currentRow) => {
if (props.isPreview || !currentRow) return; if (props.isPreview || !currentRow) return;
emitDataChange(tableDataRef.value); // 文本输入仅同步,统一发射 emitDataChange(tableDataRef.value);
}; };
// 12. 表格单元格样式 → 保留原逻辑,微调健壮性 // 12. 表格单元格样式(保留原逻辑)
const tableCellStyle = ({ row }) => { const tableCellStyle = ({ row }) => {
// 合计行高亮
if (row?.isTotal) { if (row?.isTotal) {
return { background: "#f5f7fa", fontWeight: "bold", textAlign: "right" }; return { background: "#f5f7fa", fontWeight: "bold", textAlign: "right" };
} }
// 所有行右对齐,文本行输入框单独左对齐(样式层处理)
return { textAlign: "right" }; return { textAlign: "right" };
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// 外层容器:适配滚动,和参考组件样式统一 // 外层容器:适配滚动
.mixed-table-wrap { .mixed-table-wrap {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
overflow-x: auto; overflow-x: auto;
} }
// 数字输入框样式:对齐参考组件,填满单元格 // 数字输入框样式
:deep(.el-input-number) { :deep(.el-input-number) {
width: 100%; width: 100%;
} }
...@@ -284,14 +387,14 @@ const tableCellStyle = ({ row }) => { ...@@ -284,14 +387,14 @@ const tableCellStyle = ({ row }) => {
width: 100% !important; width: 100% !important;
} }
// 文本输入框样式:适配表格,和数字框高度对齐,保留原逻辑 // 文本输入框样式
:deep(.el-input__textarea) { :deep(.el-input__textarea) {
resize: none; // 禁止手动拉伸 resize: none;
text-align: left !important; // 文本左对齐,符合阅读习惯 text-align: left !important;
min-height: 40px !important; // 固定最小高度,和数字框对齐 min-height: 40px !important;
} }
// 表格列头样式:统一对齐,保留原逻辑 // 表格列头样式
:deep(.el-table__header-cell) { :deep(.el-table__header-cell) {
text-align: right !important; text-align: right !important;
&.el-table__header-cell--align-center { &.el-table__header-cell--align-center {
...@@ -299,14 +402,14 @@ const tableCellStyle = ({ row }) => { ...@@ -299,14 +402,14 @@ const tableCellStyle = ({ row }) => {
} }
} }
// 表格单元格:优化内边距,避免输入框溢出,保留原逻辑 // 表格单元格
:deep(.el-table__cell) { :deep(.el-table__cell) {
padding: 4px 8px !important; padding: 4px 8px !important;
height: 48px !important; // 统一行高,和参考组件一致 height: 48px !important;
vertical-align: middle !important; vertical-align: middle !important;
} }
// 表格行高:统一设置,避免行高混乱 // 表格行高
:deep(.el-table__row) { :deep(.el-table__row) {
height: 48px !important; height: 48px !important;
} }
......
...@@ -353,18 +353,24 @@ const routes = [ ...@@ -353,18 +353,24 @@ const routes = [
title: "成本管理", title: "成本管理",
component: () => import("@/views/elseManage/cost.vue"), component: () => import("@/views/elseManage/cost.vue"),
}, },
{
path: "/costAdd",
name: "costAdd",
title: "成本管理",
component: () => import("@/views/elseManage/costAdd.vue"),
},
{ {
path: "/property", path: "/property",
name: "property", name: "property",
title: "成本管理", title: "资产管理情况",
component: () => import("@/views/elseManage/property.vue"), component: () => import("@/views/elseManage/property.vue"),
}, },
{ {
path: "/link", path: "/link",
name: "link", name: "link",
title: "成本管理", title: "链接管理",
component: () => import("@/views/elseManage/link.vue"), component: () => import("@/views/elseManage/link.vue"),
}, },
], ],
......
<template> <template>
<div class="building-container"> <div class="manage-container">
<img src="@/assets/images/building.png" alt="" /> <div class="manage-wrap">
<div class="title">努力开发中……</div> <div class="manage-header">
<div class="header-left"></div>
<div class="header-right">
<el-button type="primary" @click="costAdd">新增</el-button>
</div>
</div>
<div class="manage-content" v-loading="loading">
<common-table
:autoHeight="true"
:maxRows="10"
:data="tableData"
:columns="tableColumns"
:total="total"
:current-page="currentPage"
:page-size="pageSize"
:index="true"
:indexLabel="'序号'"
title=""
:border="true"
@size-change="handleSizeChange"
@current-page-change="handleCurrentPageChange"
>
<template #operations="{ row, index }">
<el-button
link
type="primary"
size="small"
@click="previewStatement(row)"
>查看</el-button
>
<el-button
link
type="primary"
size="small"
@click="editStatement(row)"
>编辑</el-button
>
<el-button
link
type="danger"
size="small"
@click="deleteStatement(row)"
>删除</el-button
>
</template>
</common-table>
</div>
</div>
</div> </div>
</template> </template>
<script setup></script> <script setup>
import { ref, onMounted, getCurrentInstance } from "vue";
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import CommonTable from "@/components/common/commonTable.vue";
<style lang="less"> const router = useRouter();
.building-container { const { proxy } = getCurrentInstance();
width: 100%;
height: 100%; let tableData = ref([]);
display: flex; let tableColumns = ref([
flex-direction: column; {
align-items: center; prop: "projectName",
justify-content: center; label: "项目信息",
img { showOverflowTooltip: true,
max-width: 300px; },
} {
.title { prop: "yjzbLen",
font-size: 18px; label: "一级指标",
color: #999; showOverflowTooltip: true,
} },
} {
prop: "ejzbLen",
label: "二级指标",
showOverflowTooltip: true,
},
{
prop: "operations",
label: "操作",
width: 170,
slot: "operations",
fixed: "right",
align: "center",
},
]);
let loading = ref(false);
let total = ref(0);
let currentPage = ref(1);
let pageSize = ref(10);
// 获取列表数据
const getStatementData = () => {
loading.value = true;
proxy.$post({
url: "/api/project/getCbglList",
data: {
page: currentPage.value,
pagesize: pageSize.value,
},
callback: (data) => {
const countValidRows = (arr) => {
if (!Array.isArray(arr)) return 0;
return arr.reduce((count, item) => {
if (Array.isArray(item?.fj) && item.fj.length > 0) {
count++;
}
return count;
}, 0);
};
tableData.value = data.rows.map((it) => {
return {
...it,
yjzbLen: `${it.yjzb.length}行`,
ejzbLen: `${it.ejzb.length}行`,
};
});
total.value = data.count;
loading.value = false;
},
});
};
// 分页
const handleSizeChange = (size) => {
pageSize.value = size;
currentPage.value = 1;
getStatementData();
};
const handleCurrentPageChange = (page) => {
currentPage.value = page;
getStatementData();
};
const costAdd = () => {
router.push("/costAdd");
};
const editStatement = (item) => {
router.push({
name: "costAdd",
query: {
id: item.id,
},
});
};
const previewStatement = (item) => {
router.push({
name: "costAdd",
query: {
isPreview: true,
id: item.id,
},
});
};
const deleteStatement = (item) => {
ElMessageBox.confirm("确认删除该项?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
proxy.$post({
url: "/api/project/deleteCbgl",
data: {
id: item.id,
},
callback: (data) => {
ElMessage.success("删除成功");
getStatementData();
},
});
})
.catch(() => {});
};
onMounted(() => {
getStatementData();
});
</script>
<style scoped lang="less">
@import "@/styles/manage.less";
</style> </style>
<template>
<div class="add-project-container">
<div class="add-project-content" v-loading="loading">
<div class="add-project-header">
<div class="header-left"></div>
<div class="header-right">
<el-button type="default" @click="backClick">返回</el-button>
<template v-if="!loading && !isPreview">
<el-button type="primary" @click="saveClick">保存</el-button>
</template>
</div>
</div>
<div class="tabs-content">
<div class="project-tab-content">
<div class="tab-content">
<el-form :model="formData" :label-width="200" :disabled="isPreview">
<el-collapse v-model="activeCollapse">
<!-- 项目信息 -->
<el-collapse-item title="项目信息" name="项目信息">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目信息" required>
<el-select
v-model="formData.projectId"
placeholder="请选择项目信息"
no-data-text="暂无数据"
@change="changeProject"
>
<el-option
v-for="item in projectList"
:key="item.key"
:label="item.projectName"
:value="item.id"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-collapse-item>
<!-- 6类主体成本指标体系一览表:给costTab1传初始化数据 -->
<el-collapse-item
title="6类主体成本指标体系一览表"
name="6类主体成本指标体系一览表"
>
<costTab
ref="costTab1Ref"
:isPreview="isPreview"
:init-table-data="formData.yjzb"
v-if="formData.yjzb && formData.yjzb.length > 0"
/>
<costTab
ref="costTab1Ref2"
:init-table-data="formData.ejzb"
v-if="formData.ejzb && formData.ejzb.length > 0"
/>
</el-collapse-item>
</el-collapse>
</el-form>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { reactive, ref, onMounted, getCurrentInstance } from "vue";
import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import costTab from "./costTab1.vue";
// 路由与全局实例
const router = useRouter();
const route = useRoute();
const { proxy } = getCurrentInstance();
// 折叠面板默认展开项
const activeCollapse = ref([
"项目信息",
"6类主体成本指标体系一览表",
"期中履约终止移交",
"期满移交",
]);
// 🌟 新增:costTab1的ref引用,用于调用子组件方法
const costTab1Ref = ref(null);
// 🌟 修改1:新增ejzb对应的子组件ref引用
const costTab1Ref2 = ref(null);
// 表单核心数据
const formData = reactive({
projectName: "",
projectId: "",
del: 0,
createdAt: "",
updatedAt: "",
// 🌟 新增:costTab1的初始化数据(编辑时从接口获取,新增时用默认值)
yjzb: [],
// 🌟 修改2:将ejzb从对象改为数组(和yjzb保持一致)
ejzb: [],
});
// 状态管理
const loading = ref(false);
const isPreview = ref(!!route.query.isPreview);
const projectList = ref([]);
const rcCgqyglId = ref(route.query.id || "");
let options = ref();
// 获取项目列表数据
const getProjectData = () => {
proxy.$post({
url: "/api/project/listProject",
data: { page: 1, pagesize: 1000, attributes: [], menuType: "xmjc" },
callback: (data) => {
projectList.value = data.rows || [];
},
});
};
// 选择项目同步名称
const changeProject = (val) => {
const selectItem = projectList.value.find((item) => item.id === val);
if (selectItem) formData.projectName = selectItem.projectName;
};
// 获取单条记录详情(编辑/预览)
const getJsqtzjcDetail = () => {
if (!rcCgqyglId.value) return;
loading.value = true;
proxy.$post({
url: "/api/project/getCbgl",
data: { id: rcCgqyglId.value },
callback: (data) => {
loading.value = false;
Object.assign(formData, data);
},
});
};
// 返回上一页
const backClick = () => {
router.back(-1);
};
// 保存/提交表单
const saveClick = async () => {
if (!formData.projectId) {
ElMessage.warning("请选择项目信息");
return;
}
loading.value = true;
try {
const costTab1EditedData = costTab1Ref.value?.getEditedTableData() || [];
const costTab1EditedData2 = costTab1Ref2.value?.getEditedTableData() || [];
console.log(costTab1EditedData, "costTab1EditedData");
console.log(costTab1EditedData2, "costTab1EditedData2");
const url = rcCgqyglId.value
? "/api/project/updateCbgl"
: "/api/project/createCbgl";
const submitData = {
...formData,
projectId: formData.projectId + "",
yjzb: costTab1EditedData,
// 🌟 修改4:将ejzb的编辑后数据加入提交参数
ejzb: costTab1EditedData2,
};
await proxy.$post({
url: url,
data: submitData,
callback: () => {
ElMessage.success(rcCgqyglId.value ? "编辑成功" : "新增成功");
router.back(-1);
},
});
} catch (error) {
ElMessage.error("保存失败,请重试");
console.error("保存失败:", error);
} finally {
loading.value = false;
}
};
// 页面初始化
onMounted(() => {
getProjectData();
options.value = JSON.parse(sessionStorage.getItem("resourceData"));
if (rcCgqyglId.value) {
getJsqtzjcDetail();
} else {
// yjzb 初始化数据(保持原有逻辑)
formData.yjzb = [
{
serialNumber: { value: "", placeholder: "1" },
entityType: { value: "", placeholder: "交投本部" },
rows: [
{
level1Code: { value: "", placeholder: " (1) " },
level1Name: { value: "", placeholder: "ROA,辅以总资产周转率" },
level1Content: {
value: "",
placeholder:
"ROA=净利率×总资产周转率;重资产企业要不断提高净利水平,要不加快资产变现。",
},
level1Target: { value: "", placeholder: "中期ROA提升2%" },
level2Code: { value: "", placeholder: "①" },
level2Name: { value: "", placeholder: "3类费用压降比例" },
level2Content: {
value: "",
placeholder: "考核非生产性支出控制成果",
},
level2Target: { value: "", placeholder: "近期降1%" },
},
{
level1Code: { value: "", placeholder: "(2)" },
level1Name: { value: "", placeholder: "成本营收比" },
level1Content: {
value: "",
placeholder: "衡量企业成本控制效率和资产盈利能力(重资产企业)。",
},
level1Target: { value: "", placeholder: "≥能建考核值的90%" },
level2Code: { value: "", placeholder: "②" },
level2Name: { value: "", placeholder: "每亿元“投资+营收”管理费" },
level2Content: { value: "", placeholder: "成本费用支出效率" },
level2Target: { value: "", placeholder: "近期升5%" },
},
{
level1Code: { value: "", placeholder: "(3)" },
level1Name: { value: "", placeholder: "投资控制偏差率*" },
level1Content: {
value: "",
placeholder: "考核建设项目投资控制能力。",
},
level1Target: { value: "", placeholder: "≤0" },
level2Code: { value: "", placeholder: "③" },
level2Name: { value: "", placeholder: "概预差*" },
level2Content: {
value: "",
placeholder: "设计阶段限额设计和设计优化的工作成果",
},
level2Target: { value: "", placeholder: "≥3%" },
},
],
},
{
serialNumber: { value: "", placeholder: "2" },
entityType: { value: "", placeholder: "建设项目" },
rows: [
{
level1Code: { value: "", placeholder: "(4)" },
level1Name: { value: "", placeholder: "投资价差收取达成率" },
level1Content: {
value: "",
placeholder:
"考核投资项目设计优化、合同策划、承包穿透管理和投资价差催收4方面综合能力。",
},
level1Target: { value: "", placeholder: "≥100%" },
level2Code: { value: "", placeholder: "④" },
level2Name: { value: "", placeholder: "施工合同降造比例*" },
level2Content: {
value: "",
placeholder:
"协同施工单位承包合同价(单价水平)较批复预算(除激励基金和风险费外)下降比例。",
},
level2Target: { value: "", placeholder: "能建要求≥15%" },
},
{
level1Code: { value: "", placeholder: "(5)" },
level1Name: { value: "", placeholder: "综合融资成本率**" },
level1Content: {
value: "",
placeholder: "建设期融资成本控制水平。",
},
level1Target: { value: "", placeholder: "≤银行业自律利率" },
level2Code: { value: "", placeholder: "⑤" },
level2Name: { value: "", placeholder: "投资价差同步收取比例" },
level2Content: {
value: "",
placeholder: "投资价差收取同步性情况。",
},
level2Target: { value: "", placeholder: "≥100%" },
},
{
level1Code: { value: "", placeholder: "/" },
level1Name: { value: "", placeholder: "" },
level1Content: { value: "", placeholder: "" },
level1Target: { value: "", placeholder: "" },
level2Code: { value: "", placeholder: "⑥" },
level2Name: { value: "", placeholder: "变更索赔费用占预备费比例" },
level2Content: {
value: "",
placeholder: "衡量履约阶段投资控制质量。",
},
level2Target: { value: "", placeholder: "<70%" },
},
],
},
{
serialNumber: { value: "", placeholder: "3" },
entityType: { value: "", placeholder: "运营项目" },
rows: [
{
level1Code: { value: "", placeholder: "(6)" },
level1Name: { value: "", placeholder: "百元营收“管理费+运行费”" },
level1Content: { value: "", placeholder: "管理、运行成本控制水平" },
level1Target: { value: "", placeholder: "同行业均值之下" },
level2Code: { value: "", placeholder: "⑦" },
level2Name: { value: "", placeholder: "集约化、智能化措施完成率" },
level2Content: {
value: "",
placeholder: "考核公司“三定方案”等降本措施落实情况",
},
level2Target: { value: "", placeholder: "100%" },
},
],
},
];
// 🌟 修改5:ejzb 初始化数据(和yjzb结构完全一致)
formData.ejzb = [
{
serialNumber: { value: "", placeholder: "4" },
entityType: { value: "", placeholder: " 工程建设类 " },
rows: [
{
level1Code: { value: "", placeholder: "(7)" },
level1Name: { value: "", placeholder: " 累计养护成本总成本比例 " },
level1Content: {
value: "",
placeholder: " 综合养护能力和养护成本控制水平。",
},
level1Target: { value: "", placeholder: " 三块算养护成本 " },
level2Code: { value: "", placeholder: "⑧" },
level2Name: { value: "", placeholder: " 百公里养护成本 " },
level2Content: {
value: "",
placeholder:
" 在 POI 等养护质量指标达标前提下,养护成本控制水平 ",
},
level2Target: { value: "", placeholder: " 降低 1%" },
},
{
level1Code: { value: "", placeholder: "(8)" },
level1Name: { value: "", placeholder: " 综合融资成本率 " },
level1Content: {
value: "",
placeholder: " 运营期融资成本控制水平。",
},
level1Target: { value: "", placeholder: " 三目制利率 + 10BP" },
level2Code: { value: "", placeholder: "⑨" },
level2Name: { value: "", placeholder: " 车车通收费人数 " },
level2Content: {
value: "",
placeholder: " 考核智慧化改造、少人化收费工作落实情况 ",
},
level2Target: { value: "", placeholder: " 降低 15%" },
},
{
level1Code: { value: "", placeholder: "(9)" },
level1Name: { value: "", placeholder: " 成本营收比 " },
level1Content: {
value: "",
placeholder: " 公司成本管控水平和业务盈利水平(有利润的营收)。",
},
level1Target: { value: "", placeholder: "≤85%" },
level2Code: { value: "", placeholder: "⑩" },
level2Name: { value: "", placeholder: " 项目平均毛利率 " },
level2Content: { value: "", placeholder: " 项目成本控制水平 " },
level2Target: { value: "", placeholder: "≥15%" },
},
{
level1Code: { value: "", placeholder: "(10)" },
level1Name: { value: "", placeholder: " 总部支出占营收比例 " },
level1Content: {
value: "",
placeholder: " 总部成本控制水平及业务发展规模。",
},
level1Target: { value: "", placeholder: "≤10%" },
level2Code: { value: "", placeholder: "⑪" },
level2Name: { value: "", placeholder: " 项目成本责任制覆盖率 " },
level2Content: {
value: "",
placeholder: " 集团标后预算等成本管理制度落实情况。",
},
level2Target: { value: "", placeholder: "100%" },
},
{
level1Code: { value: "", placeholder: "(11)" },
level1Name: { value: "", placeholder: " 项目直接费占总成本比例 " },
level1Content: {
value: "",
placeholder: " 项目直接成本水平不增的前提下降低间接成本 ",
},
level1Target: { value: "", placeholder: " 近期升 5%" },
level2Code: { value: "", placeholder: "⑫" },
level2Name: { value: "", placeholder: " 项目直接费占总成本比例 " },
level2Content: {
value: "",
placeholder: " 项目直接成本水平不增的前提下降低间接成本 ",
},
level2Target: { value: "", placeholder: " 近期升 5%" },
},
{
level1Code: { value: "", placeholder: "(12)" },
level1Name: { value: "", placeholder: " 营收收现比例 " },
level1Content: { value: "", placeholder: " 有现金流的利润。" },
level1Target: { value: "", placeholder: "≥能建考核指标 " },
level2Code: { value: "", placeholder: "⑬" },
level2Name: { value: "", placeholder: " 专业分包比例 *" },
level2Content: { value: "", placeholder: " 自营施工水平。" },
level2Target: { value: "", placeholder: "≤50%" },
},
],
},
{
serialNumber: { value: "", placeholder: "5" },
entityType: { value: "", placeholder: " 路衍经济类 " },
rows: [
{
level1Code: { value: "", placeholder: "(13)" },
level1Name: { value: "", placeholder: " 成本营收比 " },
level1Content: {
value: "",
placeholder: " 公司成本管控水平和业务盈利水平(有利润的营收)。",
},
level1Target: { value: "", placeholder: "≤85%" },
level2Code: { value: "", placeholder: "⑭" },
level2Name: { value: "", placeholder: " 商业策划覆盖比例 " },
level2Content: {
value: "",
placeholder:
" 考核商业分析、经营策划、成本核算等经营机制建立情况 ",
},
level2Target: { value: "", placeholder: "100%" },
},
{
level1Code: { value: "", placeholder: "(14)" },
level1Name: { value: "", placeholder: " 总部支出占营收比例 " },
level1Content: {
value: "",
placeholder: " 总部成本控制水平及业务发展规模。",
},
level1Target: { value: "", placeholder: "≤10%" },
level2Code: { value: "", placeholder: "⑮" },
level2Name: { value: "", placeholder: " 各类业务净利率 " },
level2Content: {
value: "",
placeholder: " 考核业务筛选、成本优化、经营能力提升等情况 ",
},
level2Target: { value: "", placeholder: " 均≥0%" },
},
],
},
{
serialNumber: { value: "", placeholder: "6" },
entityType: { value: "", placeholder: " 功能性单位 " },
rows: [
{
level1Code: { value: "", placeholder: "(15)" },
level1Name: { value: "", placeholder: " 经费预算纳入考核比例 " },
level1Content: { value: "", placeholder: " 考核成本体系建立情况 " },
level1Target: { value: "", placeholder: "100%" },
level2Code: { value: "", placeholder: "/" },
level2Name: { value: "", placeholder: "" },
level2Content: { value: "", placeholder: "" },
level2Target: { value: "", placeholder: "" },
},
{
level1Code: { value: "", placeholder: "(16)" },
level1Name: { value: "", placeholder: " 委外成果与付费挂钩比例 " },
level1Content: {
value: "",
placeholder: " 考核引入中介和委外咨询服务效率评价体系建立情况 ",
},
level1Target: { value: "", placeholder: "100%" },
level2Code: { value: "", placeholder: "/" },
level2Name: { value: "", placeholder: "" },
level2Content: { value: "", placeholder: "" },
level2Target: { value: "", placeholder: "" },
},
],
},
];
}
});
</script>
<style scoped lang="less">
@import "@/styles/verticalManages.less";
</style>
<template>
<div class="table-container">
<table class="cost-table">
<thead>
<!-- 一级表头 -->
<tr class="header-row level1">
<th rowspan="2" style="width: 70px">序号</th>
<th rowspan="2" style="width: 110px">主体类型</th>
<th colspan="4">1级指标</th>
<th colspan="4">2级指标</th>
</tr>
<!-- 二级表头 -->
<tr class="header-row level2">
<th style="width: 70px">编号</th>
<th>指标名称</th>
<th>指标考核内容</th>
<th>建议值</th>
<th style="width: 70px">编号</th>
<th>指标名称</th>
<th>指标考核内容</th>
<th>建议值</th>
</tr>
</thead>
<tbody>
<!-- 按主体分组渲染 -->
<template
v-for="(group, groupIndex) in groupedTableData"
:key="groupIndex"
>
<tr
v-for="(item, rowIndex) in group.rows"
:key="`${groupIndex}-${rowIndex}`"
class="body-row"
>
<!-- 序号:仅每组第一行显示 -->
<td
v-if="rowIndex === 0"
:rowspan="group.rows.length"
class="cell-item"
>
{{ group.serialNumber.placeholder }}
</td>
<!-- 主体类型:仅每组第一行显示 -->
<td
v-if="rowIndex === 0"
:rowspan="group.rows.length"
class="cell-item entity-cell"
>
{{ group.entityType.placeholder }}
</td>
<!-- 1级指标编号 -->
<td class="cell-item">
{{ item.level1Code.placeholder }}
</td>
<!-- 1级指标名称 -->
<td class="cell-item">
{{ item.level1Name.placeholder }}
</td>
<!-- 1级指标考核内容(textarea) -->
<td class="cell-item">
<el-input
type="textarea"
rows="3"
v-model="item.level1Content.value"
:placeholder="item.level1Content.placeholder"
size="small"
class="table-input textarea-input"
:disabled="isPreview"
/>
</td>
<!-- 1级指标建议值 -->
<td class="cell-item">
<el-input
type="textarea"
rows="3"
v-model="item.level1Target.value"
:placeholder="item.level1Target.placeholder"
size="small"
class="table-input"
:disabled="isPreview"
/>
</td>
<!-- 2级指标编号 -->
<td class="cell-item">
{{ item.level2Code.placeholder }}
</td>
<!-- 2级指标名称 -->
<td class="cell-item">
{{ item.level2Name.placeholder }}
</td>
<!-- 2级指标考核内容(textarea) -->
<td class="cell-item">
<el-input
type="textarea"
rows="3"
v-model="item.level2Content.value"
:placeholder="item.level2Content.placeholder"
size="small"
class="table-input textarea-input"
:disabled="isPreview"
/>
</td>
<!-- 2级指标建议值 -->
<td class="cell-item">
<el-input
type="textarea"
rows="3"
v-model="item.level2Target.value"
:placeholder="item.level2Target.placeholder"
size="small"
class="table-input"
:disabled="isPreview"
/>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template>
<script setup>
import { ref, watch, computed, defineProps, defineExpose } from "vue";
import { ElInput } from "element-plus";
// ===================== 1. 接收父组件传入的初始化数据 =====================
const props = defineProps({
// 新增:预览模式控制属性
isPreview: {
type: Boolean,
required: false,
default: false,
},
// 父组件传入的初始化表格数据,默认值为原有结构
initTableData: {
type: Array,
required: false,
default: () => [
{
serialNumber: { value: "", placeholder: "1" },
entityType: { value: "", placeholder: "交投本部" },
rows: [
{
level1Code: { value: "", placeholder: " (1) " },
level1Name: { value: "", placeholder: "ROA,辅以总资产周转率" },
level1Content: {
value: "",
placeholder:
"ROA=净利率×总资产周转率;重资产企业要不断提高净利水平,要不加快资产变现。",
},
level1Target: { value: "", placeholder: "中期ROA提升2%" },
level2Code: { value: "", placeholder: "①" },
level2Name: { value: "", placeholder: "3类费用压降比例" },
level2Content: {
value: "",
placeholder: "考核非生产性支出控制成果",
},
level2Target: { value: "", placeholder: "近期降1%" },
},
{
level1Code: { value: "", placeholder: "(2)" },
level1Name: { value: "", placeholder: "成本营收比" },
level1Content: {
value: "",
placeholder: "衡量企业成本控制效率和资产盈利能力(重资产企业)。",
},
level1Target: { value: "", placeholder: "≥能建考核值的90%" },
level2Code: { value: "", placeholder: "②" },
level2Name: { value: "", placeholder: "每亿元“投资+营收”管理费" },
level2Content: { value: "", placeholder: "成本费用支出效率" },
level2Target: { value: "", placeholder: "近期升5%" },
},
{
level1Code: { value: "", placeholder: "(3)" },
level1Name: { value: "", placeholder: "投资控制偏差率*" },
level1Content: {
value: "",
placeholder: "考核建设项目投资控制能力。",
},
level1Target: { value: "", placeholder: "≤0" },
level2Code: { value: "", placeholder: "③" },
level2Name: { value: "", placeholder: "概预差*" },
level2Content: {
value: "",
placeholder: "设计阶段限额设计和设计优化的工作成果",
},
level2Target: { value: "", placeholder: "≥3%" },
},
],
},
{
serialNumber: { value: "", placeholder: "2" },
entityType: { value: "", placeholder: "建设项目" },
rows: [
{
level1Code: { value: "", placeholder: "(4)" },
level1Name: { value: "", placeholder: "投资价差收取达成率" },
level1Content: {
value: "",
placeholder:
"考核投资项目设计优化、合同策划、承包穿透管理和投资价差催收4方面综合能力。",
},
level1Target: { value: "", placeholder: "≥100%" },
level2Code: { value: "", placeholder: "④" },
level2Name: { value: "", placeholder: "施工合同降造比例*" },
level2Content: {
value: "",
placeholder:
"协同施工单位承包合同价(单价水平)较批复预算(除激励基金和风险费外)下降比例。",
},
level2Target: { value: "", placeholder: "能建要求≥15%" },
},
{
level1Code: { value: "", placeholder: "(5)" },
level1Name: { value: "", placeholder: "综合融资成本率**" },
level1Content: {
value: "",
placeholder: "建设期融资成本控制水平。",
},
level1Target: { value: "", placeholder: "≤银行业自律利率" },
level2Code: { value: "", placeholder: "⑤" },
level2Name: { value: "", placeholder: "投资价差同步收取比例" },
level2Content: {
value: "",
placeholder: "投资价差收取同步性情况。",
},
level2Target: { value: "", placeholder: "≥100%" },
},
{
level1Code: { value: "", placeholder: "/" },
level1Name: { value: "", placeholder: "" },
level1Content: { value: "", placeholder: "" },
level1Target: { value: "", placeholder: "" },
level2Code: { value: "", placeholder: "⑥" },
level2Name: { value: "", placeholder: "变更索赔费用占预备费比例" },
level2Content: {
value: "",
placeholder: "衡量履约阶段投资控制质量。",
},
level2Target: { value: "", placeholder: "<70%" },
},
],
},
{
serialNumber: { value: "", placeholder: "3" },
entityType: { value: "", placeholder: "运营项目" },
rows: [
{
level1Code: { value: "", placeholder: "(6)" },
level1Name: { value: "", placeholder: "百元营收“管理费+运行费”" },
level1Content: { value: "", placeholder: "管理、运行成本控制水平" },
level1Target: { value: "", placeholder: "同行业均值之下" },
level2Code: { value: "", placeholder: "⑦" },
level2Name: { value: "", placeholder: "集约化、智能化措施完成率" },
level2Content: {
value: "",
placeholder: "考核公司“三定方案”等降本措施落实情况",
},
level2Target: { value: "", placeholder: "100%" },
},
],
},
],
},
});
// ===================== 2. 初始化可编辑数据(深拷贝,避免修改父组件传入的原始数据) =====================
const rawTableData = ref(JSON.parse(JSON.stringify(props.initTableData)));
// 计算属性:保持分组渲染逻辑
const groupedTableData = computed(() => rawTableData.value);
// ===================== 3. 暴露方法给父组件:获取编辑后的数据 =====================
const getEditedTableData = () => {
console.log(rawTableData.value, "rawTableData.value");
return rawTableData.value;
};
// 暴露方法给父组件调用
defineExpose({
getEditedTableData,
// 可选:暴露原始编辑数据(含placeholder),按需使用
getRawTableData: () => JSON.parse(JSON.stringify(rawTableData.value)),
});
</script>
<style scoped>
/* 容器样式 */
.table-container {
width: 98%;
margin: 20px auto;
padding: 10px;
font-family: "Microsoft YaHei", Arial, sans-serif;
overflow-x: auto; /* 小屏幕横向滚动 */
}
/* 表格核心样式 */
.cost-table {
width: 100%;
border-collapse: collapse;
border: 1px solid #ddd;
font-size: 14px;
color: #333;
min-width: 1200px;
}
/* 一级表头 */
.header-row.level1 th {
background-color: #2c5aa0;
color: #fff;
padding: 12px 8px;
border: 1px solid #2c5aa0;
text-align: center;
vertical-align: middle;
font-weight: 600;
font-size: 15px;
}
/* 二级表头 */
.header-row.level2 th {
background-color: #4073b8;
color: #fff;
padding: 10px 8px;
border: 1px solid #2c5aa0;
text-align: center;
vertical-align: middle;
font-weight: 500;
font-size: 14px;
}
/* 表体单元格 */
.cell-item {
padding: 8px 4px;
border: 1px solid #ddd;
text-align: center;
vertical-align: middle;
height: 80px; /* 适配textarea高度 */
}
/* 主体类型单元格特殊样式 */
.entity-cell {
font-weight: 500;
}
/* el-input通用样式 */
.table-input {
width: 95%;
--el-input-placeholder-color: #666; /* 占位符文字颜色 */
}
/* 多行文本域适配 */
.textarea-input {
resize: none;
height: 70px !important;
line-height: 1.5;
}
/* 新增:优化禁用状态的输入框样式 */
:deep(.el-input.is-disabled) {
--el-input-text-color: #333; /* 禁用时文字仍显示黑色 */
--el-input-bg-color: #f8f9fa; /* 禁用背景色更柔和 */
cursor: default;
}
:deep(.el-input__inner:disabled) {
background-color: #f8f9fa;
color: #333;
border-color: #e9ecef;
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment