Browse Source

4-19日

master
wang 2 months ago
parent
commit
21f42fe09e
  1. 8
      src/components/system/service-count/excel-statistics-detail.vue
  2. 4
      src/constants/business/message/message-const.js
  3. 32
      src/layout/components/header-user-space/header-message-detail-modal.vue
  4. 3
      src/store/modules/system/user.js
  5. 64
      src/utils/role-util.js
  6. 196
      src/views/business/erp/cost/firm-reports-form.vue
  7. 44
      src/views/business/erp/cost/firm-reports-list.vue
  8. 15
      src/views/business/erp/letter/letter-list.vue
  9. 4
      src/views/business/erp/penalty-apply/penalty-apply-list.vue
  10. 8
      src/views/business/erp/service/firm-statistics-detail.vue
  11. 200
      src/views/business/erp/service/law-firm-service-report-statistics.vue
  12. 191
      src/views/business/erp/service/lawyer-service-report-statistics.vue
  13. 3
      src/views/business/erp/service/lawyer-statistics-detail.vue
  14. 200
      src/views/business/erp/service/service-applications-list.vue
  15. 43
      src/views/business/erp/service/service-applications-report-list.vue
  16. 67
      src/views/system/home/home-notice.vue

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

@ -63,10 +63,10 @@
<div class="report-cell">序号</div>
<div class="report-cell">律师姓名</div>
<div class="report-cell">执业证号</div>
<div class="report-cell">季度累计服务时长</div>
<div class="report-cell">季度累计服务成本</div>
<div class="report-cell">年度累计服务时长</div>
<div class="report-cell">年度累计服务成本</div>
<div class="report-cell">季度累计公益服务时长</div>
<div class="report-cell">季度累计公益服务成本</div>
<div class="report-cell">年度累计公益服务时长</div>
<div class="report-cell">年度累计公益服务成本</div>
</div>
<!-- 数据行 -->

4
src/constants/business/message/message-const.js

@ -14,6 +14,10 @@ export const MESSAGE_TYPE_ENUM = {
value: 2,
desc: '订单'
},
AUDIT: {
value: 3,
desc: '审核通知'
},
};

32
src/layout/components/header-user-space/header-message-detail-modal.vue

@ -11,14 +11,20 @@
</a-descriptions-item>
</a-descriptions>
<template #footer>
<a-button type="primary" @click="showFlag = false">关闭</a-button>
<a-button v-if="messageDetail.dataId && messageDetail.messageType === 3" type="primary" @click="gotoDetail">查看详情</a-button>
<a-button type="primary" @click="showFlag = false">关闭</a-button>
</template>
</a-modal>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import { messageApi } from '/@/api/support/message-api.js';
import { useUserStore } from '/@/store/modules/system/user';
const router = useRouter();
const userStore = useUserStore();
const emit = defineEmits(['refresh']);
const messageDetail = reactive({
@ -26,6 +32,7 @@
title: '',
content: '',
createTime: '',
dataId: null,
});
const showFlag = ref(false);
@ -43,5 +50,28 @@
}
}
async function gotoDetail() {
if (messageDetail.dataId) {
showFlag.value = false;
//
const roleCode = userStore.roleCode?.toLowerCase() || '';
// roleCode "user"
const isLawyer = roleCode === 'user';
// /
// /erp/service
// /erp/service/list
const targetPath = isLawyer ? '/erp/service' : '/erp/service/list';
//
await router.push({
path: targetPath,
query: { applicationId: messageDetail.dataId }
});
}
}
defineExpose({ show });
</script>

3
src/store/modules/system/user.js

@ -35,6 +35,8 @@ export const useUserStore = defineStore({
departmentId: '',
//部门名词
departmentName: '',
//角色代码
roleCode: '',
//是否需要修改密码
needUpdatePwdFlag: false,
//是否为超级管理员
@ -161,6 +163,7 @@ export const useUserStore = defineStore({
this.phone = data.phone;
this.departmentId = data.departmentId;
this.departmentName = data.departmentName;
this.roleCode = data.roleCode || '';
this.needUpdatePwdFlag = data.needUpdatePwdFlag;
this.administratorFlag = data.administratorFlag;
this.agreementSignFlag = data.agreementSignFlag || false;

64
src/utils/role-util.js

@ -0,0 +1,64 @@
/**
* 角色判断工具
*
* @Author: wzh
* @Date: 2025-03-21
*/
/**
* 获取角色判断结果
* @param {string} roleCode - 角色代码
* @returns {Object} 角色判断结果
*/
export function getRoleInfo(roleCode) {
if (!roleCode) {
return {
isUser: false,
isNotUser: true,
isCto: false,
isCeo: false,
isAssociationRole: false,
isFirmRole: false,
isFirmAdmin: false,
canCreateApplication: true,
};
}
const roleLower = (roleCode || '').toLowerCase();
const isUser = roleLower === 'user';
const isCto = roleLower === 'cto';
const isCeo = roleLower === 'ceo';
const isFirmAdmin = roleLower === 'staff';
const isAssociationRole = roleLower === 'ceo' ||
roleLower.includes('协会') ||
roleLower.includes('association') ||
roleLower.includes('律协') ||
roleLower.includes('律师协会');
const isFirmRole = roleLower.includes('律所') ||
roleLower.includes('firm') ||
roleLower.includes('lawyer') ||
roleLower.includes('律师') ||
isCto ||
isFirmAdmin;
const isNotUser = !isUser;
const canCreateApplication = !isAssociationRole && !isCeo && !isFirmAdmin;
return {
isUser,
isNotUser,
isCto,
isCeo,
isAssociationRole,
isFirmRole,
isFirmAdmin,
canCreateApplication,
};
}

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

@ -66,7 +66,7 @@
</div>
</a-form-item>
<a-form-item label="公益活动成本(万元)" name="publicWelfareCost">
<a-form-item label="预上报公益成本(万元)" name="publicWelfareCost">
<a-input-number
style="width: 100%"
v-model:value="form.publicWelfareCost"
@ -80,13 +80,27 @@
</div>
</a-form-item>
<a-form-item label="审核后实际公益成本(万元)" name="actualPublicWelfareCost">
<a-input-number
style="width: 100%"
v-model:value="form.actualPublicWelfareCost"
:min="0"
:precision="2"
disabled
addon-after="万元"
/>
<div style="font-size: 12px; color: #666; margin-top: 4px;">
律协审核后生成不可修改
</div>
</a-form-item>
<!-- 成本比例警告信息 -->
<a-form-item v-if="showCostWarning" :wrapper-col="{ offset: 8 }">
<div style="color: #ff4d4f; font-size: 12px;">
<span v-if="costRatio <= 25"> 系统计算的公益成本已经达到收入20%</span>
<span v-else> 系统计算的公益成本超过收入25%上限上限为25%将按上限值</span>
<span v-if="costRatio <= 25"> 全年累计公益成本{{ annualActualCost }} + 本季度{{ publicWelfareCost }}已达到全年收入的20%</span>
<span v-else> 全年累计公益成本超过全年收入的25%上限当前季度最多可填报</span>
<span v-if="costRatio > 25" style="font-weight: bold;">{{ calculatedPublicWelfareCost }}万元</span>
<span v-if="costRatio > 25">进行保存按25%比例计算</span>
<span v-if="costRatio > 25">全年25%比例计算</span>
</div>
</a-form-item>
@ -103,7 +117,7 @@
</a-form-item>
</a-col>-->
<a-col :span="12">
<a-form-item label="成本/收入比" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-form-item label="预上报成本收入比" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-input
style="width: 100%"
v-model:value="form.costIncomeRatio"
@ -112,6 +126,16 @@
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="实际成本收入比" :label-col="{ span: 12 }" :wrapper-col="{ span: 12 }">
<a-input
style="width: 100%"
v-model:value="form.actualCostIncomeRatio"
disabled
addon-after="%"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
@ -126,7 +150,7 @@
<script setup>
import { reactive, ref, nextTick, computed, onMounted } from 'vue';
import _ from 'lodash';
import { message } from 'ant-design-vue';
import { message, Modal } from 'ant-design-vue';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { firmReportsApi } from '/@/api/business/cost/firm-reports-api';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
@ -156,6 +180,9 @@
totalCost: 0 //
});
//
const annualActualCost = ref(0);
//
const quarterOptions = ref([]);
@ -199,7 +226,8 @@
//
async function fetchAnnualIncome() {
try {
const result = await firmReportsApi.income();
const targetYear = form.declareYear || new Date().getFullYear();
const result = await firmReportsApi.income({ declareYear: targetYear });
if (result.data) {
annualIncomeInfo.value = {
revenue: parseFloat(result.data.revenue) || 0,
@ -212,105 +240,67 @@
}
}
//
async function getPublicWelfareCost() {
console.log('开始获取公益成本,部门ID:', departmentId.value, '年份:', form.declareYear, '季度:', form.declareQuarter);
if (!departmentId.value) {
message.warning('无法获取机构信息,请重新登录');
console.error('部门ID为空,无法获取公益成本');
return;
}
if (!form.declareYear) {
console.log('年份未设置,跳过API调用');
return;
}
if (!form.declareQuarter) {
console.log('季度未选择,跳过API调用');
return;
}
// 1-4" (Q1)"
let quarterNum = form.declareQuarter;
if (typeof quarterNum === 'string') {
// " (Q1)"1
const match = quarterNum.match(/(\d+)/);
if (match) {
quarterNum = parseInt(match[1], 10);
} else {
console.error('无法解析季度字符串:', quarterNum);
return;
}
}
try {
//
const startMonth = (quarterNum - 1) * 3 + 1;
const endMonth = startMonth + 2;
//
const response = await serviceApplicationsApi.getServiceApplicationsCost({
firmId: departmentId.value,
year: form.declareYear,
quarter: quarterNum
});
// 3
let totalCost = 0;
for (let month = startMonth; month <= endMonth; month++) {
const queryForm = {
firmId: departmentId.value,
year: form.declareYear,
month: month
};
console.log('调用接口: /serviceApplications/statistics/cost, 参数:', queryForm);
const response = await serviceApplicationsApi.getServiceApplicationsCost(queryForm);
console.log(`${month}月接口返回数据:`, response);
totalCost += parseFloat(response.data) || 0;
}
form.publicWelfareCost = totalCost;
console.log('季度累计公益成本:', form.publicWelfareCost, '万元');
form.publicWelfareCost = parseFloat(response.data) || 0;
calculateCosts();
} catch (error) {
message.error('获取公益成本失败');
console.error('获取公益成本失败:', error);
form.publicWelfareCost = 0;
calculateCosts();
}
}
async function show(rowData) {
console.log('表单show函数被调用,rowData:', rowData);
Object.assign(form, formDefault);
// IDID
form.firmId = departmentId.value;
// rowDataid
if (rowData && typeof rowData === 'object' && rowData.id) {
console.log('编辑模式,使用现有数据:', rowData);
//
Object.assign(form, rowData);
//
//
await nextTick();
console.log('编辑模式表单数据:', form.declareYear, form.declareQuarter);
await fetchAnnualActualCost();
await getPublicWelfareCost();
} else {
console.log('新建模式,等待用户选择季度后获取公益成本');
//
await initQuarterOptions();
//
await fetchAnnualIncome();
//
await fetchAnnualActualCost();
await getPublicWelfareCost();
}
visibleFlag.value = true;
nextTick(() => {
formRef.value.clearValidate();
//
calculateCosts();
});
}
@ -331,9 +321,10 @@
declareYear: new Date().getFullYear(), //
declareQuarter: undefined, // (1,2,3,4)
revenue: undefined, //
//totalCost: 0, //
publicWelfareCost: undefined, //
costIncomeRatio: '0.00', // %
publicWelfareCost: undefined, //
actualPublicWelfareCost: undefined, //
costIncomeRatio: '0.00', // %
actualCostIncomeRatio: '0.00', // %
};
let form = reactive({ ...formDefault });
@ -345,10 +336,30 @@
publicWelfareCost: [{ required: true, message: '系统正在计算公益成本,请稍后' }],
};
// 25%
const calculatedPublicWelfareCost = ref(0);
const showCostWarning = ref(false);
const costRatio = ref(0); //
const costRatio = ref(0);
/**
* 获取全年累计实际公益成本万元
* 从后端接口获取后端会计算四个季度通过审核的服务申请成本之和
*/
async function fetchAnnualActualCost() {
try {
const currentYear = form.declareYear || new Date().getFullYear();
const response = await serviceApplicationsApi.getServiceApplicationsCost({
firmId: departmentId.value,
year: currentYear
});
annualActualCost.value = parseFloat(response.data) || 0;
console.log('全年累计实际公益成本:', annualActualCost.value, '万元');
} catch (error) {
console.error('获取全年累计实际公益成本失败:', error);
annualActualCost.value = 0;
}
}
//
function onQuarterChange() {
@ -363,61 +374,49 @@
calculateCosts();
}
// /
function calculateCosts() {
// NaN
const currentRevenue = safeParseFloat(form.revenue);
const publicWelfareCost = safeParseFloat(form.publicWelfareCost);
const annualRevenue = safeParseFloat(annualIncomeInfo.value.revenue);
const annualTotalCost = safeParseFloat(annualIncomeInfo.value.totalCost);
// +
// = +
const totalRevenue = currentRevenue + annualRevenue;
// +
const totalCost = publicWelfareCost + annualTotalCost;
// = +
const totalCostForRatio = annualActualCost.value + publicWelfareCost;
//
if (totalRevenue > 0) {
costRatio.value = (totalCost / totalRevenue) * 100;
costRatio.value = (totalCostForRatio / totalRevenue) * 100;
// 20%
if (costRatio.value >= 20) {
showCostWarning.value = true;
// 25%25%
if (costRatio.value > 25) {
const maxAllowedAnnualCost = totalRevenue * 0.25; //
const remainingAllowedCost = Math.max(0, maxAllowedAnnualCost - annualTotalCost);
// * 25%
const maxAllowedCost = totalRevenue * 0.25;
// -
const remainingAllowedCost = Math.max(0, maxAllowedCost - annualActualCost.value);
//
calculatedPublicWelfareCost.value = Math.min(publicWelfareCost, remainingAllowedCost).toFixed(2);
// /使
const actualAnnualCost = annualTotalCost + parseFloat(calculatedPublicWelfareCost.value);
const ratio = (actualAnnualCost / totalRevenue) * 100;
const actualTotalCost = annualActualCost.value + parseFloat(calculatedPublicWelfareCost.value);
const ratio = (actualTotalCost / totalRevenue) * 100;
form.costIncomeRatio = ratio.toFixed(2);
} else {
// 20%25%使
calculatedPublicWelfareCost.value = publicWelfareCost.toFixed(2);
form.costIncomeRatio = costRatio.value.toFixed(2);
}
} else {
// 20%
showCostWarning.value = false;
calculatedPublicWelfareCost.value = publicWelfareCost.toFixed(2);
form.costIncomeRatio = costRatio.value.toFixed(2);
}
} else {
// 0
showCostWarning.value = false;
costRatio.value = 0;
form.costIncomeRatio = '0.00';
calculatedPublicWelfareCost.value = publicWelfareCost.toFixed(2);
}
//
form.totalCost = showCostWarning.value ? parseFloat(calculatedPublicWelfareCost.value) : publicWelfareCost;
}
//
@ -443,8 +442,18 @@
try {
await formRef.value.validateFields();
form.approvalStatus = '1'; //
await save();
message.success('提交审核成功');
//
Modal.confirm({
title: '成本上报提醒',
content: '当前核算的公益成本支出仅为律所预上报成本,最终以律协服务审核后实际公益成本为准。',
okText: '确认提交',
cancelText: '取消',
onOk: async () => {
await save();
message.success('提交审核成功');
}
});
} catch (err) {
message.error('参数验证错误,请仔细填写表单数据!');
}
@ -457,7 +466,7 @@
//
calculateCosts();
// 使
//
const saveData = { ...form };
//
@ -470,16 +479,21 @@
saveData.declareYear = parseInt(saveData.declareYear);
}
if (showCostWarning.value) {
// 使
// 25%使
if (showCostWarning.value && costRatio.value > 25) {
//
saveData.publicWelfareCost = parseFloat(calculatedPublicWelfareCost.value);
saveData.totalCost = saveData.publicWelfareCost;
//
const revenue = parseFloat(saveData.revenue) || 0;
if (revenue > 0) {
saveData.costIncomeRatio = ((saveData.publicWelfareCost / revenue) * 100).toFixed(2);
//
const totalRevenue = safeParseFloat(saveData.revenue) + safeParseFloat(annualIncomeInfo.value.revenue);
const annualTotalCost = annualActualCost.value + safeParseFloat(saveData.publicWelfareCost);
let ratio = totalRevenue > 0 ? ((annualTotalCost / totalRevenue) * 100) : 0;
// 25%
if (ratio > 25) {
ratio = 25;
}
saveData.costIncomeRatio = ratio.toFixed(2);
}
if (saveData.id) {

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

@ -191,19 +191,32 @@
// dataIndex: 'totalCost',
// ellipsis: true,
//},
{
title: '公益成本支出(单位:万元)',
{
title: '预上报公益成本(单位:万元)',
dataIndex: 'publicWelfareCost',
ellipsis: true,
},
{
title: '成本收入比',
title: '审核后实际公益成本(单位:万元)',
dataIndex: 'actualPublicWelfareCost',
ellipsis: true,
},
{
title: '预上报成本收入比',
dataIndex: 'costIncomeRatio',
ellipsis: true,
customRender: ({ text }) => {
return text ? `${text}%` : '';
},
},
{
title: '实际成本收入比',
dataIndex: 'actualCostIncomeRatio',
ellipsis: true,
customRender: ({ text }) => {
return text ? `${text}%` : '';
},
},
/**{
title: '审批状态',
dataIndex: 'approvalStatus',
@ -305,8 +318,8 @@
//
function onSubmit(data){
Modal.confirm({
title: '提示',
content: '确定要提交该成本报表吗?',
title: '成本上报提醒',
content: '当前核算的公益成本支出仅为律所预上报成本,最终以律协服务审核后实际公益成本为准。确认后将提交该成本报表。',
okText: '提交',
okType: 'primary',
onOk() {
@ -319,10 +332,10 @@
//
async function requestSubmit(data){
//
// 使==ID
if (data.approvalStatus !== 0 || (data.userId && data.userId != currentUserId.value)) {
message.warning('没有权限提交该数据');
//
// staffcto
if (data.approvalStatus !== 0) {
message.warning('只能提交未提交状态的数据');
return;
}
@ -399,9 +412,9 @@
//
async function requestDelete(data){
//
// 使==ID
if (!isCto.value || data.approvalStatus !== 0 || (data.userId && data.userId != currentUserId.value)) {
//
// staffcto
if (!isCto.value || data.approvalStatus !== 0) {
message.warning('没有权限删除该数据');
return;
}
@ -440,12 +453,13 @@
return;
}
//
//
// staffcto
const selectedRecords = tableData.value.filter(item => selectedRowKeyList.value.includes(item.id));
const invalidRecords = selectedRecords.filter(item => item.approvalStatus !== 0 || (item.userId && item.userId != currentUserId.value));
const invalidRecords = selectedRecords.filter(item => item.approvalStatus !== 0);
if (invalidRecords.length > 0) {
message.warning('只能批量删除自己未提交的数据');
message.warning('只能批量删除未提交的数据');
return;
}

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

@ -57,12 +57,13 @@
</template>
<div style="display: flex; flex-wrap: wrap; gap: 16px;">
<div
v-for="item in noticeList"
v-for="item in filteredNoticeList"
:key="item.bookId || item.noticeId || item.id"
style="display: flex; align-items: center; padding: 8px 12px; background: #f5f5f5; border-radius: 4px; cursor: pointer;"
@click="downloadNoticeAttachment(item)"
:title="hasAttachment(item) ? '点击下载' : '暂无附件'"
>
<FileTextOutlined style="font-size: 16px; color: #1890ff; margin-right: 8px;" />
<span style="color: #1890ff; font-size: 14px;">{{ item.bookName || item.title }}</span>
<DownloadOutlined v-if="hasAttachment(item)" style="font-size: 12px; color: #1890ff; margin-left: 4px;" />
@ -183,7 +184,7 @@
</a-card>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue';
import { reactive, ref, onMounted, computed } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { letterApi } from '/@/api/business/letter/letter-api';
@ -597,6 +598,16 @@ import AgreementModal from '/@/views/system/home/components/agreement.vue';
//
const noticeList = ref([]);
//
const filteredNoticeList = computed(() => {
if (!isFirmAdmin.value) {
return noticeList.value;
}
return noticeList.value.filter(item => {
return item.documentNumber !== '001';
});
});
//
async function queryNoticeData() {
try {

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

@ -53,13 +53,13 @@
申请无处罚证明
</a-button>
<!-- 律所主任申请个人无处罚证明 -->
<a-button @click="() => showForm('personal')" type="primary" v-if="isCto || isFirmAdmin">
<a-button @click="() => showForm('personal')" type="primary" v-if="isCto">
<template #icon>
<PlusOutlined />
</template>
申请个人无处罚证明
</a-button>
<!-- 律所主任申请律所无处罚证明 -->
<!-- 律所主任/内勤申请律所无处罚证明 -->
<a-button @click="() => showForm('firm')" type="primary" v-if="isCto || isFirmAdmin">
<template #icon>
<PlusOutlined />

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

@ -58,10 +58,10 @@
<div class="report-cell">律所名称</div>
<div class="report-cell">律师名称</div>
<div class="report-cell">执业证号</div>
<div class="report-cell">季度累计服务时长</div>
<div class="report-cell">季度累计服务成本</div>
<div class="report-cell">年度累计服务时长</div>
<div class="report-cell">年度累计服务成本</div>
<div class="report-cell">季度累计公益服务时长</div>
<div class="report-cell">季度累计公益服务成本</div>
<div class="report-cell">年度累计公益服务时长</div>
<div class="report-cell">年度累计公益服务成本</div>
</div>
<!-- 数据行 -->

200
src/views/business/erp/service/law-firm-service-report-statistics.vue

@ -55,66 +55,88 @@
<!---------- 统计表格 begin ----------->
<a-card size="small" :bordered="false" :hoverable="true">
<div class="statistics-title">律所活动统计报表</div>
<div class="statistics-title">律所公益活动统计报表</div>
<!-- 自定义表头 -->
<div class="custom-table-container">
<table class="custom-table" border="1" cellspacing="0" cellpadding="0">
<thead>
<!-- 第一行序号 + 律所名称 + 活动分类 -->
<!-- 第一行序号 + 律所名称 + 律师名称 + 活动分类含合计 + 服务总次数 -->
<tr>
<th rowspan="2" class="fixed-col index-col">序号</th>
<th rowspan="2" class="fixed-col firm-col">律所名称</th>
<th rowspan="2" class="fixed-col lawyer-col">律师名称</th>
<th v-for="category in categoryStructure" :key="category.categoryId" :colspan="category.activityList.length" class="category-col">
{{ category.categoryName }}
</th>
<template v-for="category in categoryStructure" :key="category.categoryId">
<!-- 如果有多个活动先添加合计列跨两行 -->
<th v-if="category.activityList.length > 1" rowspan="2" class="subtotal-header-col-fixed" :title="category.categoryName + '合计'">合计</th>
<!-- 然后是分类名称只跨越活动列 -->
<th :colspan="category.activityList.length" class="category-col">
{{ category.categoryName }}
</th>
</template>
<th rowspan="2" class="fixed-col fixed-right total-count-col">服务总次数</th>
</tr>
<!-- 第二行活动名称 -->
<!-- 第二行活动名称合计已在上行 -->
<tr>
<th v-for="activity in allActivities" :key="activity.activityId" class="activity-col">
{{ activity.activityName }}
</th>
<template v-for="category in categoryStructure" :key="'header_' + category.categoryId">
<th v-for="activity in category.activityList" :key="activity.activityId" class="activity-col">
{{ activity.activityName }}
</th>
</template>
</tr>
</thead>
<tbody>
<template v-for="(firm, firmIndex) in tableData" :key="firm.firmId">
<!-- 律所汇总行 -->
<tr class="firm-row" @click="toggleExpand(firm.firmId)" :class="{ 'expanded': expandedFirms.includes(firm.firmId) }">
<td class="fixed-col index-col" style="width: 60px; min-width: 60px; max-width: 40px;">{{ firmIndex + 1 }}</td>
<td class="fixed-col index-col">{{ firmIndex + 1 }}</td>
<td class="fixed-col firm-name">
<span class="expand-icon">{{ expandedFirms.includes(firm.firmId) ? '▼' : '▶' }}</span>
{{ firm.firmName }}
</td>
<td class="fixed-col lawyer-col">-</td>
<td v-for="activity in allActivities" :key="activity.activityId" class="data-col">
{{ firm[`activity_${activity.activityId}`] || 0 }}
</td>
<template v-for="category in categoryStructure" :key="'firm_' + category.categoryId">
<td v-if="category.activityList.length > 1" class="data-col subtotal-data-col">
{{ getCategoryTotalForRecord(firm, category) }}
</td>
<td v-for="activity in category.activityList" :key="activity.activityId" class="data-col">
{{ firm[`activity_${activity.activityId}`] || 0 }}
</td>
</template>
<td class="fixed-col fixed-right total-count">{{ firm.totalCount || 0 }}</td>
</tr>
<!-- 律师明细行 -->
<tr v-for="lawyer in firm.lawyerList" :key="lawyer.lawyerId"
<tr v-for="lawyer in firm.lawyerList" :key="lawyer.lawyerId"
v-show="expandedFirms.includes(firm.firmId)"
class="lawyer-row">
<td class="fixed-col index-col" style="width: 40px; min-width: 40px; max-width: 40px;"></td>
<td class="fixed-col index-col"></td>
<td class="fixed-col firm-name"></td>
<td class="fixed-col lawyer-name">{{ lawyer.lawyerName }}</td>
<td v-for="activity in allActivities" :key="activity.activityId" class="data-col">
{{ lawyer[`activity_${activity.activityId}`] || 0 }}
</td>
<template v-for="category in categoryStructure" :key="'lawyer_' + lawyer.lawyerId + '_' + category.categoryId">
<td v-if="category.activityList.length > 1" class="data-col subtotal-data-col">
{{ getCategoryTotalForRecord(lawyer, category) }}
</td>
<td v-for="activity in category.activityList" :key="activity.activityId" class="data-col">
{{ lawyer[`activity_${activity.activityId}`] || 0 }}
</td>
</template>
<td class="fixed-col fixed-right total-count">{{ lawyer.totalCount || 0 }}</td>
</tr>
</template>
<!-- 合计行 -->
<tr class="total-row" v-if="tableData.length > 0">
<td class="fixed-col index-col" style="width: 40px; min-width: 40px; max-width: 40px;">-</td>
<td class="fixed-col firm-name" style="font-weight: bold; color: #ff4d4f;">合计</td>
<td class="fixed-col lawyer-col" style="font-weight: bold; color: #ff4d4f;">-</td>
<td v-for="activity in allActivities" :key="activity.activityId" class="data-col" style="font-weight: bold; color: #ff4d4f;">
{{ totalRow[`activity_${activity.activityId}`] || 0 }}
</td>
<td class="fixed-col fixed-right" style="font-weight: bold; color: #ff4d4f;">
<td class="fixed-col index-col">-</td>
<td class="fixed-col firm-name">合计</td>
<td class="fixed-col lawyer-col">-</td>
<template v-for="category in categoryStructure" :key="'total_' + category.categoryId">
<td v-if="category.activityList.length > 1" class="data-col">
{{ categorySubtotals[`category_${category.categoryId}`] || 0 }}
</td>
<td v-for="activity in category.activityList" :key="activity.activityId" class="data-col">
{{ totalRow[`activity_${activity.activityId}`] || 0 }}
</td>
</template>
<td class="fixed-col fixed-right">
{{ totalRow.totalCount || 0 }}
</td>
</tr>
@ -201,17 +223,12 @@
const totalRow = computed(() => {
const row = { totalCount: 0 };
// 0
allActivities.value.forEach(activity => {
row[`activity_${activity.activityId}`] = 0;
});
//
tableData.value.forEach(firm => {
//
row.totalCount += firm.totalCount || 0;
//
allActivities.value.forEach(activity => {
const key = `activity_${activity.activityId}`;
row[key] += firm[key] || 0;
@ -221,6 +238,41 @@
return row;
});
// 1
const categoryStructureWithSubtotal = computed(() => {
return categoryStructure.value.map(category => ({
...category,
activityIds: (category.activityList || []).map(a => a.activityId),
needSubtotal: (category.activityList || []).length > 1
}));
});
//
const categorySubtotals = computed(() => {
const subtotals = {};
categoryStructureWithSubtotal.value.forEach(category => {
let total = 0;
(category.activityList || []).forEach(activity => {
tableData.value.forEach(firm => {
total += firm[`activity_${activity.activityId}`] || 0;
});
});
subtotals[`category_${category.categoryId}`] = total;
});
return subtotals;
});
//
function getCategoryTotalForRecord(record, category) {
let total = 0;
if (category.activityList && category.activityList.length > 1) {
category.activityList.forEach(activity => {
total += record[`activity_${activity.activityId}`] || 0;
});
}
return total;
}
//
const pagination = reactive({
current: 1,
@ -463,9 +515,15 @@
}
.custom-table th {
background-color: #f5f5f5;
font-weight: 600;
color: #333;
background-color: #fafafa;
}
/* 分类列(第一行)- 简洁风格 */
.custom-table thead tr:first-child th.category-col {
background-color: #f0f0f0 !important;
color: #333 !important;
}
/* 固定列基础样式 */
@ -523,21 +581,52 @@
min-width: 100px;
}
/* 分类列样式 - 第一行 */
/* 分类列样式 - 第一行(简洁灰调) */
.category-col {
color: #fff;
color: #333;
background-color: #f5f5f5;
font-weight: 600;
font-size: 11px;
padding: 8px 6px;
padding: 10px 6px;
line-height: 1.4;
min-width: 180px;
max-width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 活动列样式 - 第二行 */
/* 合计列样式 - 跨两行(简洁风格) */
.subtotal-header-col-fixed {
background-color: #f9f9f9;
color: #555;
font-weight: 600;
font-size: 11px;
padding: 10px 4px;
width: 60px;
min-width: 60px;
max-width: 60px;
border-left: 1px solid #d9d9d9;
border-right: 1px solid #e8e8e8;
}
/* 合计列样式 - 第二行备用(简洁风格) */
.subtotal-header-col {
background-color: #f9f9f9;
color: #555;
font-weight: 600;
font-size: 11px;
padding: 6px 4px;
width: 70px;
min-width: 70px;
max-width: 70px;
border-left: 1px solid #d9d9d9;
}
/* 活动列样式 - 第二行(简洁风格) */
.activity-col {
background-color: #f0f5ff;
color: #1890ff;
background-color: #fafafa;
color: #666;
font-weight: 500;
font-size: 10px;
padding: 6px 2px;
@ -554,36 +643,45 @@
min-width: 80px;
}
/* 合计行样式 */
/* 合计数据列样式(简洁) */
.subtotal-data-col {
background-color: #f5f5f5;
color: #333;
font-weight: 600;
}
/* 合计行样式(简洁风格) */
.total-row {
background-color: #fff2f0;
background-color: #f5f5f5;
}
.total-row td {
border-top: 2px solid #ff4d4f;
border-top: 2px solid #bbb;
font-weight: 600;
}
.firm-row {
cursor: pointer;
background: linear-gradient(135deg, #f6ffed 0%, #e6f7ff 100%);
background-color: #fafafa;
}
.firm-row:hover {
background: linear-gradient(135deg, #d9f7be 0%, #bae7ff 100%);
background-color: #f0f0f0;
}
.firm-row.expanded {
background: linear-gradient(135deg, #b7eb8f 0%, #91d5ff 100%);
background-color: #ebebeb;
}
.firm-name {
text-align: left;
font-weight: bold;
font-weight: 600;
color: #333;
}
.expand-icon {
margin-right: 8px;
color: #52c41a;
color: #666;
font-size: 12px;
}
@ -592,23 +690,23 @@
}
.lawyer-row:hover {
background-color: #e6f7ff;
background-color: #f9f9f9;
}
.lawyer-name {
text-align: center;
color: #1890ff;
color: #333;
font-weight: 500;
}
.total-count {
color: #52c41a;
font-weight: bold;
font-weight: 700;
color: #333;
}
.total-duration {
color: #faad14;
font-weight: bold;
font-weight: 600;
color: #666;
}
/* 分页样式 */

191
src/views/business/erp/service/lawyer-service-report-statistics.vue

@ -52,45 +52,62 @@
<!---------- 统计表格 begin ----------->
<a-card size="small" :bordered="false" :hoverable="true">
<div class="statistics-title">律师活动统计报表</div>
<div class="statistics-title">律师公益活动统计报表</div>
<!-- 自定义表头 -->
<div class="custom-table-header">
<table class="header-table" border="1" cellspacing="0" cellpadding="0">
<thead>
<!-- 第一行序号 + 活动分类 -->
<!-- 第一行序号 + 律师名称 + 活动分类含合计 + 服务总次数 -->
<tr>
<th rowspan="2" class="fixed-col index-col">序号</th>
<th rowspan="2" class="fixed-col fixed-left">律师名称</th>
<th v-for="category in categoryStructure" :key="category.categoryId" :colspan="category.activityList.length" class="category-col" :title="category.categoryName">
{{ category.categoryName }}
</th>
<template v-for="category in categoryStructure" :key="category.categoryId">
<!-- 如果有多个活动先添加合计列跨两行 -->
<th v-if="category.activityList.length > 1" rowspan="2" class="subtotal-header-col-fixed" :title="category.categoryName + '合计'">合计</th>
<!-- 然后是分类名称只跨越活动列 -->
<th :colspan="category.activityList.length" class="category-col" :title="category.categoryName">
{{ category.categoryName }}
</th>
</template>
<th rowspan="2" class="fixed-col fixed-right">服务总次数</th>
</tr>
<!-- 第二行活动名称 -->
<!-- 第二行活动名称合计已在上行 -->
<tr>
<th v-for="activity in allActivities" :key="activity.activityId" class="activity-col" :title="activity.activityName">
{{ activity.activityName }}
</th>
<template v-for="category in categoryStructure" :key="'header_' + category.categoryId">
<th v-for="activity in category.activityList" :key="activity.activityId" class="activity-col" :title="activity.activityName">
{{ activity.activityName }}
</th>
</template>
</tr>
</thead>
<tbody>
<tr v-for="(record, index) in tableData" :key="record.lawyerId">
<td class="fixed-col index-col">{{ index + 1 }}</td>
<td class="fixed-col fixed-left lawyer-name">{{ record.lawyerName }}</td>
<td v-for="activity in allActivities" :key="activity.activityId" class="data-col">
{{ record[`activity_${activity.activityId}`] || 0 }}
</td>
<template v-for="category in categoryStructure" :key="'data_' + category.categoryId">
<td v-if="category.activityList.length > 1" class="data-col subtotal-data-col">
{{ getCategoryTotalForRecord(record, category) }}
</td>
<td v-for="activity in category.activityList" :key="activity.activityId" class="data-col">
{{ record[`activity_${activity.activityId}`] || 0 }}
</td>
</template>
<td class="fixed-col fixed-right total-count">{{ record.totalCount || 0 }}</td>
</tr>
<!-- 合计行 -->
<tr class="total-row" v-if="tableData.length > 0">
<td class="fixed-col index-col" style="font-weight: bold; color: #ff4d4f;">-</td>
<td class="fixed-col fixed-left" style="font-weight: bold; color: #ff4d4f;">合计</td>
<td v-for="activity in allActivities" :key="activity.activityId" class="data-col" style="font-weight: bold; color: #ff4d4f;">
{{ totalRow[`activity_${activity.activityId}`] || 0 }}
</td>
<td class="fixed-col fixed-right" style="font-weight: bold; color: #ff4d4f;">
<td class="fixed-col index-col">-</td>
<td class="fixed-col fixed-left">合计</td>
<template v-for="category in categoryStructure" :key="'total_' + category.categoryId">
<td v-if="category.activityList.length > 1" class="data-col">
{{ categorySubtotals[`category_${category.categoryId}`] || 0 }}
</td>
<td v-for="activity in category.activityList" :key="activity.activityId" class="data-col">
{{ totalRow[`activity_${activity.activityId}`] || 0 }}
</td>
</template>
<td class="fixed-col fixed-right">
{{ totalRow.totalCount || 0 }}
</td>
</tr>
@ -166,17 +183,12 @@
const totalRow = computed(() => {
const row = { totalCount: 0 };
// 0
allActivities.value.forEach(activity => {
row[`activity_${activity.activityId}`] = 0;
});
//
tableData.value.forEach(record => {
//
row.totalCount += record.totalCount || 0;
//
allActivities.value.forEach(activity => {
const key = `activity_${activity.activityId}`;
row[key] += record[key] || 0;
@ -186,6 +198,41 @@
return row;
});
// 1
const categoryStructureWithSubtotal = computed(() => {
return categoryStructure.value.map(category => ({
...category,
activityIds: (category.activityList || []).map(a => a.activityId),
needSubtotal: (category.activityList || []).length > 1
}));
});
//
const categorySubtotals = computed(() => {
const subtotals = {};
categoryStructureWithSubtotal.value.forEach(category => {
let total = 0;
(category.activityList || []).forEach(activity => {
tableData.value.forEach(record => {
total += record[`activity_${activity.activityId}`] || 0;
});
});
subtotals[`category_${category.categoryId}`] = total;
});
return subtotals;
});
//
function getCategoryTotalForRecord(record, category) {
let total = 0;
if (category.activityList && category.activityList.length > 1) {
category.activityList.forEach(activity => {
total += record[`activity_${activity.activityId}`] || 0;
});
}
return total;
}
//
const pagination = reactive({
current: 1,
@ -387,7 +434,7 @@
width: auto;
min-width:100%;
border-collapse: collapse;
table-layout: auto;
table-layout: fixed;
font-size: 13px;
}
@ -400,9 +447,15 @@
}
.header-table thead th {
background-color: #f5f5f5;
font-weight: 600;
color: #333;
background-color: #fafafa;
}
/* 分类列(第一行)- 简洁风格 */
.header-table thead tr:first-child th.category-col {
background-color: #f0f0f0 !important;
color: #333 !important;
}
/* 固定列样式 */
@ -442,35 +495,64 @@
}
.lawyer-name {
font-weight: bold;
color: #1890ff;
font-weight: 600;
color: #333;
}
.total-count {
color: #52c41a;
font-weight: bold;
font-weight: 700;
color: #333;
}
.total-duration {
color: #faad14;
font-weight: bold;
font-weight: 600;
color: #666;
}
/* 分类列样式 - 第一行 */
/* 分类列样式 - 第一行(简洁灰调) */
.category-col {
color: #fff;
color: #333;
background-color: #f5f5f5;
font-weight: 600;
font-size: 12px;
padding: 10px 4px;
padding: 10px 8px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 200px;
}
/* 合计列样式 - 跨两行(简洁风格) */
.subtotal-header-col-fixed {
background-color: #f9f9f9;
color: #555;
font-weight: 600;
font-size: 11px;
padding: 10px 4px;
width: 60px;
min-width: 60px;
max-width: 60px;
border-left: 1px solid #d9d9d9;
border-right: 1px solid #e8e8e8;
}
/* 活动列样式 - 第二行 */
/* 合计列样式 - 第二行备用(简洁风格) */
.subtotal-header-col {
background-color: #f9f9f9;
color: #555;
font-weight: 600;
font-size: 11px;
padding: 6px 4px;
width: 70px;
min-width: 70px;
max-width: 70px;
border-left: 1px solid #d9d9d9;
}
/* 活动列样式 - 第二行(简洁风格) */
.activity-col {
background-color: #f0f5ff;
color: #1890ff;
background-color: #fafafa;
color: #666;
font-weight: 500;
font-size: 10px;
padding: 6px 2px;
@ -487,41 +569,26 @@
font-size: 13px;
}
/* 合计行样式 */
/* 合计行样式(简洁风格) */
.total-row {
background-color: #fff2f0;
background-color: #f5f5f5;
}
.total-row td {
border-top: 2px solid #ff4d4f;
border-top: 2px solid #bbb;
font-weight: 600;
}
/* 数据行hover效果 */
/* 数据行hover效果(简洁) */
.header-table tbody tr:hover {
background-color: #e6f7ff;
background-color: #f9f9f9;
}
/* 分页样式 */
.pagination-wrapper {
display: flex;
justify-content: flex-end;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
/* 活动列样式 */
.activity-col {
background-color: #f6ffed;
color: #52c41a;
font-weight: 500;
min-width: 80px;
}
/* 数据列样式 */
.data-col {
min-width: 80px;
/* 合计数据列样式(简洁) */
.subtotal-data-col {
background-color: #f5f5f5;
color: #333;
font-weight: 600;
}
/* 分页样式 */

3
src/views/business/erp/service/lawyer-statistics-detail.vue

@ -224,8 +224,7 @@ async function handleExport() {
const exportParams = {
quarter: localQueryForm.quarter,
year: localQueryForm.year,
firmId: props.params?.firmId
//
firmId: params.value?.firmId
//
};

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

@ -12,20 +12,23 @@
<a-form class="smart-query-form">
<a-row class="smart-query-form-row">
<a-form-item label="执业机构" v-if="isAssociationRole || isCeo" class="smart-query-form-item">
<DepartmentTreeSelect style="width: 250px" v-model:value="queryForm.firmId" placeholder="请选择执业机构" />
<DepartmentTreeSelect style="width: 250px" v-model:value="queryForm.firmId" placeholder="请选择执业机构" @change="handleFirmChange" />
</a-form-item>
<a-form-item label="律师名称" v-if="isCtoRole || isCeo" class="smart-query-form-item">
<a-form-item label="律师名称" v-if="isAssociationRole || isCtoRole || isCeo" class="smart-query-form-item">
<a-select
v-model:value="queryForm.userId"
style="width: 200px"
placeholder="请选择律师"
:showSearch="true"
:allowClear="true"
:filterOption="filterLawyerOption"
:filterOption="false"
optionFilterProp="children"
@focus="loadAllEmployees"
@focus="loadEmployeesByFirm"
@search="handleLawyerSearch"
@change="handleLawyerChange"
@clear="handleLawyerClear"
>
<a-select-option v-for="item in employeeList" :key="item.employeeId" :value="item.employeeId">
<a-select-option v-for="item in filteredEmployeeList" :key="item.employeeId" :value="item.employeeId">
{{ item.actualName }}
<template v-if="item.departmentName">{{ item.departmentName }}</template>
<template v-if="item.positionName"> - {{ item.positionName }}</template>
@ -189,19 +192,25 @@
:confirm-loading="auditLoading"
@ok="handleAudit"
@cancel="handleAuditCancel"
width="400px"
width="560px"
>
<div style="text-align: center; padding: 20px 0;">
<p style="margin-bottom: 20px; font-size: 16px;">请选择审核结果</p>
<a-radio-group v-model:value="auditForm.auditResult" size="large">
<a-radio :value="3" style="margin-right: 30px;">
<span style="font-size: 16px;">同意</span>
</a-radio>
<a-radio :value="4">
<span style="font-size: 16px;">拒绝</span>
</a-radio>
</a-radio-group>
</div>
<a-form :model="auditForm" layout="horizontal" :label-col="{ span: 4 }" :wrapper-col="{ span: 20 }">
<a-form-item label="审批结果" required>
<a-radio-group v-model:value="auditForm.auditResult" size="large">
<a-radio :value="3">同意</a-radio>
<a-radio :value="4">不同意</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="审批意见">
<a-textarea
v-model:value="auditForm.auditRemark"
placeholder="请输入审批意见(选填)"
rows="5"
:maxlength="200"
show-count
/>
</a-form-item>
</a-form>
</a-modal>
<!---------- 审核弹框 end ----------->
@ -221,6 +230,16 @@
<a-radio :value="4">拒绝</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="审核意见(非必填)">
<a-textarea
v-model:value="batchAuditForm.auditRemark"
placeholder="请输入审核意见"
rows="3"
:maxlength="200"
show-count
style="resize: vertical;"
/>
</a-form-item>
<a-form-item label="选中记录">
<span style="color: #1890ff;">{{ selectedRowKeyList.length }} 条记录</span>
</a-form-item>
@ -238,10 +257,10 @@
width="500px"
>
<a-form :model="rejectForm" layout="vertical">
<a-form-item label="驳回原因" required>
<a-form-item label="驳回原因(选填)">
<a-textarea
v-model:value="rejectForm.rejectReason"
placeholder="请输入驳回原因"
placeholder="请输入驳回原因(选填)"
rows="4"
:maxlength="200"
show-count
@ -308,8 +327,8 @@
</div>
</template>
<script setup>
import { reactive, ref, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { reactive, ref, onMounted, computed, watch, nextTick } from 'vue';
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';
@ -463,6 +482,28 @@ import { getRoleInfo } from '/@/utils/role-util';
//
const employeeList = ref([]);
//
const searchKeyword = ref('');
//
const filteredEmployeeList = computed(() => {
let result = employeeList.value;
//
if (queryForm.firmId) {
result = result.filter(item => item.departmentId == queryForm.firmId);
}
//
if (searchKeyword.value) {
const keyword = searchKeyword.value.toLowerCase();
result = result.filter(item =>
(item.actualName && item.actualName.toLowerCase().includes(keyword)) ||
(item.departmentName && item.departmentName.toLowerCase().includes(keyword))
);
}
return result;
});
//
const auditModalVisible = ref(false);
@ -577,6 +618,11 @@ import { getRoleInfo } from '/@/utils/role-util';
return optionText.includes(input.toLowerCase());
}
//
function handleLawyerSearch(keyword) {
searchKeyword.value = keyword;
}
//
@ -607,6 +653,7 @@ import { getRoleInfo } from '/@/utils/role-util';
await serviceApplicationsApi.batchReview(auditData);
message.success('批量审核成功');
batchAuditModalVisible.value = false;
queryData(); //
selectedRowKeyList.value = []; //
@ -797,6 +844,35 @@ import { getRoleInfo } from '/@/utils/role-util';
console.error('加载员工数据失败:', e);
}
}
//
async function loadEmployeesByFirm() {
try {
if (employeeList.value.length === 0) {
await loadAllEmployees();
}
} catch (e) {
console.error('加载员工数据失败:', e);
}
}
//
function handleFirmChange(firmId) {
//
queryForm.userId = undefined;
searchKeyword.value = '';
}
//
function handleLawyerChange() {
searchKeyword.value = '';
}
//
function handleLawyerClear() {
searchKeyword.value = '';
}
//
const total = ref(0);
@ -805,6 +881,7 @@ import { getRoleInfo } from '/@/utils/role-util';
let pageSize = queryForm.pageSize;
Object.assign(queryForm, queryFormState);
queryForm.pageSize = pageSize;
searchKeyword.value = ''; //
queryData();
}
@ -876,26 +953,37 @@ import { getRoleInfo } from '/@/utils/role-util';
}
// ---------------------------- / ----------------------------
const formRef = ref();
const costReportFormRef = ref();
const agreementModalRef = ref();
const pendingFormData = ref(null); //
//
const route = useRoute();
const route = useRoute();
const router = useRouter();
onMounted(async () => {
onMounted(async () => {
await getLoginInfo();
// URLfirmId
const firmId = route.query.firmId;
if (firmId) {
// firmId
queryForm.firmId = firmId;
}
//
queryData();
loadAllEmployees();
handleRouteApplicationDetail(route.query.applicationId);
});
watch(
() => route.query.applicationId,
(applicationId) => {
handleRouteApplicationDetail(applicationId);
}
);
// ---------------------------- ----------------------------
const agreementModalRef = ref();
const pendingFormData = ref(null); //
//
function showAgreementModal(data) {
@ -914,9 +1002,33 @@ onMounted(async () => {
pendingFormData.value = null;
}
// ---------------------------- / ----------------------------
const formRef = ref();
const costReportFormRef = ref();
async function handleRouteApplicationDetail(applicationId) {
if (!applicationId) {
return;
}
try {
const detailResult = await serviceApplicationsApi.queryDetail(applicationId);
if (detailResult.data) {
// DOM 使 setTimeout Modal
await new Promise(resolve => setTimeout(resolve, 500));
if (formRef.value && typeof formRef.value.show === 'function') {
formRef.value.show(detailResult.data, true);
} else {
console.error('formRef 未初始化或 show 方法不存在');
}
}
} catch (error) {
console.error('消息跳转加载服务申报详情失败:', error);
} finally {
if (route.query.applicationId) {
const nextQuery = { ...route.query };
delete nextQuery.applicationId;
router.replace({ path: route.path, query: nextQuery });
}
}
}
function showForm(data) {
console.log('showForm传入的data:', data);
@ -1033,30 +1145,33 @@ async function requestDelete(data){
}
//
function showAuditModal(record) {
currentAuditRecord.value = record;
auditForm.auditResult = 3; //
auditForm.auditRemark = '';
auditModalVisible.value = true;
}
function showAuditModal(record) {
currentAuditRecord.value = record;
auditForm.auditResult = 3; //
auditForm.auditRemark = '';
auditModalVisible.value = true;
}
//
//
async function handleAudit() {
if (!currentAuditRecord.value) {
message.error('未选择审核记录');
return;
}
auditLoading.value = true;
// 使
const auditData = {
applicationId: currentAuditRecord.value.applicationId,
firmAuditStatus: auditForm.auditResult
[isAssociationRole.value ? 'associationAuditStatus' : 'firmAuditStatus']: auditForm.auditResult,
[isAssociationRole.value ? 'associationAuditOpinion' : 'firmAuditOpinion']: auditForm.auditRemark
};
try {
await serviceApplicationsApi.review(auditData);
message.success('审核成功');
auditModalVisible.value = false;
queryData();
} catch (error) {
@ -1403,11 +1518,6 @@ function showAuditModal(record) {
return;
}
if (!rejectForm.rejectReason.trim()) {
message.warning('请输入驳回原因');
return;
}
rejectLoading.value = true;
//

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

@ -248,8 +248,8 @@
</div>
</template>
<script setup>
import { reactive, ref, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { reactive, ref, onMounted, computed, watch } from 'vue';
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';
@ -731,6 +731,7 @@ import { getRoleInfo } from '/@/utils/role-util';
//
const route = useRoute();
const router = useRouter();
onMounted(async () => {
await getLoginInfo();
@ -743,9 +744,18 @@ onMounted(async () => {
}
//
queryData();
await queryData();
loadAllEmployees();
handleRouteApplicationDetail(route.query.applicationId);
});
watch(
() => route.query.applicationId,
(applicationId) => {
handleRouteApplicationDetail(applicationId);
}
);
// ---------------------------- ----------------------------
const agreementModalRef = ref();
const pendingFormData = ref(null); //
@ -802,6 +812,33 @@ onMounted(async () => {
}
}
async function handleRouteApplicationDetail(applicationId) {
if (!applicationId) {
return;
}
try {
const detailResult = await serviceApplicationsApi.queryDetail(applicationId);
if (detailResult.data) {
await new Promise(resolve => setTimeout(resolve, 500));
if (formRef.value && typeof formRef.value.show === 'function') {
formRef.value.show(detailResult.data, true);
} else {
console.error('formRef 未初始化或 show 方法不存在');
}
}
} catch (error) {
console.error('消息跳转加载服务申报详情失败:', error);
} finally {
if (route.query.applicationId) {
const nextQuery = { ...route.query };
delete nextQuery.applicationId;
router.replace({ path: route.path, query: nextQuery });
}
}
}
// ---------------------------- ----------------------------
//
function onSubmit(data){

67
src/views/system/home/home-notice.vue

@ -1,60 +1,57 @@
<!--
* 首页的 通知公告
* 首页的 消息
*
-->
<template>
<default-home-card extra="更多" icon="SoundOutlined" title="通知公告" @extraClick="onMore">
<default-home-card extra="更多" icon="BellOutlined" title="消息通知" @extraClick="onMore">
<a-spin :spinning="loading">
<div class="content-wrapper" style="height: 280px">
<a-empty v-if="$lodash.isEmpty(data)" />
<ul v-else>
<li v-for="(item, index) in data" :key="index" :class="[item.viewFlag ? 'read' : 'un-read']">
<li v-for="(item, index) in data" :key="index" :class="[item.readFlag ? 'read' : 'un-read']">
<a-tooltip placement="top">
<template #title>
<span>{{ item.title }}</span>
</template>
<a class="content" @click="toDetail(item.noticeId)">
<a-badge :status="item.viewFlag ? 'default' : 'error'" />
<a class="content" @click="toDetail(item)">
<a-badge :status="item.readFlag ? 'default' : 'error'" />
{{ item.title }}
</a>
</a-tooltip>
<span class="time"> {{ item.publishDate }}</span>
<span class="time"> {{ item.createTime }}</span>
</li>
</ul>
</div>
</a-spin>
</default-home-card>
<!-- 消息详情弹框 -->
<header-message-detail-modal ref="messageDetailModalRef" @refresh="queryMessageList" />
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { noticeApi } from '/@/api/business/oa/notice-api';
import { messageApi } from '/@/api/support/message-api';
import { smartSentry } from '/@/lib/smart-sentry';
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
import HeaderMessageDetailModal from '/@/layout/components/header-user-space/header-message-detail-modal.vue';
const props = defineProps({
noticeTypeId: {
type: Number,
default: 1,
},
});
const queryForm = {
noticeTypeId: props.noticeTypeId,
pageNum: 1,
pageSize: 6,
searchCount: false,
};
const router = useRouter();
const messageDetailModalRef = ref();
let data = ref([]);
const loading = ref(false);
//
async function queryNoticeList() {
async function queryMessageList() {
try {
loading.value = true;
const result = await noticeApi.queryEmployeeNotice(queryForm);
data.value = result.data.list;
const result = await messageApi.queryMessage({
pageNum: 1,
pageSize: 6,
readFlag: null
});
data.value = result.data.list || [];
} catch (err) {
smartSentry.captureError(err);
} finally {
@ -63,23 +60,25 @@
}
onMounted(() => {
queryNoticeList();
queryMessageList();
});
//
function onMore() {
router.push({
path: '/oa/notice/notice-employee-list',
path: '/account',
query: { menuId: 'message' }
});
}
//
const router = useRouter();
function toDetail(noticeId) {
router.push({
path: '/oa/notice/notice-employee-detail',
query: { noticeId },
});
async function toDetail(item) {
if (item?.messageId) {
await messageApi.updateReadFlag(item.messageId);
//
queryMessageList();
}
//
messageDetailModalRef.value.show(item);
}
</script>
<style lang="less" scoped>

Loading…
Cancel
Save