Files
td_official/src/views/tender/bidd/index.vue
2025-09-08 20:07:09 +08:00

501 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="p-2">
<el-tabs type="border-card" @tab-change="handleTabChange" v-model="activeTab">
<el-tab-pane v-for="(item, index) in tabList" :key="index" :label="item.label" :name="item.value">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<el-card shadow="always">
<el-form :model="queryForm" :inline="true">
<el-form-item label="版本号" prop="versions">
<el-select v-model="queryForm.versions" placeholder="选择版本号" @change="changeVersions">
<el-option v-for="item in options" :key="item.versions" :label="item.versions" :value="item.versions" />
</el-select>
</el-form-item>
<el-form-item label="表名" prop="sheet" v-if="activeTab != '3'">
<el-select v-model="queryForm.sheet" placeholder="选择表名" @change="changeSheet">
<el-option v-for="item in sheets" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="toggleExpandAll">{{ isExpandAll ? '一键收起' : '一键展开' }}</el-button>
</el-form-item>
<el-form-item>
<el-upload
ref="uploadRef"
class="upload-demo"
:http-request="importExcel"
:show-file-list="false"
v-hasPermi="['tender:tenderPlanLimitList:importExcelFile']"
>
<template #trigger>
<el-button type="primary">导入excel</el-button>
</template>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleExport()" v-hasPermi="['tender:tenderPlanLimitList:export']">导出excel</el-button>
</el-form-item>
<el-form-item>
<el-button
type="warning"
icon="view"
@click="handleAudit()"
v-if="versionsData.status && versionsData.status == 'draft'"
v-hasPermi="['tender:tenderPlanLimitList:getVersionDetail']"
>审核</el-button
>
<el-button
type="warning"
icon="view"
@click="handleAudit()"
v-if="versionsData.status && versionsData.status != 'draft'"
v-hasPermi="['tender:tenderPlanLimitList:getVersionDetail']"
>查看流程</el-button
>
</el-form-item>
</el-form>
</el-card>
</transition>
<el-card shadow="never" class="mb8">
<el-table ref="tableAllRef" v-loading="loading" :data="tableData" row-key="id" border lazy :expand-row-keys="expandRowKeys">
<el-table-column prop="num" label="编号" align="center" />
<el-table-column prop="name" label="工程或费用名称" align="center" />
<el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="supplier" label="供货单位" align="center" v-if="activeTab == '3'" />
<el-table-column prop="contractNumber" label="合同编号" align="center" v-if="activeTab == '3'" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope">
{{ scope.row.children.length > 0 ? '' : scope.row.quantity }}
</template>
</el-table-column>
<el-table-column prop="remark" label="单价" align="center">
<template #default="scope">
<el-input-number
:model-value="scope.row.unitPrice"
@change="
(val) => {
scope.row.unitPrice = val;
changePrice(scope.row);
}
"
:precision="2"
:step="0.1"
:controls="false"
v-if="scope.row.quantity && scope.row.quantity != 0"
:disabled="versionsData.status != 'draft'"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="总价" align="center">
<template #default="scope">
{{ scope.row.price != 0 ? Number(scope.row.price).toFixed(2) : null }}
</template>
</el-table-column>
<el-table-column prop="taxRate" label="税率" width="100" align="center">
<template #default="scope">
{{ scope.row.taxRate !== false ? scope.row.taxRate : '' }}
</template>
</el-table-column>
<el-table-column prop="operate" label="操作" align="center">
<template #default="scope">
<el-button
type="primary"
size="small"
@click="handleSave(scope.row, 'all')"
v-if="scope.row.quantity && scope.row.quantity != 0"
v-hasPermi="['tender:tenderPlanLimitList:edit']"
:disabled="versionsData.status != 'draft'"
>确定</el-button
>
</template>
</el-table-column>
</el-table>
</el-card>
</el-tab-pane>
</el-tabs>
</div>
</template>
<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 { 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 userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
// 标签页配置
const tabList = [
{ label: '招采工程量清单', value: '2' },
{ label: '物资设备清单', value: '3' }
];
// 响应式状态
const queryForm = ref<QueryForm>({ versions: '', sheet: '' });
const versionsData = ref<VersionItem>({});
const activeTab = ref('2');
const sheets = ref<string[]>([]);
const options = ref<VersionItem[]>([]);
const tableData = ref<TableRow[]>([]);
const tableAllRef = ref<any>(null);
const uploadRef = ref<any>(null);
const isExpandAll = ref(true);
const loading = ref(false);
const versionMap = new Map<string, VersionItem>();
const expandRowKeys = ref<string[] | number[]>([]); // 控制表格展开的行ID
const modifyPrice = new Map<string | number, TableRow>();
// 切换标签页
const handleTabChange = (tab: string) => {
activeTab.value = tab;
tableData.value = [];
versionsData.value = {};
isExpandAll.value = true;
expandRowKeys.value = [];
getVersionNums();
};
// 切换版本号
const changeVersions = (value: string) => {
versionsData.value = options.value.find((item) => item.versions === value) || {};
getSheetName();
};
// 切换表名
const changeSheet = (val: string) => {
getTableData();
};
// 一键展开/收起
const toggleExpandAll = async () => {
isExpandAll.value = !isExpandAll.value;
if (!tableData.value.length || !tableAllRef.value) return;
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 () => {
try {
const params = {
projectId: currentProject.value?.id,
workOrderType: activeTab.value,
pageSize: 1000,
pageNum: 1
};
const res = await obtainAllVersionNumbers(params);
if (res.code === 200) {
options.value = res.data;
versionMap.clear();
options.value.forEach((item) => versionMap.set(item.versions, item));
if (options.value.length > 0) {
queryForm.value.versions = options.value[0].versions;
versionsData.value = options.value[0];
getSheetName();
} else {
queryForm.value.versions = '';
sheets.value = [];
tableData.value = [];
expandRowKeys.value = [];
}
}
} catch (error) {
console.error('获取版本号失败:', error);
ElMessage.error('获取版本号失败,请刷新重试');
}
};
// 获取表名列表
const getSheetName = async () => {
try {
const params = {
projectId: currentProject.value?.id,
versions: queryForm.value.versions
};
const res = await sheetList(params);
if (res.code === 200) {
sheets.value = res.data;
if (sheets.value.length > 0) {
queryForm.value.sheet = sheets.value[0];
} else {
queryForm.value.sheet = '';
tableData.value = [];
expandRowKeys.value = [];
}
getTableData();
}
} catch (error) {
console.error('获取表名失败:', error);
ElMessage.error('获取表名失败,请刷新重试');
}
};
// 获取表格数据
const getTableData = async () => {
try {
loading.value = true;
const params = {
projectId: currentProject.value?.id,
versions: queryForm.value.versions,
sheet: queryForm.value.sheet,
type: activeTab.value
};
const res = await getTableList(params);
if (res.code === 200) {
tableData.value = res.data;
if (isExpandAll.value) {
// 展开全部
isExpandAll.value = false;
toggleExpandAll();
}
modifyPrice.clear();
}
} catch (error) {
console.error('获取表格数据失败:', error);
ElMessage.error('获取表格数据失败,请刷新重试');
} finally {
loading.value = false;
}
};
// 导入Excel
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);
loading.value = true;
importExcelFile(
{
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet,
versions: queryForm.value.versions,
type: activeTab.value
},
formData
)
.then((res) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess(res.msg || '导入成功');
getTableData();
} else {
proxy?.$modal.msgError(res.msg || '导入失败');
}
})
.catch((err) => {
proxy?.$modal.msgError(err.msg || '导入失败');
})
.finally(() => {
loading.value = false;
});
};
// 导出Excel
const handleExport = () => {
if (!queryForm.value.versions || (activeTab.value !== '3' && !queryForm.value.sheet)) {
ElMessage.warning('请先选择版本号和表名(物资设备清单无需选择表名)');
return;
}
proxy?.download(
'/tender/tenderPlanLimitList/export',
{
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet,
versions: queryForm.value.versions,
type: activeTab.value
},
`招标一览_${queryForm.value.sheet || (activeTab.value === '3' ? '物资设备清单' : '')}_v${queryForm.value.versions}.xlsx`
);
};
// 记录待修改价格的行
const changePrice = (row: TableRow) => {
if (row.id && row.unitPrice !== undefined) {
modifyPrice.set(row.id, row);
} else if (row.id) {
modifyPrice.delete(row.id);
}
};
// 保存价格修改
const handleSave = (row?: TableRow, type?: 'single' | 'all') => {
if (versionsData.value.status !== 'draft') {
ElMessage.warning('仅草稿状态可修改单价');
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', '招采工程量清单审核', {
id: versionItem.id,
type: 'update'
});
} else if (activeTab.value === '3') {
proxy?.$tab.openPage('/approval/tenderBidd/indexEdit2', '物资设备清单审核', {
id: versionItem.id,
type: 'update'
});
}
};
// 监听项目切换
const listeningProject = watch(
() => currentProject.value?.id,
(newId, oldId) => {
if (newId && newId !== oldId) {
getVersionNums();
} else {
tableData.value = [];
options.value = [];
sheets.value = [];
queryForm.value = { versions: '', sheet: '' };
versionsData.value = {};
expandRowKeys.value = [];
versionMap.clear();
modifyPrice.clear();
}
}
);
// 生命周期钩子
onMounted(() => {
getVersionNums();
});
onUnmounted(() => {
listeningProject();
});
</script>
<style scoped lang="scss">
.mb8 {
margin-bottom: 8px;
}
</style>