Browse Source

daima

master
wang 9 months ago
parent
commit
d392f1b5e4
  1. BIN
      dist.zip
  2. 13
      src/api/business/penalty-apply/penalty-apply-api.js
  3. 9
      src/views/business/erp/cost/firm-reports-list.vue
  4. 2
      src/views/business/erp/letter/letter-list.vue
  5. 264
      src/views/business/erp/penalty-apply/penalty-apply-list.vue
  6. 78
      src/views/mobile/service/create.vue
  7. 57
      src/views/mobile/service/detail.vue

BIN
dist.zip

Binary file not shown.

13
src/api/business/penalty-apply/penalty-apply-api.js

@ -5,6 +5,7 @@
* @Date: 2026-01-07 21:36:44
* @Copyright 1.0
*/
import axios from 'axios';
import { postRequest, getRequest, getDownload } from '/@/lib/axios';
export const penaltyApplyApi = {
@ -52,4 +53,16 @@ export const penaltyApplyApi = {
return getDownload(`/wordCertificate/export/${id}`);
},
/**
* 预览无处罚证明 @author wzh
*/
/**
* 审核 @author wzh
*/
review: (param) => {
return postRequest('/penaltyApply/review', param);
},
};

9
src/views/business/erp/cost/firm-reports-list.vue

@ -9,7 +9,8 @@
<!---------- 查询表单form begin ----------->
<a-form class="smart-query-form">
<a-row class="smart-query-form-row">
<a-form-item label="律师事务所" class="smart-query-form-item">
<!-- 律师事务所搜索条件:只有CEO角色能看到 -->
<a-form-item label="律师事务所" class="smart-query-form-item" v-if="isCeo">
<DepartmentTreeSelect style="width: 250px" v-model:value="queryForm.firmId" placeholder="请选择律师事务所" />
</a-form-item>
<a-form-item label="报表年份" class="smart-query-form-item">
@ -23,7 +24,7 @@
style="width: 100px"
/>
</a-form-item>
<a-form-item class="smart-query-form-item" style="margin-left: 120px;">
<a-form-item class="smart-query-form-item" :style="{ marginLeft: isCeo ? '120px' : '0' }">
<a-button type="primary" @click="onSearch">
<template #icon>
<SearchOutlined />
@ -128,6 +129,7 @@
const loginInfo = ref(null);
const currentUserId = ref(null);
const isCto = ref(false);
const isCeo = ref(false);
// 获取登录信息
async function getLoginInfo() {
@ -136,9 +138,10 @@
loginInfo.value = res.data;
currentUserId.value = res.data.employeeId;
// 判断是否为CTO角色
// 判断角色
const role = (res.data?.roleCode || res.data?.roleName || '').toLowerCase();
isCto.value = role === 'cto';
isCeo.value = role === 'ceo';
} catch (e) {
smartSentry.captureError(e);

2
src/views/business/erp/letter/letter-list.vue

@ -7,7 +7,7 @@
-->
<template>
<!---------- 查询表单form begin ----------->
<a-form class="smart-query-form">
<a-form class="smart-query-form" v-if="isCeo">
<a-row class="smart-query-form-row">
<a-col :span="8">
<a-form-item label="用户名称" class="smart-query-form-item">

264
src/views/business/erp/penalty-apply/penalty-apply-list.vue

@ -74,8 +74,15 @@
<template v-if="column.dataIndex === 'action'">
<div class="smart-table-operate">
<a-space>
<a-button type="link" size="small" @click="showAuditModal(record)" :disabled="record.status !== 1" v-if="isCeo">审批</a-button>
<a-button type="link" size="small" @click="handleDownload(record)" v-if="record.status === 3 && isUser">下载</a-button>
<!-- CEO角色:判断auditStatus字段,状态为1(已提交)时显示审批按钮 -->
<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>
</div>
</template>
@ -101,6 +108,29 @@
<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
title="审批"
@ -113,7 +143,7 @@
>
<a-form :model="auditForm" :label-col="{ span: 6 }">
<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="4">拒绝</a-radio>
</a-radio-group>
@ -123,6 +153,81 @@
</a-card>
</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>
import { reactive, ref, onMounted } from 'vue';
import { message, Modal } from 'ant-design-vue';
@ -155,7 +260,7 @@ import PenaltyApplyForm from './penalty-apply-form.vue';
ellipsis: true,
},
{
title: '状态',
title: '律所审批状态',
dataIndex: 'status',
ellipsis: true,
customRender: ({ text }) => {
@ -163,6 +268,15 @@ import PenaltyApplyForm from './penalty-apply-form.vue';
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: '创建时间',
dataIndex: 'createTime',
@ -206,6 +320,9 @@ import PenaltyApplyForm from './penalty-apply-form.vue';
// 判断用户是否为User角色
const isUser = ref(false);
// 判断用户是否为CTO角色
const isCto = ref(false);
// 重置查询条件
function resetQuery() {
let pageSize = queryForm.pageSize;
@ -276,7 +393,10 @@ import PenaltyApplyForm from './penalty-apply-form.vue';
// 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 currentAuditRecord = ref(null);
const auditForm = reactive({
auditResult: 3, // 默认同意
auditStatus: 3, // 默认同意
auditRemark: ''
});
// 显示审批弹框
function showAuditModal(record) {
currentAuditRecord.value = record;
auditForm.auditResult = 3;
auditForm.auditStatus = 3;
auditForm.auditRemark = '';
auditModalVisible.value = true;
}
@ -347,17 +467,26 @@ import PenaltyApplyForm from './penalty-apply-form.vue';
auditLoading.value = true;
// 构建审核数据,使用专门的审核接口
const auditData = {
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) {
auditData.approvalRemark = auditForm.auditRemark;
auditData.auditRemark = auditForm.auditRemark;
}
try {
await penaltyApplyApi.update(auditData);
// 使用专门的审核接口而不是通用的update接口
await penaltyApplyApi.review(auditData);
message.success('审批成功');
auditModalVisible.value = false;
queryData();
@ -373,10 +502,123 @@ import PenaltyApplyForm from './penalty-apply-form.vue';
function handleAuditCancel() {
auditModalVisible.value = false;
currentAuditRecord.value = null;
auditForm.auditResult = 3;
auditForm.auditStatus = 3;
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) {

78
src/views/mobile/service/create.vue

@ -246,7 +246,10 @@
<!-- 服务内容 -->
<div class="form-item">
<label class="form-label required">服务内容描述</label>
<label class="form-label required">
服务内容描述
<span style="font-size: 12px; color: #ff4d4f; margin-left: 8px;">内容描述需要包括的内容主要是时间、地点、主题、参与人员、服务内容、活动效果等</span>
</label>
<textarea
v-model="form.serviceContent"
class="form-textarea"
@ -316,13 +319,7 @@
</div>
</div>
<!-- 承诺书弹框 -->
<AgreementModal
v-if="showAgreementModal"
:visible="showAgreementModal"
@confirm="handleAgreementConfirm"
@cancel="handleAgreementCancel"
/>
</template>
<script setup>
@ -338,15 +335,14 @@ import { goodsApi } from '/@/api/business/goods/goods-api'
import { fileApi } from '/@/api/support/file-api'
import { letterApi } from '/@/api/business/letter/letter-api'
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const'
import AgreementModal from '../components/agreement-modal.vue'
// 路由和加载状态
const router = useRouter()
const route = useRoute()
const loading = ref(false)
// 承诺书弹框状态
const showAgreementModal = ref(false)
// 承诺书签约状态
const agreementSigned = ref(true)
// 标记是否为编辑模式(加载已有数据)
@ -443,7 +439,6 @@ async function getUserInfo() {
// 检查用户签约状态(与PC端逻辑一致)
console.log('用户签约状态 agreementSignFlag:', userInfo.agreementSignFlag)
agreementSigned.value = userInfo.agreementSignFlag === true
showAgreementModal.value = !agreementSigned.value
return userInfo.agreementSignFlag
} catch (error) {
console.error('获取用户信息失败:', error)
@ -451,38 +446,7 @@ async function getUserInfo() {
}
}
// 协议确认 - 签署承诺书
async function handleAgreementConfirm() {
try {
await letterApi.add()
message.success('承诺书签署成功')
showAgreementModal.value = false
agreementSigned.value = true
} catch (error) {
message.error('承诺书签署失败,请重新登录')
console.error('承诺书签署失败:', error)
// 签署失败,退出登录
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
// 延迟跳转到登录页
setTimeout(() => {
router.push('/mobile/login')
}, 1500)
}
}
// 协议取消
function handleAgreementCancel() {
message.warning('您需要同意平台协议才能使用系统')
showAgreementModal.value = false
// 退出登录
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
// 延迟跳转到登录页
setTimeout(() => {
router.push('/mobile/login')
}, 1500)
}
// 获取下拉选项数据
async function getSelectOptions() {
@ -670,6 +634,18 @@ function removeFile(fileId) {
// 保存草稿
async function handleSave() {
// 再次检查用户是否已签约承诺书(双重保险)
if (!agreementSigned.value) {
message.warning('您尚未签署承诺书,无法进行服务申报。请先签署承诺书。')
return
}
// 再次检查用户是否已签约承诺书(双重保险)
if (!agreementSigned.value) {
message.warning('您尚未签署承诺书,无法进行服务申报。请先签署承诺书。')
return
}
// 表单验证
if (!form.positionId) {
message.error('请选择职务')
@ -1031,10 +1007,20 @@ async function loadDraftData(applicationId) {
}
}
onMounted(() => {
onMounted(async () => {
// 首先检查用户签约状态
const isSigned = await getUserInfo()
if (!isSigned) {
message.warning('您尚未签署承诺书,无法进行服务申报。请先签署承诺书。')
// 延迟后返回上一页
setTimeout(() => {
router.back()
}, 2000)
return
}
isEditMode.value = false // 重置为新建模式
getUserInfo()
getSelectOptions()
await getSelectOptions()
// 检查是否有applicationId参数(编辑草稿模式)
const applicationId = route.query.applicationId

57
src/views/mobile/service/detail.vue

@ -380,13 +380,7 @@
</div>
</div>
<!-- 承诺书弹框 -->
<AgreementModal
v-if="showAgreementModal"
:visible="showAgreementModal"
@confirm="handleAgreementConfirm"
@cancel="handleAgreementCancel"
/>
</template>
<script setup>
@ -402,15 +396,14 @@ import { goodsApi } from '/@/api/business/goods/goods-api'
import { fileApi } from '/@/api/support/file-api'
import { letterApi } from '/@/api/business/letter/letter-api'
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const'
import AgreementModal from '../components/agreement-modal.vue'
const router = useRouter()
const route = useRoute()
const loading = ref(false)
const readonlyMode = ref(true) // 默认只读模式
// 承诺书弹框状态
const showAgreementModal = ref(false)
// 承诺书签约状态
const agreementSigned = ref(true)
// 表单数据
@ -545,44 +538,12 @@ async function getUserInfo() {
// 检查用户签约状态
console.log('用户签约状态 agreementSignFlag:', userInfo.agreementSignFlag)
agreementSigned.value = userInfo.agreementSignFlag === true
showAgreementModal.value = !agreementSigned.value
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
// 协议确认 - 签署承诺书
async function handleAgreementConfirm() {
try {
await letterApi.add()
message.success('承诺书签署成功')
showAgreementModal.value = false
agreementSigned.value = true
} catch (error) {
message.error('承诺书签署失败,请重新登录')
console.error('承诺书签署失败:', error)
// 签署失败,退出登录
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
// 延迟跳转到登录页
setTimeout(() => {
router.push('/mobile/login')
}, 1500)
}
}
// 协议取消
function handleAgreementCancel() {
message.warning('您需要同意平台协议才能使用系统')
showAgreementModal.value = false
// 退出登录
localStorage.removeItem('token')
localStorage.removeItem('userInfo')
// 延迟跳转到登录页
setTimeout(() => {
router.push('/mobile/login')
}, 1500)
}
// 获取申报详情
async function getDetail() {
@ -834,6 +795,18 @@ function handleBack() {
async function handleSave() {
if (readonlyMode.value) return
// 检查用户是否已签约承诺书
if (!agreementSigned.value) {
message.warning('您尚未签署承诺书,无法进行操作。请先签署承诺书。')
return
}
// 检查用户是否已签约承诺书
if (!agreementSigned.value) {
message.warning('您尚未签署承诺书,无法进行操作。请先签署承诺书。')
return
}
// 表单验证
if (!form.positionId) {
message.error('请选择职务')

Loading…
Cancel
Save