PageInventoryCheck.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <template>
  2. <div class="safety-platform-container">
  3. <div class="safety-platform-container__header">
  4. <div class="breadcrumb-title">物资盘点</div>
  5. </div>
  6. <div class="safety-platform-container__main">
  7. <div class="search-table-container">
  8. <header class="disaster-precaution__header">
  9. <BasicSearch
  10. :searchConfig="INVENTORY_LIST_SEARCH_CONFIG"
  11. :searchData="searchData"
  12. custom-reset
  13. @update:searchData="handleSearch"
  14. @custom-reset="handleReset"
  15. >
  16. <template #taskId>
  17. <el-select v-model="searchData.taskId" placeholder="请选择盘点任务" filterable @change="handleTaskChange">
  18. <el-option
  19. v-for="item in inventoryTaskOptions"
  20. :key="item.id"
  21. :label="item.taskName"
  22. :value="item.id"
  23. />
  24. </el-select>
  25. </template>
  26. <template #emergencyType>
  27. <el-select v-model="searchData.emergencyType" placeholder="请选择应急事件类型" filterable>
  28. <el-option
  29. v-for="item in emergencyEventDice"
  30. :key="item.itemCode"
  31. :label="item.itemValue"
  32. :value="item.itemCode"
  33. />
  34. </el-select>
  35. </template>
  36. <template #supplyType>
  37. <el-select v-model="searchData.supplyType" placeholder="请选择物资类型" filterable>
  38. <el-option
  39. v-for="item in emergencySupplyDice"
  40. :key="item.itemCode"
  41. :label="item.itemValue"
  42. :value="item.itemCode"
  43. />
  44. </el-select>
  45. </template>
  46. </BasicSearch>
  47. <div class="date-time-container" v-if="endTime">
  48. <span>任务期限:{{ endTime }}</span>
  49. <el-button type="primary" @click="handleConfirm" v-if="pagination.total && isInventory">盘点确认</el-button>
  50. </div>
  51. </header>
  52. <BasicTable
  53. :tableData="tableData"
  54. :tableConfig="tableConfig"
  55. @update:pageSize="handleSizeChange"
  56. @update:pageNumber="handleCurrentChange"
  57. >
  58. <template #emergencyType="scope">
  59. <span>{{ getEmergencyEvent(scope.row.emergencyType) }}</span>
  60. </template>
  61. <template #supplyType="scope">
  62. <span>{{ getEmergencySupply(scope.row.supplyType) }}</span>
  63. </template>
  64. <template #afterKeeperName="scope">
  65. <span :class="{ 'font-red': scope.row.afterKeeperName !== scope.row.beforeKeeperName }">
  66. {{ scope.row.afterKeeperName }}
  67. </span>
  68. </template>
  69. <template #afterQuantity="scope">
  70. <span :class="{ 'font-red': scope.row.afterQuantity !== scope.row.beforeQuantity }">
  71. {{ scope.row.afterQuantity }}
  72. </span>
  73. </template>
  74. <template #supplementQuantity="scope">
  75. <div v-if="scope.row.afterQuantity === null" />
  76. <div v-else>
  77. <span v-if="scope.row.supplementQuantity > 0">
  78. <span class="font-red">{{ scope.row.supplementQuantity }}</span>
  79. </span>
  80. <span v-else>
  81. <span>0</span>
  82. </span>
  83. </div>
  84. </template>
  85. <template #park="scope">
  86. <span>{{ getPark(scope.row.park) }}</span>
  87. </template>
  88. <template #beforeLocation="scope">
  89. <span>{{ getLocation(scope.row.beforeLocation) }}</span>
  90. </template>
  91. <template #afterLocation="scope">
  92. <span :class="{ 'font-red': scope.row.afterLocation !== scope.row.beforeLocation }">
  93. {{ getLocation(scope.row.afterLocation) }}
  94. </span>
  95. </template>
  96. <template #inventoryResult="scope">
  97. <span>{{ INVENTORY_RESULT_MAP[scope.row.inventoryResult] }}</span>
  98. </template>
  99. <template #remark="scope">
  100. <div class="remark--div">
  101. <el-tooltip
  102. :content="scope.row.remark"
  103. placement="top"
  104. effect="light"
  105. :popper-class="[
  106. 'custom-tooltip',
  107. scope.row.remark && scope.row.remark.length > 10 ? '' : 'custom-tooltip--opacity0',
  108. ]"
  109. >
  110. <span>{{ scope.row.remark }}</span>
  111. </el-tooltip>
  112. </div>
  113. </template>
  114. <template #imageList="scope">
  115. <div class="image-list" v-if="scope.row.imageList && JSON.parse(scope.row.imageList).length > 0">
  116. <el-image
  117. :src="PreviewIcon"
  118. :zoom-rate="1.2"
  119. :max-scale="7"
  120. :min-scale="0.2"
  121. :preview-src-list="JSON.parse(scope.row.imageList)"
  122. :initial-index="0"
  123. show-progress
  124. preview-teleported
  125. fit="cover"
  126. />
  127. </div>
  128. <span v-else>-</span>
  129. </template>
  130. </BasicTable>
  131. </div>
  132. </div>
  133. </div>
  134. </template>
  135. <script setup lang="ts">
  136. import { ref, reactive, onMounted, watch, onBeforeUnmount } from 'vue';
  137. import { ElMessage } from 'element-plus';
  138. import BasicSearch from '@/components/BasicSearch.vue';
  139. import BasicTable from '@/components/BasicTable.vue';
  140. import {
  141. INVENTORY_LIST_SEARCH_CONFIG,
  142. INVENTORY_CHECK_TABLE_COLUMNS,
  143. INVENTORY_CHECK_TABLE_OPTIONS,
  144. INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION,
  145. INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT,
  146. } from './src/config';
  147. import useTableConfig from '@/hooks/useTableConfigHook';
  148. import { useEmergencyHook } from '../src/hoos';
  149. import { useEmergencySuppliesHook } from './src/hook';
  150. import { openMessageBox } from '@/utils/element-plus/messageBox';
  151. import type {
  152. InventoryCheckListQuery,
  153. InventoryCheckListResponse,
  154. InventoryTaskListRes,
  155. } from '@/types/emergency-supplier';
  156. import type { QueryPageRequest } from '@/types/basic-query';
  157. import {
  158. getInventoryTaskList,
  159. getInventoryCheckList,
  160. confirmInventoryTask,
  161. getInventoryTaskConfirm,
  162. } from '@/api/emergency-supplier';
  163. import { INVENTORY_RESULT_MAP } from './src/constant';
  164. import PreviewIcon from './src/svg/img-preview.svg';
  165. const { emergencyEventDice, getEmergencyEventDict, getEmergencyEvent } = useEmergencyHook();
  166. const {
  167. emergencySupplyDice,
  168. getEmergencySupplyDict,
  169. getEmergencySupply,
  170. getParkDict,
  171. getPark,
  172. getLocationDict,
  173. getLocation,
  174. } = useEmergencySuppliesHook();
  175. const { tableConfig, pagination } = useTableConfig(INVENTORY_CHECK_TABLE_COLUMNS, INVENTORY_CHECK_TABLE_OPTIONS);
  176. let inventoryCheckListQuery: QueryPageRequest<InventoryCheckListQuery> = {
  177. pageNumber: pagination.pageNumber,
  178. pageSize: pagination.pageSize,
  179. queryParam: {},
  180. };
  181. const tableData = ref<InventoryCheckListResponse[]>([]);
  182. const inventoryTaskOptions = ref<InventoryTaskListRes[]>([]);
  183. const endTime = ref<string>('');
  184. const isInventory = ref<boolean>(false);
  185. const searchData = reactive<InventoryCheckListQuery>({
  186. taskId: null,
  187. emergencyType: null,
  188. supplyType: null,
  189. supplyName: null,
  190. keeperName: null,
  191. inventoryResult: null,
  192. });
  193. // 定时器引用
  194. const pollTimer = ref<number | null>(null);
  195. const handleSearch = () => {
  196. inventoryCheckListQuery.queryParam = {};
  197. if (searchData.taskId) {
  198. inventoryCheckListQuery.queryParam.taskId = searchData.taskId;
  199. }
  200. if (searchData.emergencyType) {
  201. inventoryCheckListQuery.queryParam.emergencyType = searchData.emergencyType;
  202. }
  203. if (searchData.supplyType) {
  204. inventoryCheckListQuery.queryParam.supplyType = searchData.supplyType;
  205. }
  206. if (searchData.supplyName) {
  207. inventoryCheckListQuery.queryParam.supplyName = searchData.supplyName;
  208. }
  209. if (searchData.keeperName) {
  210. inventoryCheckListQuery.queryParam.keeperName = searchData.keeperName;
  211. }
  212. if (searchData.inventoryResult) {
  213. inventoryCheckListQuery.queryParam.inventoryResult = searchData.inventoryResult;
  214. }
  215. getTableData();
  216. };
  217. const getTableData = async () => {
  218. tableConfig.loading = true;
  219. const res = await getInventoryCheckList(inventoryCheckListQuery);
  220. tableData.value = res.records;
  221. pagination.total = res.totalRow;
  222. tableConfig.loading = false;
  223. };
  224. const handleSizeChange = (value: number) => {
  225. pagination.pageSize = value;
  226. inventoryCheckListQuery.pageSize = value;
  227. getTableData();
  228. };
  229. const handleCurrentChange = (value: number) => {
  230. pagination.pageNumber = value;
  231. inventoryCheckListQuery.pageNumber = value;
  232. getTableData();
  233. };
  234. const getFirstTaskInfo = async () => {
  235. const firstTask = inventoryTaskOptions.value[0];
  236. if (!firstTask) return;
  237. searchData.taskId = firstTask.id;
  238. endTime.value = firstTask.endTime;
  239. await checkTaskStatus();
  240. startPolling();
  241. };
  242. const getInventoryTaskOptions = async () => {
  243. inventoryTaskOptions.value = await getInventoryTaskList();
  244. await getFirstTaskInfo();
  245. handleSearch();
  246. };
  247. const handleTaskChange = async (id: number) => {
  248. await checkTaskStatus();
  249. startPolling();
  250. handleReset();
  251. const task = inventoryTaskOptions.value.find((item) => item.id === id);
  252. if (!task) return;
  253. endTime.value = task.endTime;
  254. };
  255. const handleReset = () => {
  256. for (const key in searchData) {
  257. if (key !== 'taskId') {
  258. searchData[key] = null;
  259. }
  260. }
  261. handleSearch();
  262. };
  263. // 检查任务状态
  264. const checkTaskStatus = async () => {
  265. if (!searchData.taskId) return;
  266. try {
  267. const res = await getInventoryTaskConfirm(searchData.taskId);
  268. isInventory.value = res.taskState === 2;
  269. } catch (error) {
  270. ElMessage.error(`获取任务状态失败:${error}`);
  271. }
  272. };
  273. // 开始轮询
  274. const startPolling = () => {
  275. // 清除之前的轮询
  276. if (pollTimer.value) {
  277. clearInterval(pollTimer.value);
  278. pollTimer.value = null;
  279. }
  280. // 如果有任务ID,开始轮询
  281. if (searchData.taskId) {
  282. // 设置1分钟轮询一次
  283. pollTimer.value = window.setInterval(checkTaskStatus, 30000);
  284. }
  285. };
  286. const handleConfirm = async () => {
  287. const confirmed = await openMessageBox('确认后将更新为盘点的数量和地点,是否确认盘点结果?', '盘点确认', 'warning');
  288. if (!confirmed) return;
  289. if (!searchData.taskId) return;
  290. await confirmInventoryTask(searchData.taskId);
  291. await checkTaskStatus();
  292. startPolling();
  293. ElMessage.success('盘点成功');
  294. };
  295. onMounted(() => {
  296. getInventoryTaskOptions();
  297. getEmergencyEventDict();
  298. getEmergencySupplyDict();
  299. getParkDict();
  300. getLocationDict('all');
  301. });
  302. // 组件销毁前清除定时器
  303. onBeforeUnmount(() => {
  304. if (pollTimer.value) {
  305. clearInterval(pollTimer.value);
  306. pollTimer.value = null;
  307. }
  308. });
  309. watch(
  310. () => endTime.value,
  311. () => {
  312. tableConfig.maxHeight = endTime.value
  313. ? INVENTORY_CHECK_TABLE_MAX_HEIGHT_PERMISSION
  314. : INVENTORY_CHECK_TABLE_MAX_HEIGHT_DEFAULT;
  315. },
  316. );
  317. </script>
  318. <style scoped lang="scss">
  319. @use '@/styles/page-details-layout.scss' as *;
  320. @use '@/styles/page-main-layout.scss' as *;
  321. @use './src/styles/page-common.scss' as *;
  322. .date-time-container {
  323. display: flex;
  324. align-items: center;
  325. gap: 20px;
  326. margin-top: 20px;
  327. span {
  328. font-size: 14px;
  329. color: $primary-color;
  330. }
  331. }
  332. .font-red {
  333. color: red;
  334. }
  335. .image-list {
  336. display: flex;
  337. justify-content: center;
  338. align-items: center;
  339. }
  340. </style>