This commit is contained in:
2025-09-04 18:42:45 +08:00
parent 23b6551829
commit 0f439c9220
17 changed files with 1468 additions and 452 deletions

View File

@ -1,6 +1,5 @@
<template>
<div class="overall-plan-material-supply">
<!-- tabPosition="left" -->
<el-card shadow="always">
<template #header>
<el-row :gutter="10" class="mb8">
@ -30,14 +29,16 @@
>一键全部保存</el-button
>
</el-form-item>
<el-form-item>
<!-- <el-form-item>
<el-button type="primary" @click="toggleExpandAll">{{ isExpandAll ? '一键收起' : '一键展开' }}</el-button>
</el-form-item>
</el-form-item> -->
</el-form>
<right-toolbar @queryTable="getMasterDataList"></right-toolbar>
</el-row>
</template>
</el-card>
<!-- 本地数据懒加载表格 -->
<el-table
:data="state.tableData"
v-loading="state.loading.list"
@ -47,6 +48,9 @@
row-key="id"
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:lazy="true"
:load="loadLocalChildNodes"
@expand-change="handleExpandChange"
>
<el-table-column align="center" prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" width="180" />
@ -54,87 +58,95 @@
<el-table-column prop="specification" label="规格型号" width="80" />
<el-table-column prop="quantity" label="数量" width="100" />
<el-table-column prop="batchNumber" label="批次号" width="200" />
<!-- 优化的输入框列 -->
<el-table-column prop="brand" label="品牌">
<template #default="{ row }">
<el-input
v-model="row.brand"
v-model.lazy="row.brand"
:disabled="state.masterData.status != 'draft'"
v-if="!(row.children && row.children.length > 0)"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`brand-${row.id}`"
@change="handleInputChange(row, 'brand')"
/>
</template>
</el-table-column>
<el-table-column prop="texture" label="材质">
<template #default="{ row }">
<el-input
v-model="row.texture"
v-model.lazy="row.texture"
:disabled="state.masterData.status != 'draft'"
v-if="!(row.children && row.children.length > 0)"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`texture-${row.id}`"
@change="handleInputChange(row, 'texture')"
/>
</template>
</el-table-column>
<el-table-column prop="qualityStandard" label="质量标准">
<template #default="{ row }">
<el-input
:disabled="state.masterData.status != 'draft'"
v-model="row.qualityStandard"
v-if="!(row.children && row.children.length > 0)"
v-model.lazy="row.qualityStandard"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`qualityStandard-${row.id}`"
@change="handleInputChange(row, 'qualityStandard')"
/>
</template>
</el-table-column>
<el-table-column prop="partUsed" label="使用部位">
<template #default="{ row }">
<el-input
:disabled="state.masterData.status != 'draft'"
v-model="row.partUsed"
v-if="!(row.children && row.children.length > 0)"
v-model.lazy="row.partUsed"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`partUsed-${row.id}`"
@change="handleInputChange(row, 'partUsed')"
/>
</template>
</el-table-column>
<el-table-column prop="deliveryPoints" label="交货地点">
<template #default="{ row }">
<el-input
:disabled="state.masterData.status != 'draft'"
v-model="row.deliveryPoints"
v-if="!(row.children && row.children.length > 0)"
v-model.lazy="row.deliveryPoints"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`deliveryPoints-${row.id}`"
@change="handleInputChange(row, 'deliveryPoints')"
/>
</template>
</el-table-column>
<el-table-column label="预计使用日期" width="150">
<template #default="{ row }">
<el-date-picker
v-model="row.dateService"
v-if="!(row.children && row.children.length > 0)"
v-if="!row.hasChildren"
type="date"
value-format="YYYY-MM-DD"
:disabled="state.masterData.status != 'draft'"
placeholder="请选择日期"
:key="`dateService-${row.id}`"
@change="handleInputChange(row, 'dateService')"
/>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" />
<!-- <el-table-column label="操作">
<template #default="{ row }">
<el-button
:disabled="state.masterData.status == 'waiting' || state.masterData.status == 'finish'"
type="primary"
v-hasPermi="['design:totalsupplyplan:edit']"
@click="editApprovalSheet(row)"
>修改</el-button
>
</template>
</el-table-column> -->
</el-table>
<!-- 编辑 -->
<!-- 编辑弹窗 -->
<el-dialog v-model="visible" title="修改物料信息" :width="800" :close-on-click-modal="false" @close="handleClose">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="space-y-4">
<el-row :gutter="20">
@ -149,7 +161,6 @@
</el-form-item>
</el-col>
</el-row>
<!-- 物料属性区域 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="材质" prop="texture">
@ -162,7 +173,6 @@
</el-form-item>
</el-col>
</el-row>
<!-- 日期与状态区域 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="使用部位" prop="partUsed">
@ -175,7 +185,6 @@
</el-form-item>
</el-col>
</el-row>
<!-- 其他信息区域 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预计使用日期" prop="dateService">
@ -200,7 +209,8 @@
</template>
<script setup name="billofQuantities">
import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue';
import { ref, reactive, onMounted, computed, getCurrentInstance, watch, onUnmounted } from 'vue';
import { ElMessage, ElLoading } from 'element-plus';
import {
obtainMasterDataList,
totalsupplyplan,
@ -209,13 +219,26 @@ import {
totalSupplyplanBatchEdit
} from '@/api/materials/overallPlanMaterialSupply/index';
import { useUserStoreHook } from '@/store/modules/user';
// 全局状态与实例获取
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const { proxy } = getCurrentInstance();
// 基础状态管理
const visible = ref(false);
const formRef = ref(null);
const tableRef = ref(null);
const file = ref(null);
const isExpandAll = ref(false);
const expandRowKeys = ref([]);
const editDataMap = ref(new Map()); // 用Map存储修改的数据提高查询效率
const loadingInstance = ref(null);
const fullTreeData = ref([]); // 存储完整树形数据,用于本地懒加载
// 页面核心状态
const state = reactive({
tableData: [],
tableData: [], // 初始只存放根节点
queryForm: {
projectId: currentProject.value?.id,
versions: '',
@ -228,9 +251,9 @@ const state = reactive({
sheets: false,
list: false
},
// 主id
masterData: {}
});
// 表单数据
const formData = reactive({
batchNumber: '',
@ -252,6 +275,7 @@ const formData = reactive({
texture: '',
unit: ''
});
// 表单验证规则
const formRules = reactive({
name: [
@ -268,82 +292,190 @@ const formRules = reactive({
],
compileDate: [{ required: true, message: '请选择编制日期', trigger: 'change' }]
});
// 展开状态
const isExpandAll = ref(false);
const tableRef = ref(null);
// 切换展开状态
const toggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
console.log(isExpandAll.value);
state.tableData.forEach((row) => {
tableRef.value.toggleRowExpansion(row, isExpandAll.value);
/**
* 本地数据懒加载实现:从完整数据中筛选子节点
*/
const loadLocalChildNodes = (row, treeNode, resolve) => {
// 避免重复加载
if (row.children && row.children.length > 0) {
resolve(row.children);
return;
}
// 显示加载状态
loadingInstance.value = ElLoading.service({
target: tableRef.value.$el,
text: '加载中...',
background: 'rgba(255, 255, 255, 0.8)'
});
try {
// 从完整数据中筛选当前行的子节点假设父ID对应子节点的pid
const childNodes = fullTreeData.value.filter((node) => node.pid === row.sid);
// 标记子节点是否有子节点
const formattedChildren = childNodes.map((node) => ({
...node,
hasChildren: fullTreeData.value.some((child) => child.pid === node.sid)
}));
// 缓存子节点到当前行
row.children = formattedChildren;
resolve(formattedChildren);
} catch (error) {
resolve([]);
} finally {
loadingInstance.value?.close();
}
};
// 获取主表数据
/**
* 处理展开状态变化
*/
const handleExpandChange = (row, expanded) => {
if (expanded) {
if (!expandRowKeys.value.includes(row.id)) {
expandRowKeys.value.push(row.id);
}
} else {
expandRowKeys.value = expandRowKeys.value.filter((id) => id !== row.id);
}
};
/**
* 一键展开/收起
*/
const toggleExpandAll = async () => {
isExpandAll.value = !isExpandAll.value;
if (isExpandAll.value) {
loadingInstance.value = ElLoading.service({
target: tableRef.value.$el,
text: '正在加载...',
background: 'rgba(255, 255, 255, 0.8)'
});
try {
// 递归加载所有层级子节点
const loadAllChildren = (nodes) => {
nodes.forEach((node) => {
const children = fullTreeData.value.filter((child) => child.pid === node.sid);
const formattedChildren = children.map((child) => ({
...child,
hasChildren: fullTreeData.value.some((c) => c.pid === child.sid)
}));
node.children = formattedChildren;
if (formattedChildren.length > 0) {
loadAllChildren(formattedChildren);
}
});
};
loadAllChildren(state.tableData);
// 展开所有节点
state.tableData.forEach((row) => {
tableRef.value.toggleRowExpansion(row, true);
});
ElMessage.success('所有节点已展开');
} catch (error) {
ElMessage.error(`一键展开失败:${error.message}`);
} finally {
loadingInstance.value?.close();
}
} else {
// 收起所有节点
state.tableData.forEach((row) => {
tableRef.value.toggleRowExpansion(row, false);
});
expandRowKeys.value = [];
}
};
/**
* 处理输入框变化(优化性能)
*/
const handleInputChange = (row, field) => {
// 只记录修改过的数据,避免全量对比
if (!editDataMap.value.has(row.id)) {
editDataMap.value.set(row.id, { ...row });
}
const storedData = editDataMap.value.get(row.id);
storedData[field] = row[field];
editDataMap.value.set(row.id, storedData);
};
/**
* 获取主数据列表(核心优化:拆分根节点和完整数据)
*/
async function getMasterDataList() {
try {
// 获取主数据列表
state.loading.list = true;
// 获取主数据
const masterDataRes = await obtainMasterDataList({
projectId: currentProject.value?.id
});
const { data: masterData } = masterDataRes;
console.log('masterData', masterData);
if (!masterData[0].id) {
console.warn('未获取到有效的主数据ID');
if (!masterData[0]?.id) {
state.tableData = [];
fullTreeData.value = [];
return;
}
state.masterData = masterData[0];
// 获取供应计划
// 获取完整供应计划数据
const supplyPlanRes = await totalsupplyplan({ id: masterData[0].id });
// 处理结果
if (supplyPlanRes.list == null) {
// state.tableData = supplyPlanRes.rows || [];
state.tableData = proxy?.handleTree(supplyPlanRes.rows, 'sid', 'pid');
console.log('state.tableData', state.tableData);
} else {
// 根据实际业务逻辑处理有list的情况
state.tableData = [];
}
const allData = supplyPlanRes.rows || [];
// 存储完整数据到本地
fullTreeData.value = [...allData];
// 初始只加载根节点pid为空或0的节点
const rootNodes = allData.filter((item) => !item.pid || item.pid == 0);
// 标记根节点是否有子节点
state.tableData = rootNodes.map((node) => ({
...node,
hasChildren: allData.some((child) => child.pid === node.sid)
}));
// 初始化可编辑数据映射
editDataMap.value.clear();
allData.forEach((item) => {
if (!item.hasChildren) {
// 只关注叶子节点
editDataMap.value.set(item.id, { ...item });
}
});
} catch (error) {
console.error('获取主数据列表失败:', error);
// 错误情况下给默认值,避免页面出错
state.tableData = [];
fullTreeData.value = [];
} finally {
state.loading.list = false;
}
}
// 获取详情
// 修改获取详情的方法
/**
* 获取详情
*/
async function totalSupplyplanDetail(id) {
try {
const result = await totalSupplyplanDetails(id);
if (result?.code === 200) {
const detailData = result.data || {};
// 1. 清空原有表单数据
// 清空表单并赋值
Object.keys(formData).forEach((key) => {
formData[key] = undefined;
});
// 2. 处理日期格式假设接口返回的是Date对象或ISO字符串
// 处理日期格式
const formatDate = (date) => {
if (!date) return '';
// 若为字符串先转为Date对象
const d = typeof date === 'string' ? new Date(date) : date;
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
// 3. 合并数据到formData响应式赋值
Object.assign(formData, {
...detailData,
// 单独处理日期字段,转为表单可识别的字符串格式
compileDate: formatDate(detailData.compileDate),
dateService: formatDate(detailData.dateService)
});
console.log('表单数据已更新:', formData);
} else {
ElMessage.error(`获取详情失败: ${result?.msg || '未知错误'}`);
}
@ -354,87 +486,113 @@ async function totalSupplyplanDetail(id) {
state.loading.list = false;
}
}
// 修改
/**
* 一键保存修改
*/
const editApprovalSheet = async () => {
state.loading.list = true;
await totalSupplyplanBatchEdit(state.tableData);
proxy.$modal.msgSuccess('修改成功');
state.loading.list = false;
};
// 提交表单
const handleSubmit = async () => {
try {
// 表单验证
await formRef.value.validate();
// 触发提交事件
editMaterialSupply(formData);
handleClose();
// 只提交修改过的数据从Map中获取
const modifiedData = Array.from(editDataMap.value.values());
if (modifiedData.length === 0) {
ElMessage.info('没有数据需要修改');
return;
}
await totalSupplyplanBatchEdit(modifiedData);
ElMessage.success('修改成功');
getMasterDataList(); // 重新加载数据
} catch (error) {
// 验证失败不提交
console.error('表单验证失败:', error);
return;
ElMessage.error(`保存失败:${error.message}`);
} finally {
state.loading.list = false;
}
};
// 修改物资
function editMaterialSupply(formData) {
materialChangeSupplyplan(formData).then((res) => {
if (res.code === 200) {
ElMessage.success('修改成功');
getMasterDataList();
} else {
ElMessage.error('修改失败');
}
});
}
const handleExport = async () => {
proxy?.download(
'/design/totalsupplyplan/export',
{
projectId: currentProject.value?.id
},
`物资供应总计划.xlsx`,
true
);
/**
* 提交表单
*/
const handleSubmit = async () => {
try {
await formRef.value.validate();
await materialChangeSupplyplan(formData);
ElMessage.success('修改成功');
handleClose();
getMasterDataList();
} catch (error) {
console.error('表单验证失败:', error);
}
};
// 关闭弹窗
/**
* 导出数据
*/
const handleExport = async () => {
proxy?.download('/design/totalsupplyplan/export', { projectId: currentProject.value?.id }, `物资供应总计划.xlsx`, true);
};
/**
* 关闭弹窗
*/
const handleClose = () => {
visible.value = false;
// 清空表单数据
formRef.value?.resetFields();
Object.keys(formData).forEach((key) => {
formData[key] = undefined;
});
// 重置表单验证状态
formRef.value?.resetFields();
};
// 审批
/**
* 审批
*/
function clickApprovalSheet1() {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/approval/overallPlanMaterialSupply/indexEdit`,
query: {
id: state.masterData.id,
type: 'update'
}
query: { id: state.masterData.id, type: 'update' }
});
}
// 审核流程
/**
* 查看流程
*/
function lookApprovalFlow() {
proxy.$router.push({
path: `/approval/overallPlanMaterialSupply/indexEdit`,
query: {
id: state.masterData.id,
type: 'view'
}
query: { id: state.masterData.id, type: 'view' }
});
}
onMounted(() => {
/**
* 监听项目变化
*/
const listeningProject = watch(
() => currentProject.value?.id,
(nid) => {
if (nid) getMasterDataList();
}
);
// 导入成功处理
const handleSuccess = () => {
ElMessage.success('导入成功');
getMasterDataList();
};
// 生命周期
onMounted(() => {
if (currentProject.value?.id) {
getMasterDataList();
}
});
onUnmounted(() => {
listeningProject();
loadingInstance.value?.close();
});
</script>
<style>
.overall-plan-material-supply {
padding: 20px;