上报功能开发完成
parent
bf502e76d5
commit
1f6f6d58ba
24
src/App.vue
24
src/App.vue
|
|
@ -1,6 +1,9 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { isDark } from '@/utils/is'
|
import { isDark } from '@/utils/is'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import { useUserListStore } from '@/store/modules/userList'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
import routerSearch from '@/components/RouterSearch/index.vue'
|
import routerSearch from '@/components/RouterSearch/index.vue'
|
||||||
|
|
@ -10,10 +13,31 @@ defineOptions({ name: 'APP' })
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
const prefixCls = getPrefixCls('app')
|
const prefixCls = getPrefixCls('app')
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const deptStore = useDeptStore()
|
||||||
const currentSize = computed(() => appStore.getCurrentSize)
|
const currentSize = computed(() => appStore.getCurrentSize)
|
||||||
const greyMode = computed(() => appStore.getGreyMode)
|
const greyMode = computed(() => appStore.getGreyMode)
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
// 初始化部门信息
|
||||||
|
const initDeptInfo = async () => {
|
||||||
|
await deptStore.setDeptInfoAction()
|
||||||
|
}
|
||||||
|
initDeptInfo()
|
||||||
|
|
||||||
|
// 初始化用户列表信息
|
||||||
|
const userListStore = useUserListStore()
|
||||||
|
const initUserListInfo = async () => {
|
||||||
|
await userListStore.setUserListAction()
|
||||||
|
}
|
||||||
|
initUserListInfo()
|
||||||
|
|
||||||
|
// 初始化奖惩原因信息
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
const initRewardsPunishReasonInfo = async () => {
|
||||||
|
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||||
|
}
|
||||||
|
initRewardsPunishReasonInfo()
|
||||||
|
|
||||||
// 根据浏览器当前主题设置系统主题色
|
// 根据浏览器当前主题设置系统主题色
|
||||||
const setDefaultTheme = () => {
|
const setDefaultTheme = () => {
|
||||||
let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
|
let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目通用API(详情和评论)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 查询上报详情
|
||||||
|
export const getMyReportDetail = (id: string) => {
|
||||||
|
return request.get({
|
||||||
|
url: `/submit/myReporting/get`,
|
||||||
|
params: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存评论
|
||||||
|
export const saveComment = (reportingId: string, commentVO: any) => {
|
||||||
|
return request.post({
|
||||||
|
url: `/submit/comment/${reportingId}`,
|
||||||
|
data: commentVO
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除评论
|
||||||
|
export const deleteComment = (commentId: string) => {
|
||||||
|
return request.delete({
|
||||||
|
url: `/submit/comment/${commentId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// api/submit/companyReport/index.ts
|
||||||
|
import { getMyReportPage, exportMyReport } from '../myReporting'
|
||||||
|
import { getMyReportDetail } from '../common/common'
|
||||||
|
import type { QueryParams } from '../myReporting/types'
|
||||||
|
|
||||||
|
// 查询公司通报列表(复用我的上报列表接口,通过参数区分)
|
||||||
|
export const getCompanyReportPage = (params: QueryParams) => {
|
||||||
|
// 设置参数以获取公司通报数据
|
||||||
|
return getMyReportPage({
|
||||||
|
...params,
|
||||||
|
isOwnReport: false,
|
||||||
|
isOwnApprove: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询公司通报详情(复用我的上报详情接口)
|
||||||
|
export const getCompanyReportDetail = getMyReportDetail
|
||||||
|
|
||||||
|
// 导出公司通报(复用我的上报导出接口)
|
||||||
|
export const exportCompanyReport = (params: QueryParams) => {
|
||||||
|
// 设置参数以导出公司通报数据
|
||||||
|
return exportMyReport({
|
||||||
|
...params,
|
||||||
|
isOwnReport: false,
|
||||||
|
isOwnApprove: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// api/submit/myApprove/index.ts
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import type { QueryParams } from '../myReporting/types'
|
||||||
|
import type { ApproveParams } from './types'
|
||||||
|
import { getMyReportPage, getRuleScore, exportMyReport } from '../myReporting'
|
||||||
|
|
||||||
|
// 查询审批列表 - 复用myReporting的查询接口
|
||||||
|
export const getApproveList = (params: QueryParams) => {
|
||||||
|
return getMyReportPage(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批上报
|
||||||
|
export const approveMyReport = (data: ApproveParams) => {
|
||||||
|
return request.post({
|
||||||
|
url: '/submit/myApprove/approve',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取规则分值 - 复用myReporting的规则分值接口
|
||||||
|
export { getRuleScore, exportMyReport }
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
// api/submit/myApprove/types.ts
|
||||||
|
|
||||||
|
// 用户积分记录
|
||||||
|
export interface UserIntegralRecord {
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
score: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批请求参数
|
||||||
|
export interface ApproveParams {
|
||||||
|
id: string;
|
||||||
|
action: string; // APPROVED 或 REFUSE
|
||||||
|
comment?: string;
|
||||||
|
userIntegralRecords?: UserIntegralRecord[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批列表查询参数
|
||||||
|
export interface ApproveListParams {
|
||||||
|
pageNo: number;
|
||||||
|
pageSize: number;
|
||||||
|
title?: string;
|
||||||
|
deptId?: string;
|
||||||
|
personName?: string;
|
||||||
|
status?: string;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
dateRange?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批记录详情
|
||||||
|
export interface ApproveRecord {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
deptId: string;
|
||||||
|
deptName: string;
|
||||||
|
reportType: string;
|
||||||
|
reportTypeName: string;
|
||||||
|
personName: string;
|
||||||
|
personId?: string;
|
||||||
|
remark?: string;
|
||||||
|
status: number | string;
|
||||||
|
approverId?: string;
|
||||||
|
approverName?: string;
|
||||||
|
approveTime?: string;
|
||||||
|
approveComment?: string;
|
||||||
|
createBy?: string;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string;
|
||||||
|
updateTime?: string;
|
||||||
|
userIntegralRecords?: UserIntegralRecord[];
|
||||||
|
fillingTime?: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// api/submit/myReporting/index.ts
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import type { QueryParams, SaveParams, RuleScoreResponse } from './types'
|
||||||
|
|
||||||
|
// 查询我的上报列表
|
||||||
|
export const getMyReportPage = (params: QueryParams) => {
|
||||||
|
return request.get({
|
||||||
|
url: '/submit/myReporting/page',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 创建我的上报
|
||||||
|
export const createMyReport = (data: SaveParams) => {
|
||||||
|
return request.post({
|
||||||
|
url: '/submit/myReporting/create',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新我的上报
|
||||||
|
export const updateMyReport = (data: SaveParams) => {
|
||||||
|
return request.put({
|
||||||
|
url: '/submit/myReporting/update',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除我的上报
|
||||||
|
export const deleteMyReport = (id: string) => {
|
||||||
|
return request.delete({
|
||||||
|
url: `/submit/myReporting/delete`,
|
||||||
|
params: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出我的上报
|
||||||
|
export const exportMyReport = (params: QueryParams) => {
|
||||||
|
return request.download({
|
||||||
|
url: '/submit/myReporting/export-excel',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交上报
|
||||||
|
export const commitMyReport = (id: string) => {
|
||||||
|
return request.post({
|
||||||
|
url: `/submit/myReporting/commit`,
|
||||||
|
params: { id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取规则分值
|
||||||
|
export const getRuleScore = (reasonId: string, personId: string) => {
|
||||||
|
return request.get<RuleScoreResponse>({
|
||||||
|
url: `/submit/ruleManage/score/${reasonId}/${personId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// api/submit/myReporting/types.ts
|
||||||
|
export interface UserIntegralRecord {
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
score: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Reason {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuleScoreResponse {
|
||||||
|
type: string;
|
||||||
|
scores: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReportingCommentDTO {
|
||||||
|
comment: string;
|
||||||
|
createUserName: string;
|
||||||
|
createTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MyReportDTO {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
deptId: string;
|
||||||
|
deptName: string;
|
||||||
|
reportType: string;
|
||||||
|
reportTypeName: string;
|
||||||
|
personName: string;
|
||||||
|
personId?: string;
|
||||||
|
remark?: string;
|
||||||
|
imageUrls?: string[];
|
||||||
|
status: number | string;
|
||||||
|
approverId?: string;
|
||||||
|
approverName?: string;
|
||||||
|
approveTime?: string;
|
||||||
|
approveComment?: string;
|
||||||
|
createBy?: string;
|
||||||
|
createTime?: string;
|
||||||
|
updateBy?: string;
|
||||||
|
updateTime?: string;
|
||||||
|
reason?: Reason;
|
||||||
|
userIntegralRecords?: UserIntegralRecord[];
|
||||||
|
comments?: ReportingCommentDTO[];
|
||||||
|
fillingTime?: string;
|
||||||
|
imageIds?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryParams {
|
||||||
|
pageNo: number;
|
||||||
|
pageSize: number;
|
||||||
|
title?: string;
|
||||||
|
deptId?: string;
|
||||||
|
personName?: string;
|
||||||
|
reasonId?: string;
|
||||||
|
status?: string;
|
||||||
|
isOwnReport: boolean;
|
||||||
|
isOwnApprove:boolean;
|
||||||
|
startTime?: string;
|
||||||
|
endTime?: string;
|
||||||
|
dateRange?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveParams {
|
||||||
|
id?: string;
|
||||||
|
title: string;
|
||||||
|
deptId: string;
|
||||||
|
deptName?: string;
|
||||||
|
reasonId?: string;
|
||||||
|
personName: string;
|
||||||
|
personId?: string;
|
||||||
|
remark?: string;
|
||||||
|
content?: string;
|
||||||
|
imageUrls?: string[];
|
||||||
|
imageIds?: string;
|
||||||
|
reason?: Reason;
|
||||||
|
userIntegralRecords?: UserIntegralRecord[];
|
||||||
|
comments?: Array<{ comment: string; createUserName: string }>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import type { PaySlipDTO, QueryParams, PageResponse } from './types'
|
||||||
|
|
||||||
|
// 下载工资条导入模板
|
||||||
|
export const downloadImportTemplate = () => {
|
||||||
|
return request.download({ url: '/submit/paySlip/download/importTemplate' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入工资条
|
||||||
|
export const importPaySlip = (data: FormData) => {
|
||||||
|
return request.post({ url: '/submit/paySlip/import', data, headers: { 'Content-Type': 'multipart/form-data' } })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询工资条列表
|
||||||
|
export const getPaySlipPage = (params: QueryParams) => {
|
||||||
|
return request.get<PageResponse<PaySlipDTO>>({ url: '/submit/paySlip/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询工资条详情
|
||||||
|
export const getPaySlipDetail = (id: string) => {
|
||||||
|
return request.get<PaySlipDTO>({ url: `/submit/paySlip/detail/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除工资条
|
||||||
|
export const deletePaySlip = (id: string) => {
|
||||||
|
return request.delete({ url: `/submit/paySlip/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 签名工资条
|
||||||
|
export const signPaySlip = (id: string) => {
|
||||||
|
return request.post({ url: `/submit/paySlip/sign/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送短信
|
||||||
|
export const sendMsm = (id: string) => {
|
||||||
|
return request.get({ url: `/submit/paySlip/sendMsm/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量发送短信
|
||||||
|
export const batchSendMsm = (ids: string[]) => {
|
||||||
|
// 直接将 ids 数组作为请求体发送
|
||||||
|
return request.post({ url: '/submit/paySlip/batch/sendMsm', data: ids });
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// 工资条相关类型定义
|
||||||
|
|
||||||
|
export enum YesOrNotEnum {
|
||||||
|
YES = 'YES', // 是
|
||||||
|
NO = 'NO' // 否
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工资条DTO
|
||||||
|
export interface PaySlipDTO {
|
||||||
|
id: string; // 主键
|
||||||
|
month?: string; // 月份
|
||||||
|
year?: string; // 年份
|
||||||
|
fullYearMonth?: string; // 完整月份,格式:年-月
|
||||||
|
employeeId?: string; // 工号
|
||||||
|
mobileNo?: string; // 手机号
|
||||||
|
employeeName?: string; // 真实姓名
|
||||||
|
salary?: number; // 工资
|
||||||
|
attendanceDays?: number; // 出勤天数
|
||||||
|
attendanceSalary?: number; // 出勤工资
|
||||||
|
communicationFees?: number; // 通讯费
|
||||||
|
overtimePay?: number; // 加班费
|
||||||
|
jobSubsidies?: number; // 岗位补贴
|
||||||
|
fullAttendanceBonus?: number; // 全勤奖
|
||||||
|
mealExpenses?: number; // 扣餐费
|
||||||
|
housingSupplement?: number; // 房补
|
||||||
|
supplementFees?: number; // 补发
|
||||||
|
insurancePremiumExpenses?: number; // 扣保险金
|
||||||
|
penaltyDeductionExpenses?: number; // 扣罚
|
||||||
|
marginExpenses?: number; // 扣保证金
|
||||||
|
refundOfDeposit?: number; // 退回保证金
|
||||||
|
salariesPayable?: number; // 应发工资
|
||||||
|
msmSend?: string; // 是否已发送短信 (Y/N)
|
||||||
|
msmSendTime?: string; // 短信发送时间
|
||||||
|
signed?: string; // 是否已签名 (Y/N)
|
||||||
|
signedImageIds?: string; // 签名图片IDs
|
||||||
|
signedTime?: string; // 签名时间
|
||||||
|
arrangeSigningDate?: string; // 安排签署日期
|
||||||
|
performance?: number; // 绩效
|
||||||
|
seniorityAward?: number; // 工龄奖
|
||||||
|
workingClothes?: number; // 工服费
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工资条查询参数
|
||||||
|
export interface QueryParams {
|
||||||
|
pageNo: number; // 页码
|
||||||
|
pageSize: number; // 每页条数
|
||||||
|
month?: string; // 月份
|
||||||
|
year?: string; // 年份
|
||||||
|
yearMonth?: string; // 完整月份,格式:年-月
|
||||||
|
employeeId?: string; // 工号
|
||||||
|
mobileNo?: string; // 手机号
|
||||||
|
employeeName?: string; // 真实姓名
|
||||||
|
signed?: string; // 是否已签名 (Y/N)
|
||||||
|
msmSend?: string; // 是否已发送短信 (Y/N)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应
|
||||||
|
export interface PageResponse<T> {
|
||||||
|
list: T[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import type { ReportManageDTO, QueryParams, SaveParams, PageResponse } from './types'
|
||||||
|
|
||||||
|
// 查询报表管理列表
|
||||||
|
export const getReportManagePage = (params: QueryParams) => {
|
||||||
|
return request.get<PageResponse<ReportManageDTO>>({ url: '/submit/reportManage/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询报表管理详情
|
||||||
|
export const getReportManageDetail = (id: string) => {
|
||||||
|
return request.get<ReportManageDTO>({ url: `/submit/reportManage/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建报表管理
|
||||||
|
export const createReportManage = (data: SaveParams) => {
|
||||||
|
return request.post({ url: '/submit/reportManage/create', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新报表管理
|
||||||
|
export const updateReportManage = (data: SaveParams) => {
|
||||||
|
return request.put({ url: '/submit/reportManage/update', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除报表管理
|
||||||
|
export const deleteReportManage = (id: string) => {
|
||||||
|
return request.delete({ url: `/submit/reportManage/delete/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型查询报表管理列表
|
||||||
|
export const getReportManageListByType = (type: string) => {
|
||||||
|
return request.get<ReportManageDTO[]>({ url: '/submit/reportManage/list-by-type', params: { type } })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// 报表管理相关类型定义
|
||||||
|
|
||||||
|
// 报表管理DTO
|
||||||
|
export interface ReportManageDTO {
|
||||||
|
id: string; // 主键
|
||||||
|
code: string; // 编码
|
||||||
|
name: string; // 名称
|
||||||
|
orderNum: number; // 排序序号
|
||||||
|
enable: number; // 状态(1启用 0禁用)
|
||||||
|
remark?: string; // 描述
|
||||||
|
createTime?: string; // 创建时间
|
||||||
|
updateTime?: string; // 更新时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报表管理查询参数
|
||||||
|
export interface QueryParams {
|
||||||
|
pageNo: number; // 页码
|
||||||
|
pageSize: number; // 每页条数
|
||||||
|
code?: string; // 编码
|
||||||
|
name?: string; // 名称
|
||||||
|
enable?: string; // 状态
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报表管理保存参数
|
||||||
|
export interface SaveParams {
|
||||||
|
id?: string; // 主键
|
||||||
|
code: string; // 编码
|
||||||
|
name: string; // 名称
|
||||||
|
orderNum: number; // 排序序号
|
||||||
|
enable: number; // 状态(1启用 0禁用)
|
||||||
|
remark?: string; // 描述
|
||||||
|
type: 'REWARDS_PUNISH_REASON';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应
|
||||||
|
export interface PageResponse<T> {
|
||||||
|
list: T[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
import type { IntegralRule, QueryParams, SaveParams, PageResponse } from './types'
|
||||||
|
|
||||||
|
// 查询积分规则列表
|
||||||
|
export const getIntegralRulePage = (params: QueryParams) => {
|
||||||
|
return request.get<PageResponse<IntegralRule>>({ url: '/submit/ruleManage/page', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询积分规则详情
|
||||||
|
export const getIntegralRuleDetail = (id: string) => {
|
||||||
|
return request.get<IntegralRule>({ url: `/submit/ruleManage/get/${id}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据事项ID和用户ID获取规则分值
|
||||||
|
export const getRuleScore = (reasonId: string, userId: string) => {
|
||||||
|
return request.get({ url: `/submit/ruleManage/getRuleScore/${reasonId}/${userId}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建积分规则
|
||||||
|
export const createIntegralRule = (data: SaveParams) => {
|
||||||
|
return request.post({ url: '/submit/ruleManage/create', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新积分规则
|
||||||
|
export const updateIntegralRule = (data: SaveParams) => {
|
||||||
|
return request.put({ url: '/submit/ruleManage/update', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除积分规则
|
||||||
|
export const deleteIntegralRule = (id: string) => {
|
||||||
|
return request.delete({ url: `/submit/ruleManage/delete/${id}` })
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
// 积分规则类型定义
|
||||||
|
|
||||||
|
export interface IntegralRule {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
reason: {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
type: string
|
||||||
|
score: number
|
||||||
|
scoreRange: string[]
|
||||||
|
formula: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
export interface QueryParams {
|
||||||
|
title?: string
|
||||||
|
reasonId?: string
|
||||||
|
type?: string
|
||||||
|
pageNo?: number
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存参数
|
||||||
|
export interface SaveParams {
|
||||||
|
id?: string
|
||||||
|
title: string
|
||||||
|
reasonId: string
|
||||||
|
type: string
|
||||||
|
score?: number
|
||||||
|
scoreRange?: string[]
|
||||||
|
formula?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页响应
|
||||||
|
export interface PageResponse<T> {
|
||||||
|
list: T[]
|
||||||
|
total: number
|
||||||
|
totalPages: number
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// 用户积分API接口
|
||||||
|
export const userScoreApi = {
|
||||||
|
// 查询用户积分排行
|
||||||
|
getUserScoreStatic: (params: any) => {
|
||||||
|
return request.get({ url: '/submit/userScore/static', params })
|
||||||
|
},
|
||||||
|
// 同步用户积分数据
|
||||||
|
syncUserScore: () => {
|
||||||
|
return request.get({ url: '/submit/userScore/init' })
|
||||||
|
},
|
||||||
|
// 导出用户积分排行
|
||||||
|
exportUserScoreStatic: (params: any) => {
|
||||||
|
return request.download({ url: '/submit/userScore/static/export', params })
|
||||||
|
},
|
||||||
|
// 查询用户积分明细
|
||||||
|
getUserScoreList: (params: any) => {
|
||||||
|
return request.get({ url: '/submit/userScore/list', params })
|
||||||
|
},
|
||||||
|
// 导出用户积分明细
|
||||||
|
exportUserScoreList: (params: any) => {
|
||||||
|
return request.download({ url: '/submit/userScore/export', params })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
// 用户积分列表请求参数
|
||||||
|
export interface UserScoreListReqVO {
|
||||||
|
// 部门名称
|
||||||
|
deptName?: string;
|
||||||
|
// 部门Id
|
||||||
|
orgId?: number;
|
||||||
|
// 用户名称
|
||||||
|
userName?: string;
|
||||||
|
// 用户Id
|
||||||
|
userId?: string;
|
||||||
|
// 开始月份,格式:yyyy-MM
|
||||||
|
beginMonth?: string;
|
||||||
|
// 结束月份,格式:yyyy-MM
|
||||||
|
endMonth?: string;
|
||||||
|
// 排名前N位
|
||||||
|
top?: number;
|
||||||
|
// 页码
|
||||||
|
pageNo?: number;
|
||||||
|
// 每页条数
|
||||||
|
pageSize?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户积分列表响应数据
|
||||||
|
export interface UserScoreListRespVO {
|
||||||
|
// 排名
|
||||||
|
ranking: number;
|
||||||
|
// 部门名称
|
||||||
|
deptName: string;
|
||||||
|
// 用户名称
|
||||||
|
userName: string;
|
||||||
|
// 积分
|
||||||
|
score: number;
|
||||||
|
// 用户ID
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户积分详情请求参数
|
||||||
|
export interface UserScoreDetailReqVO {
|
||||||
|
// 用户ID
|
||||||
|
userId: string;
|
||||||
|
// 开始月份,格式:yyyy-MM
|
||||||
|
beginMonth: string;
|
||||||
|
// 结束月份,格式:yyyy-MM
|
||||||
|
endMonth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户积分详情响应数据
|
||||||
|
export interface UserScoreDetailRespVO {
|
||||||
|
// 用户ID
|
||||||
|
userId: string;
|
||||||
|
// 排名
|
||||||
|
ranking: number;
|
||||||
|
// 总积分
|
||||||
|
totalScore: number;
|
||||||
|
// 用户名称
|
||||||
|
userName: string;
|
||||||
|
// 部门名称
|
||||||
|
deptName: string;
|
||||||
|
// 开始月份,格式:yyyy-MM
|
||||||
|
beginMonth: string;
|
||||||
|
// 结束月份,格式:yyyy-MM
|
||||||
|
endMonth: string;
|
||||||
|
// 积分明细列表
|
||||||
|
scoreDetails: ScoreDetail[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 积分明细
|
||||||
|
export interface ScoreDetail {
|
||||||
|
// 积分
|
||||||
|
score: number;
|
||||||
|
// 发生日期
|
||||||
|
happenedDate: string;
|
||||||
|
// 积分类型
|
||||||
|
type: string;
|
||||||
|
// 积分原因
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,12 @@ export const CACHE_KEY = {
|
||||||
ROLE_ROUTERS: 'roleRouters',
|
ROLE_ROUTERS: 'roleRouters',
|
||||||
USER: 'user',
|
USER: 'user',
|
||||||
VisitTenantId: 'visitTenantId',
|
VisitTenantId: 'visitTenantId',
|
||||||
|
// 部门相关(新增)
|
||||||
|
DEPT: 'dept',
|
||||||
|
// 用户列表相关(新增)
|
||||||
|
USER_LIST: 'userList',
|
||||||
|
// 奖惩原因相关(新增)
|
||||||
|
REWARDS_PUNISH_REASON: 'rewardsPunishReason',
|
||||||
// 系统设置
|
// 系统设置
|
||||||
IS_DARK: 'isDark',
|
IS_DARK: 'isDark',
|
||||||
LANG: 'lang',
|
LANG: 'lang',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { store } from '@/store'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
|
import { getSimpleDeptList } from '@/api/system/dept'
|
||||||
|
import { handleTree } from '@/utils/tree'
|
||||||
|
import type { DeptVO } from '@/api/system/dept'
|
||||||
|
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
interface DeptState {
|
||||||
|
deptList: DeptVO[]
|
||||||
|
deptTree: DeptVO[]
|
||||||
|
isSetDept: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDeptStore = defineStore('admin-dept', {
|
||||||
|
state: (): DeptState => ({
|
||||||
|
deptList: [],
|
||||||
|
deptTree: [],
|
||||||
|
isSetDept: false
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getDeptList(): DeptVO[] {
|
||||||
|
return this.deptList
|
||||||
|
},
|
||||||
|
getDeptTree(): DeptVO[] {
|
||||||
|
return this.deptTree
|
||||||
|
},
|
||||||
|
getIsSetDept(): boolean {
|
||||||
|
return this.isSetDept
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 设置部门信息
|
||||||
|
*/
|
||||||
|
async setDeptInfoAction() {
|
||||||
|
let deptInfo = wsCache.get(CACHE_KEY.DEPT)
|
||||||
|
if (!deptInfo) {
|
||||||
|
const deptList = await getSimpleDeptList()
|
||||||
|
const deptTree = handleTree(deptList)
|
||||||
|
deptInfo = {
|
||||||
|
deptList,
|
||||||
|
deptTree
|
||||||
|
}
|
||||||
|
wsCache.set(CACHE_KEY.DEPT, deptInfo)
|
||||||
|
} else {
|
||||||
|
// 有缓存时,尝试更新部门信息
|
||||||
|
try {
|
||||||
|
const deptList = await getSimpleDeptList()
|
||||||
|
const deptTree = handleTree(deptList)
|
||||||
|
deptInfo = { deptList, deptTree }
|
||||||
|
} catch (error) {
|
||||||
|
// 更新失败时使用缓存数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.deptList = deptInfo.deptList
|
||||||
|
this.deptTree = deptInfo.deptTree
|
||||||
|
this.isSetDept = true
|
||||||
|
wsCache.set(CACHE_KEY.DEPT, deptInfo)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新部门信息
|
||||||
|
*/
|
||||||
|
async updateDeptInfoAction() {
|
||||||
|
// 强制更新部门信息,不使用缓存
|
||||||
|
const deptList = await getSimpleDeptList()
|
||||||
|
const deptTree = handleTree(deptList)
|
||||||
|
this.deptList = deptList
|
||||||
|
this.deptTree = deptTree
|
||||||
|
this.isSetDept = true
|
||||||
|
const deptInfo = { deptList, deptTree }
|
||||||
|
wsCache.set(CACHE_KEY.DEPT, deptInfo)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 重置部门状态
|
||||||
|
*/
|
||||||
|
resetDeptState() {
|
||||||
|
this.deptList = []
|
||||||
|
this.deptTree = []
|
||||||
|
this.isSetDept = false
|
||||||
|
wsCache.delete(CACHE_KEY.DEPT)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist: false
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在组件外部使用部门 store
|
||||||
|
*/
|
||||||
|
export const useDeptStoreWithOut = () => {
|
||||||
|
return useDeptStore(store)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { store } from '@/store'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
|
import { getReportManageListByType } from '@/api/submit/systemConfig/reportManage'
|
||||||
|
import type { ReportManageDTO } from '@/api/submit/systemConfig/reportManage/types'
|
||||||
|
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
interface RewardsPunishReasonState {
|
||||||
|
// 存储奖惩原因列表
|
||||||
|
rewardsPunishReasonList: Array<{
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}>
|
||||||
|
// 是否已设置奖惩原因
|
||||||
|
isSetRewardsPunishReason: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRewardsPunishReasonStore = defineStore('admin-rewardsPunishReason', {
|
||||||
|
state: (): RewardsPunishReasonState => ({
|
||||||
|
rewardsPunishReasonList: [],
|
||||||
|
isSetRewardsPunishReason: false
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
/**
|
||||||
|
* 获取奖惩原因列表
|
||||||
|
*/
|
||||||
|
getRewardsPunishReasonList(): Array<{ id: string; name: string }> {
|
||||||
|
return this.rewardsPunishReasonList
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 是否已设置奖惩原因
|
||||||
|
*/
|
||||||
|
getIsSetRewardsPunishReason(): boolean {
|
||||||
|
return this.isSetRewardsPunishReason
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 设置奖惩原因列表信息
|
||||||
|
*/
|
||||||
|
async setRewardsPunishReasonAction() {
|
||||||
|
let rewardsPunishReasonInfo = wsCache.get(CACHE_KEY.REWARDS_PUNISH_REASON)
|
||||||
|
if (!rewardsPunishReasonInfo) {
|
||||||
|
// 无缓存时,从接口获取
|
||||||
|
const response = await getReportManageListByType('REWARDS_PUNISH_REASON')
|
||||||
|
const reasons = this.extractRewardsPunishReason(response)
|
||||||
|
rewardsPunishReasonInfo = {
|
||||||
|
rewardsPunishReasonList: reasons
|
||||||
|
}
|
||||||
|
wsCache.set(CACHE_KEY.REWARDS_PUNISH_REASON, rewardsPunishReasonInfo)
|
||||||
|
} else {
|
||||||
|
// 有缓存时,尝试更新数据
|
||||||
|
try {
|
||||||
|
const response = await getReportManageListByType('REWARDS_PUNISH_REASON')
|
||||||
|
const reasons = this.extractRewardsPunishReason(response)
|
||||||
|
rewardsPunishReasonInfo = {
|
||||||
|
rewardsPunishReasonList: reasons
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 更新失败时使用缓存数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.rewardsPunishReasonList = rewardsPunishReasonInfo.rewardsPunishReasonList
|
||||||
|
this.isSetRewardsPunishReason = true
|
||||||
|
wsCache.set(CACHE_KEY.REWARDS_PUNISH_REASON, rewardsPunishReasonInfo)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从报表管理列表中提取奖惩原因
|
||||||
|
*/
|
||||||
|
extractRewardsPunishReason(reportList: ReportManageDTO[]): Array<{ id: string; name: string }> {
|
||||||
|
// 使用Map去重
|
||||||
|
const reasonMap = new Map<string, string>()
|
||||||
|
|
||||||
|
reportList.forEach(report => {
|
||||||
|
reasonMap.set(report.id, report.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 转换为数组格式
|
||||||
|
return Array.from(reasonMap.entries()).map(([id, name]) => ({
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新奖惩原因列表信息
|
||||||
|
*/
|
||||||
|
async updateRewardsPunishReasonAction() {
|
||||||
|
// 强制更新数据,不使用缓存
|
||||||
|
const response = await getReportManageListByType('REWARDS_PUNISH_REASON')
|
||||||
|
const reasons = this.extractRewardsPunishReason(response)
|
||||||
|
this.rewardsPunishReasonList = reasons
|
||||||
|
this.isSetRewardsPunishReason = true
|
||||||
|
const rewardsPunishReasonInfo = {
|
||||||
|
rewardsPunishReasonList: reasons
|
||||||
|
}
|
||||||
|
wsCache.set(CACHE_KEY.REWARDS_PUNISH_REASON, rewardsPunishReasonInfo)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置奖惩原因状态
|
||||||
|
*/
|
||||||
|
resetRewardsPunishReasonState() {
|
||||||
|
this.rewardsPunishReasonList = []
|
||||||
|
this.isSetRewardsPunishReason = false
|
||||||
|
wsCache.delete(CACHE_KEY.REWARDS_PUNISH_REASON)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist: false
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在组件外部使用奖惩原因 store
|
||||||
|
*/
|
||||||
|
export const useRewardsPunishReasonStoreWithOut = () => {
|
||||||
|
return useRewardsPunishReasonStore(store)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { store } from '@/store'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||||
|
import { getSimpleUserList } from '@/api/system/user'
|
||||||
|
import type { UserVO } from '@/api/system/user'
|
||||||
|
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
|
interface UserListState {
|
||||||
|
userList: UserVO[]
|
||||||
|
isSetUserList: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserListStore = defineStore('admin-userList', {
|
||||||
|
state: (): UserListState => ({
|
||||||
|
userList: [],
|
||||||
|
isSetUserList: false
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getUserList(): UserVO[] {
|
||||||
|
return this.userList
|
||||||
|
},
|
||||||
|
getIsSetUserList(): boolean {
|
||||||
|
return this.isSetUserList
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 设置用户列表信息
|
||||||
|
*/
|
||||||
|
async setUserListAction() {
|
||||||
|
let userListInfo = wsCache.get(CACHE_KEY.USER_LIST)
|
||||||
|
if (!userListInfo) {
|
||||||
|
const userList = await getSimpleUserList()
|
||||||
|
userListInfo = {
|
||||||
|
userList
|
||||||
|
}
|
||||||
|
wsCache.set(CACHE_KEY.USER_LIST, userListInfo)
|
||||||
|
} else {
|
||||||
|
// 有缓存时,尝试更新用户列表信息
|
||||||
|
try {
|
||||||
|
const userList = await getSimpleUserList()
|
||||||
|
userListInfo = { userList }
|
||||||
|
} catch (error) {
|
||||||
|
// 更新失败时使用缓存数据
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.userList = userListInfo.userList
|
||||||
|
this.isSetUserList = true
|
||||||
|
wsCache.set(CACHE_KEY.USER_LIST, userListInfo)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新用户列表信息
|
||||||
|
*/
|
||||||
|
async updateUserListAction() {
|
||||||
|
// 强制更新用户列表信息,不使用缓存
|
||||||
|
const userList = await getSimpleUserList()
|
||||||
|
this.userList = userList
|
||||||
|
this.isSetUserList = true
|
||||||
|
const userListInfo = { userList }
|
||||||
|
wsCache.set(CACHE_KEY.USER_LIST, userListInfo)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 重置用户列表状态
|
||||||
|
*/
|
||||||
|
resetUserListState() {
|
||||||
|
this.userList = []
|
||||||
|
this.isSetUserList = false
|
||||||
|
wsCache.delete(CACHE_KEY.USER_LIST)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
persist: false
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在组件外部使用用户列表 store
|
||||||
|
*/
|
||||||
|
export const useUserListStoreWithOut = () => {
|
||||||
|
return useUserListStore(store)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
export const enumsType = {
|
||||||
|
reportStatus:[
|
||||||
|
{value:'STAGING',text:'暂存'},
|
||||||
|
{value:'AUDIT',text:'待审核'},
|
||||||
|
{value:'APPROVED',text:'审核通过'},
|
||||||
|
{value:'REVIEW_FAILED',text:'审核不通过'},
|
||||||
|
],
|
||||||
|
approveActions:[
|
||||||
|
{value:'PASS',text:'通过'},
|
||||||
|
{value:'REFUSE',text:'拒绝'},
|
||||||
|
],
|
||||||
|
trueOrFalse:[
|
||||||
|
{value:true,text:'是'},
|
||||||
|
{value:false,text:'否'},
|
||||||
|
],
|
||||||
|
integralRuleTypeEnum:[
|
||||||
|
{value:'FIXED_VALUE',text:'固定分值'},
|
||||||
|
{value:'MANUAL_VALUE',text:'手工分值'},
|
||||||
|
{value:'CALC_VALUE',text:'动态计算分值'},
|
||||||
|
{value:'CHOOSE_VALUE',text:'选择分值'},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -158,6 +158,7 @@ import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import { useDictStore } from '@/store/modules/dict'
|
||||||
|
|
||||||
import * as LoginApi from '@/api/login'
|
import * as LoginApi from '@/api/login'
|
||||||
import * as authUtil from '@/utils/auth'
|
import * as authUtil from '@/utils/auth'
|
||||||
|
|
@ -256,6 +257,8 @@ const tryLogin = async () => {
|
||||||
|
|
||||||
const res = await LoginApi.socialLogin(type, code, state)
|
const res = await LoginApi.socialLogin(type, code, state)
|
||||||
authUtil.setToken(res)
|
authUtil.setToken(res)
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
await dictStore.setDictMap()
|
||||||
|
|
||||||
router.push({ path: redirect || '/' })
|
router.push({ path: redirect || '/' })
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
@ -302,6 +305,8 @@ const handleLogin = async (params) => {
|
||||||
authUtil.removeLoginForm()
|
authUtil.removeLoginForm()
|
||||||
}
|
}
|
||||||
authUtil.setToken(res)
|
authUtil.setToken(res)
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
await dictStore.setDictMap()
|
||||||
if (!redirect) {
|
if (!redirect) {
|
||||||
redirect = '/'
|
redirect = '/'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ import { useIcon } from '@/hooks/web/useIcon'
|
||||||
|
|
||||||
import * as authUtil from '@/utils/auth'
|
import * as authUtil from '@/utils/auth'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import { useDictStore } from '@/store/modules/dict'
|
||||||
import * as LoginApi from '@/api/login'
|
import * as LoginApi from '@/api/login'
|
||||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||||
|
|
||||||
|
|
@ -272,6 +273,8 @@ const handleLogin = async (params: any) => {
|
||||||
authUtil.removeLoginForm()
|
authUtil.removeLoginForm()
|
||||||
}
|
}
|
||||||
authUtil.setToken(res)
|
authUtil.setToken(res)
|
||||||
|
const dictStore = useDictStore()
|
||||||
|
await dictStore.setDictMap()
|
||||||
if (!redirect.value) {
|
if (!redirect.value) {
|
||||||
redirect.value = '/'
|
redirect.value = '/'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,481 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="数据详情"
|
||||||
|
size="38%"
|
||||||
|
direction="rtl"
|
||||||
|
:close-on-click-modal="true"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:show-close="true"
|
||||||
|
>
|
||||||
|
<!-- 移除自定义 header,使用默认header -->
|
||||||
|
<div class="main-container">
|
||||||
|
<!-- 基础信息 -->
|
||||||
|
<div class="group-warp">
|
||||||
|
<el-divider content-position="left">
|
||||||
|
<span class="group-name">基础信息</span>
|
||||||
|
</el-divider>
|
||||||
|
<div class="group-container">
|
||||||
|
<el-descriptions :column="2" size="default" border>
|
||||||
|
<el-descriptions-item label="标题" :span="2">
|
||||||
|
{{ detailData.title || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="部门">
|
||||||
|
{{ detailData.deptName || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="事项">
|
||||||
|
{{ detailData.reason?.name || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="涉事人">
|
||||||
|
{{ detailData.personName || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="执行人">
|
||||||
|
{{ detailData.executorName || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="填报时间">
|
||||||
|
{{ formatDateTime(detailData.fillingTime) || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审批人">
|
||||||
|
{{ detailData.approveUserName || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审批时间">
|
||||||
|
{{ formatDateTime(detailData.approveTime) || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态">
|
||||||
|
<el-tag :type="getStatusTagType(detailData.status)">
|
||||||
|
{{ getStatusText(detailData.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注" :span="2">
|
||||||
|
{{ detailData.remark || "-" }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<template v-if="detailData.userIntegralRecords && detailData.userIntegralRecords.length > 0">
|
||||||
|
<el-descriptions-item
|
||||||
|
v-for="(item, index) in detailData.userIntegralRecords"
|
||||||
|
:key="index"
|
||||||
|
:label="'积分'"
|
||||||
|
>
|
||||||
|
<div class="user-score-detail">
|
||||||
|
<span>{{ item.userName || '-' }}</span>
|
||||||
|
<span v-if="item.happenedDate">
|
||||||
|
{{ ' 于 ' + item.happenedDate.slice(0, 10) }}
|
||||||
|
</span>
|
||||||
|
<span class="user-score-detail-value">
|
||||||
|
<el-tag size="small" :type="item.score > 0 ? 'success' : 'danger'">
|
||||||
|
{{ item.score >= 0 ? ('+' + item.score) : item.score }}
|
||||||
|
</el-tag>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-descriptions-item>
|
||||||
|
</template>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 附件区域 -->
|
||||||
|
<div class="group-warp" v-if="detailData.imageUrls && detailData.imageUrls.length > 0">
|
||||||
|
<el-divider content-position="left">
|
||||||
|
<span class="group-name">附件</span>
|
||||||
|
</el-divider>
|
||||||
|
<div class="group-container">
|
||||||
|
<div class="image-container">
|
||||||
|
<div class="image-container-item" v-for="(url, index) in detailData.imageUrls" :key="index">
|
||||||
|
<el-image
|
||||||
|
:src="getImageSrc(url)"
|
||||||
|
fit="contain"
|
||||||
|
@click="showImage(getImageSrc(url))"
|
||||||
|
>
|
||||||
|
<template #error>
|
||||||
|
<div class="image-slot">
|
||||||
|
<el-icon>
|
||||||
|
<Picture />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-image>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 评论区域 -->
|
||||||
|
<div class="group-warp">
|
||||||
|
<el-divider content-position="left">
|
||||||
|
<span class="group-name">评论信息</span>
|
||||||
|
</el-divider>
|
||||||
|
<div class="group-container comment-container">
|
||||||
|
<div class="b-white" v-if="showCommentInput === false">
|
||||||
|
<el-button type="primary" size="small" @click="showCommentInput = true">
|
||||||
|
<el-icon>
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
我要评论
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="add-comment-form" v-if="showCommentInput">
|
||||||
|
<el-input
|
||||||
|
v-model="commentText"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="1000"
|
||||||
|
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||||
|
placeholder="请输入评论内容"
|
||||||
|
resize="none"
|
||||||
|
/>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="showCommentInput = false" size="small">
|
||||||
|
<el-icon>
|
||||||
|
<CloseBold />
|
||||||
|
</el-icon>
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="addComment" :loading="commentLoading" size="small">
|
||||||
|
<el-icon>
|
||||||
|
<Select />
|
||||||
|
</el-icon>
|
||||||
|
提交
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="comment-list">
|
||||||
|
<template v-if="detailData.comments && detailData.comments.length > 0">
|
||||||
|
<el-timeline>
|
||||||
|
<el-timeline-item v-for="(comment, index) in detailData.comments" :key="index" :timestamp="formatDateTime(comment.createTime)" placement="top">
|
||||||
|
<el-card>
|
||||||
|
<div class="card-header" v-if="comment.createUserName === userId">
|
||||||
|
<el-icon class="comment-delete" color="#eb333d" @click="handleDeleteComment(index)">
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
<h4 class="comment-content">{{ comment.comment }}</h4>
|
||||||
|
<p class="comment-user-info">{{ comment.createUserName + ' 提交于 ' + formatDateTime(comment.createTime) }}</p>
|
||||||
|
</el-card>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-empty description="暂无评论" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片预览 -->
|
||||||
|
<el-image-viewer
|
||||||
|
v-if="imageViewerVisible"
|
||||||
|
:url-list="[imageViewerUrl]"
|
||||||
|
@close="imageViewerVisible = false"
|
||||||
|
/>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { getMyReportDetail, saveComment, deleteComment } from '@/api/submit/common/common'
|
||||||
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { Picture, Plus, CloseBold, Select, Delete } from '@element-plus/icons-vue'
|
||||||
|
import { enumsType } from '@/store/static'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
reportId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:visible': [value: boolean]
|
||||||
|
'refresh': []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const dialogVisible = computed({
|
||||||
|
get: () => props.visible,
|
||||||
|
set: (val) => emit('update:visible', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const detailData = ref({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
reasonId: '',
|
||||||
|
reason: { name: '' },
|
||||||
|
personName: '',
|
||||||
|
executorName: '',
|
||||||
|
comment: '',
|
||||||
|
remark: '',
|
||||||
|
imageUrls: [] as string[],
|
||||||
|
status: '',
|
||||||
|
approveUserId: '',
|
||||||
|
approveUserName: '',
|
||||||
|
approveTime: '',
|
||||||
|
fillingTime: '',
|
||||||
|
userIntegralRecords: [] as any[],
|
||||||
|
comments: [] as any[],
|
||||||
|
depId: '',
|
||||||
|
type: '',
|
||||||
|
happenedDate: '',
|
||||||
|
approverId: '',
|
||||||
|
approverName: '',
|
||||||
|
approverTime: '',
|
||||||
|
createTime: '',
|
||||||
|
createBy: '',
|
||||||
|
createUserName: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const commentText = ref('')
|
||||||
|
const showCommentInput = ref(false)
|
||||||
|
const commentLoading = ref(false)
|
||||||
|
const imageViewerVisible = ref(false)
|
||||||
|
const imageViewerUrl = ref('')
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const userId = computed(() => userStore.getUserInfo?.id)
|
||||||
|
|
||||||
|
// 监听reportId变化,加载详情数据
|
||||||
|
watch(() => props.reportId, (newId) => {
|
||||||
|
if (newId && props.visible) {
|
||||||
|
loadDetailData(newId)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 监听visible变化,确保抽屉显示时重新加载数据
|
||||||
|
watch(() => props.visible, (newVisible) => {
|
||||||
|
if (newVisible && props.reportId) {
|
||||||
|
loadDetailData(props.reportId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载详情数据
|
||||||
|
const loadDetailData = async (id: string) => {
|
||||||
|
try {
|
||||||
|
console.log('开始加载详情数据,reportId:', id)
|
||||||
|
const response = await getMyReportDetail(id)
|
||||||
|
console.log('接口返回数据:', response)
|
||||||
|
if (response) {
|
||||||
|
response.comments = response.comments || []
|
||||||
|
response.userIntegralRecords = response.userIntegralRecords || []
|
||||||
|
response.imageUrls = response.imageUrls || []
|
||||||
|
detailData.value = response
|
||||||
|
console.log('赋值给detailData的数据:', detailData.value)
|
||||||
|
} else {
|
||||||
|
console.error('接口返回数据格式错误:', response)
|
||||||
|
detailData.value = {
|
||||||
|
comments: [],
|
||||||
|
userIntegralRecords: [],
|
||||||
|
imageUrls: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取详情失败:', error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取图片URL
|
||||||
|
const getImageSrc = (url: string) => {
|
||||||
|
if (!url) return ''
|
||||||
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
if (url.startsWith('/')) {
|
||||||
|
return window.location.origin + url
|
||||||
|
}
|
||||||
|
return window.location.origin + '/' + url
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览图片
|
||||||
|
const showImage = (url: string) => {
|
||||||
|
imageViewerUrl.value = getImageSrc(url)
|
||||||
|
imageViewerVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期时间
|
||||||
|
const formatDateTime = (dateTime: any) => {
|
||||||
|
if (!dateTime) return ''
|
||||||
|
const date = new Date(dateTime)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态标签类型
|
||||||
|
const getStatusTagType = (status: any) => {
|
||||||
|
const statusMap: Record<string, string> = {
|
||||||
|
'STAGING': 'warning',
|
||||||
|
'AUDIT': 'primary',
|
||||||
|
'APPROVED': 'success',
|
||||||
|
'REVIEW_FAILED': 'danger'
|
||||||
|
}
|
||||||
|
return statusMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取状态文本
|
||||||
|
const getStatusText = (status: any) => {
|
||||||
|
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||||
|
return statusItem ? statusItem.text : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加评论
|
||||||
|
const addComment = async () => {
|
||||||
|
if (!commentText.value.trim()) {
|
||||||
|
ElMessage.warning('请输入评论内容')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
commentLoading.value = true
|
||||||
|
await saveComment(props.reportId, {
|
||||||
|
comment: commentText.value.trim()
|
||||||
|
})
|
||||||
|
ElMessage.success('评论成功')
|
||||||
|
commentText.value = ''
|
||||||
|
showCommentInput.value = false
|
||||||
|
await loadDetailData(props.reportId)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('评论失败:', error)
|
||||||
|
ElMessage.error('评论失败')
|
||||||
|
} finally {
|
||||||
|
commentLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除评论
|
||||||
|
const handleDeleteComment = async (index: number) => {
|
||||||
|
try {
|
||||||
|
const comment = detailData.value.comments[index]
|
||||||
|
if (!comment) return
|
||||||
|
|
||||||
|
await ElMessageBox.confirm('确定删除该评论吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
await deleteComment(comment.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
await loadDetailData(props.reportId)
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('删除失败:', error)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.main-container {
|
||||||
|
padding: 0px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-name {
|
||||||
|
color: #A8ABB2
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-warp {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-warp:last-child {
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-container {
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-container .user-score-detail-value {
|
||||||
|
display: inline-block;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-container .image-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-container .image-container-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 110px;
|
||||||
|
height: 110px;
|
||||||
|
margin-right: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-container .image-container-item .image-slot {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-container .image-container-item .image-slot .el-icon {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .b-white {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .comment-list {
|
||||||
|
margin-top: 10px;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .comment-list .card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .comment-list .comment-delete {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .comment-list .comment-content {
|
||||||
|
line-height: 21px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .comment-list .comment-user-info {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #A8ABB2;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .comment-list .el-card {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-container .add-comment-form .dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-descriptions__label){
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-descriptions__content){
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,512 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||||
|
<el-form-item label="标题">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.title"
|
||||||
|
placeholder="请输入标题"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="部门">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.deptId"
|
||||||
|
placeholder="请选择部门"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dept in deptOptions"
|
||||||
|
:key="dept.id"
|
||||||
|
:label="dept.name"
|
||||||
|
:value="dept.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="涉事人">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.personName"
|
||||||
|
placeholder="请输入涉事人"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="事项">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.reasonId"
|
||||||
|
placeholder="请选择事项"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in reportTypeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="填报时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="createTimeRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<!-- 表格操作区域 -->
|
||||||
|
<div class="table-header">
|
||||||
|
<el-button
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
>
|
||||||
|
<el-icon class="mr5"><Download /></el-icon>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="reportList"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
label="标题"
|
||||||
|
prop="title"
|
||||||
|
min-width="200"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="部门"
|
||||||
|
prop="deptName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="涉事人"
|
||||||
|
prop="personName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column label="事项" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.reportTypeName || row.reason?.name || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="分值" min-width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.userIntegralRecords?.[0]?.score || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="250"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.remark || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="填报时间" min-width="160" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDisplayTime(scope.row.fillingTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" min-width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||||
|
{{ getStatusText(scope.row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<span class="table-opera-item" @click="handleView(scope.row)" :title="'查看'">
|
||||||
|
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<template v-if="!loading && reportList.length === 0">
|
||||||
|
<el-empty description="暂无公司通报数据" :image-size="200" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 查看详情组件 -->
|
||||||
|
<DetailComponent
|
||||||
|
v-model:visible="detailDialogVisible"
|
||||||
|
:report-id="selectedReportId"
|
||||||
|
@refresh="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref, computed, watch } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { View, Download } from '@element-plus/icons-vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
import DetailComponent from '../common/detail.vue'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
import { enumsType } from '@/store/static'
|
||||||
|
import { getMyReportPage, exportMyReport } from '@/api/submit/myReporting'
|
||||||
|
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const deptStore = useDeptStore()
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
|
||||||
|
// Router
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const deptOptions = computed(() => deptStore.getDeptList)
|
||||||
|
const reportTypeOptions = computed(() => rewardsPunishReasonStore.getRewardsPunishReasonList)
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const reportList = ref<MyReportDTO[]>([])
|
||||||
|
const total = ref<number>(0)
|
||||||
|
const loading = ref<boolean>(true)
|
||||||
|
const exportLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const createTimeRange = ref<string[] | null>(null)
|
||||||
|
const detailDialogVisible = ref<boolean>(false)
|
||||||
|
const selectedReportId = ref<string>('')
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive<QueryParams>({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
personName: '',
|
||||||
|
reasonId: '',
|
||||||
|
status: '',
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
isOwnReport: false,
|
||||||
|
isOwnApprove: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const getStatusTagType = (status: string | number) => {
|
||||||
|
const typeMap: Record<string | number, string> = {
|
||||||
|
'STAGING': 'warning', 1: 'warning',
|
||||||
|
'AUDIT': 'primary', 2: 'primary',
|
||||||
|
'APPROVED': 'success', 3: 'success',
|
||||||
|
'REVIEW_FAILED': 'danger', 4: 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusText = (status: string | number) => {
|
||||||
|
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||||
|
return statusItem ? statusItem.text : '未知状态'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDateTime = (dateTime: string | undefined) => {
|
||||||
|
if (!dateTime) return ''
|
||||||
|
const date = new Date(dateTime)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDisplayTime = (time: string | undefined) => {
|
||||||
|
return time ? formatDateTime(time) : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
await initData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
try {
|
||||||
|
// 并行加载数据
|
||||||
|
await Promise.all([
|
||||||
|
!deptStore.getIsSetDept ? deptStore.setDeptInfoAction() : Promise.resolve(),
|
||||||
|
!rewardsPunishReasonStore.getIsSetRewardsPunishReason ? rewardsPunishReasonStore.setRewardsPunishReasonAction() : Promise.resolve()
|
||||||
|
])
|
||||||
|
await getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化数据失败:', error)
|
||||||
|
ElMessage.error('初始化数据失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
queryParams.status = 'APPROVED'
|
||||||
|
try {
|
||||||
|
const response = await getMyReportPage(queryParams)
|
||||||
|
reportList.value = response.list || []
|
||||||
|
total.value = response.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取公司通报列表失败:', error)
|
||||||
|
ElMessage.error('获取公司通报列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
|
||||||
|
// 处理填报时间范围
|
||||||
|
if (createTimeRange.value && createTimeRange.value.length === 2) {
|
||||||
|
queryParams.startTime = createTimeRange.value[0] + 'T00:00:00'
|
||||||
|
queryParams.endTime = createTimeRange.value[1] + 'T23:59:59'
|
||||||
|
} else {
|
||||||
|
queryParams.startTime = undefined
|
||||||
|
queryParams.endTime = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询
|
||||||
|
const resetQuery = () => {
|
||||||
|
Object.assign(queryParams, {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
personName: '',
|
||||||
|
reasonId: '',
|
||||||
|
status: '',
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
isOwnReport: false,
|
||||||
|
isOwnApprove: false
|
||||||
|
})
|
||||||
|
|
||||||
|
createTimeRange.value = null
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleView = (row: MyReportDTO) => {
|
||||||
|
selectedReportId.value = row.id
|
||||||
|
detailDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化,当路由发生变化时关闭抽屉
|
||||||
|
watch(
|
||||||
|
[() => route.path, () => route.fullPath],
|
||||||
|
() => {
|
||||||
|
detailDialogVisible.value = false
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
const handleExport = async () => {
|
||||||
|
exportLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await exportMyReport(queryParams)
|
||||||
|
|
||||||
|
// 处理文件下载
|
||||||
|
if (response instanceof Blob) {
|
||||||
|
// 创建Blob URL
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||||
|
})
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
|
||||||
|
// 从响应头获取文件名,或者使用默认文件名
|
||||||
|
const filename = getFilenameFromResponse(response) || `公司通报数据.xlsx`
|
||||||
|
link.download = filename
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
ElMessage.success('导出成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.success('导出请求已发送')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
ElMessage.error('导出失败')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从响应中获取文件名
|
||||||
|
const getFilenameFromResponse = (response: any) => {
|
||||||
|
try {
|
||||||
|
// 如果是Response对象,从headers中获取
|
||||||
|
if (response.headers) {
|
||||||
|
const contentDisposition = response.headers.get('content-disposition')
|
||||||
|
if (contentDisposition) {
|
||||||
|
const filenameMatch = contentDisposition.match(/filename="?(.+?)\"?/i)
|
||||||
|
if (filenameMatch && filenameMatch[1]) {
|
||||||
|
return decodeURIComponent(filenameMatch[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取文件名失败:', e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作列样式 */
|
||||||
|
.table-opera-item {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
transition: color 0.3s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-item:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr5 {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
color: #eb333d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon:hover {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
animation: rotate 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,759 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="optType === 'approve' ? '审批' : 'Dialog'"
|
||||||
|
:model-value="dialogVisible"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
width="600px"
|
||||||
|
@update:model-value="handleDialogUpdate"
|
||||||
|
>
|
||||||
|
<el-form size="default" @submit.prevent :model="entity" ref="formRef" :rules="rules" class="member" label-position="top">
|
||||||
|
<!-- 基础信息区域 -->
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="标题" class="labelTop">
|
||||||
|
<el-input
|
||||||
|
size="default"
|
||||||
|
class="fw"
|
||||||
|
v-model="entity.title"
|
||||||
|
disabled
|
||||||
|
placeholder="-"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="部门" class="labelTop">
|
||||||
|
<el-input
|
||||||
|
v-model="entity.deptName"
|
||||||
|
disabled
|
||||||
|
placeholder="-"
|
||||||
|
:suffix-icon="Check"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="涉事人" class="labelTop">
|
||||||
|
<el-input
|
||||||
|
v-model="entity.personName"
|
||||||
|
disabled
|
||||||
|
placeholder="-"
|
||||||
|
:suffix-icon="Check"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="事项" class="labelTop">
|
||||||
|
<el-input
|
||||||
|
v-model="entity.reason.name"
|
||||||
|
disabled
|
||||||
|
placeholder="-"
|
||||||
|
:suffix-icon="Check"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="备注" class="labelTop">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
maxlength="1000"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
class="fw"
|
||||||
|
v-model="entity.remark"
|
||||||
|
disabled
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 分值设置区域 -->
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="分值" class="labelTop new-label" required>
|
||||||
|
<div class="score-instruction">
|
||||||
|
<el-text size="small" type="info">请为相关人员进行分值设置(-10到10分,支持0.5分间隔)</el-text>
|
||||||
|
</div>
|
||||||
|
<div v-for="(item, index) in entity.userIntegralRecords" :key="index" class="res-item">
|
||||||
|
<el-select
|
||||||
|
v-model="item.userName"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择用户"
|
||||||
|
clearable
|
||||||
|
:disabled="index === 0"
|
||||||
|
class="user-select"
|
||||||
|
@change="handleUserChange(index, $event)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="user in persons"
|
||||||
|
:key="user.id"
|
||||||
|
:label="user.name"
|
||||||
|
:value="user.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<!-- 带加减按钮的分值输入 -->
|
||||||
|
<div class="score-control">
|
||||||
|
<el-button
|
||||||
|
@click="decreaseScore(index)"
|
||||||
|
:disabled="item.score <= -10"
|
||||||
|
class="score-btn decrease-btn"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</el-button>
|
||||||
|
<el-input
|
||||||
|
size="default"
|
||||||
|
class="score-input"
|
||||||
|
v-model="item.score"
|
||||||
|
type="number"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
:step="0.5"
|
||||||
|
@change="handleScoreChange(item, index)"
|
||||||
|
@blur="handleScoreBlur(item, index, true)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
@click="increaseScore(index)"
|
||||||
|
:disabled="item.score >= 10"
|
||||||
|
class="score-btn increase-btn"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="action-icons">
|
||||||
|
<el-icon
|
||||||
|
v-if="index === 0"
|
||||||
|
@click="addUserScore"
|
||||||
|
class="action-icon add-icon"
|
||||||
|
title="添加人员"
|
||||||
|
>
|
||||||
|
<CirclePlus />
|
||||||
|
</el-icon>
|
||||||
|
<el-icon
|
||||||
|
v-else
|
||||||
|
@click="removeUserScore(index)"
|
||||||
|
class="action-icon remove-icon"
|
||||||
|
title="移除"
|
||||||
|
>
|
||||||
|
<Remove />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 审批结论 -->
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="审批结论" class="labelTop" prop="action">
|
||||||
|
<el-select
|
||||||
|
v-model="entity.action"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
class="action-select"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in approveActions"
|
||||||
|
:key="item.code"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 审批意见 -->
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="审批意见" class="labelTop">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
maxlength="500"
|
||||||
|
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||||
|
class="fw"
|
||||||
|
v-model="entity.comment"
|
||||||
|
placeholder="请输入审批意见"
|
||||||
|
clearable
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="cancel">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submit" :loading="submitting">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { CirclePlus, Remove, Check } from '@element-plus/icons-vue'
|
||||||
|
import { useUserListStore } from '@/store/modules/userList'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import type { MyReportDTO } from '@/api/submit/myReporting/types'
|
||||||
|
import { approveMyReport } from '@/api/submit/myApprove'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps<{
|
||||||
|
dialogVisible: boolean
|
||||||
|
optType: 'approve' | 'view'
|
||||||
|
formData: MyReportDTO
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:dialogVisible': [value: boolean]
|
||||||
|
success: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const userListStore = useUserListStore()
|
||||||
|
const deptStore = useDeptStore()
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
|
||||||
|
// Ref
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const entity = reactive({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
personId: '',
|
||||||
|
personName: '',
|
||||||
|
reason: {
|
||||||
|
id: '',
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
userIntegralRecords: [{
|
||||||
|
userId: '',
|
||||||
|
userName: '',
|
||||||
|
score: 0
|
||||||
|
}],
|
||||||
|
remark: '',
|
||||||
|
comment: '',
|
||||||
|
action: '',
|
||||||
|
status: 'AUDIT'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
action: [
|
||||||
|
{ required: true, message: '请选择审批结论', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const persons = computed(() => {
|
||||||
|
return userListStore.getUserList?.map(user => ({
|
||||||
|
id: user.id,
|
||||||
|
name: user.nickname || user.realName || user.username
|
||||||
|
})) || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const deptOptions = computed(() => {
|
||||||
|
return deptStore.getDeptList || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const reasonOptions = computed(() => {
|
||||||
|
return rewardsPunishReasonStore.getRewardsPunishReasonList || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const approveActions = computed(() => [
|
||||||
|
{ code: 'PASS', name: '通过' },
|
||||||
|
{ code: 'REFUSE', name: '驳回' }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 增加分数
|
||||||
|
const increaseScore = (index: number) => {
|
||||||
|
const currentScore = entity.userIntegralRecords[index].score
|
||||||
|
const newScore = Math.min(currentScore + 0.5, 10)
|
||||||
|
entity.userIntegralRecords[index].score = newScore
|
||||||
|
validateScore(entity.userIntegralRecords[index], index, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 减少分数
|
||||||
|
const decreaseScore = (index: number) => {
|
||||||
|
const currentScore = entity.userIntegralRecords[index].score
|
||||||
|
const newScore = Math.max(currentScore - 0.5, -10)
|
||||||
|
entity.userIntegralRecords[index].score = newScore
|
||||||
|
validateScore(entity.userIntegralRecords[index], index, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证分值
|
||||||
|
const validateScore = (item: any, index: number, showMessage: boolean = false) => {
|
||||||
|
let score = item.score
|
||||||
|
|
||||||
|
// 确保是数字
|
||||||
|
if (typeof score !== 'number') {
|
||||||
|
score = parseFloat(score)
|
||||||
|
if (isNaN(score)) score = 0
|
||||||
|
item.score = score
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查范围
|
||||||
|
if (score < -10) {
|
||||||
|
item.score = -10
|
||||||
|
if (showMessage) {
|
||||||
|
ElMessage.warning('分值不能小于-10')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score > 10) {
|
||||||
|
item.score = 10
|
||||||
|
if (showMessage) {
|
||||||
|
ElMessage.warning('分值不能大于10')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为 0.5 的倍数
|
||||||
|
if (score !== 0) {
|
||||||
|
const remainder = (score * 10) % 5
|
||||||
|
if (Math.abs(remainder) > 0.01 && Math.abs(remainder) < 4.99) {
|
||||||
|
// 自动修正为最接近的 0.5 倍数
|
||||||
|
const correctedScore = Math.round(score * 2) / 2
|
||||||
|
item.score = correctedScore
|
||||||
|
|
||||||
|
if (showMessage) {
|
||||||
|
ElMessage.warning({
|
||||||
|
message: `分值必须是0.5的倍数,已自动调整为 ${correctedScore}`,
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理对话框显示状态更新
|
||||||
|
const handleDialogUpdate = (visible: boolean) => {
|
||||||
|
emit('update:dialogVisible', visible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加用户分值行
|
||||||
|
const addUserScore = () => {
|
||||||
|
entity.userIntegralRecords.push({
|
||||||
|
userId: '',
|
||||||
|
userName: '',
|
||||||
|
score: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户分值行
|
||||||
|
const removeUserScore = (index: number) => {
|
||||||
|
if (entity.userIntegralRecords.length > 1) {
|
||||||
|
entity.userIntegralRecords.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户选择变化处理
|
||||||
|
const handleUserChange = (index: number, userId: string) => {
|
||||||
|
if (userId) {
|
||||||
|
const user = persons.value.find(u => u.id === userId)
|
||||||
|
if (user) {
|
||||||
|
entity.userIntegralRecords[index].userName = user.name
|
||||||
|
entity.userIntegralRecords[index].userId = userId
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entity.userIntegralRecords[index].userName = ''
|
||||||
|
entity.userIntegralRecords[index].userId = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分值变化处理
|
||||||
|
const handleScoreChange = (item: any, index: number) => {
|
||||||
|
validateScore(item, index, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分值失去焦点处理
|
||||||
|
const handleScoreBlur = (item: any, index: number, showMessage: boolean = false) => {
|
||||||
|
validateScore(item, index, showMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
const cancel = () => {
|
||||||
|
emit('update:dialogVisible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交审批
|
||||||
|
const submit = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
// 验证分值项数据
|
||||||
|
const invalidRecords = entity.userIntegralRecords.filter(item => !item.userId)
|
||||||
|
if (invalidRecords.length > 0) {
|
||||||
|
ElMessage.error('请为所有分值项选择用户')
|
||||||
|
submitting.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有重复用户
|
||||||
|
const userIds = entity.userIntegralRecords.map(item => item.userId)
|
||||||
|
const uniqueUserIds = [...new Set(userIds)]
|
||||||
|
if (userIds.length !== uniqueUserIds.length) {
|
||||||
|
ElMessage.error('请勿选择重复的用户')
|
||||||
|
submitting.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查分值是否是0.5的倍数
|
||||||
|
const invalidScores = entity.userIntegralRecords.filter((item: any) => {
|
||||||
|
if (item.score === 0 || item.score === 0.0) return false
|
||||||
|
const remainder = Math.abs((item.score * 10) % 5)
|
||||||
|
return remainder > 0.01 && remainder < 4.99
|
||||||
|
})
|
||||||
|
|
||||||
|
if (invalidScores.length > 0) {
|
||||||
|
ElMessage.error('分值必须是0.5的倍数')
|
||||||
|
submitting.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 构建请求参数
|
||||||
|
const params = {
|
||||||
|
action: entity.action,
|
||||||
|
comment: entity.comment || '',
|
||||||
|
userIntegralRecords: entity.userIntegralRecords.map(item => ({
|
||||||
|
userId: item.userId,
|
||||||
|
userName: item.userName,
|
||||||
|
score: item.score
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用审批接口
|
||||||
|
await approveMyReport({ reportId: entity.id, ...params })
|
||||||
|
|
||||||
|
ElMessage.success('审批成功')
|
||||||
|
emit('success')
|
||||||
|
emit('update:dialogVisible', false)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('审批失败:', error)
|
||||||
|
ElMessage.error(error.msg || '审批失败,请重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听props变化
|
||||||
|
watch(
|
||||||
|
() => props.formData,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal && newVal.id) {
|
||||||
|
// 检查部门字段
|
||||||
|
const deptName = newVal.deptName || (newVal as any).depthName || ''
|
||||||
|
const deptId = newVal.deptId || (newVal as any).depthId || ''
|
||||||
|
|
||||||
|
// 1. 设置基础字段
|
||||||
|
Object.assign(entity, {
|
||||||
|
id: newVal.id || '',
|
||||||
|
title: newVal.title || '',
|
||||||
|
deptId: deptId,
|
||||||
|
deptName: deptName,
|
||||||
|
personId: newVal.personId || '',
|
||||||
|
personName: newVal.personName || '',
|
||||||
|
remark: newVal.remark || '',
|
||||||
|
comment: '',
|
||||||
|
action: '',
|
||||||
|
status: newVal.status || 'AUDIT'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. 设置事项字段
|
||||||
|
let reasonName = ''
|
||||||
|
let reasonId = ''
|
||||||
|
|
||||||
|
// 尝试从多个来源获取事项信息
|
||||||
|
if (newVal.reason && typeof newVal.reason === 'object') {
|
||||||
|
reasonId = newVal.reason.id || ''
|
||||||
|
reasonName = newVal.reason.name || ''
|
||||||
|
} else if ((newVal as any).reasonName) {
|
||||||
|
// 备选:reasonName 字段
|
||||||
|
reasonName = (newVal as any).reasonName
|
||||||
|
reasonId = newVal.reasonId || ''
|
||||||
|
} else if (newVal.reasonId) {
|
||||||
|
reasonId = newVal.reasonId
|
||||||
|
const reasonOption = reasonOptions.value.find(r => r.id === reasonId)
|
||||||
|
reasonName = reasonOption?.name || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.reason = {
|
||||||
|
id: reasonId,
|
||||||
|
name: reasonName || '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 设置积分记录
|
||||||
|
if (newVal.userIntegralRecords && Array.isArray(newVal.userIntegralRecords) && newVal.userIntegralRecords.length > 0) {
|
||||||
|
// 使用原始积分记录
|
||||||
|
entity.userIntegralRecords = newVal.userIntegralRecords.map(item => ({
|
||||||
|
userId: item.userId || '',
|
||||||
|
userName: item.userName || '',
|
||||||
|
score: item.score || 0
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// 创建默认积分记录
|
||||||
|
entity.userIntegralRecords = [{
|
||||||
|
userId: newVal.personId || '',
|
||||||
|
userName: newVal.personName || '',
|
||||||
|
score: 0
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true, immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听对话框显示状态
|
||||||
|
watch(
|
||||||
|
() => props.dialogVisible,
|
||||||
|
(newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
// 弹窗关闭时重置表单
|
||||||
|
setTimeout(() => {
|
||||||
|
Object.assign(entity, {
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
personId: '',
|
||||||
|
personName: '',
|
||||||
|
reason: { id: '', name: '' },
|
||||||
|
userIntegralRecords: [{ userId: '', userName: '', score: 0 }],
|
||||||
|
remark: '',
|
||||||
|
comment: '',
|
||||||
|
action: '',
|
||||||
|
status: 'AUDIT'
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
// 确保部门数据已加载
|
||||||
|
if (!deptStore.getIsSetDept) {
|
||||||
|
await deptStore.setDeptInfoAction()
|
||||||
|
}
|
||||||
|
// 确保人员数据已加载
|
||||||
|
if (!userListStore.getIsSetUserList) {
|
||||||
|
await userListStore.setUserListAction()
|
||||||
|
}
|
||||||
|
// 确保奖惩原因数据已加载
|
||||||
|
if (!rewardsPunishReasonStore.getIsSetRewardsPunishReason) {
|
||||||
|
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.member {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelTop {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelTop :deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fw {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-instruction {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-label {
|
||||||
|
.res-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-select {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 带加减按钮的分值输入 */
|
||||||
|
.score-control {
|
||||||
|
flex: 0 0 160px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-btn {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decrease-btn {
|
||||||
|
color: #f56c6c;
|
||||||
|
border-color: #fde2e2;
|
||||||
|
background-color: #fef0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decrease-btn:hover {
|
||||||
|
background-color: #fde2e2;
|
||||||
|
border-color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.increase-btn {
|
||||||
|
color: #67c23a;
|
||||||
|
border-color: #e1f3d8;
|
||||||
|
background-color: #f0f9eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.increase-btn:hover {
|
||||||
|
background-color: #e1f3d8;
|
||||||
|
border-color: #67c23a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-input :deep(.el-input__inner) {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作图标样式 */
|
||||||
|
.action-icons {
|
||||||
|
flex: 0 0 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon {
|
||||||
|
color: #67c23a;
|
||||||
|
background-color: #f0f9eb;
|
||||||
|
border: 1px solid #e1f3d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon:hover {
|
||||||
|
background-color: #e1f3d8;
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 2px 4px rgba(103, 194, 58, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-icon {
|
||||||
|
color: #f56c6c;
|
||||||
|
background-color: #fef0f0;
|
||||||
|
border: 1px solid #fde2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-icon:hover {
|
||||||
|
background-color: #fde2e2;
|
||||||
|
transform: scale(1.1);
|
||||||
|
box-shadow: 0 2px 4px rgba(245, 108, 108, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-display .el-tag {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input.is-disabled .el-input__wrapper) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-textarea.is-disabled .el-textarea__inner) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,560 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 筛选区域 -->
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||||
|
<el-form-item label="标题">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.title"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="部门">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.deptId"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dept in deptOptions"
|
||||||
|
:key="dept.id"
|
||||||
|
:label="dept.name"
|
||||||
|
:value="dept.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="涉事人">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.personName"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="事项">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.reasonId"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in reportTypeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.status"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
@change="handleStatusChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in reportStatus"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.text"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="填报时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="createTimeRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="table-header">
|
||||||
|
<el-button @click="handleExport" :loading="exportLoading">
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="reportList"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无审批数据" :image-size="200">
|
||||||
|
<el-button type="primary" @click="handleQuery">刷新数据</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</template>
|
||||||
|
<el-table-column
|
||||||
|
label="标题"
|
||||||
|
prop="title"
|
||||||
|
min-width="200"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="部门"
|
||||||
|
prop="deptName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="涉事人"
|
||||||
|
prop="personName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column label="事项" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.reportTypeName || row.reason?.name || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="250"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.remark || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="执行人"
|
||||||
|
prop="executorName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column label="填报时间" min-width="160" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDisplayTime(scope.row.fillingTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" min-width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||||
|
{{ getStatusText(scope.row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="审批人"
|
||||||
|
prop="approveUserName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="审批时间"
|
||||||
|
prop="approveTime"
|
||||||
|
min-width="160"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<span
|
||||||
|
class="table-opera-item"
|
||||||
|
@click="handleApprove(scope.row)"
|
||||||
|
v-if="scope.row.status === 'AUDIT' || scope.row.status === 2 || scope.row.status === '待审批'"
|
||||||
|
:title="'审批'"
|
||||||
|
style="z-index: 10; position: relative;"
|
||||||
|
>
|
||||||
|
<el-icon class="table-opera-icon"><Notification /></el-icon>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="table-opera-item ml10"
|
||||||
|
@click="handleView(scope.row)"
|
||||||
|
:title="'查看'"
|
||||||
|
style="z-index: 10; position: relative;"
|
||||||
|
>
|
||||||
|
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 引入审批弹窗组件 -->
|
||||||
|
<Approve
|
||||||
|
:dialog-visible="dialogVisible"
|
||||||
|
:opt-type="optType"
|
||||||
|
:form-data="formData"
|
||||||
|
@update:dialog-visible="handleDialogVisibleUpdate"
|
||||||
|
@success="handleApproveSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 查看详情组件 -->
|
||||||
|
<DetailComponent
|
||||||
|
v-model:visible="detailDialogVisible"
|
||||||
|
:report-id="selectedReportId"
|
||||||
|
@refresh="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref, computed, watch } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { View, Notification } from '@element-plus/icons-vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import DetailComponent from '../common/detail.vue'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
import Approve from './approve.vue'
|
||||||
|
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
|
||||||
|
import { getApproveList, exportMyReport } from '@/api/submit/myApprove'
|
||||||
|
import { enumsType } from '@/store/static'
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
const deptStore = useDeptStore()
|
||||||
|
|
||||||
|
// Router
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const deptOptions = computed(() => deptStore.getDeptList)
|
||||||
|
const reportTypeOptions = computed(() => rewardsPunishReasonStore.getRewardsPunishReasonList)
|
||||||
|
const reportStatus = computed(() => {
|
||||||
|
return enumsType.reportStatus.filter(item => item.value !== 'STAGING' && item.value !== 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const reportList = ref<MyReportDTO[]>([])
|
||||||
|
const total = ref<number>(0)
|
||||||
|
const loading = ref<boolean>(true)
|
||||||
|
const exportLoading = ref<boolean>(false)
|
||||||
|
|
||||||
|
const createTimeRange = ref<string[] | null>(null)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const detailDialogVisible = ref<boolean>(false)
|
||||||
|
const selectedReportId = ref<string>('')
|
||||||
|
const optType = ref<'approve'>('approve')
|
||||||
|
const formData = ref<MyReportDTO>({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
reasonId: '',
|
||||||
|
reportTypeName: '',
|
||||||
|
personName: '',
|
||||||
|
content: '',
|
||||||
|
remark: '',
|
||||||
|
imageUrls: [],
|
||||||
|
status: 'AUDIT',
|
||||||
|
approverId: '',
|
||||||
|
approverName: '',
|
||||||
|
approveTime: '',
|
||||||
|
approveComment: '',
|
||||||
|
createBy: '',
|
||||||
|
createTime: '',
|
||||||
|
userIntegralRecords: [],
|
||||||
|
comments: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive<QueryParams>({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
personName: '',
|
||||||
|
reasonId: '',
|
||||||
|
status: 'AUDIT',
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理对话框显示状态更新
|
||||||
|
const handleDialogVisibleUpdate = (visible: boolean) => {
|
||||||
|
dialogVisible.value = visible
|
||||||
|
}
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const getStatusTagType = (status: string | number) => {
|
||||||
|
const typeMap: Record<string | number, string> = {
|
||||||
|
AUDIT: 'primary',
|
||||||
|
2: 'primary',
|
||||||
|
APPROVED: 'success',
|
||||||
|
3: 'success',
|
||||||
|
REVIEW_FAILED: 'danger',
|
||||||
|
4: 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusText = (status: string | number) => {
|
||||||
|
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||||
|
return statusItem ? statusItem.text : '未知状态'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDateTime = (dateTime: string | undefined) => {
|
||||||
|
if (!dateTime) return ''
|
||||||
|
const date = new Date(dateTime)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDisplayTime = (time: string | undefined) => {
|
||||||
|
return time ? formatDateTime(time) : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态变化处理
|
||||||
|
const handleStatusChange = () => {
|
||||||
|
queryParams.isOwnApprove = queryParams.status === 'AUDIT' || queryParams.status === 2 ? false : true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
await initData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
try {
|
||||||
|
// 并行加载数据
|
||||||
|
await Promise.all([
|
||||||
|
!rewardsPunishReasonStore.getIsSetRewardsPunishReason
|
||||||
|
? rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||||
|
: Promise.resolve(),
|
||||||
|
!deptStore.getIsSetDept
|
||||||
|
? deptStore.setDeptInfoAction()
|
||||||
|
: Promise.resolve()
|
||||||
|
])
|
||||||
|
|
||||||
|
await getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化数据失败:', error)
|
||||||
|
ElMessage.error('初始化数据失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理审批成功回调
|
||||||
|
const handleApproveSuccess = () => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await getApproveList(queryParams)
|
||||||
|
reportList.value = response.list || []
|
||||||
|
total.value = response.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列表失败:', error)
|
||||||
|
ElMessage.error('获取列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
|
||||||
|
// 处理填报时间范围
|
||||||
|
if (createTimeRange.value && createTimeRange.value.length === 2) {
|
||||||
|
queryParams.startTime = `${createTimeRange.value[0]}T00:00:00`
|
||||||
|
queryParams.endTime = `${createTimeRange.value[1]}T23:59:59`
|
||||||
|
} else {
|
||||||
|
queryParams.startTime = undefined
|
||||||
|
queryParams.endTime = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询
|
||||||
|
const resetQuery = () => {
|
||||||
|
Object.assign(queryParams, {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
personName: '',
|
||||||
|
reasonId: '',
|
||||||
|
status: 'AUDIT',
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
createTimeRange.value = null
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批 - 修复后的函数
|
||||||
|
const handleApprove = (row: MyReportDTO) => {
|
||||||
|
|
||||||
|
optType.value = 'approve'
|
||||||
|
|
||||||
|
// 修复:确保传递的数据包含所有必要字段
|
||||||
|
formData.value = row
|
||||||
|
|
||||||
|
console.log('传递给弹窗的数据:', formData.value)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleView = (row: MyReportDTO) => {
|
||||||
|
selectedReportId.value = row.id
|
||||||
|
detailDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化,当路由发生变化时关闭抽屉
|
||||||
|
watch(
|
||||||
|
[() => route.path, () => route.fullPath],
|
||||||
|
() => {
|
||||||
|
detailDialogVisible.value = false
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
const handleExport = async () => {
|
||||||
|
exportLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await exportMyReport(queryParams)
|
||||||
|
|
||||||
|
// 处理文件下载
|
||||||
|
if (response instanceof Blob) {
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||||
|
})
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
const filename = '审批数据.xlsx'
|
||||||
|
link.download = filename
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
ElMessage.success('导出成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.success('导出请求已发送')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
ElMessage.error('导出失败')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-item {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
transition: color 0.3s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-item:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 使用 :deep() 简化样式 */
|
||||||
|
:deep(.el-card) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,478 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="dialogTitle"
|
||||||
|
v-model="visible"
|
||||||
|
width="800px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="标题:" prop="title">
|
||||||
|
<el-input
|
||||||
|
v-model="form.title"
|
||||||
|
placeholder="请输入标题"
|
||||||
|
style="width: 100%"
|
||||||
|
maxlength="50"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="部门:" prop="deptId">
|
||||||
|
<el-select
|
||||||
|
v-model="form.deptId"
|
||||||
|
placeholder="请选择部门"
|
||||||
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
@change="handleDeptChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dept in deptOptions"
|
||||||
|
:key="dept.id"
|
||||||
|
:label="dept.name"
|
||||||
|
:value="String(dept.id)"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="涉事人:" prop="personId">
|
||||||
|
<el-select
|
||||||
|
v-model="form.personId"
|
||||||
|
placeholder="请选择涉事人"
|
||||||
|
style="width: 100%"
|
||||||
|
filterable
|
||||||
|
@change="handlePersonChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in personOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="String(item.id)"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="事项:" prop="reasonId">
|
||||||
|
<el-select
|
||||||
|
v-model="form.reasonId"
|
||||||
|
placeholder="请选择事项"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="handleReasonChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in reportTypeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="分值:" prop="score">
|
||||||
|
<el-input-number
|
||||||
|
v-if="showScoreInput"
|
||||||
|
v-model="form.userIntegralRecords[0].score"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
placeholder="请输入分值"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="scoreDisabled"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-if="showScoreSelect"
|
||||||
|
v-model="form.userIntegralRecords[0].score"
|
||||||
|
placeholder="请选择分值"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="scoreDisabled"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="score in ruleScore.scores"
|
||||||
|
:key="score"
|
||||||
|
:label="score"
|
||||||
|
:value="score"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="图片:">
|
||||||
|
<UploadImgs
|
||||||
|
v-model="form.imageUrls"
|
||||||
|
:limit="5"
|
||||||
|
:file-size="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="备注:">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
:rows="3"
|
||||||
|
resize="none"
|
||||||
|
style="width: 100%"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button
|
||||||
|
@click="handleClose"
|
||||||
|
size="large"
|
||||||
|
style="width: 100px;"
|
||||||
|
:disabled="submitLoading"
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="submitForm"
|
||||||
|
size="large"
|
||||||
|
style="width: 100px;"
|
||||||
|
:loading="submitLoading"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import { UploadImgs } from '@/components/UploadFile'
|
||||||
|
import type { MyReportDTO, SaveParams, UserIntegralRecord, RuleScoreResponse } from '@/api/submit/myReporting/types'
|
||||||
|
import { createMyReport, updateMyReport, getRuleScore } from '@/api/submit/myReporting'
|
||||||
|
import { getUserPage } from '@/api/system/user'
|
||||||
|
import { enumsType } from '@/store/static'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
interface Props {
|
||||||
|
dialogVisible: boolean
|
||||||
|
optType: 'add' | 'edit'
|
||||||
|
formData?: MyReportDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
dialogVisible: false,
|
||||||
|
optType: 'add',
|
||||||
|
formData: () => ({})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:dialogVisible', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
const deptStore = useDeptStore()
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const deptOptions = computed(() => deptStore.getDeptList)
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const submitLoading = ref<boolean>(false)
|
||||||
|
const personLoading = ref<boolean>(false)
|
||||||
|
const dialogTitle = ref<string>('新增上报')
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const visible = ref<boolean>(props.dialogVisible)
|
||||||
|
|
||||||
|
// 监听弹窗显示状态变化
|
||||||
|
watch(
|
||||||
|
() => visible.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:dialogVisible', newVal)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 监听父组件传入的dialogVisible变化
|
||||||
|
watch(
|
||||||
|
() => props.dialogVisible,
|
||||||
|
(newVal) => {
|
||||||
|
visible.value = newVal
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const personOptions = ref<any[]>([])
|
||||||
|
const reportTypeOptions = ref<any[]>([])
|
||||||
|
|
||||||
|
// 分值相关状态
|
||||||
|
const ruleScore = ref<RuleScoreResponse>({ type: '', scores: [] })
|
||||||
|
const showScoreInput = ref(true)
|
||||||
|
const showScoreSelect = ref(false)
|
||||||
|
const scoreDisabled = ref(true)
|
||||||
|
|
||||||
|
// 表单初始值
|
||||||
|
const getInitialForm = (): SaveParams => ({
|
||||||
|
id: undefined,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
reasonId: '',
|
||||||
|
personName: '',
|
||||||
|
personId: '',
|
||||||
|
remark: '',
|
||||||
|
content: '',
|
||||||
|
imageUrls: [],
|
||||||
|
userIntegralRecords: [{
|
||||||
|
userId: '',
|
||||||
|
userName: '',
|
||||||
|
score: null
|
||||||
|
}],
|
||||||
|
comments: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = reactive<SaveParams>(getInitialForm())
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
title: [
|
||||||
|
{ required: true, message: '标题不能为空', trigger: 'blur' },
|
||||||
|
{ min: 1, max: 50, message: '标题长度在 1 到 50 个字符', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
deptId: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||||
|
reasonId: [{ required: true, message: '事项不能为空', trigger: 'change' }],
|
||||||
|
personId: [{ required: true, message: '涉事人不能为空', trigger: 'change' }],
|
||||||
|
'userIntegralRecords[0].score': [{ required: true, message: '分值不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听弹窗显示状态
|
||||||
|
watch(visible, (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
dialogTitle.value = props.optType === 'add' ? '新增上报' : '编辑上报'
|
||||||
|
|
||||||
|
// 加载事项类型
|
||||||
|
loadReportTypes()
|
||||||
|
|
||||||
|
if (props.optType === 'edit' && props.formData) {
|
||||||
|
// 编辑模式下填充表单数据
|
||||||
|
// 确保userIntegralRecords只保留一条记录
|
||||||
|
const integralRecord = props.formData.userIntegralRecords && props.formData.userIntegralRecords.length > 0
|
||||||
|
? props.formData.userIntegralRecords[0]
|
||||||
|
: { userId: '', userName: '', score: null }
|
||||||
|
|
||||||
|
// 过滤评论数据,只保留后端需要的字段
|
||||||
|
const filteredComments = (props.formData.comments || []).map(comment => ({
|
||||||
|
comment: comment.comment,
|
||||||
|
createUserName: comment.createUserName
|
||||||
|
}));
|
||||||
|
|
||||||
|
Object.assign(form, {
|
||||||
|
id: props.formData.id,
|
||||||
|
title: props.formData.title,
|
||||||
|
deptId: String(props.formData.deptId),
|
||||||
|
deptName: props.formData.deptName,
|
||||||
|
reasonId: String(props.formData.reportType || props.formData.reasonId),
|
||||||
|
personName: props.formData.personName,
|
||||||
|
personId: String(props.formData.personId),
|
||||||
|
remark: props.formData.remark,
|
||||||
|
imageUrls: props.formData.imageUrls || [],
|
||||||
|
userIntegralRecords: [integralRecord],
|
||||||
|
comments: filteredComments
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载部门用户
|
||||||
|
if (form.deptId) {
|
||||||
|
loadPersonOptions(form.deptId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 新增模式下重置表单
|
||||||
|
Object.assign(form, getInitialForm())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 加载事项类型
|
||||||
|
const loadReportTypes = async () => {
|
||||||
|
try {
|
||||||
|
// 确保数据已加载
|
||||||
|
if (!rewardsPunishReasonStore.getIsSetRewardsPunishReason) {
|
||||||
|
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||||
|
}
|
||||||
|
reportTypeOptions.value = rewardsPunishReasonStore.getRewardsPunishReasonList
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载事项类型失败:', error)
|
||||||
|
reportTypeOptions.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载部门用户
|
||||||
|
const loadPersonOptions = async (deptId: string | number) => {
|
||||||
|
if (!deptId) {
|
||||||
|
personOptions.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
personLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await getUserPage({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 100,
|
||||||
|
deptId
|
||||||
|
})
|
||||||
|
personOptions.value = response.list || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取部门用户失败:', error)
|
||||||
|
ElMessage.error('获取部门用户失败')
|
||||||
|
personOptions.value = []
|
||||||
|
} finally {
|
||||||
|
personLoading.value = false
|
||||||
|
|
||||||
|
// 加载完成后,检查是否有已选的涉事人,如果有则自动填充名称
|
||||||
|
if (form.personId) {
|
||||||
|
const selectedPerson = personOptions.value.find(item => String(item.id) === form.personId)
|
||||||
|
if (selectedPerson) {
|
||||||
|
form.personName = selectedPerson.nickname
|
||||||
|
form.userIntegralRecords[0].userId = Number(form.personId!) || ''
|
||||||
|
form.userIntegralRecords[0].userName = form.personName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载分值规则
|
||||||
|
const loadScore = async () => {
|
||||||
|
if (!form.personId || !form.reasonId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scoreDisabled.value = true
|
||||||
|
try {
|
||||||
|
const response = await getRuleScore(form.reasonId, form.personId)
|
||||||
|
ruleScore.value = response
|
||||||
|
|
||||||
|
switch (ruleScore.value.type) {
|
||||||
|
case 'MANUAL_VALUE':
|
||||||
|
form.userIntegralRecords[0].score = null
|
||||||
|
showScoreSelect.value = false
|
||||||
|
showScoreInput.value = true
|
||||||
|
break
|
||||||
|
case 'FIXED_VALUE':
|
||||||
|
case 'CALC_VALUE':
|
||||||
|
showScoreSelect.value = false
|
||||||
|
showScoreInput.value = true
|
||||||
|
form.userIntegralRecords[0].score = ruleScore.value.scores[0] || null
|
||||||
|
break
|
||||||
|
case 'CHOOSE_VALUE':
|
||||||
|
showScoreSelect.value = true
|
||||||
|
showScoreInput.value = false
|
||||||
|
form.userIntegralRecords[0].score = null
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
showScoreSelect.value = false
|
||||||
|
showScoreInput.value = true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载分值规则失败:', error)
|
||||||
|
form.userIntegralRecords[0].score = null
|
||||||
|
showScoreSelect.value = false
|
||||||
|
showScoreInput.value = true
|
||||||
|
} finally {
|
||||||
|
scoreDisabled.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 涉事人变更处理
|
||||||
|
const handlePersonChange = () => {
|
||||||
|
const selectedPerson = personOptions.value.find(item => String(item.id) === form.personId)
|
||||||
|
if (selectedPerson) {
|
||||||
|
form.personName = selectedPerson.nickname
|
||||||
|
form.userIntegralRecords[0].userId = Number(form.personId!) || ''
|
||||||
|
form.userIntegralRecords[0].userName = form.personName
|
||||||
|
}
|
||||||
|
loadScore()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 部门变更处理
|
||||||
|
const handleDeptChange = async () => {
|
||||||
|
// 清除已选涉事人
|
||||||
|
form.personId = ''
|
||||||
|
form.personName = ''
|
||||||
|
form.userIntegralRecords[0].userId = ''
|
||||||
|
form.userIntegralRecords[0].userName = ''
|
||||||
|
|
||||||
|
// 填充部门名称
|
||||||
|
const selectedDept = deptOptions.value.find(dept => String(dept.id) === form.deptId)
|
||||||
|
if (selectedDept) {
|
||||||
|
form.deptName = selectedDept.name
|
||||||
|
} else {
|
||||||
|
form.deptName = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadPersonOptions(form.deptId)
|
||||||
|
loadScore()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事项变更处理
|
||||||
|
const handleReasonChange = () => {
|
||||||
|
loadScore()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitForm = async () => {
|
||||||
|
if (!formRef.value) return
|
||||||
|
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
submitLoading.value = true
|
||||||
|
try {
|
||||||
|
if (props.optType === 'add') {
|
||||||
|
await createMyReport(form)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
} else {
|
||||||
|
await updateMyReport(form)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
}
|
||||||
|
emit('success')
|
||||||
|
handleClose()
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
ElMessage.error(error.msg || '操作失败')
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,700 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 筛选区域 -->
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||||
|
<el-form-item label="标题">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.title"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="部门">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.deptId"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dept in deptOptions"
|
||||||
|
:key="dept.id"
|
||||||
|
:label="dept.name"
|
||||||
|
:value="dept.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="涉事人">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.personName"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="事项">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.reasonId"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in reportTypeOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.status"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in enumsType.reportStatus"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.text"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="填报时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="createTimeRange"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
value-format="YYYY-MM-DD"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="table-header">
|
||||||
|
<el-button type="primary" @click="handleAdd">
|
||||||
|
新增
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="handleExport" :loading="exportLoading">
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="reportList"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column
|
||||||
|
label="标题"
|
||||||
|
prop="title"
|
||||||
|
min-width="200"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="部门"
|
||||||
|
prop="deptName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="涉事人"
|
||||||
|
prop="personName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column label="事项" min-width="120" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ row.reportTypeName || row.reason?.name || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="分值" min-width="80" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.userIntegralRecords?.[0]?.score || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="250"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.remark || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="填报时间" min-width="160" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ formatDisplayTime(scope.row.fillingTime) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="状态" min-width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||||
|
{{ getStatusText(scope.row.status) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="审批人"
|
||||||
|
prop="approverName"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="审批时间"
|
||||||
|
prop="approveTime"
|
||||||
|
min-width="160"
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
min-width="120"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="scope">
|
||||||
|
<span class="table-opera-item" @click="handleView(scope.row)" :title="'查看'">
|
||||||
|
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||||
|
</span>
|
||||||
|
<span v-if="scope.row.status === 1 || scope.row.status === 'STAGING'" class="table-opera-item ml10" @click="handleEdit(scope.row)" :title="'编辑'">
|
||||||
|
<el-icon class="table-opera-icon"><Edit /></el-icon>
|
||||||
|
</span>
|
||||||
|
<span v-if="scope.row.status === 1 || scope.row.status === 'STAGING'" class="table-opera-item ml10" @click="handleCommit(scope.row.id)" :title="'提交'">
|
||||||
|
<el-icon class="table-opera-icon" :class="{ 'loading-icon': commitLoading === scope.row.id }">
|
||||||
|
<UploadFilled /></el-icon>
|
||||||
|
</span>
|
||||||
|
<span v-if="scope.row.status === 1 || scope.row.status === 'STAGING'" class="table-opera-item ml10" @click="handleDelete(scope.row.id)" :title="'删除'">
|
||||||
|
<el-icon class="table-opera-icon delete-icon"><Delete /></el-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<template v-if="!loading && reportList.length === 0">
|
||||||
|
<el-empty description="暂无上报数据" :image-size="200">
|
||||||
|
<el-button type="primary" @click="handleAdd">新增上报</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 引入新增/编辑弹窗组件 -->
|
||||||
|
<AddReport
|
||||||
|
v-model:dialogVisible="dialogVisible"
|
||||||
|
:optType="optType"
|
||||||
|
:formData="formData"
|
||||||
|
@success="handleAddEditSuccess"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 查看详情组件 -->
|
||||||
|
<DetailComponent
|
||||||
|
v-model:visible="detailDialogVisible"
|
||||||
|
:report-id="selectedReportId"
|
||||||
|
@refresh="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref, computed, watch } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { View, Edit, UploadFilled, Delete } from '@element-plus/icons-vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import DetailComponent from '../common/detail.vue'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
import AddReport from './add-report.vue'
|
||||||
|
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
|
||||||
|
import { getMyReportPage, deleteMyReport, exportMyReport, commitMyReport } from '@/api/submit/myReporting'
|
||||||
|
import { getMyReportDetail } from '@/api/submit/common/common'
|
||||||
|
import { enumsType } from '@/store/static'
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
const deptStore = useDeptStore()
|
||||||
|
|
||||||
|
// Router
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const deptOptions = computed(() => deptStore.getDeptList)
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const reportList = ref<MyReportDTO[]>([])
|
||||||
|
const reportTypeOptions = ref<any[]>([])
|
||||||
|
const total = ref<number>(0)
|
||||||
|
const loading = ref<boolean>(true)
|
||||||
|
const exportLoading = ref<boolean>(false)
|
||||||
|
const commitLoading = ref<string>('')
|
||||||
|
|
||||||
|
const createTimeRange = ref<string[] | null>(null)
|
||||||
|
const dialogVisible = ref<boolean>(false)
|
||||||
|
const detailDialogVisible = ref<boolean>(false)
|
||||||
|
const selectedReportId = ref<string>('')
|
||||||
|
const optType = ref<'add' | 'edit'>('add')
|
||||||
|
const formData = ref<MyReportDTO>({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
reasonId: '',
|
||||||
|
reportTypeName: '',
|
||||||
|
personName: '',
|
||||||
|
content: '',
|
||||||
|
remark: '',
|
||||||
|
imageUrls: [],
|
||||||
|
status: 1,
|
||||||
|
approverId: '',
|
||||||
|
approverName: '',
|
||||||
|
approveTime: '',
|
||||||
|
approveComment: '',
|
||||||
|
createBy: '',
|
||||||
|
createTime: '',
|
||||||
|
userIntegralRecords: [],
|
||||||
|
comments: []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 查询参数 - 搜索时使用 reasonId
|
||||||
|
const queryParams = reactive<QueryParams>({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
personName: '',
|
||||||
|
reasonId: '', // 搜索时使用 reasonId
|
||||||
|
status: '',
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
isOwnReport: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 工具函数
|
||||||
|
const getStatusTagType = (status: string | number) => {
|
||||||
|
const typeMap: Record<string | number, string> = {
|
||||||
|
'STAGING': 'warning', 1: 'warning',
|
||||||
|
'AUDIT': 'primary', 2: 'primary',
|
||||||
|
'APPROVED': 'success', 3: 'success',
|
||||||
|
'REVIEW_FAILED': 'danger', 4: 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[status] || 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusText = (status: string | number) => {
|
||||||
|
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||||
|
return statusItem ? statusItem.text : '未知状态'
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDateTime = (dateTime: string | undefined) => {
|
||||||
|
if (!dateTime) return ''
|
||||||
|
const date = new Date(dateTime)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDisplayTime = (time: string | undefined) => {
|
||||||
|
return time ? formatDateTime(time) : '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(async () => {
|
||||||
|
await initData()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
try {
|
||||||
|
// 并行加载数据
|
||||||
|
await Promise.all([
|
||||||
|
!rewardsPunishReasonStore.getIsSetRewardsPunishReason ? rewardsPunishReasonStore.setRewardsPunishReasonAction() : Promise.resolve(),
|
||||||
|
!deptStore.getIsSetDept ? deptStore.setDeptInfoAction() : Promise.resolve()
|
||||||
|
])
|
||||||
|
|
||||||
|
// 加载事项类型
|
||||||
|
reportTypeOptions.value = rewardsPunishReasonStore.getRewardsPunishReasonList
|
||||||
|
|
||||||
|
await getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化数据失败:', error)
|
||||||
|
ElMessage.error('初始化数据失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理新增/编辑成功回调
|
||||||
|
const handleAddEditSuccess = () => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await getMyReportPage(queryParams)
|
||||||
|
reportList.value = response.list || []
|
||||||
|
total.value = response.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列表失败:', error)
|
||||||
|
ElMessage.error('获取列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
|
||||||
|
// 处理填报时间范围
|
||||||
|
if (createTimeRange.value && createTimeRange.value.length === 2) {
|
||||||
|
queryParams.startTime = createTimeRange.value[0] + 'T00:00:00'
|
||||||
|
queryParams.endTime = createTimeRange.value[1] + 'T23:59:59'
|
||||||
|
} else {
|
||||||
|
queryParams.startTime = undefined
|
||||||
|
queryParams.endTime = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询
|
||||||
|
const resetQuery = () => {
|
||||||
|
Object.assign(queryParams, {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
personName: '',
|
||||||
|
reasonId: '', // 重置 reasonId
|
||||||
|
status: '',
|
||||||
|
startTime: undefined,
|
||||||
|
endTime: undefined,
|
||||||
|
isOwnReport: true
|
||||||
|
})
|
||||||
|
|
||||||
|
createTimeRange.value = null
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
optType.value = 'add'
|
||||||
|
// 重置formData为初始值
|
||||||
|
formData.value = {
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
deptId: '',
|
||||||
|
deptName: '',
|
||||||
|
reasonId: '',
|
||||||
|
reportTypeName: '',
|
||||||
|
personName: '',
|
||||||
|
content: '',
|
||||||
|
remark: '',
|
||||||
|
imageUrls: [],
|
||||||
|
status: 1,
|
||||||
|
approverId: '',
|
||||||
|
approverName: '',
|
||||||
|
approveTime: '',
|
||||||
|
approveComment: '',
|
||||||
|
createBy: '',
|
||||||
|
createTime: '',
|
||||||
|
userIntegralRecords: [],
|
||||||
|
comments: []
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
const handleEdit = async (row: MyReportDTO) => {
|
||||||
|
try {
|
||||||
|
// 获取完整的详情数据,包括comments字段
|
||||||
|
const response = await getMyReportDetail(row.id)
|
||||||
|
optType.value = 'edit'
|
||||||
|
formData.value = response
|
||||||
|
dialogVisible.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取详情失败:', error)
|
||||||
|
ElMessage.error('获取详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const handleView = (row: MyReportDTO) => {
|
||||||
|
selectedReportId.value = row.id
|
||||||
|
detailDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听路由变化,当路由发生变化时关闭抽屉
|
||||||
|
watch(
|
||||||
|
[() => route.path, () => route.fullPath],
|
||||||
|
() => {
|
||||||
|
detailDialogVisible.value = false
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const handleDelete = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定删除该上报记录吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
await deleteMyReport(id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
getList()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('删除失败:', error)
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交上报
|
||||||
|
const handleCommit = async (id: string) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定提交该上报记录吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
|
||||||
|
commitLoading.value = id
|
||||||
|
await commitMyReport(id)
|
||||||
|
ElMessage.success('提交成功')
|
||||||
|
getList()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
ElMessage.error('提交失败')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
commitLoading.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
// 导出
|
||||||
|
const handleExport = async () => {
|
||||||
|
exportLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await exportMyReport(queryParams)
|
||||||
|
|
||||||
|
// 处理文件下载
|
||||||
|
if (response instanceof Blob) {
|
||||||
|
// 创建Blob URL
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||||
|
})
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
|
||||||
|
// 从响应头获取文件名,或者使用默认文件名
|
||||||
|
const filename = getFilenameFromResponse(response) || `上报数据.xlsx`
|
||||||
|
link.download = filename
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
ElMessage.success('导出成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.success('导出请求已发送')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
ElMessage.error('导出失败')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从响应中获取文件名
|
||||||
|
const getFilenameFromResponse = (response: any) => {
|
||||||
|
try {
|
||||||
|
// 如果是Response对象,从headers中获取
|
||||||
|
if (response.headers) {
|
||||||
|
const contentDisposition = response.headers.get('content-disposition')
|
||||||
|
if (contentDisposition) {
|
||||||
|
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i)
|
||||||
|
if (filenameMatch && filenameMatch[1]) {
|
||||||
|
return decodeURIComponent(filenameMatch[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('获取文件名失败:', e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-preview {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments-container {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item {
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-content {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作列样式 */
|
||||||
|
.table-opera-item {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
transition: color 0.3s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-item:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon {
|
||||||
|
color: #eb333d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-icon:hover {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
animation: rotate 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="导入数据"
|
||||||
|
v-model="showDialog"
|
||||||
|
:before-close="handleClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
size="default"
|
||||||
|
@submit.prevent
|
||||||
|
:model="entity"
|
||||||
|
ref="addForm"
|
||||||
|
:rules="rules"
|
||||||
|
class="member"
|
||||||
|
label-position="left"
|
||||||
|
>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="月份" class="labelTop" prop="yearMonth">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="entity.yearMonth"
|
||||||
|
type="month"
|
||||||
|
placeholder="请选择..."
|
||||||
|
value-format="YYYY-MM"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="文件" class="labelTop" prop="file">
|
||||||
|
<el-upload
|
||||||
|
ref="uploadFileRef"
|
||||||
|
:limit="1"
|
||||||
|
:auto-upload="false"
|
||||||
|
drag
|
||||||
|
action="none"
|
||||||
|
accept=".xlsx"
|
||||||
|
:on-exceed="exceedFile"
|
||||||
|
:file-list="fileList"
|
||||||
|
:show-file-list="true"
|
||||||
|
:on-remove="handleRemove"
|
||||||
|
:on-change="handleChange"
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload">
|
||||||
|
<UploadFilled />
|
||||||
|
</el-icon>
|
||||||
|
<div class="el-upload__text">
|
||||||
|
Drop file here or <em>click to upload</em>
|
||||||
|
</div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">
|
||||||
|
<el-link type="primary" @click="downloadTemplate" target="_blank">
|
||||||
|
<template #default>
|
||||||
|
<el-icon>
|
||||||
|
<Download />
|
||||||
|
</el-icon>
|
||||||
|
下载模板
|
||||||
|
</template>
|
||||||
|
</el-link>
|
||||||
|
,仅支持 .xlsx 格式文件
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer v-if="optType !== 'view'">
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button type="primary" @click="save">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
|
import { ElMessage, ElLoading } from 'element-plus'
|
||||||
|
import { UploadFilled, Download } from '@element-plus/icons-vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { importPaySlip, downloadImportTemplate } from '@/api/submit/paySlip'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
optType: {
|
||||||
|
type: String,
|
||||||
|
default: 'import'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['handle-close'])
|
||||||
|
|
||||||
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
|
const showDialog = computed({
|
||||||
|
get: () => props.dialogVisible,
|
||||||
|
set: (val) => emit('handle-close', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const addForm = ref()
|
||||||
|
const uploadFileRef = ref()
|
||||||
|
|
||||||
|
const entity = reactive({
|
||||||
|
yearMonth: '',
|
||||||
|
file: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const fileList = ref([])
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
yearMonth: [
|
||||||
|
{ required: true, message: '请选择月份', trigger: 'change' }
|
||||||
|
],
|
||||||
|
file: [
|
||||||
|
{ required: true, message: '请选择文件', trigger: 'change' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const exceedFile = () => {
|
||||||
|
ElMessage.warning('只能上传一个文件')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRemove = (file) => {
|
||||||
|
fileList.value = []
|
||||||
|
entity.file = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (file) => {
|
||||||
|
fileList.value = [file]
|
||||||
|
entity.file = file.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
showDialog.value = false
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
if (addForm.value) {
|
||||||
|
addForm.value.resetFields()
|
||||||
|
}
|
||||||
|
fileList.value = []
|
||||||
|
entity.file = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadTemplate = () => {
|
||||||
|
downloadImportTemplate().then((res) => {
|
||||||
|
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = '工资条导入模板.xlsx'
|
||||||
|
link.click()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
addForm.value.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
if (!entity.file) {
|
||||||
|
ElMessage.warning('请选择文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const loading = ElLoading.service({
|
||||||
|
lock: true,
|
||||||
|
text: '导入中...',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', entity.file)
|
||||||
|
// 将yearMonth拆分成year和month两个字段
|
||||||
|
if (entity.yearMonth) {
|
||||||
|
const [year, month] = entity.yearMonth.split('-')
|
||||||
|
formData.append('year', year)
|
||||||
|
formData.append('month', month)
|
||||||
|
}
|
||||||
|
importPaySlip(formData).then(() => {
|
||||||
|
loading.close()
|
||||||
|
ElMessage.success('导入成功')
|
||||||
|
showDialog.value = false
|
||||||
|
resetForm()
|
||||||
|
emit('handle-close')
|
||||||
|
}).catch(() => {
|
||||||
|
loading.close()
|
||||||
|
ElMessage.error('导入失败')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dialog-footer {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelTop {
|
||||||
|
.el-form-item__label {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,538 @@
|
||||||
|
<template>
|
||||||
|
<div class="manage-container">
|
||||||
|
<div class="wrap">
|
||||||
|
<el-form
|
||||||
|
class="wrap b-white top"
|
||||||
|
:model="search"
|
||||||
|
ref="searchForm"
|
||||||
|
label-width="120px"
|
||||||
|
label-position="right"
|
||||||
|
size="default"
|
||||||
|
>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item class="label1" label="月份">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="search.yearMonth"
|
||||||
|
type="month"
|
||||||
|
placeholder="请选择..."
|
||||||
|
value-format="YYYY-MM"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item class="label1" label="工号">
|
||||||
|
<el-input
|
||||||
|
v-model="search.employeeId"
|
||||||
|
:maxlength="20"
|
||||||
|
auto-complete="off"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item class="label1" label="手机号">
|
||||||
|
<el-input
|
||||||
|
v-model="search.mobileNo"
|
||||||
|
:maxlength="30"
|
||||||
|
auto-complete="off"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item class="label1" label="姓名">
|
||||||
|
<el-input
|
||||||
|
v-model="search.employeeName"
|
||||||
|
:maxlength="30"
|
||||||
|
auto-complete="off"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item class="label1" label="已发短信">
|
||||||
|
<el-select v-model="search.msmSend" placeholder="请选择..." clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in yesOrNoList"
|
||||||
|
:key="index"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item class="label1" label="已签署">
|
||||||
|
<el-select v-model="search.signed" placeholder="请选择..." clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in yesOrNoList"
|
||||||
|
:key="index"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6" :offset="14" class="mt4">
|
||||||
|
<el-button size="small" type="primary" @click="searchFun">
|
||||||
|
<el-icon class="btn-icon">
|
||||||
|
<Search />
|
||||||
|
</el-icon>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" type @click="reset">
|
||||||
|
<el-icon class="btn-icon">
|
||||||
|
<RefreshLeft />
|
||||||
|
</el-icon>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div class="line"></div>
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="table-content">
|
||||||
|
<div class="b-white">
|
||||||
|
<el-button type="primary" size="small" @click="importData">
|
||||||
|
<el-icon class="btn-icon">
|
||||||
|
<UploadFilled />
|
||||||
|
</el-icon>
|
||||||
|
导入
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="ml10"
|
||||||
|
@click="batchSend"
|
||||||
|
:disabled="!canBatchSend"
|
||||||
|
>
|
||||||
|
<el-icon class="btn-icon">
|
||||||
|
<Message />
|
||||||
|
</el-icon>
|
||||||
|
批量发送
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
border
|
||||||
|
stripe
|
||||||
|
size="default"
|
||||||
|
ref="table"
|
||||||
|
class="mt16"
|
||||||
|
v-loading="listLoading"
|
||||||
|
:data="tableListData"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无数据" />
|
||||||
|
</template>
|
||||||
|
<el-table-column type="selection" width="55" :selectable="isRowSelectable" />
|
||||||
|
<el-table-column show-overflow-tooltip label="月份" prop="fullYearMonth" />
|
||||||
|
<el-table-column show-overflow-tooltip label="工号" prop="employeeId" />
|
||||||
|
<el-table-column
|
||||||
|
show-overflow-tooltip
|
||||||
|
label="手机号"
|
||||||
|
prop="mobileNo"
|
||||||
|
min-width="100px"
|
||||||
|
/>
|
||||||
|
<el-table-column show-overflow-tooltip label="姓名" prop="employeeName" />
|
||||||
|
<el-table-column show-overflow-tooltip label="工资" prop="salary" />
|
||||||
|
<el-table-column show-overflow-tooltip label="出勤天数" prop="attendanceDays" />
|
||||||
|
<el-table-column show-overflow-tooltip label="出勤工资" prop="attendanceSalary" />
|
||||||
|
<el-table-column show-overflow-tooltip label="通讯费" prop="communicationFees" />
|
||||||
|
<el-table-column show-overflow-tooltip label="加班费" prop="overtimePay" />
|
||||||
|
<el-table-column show-overflow-tooltip label="岗位补贴" prop="jobSubsidies" />
|
||||||
|
<el-table-column show-overflow-tooltip label="全勤奖" prop="fullAttendanceBonus" />
|
||||||
|
<el-table-column show-overflow-tooltip label="扣餐费" prop="mealExpenses" />
|
||||||
|
<el-table-column show-overflow-tooltip label="房补" prop="housingSupplement" />
|
||||||
|
<el-table-column show-overflow-tooltip label="补发" prop="supplementFees" />
|
||||||
|
<el-table-column show-overflow-tooltip label="扣保险" prop="insurancePremiumExpenses" />
|
||||||
|
<el-table-column show-overflow-tooltip label="扣罚" prop="penaltyDeductionExpenses" />
|
||||||
|
<el-table-column show-overflow-tooltip label="扣保证金" prop="marginExpenses" />
|
||||||
|
<el-table-column show-overflow-tooltip label="退回保证金" prop="refundOfDeposit" />
|
||||||
|
<el-table-column show-overflow-tooltip label="应发工资" prop="salariesPayable" />
|
||||||
|
<el-table-column show-overflow-tooltip label="安排签署日期" prop="arrangeSigningDate" />
|
||||||
|
<el-table-column show-overflow-tooltip label="发送时间" prop="msmSendTime" />
|
||||||
|
<el-table-column show-overflow-tooltip label="签署时间" prop="signedTime" />
|
||||||
|
<el-table-column fixed="right" label="操作">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span
|
||||||
|
class="my-primary"
|
||||||
|
@click="deleteRow(row)"
|
||||||
|
v-if="row.msmSend === 'N' && row.signed === 'N'"
|
||||||
|
>
|
||||||
|
<el-icon class="table-opera-icon" title="删除">
|
||||||
|
<Delete color="#eb333d" />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<span class="my-primary ml10" @click="handleSendMsm(row)" v-if="row.msmSend === 'N'">
|
||||||
|
<el-icon class="table-opera-icon" title="发送">
|
||||||
|
<Message />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<span class="my-primary ml10" @click="view(row)">
|
||||||
|
<el-icon class="table-opera-icon" title="查看">
|
||||||
|
<View />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
class="mt16 tr"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
:page-sizes="[10, 20, 50, 100, 200]"
|
||||||
|
@current-change="pageList"
|
||||||
|
:current-page="pageData.pageNum"
|
||||||
|
:page-size="pageData.pageSize"
|
||||||
|
:total="pageData.total"
|
||||||
|
layout="->,total, sizes, prev, pager, next, jumper"
|
||||||
|
:page-count="pageData.pages"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ImportPaySlip :optType="optType" :dialogVisible="dialogVisible" @handle-close="close" />
|
||||||
|
<PaySlipDetail
|
||||||
|
ref="paySlipDetail"
|
||||||
|
:dialog-visible="detailDialogVisible"
|
||||||
|
@update:dialog-visible="detailDialogVisible = $event"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { Search, RefreshLeft, UploadFilled, Delete, Message, View } from '@element-plus/icons-vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import ImportPaySlip from './import.vue'
|
||||||
|
import PaySlipDetail from './paySlipDetail.vue'
|
||||||
|
import { getPaySlipPage, deletePaySlip, sendMsm, batchSendMsm } from '@/api/submit/paySlip'
|
||||||
|
import { PaySlipDTO, QueryParams } from '@/api/submit/paySlip/types'
|
||||||
|
|
||||||
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
|
const searchForm = ref()
|
||||||
|
const table = ref()
|
||||||
|
const paySlipDetail = ref()
|
||||||
|
|
||||||
|
// 定义搜索条件类型
|
||||||
|
interface SearchParams {
|
||||||
|
yearMonth: string;
|
||||||
|
employeeId: string;
|
||||||
|
employeeName: string;
|
||||||
|
mobileNo: string;
|
||||||
|
signed: string; // 前端使用字符串'Y'/'N',后端接口需要布尔值
|
||||||
|
msmSend: string; // 前端使用字符串'Y'/'N',后端接口需要布尔值
|
||||||
|
year: string;
|
||||||
|
month: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = reactive<SearchParams>({
|
||||||
|
yearMonth: '',
|
||||||
|
employeeId: '',
|
||||||
|
employeeName: '',
|
||||||
|
mobileNo: '',
|
||||||
|
signed: '',
|
||||||
|
msmSend: '',
|
||||||
|
year: '',
|
||||||
|
month: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const listLoading = ref(false)
|
||||||
|
const tableListData = ref([])
|
||||||
|
const pageData = reactive({
|
||||||
|
total: 0,
|
||||||
|
pages: 1,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
})
|
||||||
|
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
const optType = ref('import')
|
||||||
|
const detailDialogVisible = ref(false)
|
||||||
|
|
||||||
|
// 选中的行数据
|
||||||
|
const selectionData = ref([])
|
||||||
|
|
||||||
|
// 判断是否可以批量发送
|
||||||
|
const canBatchSend = computed(() => {
|
||||||
|
if (!selectionData.value || selectionData.value.length === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// 检查是否有未发送的记录
|
||||||
|
return selectionData.value.some(item => item?.msmSend === 'N')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 选择变化时的处理函数
|
||||||
|
const handleSelectionChange = (selection) => {
|
||||||
|
selectionData.value = selection || []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量发送
|
||||||
|
const batchSend = () => {
|
||||||
|
if (!selectionData.value || selectionData.value.length === 0) {
|
||||||
|
ElMessage.warning('请先选择要发送的数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤出未发送的记录
|
||||||
|
const unSendList = selectionData.value.filter(item => item?.msmSend === 'N')
|
||||||
|
if (!unSendList || unSendList.length === 0) {
|
||||||
|
ElMessage.warning('所选数据中没有未发送的记录')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认批量发送
|
||||||
|
ElMessageBox.confirm('确定要批量发送选中的记录吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
const ids = unSendList.map(item => item.id)
|
||||||
|
batchSendMsm(ids).then(() => {
|
||||||
|
ElMessage.success('批量发送成功')
|
||||||
|
pageList() // 刷新列表
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error('批量发送失败')
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户取消操作
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const yesOrNoList = [
|
||||||
|
{ code: 'Y', name: '是' },
|
||||||
|
{ code: 'N', name: '否' }
|
||||||
|
]
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
pageList()
|
||||||
|
})
|
||||||
|
|
||||||
|
const searchFun = () => {
|
||||||
|
pageData.pageNum = 1
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
Object.assign(search, {
|
||||||
|
yearMonth: '',
|
||||||
|
employeeId: '',
|
||||||
|
employeeName: '',
|
||||||
|
mobileNo: '',
|
||||||
|
signed: '',
|
||||||
|
msmSend: ''
|
||||||
|
})
|
||||||
|
pageData.pageNum = 1
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageList = () => {
|
||||||
|
listLoading.value = true
|
||||||
|
// 处理查询参数
|
||||||
|
const query: QueryParams = {
|
||||||
|
// 将页码和每页条数映射为接口需要的字段名
|
||||||
|
pageNo: pageData.pageNum,
|
||||||
|
pageSize: pageData.pageSize,
|
||||||
|
// 提取工号、姓名、手机号
|
||||||
|
employeeId: search.employeeId,
|
||||||
|
employeeName: search.employeeName,
|
||||||
|
mobileNo: search.mobileNo
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将yearMonth拆分为year和month
|
||||||
|
if (search.yearMonth) {
|
||||||
|
const [year, month] = search.yearMonth.split('-')
|
||||||
|
query.year = year
|
||||||
|
query.month = month
|
||||||
|
} else {
|
||||||
|
// 如果没有选择yearMonth,使用单独的year和month字段
|
||||||
|
query.year = search.year
|
||||||
|
query.month = search.month
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将signed直接传递为'Y'/'N'
|
||||||
|
if (search.signed) {
|
||||||
|
query.signed = search.signed
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将msmSend直接传递为'Y'/'N'
|
||||||
|
if (search.msmSend) {
|
||||||
|
query.msmSend = search.msmSend
|
||||||
|
}
|
||||||
|
|
||||||
|
getPaySlipPage(query).then(res => {
|
||||||
|
listLoading.value = false
|
||||||
|
tableListData.value = res.list; // 列表字段是 list
|
||||||
|
pageData.total = res.total; // total 是根节点的字段
|
||||||
|
pageData.pages = Math.ceil(res.total / pageData.pageSize);
|
||||||
|
}).catch(() => {
|
||||||
|
listLoading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (size: number) => {
|
||||||
|
pageData.pageSize = size
|
||||||
|
pageData.pageNum = 1
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const importData = () => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRow = (row: PaySlipDTO) => {
|
||||||
|
ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
deletePaySlip(row.id).then(() => {
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
pageList()
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error('删除失败')
|
||||||
|
})
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSendMsm = (row: PaySlipDTO) => {
|
||||||
|
ElMessageBox.confirm('确定要发送短信吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
sendMsm(row.id).then(() => {
|
||||||
|
ElMessage.success('发送成功')
|
||||||
|
pageList()
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error('发送失败')
|
||||||
|
})
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = (row: PaySlipDTO) => {
|
||||||
|
paySlipDetail.value.setEntity(row)
|
||||||
|
detailDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断行是否可以选择
|
||||||
|
const isRowSelectable = (row: PaySlipDTO) => {
|
||||||
|
// 只有查看按钮可见的情况:
|
||||||
|
// 1. msmSend为Y且signed为Y:删除和发送按钮都不可见
|
||||||
|
// 2. msmSend为Y且signed为N:删除和发送按钮都不可见
|
||||||
|
// 因此,当msmSend为Y时,只有查看按钮可见,禁止选择
|
||||||
|
return row.msmSend !== 'Y'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.manage-container {
|
||||||
|
width: 100%;
|
||||||
|
min-height: calc(100vh - 60px);
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
// 整体容器样式
|
||||||
|
.wrap {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分割线样式
|
||||||
|
.line {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卡片样式
|
||||||
|
.table-content {
|
||||||
|
.b-white {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 间距类
|
||||||
|
.mt16 {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt4 {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml10 {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链接样式
|
||||||
|
.my-primary {
|
||||||
|
color: #409eff;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮图标样式
|
||||||
|
.btn-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格操作图标样式
|
||||||
|
.table-opera-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格样式优化
|
||||||
|
.el-table {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
// 优化表头样式
|
||||||
|
th {
|
||||||
|
background-color: #fafafa;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优化表格内容
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优化表格行悬停效果
|
||||||
|
.el-table__row {
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页器样式
|
||||||
|
.el-pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,199 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-drawer
|
||||||
|
:model-value="dialogVisible"
|
||||||
|
@update:model-value="handleClose"
|
||||||
|
title="工资条详情"
|
||||||
|
size="50%"
|
||||||
|
:close-on-click-modal="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
direction="rtl"
|
||||||
|
>
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="group-warp">
|
||||||
|
<div class="title">{{ entity.fullYearMonth }}工资条</div>
|
||||||
|
<el-descriptions :column="2" border>
|
||||||
|
<el-descriptions-item label="月份">{{ entity.fullYearMonth }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="工号">{{ entity.employeeId }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="姓名">{{ entity.employeeName }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="手机号">{{ entity.mobileNo }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="工资">{{ entity.salary }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="出勤天数">{{ entity.attendanceDays }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="出勤工资">{{ entity.attendanceSalary }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="通讯费">{{ entity.communicationFees }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="加班费">{{ entity.overtimePay }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="岗位补贴">{{ entity.jobSubsidies }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="全勤奖">{{ entity.fullAttendanceBonus }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="扣餐费">{{ entity.mealExpenses }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="房补">{{ entity.housingSupplement }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="补发">{{ entity.supplementFees }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="扣保险">{{ entity.insurancePremiumExpenses }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="扣罚">{{ entity.penaltyDeductionExpenses }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="扣保证金">{{ entity.marginExpenses }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="退回保证金">{{ entity.refundOfDeposit }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="应发工资">{{ entity.salariesPayable }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="安排签署日期">{{ entity.arrangeSigningDate }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="发送时间">{{ entity.msmSendTime }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="签署时间">{{ entity.signedTime }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
<div class="group-warp">
|
||||||
|
<div class="title">签名</div>
|
||||||
|
<div class="img-wrap">
|
||||||
|
<el-image
|
||||||
|
v-if="imagesArray.length > 0"
|
||||||
|
:src="imagesArray[0]"
|
||||||
|
fit="cover"
|
||||||
|
@click="showImage(0)"
|
||||||
|
class="img"
|
||||||
|
/>
|
||||||
|
<el-empty v-else description="暂无签名" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dialogVisible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:dialogVisible'])
|
||||||
|
|
||||||
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
|
const entity = reactive({
|
||||||
|
fullYearMonth: '',
|
||||||
|
employeeId: '',
|
||||||
|
employeeName: '',
|
||||||
|
mobileNo: '',
|
||||||
|
salary: '',
|
||||||
|
attendanceDays: '',
|
||||||
|
attendanceSalary: '',
|
||||||
|
communicationFees: '',
|
||||||
|
overtimePay: '',
|
||||||
|
jobSubsidies: '',
|
||||||
|
fullAttendanceBonus: '',
|
||||||
|
mealExpenses: '',
|
||||||
|
housingSupplement: '',
|
||||||
|
supplementFees: '',
|
||||||
|
insurancePremiumExpenses: '',
|
||||||
|
penaltyDeductionExpenses: '',
|
||||||
|
marginExpenses: '',
|
||||||
|
refundOfDeposit: '',
|
||||||
|
salariesPayable: '',
|
||||||
|
arrangeSigningDate: '',
|
||||||
|
msmSendTime: '',
|
||||||
|
signedTime: '',
|
||||||
|
signatureUrl: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const imagesArray = ref([])
|
||||||
|
|
||||||
|
watch(() => props.dialogVisible, (newVal) => {
|
||||||
|
if (!newVal) {
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const setEntity = (data) => {
|
||||||
|
// 深拷贝数据避免修改原对象
|
||||||
|
const formattedData = JSON.parse(JSON.stringify(data))
|
||||||
|
|
||||||
|
// 格式化时间戳字段
|
||||||
|
if (formattedData.msmSendTime) {
|
||||||
|
formattedData.msmSendTime = formatDate(new Date(formattedData.msmSendTime))
|
||||||
|
}
|
||||||
|
if (formattedData.signedTime) {
|
||||||
|
formattedData.signedTime = formatDate(new Date(formattedData.signedTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(entity, formattedData)
|
||||||
|
|
||||||
|
if (formattedData.signatureUrl) {
|
||||||
|
imagesArray.value = [formattedData.signatureUrl]
|
||||||
|
} else {
|
||||||
|
imagesArray.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
Object.assign(entity, {
|
||||||
|
fullYearMonth: '',
|
||||||
|
employeeId: '',
|
||||||
|
employeeName: '',
|
||||||
|
mobileNo: '',
|
||||||
|
salary: '',
|
||||||
|
attendanceDays: '',
|
||||||
|
attendanceSalary: '',
|
||||||
|
communicationFees: '',
|
||||||
|
overtimePay: '',
|
||||||
|
jobSubsidies: '',
|
||||||
|
fullAttendanceBonus: '',
|
||||||
|
mealExpenses: '',
|
||||||
|
housingSupplement: '',
|
||||||
|
supplementFees: '',
|
||||||
|
insurancePremiumExpenses: '',
|
||||||
|
penaltyDeductionExpenses: '',
|
||||||
|
marginExpenses: '',
|
||||||
|
refundOfDeposit: '',
|
||||||
|
salariesPayable: '',
|
||||||
|
arrangeSigningDate: '',
|
||||||
|
msmSendTime: '',
|
||||||
|
signedTime: '',
|
||||||
|
signatureUrl: ''
|
||||||
|
})
|
||||||
|
imagesArray.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const showImage = (index) => {
|
||||||
|
// 这里可以实现图片预览功能
|
||||||
|
console.log('预览图片:', imagesArray.value[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('update:dialogVisible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
setEntity
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.main-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-warp {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-wrap {
|
||||||
|
margin-top: 10px;
|
||||||
|
.img {
|
||||||
|
width: 200px;
|
||||||
|
height: 100px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,358 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||||
|
<el-form-item label="编码">
|
||||||
|
<el-input v-model="queryParams.code" placeholder="请输入" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="名称">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="请输入" clearable style="width: 200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="是否启用">
|
||||||
|
<el-select v-model="queryParams.enable" placeholder="请选择" clearable style="width: 200px">
|
||||||
|
<el-option label="是" value="1" />
|
||||||
|
<el-option label="否" value="0" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery">查询</el-button>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="table-header">
|
||||||
|
<el-button type="primary" icon="ep:plus" @click="handleAdd">新增</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="reportManageList"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<el-table-column label="编码" prop="code" min-width="100" align="center" />
|
||||||
|
<el-table-column label="名称" prop="name" min-width="120" align="center" />
|
||||||
|
<el-table-column label="描述" prop="remark" min-width="250" align="center" show-overflow-tooltip>
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.remark || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="排序序号" prop="orderNum" min-width="90" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.orderNum || 0 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<!-- 修正点:将 status 改为 enable -->
|
||||||
|
<el-table-column label="是否启用" prop="enable" min-width="90" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.enable === 1 ? 'success' : 'info'">
|
||||||
|
{{ scope.row.enable === 1 ? '是' : '否' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" min-width="80" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button type="primary" link icon="ep:edit" @click="handleUpdate(scope.row)">编辑</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total > 0"
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加或修改上报事项对话框 -->
|
||||||
|
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="编码:" prop="code">
|
||||||
|
<el-input v-model="form.code" placeholder="请输入编码" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="名称:" prop="name">
|
||||||
|
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="排序序号:" prop="orderNum">
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.orderNum"
|
||||||
|
:min="0"
|
||||||
|
:max="9999"
|
||||||
|
controls-position="right"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<!-- 修正点:将 status 改为 enable -->
|
||||||
|
<el-form-item label="是否启用:" prop="enable">
|
||||||
|
<el-radio-group v-model="form.enable">
|
||||||
|
<el-radio :label="1">是</el-radio>
|
||||||
|
<el-radio :label="0">否</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="描述:" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="请输入描述"
|
||||||
|
:rows="4"
|
||||||
|
resize="none"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="cancel" size="large" style="width: 100px;">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" size="large" style="width: 100px;">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
// 注意:请确保你的 TypeScript 接口定义(ReportManageDTO, QueryParams, SaveParams)也已同步更新
|
||||||
|
import type { ReportManageDTO, QueryParams, SaveParams } from '@/api/submit/systemConfig/reportManage/types'
|
||||||
|
import { getReportManagePage, createReportManage, updateReportManage } from '@/api/submit/systemConfig/reportManage'
|
||||||
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const reportManageList = ref<ReportManageDTO[]>([])
|
||||||
|
const total = ref<number>(0)
|
||||||
|
const loading = ref<boolean>(true)
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive<QueryParams>({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
enable: '' // 修正点:将 status 改为 enable
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = reactive<SaveParams>({
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
orderNum: 0,
|
||||||
|
enable: 1, // 修正点:将 status 改为 enable
|
||||||
|
remark: '',
|
||||||
|
type: 'REWARDS_PUNISH_REASON'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = reactive<FormRules>({
|
||||||
|
code: [{ required: true, message: '编码不能为空', trigger: 'blur' }],
|
||||||
|
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
|
||||||
|
orderNum: [{ required: true, message: '排序等级不能为空', trigger: 'blur' }],
|
||||||
|
enable: [{ required: true, message: '是否启用不能为空', trigger: 'change' }] // 修正点:将 status 改为 enable
|
||||||
|
})
|
||||||
|
|
||||||
|
// 对话框状态
|
||||||
|
const open = ref<boolean>(false)
|
||||||
|
const title = ref<string>('')
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const response = await getReportManagePage(queryParams)
|
||||||
|
reportManageList.value = response.list || []
|
||||||
|
total.value = response.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列表失败:', error)
|
||||||
|
ElMessage.error('获取列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
Object.assign(queryParams, {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
enable: '' // 修正点:将 status 改为 enable
|
||||||
|
})
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const handleAdd = () => {
|
||||||
|
open.value = true
|
||||||
|
title.value = '新增上报事项'
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
const handleUpdate = (row: ReportManageDTO) => {
|
||||||
|
open.value = true
|
||||||
|
title.value = '修改上报事项'
|
||||||
|
// 修正点:将 status 改为 enable
|
||||||
|
Object.assign(form, {
|
||||||
|
...row,
|
||||||
|
enable: Number(row.enable) // 确保是数字类型
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
Object.assign(form, {
|
||||||
|
id: undefined,
|
||||||
|
code: '',
|
||||||
|
name: '',
|
||||||
|
orderNum: 0,
|
||||||
|
enable: 1, // 修正点:将 status 改为 enable
|
||||||
|
remark: '',
|
||||||
|
type: 'REWARDS_PUNISH_REASON'
|
||||||
|
})
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const cancel = () => {
|
||||||
|
open.value = false
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitForm = async () => {
|
||||||
|
const valid = await formRef.value?.validate()
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (form.id) {
|
||||||
|
await updateReportManage(form)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await createReportManage(form)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
}
|
||||||
|
open.value = false
|
||||||
|
getList()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
ElMessage.error(form.id ? '修改失败' : '新增失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 样式部分无需修改 */
|
||||||
|
.app-container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table) {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table th) {
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单样式 */
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input),
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-radio-group) {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-dialog__footer) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,418 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:title="dialogTitle"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:before-close="handleClose"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@update:model-value="emit('update:modelValue', $event)"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
size="default"
|
||||||
|
@submit.prevent
|
||||||
|
:model="entity"
|
||||||
|
ref="addForm"
|
||||||
|
:rules="rules"
|
||||||
|
class="member"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="标题:" prop="title">
|
||||||
|
<el-input
|
||||||
|
:maxlength="50"
|
||||||
|
size="default"
|
||||||
|
class="fw"
|
||||||
|
v-model="entity.title"
|
||||||
|
placeholder="请输入标题"
|
||||||
|
clearable
|
||||||
|
:disabled="isView"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="事项:" prop="reasonId">
|
||||||
|
<el-select
|
||||||
|
v-model="entity.reasonId"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择事项"
|
||||||
|
clearable
|
||||||
|
:disabled="isView"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in enums['REWARDS_PUNISH_REASON']"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="规则类型:" prop="type">
|
||||||
|
<el-select
|
||||||
|
v-model="entity.type"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择规则类型"
|
||||||
|
clearable
|
||||||
|
@change="initScore"
|
||||||
|
:disabled="isView"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in integralRuleTypeEnum"
|
||||||
|
:key="index"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20" v-if="entity.type === 'FIXED_VALUE'">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="分值:">
|
||||||
|
<el-input-number
|
||||||
|
size="default"
|
||||||
|
class="fw"
|
||||||
|
v-model="entity.score"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
placeholder="请输入分值"
|
||||||
|
:disabled="isView"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20" v-if="entity.type === 'CHOOSE_VALUE'">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="分值范围:">
|
||||||
|
<div class="score-range">
|
||||||
|
<div class="score-range-value" v-for="(item, index) in entity.scoreRange" :key="index">
|
||||||
|
<el-input-number
|
||||||
|
size="default"
|
||||||
|
class="fw"
|
||||||
|
v-model="item.value"
|
||||||
|
:precision="1"
|
||||||
|
:step="0.5"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
placeholder="请输入分值"
|
||||||
|
:disabled="isView"
|
||||||
|
/>
|
||||||
|
<i v-if="index === 0 && !isView" @click="addUserScore">
|
||||||
|
<el-icon class="icon-opera" color="green">
|
||||||
|
<CirclePlus />
|
||||||
|
</el-icon>
|
||||||
|
</i>
|
||||||
|
<i v-else-if="!isView" @click="removeUserScore(index)">
|
||||||
|
<el-icon class="icon-opera" color="#eb333d">
|
||||||
|
<Remove />
|
||||||
|
</el-icon>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20" v-if="entity.type === 'CALC_VALUE'">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="公式(SQL):" prop="formula">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
maxlength="1000"
|
||||||
|
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||||
|
size="default"
|
||||||
|
class="fw"
|
||||||
|
v-model="entity.formula"
|
||||||
|
placeholder="请输入SQL公式"
|
||||||
|
:disabled="isView"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer v-if="!isView">
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="handleClose" size="small">取消</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="submitForm" :loading="loading">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, watch, computed } from 'vue'
|
||||||
|
import { CirclePlus, Remove } from '@element-plus/icons-vue'
|
||||||
|
import { createIntegralRule, updateIntegralRule } from '@/api/submit/systemConfig/ruleManage'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { IntegralRule } from '@/api/submit/systemConfig/ruleManage/types'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean,
|
||||||
|
optType: string,
|
||||||
|
viewData: IntegralRule
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'handle-close', refresh: boolean): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 组件状态
|
||||||
|
const addForm = ref()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 获取奖惩原因store实例
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
|
||||||
|
// 从store获取奖惩原因列表
|
||||||
|
const rewardsPunishReasonList = computed(() =>
|
||||||
|
rewardsPunishReasonStore.getRewardsPunishReasonList
|
||||||
|
)
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const isView = computed(() => props.optType === 'view')
|
||||||
|
const dialogTitle = computed(() => {
|
||||||
|
switch (props.optType) {
|
||||||
|
case 'add': return '新建规则'
|
||||||
|
case 'edit': return '修改规则'
|
||||||
|
case 'view': return '规则详情'
|
||||||
|
default: return '规则'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const entity = reactive({
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
reasonId: '',
|
||||||
|
type: '',
|
||||||
|
score: 0,
|
||||||
|
scoreRange: [] as { value: number }[],
|
||||||
|
formula: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = reactive({
|
||||||
|
title: [
|
||||||
|
{ required: true, message: '请输入标题', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
reasonId: [
|
||||||
|
{ required: true, message: '请选择事项', trigger: 'change' }
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
{ required: true, message: '请选择规则类型', trigger: 'change' }
|
||||||
|
],
|
||||||
|
formula: [
|
||||||
|
{ required: true, message: '请输入公式', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 枚举数据 - 从rewardsPunishReasonStore获取
|
||||||
|
const enums = reactive({
|
||||||
|
REWARDS_PUNISH_REASON: rewardsPunishReasonList.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听对话框显示状态,确保奖惩原因数据加载完成
|
||||||
|
watch(() => props.modelValue, async (newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
if (!rewardsPunishReasonStore.getIsSetRewardsPunishReason) {
|
||||||
|
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||||
|
}
|
||||||
|
enums.REWARDS_PUNISH_REASON = rewardsPunishReasonList.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 规则类型枚举
|
||||||
|
const integralRuleTypeEnum = [
|
||||||
|
{code: 'FIXED_VALUE', name: '固定分值'},
|
||||||
|
{code: 'MANUAL_VALUE', name: '手工分值'},
|
||||||
|
{code: 'CALC_VALUE', name: '动态计算分值'},
|
||||||
|
{code: 'CHOOSE_VALUE', name: '选择分值'}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 监听 modelValue 变化来初始化表单
|
||||||
|
watch(() => props.modelValue, (val) => {
|
||||||
|
if (val) {
|
||||||
|
if (props.optType !== 'add') {
|
||||||
|
initializeFormData()
|
||||||
|
} else {
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
// 初始化表单数据
|
||||||
|
const initializeFormData = () => {
|
||||||
|
const data = props.viewData
|
||||||
|
entity.id = data.id || ''
|
||||||
|
entity.title = data.title || ''
|
||||||
|
entity.reasonId = data.reasonId || ''
|
||||||
|
entity.type = data.type || ''
|
||||||
|
entity.score = Number(data.score) || 0
|
||||||
|
entity.formula = data.formula || ''
|
||||||
|
|
||||||
|
if (data.scoreRange) {
|
||||||
|
if (Array.isArray(data.scoreRange)) {
|
||||||
|
entity.scoreRange = data.scoreRange.map(item => ({ value: Number(item) || 0 }))
|
||||||
|
} else if (typeof data.scoreRange === 'string' && data.scoreRange.trim()) {
|
||||||
|
entity.scoreRange = data.scoreRange.split(',').map(item => ({
|
||||||
|
value: Number(item.trim()) || 0
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
entity.scoreRange = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entity.scoreRange = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
const resetForm = () => {
|
||||||
|
Object.assign(entity, {
|
||||||
|
id: '',
|
||||||
|
title: '',
|
||||||
|
reasonId: '',
|
||||||
|
type: '',
|
||||||
|
score: 0,
|
||||||
|
scoreRange: [],
|
||||||
|
formula: '',
|
||||||
|
})
|
||||||
|
addForm.value?.clearValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除分值范围项
|
||||||
|
const removeUserScore = (index: number) => {
|
||||||
|
entity.scoreRange.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加分值范围项
|
||||||
|
const addUserScore = () => {
|
||||||
|
entity.scoreRange.push({ value: 0 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换规则类型时初始化分值相关字段
|
||||||
|
const initScore = () => {
|
||||||
|
entity.score = 0
|
||||||
|
entity.scoreRange = []
|
||||||
|
entity.formula = ''
|
||||||
|
|
||||||
|
if (entity.type === 'CHOOSE_VALUE') {
|
||||||
|
entity.scoreRange.push({ value: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话框关闭处理
|
||||||
|
const handleClose = () => {
|
||||||
|
addForm.value?.clearValidate()
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
emit('handle-close', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
const submitForm = async () => {
|
||||||
|
if (!addForm.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
await addForm.value.validate()
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
// 构建 params
|
||||||
|
const params: any = {
|
||||||
|
id: entity.id,
|
||||||
|
title: entity.title.trim(),
|
||||||
|
reasonId: entity.reasonId,
|
||||||
|
type: entity.type,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (entity.type) {
|
||||||
|
case 'FIXED_VALUE':
|
||||||
|
params.score = Number(entity.score) || 0
|
||||||
|
break
|
||||||
|
case 'CHOOSE_VALUE':
|
||||||
|
const scoreValues = entity.scoreRange.map(item => Number(item.value) || 0)
|
||||||
|
const uniqueScores = [...new Set(scoreValues)].filter(value => !isNaN(value))
|
||||||
|
params.scoreRange = uniqueScores.join(',')
|
||||||
|
break
|
||||||
|
case 'CALC_VALUE':
|
||||||
|
params.formula = entity.formula.trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起请求 - 直接调用,不检查响应格式
|
||||||
|
if (entity.id) {
|
||||||
|
await updateIntegralRule(params)
|
||||||
|
ElMessage.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await createIntegralRule(params)
|
||||||
|
ElMessage.success('新增成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
emit('update:modelValue', false)
|
||||||
|
emit('handle-close', true)
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('提交失败:', error)
|
||||||
|
const errorMsg = error?.message || error?.msg || (entity.id ? '修改失败' : '新增失败')
|
||||||
|
ElMessage.error(errorMsg)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.score-range {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-range-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-opera {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,434 @@
|
||||||
|
<template>
|
||||||
|
<div class="manage-container">
|
||||||
|
<!-- 搜索栏区域 -->
|
||||||
|
<div class="search-container">
|
||||||
|
<el-form :model="search" ref="searchForm" inline label-width="60px" size="small">
|
||||||
|
<el-form-item label="标题">
|
||||||
|
<el-input
|
||||||
|
v-model="search.title"
|
||||||
|
:maxlength="50"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
style="width: 180px;"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="事项">
|
||||||
|
<el-select
|
||||||
|
v-model="search.reasonId"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择..."
|
||||||
|
clearable
|
||||||
|
style="width: 150px;"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in enums['REWARDS_PUNISH_REASON']"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类型">
|
||||||
|
<el-select
|
||||||
|
v-model="search.type"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择..."
|
||||||
|
clearable
|
||||||
|
style="width: 150px;"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="(item, index) in integralRuleTypeEnum"
|
||||||
|
:key="index"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="ep:search" size="small" @click="searchFun">查询</el-button>
|
||||||
|
<el-button icon="ep:refresh" size="small" @click="reset">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格操作栏 + 表格区域 -->
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="table-toolbar">
|
||||||
|
<el-button type="primary" icon="ep:plus" size="small" @click="add">新建</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格 -->
|
||||||
|
<el-table
|
||||||
|
:data="tableListData"
|
||||||
|
v-loading="listLoading"
|
||||||
|
border
|
||||||
|
size="small"
|
||||||
|
:header-cell-style="{ background: '#f8f9fa', color: '#333', fontWeight: '500' }"
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无数据" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
prop="title"
|
||||||
|
label="标题"
|
||||||
|
min-width="180"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="事项"
|
||||||
|
min-width="120"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ getReasonName(row.reasonId) || '-' }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="规则类型"
|
||||||
|
min-width="100"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ filterIntegralRuleTypeValue(row.type) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<!-- 操作列:使用 link 类型 + 图标 -->
|
||||||
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
width="150"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="operation-btn-group">
|
||||||
|
<!-- 编辑按钮 -->
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="edit(row)"
|
||||||
|
title="编辑"
|
||||||
|
>
|
||||||
|
<el-icon><Edit /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="deleteRow(row)"
|
||||||
|
title="删除"
|
||||||
|
>
|
||||||
|
<el-icon><Delete /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
<!-- 查看按钮 -->
|
||||||
|
<el-button
|
||||||
|
type="info"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="view(row)"
|
||||||
|
title="查看"
|
||||||
|
>
|
||||||
|
<el-icon><View /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-container">
|
||||||
|
<el-pagination
|
||||||
|
class="pagination"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="pageList"
|
||||||
|
:current-page="pageData.pageNum"
|
||||||
|
:page-size="pageData.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
:total="pageData.total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
background
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 弹窗组件 -->
|
||||||
|
<AddRule
|
||||||
|
v-model="open"
|
||||||
|
:optType="optType"
|
||||||
|
:viewData="viewData"
|
||||||
|
@handle-close="onRuleClose"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import AddRule from './addRule.vue'
|
||||||
|
import { Edit, Delete, View } from '@element-plus/icons-vue'
|
||||||
|
import { getIntegralRulePage, deleteIntegralRule } from '@/api/submit/systemConfig/ruleManage'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import type { IntegralRule } from '@/api/submit/systemConfig/ruleManage/types'
|
||||||
|
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||||
|
|
||||||
|
// 组件状态
|
||||||
|
const searchForm = ref()
|
||||||
|
const listLoading = ref(false)
|
||||||
|
|
||||||
|
// 弹窗状态
|
||||||
|
const open = ref(false)
|
||||||
|
const optType = ref('add')
|
||||||
|
const viewData = ref<IntegralRule>({})
|
||||||
|
|
||||||
|
// 获取奖惩原因store实例
|
||||||
|
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||||
|
|
||||||
|
// 从store获取奖惩原因列表
|
||||||
|
const rewardsPunishReasonList = computed(() =>
|
||||||
|
rewardsPunishReasonStore.getRewardsPunishReasonList
|
||||||
|
)
|
||||||
|
|
||||||
|
// 搜索条件
|
||||||
|
const search = reactive({
|
||||||
|
title: '',
|
||||||
|
reasonId: '',
|
||||||
|
type: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页数据
|
||||||
|
const pageData = reactive({
|
||||||
|
total: 0,
|
||||||
|
pages: 1,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const tableListData = ref<IntegralRule[]>([])
|
||||||
|
|
||||||
|
// 枚举数据 - 从rewardsPunishReasonStore获取
|
||||||
|
const enums = reactive({
|
||||||
|
REWARDS_PUNISH_REASON: rewardsPunishReasonList.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// 规则类型枚举
|
||||||
|
const integralRuleTypeEnum = [
|
||||||
|
{code: 'FIXED_VALUE', name: '固定分值'},
|
||||||
|
{code: 'MANUAL_VALUE', name: '手工分值'},
|
||||||
|
{code: 'CALC_VALUE', name: '动态计算分值'},
|
||||||
|
{code: 'CHOOSE_VALUE', name: '选择分值'}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 根据 reasonId 获取事项名称
|
||||||
|
const getReasonName = (reasonId: string) => {
|
||||||
|
if (!reasonId) return ''
|
||||||
|
const reasonItem = enums.REWARDS_PUNISH_REASON.find(item => item.id === reasonId)
|
||||||
|
return reasonItem?.name || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤规则类型显示值
|
||||||
|
const filterIntegralRuleTypeValue = (value: string) => {
|
||||||
|
if (!value) return '-'
|
||||||
|
const item = integralRuleTypeEnum.find(item => item.code === value)
|
||||||
|
return item?.name || value
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const searchFun = () => {
|
||||||
|
pageData.pageNum = 1
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const reset = () => {
|
||||||
|
Object.assign(search, {
|
||||||
|
title: '',
|
||||||
|
reasonId: '',
|
||||||
|
type: '',
|
||||||
|
})
|
||||||
|
pageData.pageNum = 1
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页大小变化
|
||||||
|
const handleSizeChange = (val: number) => {
|
||||||
|
pageData.pageSize = val
|
||||||
|
pageData.pageNum = 1
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时确保奖惩原因数据加载完成
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!rewardsPunishReasonStore.getIsSetRewardsPunishReason) {
|
||||||
|
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||||
|
}
|
||||||
|
await pageList()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const pageList = async (pageNum?: number) => {
|
||||||
|
if (pageNum !== undefined) {
|
||||||
|
pageData.pageNum = pageNum
|
||||||
|
}
|
||||||
|
|
||||||
|
tableListData.value = []
|
||||||
|
listLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await getIntegralRulePage({
|
||||||
|
...search,
|
||||||
|
pageNo: pageData.pageNum,
|
||||||
|
pageSize: pageData.pageSize,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
tableListData.value = response.list || []
|
||||||
|
pageData.total = response.total || 0
|
||||||
|
pageData.pages = Math.ceil(pageData.total / pageData.pageSize) || 1
|
||||||
|
} else {
|
||||||
|
ElMessage.error('获取数据失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列表失败:', error)
|
||||||
|
ElMessage.error('网络错误,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const add = () => {
|
||||||
|
optType.value = 'add'
|
||||||
|
viewData.value = {}
|
||||||
|
open.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看
|
||||||
|
const view = (row: IntegralRule) => {
|
||||||
|
optType.value = 'view'
|
||||||
|
viewData.value = { ...row }
|
||||||
|
open.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
const edit = (row: IntegralRule) => {
|
||||||
|
optType.value = 'edit'
|
||||||
|
viewData.value = { ...row }
|
||||||
|
open.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const deleteRow = async (row: IntegralRule) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('确定删除该规则数据吗?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
|
||||||
|
await deleteIntegralRule(row.id!)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
pageList()
|
||||||
|
} catch (error) {
|
||||||
|
// 用户取消删除
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗关闭后的回调函数
|
||||||
|
const onRuleClose = (refresh = false) => {
|
||||||
|
if (refresh) {
|
||||||
|
pageList(pageData.pageNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.manage-container {
|
||||||
|
min-height: calc(100vh - 60px);
|
||||||
|
background: #fff;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索栏样式 */
|
||||||
|
.search-container {
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格区域样式 */
|
||||||
|
.table-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格操作栏 */
|
||||||
|
.table-toolbar {
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-bottom: 1px solid #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格样式 */
|
||||||
|
:deep(.el-table) {
|
||||||
|
--el-table-row-hover-bg-color: #f8f9fa;
|
||||||
|
--el-table-border-color: #e6e6e6;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.el-table__body tr {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
.el-table__header th {
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
.el-table__body td {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 操作按钮组样式 */
|
||||||
|
.operation-btn-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
/* 确保链接按钮没有背景 */
|
||||||
|
:deep(.el-button--link) {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 4px 6px;
|
||||||
|
min-height: auto;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页容器 */
|
||||||
|
.pagination-container {
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
border-top: 1px solid #f1f1f1;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分页样式 */
|
||||||
|
.pagination {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 筛选区域 -->
|
||||||
|
<div class="filter-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||||
|
<el-form-item label="部门">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.orgId"
|
||||||
|
placeholder="请选择"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dept in deptOptions"
|
||||||
|
:key="dept.id"
|
||||||
|
:label="dept.name"
|
||||||
|
:value="dept.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.userName"
|
||||||
|
placeholder="请输入"
|
||||||
|
clearable
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="月份范围">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="monthRange"
|
||||||
|
type="monthrange"
|
||||||
|
range-separator="至"
|
||||||
|
start-placeholder="开始月份"
|
||||||
|
end-placeholder="结束月份"
|
||||||
|
format="YYYY-MM"
|
||||||
|
value-format="YYYY-MM"
|
||||||
|
style="width: 250px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleQuery" :loading="listLoading">
|
||||||
|
<el-icon class="table-opera-icon"><Search /></el-icon>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetQuery">
|
||||||
|
<el-icon class="table-opera-icon"><RefreshLeft /></el-icon>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 操作区域 -->
|
||||||
|
<div class="table-header">
|
||||||
|
<el-button type="primary" size="small" @click="handleExport" :loading="exportLoading">
|
||||||
|
<el-icon class="table-opera-icon"><Download /></el-icon>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="syncUser" :loading="syncLoading">
|
||||||
|
<el-icon class="table-opera-icon"><Refresh /></el-icon>
|
||||||
|
同步数据
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-container">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<el-table
|
||||||
|
v-loading="listLoading"
|
||||||
|
:data="tableListData"
|
||||||
|
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||||
|
stripe
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无数据" :image-size="200">
|
||||||
|
<el-button type="primary" @click="handleQuery">刷新数据</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</template>
|
||||||
|
<el-table-column show-overflow-tooltip label="排名" prop="ranking" min-width="80" align="center" />
|
||||||
|
<el-table-column show-overflow-tooltip label="部门" prop="deptName" min-width="120" align="center" />
|
||||||
|
<el-table-column show-overflow-tooltip label="用户名" prop="userName" min-width="120" align="center" />
|
||||||
|
<el-table-column show-overflow-tooltip label="积分" prop="score" min-width="100" align="center" />
|
||||||
|
|
||||||
|
<el-table-column
|
||||||
|
label="操作"
|
||||||
|
min-width="100"
|
||||||
|
align="center"
|
||||||
|
fixed="right"
|
||||||
|
>
|
||||||
|
<template #default="{row}">
|
||||||
|
<span class="table-opera-item" @click="view(row)" :title="'详情'">
|
||||||
|
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="pageData.total > 0"
|
||||||
|
:total="pageData.total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<user-score-detail ref="userScoreDetail" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Search, RefreshLeft, Download, Refresh, View } from '@element-plus/icons-vue'
|
||||||
|
import { useDeptStore } from '@/store/modules/dept'
|
||||||
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
import UserScoreDetail from './userScoreDetail.vue'
|
||||||
|
import { userScoreApi } from '@/api/submit/userScore'
|
||||||
|
import type { UserScoreListReqVO, UserScoreListRespVO } from '@/api/submit/userScore/types'
|
||||||
|
|
||||||
|
// Store
|
||||||
|
const deptStore = useDeptStore()
|
||||||
|
|
||||||
|
// 部门列表
|
||||||
|
const deptOptions = computed(() => deptStore.getDeptList)
|
||||||
|
|
||||||
|
// 搜索参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
deptName: '',
|
||||||
|
orgId: '',
|
||||||
|
userName: '',
|
||||||
|
beginMonth: undefined,
|
||||||
|
endMonth: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 月份范围
|
||||||
|
const monthRange = ref<string[] | null>(null)
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const tableListData = ref<UserScoreListRespVO[]>([])
|
||||||
|
const listLoading = ref(false)
|
||||||
|
const syncLoading = ref(false)
|
||||||
|
const exportLoading = ref(false) // 导出加载状态(和其他页面一致)
|
||||||
|
|
||||||
|
// 分页数据
|
||||||
|
const pageData = reactive({
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 组件引用
|
||||||
|
const userScoreDetail = ref()
|
||||||
|
|
||||||
|
// 页面加载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
handleQuery()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 同步用户数据
|
||||||
|
const syncUser = async () => {
|
||||||
|
syncLoading.value = true
|
||||||
|
try {
|
||||||
|
await userScoreApi.syncUserScore()
|
||||||
|
ElMessage.success('用户数据同步成功')
|
||||||
|
handleQuery()
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('同步失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
syncLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
const handleQuery = async () => {
|
||||||
|
// 设置月份范围参数
|
||||||
|
if (monthRange.value && monthRange.value.length === 2) {
|
||||||
|
queryParams.beginMonth = monthRange.value[0]
|
||||||
|
queryParams.endMonth = monthRange.value[1]
|
||||||
|
} else {
|
||||||
|
queryParams.beginMonth = undefined
|
||||||
|
queryParams.endMonth = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
tableListData.value = []
|
||||||
|
listLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 转换查询参数
|
||||||
|
const params: UserScoreListReqVO = {
|
||||||
|
userName: queryParams.userName,
|
||||||
|
deptName: queryParams.deptName,
|
||||||
|
orgId: queryParams.orgId,
|
||||||
|
beginMonth: queryParams.beginMonth,
|
||||||
|
endMonth: queryParams.endMonth,
|
||||||
|
pageNo: queryParams.pageNo,
|
||||||
|
pageSize: queryParams.pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await userScoreApi.getUserScoreStatic(params)
|
||||||
|
|
||||||
|
// 前端计算排名(接口无ranking字段)
|
||||||
|
const rankedList = res.list.map((item: any, index: number) => ({
|
||||||
|
...item,
|
||||||
|
ranking: index + 1
|
||||||
|
}))
|
||||||
|
tableListData.value = rankedList
|
||||||
|
pageData.total = res.total
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('查询失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryParams.deptName = ''
|
||||||
|
queryParams.orgId = ''
|
||||||
|
queryParams.userName = ''
|
||||||
|
queryParams.beginMonth = undefined
|
||||||
|
queryParams.endMonth = undefined
|
||||||
|
monthRange.value = null
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
queryParams.pageSize = 10
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从响应头提取文件名(和其他页面逻辑一致)
|
||||||
|
const getFilenameFromResponse = (response: any) => {
|
||||||
|
// 适配不同响应头格式(content-disposition 可能是小写)
|
||||||
|
const disposition = response.headers?.['content-disposition'] || response.headers?.['Content-Disposition']
|
||||||
|
if (!disposition) return null
|
||||||
|
|
||||||
|
// 匹配文件名(兼容 "filename=xxx.xlsx" 或 "filename*=UTF-8''xxx.xlsx" 格式)
|
||||||
|
const match = disposition.match(/filename=([^;]+)/i) || disposition.match(/filename\*=UTF-8''([^;]+)/i)
|
||||||
|
if (match && match[1]) {
|
||||||
|
return decodeURIComponent(match[1].replace(/["']/g, '')) // 解码并去除引号
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出(完全按照其他页面样式修改)
|
||||||
|
const handleExport = async () => {
|
||||||
|
exportLoading.value = true
|
||||||
|
try {
|
||||||
|
// 构造导出参数(无需分页,导出全部符合条件数据)
|
||||||
|
const exportParams: UserScoreListReqVO = {
|
||||||
|
userName: queryParams.userName,
|
||||||
|
deptName: queryParams.deptName,
|
||||||
|
orgId: queryParams.orgId,
|
||||||
|
beginMonth: queryParams.beginMonth,
|
||||||
|
endMonth: queryParams.endMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用导出接口(传递 responseType: 'blob' 配置)
|
||||||
|
const response = await userScoreApi.exportUserScoreStatic(exportParams, {
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理文件下载(和其他页面逻辑完全一致)
|
||||||
|
if (response instanceof Blob) {
|
||||||
|
// 创建Blob URL
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||||
|
})
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
|
||||||
|
// 从响应头获取文件名,或者使用默认文件名
|
||||||
|
const filename = getFilenameFromResponse(response) || `用户积分排名.xlsx`
|
||||||
|
link.download = filename
|
||||||
|
|
||||||
|
// 触发下载
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
|
||||||
|
// 清理资源
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
ElMessage.success('导出成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.success('导出请求已发送')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
ElMessage.error('导出失败')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看详情
|
||||||
|
const view = (row: UserScoreListRespVO) => {
|
||||||
|
userScoreDetail.value.open({
|
||||||
|
monthRange: monthRange.value || [],
|
||||||
|
row
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.app-container {
|
||||||
|
min-height: 800px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-item {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #409eff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-opera-item:hover {
|
||||||
|
color: #66b1ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manage-container {
|
||||||
|
min-height: 800px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manage-container .wrap {
|
||||||
|
padding: 10px 10px;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.manage-container .wrap .top {
|
||||||
|
background: #fff;
|
||||||
|
padding: 30px 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manage-container .wrap .table-content {
|
||||||
|
background: #fff;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manage-container .line {
|
||||||
|
height: 10px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manage-container .operas-btns {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
<template>
|
||||||
|
<el-drawer title="数据详情" v-model="dialogVisible" size="38%" :before-close="handleClose">
|
||||||
|
<div class="drawer-content">
|
||||||
|
<div class="group-warp">
|
||||||
|
<el-divider content-position="left"><span class="group-name">基础信息</span></el-divider>
|
||||||
|
<div class="group-container basic">
|
||||||
|
<el-descriptions
|
||||||
|
:column="2"
|
||||||
|
size="default"
|
||||||
|
border>
|
||||||
|
<el-descriptions-item :span="1" label="排名">
|
||||||
|
{{ search.row.ranking }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="1" label="积分">
|
||||||
|
{{ search.row.score }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="姓名">
|
||||||
|
{{ search.row.userName }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="部门">
|
||||||
|
{{ search.row.deptName }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="月份范围">
|
||||||
|
{{ monthRangeStr }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group-warp">
|
||||||
|
<el-divider content-position="left"><span class="group-name">月度明细</span></el-divider>
|
||||||
|
<div class="group-container detail">
|
||||||
|
<div class="table-content">
|
||||||
|
<div class="b-white">
|
||||||
|
<el-button type="primary" size="small" @click="exportExcel" :loading="exportLoading">
|
||||||
|
<el-icon class="btn-icon">
|
||||||
|
<Download />
|
||||||
|
</el-icon>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table border stripe size="default" ref="table" class="mt16" v-loading="listLoading" :data="tableListData">
|
||||||
|
<template #empty>
|
||||||
|
<el-empty description="暂无数据" />
|
||||||
|
</template>
|
||||||
|
<el-table-column show-overflow-tooltip label="用户名" prop="userName" />
|
||||||
|
<el-table-column show-overflow-tooltip label="年度" prop="year" />
|
||||||
|
<el-table-column show-overflow-tooltip label="月度" prop="month" />
|
||||||
|
<el-table-column show-overflow-tooltip label="积分" prop="score" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
v-if="pageData.total > 0"
|
||||||
|
class="mt16 tr"
|
||||||
|
v-model:pageNo="queryParams.pageNo"
|
||||||
|
v-model:pageSize="queryParams.pageSize"
|
||||||
|
:total="pageData.total"
|
||||||
|
@query="pageList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Download } from '@element-plus/icons-vue'
|
||||||
|
import Pagination from '@/components/Pagination/index.vue'
|
||||||
|
import { userScoreApi } from '@/api/submit/userScore'
|
||||||
|
// 修复类型导入路径(确保与项目实际路径一致)
|
||||||
|
import type { UserScoreListReqVO, UserScoreListRespVO } from '@/api/submit/userScore/type'
|
||||||
|
|
||||||
|
// 搜索参数
|
||||||
|
const search = reactive({
|
||||||
|
row: {} as Partial<UserScoreListRespVO & { userId: string }>, // 补充userId类型
|
||||||
|
monthRange: [] as string[]
|
||||||
|
})
|
||||||
|
|
||||||
|
// 导出加载状态
|
||||||
|
const exportLoading = ref(false)
|
||||||
|
|
||||||
|
// 对话框显示状态
|
||||||
|
const dialogVisible = ref(false)
|
||||||
|
|
||||||
|
// 查询参数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
userId: '',
|
||||||
|
beginMonth: undefined as string | undefined,
|
||||||
|
endMonth: undefined as string | undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const tableListData = ref<UserScoreListRespVO[]>([])
|
||||||
|
const listLoading = ref(false)
|
||||||
|
|
||||||
|
// 分页数据
|
||||||
|
const pageData = reactive({
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 月份范围字符串(格式化显示)
|
||||||
|
const monthRangeStr = computed(() => {
|
||||||
|
if (search.monthRange && search.monthRange.length === 2) {
|
||||||
|
return search.monthRange.join(' 至 ')
|
||||||
|
}
|
||||||
|
return '暂无'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 从响应头提取文件名(核心工具方法)
|
||||||
|
const getFilenameFromResponse = (response: any) => {
|
||||||
|
// 适配不同后端返回的响应头格式(大小写兼容)
|
||||||
|
const disposition = response.headers?.['content-disposition'] || response.headers?.['Content-Disposition']
|
||||||
|
if (!disposition) return null
|
||||||
|
|
||||||
|
// 匹配两种常见文件名格式:filename=xxx.xlsx 或 filename*=UTF-8''xxx.xlsx
|
||||||
|
const match = disposition.match(/filename=([^;]+)/i) || disposition.match(/filename\*=UTF-8''([^;]+)/i)
|
||||||
|
if (match && match[1]) {
|
||||||
|
// 解码文件名(处理中文乱码)并去除引号
|
||||||
|
return decodeURIComponent(match[1].replace(/["']/g, ''))
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询月度明细
|
||||||
|
const pageList = async () => {
|
||||||
|
tableListData.value = []
|
||||||
|
listLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params: UserScoreListReqVO = {
|
||||||
|
userId: queryParams.userId,
|
||||||
|
beginMonth: queryParams.beginMonth,
|
||||||
|
endMonth: queryParams.endMonth,
|
||||||
|
pageNo: queryParams.pageNo,
|
||||||
|
pageSize: queryParams.pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await userScoreApi.getUserScoreList(params)
|
||||||
|
if (res.code === 200 && res.data) { // 增加res.data判空,避免报错
|
||||||
|
tableListData.value = res.data.list || []
|
||||||
|
pageData.total = res.data.total || 0
|
||||||
|
} else {
|
||||||
|
ElMessage.warning('暂无相关明细数据')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('月度明细查询失败:', error)
|
||||||
|
ElMessage.error('查询失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
listLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出月度明细Excel
|
||||||
|
const exportExcel = async () => {
|
||||||
|
exportLoading.value = true
|
||||||
|
try {
|
||||||
|
// 构造导出参数(不含分页,导出全部符合条件数据)
|
||||||
|
const params: UserScoreListReqVO = {
|
||||||
|
userId: queryParams.userId,
|
||||||
|
beginMonth: queryParams.beginMonth,
|
||||||
|
endMonth: queryParams.endMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用导出接口,指定响应类型为blob(接收文件流)
|
||||||
|
const response = await userScoreApi.exportUserScoreList(params, {
|
||||||
|
responseType: 'blob'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理文件下载逻辑
|
||||||
|
if (response instanceof Blob) {
|
||||||
|
// 创建Excel格式的Blob对象
|
||||||
|
const blob = new Blob([response], {
|
||||||
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||||
|
})
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
|
||||||
|
// 提取文件名(优先用后端返回的文件名,否则用默认名)
|
||||||
|
const username = search.row.userName || '未知用户'
|
||||||
|
const filename = getFilenameFromResponse(response) || `${username}_积分月度明细.xlsx`
|
||||||
|
link.download = filename
|
||||||
|
|
||||||
|
// 触发下载并清理资源
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
ElMessage.success('导出成功')
|
||||||
|
} else {
|
||||||
|
ElMessage.success('导出请求已发送')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('导出失败:', error)
|
||||||
|
ElMessage.error('导出失败,请稍后重试')
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开详情抽屉
|
||||||
|
const open = (data: { row: Partial<UserScoreListRespVO & { userId: string }>, monthRange: string[] }) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
search.row = data.row || {}
|
||||||
|
search.monthRange = data.monthRange || []
|
||||||
|
|
||||||
|
// 初始化查询参数
|
||||||
|
queryParams.userId = data.row?.userId || ''
|
||||||
|
queryParams.beginMonth = data.monthRange?.[0]
|
||||||
|
queryParams.endMonth = data.monthRange?.[1]
|
||||||
|
queryParams.pageNo = 1 // 重置为第一页
|
||||||
|
|
||||||
|
// 触发分页查询
|
||||||
|
pageList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭抽屉(重置状态)
|
||||||
|
const handleClose = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 重置搜索参数
|
||||||
|
search.row = {}
|
||||||
|
search.monthRange = []
|
||||||
|
// 重置列表数据
|
||||||
|
tableListData.value = []
|
||||||
|
pageData.total = 0
|
||||||
|
// 重置查询参数
|
||||||
|
queryParams.userId = ''
|
||||||
|
queryParams.beginMonth = undefined
|
||||||
|
queryParams.endMonth = undefined
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露open方法给父组件调用
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drawer-content {
|
||||||
|
padding: 0px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content .group-name {
|
||||||
|
color: #A8ABB2;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content .group-warp {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content .group-warp:last-child {
|
||||||
|
padding-bottom: 0px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-content .group-container {
|
||||||
|
padding: 10px 15px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-content {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b-white {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt16 {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tr {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化描述列表样式 */
|
||||||
|
.el-descriptions {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-descriptions-item__label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue