<template>
<el-main>
<!-- 查询条件 -->
<el-form
:model="searchModel"
ref="searchForm"
label-width="80px"
:inline="true"
size="small"
>
<el-form-item>
<el-input
v-model="searchModel.name"
placeholder="请输入姓名"
style="width: 220px"
></el-input>
</el-form-item>
<el-form-item>
<el-input
v-model="searchModel.category"
placeholder="请输入事项类别"
style="width: 220px"
></el-input>
</el-form-item>
<!-- 审核状态下拉框查询条件 -->
<el-form-item label="审核状态">
<el-select
v-model="searchModel.auditStatus"
placeholder="全部"
style="width: 180px"
@change="search()"
>
<el-option label="全部" value="" />
<el-option label="待审核" value="0" />
<el-option label="已初审" value="1" />
<el-option label="初审驳回" value="2" />
<el-option label="已终审" value="3" />
<el-option label="终审驳回" value="4" />
</el-select>
</el-form-item>
<el-form-item>
<div style="display: flex; align-items: center; gap: 10px">
<!-- 查询/重置/新增按钮组 -->
<div>
<el-button type="primary" icon="el-icon-search" @click="search()"
>查询</el-button
>
<el-button icon="el-icon-refresh-right" @click="resetValue()"
>重置</el-button
>
<el-button
type="success"
icon="el-icon-plus"
@click="openAddWindow()"
>积分申请</el-button
>
</div>
<!-- 导出/导入按钮组 -->
<div style="display: flex; gap: 10px; margin-left: 10px">
<el-button
type="primary"
icon="el-icon-download"
@click="exportExcel()"
>导出Excel</el-button
>
<el-upload
:action="importExcelUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="importExcel"
:before-upload="beforeUpload"
>
<el-button type="success" icon="el-icon-upload2"
>导入Excel</el-button
>
</el-upload>
</div>
</div>
</el-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
:data="scoreDetailList"
:height="tableHeight"
border
stripe
class="el-table-ellipsis"
style="width: 100%; margin-bottom: 10px"
show-summary
:summary-method="getSummaries"
>
<!-- 原有列保持不变 -->
<el-table-column
prop="id"
label="序号"
width="80"
align="center"
fixed="left"
></el-table-column>
<el-table-column
prop="recordDate"
label="日期"
width="120"
header-align="center"
align="center"
fixed="left"
></el-table-column>
<el-table-column
prop="name"
label="姓名"
width="100"
header-align="center"
align="center"
fixed="left"
></el-table-column>
<!-- 新增图像列 -->
<el-table-column
prop="avatar"
label="图像"
width="120"
align="center"
header-align="center"
>
<template slot-scope="scope">
<el-image
v-if="scope.row.avatar"
style="width: 50px; height: 50px; border-radius: 10%"
:src="getAvatarUrl(scope.row.avatar)"
:preview-src-list="[getAvatarUrl(scope.row.avatar)]"
@error="() => handleImageError(scope.row)"
>
<div slot="error" class="image-error" />
</el-image>
</template>
</el-table-column>
<!-- 剩余列保持不变 -->
<el-table-column
prop="category"
label="事项类别"
width="180"
header-align="center"
></el-table-column>
<el-table-column
prop="score"
label="奖惩积分"
width="120"
header-align="center"
align="center"
></el-table-column>
<el-table-column
prop="originalScore"
label="原始分值"
width="120"
header-align="center"
align="center"
></el-table-column>
<el-table-column
prop="executor"
label="执行扣分人"
width="100"
header-align="center"
align="center"
></el-table-column>
<el-table-column
prop="errorType"
label="错误类型"
width="180"
header-align="center"
></el-table-column>
<el-table-column
prop="mistakeCount"
label="犯错次数"
width="80"
header-align="center"
align="center"
></el-table-column>
<el-table-column
prop="redEnvelope"
label="红包"
width="80"
header-align="center"
align="center"
></el-table-column>
<el-table-column
prop="details"
label="具体事情"
width="250"
header-align="center"
></el-table-column>
<el-table-column
prop="creator"
label="制单人"
width="120"
header-align="center"
></el-table-column>
<el-table-column
prop="createDate"
label="制单日期"
width="120"
header-align="center"
align="center"
></el-table-column>
<el-table-column
prop="remarks"
label="备注"
width="200"
header-align="center"
></el-table-column>
<el-table-column
prop="auditStatus"
label="审核状态"
width="100"
align="center"
header-align="center"
>
<template slot-scope="scope">
{{ formatAuditStatus(scope.row.auditStatus) }}
</template>
</el-table-column>
<el-table-column
prop="firstAuditOpinion"
label="初审意见"
width="200"
header-align="center"
></el-table-column>
<el-table-column
prop="finalAuditOpinion"
label="终审意见"
width="200"
header-align="center"
></el-table-column>
<!-- 操作列 -->
<el-table-column label="操作" align="center" width="400" fixed="right">
<template slot-scope="scope">
<!-- 编辑按钮:仅待审核、初审驳回、终审驳回时可见 -->
<el-button
icon="el-icon-edit"
type="primary"
size="small"
@click="handleEdit(scope.row)"
:disabled="scope.row.auditStatus === 3"
v-if="[0, 2, 4].includes(scope.row.auditStatus)"
>编辑</el-button
>
<!-- 删除按钮:仅待审核、终审驳回时可见 -->
<el-button
icon="el-icon-delete"
type="danger"
size="small"
@click="handleDelete(scope.row)"
:disabled="
scope.row.auditStatus === 1 || scope.row.auditStatus === 3
"
v-if="[0, 4].includes(scope.row.auditStatus)"
>删除</el-button
>
<!-- 初审按钮:仅待审核、初审驳回时可见,已初审状态显示为浅蓝色 -->
<el-button
icon="el-icon-edit"
:type="scope.row.auditStatus === 1 ? 'info' : 'primary'"
size="small"
@click="firstAudit(scope.row)"
v-if="
hasPermission('api:scoreDetail:audit:save') &&
[0, 1, 2].includes(scope.row.auditStatus)
"
>初审</el-button
>
<!-- 终审按钮:仅初审通过、终审驳回时可见,已终审状态显示为浅蓝色 -->
<el-button
icon="el-icon-edit"
:type="scope.row.auditStatus === 3 ? 'info' : 'success'"
size="small"
@click="finalAudit(scope.row)"
v-if="
hasPermission('api:scoreDetail:audit:save') &&
[1, 3, 4].includes(scope.row.auditStatus)
"
>终审</el-button
>
<!-- 行内上传图像按钮 -->
<el-upload
:action="uploadFileUrl"
:show-file-list="false"
:headers="uploadHeaders"
:before-upload="(file) => beforeAvatarUpload(file, scope.row)"
:on-success="
(response, file, fileList) =>
handleRowAvatarUpload(scope.row, response)
"
:on-error="
(error, file, fileList) =>
handleAvatarUploadError(scope.row, error)
"
style="display: inline-flex; align-items: center"
>
<el-button type="primary" size="small" icon="el-icon-upload"
>上传图像</el-button
>
</el-upload>
</template>
</el-table-column>
</el-table>
<!-- 分页工具栏 -->
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[10, 20, 30, 40, 50]"
:page-size="10"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
<!-- 添加和修改分数详情窗口 -->
<el-dialog
:title="scoreDetailDialog.title"
:visible.sync="scoreDetailDialog.visible"
width="80%"
>
<el-form
:model="scoreDetail"
ref="scoreDetailForm"
:rules="rules"
label-width="80px"
:inline="false"
size="small"
>
<!-- 第1行:增加奖惩类型单选按钮 -->
<el-row>
<el-col :span="24">
<el-form-item label="奖惩类型">
<el-radio-group
v-model="scoreType"
@change="handleScoreTypeChange"
>
<el-radio label="reward">奖分</el-radio>
<el-radio label="punish">扣分</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<!-- 第2行 -->
<el-row>
<el-col :span="6">
<el-form-item label="日期" prop="recordDate">
<el-date-picker
v-model="scoreDetail.recordDate"
type="date"
placeholder="选择日期"
style="width: 180px"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="姓名" prop="name">
<el-input
v-model="scoreDetail.name"
style="width: 150px"
></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="事项类别" prop="category">
<el-input
v-model="scoreDetail.category"
style="width: 180px"
></el-input>
<el-button
type="success"
size="small"
@click="openStandardDialog()"
style="margin-left: 5px"
>
积分标准
</el-button>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="奖惩积分" prop="score">
<el-input
v-model.number="scoreDetail.score"
style="width: 180px"
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 第3行 -->
<el-row>
<!-- 左侧12列区域 - 跨两行显示积分标准 -->
<el-col :span="12">
<el-form-item label="积分标准" prop="standard">
<!-- 使用文本域并设置合适的高度,使其视觉上占据两行空间 -->
<el-input
v-model="scoreDetail.standard"
type="textarea"
:rows="5"
style="width: 100%;" placeholder="请输入积分标准" ></el-input
>
</el-form-item>
</el-col>
<!-- 仅扣分时显示 -->
<el-col :span="4" v-if="scoreType === 'punish'">
<el-form-item label="原始分值" prop="originalScore">
<el-input
v-model.number="scoreDetail.originalScore"
style="width: 180px"
></el-input>
</el-form-item>
</el-col>
<el-col :span="4" v-if="scoreType === 'punish'">
<el-form-item label="犯错次数" prop="mistakeCount">
<el-input
v-model.number="scoreDetail.mistakeCount"
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
<el-col :span="4" v-if="scoreType === 'punish'">
<el-form-item label="红包" prop="redEnvelope">
<el-input
v-model.number="scoreDetail.redEnvelope"
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 第4行 -->
<el-row>
<el-col :span="12">
</el-col>
<!-- 仅扣分时显示 -->
<el-col :span="8" v-if="scoreType === 'punish'">
<el-form-item label="执行扣分人" prop="executor">
<el-input
v-model="scoreDetail.executor"
style="width: 200px"
@click.native="openPersonSelectDialog"
:readonly="true"
></el-input>
</el-form-item>
</el-col>
<el-col :span="4" v-if="scoreType === 'punish'">
<el-form-item label="错误类型" prop="errorType">
<el-input
v-model="scoreDetail.errorType"
style="width: 180px"
@click.native="openErrorTypeDialog"
:readonly="true"
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 第5行 -->
<el-row>
<el-col :span="12">
<el-form-item label="具体事情" prop="details">
<el-input
v-model="scoreDetail.details"
type="textarea"
:rows="4"
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<!-- 图像容器:整体居中 -->
<div
style="
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
"
>
<!-- 上传按钮:在图像上方居中 -->
<el-form-item
label="图像"
prop="avatar"
style="width: 100%; text-align: center; margin-bottom: 15px"
>
<el-upload
:action="uploadFileUrl"
:show-file-list="false"
:headers="uploadHeaders"
:before-upload="beforeAvatarUpload"
:on-success="handleFormAvatarUpload"
:on-error="handleAvatarUploadError"
style="display: inline-block"
>
<el-button type="primary" size="small" icon="el-icon-upload"
>上传图像</el-button
>
</el-upload>
</el-form-item>
<!-- 图像预览区:居中显示 -->
<div
style="
flex: 1;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
"
>
<el-image
v-if="scoreDetail.avatar"
style="width: 120px; height: 120px; border-radius: 10%"
:src="getAvatarUrl(scoreDetail.avatar)"
:preview-src-list="[getAvatarUrl(scoreDetail.avatar)]"
@error="handleFormImageError"
>
<div
slot="error"
class="image-error"
style="
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
"
/>
</el-image>
<!-- 无图像时的占位符:居中显示 -->
<div
v-else
style="
width: 120px;
height: 120px;
border-radius: 10%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
font-size: 14px;
"
>
暂无图像
</div>
</div>
</div>
</el-col>
</el-row>
<!-- 第6行 -->
<el-row>
<el-col :span="6">
<el-form-item label="制单人" prop="creator">
<el-input
v-model="scoreDetail.creator"
style="width: 180px"
></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="制单日期" prop="createDate">
<el-date-picker
v-model="scoreDetail.createDate"
type="date"
placeholder="选择日期"
style="width: 180px"
></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="scoreDetail.remarks"
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="审核状态" prop="auditStatus">
<!-- 显示汉字状态而非数字 -->
<el-input
v-model="currentAuditStatusText"
style="width: 100px"
disabled
></el-input>
</el-form-item>
</el-col>
</el-row>
<!-- 审核区域 -->
<div v-if="['初审', '终审'].includes(scoreDetailDialog.title)">
<el-row>
<el-col :span="8">
<el-form-item
label="审核意见"
prop="auditOpinion"
:rules="getAuditOpinionRules()"
>
<el-input
v-model="scoreDetail.auditOpinion"
type="textarea"
:rows="2"
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="审核人员">
<el-input
v-model="currentAuditUser"
disabled
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="审核时间">
<el-input
v-model="currentAuditTime"
disabled
style="width: 100%"
></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item>
<el-button type="success" @click="handleAuditApprove"
>审核通过</el-button
>
<el-button @click="handleAuditCancel">取消审核</el-button>
<el-button type="danger" @click="handleAuditReject"
>审核驳回</el-button
>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="scoreDetailDialog.visible = false">取消</el-button>
<el-button
type="primary"
@click="onConfirm"
v-if="
['新增分数详情', '编辑分数详情'].includes(scoreDetailDialog.title)
"
>确定</el-button
>
</div>
</el-dialog>
<!-- 积分标准选择窗口 -->
<el-dialog
:title="standardSelectDialog.title"
:visible.sync="standardSelectDialog.visible"
width="60%"
>
<!-- 保持不变 -->
<el-form
:model="standardSearchModel"
ref="standardSearchForm"
label-width="80px"
:inline="true"
size="small"
style="margin-bottom: 10px"
>
<el-form-item label="事项类别">
<el-input
v-model="standardSearchModel.category"
placeholder="请输入事项类别"
style="width: 220px"
></el-input>
</el-form-item>
<el-form-item label="积分标准">
<el-input
v-model="standardSearchModel.standard"
placeholder="请输入积分标准"
style="width: 220px"
></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
@click="searchStandards()"
>查询</el-button
>
</el-form-item>
</el-form>
<el-table
:data="standardList"
:height="standardTableHeight"
border
stripe
class="el-table-ellipsis"
style="width: 100%; margin-bottom: 10px"
>
<el-table-column
prop="id"
label="序号"
width="80"
align="center"
fixed="left"
></el-table-column>
<el-table-column
prop="category"
label="事项类别"
width="200"
header-align="center"
></el-table-column>
<el-table-column
prop="standard"
label="事项标准"
header-align="center"
></el-table-column>
<el-table-column
prop="score"
label="分值"
width="120"
header-align="center"
></el-table-column>
<el-table-column
prop="remarks"
label="备注"
header-align="center"
></el-table-column>
<!-- 操作列 -->
<el-table-column label="操作" align="center" width="120" fixed="right">
<template slot-scope="scope">
<el-button
icon="el-icon-check"
type="success"
size="small"
@click="selectStandard(scope.row)"
>选择</el-button
>
</template>
</el-table-column>
</el-table>
<!-- 积分标准选择窗口分页工具栏 -->
<el-pagination
v-if="standardTotal > 0"
background
@size-change="handleStandardSizeChange"
@current-change="handleStandardCurrentChange"
:current-page="standardPageNum"
:page-sizes="[10, 20, 30, 40, 50]"
:page-size="standardPageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="standardTotal"
></el-pagination>
<div slot="footer" class="dialog-footer">
<el-button @click="standardSelectDialog.visible = false"
>取消</el-button
>
</div>
</el-dialog>
<!-- 新增:选择执行扣分人对话框 -->
<el-dialog
:title="personSelectDialog.title"
:visible.sync="personSelectDialog.visible"
width="20%"
>
<el-form
:model="personSearchModel"
ref="personSearchForm"
label-width="80px"
:inline="true"
size="small"
style="margin-bottom: 10px"
>
<el-form-item label="人员姓名">
<el-input
v-model="personSearchModel.name"
placeholder="请输入人员姓名"
style="width: 220px"
></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
@click="searchPersons()"
>查询</el-button
>
</el-form-item>
</el-form>
<el-table
:data="personList"
height="400"
border
stripe
class="el-table-ellipsis"
style="width: 100%; margin-bottom: 10px"
>
<el-table-column
prop="id"
label="序号"
width="80"
align="center"
></el-table-column>
<el-table-column
prop="name"
label="姓名"
width="120"
header-align="center"
>
<template slot-scope="scope">
<el-button type="text" @click="selectPerson(scope.row)">
{{ scope.row.name }}
</el-button>
</template>
</el-table-column>
<!-- 操作列 -->
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="personSelectDialog.visible = false">取消</el-button>
</div>
</el-dialog>
<!-- 新增:错误类型选择对话框 -->
<el-dialog
:title="errorTypeDialog.title"
:visible.sync="errorTypeDialog.visible"
width="30%"
>
<el-form
:model="errorTypeSearchModel"
ref="errorTypeSearchForm"
label-width="80px"
:inline="true"
size="small"
style="margin-bottom: 10px"
>
<el-form-item label="错误类型">
<el-input
v-model="errorTypeSearchModel.name"
placeholder="请输入错误类型"
style="width: 220px"
></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="el-icon-search"
@click="searchErrorTypes()"
>查询</el-button
>
</el-form-item>
</el-form>
<el-table
:data="errorTypeList"
height="400"
border
stripe
style="width: 100%; margin-bottom: 10px"
>
<el-table-column
prop="id"
label="序号"
width="80"
align="center"
></el-table-column>
<el-table-column prop="name" label="错误类型" header-align="center">
<!-- 修改:点击文本即可选择 -->
<template slot-scope="scope">
<el-button type="text" @click="selectErrorType(scope.row)">
{{ scope.row.name }}
</el-button>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="errorTypeDialog.visible = false">取消</el-button>
</div>
</el-dialog>
</el-main>
</template>
<script>
import {
getScoreDetails,
addScoreDetail,
updateScoreDetail,
deleteScoreDetail,
getErrorCount,
getScoreErrorTypes,
} from "@/api/company/hr/scoreDetail";
import { getScoreStandards } from "@/api/company/hr/scoreStandard";
import { getScorePersons } from "@/api/company/hr/scorePerson";
import { getToken, getUsername } from "@/utils/auth";
import axios from "axios";
export default {
name: "scoreDetailList",
data() {
return {
// 新增:奖惩类型,默认奖分
scoreType: "reward",
// 错误类型对话框相关数据
errorTypeDialog: {
title: "选择错误类型",
visible: false,
},
errorTypeSearchModel: {
errorType: "",
pageNum: 1,
pageSize: 10,
},
errorTypeList: [],
errorTypePageNum: 1,
errorTypePageSize: 10,
errorTypeTotal: 0,
// 当前审核记录在列表中的索引(用于自动跳转)
currentAuditIndex: -1,
// 审核状态映射(数字转汉字)
auditStatusMap: {
0: "待审核",
1: "已初审",
2: "初审驳回",
3: "已终审",
4: "终审驳回",
},
// 查询条件
searchModel: {
name: "",
category: "",
auditStatus: "", // 审核状态:空-全部,0-待审核,1-已初审,2-初审驳回,3-已终审,4-终审驳回
pageNum: 1,
pageSize: 10,
},
// 表格数据
scoreDetailList: [],
tableHeight: 0,
pageNum: 1,
pageSize: 10,
total: 0,
// 分数详情表单
scoreDetail: {
id: "",
recordDate: new Date(),
name: this.$store.getters.name, // 当前登录用户
category: "",
score: "",
originalScore: "",
executor: "",
errorType: "",
mistakeCount: "",
redEnvelope: "",
details: "",
creator: "",
createDate: new Date(),
remarks: "",
auditStatus: 0,
auditOpinion: "",
firstAuditUser: "",
firstAuditTime: "",
finalAuditUser: "",
finalAuditTime: "",
avatar: "", // 图像字段
},
// 积分标准选择对话框数据
standardSelectDialog: {
title: "选择积分标准",
visible: false,
},
standardSearchModel: {
category: "",
standard: "",
pageNum: 1,
pageSize: 10,
},
standardList: [],
standardTableHeight: 0,
standardPageNum: 1,
standardPageSize: 10,
standardTotal: 0,
// 表单验证规则
rules: {
name: [{ required: true, trigger: "blur", message: "请输入姓名" }],
recordDate: [
{ required: true, trigger: "blur", message: "请选择日期" },
],
category: [
{ required: true, trigger: "blur", message: "请输入事项类别" },
],
score: [{ required: true, trigger: "blur", message: "请输入奖惩积分" }],
},
// 对话框
scoreDetailDialog: {
title: "",
visible: false,
},
// 导入导出相关
uploadHeaders: { token: "" },
importExcelUrl:
process.env.VUE_APP_BASE_API + "api/scoreDetail/import/easyExcel",
// 图像上传相关配置
uploadFileUrl: process.env.VUE_APP_BASE_API + "files/upload", // 图像上传接口
uploadAvatarFileUrl: process.env.VUE_APP_BASE_API + "files/", // 图像显示基础路径
// 审核相关数据
currentAuditType: "", // 'first' 或 'final'
currentAuditUser: "",
currentAuditTime: "",
currentAuditAction: "", // 'approve' 或 'reject'
// 审核状态控制
auditStatusControl: {
firstAudit: {
enabled: true, // 始终启用初审
status: 0, // 0:待审核, 1:已初审, 2:初审驳回
},
finalAudit: {
enabled: true, // 始终启用终审
status: 0, // 0:待终审, 1:已终审, 2:终审驳回
},
},
// 新增:选择执行扣分人对话框数据
personSelectDialog: {
title: "选择执行扣分人",
visible: false,
},
personSearchModel: {
name: "",
pageNum: 1,
pageSize: 10,
},
personList: [],
personTotal: 0,
personPageNum: 1,
personPageSize: 10,
};
},
computed: {
currentToken() {
return getToken();
},
currentUsername() {
return this.$store.getters.name;
},
// 计算属性:当前审核状态文本
currentAuditStatusText() {
const statusMap = {
0: "待审核",
1: "已初审",
2: "初审驳回",
3: "已终审",
4: "终审驳回",
};
// 处理新增时的默认状态
return this.scoreDetail.id
? statusMap[this.scoreDetail.auditStatus] || "未知状态"
: "待审核";
},
},
watch: {
currentToken(newVal) {
this.uploadHeaders.token = newVal;
},
// 监听对话框标题变化,更新审核相关数据
"scoreDetailDialog.title"(newVal) {
if (newVal === "初审") {
this.currentAuditType = "first";
this.currentAuditUser =
this.scoreDetail.firstAuditUser || this.currentUsername;
this.currentAuditTime = this.scoreDetail.firstAuditTime;
this.scoreDetail.auditOpinion =
this.scoreDetail.firstAuditOpinion || "";
// 更新审核状态控制
this.auditStatusControl.firstAudit.status =
this.scoreDetail.auditStatus;
} else if (newVal === "终审") {
this.currentAuditType = "final";
this.currentAuditUser =
this.scoreDetail.finalAuditUser || this.currentUsername;
this.currentAuditTime = this.scoreDetail.finalAuditTime;
this.scoreDetail.auditOpinion =
this.scoreDetail.finalAuditOpinion || "";
// 更新审核状态控制
this.auditStatusControl.finalAudit.status =
this.scoreDetail.auditStatus;
}
},
},
methods: {
// 新增:表格合计方法
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
// 第一列显示"合计"
sums[index] = '合计';
return;
}
// 只对奖惩积分列进行合计
if (column.property === 'score') {
const values = data.map(item => Number(item.score));
if (!values.every(value => isNaN(value))) {
const sum = values.reduce((prev, curr) => {
const value = Number(curr);
return isNaN(value) ? prev : prev + value;
}, 0);
// 格式化合计值(保留两位小数,千分位分隔)
sums[index] = sum.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
} else {
sums[index] = '';
}
} else {
// 其他列不显示合计
sums[index] = '';
}
});
return sums;
},
// 新增:处理奖惩类型变更
handleScoreTypeChange(val) {
// 当切换为奖分时清空扣分相关字段
if (val === "reward") {
this.scoreDetail.originalScore = "";
this.scoreDetail.mistakeCount = "";
this.scoreDetail.redEnvelope = "";
this.scoreDetail.executor = "";
this.scoreDetail.errorType = "";
}
},
// 打开错误类型对话框
openErrorTypeDialog() {
this.errorTypeSearchModel = {
errorType: "",
pageNum: 1,
pageSize: 10,
};
this.searchErrorTypes();
this.errorTypeDialog.visible = true;
},
// 查询错误类型列表
async searchErrorTypes() {
try {
const res = await getScoreErrorTypes({
...this.errorTypeSearchModel,
pageNum: this.errorTypePageNum,
pageSize: this.errorTypePageSize,
});
if (res.code === 200) {
this.errorTypeList = res.data.rows || [];
this.errorTypeTotal = res.data.total || 0;
} else {
this.$message.error(res.message || "查询错误类型失败");
}
} catch (error) {
this.$message.error("查询错误类型请求异常");
}
},
// 选择错误类型 - 简化逻辑
async selectErrorType(row) {
// 直接使用row.name作为错误类型值
this.scoreDetail.errorType = row.name;
this.errorTypeDialog.visible = false;
// 强制触发视图更新
this.$forceUpdate();
// 自动更新犯错次数
await this.updateMistakeCount();
},
// 更新犯错次数
async updateMistakeCount() {
if (
this.scoreDetail.recordDate &&
this.scoreDetail.name &&
this.scoreDetail.errorType
) {
try {
// 从日期中提取年份
const year = new Date(this.scoreDetail.recordDate).getFullYear();
// 调用API获取错误次数
const res = await getErrorCount({
name: this.scoreDetail.name,
year: year,
errorType: this.scoreDetail.errorType,
});
if (res.code === 200) {
// 使用$set确保响应式更新
const newCount = (res.data || 0) + 1;
this.$set(this.scoreDetail, "mistakeCount", newCount);
// 再次强制更新,确保视图同步
this.$nextTick(() => {
this.$forceUpdate();
});
} else {
this.$message.error(res.message || "获取错误次数失败");
}
} catch (error) {
console.error("获取错误次数请求异常:", error);
this.$message.error(
`获取错误次数失败: ${error.message || "网络错误"}`
);
}
} else {
this.$message.warning("请先填写日期、姓名和错误类型");
}
},
// 日期格式化辅助函数
formatDate(date) {
const d = new Date(date);
let month = "" + (d.getMonth() + 1);
let day = "" + d.getDate();
const year = d.getFullYear();
if (month.length < 2) month = "0" + month;
if (day.length < 2) day = "0" + day;
return [year, month, day].join("-");
},
// 新增:打开选择执行扣分人对话框
openPersonSelectDialog() {
this.personSearchModel = {
name: "",
pageNum: 1,
pageSize: 10,
};
this.searchPersons();
this.personSelectDialog.visible = true;
},
// 新增:查询执行扣分人列表
async searchPersons() {
try {
const res = await getScorePersons({
...this.personSearchModel,
pageNum: this.personPageNum,
pageSize: this.personPageSize,
});
if (res.code === 200) {
this.personList = res.data.rows || [];
this.personTotal = res.data.total || 0;
} else {
this.$message.error(res.message || "查询人员列表失败");
}
} catch (error) {
this.$message.error("查询人员列表请求异常");
}
},
// 新增:选择执行扣分人
selectPerson(row) {
this.scoreDetail.executor = row.name;
this.personSelectDialog.visible = false;
},
// 获取审核意见的验证规则
getAuditOpinionRules() {
return this.currentAuditAction === "reject"
? { required: true, message: "请输入驳回意见", trigger: "blur" }
: [];
},
// 打开初审窗口
firstAudit(row) {
this.scoreDetailDialog.title = "初审";
this.scoreDetailDialog.visible = true;
this.scoreDetail = { ...row };
// 记录当前索引
this.currentAuditIndex = this.scoreDetailList.findIndex(
(item) => item.id === row.id
);
// 初始化当前审核人
this.currentAuditUser = this.currentUsername;
this.currentAuditType = "first";
// 更新审核状态控制
this.auditStatusControl.firstAudit.status = this.scoreDetail.auditStatus;
},
// 打开终审窗口
finalAudit(row) {
// 允许对任何状态记录进行终审操作
this.scoreDetailDialog.title = "终审";
this.scoreDetailDialog.visible = true;
this.scoreDetail = { ...row };
this.currentAuditIndex = this.scoreDetailList.findIndex(
(item) => item.id === row.id
);
this.currentAuditUser = this.currentUsername;
this.currentAuditType = "final";
// 更新审核状态控制
this.auditStatusControl.finalAudit.status = this.scoreDetail.auditStatus;
},
// 审核通过
async handleAuditApprove() {
this.currentAuditAction = "approve"; // 设置当前操作为通过
this.$refs.scoreDetailForm.validate(async (valid) => {
if (!valid) return;
const now = new Date();
const auditTime = now.toISOString().slice(0, 19).replace("T", " ");
if (this.currentAuditType === "first") {
this.scoreDetail.auditStatus = 1;
this.scoreDetail.firstAuditUser = this.currentUsername;
this.scoreDetail.firstAuditTime = auditTime;
this.scoreDetail.firstAuditOpinion = this.scoreDetail.auditOpinion;
this.auditStatusControl.firstAudit.status = 1; // 更新状态为已初审
} else {
this.scoreDetail.auditStatus = 3;
this.scoreDetail.finalAuditUser = this.currentUsername;
this.scoreDetail.finalAuditTime = auditTime;
this.scoreDetail.finalAuditOpinion = this.scoreDetail.auditOpinion;
this.auditStatusControl.finalAudit.status = 1; // 更新状态为已终审
}
await this.saveAuditInfo();
this.$message.success(`${this.scoreDetailDialog.title}通过`);
this.jumpToNextAuditRecord(this.currentAuditType === "first" ? 0 : 1);
});
},
// 取消审核
async handleAuditCancel() {
if (this.currentAuditType === "first") {
this.scoreDetail.auditStatus = 0;
this.scoreDetail.firstAuditOpinion = "";
this.scoreDetail.firstAuditTime = null;
this.auditStatusControl.firstAudit.status = 0; // 更新状态为待审核
} else {
// 终审取消,回到初审通过状态
this.scoreDetail.auditStatus = 1;
this.scoreDetail.finalAuditOpinion = "";
this.scoreDetail.finalAuditTime = null;
this.auditStatusControl.finalAudit.status = 0; // 更新状态为待终审
}
await this.saveAuditInfo();
this.$message.success(`已取消${this.scoreDetailDialog.title}`);
this.scoreDetailDialog.visible = false;
},
// 审核驳回
async handleAuditReject() {
this.currentAuditAction = "reject"; // 设置当前操作为驳回
this.$refs.scoreDetailForm.validate(async (valid) => {
if (!valid) return;
const now = new Date();
const auditTime = now.toISOString().slice(0, 19).replace("T", " ");
if (this.currentAuditType === "first") {
this.scoreDetail.auditStatus = 2;
this.scoreDetail.firstAuditUser = this.currentUsername;
this.scoreDetail.firstAuditTime = auditTime;
this.scoreDetail.firstAuditOpinion = this.scoreDetail.auditOpinion;
this.auditStatusControl.firstAudit.status = 2; // 更新状态为初审驳回
} else {
this.scoreDetail.auditStatus = 4;
this.scoreDetail.finalAuditUser = this.currentUsername;
this.scoreDetail.finalAuditTime = auditTime;
this.scoreDetail.finalAuditOpinion = this.scoreDetail.auditOpinion;
this.auditStatusControl.finalAudit.status = 2; // 更新状态为终审驳回
}
await this.saveAuditInfo();
this.$message.success(`${this.scoreDetailDialog.title}驳回`);
this.jumpToNextAuditRecord(this.currentAuditType === "first" ? 0 : 1);
});
},
// 保存审核信息到后端
async saveAuditInfo() {
try {
const res = await updateScoreDetail(this.scoreDetail);
if (res.code === 200) {
this.search(); // 刷新列表
} else {
this.$message.error(res.message);
}
} catch (error) {
this.$message.error("审核操作失败");
}
},
// 自动跳转到下一个符合条件的记录
jumpToNextAuditRecord(targetStatus) {
const nextIndex = this.scoreDetailList.findIndex(
(item, index) =>
index > this.currentAuditIndex && item.auditStatus === targetStatus
);
if (nextIndex > -1) {
const nextRecord = this.scoreDetailList[nextIndex];
this.scoreDetailDialog.visible = false;
// 根据目标状态决定打开初审还是终审
targetStatus === 0
? this.firstAudit(nextRecord)
: this.finalAudit(nextRecord);
} else {
this.scoreDetailDialog.visible = false;
this.$message.info("已无符合条件的下一条记录");
}
},
// 表格列格式化审核状态
formatAuditStatus(status) {
return this.auditStatusMap[status] || "未知状态";
},
// 初始化组件时获取用户名
initUserInfo() {
this.scoreDetail.name = this.currentUsername;
this.scoreDetail.creator = this.currentUsername;
},
// 分数详情列表查询
async search() {
try {
const res = await getScoreDetails(this.searchModel);
if (res.code === 200) {
this.scoreDetailList = res.data.rows || [];
this.total = res.data.total || 0;
} else {
this.$message.error(res.message || "查询失败");
}
} catch (error) {
this.$message.error("请求异常");
}
},
// 重置查询条件
resetValue() {
this.searchModel = {
name: "",
category: "",
auditStatus: "", // 重置为全部
pageNum: 1,
pageSize: 10,
};
this.search();
},
// 分页事件处理
handleSizeChange(size) {
this.searchModel.pageSize = size;
this.search();
},
handleCurrentChange(page) {
this.searchModel.pageNum = page;
this.search();
},
// 打开新增窗口(优化:确保表单完全重置)
openAddWindow() {
// 先销毁表单验证状态,再重置字段
if (this.$refs.scoreDetailForm) {
this.$refs.scoreDetailForm.clearValidate(); // 清除所有验证状态
this.$refs.scoreDetailForm.resetFields(); // 重置字段值
}
// 初始化新增表单数据
this.scoreType = "reward"; // 默认奖分
this.scoreDetail = {
recordDate: new Date(),
createDate: new Date(),
name: this.currentUsername,
creator: this.currentUsername,
auditStatus: 0,
category: "",
score: "",
avatar: "",
};
this.scoreDetailDialog.title = "新增分数详情";
this.scoreDetailDialog.visible = true;
},
// 编辑分数详情
handleEdit(row) {
this.scoreDetail = { ...row };
// 设置奖惩类型
this.scoreType =
row.originalScore || row.mistakeCount || row.redEnvelope
? "punish"
: "reward";
// 日期格式转换
if (this.scoreDetail.recordDate) {
this.scoreDetail.recordDate = new Date(this.scoreDetail.recordDate);
}
if (this.scoreDetail.createDate) {
this.scoreDetail.createDate = new Date(this.scoreDetail.createDate);
}
this.scoreDetailDialog.title = "编辑分数详情";
this.scoreDetailDialog.visible = true;
},
// 打开积分标准选择对话框
openStandardDialog() {
this.standardSearchModel = {
category: "",
standard: "",
pageNum: 1,
pageSize: 10,
};
this.searchStandards();
this.standardSelectDialog.visible = true;
},
// 查询积分标准
async searchStandards() {
try {
const res = await getScoreStandards({
...this.standardSearchModel,
pageNum: this.standardPageNum,
pageSize: this.standardPageSize,
});
if (res.code === 200) {
this.standardList = res.data.rows || [];
this.standardTotal = res.data.total || 0;
} else {
this.$message.error(res.message || "查询积分标准失败");
}
} catch (error) {
this.$message.error("查询积分标准请求异常");
}
},
// 积分标准分页大小变更
handleStandardSizeChange(size) {
this.standardPageSize = size;
this.standardSearchModel.pageSize = size;
this.searchStandards();
},
// 积分标准分页页码变更
handleStandardCurrentChange(page) {
this.standardPageNum = page;
this.standardSearchModel.pageNum = page;
this.searchStandards();
},
// 选择积分标准
selectStandard(row) {
// 给事项类别、积分标准、奖惩积分赋值
this.scoreDetail.category = row.category;
this.scoreDetail.standard = row.standard;
this.scoreDetail.score = row.score;
// 手动触发表单验证
if (this.$refs.scoreDetailForm) {
this.$refs.scoreDetailForm.validateField(["category", "score"]);
}
// 关闭选择窗口
this.standardSelectDialog.visible = false;
},
// 保存分数详情
async onConfirm() {
this.$refs.scoreDetailForm.validate(async (valid) => {
if (!valid) return;
try {
const res = this.scoreDetail.id
? await updateScoreDetail(this.scoreDetail)
: await addScoreDetail(this.scoreDetail);
if (res.code === 200) {
this.$message.success(res.message);
this.scoreDetailDialog.visible = false;
this.search();
} else {
this.$message.error(res.message);
}
} catch (error) {
this.$message.error("操作失败");
}
});
},
// 删除分数详情
async handleDelete(row) {
try {
await this.$confirm("确定删除该分数详情?", "提示", {
type: "warning",
});
const res = await deleteScoreDetail(row.id);
if (res.code === 200) {
this.$message.success(res.message);
this.search();
} else {
this.$message.error(res.message);
}
} catch (error) {
// 取消删除
}
},
// 导出Excel
async exportExcel() {
try {
const instance = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
headers: { token: getToken() },
responseType: "blob",
});
const response = await instance.get(
"/api/scoreDetail/export/easyExcel",
{
params: {
name: this.searchModel.name,
},
}
);
const fileName = response.headers["content-disposition"]
? decodeURIComponent(
response.headers["content-disposition"].split("filename=")[1]
).replace(/"/g, "")
: "scoreDetails_export.xls";
const link = document.createElement("a");
link.href = URL.createObjectURL(new Blob([response.data]));
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
} catch (error) {
this.handleExportError(error);
}
},
// 导入Excel
importExcel(response) {
if (response.code === 200) {
this.search();
this.$message.success("导入成功");
} else {
this.$message.error(response.message || "导入失败");
}
},
// 导入文件校验
beforeUpload(file) {
const allowedTypes = [
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.ms-excel",
];
if (!allowedTypes.includes(file.type)) {
this.$message.error("仅支持 .xls 或 .xlsx 格式文件");
return false;
}
return true;
},
// 导出错误处理
handleExportError(error) {
if (error.response?.status === 401) {
this.$message.error("登录已过期,请重新登录");
this.$store.dispatch("user/logout");
this.$router.push("/login");
} else if (error.response?.data instanceof Blob) {
const reader = new FileReader();
reader.onload = () => {
try {
const errorData = JSON.parse(reader.result);
this.$message.error(errorData.message || "导出失败");
} catch {
this.$message.error("导出文件解析失败");
}
};
reader.readAsText(error.response.data);
} else {
this.$message.error(error.message || "导出失败");
}
},
// 图像上传相关方法
// 获取图像完整URL
getAvatarUrl(avatar) {
if (!avatar) return "";
const timestamp = Date.now();
return `${this.uploadAvatarFileUrl}${encodeURIComponent(avatar)}?token=${
this.currentToken
}&t=${timestamp}`;
},
// 处理图片加载错误
handleImageError(row) {
console.error("图片加载失败:", row.avatar);
// 简单的错误处理
this.$set(row, "avatar", "");
},
// 表单图片加载错误处理
handleFormImageError() {
console.error("表单图片加载失败");
this.scoreDetail.avatar = "";
},
// 图像上传前验证
beforeAvatarUpload(file, row) {
const isImage = file.type.startsWith("image/");
const isLt5M = file.size / 1024 / 1024 < 5;
if (!isImage) {
this.$message.error("只能上传图片文件!");
}
if (!isLt5M) {
this.$message.error("图像图片大小不能超过5MB!");
}
return isImage && isLt5M;
},
// 处理表单中的图像上传成功
handleFormAvatarUpload(response) {
if (response.code === 200) {
this.scoreDetail.avatar = response.data;
this.$message.success("图像上传成功");
} else {
this.$message.error(response.message || "图像上传失败");
}
},
// 处理行内图像上传成功
handleRowAvatarUpload(row, response) {
if (response.code === 200) {
// 更新当前行的图像信息
this.$set(row, "avatar", response.data);
this.$message.success("图像上传成功");
// 同步更新到后端
const updateData = {
id: row.id,
avatar: response.data,
};
updateScoreDetail(updateData)
.then((res) => {
if (res.code !== 200) {
this.$message.warning("图像信息同步失败");
}
})
.catch(() => {
this.$message.warning("图像信息同步失败");
});
} else {
this.$message.error(response.message || "图像上传失败");
}
},
// 处理图像上传失败
handleAvatarUploadError(row, error) {
console.error("图像上传失败:", error);
this.$message.error("图像上传失败,请重试");
},
},
created() {
this.search();
},
mounted() {
this.uploadHeaders.token = getToken();
this.initUserInfo();
this.$nextTick(() => {
this.tableHeight = window.innerHeight - 220;
this.standardTableHeight = window.innerHeight - 300;
});
},
};
</script>
<style>
/* 全局单元格样式 */
.el-table-ellipsis .cell {
white-space: nowrap !important; /* 强制不换行 */
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/* 禁用操作列的省略号效果 */
.el-table-ellipsis .no-ellipsis .cell {
white-space: normal !important; /* 恢复默认换行 */
overflow: visible !important;
text-overflow: clip !important;
}
/* 可选:表头对齐方式 */
.el-table-ellipsis th > .cell {
display: block; /* 解决表头与内容列宽度轻微错位问题 */
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* 图片错误占位样式 */
.image-error {
width: 100%;
height: 100%;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
}
.image-error::after {
content: "图片错误";
font-size: 12px;
}
/* 新增:合计行样式 */
.el-table .el-table__footer .cell {
font-weight: bold;
color: #409EFF;
}
</style>
报错:
vue.runtime.esm.js:620 [Vue warn]: Property or method "showSettings1" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <Layout> at src/layout/index.vue
<App> at src/App.vue
<Root>
6
vue.runtime.esm.js:620 [Vue warn]: Property or method "formatNumber" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> <ScoreTotal> at src/views/company/hr/scoreTotal.vue
<AppMain> at src/layout/components/AppMain.vue
<Layout> at src/layout/index.vue
<App> at src/App.vue
<Root>
合计数没有展示出来,优化代码
最新发布