Act.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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 ? scope.row.speed + ' km/h' : '-' }}</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. <div class="action-container--div" style="justify-content: left">
  130. <ActionButton
  131. v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
  132. text="编辑"
  133. @click="handleEditAct(scope.row.id)"
  134. />
  135. <ActionButton
  136. v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
  137. text="通知"
  138. @click="handleNoticeAct(scope.row.id)"
  139. />
  140. <ActionButton
  141. text="删除"
  142. :popconfirm="{
  143. title: '确定要删除?',
  144. }"
  145. @confirm="handleDeleteAct(scope.row.id)"
  146. />
  147. </div>
  148. </template>
  149. </BasicTable>
  150. </div>
  151. </div>
  152. </main>
  153. <BatchImport
  154. :visible="batchImportVisible"
  155. :importApiUrl="importApiUrl"
  156. :templateUrl="templateUrl"
  157. :templateName="'违规行为记录-批量导入模版'"
  158. @close="() => (batchImportVisible = false)"
  159. @update="handleUpdate"
  160. />
  161. </div>
  162. </template>
  163. <script setup lang="ts">
  164. import BasicTable from '@/components/BasicTable.vue';
  165. import useTableConfig from '@/hooks/useTableConfigHook';
  166. import SelectableInput from '@/components/formItems/selectableInput/SelectableInput.vue';
  167. import ActionButton from '@/components/ActionButton.vue';
  168. import RealtimeNotice from './components/RealtimeNotice.vue';
  169. import dayjs from 'dayjs';
  170. import { ElMessage } from 'element-plus';
  171. import { TABLE_OPTIONS, VIOLATION_ACT_TABLE_COLUMNS, VIOLATION_ACT_TABLE_COLUMNS_CHECKONLY } from './configs/tables';
  172. import {
  173. ACT_NOTICE_DATA_SOURCE_LABEL,
  174. ACT_VIOLATION_TYPE,
  175. ACT_VIOLATION_TYPE_LABEL,
  176. ACT_TABLE_SEARCH_OPTIONS,
  177. ACT_VIOLATION_TYPE_OPTIONS,
  178. ACT_NOTICE_STATE_OPTIONS,
  179. ACT_NOTICE_STATE,
  180. ACT_NOTICE_STATE_LABEL,
  181. ACT_NOTICE_STATE_COLOR,
  182. ACT_MANAGEMENT_PROMISSION_CODE,
  183. } from './constants';
  184. import { ref, reactive, onMounted } from 'vue';
  185. import { Close, Plus } from '@element-plus/icons-vue';
  186. import { useRouter } from 'vue-router';
  187. import { openMessageBox } from '@/utils/element-plus/messageBox';
  188. import type { QueryPageRequest } from '@/types/basic-query';
  189. import type { ActTableSearch, ActTableQuery, ActTableData, UpdateActQuery } from './types';
  190. import {
  191. getActTableList,
  192. noticeActData,
  193. deleteActData,
  194. exportActViolation,
  195. } from '@/api/traffic-violation/traffic-act';
  196. import { downloadFile } from '@/views/disaster/utils/download';
  197. import ImageViewer from './components/ImageViewer.vue';
  198. import { BatchImport } from '@/components/batch-import';
  199. import { useGlobSetting } from '@/hooks/setting';
  200. import urlJoin from 'url-join';
  201. import { useUserInfoHook } from '@/hooks/useUserInfoHook';
  202. const router = useRouter();
  203. const { permissions } = useUserInfoHook();
  204. const actManagementPermission = ref<Boolean>(
  205. Boolean(permissions.find((item: { code: string }) => item.code === ACT_MANAGEMENT_PROMISSION_CODE)),
  206. );
  207. // 搜索栏
  208. const selectableInputRef = ref<InstanceType<typeof SelectableInput>>();
  209. const searchData = reactive<ActTableSearch>({});
  210. function getQuery() {
  211. if (!selectableInputRef.value) return;
  212. tableQuery.queryParam = {
  213. pageType: 1,
  214. };
  215. const selectableSearch = selectableInputRef.value.getValue();
  216. if (selectableSearch) {
  217. tableQuery.queryParam[selectableSearch.key as string] = selectableSearch.value;
  218. }
  219. if (searchData.isNotice != null) {
  220. tableQuery.queryParam.isNotice = searchData.isNotice;
  221. }
  222. if (searchData.violateType != null) {
  223. tableQuery.queryParam.violateType = searchData.violateType;
  224. }
  225. if (searchData.searchTime) {
  226. tableQuery.queryParam.startTime = dayjs(searchData.searchTime[0]).format('YYYY-MM-DD HH:MM:ss');
  227. tableQuery.queryParam.endTime = dayjs(searchData.searchTime[1]).format('YYYY-MM-DD HH:MM:ss');
  228. }
  229. }
  230. function handleSearch() {
  231. getQuery();
  232. getTableData();
  233. }
  234. function handleReset() {
  235. selectableInputRef.value?.clearValue();
  236. searchData.carNumber = undefined;
  237. searchData.violateName = undefined;
  238. searchData.deptName = undefined;
  239. searchData.isNotice = undefined;
  240. searchData.violateType = undefined;
  241. searchData.searchTime = undefined;
  242. handleSearch();
  243. }
  244. async function handleDownload() {
  245. getQuery();
  246. try {
  247. const res = await exportActViolation(tableQuery.queryParam);
  248. if (res.size === 0) return;
  249. const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
  250. const url = window.URL.createObjectURL(blob);
  251. downloadFile(url, '违规行为记录.xlsx');
  252. } catch (e) {
  253. ElMessage.error('下载失败');
  254. console.log(e);
  255. }
  256. }
  257. // 表格
  258. const basicTableRef = ref<InstanceType<typeof BasicTable>>();
  259. const { tableConfig, pagination } = useTableConfig(
  260. actManagementPermission.value ? VIOLATION_ACT_TABLE_COLUMNS : VIOLATION_ACT_TABLE_COLUMNS_CHECKONLY,
  261. TABLE_OPTIONS,
  262. );
  263. const tableData = ref<ActTableData[]>([]);
  264. const tableQuery = reactive<QueryPageRequest<ActTableQuery>>({
  265. pageNumber: pagination.pageNumber,
  266. pageSize: pagination.pageSize,
  267. queryParam: {
  268. pageType: 1,
  269. },
  270. });
  271. const handleSizeChange = (value: number) => {
  272. pagination.pageSize = value;
  273. tableQuery.pageSize = value;
  274. getTableData();
  275. };
  276. const handleCurrentChange = (value: number) => {
  277. pagination.pageNumber = value;
  278. tableQuery.pageNumber = value;
  279. getTableData();
  280. };
  281. const selectionItems = ref<any[]>([]);
  282. const handleSelectionChange = (selection: any[]) => {
  283. selectionItems.value = selection;
  284. };
  285. const handleCloseBatchOperation = () => {
  286. if (!basicTableRef.value) return;
  287. basicTableRef.value.clearSelection();
  288. };
  289. const handleBatchNotice = async () => {
  290. const confirmed = await openMessageBox('', '确认通知任务吗?', 'warning');
  291. if (!confirmed) return;
  292. const unNoticeItem = selectionItems.value.filter((item) => item.isNotice === ACT_NOTICE_STATE.INACTIVE);
  293. if (!unNoticeItem.length) {
  294. ElMessage.warning('通知成功!共通知0条记录');
  295. } else {
  296. const noticeIds = unNoticeItem.map((item) => item.id);
  297. try {
  298. await noticeActData(noticeIds);
  299. ElMessage.success(`通知成功!共通知${noticeIds.length}条记录`);
  300. } catch (e) {
  301. ElMessage.error('批量通知失败');
  302. }
  303. getTableData();
  304. }
  305. };
  306. const handleBatchDelete = async () => {
  307. const confirmed = await openMessageBox('', '删除后任务不可恢复,确认删除吗?', 'warning');
  308. if (!confirmed) return;
  309. const deleteIds = selectionItems.value.map((item) => item.id);
  310. try {
  311. await deleteActData(deleteIds);
  312. ElMessage.success('批量删除成功');
  313. } catch (e) {
  314. ElMessage.error('批量删除失败');
  315. }
  316. getTableData();
  317. };
  318. async function getTableData() {
  319. tableConfig.loading = true;
  320. const res = await getActTableList(tableQuery);
  321. tableData.value = res.records;
  322. pagination.total = res.totalRow;
  323. tableConfig.loading = false;
  324. }
  325. // 批量导入
  326. const batchImportVisible = ref(false);
  327. const { urlPrefix } = useGlobSetting();
  328. const importApiUrl = ref(urlJoin(urlPrefix, '/trafficViolation/importTrafficViolationList'));
  329. const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-violation-template.xlsx');
  330. const handleUpdate = () => {
  331. batchImportVisible.value = false;
  332. getTableData();
  333. };
  334. onMounted(async () => {
  335. await getTableData();
  336. });
  337. function handleCreateAct() {
  338. router.push({
  339. name: 'traffic-violation-act-item',
  340. query: {
  341. operate: 'act-create',
  342. },
  343. });
  344. }
  345. function handleEditAct(id: number) {
  346. router.push({
  347. name: 'traffic-violation-act-item',
  348. query: {
  349. id,
  350. operate: 'act-edit',
  351. },
  352. });
  353. }
  354. async function handleNoticeAct(id: number) {
  355. tableConfig.loading = true;
  356. try {
  357. await noticeActData(id);
  358. ElMessage.success('通知成功');
  359. } catch (e) {
  360. ElMessage.error('通知失败');
  361. return;
  362. } finally {
  363. tableConfig.loading = false;
  364. }
  365. getTableData();
  366. }
  367. async function handleDeleteAct(id: number) {
  368. tableConfig.loading = true;
  369. try {
  370. await deleteActData(id);
  371. } catch (e) {
  372. ElMessage.error('删除失败');
  373. return;
  374. } finally {
  375. tableConfig.loading = false;
  376. }
  377. getTableData();
  378. }
  379. </script>
  380. <style scoped lang="scss">
  381. @use '@/styles/page-details-layout.scss' as *;
  382. @use '@/styles/page-main-layout.scss' as *;
  383. @use '@/styles/basic-table-action.scss' as *;
  384. @use '../style/act-search-table.scss' as *;
  385. </style>