Browse Source

daima

master
wang 2 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. 266
      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">

266
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,8 +153,83 @@
</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 { reactive, ref, onMounted } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { penaltyApplyApi } from '/@/api/business/penalty-apply/penalty-apply-api';
@ -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