PageSupplyRequestDetail.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <div class="safety-platform-container">
  3. <div class="safety-platform-container__header">
  4. <div class="bread">
  5. <BreadcrumbBack />
  6. <div class="breadcrumb-title">{{ supplyRequestInfo?.planName || '物资采购申领详情' }}</div>
  7. </div>
  8. </div>
  9. <div class="safety-platform-container__main">
  10. <div class="detail-container">
  11. <header class="detail-header">
  12. <el-button
  13. type="primary"
  14. :icon="Plus"
  15. @click="handleAddMaterial"
  16. v-if="supplyRequestInfo?.status === SUPPLY_REQUEST_STATUS.APPLYING && supplyRequestManagePermission"
  17. >添加需求物资</el-button
  18. >
  19. <el-button type="primary" :icon="Download" @click="handleDownloadRecord">下载采购记录</el-button>
  20. </header>
  21. <div class="table-container">
  22. <el-table :data="tableData" :span-method="handleSpanMethod" border v-loading="loading" style="width: 100%">
  23. <el-table-column type="index" label="序号" width="80" align="center" />
  24. <el-table-column prop="materialName" label="物资名称" min-width="140" align="center" />
  25. <el-table-column prop="specification" label="规格" min-width="200" align="center" />
  26. <el-table-column prop="unitPrice" label="单价 (元)" width="120" align="center" />
  27. <el-table-column prop="subtotal" label="小计" min-width="200" align="center" />
  28. <el-table-column prop="department" label="需求部门" min-width="140" align="center" />
  29. <el-table-column prop="quantity" label="数量" width="100" align="center" />
  30. <el-table-column prop="sizeDetails" label="尺寸明细" min-width="200" align="center" />
  31. <el-table-column prop="status" label="状态" width="120" align="center">
  32. <template #default="scope">
  33. <span>{{ getStatusText(scope.row.status) }}</span>
  34. </template>
  35. </el-table-column>
  36. <el-table-column prop="claim" label="领用" width="100" align="center" v-if="supplyRequestManagePermission">
  37. <template #default="scope">
  38. <el-link
  39. v-if="scope.row.status === SUPPLY_REQUEST_STATUS.PURCHASING && !scope.row.isClaimed"
  40. type="primary"
  41. @click="handleClaim(scope.row)"
  42. >
  43. 领用
  44. </el-link>
  45. <span v-else>-</span>
  46. </template>
  47. </el-table-column>
  48. <el-table-column
  49. prop="action"
  50. label="物资操作"
  51. width="150"
  52. align="center"
  53. fixed="right"
  54. v-if="supplyRequestManagePermission"
  55. >
  56. <template #default="scope">
  57. <div class="action-container">
  58. <el-link
  59. v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING"
  60. type="primary"
  61. @click="handleEdit(scope.row)"
  62. >
  63. 编辑
  64. </el-link>
  65. <el-link
  66. v-if="scope.row.status === SUPPLY_REQUEST_STATUS.APPLYING"
  67. type="primary"
  68. style="margin-left: 8px"
  69. @click="handleDelete(scope.row)"
  70. >
  71. 删除
  72. </el-link>
  73. <span v-if="scope.row.status !== SUPPLY_REQUEST_STATUS.APPLYING">-</span>
  74. </div>
  75. </template>
  76. </el-table-column>
  77. </el-table>
  78. </div>
  79. </div>
  80. </div>
  81. <!-- 领用弹窗 -->
  82. <ReceiveSupplyDialog ref="receiveSupplyDialogRef" @success="handleClaimSuccess" />
  83. <!-- 添加物资弹窗 -->
  84. <AddSuppliesDrawer ref="addSupplyDrawerRef" @success="handleAddMaterialSuccess" />
  85. </div>
  86. </template>
  87. <script setup lang="ts">
  88. import { ref, onMounted } from 'vue';
  89. import { useRoute } from 'vue-router';
  90. import { Plus, Download } from '@element-plus/icons-vue';
  91. import { ElMessage, ElMessageBox } from 'element-plus';
  92. import BreadcrumbBack from '@/components/BreadcrumbBack.vue';
  93. import { SUPPLY_REQUEST_STATUS, SUPPLY_REQUEST_DETAIL_STATUS_MAP } from './src/constant';
  94. import {
  95. getSupplyRequestInfoById,
  96. getSupplyRequestDetail,
  97. deleteSupplyRequestDetail,
  98. exportSupplyRequestRecord,
  99. } from '@/api/emergency-supplier';
  100. import type { SupplyRequestDetailItem } from '@/types/emergency-supplier';
  101. import { SupplyRequestListItem } from '@/types/emergency-supplier';
  102. import { downloadFile } from '@/views/disaster/utils/download';
  103. import ReceiveSupplyDialog from './src/components/ReceiveSupplyDialog.vue';
  104. import { useUserInfoHook } from '@/hooks/useUserInfoHook';
  105. import { EMERGENCY_PERMISSIONS } from '@/views/emergency/src/constant';
  106. import AddSuppliesDrawer from './src/components/AddSuppliesDrawer.vue';
  107. const { permissions } = useUserInfoHook();
  108. const supplyRequestManagePermission = ref<Boolean>(false);
  109. // 表格行数据类型
  110. interface TableRowData {
  111. id: number; // detailList 的 id
  112. requestId: number; // detailList 的 requestId
  113. infoId: number; // info 的 id,用于合并单元格
  114. materialName: string; // info 的 supplyName
  115. specification: string; // info 的 specs
  116. unitPrice: number; // info 的 unitPrice
  117. subtotal: string; // info 的 subtotal
  118. department: string; // detailList 的 deptName
  119. quantity: number; // detailList 的 quantity
  120. sizeDetails: string; // detailList 的 sizeDetail
  121. status: number; // detailList 的 status
  122. isClaimed?: boolean; // 是否已领用(根据状态判断)
  123. }
  124. const route = useRoute();
  125. const id = Number(route.params.id);
  126. const supplyRequestInfo = ref<SupplyRequestListItem>();
  127. const loading = ref(false);
  128. const tableData = ref<TableRowData[]>([]);
  129. const addSupplyDrawerRef = ref<InstanceType<typeof AddSuppliesDrawer>>();
  130. const supplyRequestDetailData = ref<SupplyRequestDetailItem[]>([]);
  131. // 领用弹窗相关
  132. const receiveSupplyDialogRef = ref<InstanceType<typeof ReceiveSupplyDialog>>();
  133. // 获取状态文本
  134. const getStatusText = (status: number) => {
  135. return SUPPLY_REQUEST_DETAIL_STATUS_MAP[status] || '';
  136. };
  137. // 合并单元格方法
  138. const handleSpanMethod = ({ row, rowIndex, columnIndex }: any) => {
  139. // 需要合并的列索引:物资名称(1)、规格(2)、单价(3)、小计(4)、物资操作(10)
  140. const mergeColumns = [1, 2, 3, 4, 10];
  141. if (!mergeColumns.includes(columnIndex)) {
  142. return { rowspan: 1, colspan: 1 };
  143. }
  144. // 按照 infoId 来分组,找到同一个 info 下的所有行
  145. const infoId = row.infoId;
  146. const sameInfoRows = tableData.value.filter((item) => item.infoId === infoId);
  147. const firstRowIndex = tableData.value.findIndex((item) => item.infoId === infoId);
  148. // 如果是第一行,返回合并的行数
  149. if (rowIndex === firstRowIndex) {
  150. return {
  151. rowspan: sameInfoRows.length,
  152. colspan: 1,
  153. };
  154. }
  155. // 其他行隐藏
  156. return {
  157. rowspan: 0,
  158. colspan: 0,
  159. };
  160. };
  161. // 获取详情数据
  162. const getDetailData = async () => {
  163. loading.value = true;
  164. try {
  165. const res = await getSupplyRequestDetail(id);
  166. supplyRequestDetailData.value = res;
  167. // 将 SupplyRequestDetailItem[] 转换为表格需要的扁平化数据结构
  168. const flatData: TableRowData[] = [];
  169. res.forEach((item) => {
  170. // 将每个 detailList 项与 info 合并成一行
  171. item.detailList.forEach((detail) => {
  172. flatData.push({
  173. id: detail.id,
  174. requestId: detail.requestId,
  175. infoId: item.info.id, // 用于合并单元格
  176. materialName: item.info.supplyName,
  177. specification: item.info.specs,
  178. unitPrice: item.info.unitPrice,
  179. subtotal: item.info.subtotal,
  180. department: detail.deptName,
  181. quantity: detail.quantity,
  182. sizeDetails: detail.sizeDetail,
  183. status: detail.status,
  184. isClaimed: detail.status === SUPPLY_REQUEST_STATUS.RECEIVED,
  185. });
  186. });
  187. });
  188. tableData.value = flatData;
  189. } catch (error) {
  190. console.error('获取详情失败:', error);
  191. ElMessage.error('获取详情失败');
  192. } finally {
  193. loading.value = false;
  194. }
  195. };
  196. // 添加需求物资
  197. const handleAddMaterial = () => {
  198. addSupplyDrawerRef.value?.openDrawer();
  199. };
  200. // 添加物资成功回调
  201. const handleAddMaterialSuccess = async () => {
  202. await getDetailData();
  203. };
  204. // 下载采购记录
  205. const handleDownloadRecord = async () => {
  206. try {
  207. const res = await exportSupplyRequestRecord(id);
  208. if (res.size === 0) return;
  209. const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  210. const url = window.URL.createObjectURL(blob);
  211. downloadFile(url, '采购申领记录.xlsx');
  212. } catch (error) {
  213. ElMessage.error('下载失败');
  214. }
  215. };
  216. // 领用
  217. const handleClaim = (row: TableRowData) => {
  218. receiveSupplyDialogRef.value?.openDialog(row.materialName, row.quantity, row.id, id);
  219. };
  220. // 领用成功回调
  221. const handleClaimSuccess = async () => {
  222. await getDetailData();
  223. };
  224. // 编辑
  225. const handleEdit = (row: TableRowData) => {
  226. // 根据 infoId 找到对应的 SupplyRequestDetailItem
  227. const editData = supplyRequestDetailData.value.find((item) => item.info.id === row.infoId);
  228. if (!editData) {
  229. ElMessage.error('未找到要编辑的数据');
  230. return;
  231. }
  232. addSupplyDrawerRef.value?.openDrawer(editData);
  233. };
  234. // 删除
  235. const handleDelete = async (row: TableRowData) => {
  236. try {
  237. await ElMessageBox.confirm('确定要删除该物资吗?', '提示', {
  238. confirmButtonText: '确定',
  239. cancelButtonText: '取消',
  240. type: 'warning',
  241. });
  242. await deleteSupplyRequestDetail(row.infoId);
  243. ElMessage.success('删除成功');
  244. await getDetailData();
  245. } catch (error) {
  246. console.error('删除失败:', error);
  247. }
  248. };
  249. onMounted(async () => {
  250. getDetailData();
  251. supplyRequestInfo.value = await getSupplyRequestInfoById(id);
  252. supplyRequestManagePermission.value = Boolean(
  253. permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.SUPPLY_REQUEST_MANAGE),
  254. );
  255. });
  256. </script>
  257. <style scoped lang="scss">
  258. @use '@/styles/page-details-layout.scss' as *;
  259. @use './src/styles/page-common.scss' as *;
  260. .bread {
  261. display: flex;
  262. align-items: center;
  263. gap: 8px;
  264. }
  265. .detail-container {
  266. background: #fff;
  267. padding: 16px;
  268. border-radius: 4px;
  269. }
  270. .detail-header {
  271. display: flex;
  272. gap: 12px;
  273. margin-bottom: 16px;
  274. }
  275. .table-container {
  276. width: 100%;
  277. }
  278. .action-container {
  279. display: flex;
  280. align-items: center;
  281. justify-content: center;
  282. }
  283. </style>