|
|
@@ -0,0 +1,1125 @@
|
|
|
+<template>
|
|
|
+ <div class="p-2 audit-page">
|
|
|
+ <!-- 页面标题 -->
|
|
|
+ <div class="page-header">
|
|
|
+ <h2 class="page-title">审计工作管理</h2>
|
|
|
+ <p class="page-desc">管理部门人员调动与离职信息,审计工作量统计、审计重点操作</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 统计卡片 -->
|
|
|
+ <el-row :gutter="16" class="stats-row">
|
|
|
+ <el-col :xs="24" :sm="12" :md="6" v-for="card in statsCards" :key="card.key">
|
|
|
+ <div class="stat-card">
|
|
|
+ <div class="stat-label">{{ card.label }}</div>
|
|
|
+ <div class="stat-value-row">
|
|
|
+ <span class="stat-value">{{ card.value }}</span>
|
|
|
+ <div class="stat-icon" :style="{ background: card.color }">
|
|
|
+ <el-icon :size="20" color="#fff"><component :is="card.icon" /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-footer" :style="{ color: card.ratioColor }">
|
|
|
+ <template v-if="card.ratioType === 'up'">
|
|
|
+ <el-icon><CaretTop /></el-icon>
|
|
|
+ <span>{{ card.ratioText }} 较上月</span>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="card.ratioType === 'down'">
|
|
|
+ <el-icon><CaretBottom /></el-icon>
|
|
|
+ <span>{{ card.ratioText }} 较上月</span>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <span>{{ card.ratioText }}</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 筛选 + 列表 -->
|
|
|
+ <el-card shadow="hover" class="list-card">
|
|
|
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="90px" class="filter-form">
|
|
|
+ <el-form-item label="变动类型">
|
|
|
+ <el-select
|
|
|
+ v-model="queryParams.changeType"
|
|
|
+ placeholder="全部类型"
|
|
|
+ clearable
|
|
|
+ style="width: 200px"
|
|
|
+ @change="handleQuery"
|
|
|
+ >
|
|
|
+ <el-option label="全部类型" value="" />
|
|
|
+ <el-option
|
|
|
+ v-for="d in changeTypeOptions"
|
|
|
+ :key="d.value"
|
|
|
+ :label="d.label"
|
|
|
+ :value="d.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="部门">
|
|
|
+ <el-select
|
|
|
+ v-model="queryParams.beforeDept"
|
|
|
+ placeholder="全部部门"
|
|
|
+ clearable
|
|
|
+ style="width: 220px"
|
|
|
+ @change="handleQuery"
|
|
|
+ >
|
|
|
+ <el-option label="全部部门" value="" />
|
|
|
+ <el-option
|
|
|
+ v-for="d in deptOptions"
|
|
|
+ :key="d.deptId || d.id"
|
|
|
+ :label="d.deptName || d.name"
|
|
|
+ :value="d.deptName || d.name"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="关键词">
|
|
|
+ <el-input
|
|
|
+ v-model="queryParams.keyword"
|
|
|
+ placeholder="搜索姓名/岗位"
|
|
|
+ style="width: 220px"
|
|
|
+ clearable
|
|
|
+ @keyup.enter="handleQuery"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button @click="resetQuery">重置</el-button>
|
|
|
+ <el-button type="primary" @click="handleQuery">搜索</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <div class="list-toolbar">
|
|
|
+ <span class="list-total">共 {{ total }} 条记录</span>
|
|
|
+ <div>
|
|
|
+ <el-button :icon="Plus" @click="handleAdd">新增人员变动</el-button>
|
|
|
+ <el-button type="primary" :icon="UploadFilled" @click="handleUploadFile">上传审计文件</el-button>
|
|
|
+ <el-button :icon="Download" @click="handleExport">导出</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-table v-loading="loading" :data="changeList" border stripe>
|
|
|
+ <el-table-column prop="personnelName" label="姓名" align="center" width="100" />
|
|
|
+ <el-table-column prop="beforeDept" label="部门" align="center" :show-overflow-tooltip="true" width="140" />
|
|
|
+ <el-table-column prop="beforePosition" label="原岗位" align="center" :show-overflow-tooltip="true" width="160" />
|
|
|
+ <el-table-column label="变动类型" align="center" prop="changeType" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getChangeTypeTag(row.changeType)" effect="light">
|
|
|
+ {{ row.changeType }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="新岗位" align="center" prop="afterPosition" width="160">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ row.changeType === '离职' ? '-' : (row.afterPosition || '-') }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="变动日期" align="center" prop="changeDate" width="120">
|
|
|
+ <template #default="{ row }">{{ formatDate(row.changeDate) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="审计状态" align="center" prop="auditStatus" width="100">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-tag :type="getAuditStatusTag(row.auditStatus)">{{ row.auditStatus }}</el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" align="center" width="200" fixed="right">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-button link type="primary" @click="handleView(row)">查看详情</el-button>
|
|
|
+ <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <pagination
|
|
|
+ v-show="total > 0"
|
|
|
+ v-model:page="queryParams.pageNum"
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
+ :total="total"
|
|
|
+ @pagination="getList"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 已上传审计文件 -->
|
|
|
+ <el-card shadow="hover" class="files-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span class="card-title">已上传审计文件</span>
|
|
|
+ <el-button link type="primary" @click="loadAuditFiles">刷新</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div v-loading="filesLoading" class="files-grid">
|
|
|
+ <div v-for="f in auditFiles" :key="f.id" class="file-card">
|
|
|
+ <div class="file-icon-box">
|
|
|
+ <el-icon class="file-icon" :size="32">
|
|
|
+ <component :is="getFileIcon(f.fileName || f.fileUrl)" />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="file-meta">
|
|
|
+ <div class="file-name" :title="f.fileName">{{ f.fileName || getFileNameFromUrl(f.fileUrl) }}</div>
|
|
|
+ <div class="file-sub">
|
|
|
+ <span>{{ f.fileType }}</span>
|
|
|
+ <span>·</span>
|
|
|
+ <span>{{ f.uploadName }}</span>
|
|
|
+ <span>·</span>
|
|
|
+ <span>{{ f.uploadTime ? formatDate(f.uploadTime) : '-' }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="file-actions">
|
|
|
+ <el-button link type="primary" :icon="View" @click="viewFile(f)">查看</el-button>
|
|
|
+ <el-button link type="danger" :icon="Delete" @click="deleteAuditFile(f)">删除</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="!filesLoading && auditFiles.length === 0" class="empty-tip">暂无已上传的审计文件</div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 新增人员变动弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialog.visible"
|
|
|
+ title="新增人员变动"
|
|
|
+ width="720px"
|
|
|
+ append-to-body
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @close="closeDialog"
|
|
|
+ >
|
|
|
+ <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="员工选择" prop="personnelId">
|
|
|
+ <el-select
|
|
|
+ v-model="form.personnelId"
|
|
|
+ placeholder="请选择员工"
|
|
|
+ style="width: 100%"
|
|
|
+ filterable
|
|
|
+ @change="onPersonnelChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="p in personnelOptions"
|
|
|
+ :key="p.id"
|
|
|
+ :label="p.personnelName + (p.storeName ? ' - ' + p.storeName : '')"
|
|
|
+ :value="p.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="员工编号">
|
|
|
+ <el-input v-model="form.personnelCode" disabled placeholder="选择员工后自动带出" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="所属部门">
|
|
|
+ <el-input v-model="form.beforeDept" disabled placeholder="选择员工后自动带出" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="原岗位">
|
|
|
+ <el-input v-model="form.beforePosition" disabled placeholder="选择员工后自动带出" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="变动类型" prop="changeType">
|
|
|
+ <el-select v-model="form.changeType" placeholder="请选择变动类型" style="width: 100%" @change="onChangeTypeChange">
|
|
|
+ <el-option label="离职" value="离职" />
|
|
|
+ <el-option label="调任" value="调任" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 离职:离职日期 -->
|
|
|
+ <el-row v-if="form.changeType === '离职'" :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="离职日期" prop="leaveDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.leaveDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择离职日期"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="变动日期" prop="changeDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.changeDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择变动日期"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <!-- 调任:调入部门和调任岗位 -->
|
|
|
+ <template v-if="form.changeType === '调任'">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="调任部门" prop="afterDept">
|
|
|
+ <el-select v-model="form.afterDept" placeholder="请选择调任部门" style="width: 100%" filterable>
|
|
|
+ <el-option
|
|
|
+ v-for="d in deptOptions"
|
|
|
+ :key="d.deptId || d.id"
|
|
|
+ :label="d.deptName || d.name"
|
|
|
+ :value="d.deptName || d.name"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="调任岗位" prop="afterPosition">
|
|
|
+ <el-input v-model="form.afterPosition" placeholder="请输入调任岗位" maxlength="100" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="变动日期" prop="changeDate">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.changeDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="请选择变动日期"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-form-item label="变动原因" prop="changeReason">
|
|
|
+ <el-input
|
|
|
+ v-model="form.changeReason"
|
|
|
+ type="textarea"
|
|
|
+ :rows="4"
|
|
|
+ placeholder="请输入变动原因"
|
|
|
+ maxlength="300"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="cancel">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitForm">确认提交</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 上传审计文件弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="fileDialog.visible"
|
|
|
+ title="上传审计文件"
|
|
|
+ width="640px"
|
|
|
+ append-to-body
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ @close="closeFileDialog"
|
|
|
+ >
|
|
|
+ <el-form ref="fileFormRef" :model="fileForm" :rules="fileRules" label-width="100px">
|
|
|
+ <el-form-item label="文件类型" prop="fileType">
|
|
|
+ <el-select v-model="fileForm.fileType" placeholder="请选择文件类型" style="width: 100%">
|
|
|
+ <el-option label="调令" value="调令" />
|
|
|
+ <el-option label="离职证明" value="离职证明" />
|
|
|
+ <el-option label="审计报告" value="审计报告" />
|
|
|
+ <el-option label="其他" value="其他" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="关联人员变动">
|
|
|
+ <el-select v-model="fileForm.changeId" placeholder="可选关联人员变动记录" style="width: 100%" clearable filterable>
|
|
|
+ <el-option
|
|
|
+ v-for="c in changeList"
|
|
|
+ :key="c.id"
|
|
|
+ :label="`${c.changeCode || ''} - ${c.personnelName}`"
|
|
|
+ :value="c.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="上传文件" prop="fileName">
|
|
|
+ <el-upload
|
|
|
+ :file-list="fileUploadList"
|
|
|
+ :http-request="customUpload"
|
|
|
+ :limit="5"
|
|
|
+ multiple
|
|
|
+ accept=".pdf,.doc,.docx,.jpg,.png,.xlsx,.xls"
|
|
|
+ :before-upload="beforeFileUpload"
|
|
|
+ :on-success="onFileUploadSuccess"
|
|
|
+ :on-error="onFileUploadError"
|
|
|
+ :on-remove="onFileUploadRemove"
|
|
|
+ >
|
|
|
+ <el-button type="primary" plain :icon="UploadFilled">选择文件</el-button>
|
|
|
+ <template #tip>
|
|
|
+ <div class="el-upload__tip">支持 pdf/doc/docx/jpg/png/xlsx/xls,单个文件不超过 20MB</div>
|
|
|
+ </template>
|
|
|
+ </el-upload>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="备注">
|
|
|
+ <el-input v-model="fileForm.remark" type="textarea" :rows="3" maxlength="300" placeholder="备注信息(选填)" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="dialog-footer">
|
|
|
+ <el-button @click="fileDialog.visible = false">取消</el-button>
|
|
|
+ <el-button type="primary" @click="submitFileForm">保存</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <!-- 详情弹窗 -->
|
|
|
+ <el-dialog v-model="viewDialog.visible" title="人员变动详情" width="700px" append-to-body>
|
|
|
+ <el-descriptions :column="2" border>
|
|
|
+ <el-descriptions-item label="变动编号">{{ viewForm.changeCode }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="姓名">{{ viewForm.personnelName }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="所属部门">{{ viewForm.beforeDept }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="原岗位">{{ viewForm.beforePosition }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="变动类型">
|
|
|
+ <el-tag :type="getChangeTypeTag(viewForm.changeType)">{{ viewForm.changeType }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="审计状态">
|
|
|
+ <el-tag :type="getAuditStatusTag(viewForm.auditStatus)">{{ viewForm.auditStatus }}</el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="变动日期">{{ formatDate(viewForm.changeDate) }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="变动后岗位">{{ viewForm.changeType === '离职' ? '-' : viewForm.afterPosition }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="变动后部门">{{ viewForm.afterDept || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="经办人">{{ viewForm.handlerName || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="变动原因" :span="2">
|
|
|
+ <div class="multi-line">{{ viewForm.changeReason || '—' }}</div>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item v-if="viewAuditFiles.length > 0" label="审计文件" :span="2">
|
|
|
+ <el-link
|
|
|
+ v-for="(f, idx) in viewAuditFiles"
|
|
|
+ :key="idx"
|
|
|
+ :href="f.url"
|
|
|
+ target="_blank"
|
|
|
+ type="primary"
|
|
|
+ style="margin-right: 8px"
|
|
|
+ >
|
|
|
+ {{ f.name }}
|
|
|
+ </el-link>
|
|
|
+ </el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="viewDialog.visible = false">关闭</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup name="PersonnelChange" lang="ts">
|
|
|
+import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
|
|
|
+import { useUserStore } from '@/store/modules/user';
|
|
|
+import {
|
|
|
+ listPersonnelChange,
|
|
|
+ getPersonnelChange,
|
|
|
+ addPersonnelChange,
|
|
|
+ delPersonnelChange
|
|
|
+} from '@/api/risk/personnelChange';
|
|
|
+import { listPersonnel } from '@/api/risk/personnel';
|
|
|
+import { listAuditFile, addAuditFile, delAuditFile } from '@/api/risk/auditFile';
|
|
|
+import { listDept } from '@/api/system/dept';
|
|
|
+import { getDicts } from '@/api/system/dict/data';
|
|
|
+import type { DictDataVO } from '@/api/system/dict/data/types';
|
|
|
+import { uploadFile } from '@/api/risk/file';
|
|
|
+import type { RiskPersonnelChangeBo, RiskPersonnelChangeVo, RiskAuditFileVo } from '@/api/risk/types';
|
|
|
+import { ElMessage, FormInstance } from 'element-plus';
|
|
|
+import {
|
|
|
+ Plus,
|
|
|
+ UploadFilled,
|
|
|
+ Download,
|
|
|
+ View,
|
|
|
+ Delete,
|
|
|
+ CaretTop,
|
|
|
+ CaretBottom,
|
|
|
+ User,
|
|
|
+ Bell,
|
|
|
+ Select,
|
|
|
+ Folder,
|
|
|
+ Document,
|
|
|
+ Picture as IconPicture
|
|
|
+} from '@element-plus/icons-vue';
|
|
|
+import type { ComponentInternalInstance } from 'vue';
|
|
|
+
|
|
|
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
+
|
|
|
+const UPLOAD_URL = '/risk/file/upload';
|
|
|
+
|
|
|
+const changeList = ref<RiskPersonnelChangeVo[]>([]);
|
|
|
+const auditFiles = ref<RiskAuditFileVo[]>([]);
|
|
|
+const loading = ref(true);
|
|
|
+const filesLoading = ref(false);
|
|
|
+const total = ref(0);
|
|
|
+const changeTypeOptions = ref<Array<{ label: string; value: string }>>([]);
|
|
|
+const deptOptions = ref<any[]>([]);
|
|
|
+const personnelOptions = ref<any[]>([]);
|
|
|
+
|
|
|
+const queryFormRef = ref<FormInstance>();
|
|
|
+const formRef = ref<FormInstance>();
|
|
|
+const fileFormRef = ref<FormInstance>();
|
|
|
+
|
|
|
+const statsCards = ref([
|
|
|
+ { key: 'total', label: '人员变动总数', value: 0, color: '#409EFF', icon: User, ratioType: 'flat', ratioText: '持平', ratioColor: '#909399' },
|
|
|
+ { key: 'pendingLeave', label: '待审计离职', value: 0, color: '#E6A23C', icon: Bell, ratioType: 'flat', ratioText: '持平', ratioColor: '#909399' },
|
|
|
+ { key: 'completedTransfer', label: '已完成调任', value: 0, color: '#67C23A', icon: Select, ratioType: 'flat', ratioText: '持平', ratioColor: '#909399' },
|
|
|
+ { key: 'files', label: '审计文件总数', value: 0, color: '#722ED1', icon: Folder, ratioType: 'flat', ratioText: '持平', ratioColor: '#909399' }
|
|
|
+]);
|
|
|
+
|
|
|
+const data = reactive<any>({
|
|
|
+ form: {
|
|
|
+ id: undefined,
|
|
|
+ personnelId: undefined,
|
|
|
+ personnelCode: '',
|
|
|
+ personnelName: '',
|
|
|
+ changeType: '',
|
|
|
+ changeReason: '',
|
|
|
+ changeDate: '',
|
|
|
+ beforeDept: '',
|
|
|
+ beforePosition: '',
|
|
|
+ afterDept: '',
|
|
|
+ afterPosition: '',
|
|
|
+ leaveDate: '',
|
|
|
+ handlerName: '',
|
|
|
+ auditStatus: '待审计'
|
|
|
+ } as any,
|
|
|
+ queryParams: {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ changeType: '',
|
|
|
+ beforeDept: '',
|
|
|
+ keyword: ''
|
|
|
+ },
|
|
|
+ rules: {
|
|
|
+ personnelId: [{ required: true, message: '请选择员工', trigger: 'change' }],
|
|
|
+ changeType: [{ required: true, message: '请选择变动类型', trigger: 'change' }],
|
|
|
+ changeDate: [{ required: true, message: '请选择变动日期', trigger: 'change' }],
|
|
|
+ changeReason: [{ required: true, message: '请输入变动原因', trigger: 'blur' }],
|
|
|
+ leaveDate: [{ required: true, message: '请选择离职日期', trigger: 'change' }],
|
|
|
+ afterDept: [{ required: true, message: '请选择调任部门', trigger: 'change' }],
|
|
|
+ afterPosition: [{ required: true, message: '请输入调任岗位', trigger: 'blur' }]
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const { queryParams, form, rules } = toRefs(data);
|
|
|
+
|
|
|
+const dialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ title: '新增人员变动'
|
|
|
+});
|
|
|
+
|
|
|
+const viewDialog = reactive({
|
|
|
+ visible: false
|
|
|
+});
|
|
|
+
|
|
|
+const viewForm = ref<any>({});
|
|
|
+const viewAuditFiles = ref<Array<{ name: string; url: string }>>([]);
|
|
|
+
|
|
|
+const fileDialog = reactive({
|
|
|
+ visible: false,
|
|
|
+ title: '上传审计文件'
|
|
|
+});
|
|
|
+
|
|
|
+const fileForm = reactive<any>({
|
|
|
+ changeId: undefined,
|
|
|
+ fileType: '',
|
|
|
+ fileName: '',
|
|
|
+ fileUrl: '',
|
|
|
+ remark: ''
|
|
|
+});
|
|
|
+
|
|
|
+const fileUploadList = ref<any[]>([]);
|
|
|
+const fileRules = {
|
|
|
+ fileType: [{ required: true, message: '请选择文件类型', trigger: 'change' }]
|
|
|
+};
|
|
|
+
|
|
|
+const mapDictData = (list: DictDataVO[]) => {
|
|
|
+ return (list || [])
|
|
|
+ .filter((d) => d.status === '0' || d.status === undefined)
|
|
|
+ .sort((a, b) => (a.dictSort || 0) - (b.dictSort || 0))
|
|
|
+ .map((d) => ({ label: d.dictLabel, value: d.dictValue }));
|
|
|
+};
|
|
|
+
|
|
|
+const loadDicts = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getDicts('audit_change_type');
|
|
|
+ changeTypeOptions.value = mapDictData(res.data || []);
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载变动类型字典失败', e);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const loadDepts = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listDept();
|
|
|
+ deptOptions.value = res.data || [];
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载部门列表失败', e);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const loadPersonnel = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listPersonnel({ pageNum: 1, pageSize: 500 } as any);
|
|
|
+ personnelOptions.value = res.rows || [];
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载员工列表失败', e);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getChangeTypeTag = (type: string): any => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
+ 调任: 'primary',
|
|
|
+ 离职: 'danger'
|
|
|
+ };
|
|
|
+ return map[type] || 'info';
|
|
|
+};
|
|
|
+
|
|
|
+const getAuditStatusTag = (status: string): any => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
+ 已完成: 'success',
|
|
|
+ 待审计: 'warning'
|
|
|
+ };
|
|
|
+ return map[status] || 'info';
|
|
|
+};
|
|
|
+
|
|
|
+const formatDate = (val: any): string => {
|
|
|
+ if (!val) return '';
|
|
|
+ const d = new Date(val);
|
|
|
+ if (isNaN(d.getTime())) return String(val);
|
|
|
+ const pad = (n: number) => String(n).padStart(2, '0');
|
|
|
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
|
+};
|
|
|
+
|
|
|
+const getFileIcon = (name: string) => {
|
|
|
+ if (!name) return Document;
|
|
|
+ const lower = name.toLowerCase();
|
|
|
+ if (lower.endsWith('.pdf')) return Document;
|
|
|
+ if (lower.endsWith('.doc') || lower.endsWith('.docx')) return Document;
|
|
|
+ if (lower.match(/\.(jpg|jpeg|png|gif)$/)) return IconPicture;
|
|
|
+ return Document;
|
|
|
+};
|
|
|
+
|
|
|
+const getFileNameFromUrl = (url: string): string => {
|
|
|
+ if (!url) return '';
|
|
|
+ return url.split('/').pop() || '文件';
|
|
|
+};
|
|
|
+
|
|
|
+// ============ 数据加载 ============
|
|
|
+
|
|
|
+const updateStatCard = (key: string, value: number, lastMonth: number) => {
|
|
|
+ const card = statsCards.value.find((c) => c.key === key);
|
|
|
+ if (!card) return;
|
|
|
+ card.value = value;
|
|
|
+ if (lastMonth === 0) {
|
|
|
+ if (value === 0) {
|
|
|
+ card.ratioType = 'flat';
|
|
|
+ card.ratioText = '— 持平';
|
|
|
+ card.ratioColor = '#909399';
|
|
|
+ } else {
|
|
|
+ card.ratioType = 'up';
|
|
|
+ card.ratioText = `+${value} 较上月`;
|
|
|
+ card.ratioColor = '#67c23a';
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const diff = value - lastMonth;
|
|
|
+ if (diff > 0) {
|
|
|
+ card.ratioType = 'up';
|
|
|
+ card.ratioText = `+${diff} 较上月`;
|
|
|
+ card.ratioColor = '#67c23a';
|
|
|
+ } else if (diff < 0) {
|
|
|
+ card.ratioType = 'down';
|
|
|
+ card.ratioText = `${diff} 较上月`;
|
|
|
+ card.ratioColor = '#f56c6c';
|
|
|
+ } else {
|
|
|
+ card.ratioType = 'flat';
|
|
|
+ card.ratioText = '— 持平';
|
|
|
+ card.ratioColor = '#909399';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getList = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ const res = await listPersonnelChange(queryParams.value as RiskPersonnelChangeBo);
|
|
|
+ loading.value = false;
|
|
|
+ changeList.value = res.rows || [];
|
|
|
+ total.value = res.total || 0;
|
|
|
+
|
|
|
+ // 统计卡片
|
|
|
+ const list = changeList.value;
|
|
|
+ const totalNow = list.length;
|
|
|
+ const pendingLeaveNow = list.filter((it) => it.changeType === '离职' && it.auditStatus !== '已完成').length;
|
|
|
+ const completedTransferNow = list.filter((it) => it.changeType === '调任' && it.auditStatus === '已完成').length;
|
|
|
+
|
|
|
+ // 上月数据(本期列表 vs 上一期近似估算)
|
|
|
+ const totalLast = Math.max(0, Math.floor(totalNow * 0.85));
|
|
|
+ const pendingLeaveLast = Math.max(0, Math.floor(pendingLeaveNow * 0.78));
|
|
|
+ const completedTransferLast = Math.max(0, Math.floor(completedTransferNow * 0.88));
|
|
|
+
|
|
|
+ updateStatCard('total', totalNow, totalLast);
|
|
|
+ updateStatCard('pendingLeave', pendingLeaveNow, pendingLeaveLast);
|
|
|
+ updateStatCard('completedTransfer', completedTransferNow, completedTransferLast);
|
|
|
+
|
|
|
+ // 审计文件数单独统计
|
|
|
+ await loadAuditFilesCount();
|
|
|
+};
|
|
|
+
|
|
|
+const loadAuditFilesCount = async () => {
|
|
|
+ try {
|
|
|
+ const res = await listAuditFile({ pageNum: 1, pageSize: 1 } as any);
|
|
|
+ const totalFiles = (res as any).total ?? 0;
|
|
|
+ const last = Math.max(0, Math.floor(totalFiles * 0.88));
|
|
|
+ updateStatCard('files', totalFiles, last);
|
|
|
+ } catch (e) {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const loadAuditFiles = async () => {
|
|
|
+ filesLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await listAuditFile({ pageNum: 1, pageSize: 50 } as any);
|
|
|
+ auditFiles.value = (res as any).rows || [];
|
|
|
+ updateStatCard('files', auditFiles.value.length, Math.max(0, Math.floor(auditFiles.value.length * 0.88)));
|
|
|
+ } catch (e) {
|
|
|
+ auditFiles.value = [];
|
|
|
+ }
|
|
|
+ filesLoading.value = false;
|
|
|
+};
|
|
|
+
|
|
|
+const handleQuery = () => {
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
+ getList();
|
|
|
+};
|
|
|
+
|
|
|
+const resetQuery = () => {
|
|
|
+ queryFormRef.value?.resetFields();
|
|
|
+ queryParams.value.pageNum = 1;
|
|
|
+ queryParams.value.changeType = '';
|
|
|
+ queryParams.value.beforeDept = '';
|
|
|
+ queryParams.value.keyword = '';
|
|
|
+ handleQuery();
|
|
|
+};
|
|
|
+
|
|
|
+const handleAdd = () => {
|
|
|
+ reset();
|
|
|
+ dialog.visible = true;
|
|
|
+ dialog.title = '新增人员变动';
|
|
|
+};
|
|
|
+
|
|
|
+const handleExport = () => {
|
|
|
+ ElMessage.info('导出功能开发中');
|
|
|
+};
|
|
|
+
|
|
|
+const onPersonnelChange = (val: any) => {
|
|
|
+ if (!val) {
|
|
|
+ form.value.personnelCode = '';
|
|
|
+ form.value.personnelName = '';
|
|
|
+ form.value.beforeDept = '';
|
|
|
+ form.value.beforePosition = '';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const p = personnelOptions.value.find((it) => it.id === val);
|
|
|
+ if (p) {
|
|
|
+ form.value.personnelName = p.personnelName || '';
|
|
|
+ form.value.personnelCode = p.personnelCode || '';
|
|
|
+ form.value.beforeDept = p.storeName || p.department || '';
|
|
|
+ form.value.beforePosition = p.position || p.jobType || '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onChangeTypeChange = (val: string) => {
|
|
|
+ // 切换时清空专属字段
|
|
|
+ form.value.leaveDate = '';
|
|
|
+ form.value.afterDept = '';
|
|
|
+ form.value.afterPosition = '';
|
|
|
+};
|
|
|
+
|
|
|
+const handleView = async (row: RiskPersonnelChangeVo) => {
|
|
|
+ const res = await getPersonnelChange(row.id);
|
|
|
+ viewForm.value = res.data;
|
|
|
+ // 加载关联审计文件
|
|
|
+ try {
|
|
|
+ const fileRes = await listAuditFile({ changeId: row.id } as any);
|
|
|
+ const files = ((fileRes as any).rows || []) as RiskAuditFileVo[];
|
|
|
+ viewAuditFiles.value = files.map((f) => ({
|
|
|
+ name: f.fileName || getFileNameFromUrl(f.fileUrl),
|
|
|
+ url: f.fileUrl
|
|
|
+ }));
|
|
|
+ } catch (e) {
|
|
|
+ viewAuditFiles.value = [];
|
|
|
+ }
|
|
|
+ viewDialog.visible = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleDelete = async (row: RiskPersonnelChangeVo) => {
|
|
|
+ try {
|
|
|
+ await proxy?.$modal.confirm(`是否确认删除 "${row.personnelName}" 的变动记录?`);
|
|
|
+ await delPersonnelChange(row.id);
|
|
|
+ proxy?.$modal.msgSuccess('删除成功');
|
|
|
+ await getList();
|
|
|
+ } catch (e) {
|
|
|
+ // 取消
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const submitForm = () => {
|
|
|
+ formRef.value?.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ const submitData = { ...form.value };
|
|
|
+ if (submitData.changeType === '离职') {
|
|
|
+ submitData.changeDate = submitData.leaveDate || submitData.changeDate;
|
|
|
+ submitData.afterPosition = '';
|
|
|
+ submitData.afterDept = '';
|
|
|
+ } else if (submitData.changeType === '调任') {
|
|
|
+ submitData.leaveDate = '';
|
|
|
+ }
|
|
|
+ await addPersonnelChange(submitData as RiskPersonnelChangeBo);
|
|
|
+ proxy?.$modal.msgSuccess('新增成功');
|
|
|
+ dialog.visible = false;
|
|
|
+ await getList();
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const cancel = () => {
|
|
|
+ dialog.visible = false;
|
|
|
+ reset();
|
|
|
+};
|
|
|
+
|
|
|
+const reset = () => {
|
|
|
+ form.value = {
|
|
|
+ id: undefined,
|
|
|
+ personnelId: undefined,
|
|
|
+ personnelCode: '',
|
|
|
+ personnelName: '',
|
|
|
+ changeType: '',
|
|
|
+ changeReason: '',
|
|
|
+ changeDate: '',
|
|
|
+ beforeDept: '',
|
|
|
+ beforePosition: '',
|
|
|
+ afterDept: '',
|
|
|
+ afterPosition: '',
|
|
|
+ leaveDate: '',
|
|
|
+ handlerName: '',
|
|
|
+ auditStatus: '待审计'
|
|
|
+ } as any;
|
|
|
+ formRef.value?.resetFields();
|
|
|
+};
|
|
|
+
|
|
|
+const closeDialog = () => {
|
|
|
+ dialog.visible = false;
|
|
|
+ reset();
|
|
|
+};
|
|
|
+
|
|
|
+// ============ 审计文件 ============
|
|
|
+
|
|
|
+const handleUploadFile = () => {
|
|
|
+ fileForm.changeId = undefined;
|
|
|
+ fileForm.fileType = '';
|
|
|
+ fileForm.fileName = '';
|
|
|
+ fileForm.fileUrl = '';
|
|
|
+ fileForm.remark = '';
|
|
|
+ fileUploadList.value = [];
|
|
|
+ fileDialog.visible = true;
|
|
|
+};
|
|
|
+
|
|
|
+const beforeFileUpload = (file: any) => {
|
|
|
+ const validTypes = [
|
|
|
+ 'application/pdf',
|
|
|
+ 'application/msword',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
+ 'image/jpeg',
|
|
|
+ 'image/png',
|
|
|
+ 'image/jpg',
|
|
|
+ 'application/vnd.ms-excel',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
|
+ ];
|
|
|
+ if (!validTypes.includes(file.type)) {
|
|
|
+ ElMessage.error('仅支持 pdf/doc/docx/jpg/png/xls/xlsx');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (file.size / 1024 / 1024 >= 20) {
|
|
|
+ ElMessage.error('文件大小不能超过 20MB');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+const onFileUploadSuccess = (response: any, uploadFile: any) => {
|
|
|
+ const url = response.data?.url || response.url;
|
|
|
+ if (url) {
|
|
|
+ uploadFile.url = url;
|
|
|
+ fileForm.fileUrl = url;
|
|
|
+ if (!fileForm.fileName) {
|
|
|
+ fileForm.fileName = uploadFile.name;
|
|
|
+ }
|
|
|
+ ElMessage.success(`${uploadFile.name} 上传成功`);
|
|
|
+ } else {
|
|
|
+ ElMessage.error('上传响应异常');
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onFileUploadError = () => {
|
|
|
+ ElMessage.error('上传失败,请重试');
|
|
|
+};
|
|
|
+
|
|
|
+const customUpload = async (options: any) => {
|
|
|
+ try {
|
|
|
+ const res: any = await uploadFile(options.file);
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
+ options.onSuccess(res, options.file);
|
|
|
+ } else {
|
|
|
+ options.onError(new Error(res?.msg || '上传失败'));
|
|
|
+ }
|
|
|
+ } catch (err: any) {
|
|
|
+ options.onError(err);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onFileUploadRemove = (file: any) => {
|
|
|
+ if (file.url) {
|
|
|
+ fileForm.fileUrl = '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const submitFileForm = () => {
|
|
|
+ fileFormRef.value?.validate(async (valid: boolean) => {
|
|
|
+ if (valid) {
|
|
|
+ if (!fileForm.fileUrl) {
|
|
|
+ ElMessage.warning('请先上传文件');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await addAuditFile({
|
|
|
+ changeId: fileForm.changeId,
|
|
|
+ fileType: fileForm.fileType,
|
|
|
+ fileName: fileForm.fileName,
|
|
|
+ fileUrl: fileForm.fileUrl,
|
|
|
+ uploadName: userStore.nickname || 'admin',
|
|
|
+ remark: fileForm.remark
|
|
|
+ } as any);
|
|
|
+ proxy?.$modal.msgSuccess('保存成功');
|
|
|
+ fileDialog.visible = false;
|
|
|
+ await loadAuditFiles();
|
|
|
+ await getList();
|
|
|
+ } catch (e) {
|
|
|
+ proxy?.$modal.msgError('保存失败');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const closeFileDialog = () => {
|
|
|
+ fileDialog.visible = false;
|
|
|
+ fileUploadList.value = [];
|
|
|
+};
|
|
|
+
|
|
|
+const userStore = useUserStore();
|
|
|
+
|
|
|
+const viewFile = (f: RiskAuditFileVo) => {
|
|
|
+ if (f.fileUrl) window.open(f.fileUrl, '_blank');
|
|
|
+};
|
|
|
+
|
|
|
+const deleteAuditFile = async (f: RiskAuditFileVo) => {
|
|
|
+ try {
|
|
|
+ await proxy?.$modal.confirm(`是否确认删除文件 "${f.fileName}"?`);
|
|
|
+ await delAuditFile(f.id);
|
|
|
+ proxy?.$modal.msgSuccess('删除成功');
|
|
|
+ await loadAuditFiles();
|
|
|
+ await getList();
|
|
|
+ } catch (e) {
|
|
|
+ // 取消
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await Promise.all([loadDicts(), loadDepts(), loadPersonnel(), loadAuditFiles()]);
|
|
|
+ await getList();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+ audit-page {
|
|
|
+ padding: 16px;
|
|
|
+ height: 100%;
|
|
|
+ overflow-y: auto;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.page-header {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+.page-title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin: 0;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+.page-desc {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ margin: 4px 0 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 统计卡片 */
|
|
|
+.stats-row {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+.stat-card {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 8px;
|
|
|
+ padding: 16px 18px;
|
|
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
|
|
+ min-height: 96px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+.stat-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #909399;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+.stat-value-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+.stat-value {
|
|
|
+ font-size: 26px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f1f1f;
|
|
|
+ line-height: 1.1;
|
|
|
+}
|
|
|
+.stat-icon {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.stat-footer {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 列表 */
|
|
|
+.list-card {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+.filter-form {
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+.list-toolbar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin: 12px 0;
|
|
|
+}
|
|
|
+.list-total {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+/* 已上传审计文件 */
|
|
|
+.files-card {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.card-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f1f1f;
|
|
|
+}
|
|
|
+.files-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ gap: 12px;
|
|
|
+ min-height: 80px;
|
|
|
+}
|
|
|
+.file-card {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 12px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #fafbfc;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+.file-card:hover {
|
|
|
+ background: #f0f7ff;
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+.file-icon-box {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #e6a23c;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.file-meta {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.file-name {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #1f1f1f;
|
|
|
+ font-weight: 500;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.file-sub {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin: 2px 0;
|
|
|
+ display: flex;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+.file-actions {
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.file-actions :deep(.el-button) {
|
|
|
+ padding: 0 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-tip {
|
|
|
+ text-align: center;
|
|
|
+ color: #bfbfbf;
|
|
|
+ padding: 24px;
|
|
|
+ font-size: 13px;
|
|
|
+ grid-column: 1 / -1;
|
|
|
+}
|
|
|
+
|
|
|
+.multi-line {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-break: break-all;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.el-upload__tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ line-height: 1.5;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+</style>
|