Quellcode durchsuchen

Merge branch 'qhse_cert' of shuzhihua/pms-iot-vue into master

yanghao vor 4 Tagen
Ursprung
Commit
515615ad94

+ 3 - 0
src/api/infra/file/index.ts

@@ -41,3 +41,6 @@ export const updateFile = (data: any) => {
 export const updateFilePath = (data: any) => {
   return request.upload({ url: '/infra/file/upload/path', data })
 }
+export const updateFileQhsePath = (data: any) => {
+  return request.upload({ url: '/infra/file/upload/qhse/path', data })
+}

+ 28 - 0
src/api/pms/qhse/index.ts

@@ -343,3 +343,31 @@ export const IotSocSummaryApi = {
     return await request.get({ url: `/rq/iot-soc-summary/get?id=` + id })
   }
 }
+
+// JSA 分析
+export const QHSEJsaApi = {
+  // 获得JSA分析分页
+  getJsaList: async (params) => {
+    return await request.get({ url: `/rq/qhse-jsa/page`, params })
+  },
+  // 删除JSA分析
+  deleteJsa: async (id) => {
+    return await request.delete({ url: `/rq/qhse-jsa/delete?id=` + id })
+  },
+  // 添加JSA分析
+  createJsa: async (data) => {
+    return await request.post({ url: `/rq/qhse-jsa/create`, data })
+  },
+  // 修改JSA分析
+  updateJsa: async (data) => {
+    return await request.put({ url: `/rq/qhse-jsa/update`, data })
+  },
+  // 导出JSA分析 Excel
+  exportJsa: async (params) => {
+    return await request.download({ url: `/rq/qhse-jsa/export-excel`, params })
+  },
+  // 获取详情
+  getJsa: async (id) => {
+    return await request.get({ url: `/rq/qhse-jsa/get?id=` + id })
+  }
+}

+ 323 - 0
src/components/UploadFile/src/QHSEUploadImgs.vue

@@ -0,0 +1,323 @@
+<template>
+  <div class="upload-box">
+    <el-upload
+      v-model:file-list="fileList"
+      :accept="fileType.join(',')"
+      :action="uploadUrl"
+      :before-upload="beforeUpload"
+      :class="['upload', drag ? 'no-border' : '']"
+      :disabled="disabled"
+      :drag="drag"
+      :http-request="httpRequestQhseOnlyPath"
+      :limit="limit"
+      :multiple="true"
+      :on-error="uploadError"
+      :on-exceed="handleExceed"
+      :on-success="uploadSuccess"
+      list-type="picture-card"
+    >
+      <div class="upload-empty">
+        <slot name="empty">
+          <Icon icon="ep:plus" />
+          <!-- <span>请上传图片</span> -->
+        </slot>
+      </div>
+      <template #file="{ file }">
+        <img :src="file.url" class="upload-image" />
+        <div class="upload-handle" @click.stop>
+          <div class="handle-icon" @click="imagePreview(file.url!)">
+            <Icon icon="ep:zoom-in" />
+            <span>查看</span>
+          </div>
+          <div v-if="!disabled" class="handle-icon" @click="handleRemove(file)">
+            <Icon icon="ep:delete" />
+            <span>删除</span>
+          </div>
+        </div>
+      </template>
+    </el-upload>
+    <div class="el-upload__tip">
+      <slot name="tip"></slot>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'
+import { ElNotification } from 'element-plus'
+import { createImageViewer } from '@/components/ImageViewer'
+import { ref } from 'vue'
+
+import { propTypes } from '@/utils/propTypes'
+import { useUpload } from '@/components/UploadFile/src/useUpload'
+
+defineOptions({ name: 'UploadImgs' })
+
+const message = useMessage() // 消息弹窗
+// 查看图片
+const imagePreview = (imgUrl: string) => {
+  createImageViewer({
+    zIndex: 9999999,
+    urlList: [imgUrl]
+  })
+}
+
+type FileTypes =
+  | 'image/apng'
+  | 'image/bmp'
+  | 'image/gif'
+  | 'image/jpeg'
+  | 'image/pjpeg'
+  | 'image/png'
+  | 'image/svg+xml'
+  | 'image/tiff'
+  | 'image/webp'
+  | 'image/x-icon'
+
+const props = defineProps({
+  modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,
+  drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true)
+  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传(默认为 false)
+  limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传(默认为 5张)
+  fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M)
+  fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])
+  height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px)
+  width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px)
+  borderradius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px)
+})
+
+let uploadUrl = ref(
+  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload/qhse/path'
+)
+
+const { httpRequestQhseOnlyPath } = useUpload()
+
+const fileList = ref<UploadUserFile[]>([])
+const uploadNumber = ref<number>(0)
+const uploadList = ref<UploadUserFile[]>([])
+/**
+ * @description 文件上传之前判断
+ * @param rawFile 上传的文件
+ * */
+const beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
+  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize
+  const imgType = props.fileType
+  if (!imgType.includes(rawFile.type as FileTypes))
+    ElNotification({
+      title: '温馨提示',
+      message: '上传图片不符合所需的格式!',
+      type: 'warning'
+    })
+  if (!imgSize)
+    ElNotification({
+      title: '温馨提示',
+      message: `上传图片大小不能超过 ${props.fileSize}M!`,
+      type: 'warning'
+    })
+  uploadNumber.value++
+  return imgType.includes(rawFile.type as FileTypes) && imgSize
+}
+
+// 图片上传成功
+interface UploadEmits {
+  (e: 'update:modelValue', value: string[]): void
+}
+
+const emit = defineEmits<UploadEmits>()
+const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
+  message.success('上传成功')
+  // 删除自身
+  const index = fileList.value.findIndex((item) => item.response?.data === res.data)
+  fileList.value.splice(index, 1)
+  uploadList.value.push({ name: res.data, url: res.data })
+  if (uploadList.value.length == uploadNumber.value) {
+    fileList.value.push(...uploadList.value)
+    uploadList.value = []
+    uploadNumber.value = 0
+    emitUpdateModelValue()
+  }
+}
+
+// 监听模型绑定值变动
+watch(
+  () => props.modelValue,
+  (val: string | string[]) => {
+    if (!val) {
+      fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
+      return
+    }
+
+    fileList.value = [] // 保障数据为空
+    fileList.value.push(
+      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
+    )
+  },
+  { immediate: true, deep: true }
+)
+// 发送图片链接列表更新
+const emitUpdateModelValue = () => {
+  let result: string[] = fileList.value.map((file) => file.url!)
+  emit('update:modelValue', result)
+}
+// 删除图片
+const handleRemove = (uploadFile: UploadFile) => {
+  fileList.value = fileList.value.filter(
+    (item) => item.url !== uploadFile.url || item.name !== uploadFile.name
+  )
+  emit(
+    'update:modelValue',
+    fileList.value.map((file) => file.url!)
+  )
+}
+
+// 图片上传错误提示
+const uploadError = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: '图片上传失败,请您重新上传!',
+    type: 'error'
+  })
+}
+
+// 文件数超出提示
+const handleExceed = () => {
+  ElNotification({
+    title: '温馨提示',
+    message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,
+    type: 'warning'
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.is-error {
+  .upload {
+    :deep(.el-upload--picture-card),
+    :deep(.el-upload-dragger) {
+      border: 1px dashed var(--el-color-danger) !important;
+
+      &:hover {
+        border-color: var(--el-color-primary) !important;
+      }
+    }
+  }
+}
+
+:deep(.disabled) {
+  .el-upload--picture-card,
+  .el-upload-dragger {
+    cursor: not-allowed;
+    background: var(--el-disabled-bg-color) !important;
+    border: 1px dashed var(--el-border-color-darker);
+
+    &:hover {
+      border-color: var(--el-border-color-darker) !important;
+    }
+  }
+}
+
+.upload-box {
+  .no-border {
+    :deep(.el-upload--picture-card) {
+      border: none !important;
+    }
+  }
+
+  :deep(.upload) {
+    .el-upload-dragger {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      width: 100%;
+      height: 100%;
+      padding: 0;
+      overflow: hidden;
+      border: 1px dashed var(--el-border-color-darker);
+      border-radius: v-bind(borderradius);
+
+      &:hover {
+        border: 1px dashed var(--el-color-primary);
+      }
+    }
+
+    .el-upload-dragger.is-dragover {
+      background-color: var(--el-color-primary-light-9);
+      border: 2px dashed var(--el-color-primary) !important;
+    }
+
+    .el-upload-list__item,
+    .el-upload--picture-card {
+      width: v-bind(width);
+      height: v-bind(height);
+      background-color: transparent;
+      border-radius: v-bind(borderradius);
+    }
+
+    .upload-image {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
+
+    .upload-handle {
+      position: absolute;
+      top: 0;
+      right: 0;
+      display: flex;
+      width: 100%;
+      height: 100%;
+      cursor: pointer;
+      background: rgb(0 0 0 / 60%);
+      opacity: 0;
+      box-sizing: border-box;
+      transition: var(--el-transition-duration-fast);
+      align-items: center;
+      justify-content: center;
+
+      .handle-icon {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: 0 6%;
+        color: aliceblue;
+
+        .el-icon {
+          margin-bottom: 15%;
+          font-size: 140%;
+        }
+
+        span {
+          font-size: 100%;
+        }
+      }
+    }
+
+    .el-upload-list__item {
+      &:hover {
+        .upload-handle {
+          opacity: 1;
+        }
+      }
+    }
+
+    .upload-empty {
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      font-size: 12px;
+      line-height: 30px;
+      color: var(--el-color-info);
+
+      .el-icon {
+        font-size: 28px;
+        color: var(--el-text-color-secondary);
+      }
+    }
+  }
+
+  .el-upload__tip {
+    line-height: 15px;
+    text-align: center;
+  }
+}
+</style>

+ 41 - 1
src/components/UploadFile/src/useUpload.ts

@@ -12,10 +12,15 @@ export const getUploadUrl = (): string => {
 export const getUploadUrlOnlyPath = (): string => {
   return import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload/path'
 }
+
+export const getUploadUrlQhsePath = (): string => {
+  return import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/infra/file/upload/qhse/path'
+}
 export const useUpload = () => {
   // 后端上传地址
   const uploadUrl = getUploadUrl()
   const uploadUrlPath = getUploadUrlOnlyPath()
+  const uploadUrlQhsePath = getUploadUrlQhsePath()
   // 是否使用前端直连上传
   const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE
   // 重写ElUpload上传方法
@@ -98,11 +103,46 @@ export const useUpload = () => {
     }
   }
 
+  const httpRequestQhseOnlyPath = async (options: UploadRequestOptions) => {
+    // 妯″紡涓€锛氬墠绔笂浼?
+    if (isClientUpload) {
+      const fileName = await generateFileName(options.file)
+      const presignedInfoPath = await FileApi.getFilePresignedUrl(fileName)
+      return axios
+        .put(presignedInfoPath.uploadUrlPath, options.file, {
+          headers: {
+            'Content-Type': options.file.type
+          }
+        })
+        .then(() => {
+          createFile(presignedInfoPath, fileName, options.file)
+          return { data: presignedInfoPath.url }
+        })
+    } else {
+      // 妯″紡浜岋細鍚庣涓婁紶
+      return new Promise((resolve, reject) => {
+        FileApi.updateFileQhsePath({ file: options.file })
+          .then((res) => {
+            if (res.code === 0) {
+              resolve(res)
+            } else {
+              reject(res)
+            }
+          })
+          .catch((res) => {
+            reject(res)
+          })
+      })
+    }
+  }
+
   return {
     uploadUrl,
     uploadUrlPath,
+    uploadUrlQhsePath,
     httpRequest,
-    httpRequestOnlyPath
+    httpRequestOnlyPath,
+    httpRequestQhseOnlyPath
   }
 }
 

+ 1 - 1
src/views/pms/qhse/certificate.vue

@@ -328,7 +328,7 @@ import { ElMessageBox, ElMessage } from 'element-plus'
 const deptList = ref<Tree[]>([]) // 树形结构
 const deptList2 = ref<Tree[]>([]) // 树形结构
 import { formatDate } from '@/utils/formatTime'
-import UploadImage from '@/components/UploadFile/src/UploadImgs.vue'
+import UploadImage from '@/components/UploadFile/src/QHSEUploadImgs.vue'
 import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
 import { defaultProps } from '@/utils/tree'
 import { selectedDeptsEmployee } from '@/api/system/user'

+ 168 - 0
src/views/pms/qhse/jsa/IotSocSummaryForm.vue

@@ -0,0 +1,168 @@
+<template>
+  <Dialog :title="dialogTitle" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="100px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="部门" prop="deptId">
+        <el-tree-select
+          clearable
+          v-model="formData.deptId"
+          :data="deptList2"
+          :props="defaultProps"
+          :check-strictly="true"
+          node-key="id"
+          filterable
+          placeholder="请选择所在部门"
+        />
+      </el-form-item>
+
+      <el-form-item label="日期" prop="jsaTime">
+        <el-date-picker
+          v-model="formData.jsaTime"
+          type="date"
+          value-format="x"
+          placeholder="选择日期"
+          style="width: 100%"
+        />
+      </el-form-item>
+
+      <el-form-item label="序号" prop="jobXh">
+        <el-input v-model="formData.jobXh" placeholder="请输入序号" />
+      </el-form-item>
+
+      <el-form-item label="工作任务" prop="jobTask">
+        <el-input v-model="formData.jobTask" placeholder="请输入工作任务" />
+      </el-form-item>
+
+      <el-form-item label="工作地点" prop="jobAddress">
+        <el-input v-model="formData.jobAddress" placeholder="请输入工作地点" />
+      </el-form-item>
+
+      <el-form-item label="工作负责人" prop="jobDuty">
+        <el-input v-model="formData.jobDuty" placeholder="请输入工作负责人" />
+      </el-form-item>
+
+      <el-form-item label="附件" prop="jsaFile">
+        <UploadFile
+          v-model="formData.jsaFile"
+          :file-type="['doc', 'docx', 'pdf', 'jpg', 'png', 'jpeg']"
+          :limit="3"
+          :file-size="100"
+          class="min-w-80px"
+        />
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </Dialog>
+</template>
+<script setup lang="ts">
+import { QHSEJsaApi } from '@/api/pms/qhse/index'
+import { defaultProps } from '@/utils/tree'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
+/** SOC卡汇总 表单 */
+defineOptions({ name: 'IotSocSummaryForm' })
+
+const { t } = useI18n() // 国际化
+const message = useMessage() // 消息弹窗
+const deptList2 = ref<Tree[]>([]) // 树形结构
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('') // 弹窗的标题
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formType = ref('') // 表单的类型:create - 新增;update - 修改
+const formData = ref({
+  jsaTime: undefined,
+  jobXh: undefined,
+  jobTask: undefined,
+  jobAddress: undefined,
+  jobDuty: undefined,
+  jsaFile: [] as any[],
+  remark: undefined,
+  deptId: undefined
+})
+const formRules = reactive({
+  jsaTime: [{ required: true, message: '请选择日期', trigger: 'blur' }],
+  jobXh: [{ required: true, message: '请输入序号', trigger: 'blur' }],
+  jobTask: [{ required: true, message: '请输入工作任务', trigger: 'blur' }],
+
+  jsaFile: [{ required: true, message: '请上传附件', trigger: 'blur' }],
+
+  deptId: [{ required: true, message: '请选择部门', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+
+/** 打开弹窗 */
+const open = async (type: string, id?: number) => {
+  dialogVisible.value = true
+  dialogTitle.value = t('action.' + type)
+  formType.value = type
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      formData.value = await QHSEJsaApi.getJsa(id)
+    } finally {
+      formLoading.value = false
+    }
+  }
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = { ...formData.value }
+    data.jsaFile = data.jsaFile?.map((item) => item.url).join(',') as any
+    if (formType.value === 'create') {
+      await QHSEJsaApi.createJsa(data)
+      message.success(t('common.createSuccess'))
+    } else {
+      await QHSEJsaApi.updateJsa(data)
+      message.success(t('common.updateSuccess'))
+    }
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    project: undefined,
+    observationDate: undefined,
+    socClass: undefined,
+    className: undefined,
+    userName: undefined,
+    post: undefined,
+    deptId: undefined,
+    remark: undefined,
+    deptName: undefined
+  }
+  formRef.value?.resetFields()
+}
+
+onMounted(async () => {
+  deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+})
+</script>

+ 234 - 0
src/views/pms/qhse/jsa/index.vue

@@ -0,0 +1,234 @@
+<template>
+  <el-row :gutter="20">
+    <!-- 左侧部门树 -->
+    <DeptTree @node-click="handleDeptNodeClick" v-model:collapsed="isLeftContentCollapsed" />
+
+    <el-col :span="isLeftContentCollapsed ? 24 : 20" :xs="24">
+      <ContentWrap>
+        <!-- 搜索工作栏 -->
+        <el-form
+          class="-mb-15px"
+          :model="queryParams"
+          ref="queryFormRef"
+          :inline="true"
+          border
+          label-width="68px"
+        >
+          <el-form-item label="姓名" prop="userName">
+            <el-input
+              v-model="queryParams.userName"
+              placeholder="请输入姓名"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+
+          <el-form-item label="队伍名称" prop="deptName">
+            <el-input
+              v-model="queryParams.deptName"
+              placeholder="请输入队伍名称"
+              clearable
+              @keyup.enter="handleQuery"
+              class="!w-240px"
+            />
+          </el-form-item>
+          <el-form-item>
+            <el-button @click="handleQuery"
+              ><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button
+            >
+            <el-button @click="resetQuery"
+              ><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button
+            >
+            <el-button
+              type="primary"
+              plain
+              @click="openForm('create')"
+              v-hasPermi="['rq:iot-soc-summary:create']"
+            >
+              <Icon icon="ep:plus" class="mr-5px" /> 新增
+            </el-button>
+            <el-button
+              type="success"
+              plain
+              @click="handleExport"
+              :loading="exportLoading"
+              v-hasPermi="['rq:iot-soc-summary:export']"
+            >
+              <Icon icon="ep:download" class="mr-5px" /> 导出
+            </el-button>
+          </el-form-item>
+        </el-form>
+      </ContentWrap>
+
+      <!-- 列表 -->
+      <ContentWrap>
+        <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+          <el-table-column :label="t('monitor.serial')" width="70" align="center">
+            <template #default="scope">
+              {{ scope.$index + 1 }}
+            </template>
+          </el-table-column>
+          <el-table-column label="项目部" align="center" prop="projectName" width="120" />
+          <!-- <el-table-column label="部门名称" align="center" prop="deptName" /> -->
+          <el-table-column label="队伍名称" align="center" prop="deptName" />
+          <el-table-column
+            label="观察日期"
+            align="center"
+            prop="observationDate"
+            :formatter="dateFormatter"
+            width="180px"
+          />
+
+          <el-table-column label="类型名称" align="center" prop="className" />
+          <el-table-column label="姓名" align="center" prop="userName" />
+          <el-table-column label="岗位" align="center" prop="post" />
+
+          <el-table-column label="备注" align="center" prop="remark" />
+          <el-table-column
+            label="创建时间"
+            align="center"
+            prop="createTime"
+            :formatter="dateFormatter"
+            width="180px"
+          />
+
+          <el-table-column label="操作" align="center" min-width="120px">
+            <template #default="scope">
+              <el-button
+                link
+                type="primary"
+                @click="openForm('update', scope.row.id)"
+                v-hasPermi="['rq:iot-soc-summary:update']"
+              >
+                编辑
+              </el-button>
+              <el-button
+                link
+                type="danger"
+                @click="handleDelete(scope.row.id)"
+                v-hasPermi="['rq:iot-soc-summary:delete']"
+              >
+                删除
+              </el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <!-- 分页 -->
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList"
+        />
+      </ContentWrap>
+    </el-col>
+  </el-row>
+
+  <!-- 表单弹窗:添加/修改 -->
+  <IotSocSummaryForm ref="formRef" @success="getList" />
+</template>
+
+<script setup lang="ts">
+import { dateFormatter } from '@/utils/formatTime'
+import download from '@/utils/download'
+import { QHSEJsaApi } from '@/api/pms/qhse/index'
+import IotSocSummaryForm from './IotSocSummaryForm.vue'
+import DeptTree from '@/views/system/user/DeptTree2.vue'
+
+/** SOC卡汇总 列表 */
+defineOptions({ name: 'IotSocSummary' })
+
+const message = useMessage() // 消息弹窗
+const { t } = useI18n() // 国际化
+
+const loading = ref(true) // 列表的加载中
+const list = ref([]) // 列表的数据
+const total = ref(0) // 列表的总页数
+const isLeftContentCollapsed = ref(false)
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  project: undefined,
+  observationDate: [],
+  socClass: undefined,
+  className: undefined,
+  userName: undefined,
+  post: undefined,
+  deptId: undefined,
+  remark: undefined,
+  createTime: [],
+  deptName: undefined
+})
+const queryFormRef = ref() // 搜索的表单
+const exportLoading = ref(false) // 导出的加载中
+
+/** 查询列表 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await QHSEJsaApi.getJsaList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleDeptNodeClick = async (row) => {
+  queryParams.deptId = row.id
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+/** 添加/修改操作 */
+const formRef = ref()
+const openForm = (type: string, id?: number) => {
+  formRef.value.open(type, id)
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (id: number) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm()
+    // 发起删除
+    await QHSEJsaApi.deleteJsa(id)
+    message.success(t('common.delSuccess'))
+    // 刷新列表
+    await getList()
+  } catch {}
+}
+
+/** 导出按钮操作 */
+const handleExport = async () => {
+  try {
+    // 导出的二次确认
+    await message.exportConfirm()
+    // 发起导出
+    exportLoading.value = true
+    const data = await QHSEJsaApi.exportJsa(queryParams)
+    download.excel(data, 'SOC卡汇总.xls')
+  } catch {
+  } finally {
+    exportLoading.value = false
+  }
+}
+
+/** 初始化 **/
+onMounted(() => {
+  getList()
+})
+</script>