|
|
@ -74,8 +74,15 @@ |
|
|
<template v-if="column.dataIndex === 'action'"> |
|
|
<template v-if="column.dataIndex === 'action'"> |
|
|
<div class="smart-table-operate"> |
|
|
<div class="smart-table-operate"> |
|
|
<a-space> |
|
|
<a-space> |
|
|
<a-button type="link" size="small" @click="showAuditModal(record)" :disabled="record.status !== 1" v-if="isCeo">审批</a-button> |
|
|
<!-- CEO角色:判断auditStatus字段,状态为1(已提交)时显示审批按钮 --> |
|
|
<a-button type="link" size="small" @click="handleDownload(record)" v-if="record.status === 3 && isUser">下载</a-button> |
|
|
<a-button type="link" size="small" @click="showAuditModal(record)" :disabled="record.auditStatus !== 1" v-if="isCeo">审批</a-button> |
|
|
|
|
|
<a-button type="link" size="small" @click="handlePreview(record)" v-if="isCeo">预览</a-button> |
|
|
|
|
|
|
|
|
|
|
|
<!-- CTO角色:判断status字段,状态为1(已提交)时显示审批按钮 --> |
|
|
|
|
|
<a-button type="link" size="small" @click="showAuditModal(record)" :disabled="record.status !== 1" v-if="isCto">审批</a-button> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 用户角色:当auditStatus为3(已批准)且当前用户是文件所有者时显示下载 --> |
|
|
|
|
|
<a-button type="link" size="small" @click="handleDownload(record)" v-if="record.auditStatus === 3 && !isCeo && record.userId === loginInfo?.userId">下载</a-button> |
|
|
</a-space> |
|
|
</a-space> |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
</template> |
|
|
@ -101,6 +108,29 @@ |
|
|
|
|
|
|
|
|
<PenaltyApplyForm ref="formRef" @reloadList="queryData"/> |
|
|
<PenaltyApplyForm ref="formRef" @reloadList="queryData"/> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 图片预览弹框 --> |
|
|
|
|
|
<a-modal |
|
|
|
|
|
:footer="null" |
|
|
|
|
|
:open="previewVisible" |
|
|
|
|
|
@cancel="handleCancelPreview" |
|
|
|
|
|
:width="null" |
|
|
|
|
|
class="image-preview-modal" |
|
|
|
|
|
:centered="true" |
|
|
|
|
|
:closable="true" |
|
|
|
|
|
:maskClosable="true" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="preview-container"> |
|
|
|
|
|
<div class="preview-content"> |
|
|
|
|
|
<img |
|
|
|
|
|
:src="previewUrl" |
|
|
|
|
|
alt="无处罚证明" |
|
|
|
|
|
class="preview-image" |
|
|
|
|
|
@error="handleImageError" |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</a-modal> |
|
|
|
|
|
|
|
|
<!-- 审批弹框 --> |
|
|
<!-- 审批弹框 --> |
|
|
<a-modal |
|
|
<a-modal |
|
|
title="审批" |
|
|
title="审批" |
|
|
@ -113,7 +143,7 @@ |
|
|
> |
|
|
> |
|
|
<a-form :model="auditForm" :label-col="{ span: 6 }"> |
|
|
<a-form :model="auditForm" :label-col="{ span: 6 }"> |
|
|
<a-form-item label="审批结果" required> |
|
|
<a-form-item label="审批结果" required> |
|
|
<a-radio-group v-model:value="auditForm.auditResult"> |
|
|
<a-radio-group v-model:value="auditForm.auditStatus"> |
|
|
<a-radio :value="3">同意</a-radio> |
|
|
<a-radio :value="3">同意</a-radio> |
|
|
<a-radio :value="4">拒绝</a-radio> |
|
|
<a-radio :value="4">拒绝</a-radio> |
|
|
</a-radio-group> |
|
|
</a-radio-group> |
|
|
@ -123,8 +153,83 @@ |
|
|
|
|
|
|
|
|
</a-card> |
|
|
</a-card> |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
|
|
|
<style scoped> |
|
|
|
|
|
.preview-container { |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
padding: 0; |
|
|
|
|
|
background: #fff; |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-header { |
|
|
|
|
|
padding: 16px 20px; |
|
|
|
|
|
border-bottom: 1px solid #f0f0f0; |
|
|
|
|
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-title { |
|
|
|
|
|
font-size: 16px; |
|
|
|
|
|
font-weight: 600; |
|
|
|
|
|
color: #2c3e50; |
|
|
|
|
|
letter-spacing: 0.5px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-content { |
|
|
|
|
|
padding: 20px; |
|
|
|
|
|
min-height: 200px; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
background: #f8f9fa; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-image { |
|
|
|
|
|
max-width: 95vw; |
|
|
|
|
|
max-height: 85vh; |
|
|
|
|
|
object-fit: contain; |
|
|
|
|
|
border-radius: 6px; |
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
|
|
|
|
|
transition: transform 0.2s ease; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-image:hover { |
|
|
|
|
|
transform: scale(1.01); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-footer { |
|
|
|
|
|
padding: 12px 20px; |
|
|
|
|
|
border-top: 1px solid #f0f0f0; |
|
|
|
|
|
background: #fafafa; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.preview-tip { |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #7f8c8d; |
|
|
|
|
|
font-style: italic; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 弹框样式优化 */ |
|
|
|
|
|
:deep(.image-preview-modal .ant-modal-content) { |
|
|
|
|
|
border-radius: 12px; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.image-preview-modal .ant-modal-close) { |
|
|
|
|
|
top: 12px; |
|
|
|
|
|
right: 12px; |
|
|
|
|
|
color: #666; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
:deep(.image-preview-modal .ant-modal-close:hover) { |
|
|
|
|
|
color: #333; |
|
|
|
|
|
background: rgba(0, 0, 0, 0.05); |
|
|
|
|
|
} |
|
|
|
|
|
</style> |
|
|
<script setup> |
|
|
<script setup> |
|
|
import { reactive, ref, onMounted } from 'vue'; |
|
|
import { reactive, ref, onMounted } from 'vue'; |
|
|
import { message, Modal } from 'ant-design-vue'; |
|
|
import { message, Modal } from 'ant-design-vue'; |
|
|
import { SmartLoading } from '/@/components/framework/smart-loading'; |
|
|
import { SmartLoading } from '/@/components/framework/smart-loading'; |
|
|
import { penaltyApplyApi } from '/@/api/business/penalty-apply/penalty-apply-api'; |
|
|
import { penaltyApplyApi } from '/@/api/business/penalty-apply/penalty-apply-api'; |
|
|
@ -155,7 +260,7 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
ellipsis: true, |
|
|
ellipsis: true, |
|
|
}, |
|
|
}, |
|
|
{ |
|
|
{ |
|
|
title: '状态', |
|
|
title: '律所审批状态', |
|
|
dataIndex: 'status', |
|
|
dataIndex: 'status', |
|
|
ellipsis: true, |
|
|
ellipsis: true, |
|
|
customRender: ({ text }) => { |
|
|
customRender: ({ text }) => { |
|
|
@ -163,6 +268,15 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
return status ? status.desc : text; |
|
|
return status ? status.desc : text; |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
title: '律协审批状态', |
|
|
|
|
|
dataIndex: 'auditStatus', |
|
|
|
|
|
ellipsis: true, |
|
|
|
|
|
customRender: ({ text }) => { |
|
|
|
|
|
const auditStatus = Object.values(REVIEW_ENUM).find(item => item.value === text); |
|
|
|
|
|
return auditStatus ? auditStatus.desc : text; |
|
|
|
|
|
}, |
|
|
|
|
|
}, |
|
|
{ |
|
|
{ |
|
|
title: '创建时间', |
|
|
title: '创建时间', |
|
|
dataIndex: 'createTime', |
|
|
dataIndex: 'createTime', |
|
|
@ -205,6 +319,9 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
|
|
|
|
|
|
// 判断用户是否为User角色 |
|
|
// 判断用户是否为User角色 |
|
|
const isUser = ref(false); |
|
|
const isUser = ref(false); |
|
|
|
|
|
|
|
|
|
|
|
// 判断用户是否为CTO角色 |
|
|
|
|
|
const isCto = ref(false); |
|
|
|
|
|
|
|
|
// 重置查询条件 |
|
|
// 重置查询条件 |
|
|
function resetQuery() { |
|
|
function resetQuery() { |
|
|
@ -276,7 +393,10 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
// User角色判断 |
|
|
// User角色判断 |
|
|
isUser.value = roleLower === 'user'; |
|
|
isUser.value = roleLower === 'user'; |
|
|
|
|
|
|
|
|
console.log('用户角色:', userRole, 'isCeo:', isCeo.value, 'isUser:', isUser.value); |
|
|
// CTO角色判断 |
|
|
|
|
|
isCto.value = roleLower === 'cto'; |
|
|
|
|
|
|
|
|
|
|
|
console.log('用户角色:', userRole, 'isCeo:', isCeo.value, 'isUser:', isUser.value, 'isCto:', isCto.value); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -326,14 +446,14 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
const auditLoading = ref(false); |
|
|
const auditLoading = ref(false); |
|
|
const currentAuditRecord = ref(null); |
|
|
const currentAuditRecord = ref(null); |
|
|
const auditForm = reactive({ |
|
|
const auditForm = reactive({ |
|
|
auditResult: 3, // 默认同意 |
|
|
auditStatus: 3, // 默认同意 |
|
|
auditRemark: '' |
|
|
auditRemark: '' |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 显示审批弹框 |
|
|
// 显示审批弹框 |
|
|
function showAuditModal(record) { |
|
|
function showAuditModal(record) { |
|
|
currentAuditRecord.value = record; |
|
|
currentAuditRecord.value = record; |
|
|
auditForm.auditResult = 3; |
|
|
auditForm.auditStatus = 3; |
|
|
auditForm.auditRemark = ''; |
|
|
auditForm.auditRemark = ''; |
|
|
auditModalVisible.value = true; |
|
|
auditModalVisible.value = true; |
|
|
} |
|
|
} |
|
|
@ -347,17 +467,26 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
|
|
|
|
|
|
auditLoading.value = true; |
|
|
auditLoading.value = true; |
|
|
|
|
|
|
|
|
|
|
|
// 构建审核数据,使用专门的审核接口 |
|
|
const auditData = { |
|
|
const auditData = { |
|
|
id: currentAuditRecord.value.id, |
|
|
id: currentAuditRecord.value.id, |
|
|
status: auditForm.auditResult |
|
|
auditStatus: auditForm.auditStatus // 审核结果:3-已批准,5-已驳回 |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 根据角色使用不同的审核状态字段 |
|
|
|
|
|
if (isCeo.value) { |
|
|
|
|
|
auditData.auditStatusField = 'auditStatus'; // CEO角色使用律协审批状态字段 |
|
|
|
|
|
} else if (isCto.value) { |
|
|
|
|
|
auditData.auditStatusField = 'status'; // CTO角色使用律所审批状态字段 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (auditForm.auditRemark) { |
|
|
if (auditForm.auditRemark) { |
|
|
auditData.approvalRemark = auditForm.auditRemark; |
|
|
auditData.auditRemark = auditForm.auditRemark; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await penaltyApplyApi.update(auditData); |
|
|
// 使用专门的审核接口而不是通用的update接口 |
|
|
|
|
|
await penaltyApplyApi.review(auditData); |
|
|
message.success('审批成功'); |
|
|
message.success('审批成功'); |
|
|
auditModalVisible.value = false; |
|
|
auditModalVisible.value = false; |
|
|
queryData(); |
|
|
queryData(); |
|
|
@ -373,10 +502,123 @@ import PenaltyApplyForm from './penalty-apply-form.vue'; |
|
|
function handleAuditCancel() { |
|
|
function handleAuditCancel() { |
|
|
auditModalVisible.value = false; |
|
|
auditModalVisible.value = false; |
|
|
currentAuditRecord.value = null; |
|
|
currentAuditRecord.value = null; |
|
|
auditForm.auditResult = 3; |
|
|
auditForm.auditStatus = 3; |
|
|
auditForm.auditRemark = ''; |
|
|
auditForm.auditRemark = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------- 预览相关 ---------------------------- |
|
|
|
|
|
|
|
|
|
|
|
// 处理预览 |
|
|
|
|
|
async function handlePreview(record) { |
|
|
|
|
|
try { |
|
|
|
|
|
previewLoading.value = true; |
|
|
|
|
|
message.loading('正在获取文件...', 0); |
|
|
|
|
|
|
|
|
|
|
|
// 直接使用现有的下载接口,但通过fetch获取文件流 |
|
|
|
|
|
const token = localStorage.getItem('smart_admin_user_token'); |
|
|
|
|
|
const response = await fetch(`/api/wordCertificate/export/${record.id}`, { |
|
|
|
|
|
method: 'GET', |
|
|
|
|
|
headers: { |
|
|
|
|
|
'Authorization': `Bearer ${token}` |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (response.ok) { |
|
|
|
|
|
const contentType = response.headers.get('content-type'); |
|
|
|
|
|
console.log('Content-Type:', contentType); |
|
|
|
|
|
|
|
|
|
|
|
if (contentType && contentType.includes('application/json')) { |
|
|
|
|
|
// 如果返回JSON,说明可能有错误信息 |
|
|
|
|
|
const jsonData = await response.json(); |
|
|
|
|
|
console.error('后端返回错误:', jsonData); |
|
|
|
|
|
message.destroy(); |
|
|
|
|
|
message.warning(jsonData.message || '获取文件时发生错误'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const blob = await response.blob(); |
|
|
|
|
|
|
|
|
|
|
|
// 检查文件大小,避免空文件 |
|
|
|
|
|
if (blob.size === 0) { |
|
|
|
|
|
message.destroy(); |
|
|
|
|
|
message.warning('文件为空,无法预览'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是图片类型 |
|
|
|
|
|
if (blob.type && !blob.type.startsWith('image/')) { |
|
|
|
|
|
message.destroy(); |
|
|
|
|
|
message.warning('文件不是图片格式,无法预览'); |
|
|
|
|
|
console.warn('非图片类型:', blob.type); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建文件URL |
|
|
|
|
|
const fileUrl = URL.createObjectURL(blob); |
|
|
|
|
|
|
|
|
|
|
|
console.log('创建的预览URL:', fileUrl); |
|
|
|
|
|
|
|
|
|
|
|
message.destroy(); |
|
|
|
|
|
showImagePreview(fileUrl); |
|
|
|
|
|
} else { |
|
|
|
|
|
message.destroy(); |
|
|
|
|
|
// 尝试读取错误响应 |
|
|
|
|
|
const contentType = response.headers.get('content-type'); |
|
|
|
|
|
let errorMessage = '获取文件失败,请检查文件是否存在'; |
|
|
|
|
|
|
|
|
|
|
|
if (contentType && contentType.includes('application/json')) { |
|
|
|
|
|
try { |
|
|
|
|
|
const errorJson = await response.json(); |
|
|
|
|
|
errorMessage = errorJson.message || errorMessage; |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
console.error('解析错误响应失败:', e); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
try { |
|
|
|
|
|
const errorText = await response.text(); |
|
|
|
|
|
errorMessage = errorText; |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
console.error('读取错误文本失败:', e); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.error('获取文件失败:', errorMessage); |
|
|
|
|
|
message.warning(errorMessage); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('预览失败:', error); |
|
|
|
|
|
message.destroy(); |
|
|
|
|
|
message.error('预览失败,请稍后重试'); |
|
|
|
|
|
} finally { |
|
|
|
|
|
previewLoading.value = false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 图片预览相关状态 |
|
|
|
|
|
const previewVisible = ref(false); |
|
|
|
|
|
const previewUrl = ref(''); |
|
|
|
|
|
const previewLoading = ref(false); |
|
|
|
|
|
|
|
|
|
|
|
// 显示图片预览 |
|
|
|
|
|
function showImagePreview(url) { |
|
|
|
|
|
previewUrl.value = url; |
|
|
|
|
|
previewVisible.value = true; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 关闭图片预览 |
|
|
|
|
|
function handleCancelPreview() { |
|
|
|
|
|
previewVisible.value = false; |
|
|
|
|
|
previewUrl.value = ''; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 图片加载错误处理 |
|
|
|
|
|
function handleImageError(event) { |
|
|
|
|
|
// 当预览关闭时,src会被清空,此时不应该显示错误提示 |
|
|
|
|
|
if (!event.target.src || event.target.src === '') { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// ---------------------------- 下载相关 ---------------------------- |
|
|
// ---------------------------- 下载相关 ---------------------------- |
|
|
// 处理下载 |
|
|
// 处理下载 |
|
|
function handleDownload(record) { |
|
|
function handleDownload(record) { |
|
|
|