PageManagement.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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. <el-button
  10. type="primary"
  11. class="search-table-container--button"
  12. :icon="Plus"
  13. v-if="planManagementPremissions"
  14. @click="handleAdd"
  15. >添加预案
  16. </el-button>
  17. <BasicSearch
  18. :searchConfig="EMERGENCY_PLAN_MANAGEMENT_SEARCH_CONFIG"
  19. :searchData="searchData"
  20. @update:searchData="handleSearch"
  21. >
  22. <template #planType>
  23. <el-select v-model="searchData.planType" placeholder="请输入预案类型" filterable>
  24. <el-option
  25. v-for="item in planTypeDice"
  26. :key="item.itemCode"
  27. :label="item.itemValue"
  28. :value="item.itemCode"
  29. />
  30. </el-select>
  31. </template>
  32. <template #eventType>
  33. <el-select v-model="searchData.eventType" placeholder="请选择事件类型" filterable>
  34. <el-option
  35. v-for="item in emergencyEventDice"
  36. :key="item.itemCode"
  37. :label="item.itemValue"
  38. :value="item.itemCode"
  39. />
  40. </el-select>
  41. </template>
  42. </BasicSearch>
  43. </header>
  44. <BasicTable
  45. :tableData="tableData"
  46. :tableConfig="tableConfig"
  47. @update:pageSize="handleSizeChange"
  48. @update:pageNumber="handleCurrentChange"
  49. >
  50. <template #planType="scope">
  51. <span>{{ getPlanType(scope.row.planType) }}</span>
  52. </template>
  53. <template #eventType="scope">
  54. <span>{{ getEmergencyEvent(scope.row.eventType) }}</span>
  55. </template>
  56. <template #taskSite="scope">
  57. <span>{{ TASK_SITE_MAP[scope.row.taskSite] }}</span>
  58. </template>
  59. <template #status="scope">
  60. <span
  61. :class="
  62. scope.row.status === EMERGENCY_PLAN_STATUS.APPROVAL_IN_PROGRESS ||
  63. scope.row.status === EMERGENCY_PLAN_STATUS.RETURNED
  64. ? 'highlight-text'
  65. : ''
  66. "
  67. @click="handleViewApprovalProcess(scope.row)"
  68. >{{ getEmergencyPlanStatusLabel(scope.row.status) }}</span
  69. >
  70. </template>
  71. <template #isDisabled="scope">
  72. <div
  73. style="cursor: pointer; width: 50px; margin: auto"
  74. @click="handleChangeDisable(scope.row.id, scope.row.isDisabled)"
  75. >
  76. <el-switch
  77. v-model="scope.row.isDisabled"
  78. :active-value="0"
  79. :inactive-value="1"
  80. style="pointer-events: none"
  81. />
  82. </div>
  83. </template>
  84. <template #action="scope">
  85. <div class="action-container--div">
  86. <ActionButton text="查看" @click="handleView(scope.row.id)" />
  87. <ActionButton
  88. v-if="planManagementPremissions && scope.row.status !== EMERGENCY_PLAN_STATUS.APPROVAL_IN_PROGRESS"
  89. text="编辑"
  90. @click="handleEdit(scope.row.id)"
  91. />
  92. <ActionButton
  93. v-if="planManagementPremissions && scope.row.status !== EMERGENCY_PLAN_STATUS.APPROVAL_IN_PROGRESS"
  94. text="删除"
  95. :popconfirm="{ title: '确认删除' }"
  96. @confirm="handleDelete(scope.row.id)"
  97. />
  98. </div>
  99. </template>
  100. </BasicTable>
  101. </div>
  102. </div>
  103. </div>
  104. <BasicDialog ref="basicDialogRef" title="审批流程" width="1000">
  105. <template #form>
  106. <BasicTable :tableData="approvalProcessData" :tableConfig="approvalProcessConfig">
  107. <template #approvalType="scope">
  108. <span>{{ APPROVAL_TYPE_MAP[scope.row.approvalType] }}</span>
  109. </template>
  110. <template #approvalStatus="scope">
  111. <span>{{ APPROVAL_STATUS_MAP[scope.row.approvalStatus] }}</span>
  112. </template>
  113. <template #approvalTime="scope">
  114. <span v-if="scope.row.approvalStatus !== APPROVAL_STATUS.OHTER">{{ scope.row.approvalTime }}</span>
  115. </template>
  116. </BasicTable>
  117. </template>
  118. </BasicDialog>
  119. </template>
  120. <script setup lang="ts">
  121. import { useRouter } from 'vue-router';
  122. import { ref, reactive, onMounted } from 'vue';
  123. import { Plus } from '@element-plus/icons-vue';
  124. import { ElMessage, ElSwitch } from 'element-plus';
  125. import BasicSearch from '@/components/BasicSearch.vue';
  126. import BasicTable from '@/components/BasicTable.vue';
  127. import BasicDialog from '@/components/BasicDialog.vue';
  128. import ActionButton from '@/components/ActionButton.vue';
  129. import useTableConfig from '@/hooks/useTableConfigHook';
  130. import { useEmergencyHook } from '../src/hoos';
  131. import { useEmergencyPlanHook } from './src/hook';
  132. import { useUserInfoHook } from '@/hooks/useUserInfoHook';
  133. import type { QueryPageRequest } from '@/types/basic-query';
  134. import type {
  135. PlanEmergencyListQuery,
  136. PlanEmergencyListResponse,
  137. ApprovalProcessList,
  138. ApprovalProcessResponse,
  139. ProcessInfoListType,
  140. } from '@/types/emergency-plan';
  141. import {
  142. getEmergencyPlanList,
  143. deleteEmergencyPlan,
  144. updateEmergencyPlanDisabled,
  145. queryApprovalProcess,
  146. } from '@/api/emergency-plan';
  147. import {
  148. EMERGENCY_PLAN_MANAGEMENT_SEARCH_CONFIG,
  149. EMERGENCY_PLAN_MANAGEMENT_TABLE_OPTIONS,
  150. EMERGENCY_PLAN_MANAGEMENT_TABLE_COLUMNS,
  151. TABLE_MAX_HEIGHT_DEFAULT,
  152. TABLE_MAX_HEIGHT_PERMISSION,
  153. APPROVAL_PROCESS_TABLE_COLUMNS,
  154. APPROVAL_PROCESS_TABLE_OPTIONS,
  155. } from './src/config';
  156. import { EMERGENCY_PERMISSIONS } from '@/views/emergency/src/constant';
  157. import {
  158. EMERGENCY_PLAN_STATUS,
  159. APPROVAL_TYPE_MAP,
  160. APPROVAL_STATUS_MAP,
  161. APPROVAL_STATUS,
  162. TASK_SITE_MAP,
  163. } from './src/constant';
  164. const router = useRouter();
  165. const planManagementPremissions = ref<boolean>(false);
  166. const { tableConfig, pagination } = useTableConfig(
  167. EMERGENCY_PLAN_MANAGEMENT_TABLE_COLUMNS,
  168. EMERGENCY_PLAN_MANAGEMENT_TABLE_OPTIONS,
  169. );
  170. // 添加合并单元格方法
  171. const mergeApprovalProcess = ({ column, rowIndex }) => {
  172. // 合并流程步骤列
  173. if (column.label === '流程步骤' || column.label === '节点描述') {
  174. if (
  175. rowIndex > 0 &&
  176. approvalProcessData.value[rowIndex].approvalOrder === approvalProcessData.value[rowIndex - 1].approvalOrder
  177. ) {
  178. return {
  179. rowspan: 0,
  180. colspan: 0,
  181. };
  182. } else {
  183. let count = 1;
  184. for (let i = rowIndex + 1; i < approvalProcessData.value.length; i++) {
  185. if (approvalProcessData.value[i].approvalOrder === approvalProcessData.value[rowIndex].approvalOrder) {
  186. count++;
  187. } else {
  188. break;
  189. }
  190. }
  191. return {
  192. rowspan: count,
  193. colspan: 1,
  194. };
  195. }
  196. }
  197. // 其他列正常显示
  198. return {
  199. rowspan: 1,
  200. colspan: 1,
  201. };
  202. };
  203. // 高亮表格
  204. const highlightCell = ({ rowIndex }) => {
  205. // 找到应该高亮的行索引
  206. const findHighlightRowIndex = () => {
  207. // 遍历所有行,寻找符合条件的行
  208. for (let i = 0; i < approvalProcessData.value.length; i++) {
  209. const row = approvalProcessData.value[i];
  210. // 如果没有审批时间,返回该行索引
  211. if (!row.approvalTime) {
  212. return i;
  213. }
  214. // 如果有审批时间且状态为退回,返回该行索引
  215. if (row.approvalTime && row.approvalStatus === APPROVAL_STATUS.REJECTED) {
  216. return i;
  217. }
  218. }
  219. // 如果没有找到符合条件的行,返回最后一行索引
  220. return approvalProcessData.value.length - 1;
  221. };
  222. // 获取应该高亮的行索引
  223. const highlightRowIndex = findHighlightRowIndex();
  224. // 如果当前行是应该高亮的行,则高亮
  225. if (rowIndex === highlightRowIndex) {
  226. return 'row--highlight';
  227. }
  228. // 特殊处理:如果高亮行是或签,且当前行与高亮行属于同一流程步骤,也高亮
  229. const highlightRow = approvalProcessData.value[highlightRowIndex];
  230. if (highlightRow) {
  231. // 检查当前行是否与高亮行属于同一流程步骤
  232. const currentRow = approvalProcessData.value[rowIndex];
  233. if (currentRow && currentRow.approvalOrder === highlightRow.approvalOrder) {
  234. return 'row--highlight';
  235. }
  236. }
  237. return '';
  238. };
  239. const { tableConfig: approvalProcessConfig } = useTableConfig(
  240. APPROVAL_PROCESS_TABLE_COLUMNS,
  241. {
  242. ...APPROVAL_PROCESS_TABLE_OPTIONS,
  243. spanMethod: mergeApprovalProcess,
  244. rowClassName: highlightCell,
  245. },
  246. false,
  247. );
  248. const { emergencyEventDice, getEmergencyEventDict, getEmergencyEvent } = useEmergencyHook();
  249. const { planTypeDice, getPlanTypeDict, getPlanType, getEmergencyPlanStatusLabel } = useEmergencyPlanHook();
  250. const { permissions } = useUserInfoHook();
  251. let planManagementListQuery: QueryPageRequest<PlanEmergencyListQuery> = {
  252. pageNumber: pagination.pageNumber,
  253. pageSize: pagination.pageSize,
  254. queryParam: {},
  255. };
  256. const tableData = ref<PlanEmergencyListResponse[]>([]);
  257. const searchData = reactive({
  258. planName: null,
  259. planType: null,
  260. eventType: null,
  261. taskSite: null,
  262. status: null,
  263. });
  264. const handleSearch = () => {
  265. planManagementListQuery.queryParam = {};
  266. if (searchData.planName) {
  267. planManagementListQuery.queryParam.planName = searchData.planName;
  268. }
  269. if (searchData.planType) {
  270. planManagementListQuery.queryParam.planType = searchData.planType;
  271. }
  272. if (searchData.eventType) {
  273. planManagementListQuery.queryParam.eventType = searchData.eventType;
  274. }
  275. if (searchData.taskSite) {
  276. planManagementListQuery.queryParam.taskSite = searchData.taskSite;
  277. }
  278. if (searchData.status !== null) {
  279. planManagementListQuery.queryParam.status = searchData.status;
  280. }
  281. getTableData();
  282. };
  283. const getTableData = async () => {
  284. tableConfig.loading = true;
  285. const res = await getEmergencyPlanList(planManagementListQuery);
  286. tableData.value = res.records;
  287. pagination.total = res.totalRow;
  288. tableConfig.loading = false;
  289. };
  290. const handleSizeChange = (value: number) => {
  291. pagination.pageSize = value;
  292. planManagementListQuery.pageSize = value;
  293. getTableData();
  294. };
  295. const handleCurrentChange = (value: number) => {
  296. pagination.pageNumber = value;
  297. planManagementListQuery.pageNumber = value;
  298. getTableData();
  299. };
  300. const handleChangeDisable = async (id: number, value: number) => {
  301. if (!planManagementPremissions.value) {
  302. ElMessage.error('暂无权限');
  303. return false;
  304. }
  305. try {
  306. await updateEmergencyPlanDisabled({ planId: id, isDisabled: value === 1 ? 0 : 1 });
  307. ElMessage.success('操作成功');
  308. } catch (error) {
  309. ElMessage.error('操作失败');
  310. }
  311. getTableData();
  312. return false;
  313. };
  314. const defaultName = 'plan-management-detail';
  315. const handleAdd = () => {
  316. router.push({
  317. name: defaultName,
  318. query: {
  319. type: 'add',
  320. },
  321. });
  322. };
  323. const handleEdit = (id: number) => {
  324. router.push({
  325. name: defaultName,
  326. query: {
  327. id,
  328. type: 'edit',
  329. },
  330. });
  331. };
  332. const handleView = (id: number) => {
  333. router.push({
  334. name: defaultName,
  335. query: {
  336. id,
  337. type: 'view',
  338. },
  339. });
  340. };
  341. const handleDelete = async (id: number) => {
  342. await deleteEmergencyPlan(id);
  343. ElMessage.success('删除成功');
  344. await getTableData();
  345. };
  346. const basicDialogRef = ref<InstanceType<typeof BasicDialog>>();
  347. const approvalProcessData = ref<ApprovalProcessList[]>([]);
  348. const handleViewApprovalProcess = async (row: PlanEmergencyListResponse) => {
  349. if (row.status !== EMERGENCY_PLAN_STATUS.APPROVAL_IN_PROGRESS && row.status !== EMERGENCY_PLAN_STATUS.RETURNED)
  350. return;
  351. const res = await queryApprovalProcess(row.approvalTemplateId, row.id);
  352. // 处理审批流程数据
  353. approvalProcessData.value = [];
  354. res.forEach((item: ApprovalProcessResponse) => {
  355. if (item.processInfoList && item.processInfoList.length > 0) {
  356. item.processInfoList.forEach((process: ProcessInfoListType) => {
  357. approvalProcessData.value.push({
  358. approvalOrder: item.approvalOrder,
  359. nodeDescription: item.nodeDescription,
  360. approvalType: process.approvalType,
  361. approverName: process.approverName,
  362. approvalContent: process.approvalContent,
  363. approvalStatus: process.approvalStatus,
  364. approvalTime: process.approvalTime,
  365. });
  366. });
  367. }
  368. });
  369. basicDialogRef.value?.openDialog();
  370. };
  371. onMounted(() => {
  372. getPlanTypeDict();
  373. getEmergencyEventDict();
  374. getTableData();
  375. planManagementPremissions.value = Boolean(
  376. permissions.find((item: { code: string }) => item.code === EMERGENCY_PERMISSIONS.PLANE_MANAGEMENT),
  377. );
  378. tableConfig.maxHeight = planManagementPremissions.value ? TABLE_MAX_HEIGHT_PERMISSION : TABLE_MAX_HEIGHT_DEFAULT;
  379. });
  380. </script>
  381. <style scoped lang="scss">
  382. @use '@/styles/page-details-layout.scss' as *;
  383. @use '@/styles/page-main-layout.scss' as *;
  384. @use '@/styles/basic-table-action.scss' as *;
  385. .highlight-text {
  386. cursor: pointer;
  387. color: $primary-color;
  388. }
  389. </style>