OfficeSystem/pages/verify/detail/index.vue
2025-11-26 15:13:25 +08:00

529 lines
12 KiB
Vue

<template>
<view class="verify-detail-page">
<!-- 自定义导航栏 -->
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y v-if="!loading">
<view class="content-wrapper">
<!-- 详情表格 -->
<view class="info-card">
<view class="info-row">
<text class="info-label">所属项目</text>
<text class="info-value">{{ detailData.projectName || '--' }}</text>
</view>
<view class="info-row">
<text class="info-label">任务类型</text>
<view class="info-value">
<uv-tags
:text="getTaskTypeLabel(detailData.task?.type)"
type="success"
size="mini"
:plain="false"
></uv-tags>
</view>
</view>
<view class="info-row">
<text class="info-label">优先级</text>
<view class="info-value">
<uv-tags
:text="getPriorityLabel(detailData.task?.level)"
:type="getPriorityType(detailData.task?.level)"
size="mini"
:plain="false"
></uv-tags>
</view>
</view>
<view class="info-row">
<text class="info-label">任务状态</text>
<view class="info-value">
<uv-tags
:text="getTaskStatusLabel(detailData.task?.status)"
:type="getTaskStatusType(detailData.task?.status)"
size="mini"
:plain="false"
></uv-tags>
</view>
</view>
<view class="info-row">
<text class="info-label">审核状态</text>
<view class="info-value">
<uv-tags
:text="getVerifyStatusLabel(detailData.status)"
:type="getVerifyStatusType(detailData.status)"
size="mini"
:plain="false"
></uv-tags>
</view>
</view>
<view class="info-row">
<text class="info-label">创建人</text>
<text class="info-value">{{ detailData?.createName || '--' }}</text>
</view>
<view class="info-row">
<text class="info-label">创建时间</text>
<text class="info-value">{{ detailData.createTime || '--' }}</text>
</view>
<view class="info-row" v-if="detailData.task?.expireTime">
<text class="info-label">截止时间</text>
<text class="info-value">{{ detailData.task.expireTime }}</text>
</view>
<view class="info-row" v-if="detailData.task?.passTime">
<text class="info-label">结束时间</text>
<text class="info-value">{{ detailData.task.passTime }}</text>
</view>
<view class="info-row" v-if="detailData.userName">
<text class="info-label">审核人</text>
<text class="info-value">{{ detailData.userName }}</text>
</view>
<view class="info-row" v-if="detailData.verifyTime">
<text class="info-label">审核时间</text>
<text class="info-value">{{ detailData.verifyTime }}</text>
</view>
</view>
<!-- 任务描述 -->
<view class="info-card" v-if="detailData.task?.description">
<view class="card-title">任务描述</view>
<view class="content-text">{{ detailData.task.description }}</view>
</view>
<!-- 备注 -->
<view class="info-card" v-if="detailData.createRemark">
<view class="card-title">备注</view>
<view class="content-text">{{ detailData.createRemark }}</view>
</view>
<!-- 改动值 -->
<view class="info-card" v-if="changeData">
<view class="card-title">改动值</view>
<view class="info-row">
<text class="info-label">字段</text>
<text class="info-value">{{ changeData.field || '--' }}</text>
</view>
<view class="info-row">
<text class="info-label"></text>
<text class="info-value">{{ changeData.value || '--' }}</text>
</view>
</view>
<!-- 评论输入框 -->
<view class="info-card">
<view class="card-title">审核意见</view>
<textarea
v-model="commentText"
class="comment-textarea"
placeholder="请输入审核意见"
:maxlength="500"
:auto-height="true"
></textarea>
</view>
</view>
</scroll-view>
<!-- 加载中 -->
<view class="loading-wrapper" v-if="loading">
<text>加载中...</text>
</view>
<!-- 底部操作按钮 -->
<view class="action-buttons" v-if="!loading && detailData.status === '3' && showPrivateSwitch">
<view class="btn-wrapper">
<uv-button type="primary" size="normal" @click="handleApprove">通过</uv-button>
</view>
<view class="btn-wrapper">
<uv-button type="error" size="normal" @click="handleReject">驳回</uv-button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getVerifyDetail, handleVerify } from '@/api/verify'
import { getDictLabel } from '@/utils/dict'
import {useUserStore} from "@/store/user";
import {storeToRefs} from "pinia";
const userStore = useUserStore()
const { userInfo } = storeToRefs(userStore)
const showPrivateSwitch = computed(() =>
userInfo.value?.roles?.some(r => ['admin', 'sys_admin'].includes(r))
)
// 页面参数
const verifyId = ref('')
const detailData = ref({})
const loading = ref(false)
const commentText = ref('')
// 解析改动数据
const changeData = computed(() => {
if (!detailData.value.data) return null
try {
const data = typeof detailData.value.data === 'string'
? JSON.parse(detailData.value.data)
: detailData.value.data
if (data.expireTime) {
return {
field: '截止时间',
value: data.expireTime
}
}
return null
} catch (e) {
console.error('解析改动数据失败:', e)
return null
}
})
// 获取任务类型标签
const getTaskTypeLabel = (type) => {
if (!type) return '--'
console.log('getDictLabel', type)
console.log('getDictLabel',getDictLabel('task_type', type))
return getDictLabel('task_type', type)
}
// 获取优先级标签
const getPriorityLabel = (level) => {
if (!level) return '--'
return getDictLabel('task_level', level)
}
// 获取优先级类型(用于标签颜色)
const getPriorityType = (level) => {
const typeMap = {
'1': 'error', // 高优先级 - 红色
'2': 'warning', // 中优先级 - 黄色
'3': 'success' // 低优先级 - 绿色
}
return typeMap[level] || 'primary'
}
// 获取任务状态标签
const getTaskStatusLabel = (status) => {
if (!status) return '--'
return getDictLabel('task_status', status)
}
// 获取任务状态类型(用于标签颜色)
const getTaskStatusType = (status) => {
const typeMap = {
'1': 'primary', // 待接收
'2': 'warning', // 进行中
'4': 'success', // 已完成
'6': 'error' // 已取消
}
return typeMap[status] || 'primary'
}
// 获取审核状态标签
const getVerifyStatusLabel = (status) => {
if (!status) return '--'
return getDictLabel('verify_status', status)
}
// 获取审核状态类型(用于标签颜色)
const getVerifyStatusType = (status) => {
const typeMap = {
'1': 'success', // 已通过
'2': 'error', // 已驳回error
'3': 'warning' // 待审核
}
return typeMap[status] || 'primary'
}
// 加载审核详情
const loadVerifyDetail = async (id) => {
if (!id) {
uni.showToast({
title: '审核ID不能为空',
icon: 'none'
})
return
}
try {
loading.value = true
const res = await getVerifyDetail(id)
console.log('审核详情数据:', res)
detailData.value = res || {}
loading.value = false
} catch (err) {
console.error('加载审核详情失败:', err)
loading.value = false
uni.showToast({
title: '加载审核详情失败',
icon: 'none'
})
}
}
// 处理审核通过
const handleApprove = async () => {
if (!verifyId.value) {
uni.showToast({
title: '审核ID不能为空',
icon: 'none'
})
return
}
try {
uni.showLoading({
title: '处理中...'
})
await handleVerify(verifyId.value, '1', commentText.value)
uni.hideLoading()
uni.showToast({
title: '审核通过',
icon: 'success'
})
// 刷新详情数据
await loadVerifyDetail(verifyId.value)
// 发送刷新列表事件
uni.$emit('verifyListRefresh')
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (err) {
uni.hideLoading()
console.error('审核通过失败:', err)
uni.showToast({
title: err.message || '审核通过失败',
icon: 'none'
})
}
}
// 处理审核驳回
const handleReject = async () => {
if (!verifyId.value) {
uni.showToast({
title: '审核ID不能为空',
icon: 'none'
})
return
}
// 确认对话框
uni.showModal({
title: '确认驳回',
content: '确定要驳回此审核吗?',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: '处理中...'
})
await handleVerify(verifyId.value, '2', commentText.value)
uni.hideLoading()
uni.showToast({
title: '已驳回',
icon: 'success'
})
// 刷新详情数据
await loadVerifyDetail(verifyId.value)
// 发送刷新列表事件
uni.$emit('verifyListRefresh')
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (err) {
uni.hideLoading()
console.error('审核驳回失败:', err)
uni.showToast({
title: err.message || '审核驳回失败',
icon: 'none'
})
}
}
}
})
}
// 返回上一页
const handleBack = () => {
uni.navigateBack()
}
// 页面加载
onLoad((options) => {
const id = options.id || options.verifyId
if (id) {
verifyId.value = id
loadVerifyDetail(id)
} else {
uni.showToast({
title: '缺少审核ID',
icon: 'none'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
}
})
</script>
<style lang="scss" scoped>
.verify-detail-page {
width: 100%;
height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.custom-navbar {
background: #fff;
border-bottom: 1px solid #e0e0e0;
position: sticky;
top: 0;
z-index: 100;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
height: 44px;
}
.nav-btn {
font-size: 24px;
color: #333;
width: 32px;
text-align: center;
line-height: 32px;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.content-scroll {
flex: 1;
overflow-y: auto;
}
.content-wrapper {
padding: 16px;
}
.info-card {
background: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.info-row {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.info-label {
font-size: 14px;
color: #666;
width: 100px;
flex-shrink: 0;
}
.info-value {
font-size: 14px;
color: #333;
flex: 1;
display: flex;
justify-content: flex-end;
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}
.content-text {
font-size: 14px;
color: #333;
line-height: 1.6;
word-break: break-all;
}
.comment-textarea {
width: 100%;
min-height: 100px;
padding: 12px;
font-size: 14px;
color: #333;
background: #f5f5f5;
border-radius: 8px;
border: 1px solid #e0e0e0;
box-sizing: border-box;
}
.loading-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
}
.action-buttons {
display: flex;
gap: 12px;
padding: 16px;
background: #fff;
border-top: 1px solid #e0e0e0;
position: sticky;
bottom: 0;
z-index: 100;
}
.btn-wrapper {
flex: 1;
}
</style>