Преглед изворни кода

feat: 门禁事件记录联调

wyf пре 7 месеци
родитељ
комит
8188b8be98

+ 61 - 0
src/api/security-confidentiality-access/index.ts

@@ -0,0 +1,61 @@
+import { http } from '@/utils/http/axios';
+import type { QueryPageRequest, QueryPageResponse } from '@/types/basic-query';
+
+import type {
+  AccessTableQuery,
+  AccessDetailResponse,
+  CreateAccessQuery,
+  UpdateAccessQuery,
+} from '@/views/security-confidentiality/access-control/types';
+
+export function getAccessRecordList(data: QueryPageRequest<AccessTableQuery>) {
+  return http.request<QueryPageResponse<AccessDetailResponse>>({
+    url: '/accessControlAbnormalRecord/queryAccessControlAbnormalRecordPage',
+    method: 'POST',
+    data,
+  });
+}
+
+export function exportAccessRecordList(data: AccessTableQuery) {
+  return http.request(
+    {
+      url: '/accessControlAbnormalRecord/exportAccessControlAbnormalRecord',
+      method: 'post',
+      data,
+      responseType: 'blob',
+    },
+    {
+      isTransformResponse: false,
+    },
+  );
+}
+
+export function deleteAccessRecord(id: number) {
+  return http.request({
+    url: `/accessControlAbnormalRecord/deleteAccessControlAbnormalRecord?recordId=${id}`,
+    method: 'DELETE',
+  });
+}
+
+export function createAccessRecord(data: CreateAccessQuery) {
+  return http.request({
+    url: '/accessControlAbnormalRecord/saveAccessControlAbnormalRecord',
+    method: 'POST',
+    data,
+  });
+}
+
+export function getAccessRecordDetail(id: number) {
+  return http.request<AccessDetailResponse>({
+    url: `/accessControlAbnormalRecord/exportAccessControlAbnormalRecord?recordId=${id}`,
+    method: 'GET',
+  });
+}
+
+export function updateAccessRecord(data: UpdateAccessQuery) {
+  return http.request({
+    url: '/accessControlAbnormalRecord/saveAccessControlAbnormalRecord',
+    method: 'POST',
+    data,
+  });
+}

+ 77 - 27
src/views/security-confidentiality/access-control/AccessControl.vue

@@ -45,6 +45,19 @@
           @update:pageSize="handleSizeChange"
           @update:pageNumber="handleCurrentChange"
         >
+          <template #images="scope">
+            <ImageViewer :file-list="scope.row.images" />
+          </template>
+          <template #actions="scope">
+            <ActionButton text="编辑" @click="handleEditRecord(scope.row.id)" />
+            <ActionButton
+              text="删除"
+              :popconfirm="{
+                title: '确定要删除?',
+              }"
+              @confirm="handleDeleteRecord(scope.row.id)"
+            />
+          </template>
         </BasicTable>
       </div>
     </main>
@@ -56,18 +69,26 @@
   import useTableConfig from '@/hooks/useTableConfigHook';
   import ActionButton from '@/components/ActionButton.vue';
   import SelectableInput from '@/components/formItems/selectableInput/SelectableInput.vue';
+  import ImageViewer from '@/views/traffic/violation/act/components/ImageViewer.vue';
   import { useRouter } from 'vue-router';
   import { ref, reactive, onMounted } from 'vue';
   import { Plus } from '@element-plus/icons-vue';
+  import { ElMessage } from 'element-plus';
+  import { downloadFile } from '@/views/disaster/utils';
   import { useUserInfoHook } from '@/hooks/useUserInfoHook';
-  import { ACCESS_MANAGEMENT_PERMISSION, ACCESS_TABLE_SEARCH_OPTIONS } from './constant';
+  import {
+    getAccessRecordList,
+    exportAccessRecordList,
+    deleteAccessRecord,
+  } from '@/api/security-confidentiality-access';
+  import { ACCESS_MANAGEMENT_PERMISSION, ACCESS_TABLE_SEARCH_OPTIONS } from './constants';
   import { TABLE_OPTIONS, ACCESS_TABEL_COLUMNS, ACCESS_TABEL_COLUMNS_CHECKONLY } from './configs/tables';
-  import type { AccessTableData, AccessTableQuery } from './types';
+  import type { AccessDetailResponse, AccessTableQuery } from './types';
   import type { QueryPageRequest } from '@/types/basic-query';
 
   const { permissions } = useUserInfoHook();
   const managementPermission = ref<Boolean>(
-    Boolean(permissions.find((item: { code: string }) => item.code === ACCESS_MANAGEMENT_PERMISSION)),
+    !Boolean(permissions.find((item: { code: string }) => item.code === ACCESS_MANAGEMENT_PERMISSION)),
   );
 
   const router = useRouter();
@@ -80,17 +101,22 @@
     });
   };
 
-  const searchData = reactive<AccessTableQuery>({});
   const searchTime = ref<string[]>([]);
   const selectableInputRef = ref<InstanceType<typeof SelectableInput>>();
   function getQuery() {
-    // TODO
+    const selectableSearch = selectableInputRef.value?.getValue();
+    tableQuery.queryParam = {
+      fieldType: selectableSearch?.key as number | null,
+      fieldContent: selectableSearch?.value,
+      startTime: searchTime.value[0],
+      endTime: searchTime.value[1],
+    };
   }
   async function getTableData() {
     tableConfig.loading = true;
-    // const res = await getInnerPersonRecordList(tableQuery);
-    // tableData.value = res.records;
-    // pagination.total = res.totalRow;
+    const res = await getAccessRecordList(tableQuery);
+    tableData.value = res.records;
+    pagination.total = res.totalRow;
     tableConfig.loading = false;
   }
   function handleSearch() {
@@ -100,9 +126,9 @@
 
   function handleReset() {
     selectableInputRef.value?.clearValue();
-    for (const key in searchData) {
-      if (searchData.hasOwnProperty(key)) {
-        searchData[key] = undefined;
+    for (const key in tableQuery.queryParam) {
+      if (tableQuery.queryParam.hasOwnProperty(key)) {
+        tableQuery.queryParam[key] = undefined;
       }
     }
     searchTime.value = [];
@@ -110,17 +136,17 @@
   }
 
   async function handleDownload() {
-    // getQuery();
-    // try {
-    //   const res = await exportInnerPersonRecordList(tableQuery.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);
-    // }
+    getQuery();
+    try {
+      const res = await exportAccessRecordList(tableQuery.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);
+    }
   }
 
   // 表格
@@ -128,24 +154,48 @@
     managementPermission.value ? ACCESS_TABEL_COLUMNS : ACCESS_TABEL_COLUMNS_CHECKONLY,
     TABLE_OPTIONS,
   );
-  const tableData = ref<AccessTableData[]>([]);
+  const tableData = ref<AccessDetailResponse[]>([]);
 
-  const tableQuery = ref<QueryPageRequest<AccessTableQuery>>({
+  const tableQuery = reactive<QueryPageRequest<AccessTableQuery>>({
     pageNumber: pagination.pageNumber,
     pageSize: pagination.pageSize,
-    queryParam: searchData,
+    queryParam: {},
   });
 
   const handleSizeChange = (value: number) => {
     pagination.pageSize = value;
-    tableQuery.value.pageSize = value;
+    tableQuery.pageSize = value;
     getTableData();
   };
   const handleCurrentChange = (value: number) => {
     pagination.pageNumber = value;
-    tableQuery.value.pageNumber = value;
+    tableQuery.pageNumber = value;
+    getTableData();
+  };
+
+  const handleEditRecord = (id: number) => {
+    router.push({
+      name: 'security-confidentiality-access-control-item',
+      query: {
+        operate: 'access-edit',
+        id,
+      },
+    });
+  };
+
+  const handleDeleteRecord = async (id: number) => {
+    try {
+      await deleteAccessRecord(id);
+      ElMessage.success('删除成功');
+    } catch (e) {
+      ElMessage.error('删除失败');
+    }
     getTableData();
   };
+
+  onMounted(() => {
+    getTableData();
+  });
 </script>
 
 <style scoped lang="scss">

+ 50 - 3
src/views/security-confidentiality/access-control/AccessControlItem.vue

@@ -1,7 +1,54 @@
 <template>
-  <div> overview </div>
+  <div class="safety-platform-container">
+    <header class="safety-platform-container__header">
+      <BreadcrumbBack />
+      <span class="breadcrumb-title">{{ headerTitle }}</span>
+    </header>
+    <component :is="dynamicComponent" :id="id" ref="dynamicComponentRef" @record-submitted="handleRecordSubmitted" />
+  </div>
 </template>
 
-<script setup lang="ts"></script>
+<script setup lang="ts">
+  import { ref, computed, defineAsyncComponent } from 'vue';
+  import { useRoute } from 'vue-router';
 
-<style scoped></style>
+  const route = useRoute();
+  const operate = route.query.operate;
+  const id = Number(route.query.id);
+  const headerTitle = computed(() => {
+    switch (operate) {
+      case 'access-create':
+        return `创建门禁事件记录`;
+      case 'access-edit':
+        return `编辑门禁事件记录`;
+      default:
+        return '未知操作';
+    }
+  });
+
+  const dynamicComponent = computed(() => {
+    switch (operate) {
+      case 'access-create':
+        return defineAsyncComponent(() => import('./components/AccessCreate.vue'));
+      case 'access-edit':
+        return defineAsyncComponent(() => import('./components/AccessEdit.vue'));
+      default:
+        return '';
+    }
+  });
+
+  const dynamicComponentRef = ref();
+
+  function handleRecordSubmitted() {}
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+  @use '@/styles/page-main-layout.scss' as *;
+
+  .safety-platform-container__header {
+    flex-direction: row !important;
+    justify-content: flex-start !important;
+    gap: 8px !important;
+  }
+</style>

+ 82 - 0
src/views/security-confidentiality/access-control/components/AccessCreate.vue

@@ -0,0 +1,82 @@
+<template>
+  <main class="safety-platform-container__main">
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #images>
+        <UploadImages :maxCount="9" ref="uploadImagesRef" @upload-success="handleUploadChange" />
+      </template>
+    </BasicForm>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">取消</el-button>
+    <el-button type="primary" @click="handleSubmit">提交</el-button>
+  </footer>
+  <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { useUserInfoHook } from '@/views/disaster/hooks';
+  import { createAccessRecord } from '@/api/security-confidentiality-access';
+  import { CreateAccessRuleForm, CreateAccessQuery } from '../types';
+  import { ACCESS_FORM_CONFIG, ACCESS_FORM_DATA, ACCESS_FORM_RULES } from '../configs/form';
+  import { formatImageList } from '../utils';
+
+  const { realname } = useUserInfoHook();
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<CreateAccessRuleForm>(ACCESS_FORM_CONFIG, ACCESS_FORM_DATA, ACCESS_FORM_RULES);
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    return res;
+  };
+
+  const getFormData = async () => {
+    cloneRuleFormData();
+    const res: CreateAccessQuery = {
+      ...ruleFormData,
+      images: JSON.stringify(await formatImageList(ruleFormData.images)),
+    };
+    return res;
+  };
+
+  const formLoading = ref(false);
+  const router = useRouter();
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      formLoading.value = true;
+      const params = await getFormData();
+      await createAccessRecord(params);
+      ElMessage.success('创建成功');
+      router.back();
+    } catch (e) {
+      console.log(e);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+  const handleUploadChange = () => {
+    ruleFormData.images = uploadImagesRef.value!.getUploadedImages();
+  };
+
+  onMounted(() => {
+    ruleFormData.creatName = realname;
+    cloneRuleFormData();
+    beforeRouteLeave();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+</style>

+ 107 - 0
src/views/security-confidentiality/access-control/components/AccessEdit.vue

@@ -0,0 +1,107 @@
+<template>
+  <main class="safety-platform-container__main">
+    <BasicForm ref="basicFormRef" :formData="ruleFormData" :formRules="formRules" :formConfig="ruleFormConfig">
+      <template #images>
+        <UploadImages
+          :maxCount="9"
+          ref="uploadImagesRef"
+          :image-list="imageList"
+          @upload-success="handleUploadChange"
+        />
+      </template>
+    </BasicForm>
+  </main>
+  <footer class="safety-platform-container__footer">
+    <el-button @click="router.back()">取消</el-button>
+    <el-button type="primary" @click="handleSubmit">提交</el-button>
+  </footer>
+  <UploadLoading :form-loading="formLoading" v-if="formLoading" />
+</template>
+
+<script setup lang="ts">
+  import { onMounted, ref } from 'vue';
+  import { useRouter } from 'vue-router';
+  import { ElMessage } from 'element-plus';
+  import BasicForm from '@/components/BasicForm.vue';
+  import UploadLoading from '@/components/UploadLoading.vue';
+  import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
+  import { useFormConfigHook } from '@/hooks/useFormConfigHook';
+  import { createAccessRecord, getAccessRecordDetail } from '@/api/security-confidentiality-access';
+  import type { AccessDetailResponse, CreateAccessRuleForm, UpdateAccessQuery } from '../types';
+  import { ACCESS_FORM_CONFIG, ACCESS_FORM_DATA, ACCESS_FORM_RULES } from '../configs/form';
+  import { formatImageList, unformatImage } from '../utils';
+
+  const props = defineProps<{
+    id: number;
+  }>();
+
+  const { ruleFormData, formRules, ruleFormConfig, cloneRuleFormData, beforeRouteLeave } =
+    useFormConfigHook<CreateAccessRuleForm>(ACCESS_FORM_CONFIG, ACCESS_FORM_DATA, ACCESS_FORM_RULES);
+
+  const basicFormRef = ref<InstanceType<typeof BasicForm>>();
+  const uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
+  const imageList = ref<string[]>([]);
+
+  const handleValidate = async () => {
+    if (!basicFormRef.value) return;
+    const res = await basicFormRef.value.validateForm();
+    return res;
+  };
+
+  const getDetail = async () => {
+    const res: AccessDetailResponse = await getAccessRecordDetail(props.id);
+    if (!res) return;
+    ruleFormData.eventDescription = res.eventDescription;
+    ruleFormData.eventLocation = res.eventLocation;
+    ruleFormData.eventTime = res.eventTime;
+    ruleFormData.remark = res.remark;
+    ruleFormData.creatName = res.creatName;
+    ruleFormData.images = unformatImage(res.images)?.map((x) => {
+      return { url: x };
+    });
+
+    imageList.value = ruleFormData.images?.map((x) => x.url)!;
+    cloneRuleFormData();
+  };
+
+  const getFormData = async () => {
+    cloneRuleFormData();
+    const res: UpdateAccessQuery = {
+      id: props.id,
+      ...ruleFormData,
+      images: JSON.stringify(await formatImageList(ruleFormData.images)),
+    };
+    return res;
+  };
+
+  const formLoading = ref(false);
+  const router = useRouter();
+  const handleSubmit = async () => {
+    const res = await handleValidate();
+    if (!res) return;
+    try {
+      formLoading.value = true;
+      const params = await getFormData();
+      await createAccessRecord(params);
+      ElMessage.success('创建成功');
+      router.back();
+    } catch (e) {
+      console.log(e);
+    } finally {
+      formLoading.value = false;
+    }
+  };
+  const handleUploadChange = () => {
+    ruleFormData.images = uploadImagesRef.value!.getUploadedImages();
+  };
+
+  onMounted(() => {
+    getDetail();
+    cloneRuleFormData();
+    beforeRouteLeave();
+  });
+</script>
+
+<style scoped lang="scss">
+  @use '@/styles/page-details-layout.scss' as *;
+</style>

+ 73 - 0
src/views/security-confidentiality/access-control/configs/form.ts

@@ -0,0 +1,73 @@
+import { FormConfig } from '@/types/basic-form';
+
+export const ACCESS_FORM_CONFIG: FormConfig[] = [
+  {
+    prop: 'eventDescription',
+    label: '事件描述:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入事件的具体描述',
+      type: 'textarea',
+      rows: 5,
+    },
+  },
+  {
+    prop: 'eventLocation',
+    label: '事件地点:',
+    component: 'ElInput',
+    componentProps: {
+      placeholder: '请输入事件发生地点',
+    },
+  },
+  {
+    prop: 'eventTime',
+    label: '事件时间:',
+    component: 'ElDatePicker',
+    componentProps: {
+      placeholder: '请选择事件发生时间',
+      type: 'datetime',
+      format: 'YYYY-MM-DD HH:mm:ss',
+      dateFormat: 'MMM DD, YYYY',
+      timeFormat: 'HH:mm:ss',
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      disabledDate: (time) => time.getTime() > Date.now(),
+    },
+  },
+  {
+    prop: 'images',
+    label: '抓拍照片:',
+    slot: 'images',
+  },
+  {
+    label: '备注:',
+    prop: 'remark',
+    component: 'ElInput',
+    componentProps: {
+      type: 'textarea',
+      rows: 5,
+    },
+  },
+  {
+    label: '创建人:',
+    prop: 'creatName',
+    component: 'ElInput',
+    componentProps: {
+      disabled: true,
+    },
+  },
+];
+
+export const ACCESS_FORM_DATA = {
+  eventDescription: '',
+  eventLocation: '',
+  eventTime: '',
+  images: [],
+  remark: '',
+  creatName: '',
+};
+
+export const ACCESS_FORM_RULES = {
+  eventDescription: [{ required: true, message: '请输入事件的具体描述', trigger: 'blur' }],
+  eventLocation: [{ required: true, message: '请输入事件发生地点', trigger: 'blur' }],
+  eventTime: [{ required: true, message: '请选择事件发生时间', trigger: 'change' }],
+};

src/views/security-confidentiality/access-control/constant.ts → src/views/security-confidentiality/access-control/constants.ts


+ 9 - 7
src/views/security-confidentiality/access-control/types.ts

@@ -12,29 +12,31 @@ export interface AccessTableQuery {
   endTime?: string | null;
 }
 
-export interface AccessTableData {
+export interface AccessDetailResponse {
   id: number;
   eventDescription: string;
   eventLocation: string;
-  images: string;
   eventTime: string;
-  remark: string;
+  images?: string;
+  remark?: string;
+  creatName?: string;
 }
 
 export interface CreateAccessRuleForm {
   eventDescription: string;
   eventLocation: string;
-  images: string;
   eventTime: string;
-  remark: string;
+  images?: ImageItem[];
+  remark?: string;
+  creatName?: string;
 }
 
 export interface CreateAccessQuery {
   eventDescription: string;
   eventLocation: string;
-  images: string;
   eventTime: string;
-  remark: string;
+  images?: string;
+  remark?: string;
 }
 
 export interface UpdateAccessQuery extends CreateAccessQuery {

+ 32 - 0
src/views/security-confidentiality/access-control/utils.ts

@@ -0,0 +1,32 @@
+import { uploadFileApi, UPLOAD_BIZ_TYPE } from '@/api/minio';
+import type { ImageItem } from './types';
+
+export function stringToArray(str?: string): number[] | undefined {
+  if (!str) return undefined;
+  return JSON.parse('[' + str + ']');
+}
+
+export function unformatImage(file?: string) {
+  if (!file) return undefined;
+  const fileData: string[] = JSON.parse(file);
+  return fileData;
+}
+
+const formatImage = async (data: ImageItem) => {
+  if (!data.file) return data;
+  const name = data.file.name;
+  const res = await uploadFileApi({ bizType: UPLOAD_BIZ_TYPE.ATTACHMENT, fileName: name, file: data.file });
+  return res.url;
+};
+
+export const formatImageList = async (data: Array<ImageItem> | undefined) => {
+  if (!data || data.length === 0) return null;
+  const res = await Promise.all(
+    data.map(async (item) => {
+      if (!item.file) return item.url;
+      const res = await formatImage(item);
+      return res;
+    }),
+  );
+  return res;
+};

+ 0 - 0
src/views/security-confidentiality/vehicle-management/configs/form.ts


src/views/security-confidentiality/vehicle-management/constant.ts → src/views/security-confidentiality/vehicle-management/constants.ts