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

@ -14,6 +14,18 @@ export const listCompany = (query?: CompanyQuery): AxiosPromise<CompanyVO[]> =>
method: 'get', method: 'get',
params: query params: query
}); });
}; /**
* 查询材料提供商
* @param query
* @returns {*}
*/
export const supplierInputGet = (query?) => {
return request({
url: '/supplierInput/supplierInput/getList',
method: 'get',
params: query
});
}; };
/** /**

View File

@ -58,9 +58,10 @@
<el-card shadow="never" class="mb8"> <el-card shadow="never" class="mb8">
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all> <el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" /> <el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" /> <el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" /> <el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="quantity" label="数量" /> <el-table-column prop="quantity" label="数量" align="center" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="remark" label="单价" align="center"> <el-table-column prop="remark" label="单价" align="center">
<template #default="scope"> <template #default="scope">
<span>{{ scope.row.unitPrice }}</span> <span>{{ scope.row.unitPrice }}</span>
@ -210,8 +211,6 @@ const tableRef = ref<any>();
const toggleExpandAll = () => { const toggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value; isExpandAll.value = !isExpandAll.value;
console.log(isExpandAll.value);
tableData.value.forEach((row) => { tableData.value.forEach((row) => {
tableRef.value.toggleRowExpansion(row, isExpandAll.value); tableRef.value.toggleRowExpansion(row, isExpandAll.value);
}); });
@ -249,7 +248,7 @@ const handleExport = () => {
projectId: currentProject.value?.id, projectId: currentProject.value?.id,
sheet: queryForm.value.sheet sheet: queryForm.value.sheet
}, },
`限价一览表${queryForm.value.sheet}.xlsx` `投标成本核算清单${queryForm.value.sheet}.xlsx`
); );
}; };
// 审核 // 审核

View File

@ -59,8 +59,9 @@
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all> <el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" /> <el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" /> <el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" /> <el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="quantity" label="数量"> <el-table-column prop="specification" label="规格" align="center"/>
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope"> <template #default="scope">
{{ scope.row.children.length > 0 ? '' : scope.row.quantity }} {{ scope.row.children.length > 0 ? '' : scope.row.quantity }}
</template> </template>

View File

@ -60,8 +60,10 @@
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all> <el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" /> <el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" /> <el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" /> <el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="quantity" label="数量"> <el-table-column prop="taxRate" label="税率" align="center" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope"> <template #default="scope">
{{ scope.row.children.length > 0 ? '' : scope.row.quantity }} {{ scope.row.children.length > 0 ? '' : scope.row.quantity }}
</template> </template>
@ -84,7 +86,7 @@
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="price" label="总价" align="center"> <el-table-column prop="price" label="总价" align="center" >
<template #default="scope"> <template #default="scope">
<!-- {{ scope.row.children.length > 0 ? scope.row.children.reduce((sum, child) => sum + child.price, 0) : scope.row.price }} --> <!-- {{ scope.row.children.length > 0 ? scope.row.children.reduce((sum, child) => sum + child.price, 0) : scope.row.price }} -->
{{ scope.row.price != 0 ? Number(scope.row.price).toFixed(2) : null }} {{ scope.row.price != 0 ? Number(scope.row.price).toFixed(2) : null }}
@ -307,7 +309,7 @@ const handleExport = () => {
versions: queryForm.value.versions, versions: queryForm.value.versions,
type: '1' type: '1'
}, },
`限价一览${queryForm.value.sheet}.xlsx` `限价一览${queryForm.value.sheet}.xlsx`
); );
}; };
// 审批 // 审批

View File

@ -93,9 +93,7 @@
</el-row> </el-row>
<el-form-item label="附图"> <el-form-item label="附图">
<file-Upload v-model="form.attachmentsImg" :file-type="['pdf', 'png', 'jpg', 'jpeg', 'gif', 'bmp']"> <file-Upload :fileSize="50" v-model="form.attachmentsImg" :file-type="['png', 'jpg', 'jpeg']"> </file-Upload>
<el-button type="primary">上传附件</el-button>
</file-Upload>
</el-form-item> </el-form-item>
<!-- 变更原因 --> <!-- 变更原因 -->
<el-form-item label="变更原因"> <el-form-item label="变更原因">
@ -114,7 +112,7 @@
<el-input v-model="form.content" type="textarea" :rows="6" placeholder="请输入内容" /> <el-input v-model="form.content" type="textarea" :rows="6" placeholder="请输入内容" />
</el-form-item> </el-form-item>
<el-form-item label="附件" prop="attachments"> <el-form-item label="附件" prop="attachments">
<file-upload v-model="form.attachments" :limit="1" :file-type="['pdf']"></file-upload> <file-upload :fileSize="50" v-model="form.attachments" :limit="1" :file-type="['pdf']"></file-upload>
</el-form-item> </el-form-item>
<el-form-item label="变更费用估算" prop="costEstimation"> <el-form-item label="变更费用估算" prop="costEstimation">
<el-input v-model="form.costEstimation" :rows="6" type="number" placeholder="请输入变更费用估算" /> <el-input v-model="form.costEstimation" :rows="6" type="number" placeholder="请输入变更费用估算" />

View File

@ -16,7 +16,7 @@
<!-- 表单区域 --> <!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden"> <el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100"> <div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">标工程清单</h3> <h3 class="text-lg font-semibold text-gray-800">标工程清单</h3>
</div> </div>
<div class="p-6"> <div class="p-6">
<el-form <el-form

View File

@ -27,19 +27,6 @@
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['materials:materials:add']"> 新增 </el-button> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['materials:materials:add']"> 新增 </el-button>
</el-col> </el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['materials:materials:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['materials:materials:remove']"
>删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['materials:materials:export']">导出 </el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
</template> </template>
@ -92,48 +79,57 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card> </el-card>
<!-- 添加或修改材料名称对话框 --> <!-- 添加或修改材料名称对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body> <el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
<el-form ref="materialsFormRef" :model="form" :rules="rules" label-width="120px"> <el-form ref="materialsFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="材料名称" prop="materialsName"> <el-row>
<el-input v-model="form.materialsName" placeholder="请输入材料名称" /> <el-col :span="12">
</el-form-item> <el-form-item label="材料名称" prop="materialsName"> <el-input v-model="form.materialsName" placeholder="请输入材料名称" /> </el-form-item
<el-form-item label="规格型号名称" prop="typeSpecificationName"> ></el-col>
<el-input v-model="form.typeSpecificationName" placeholder="请输入规格型号名称" /> <el-col :span="12">
</el-form-item> <el-form-item label="规格型号名称" prop="typeSpecificationName">
<el-form-item label="材料供应商" prop="companyId"> <el-input v-model="form.typeSpecificationName" placeholder="请输入规格型号名称" /> </el-form-item
<el-select v-model="form.companyId" clearable placeholder="请选择材料提供商"> ></el-col>
<el-option v-for="item in companyOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-col :span="12"
</el-select> ><el-form-item label="材料提供商" prop="companyId">
</el-form-item> <el-select v-model="form.companyId" clearable placeholder="请选择材料提供商">
<el-form-item label="使用部位" prop="usePart"> <el-option v-for="item in companyOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-input v-model="form.usePart" placeholder="请输入使用部位" /> </el-select> </el-form-item
</el-form-item> ></el-col>
<el-form-item label="计量单位" prop="weightId"> <el-col :span="12">
<el-input v-model="form.weightId" placeholder="请输入计量单位" /> <el-form-item label="使用部位" prop="usePart"> <el-input v-model="form.usePart" placeholder="请输入使用部位" /> </el-form-item
</el-form-item> ></el-col>
<el-form-item label="预计材料数量" prop="quantityCount"> <el-col :span="12"
<el-input v-model="form.quantityCount" placeholder="请输入预计材料数量" /> ><el-form-item label="计量单位" prop="weightId"> <el-input v-model="form.weightId" placeholder="请输入计量单位" /> </el-form-item
</el-form-item> ></el-col>
<el-form-item label="备注" prop="remark"> <el-col :span="12">
<el-input v-model="form.remark" placeholder="请输入备注" /> <el-form-item label="预计材料数量" prop="quantityCount">
</el-form-item> <el-input v-model="form.quantityCount" type="number" min="0" placeholder="请输入预计材料数量" /> </el-form-item
<el-form-item label="材料文件" prop="fileOssIdMap"> ></el-col>
<div :key="item.value" v-for="item in materials_file_type"> <el-col :span="24">
<h3>{{ item.label }}</h3> <el-form-item label="备注" prop="remark"> <el-input type="textarea" v-model="form.remark" placeholder="请输入备注" /> </el-form-item
<file-upload ></el-col>
v-model="ossIdMap[item.value]" <el-col :span="12" :key="item.value" v-for="item in materials_file_type">
:limit="1" <el-form-item label="材料文件" prop="fileOssIdMap">
:file-size="50" <div>
:file-type="['pdf']" <h3>{{ item.label }}</h3>
@update:model-value=" <file-upload
(args) => { :isShowTip="false"
handleOssUpdate(args, item.value); v-model="ossIdMap[item.value]"
} :limit="1"
" :file-size="50"
/> :file-type="['pdf']"
</div> @update:model-value="
</el-form-item> (args) => {
handleOssUpdate(args, item.value);
}
"
/>
</div> </el-form-item
></el-col>
<el-col :span="24" style="color: rgb(237 70 61)">注意:请上传pdf格式文件</el-col>
</el-row>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button> <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
@ -154,7 +150,7 @@ import { MaterialsForm, MaterialsQuery, MaterialsVO } from '@/api/materials/mate
import { useUserStoreHook } from '@/store/modules/user'; import { useUserStoreHook } from '@/store/modules/user';
import MaterialsInventoryTable from '@/views/materials/materials/component/MaterialsInventoryTable.vue'; import MaterialsInventoryTable from '@/views/materials/materials/component/MaterialsInventoryTable.vue';
import MaterialsInventoryAddDialog from '@/views/materials/materials/component/MaterialsInventoryAddDialog.vue'; import MaterialsInventoryAddDialog from '@/views/materials/materials/component/MaterialsInventoryAddDialog.vue';
import { listCompany } from '@/api/materials/company'; import { listCompany, supplierInputGet } from '@/api/materials/company';
import { CompanyVO } from '@/api/materials/company/types'; import { CompanyVO } from '@/api/materials/company/types';
import MaterialsDetailDrawer from '@/views/materials/materials/component/MaterialsDetailDrawer.vue'; import MaterialsDetailDrawer from '@/views/materials/materials/component/MaterialsDetailDrawer.vue';
@ -230,14 +226,14 @@ const getList = async () => {
/** 获取当前项目下的公司列表 */ /** 获取当前项目下的公司列表 */
const getCompanyList = async () => { const getCompanyList = async () => {
loading.value = true; loading.value = true;
const companyRes = await listCompany({ const companyRes = await supplierInputGet({
pageNum: 1,
pageSize: 1000,
projectId: currentProject.value?.id projectId: currentProject.value?.id
}); });
companyOptions.value = companyRes.rows.map((company: CompanyVO) => ({ console.log(companyRes);
companyOptions.value = companyRes.data.map((company) => ({
value: company.id, value: company.id,
label: company.companyName label: company.supplierName
})); }));
loading.value = false; loading.value = false;
}; };
@ -366,6 +362,7 @@ const listeningProject = watch(
(nid, oid) => { (nid, oid) => {
queryParams.value.projectId = nid; queryParams.value.projectId = nid;
form.value.projectId = nid; form.value.projectId = nid;
getCompanyList();
getList(); getList();
} }
); );

View File

@ -5,8 +5,8 @@
<template #header> <template #header>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> <el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="使用部位" prop="usePart"> <el-form-item label="物资名称" prop="materialsName">
<el-input v-model="queryParams.usePart" placeholder="请输入使用部位" clearable @keyup.enter="handleQuery" /> <el-input v-model="queryParams.materialsName" placeholder="请输入物资名称" clearable @keyup.enter="handleQuery" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@ -16,13 +16,20 @@
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row> </el-row>
</template> </template>
<!-- 外层表格使用expandedRowKeys控制展开状态 -->
<!-- 外层表格添加ref用于控制展开状态 --> <el-table
<el-table ref="outerTableRef" v-loading="loading" :data="materialsUseInventoryList" @expand-change="handleExpandChange" border> ref="outerTableRef"
v-loading="loading"
:data="materialsUseInventoryList"
@expand-change="handleExpandChange"
border
:expanded-row-keys="expandedRowKeys"
>
<el-table-column type="expand"> <el-table-column type="expand">
<template #default="props"> <template #default="props">
<div style="margin-left: 60px"> <div style="margin-left: 60px">
<el-table :data="materialsUseRecordList" border v-loading="loadingChild"> <!-- 子表格使用当前行的独立数据 -->
<el-table :data="getChildData(props.row.id)" border v-loading="getChildLoading(props.row.id)">
<el-table-column label="序号" align="center" type="index" width="60" /> <el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="使用数量" align="center" prop="useNumber" /> <el-table-column label="使用数量" align="center" prop="useNumber" />
<el-table-column label="剩余量" align="center" prop="residueNumber" /> <el-table-column label="剩余量" align="center" prop="residueNumber" />
@ -35,19 +42,20 @@
type="primary" type="primary"
icon="delete" icon="delete"
v-if="scope.row.ishow" v-if="scope.row.ishow"
@click="handleDelete(scope.row)" @click="handleDelete(scope.row, props.row.id)"
v-hasPermi="['materials:materialsUseRecord:remove']" v-hasPermi="['materials:materialsUseRecord:remove']"
>删除</el-button >删除</el-button
> >
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 子分页使用当前行的独立分页参数 -->
<pagination <pagination
v-show="totalChild > 0" v-show="getChildTotal(props.row.id) > 0"
:total="totalChild" :total="getChildTotal(props.row.id)"
v-model:page="queryParamsChild.pageNum" v-model:page="getChildQueryParams(props.row.id).pageNum"
v-model:limit="queryParamsChild.pageSize" v-model:limit="getChildQueryParams(props.row.id).pageSize"
@pagination="getListChild" @pagination="(page, limit) => handleChildPagination(props.row.id, page, limit)"
/> />
</div> </div>
</template> </template>
@ -104,39 +112,53 @@ import {
updateMaterialsUseRecord updateMaterialsUseRecord
} from '@/api/materials/materialsUseRecord'; } from '@/api/materials/materialsUseRecord';
import { MaterialsUseRecordVO, MaterialsUseRecordQuery, MaterialsUseRecordForm } from '@/api/materials/materialsUseRecord/types'; import { MaterialsUseRecordVO, MaterialsUseRecordQuery, MaterialsUseRecordForm } from '@/api/materials/materialsUseRecord/types';
import { getCurrentInstance, ComponentInternalInstance, onMounted, ref, reactive, toRefs, computed } from 'vue'; import { getCurrentInstance, ComponentInternalInstance, onMounted, ref, reactive, toRefs, computed, watch, WatchStopHandle } from 'vue';
import { ElFormInstance, ElTable } from 'element-plus'; import { ElFormInstance, ElTable } from 'element-plus';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { get } from 'lodash'; import { get } from 'lodash';
// 类型定义补充若项目中无全局DialogOption类型需添加 // 类型定义补充
interface DialogOption { interface DialogOption {
visible: boolean; visible: boolean;
title: string; title: string;
} }
// 子列表状态接口:为每个父行存储独立数据
interface ChildRowState {
list: MaterialsUseRecordVO[];
queryParams: MaterialsUseRecordQuery & {
pageNum: number;
pageSize: number;
inventoryId: number;
projectId?: number;
};
loading: boolean;
total: number;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore(); const userStore = useUserStore();
const currentProject = computed(() => userStore.selectedProject); const currentProject = computed(() => userStore.selectedProject);
// 核心数据响应式定义 // 核心数据响应式定义
const materialsUseRecordList = ref<MaterialsUseRecordVO[]>([]); const materialsUseInventoryList = ref<any[]>([]);
const materialsUseInventoryList = ref<any[]>([]); // 外层列表数据类型可根据实际接口返回调整
const buttonLoading = ref(false); const buttonLoading = ref(false);
const loading = ref(true); const loading = ref(true);
const loadingChild = ref(true);
const showSearch = ref(true); const showSearch = ref(true);
const ids = ref<Array<string | number>>([]); const ids = ref<Array<string | number>>([]);
const single = ref(true); const single = ref(true);
const multiple = ref(true); const multiple = ref(true);
const total = ref(0); const total = ref(0);
const totalChild = ref(0); const expandedRowKeys = ref<number[]>([]); // 控制展开行的ID集合
// 子列表状态管理使用对象存储不同父行的子数据key为inventoryId
const childRowStates = ref<Record<number, ChildRowState>>({});
// 组件Ref定义 // 组件Ref定义
const queryFormRef = ref<ElFormInstance>(); const queryFormRef = ref<ElFormInstance>();
const materialsUseRecordFormRef = ref<ElFormInstance>(); const materialsUseRecordFormRef = ref<ElFormInstance>();
const outerTableRef = ref<InstanceType<typeof ElTable> | undefined>(undefined); // 外层表格Ref控制展开 const outerTableRef = ref<InstanceType<typeof ElTable> | undefined>(undefined);
const currentExpandInventoryId = ref<number | undefined>(undefined); // 存储当前展开行的inventoryId const currentOperateInventoryId = ref<number | undefined>(undefined); // 当前操作的父行ID
// 对话框与表单数据 // 对话框与表单数据
const dialog = reactive<DialogOption>({ const dialog = reactive<DialogOption>({
@ -160,72 +182,132 @@ const data = reactive({
pageSize: 10, pageSize: 10,
projectId: currentProject.value?.id, projectId: currentProject.value?.id,
outPut: 1, outPut: 1,
usePart: undefined // 补充usePart字段与表单prop对应 materialsName: undefined,
} as MaterialsUseRecordQuery & { outPut?: number; usePart?: string }, usePart: undefined
queryParamsChild: { } as MaterialsUseRecordQuery & {
pageNum: 1, outPut?: number;
pageSize: 10, materialsName?: string;
inventoryId: undefined, usePart?: string;
usePart: undefined, },
useNumber: undefined,
residueNumber: undefined
} as MaterialsUseRecordQuery,
rules: { rules: {
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }],
inventoryId: [{ required: true, message: '库存ID不能为空', trigger: 'blur' }], inventoryId: [{ required: true, message: '库存ID不能为空', trigger: 'blur' }],
usePart: [{ required: true, message: '使用部位不能为空', trigger: 'blur' }], usePart: [{ required: true, message: '使用部位不能为空', trigger: 'blur' }],
useNumber: [{ required: true, message: '使用数量不能为空', trigger: 'blur' }], useNumber: [{ required: true, message: '使用数量不能为空', trigger: 'blur' }]
residueNumber: [{ required: true, message: '剩余量不能为空', trigger: 'blur' }]
} }
}); });
const { queryParams, form, rules, queryParamsChild } = toRefs(data); const { form, rules } = toRefs(data);
const queryParams = ref<typeof data.queryParams>({ ...data.queryParams });
/** 查询材料使用登记列表(外层列表) */ // ------------------------------ 子列表状态工具函数 ------------------------------
/** 获取或初始化指定父行的子列表状态 */
const getOrInitChildState = (inventoryId: number): ChildRowState => {
if (!childRowStates.value[inventoryId]) {
childRowStates.value[inventoryId] = {
list: [],
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
inventoryId,
projectId: currentProject.value?.id
}
};
}
return childRowStates.value[inventoryId];
};
/** 获取指定父行的子列表数据 */
const getChildData = (inventoryId: number): MaterialsUseRecordVO[] => {
return getOrInitChildState(inventoryId).list;
};
/** 获取指定父行的子列表加载状态 */
const getChildLoading = (inventoryId: number): boolean => {
return getOrInitChildState(inventoryId).loading;
};
/** 获取指定父行的子列表总条数 */
const getChildTotal = (inventoryId: number): number => {
return getOrInitChildState(inventoryId).total;
};
/** 获取指定父行的子分页参数 */
const getChildQueryParams = (inventoryId: number) => {
return getOrInitChildState(inventoryId).queryParams;
};
// ------------------------------ 核心业务逻辑 ------------------------------
/** 查询外层列表数据 */
const getList = async () => { const getList = async () => {
loading.value = true; loading.value = true;
try { try {
const res = await listMaterialsUseInventory(queryParams.value); const res = await listMaterialsUseInventory(queryParams.value);
materialsUseInventoryList.value = res.rows; materialsUseInventoryList.value = res.rows;
total.value = res.total; total.value = res.total;
// 保持已展开行的状态
if (expandedRowKeys.value.length > 0 && outerTableRef.value) {
expandedRowKeys.value.forEach((id) => {
const targetRow = materialsUseInventoryList.value.find((item) => item.id === id);
targetRow && outerTableRef.value!.toggleRowExpansion(targetRow, true);
});
}
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
/** 处理外层表格展开/折叠记录当前展开行的inventoryId */ /** 处理外层表格展开/折叠 */
const handleExpandChange = (row: any) => { const handleExpandChange = async (row: any, expandedRows: any[]) => {
currentExpandInventoryId.value = row.id; // 记录展开行ID const inventoryId = row.id;
queryParamsChild.value.inventoryId = row.id; // 更新展开行ID集合
getListChild(); expandedRowKeys.value = expandedRows.map((item) => item.id);
// 展开时加载子数据(仅首次加载)
const childState = getOrInitChildState(inventoryId);
if (expandedRows.some((item) => item.id === inventoryId) && childState.list.length === 0) {
await getListChild(inventoryId);
}
}; };
/** 查询材料子级登记列表(内层列表) */ /** 查询指定父行的子列表数据 */
const getListChild = async () => { const getListChild = async (inventoryId: number) => {
loadingChild.value = true; const childState = getOrInitChildState(inventoryId);
childState.loading = true;
try { try {
const res = await listMaterialsUseRecord(queryParamsChild.value); const res = await listMaterialsUseRecord(childState.queryParams);
materialsUseRecordList.value = res.rows; childState.list = res.rows;
// 控制首行删除按钮显示 // 控制首行删除按钮显示
if (res.rows.length > 0) { if (childState.list.length > 0) {
materialsUseRecordList.value[0].ishow = true; childState.list[0].ishow = true;
} }
totalChild.value = res.total; childState.total = res.total;
} finally { } finally {
loadingChild.value = false; childState.loading = false;
} }
}; };
/** 处理子列表分页变化 */
const handleChildPagination = async (inventoryId: number, page: number, limit: number) => {
const childState = getOrInitChildState(inventoryId);
childState.queryParams.pageNum = page;
childState.queryParams.pageSize = limit;
await getListChild(inventoryId);
};
/** 取消按钮 */ /** 取消按钮 */
const cancel = () => { const cancel = () => {
reset(); reset();
dialog.visible = false; dialog.visible = false;
currentOperateInventoryId.value = undefined;
}; };
/** 表单重置 */ /** 表单重置 */
const reset = () => { const reset = () => {
form.value = { ...initFormData }; form.value = { ...initFormData, projectId: currentProject.value?.id };
materialsUseRecordFormRef.value?.resetFields(); materialsUseRecordFormRef.value?.resetFields();
}; };
@ -238,10 +320,16 @@ const handleQuery = () => {
/** 重置按钮操作 */ /** 重置按钮操作 */
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value?.resetFields(); queryFormRef.value?.resetFields();
queryParams.value = {
...data.queryParams,
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id
};
handleQuery(); handleQuery();
}; };
/** 多选框选中数据(当前模板未使用,保留原逻辑) */ /** 多选框选中数据处理 */
const handleSelectionChange = (selection: MaterialsUseRecordVO[]) => { const handleSelectionChange = (selection: MaterialsUseRecordVO[]) => {
ids.value = selection.map((item) => item.id); ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1; single.value = selection.length !== 1;
@ -252,75 +340,85 @@ const handleSelectionChange = (selection: MaterialsUseRecordVO[]) => {
const handleAdd = async (row: any) => { const handleAdd = async (row: any) => {
reset(); reset();
form.value.inventoryId = row.id; form.value.inventoryId = row.id;
currentOperateInventoryId.value = row.id;
dialog.visible = true; dialog.visible = true;
dialog.title = '添加材料使用登记'; dialog.title = '添加材料使用登记';
}; };
/** 修改按钮操作(当前模板未使用,保留原逻辑) */ /** 修改按钮操作 */
const handleUpdate = async (row?: MaterialsUseRecordVO) => { const handleUpdate = async (row?: MaterialsUseRecordVO) => {
if (!row && ids.value.length === 0) return;
reset(); reset();
const _id = row?.id || ids.value[0]; const _id = row?.id || ids.value[0];
const res = await getMaterialsUseRecord(_id); const res = await getMaterialsUseRecord(_id);
Object.assign(form.value, res.data); Object.assign(form.value, res.data);
currentOperateInventoryId.value = form.value.inventoryId;
dialog.visible = true; dialog.visible = true;
dialog.title = '修改材料使用登记'; dialog.title = '修改材料使用登记';
}; };
/** 提交按钮:核心优化逻辑(刷新外层列表+保留展开状态) */ /** 提交表单操作 */
const submitForm = () => { const submitForm = () => {
materialsUseRecordFormRef.value?.validate(async (valid: boolean) => { materialsUseRecordFormRef.value?.validate(async (valid: boolean) => {
if (valid) { if (!valid || !currentOperateInventoryId.value) return;
buttonLoading.value = true;
try { buttonLoading.value = true;
// 1. 执行添加/修改接口请求 try {
await addMaterialsUseRecord(form.value); // 执行添加或修改操作
// 2. 刷新外层列表(确保外层数据同步,如剩余量) const apiFn = form.value.id ? updateMaterialsUseRecord : addMaterialsUseRecord;
await getList(); await apiFn(form.value);
// 3. 刷新当前展开行的子列表
await getListChild(); // 刷新外层列表和当前操作行的子列表
// 4. 恢复展开状态根据记录的inventoryId重新展开行 await getList();
if (currentExpandInventoryId.value && outerTableRef.value) { await getListChild(currentOperateInventoryId.value);
const targetRow = materialsUseInventoryList.value.find((item) => item.id === currentExpandInventoryId.value);
if (targetRow) { proxy?.$modal.msgSuccess('操作成功');
outerTableRef.value.toggleRowExpansion(targetRow, true); dialog.visible = false;
} } finally {
} buttonLoading.value = false;
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
} finally {
buttonLoading.value = false;
}
} }
}); });
}; };
/** 删除按钮操作 */ /** 删除操作 */
const handleDelete = async (row?: MaterialsUseRecordVO) => { const handleDelete = async (row: MaterialsUseRecordVO, inventoryId: number) => {
const _ids = row?.id || ids.value;
try { try {
await proxy?.$modal.confirm(`是否确认删除材料使用登记编号为"${_ids}"的数据项`); await proxy?.$modal.confirm(`是否确认删除该记录`);
await delMaterialsUseRecord(_ids); await delMaterialsUseRecord(row.id);
await getList(); await getList();
await getListChild(); await getListChild(inventoryId);
if (currentExpandInventoryId.value && outerTableRef.value) {
const targetRow = materialsUseInventoryList.value.find((item) => item.id === currentExpandInventoryId.value);
if (targetRow) {
outerTableRef.value.toggleRowExpansion(targetRow, true);
}
}
proxy?.$modal.msgSuccess('删除成功'); proxy?.$modal.msgSuccess('删除成功');
await getListChild(); } catch (error) {
} finally { // 取消删除时不做处理
loading.value = false;
} }
}; };
/** 导出按钮操作(保留原逻辑) */ /** 导出操作 */
const handleExport = () => { const handleExport = () => {
proxy?.download('materials/materialsUseRecord/export', { ...queryParams.value }, `materialsUseRecord_${new Date().getTime()}.xlsx`); proxy?.download('materials/materialsUseRecord/export', { ...queryParams.value }, `materialsUseRecord_${new Date().getTime()}.xlsx`);
}; };
/** 页面挂载时加载外层列表 */ // 监听项目ID变化刷新数据
const listeningProject: WatchStopHandle = watch(
() => currentProject.value?.id,
(newId) => {
queryParams.value.projectId = newId;
form.value.projectId = newId;
// 更新所有子列表的项目ID
Object.values(childRowStates.value).forEach((state) => {
state.queryParams.projectId = newId;
});
getList();
}
);
// 页面卸载时清理监听
onUnmounted(() => {
listeningProject();
});
// 页面挂载时加载数据
onMounted(() => { onMounted(() => {
getList(); getList();
}); });

View File

@ -0,0 +1,619 @@
<template>
<div class="overall-plan-material-supply">
<el-card shadow="always">
<template #header>
<el-row :gutter="10" class="mb8">
<el-form :inline="true">
<el-form-item v-if="state.masterData.status == 'draft'">
<el-button type="primary" icon="edit" @click="clickApprovalSheet1()">审批</el-button>
</el-form-item>
<el-form-item v-if="state.masterData.status == 'waiting' || state.masterData.status == 'finish'">
<el-button icon="view" @click="lookApprovalFlow()" type="warning">查看流程</el-button>
</el-form-item>
<el-form-item>
<file-upload
upload-url="/design/totalsupplyplan/import"
v-model="file"
:limit="1"
:file-type="['xls', 'xlsx']"
:on-upload-success="handleSuccess"
>
<el-button :disabled="state.masterData.status == 'finish'" type="primary" plain icon="upload">导入</el-button>
</file-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Download" @click="handleExport">导出</el-button>
</el-form-item>
<el-form-item>
<el-button icon="Finished" @click="editApprovalSheet()" type="success" :disabled="state.masterData.status == 'finish'"
>一键全部保存</el-button
>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="toggleExpandAll">{{ isExpandAll ? '一键收起' : '一键展开' }}</el-button>
</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"
ref="tableRef"
stripe
style="width: 100%; margin-bottom: 20px; height: calc(100vh - 230px)"
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" />
<el-table-column prop="unit" label="单位" />
<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.lazy="row.brand"
:disabled="state.masterData.status != 'draft'"
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.lazy="row.texture"
:disabled="state.masterData.status != 'draft'"
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.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.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.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.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>
<!-- 编辑弹窗 -->
<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">
<el-col :span="12">
<el-form-item label="批次号" prop="batchNumber">
<el-input disabled v-model="formData.batchNumber" placeholder="请输入批次号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="品牌" prop="brand">
<el-input v-model="formData.brand" placeholder="请输入品牌" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="材质" prop="texture">
<el-input v-model="formData.texture" placeholder="请输入材质" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="质量标准" prop="qualityStandard">
<el-input v-model="formData.qualityStandard" placeholder="请输入质量标准" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="使用部位" prop="partUsed">
<el-input v-model="formData.partUsed" placeholder="请输入使用部位" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="交货地点" prop="deliveryPoints">
<el-input v-model="formData.deliveryPoints" placeholder="请输入交货地点" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预计使用日期" prop="dateService">
<el-date-picker v-model="formData.dateService" type="date" placeholder="选择预计使用日期" format="YYYY-MM-DD" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注信息" type="textarea" rows="3" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="billofQuantities">
import { ref, reactive, onMounted, computed, getCurrentInstance, watch, onUnmounted } from 'vue';
import { ElMessage, ElLoading } from 'element-plus';
import {
obtainMasterDataList,
totalsupplyplan,
totalSupplyplanDetails,
materialChangeSupplyplan,
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: [], // 初始只存放根节点
queryForm: {
projectId: currentProject.value?.id,
versions: '',
sheet: '',
pageSize: 20,
pageNum: 1
},
loading: {
versions: false,
sheets: false,
list: false
},
masterData: {}
});
// 表单数据
const formData = reactive({
batchNumber: '',
brand: '',
compileDate: '',
dateService: '',
deliveryPoints: '',
id: undefined,
name: '',
num: '',
partUsed: '',
planNumber: '',
projectId: undefined,
qualityStandard: '',
quantity: 0,
remark: '',
specification: '',
status: '',
texture: '',
unit: ''
});
// 表单验证规则
const formRules = reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 100, message: '名称长度不能超过100个字符', trigger: 'blur' }
],
num: [
{ required: true, message: '请输入编号', trigger: 'blur' },
{ max: 50, message: '编号长度不能超过50个字符', trigger: 'blur' }
],
quantity: [
{ required: true, message: '请输入数量', trigger: 'blur' },
{ type: 'number', min: 0, message: '数量不能为负数', trigger: 'blur' }
],
compileDate: [{ required: true, message: '请选择编制日期', trigger: 'change' }]
});
/**
* 本地数据懒加载实现:从完整数据中筛选子节点
*/
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.id);
// 标记子节点是否有子节点
const formattedChildren = childNodes.map((node) => ({
...node,
hasChildren: fullTreeData.value.some((child) => child.pid === node.id)
}));
// 缓存子节点到当前行
row.children = formattedChildren;
resolve(formattedChildren);
} catch (error) {
ElMessage.error(`加载子节点异常:${error.message}`);
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.sid === node.id);
const formattedChildren = children.map((child) => ({
...child,
hasChildren: fullTreeData.value.some((c) => c.sid === child.id)
}));
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;
if (!masterData[0]?.id) {
state.tableData = [];
fullTreeData.value = [];
return;
}
state.masterData = masterData[0];
// 获取完整供应计划数据
const supplyPlanRes = await totalsupplyplan({ id: masterData[0].id });
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.id)
}));
// 初始化可编辑数据映射
editDataMap.value.clear();
allData.forEach((item) => {
if (!item.hasChildren) {
// 只关注叶子节点
editDataMap.value.set(item.id, { ...item });
}
});
} catch (error) {
ElMessage.error(`数据加载失败:${error.message}`);
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 || {};
// 清空表单并赋值
Object.keys(formData).forEach((key) => {
formData[key] = undefined;
});
// 处理日期格式
const formatDate = (date) => {
if (!date) return '';
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')}`;
};
Object.assign(formData, {
...detailData,
compileDate: formatDate(detailData.compileDate),
dateService: formatDate(detailData.dateService)
});
} else {
ElMessage.error(`获取详情失败: ${result?.msg || '未知错误'}`);
}
} catch (err) {
ElMessage.error(`接口请求失败: ${err.message}`);
console.error('详情接口错误:', err);
} finally {
state.loading.list = false;
}
}
/**
* 一键保存修改
*/
const editApprovalSheet = async () => {
state.loading.list = true;
try {
// 只提交修改过的数据从Map中获取
const modifiedData = Array.from(editDataMap.value.values());
if (modifiedData.length === 0) {
ElMessage.info('没有数据需要修改');
return;
}
await totalSupplyplanBatchEdit(modifiedData);
ElMessage.success('修改成功');
getMasterDataList(); // 重新加载数据
} catch (error) {
ElMessage.error(`保存失败:${error.message}`);
} finally {
state.loading.list = false;
}
};
/**
* 提交表单
*/
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;
});
};
/**
* 审批
*/
function clickApprovalSheet1() {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/approval/overallPlanMaterialSupply/indexEdit`,
query: { id: state.masterData.id, type: 'update' }
});
}
/**
* 查看流程
*/
function lookApprovalFlow() {
proxy.$router.push({
path: `/approval/overallPlanMaterialSupply/indexEdit`,
query: { id: state.masterData.id, type: 'view' }
});
}
/**
* 监听项目变化
*/
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;
}
.space-y-4 > .el-row {
margin-bottom: 16px;
}
.space-y-4 > .el-row:last-child {
margin-bottom: 0;
}
</style>

View File

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

View File

@ -51,9 +51,10 @@
size="small" size="small"
icon="Plus" icon="Plus"
type="primary" type="primary"
v-if="scope.row.purchaseSubmission == '0'"
link link
@click="handleAddSon(scope.row)" @click="handleAddSon(scope.row)"
>添加</el-button >提交</el-button
> >
<el-button type="primary" v-hasPermi="['cailiaoshebei:physicalsupply:edit']" size="small" icon="Edit" link @click="handleEdit(scope.row)" <el-button type="primary" v-hasPermi="['cailiaoshebei:physicalsupply:edit']" size="small" icon="Edit" link @click="handleEdit(scope.row)"
>修改</el-button >修改</el-button
@ -113,7 +114,7 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="运算周期(天)" prop="executionCycle"> <el-form-item label="运算周期(天)" prop="executionCycle">
<el-input v-model.number="formData.executionCycle" placeholder="请输入运算周期" type="number"></el-input> <el-input v-model.number="formData.executionCycle" min="0" placeholder="请输入运算周期" type="number"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -121,12 +122,12 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="安装量" prop="installationQuantity"> <el-form-item label="安装量" prop="installationQuantity">
<el-input v-model="formData.installationQuantity" placeholder="请输入安装量"></el-input> <el-input v-model="formData.installationQuantity" min="0" type="number" placeholder="请输入安装量"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="安装比例" prop="installationRatio"> <el-form-item label="安装比例" prop="installationRatio">
<el-input v-model="formData.installationRatio" placeholder="请输入安装比例" suffix="%"></el-input> <el-input v-model="formData.installationRatio" min="0" type="number" placeholder="请输入安装比例" suffix="%"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@ -296,22 +297,7 @@ const handleSearch = () => {
// 刷新数据 // 刷新数据
const refreshData = () => { const refreshData = () => {
fetchData(); fetchData();
ElMessage.success('数据已刷新');
}; };
// 分页大小改变
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
fetchData();
};
// 当前页改变
const handleCurrentChange = (val) => {
currentPage.value = val;
fetchData();
};
// 新增 // 新增
const handleAdd = () => { const handleAdd = () => {
dialogType.value = 'add'; dialogType.value = 'add';
@ -476,6 +462,17 @@ const jumpRouter = (row) => {
} }
}); });
}; };
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value?.id,
(nid, oid) => {
fetchData();
}
);
onUnmounted(() => {
listeningProject();
});
// 初始化页面 // 初始化页面
onMounted(() => { onMounted(() => {
fetchData(); fetchData();

View File

@ -30,9 +30,7 @@
:row-class-name="tableRowClassName" :row-class-name="tableRowClassName"
> >
<!-- 基础信息列 --> <!-- 基础信息列 -->
<el-table-column prop="id" label="ID" width="180" align="center"></el-table-column>
<el-table-column prop="batch" label="批次" align="center"></el-table-column> <el-table-column prop="batch" label="批次" align="center"></el-table-column>
<el-table-column prop="physicalsupplyId" label="使用情况ID" width="180" align="center"></el-table-column>
<!-- 时间相关列 --> <!-- 时间相关列 -->
<el-table-column prop="issuanceTime" label="联系单下达时间" min-width="160" align="center"> <el-table-column prop="issuanceTime" label="联系单下达时间" min-width="160" align="center">
<template #default="scope"> <template #default="scope">

View File

@ -27,9 +27,9 @@
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:enterRoad:add']">新增</el-button> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:enterRoad:add']">新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button> <el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button>
</el-col> </el-col> -->
<el-col :span="1.5"> <el-col :span="1.5">
<el-upload <el-upload
ref="uploadRef" ref="uploadRef"

View File

@ -36,9 +36,9 @@
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:landBlock:add']">新增</el-button> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:landBlock:add']">新增</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <!-- <el-col :span="1.5">
<el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button> <el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button>
</el-col> </el-col> -->
<el-col :span="1.5"> <el-col :span="1.5">
<el-upload ref="uploadRef" class="upload-demo" :http-request="handleImport" :show-file-list="false"> <el-upload ref="uploadRef" class="upload-demo" :http-request="handleImport" :show-file-list="false">
<template #trigger> <template #trigger>

View File

@ -58,11 +58,13 @@
</el-card> </el-card>
</transition> </transition>
<el-card shadow="never" class="mb8"> <el-card shadow="never" class="mb8">
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all> <el-table ref="tableAllRef" v-loading="loading" :data="tableData" row-key="id" border lazy :expand-row-keys="expandRowKeys">
<el-table-column prop="num" label="编号" /> <el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" /> <el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" /> <el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="quantity" label="数量"> <el-table-column prop="taxRate" label="税率" align="center" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope"> <template #default="scope">
{{ scope.row.children.length > 0 ? '' : scope.row.quantity }} {{ scope.row.children.length > 0 ? '' : scope.row.quantity }}
</template> </template>
@ -111,64 +113,141 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
import { getCurrentInstance, ComponentInternalInstance } from 'vue';
import { ElMessage } from 'element-plus';
import { useUserStoreHook } from '@/store/modules/user'; import { useUserStoreHook } from '@/store/modules/user';
import { obtainAllVersionNumbers, sheetList, getTableList, updatePrice, importExcelFile } from '@/api/tender/index'; import { obtainAllVersionNumbers, sheetList, getTableList, updatePrice, importExcelFile } from '@/api/tender/index';
// 类型定义
interface QueryForm {
versions: string;
sheet: string;
}
interface TableRow {
id: string | number;
num?: string;
name?: string;
unit?: string;
taxRate?: string | number;
specification?: string;
quantity?: number;
unitPrice?: number;
price?: number;
remark?: string;
children?: TableRow[];
}
interface VersionItem {
versions: string;
status: string;
id?: string | number;
[key: string]: any;
}
// 实例与状态初始化
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook(); const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject); const currentProject = computed(() => userStore.selectedProject);
// 标签页配置
const tabList = [ const tabList = [
{ { label: '招采工程量清单', value: '2' },
label: '招采工程量清单', { label: '物资设备清单', value: '3' }
value: '2'
},
{
label: '物资设备清单',
value: '3'
}
]; ];
const queryForm = ref({
versions: '',
sheet: ''
});
const versionsData = ref<any>({});
// 响应式状态
const queryForm = ref<QueryForm>({ versions: '', sheet: '' });
const versionsData = ref<VersionItem>({});
const activeTab = ref('2'); const activeTab = ref('2');
const sheets = ref([]); const sheets = ref<string[]>([]);
const options = ref([]); const options = ref<VersionItem[]>([]);
const tableData = ref([]); const tableData = ref<TableRow[]>([]);
const tableRef = ref(); const tableAllRef = ref<any>(null);
const uploadRef = ref<any>(null);
const isExpandAll = ref(true); const isExpandAll = ref(true);
const loading = ref(false); const loading = ref(false);
const versionMap = new Map(); const versionMap = new Map<string, VersionItem>();
const expandRowKeys = ref<string[] | number[]>([]); // 控制表格展开的行ID
const modifyPrice = new Map<string | number, TableRow>();
// 切换tab // 切换标签页
const handleTabChange = (tab: string) => { const handleTabChange = (tab: string) => {
activeTab.value = tab; activeTab.value = tab;
tableData.value = []; tableData.value = [];
versionsData.value = {}; versionsData.value = {};
isExpandAll.value = true;
expandRowKeys.value = [];
getVersionNums(); getVersionNums();
}; };
//切换版本
const changeVersions = (value) => { // 切换版本号
versionsData.value = options.value.find((item) => item.versions == value); const changeVersions = (value: string) => {
versionsData.value = options.value.find((item) => item.versions === value) || {};
getSheetName(); getSheetName();
}; };
//切换表格
const changeSheet = (val: any) => { // 切换表名
const changeSheet = (val: string) => {
getTableData(); getTableData();
}; };
//展开树
const toggleExpandAll = () => { // 一键展开/收起
const toggleExpandAll = async () => {
isExpandAll.value = !isExpandAll.value; isExpandAll.value = !isExpandAll.value;
console.log(isExpandAll.value); if (!tableData.value.length || !tableAllRef.value) return;
tableData.value.forEach((row) => {
tableRef.value.toggleRowExpansion(row, isExpandAll.value); if (isExpandAll.value) {
// 收集所有已加载节点的ID
const allExpandIds = collectAllNodeIds(tableData.value);
expandRowKeys.value = allExpandIds;
// 处理懒加载节点
await loadAndExpandAllLazyNodes();
} else {
// 全部收起
expandRowKeys.value = [];
}
};
// 辅助函数递归收集所有节点ID包括子节点
const collectAllNodeIds = (nodes: TableRow[]): (string | number)[] => {
let ids: (string | number)[] = [];
nodes.forEach((node) => {
ids.push(node.id);
if (node.children && node.children.length > 0) {
ids = [...ids, ...collectAllNodeIds(node.children)];
}
}); });
return ids;
}; };
//获取版本号 // 辅助函数:加载并展开所有懒加载子节点
const loadAndExpandAllLazyNodes = async () => {
if (!tableAllRef.value) return;
try {
// 获取所有可能有子节点的父节点
const parentNodes = tableData.value.filter((node) => node.hasChildren || (node.children && node.children.length === 0));
if (!parentNodes.length) return;
// 逐个加载并展开子节点
for (const parent of parentNodes) {
if (tableAllRef.value.loadOrToggleRow) {
await tableAllRef.value.loadOrToggleRow(parent);
}
await nextTick();
// 递归展开子节点的子节点
if (parent.children && parent.children.length > 0) {
const childIds = collectAllNodeIds(parent.children);
expandRowKeys.value = [...new Set([...expandRowKeys.value, ...childIds])];
}
}
} catch (error) {
console.error('加载并展开懒加载节点失败:', error);
} finally {
}
};
// 获取版本号列表
const getVersionNums = async () => { const getVersionNums = async () => {
try { try {
const params = { const params = {
@ -179,93 +258,126 @@ const getVersionNums = async () => {
}; };
const res = await obtainAllVersionNumbers(params); const res = await obtainAllVersionNumbers(params);
if (res.code == 200) { if (res.code === 200) {
options.value = res.data; options.value = res.data;
if (res.data.length > 0) { versionMap.clear();
res.data.forEach((item: any) => { options.value.forEach((item) => versionMap.set(item.versions, item));
versionMap.set(item.versions, item);
}); if (options.value.length > 0) {
queryForm.value.versions = res.data[0].versions; queryForm.value.versions = options.value[0].versions;
versionsData.value = options.value.find((item) => item.versions == queryForm.value.versions); versionsData.value = options.value[0];
getSheetName(); getSheetName();
} else { } else {
queryForm.value.versions = ''; queryForm.value.versions = '';
// getSheetName(); sheets.value = [];
tableData.value = [];
expandRowKeys.value = [];
} }
} }
} catch (error) { } catch (error) {
console.log(error); console.error('获取版本号失败:', error);
ElMessage.error('获取版本号失败,请刷新重试');
} }
}; };
//获取表名
// 获取表名列表
const getSheetName = async () => { const getSheetName = async () => {
try { try {
const params = { const params = {
projectId: currentProject.value?.id, projectId: currentProject.value?.id,
versions: queryForm.value.versions versions: queryForm.value.versions
}; };
const res = await sheetList(params); const res = await sheetList(params);
if (res.code == 200) { if (res.code === 200) {
sheets.value = res.data; sheets.value = res.data;
if (res.data.length > 0) { if (sheets.value.length > 0) {
queryForm.value.sheet = res.data[0]; queryForm.value.sheet = sheets.value[0];
} else { } else {
queryForm.value.sheet = ''; queryForm.value.sheet = '';
tableData.value = [];
expandRowKeys.value = [];
} }
getTableData(); getTableData();
} }
} catch (error) { } catch (error) {
console.log(error); console.error('获取表名失败:', error);
ElMessage.error('获取表名失败,请刷新重试');
} }
}; };
//获取表格数据
// 获取表格数据
const getTableData = async () => { const getTableData = async () => {
try { try {
loading.value = true; loading.value = true;
const params = { const params = {
projectId: currentProject.value?.id, projectId: currentProject.value?.id,
versions: queryForm.value.versions, versions: queryForm.value.versions,
sheet: queryForm.value.sheet, sheet: queryForm.value.sheet,
type: activeTab.value type: activeTab.value
}; };
const res = await getTableList(params); const res = await getTableList(params);
if (res.code == 200) { if (res.code === 200) {
tableData.value = res.data; tableData.value = res.data;
if (isExpandAll.value) {
// 展开全部
isExpandAll.value = false;
toggleExpandAll();
}
modifyPrice.clear();
} }
} catch (error) { } catch (error) {
console.log(error); console.error('获取表格数据失败:', error);
ElMessage.error('获取表格数据失败,请刷新重试');
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; };
//导入
const importExcel = (options: any): any => { // 导入Excel
let formData = new FormData(); const importExcel = (options: any): void => {
if (!queryForm.value.versions || (activeTab.value !== '3' && !queryForm.value.sheet)) {
ElMessage.warning('请先选择版本号和表名(物资设备清单无需选择表名)');
return;
}
const formData = new FormData();
formData.append('file', options.file); formData.append('file', options.file);
loading.value = true; loading.value = true;
importExcelFile( importExcelFile(
{ projectId: currentProject.value?.id, sheet: queryForm.value.sheet, versions: queryForm.value.versions, type: activeTab.value }, {
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet,
versions: queryForm.value.versions,
type: activeTab.value
},
formData formData
) )
.then((res) => { .then((res) => {
const { code } = res; if (res.code === 200) {
if (code == 200) { proxy?.$modal.msgSuccess(res.msg || '导入成功');
proxy.$modal.msgSuccess(res.msg || '导入成功');
getTableData(); getTableData();
} else { } else {
proxy.$modal.msgError(res.msg || '导入失败'); proxy?.$modal.msgError(res.msg || '导入失败');
} }
}) })
.catch((err) => { .catch((err) => {
proxy.$modal.msgError(err.msg || '导入失败'); proxy?.$modal.msgError(err.msg || '导入失败');
}) })
.finally(() => { .finally(() => {
loading.value = false; loading.value = false;
}); });
}; };
//导出
// 导出Excel
const handleExport = () => { const handleExport = () => {
if (!queryForm.value.versions || (activeTab.value !== '3' && !queryForm.value.sheet)) {
ElMessage.warning('请先选择版本号和表名(物资设备清单无需选择表名)');
return;
}
proxy?.download( proxy?.download(
'/tender/tenderPlanLimitList/export', '/tender/tenderPlanLimitList/export',
{ {
@ -274,90 +386,108 @@ const handleExport = () => {
versions: queryForm.value.versions, versions: queryForm.value.versions,
type: activeTab.value type: activeTab.value
}, },
`招标一览${queryForm.value.sheet}.xlsx` `招标一览_${queryForm.value.sheet || (activeTab.value === '3' ? '物资设备清单' : '')}_v${queryForm.value.versions}.xlsx`
); );
}; };
const modifyPrice = new Map();
const changePrice = (row: any) => { // 记录待修改价格的行
modifyPrice.set(row.id, row); const changePrice = (row: TableRow) => {
// if (!row.unitPrice) { if (row.id && row.unitPrice !== undefined) {
// modifyPrice.delete(row.id); modifyPrice.set(row.id, row);
// } } else if (row.id) {
}; modifyPrice.delete(row.id);
//修改单价
const handleSave = (row?: any, type?: any) => {
try {
if (type == 'single') {
loading.value = true;
const list = [{ ...row, type: activeTab.value }];
updatePrice(list).then((res) => {
if (res.code == 200) {
ElMessage({
message: '修改成功',
type: 'success'
});
getTableData();
}
});
}
if (type == 'all') {
loading.value = true;
const list = [];
modifyPrice.forEach((item) => {
list.push({ ...item, type: activeTab.value });
});
updatePrice(list).then((res) => {
if (res.code == 200) {
ElMessage({
message: '修改成功',
type: 'success'
});
getTableData();
}
});
}
} catch (error) {
ElMessage({
message: '修改失败',
type: 'error'
});
} finally {
loading.value = false;
} }
}; };
/** 审核按钮操作 */ // 保存价格修改
const handleAudit = async () => { const handleSave = (row?: TableRow, type?: 'single' | 'all') => {
let id = versionMap.get(queryForm.value.versions).id; if (versionsData.value.status !== 'draft') {
console.log(id); ElMessage.warning('仅草稿状态可修改单价');
if (activeTab.value == '2') { return;
}
let updateList: TableRow[] = [];
if (type === 'single' && row?.id) {
updateList = [{ ...row, type: activeTab.value }];
} else if (type === 'all') {
updateList = Array.from(modifyPrice.values()).map((item) => ({ ...item, type: activeTab.value }));
if (updateList.length === 0) {
ElMessage.warning('暂无待修改的单价数据');
return;
}
}
loading.value = true;
updatePrice(updateList)
.then((res) => {
if (res.code === 200) {
ElMessage.success('修改成功');
getTableData();
modifyPrice.clear();
} else {
ElMessage.error(res.msg || '修改失败');
}
})
.catch((err) => {
ElMessage.error(err.msg || '修改失败');
})
.finally(() => {
loading.value = false;
});
};
// 审核/查看流程
const handleAudit = () => {
const versionItem = versionMap.get(queryForm.value.versions);
if (!versionItem?.id) {
ElMessage.warning('请先选择有效的版本号');
return;
}
if (activeTab.value === '2') {
proxy?.$tab.openPage('/approval/tenderBidd/indexEdit', '招采工程量清单审核', { proxy?.$tab.openPage('/approval/tenderBidd/indexEdit', '招采工程量清单审核', {
id: id, id: versionItem.id,
type: 'update' type: 'update'
}); });
} } else if (activeTab.value === '3') {
if (activeTab.value == '3') {
proxy?.$tab.openPage('/approval/tenderBidd/indexEdit2', '物资设备清单审核', { proxy?.$tab.openPage('/approval/tenderBidd/indexEdit2', '物资设备清单审核', {
id: id, id: versionItem.id,
type: 'update' type: 'update'
}); });
} }
}; };
//监听项目id刷新数据 // 监听项目切换
const listeningProject = watch( const listeningProject = watch(
() => currentProject.value?.id, () => currentProject.value?.id,
(nid, oid) => { (newId, oldId) => {
getVersionNums(); if (newId && newId !== oldId) {
getVersionNums();
} else {
tableData.value = [];
options.value = [];
sheets.value = [];
queryForm.value = { versions: '', sheet: '' };
versionsData.value = {};
expandRowKeys.value = [];
versionMap.clear();
modifyPrice.clear();
}
} }
); );
onUnmounted(() => {
listeningProject(); // 生命周期钩子
});
onMounted(() => { onMounted(() => {
getVersionNums(); getVersionNums();
}); });
onUnmounted(() => {
listeningProject();
});
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss">
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -195,7 +195,9 @@
(scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) - (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) -
(scope.row.selectNum ? Number(scope.row.selectNum) : 0) == (scope.row.selectNum ? Number(scope.row.selectNum) : 0) ==
0 0
? '' ? activeTab == 2
? 0
: ''
: (scope.row.quantity ? Number(scope.row.quantity) : 0) - : (scope.row.quantity ? Number(scope.row.quantity) : 0) -
(scope.row.selectNum ? Number(scope.row.selectNum) : 0) - (scope.row.selectNum ? Number(scope.row.selectNum) : 0) -
(scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0)
@ -613,17 +615,21 @@ const changeBiddingTime = (value: any, row: any) => {
}; };
//修改合同金额 //修改合同金额
const changeContractPrice = (value: any, row: any) => { const changeContractPrice = (value: any, row: any) => {
updateTenderPlan({ if (value <= Number(row.price)) {
...row updateTenderPlan({
}).then((res) => { ...row
if (res.code == 200) { }).then((res) => {
ElMessage({ if (res.code == 200) {
message: '修改成功', ElMessage({
type: 'success' message: '修改成功',
}); type: 'success'
getList(); });
} getList();
}); }
});
} else {
ElMessage.error('合同金额不能大于限价金额');
}
}; };
//上传投标文件 //上传投标文件

View File

@ -104,8 +104,9 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="近三年营业额" prop="pastThreeYears"> <el-form-item label="近三年营业额" prop="pastThreeYears">
<el-input v-model="form.pastThreeYears" placeholder="请输入近三年营业额" clearable /> <el-input v-model="form.pastThreeYears" placeholder="请输入近三年营业额" clearable />
</el-form-item> </el-col </el-form-item>
><el-col :span="12"> </el-col>
<!-- <el-col :span="12">
<el-form-item label="生产许可证编号" prop="safeCode"> <el-form-item label="生产许可证编号" prop="safeCode">
<el-input v-model="form.safeCode" placeholder="请输入许可证编号" clearable /> <el-input v-model="form.safeCode" placeholder="请输入许可证编号" clearable />
</el-form-item> </el-form-item>
@ -118,7 +119,7 @@
<el-form-item label="生产许可证发证日期" prop="safeCertificateValidity"> <el-form-item label="生产许可证发证日期" prop="safeCertificateValidity">
<el-date-picker v-model="form.safeCertificateValidity" type="date" placeholder="请选择发证日期" /> <el-date-picker v-model="form.safeCertificateValidity" type="date" placeholder="请选择发证日期" />
</el-form-item> </el-form-item>
</el-col> </el-col> -->
</el-row> </el-row>
<el-row class="mb-4" v-if="form.supplierType === '劳务'"> <el-row class="mb-4" v-if="form.supplierType === '劳务'">
<el-col :span="12"> <el-col :span="12">