Просмотр исходного кода

feat: 交通安全模块(总览+交通事故管理)

bxy 7 месяцев назад
Родитель
Сommit
7202d5a85f

+ 17 - 2
src/api/traffic-accident/index.ts

@@ -62,7 +62,7 @@ export const addVehicleInfo = (params: AddAccidentInfoStruct) => {
 /**
  * @description: 更新交通事故记录
  */
-export const updateVehicleInfo = (params: AccidentInfoStruct) => {
+export const updateVehicleInfo = (params: AddAccidentInfoStruct) => {
   return http.request({
     url: '/trafficAccident/updateTrafficAccidentRecord',
     method: 'put',
@@ -73,7 +73,7 @@ export const updateVehicleInfo = (params: AccidentInfoStruct) => {
 /**
  * @description: 删除交通事故记录(单个/批量)
  */
-export const deleteVehicleInfo = (params: { accidentRecordIds: number[] }) => {
+export const deleteAccidentInfo = (params: { accidentRecordIds: number[] }) => {
   return http.request({
     url: '/trafficAccident/deleteTrafficAccidentRecord',
     method: 'delete',
@@ -90,3 +90,18 @@ export const getAccidentInfoDetail = (params: { accidentRecordId: number }) => {
     method: 'get',
   });
 };
+
+/**
+ * @description: 导出交通事故记录
+ */
+export function exportAccidentInfo(params: AccidentListQuery) {
+  return http.request(
+    {
+      url: '/trafficAccident/exportTrafficAccidentRecord',
+      method: 'post',
+      responseType: 'blob',
+      params,
+    },
+    { isTransformResponse: false },
+  );
+}

+ 130 - 0
src/api/traffic-overview/index.ts

@@ -0,0 +1,130 @@
+/**
+ * @description: 交通安全-总览
+ */
+import { http } from '@/utils/http/axios';
+import type { QueryPageResponse } from '@/types/basic-query';
+
+export interface QueryPageParams {
+  pageNumber: number;
+  pageSize: number;
+}
+
+/**
+ * @description: 违规行为统计
+ */
+export interface QueryViolationStatisticsOverviewRes {
+  vehicleViolationCount: number; // 当月车辆违规数
+  violationNoticeCount: number; // 当月违规通知数
+  trafficAccidentCount: number; // 当月交通事故数
+}
+export const getViolationStatisticsOverview = () => {
+  return http.request<QueryViolationStatisticsOverviewRes>({
+    url: '/overview/queryViolationStatisticsOverview',
+    method: 'get',
+  });
+};
+
+/**
+ * @description: 添加当月车辆违规数
+ */
+export interface AddVehicleViolationCountParams {
+  id?: number;
+  violateCount?: number; // 当月车辆违规数
+  createdBy?: number; // 创建人
+  createdAt?: string; // 创建时间
+  updatedAt?: string; // 更新时间
+  isDeleted?: number; // 0-未删除,大于0(时间戳)-已删除
+}
+export const addVehicleViolationCount = (params: AddVehicleViolationCountParams) => {
+  return http.request({
+    url: '/overview/saveTrafficViolationDisplay',
+    method: 'post',
+    params,
+  });
+};
+
+/**
+ * @description: 交通管理规定与通知
+ */
+export interface QueryRuleNoticeInfoListOverviewRes {
+  id: number; // 自增主键
+  managementType: number; // 管理类型:1-管理规定,2-管理通知
+  name: string; // 名称或标题
+  content: string; // 通知内容
+  attachment: string; // 附件
+  effectState: number; // 生效状态:0-未生效,1-生效
+  isPush: number; // 是否推送:0-不推送 1-推送
+  pushTime: string; // 发布时间
+  userGroupList: string; // 用户分组列表
+  createdBy: number; // 创建人
+  remark: string; // 备注
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+  creatName: string; // 创建人姓名
+}
+export const getRuleNoticeInfoListOverview = () => {
+  return http.request<QueryRuleNoticeInfoListOverviewRes[]>({
+    url: '/overview/queryRuleNoticeInfoListOverview',
+    method: 'get',
+  });
+};
+
+/**
+ * @description: 本月车辆违规记录
+ */
+export interface QueryTrafficViolationOverviewPageRes {
+  id: number; // 自增主键
+  createSource: number; // 创建来源:1-人工创建 2-外部获取
+  carNumber: string; // 车牌
+  violateBy: number; // 违规人
+  violateType: number; // 违规类型:1-超速 2-逆行 3-违规停车
+  speed: number; // 车速
+  violateLocation: string; // 违规地点
+  captureTime: string; // 抓拍时间
+  capturePhotos: string; // 抓拍照片
+  createdBy: number; // 创建人
+  remark: string; // 备注
+  isNotice: number; // 是否通知:0-未通知 1-已通知
+  noticeTime: string; // 通知时间
+  noticeType: number; // 创建来源:1-手动通知 2-自动通知
+  speedLimit: number; // 限制车速
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+  creatName: string; // 创建人姓名
+  violateName: string; // 违规人姓名
+  deptName: string; // 所属部门
+  staffNo: string; // 工号
+}
+export const getTrafficViolationOverview = (params: QueryPageParams) => {
+  return http.request<QueryPageResponse<QueryTrafficViolationOverviewPageRes>>({
+    url: '/overview/queryTrafficViolationOverviewPage',
+    method: 'post',
+    params,
+  });
+};
+
+/**
+ * @description: 本月交通事故记录
+ */
+export interface QueryTrafficAccidentRecordOverviewPageRes {
+  id: number; // 自增主键
+  accidentLocation: string; // 事故地点
+  accidentTime: string; // 事故时间
+  accidentDescription: string; // 事故描述
+  accidentImages: string; // 事故图片
+  remark: string; // 备注
+  createdById: number; // 创建人id
+  createdByName: string; // 创建人姓名
+  createdAt: string; // 创建时间
+  updatedAt: string; // 更新时间
+  isDeleted: number; // 0-未删除,大于0(时间戳)-已删除
+}
+export const getTrafficAccidentRecordOverview = (params: QueryPageParams) => {
+  return http.request<QueryPageResponse<QueryTrafficAccidentRecordOverviewPageRes>>({
+    url: '/overview/queryTrafficAccidentRecordOverview',
+    method: 'post',
+    params,
+  });
+};

+ 15 - 4
src/router/routers/traffic.ts

@@ -1,5 +1,3 @@
-import { id } from 'element-plus/es/locale';
-
 const trafficRoutes = {
   id: 4000,
   parentId: -1,
@@ -107,7 +105,6 @@ const trafficRoutes = {
           parentId: 4004,
           name: 'traffic-violation-notice',
           path: 'notice',
-
           component: '/traffic/violation/notice/Notice',
           meta: {
             title: '违规通知',
@@ -123,7 +120,7 @@ const trafficRoutes = {
       parentId: 4000,
       name: 'traffic-accident',
       path: 'accident',
-      component: '/traffic/accidents/Accident',
+      component: '/traffic/accident/Accident',
       redirect: '',
       meta: {
         title: '交通事故管理',
@@ -133,6 +130,20 @@ const trafficRoutes = {
         noCache: false,
       },
     },
+    {
+      id: 400501,
+      parentId: 4005,
+      name: 'traffic-accident-item',
+      path: 'accident-item',
+      component: '/traffic/accident/AccidentItem',
+      meta: {
+        activeMenu: '/traffic/accident',
+        title: '交通事故管理记录详情',
+        isRoot: false,
+        hidden: true,
+        noCache: false,
+      },
+    },
     {
       id: 4006,
       parentId: 4000,

+ 378 - 3
src/views/traffic/Accident/Accident.vue

@@ -1,7 +1,382 @@
 <template>
-  <div> </div>
+  <div class="safety-platform-container">
+    <div class="safety-platform-container__header">
+      <div class="breadcrumb-title">交通事故管理</div>
+    </div>
+    <div class="safety-platform-container__main">
+      <div class="search-table-container">
+        <header class="disaster-precaution__header">
+          <el-button
+            v-if="accidentManagePermission"
+            class="search-table-container--button"
+            type="primary"
+            :icon="Plus"
+            @click="handleAddVehicleInfo"
+          >
+            添加交通事故记录
+          </el-button>
+          <el-button
+            v-if="accidentManagePermission"
+            class="search-table-container--button"
+            @click="batchImportVisible = true"
+          >
+            批量导入
+          </el-button>
+          <div class="search-container">
+            <el-input
+              v-model="searchKeyword"
+              :placeholder="`请输入${curSearchTypeLabel}进行搜索`"
+              clearable
+              @input="handleSearch"
+              @clear="handleClear"
+              @keyup.enter="handleSearch"
+              style="width: 380px"
+            >
+              <template #prefix>
+                <el-icon color="#1777ff"><Search /></el-icon>
+              </template>
+              <template #prepend>
+                <el-select
+                  v-model="searchSelectedType"
+                  placeholder="选择搜索项"
+                  @change="handleSelectedTypeChange"
+                  style="width: 100px"
+                >
+                  <el-option
+                    v-for="item in accidentQueryOptions"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                  />
+                </el-select>
+              </template>
+            </el-input>
+            <div class="search-time">
+              <span>事故时间:</span>
+              <el-date-picker
+                v-model="queryTimes"
+                type="datetimerange"
+                range-separator="-"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD HH:mm:ss"
+                @change="handleSearch"
+              />
+            </div>
+            <div class="search-container-btn">
+              <el-button type="primary" @click="handleSearch">查询</el-button>
+              <el-button @click="handleReset">重置</el-button>
+              <el-button @click="handleExport">导出</el-button>
+            </div>
+          </div>
+        </header>
+        <div class="batch-table">
+          <div class="batch-operation--div" v-show="accidentManagePermission && 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="handleBatchDelete">批量删除</el-button>
+              </div>
+              <el-icon class="close-icon" @click="handleCloseBatchOperation"><Close /></el-icon>
+            </div>
+          </div>
+          <BasicTable
+            ref="basicTableRef"
+            :tableData="tableData"
+            :tableConfig="tableConfig"
+            @update:page-number="handleCurrentPageChange"
+            @update:page-size="handlePageSizeChange"
+            @update:selection="handleSelectionChange"
+          >
+            <template #action="scope">
+              <div class="action-container--div">
+                <ActionButton text="查看" @click="handleViewDetail(scope.row.id)" />
+                <ActionButton text="编辑" @click="handleEditVehicleInfo(scope.row.id)" />
+                <ActionButton
+                  text="删除"
+                  :popconfirm="{
+                    title: '是否删除该交通事故记录?',
+                  }"
+                  @confirm="handleDeleteVehicleInfo(scope.row)"
+                />
+              </div>
+            </template>
+          </BasicTable>
+        </div>
+      </div>
+    </div>
+  </div>
+  <BatchImport
+    :visible="batchImportVisible"
+    :importApiUrl="importApiUrl"
+    :templateUrl="templateUrl"
+    :templateName="'交通事故记录-批量导入模版'"
+    @close="() => (batchImportVisible = false)"
+    @update="handleUpdate"
+  />
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { computed, onMounted, ref } from 'vue';
+  import urlJoin from 'url-join';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import { Plus, Search, Close } from '@element-plus/icons-vue';
+  import { openMessageBox } from '@/utils/element-plus/messageBox';
+  import BasicTable from '@/components/BasicTable.vue';
+  import ActionButton from '@/components/ActionButton.vue';
+  import { BatchImport } from '@/components/batch-import';
+  import { downloadByData } from '@/utils/file/download';
+  import { msgConfirm } from '@/utils/element-plus/messageBox';
+  import { getCurrentDateTimeString } from '@/utils/dateUtil';
+  import type { QueryPageRequest } from '@/types/basic-query';
+  import { useGlobSetting } from '@/hooks/setting';
+  import useTableConfig from '@/hooks/useTableConfigHook';
+  import { useUserInfoHook } from '@/hooks/useUserInfoHook';
+  import { TRAFFIC_PERMISSIONS } from '@/views/traffic/constant';
+  import {
+    ACCIDENT_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+    ACCIDENT_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+    ACCIDENT_LIST_TABLE_OPTIONS,
+    ACCIDENT_LIST_TABLE_COLUMNS,
+  } from './config';
+  import { FIELDTYPE, FIELD_CONTENT, accidentQueryOptions } from './constant';
+  import {
+    AccidentListQuery,
+    AccidentInfoStruct,
+    getAccidentInfoList,
+    deleteAccidentInfo,
+    exportAccidentInfo,
+  } from '@/api/traffic-accident';
 
-<style scoped></style>
+  const { tableConfig, pagination } = useTableConfig(ACCIDENT_LIST_TABLE_COLUMNS, ACCIDENT_LIST_TABLE_OPTIONS);
+
+  const { permissions } = useUserInfoHook();
+  const accidentManagePermission = ref<boolean>(false);
+
+  const router = useRouter();
+
+  const searchSelectedType = ref(FIELDTYPE.LOCATION);
+  const searchKeyword = ref('');
+  const curSearchTypeLabel = computed(() => {
+    const option = accidentQueryOptions.find((item) => item.value === searchSelectedType.value);
+    return option ? option.label : FIELD_CONTENT[searchSelectedType.value];
+  });
+  const queryTimes = ref(['', '']);
+
+  const accidentTableQuery: QueryPageRequest<AccidentListQuery> = {
+    pageNumber: pagination.pageNumber,
+    pageSize: pagination.pageSize,
+    queryParam: {},
+  };
+
+  const basicTableRef = ref<InstanceType<typeof BasicTable>>();
+  const tableData = ref<AccidentInfoStruct[]>([]);
+
+  const selectionItems = ref<any[]>([]);
+
+  const handleSelectedTypeChange = () => {
+    searchKeyword.value = '';
+  };
+
+  const handleSearch = () => {
+    if (!queryTimes.value) queryTimes.value = ['', ''];
+    getTableData();
+  };
+
+  const handleClear = () => {
+    searchKeyword.value = '';
+    getTableData();
+  };
+
+  const handleReset = () => {
+    searchSelectedType.value = FIELDTYPE.LOCATION;
+    searchKeyword.value = '';
+    getTableData();
+  };
+
+  // 导出
+  const handleExport = () => {
+    msgConfirm('确定导出所查询数据?', '导出', {
+      confirmButtonText: '确定',
+      showCancelButton: true,
+      type: 'warning',
+    })
+      .then(() => {
+        exportAccidentInfo(accidentTableQuery.queryParam).then(async (responnse) => {
+          if (!responnse) {
+            throw new Error('下载文件失败');
+          }
+          downloadByData(responnse, `交通事故记录_${getCurrentDateTimeString()}.xlsx`);
+          ElMessage.success('下载文件成功');
+        });
+      })
+      .catch(() => {
+        ElMessage({
+          type: 'info',
+          message: '取消导出',
+        });
+      });
+  };
+
+  const handleCurrentPageChange = (pageNumber: number) => {
+    pagination.pageNumber = pageNumber;
+    accidentTableQuery.pageNumber = pageNumber;
+    getTableData();
+  };
+
+  const handlePageSizeChange = (pageSize: number) => {
+    pagination.pageSize = pageSize;
+    accidentTableQuery.pageSize = pageSize;
+    getTableData();
+  };
+
+  const handleAddVehicleInfo = () => {
+    router.push({
+      name: 'traffic-accident-item',
+      query: { operate: 'create' },
+    });
+  };
+
+  const handleEditVehicleInfo = (id: number) => {
+    router.push({
+      name: 'traffic-accident-item',
+      query: { operate: 'edit', id },
+    });
+  };
+
+  const handleViewDetail = (id: number) => {
+    router.push({
+      name: 'traffic-accident-item',
+      query: { id },
+    });
+  };
+
+  const handleDeleteVehicleInfo = (row: AccidentInfoStruct) => {
+    if (!row.id) return;
+    deleteAccidentInfo({ accidentRecordIds: [row.id] }).then(() => {
+      ElMessage.success('删除成功');
+      getTableData();
+    });
+  };
+
+  // 批量删除
+  const handleSelectionChange = (selection: any[]) => {
+    selectionItems.value = selection;
+  };
+
+  const handleCloseBatchOperation = () => {
+    if (!basicTableRef.value) return;
+    basicTableRef.value.clearSelection();
+  };
+
+  const handleBatchDelete = async () => {
+    const confirmed = await openMessageBox('', '删除后信息不可恢复,确认删除吗?', 'warning');
+    if (!confirmed) return;
+    const deleteIds = selectionItems.value.map((item) => item.id);
+    if (!deleteIds.length) return;
+    deleteAccidentInfo({ accidentRecordIds: deleteIds }).then(() => {
+      ElMessage.success('批量删除成功');
+      getTableData();
+    });
+  };
+
+  // 批量导入
+  const batchImportVisible = ref(false);
+  const { urlPrefix } = useGlobSetting();
+  const importApiUrl = ref(urlJoin(urlPrefix, '/trafficAccident/importTrafficAccidentRecord'));
+  const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/import-traffic-template.xlsx');
+
+  const handleUpdate = () => {
+    batchImportVisible.value = false;
+    getTableData();
+  };
+
+  const getTableData = () => {
+    tableConfig.loading = true;
+    accidentTableQuery.queryParam = {
+      fieldType: searchSelectedType.value,
+      fieldContent: searchKeyword.value,
+      startTime: queryTimes.value[0] || '',
+      endTime: queryTimes.value[1] || '',
+    };
+    getAccidentInfoList(accidentTableQuery).then((res) => {
+      tableData.value = res?.records || [];
+      pagination.total = res?.totalRow || 0;
+    });
+    tableConfig.loading = false;
+  };
+
+  // 动态生成表格列配置
+  const getTableColumns = () => {
+    if (accidentManagePermission.value) {
+      return ACCIDENT_LIST_TABLE_COLUMNS;
+    } else {
+      // 过滤掉操作列
+      return ACCIDENT_LIST_TABLE_COLUMNS.filter((column) => column.prop !== 'action' && column.type !== 'selection');
+    }
+  };
+
+  onMounted(() => {
+    getTableData();
+    accidentManagePermission.value = Boolean(
+      permissions.find((item: { code: string }) => item.code === TRAFFIC_PERMISSIONS.ACCIDENT_MANAGE),
+    );
+    tableConfig.maxHeight = accidentManagePermission.value
+      ? ACCIDENT_LIST_TABLE_MAX_HEIGHT_PERMISSION
+      : ACCIDENT_LIST_TABLE_MAX_HEIGHT_DEFAULT;
+    tableConfig.columns = getTableColumns();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-main-layout.scss' as *;
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/basic-table-action.scss' as *;
+
+  .search-container {
+    display: flex;
+
+    .search-time {
+      color: rgba(0, 0, 0, 0.85);
+      font-size: 14px;
+      margin-left: 20px;
+      display: flex;
+      align-items: center;
+    }
+
+    .search-container-btn {
+      margin-left: auto;
+    }
+  }
+
+  .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>

+ 96 - 0
src/views/traffic/Accident/AccidentItem.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <main class="safety-platform-container__main">
+      <component :is="dynamicComponent" :id="id" ref="dynamicComponentRef" />
+    </main>
+    <footer class="safety-platform-container__footer" v-if="operate">
+      <el-button @click="router.back()">取消</el-button>
+      <el-button type="primary" @click="submit">提交</el-button>
+    </footer>
+    <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useRoute, useRouter } from 'vue-router';
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { ElMessage } from 'element-plus';
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import { AddAccidentInfoStruct, addVehicleInfo, updateVehicleInfo } from '@/api/traffic-accident';
+
+  const router = useRouter();
+  const route = useRoute();
+  const operate = route.query.operate;
+  const id = route.query.id;
+
+  const formLoading = ref(false);
+
+  const headerTitle = computed(() => {
+    const title = '交通事故记录';
+    if (operate === 'create') {
+      return `创建${title}`;
+    } else if (operate === 'edit') {
+      return `编辑${title}`;
+    }
+    return `查看${title}`;
+  });
+
+  const dynamicComponent = computed(() => {
+    if (operate === 'create' || operate === 'edit')
+      return defineAsyncComponent(() => import('./components/ManageAccidentItem.vue'));
+    return defineAsyncComponent(() => import('./components/ViewAccidentItem.vue'));
+  });
+
+  const dynamicComponentRef = ref();
+
+  const createTaskManagementItemFunc = async (formData: AddAccidentInfoStruct) => {
+    await addVehicleInfo(formData);
+  };
+
+  const editTaskManagementItemFunc = async (formData: AddAccidentInfoStruct) => {
+    await updateVehicleInfo(formData);
+  };
+
+  const submit = async () => {
+    if (!dynamicComponentRef.value) return;
+    const res = await dynamicComponentRef.value.handleValidate();
+    if (res) {
+      const formData = dynamicComponentRef.value.getFormData();
+      let message;
+      try {
+        formLoading.value = true;
+        if (operate === 'create') {
+          await createTaskManagementItemFunc(formData);
+          message = '创建成功';
+        } else if (operate === 'edit') {
+          await editTaskManagementItemFunc(formData);
+          message = '编辑成功';
+        }
+        ElMessage.success(message);
+        // 重置表单状态,避免触发二次确认
+        if (dynamicComponentRef.value && dynamicComponentRef.value.resetFormState) {
+          dynamicComponentRef.value.resetFormState();
+        }
+        router.back();
+      } finally {
+        formLoading.value = false;
+      }
+    } else {
+      console.log('不提交');
+    }
+  };
+</script>
+
+<style lang="scss" scoped>
+  @use '@/styles/page-details-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>

+ 431 - 0
src/views/traffic/Accident/components/ManageAccidentItem.vue

@@ -0,0 +1,431 @@
+<template>
+  <div class="accident-create-container">
+    <el-form ref="formRef" :model="formData" :rules="rules" class="form-wrap">
+      <div class="personnel-section">
+        <div v-for="(person, index) in personnelList" :key="index" class="personnel-item">
+          <el-row :gutter="40">
+            <el-col :span="6">
+              <el-form-item :prop="`accidentPersonnelInfoList.${index}.carNum`" :rules="carNumRules" label="车牌号:">
+                <el-input
+                  v-model="person.carNum"
+                  placeholder="请输入违规车辆车牌号码"
+                  maxlength="8"
+                  show-word-limit
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="10">
+              <el-form-item
+                :prop="`accidentPersonnelInfoList.${index}.accidentPersonnel`"
+                :rules="personRules"
+                label="事故人员:"
+              >
+                <el-input
+                  v-model="person.accidentPersonnel"
+                  placeholder="请输入事故人员姓名"
+                  maxlength="20"
+                  show-word-limit
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="6">
+              <el-form-item
+                :prop="`accidentPersonnelInfoList.${index}.phoneNum`"
+                :rules="phoneRules"
+                label="联系方式:"
+              >
+                <el-input
+                  v-model="person.phoneNum"
+                  placeholder="请输入联系方式"
+                  maxlength="11"
+                  show-word-limit
+                  clearable
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="2" class="delete-col">
+              <el-button
+                type="danger"
+                link
+                :icon="Delete"
+                :disabled="personnelList.length === 1"
+                @click="removePersonnel(index)"
+                >删除</el-button
+              >
+            </el-col>
+          </el-row>
+        </div>
+        <el-button type="primary" :icon="Plus" :disabled="personnelList.length >= 10" @click="addPersonnel">
+          新增
+        </el-button>
+      </div>
+
+      <el-row :gutter="40" class="basic-info">
+        <el-col :span="12">
+          <el-form-item label="事故地点:" prop="trafficAccidentRecord.accidentLocation">
+            <el-input
+              v-model="formData.trafficAccidentRecord.accidentLocation"
+              placeholder="请输入事故发生地点"
+              clearable
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="事故时间:" prop="trafficAccidentRecord.accidentTime">
+            <el-date-picker
+              v-model="formData.trafficAccidentRecord.accidentTime"
+              type="datetime"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              placeholder="请选择事故发生时间"
+              style="width: 100%"
+              :disabled-date="disabledDate"
+              :disabled-time="disabledTime"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-form-item label="事故描述:" prop="trafficAccidentRecord.accidentDescription">
+        <el-input
+          v-model="formData.trafficAccidentRecord.accidentDescription"
+          type="textarea"
+          :rows="4"
+          placeholder="请输入事故描述"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="事故图片:" label-width="92.54px">
+        <UploadImages
+          ref="uploadImagesRef"
+          :maxCount="5"
+          :image-list="recordImageList"
+          @upload-success="handleUploadChange"
+        />
+      </el-form-item>
+      <el-form-item label="备注:" label-width="92.54px">
+        <el-input v-model="formData.trafficAccidentRecord.remark" placeholder="请输入备注" clearable />
+      </el-form-item>
+      <el-form-item label="创建人:" label-width="92.54px">
+        <el-input v-model="formData.trafficAccidentRecord.createdByName" placeholder="创建人" disabled />
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted, computed } from 'vue';
+  import { useRoute, onBeforeRouteLeave } from 'vue-router';
+  import { ElForm } from 'element-plus';
+  import { Delete, Plus } from '@element-plus/icons-vue';
+  import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
+  import { useUserInfoHook } from '@/views/disaster/hooks';
+  import { ImageItem } from '@/types/disaster-control';
+  import { UPLOAD_BIZ_TYPE, uploadFileApi } from '@/api/minio';
+  import { AccidentPersonnelInfoStruct, AddAccidentInfoStruct, getAccidentInfoDetail } from '@/api/traffic-accident';
+  import { msgConfirm } from '@/utils/element-plus/messageBox';
+
+  const { realname } = useUserInfoHook();
+
+  const route = useRoute();
+  const operate = route.query.operate;
+  const id = route.query.id;
+
+  const formRef = ref<InstanceType<typeof ElForm>>();
+
+  const formData = ref<AddAccidentInfoStruct>({
+    trafficAccidentRecord: {
+      accidentImages: '',
+      accidentLocation: '',
+      accidentTime: '',
+      accidentDescription: '',
+      remark: '',
+      createdByName: '',
+    },
+    accidentPersonnelInfoList: [{ carNum: '', accidentPersonnel: '', phoneNum: '' }],
+  });
+
+  const personnelList = ref<AccidentPersonnelInfoStruct[]>([]);
+
+  // 保存初始表单数据用于比较
+  const initialFormData = ref<AddAccidentInfoStruct>({
+    trafficAccidentRecord: {
+      accidentImages: '',
+      accidentLocation: '',
+      accidentTime: '',
+      accidentDescription: '',
+      remark: '',
+      createdByName: '',
+    },
+    accidentPersonnelInfoList: [],
+  });
+  const initialPersonnelList = ref<AccidentPersonnelInfoStruct[]>([]);
+  const initialImagesString = ref<string>('');
+
+  const rules = reactive({
+    'trafficAccidentRecord.accidentLocation': [{ required: true, message: '请输入事故地点', trigger: 'blur' }],
+    'trafficAccidentRecord.accidentTime': [{ required: true, message: '请选择事故时间', trigger: 'change' }],
+    'trafficAccidentRecord.accidentDescription': [{ required: true, message: '请输入事故描述', trigger: 'blur' }],
+  });
+
+  const carNumRules = [
+    {
+      validator: (_: unknown, value: string, callback: (err?: Error) => void) => {
+        if (!value) return callback();
+        if (!/^.{0,8}$/.test(value)) return callback(new Error('车牌号格式错误'));
+        callback();
+      },
+      trigger: ['blur', 'change'],
+    },
+  ];
+  const personRules = [
+    { required: true, message: '请输入事故人员姓名', trigger: 'blur' },
+    { min: 1, max: 20, message: '最多20个字符', trigger: 'blur' },
+  ];
+  const phoneRules = [
+    {
+      validator: (_: unknown, value: string, callback: (err?: Error) => void) => {
+        if (!value) return callback();
+        if (!/^1\d{10}$/.test(value)) return callback(new Error('联系方式非11位数字'));
+        callback();
+      },
+      trigger: ['blur', 'change'],
+    },
+  ];
+
+  // 新增
+  const addPersonnel = () => {
+    if (personnelList.value.length >= 10) return;
+    personnelList.value.push({ carNum: '', accidentPersonnel: '', phoneNum: '' });
+  };
+
+  // 删除
+  const removePersonnel = (index: number) => {
+    if (personnelList.value.length === 1) return;
+    personnelList.value.splice(index, 1);
+  };
+
+  // 禁用未来日期
+  const disabledDate = (time: Date) => {
+    return time.getTime() > Date.now();
+  };
+
+  // 禁用未来时间
+  const disabledTime = (date: Date) => {
+    const now = new Date();
+    if (date.toDateString() === now.toDateString()) {
+      return {
+        disabledHours: () => {
+          const hours: number[] = [];
+          for (let i = now.getHours() + 1; i < 24; i++) {
+            hours.push(i);
+          }
+          return hours;
+        },
+        disabledMinutes: (hour: number) => {
+          if (hour === now.getHours()) {
+            const minutes: number[] = [];
+            for (let i = now.getMinutes() + 1; i < 60; i++) {
+              minutes.push(i);
+            }
+            return minutes;
+          }
+          return [];
+        },
+        disabledSeconds: (hour: number, minute: number) => {
+          if (hour === now.getHours() && minute === now.getMinutes()) {
+            const seconds: number[] = [];
+            for (let i = now.getSeconds() + 1; i < 60; i++) {
+              seconds.push(i);
+            }
+            return seconds;
+          }
+          return [];
+        },
+      };
+    }
+    return {};
+  };
+
+  // 事故图片
+  const uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
+  const uploadImages = ref<ImageItem[]>([]);
+  const imagesString = ref<string>('');
+
+  const recordImageList = computed(() => {
+    if (!formData.value.trafficAccidentRecord.accidentImages) return [];
+    return JSON.parse(formData.value.trafficAccidentRecord.accidentImages);
+  });
+
+  // 格式化事故图片
+  const formatImageList = async (file: File) => {
+    if (!file) return file;
+    const fileName = file.name;
+    const res = await uploadFileApi({ bizType: UPLOAD_BIZ_TYPE.ATTACHMENT, fileName, file });
+    return '"' + res.url + '"';
+  };
+
+  const handleUploadChange = async () => {
+    uploadImages.value = uploadImagesRef.value!.getUploadedImages();
+    const images = await Promise.all(
+      (uploadImages.value || []).map((item) => {
+        if (!item.file && item.url) {
+          return '"' + item.url + '"';
+        } else {
+          return formatImageList(item.file!);
+        }
+      }),
+    );
+    imagesString.value = '[' + images.toString() + ']';
+  };
+
+  // 检测表单是否有修改
+  const hasFormChanged = () => {
+    // 比较事故记录数据
+    const currentRecord = formData.value.trafficAccidentRecord;
+    const initialRecord = initialFormData.value.trafficAccidentRecord;
+
+    if (
+      currentRecord.accidentLocation !== initialRecord.accidentLocation ||
+      currentRecord.accidentTime !== initialRecord.accidentTime ||
+      currentRecord.accidentDescription !== initialRecord.accidentDescription ||
+      currentRecord.remark !== initialRecord.remark ||
+      imagesString.value !== initialImagesString.value
+    ) {
+      return true;
+    }
+
+    // 比较人员信息数据
+    if (personnelList.value.length !== initialPersonnelList.value.length) {
+      return true;
+    }
+
+    for (let i = 0; i < personnelList.value.length; i++) {
+      const current = personnelList.value[i];
+      const initial = initialPersonnelList.value[i];
+      if (
+        current.carNum !== initial.carNum ||
+        current.accidentPersonnel !== initial.accidentPersonnel ||
+        current.phoneNum !== initial.phoneNum
+      ) {
+        return true;
+      }
+    }
+
+    return false;
+  };
+
+  // 保存初始数据
+  const saveInitialData = () => {
+    initialFormData.value = JSON.parse(JSON.stringify(formData.value));
+    initialPersonnelList.value = JSON.parse(JSON.stringify(personnelList.value));
+    initialImagesString.value = imagesString.value;
+  };
+
+  // 重置表单状态(提交成功后调用)
+  const resetFormState = () => {
+    saveInitialData();
+  };
+
+  const handleValidate = async () => {
+    const form = formRef.value;
+    if (!form) return false;
+    try {
+      await form.validate();
+      return true;
+    } catch (_) {
+      return false;
+    }
+  };
+
+  const getFormData = (): AddAccidentInfoStruct => {
+    formData.value.trafficAccidentRecord.accidentImages = imagesString.value;
+    const payload: AddAccidentInfoStruct = {
+      trafficAccidentRecord: formData.value.trafficAccidentRecord,
+      accidentPersonnelInfoList: personnelList.value.map(({ carNum, accidentPersonnel, phoneNum }) => ({
+        carNum,
+        accidentPersonnel,
+        phoneNum,
+      })),
+    } as unknown as AddAccidentInfoStruct;
+    return payload;
+  };
+
+  // 路由守卫 - 离开页面前的确认
+  onBeforeRouteLeave((to, from, next) => {
+    const hasChange = hasFormChanged();
+    if (!hasChange) {
+      next();
+      return;
+    }
+
+    setTimeout(() => {
+      msgConfirm('当前页面存在修改,是否确认离开当前页面?', '提示', {
+        confirmButtonText: '确认',
+        cancelButtonText: '取消',
+        customClass: 'customMessageBox--warning',
+      })
+        .then(() => {
+          next();
+        })
+        .catch(() => {
+          next(false);
+        });
+    }, 200);
+  });
+
+  defineExpose({ handleValidate, getFormData, resetFormState });
+
+  onMounted(() => {
+    formData.value.trafficAccidentRecord.createdByName = realname;
+    personnelList.value = formData.value.accidentPersonnelInfoList;
+
+    if (operate === 'edit') {
+      getAccidentInfoDetail({ accidentRecordId: Number(id) }).then((res) => {
+        formData.value = res;
+        personnelList.value = res.accidentPersonnelInfoList;
+        imagesString.value = res.trafficAccidentRecord.accidentImages;
+
+        // 数据加载完成后保存初始数据
+        setTimeout(() => {
+          saveInitialData();
+        }, 100);
+      });
+    } else {
+      // 创建模式,立即保存初始数据
+      setTimeout(() => {
+        saveInitialData();
+      }, 100);
+    }
+  });
+</script>
+
+<style scoped lang="scss">
+  .accident-create-container {
+    .personnel-section {
+      background: #f5f9ff;
+      border-radius: 4px;
+      padding: 20px 34px;
+      margin-bottom: 30px;
+
+      .personnel-item {
+        margin-bottom: 20px;
+
+        :deep(.el-form-item) {
+          margin-bottom: 0;
+        }
+
+        .el-row {
+          align-items: center;
+        }
+      }
+
+      .delete-col {
+        height: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+      }
+    }
+  }
+</style>

+ 159 - 0
src/views/traffic/Accident/components/ViewAccidentItem.vue

@@ -0,0 +1,159 @@
+<template>
+  <div class="accident-view-container">
+    <div v-for="(person, index) in personnelList" :key="index" class="personnel-item">
+      <div class="field-item">
+        <label class="field-label">车牌号:</label>
+        <div class="field-value">{{ person.carNum || '-' }}</div>
+      </div>
+      <div class="field-item">
+        <label class="field-label">事故人员:</label>
+        <div class="field-value">{{ person.accidentPersonnel || '-' }}</div>
+      </div>
+      <div class="field-item">
+        <label class="field-label">联系方式:</label>
+        <div class="field-value">{{ person.phoneNum || '-' }}</div>
+      </div>
+    </div>
+    <div class="field-item">
+      <label class="field-label">事故地点:</label>
+      <div class="field-value">{{ formData.trafficAccidentRecord.accidentLocation || '-' }}</div>
+    </div>
+    <div class="field-item">
+      <label class="field-label">事故时间:</label>
+      <div class="field-value">{{ formData.trafficAccidentRecord.accidentTime || '-' }}</div>
+    </div>
+    <div class="field-item">
+      <label class="field-label">事故描述:</label>
+      <div class="field-value description">{{ formData.trafficAccidentRecord.accidentDescription || '-' }}</div>
+    </div>
+    <div class="field-item">
+      <label class="field-label">备注:</label>
+      <div class="field-value">{{ formData.trafficAccidentRecord.remark || '-' }}</div>
+    </div>
+    <div class="field-item">
+      <label class="field-label">创建人:</label>
+      <div class="field-value">{{ formData.trafficAccidentRecord.createdByName || '-' }}</div>
+    </div>
+    <div class="field-item image-item">
+      <label class="field-label">事故图片:</label>
+      <div class="field-value">
+        <ShowImages v-if="recordImageList.length > 0" style="min-height: 100px" :image-list="recordImageList" />
+        <div v-else class="no-data">暂无图片</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, onMounted, computed } from 'vue';
+  import { useRoute } from 'vue-router';
+  import ShowImages from '@/views/emergency/emergency-drill/components/ShowImages.vue';
+  import type { AddAccidentInfoStruct, AccidentPersonnelInfoStruct } from '@/api/traffic-accident';
+  import { getAccidentInfoDetail } from '@/api/traffic-accident';
+
+  const route = useRoute();
+  const id = route.query.id as string;
+
+  const formData = ref<AddAccidentInfoStruct>({
+    trafficAccidentRecord: {
+      accidentImages: '',
+      accidentLocation: '',
+      accidentTime: '',
+      accidentDescription: '',
+      remark: '',
+      createdByName: '',
+    },
+    accidentPersonnelInfoList: [],
+  });
+
+  const personnelList = ref<AccidentPersonnelInfoStruct[]>([]);
+
+  const recordImageList = computed(() => {
+    if (!formData.value.trafficAccidentRecord.accidentImages) return [];
+    try {
+      return JSON.parse(formData.value.trafficAccidentRecord.accidentImages);
+    } catch {
+      return [];
+    }
+  });
+
+  // 加载数据
+  const loadData = async () => {
+    if (!id) return;
+    try {
+      const response = await getAccidentInfoDetail({ accidentRecordId: Number(id) });
+      if (response) {
+        // 填充事故记录数据
+        formData.value.trafficAccidentRecord = {
+          ...response.trafficAccidentRecord,
+          id: response.trafficAccidentRecord.id,
+        };
+
+        // 填充人员信息数据
+        if (response.accidentPersonnelInfoList && response.accidentPersonnelInfoList.length > 0) {
+          personnelList.value = response.accidentPersonnelInfoList;
+        } else {
+          // 如果没有人员信息,显示空状态
+          personnelList.value = [];
+        }
+      }
+    } catch (error) {
+      console.error('加载数据失败:', error);
+    }
+  };
+
+  onMounted(() => {
+    loadData();
+  });
+</script>
+
+<style scoped lang="scss">
+  .accident-view-container {
+    .personnel-item {
+      display: flex;
+      align-items: center;
+
+      .field-value {
+        width: 300px;
+      }
+    }
+
+    .field-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 20px;
+
+      .field-label {
+        width: 92.54px;
+        color: rgba(0, 0, 0, 0.85);
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 32px;
+        flex-shrink: 0;
+      }
+
+      .field-value {
+        flex: 1;
+        color: rgba(0, 0, 0, 0.65);
+        font-size: 16px;
+        line-height: 32px;
+        word-break: break-all;
+
+        &.description {
+          line-height: 1.5;
+          white-space: pre-wrap;
+        }
+      }
+
+      .no-data {
+        color: rgba(0, 0, 0, 0.25);
+        font-style: italic;
+      }
+    }
+
+    .image-item {
+      display: flex;
+      align-items: flex-start;
+    }
+  }
+</style>

+ 13 - 0
src/views/traffic/Accident/config/index.ts

@@ -0,0 +1,13 @@
+import {
+  ACCIDENT_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+  ACCIDENT_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  ACCIDENT_LIST_TABLE_OPTIONS,
+  ACCIDENT_LIST_TABLE_COLUMNS,
+} from './table';
+
+export {
+  ACCIDENT_LIST_TABLE_MAX_HEIGHT_DEFAULT,
+  ACCIDENT_LIST_TABLE_MAX_HEIGHT_PERMISSION,
+  ACCIDENT_LIST_TABLE_OPTIONS,
+  ACCIDENT_LIST_TABLE_COLUMNS,
+};

+ 60 - 0
src/views/traffic/Accident/config/table.ts

@@ -0,0 +1,60 @@
+/**
+ * 车辆信息表格配置
+ */
+import type { TableColumnProps } from '@/types/basic-table';
+
+export const ACCIDENT_LIST_TABLE_MAX_HEIGHT_DEFAULT = 'calc(70vh - 80px)';
+export const ACCIDENT_LIST_TABLE_MAX_HEIGHT_PERMISSION = 'calc(70vh - 130px)';
+
+// 基础表格样式配置
+const TABLE_OPTIONS = {
+  emptyText: '暂无数据',
+  loading: true,
+};
+
+// 车辆信息表格样式配置
+export const ACCIDENT_LIST_TABLE_OPTIONS = {
+  ...TABLE_OPTIONS,
+};
+
+// 应急处置表格列配置
+export const ACCIDENT_LIST_TABLE_COLUMNS: TableColumnProps[] = [
+  {
+    label: '',
+    width: '55px',
+    type: 'selection',
+  },
+  {
+    label: '序号',
+    prop: 'index',
+    width: '80px',
+    type: 'index',
+    align: 'center',
+  },
+  {
+    label: '事故地点',
+    prop: 'accidentLocation',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '事故时间',
+    prop: 'accidentTime',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '事故描述',
+    prop: 'accidentDescription',
+    align: 'center',
+    minWidth: '180px',
+  },
+  {
+    label: '操作',
+    prop: 'action',
+    align: 'center',
+    slot: 'action',
+    fixed: 'right',
+    width: '180px',
+  },
+];

+ 3 - 3
src/views/traffic/Accident/constant/index.ts

@@ -9,13 +9,13 @@ export const FIELD_CONTENT = {
   [FIELDTYPE.DESCRIPTION]: '事故描述',
 };
 
-export const FIELD_CONTENT_OPTIONS = [
+export const accidentQueryOptions = [
   {
     label: FIELD_CONTENT[FIELDTYPE.LOCATION],
     value: FIELDTYPE.LOCATION,
   },
   {
-    label: FIELD_CONTENT[FIELDTYPE.LOCATION],
-    value: FIELDTYPE.LOCATION,
+    label: FIELD_CONTENT[FIELDTYPE.DESCRIPTION],
+    value: FIELDTYPE.DESCRIPTION,
   },
 ];

+ 2 - 0
src/views/traffic/constant/index.ts

@@ -2,6 +2,8 @@
 export const TRAFFIC_PERMISSIONS = {
   // 总览——当月车辆违规数量编辑权限
   EDIT_VEHICLE_VIOLATIONS: 'traffic_business_module:edit_vehicle_violations',
+  // 交通事故管理——交通事故记录管理权限
+  ACCIDENT_MANAGE: 'traffic_business_module:accident_manage',
   // 车辆信息管理——车辆信息记录管理权限
   VEHICLE_MANAGE: 'traffic_business_module:vehicle_manage',
 };

+ 45 - 3
src/views/traffic/overview/components/AccidentRecords.vue

@@ -7,18 +7,60 @@
         >查看全部<el-icon><ArrowRight /></el-icon
       ></span>
     </div>
-    <div class="table-container"></div>
+    <div class="table-container">
+      <el-table :data="accidentRecords" style="width: 100%">
+        <el-table-column prop="accidentLocation" label="事故地点" />
+        <el-table-column prop="accidentTime" label="事故时间" />
+        <el-table-column prop="accidentDescription" label="事故描述" />
+        <el-table-column prop="action" label="操作" width="140px" align="center" fixed="right">
+          <template #default="scope">
+            <el-button link type="primary" size="small" @click="handleViewDetail(scope.row.id)"> 查看详情 </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import router from '@/router';
   import { ElIcon } from 'element-plus';
   import { ArrowRight } from '@element-plus/icons-vue';
+  import {
+    QueryPageParams,
+    QueryTrafficAccidentRecordOverviewPageRes,
+    getTrafficAccidentRecordOverview,
+  } from '@/api/traffic-overview';
+
+  const queryPageParams = ref<QueryPageParams>({
+    pageNumber: 1,
+    pageSize: 20,
+  });
+  const accidentRecords = ref<QueryTrafficAccidentRecordOverviewPageRes[]>([]);
 
   const handleClickMore = () => {
-    // TODO: 点击跳转“管理规定与通知”菜单
-    console.log('1111111111111111111111');
+    router.push({
+      path: '/traffic/accident',
+    });
+  };
+
+  const handleViewDetail = (id: number) => {
+    router.push({
+      path: '/traffic/accident-item',
+      query: { id: id },
+    });
   };
+
+  const getTrafficAccidentRecordOverviewData = () => {
+    getTrafficAccidentRecordOverview(queryPageParams.value).then((res) => {
+      accidentRecords.value = res.records;
+    });
+  };
+
+  onMounted(() => {
+    getTrafficAccidentRecordOverviewData();
+  });
 </script>
 
 <style scoped lang="scss">

+ 33 - 21
src/views/traffic/overview/components/RegulationList.vue

@@ -12,44 +12,56 @@
         class="regulation-item"
         v-for="(item, index) in regulationList"
         :key="index"
-        :title="item.title"
+        :title="item.name"
         @click="handleClick(item)"
       >
-        {{ item.title }}
+        {{ item.name }}
       </div>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { ref } from 'vue';
+  import { ref, onMounted } from 'vue';
+  import router from '@/router';
   import { ElIcon } from 'element-plus';
   import { ArrowRight } from '@element-plus/icons-vue';
+  import { getManagementTypeUrl, MANAGEMENT_TYPE } from '../constant';
+  import { QueryRuleNoticeInfoListOverviewRes, getRuleNoticeInfoListOverview } from '@/api/traffic-overview';
 
-  const regulationList = ref([
-    {
-      id: 1,
-      title: '应急预案',
-    },
-    {
-      id: 2,
-      title: '长文案去去去去去去去去去前期前期前期前期前期前期前期前期前期',
-    },
-    {
-      id: 3,
-      title: '交通管理通知是指在交通管理中,为了保障交通安全、有序进行而发布的一系列通知。',
-    },
-  ]);
+  const regulationList = ref<QueryRuleNoticeInfoListOverviewRes[]>([]);
 
   const handleClickMore = () => {
-    // TODO: 点击跳转“管理规定与通知”菜单
-    console.log('1111111111111111111111');
+    router.push({
+      path: '/traffic/regulation',
+    });
   };
 
   const handleClick = (item) => {
-    // TODO: 点击跳转到具体详情页
-    console.log(item);
+    if (item.managementType === MANAGEMENT_TYPE.MANAGEMENT_NOTICE) {
+      router.push({
+        path: getManagementTypeUrl(item.managementType),
+        query: {
+          id: item.id,
+          operate: 'notice-view',
+        },
+      });
+    } else {
+      router.push({
+        path: getManagementTypeUrl(item.managementType),
+      });
+    }
+  };
+
+  const getRuleNoticeInfoListOverviewData = () => {
+    getRuleNoticeInfoListOverview().then((res) => {
+      regulationList.value = res;
+    });
   };
+
+  onMounted(() => {
+    getRuleNoticeInfoListOverviewData();
+  });
 </script>
 
 <style scoped lang="scss">

+ 42 - 3
src/views/traffic/overview/components/ViolationRecords.vue

@@ -7,18 +7,57 @@
         >查看全部<el-icon><ArrowRight /></el-icon
       ></span>
     </div>
-    <div class="table-container"></div>
+    <div class="table-container">
+      <el-table :data="violationRecords" style="width: 100%">
+        <el-table-column prop="carNumber" label="车牌号" />
+        <el-table-column prop="violateBy" label="车主" />
+        <el-table-column prop="deptName" label="所属部门" />
+        <el-table-column prop="violateType" label="违规类型">
+          <template #default="scope">
+            {{ getVehicleViolationTypeMap(scope.row.violateType) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="violateLocation" label="违规地点" />
+        <el-table-column prop="captureTime" label="违规时间" />
+        <el-table-column prop="capturePhotos" label="抓拍照片" />
+      </el-table>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
+  import { ref, onMounted } from 'vue';
+  import router from '@/router';
   import { ElIcon } from 'element-plus';
   import { ArrowRight } from '@element-plus/icons-vue';
+  import { getVehicleViolationTypeMap } from '@/views/traffic/overview/constant';
+  import {
+    QueryPageParams,
+    QueryTrafficViolationOverviewPageRes,
+    getTrafficViolationOverview,
+  } from '@/api/traffic-overview';
+
+  const queryPageParams = ref<QueryPageParams>({
+    pageNumber: 1,
+    pageSize: 20,
+  });
+  const violationRecords = ref<QueryTrafficViolationOverviewPageRes[]>([]);
 
   const handleClickMore = () => {
-    // TODO: 点击跳转“管理规定与通知”菜单
-    console.log('1111111111111111111111');
+    router.push({
+      path: '/traffic/violation/notice',
+    });
   };
+
+  const getTrafficViolationOverviewData = () => {
+    getTrafficViolationOverview(queryPageParams.value).then((res) => {
+      violationRecords.value = res.records;
+    });
+  };
+
+  onMounted(() => {
+    getTrafficViolationOverviewData();
+  });
 </script>
 
 <style scoped lang="scss">

+ 18 - 4
src/views/traffic/overview/components/ViolationStatistics.vue

@@ -47,13 +47,14 @@
   import { Edit } from '@element-plus/icons-vue';
   import { useUserInfoHook } from '@/hooks/useUserInfoHook';
   import { TRAFFIC_PERMISSIONS } from '@/views/traffic/constant';
+  import { getViolationStatisticsOverview, addVehicleViolationCount } from '@/api/traffic-overview';
 
   const { permissions } = useUserInfoHook();
   const editVehicleViolations = ref<boolean>();
 
-  const vehicleCount = ref<number>(1111);
-  const regulationCount = ref<number>(2222);
-  const accidentCount = ref<number>(3333);
+  const vehicleCount = ref<number>(0);
+  const regulationCount = ref<number>(0);
+  const accidentCount = ref<number>(0);
 
   const vehicleCountCopy = ref<number>(0);
   const dialogVisible = ref<boolean>(false);
@@ -77,14 +78,27 @@
 
   const handleConfirm = async () => {
     if (!isValid.value) return;
-    // TODO: 调用后端API更新数据,重新调用接口获取数据
+    addVehicleViolationCount({
+      violateCount: vehicleCountCopy.value,
+    }).then(() => {
+      getViolationStatisticsOverviewData();
+    });
     dialogVisible.value = false;
   };
 
+  const getViolationStatisticsOverviewData = () => {
+    getViolationStatisticsOverview().then((res) => {
+      vehicleCount.value = res.vehicleViolationCount || 0;
+      regulationCount.value = res.violationNoticeCount || 0;
+      accidentCount.value = res.trafficAccidentCount || 0;
+    });
+  };
+
   onMounted(() => {
     editVehicleViolations.value = Boolean(
       permissions.find((item: { code: string }) => item.code === TRAFFIC_PERMISSIONS.EDIT_VEHICLE_VIOLATIONS),
     );
+    getViolationStatisticsOverviewData();
   });
 </script>
 

+ 35 - 0
src/views/traffic/overview/constant/index.ts

@@ -0,0 +1,35 @@
+/**
+ * @description: 交通管理规定与通知:1-管理规定,2-管理通知
+ */
+export enum MANAGEMENT_TYPE {
+  MANAGEMENT_REGULATION = 1,
+  MANAGEMENT_NOTICE = 2,
+}
+
+export const MANAGEMENT_TYPE_MAP = {
+  [MANAGEMENT_TYPE.MANAGEMENT_REGULATION]: '管理规定',
+  [MANAGEMENT_TYPE.MANAGEMENT_NOTICE]: '管理通知',
+};
+
+export const getManagementTypeUrl = (type: MANAGEMENT_TYPE) => {
+  return type === MANAGEMENT_TYPE.MANAGEMENT_REGULATION ? '/traffic/regulation' : '/traffic/regulation-item';
+};
+
+/**
+ * @description: 车辆违规类型:1-超速,2-逆行,3-违规停车
+ */
+export enum VEHICLE_VIOLATION_TYPE {
+  OVER_SPEED = 1,
+  REVERSE = 2,
+  PARKING = 3,
+}
+
+export const VEHICLE_VIOLATION_TYPE_MAP = {
+  [VEHICLE_VIOLATION_TYPE.OVER_SPEED]: '超速',
+  [VEHICLE_VIOLATION_TYPE.REVERSE]: '逆行',
+  [VEHICLE_VIOLATION_TYPE.PARKING]: '违规停车',
+};
+
+export const getVehicleViolationTypeMap = (type: VEHICLE_VIOLATION_TYPE) => {
+  return VEHICLE_VIOLATION_TYPE_MAP[type];
+};