|
|
@@ -17,7 +17,13 @@ import {
|
|
|
Search,
|
|
|
Refresh
|
|
|
} from '@element-plus/icons-vue'
|
|
|
-import { kanbanApi, IotDangerApi, IotInstrumentApi, IotMeasureCertApi } from '@/api/pms/qhse/index'
|
|
|
+import {
|
|
|
+ kanbanApi,
|
|
|
+ IotDangerApi,
|
|
|
+ IotInstrumentApi,
|
|
|
+ IotMeasureCertApi,
|
|
|
+ IotCertificateApi
|
|
|
+} from '@/api/pms/qhse/index'
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
import {
|
|
|
ANIMATION,
|
|
|
@@ -871,6 +877,26 @@ function destroySocChart() {
|
|
|
socChart = null
|
|
|
}
|
|
|
|
|
|
+const tableRowStyle = ({ row }) => {
|
|
|
+ if (row.expired) {
|
|
|
+ return { backgroundColor: '#ffe6e6' }
|
|
|
+ }
|
|
|
+ if (row.alertWarn) {
|
|
|
+ return { backgroundColor: '#e19f1a' }
|
|
|
+ }
|
|
|
+ return {}
|
|
|
+}
|
|
|
+
|
|
|
+const tableRowClassName = ({ row }) => {
|
|
|
+ if (row.expired) {
|
|
|
+ return 'expired-row'
|
|
|
+ }
|
|
|
+ if (row.alertWarn) {
|
|
|
+ return 'alert-warn-row'
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+}
|
|
|
+
|
|
|
function formatPercent(numerator: number, denominator: number) {
|
|
|
if (!denominator || Number.isNaN(denominator)) {
|
|
|
return '0%'
|
|
|
@@ -915,9 +941,7 @@ const certTotal = ref(0)
|
|
|
const certQueryParams = reactive({
|
|
|
pageNo: 1,
|
|
|
pageSize: 10,
|
|
|
- type: undefined as string | undefined,
|
|
|
- classify: undefined as string | undefined,
|
|
|
- userName: '',
|
|
|
+
|
|
|
expired: undefined as string | undefined,
|
|
|
alertWarn: undefined as string | undefined
|
|
|
})
|
|
|
@@ -931,22 +955,59 @@ function formatDateCorrectly(timestamp: any) {
|
|
|
return formatDate(time).substring(0, 10)
|
|
|
}
|
|
|
|
|
|
-function getCertificateTypeText(type: string) {
|
|
|
- const map: Record<string, string> = {
|
|
|
- personal: '个人证书',
|
|
|
- organization: '组织证书',
|
|
|
- other: '其他'
|
|
|
+let dialogFileView = ref(false)
|
|
|
+let fileList = ref([])
|
|
|
+const viewFile = (file) => {
|
|
|
+ fileList.value = file.split(',')
|
|
|
+ dialogFileView.value = true
|
|
|
+ // window.open(file)
|
|
|
+}
|
|
|
+
|
|
|
+const viewFileInfo = (file) => {
|
|
|
+ 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]
|
|
|
+ // URL 解码
|
|
|
+ return decodeURIComponent(fileName) || url
|
|
|
+ } catch {
|
|
|
+ // 如果解析失败,返回原始 URL
|
|
|
+ return url
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleDownload = async (url) => {
|
|
|
+ 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() // 自动获取文件名:ml-citation{ref="3" data="citationList"}
|
|
|
+ link.click()
|
|
|
+
|
|
|
+ URL.revokeObjectURL(downloadUrl)
|
|
|
+ } catch (error) {
|
|
|
+ console.error('下载失败:', error)
|
|
|
}
|
|
|
- return map[type] || type
|
|
|
}
|
|
|
|
|
|
async function getCertificateBoardList() {
|
|
|
certLoading.value = true
|
|
|
try {
|
|
|
- const data = await IotMeasureCertApi.getIotMeasureCertPage({
|
|
|
- ...certQueryParams,
|
|
|
- deptId: userStore.user.deptId,
|
|
|
- type: 'organization'
|
|
|
+ const data = await IotCertificateApi.getCertificateList({
|
|
|
+ ...certQueryParams
|
|
|
+ // deptId: userStore.user.deptId
|
|
|
+ // type: 'organization'
|
|
|
})
|
|
|
certTableData.value = data.list || []
|
|
|
certTotal.value = data.total || 0
|
|
|
@@ -963,9 +1024,7 @@ function handleCertificateQuery() {
|
|
|
function resetCertificateQuery() {
|
|
|
certQueryParams.pageNo = 1
|
|
|
certQueryParams.pageSize = 10
|
|
|
- certQueryParams.type = undefined
|
|
|
- certQueryParams.classify = undefined
|
|
|
- certQueryParams.userName = ''
|
|
|
+
|
|
|
certQueryParams.expired = undefined
|
|
|
certQueryParams.alertWarn = undefined
|
|
|
getCertificateBoardList()
|
|
|
@@ -1252,31 +1311,6 @@ onUnmounted(() => {
|
|
|
<div v-else class="certificate-board">
|
|
|
<section class="panel certificate-filter-panel">
|
|
|
<div class="certificate-filters">
|
|
|
- <div class="certificate-filter-item">
|
|
|
- <span class="certificate-filter-item__label">证书类别</span>
|
|
|
- <el-select
|
|
|
- v-model="certQueryParams.classify"
|
|
|
- clearable
|
|
|
- placeholder="证书类别"
|
|
|
- class="certificate-filter-item__control">
|
|
|
- <el-option
|
|
|
- v-for="dict in getStrDictOptions(DICT_TYPE.PERSON_CERT).concat(
|
|
|
- getStrDictOptions(DICT_TYPE.ORG_CERT)
|
|
|
- )"
|
|
|
- :key="dict.value"
|
|
|
- :label="dict.label"
|
|
|
- :value="dict.value" />
|
|
|
- </el-select>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="certificate-filter-item">
|
|
|
- <span class="certificate-filter-item__label">所属人</span>
|
|
|
- <el-input
|
|
|
- v-model="certQueryParams.userName"
|
|
|
- placeholder="输入所属人"
|
|
|
- class="certificate-filter-item__control" />
|
|
|
- </div>
|
|
|
-
|
|
|
<div class="certificate-filter-item">
|
|
|
<span class="certificate-filter-item__label">是否过期</span>
|
|
|
<el-select
|
|
|
@@ -1320,6 +1354,8 @@ onUnmounted(() => {
|
|
|
<el-table
|
|
|
:loading="certLoading"
|
|
|
:data="certTableData"
|
|
|
+ :row-style="tableRowStyle"
|
|
|
+ :row-class-name="tableRowClassName"
|
|
|
height="100vh"
|
|
|
:header-cell-style="{
|
|
|
height: '42px'
|
|
|
@@ -1331,41 +1367,40 @@ onUnmounted(() => {
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
- <el-table-column label="证书类型" align="center" min-width="120">
|
|
|
+ <el-table-column label="证书类别" align="center" width="150" prop="classify">
|
|
|
<template #default="scope">
|
|
|
- {{ getCertificateTypeText(scope.row.type) }}
|
|
|
+ <dict-tag :type="DICT_TYPE.ORG_CERT" :value="scope.row.classify" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="证书类别" align="center" min-width="140">
|
|
|
- <template #default="scope">
|
|
|
- <dict-tag
|
|
|
- v-if="scope.row.type === 'organization'"
|
|
|
- :type="DICT_TYPE.ORG_CERT"
|
|
|
- :value="scope.row.classify" />
|
|
|
- <dict-tag v-else :type="DICT_TYPE.PERSON_CERT" :value="scope.row.classify" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="证书名称" prop="certName" align="center" min-width="160" />
|
|
|
- <el-table-column label="所属人" prop="userName" align="center" min-width="100" />
|
|
|
- <el-table-column label="所在部门" prop="deptName" align="center" min-width="140" />
|
|
|
- <el-table-column label="颁发机构" prop="certOrg" align="center" min-width="220" />
|
|
|
<el-table-column
|
|
|
- label="证书标准"
|
|
|
- prop="certStandard"
|
|
|
+ label="证书名称"
|
|
|
align="center"
|
|
|
- min-width="140" />
|
|
|
- <el-table-column label="颁发时间" align="center" min-width="120">
|
|
|
+ prop="certName"
|
|
|
+ show-overflow-tooltip />
|
|
|
+
|
|
|
+ <el-table-column label="所在部门" align="center" prop="deptName" />
|
|
|
+
|
|
|
+ <el-table-column label="颁发机构" align="center" prop="certOrg" />
|
|
|
+
|
|
|
+ <el-table-column label="证书标准" align="center" prop="certStandard" />
|
|
|
+
|
|
|
+ <el-table-column label="颁发时间" align="center" prop="certIssue">
|
|
|
<template #default="scope">
|
|
|
{{ formatDateCorrectly(scope.row.certIssue) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column label="有效期" align="center" min-width="120">
|
|
|
+
|
|
|
+ <el-table-column label="有效期" align="center">
|
|
|
<template #default="scope">
|
|
|
{{ formatDateCorrectly(scope.row.certExpire) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="到期提醒" align="center">
|
|
|
+ <template #default="scope"> {{ scope.row.noticeBefore }}天前提醒 </template>
|
|
|
+ </el-table-column>
|
|
|
+
|
|
|
+ <el-table-column label="备注" align="center" prop="remark" />
|
|
|
</el-table>
|
|
|
|
|
|
<div class="certificate-pagination">
|
|
|
@@ -1375,11 +1410,49 @@ onUnmounted(() => {
|
|
|
v-model:limit="certQueryParams.pageSize"
|
|
|
@pagination="getCertificateBoardList" />
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class="shadow rounded-lg p-3 min-w-0 flex gap-4 justify-between">
|
|
|
+ <div
|
|
|
+ class="bg-[#fef0f0] text-[#f57979] px-3 flex-1 flex items-center justify-center text-center py-2 text-sm rounded-md"
|
|
|
+ ><el-icon class="stats-card__icon" :size="28">
|
|
|
+ <Icon icon="ep:info-filled" /> </el-icon
|
|
|
+ >证书已过期红色预警</div
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="bg-[#fdf6ec] flex-1 text-[#e6a23c] text-center flex items-center justify-center px-3 py-2 text-sm rounded-md"
|
|
|
+ ><el-icon class="stats-card__icon" :size="28">
|
|
|
+ <Icon icon="ep:bell-filled" /> </el-icon
|
|
|
+ >证书60天橙色预警</div
|
|
|
+ >
|
|
|
+ </div>
|
|
|
</section>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <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>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
@@ -1922,4 +1995,42 @@ onUnmounted(() => {
|
|
|
line-height: 14px !important;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/* 过期行的红色背景 - 基础状态 */
|
|
|
+:deep(.el-table__body tr.expired-row > td.el-table__cell) {
|
|
|
+ background-color: #ffe6e6 !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 过期行 - 鼠标悬浮状态 */
|
|
|
+:deep(.el-table__body tr.expired-row:hover > td.el-table__cell) {
|
|
|
+ background-color: #ffcccc !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保斑马纹不影响过期行 */
|
|
|
+:deep(.el-table__body tr.expired-row.el-table__row--striped > td.el-table__cell) {
|
|
|
+ background-color: #ffe6e6 !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-table__body tr.expired-row.el-table__row--striped:hover > td.el-table__cell) {
|
|
|
+ background-color: #ffcccc !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 预警行的橙色背景 - 基础状态 */
|
|
|
+:deep(.el-table__body tr.alert-warn-row > td.el-table__cell) {
|
|
|
+ background-color: #fff1df !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 预警行 - 鼠标悬浮状态 */
|
|
|
+:deep(.el-table__body tr.alert-warn-row:hover > td.el-table__cell) {
|
|
|
+ background-color: #ffe2bf !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保斑马纹不影响预警行 */
|
|
|
+:deep(.el-table__body tr.alert-warn-row.el-table__row--striped > td.el-table__cell) {
|
|
|
+ background-color: #fff1df !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-table__body tr.alert-warn-row.el-table__row--striped:hover > td.el-table__cell) {
|
|
|
+ background-color: #ffe2bf !important;
|
|
|
+}
|
|
|
</style>
|