Jelajahi Sumber

应急演练

yanghao 4 hari lalu
induk
melakukan
9d4834858b

+ 278 - 0
src/views/pms/qhse/emergencyDrill/CertDrawer.vue

@@ -0,0 +1,278 @@
+<template>
+  <el-drawer v-model="drawerVisible" title="证书检测列表" size="60%" :before-close="handleClose">
+    <div class="cert-drawer-content">
+      <el-form
+        :model="queryParams"
+        ref="queryFormRef"
+        class="bg-white dark:bg-[#1d1e1f] rounded-lg shadow px-4 pt-4 mb-4">
+        <div class="flex items-center gap-4 flex-wrap">
+          <el-form-item label="应急物资名称" prop="emergencyName">
+            <el-input
+              v-model="queryParams.emergencyName"
+              placeholder="请输入应急物资名称"
+              clearable
+              class="!w-150px" />
+          </el-form-item>
+          <el-form-item label="证书编号" prop="certNo">
+            <el-input
+              v-model="queryParams.certNo"
+              placeholder="请输入证书编号"
+              clearable
+              class="!w-200px" />
+          </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-form-item>
+        </div>
+      </el-form>
+
+      <div class="cert-table-wrapper">
+        <el-auto-resizer>
+          <template #default="{ width, height }">
+            <zm-table
+              :loading="loading"
+              :data="list"
+              :width="width"
+              :height="height"
+              :max-height="height">
+              <zm-table-column :label="t('monitor.serial')" width="70" align="center" fixed="left">
+                <template #default="scope">
+                  {{ scope.$index + 1 }}
+                </template>
+              </zm-table-column>
+              <zm-table-column
+                label="物资名称"
+                align="center"
+                prop="emergencyName"
+                fixed="left"
+                min-width="140" />
+              <zm-table-column label="证书编号" align="center" prop="certNo" min-width="140" />
+              <zm-table-column label="检测日期" align="center" prop="certTime" width="140">
+                <template #default="scope">
+                  <span class="iot-md-date">{{ formatDateCorrectly(scope.row.certTime) }}</span>
+                </template>
+              </zm-table-column>
+              <zm-table-column label="检测单位" align="center" prop="certOrg" min-width="160" />
+              <zm-table-column label="有效期" align="center" prop="certExpire" width="140">
+                <template #default="scope">
+                  <span class="iot-md-date">{{ formatDateCorrectly(scope.row.certExpire) }}</span>
+                </template>
+              </zm-table-column>
+              <zm-table-column
+                label="备注"
+                align="center"
+                prop="remark"
+                min-width="160"
+                show-overflow-tooltip />
+              <zm-table-column label="附件" fixed="right" align="center" prop="file" width="90">
+                <template #default="scope">
+                  <el-button
+                    v-if="scope.row.file"
+                    link
+                    type="primary"
+                    @click="viewFile(scope.row.file)">
+                    查看
+                  </el-button>
+                </template>
+              </zm-table-column>
+            </zm-table>
+          </template>
+        </el-auto-resizer>
+      </div>
+
+      <div class="h-8 mt-2 flex items-center justify-end">
+        <Pagination
+          :total="total"
+          v-model:page="queryParams.pageNo"
+          v-model:limit="queryParams.pageSize"
+          @pagination="getList" />
+      </div>
+    </div>
+  </el-drawer>
+
+  <Dialog v-model="dialogFileView" title="附件" width="500">
+    <div
+      v-for="(file, index) in fileList"
+      :key="index"
+      class="flex items-center justify-between mt-5">
+      <span class="file-name-text">{{ extractFileName(file) }}</span>
+      <div>
+        <el-button link type="primary" @click="viewFileInfo(file)">
+          <Icon icon="ep:view" class="mr-2px" />查看
+        </el-button>
+        <el-button link type="primary" @click="handleDownload(file)">
+          <Icon icon="ep:download" class="mr-2px" />下载
+        </el-button>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer mt-10">
+        <el-button type="primary" @click="dialogFileView = false">确认</el-button>
+      </div>
+    </template>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import { EmergencyDrillCertApi } from '@/api/pms/qhse/index'
+import { useTableComponents } from '@/components/ZmTable/useTableComponents'
+import { formatDate } from '@/utils/formatTime'
+
+const { ZmTable, ZmTableColumn } = useTableComponents()
+
+defineOptions({ name: 'CertDrawer' })
+
+const message = useMessage()
+const { t } = useI18n()
+
+const drawerVisible = ref(false)
+const loading = ref(false)
+const list = ref<any[]>([])
+const total = ref(0)
+const currentEmergencyId = ref<number | undefined>(undefined)
+
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  emergencyId: undefined,
+  certNo: undefined,
+  emergencyName: undefined
+})
+
+const queryFormRef = ref()
+
+const getList = async () => {
+  if (!currentEmergencyId.value) return
+
+  loading.value = true
+  try {
+    const data = await EmergencyDrillCertApi.getEmergencyDrillCertList(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    loading.value = false
+  }
+}
+
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  handleQuery()
+}
+
+const open = (emergencyId: number) => {
+  currentEmergencyId.value = emergencyId
+  queryParams.emergencyId = emergencyId
+  queryParams.pageNo = 1
+  queryParams.certNo = undefined
+  drawerVisible.value = true
+  getList()
+}
+
+const handleClose = () => {
+  drawerVisible.value = false
+  currentEmergencyId.value = undefined
+  list.value = []
+  total.value = 0
+}
+
+const formatDateCorrectly = (timestamp) => {
+  if (!timestamp) return ''
+
+  let time = Number(timestamp)
+
+  if (time < 10000000000) {
+    time = time * 1000
+  }
+
+  const date = new Date(time)
+  if (isNaN(date.getTime())) {
+    return ''
+  }
+
+  const minValidYear = 1900
+  if (date.getFullYear() < minValidYear) {
+    console.warn('Invalid date detected:', timestamp)
+    return ''
+  }
+
+  return formatDate(time).substring(0, 10)
+}
+
+const dialogFileView = ref(false)
+const fileList = ref<string[]>([])
+
+const viewFile = (file: string) => {
+  fileList.value = file.split(',')
+  dialogFileView.value = true
+}
+
+const viewFileInfo = (file: string) => {
+  window.open(
+    'http://doc.deepoil.cc:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(file))
+  )
+}
+
+const extractFileName = (url: string): string => {
+  try {
+    const cleanUrl = url.split('?')[0].split('#')[0]
+    const parts = cleanUrl.split('/')
+    const fileName = parts[parts.length - 1]
+    return decodeURIComponent(fileName) || url
+  } catch {
+    return url
+  }
+}
+
+const handleDownload = async (url: string) => {
+  try {
+    const response = await fetch(url)
+    const blob = await response.blob()
+    const downloadUrl = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = downloadUrl
+    link.download = url.split('/').pop() || 'file'
+    link.click()
+    URL.revokeObjectURL(downloadUrl)
+  } catch (error) {
+    console.error('下载失败:', error)
+  }
+}
+
+defineExpose({
+  open
+})
+</script>
+
+<style scoped>
+.cert-drawer-content {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+
+.cert-table-wrapper {
+  flex: 1;
+  min-height: 0;
+  position: relative;
+}
+
+.file-name-text {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  margin-right: 12px;
+  color: var(--el-text-color-primary);
+}
+</style>

+ 63 - 0
src/views/pms/qhse/emergencyDrill/bookCheck/EmergencyBookForm.vue

@@ -192,6 +192,29 @@
       <el-button @click="closeDeviceDialog">取 消</el-button>
     </template>
   </Dialog>
+
+  <Dialog v-model="dialogFileView" title="附件" width="500">
+    <div
+      v-for="(file, index) in fileList"
+      :key="index"
+      class="flex items-center justify-between mt-5">
+      <span class="file-name-text">{{ extractFileName(file) }}</span>
+      <div>
+        <el-button link type="primary" @click="viewFileInfo(file)">
+          <Icon icon="ep:view" class="mr-2px" />查看
+        </el-button>
+        <el-button link type="primary" @click="handleDownload(file)">
+          <Icon icon="ep:download" class="mr-2px" />下载
+        </el-button>
+      </div>
+    </div>
+
+    <template #footer>
+      <div class="dialog-footer mt-10">
+        <el-button type="primary" @click="dialogFileView = false">确认</el-button>
+      </div>
+    </template>
+  </Dialog>
 </template>
 
 <script setup lang="ts">
@@ -370,6 +393,46 @@ const confirmSelectDevice = () => {
   formData.value.deptId = selectedDevice.value.deptId
   closeDeviceDialog()
 }
+
+const dialogFileView = ref(false)
+const fileList = ref<string[]>([])
+const viewFile = (file: string) => {
+  fileList.value = file.split(',')
+  dialogFileView.value = true
+}
+
+const viewFileInfo = (file: string) => {
+  window.open(
+    'http://doc.deepoil.cc:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(file))
+  )
+}
+
+const extractFileName = (url: string): string => {
+  try {
+    const cleanUrl = url.split('?')[0].split('#')[0]
+    const parts = cleanUrl.split('/')
+    const fileName = parts[parts.length - 1]
+    return decodeURIComponent(fileName) || url
+  } catch {
+    return url
+  }
+}
+
+const handleDownload = async (url: string) => {
+  try {
+    const response = await fetch(url)
+    const blob = await response.blob()
+    const downloadUrl = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = downloadUrl
+    link.download = url.split('/').pop() || 'file'
+    link.click()
+    URL.revokeObjectURL(downloadUrl)
+  } catch (error) {
+    console.error('下载失败:', error)
+  }
+}
+
 let deptType = ref('')
 onMounted(async () => {
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())

+ 15 - 1
src/views/pms/qhse/emergencyDrill/index.vue

@@ -110,8 +110,15 @@
                   </el-button>
                 </template>
               </zm-table-column>
-              <zm-table-column label="操作" align="center" width="140" fixed="right" action>
+              <zm-table-column label="操作" align="center" width="160" fixed="right" action>
                 <template #default="scope">
+                  <el-button
+                    v-hasPermi="['rq:qhse-emergency-book:update']"
+                    link
+                    type="primary"
+                    @click="openCertDrawer(scope.row.id)">
+                    查看证书
+                  </el-button>
                   <el-button
                     v-hasPermi="['rq:qhse-emergency-book:update']"
                     link
@@ -166,12 +173,14 @@
   </Dialog>
 
   <EmergencyDrillForm ref="formRef" @success="getList" />
+  <CertDrawer ref="certDrawerRef" />
 </template>
 
 <script setup lang="ts">
 import download from '@/utils/download'
 import { EmergencyDrillApi } from '@/api/pms/qhse/index'
 import EmergencyDrillForm from './EmergencyDrillForm.vue'
+import CertDrawer from './CertDrawer.vue'
 import DeptTreeSelect from '@/components/DeptTreeSelect/index.vue'
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 import { useUserStore } from '@/store/modules/user'
@@ -291,6 +300,11 @@ const handleDownload = async (url: string) => {
   }
 }
 
+const certDrawerRef = ref()
+
+const openCertDrawer = (emergencyId: number) => {
+  certDrawerRef.value?.open(emergencyId)
+}
 onMounted(async () => {
   getList()
 })