Act.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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 v-if="actManagementPermission" style="position: relative">
  10. <el-button type="primary" class="search-table-container--button" :icon="Plus" @click="handleCreateAct">
  11. 新建记录
  12. </el-button>
  13. <el-button plain class="search-table-container--button" @click="batchImportVisible = true">
  14. 批量导入
  15. </el-button>
  16. <RealtimeNotice />
  17. </div>
  18. <div class="act-search">
  19. <section class="select-box">
  20. <SelectableInput ref="selectableInputRef" :options="ACT_TABLE_SEARCH_OPTIONS" />
  21. <div class="select-box--item">
  22. <span>违规类型:</span>
  23. <el-select
  24. v-model="searchData.violateType"
  25. placeholder="请选择违规类型"
  26. class="select-box--select"
  27. clearable
  28. >
  29. <el-option
  30. v-for="item in ACT_VIOLATION_TYPE_OPTIONS"
  31. :key="item.value"
  32. :value="item.value"
  33. :label="item.label"
  34. :disabled="item.disabled"
  35. />
  36. </el-select>
  37. </div>
  38. <div class="select-box--item">
  39. <span>通知状态:</span>
  40. <el-select
  41. v-model="searchData.isNotice"
  42. placeholder="请选择通知状态"
  43. class="select-box--select"
  44. clearable
  45. >
  46. <el-option
  47. v-for="item in ACT_NOTICE_STATE_OPTIONS"
  48. :key="item.value"
  49. :value="item.value"
  50. :label="item.label"
  51. :disabled="item.disabled"
  52. />
  53. </el-select>
  54. </div>
  55. <div>
  56. <span>时间:</span>
  57. <el-date-picker
  58. v-model="searchData.searchTime"
  59. type="datetimerange"
  60. range-separator="至"
  61. start-placeholder="开始时间"
  62. end-placeholder="结束时间"
  63. />
  64. </div>
  65. </section>
  66. <section class="search-btn">
  67. <el-button type="primary" @click="handleSearch">查询</el-button>
  68. <el-button @click="handleReset">重置</el-button>
  69. <el-button @click="handleDownload">导出</el-button>
  70. </section>
  71. </div>
  72. </header>
  73. <!-- 表格 -->
  74. <div class="batch-table">
  75. <div class="batch-operation--div" v-show="actManagementPermission && selectionItems.length > 0">
  76. <span>已选{{ selectionItems.length }}项</span>
  77. <div class="batch-operation--div--close">
  78. <div class="batch-operation--div--button">
  79. <el-button class="custom-el-button" @click="handleBatchNotice">批量通知</el-button>
  80. <el-button class="custom-el-button" @click="handleBatchDelete">批量删除</el-button>
  81. </div>
  82. <el-icon class="close-icon" @click="handleCloseBatchOperation"><Close /></el-icon>
  83. </div>
  84. </div>
  85. <BasicTable
  86. ref="basicTableRef"
  87. :tableData="tableData"
  88. :tableConfig="tableConfig"
  89. @update:pageSize="handleSizeChange"
  90. @update:pageNumber="handleCurrentChange"
  91. @update:selection="handleSelectionChange"
  92. >
  93. <template #violateName="scope">
  94. <span>{{ scope.row.violateName ? scope.row.violateName + '(' + scope.row.staffNo + ')' : '-' }}</span>
  95. </template>
  96. <template #deptName="scope">
  97. <span>{{ scope.row.deptName || '-' }}</span>
  98. </template>
  99. <template #violateType="scope">
  100. <span>{{ ACT_VIOLATION_TYPE_LABEL[scope.row.violateType] }}</span>
  101. </template>
  102. <template #speed="scope">
  103. <span>{{ scope.row.speed || '-' }}</span>
  104. </template>
  105. <template #capturePhotos="scope">
  106. <ImageViewer :file-list="scope.row.capturePhotos" />
  107. </template>
  108. <template #violateLocation="scope">
  109. <span>{{ scope.row.violateLocation || '-' }}</span>
  110. </template>
  111. <template #createSource="scope">
  112. <span>{{ ACT_NOTICE_DATA_SOURCE_LABEL[scope.row.createSource] }}</span>
  113. </template>
  114. <template #isNotice="scope">
  115. <div class="notice-state">
  116. <div
  117. :style="{
  118. backgroundColor: ACT_NOTICE_STATE_COLOR[scope.row.isNotice],
  119. width: '6px',
  120. height: '6px',
  121. borderRadius: '50%',
  122. marginRight: '5px',
  123. }"
  124. ></div>
  125. <span>{{ ACT_NOTICE_STATE_LABEL[scope.row.isNotice] }}</span>
  126. </div>
  127. </template>
  128. <template #action="scope">
  129. <ActionButton
  130. v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
  131. text="编辑"
  132. @click="handleEditAct(scope.row.id)"
  133. />
  134. <ActionButton
  135. v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
  136. text="通知"
  137. @click="handleNoticeAct(scope.row.id)"
  138. />
  139. <ActionButton
  140. text="删除"
  141. :popconfirm="{
  142. title: '确定要删除?',
  143. }"
  144. @confirm="handleDeleteAct(scope.row.id)"
  145. />
  146. </template>
  147. </BasicTable>
  148. </div>
  149. </div>
  150. </main>
  151. <BatchImport
  152. :visible="batchImportVisible"
  153. :importApiUrl="importApiUrl"
  154. :templateUrl="templateUrl"
  155. :templateName="'违规行为记录-批量导入模版'"
  156. @close="() => (batchImportVisible = false)"
  157. @update="handleUpdate"
  158. />
  159. </div>
  160. </template>
  161. <script setup lang="ts">
  162. import BasicTable from '@/components/BasicTable.vue';
  163. import useTableConfig from '@/hooks/useTableConfigHook';
  164. import SelectableInput from '@/components/formItems/selectableInput/SelectableInput.vue';
  165. import ActionButton from '@/components/ActionButton.vue';
  166. import RealtimeNotice from './components/RealtimeNotice.vue';
  167. import dayjs from 'dayjs';
  168. import { ElMessage } from 'element-plus';
  169. import { TABLE_OPTIONS, VIOLATION_ACT_TABLE_COLUMNS, VIOLATION_ACT_TABLE_COLUMNS_CHECKONLY } from './configs/tables';
  170. import {
  171. ACT_NOTICE_DATA_SOURCE_LABEL,
  172. ACT_VIOLATION_TYPE,
  173. ACT_VIOLATION_TYPE_LABEL,
  174. ACT_TABLE_SEARCH_OPTIONS,
  175. ACT_VIOLATION_TYPE_OPTIONS,
  176. ACT_NOTICE_STATE_OPTIONS,
  177. ACT_NOTICE_STATE,
  178. ACT_NOTICE_STATE_LABEL,
  179. ACT_NOTICE_STATE_COLOR,
  180. ACT_MANAGEMENT_PROMISSION_CODE,
  181. } from './constants';
  182. import { ref, reactive, onMounted } from 'vue';
  183. import { Close, Plus } from '@element-plus/icons-vue';
  184. import { useRouter } from 'vue-router';
  185. import { openMessageBox } from '@/utils/element-plus/messageBox';
  186. import type { QueryPageRequest } from '@/types/basic-query';
  187. import type { ActTableSearch, ActTableQuery, ActTableData, UpdateActQuery } from './types';
  188. import {
  189. getActTableList,
  190. noticeActData,
  191. deleteActData,
  192. exportActViolation,
  193. } from '@/api/traffic-violation/traffic-act';
  194. import { downloadFile } from '@/views/disaster/utils/download';
  195. import ImageViewer from './components/ImageViewer.vue';
  196. import { BatchImport } from '@/components/batch-import';
  197. import { useGlobSetting } from '@/hooks/setting';
  198. import urlJoin from 'url-join';
  199. import { useUserInfoHook } from '@/hooks/useUserInfoHook';
  200. const router = useRouter();
  201. const { permissions } = useUserInfoHook();
  202. const actManagementPermission = ref<Boolean>(
  203. Boolean(permissions.find((item: { code: string }) => item.code === ACT_MANAGEMENT_PROMISSION_CODE)),
  204. );
  205. // 搜索栏
  206. const selectableInputRef = ref<InstanceType<typeof SelectableInput>>();
  207. const searchData = reactive<ActTableSearch>({});
  208. function getQuery() {
  209. if (!selectableInputRef.value) return;
  210. tableQuery.queryParam = {
  211. pageType: 1,
  212. };
  213. const selectableSearch = selectableInputRef.value.getValue();
  214. if (selectableSearch) {
  215. tableQuery.queryParam[selectableSearch.key as string] = selectableSearch.value;
  216. }
  217. if (searchData.isNotice != null) {
  218. tableQuery.queryParam.isNotice = searchData.isNotice;
  219. }
  220. if (searchData.violateType != null) {
  221. tableQuery.queryParam.violateType = searchData.violateType;
  222. }
  223. if (searchData.searchTime) {
  224. tableQuery.queryParam.startTime = dayjs(searchData.searchTime[0]).format('YYYY-MM-DD HH:MM:ss');
  225. tableQuery.queryParam.endTime = dayjs(searchData.searchTime[1]).format('YYYY-MM-DD HH:MM:ss');
  226. }
  227. }
  228. function handleSearch() {
  229. getQuery();
  230. getTableData();
  231. }
  232. function handleReset() {
  233. selectableInputRef.value?.clearValue();
  234. searchData.carNumber = undefined;
  235. searchData.violateName = undefined;
  236. searchData.deptName = undefined;
  237. searchData.isNotice = undefined;
  238. searchData.violateType = undefined;
  239. searchData.searchTime = undefined;
  240. }
  241. async function handleDownload() {
  242. getQuery();
  243. try {
  244. const res = await exportActViolation(tableQuery.queryParam);
  245. if (res.size === 0) return;
  246. const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  247. const url = window.URL.createObjectURL(blob);
  248. downloadFile(url, '违规行为记录.xlsx');
  249. } catch (e) {
  250. ElMessage.error('下载失败');
  251. console.log(e);
  252. }
  253. }
  254. // 表格
  255. const basicTableRef = ref<InstanceType<typeof BasicTable>>();
  256. const { tableConfig, pagination } = useTableConfig(
  257. actManagementPermission ? VIOLATION_ACT_TABLE_COLUMNS : VIOLATION_ACT_TABLE_COLUMNS_CHECKONLY,
  258. TABLE_OPTIONS,
  259. );
  260. const tableData = ref<ActTableData[]>([]);
  261. const tableQuery = reactive<QueryPageRequest<ActTableQuery>>({
  262. pageNumber: pagination.pageNumber,
  263. pageSize: pagination.pageSize,
  264. queryParam: {
  265. pageType: 1,
  266. },
  267. });
  268. const handleSizeChange = (value: number) => {
  269. pagination.pageSize = value;
  270. tableQuery.pageSize = value;
  271. getTableData();
  272. };
  273. const handleCurrentChange = (value: number) => {
  274. pagination.pageNumber = value;
  275. tableQuery.pageNumber = value;
  276. getTableData();
  277. };
  278. const selectionItems = ref<any[]>([]);
  279. const handleSelectionChange = (selection: any[]) => {
  280. selectionItems.value = selection;
  281. };
  282. const handleCloseBatchOperation = () => {
  283. if (!basicTableRef.value) return;
  284. basicTableRef.value.clearSelection();
  285. };
  286. const handleBatchNotice = async () => {
  287. const confirmed = await openMessageBox('', '确认通知任务吗?', 'warning');
  288. if (!confirmed) return;
  289. const unNoticeItem = selectionItems.value.filter((item) => item.isNotice === ACT_NOTICE_STATE.INACTIVE);
  290. if (!unNoticeItem.length) {
  291. ElMessage.warning('通知成功!共通知0条记录');
  292. } else {
  293. const noticeIds = unNoticeItem.map((item) => item.id);
  294. try {
  295. await noticeActData(noticeIds);
  296. ElMessage.success(`通知成功!共通知${noticeIds.length}条记录`);
  297. } catch (e) {
  298. ElMessage.error('批量通知失败');
  299. }
  300. getTableData();
  301. }
  302. };
  303. const handleBatchDelete = async () => {
  304. const confirmed = await openMessageBox('', '删除后任务不可恢复,确认删除吗?', 'warning');
  305. if (!confirmed) return;
  306. const deleteIds = selectionItems.value.map((item) => item.id);
  307. try {
  308. await deleteActData(deleteIds);
  309. ElMessage.success('批量删除成功');
  310. } catch (e) {
  311. ElMessage.error('批量删除失败');
  312. }
  313. getTableData();
  314. };
  315. async function getTableData() {
  316. tableConfig.loading = true;
  317. const res = await getActTableList(tableQuery);
  318. tableData.value = res.records;
  319. pagination.total = res.totalRow;
  320. tableConfig.loading = false;
  321. }
  322. // 批量导入
  323. const batchImportVisible = ref(false);
  324. const { urlPrefix } = useGlobSetting();
  325. const importApiUrl = ref(urlJoin(urlPrefix, '/trafficViolation/importTrafficViolationList'));
  326. const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-violation-template.xlsx');
  327. const handleUpdate = () => {
  328. batchImportVisible.value = false;
  329. getTableData();
  330. };
  331. onMounted(async () => {
  332. await getTableData();
  333. });
  334. function handleCreateAct() {
  335. router.push({
  336. name: 'traffic-violation-act-item',
  337. query: {
  338. operate: 'act-create',
  339. },
  340. });
  341. }
  342. function handleEditAct(id: number) {
  343. router.push({
  344. name: 'traffic-violation-act-item',
  345. query: {
  346. id,
  347. operate: 'act-edit',
  348. },
  349. });
  350. }
  351. async function handleNoticeAct(id: number) {
  352. tableConfig.loading = true;
  353. try {
  354. await noticeActData(id);
  355. } catch (e) {
  356. ElMessage.error('通知失败');
  357. return;
  358. } finally {
  359. tableConfig.loading = false;
  360. }
  361. getTableData();
  362. }
  363. async function handleDeleteAct(id: number) {
  364. tableConfig.loading = true;
  365. try {
  366. await deleteActData(id);
  367. } catch (e) {
  368. ElMessage.error('删除失败');
  369. return;
  370. } finally {
  371. tableConfig.loading = false;
  372. }
  373. getTableData();
  374. }
  375. </script>
  376. <style scoped lang="scss">
  377. @use '@/styles/page-details-layout.scss' as *;
  378. @use '@/styles/page-main-layout.scss' as *;
  379. @use '@/styles/basic-table-action.scss' as *;
  380. .act-search-input {
  381. max-width: 500px;
  382. }
  383. .act-search {
  384. display: flex;
  385. align-items: center;
  386. justify-content: space-between;
  387. width: 100%;
  388. }
  389. .select-box {
  390. display: flex;
  391. align-items: center;
  392. flex-wrap: wrap;
  393. gap: 32px;
  394. &--item {
  395. @include flex-center;
  396. white-space: nowrap;
  397. }
  398. span {
  399. color: rgba(0, 0, 0, 0.85);
  400. font-size: 14px;
  401. }
  402. .el-select {
  403. width: 200px;
  404. }
  405. }
  406. .search-btn {
  407. display: flex;
  408. }
  409. .notice-state {
  410. display: flex;
  411. align-items: center;
  412. justify-self: center;
  413. }
  414. .batch-table {
  415. position: relative;
  416. width: 100%;
  417. height: 100%;
  418. }
  419. .batch-operation--div {
  420. @include flex-center;
  421. justify-content: flex-start;
  422. position: absolute;
  423. top: 0;
  424. left: 0;
  425. gap: 60px;
  426. width: 100%;
  427. height: 48px;
  428. border: 4px;
  429. padding: 16px 25px;
  430. background-color: #ddefff;
  431. z-index: 100;
  432. &--close {
  433. @include flex-center;
  434. justify-content: space-between;
  435. flex: 1;
  436. }
  437. .close-icon {
  438. font-size: 20px;
  439. color: #ff4d4f;
  440. cursor: pointer;
  441. }
  442. }
  443. </style>