safetySystemConstructionWorkPlanManagement.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <template>
  2. <div class="safety-platform-container">
  3. <header class="safety-platform-container__header">
  4. <div class="breadcrumb-title"> 安全体系建设工作计划管理(管理员) </div>
  5. </header>
  6. <main class="safety-platform-container__main">
  7. <div class="search-table-container">
  8. <header>
  9. <div style="position: relative">
  10. <el-button type="primary" :icon="Plus" class="search-table-container--button" @click="handleCreate"> 添加 </el-button>
  11. <el-button plain class="search-table-container--button" @click="handleImport">导入</el-button>
  12. </div>
  13. <div class="act-search">
  14. <section class="select-box">
  15. <div class="select-box--item">
  16. <span>工作内容/计划名称:</span>
  17. <el-input
  18. v-model="tableQuery.queryParam.keyword"
  19. placeholder="搜索工作内容或计划名称"
  20. class="act-search-input"
  21. />
  22. </div>
  23. <div class="select-box--item">
  24. <span>状态:</span>
  25. <el-select v-model="tableQuery.queryParam.status" placeholder="请选择状态" clearable>
  26. <el-option
  27. v-for="item in WORK_PLAN_STATUS_OPTIONS"
  28. :key="item.value"
  29. :label="item.label"
  30. :value="item.value"
  31. />
  32. </el-select>
  33. </div>
  34. <div class="select-box--item">
  35. <span>分类名称:</span>
  36. <el-select
  37. v-model="tableQuery.queryParam.categoryName"
  38. placeholder="请选择分类名称"
  39. filterable
  40. clearable
  41. >
  42. <el-option
  43. v-for="item in classifyNameOptions"
  44. :key="item.value"
  45. :label="item.label"
  46. :value="item.value"
  47. />
  48. </el-select>
  49. </div>
  50. <div>
  51. <span>计划日期范围:</span>
  52. <el-date-picker
  53. v-model="dateRange"
  54. type="daterange"
  55. range-separator="至"
  56. start-placeholder="开始日期"
  57. end-placeholder="结束日期"
  58. value-format="YYYY-MM-DD"
  59. format="YYYY-MM-DD"
  60. @change="onChangeDateRange"
  61. />
  62. </div>
  63. </section>
  64. <section class="search-btn">
  65. <el-button type="primary" @click="handleSearch">查询</el-button>
  66. <el-button @click="handleReset">重置</el-button>
  67. <el-button plain @click="handleDownload">导出</el-button>
  68. </section>
  69. </div>
  70. </header>
  71. <div class="batch-table">
  72. <BasicTable
  73. ref="basicTableRef"
  74. :tableData="tableData"
  75. :tableConfig="tableConfig"
  76. @update:pageSize="handleSizeChange"
  77. @update:pageNumber="handleCurrentChange"
  78. >
  79. <template #feedbackCount="scope">
  80. {{ (scope.row.feedbackCount === 0 && scope.row.status === 1) ? '-' : scope.row.feedbackCount }}
  81. </template>
  82. <template #issuedCount="scope">
  83. {{ (scope.row.issuedCount === 0 && scope.row.status === 1) ? '-' : scope.row.issuedCount }}
  84. </template>
  85. <template #feedbackRatio="scope">
  86. {{ (scope.row.feedbackRatio === 0 && scope.row.status === 1) ? '-' : scope.row.feedbackRatio+'%' }}
  87. </template>
  88. <template #plannedEndTime="scope">
  89. {{ scope.row.status === 1 ? '-' : scope.row.plannedEndTime }}
  90. </template>
  91. <template #action="scope">
  92. <div class="action-container--div" style="justify-content: left">
  93. <!-- 1-未下发、2-待反馈、3-已完成 4- 已作废 -->
  94. <!-- 未下发(1):编辑、删除、下发 -->
  95. <template v-if="Number(scope.row.status) === 1">
  96. <ActionButton text="查看" @click="handleView(scope.row.id)" />
  97. <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
  98. <ActionButton
  99. text="删除"
  100. :popconfirm="{
  101. title: '确定要删除?',
  102. }"
  103. @confirm="handleDelete(scope.row.id)"
  104. />
  105. <ActionButton text="下发" @click="handleIssue(scope.row.id)" />
  106. </template>
  107. <!-- (2)待反馈-->
  108. <template v-else-if="Number(scope.row.status) === 2">
  109. <ActionButton text="查看发送对象" @click="handleViewRecipients(scope.row.id)" />
  110. <ActionButton
  111. text="作废"
  112. :popconfirm="{
  113. title: '确定要作废?',
  114. }"
  115. @confirm="handleViewInvalid(scope.row.id)"
  116. />
  117. </template>
  118. <!-- 已完成(3)/已作废(4)-->
  119. <template v-else>
  120. <ActionButton
  121. text="删除"
  122. :popconfirm="{
  123. title: '确定要删除?',
  124. }"
  125. @confirm="handleDelete(scope.row.id)"
  126. />
  127. <ActionButton text="查看发送对象" @click="handleViewRecipients(scope.row.id)" />
  128. </template>
  129. <ActionButton text="复制" @click="handleCopyData(scope.row.id)" />
  130. </div>
  131. </template>
  132. </BasicTable>
  133. </div>
  134. </div>
  135. <!-- 生产安全计划时间设置(复制) -->
  136. <el-dialog
  137. v-model="copyDialogVisible"
  138. title="生产安全计划时间设置"
  139. width="500"
  140. :close-on-click-modal="false"
  141. :close-on-press-escape="false"
  142. >
  143. <div>
  144. <el-form ref="copyFormRef" :model="copyParams" :rules="copyParamsRules" label-width="150px">
  145. <el-form-item label="计划结束时间:" prop="plannedEndTime">
  146. <el-date-picker
  147. v-model="copyParams.plannedEndTime"
  148. type="date"
  149. value-format="YYYY-MM-DD"
  150. placeholder="请选择完成时间"
  151. style="width: 100%"
  152. />
  153. </el-form-item>
  154. </el-form>
  155. </div>
  156. <template #footer>
  157. <div class="dialog-footer">
  158. <el-button @click="copyDialogVisible = false">取消</el-button>
  159. <el-button type="primary" @click="handleConfirmCopy"> 保存 </el-button>
  160. </div>
  161. </template>
  162. </el-dialog>
  163. <!-- 生产安全计划下发 -->
  164. <el-dialog
  165. v-model="issueWorkPlanDialogVisible"
  166. title="生产安全计划下发"
  167. width="500"
  168. :close-on-click-modal="false"
  169. :close-on-press-escape="false"
  170. >
  171. <div>
  172. <el-form ref="formRef" :model="issueWorkPlanForm" :rules="issueWorkPlanFormRules" label-width="150px">
  173. <el-form-item label="计划执行部门分组:" prop="executGroupIds">
  174. <el-select
  175. v-model="issueWorkPlanForm.executGroupIds"
  176. multiple
  177. size="large"
  178. placeholder="分组名称"
  179. style="width: 100%"
  180. >
  181. <el-option v-for="group in groupList" :key="group.id" :label="group.name" :value="group.id" />
  182. </el-select>
  183. </el-form-item>
  184. <el-form-item label="计划开始时间:" prop="plannedStartTime" required>
  185. <el-date-picker
  186. v-model="issueWorkPlanForm.plannedStartTime"
  187. type="date"
  188. placeholder="请选择完成时间"
  189. style="width: 100%"
  190. />
  191. </el-form-item>
  192. <el-form-item label="计划完成时间:" prop="plannedEndTime" required>
  193. <el-date-picker
  194. v-model="issueWorkPlanForm.plannedEndTime"
  195. type="date"
  196. placeholder="请选择完成时间"
  197. style="width: 100%"
  198. />
  199. </el-form-item>
  200. </el-form>
  201. </div>
  202. <template #footer>
  203. <div class="dialog-footer">
  204. <el-button @click="issueWorkPlanDialogVisible = false">取消</el-button>
  205. <el-button type="primary" @click="handleConfirmIssue"> 确认 </el-button>
  206. </div>
  207. </template>
  208. </el-dialog>
  209. </main>
  210. <BatchImport
  211. v-if="batchImportVisible"
  212. :visible="batchImportVisible"
  213. :import-api-url="importApiUrl"
  214. :template-url="templateUrl"
  215. template-name="安全体系建设工作计划导入模版"
  216. :show-template="true"
  217. @close="batchImportVisible = false"
  218. @update="handleUpdate"
  219. />
  220. </div>
  221. </template>
  222. <script setup lang="ts">
  223. import { onMounted, reactive, ref } from 'vue';
  224. import { ElMessage } from 'element-plus';
  225. import { Plus } from '@element-plus/icons-vue';
  226. import BasicTable from '@/components/BasicTable.vue';
  227. import useTableConfig from '@/hooks/useTableConfigHook';
  228. import ActionButton from '@/components/ActionButton.vue';
  229. import {
  230. TABLE_OPTIONS,
  231. WORK_PLAN_TABLE_COLUMNS,
  232. WORK_PLAN_STATUS_OPTIONS,
  233. WORK_PLAN_STATUS_LABEL,
  234. } from './configs/tables';
  235. import { useRouter } from 'vue-router';
  236. import type { QueryPageRequest } from '@/types/basic-query';
  237. import {
  238. queryWorkPlanPage,
  239. cancelWorkPlan,
  240. issueWorkPlan,
  241. deleteWorkPlan,
  242. exportTableData,
  243. copyWorkPlan,
  244. type IssueWorkPlanParams,
  245. } from '@/api/safety-system-construction-work-plan';
  246. import { queryUserGroupPage } from '@/api/system/person-group';
  247. import { IssueWorkPlanFormRules } from './configs/form';
  248. import { downloadByData } from '@/utils/file/download';
  249. import BatchImport from '@/components/batch-import/BatchImport.vue';
  250. import { useGlobSetting } from '@/hooks/setting';
  251. import urlJoin from 'url-join';
  252. const router = useRouter();
  253. const groupList = ref<any>([]);
  254. // 表格
  255. const basicTableRef = ref<InstanceType<typeof BasicTable>>();
  256. const { tableConfig, pagination } = useTableConfig(WORK_PLAN_TABLE_COLUMNS, TABLE_OPTIONS);
  257. const tableData = ref<any[]>([]);
  258. // 日期范围
  259. const dateRange = ref<[string, string] | null>(null);
  260. // 分类名称选项
  261. const classifyNameOptions = ref<Array<{ label: string; value: string }>>([
  262. { label: '全部', value: '全部' },
  263. { label: '安全综合工作', value: '安全综合工作' },
  264. { label: '生产安全工作', value: '生产安全工作' },
  265. { label: '安全文化活动', value: '安全文化活动' },
  266. ]);
  267. const tableQuery = reactive<QueryPageRequest<any>>({
  268. pageNumber: pagination.pageNumber,
  269. pageSize: pagination.pageSize,
  270. queryParam: {
  271. keyword: '',
  272. status: '',
  273. categoryName: '',
  274. startDate: '',
  275. endDate: '',
  276. /** 排序 */
  277. // sortField: "created_at",
  278. // sortOrder: false,
  279. },
  280. });
  281. /**
  282. * @description: 下发
  283. * @return {*}
  284. */
  285. const issueWorkPlanDialogVisible = ref(false);
  286. /**
  287. * @description: 下发参数
  288. * @return {*}
  289. */
  290. const issueWorkPlanForm = reactive<IssueWorkPlanParams>({
  291. id: 0,
  292. executGroupIds: '',
  293. plannedStartTime: '',
  294. plannedEndTime: '',
  295. });
  296. /**
  297. * @description: 下发rules验证
  298. * @param {*} value
  299. * @return {*}
  300. */
  301. const issueWorkPlanFormRules = ref(IssueWorkPlanFormRules);
  302. /**
  303. * @description: 部门接口
  304. * @return {*}
  305. */
  306. const cascaderProp = {
  307. multiple: true,
  308. expandTrigger: 'hover',
  309. checkStrictly: true,
  310. emitPath: false,
  311. value: 'id',
  312. label: 'deptName',
  313. };
  314. // 格式化时间
  315. const formatDate = (date) => {
  316. if (!date) return '';
  317. const year = date.getFullYear();
  318. const month = String(date.getMonth() + 1).padStart(2, '0');
  319. const day = String(date.getDate()).padStart(2, '0');
  320. return `${year}-${month}-${day}`; // 输出:2026/02/11
  321. };
  322. // 获取级联部门数据
  323. const loadGroupTreeData = async () => {
  324. queryUserGroupPage({
  325. pageNumber: 1,
  326. pageSize: 500,
  327. }).then((res) => {
  328. groupList.value = res.records;
  329. });
  330. };
  331. /**
  332. * @description: 下发
  333. * @param {*} id
  334. * @return {*}
  335. */
  336. const handleIssue = async (id: number) => {
  337. issueWorkPlanDialogVisible.value = true;
  338. issueWorkPlanForm.id = id;
  339. Object.assign(issueWorkPlanForm, {
  340. executGroupIds: '',
  341. plannedStartTime: '',
  342. plannedEndTime: '',
  343. });
  344. };
  345. const handleConfirmIssue = async () => {
  346. if (!issueWorkPlanForm.executGroupIds || issueWorkPlanForm.executGroupIds.length === 0) {
  347. ElMessage.error('请选择执行部门');
  348. return;
  349. }
  350. if (!issueWorkPlanForm.plannedStartTime || !issueWorkPlanForm.plannedEndTime) {
  351. ElMessage.error('请选择计划时间');
  352. return;
  353. }
  354. if (issueWorkPlanForm.plannedStartTime >= issueWorkPlanForm.plannedEndTime) {
  355. ElMessage.error('计划开始时间不能晚于计划结束时间');
  356. return;
  357. }
  358. try {
  359. await issueWorkPlan({
  360. ...issueWorkPlanForm,
  361. executGroupIds: issueWorkPlanForm.executGroupIds.toString(),
  362. plannedStartTime: formatDate(issueWorkPlanForm.plannedStartTime),
  363. plannedEndTime: formatDate(issueWorkPlanForm.plannedEndTime),
  364. });
  365. ElMessage.success('下发成功');
  366. getTableData();
  367. } catch (e) {
  368. console.error('下发工作计划失败:', e);
  369. ElMessage.error('下发失败,请重试');
  370. }
  371. issueWorkPlanDialogVisible.value = false;
  372. };
  373. const handleSizeChange = (value: number) => {
  374. pagination.pageSize = value;
  375. tableQuery.pageSize = value;
  376. getTableData();
  377. };
  378. const handleCurrentChange = (value: number) => {
  379. pagination.pageNumber = value;
  380. tableQuery.pageNumber = value;
  381. getTableData();
  382. };
  383. const onChangeDateRange = (value) => {
  384. tableQuery.queryParam.startDate = value[0];
  385. tableQuery.queryParam.endDate = value[1];
  386. getTableData();
  387. };
  388. async function getTableData() {
  389. tableConfig.loading = true;
  390. try {
  391. const res = await queryWorkPlanPage(tableQuery);
  392. if (res) {
  393. tableData.value = res.records;
  394. pagination.total = res.totalRow;
  395. }
  396. } catch (e) {
  397. console.error('获取工作计划列表失败:', e);
  398. ElMessage.error('获取工作计划列表失败');
  399. tableData.value = [];
  400. pagination.total = 0;
  401. } finally {
  402. tableConfig.loading = false;
  403. }
  404. }
  405. const handleSearch = () => {
  406. pagination.pageNumber = 1;
  407. tableQuery.pageNumber = 1;
  408. getTableData();
  409. };
  410. const handleReset = () => {
  411. tableQuery.pageNumber = 1;
  412. tableQuery.queryParam = {
  413. keyword: '',
  414. status: '',
  415. categoryName: '',
  416. startDate: '',
  417. endDate: '',
  418. /** 排序 */
  419. // sortField: "created_at",
  420. // sortOrder: false,
  421. };
  422. dateRange.value = null;
  423. handleSearch();
  424. };
  425. const handleCreate = () => {
  426. router.push({
  427. name: 'SafetySystemConstructionWorkPlanManagementItem',
  428. query: {
  429. operate: 'work-plan-create',
  430. },
  431. });
  432. };
  433. const handleEdit = (id: number) => {
  434. router.push({
  435. name: 'SafetySystemConstructionWorkPlanManagementItem',
  436. query: {
  437. id,
  438. operate: 'work-plan-edit',
  439. },
  440. });
  441. };
  442. const handleDelete = async (id: number) => {
  443. try {
  444. await deleteWorkPlan(id);
  445. ElMessage.success('删除成功');
  446. getTableData();
  447. } catch (e) {
  448. console.error('删除工作计划失败:', e);
  449. ElMessage.error('删除失败,请重试');
  450. }
  451. };
  452. const handleView = (id: number) => {
  453. router.push({
  454. name: 'SafetySystemConstructionWorkPlanManagementItem',
  455. query: {
  456. id,
  457. operate: 'work-plan-view',
  458. },
  459. });
  460. };
  461. const handleViewInvalid = async (id: number) => {
  462. try {
  463. await cancelWorkPlan(id);
  464. ElMessage.success('作废成功');
  465. getTableData();
  466. } catch (e) {
  467. console.error('作废工作计划失败:', e);
  468. ElMessage.error('作废失败,请重试');
  469. }
  470. };
  471. const handleViewRecipients = (id: number) => {
  472. router.push({
  473. name: 'SafetySystemConstructionWorkPlanManagementViewSender',
  474. query: {
  475. id,
  476. },
  477. });
  478. };
  479. // 复制
  480. const copyDialogVisible = ref(false)
  481. const copyFormRef = ref()
  482. const copyParams = reactive({
  483. id: 0,
  484. plannedEndTime: ''
  485. })
  486. const copyParamsRules = ref({
  487. plannedEndTime: [{ required: true, message: '请选择计划结束日期', trigger: 'change' }]
  488. })
  489. const handleCopyData = (id:number)=>{
  490. copyDialogVisible.value = true
  491. copyParams.id = id
  492. }
  493. const handleValidate = async () => {
  494. if (!copyFormRef.value) return false;
  495. try {
  496. await copyFormRef.value.validate();
  497. return true;
  498. } catch (error) {
  499. return false;
  500. }
  501. };
  502. // 保存复制
  503. const handleConfirmCopy = async ()=>{
  504. const res = await handleValidate();
  505. if (!res) return;
  506. try {
  507. await copyWorkPlan(copyParams)
  508. ElMessage.success('复制成功')
  509. copyDialogVisible.value = false
  510. getTableData();
  511. copyFormRef.value.resetFields()
  512. } catch (error:any) {
  513. ElMessage.error('复制失败', error.message)
  514. copyDialogVisible.value = false
  515. copyFormRef.value.resetFields()
  516. }
  517. }
  518. // 批量导入
  519. const batchImportVisible = ref(false);
  520. const { urlPrefix } = useGlobSetting();
  521. const importApiUrl = ref(urlJoin(urlPrefix, '/safetyWorkPlan/workPlan/import'));
  522. const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/安全体系建设工作计划导入模版.xlsx');
  523. // 导入
  524. const handleImport = () => {
  525. batchImportVisible.value = true;
  526. };
  527. const handleUpdate = () => {
  528. batchImportVisible.value = false;
  529. getTableData();
  530. };
  531. // 导出
  532. const handleDownload = async () => {
  533. try {
  534. const response = await exportTableData(tableQuery.queryParam);
  535. if (response) {
  536. const fileName = `教育培训计划管理(管理员)_${new Date().toISOString().split('T')[0]}.xlsx`;
  537. downloadByData(response, fileName);
  538. ElMessage.success('导出成功');
  539. }
  540. } catch (e) {
  541. console.error('导出教育培训计划管理(管理员)失败:', e);
  542. ElMessage.error('导出失败,请重试');
  543. }
  544. };
  545. onMounted(() => {
  546. getTableData();
  547. loadGroupTreeData();
  548. });
  549. </script>
  550. <style scoped lang="scss">
  551. @use '@/styles/page-details-layout.scss' as *;
  552. @use '@/styles/page-main-layout.scss' as *;
  553. @use '@/styles/basic-table-action.scss' as *;
  554. @use '@/views/traffic/violation/style/act-search-table.scss' as *;
  555. </style>