PageSupplyRequestDetail.vue 11 KB

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