|
|
@@ -1,140 +1,166 @@
|
|
|
<template>
|
|
|
- <div class="p-2">
|
|
|
+ <div class="audit-page">
|
|
|
+ <div class="page-header">
|
|
|
+ <div>
|
|
|
+ <div class="page-title">审计工作管理</div>
|
|
|
+ <div class="page-desc">部门重点人员及关键岗位人员离职、调任信息,审计重点工作上传</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 顶部统计卡片 -->
|
|
|
- <el-row :gutter="16" class="mb-[10px]">
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+ <el-row :gutter="16" class="stat-row">
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">人员变动总数</p>
|
|
|
- <p class="text-2xl font-bold text-blue-500">{{ statsData.totalChanges }}</p>
|
|
|
- <p class="text-xs mt-1">
|
|
|
- <span :class="statsData.totalTrend >= 0 ? 'text-green-500' : 'text-red-500'">
|
|
|
- {{ statsData.totalTrend >= 0 ? '↑' : '↓' }} 较上月 {{ Math.abs(statsData.totalTrend) }}
|
|
|
- </span>
|
|
|
- </p>
|
|
|
+ <div class="stat-label">人员变动总数</div>
|
|
|
+ <div class="stat-value">{{ statsData.totalChanges }}</div>
|
|
|
+ <div class="stat-sub">
|
|
|
+ <span :class="trendClass(statsData.totalTrend)"> {{ trendArrow(statsData.totalTrend) }} {{ trendText(statsData.totalTrend) }} </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon stat-icon-blue">
|
|
|
+ <el-icon><UserFilled /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#409EFF"><UserFilled /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">待审计离职</p>
|
|
|
- <p class="text-2xl font-bold text-orange-500">{{ statsData.pendingAudit }}</p>
|
|
|
- <p class="text-xs mt-1">
|
|
|
- <span :class="statsData.pendingTrend >= 0 ? 'text-green-500' : 'text-red-500'">
|
|
|
- {{ statsData.pendingTrend >= 0 ? '↑' : '↓' }} 较上月 {{ Math.abs(statsData.pendingTrend) }}
|
|
|
+ <div class="stat-label">待审计离职</div>
|
|
|
+ <div class="stat-value stat-value-orange">{{ statsData.pendingAudit }}</div>
|
|
|
+ <div class="stat-sub">
|
|
|
+ <span :class="trendClass(statsData.pendingTrend)">
|
|
|
+ {{ trendArrow(statsData.pendingTrend) }} {{ trendText(statsData.pendingTrend) }}
|
|
|
</span>
|
|
|
- </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon stat-icon-orange">
|
|
|
+ <el-icon><Clock /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#E6A23C"><Clock /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">已完成调任</p>
|
|
|
- <p class="text-2xl font-bold text-green-500">{{ statsData.completedTransfer }}</p>
|
|
|
- <p class="text-xs mt-1">
|
|
|
- <span :class="statsData.transferTrend >= 0 ? 'text-green-500' : 'text-red-500'">
|
|
|
- {{ statsData.transferTrend >= 0 ? '↑' : '↓' }} 较上月 {{ Math.abs(statsData.transferTrend) }}
|
|
|
+ <div class="stat-label">已完成调任</div>
|
|
|
+ <div class="stat-value stat-value-green">{{ statsData.completedTransfer }}</div>
|
|
|
+ <div class="stat-sub">
|
|
|
+ <span :class="trendClass(statsData.transferTrend)">
|
|
|
+ {{ trendArrow(statsData.transferTrend) }} {{ trendText(statsData.transferTrend) }}
|
|
|
</span>
|
|
|
- </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon stat-icon-green">
|
|
|
+ <el-icon><CircleCheckFilled /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#67C23A"><CircleCheckFilled /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">审计文件总数</p>
|
|
|
- <p class="text-2xl font-bold text-purple-500">{{ statsData.totalFiles }}</p>
|
|
|
- <p class="text-xs mt-1">
|
|
|
- <span :class="statsData.fileTrend >= 0 ? 'text-green-500' : 'text-red-500'">
|
|
|
- {{ statsData.fileTrend >= 0 ? '↑' : '↓' }} 较上月 {{ Math.abs(statsData.fileTrend) }}
|
|
|
- </span>
|
|
|
- </p>
|
|
|
+ <div class="stat-label">审计文件总数</div>
|
|
|
+ <div class="stat-value stat-value-purple">{{ statsData.totalFiles }}</div>
|
|
|
+ <div class="stat-sub">
|
|
|
+ <span :class="trendClass(statsData.fileTrend)"> {{ trendArrow(statsData.fileTrend) }} {{ trendText(statsData.fileTrend) }} </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon stat-icon-purple">
|
|
|
+ <el-icon><FolderOpened /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#8B5CF6"><FolderOpened /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
- <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
- <div v-show="showSearch" class="mb-[10px]">
|
|
|
- <el-card shadow="hover">
|
|
|
- <el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
|
- <el-form-item label="变动类型" prop="changeType">
|
|
|
- <el-select v-model="queryParams.changeType" placeholder="类型" clearable>
|
|
|
- <el-option label="调任" value="调任" />
|
|
|
- <el-option label="离职" value="离职" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="部门" prop="department">
|
|
|
- <el-input v-model="queryParams.department" placeholder="部门" clearable @keyup.enter="handleQuery" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="关键词" prop="employeeName">
|
|
|
- <el-input v-model="queryParams.employeeName" placeholder="搜索姓名" clearable @keyup.enter="handleQuery" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </el-card>
|
|
|
- </div>
|
|
|
- </transition>
|
|
|
+ <!-- 筛选区 -->
|
|
|
+ <el-card shadow="hover" class="search-card">
|
|
|
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true" class="search-form">
|
|
|
+ <el-form-item label="变动类型" prop="changeType">
|
|
|
+ <el-select v-model="queryParams.changeType" placeholder="全部类型" clearable class="search-select">
|
|
|
+ <el-option v-for="dict in auditChangeTypeOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
|
|
|
- <el-card shadow="hover">
|
|
|
+ <el-form-item label="部门" prop="departmentId">
|
|
|
+ <el-select v-model="queryParams.departmentId" placeholder="全部部门" clearable class="search-select">
|
|
|
+ <el-option v-for="dept in deptOptions" :key="dept.value" :label="dept.label" :value="dept.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="关键词" prop="keyword">
|
|
|
+ <el-input v-model="queryParams.keyword" placeholder="搜索姓名/岗位" clearable class="search-keyword" @keyup.enter="handleQuery">
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <div class="search-actions">
|
|
|
+ <el-button @click="resetQuery">重置</el-button>
|
|
|
+ <el-button type="primary" @click="handleQuery">搜索</el-button>
|
|
|
+ <el-button type="primary" class="primary-btn" @click="handleAdd">
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ 新增人员变动
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" class="primary-btn" @click="uploadFileOpen = true">
|
|
|
+ <el-icon><Upload /></el-icon>
|
|
|
+ 上传审计文件
|
|
|
+ </el-button>
|
|
|
+ <el-button type="primary" class="primary-btn" @click="handleExport">
|
|
|
+ <el-icon><Download /></el-icon>
|
|
|
+ 导出
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 列表 -->
|
|
|
+ <el-card shadow="hover" class="table-card">
|
|
|
<template #header>
|
|
|
- <el-row :gutter="10">
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:auditPersonChange:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增人员变动</el-button>
|
|
|
- </el-col>
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:auditPersonChange:upload']" type="warning" plain icon="Upload" @click="uploadFileOpen = true">上传审计文件</el-button>
|
|
|
- </el-col>
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:auditPersonChange:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">删除</el-button>
|
|
|
- </el-col>
|
|
|
- <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
|
|
|
- </el-row>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>人员变动信息列表</span>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<el-table v-loading="loading" border :data="changeList" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="50" align="center" />
|
|
|
- <el-table-column v-if="columns[0].visible" key="employeeName" label="员工姓名" align="center" prop="employeeName" width="100" />
|
|
|
- <el-table-column v-if="columns[1].visible" key="department" label="部门" align="center" prop="department" width="120" />
|
|
|
- <el-table-column v-if="columns[2].visible" key="oldPosition" label="原岗位" align="center" prop="oldPosition" :show-overflow-tooltip="true" />
|
|
|
- <el-table-column v-if="columns[3].visible" key="newPosition" label="变动后岗位" align="center" prop="newPosition" :show-overflow-tooltip="true" />
|
|
|
- <el-table-column v-if="columns[4].visible" key="changeType" label="变动类型" align="center" prop="changeType" width="100">
|
|
|
+ <el-table-column label="姓名" align="center" prop="employeeName" min-width="110" />
|
|
|
+ <el-table-column label="部门" align="center" prop="departmentName" min-width="120" />
|
|
|
+ <el-table-column label="原岗位" align="center" prop="oldPosition" min-width="160" :show-overflow-tooltip="true" />
|
|
|
+ <el-table-column label="变动类型" align="center" prop="changeType" width="110">
|
|
|
+ <template #default="scope">
|
|
|
+ <span class="tag-pill" :class="getChangeTypeClass(scope.row.changeType)">
|
|
|
+ {{ getChangeTypeLabel(scope.row.changeType) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="新岗位" align="center" prop="transferPosition" min-width="140">
|
|
|
<template #default="scope">
|
|
|
- <el-tag :type="scope.row.changeType === '离职' ? 'danger' : 'primary'">{{ scope.row.changeType }}</el-tag>
|
|
|
+ <span>{{ scope.row.transferPosition || scope.row.newPosition || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column v-if="columns[5].visible" key="changeDate" label="变动日期" align="center" prop="changeDate" width="120" />
|
|
|
- <el-table-column v-if="columns[6].visible" key="auditStatus" label="审计状态" align="center" prop="auditStatus" width="100">
|
|
|
+ <el-table-column label="变动日期" align="center" prop="changeDate" width="120" />
|
|
|
+ <el-table-column label="审计状态" align="center" prop="auditStatus" width="110">
|
|
|
<template #default="scope">
|
|
|
- <el-tag :type="scope.row.auditStatus === '已完成' ? 'success' : 'warning'">{{ scope.row.auditStatus }}</el-tag>
|
|
|
+ <span class="tag-pill" :class="getAuditStatusClass(scope.row.auditStatus)">
|
|
|
+ {{ scope.row.auditStatus }}
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column v-if="columns[7].visible" label="创建时间" align="center" prop="createTime" width="160" />
|
|
|
- <el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
|
|
|
+ <el-table-column label="操作" fixed="right" width="160" align="center">
|
|
|
<template #default="scope">
|
|
|
- <el-tooltip content="修改" placement="top">
|
|
|
- <el-button v-has-permi="['riskctrl:auditPersonChange:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
|
|
- </el-tooltip>
|
|
|
- <el-tooltip content="删除" placement="top">
|
|
|
- <el-button v-has-permi="['riskctrl:auditPersonChange:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
|
|
- </el-tooltip>
|
|
|
+ <el-button link type="primary" @click="handleView(scope.row)">查看详情</el-button>
|
|
|
+ <el-button v-has-permi="['riskctrl:auditPersonChange:remove']" link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
@@ -142,83 +168,162 @@
|
|
|
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
|
|
</el-card>
|
|
|
|
|
|
- <!-- 新增/修改弹窗 -->
|
|
|
- <el-dialog v-model="open" :title="title" width="700px" append-to-body>
|
|
|
- <el-form ref="changeFormRef" :model="form" :rules="rules" label-width="110px">
|
|
|
+ <!-- 已上传审计文件 -->
|
|
|
+ <el-card shadow="hover" class="file-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>已上传审计文件</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <div class="file-grid">
|
|
|
+ <div v-for="item in auditFileList" :key="item.ossId" class="file-item">
|
|
|
+ <div class="file-left">
|
|
|
+ <div class="file-icon" :class="getFileIconClass(item.fileName)">
|
|
|
+ <span>{{ getFileExtLabel(item.fileName) }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="file-meta">
|
|
|
+ <div class="file-name" :title="item.fileName">{{ item.fileName }}</div>
|
|
|
+ <div class="file-desc">大小:{{ formatFileSize(item.fileSize) }} · 上传于:{{ formatDate(item.createTime) }}</div>
|
|
|
+ <div class="file-actions">
|
|
|
+ <el-button link type="primary" @click="download.oss(item.ossId)">下载</el-button>
|
|
|
+ <el-button link type="danger" @click="handleRemoveFile(item)">删除</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 新增/修改/查看弹窗 -->
|
|
|
+ <el-dialog v-model="open" :title="dialogTitle" width="980px" append-to-body :close-on-click-modal="false" destroy-on-close class="person-dialog">
|
|
|
+ <el-form ref="changeFormRef" :model="form" :rules="rules" label-position="top" class="person-form">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="员工选择" prop="employeeId" required>
|
|
|
+ <el-select
|
|
|
+ v-model="form.employeeId"
|
|
|
+ placeholder="请选择员工"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ class="w-full"
|
|
|
+ :disabled="isViewMode"
|
|
|
+ @change="handleEmployeeChange"
|
|
|
+ >
|
|
|
+ <el-option v-for="emp in employeeOptions" :key="emp.employeeId" :label="emp.employeeName" :value="emp.employeeId" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="员工姓名" prop="employeeName">
|
|
|
- <el-input v-model="form.employeeName" placeholder="请输入员工姓名" />
|
|
|
+ <el-form-item label="员工编号" prop="employeeCode">
|
|
|
+ <el-input v-model="form.employeeCode" placeholder="自动带出" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="所属部门" prop="department">
|
|
|
- <el-input v-model="form.department" placeholder="自动带出" />
|
|
|
+ <el-form-item label="所属部门" prop="departmentName">
|
|
|
+ <el-input v-model="form.departmentName" placeholder="自动带出" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
+
|
|
|
<el-row :gutter="20">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="变动类型" prop="changeType">
|
|
|
- <el-select v-model="form.changeType" placeholder="请选择" class="w-full" @change="onChangeType">
|
|
|
- <el-option label="调任" value="调任" />
|
|
|
- <el-option label="离职" value="离职" />
|
|
|
- </el-select>
|
|
|
+ <el-form-item label="原岗位" prop="oldPosition">
|
|
|
+ <el-input v-model="form.oldPosition" placeholder="自动带出" :disabled="isViewMode" />
|
|
|
</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" class="w-full" />
|
|
|
+ <el-form-item label="变动类型" prop="changeType" required>
|
|
|
+ <el-select v-model="form.changeType" placeholder="请选择变动类型" class="w-full" :disabled="isViewMode" @change="onChangeType">
|
|
|
+ <el-option v-for="dict in auditChangeTypeOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
- <el-row :gutter="20">
|
|
|
+
|
|
|
+ <el-row :gutter="20" v-if="isTransferType">
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="原岗位" prop="oldPosition">
|
|
|
- <el-input v-model="form.oldPosition" placeholder="请输入原岗位" />
|
|
|
+ <el-form-item label="调任部门" prop="transferDepartmentId" required>
|
|
|
+ <el-select
|
|
|
+ v-model="form.transferDepartmentId"
|
|
|
+ placeholder="请选择调任部门"
|
|
|
+ clearable
|
|
|
+ class="w-full"
|
|
|
+ :disabled="isViewMode"
|
|
|
+ @change="handleTransferDeptChange"
|
|
|
+ >
|
|
|
+ <el-option v-for="dept in deptOptions" :key="dept.value" :label="dept.label" :value="dept.value" />
|
|
|
+ </el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
+
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="变动后岗位" prop="newPosition">
|
|
|
- <el-input v-model="form.newPosition" placeholder="离职填写-" :disabled="form.changeType === '离职'" />
|
|
|
+ <el-form-item label="调任岗位" prop="transferPosition" required>
|
|
|
+ <el-input v-model="form.transferPosition" placeholder="请输入调任岗位" :disabled="isViewMode" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
- <el-form-item v-if="form.changeType === '调任'" label="调入部门" prop="newDepartment">
|
|
|
- <el-input v-model="form.newDepartment" placeholder="请输入调入部门" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="备注" prop="remark">
|
|
|
- <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
|
|
+
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item :label="changeDateLabel" prop="changeDate" required>
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.changeDate"
|
|
|
+ type="date"
|
|
|
+ placeholder="年/月/日"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ class="w-full"
|
|
|
+ :disabled="isViewMode"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12"></el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
+ <el-form-item label="变动原因" prop="remark">
|
|
|
+ <el-input v-model="form.remark" type="textarea" :rows="4" placeholder="请输入变动原因" :disabled="isViewMode" />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
+
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button type="primary" @click="submitForm">确 定</el-button>
|
|
|
- <el-button @click="cancel">取 消</el-button>
|
|
|
+ <el-button v-if="!isViewMode" @click="cancel">取消</el-button>
|
|
|
+ <el-button v-if="isViewMode" type="primary" @click="open = false">关闭</el-button>
|
|
|
+ <el-button v-else type="primary" @click="submitForm">确认提交</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 上传审计文件弹窗 -->
|
|
|
- <el-dialog v-model="uploadFileOpen" title="上传审计文件" width="550px" append-to-body>
|
|
|
+ <el-dialog v-model="uploadFileOpen" title="上传审计文件" width="620px" append-to-body destroy-on-close class="audit-dialog">
|
|
|
<el-upload
|
|
|
- v-model:file-list="auditFileList"
|
|
|
- action="#"
|
|
|
- :auto-upload="false"
|
|
|
- multiple
|
|
|
- drag
|
|
|
+ v-model:file-list="uploadQueue"
|
|
|
+ :action="uploadUrl"
|
|
|
+ :headers="uploadHeaders"
|
|
|
+ :auto-upload="true"
|
|
|
:limit="10"
|
|
|
+ :multiple="true"
|
|
|
+ drag
|
|
|
+ accept=".pdf,.doc,.docx,.xls,.xlsx,.png,.jpg,.jpeg"
|
|
|
+ :on-success="handleUploadSuccess"
|
|
|
+ :on-exceed="handleUploadExceed"
|
|
|
+ :before-upload="beforeUpload"
|
|
|
+ :on-remove="handleUploadRemove"
|
|
|
>
|
|
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
|
|
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
|
<template #tip>
|
|
|
- <div class="el-upload__tip">支持 PDF、Word、Excel 等格式,单文件不超过 10MB</div>
|
|
|
+ <div class="el-upload__tip">支持 PDF、Word、Excel、图片等格式,单文件不超过 20MB</div>
|
|
|
</template>
|
|
|
</el-upload>
|
|
|
+
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button type="primary" @click="submitAuditFiles">确 定</el-button>
|
|
|
- <el-button @click="uploadFileOpen = false">取 消</el-button>
|
|
|
+ <el-button @click="uploadFileOpen = false">关闭</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
@@ -226,24 +331,61 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup name="AuditPersonChange" lang="ts">
|
|
|
-import { listAuditPersonChange, getAuditPersonChange, delAuditPersonChange, addAuditPersonChange, updateAuditPersonChange } from '@/api/riskctrl/auditPersonChange';
|
|
|
-import { AuditPersonChangeForm, AuditPersonChangeQuery, AuditPersonChangeVO } from '@/api/riskctrl/auditPersonChange/types';
|
|
|
+import { computed, getCurrentInstance, nextTick, onMounted, reactive, ref } from 'vue';
|
|
|
+import type { ComponentInternalInstance } from 'vue';
|
|
|
+import { CircleCheckFilled, Clock, Delete, Download, FolderOpened, Plus, Search, Upload, UploadFilled, UserFilled } from '@element-plus/icons-vue';
|
|
|
+import { getToken } from '@/utils/auth';
|
|
|
+import download from '@/plugins/download';
|
|
|
+import { listDept } from '@/api/system/dept';
|
|
|
+import { getDictOptions } from '@/api/riskctrl/dictData';
|
|
|
+import {
|
|
|
+ listAuditPersonChange,
|
|
|
+ getAuditPersonChange,
|
|
|
+ delAuditPersonChange,
|
|
|
+ addAuditPersonChange,
|
|
|
+ updateAuditPersonChange
|
|
|
+} from '@/api/riskctrl/auditPersonChange';
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
|
-const changeList = ref<AuditPersonChangeVO[]>([]);
|
|
|
-const loading = ref(true);
|
|
|
-const showSearch = ref(true);
|
|
|
-const ids = ref<Array<string | number>>([]);
|
|
|
-const single = ref(true);
|
|
|
-const multiple = ref(true);
|
|
|
-const total = ref(0);
|
|
|
-const title = ref('');
|
|
|
+const queryFormRef = ref<ElFormInstance>();
|
|
|
+const changeFormRef = ref<ElFormInstance>();
|
|
|
+
|
|
|
+const loading = ref(false);
|
|
|
const open = ref(false);
|
|
|
const uploadFileOpen = ref(false);
|
|
|
+const total = ref(0);
|
|
|
+const multiple = ref(true);
|
|
|
+const ids = ref<Array<string | number>>([]);
|
|
|
+const changeList = ref<any[]>([]);
|
|
|
+const uploadQueue = ref<any[]>([]);
|
|
|
const auditFileList = ref<any[]>([]);
|
|
|
+const employeeOptions = ref<any[]>([]);
|
|
|
+const deptOptions = ref<any[]>([]);
|
|
|
+const auditChangeTypeOptions = ref<any[]>([]);
|
|
|
+
|
|
|
+const dialogMode = ref<'add' | 'edit' | 'view'>('add');
|
|
|
+const isViewMode = computed(() => dialogMode.value === 'view');
|
|
|
+const dialogTitle = computed(() => {
|
|
|
+ if (dialogMode.value === 'add') return '新增人员变动';
|
|
|
+ if (dialogMode.value === 'edit') return '修改人员变动';
|
|
|
+ return '查看人员变动';
|
|
|
+});
|
|
|
+
|
|
|
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload');
|
|
|
+const uploadHeaders = ref({
|
|
|
+ Authorization: 'Bearer ' + getToken(),
|
|
|
+ clientid: import.meta.env.VITE_APP_CLIENT_ID
|
|
|
+});
|
|
|
+
|
|
|
+const queryParams = reactive<any>({
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ changeType: undefined,
|
|
|
+ departmentId: undefined,
|
|
|
+ keyword: undefined
|
|
|
+});
|
|
|
|
|
|
-// 统计数据
|
|
|
const statsData = reactive({
|
|
|
totalChanges: 0,
|
|
|
pendingAudit: 0,
|
|
|
@@ -255,150 +397,717 @@ const statsData = reactive({
|
|
|
fileTrend: 0
|
|
|
});
|
|
|
|
|
|
-const queryFormRef = ref<ElFormInstance>();
|
|
|
-const changeFormRef = ref<ElFormInstance>();
|
|
|
-
|
|
|
-const queryParams = reactive<AuditPersonChangeQuery & { department?: string }>({
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
- employeeName: undefined,
|
|
|
- department: undefined,
|
|
|
- changeType: undefined,
|
|
|
- auditStatus: undefined
|
|
|
-});
|
|
|
-
|
|
|
-const form = reactive<AuditPersonChangeForm & { newDepartment?: string }>({
|
|
|
+const form = reactive<any>({
|
|
|
changeId: undefined,
|
|
|
+ employeeId: undefined,
|
|
|
employeeName: '',
|
|
|
- department: '',
|
|
|
+ employeeCode: '',
|
|
|
+ departmentId: undefined,
|
|
|
+ departmentName: '',
|
|
|
oldPosition: '',
|
|
|
- newPosition: '',
|
|
|
changeType: '',
|
|
|
changeDate: '',
|
|
|
+ transferDepartmentId: undefined,
|
|
|
+ transferDepartmentName: '',
|
|
|
+ transferPosition: '',
|
|
|
+ auditStatus: '待审计',
|
|
|
remark: ''
|
|
|
});
|
|
|
|
|
|
-const rules = {
|
|
|
- employeeName: [{ required: true, message: '员工姓名不能为空', trigger: 'blur' }],
|
|
|
- changeType: [{ required: true, message: '变动类型不能为空', trigger: 'change' }]
|
|
|
-};
|
|
|
-
|
|
|
-const columns = ref([
|
|
|
- { key: 0, label: '员工姓名', visible: true },
|
|
|
- { key: 1, label: '部门', visible: true },
|
|
|
- { key: 2, label: '原岗位', visible: true },
|
|
|
- { key: 3, label: '变动后岗位', visible: true },
|
|
|
- { key: 4, label: '变动类型', visible: true },
|
|
|
- { key: 5, label: '变动日期', visible: true },
|
|
|
- { key: 6, label: '审计状态', visible: true },
|
|
|
- { key: 7, label: '创建时间', visible: true }
|
|
|
-]);
|
|
|
-
|
|
|
-const onChangeType = (val: string) => {
|
|
|
- if (val === '离职') {
|
|
|
- form.newPosition = '-';
|
|
|
- } else {
|
|
|
- form.newPosition = '';
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const getList = async () => {
|
|
|
- loading.value = true;
|
|
|
- const res = await listAuditPersonChange(queryParams);
|
|
|
- changeList.value = res.rows;
|
|
|
- total.value = res.total;
|
|
|
- loading.value = false;
|
|
|
-};
|
|
|
+const changeTypeLabel = computed(() => getDictLabel(auditChangeTypeOptions.value, form.changeType));
|
|
|
+const isTransferType = computed(() => changeTypeLabel.value === '调任');
|
|
|
+const isLeaveType = computed(() => changeTypeLabel.value === '离职');
|
|
|
+const changeDateLabel = computed(() => (isLeaveType.value ? '离职日期' : '变动日期'));
|
|
|
|
|
|
-const cancel = () => {
|
|
|
- open.value = false;
|
|
|
- reset();
|
|
|
-};
|
|
|
+const rules = computed(() => ({
|
|
|
+ employeeId: [{ required: true, message: '请选择员工', trigger: 'change' }],
|
|
|
+ changeType: [{ required: true, message: '请选择变动类型', trigger: 'change' }],
|
|
|
+ changeDate: [{ required: true, message: '请选择日期', trigger: 'change' }],
|
|
|
+ transferDepartmentId: isTransferType.value ? [{ required: true, message: '请选择调任部门', trigger: 'change' }] : [],
|
|
|
+ transferPosition: isTransferType.value ? [{ required: true, message: '请输入调任岗位', trigger: 'blur' }] : [],
|
|
|
+ remark: [{ required: true, message: '请输入变动原因', trigger: 'blur' }]
|
|
|
+}));
|
|
|
|
|
|
-const reset = () => {
|
|
|
+const resetForm = () => {
|
|
|
form.changeId = undefined;
|
|
|
+ form.employeeId = undefined;
|
|
|
form.employeeName = '';
|
|
|
- form.department = '';
|
|
|
+ form.employeeCode = '';
|
|
|
+ form.departmentId = undefined;
|
|
|
+ form.departmentName = '';
|
|
|
form.oldPosition = '';
|
|
|
- form.newPosition = '';
|
|
|
form.changeType = '';
|
|
|
form.changeDate = '';
|
|
|
- form.newDepartment = '';
|
|
|
+ form.transferDepartmentId = undefined;
|
|
|
+ form.transferDepartmentName = '';
|
|
|
+ form.transferPosition = '';
|
|
|
+ form.auditStatus = '待审计';
|
|
|
form.remark = '';
|
|
|
- nextTick(() => { changeFormRef.value?.resetFields(); });
|
|
|
+ nextTick(() => changeFormRef.value?.clearValidate());
|
|
|
+};
|
|
|
+
|
|
|
+const getList = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const res: any = await listAuditPersonChange(queryParams);
|
|
|
+ changeList.value = res.rows || [];
|
|
|
+ total.value = res.total || 0;
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const loadStats = async () => {
|
|
|
+ try {
|
|
|
+ const res: any = await listAuditPersonChange({ pageNum: 1, pageSize: 9999 });
|
|
|
+ const allData = res.rows || [];
|
|
|
+ statsData.totalChanges = allData.length;
|
|
|
+ statsData.pendingAudit = allData.filter(
|
|
|
+ (d: any) => getDictLabel(auditChangeTypeOptions.value, d.changeType) === '离职' && d.auditStatus === '待审计'
|
|
|
+ ).length;
|
|
|
+ statsData.completedTransfer = allData.filter(
|
|
|
+ (d: any) => getDictLabel(auditChangeTypeOptions.value, d.changeType) === '调任' && d.auditStatus === '已完成'
|
|
|
+ ).length;
|
|
|
+ statsData.totalFiles = auditFileList.value.length;
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
-const handleQuery = () => { queryParams.pageNum = 1; getList(); };
|
|
|
-const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery(); };
|
|
|
+const loadDeptOptions = async () => {
|
|
|
+ const res: any = await listDept();
|
|
|
+ const flat: any[] = [];
|
|
|
+
|
|
|
+ const walk = (nodes: any[]) => {
|
|
|
+ (nodes || []).forEach((n) => {
|
|
|
+ flat.push({
|
|
|
+ label: n.label || n.deptName || n.name,
|
|
|
+ value: n.id || n.deptId
|
|
|
+ });
|
|
|
+ if (n.children?.length) walk(n.children);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ walk(res.data || []);
|
|
|
+ deptOptions.value = flat;
|
|
|
+};
|
|
|
+
|
|
|
+const loadEmployeeOptions = async () => {
|
|
|
+ // TODO:后续要替换为员工查询接口
|
|
|
+ employeeOptions.value = [
|
|
|
+ { employeeId: 1, employeeName: '张明', employeeCode: 'E1001', departmentId: 10, departmentName: '技术部', oldPosition: '高级开发工程师' },
|
|
|
+ { employeeId: 2, employeeName: '李婷', employeeCode: 'E1002', departmentId: 20, departmentName: '财务部', oldPosition: '财务总监' }
|
|
|
+ ];
|
|
|
+};
|
|
|
+
|
|
|
+const getDicts = async () => {
|
|
|
+ const changeRes = await getDictOptions('audit_change_type');
|
|
|
+ auditChangeTypeOptions.value = changeRes || [];
|
|
|
+};
|
|
|
+
|
|
|
+const handleQuery = async () => {
|
|
|
+ queryParams.pageNum = 1;
|
|
|
+ await getList();
|
|
|
+};
|
|
|
+
|
|
|
+const resetQuery = async () => {
|
|
|
+ queryFormRef.value?.resetFields();
|
|
|
+ queryParams.changeType = undefined;
|
|
|
+ queryParams.departmentId = undefined;
|
|
|
+ queryParams.keyword = undefined;
|
|
|
+ queryParams.pageNum = 1;
|
|
|
+ await getList();
|
|
|
+};
|
|
|
|
|
|
-const handleSelectionChange = (selection: AuditPersonChangeVO[]) => {
|
|
|
+const handleSelectionChange = (selection: any[]) => {
|
|
|
ids.value = selection.map((item) => item.changeId);
|
|
|
- single.value = selection.length !== 1;
|
|
|
- multiple.value = !selection.length;
|
|
|
+ multiple.value = selection.length === 0;
|
|
|
};
|
|
|
|
|
|
-const handleAdd = () => { reset(); open.value = true; title.value = '新增人员变动'; };
|
|
|
+const handleEmployeeChange = (employeeId: string | number) => {
|
|
|
+ const emp = employeeOptions.value.find((item) => String(item.employeeId) === String(employeeId));
|
|
|
+ if (!emp) return;
|
|
|
+ form.employeeName = emp.employeeName;
|
|
|
+ form.employeeCode = emp.employeeCode;
|
|
|
+ form.departmentId = emp.departmentId;
|
|
|
+ form.departmentName = emp.departmentName;
|
|
|
+ form.oldPosition = emp.oldPosition;
|
|
|
+};
|
|
|
+
|
|
|
+const handleTransferDeptChange = (deptId: string | number) => {
|
|
|
+ const dept = deptOptions.value.find((item) => String(item.value) === String(deptId));
|
|
|
+ form.transferDepartmentName = dept?.label || '';
|
|
|
+};
|
|
|
|
|
|
-const handleUpdate = async (row?: AuditPersonChangeVO) => {
|
|
|
- reset();
|
|
|
+const onChangeType = () => {
|
|
|
+ if (isTransferType.value) {
|
|
|
+ form.transferDepartmentId = form.transferDepartmentId || undefined;
|
|
|
+ form.transferPosition = form.transferPosition || '';
|
|
|
+ } else {
|
|
|
+ form.transferDepartmentId = undefined;
|
|
|
+ form.transferDepartmentName = '';
|
|
|
+ form.transferPosition = '';
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleAdd = () => {
|
|
|
+ dialogMode.value = 'add';
|
|
|
+ resetForm();
|
|
|
+ open.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleView = async (row: any) => {
|
|
|
+ dialogMode.value = 'view';
|
|
|
+ resetForm();
|
|
|
+ const res: any = await getAuditPersonChange(row.changeId);
|
|
|
+ const data = res.data || res || {};
|
|
|
+ Object.assign(form, data);
|
|
|
+ open.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleUpdate = async (row?: any) => {
|
|
|
+ dialogMode.value = 'edit';
|
|
|
+ resetForm();
|
|
|
const changeId = row?.changeId || ids.value[0];
|
|
|
- const res = await getAuditPersonChange(changeId);
|
|
|
- Object.assign(form, res.data);
|
|
|
+ const res: any = await getAuditPersonChange(changeId);
|
|
|
+ Object.assign(form, res.data || res || {});
|
|
|
open.value = true;
|
|
|
- title.value = '修改人员变动';
|
|
|
+};
|
|
|
+
|
|
|
+const cancel = () => {
|
|
|
+ open.value = false;
|
|
|
+ resetForm();
|
|
|
};
|
|
|
|
|
|
const submitForm = () => {
|
|
|
changeFormRef.value?.validate(async (valid: boolean) => {
|
|
|
- if (valid) {
|
|
|
- if (form.changeId !== undefined) {
|
|
|
- await updateAuditPersonChange(form as AuditPersonChangeForm);
|
|
|
- proxy?.$modal.msgSuccess('修改成功');
|
|
|
- } else {
|
|
|
- await addAuditPersonChange(form as AuditPersonChangeForm);
|
|
|
- proxy?.$modal.msgSuccess('新增成功');
|
|
|
- }
|
|
|
- open.value = false;
|
|
|
- await getList();
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ const payload: any = {
|
|
|
+ ...form,
|
|
|
+ changeType: form.changeType,
|
|
|
+ changeDate: form.changeDate,
|
|
|
+ transferDepartmentName: isTransferType.value ? form.transferDepartmentName : '',
|
|
|
+ transferPosition: isTransferType.value ? form.transferPosition : '',
|
|
|
+ transferDepartmentId: isTransferType.value ? form.transferDepartmentId : undefined
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!isTransferType.value) {
|
|
|
+ payload.transferDepartmentId = undefined;
|
|
|
+ payload.transferDepartmentName = '';
|
|
|
+ payload.transferPosition = '';
|
|
|
}
|
|
|
+
|
|
|
+ if (payload.changeId !== undefined) {
|
|
|
+ await updateAuditPersonChange(payload);
|
|
|
+ proxy?.$modal.msgSuccess('修改成功');
|
|
|
+ } else {
|
|
|
+ await addAuditPersonChange(payload);
|
|
|
+ proxy?.$modal.msgSuccess('新增成功');
|
|
|
+ }
|
|
|
+
|
|
|
+ open.value = false;
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const handleDelete = async (row?: AuditPersonChangeVO) => {
|
|
|
+const handleDelete = async (row?: any) => {
|
|
|
const changeIds = row?.changeId || ids.value;
|
|
|
await proxy?.$modal.confirm('是否确认删除该人员变动记录?');
|
|
|
await delAuditPersonChange(changeIds);
|
|
|
- await getList();
|
|
|
proxy?.$modal.msgSuccess('删除成功');
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
};
|
|
|
|
|
|
-// 加载统计数据
|
|
|
-const loadStats = async () => {
|
|
|
- try {
|
|
|
- const res = await listAuditPersonChange({ pageNum: 1, pageSize: 9999 } as any);
|
|
|
- const allData = (res as any).rows || [];
|
|
|
- statsData.totalChanges = allData.length;
|
|
|
- statsData.pendingAudit = allData.filter((d: any) => d.auditStatus === '待审计').length;
|
|
|
- statsData.completedTransfer = allData.filter((d: any) => d.changeType === '调任' && d.auditStatus === '已完成').length;
|
|
|
- } catch (e) {
|
|
|
- console.error('加载统计数据失败', e);
|
|
|
+const handleExport = () => {
|
|
|
+ // 替换成你后端实际导出接口
|
|
|
+ download.zip('/riskctrl/auditPersonChange/export', '审计工作管理导出.zip');
|
|
|
+};
|
|
|
+
|
|
|
+const beforeUpload = (file: File) => {
|
|
|
+ const allow = [
|
|
|
+ 'application/pdf',
|
|
|
+ 'application/msword',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
|
+ 'application/vnd.ms-excel',
|
|
|
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
+ 'image/png',
|
|
|
+ 'image/jpeg'
|
|
|
+ ];
|
|
|
+ if (!allow.includes(file.type)) {
|
|
|
+ proxy?.$modal.msgError('仅支持 PDF、Word、Excel、图片文件');
|
|
|
+ return false;
|
|
|
}
|
|
|
+ if (file.size / 1024 / 1024 > 20) {
|
|
|
+ proxy?.$modal.msgError('单文件不能超过 20MB');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleUploadExceed = () => {
|
|
|
+ proxy?.$modal.msgWarning('最多只能上传 10 个文件');
|
|
|
};
|
|
|
|
|
|
-// 提交审计文件
|
|
|
-const submitAuditFiles = () => {
|
|
|
- if (auditFileList.value.length === 0) {
|
|
|
- proxy?.$modal.msgWarning('请选择要上传的文件');
|
|
|
+const handleUploadSuccess = async (response: any, file: any) => {
|
|
|
+ if (response?.code !== 200 && response?.data?.ossId == null && response?.data?.url == null) {
|
|
|
+ proxy?.$modal.msgError(response?.msg || '上传失败');
|
|
|
return;
|
|
|
}
|
|
|
- statsData.totalFiles += auditFileList.value.length;
|
|
|
- proxy?.$modal.msgSuccess(`成功上传 ${auditFileList.value.length} 个审计文件`);
|
|
|
- uploadFileOpen.value = false;
|
|
|
- auditFileList.value = [];
|
|
|
+
|
|
|
+ const item = {
|
|
|
+ ossId: response?.data?.ossId || response?.data?.id || response?.ossId || Date.now(),
|
|
|
+ fileName: file?.name || response?.data?.fileName || response?.fileName || '未命名文件',
|
|
|
+ fileSize: file?.size || response?.data?.fileSize || 0,
|
|
|
+ createTime: response?.data?.createTime || new Date().toISOString()
|
|
|
+ };
|
|
|
+
|
|
|
+ auditFileList.value.unshift(item);
|
|
|
+ statsData.totalFiles = auditFileList.value.length;
|
|
|
+ proxy?.$modal.msgSuccess('上传成功');
|
|
|
+};
|
|
|
+
|
|
|
+const handleUploadRemove = () => {};
|
|
|
+
|
|
|
+const handleRemoveFile = async (row: any) => {
|
|
|
+ await proxy?.$modal.confirm(`是否确认删除文件 ${row.fileName}?`);
|
|
|
+ auditFileList.value = auditFileList.value.filter((item) => item.ossId !== row.ossId);
|
|
|
+ statsData.totalFiles = auditFileList.value.length;
|
|
|
+ proxy?.$modal.msgSuccess('删除成功');
|
|
|
+};
|
|
|
+
|
|
|
+const getDictLabel = (options: any[], value: any) => {
|
|
|
+ const item = options.find((d) => String(d.value) === String(value));
|
|
|
+ return item?.label || value || '';
|
|
|
+};
|
|
|
+
|
|
|
+const getChangeTypeLabel = (value: any) => getDictLabel(auditChangeTypeOptions.value, value);
|
|
|
+
|
|
|
+const getChangeTypeClass = (val: any) => {
|
|
|
+ const label = getChangeTypeLabel(val);
|
|
|
+ if (label === '调任') return 'tag-blue';
|
|
|
+ if (label === '离职') return 'tag-red';
|
|
|
+ return 'tag-gray';
|
|
|
+};
|
|
|
+
|
|
|
+const getAuditStatusClass = (val: string) => {
|
|
|
+ if (val === '已完成') return 'tag-green';
|
|
|
+ if (val === '待审计') return 'tag-orange';
|
|
|
+ return 'tag-gray';
|
|
|
+};
|
|
|
+
|
|
|
+const trendClass = (val: number) => {
|
|
|
+ if (val > 0) return 'trend-up';
|
|
|
+ if (val < 0) return 'trend-down';
|
|
|
+ return 'trend-flat';
|
|
|
+};
|
|
|
+
|
|
|
+const trendArrow = (val: number) => (val > 0 ? '↑' : val < 0 ? '↓' : '—');
|
|
|
+const trendText = (val: number) => {
|
|
|
+ if (val > 0) return `较上月 ${Math.abs(val)}%`;
|
|
|
+ if (val < 0) return `较上月 ${Math.abs(val)}%`;
|
|
|
+ return '持平';
|
|
|
+};
|
|
|
+
|
|
|
+const getFileExtLabel = (fileName: string) => {
|
|
|
+ const ext = (fileName || '').split('.').pop()?.toLowerCase() || '';
|
|
|
+ if (ext === 'pdf') return 'PDF';
|
|
|
+ if (ext === 'doc' || ext === 'docx') return 'DOC';
|
|
|
+ if (ext === 'xls' || ext === 'xlsx') return 'XLS';
|
|
|
+ if (ext === 'png' || ext === 'jpg' || ext === 'jpeg') return 'IMG';
|
|
|
+ return 'FILE';
|
|
|
+};
|
|
|
+
|
|
|
+const getFileIconClass = (fileName: string) => {
|
|
|
+ const ext = (fileName || '').split('.').pop()?.toLowerCase() || '';
|
|
|
+ if (ext === 'pdf') return 'file-pdf';
|
|
|
+ if (ext === 'doc' || ext === 'docx') return 'file-doc';
|
|
|
+ if (ext === 'xls' || ext === 'xlsx') return 'file-xls';
|
|
|
+ if (ext === 'png' || ext === 'jpg' || ext === 'jpeg') return 'file-img';
|
|
|
+ return 'file-other';
|
|
|
+};
|
|
|
+
|
|
|
+const formatFileSize = (size: number) => {
|
|
|
+ if (!size) return '-';
|
|
|
+ if (size < 1024) return `${size}B`;
|
|
|
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(0)}KB`;
|
|
|
+ return `${(size / 1024 / 1024).toFixed(1)}MB`;
|
|
|
};
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- getList();
|
|
|
- loadStats();
|
|
|
+const formatDate = (val: string) => {
|
|
|
+ if (!val) return '-';
|
|
|
+ return String(val).slice(0, 10);
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await Promise.all([getList(), loadStats(), loadDeptOptions(), loadEmployeeOptions(), getDicts()]);
|
|
|
});
|
|
|
</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.audit-page {
|
|
|
+ padding: 16px;
|
|
|
+ background: #f5f7fb;
|
|
|
+ min-height: calc(100vh - 50px);
|
|
|
+}
|
|
|
+
|
|
|
+.page-header {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-title {
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-desc {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-row {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card,
|
|
|
+.search-card,
|
|
|
+.table-card,
|
|
|
+.file-card {
|
|
|
+ border-radius: 14px;
|
|
|
+ border: none;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-inner {
|
|
|
+ min-height: 88px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #64748b;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 34px;
|
|
|
+ font-weight: 700;
|
|
|
+ line-height: 1;
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value-orange {
|
|
|
+ color: #f97316;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value-green {
|
|
|
+ color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value-purple {
|
|
|
+ color: #8b5cf6;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-sub {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-up {
|
|
|
+ color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-down {
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.trend-flat {
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 12px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 22px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-blue {
|
|
|
+ background: #e7efff;
|
|
|
+ color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-orange {
|
|
|
+ background: #ffedd5;
|
|
|
+ color: #f97316;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-green {
|
|
|
+ background: #dcfce7;
|
|
|
+ color: #16a34a;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-purple {
|
|
|
+ background: #ede9fe;
|
|
|
+ color: #8b5cf6;
|
|
|
+}
|
|
|
+
|
|
|
+.search-card,
|
|
|
+.table-card,
|
|
|
+.file-card {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-form {
|
|
|
+ width: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-select {
|
|
|
+ width: 140px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-keyword {
|
|
|
+ width: 210px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-actions {
|
|
|
+ margin-left: auto;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+}
|
|
|
+
|
|
|
+.primary-btn {
|
|
|
+ background: #2563eb;
|
|
|
+ border-color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-pill {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 72px;
|
|
|
+ height: 28px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border-radius: 999px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-blue {
|
|
|
+ background: #dbeafe;
|
|
|
+ color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-red {
|
|
|
+ background: #fee2e2;
|
|
|
+ color: #dc2626;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-green {
|
|
|
+ background: #dcfce7;
|
|
|
+ color: #16a34a;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-orange {
|
|
|
+ background: #ffedd5;
|
|
|
+ color: #ea580c;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-gray {
|
|
|
+ background: #f1f5f9;
|
|
|
+ color: #64748b;
|
|
|
+}
|
|
|
+
|
|
|
+.file-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.file-item {
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 10px;
|
|
|
+ padding: 14px;
|
|
|
+ background: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.file-left {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.file-icon {
|
|
|
+ width: 42px;
|
|
|
+ height: 42px;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-weight: 700;
|
|
|
+ font-size: 12px;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.file-pdf {
|
|
|
+ background: #fee2e2;
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.file-doc {
|
|
|
+ background: #dbeafe;
|
|
|
+ color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.file-xls {
|
|
|
+ background: #dcfce7;
|
|
|
+ color: #16a34a;
|
|
|
+}
|
|
|
+
|
|
|
+.file-img {
|
|
|
+ background: #ede9fe;
|
|
|
+ color: #8b5cf6;
|
|
|
+}
|
|
|
+
|
|
|
+.file-other {
|
|
|
+ background: #f1f5f9;
|
|
|
+ color: #64748b;
|
|
|
+}
|
|
|
+
|
|
|
+.file-meta {
|
|
|
+ min-width: 0;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.file-name {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1f2937;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+}
|
|
|
+
|
|
|
+.file-desc {
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #64748b;
|
|
|
+}
|
|
|
+
|
|
|
+.file-actions {
|
|
|
+ margin-top: 8px;
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.person-dialog {
|
|
|
+ :deep(.el-dialog__header) {
|
|
|
+ margin-right: 0;
|
|
|
+ padding: 16px 20px 10px;
|
|
|
+ border-bottom: 1px solid #eef2f7;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__body) {
|
|
|
+ padding: 18px 20px 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__footer) {
|
|
|
+ padding: 14px 20px 20px;
|
|
|
+ border-top: 1px solid #eef2f7;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.audit-dialog {
|
|
|
+ :deep(.el-dialog__header) {
|
|
|
+ margin-right: 0;
|
|
|
+ padding: 16px 20px 12px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__body) {
|
|
|
+ padding: 18px 20px 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__footer) {
|
|
|
+ padding: 12px 20px 18px;
|
|
|
+ border-top: 1px solid #f1f5f9;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.person-form {
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ padding-bottom: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__wrapper),
|
|
|
+ :deep(.el-select__wrapper),
|
|
|
+ :deep(.el-textarea__inner) {
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-form-item.is-required > .el-form-item__label:before) {
|
|
|
+ color: #f56c6c;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-card__body) {
|
|
|
+ padding: 18px 18px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-form-item) {
|
|
|
+ margin-bottom: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 1200px) {
|
|
|
+ .file-grid {
|
|
|
+ grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .file-grid {
|
|
|
+ grid-template-columns: 1fr;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-actions {
|
|
|
+ margin-left: 0;
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-select,
|
|
|
+ .search-keyword {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|