Browse Source

feat:优化

master
wang 2 weeks ago
parent
commit
da2fe9ec41
  1. 7
      src/api/business/cost/firm-reports-api.js
  2. 24
      src/api/business/penalty-apply/penalty-apply-api.js
  3. 7
      src/api/business/service-applications/service-applications-api.js
  4. 7
      src/api/system/login-api.js
  5. BIN
      src/assets/images/login/login-min.gif
  6. BIN
      src/assets/images/login/login.gif
  7. BIN
      src/assets/images/login/loginnew.jpeg
  8. BIN
      src/assets/images/login/loginold.gif
  9. BIN
      src/assets/images/login/mi1n11.gif
  10. 28
      src/components/support/file-upload/index.vue
  11. 30
      src/components/system/service-count/excel-statistics-detail.vue
  12. 2
      src/layout/components/side-expand-menu/sub-menu.vue
  13. 14
      src/layout/components/side-menu/recursion-menu.vue
  14. 2
      src/layout/components/side-menu/sub-menu.vue
  15. 2
      src/layout/components/smart-footer/index.vue
  16. 2
      src/layout/components/top-expand-menu/sub-menu.vue
  17. 2
      src/layout/components/top-menu/sub-menu.vue
  18. 13
      src/views/business/erp/cost/firm-reports-form.vue
  19. 5
      src/views/business/erp/goods/components/goods-form-modal.vue
  20. 18
      src/views/business/erp/goods/goods-list.vue
  21. 72
      src/views/business/erp/penalty-apply/penalty-apply-form.vue
  22. 1351
      src/views/business/erp/penalty-apply/penalty-apply-list.vue
  23. 41
      src/views/business/erp/service/firm-statistics-detail.vue
  24. 1118
      src/views/business/erp/service/service-applications-form.vue
  25. 121
      src/views/business/erp/service/service-applications-list.vue
  26. 27
      src/views/business/erp/service/service-applications-report-list.vue
  27. 2311
      src/views/mobile/service/create.vue
  28. 34
      src/views/mobile/service/detail.vue
  29. 8
      src/views/system/account/components/message/index.vue
  30. 13
      src/views/system/employee/components/employee-form-modal/index.vue
  31. 21
      src/views/system/login/login.vue
  32. 4
      src/views/system/login3/login.vue

7
src/api/business/cost/firm-reports-api.js

@ -59,6 +59,13 @@ export const firmReportsApi = {
return getRequest('/firmReports/query');
},
queryByQuarter: (year, quarter) => {
return getRequest(`/firmReports/queryByQuarter?year=${year}&quarter=${quarter}`);
},
firmHasCostPermission: () => {
return getRequest('/firmReports/firmHasCostPermission');
},
/**
* 查询本年度已经提交的收入 @author wzh
*/

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

@ -1,5 +1,5 @@
/**
* 无处罚证明申请主表 api 封装
* 无处分证明申请主表 api 封装
*
* @Author: wzh
* @Date: 2026-01-07 21:36:44
@ -9,11 +9,10 @@ import axios from 'axios';
import { postRequest, getRequest, getDownload } from '/@/lib/axios';
export const penaltyApplyApi = {
/**
* 分页查询 @author wzh
*/
queryPage : (param) => {
queryPage: (param) => {
return postRequest('/penaltyApply/queryPage', param);
},
@ -21,48 +20,45 @@ export const penaltyApplyApi = {
* 增加 @author wzh
*/
add: (param) => {
return postRequest('/penaltyApply/add', param);
return postRequest('/penaltyApply/add', param);
},
/**
* 修改 @author wzh
*/
update: (param) => {
return postRequest('/penaltyApply/update', param);
return postRequest('/penaltyApply/update', param);
},
/**
* 删除 @author wzh
*/
delete: (id) => {
return getRequest(`/penaltyApply/delete/${id}`);
return getRequest(`/penaltyApply/delete/${id}`);
},
/**
* 批量删除 @author wzh
*/
batchDelete: (idList) => {
return postRequest('/penaltyApply/batchDelete', idList);
return postRequest('/penaltyApply/batchDelete', idList);
},
/**
* 无处罚证明下载 @author wzh
* 无处分证明下载 @author wzh
*/
exportWordCertificate: (id) => {
return getDownload(`/wordCertificate/export/${id}`);
return getDownload(`/wordCertificate/export/${id}`);
},
/**
* 预览无处罚证明 @author wzh
* 预览无处分证明 @author wzh
*/
/**
* 审核 @author wzh
*/
review: (param) => {
return postRequest('/penaltyApply/review', param);
return postRequest('/penaltyApply/review', param);
},
};

7
src/api/business/service-applications/service-applications-api.js

@ -205,4 +205,11 @@ export const serviceApplicationsApi = {
exportFirmActivityStatistics: (params) => {
return postDownload('/serviceApplications/export/firmActivityCount', params);
},
/**
* 获取同律所已签署承诺书的合格同行律师列表
*/
querySameFirmLawyers: () => {
return getRequest('/serviceApplications/querySameFirmLawyers');
},
};

7
src/api/system/login-api.js

@ -51,4 +51,11 @@ export const loginApi = {
getTwoFactorLoginFlag: () => {
return getRequest('/login/getTwoFactorLoginFlag');
},
/**
* 获取初始密码提示
*/
getInitialPassword: () => {
return getRequest('/login/getInitialPassword');
},
};

BIN
src/assets/images/login/login-min.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
src/assets/images/login/login.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
src/assets/images/login/loginnew.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

BIN
src/assets/images/login/loginold.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

BIN
src/assets/images/login/mi1n11.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

28
src/components/support/file-upload/index.vue

@ -240,19 +240,6 @@
// 图片类型的后缀名
const imgFileType = ['jpg', 'jpeg', 'png', 'gif','.PNG','.GIF','.JPG'];
// 重新修改图片展示字段
const files = computed(() => {
let res = [];
if (props.defaultFileList && props.defaultFileList.length > 0) {
props.defaultFileList.forEach((element) => {
element.url = element.fileUrl;
element.name = element.fileName;
res.push(element);
});
return res;
}
return res;
});
// -------------------- 逻辑 --------------------
const previewVisible = ref(false);
@ -292,14 +279,19 @@
return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(previewFileType.value.toLowerCase());
});
// 弹窗复用、异步查询附件时都重新构造列表;不能直接修改 props 中的文件对象。
watch(
files,
() => props.defaultFileList,
(value) => {
fileList.value = value;
fileList.value = (value || []).map((element) => ({
...element,
uid: element.uid || element.fileId,
url: element.url || element.fileUrl,
name: element.name || element.fileName,
status: element.status || 'done',
}));
},
{
immediate: true,
}
{ immediate: true, deep: true }
);
const emit = defineEmits(['update:value', 'change']);

30
src/components/system/service-count/excel-statistics-detail.vue

@ -114,6 +114,7 @@ import { ref, reactive, computed, onMounted } from 'vue';
import { ExportOutlined, SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
import { getRoleInfo } from '/@/utils/role-util';
// 接收父组件传递的参数
const props = defineProps({
@ -338,6 +339,35 @@ async function handleExport() {
}
}
// 律所汇总导出(仅律协角色可见)
async function handleExportDepartment() {
if (tableData.value.length === 0) {
message.warning('暂无数据可导出');
return;
}
exportDepartmentLoading.value = true;
try {
console.log('开始导出律所汇总统计...');
const exportParams = {
quarter: localQueryForm.quarter,
year: localQueryForm.year,
lawyerName: localQueryForm.lawyerName,
firmName: localQueryForm.firmName
};
// 调用律所汇总导出接口
await serviceApplicationsApi.exportLawyerByDepartment(exportParams);
message.success('导出成功');
console.log('律所汇总统计导出成功');
} catch (error) {
message.error('导出失败');
console.error('导出律所汇总统计失败:', error);
} finally {
exportDepartmentLoading.value = false;
}
}
onMounted(() => {
initYearOptions();
// 如果没有传入数据,则自动查询

2
src/layout/components/side-expand-menu/sub-menu.vue

@ -15,7 +15,7 @@
<template #title>{{ props.menuInfo.menuName }}</template>
<template v-for="item in props.menuInfo.children" :key="item.menuId">
<template v-if="item.visibleFlag">
<template v-if="!item.children">
<template v-if="!item.children || item.children.length === 0">
<a-menu-item :key="item.menuId.toString()" @click="turnToPage(item)">
<template #icon>
<component :is="$antIcons[item.icon]" />

14
src/layout/components/side-menu/recursion-menu.vue

@ -65,7 +65,10 @@
*/
function updateOpenKeysAndSelectKeys() {
// 更新选中
selectedKeys.value = [_.toNumber(currentRoute.name)];
const nextSelectedKeys = [_.toNumber(currentRoute.name)];
if (!_.isEqual(selectedKeys.value, nextSelectedKeys)) {
selectedKeys.value = nextSelectedKeys;
}
/**
* 更新展开(1、获取新展开的menu key集合;2、保留原有的openkeys,然后把新展开的与之合并)
@ -81,10 +84,15 @@
let needOpenKeys = _.map(parentList, 'name').map(Number);
if (menuSingleExpandFlag.value) {
openKeys.value = [...needOpenKeys];
if (!_.isEqual(openKeys.value, needOpenKeys)) {
openKeys.value = [...needOpenKeys];
}
} else {
// 使用lodash的union函数,进行 去重合并两个数组
openKeys.value = _.union(openKeys.value, needOpenKeys);
const nextOpenKeys = _.union(openKeys.value, needOpenKeys);
if (!_.isEqual(openKeys.value, nextOpenKeys)) {
openKeys.value = nextOpenKeys;
}
}
}

2
src/layout/components/side-menu/sub-menu.vue

@ -10,7 +10,7 @@
<template #title>{{ menuInfo.menuName }}</template>
<template v-for="item in menuInfo.children" :key="item.menuId">
<template v-if="item.visibleFlag && !item.disabledFlag">
<template v-if="!item.children">
<template v-if="!item.children || item.children.length === 0">
<a-menu-item :key="item.menuId" @click="turnToPage(item)">
<template #icon>
<component :is="$antIcons[item.icon]" />

2
src/layout/components/smart-footer/index.vue

@ -3,7 +3,7 @@
-->
<template>
<div class="version">
<a target="_blank" class="smart-copyright">{{ currentYear }}@ 安徽三朵云信息科技有限公司</a><a style="margin-left:20px">服务经理:王经理</a><a style="margin-left:10px">联系方式:18226700205</a>
<a target="_blank" class="smart-copyright">{{ currentYear }}@ 安徽三朵云信息科技有限公司</a><a style="margin-left:20px">技术人员:</a><a style="margin-left:10px">联系方式:18755103557</a>
<a style="margin-left:20px">应急联系人:耿经理</a><a style="margin-left:10px">应急电话:15255503352</a>
</div>
</template>

2
src/layout/components/top-expand-menu/sub-menu.vue

@ -15,7 +15,7 @@
<template #title>{{ props.menuInfo.menuName }}</template>
<template v-for="item in props.menuInfo.children" :key="item.menuId">
<template v-if="item.visibleFlag">
<template v-if="!item.children">
<template v-if="!item.children || item.children.length === 0">
<a-menu-item :key="item.menuId.toString()" @click="turnToPage(item)">
<template #icon>
<component :is="$antIcons[item.icon]" />

2
src/layout/components/top-menu/sub-menu.vue

@ -15,7 +15,7 @@
<template #title>{{ menuInfo.menuName }}</template>
<template v-for="item in menuInfo.children" :key="item.menuId">
<template v-if="item.visibleFlag && !item.disabledFlag">
<template v-if="!item.children">
<template v-if="!item.children || item.children.length === 0">
<a-menu-item :key="item.menuId" @click="turnToPage(item)">
<template #icon>
<component :is="$antIcons[item.icon]" />

13
src/views/business/erp/cost/firm-reports-form.vue

@ -187,7 +187,16 @@
const quarterOptions = ref([]);
// 初始化季度选项,只能选择上一个季度
async function initQuarterOptions() {
async function initQuarterOptions(presetPeriod) {
if (presetPeriod?.year && presetPeriod?.quarter) {
form.declareYear = Number(presetPeriod.year);
form.declareQuarter = Number(presetPeriod.quarter);
quarterOptions.value = [
{ label: `第${form.declareQuarter}季度`, value: form.declareQuarter }
];
return;
}
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1;
@ -292,7 +301,7 @@
await fetchAnnualActualCost();
await getPublicWelfareCost();
} else {
await initQuarterOptions();
await initQuarterOptions(rowData);
await fetchAnnualIncome();
await fetchAnnualActualCost();
await getPublicWelfareCost();

5
src/views/business/erp/goods/components/goods-form-modal.vue

@ -27,6 +27,10 @@
style="width: 100%"
/>
</a-form-item>
<a-form-item label="允许多人代报" name="canBatchSelect">
<a-switch v-model:checked="form.canBatchSelect" checked-children="允许" un-checked-children="禁止" />
<div style="color: #8c8c8c; font-size: 12px; margin-top: 4px;">开启后,申报该活动时可勾选同律所同行一并申报。</div>
</a-form-item>
<a-form-item label="备注" name="remark">
<a-input style="width: 100%" placeholder="请输入备注" v-model:value="form.remark" />
</a-form-item>
@ -81,6 +85,7 @@
shelvesFlag: true,
//计算类型
timeType: undefined,
canBatchSelect: false,
//备注
remark: '',
//活动id

18
src/views/business/erp/goods/goods-list.vue

@ -229,14 +229,10 @@
sorter: true,
width: 250,
ellipsis: true,
customRender: ({ text }) => {
return {
children: text,
attrs: {
title: text
}
};
}
customRender: ({ text }) => text,
customCell: (record) => ({
title: record.categoryName,
}),
},
{
title: '活动名称',
@ -275,6 +271,12 @@
width: 100,
customRender: ({ text }) => h(DictLabel, { dictCode: 'COMPUTATIONTYPE', dataValue: text }),
},
{
title: '多人代报',
dataIndex: 'canBatchSelect',
width: 100,
customRender: ({ text }) => text ? '允许' : '禁止',
},
{
title: '创建时间',
dataIndex: 'createTime',

72
src/views/business/erp/penalty-apply/penalty-apply-form.vue

@ -1,5 +1,5 @@
<!--
* 无处罚证明申请主表
* 无处分证明申请主表
*
* @Author: wzh
* @Date: 2026-01-07 21:36:44
@ -7,24 +7,24 @@
-->
<template>
<a-modal
:title="form.id ? '编辑无处罚证明申请' : '新增无处罚证明申请'"
:width="500"
:open="visibleFlag"
@cancel="onClose"
:maskClosable="false"
:destroyOnClose="true"
:title="form.id ? '编辑无处分证明申请' : '新增无处分证明申请'"
:width="500"
:open="visibleFlag"
@cancel="onClose"
:maskClosable="false"
:destroyOnClose="true"
>
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 6 }" >
<!-- 只有律所主任显示申请类型选择(个人/律所),行政人员只申请律所证明 -->
<a-form-item v-if="showApplyTypeSelect" label="申请类型" name="applyType">
<a-radio-group v-model:value="form.applyType" @change="onApplyTypeChange">
<a-radio value="PERSONAL">个人证明</a-radio>
<a-radio value="FIRM">律所证明</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="申请事由" name="usePurpose">
<a-textarea v-model:value="form.usePurpose" placeholder="请输入申请事由" :rows="4" />
</a-form-item>
<a-form ref="formRef" :model="form" :rules="rules" :label-col="{ span: 6 }">
<!-- 只有律所主任显示申请类型选择(个人/律所),行政人员只申请律所证明 -->
<a-form-item v-if="showApplyTypeSelect" label="申请类型" name="applyType">
<a-radio-group v-model:value="form.applyType" @change="onApplyTypeChange">
<a-radio value="PERSONAL">个人证明</a-radio>
<a-radio value="FIRM">律所证明</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="申请事由" name="usePurpose">
<a-textarea v-model:value="form.usePurpose" placeholder="请输入申请事由" :rows="4" />
</a-form-item>
</a-form>
<template #footer>
@ -55,13 +55,13 @@
// ------------------------ 用户信息 ------------------------
const userStore = useUserStore();
// 当前用户信息
const currentUser = computed(() => ({
userId: userStore.userId,
userName: userStore.actualName,
departmentName: userStore.departmentName,
roleCodeList: userStore.roleCodeList || []
roleCodeList: userStore.roleCodeList || [],
}));
// 判断是否为律所主任(CTO角色)
@ -154,25 +154,25 @@
const formRef = ref();
const formDefault = {
id: undefined, //申请ID
userId: undefined, //申请人ID
userName: undefined, //申请人姓名
applyDate: undefined, //申请日期
usePurpose: undefined, //申请事由
proofMaterials: undefined, //证明材料
status: undefined, //状态(0-未提交,1-已提交,2-审核中,3-已批准,5-已驳回)
approvalRemark: undefined, //审批意见
createTime: undefined, //创建时间
updateTime: undefined, //更新时间
type: undefined, // 类型(兼容旧数据)
applyType: undefined, // 申请类型:PERSONAL-个人证明,FIRM-律所证明
id: undefined, //申请ID
userId: undefined, //申请人ID
userName: undefined, //申请人姓名
applyDate: undefined, //申请日期
usePurpose: undefined, //申请事由
proofMaterials: undefined, //证明材料
status: undefined, //状态(0-未提交,1-已提交,2-审核中,3-已批准,5-已驳回)
approvalRemark: undefined, //审批意见
createTime: undefined, //创建时间
updateTime: undefined, //更新时间
type: undefined, // 类型(兼容旧数据)
applyType: undefined, // 申请类型:PERSONAL-个人证明,FIRM-律所证明
};
let form = reactive({ ...formDefault });
const rules = {
usePurpose: [{ required: true, message: '申请事由 必填' }],
applyType: [{ required: true, message: '申请类型 必填' }],
usePurpose: [{ required: true, message: '申请事由 必填' }],
applyType: [{ required: true, message: '申请类型 必填' }],
};
// ------------------------ 提交申请 ------------------------
@ -181,11 +181,11 @@
try {
await formRef.value.validateFields();
SmartLoading.show();
// 提交申请,状态为1(已提交)
const submitData = { ...form };
submitData.status = 1;
if (submitData.id) {
await penaltyApplyApi.update(submitData);
message.success('提交申请成功');

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

File diff suppressed because it is too large

41
src/views/business/erp/service/firm-statistics-detail.vue

@ -30,7 +30,11 @@
<a-form-item style="margin-left: auto;">
<a-button type="primary" @click="handleExport" :loading="exportLoading">
<ExportOutlined />
导出
律师明细导出
</a-button>
<a-button v-if="isAssociationRole" type="primary" @click="handleExportDepartment" :loading="exportDepartmentLoading" style="margin-left: 8px">
<ExportOutlined />
律所汇总导出
</a-button>
</a-form-item>
</a-form>
@ -144,6 +148,8 @@ import { ExportOutlined, ArrowLeftOutlined, DownOutlined, RightOutlined, SearchO
import { message } from 'ant-design-vue';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
import { getRoleInfo } from '/@/utils/role-util';
import { useUserStore } from '/@/store/modules/system/user';
// 展开/折叠状态管理
const expandedRows = ref(new Set());
@ -177,9 +183,15 @@ const props = defineProps({
});
const exportLoading = ref(false);
const exportDepartmentLoading = ref(false);
const tableData = ref([]);
const loading = ref(false);
// 角色判断
const userStore = useUserStore();
const roleInfo = getRoleInfo(userStore.roleCode);
const isAssociationRole = roleInfo.isAssociationRole;
// 分页配置
const pagination = reactive({
pageNum: 1,
@ -381,6 +393,33 @@ async function handleExport() {
}
}
// 律所汇总导出(仅律协角色可见)
async function handleExportDepartment() {
if (!tableData.value || tableData.value.length === 0) {
message.warning('暂无数据可导出');
return;
}
exportDepartmentLoading.value = true;
try {
console.log('开始导出律所汇总统计...');
const exportParams = {
quarter: queryForm.quarter,
year: queryForm.year,
firmId: queryForm.firmId
};
await serviceApplicationsApi.exportLawyerByDepartment(exportParams);
message.success('导出成功');
console.log('律所汇总统计导出成功');
} catch (error) {
message.error('导出失败');
console.error('导出律所汇总统计失败:', error);
} finally {
exportDepartmentLoading.value = false;
}
}
// 监听参数变化,重新获取数据
watch(() => props.params, (newParams) => {
if (newParams) {

1118
src/views/business/erp/service/service-applications-form.vue

File diff suppressed because it is too large

121
src/views/business/erp/service/service-applications-list.vue

@ -63,7 +63,7 @@
<a-range-picker style="width: 240px" v-model:value="queryForm.firmAuditTimeRange" :placeholder="['开始时间', '结束时间']" />
</a-form-item>
<a-form-item label="备案编号" class="smart-query-form-item">
<a-input style="width: 160px" v-model:value="queryForm.filingNo" placeholder="请输入备案编号" />
<a-input style="width: 160px" v-model:value="queryForm.recordNo" placeholder="请输入备案编号" />
</a-form-item>
<a-form-item class="smart-query-form-item smart-margin-left10">
<a-button v-privilege="'serviceApplications:query'" type="primary" @click="onSearch">
@ -172,11 +172,21 @@
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
>
<template #bodyCell="{ record, column }">
<template v-if="column.dataIndex === 'userName'">
<span>{{ record.userName }}</span>
<a-tag v-if="record.isBatch === 1 && record.parentApplicationId" color="orange" style="margin-left: 6px;">
由 {{ record.declarerName || '同行律师' }} 代报批
</a-tag>
<a-tag v-else-if="record.isBatch === 1 && !record.parentApplicationId" color="blue" style="margin-left: 6px;">
同行统一代报批 (包含: {{ record.participantNames || '多人' }})
</a-tag>
</template>
<template v-if="column.dataIndex === 'action'">
<div class="smart-table-operate">
<a-button @click="showDetail(record)" type="link">详情</a-button>
<!-- 只有未提交状态才能编辑,且只能编辑自己的记录 -->
<a-button v-if="record.firmAuditStatus === 0 && record.userId === loginInfo?.userId" @click="showEdit(record)" type="link">编辑</a-button>
<!-- 只有未提交状态才能编辑,且只能编辑自己的主记录 -->
<a-button v-if="record.firmAuditStatus === 0 && record.userId === loginInfo?.userId && (!record.declarerId || record.declarerId === loginInfo?.userId)" @click="showEdit(record)" type="link">编辑</a-button>
<a-button v-if="record.firmAuditStatus === 0 && record.userId === loginInfo?.userId && (!record.declarerId || record.declarerId === loginInfo?.userId)" @click="onSubmit(record)" type="link">提交</a-button>
<!-- 执业机构审核状态为拒绝时,只显示详情按钮 -->
<template v-if="record.firmAuditStatus !== 4">
<a-button v-if="canAuditRecord(record)" @click="showAuditModal(record)" type="link">审核</a-button>
@ -457,7 +467,7 @@ import dayjs from 'dayjs';
},
{
title: '备案编号',
dataIndex: 'filingNo',
dataIndex: 'recordNo',
ellipsis: true,
width: 150,
},
@ -483,7 +493,7 @@ import dayjs from 'dayjs';
firmAuditStatus: undefined, // 执业机构审核状态
firmAuditTimeRange: undefined, // 执业机构审核时间范围
associationAuditStatus: undefined, // 协会审核状态
filingNo: undefined, // 备案编号
recordNo: undefined, // 用户填写的备案/案件编号
selfFirmFilter: undefined, // 本人本所筛选条件
};
// 查询表单form
@ -679,31 +689,20 @@ import dayjs from 'dayjs';
return;
}
// 如果已签署律所承诺书,检查成本权限
// 成本权限按律所判断:本所任一有效人员具备权限即视为本所有权限
if (loginInfo.value && loginInfo.value.lawFirmAgreementSignFlag === true) {
// 检查是否有成本查询权限
if (loginInfo.value.costVisibleFlag === true) {
const permissionResult = await firmReportsApi.firmHasCostPermission();
if (permissionResult.data === true) {
try {
// 先检查是否有未审核的数据
const hasUnreviewedData = await checkUnreviewedData();
if (hasUnreviewedData) {
message.warning('有律师提交的数据尚未审核,请先完成审核后再进行批量上报。');
return;
}
const costPermissionResult = await firmReportsApi.query();
if (costPermissionResult.data === false) {
// 成本权限检查返回false,需要先填报成本报表
message.warning('您需要先填报和提交律所收入,才能进行服务上报。');
// 调用成本管理新增弹框
showCostReportForm();
return;
}
} catch (error) {
console.error('检查成本权限失败:', error);
}
}
// 如果没有成本查询权限,直接跳过成本检查
}
// 检查选中的记录是否符合上报条件
@ -712,7 +711,14 @@ import dayjs from 'dayjs';
);
if (canReportRecords.length === 0) {
message.warning('选中的记录不符合上报条件,只能上报上个季度的数据');
message.warning('选中的记录不符合上报条件,请选择已通过律所审核且尚未上报的记录');
return;
}
const missingPeriods = await queryMissingIncomePeriods(canReportRecords);
if (missingPeriods.length > 0) {
message.warning(`以下活动所属季度尚未提交营业收入:${missingPeriods.map(item => item.label).join('、')},请完成填报后再上报律协`);
showCostReportForm(missingPeriods[0]);
return;
}
@ -733,60 +739,41 @@ import dayjs from 'dayjs';
const isAuditPassed = record.firmAuditStatus === 3 &&
(record.associationAuditStatus === 0 || record.associationAuditStatus === 4);
// 如果有成本查询权限,需要检查季度限制;否则不限制
const hasCostPermission = loginInfo.value && loginInfo.value.costVisibleFlag === true;
if (hasCostPermission) {
// 有成本权限:只能上报上个季度的数据
// 律协要求:二季度上报一季度的数据,三季度上报二季度的数据
const isLastQuarterData = isLastQuarterRecord(record);
return isAuditPassed && isLastQuarterData;
} else {
// 无成本权限:不限制季度,可以上报所有数据
return isAuditPassed;
}
// 活动季度及对应收入报表由后端按 serviceEnd 统一校验
return isAuditPassed;
}
// 判断记录是否为上个季度的数据
function isLastQuarterRecord(record) {
if (!record.reportTime) return false;
const currentDate = new Date();
const currentYear = currentDate.getFullYear();
const currentMonth = currentDate.getMonth() + 1;
const currentQuarter = Math.ceil(currentMonth / 3);
// 计算上个季度
let lastQuarter = currentQuarter - 1;
let lastQuarterYear = currentYear;
if (lastQuarter === 0) {
lastQuarter = 4;
lastQuarterYear = currentYear - 1;
async function queryMissingIncomePeriods(records) {
const periodMap = new Map();
records.forEach(record => {
if (!record.serviceEnd) return;
const activityDate = dayjs(record.serviceEnd);
const year = activityDate.year();
const quarter = Math.ceil((activityDate.month() + 1) / 3);
periodMap.set(`${year}-${quarter}`, { year, quarter });
});
const missing = [];
for (const period of periodMap.values()) {
const result = await firmReportsApi.queryByQuarter(period.year, period.quarter);
if (result.data !== true) {
missing.push({
...period,
label: `${period.year}年第${period.quarter}季度`,
});
}
}
// 解析记录的提交时间
const reportDate = new Date(record.reportTime);
const reportYear = reportDate.getFullYear();
const reportMonth = reportDate.getMonth() + 1;
const reportQuarter = Math.ceil(reportMonth / 3);
// 检查是否为上个季度的数据
return reportYear === lastQuarterYear && reportQuarter === lastQuarter;
return missing;
}
// 检查是否有未审核的数据
// 检查当前律所是否有未审核的数据
async function checkUnreviewedData() {
try {
// 调用后端提供的专门接口查询是否有未审核的数据
const result = await serviceApplicationsApi.queryNoReview();
// 如果返回的数据大于0,说明有未审核的记录
return result.data && result.data > 0;
} catch (error) {
console.error('检查未审核数据失败:', error);
return false; // 出错时默认返回false,避免阻塞用户操作
return false;
}
}
@ -1057,9 +1044,9 @@ import dayjs from 'dayjs';
}
// ---------------------------- 成本报表 ----------------------------
function showCostReportForm() {
function showCostReportForm(period) {
// 打开成本报表新增弹框
costReportFormRef.value.show();
costReportFormRef.value.show(period);
}
// ---------------------------- 详情 ----------------------------

27
src/views/business/erp/service/service-applications-report-list.vue

@ -253,6 +253,7 @@ import { useRoute, useRouter } from 'vue-router';
import { message, Modal } from 'ant-design-vue';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
import { firmReportsApi } from '/@/api/business/cost/firm-reports-api';
import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
import { smartSentry } from '/@/lib/smart-sentry';
import TableOperator from '/@/components/support/table-operator/index.vue';
@ -562,7 +563,7 @@ import { getRoleInfo } from '/@/utils/role-util';
}
// 批量上报
function batchReport() {
async function batchReport() {
if (selectedRowKeyList.value.length === 0) {
message.warning('请选择要上报的记录');
return;
@ -577,6 +578,26 @@ import { getRoleInfo } from '/@/utils/role-util';
message.warning('选中的记录不符合上报条件');
return;
}
const periods = new Map();
canReportRecords.forEach(record => {
if (!record.serviceEnd) return;
const date = dayjs(record.serviceEnd);
const year = date.year();
const quarter = Math.ceil((date.month() + 1) / 3);
periods.set(`${year}-${quarter}`, { year, quarter });
});
const missingPeriods = [];
for (const period of periods.values()) {
const result = await firmReportsApi.queryByQuarter(period.year, period.quarter);
if (result.data !== true) {
missingPeriods.push(`${period.year}年第${period.quarter}季度`);
}
}
if (missingPeriods.length > 0) {
message.warning(`以下活动所属季度尚未提交营业收入:${missingPeriods.join('、')},请完成填报后再上报律协`);
return;
}
// 确认上报
Modal.confirm({
@ -591,9 +612,9 @@ import { getRoleInfo } from '/@/utils/role-util';
// 判断记录是否符合上报条件
function canReportRecord(record) {
// 只能上报已通过执业机构审核的记录(状态为3:已通过)
// 且协会审核状态必须是未提交(0)
// 且协会审核状态必须是未提交(0)或被驳回(4)
return record.firmAuditStatus === 3 &&
record.associationAuditStatus === 0;
(record.associationAuditStatus === 0 || record.associationAuditStatus === 4);
}
// 执行批量上报

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

File diff suppressed because it is too large

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

@ -151,6 +151,16 @@
</select>
</template>
</div>
<!-- 多人代报信息 -->
<div class="form-item" v-if="readonlyMode && Number(form.isBatch) === 1 && !form.parentApplicationId">
<label class="form-label">同所同行人</label>
<input :value="peerParticipantNames" class="form-input" readonly />
</div>
<div class="form-item" v-if="readonlyMode && Number(form.isBatch) === 1 && form.parentApplicationId">
<label class="form-label">代报人</label>
<input :value="form.declarerName || '-'" class="form-input" readonly />
</div>
<!-- dict类型:显示服务时长、服务开始时间和服务结束时间 -->
<template v-if="activityType === 'DICT'">
@ -202,11 +212,11 @@
<template v-else-if="activityType === 'TIME'">
<!-- 案件编号 -->
<div class="form-item">
<label class="form-label">案件编号</label>
<label class="form-label">备案/案件编号</label>
<input
v-model="form.recordNo"
class="form-input"
placeholder="请输入案件编号"
placeholder="请输入备案/案件编号"
:readonly="readonlyMode"
/>
</div>
@ -227,7 +237,7 @@
</template>
<!-- amount类型:显示金额输入框 -->
<template v-else-if="activityType === 'AMOUT'">
<template v-else-if="['AMOUNT', 'AMOUT'].includes(activityType)">
<!-- 金额 -->
<div class="form-item">
<label class="form-label">金额</label>
@ -287,7 +297,7 @@
<input
v-model="form.organizerPhone"
class="form-input"
placeholder="联系方式"
placeholder="手机号、座机或邮箱(如 0571-12345678)"
:readonly="readonlyMode"
/>
</div>
@ -534,7 +544,7 @@ watch(
// 默认值
form.serviceDuration = 25;
}
} else if (selectedActivity.timeType === 'AMOUT') {
} else if (['AMOUNT', 'AMOUT'].includes(selectedActivity.timeType)) {
// 保留现有值,不设为undefined
} else if (selectedActivity.timeType === 'DICT') {
// DICT类型:清空服务时长,让用户通过选择时间或手动填写
@ -888,7 +898,7 @@ async function handleSave() {
return
}
}
} else if (currentActivityType.value === 'AMOUT') {
} else if (['AMOUNT', 'AMOUT'].includes(currentActivityType.value)) {
// AMOUNT类型:验证金额和服务时长范围
if (!form.workloadScore) {
message.error('请填写金额')
@ -992,7 +1002,7 @@ async function handleSubmit() {
return
}
}
} else if (currentActivityType.value === 'AMOUT') {
} else if (['AMOUNT', 'AMOUT'].includes(currentActivityType.value)) {
// AMOUNT类型:验证金额和服务时长范围
if (!form.workloadScore) {
message.error('请填写金额')
@ -1117,6 +1127,16 @@ function getActivityName() {
onMounted(() => {
getDetail()
})
// 兼容历史数据:旧记录的 participantNames 可能包含申报人本人。
const peerParticipantNames = computed(() => {
const selfName = String(form.actualName || '').trim()
return String(form.participantNames || '')
.split('、')
.map((name) => name.trim())
.filter((name) => name && name !== selfName)
.join('、') || '-'
})
</script>
<style scoped>

8
src/views/system/account/components/message/index.vue

@ -74,7 +74,7 @@
v-model:current="queryForm.pageNum"
v-model:pageSize="queryForm.pageSize"
:total="total"
@change="ajaxQuery"
@change="handlePageChange"
:show-total="(total) => `共${total}条`"
/>
</div>
@ -143,6 +143,12 @@
ajaxQuery();
}
function handlePageChange(pageNum, pageSize) {
queryForm.pageNum = pageNum;
queryForm.pageSize = pageSize;
ajaxQuery();
}
// 查询
async function ajaxQuery() {
try {

13
src/views/system/employee/components/employee-form-modal/index.vue

@ -16,7 +16,6 @@
@close="onClose"
destroyOnClose
>
<br />
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-form-item label="姓名" name="actualName">
@ -50,13 +49,15 @@
<a-form-item label="角色" name="roleIdList">
<a-select v-model:value="form.roleIdList" optionFilterProp="title" placeholder="请选择角色" :mode="null">
<a-select-option v-for="item in roleList" :key="item.roleId" :value="item.roleId" :title="item.roleName">{{ item.roleName }}</a-select-option>
<a-select-option v-for="item in roleList" :key="item.roleId" :value="item.roleId" :title="item.roleName">{{
item.roleName
}}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="成本查看权限" name="costVisibleFlag">
<a-switch v-model:checked="form.costVisibleFlag" checked-children="有" un-checked-children="无" />
</a-form-item>
<a-form-item label="无处罚证明查看权限" name="penaltyFlag">
<a-form-item label="无处分证明查看权限" name="penaltyFlag">
<a-switch v-model:checked="form.penaltyFlag" checked-children="有" un-checked-children="无" />
</a-form-item>
</a-form>
@ -132,7 +133,7 @@
positionId: undefined,
email: undefined,
costVisibleFlag: false, //成本查看权限
penaltyFlag: false, //无处罚证明查看权限
penaltyFlag: false, //无处分证明查看权限
};
let form = reactive(_.cloneDeep(formDefault));
@ -188,10 +189,10 @@
message.error('参数验证错误,请仔细填写表单数据!');
return;
}
// 准备提交数据,确保roleIdList是单个Long值
const submitData = { ...form };
SmartLoading.show();
if (form.employeeId) {
await updateEmployee(submitData, keepAdding);

21
src/views/system/login/login.vue

@ -31,7 +31,7 @@
</a-form-item>
</a-form>
</div>
<div class="tech-contact">技术支持电话:18226700205</div>
<div class="tech-contact">技术支持电话:18755103557</div>
</div>
</template>
<script setup>
@ -79,25 +79,6 @@
onLogin();
}
};
notification['success']({
message: '温馨提示',
description: 'SmartAdmin 提供 9种 登录背景风格哦!',
duration: 8,
onClick: () => {},
btn: () =>
h(
Button,
{
type: 'primary',
target: '_blank',
size: 'small',
href: 'https://smartadmin.vip/views/doc/front/Login.html',
onClick: () => {},
},
{ default: () => '去看看' }
),
});
});
onUnmounted(() => {

4
src/views/system/login3/login.vue

@ -39,7 +39,7 @@
<div class="btn" @click="onLogin">登录</div>
</a-form-item>
</a-form>
<div class="tech-contact">技术支持联系电话:王经理 18226700205</div>
<div class="tech-contact">技术支持联系电话: 18755103557</div>
</div>
</div>
</div>
@ -53,7 +53,7 @@
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
import { useUserStore } from '/@/store/modules/system/user';
import loginQR from '/@/assets/images/login/login-qr.png';
import loginGif from '/@/assets/images/login/login-min.gif';
import loginGif from '/src/assets/images/login/loginnew.jpeg';
import { buildRoutes } from '/@/router/index';
import { smartSentry } from '/@/lib/smart-sentry';

Loading…
Cancel
Save