|
@@ -0,0 +1,313 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="safety-platform-container">
|
|
|
|
|
+ <div class="safety-platform-container__header">
|
|
|
|
|
+ <div class="bread">
|
|
|
|
|
+ <BreadcrumbBack />
|
|
|
|
|
+ <div class="breadcrumb-title">{{ supplyRequestInfo?.planName || '物资采购申领详情' }}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="safety-platform-container__main">
|
|
|
|
|
+ <div class="detail-container">
|
|
|
|
|
+ <header class="detail-header">
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :icon="Plus"
|
|
|
|
|
+ @click="handleAddMaterial"
|
|
|
|
|
+ v-if="supplyRequestInfo?.status === SUPPLY_REQUEST_STATUS.APPLYING && supplyRequestManagePermission"
|
|
|
|
|
+ >添加需求物资</el-button
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button type="primary" :icon="Download" @click="handleDownloadRecord">下载采购记录</el-button>
|
|
|
|
|
+ </header>
|
|
|
|
|
+ <div class="table-container">
|
|
|
|
|
+ <el-table :data="tableData" :span-method="handleSpanMethod" border v-loading="loading" style="width: 100%">
|
|
|
|
|
+ <el-table-column type="index" label="序号" width="80" align="center" />
|
|
|
|
|
+ <el-table-column prop="materialName" label="物资名称" min-width="140" align="center" />
|
|
|
|
|
+ <el-table-column prop="specification" label="规格" min-width="200" align="center" />
|
|
|
|
|
+ <el-table-column prop="unitPrice" label="单价 (元)" width="120" align="center" />
|
|
|
|
|
+ <el-table-column prop="subtotal" label="小计" min-width="200" align="center" />
|
|
|
|
|
+ <el-table-column prop="department" label="需求部门" min-width="140" align="center" />
|
|
|
|
|
+ <el-table-column prop="quantity" label="数量" width="100" align="center" />
|
|
|
|
|
+ <el-table-column prop="sizeDetails" label="尺寸明细" min-width="200" align="center" />
|
|
|
|
|
+ <el-table-column prop="status" label="状态" width="120" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <span>{{ getStatusText(scope.row.status) }}</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="claim" label="领用" width="100" align="center" v-if="supplyRequestManagePermission">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-link
|
|
|
|
|
+ v-if="scope.row.status === SUPPLY_REQUEST_STATUS.PURCHASING && !scope.row.isClaimed"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="handleClaim(scope.row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 领用
|
|
|
|
|
+ </el-link>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column
|
|
|
|
|
+ prop="action"
|
|
|
|
|
+ label="物资操作"
|
|
|
|
|
+ width="150"
|
|
|
|
|
+ align="center"
|
|
|
|
|
+ fixed="right"
|
|
|
|
|
+ v-if="supplyRequestManagePermission"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <div class="action-container">
|
|
|
|
|
+ <el-link
|
|
|
|
|
+ v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="handleEdit(scope.row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 编辑
|
|
|
|
|
+ </el-link>
|
|
|
|
|
+ <el-link
|
|
|
|
|
+ v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ style="margin-left: 8px"
|
|
|
|
|
+ @click="handleDelete(scope.row)"
|
|
|
|
|
+ >
|
|
|
|
|
+ 删除
|
|
|
|
|
+ </el-link>
|
|
|
|
|
+ <span v-if="scope.row.status !== SUPPLY_REQUEST_STATUS.APPLYING">-</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 领用弹窗 -->
|
|
|
|
|
+ <ReceiveSupplyDialog ref="receiveSupplyDialogRef" @success="handleClaimSuccess" />
|
|
|
|
|
+ <!-- 添加物资弹窗 -->
|
|
|
|
|
+ <AddSuppliesDrawer ref="addSupplyDrawerRef" @success="handleAddMaterialSuccess" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+ import { ref, onMounted } from 'vue';
|
|
|
|
|
+ import { useRoute } from 'vue-router';
|
|
|
|
|
+ import { Plus, Download } from '@element-plus/icons-vue';
|
|
|
|
|
+ import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
|
|
+ import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
|
|
|
|
|
+ import { SUPPLY_REQUEST_STATUS, SUPPLY_REQUEST_DETAIL_STATUS_MAP } from './src/constant';
|
|
|
|
|
+ import {
|
|
|
|
|
+ getSupplyRequestInfoById,
|
|
|
|
|
+ getSupplyRequestDetail,
|
|
|
|
|
+ deleteSupplyRequestDetail,
|
|
|
|
|
+ exportSupplyRequestRecord,
|
|
|
|
|
+ } from '@/api/emergency-supplier';
|
|
|
|
|
+ import type { SupplyRequestDetailItem } from '@/types/emergency-supplier';
|
|
|
|
|
+ import { SupplyRequestListItem } from '@/types/emergency-supplier';
|
|
|
|
|
+ import { downloadFile } from '@/views/disaster/utils/download';
|
|
|
|
|
+ import ReceiveSupplyDialog from './src/components/ReceiveSupplyDialog.vue';
|
|
|
|
|
+ import { useUserInfoHook } from '@/hooks/useUserInfoHook';
|
|
|
|
|
+ import { EMERGENCY_PERMISSIONS } from '@/views/emergency/src/constant';
|
|
|
|
|
+ import AddSuppliesDrawer from './src/components/AddSuppliesDrawer.vue';
|
|
|
|
|
+
|
|
|
|
|
+ const { permissions } = useUserInfoHook();
|
|
|
|
|
+ const supplyRequestManagePermission = ref<Boolean>(false);
|
|
|
|
|
+
|
|
|
|
|
+ // 表格行数据类型
|
|
|
|
|
+ interface TableRowData {
|
|
|
|
|
+ id: number; // detailList 的 id
|
|
|
|
|
+ requestId: number; // detailList 的 requestId
|
|
|
|
|
+ infoId: number; // info 的 id,用于合并单元格
|
|
|
|
|
+ materialName: string; // info 的 supplyName
|
|
|
|
|
+ specification: string; // info 的 specs
|
|
|
|
|
+ unitPrice: number; // info 的 unitPrice
|
|
|
|
|
+ subtotal: string; // info 的 subtotal
|
|
|
|
|
+ department: string; // detailList 的 deptName
|
|
|
|
|
+ quantity: number; // detailList 的 quantity
|
|
|
|
|
+ sizeDetails: string; // detailList 的 sizeDetail
|
|
|
|
|
+ status: number; // detailList 的 status
|
|
|
|
|
+ isClaimed?: boolean; // 是否已领用(根据状态判断)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const route = useRoute();
|
|
|
|
|
+ const id = Number(route.params.id);
|
|
|
|
|
+
|
|
|
|
|
+ const supplyRequestInfo = ref<SupplyRequestListItem>();
|
|
|
|
|
+
|
|
|
|
|
+ const loading = ref(false);
|
|
|
|
|
+ const tableData = ref<TableRowData[]>([]);
|
|
|
|
|
+ const addSupplyDrawerRef = ref<InstanceType<typeof AddSuppliesDrawer>>();
|
|
|
|
|
+ const supplyRequestDetailData = ref<SupplyRequestDetailItem[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+ // 领用弹窗相关
|
|
|
|
|
+ const receiveSupplyDialogRef = ref<InstanceType<typeof ReceiveSupplyDialog>>();
|
|
|
|
|
+
|
|
|
|
|
+ // 获取状态文本
|
|
|
|
|
+ const getStatusText = (status: number) => {
|
|
|
|
|
+ return SUPPLY_REQUEST_DETAIL_STATUS_MAP[status] || '';
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 合并单元格方法
|
|
|
|
|
+ const handleSpanMethod = ({ row, rowIndex, columnIndex }: any) => {
|
|
|
|
|
+ // 需要合并的列索引:物资名称(1)、规格(2)、单价(3)、小计(4)、物资操作(10)
|
|
|
|
|
+ const mergeColumns = [1, 2, 3, 4, 10];
|
|
|
|
|
+ if (!mergeColumns.includes(columnIndex)) {
|
|
|
|
|
+ return { rowspan: 1, colspan: 1 };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 按照 infoId 来分组,找到同一个 info 下的所有行
|
|
|
|
|
+ const infoId = row.infoId;
|
|
|
|
|
+ const sameInfoRows = tableData.value.filter((item) => item.infoId === infoId);
|
|
|
|
|
+ const firstRowIndex = tableData.value.findIndex((item) => item.infoId === infoId);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是第一行,返回合并的行数
|
|
|
|
|
+ if (rowIndex === firstRowIndex) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ rowspan: sameInfoRows.length,
|
|
|
|
|
+ colspan: 1,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 其他行隐藏
|
|
|
|
|
+ return {
|
|
|
|
|
+ rowspan: 0,
|
|
|
|
|
+ colspan: 0,
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 获取详情数据
|
|
|
|
|
+ const getDetailData = async () => {
|
|
|
|
|
+ loading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getSupplyRequestDetail(id);
|
|
|
|
|
+ supplyRequestDetailData.value = res;
|
|
|
|
|
+
|
|
|
|
|
+ // 将 SupplyRequestDetailItem[] 转换为表格需要的扁平化数据结构
|
|
|
|
|
+ const flatData: TableRowData[] = [];
|
|
|
|
|
+
|
|
|
|
|
+ res.forEach((item) => {
|
|
|
|
|
+ // 将每个 detailList 项与 info 合并成一行
|
|
|
|
|
+ item.detailList.forEach((detail) => {
|
|
|
|
|
+ flatData.push({
|
|
|
|
|
+ id: detail.id,
|
|
|
|
|
+ requestId: detail.requestId,
|
|
|
|
|
+ infoId: item.info.id, // 用于合并单元格
|
|
|
|
|
+ materialName: item.info.supplyName,
|
|
|
|
|
+ specification: item.info.specs,
|
|
|
|
|
+ unitPrice: item.info.unitPrice,
|
|
|
|
|
+ subtotal: item.info.subtotal,
|
|
|
|
|
+ department: detail.deptName,
|
|
|
|
|
+ quantity: detail.quantity,
|
|
|
|
|
+ sizeDetails: detail.sizeDetail,
|
|
|
|
|
+ status: detail.status,
|
|
|
|
|
+ isClaimed: detail.status === SUPPLY_REQUEST_STATUS.RECEIVED,
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ tableData.value = flatData;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取详情失败:', error);
|
|
|
|
|
+ ElMessage.error('获取详情失败');
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 添加需求物资
|
|
|
|
|
+ const handleAddMaterial = () => {
|
|
|
|
|
+ addSupplyDrawerRef.value?.openDrawer();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 添加物资成功回调
|
|
|
|
|
+ const handleAddMaterialSuccess = async () => {
|
|
|
|
|
+ await getDetailData();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 下载采购记录
|
|
|
|
|
+ const handleDownloadRecord = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await exportSupplyRequestRecord(id);
|
|
|
|
|
+ if (res.size === 0) return;
|
|
|
|
|
+ const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
|
|
|
+ const url = window.URL.createObjectURL(blob);
|
|
|
|
|
+ downloadFile(url, '采购申领记录.xlsx');
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error('下载失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 领用
|
|
|
|
|
+ const handleClaim = (row: TableRowData) => {
|
|
|
|
|
+ receiveSupplyDialogRef.value?.openDialog(row.materialName, row.quantity, row.id, id);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 领用成功回调
|
|
|
|
|
+ const handleClaimSuccess = async () => {
|
|
|
|
|
+ await getDetailData();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 编辑
|
|
|
|
|
+ const handleEdit = (row: TableRowData) => {
|
|
|
|
|
+ // 根据 infoId 找到对应的 SupplyRequestDetailItem
|
|
|
|
|
+ const editData = supplyRequestDetailData.value.find((item) => item.info.id === row.infoId);
|
|
|
|
|
+ if (!editData) {
|
|
|
|
|
+ ElMessage.error('未找到要编辑的数据');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ addSupplyDrawerRef.value?.openDrawer(editData);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 删除
|
|
|
|
|
+ const handleDelete = async (row: TableRowData) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ await ElMessageBox.confirm('确定要删除该物资吗?', '提示', {
|
|
|
|
|
+ confirmButtonText: '确定',
|
|
|
|
|
+ cancelButtonText: '取消',
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ });
|
|
|
|
|
+ await deleteSupplyRequestDetail(row.infoId);
|
|
|
|
|
+ ElMessage.success('删除成功');
|
|
|
|
|
+ await getDetailData();
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error('删除失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ onMounted(async () => {
|
|
|
|
|
+ getDetailData();
|
|
|
|
|
+ supplyRequestInfo.value = await getSupplyRequestInfoById(id);
|
|
|
|
|
+ supplyRequestManagePermission.value = Boolean(
|
|
|
|
|
+ permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.SUPPLY_REQUEST_MANAGE),
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+ @use '@/styles/page-details-layout.scss' as *;
|
|
|
|
|
+ @use './src/styles/page-common.scss' as *;
|
|
|
|
|
+
|
|
|
|
|
+ .bread {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .detail-container {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .detail-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ margin-bottom: 16px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .table-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .action-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|