Pārlūkot izejas kodu

feat: 表格批量操作

wyf 7 mēneši atpakaļ
vecāks
revīzija
9f97b5b22e

+ 123 - 70
src/views/traffic/violation/act/Act.vue

@@ -72,56 +72,68 @@
           </div>
         </header>
         <!-- 表格 -->
-        <BasicTable
-          :tableData="tableData"
-          :tableConfig="tableConfig"
-          @update:pageSize="handleSizeChange"
-          @update:pageNumber="handleCurrentChange"
-          @update:selection="handleSelectionChange"
-        >
-          <template #violateType="scope">
-            <span>{{ ACT_VIOLATION_TYPE_LABEL[scope.row.violateType] }}</span>
-          </template>
-          <template #capturePhotos="scope">
-            <ImageViewer :file-list="scope.row.capturePhotos" />
-          </template>
-          <template #createSource="scope">
-            <span>{{ ACT_NOTICE_DATA_SOURCE_LABEL[scope.row.createSource] }}</span>
-          </template>
-          <template #isNotice="scope">
-            <div class="notice-state">
-              <div
-                :style="{
-                  backgroundColor: ACT_NOTICE_STATE_COLOR[scope.row.isNotice],
-                  width: '6px',
-                  height: '6px',
-                  borderRadius: '50%',
-                  marginRight: '5px',
-                }"
-              ></div>
-              <span>{{ ACT_NOTICE_STATE_LABEL[scope.row.isNotice] }}</span>
+        <div class="batch-table">
+          <div class="batch-operation--div" v-show="trafficManagementPermission && selectionItems.length > 0">
+            <span>已选{{ selectionItems.length }}项</span>
+            <div class="batch-operation--div--close">
+              <div class="batch-operation--div--button">
+                <el-button class="custom-el-button" @click="handleBatchNotice">批量通知</el-button>
+                <el-button class="custom-el-button" @click="handleBatchDelete">批量删除</el-button>
+              </div>
+              <el-icon class="close-icon" @click="handleCloseBatchOperation"><Close /></el-icon>
             </div>
-          </template>
-          <template #action="scope">
-            <ActionButton
-              v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
-              text="编辑"
-              @click="handleEditAct(scope.row.id)"
-            />
-            <ActionButton
-              v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
-              text="通知"
-              @click="handleNoticeAct(scope.row.id)"
-            />
-            <ActionButton
-              text="删除"
-              :popconfirm="{
-                title: '确定要删除?',
-              }"
-              @confirm="handleDeleteAct(scope.row.id)"
-            />
-          </template>
-        </BasicTable>
+          </div>
+          <BasicTable
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:pageSize="handleSizeChange"
+            @update:pageNumber="handleCurrentChange"
+            @update:selection="handleSelectionChange"
+          >
+            <template #violateType="scope">
+              <span>{{ ACT_VIOLATION_TYPE_LABEL[scope.row.violateType] }}</span>
+            </template>
+            <template #capturePhotos="scope">
+              <ImageViewer :file-list="scope.row.capturePhotos" />
+            </template>
+            <template #createSource="scope">
+              <span>{{ ACT_NOTICE_DATA_SOURCE_LABEL[scope.row.createSource] }}</span>
+            </template>
+            <template #isNotice="scope">
+              <div class="notice-state">
+                <div
+                  :style="{
+                    backgroundColor: ACT_NOTICE_STATE_COLOR[scope.row.isNotice],
+                    width: '6px',
+                    height: '6px',
+                    borderRadius: '50%',
+                    marginRight: '5px',
+                  }"
+                ></div>
+                <span>{{ ACT_NOTICE_STATE_LABEL[scope.row.isNotice] }}</span>
+              </div>
+            </template>
+            <template #action="scope">
+              <ActionButton
+                v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
+                text="编辑"
+                @click="handleEditAct(scope.row.id)"
+              />
+              <ActionButton
+                v-if="scope.row.isNotice === ACT_NOTICE_STATE.INACTIVE"
+                text="通知"
+                @click="handleNoticeAct(scope.row.id)"
+              />
+              <ActionButton
+                text="删除"
+                :popconfirm="{
+                  title: '确定要删除?',
+                }"
+                @confirm="handleDeleteAct(scope.row.id)"
+              />
+            </template>
+          </BasicTable>
+        </div>
       </div>
     </main>
     <BatchImport
@@ -154,6 +166,7 @@
     ACT_NOTICE_STATE,
     ACT_NOTICE_STATE_LABEL,
     ACT_NOTICE_STATE_COLOR,
+    ACT_MANAGEMENT_PROMISSION_CODE,
   } from './constants';
   import { ref, reactive, onMounted } from 'vue';
   import { Search, Plus } from '@element-plus/icons-vue';
@@ -172,9 +185,15 @@
   import { BatchImport } from '@/components/batch-import';
   import { useGlobSetting } from '@/hooks/setting';
   import urlJoin from 'url-join';
+  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
 
   const router = useRouter();
 
+  const { permissions } = useUserInfoHook();
+  const trafficManagementPermission = ref<Boolean>(
+    Boolean(permissions.find((item: { code: string }) => item.code === ACT_MANAGEMENT_PROMISSION_CODE)),
+  );
+
   // 搜索栏
   const selectableInputRef = ref<InstanceType<typeof SelectableInput>>();
   const searchData = reactive<ActTableSearch>({});
@@ -254,29 +273,33 @@
     getTableData();
   };
 
-  const handleSelectionChange = (value: any[]) => {};
+  const selectionItems = ref<any[]>([]);
 
-  // const handleCloseBatchOperation = () => {
-  //   if (!basicTableRef.value) return;
-  //   basicTableRef.value.clearSelection();
-  // };
+  const handleSelectionChange = (selection: any[]) => {
+    selectionItems.value = selection;
+  };
 
-  // const handleBatchNotice = async () => {
-  //   const confirmed = await openMessageBox('', '确认通知任务吗?', 'warning');
-  //   if (!confirmed) return;
-  //   const noticeIds = getSelectionIds(ACTIVE_STATUS.NOT_EFFECTIVE);
-  //     await noticeActData(noticeIds);
-  //     ElMessage.success('批量通知成功');
-  //     getTableData();
-  // };
-  // const handleBatchDelete = async () => {
-  //   const confirmed = await openMessageBox('', '删除后任务不可恢复,确认删除吗?', 'warning');
-  //   if (!confirmed) return;
-  //   const deleteIds = getSelectionIds(ACTIVE_STATUS.NOT_EFFECTIVE);
-  //   await deleteActData(deleteIds);
-  //   ElMessage.success('批量删除成功');
-  //   getTableData();
-  // };
+  const handleCloseBatchOperation = () => {
+    if (!basicTableRef.value) return;
+    basicTableRef.value.clearSelection();
+  };
+
+  const handleBatchNotice = async () => {
+    const confirmed = await openMessageBox('', '确认通知任务吗?', 'warning');
+    if (!confirmed) return;
+    const noticeIds = selectionItems.value.map((item) => item.id);
+    await noticeActData(noticeIds);
+    ElMessage.success('批量通知成功');
+    getTableData();
+  };
+  const handleBatchDelete = async () => {
+    const confirmed = await openMessageBox('', '删除后任务不可恢复,确认删除吗?', 'warning');
+    if (!confirmed) return;
+    const deleteIds = selectionItems.value.map((item) => item.id);
+    await deleteActData(deleteIds);
+    ElMessage.success('批量删除成功');
+    getTableData();
+  };
 
   async function getTableData() {
     tableConfig.loading = true;
@@ -385,4 +408,34 @@
     align-items: center;
     justify-self: center;
   }
+
+  .batch-table {
+    position: relative;
+    width: 100%;
+    height: 100%;
+  }
+  .batch-operation--div {
+    @include flex-center;
+    justify-content: flex-start;
+    position: absolute;
+    top: 0;
+    left: 0;
+    gap: 60px;
+    width: 100%;
+    height: 48px;
+    border: 4px;
+    padding: 16px 25px;
+    background-color: #ddefff;
+    z-index: 100;
+    &--close {
+      @include flex-center;
+      justify-content: space-between;
+      flex: 1;
+    }
+    .close-icon {
+      font-size: 20px;
+      color: #ff4d4f;
+      cursor: pointer;
+    }
+  }
 </style>

+ 78 - 4
src/views/traffic/violation/act/configs/tables.ts

@@ -22,14 +22,14 @@ export const VIOLATION_ACT_TABLE_COLUMNS: TableColumnProps[] = [
     type: 'index',
   },
   {
-    label: '车',
-    prop: 'violateName',
+    label: '车牌号',
+    prop: 'carNumber',
     align: 'center',
     minWidth: '120px',
   },
   {
-    label: '车牌号',
-    prop: 'carNumber',
+    label: '车',
+    prop: 'violateName',
     align: 'center',
     minWidth: '120px',
   },
@@ -95,3 +95,77 @@ export const VIOLATION_ACT_TABLE_COLUMNS: TableColumnProps[] = [
     align: 'center',
   },
 ];
+
+export const VIOLATION_NOTICE_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '序号',
+    align: 'center',
+    width: '80px',
+    type: 'index',
+  },
+  {
+    label: '车牌号',
+    prop: 'carNumber',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '车主',
+    prop: 'violateName',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '所属部门',
+    prop: 'deptName',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '违规类型',
+    prop: 'violateType',
+    slot: 'violateType',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '车速',
+    prop: 'speed',
+    align: 'center',
+    minWidth: '120px',
+  },
+
+  {
+    label: '抓拍图片',
+    prop: 'capturePhotos',
+    slot: 'capturePhotos',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '违规地点',
+    prop: 'violateLocation',
+    align: 'center',
+    minWidth: '120px',
+  },
+  {
+    label: '时间',
+    prop: 'captureTime',
+    align: 'center',
+    width: '200px',
+  },
+  {
+    label: '数据来源',
+    prop: 'createSource',
+    slot: 'createSource',
+    align: 'center',
+    width: '200px',
+  },
+  {
+    label: '通知状态',
+    prop: 'isNotice',
+    slot: 'isNotice',
+    align: 'center',
+    width: '200px',
+  },
+];

+ 3 - 0
src/views/traffic/violation/act/constants.ts

@@ -83,3 +83,6 @@ export const ACT_NOTICE_DATA_SOURCE_LABEL = {
   [ACT_NOTICE_DATA_SOURCE.ARTIFICAL]: '人工上报',
   [ACT_NOTICE_DATA_SOURCE.OUTSIDE]: '外部创建',
 };
+
+export const ACT_MANAGEMENT_PROMISSION_CODE = 'traffic_business_module:violation_record';
+export const NOTICE_MANAGEMENT_PROMISSION_CODE = 'traffic_business_module:violation_notice';

+ 255 - 3
src/views/traffic/violation/notice/Notice.vue

@@ -1,7 +1,259 @@
 <template>
-  <div> </div>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <div class="breadcrumb-title"> 违规行为记录 </div>
+    </header>
+    <main class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header>
+          <div class="act-search">
+            <section class="select-box">
+              <SelectableInput ref="selectableInputRef" :options="ACT_TABLE_SEARCH_OPTIONS" />
+              <div class="select-box--item">
+                <span>违规类型:</span>
+                <el-select
+                  v-model="searchData.violationType"
+                  placeholder="请选择违规类型"
+                  class="select-box--select"
+                  clearable
+                >
+                  <el-option
+                    v-for="item in ACT_VIOLATION_TYPE_OPTIONS"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label"
+                    :disabled="item.disabled"
+                  />
+                </el-select>
+              </div>
+              <div class="select-box--item">
+                <span>通知状态:</span>
+                <el-select
+                  v-model="searchData.isNotice"
+                  placeholder="请选择通知状态"
+                  class="select-box--select"
+                  clearable
+                >
+                  <el-option
+                    v-for="item in ACT_NOTICE_STATE_OPTIONS"
+                    :key="item.value"
+                    :value="item.value"
+                    :label="item.label"
+                    :disabled="item.disabled"
+                  />
+                </el-select>
+              </div>
+              <div>
+                <span>时间:</span>
+                <el-date-picker
+                  v-model="searchData.searchTime"
+                  type="datetimerange"
+                  range-separator="至"
+                  start-placeholder="开始时间"
+                  end-placeholder="结束时间"
+                />
+              </div>
+            </section>
+            <section class="search-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-button @click="handleDownload">导出</el-button>
+            </section>
+          </div>
+        </header>
+        <!-- 表格 -->
+        <BasicTable
+          :tableData="tableData"
+          :tableConfig="tableConfig"
+          @update:pageSize="handleSizeChange"
+          @update:pageNumber="handleCurrentChange"
+        >
+          <template #violateType="scope">
+            <span>{{ ACT_VIOLATION_TYPE_LABEL[scope.row.violateType] }}</span>
+          </template>
+          <template #capturePhotos="scope">
+            <ImageViewer :file-list="scope.row.capturePhotos" />
+          </template>
+          <template #createSource="scope">
+            <span>{{ ACT_NOTICE_DATA_SOURCE_LABEL[scope.row.createSource] }}</span>
+          </template>
+          <template #isNotice="scope">
+            <div class="notice-state">
+              <div
+                :style="{
+                  backgroundColor: ACT_NOTICE_STATE_COLOR[scope.row.isNotice],
+                  width: '6px',
+                  height: '6px',
+                  borderRadius: '50%',
+                  marginRight: '5px',
+                }"
+              ></div>
+              <span>{{ ACT_NOTICE_STATE_LABEL[scope.row.isNotice] }}</span>
+            </div>
+          </template>
+        </BasicTable>
+      </div>
+    </main>
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import BasicTable from '@/components/BasicTable.vue';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import SelectableInput from '@/components/formItems/selectableInput/SelectableInput.vue';
+  import dayjs from 'dayjs';
+  import { ElMessage } from 'element-plus';
+  import { TABLE_OPTIONS, VIOLATION_NOTICE_TABLE_COLUMNS } from '../act/configs/tables';
+  import {
+    ACT_NOTICE_DATA_SOURCE_LABEL,
+    ACT_VIOLATION_TYPE,
+    ACT_VIOLATION_TYPE_LABEL,
+    ACT_TABLE_SEARCH_OPTIONS,
+    ACT_VIOLATION_TYPE_OPTIONS,
+    ACT_NOTICE_STATE_OPTIONS,
+    ACT_NOTICE_STATE,
+    ACT_NOTICE_STATE_LABEL,
+    ACT_NOTICE_STATE_COLOR,
+  } from '../act/constants';
+  import { ref, reactive, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import type { ActTableSearch, ActTableQuery, ActTableData } from '../act/types';
+  import { getActTableList, exportActViolation } from '@/api/traffic-violation/traffic-act';
+  import { downloadFile } from '@/views/disaster/utils/download';
+  import ImageViewer from '../act/components/ImageViewer.vue';
 
-<style scoped></style>
+  const router = useRouter();
+
+  // 搜索栏
+  const selectableInputRef = ref<InstanceType<typeof SelectableInput>>();
+  const searchData = reactive<ActTableSearch>({});
+
+  function getQuery() {
+    if (!selectableInputRef.value) return;
+    tabelQuery.queryParam = {
+      pageType: 2,
+    };
+    const selectableSearch = selectableInputRef.value.getValue();
+    if (selectableSearch) {
+      tabelQuery.queryParam[selectableSearch.key as string] = selectableSearch.value;
+    }
+    if (searchData.isNotice) {
+      tabelQuery.queryParam.isNotice = searchData.isNotice;
+    }
+    if (searchData.violationType) {
+      tabelQuery.queryParam.violationType = searchData.violationType;
+    }
+    if (searchData.searchTime) {
+      tabelQuery.queryParam.startTime = dayjs(searchData.searchTime[0]).format('YYYY-MM-DD HH:MM:ss');
+      tabelQuery.queryParam.endTime = dayjs(searchData.searchTime[1]).format('YYYY-MM-DD HH:MM:ss');
+    }
+  }
+
+  function handleSearch() {
+    getQuery();
+    getTableData();
+  }
+
+  function handleReset() {
+    selectableInputRef.value?.clearValue();
+    searchData.carNumber = undefined;
+    searchData.violateName = undefined;
+    searchData.deptName = undefined;
+    searchData.violationType = undefined;
+    searchData.searchTime = undefined;
+  }
+
+  async function handleDownload() {
+    getQuery();
+    try {
+      const res = await exportActViolation(tabelQuery.queryParam);
+      if (res.size === 0) return;
+      const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+      const url = window.URL.createObjectURL(blob);
+      downloadFile(url, '违规行为记录.xlsx');
+    } catch (e) {
+      ElMessage.error('下载失败');
+      console.log(e);
+    }
+  }
+
+  // 表格
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+
+  const { tableConfig, pagination } = useTableConfig(VIOLATION_NOTICE_TABLE_COLUMNS, TABLE_OPTIONS);
+
+  const tableData = ref<ActTableData[]>([]);
+
+  const tabelQuery = reactive<QueryPageRequest<ActTableQuery>>({
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {
+      pageType: 2,
+    },
+  });
+
+  const handleSizeChange = (value: number) => {
+    pagination.pageNumber = value;
+    tabelQuery.pageSize = value;
+    getTableData();
+  };
+  const handleCurrentChange = (value: number) => {
+    pagination.pageNumber = value;
+    tabelQuery.pageSize = value;
+    getTableData();
+  };
+
+  async function getTableData() {
+    tableConfig.loading = true;
+    const res = await getActTableList(tabelQuery);
+    tableData.value = res.records;
+    pagination.total = res.totalRow;
+    tableConfig.loading = false;
+  }
+
+  onMounted(async () => {
+    await getTableData();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+  .act-search-input {
+    max-width: 500px;
+  }
+  .act-search {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    width: 100%;
+  }
+  .select-box {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: 32px;
+    &--item {
+      @include flex-center;
+      white-space: nowrap;
+    }
+    span {
+      color: rgba(0, 0, 0, 0.85);
+      font-size: 14px;
+    }
+    .el-select {
+      width: 200px;
+    }
+  }
+  .search-btn {
+    display: flex;
+  }
+
+  .notice-state {
+    display: flex;
+    align-items: center;
+    justify-self: center;
+  }
+</style>