律师系统前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

634 lines
23 KiB

4 months ago
<!--
* 服务申报表
*
* @Author: wzh
* @Date: 2025-12-20 14:44:06
* @Copyright 1.0
-->
<template>
<a-modal
:title="form.applicationId ? '编辑' : '申报'"
:width="1000"
:open="visibleFlag"
@cancel="onClose"
:maskClosable="false"
:destroyOnClose="true"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical" >
<!-- 基本信息 -->
<a-row :gutter="16">
<a-col :span="24">
<div style="font-weight: bold; font-size: 16px; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 1px solid #d9d9d9;">
基础信息
</div>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="姓名" name="actualName">
<a-input style="width: 100%" v-model:value="form.actualName" placeholder="员工姓名" disabled />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="执业证号" name="certificateNumber">
<a-input style="width: 100%" v-model:value="form.certificateNumber" placeholder="执业证号" disabled />
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="执业机构" name="firmId">
<DepartmentTreeSelect v-model:value="form.firmId" placeholder="执业机构名称" width="100%" disabled />
</a-form-item>
</a-col>
</a-row>
3 months ago
<a-row :gutter="24">
<a-col :span="8">
<a-form-item label="职务" name="positionId">
<PositionSelect v-model:value="form.positionId" placeholder="请选择职务" width="100%" :disabled="readonlyMode" />
</a-form-item>
</a-col>
</a-row>
4 months ago
<!-- 服务信息 -->
<a-row :gutter="16">
<a-col :span="24">
<div style="font-weight: bold; font-size: 16px; margin: 24px 0 16px 0; padding-bottom: 8px; border-bottom: 1px solid #d9d9d9;">
服务信息
</div>
</a-col>
</a-row>
<!-- 服务时间信息 -->
<a-row :gutter="24">
3 months ago
<a-col :span="8">
<a-form-item label="服务开始时间" name="serviceStart" label-align="left">
<a-date-picker show-time format="YYYY-MM-DD HH:00:00" valueFormat="YYYY-MM-DD HH:00:00" v-model:value="form.serviceStart" style="width: 100%" placeholder="服务开始时间" :disabled="readonlyMode" />
4 months ago
</a-form-item>
</a-col>
<a-col :span="8">
3 months ago
<a-form-item label="服务结束时间" name="serviceEnd" label-align="left">
<a-date-picker show-time format="YYYY-MM-DD HH:00:00" valueFormat="YYYY-MM-DD HH:00:00" v-model:value="form.serviceEnd" style="width: 100%" placeholder="服务结束时间" :disabled="readonlyMode" />
4 months ago
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item name="serviceDuration">
<template #label>
<span>服务时长小时</span>
<span style="color: #999; font-size: 12px; margin-left: 4px;">可通过时间选择也可手动填写</span>
</template>
3 months ago
<a-input-number style="width: 100%" v-model:value="form.serviceDuration" placeholder="服务时长(小时)" :disabled="readonlyMode" />
4 months ago
</a-form-item>
3 months ago
<div style="font-size: 12px; color: #f00d0dff; margin-top: -12px; margin-bottom: 12px;">:不足30分钟不含本数不计入时长超过30分钟不足1个小时的含本数按照一个小时填报</div>
4 months ago
</a-col>
</a-row>
<!-- 活动信息 -->
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="活动类型" name="activityCategoryId">
3 months ago
<CategoryTree
v-model:value="form.activityCategoryId"
:category-type="CATEGORY_TYPE_ENUM.GOODS.value"
placeholder="请选择活动类型"
style="width: 100%"
:disabled="readonlyMode"
@change="onActivityCategoryChange"
/>
</a-form-item>
4 months ago
</a-col>
<a-col :span="16">
<a-form-item label="活动名称" name="activityNameId">
<a-select
v-model:value="form.activityNameId"
placeholder="请选择活动名称"
style="width: 100%"
3 months ago
:disabled="!form.activityCategoryId || readonlyMode"
4 months ago
:options="activityList"
show-search
:filter-option="filterActivityOption"
/>
</a-form-item>
</a-col>
</a-row>
<!-- 服务统计信息 -->
<a-row :gutter="16">
<a-col :span="8">
3 months ago
<a-form-item label="参加人数(受益人数)" name="beneficiaryCount">
<a-input-number style="width: 100%" v-model:value="form.beneficiaryCount" placeholder="参加人数(受益人数)" :disabled="readonlyMode" />
4 months ago
</a-form-item>
</a-col>
<a-col :span="16">
<a-form-item label="组织单位名称" name="organizerName">
3 months ago
<a-input style="width: 100%" v-model:value="form.organizerName" placeholder="组织单位名称" :disabled="readonlyMode" />
4 months ago
</a-form-item>
</a-col>
</a-row>
<!-- 联系人信息 -->
<a-row :gutter="16">
<a-col :span="8">
3 months ago
<a-form-item label="服务对象负责人/联系人姓名" name="organizerContact">
<a-input style="width: 100%" v-model:value="form.organizerContact" placeholder="服务对象负责人/联系人姓名" :disabled="readonlyMode" />
4 months ago
</a-form-item>
</a-col>
<a-col :span="16">
<a-form-item label="联系方式" name="organizerPhone">
3 months ago
<a-input style="width: 100%" v-model:value="form.organizerPhone" placeholder="联系电话或邮箱" :disabled="readonlyMode" />
4 months ago
</a-form-item>
</a-col>
</a-row>
<!-- 详细描述和证明材料 -->
<a-form-item label="服务内容描述" name="serviceContent">
3 months ago
<SmartWangeditor ref="serviceContentRef" v-model="form.serviceContent" :height="200" :readonly="readonlyMode" />
4 months ago
</a-form-item>
3 months ago
<a-form-item name="proofMaterials" required>
<template #label>
<span class="ant-form-item-required">证明材料</span>
3 months ago
<span style="font-size: 12px; color: #f00d0dff;"> :请上传活动方案活动记录照片新闻报道等材料支持图片(JPG/PNG)文档(PDF/Word/PPT)格式单文件最大10MB最多上传5个文件</span>
3 months ago
</template>
<template v-if="readonlyMode">
<div v-if="defaultFileList.length > 0">
3 months ago
<Upload
:defaultFileList="defaultFileList"
:maxUploadSize="5"
:maxSize="10"
:multiple="true"
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.ppt,.pptx,.PNG,.JPG,.GIF"
:folder="FILE_FOLDER_TYPE_ENUM.COMMON.value"
buttonText=""
listType="text"
:showUploadBtn="false"
readonly
/>
3 months ago
</div>
3 months ago
<div v-else>无证明材料 (defaultFileList长度: {{ defaultFileList.length }})</div>
3 months ago
</template>
3 months ago
4 months ago
<Upload
3 months ago
v-else
4 months ago
:defaultFileList="defaultFileList"
3 months ago
:maxUploadSize="5"
:maxSize="10"
4 months ago
:multiple="true"
3 months ago
accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.ppt,.pptx,.PNG,.JPG,.GIF"
4 months ago
:folder="FILE_FOLDER_TYPE_ENUM.COMMON.value"
buttonText="上传证明材料"
listType="text"
3 months ago
extraMsg="活动方案、活动记录、照片、新闻报道等,支持图片(JPG/PNG)、文档(PDF/Word/PPT)格式,单文件最大10MB,最多上传5个文件"
4 months ago
@change="changeAttachment"
/>
</a-form-item>
</a-form>
<template #footer>
<a-space>
<a-button @click="onClose">取消</a-button>
3 months ago
<a-button v-if="!readonlyMode" type="primary" @click="onSave">保存</a-button>
<a-button v-if="!readonlyMode" type="primary" @click="onSubmit">提交</a-button>
4 months ago
</a-space>
</template>
</a-modal>
</template>
<script setup>
import { reactive, ref, nextTick, watch, onMounted } from 'vue';
import _ from 'lodash';
import { message } from 'ant-design-vue';
import dayjs from 'dayjs';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
import { smartSentry } from '/@/lib/smart-sentry';
import SmartWangeditor from '/@/components/framework/wangeditor/index.vue';
import Upload from '/@/components/support/file-upload/index.vue';
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';
import { fileApi } from '/@/api/support/file-api';
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
import EmployeeSelect from '/@/components/system/employee-select/index.vue';
import { loginApi } from '/@/api/system/login-api';
import { useUserStore } from '/@/store/modules/system/user';
import CategoryTree from '/@/components/business/category-tree-select/index.vue';
import { CATEGORY_TYPE_ENUM } from '/@/constants/business/erp/category-const';
import { goodsApi } from '/@/api/business/goods/goods-api';
3 months ago
import PositionSelect from '/@/components/system/position-select/index.vue';
4 months ago
// ------------------------ 事件 ------------------------
const emits = defineEmits(['reloadList']);
// ------------------------ 显示与隐藏 ------------------------
// 是否显示
const visibleFlag = ref(false);
3 months ago
// 只读模式(详情查看模式)
const readonlyMode = ref(false);
4 months ago
// 当前用户信息
let currentUserInfo = ref(null);
// 获取当前用户信息
async function getCurrentUserInfo() {
try {
const res = await loginApi.getLoginInfo();
currentUserInfo.value = res.data;
// 返回用户信息,不直接修改表单
return res.data;
} catch (error) {
smartSentry.captureError(error);
return null;
}
}
// 活动列表数据
const activityList = ref([]);
// 活动类型变化处理
async function onActivityCategoryChange(categoryId) {
// 清空活动名称
form.activityNameId = undefined;
if (!categoryId) {
activityList.value = [];
return;
}
try {
// 根据分类ID查询活动列表
const queryParams = {
categoryId: categoryId,
shelvesFlag: true, // 只查询上架的活动
pageNum: 1,
pageSize: 100, // 获取足够多的数据
};
const result = await goodsApi.queryGoodsList(queryParams);
if (result.data && result.data.list) {
// 将活动列表转换为下拉选项格式
activityList.value = result.data.list.map(item => ({
label: item.goodsName,
value: item.goodsId, // 传递活动ID而不是名称
goodsId: item.goodsId,
}));
} else {
activityList.value = [];
}
} catch (e) {
smartSentry.captureError(e);
activityList.value = [];
message.error('获取活动列表失败');
}
}
// 活动名称搜索过滤
function filterActivityOption(input, option) {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}
3 months ago
async function show(rowData, isDetail = false) {
4 months ago
Object.assign(form, formDefault);
3 months ago
// 设置只读模式
readonlyMode.value = isDetail;
4 months ago
// 先获取当前用户信息
const userInfo = await getCurrentUserInfo();
if (rowData && !_.isEmpty(rowData)) {
3 months ago
// 编辑模式或详情模式:合并用户信息和现有数据
4 months ago
Object.assign(form, rowData);
// 确保用户信息不被覆盖
if (userInfo) {
form.userId = userInfo.employeeId;
form.actualName = userInfo.actualName || form.actualName;
form.certificateNumber = userInfo.licenseNumber || form.certificateNumber;
form.firmId = userInfo.departmentId || form.firmId;
form.departmentName = userInfo.departmentName || form.departmentName;
}
3 months ago
// 编辑模式或详情模式下,如果有attachmentIds,则获取文件列表
4 months ago
if (form.attachmentIds && form.attachmentIds.trim()) {
await getFileListByAttachmentIds(form.attachmentIds);
}
} else {
// 新增模式:自动填充当前用户信息
if (userInfo) {
form.userId = userInfo.employeeId;
form.actualName = userInfo.actualName || '';
form.certificateNumber = userInfo.licenseNumber || '';
form.firmId = userInfo.departmentId;
form.departmentName = userInfo.departmentName || '';
}
// 新增模式下清空文件列表
defaultFileList.value = [];
form.proofMaterials = [];
form.attachmentIds = '';
}
// 使用字典时把下面这注释修改成自己的字典字段 有多个字典字段就复制多份同理修改 不然打开表单时不显示字典初始值
// if (form.status && form.status.length > 0) {
// form.status = form.status.map((e) => e.valueCode);
// }
visibleFlag.value = true;
nextTick(() => {
formRef.value.clearValidate();
});
}
// 重置表单
function resetForm() {
Object.assign(form, formDefault);
nextTick(() => {
formRef.value.clearValidate();
});
}
// 根据attachmentIds获取文件列表
async function getFileListByAttachmentIds(attachmentIds) {
try {
if (!attachmentIds || !attachmentIds.trim()) {
defaultFileList.value = [];
return;
}
// 直接传递attachmentIds字符串,后端接口接收字符串参数
const result = await fileApi.getFileList(attachmentIds);
if (result.data && result.data.length > 0) {
3 months ago
// 将文件信息转换为Upload组件需要的格式,并清理URL
defaultFileList.value = result.data.map(file => {
const cleanUrl = file.fileUrl ? file.fileUrl.replace(/[`\s]/g, '') : '';
return {
fileId: file.fileId,
fileName: file.fileName, // 显示文件名
fileUrl: cleanUrl,
fileType: file.fileType,
fileKey: file.fileKey, // 用于文件下载
uid: file.fileId,
name: file.fileName,
status: 'done',
url: cleanUrl
};
});
4 months ago
// 同时更新表单中的proofMaterials字段
form.proofMaterials = result.data.map(file => ({
fileName: file.fileName,
3 months ago
fileUrl: file.fileUrl ? file.fileUrl.replace(/[`\s]/g, '') : '',
4 months ago
fileType: file.fileType
}));
3 months ago
console.log('文件列表已清理URL:', defaultFileList.value);
4 months ago
} else {
defaultFileList.value = [];
form.proofMaterials = [];
}
} catch (error) {
smartSentry.captureError(error);
defaultFileList.value = [];
form.proofMaterials = [];
message.error('获取文件列表失败');
}
}
// 处理附件上传
function changeAttachment(fileList) {
// 将文件信息保存到表单中
form.proofMaterials = fileList.map(file => {
return {
fileName: file.name,
fileUrl: file.url || file.response?.data?.fileUrl,
fileType: file.type
};
});
// 提取fileId并用逗号隔开
const fileIds = fileList.map(file => {
// 从文件对象中获取fileId,优先从response中获取
return file.response?.data?.fileId || file.fileId || '';
}).filter(id => id); // 过滤掉空值
form.attachmentIds = fileIds.join(',');
3 months ago
// 手动触发表单验证,确保验证及时更新
nextTick(() => {
if (formRef.value) {
formRef.value.validateFields(['proofMaterials']);
}
});
4 months ago
}
function onClose() {
Object.assign(form, formDefault);
// 清空附件数据
defaultFileList.value = [];
form.proofMaterials = [];
form.attachmentIds = '';
3 months ago
// 重置只读模式
readonlyMode.value = false;
4 months ago
visibleFlag.value = false;
}
// ------------------------ 表单 ------------------------
// 组件ref
const formRef = ref();
const serviceContentRef = ref();
// 证明材料上传
const defaultFileList = ref([]);
const formDefault = {
applicationId: undefined, //申报ID
userId: undefined, //姓名ID
actualName: undefined, //员工姓名
certificateNumber: undefined,
firmId: undefined, //执业机构ID
departmentName: undefined, //部门名称
serviceStart: undefined, //服务开始时间
serviceEnd: undefined, //服务结束时间
serviceDuration: undefined, //服务时长(小时)
activityCategoryId: undefined, //活动类型
activityNameId: undefined, //活动名称
3 months ago
beneficiaryCount: undefined, //参加人数(受益人数)
4 months ago
organizerName: undefined, //组织单位名称
organizerContact: undefined, //负责人姓名
organizerPhone: undefined, //联系方式
serviceContent: undefined, //服务内容描述
proofMaterials: undefined, //证明材料
attachmentIds: undefined, //附件ID列表(逗号隔开)
firmAuditStatus: undefined, //执业机构审核状态:0-待审核,1-通过,2-退回
recordStatus: undefined, //备案状态:0-未备案,1-已备案
updateTime: undefined, //更新时间
createTime: undefined, //创建时间
};
let form = reactive({ ...formDefault });
// 监听服务开始时间和结束时间变化,自动计算服务时长
watch(
() => [form.serviceStart, form.serviceEnd],
([startTime, endTime]) => {
if (startTime && endTime) {
const start = dayjs(startTime);
const end = dayjs(endTime);
if (end.isAfter(start)) {
// 计算时间差(分钟),然后转换为小时(保留2位小数)
const durationMinutes = end.diff(start, 'minute');
form.serviceDuration = parseFloat((durationMinutes / 60).toFixed(2));
} else {
form.serviceDuration = undefined;
message.warning('服务结束时间必须晚于开始时间');
}
} else {
form.serviceDuration = undefined;
}
},
{ immediate: true }
);
const rules = {
actualName: [{ required: true, message: '姓名 必填' }],
certificateNumber: [{ required: true, message: '执业证号 必填' }],
firmId: [{ required: true, message: '执业机构 必填' }],
serviceStart: [{ required: true, message: '服务开始时间 必填' }],
serviceEnd: [{ required: true, message: '服务结束时间 必填' }],
serviceDuration: [{ required: true, message: '服务时长(小时) 必填' }],
activityCategoryId: [{ required: true, message: '活动类型 必填' }],
activityNameId: [{ required: true, message: '活动名称 必填' }],
3 months ago
beneficiaryCount: [{ required: true, message: '参加人数(受益人数) 必填' }],
4 months ago
organizerName: [{ required: true, message: '组织单位名称 必填' }],
organizerContact: [{ required: true, message: '负责人姓名 必填' }],
organizerPhone: [
{ required: true, message: '联系方式 必填' },
{
pattern: /^(1[3-9]\d{9}|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/,
message: '请输入正确的手机号或邮箱格式'
}
],
serviceContent: [{ required: true, message: '服务内容描述 必填' }],
3 months ago
proofMaterials: [
{
validator: async (rule, value) => {
// 检查是否有上传的文件
if (!form.proofMaterials || form.proofMaterials.length === 0) {
return Promise.reject('请上传证明材料');
}
return Promise.resolve();
}
}
],
4 months ago
};
4 months ago
// 点击保存,验证表单并保存
async function onSave() {
4 months ago
try {
// 验证富文本编辑器内容
const editorContent = serviceContentRef.value?.getHtml() || '';
const cleanContent = editorContent.replace(/<[^>]*>/g, '').trim();
if (!cleanContent) {
message.error('服务内容描述 必填');
return;
}
await formRef.value.validateFields();
save();
} catch (err) {
message.error('参数验证错误,请仔细填写表单数据!');
}
}
4 months ago
// 点击提交,验证表单并提交
async function onSubmit() {
try {
// 验证富文本编辑器内容
const editorContent = serviceContentRef.value?.getHtml() || '';
const cleanContent = editorContent.replace(/<[^>]*>/g, '').trim();
if (!cleanContent) {
message.error('服务内容描述 必填');
return;
}
await formRef.value.validateFields();
submit();
} catch (err) {
message.error('参数验证错误,请仔细填写表单数据!');
}
}
// 新建、编辑保存API
4 months ago
async function save() {
SmartLoading.show();
try {
// 准备提交数据
const submitData = { ...form };
// 确保attachmentIds字段存在且为字符串格式
if (!submitData.attachmentIds) {
submitData.attachmentIds = '';
}
if (form.applicationId) {
4 months ago
// 编辑保存:调用update接口
4 months ago
await serviceApplicationsApi.update(submitData);
} else {
4 months ago
// 新增保存:调用add接口
4 months ago
await serviceApplicationsApi.add(submitData);
}
4 months ago
message.success('保存成功');
emits('reloadList');
onClose();
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
}
}
// 新建、编辑提交API
async function submit() {
SmartLoading.show();
try {
// 准备提交数据
const submitData = { ...form };
// 确保attachmentIds字段存在且为字符串格式
if (!submitData.attachmentIds) {
submitData.attachmentIds = '';
}
if (form.applicationId) {
// 编辑提交:调用submit接口,传递applicationId
await serviceApplicationsApi.submit(form.applicationId);
} else {
// 新增提交:调用addSubmit接口,传递完整数据
await serviceApplicationsApi.addSubmit(submitData);
}
message.success('提交成功');
4 months ago
emits('reloadList');
onClose();
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
}
}
defineExpose({
show,
});
3 months ago
// 获取文件URL
function getFileUrl(file) {
const url = file.fileUrl;
return url;
}
// 获取文件名
function getFileName(file) {
return file.fileName;
}
4 months ago
</script>