Browse Source

fix:修改问题

master
“wangzihua” 3 months ago
parent
commit
10dc0389de
  1. 21
      src/api/business/service-applications/service-applications-api.js
  2. 365
      src/components/system/service-count/excel-statistics-detail.vue
  3. 157
      src/components/system/service-count/firm-statistics-detail.vue
  4. 4
      src/constants/business/erp/cost-const.js
  5. 4
      src/constants/system/review-const.js
  6. 115
      src/views/business/erp/service/service-applications-count.vue
  7. 577
      src/views/business/erp/service/service-applications-list.vue

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

@ -42,6 +42,20 @@ export const serviceApplicationsApi = {
return postRequest('/serviceApplications/review', param);
},
/**
* 批量审核 @author wzh
*/
batchReview: (param) => {
return postRequest('/serviceApplications/batchReview', param);
},
/**
* 批量上报到协会审核 @author wzh
*/
batchSubmitAsFirm: (param) => {
return postRequest('/serviceApplications/batchSubmitAsFirm', param);
},
/**
* 新增提交 @author wzh
*/
@ -119,4 +133,11 @@ export const serviceApplicationsApi = {
exportLawyerByDepartment: (params) => {
return getDownload('/serviceApplications/exportLawyerByDepartment', params);
},
/**
* 服务上报统计 @author
*/
reportStatistics: (params) => {
return postRequest('/serviceApplications/reportStatistics', params);
},
};

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

@ -0,0 +1,365 @@
<!--
* Excel样式统计报表组件
*
* @Author: wzh
* @Date: 2025-12-24 14:44:06
* @Copyright 1.0
-->
<template>
<div class="excel-statistics-detail">
<!-- 查询条件和导出按钮 -->
<div class="header-section">
<div class="query-section">
<a-form :model="localQueryForm" layout="inline">
<a-form-item label="年度">
<a-select v-model:value="localQueryForm.year" placeholder="请选择年度" style="width: 120px">
<a-select-option v-for="year in yearOptions" :key="year" :value="year">
{{ year }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="季度">
<a-select v-model:value="localQueryForm.quarter" placeholder="请选择季度" style="width: 120px">
<a-select-option v-for="quarter in quarterOptions" :key="quarter.value" :value="quarter.value">
{{ quarter.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="律师姓名">
<a-input v-model:value="localQueryForm.lawyerName" placeholder="请输入律师姓名" style="width: 150px" />
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleQuery" :loading="queryLoading">
<SearchOutlined />
查询
</a-button>
<a-button @click="handleReset" style="margin-left: 8px">
<ReloadOutlined />
重置
</a-button>
</a-form-item>
</a-form>
</div>
<div class="export-section">
<a-button type="primary" @click="handleExport" :loading="exportLoading">
<ExportOutlined />
导出Excel
</a-button>
</div>
</div>
<!-- Excel样式统计报表 -->
<div class="statistics-report-container">
<!-- 报表标题 -->
<div class="report-header">
<h1>律师服务统计报表</h1>
<div class="report-subtitle">统计时间{{ localQueryForm.year }}<span v-if="localQueryForm.quarter != null">{{ localQueryForm.quarter }}季度</span></div>
</div>
<!-- 报表表格 -->
<div class="report-table">
<!-- 表头 -->
<div class="report-row header-row">
<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>
<!-- 数据行 -->
<div v-for="(item, index) in tableData" :key="index" class="report-row data-row">
<div class="report-cell">{{ index + 1 }}</div>
<div class="report-cell">{{ item.lawyerName || '-' }}</div>
<div class="report-cell">{{ formatNumber(item.quarterlyServiceDuration) }}</div>
<div class="report-cell">{{ formatCurrency(item.quarterlyServiceCost) }}</div>
<div class="report-cell">{{ formatNumber(item.annualServiceDuration) }}</div>
<div class="report-cell">{{ formatCurrency(item.annualServiceCost) }}</div>
</div>
<!-- 汇总行 -->
<div v-if="summaryData" class="report-row summary-row">
<div class="report-cell">汇总</div>
<div class="report-cell">-</div>
<div class="report-cell">{{ formatNumber(summaryData.totalQuarterlyDuration) }}</div>
<div class="report-cell">{{ formatCurrency(summaryData.totalQuarterlyCost) }}</div>
<div class="report-cell">{{ formatNumber(summaryData.totalAnnualDuration) }}</div>
<div class="report-cell">{{ formatCurrency(summaryData.totalAnnualCost) }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
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';
//
const props = defineProps({
queryParams: {
type: Object,
default: () => ({})
},
tableData: {
type: Array,
default: () => []
}
});
const exportLoading = ref(false);
const queryLoading = ref(false);
//
const localQueryForm = reactive({
quarter: props.queryParams.quarter || null,
year: props.queryParams.year || new Date().getFullYear(),
lawyerName: props.queryParams.lawyerName || '',
firmName: props.queryParams.firmName || ''
});
//
const quarterOptions = [
{ label: '第一季度', value: 1 },
{ label: '第二季度', value: 2 },
{ label: '第三季度', value: 3 },
{ label: '第四季度', value: 4 }
];
// 5
const yearOptions = ref([]);
//
function initYearOptions() {
const currentYear = new Date().getFullYear();
for (let i = currentYear; i >= currentYear - 4; i--) {
yearOptions.value.push(i);
}
}
//
const tableData = ref(props.tableData || []);
//
const summaryData = computed(() => {
if (!tableData.value || tableData.value.length === 0) {
return null;
}
const summary = {
totalQuarterlyDuration: 0,
totalQuarterlyCost: 0,
totalAnnualDuration: 0,
totalAnnualCost: 0
};
tableData.value.forEach(item => {
summary.totalQuarterlyDuration += Number(item.quarterlyServiceDuration) || 0;
summary.totalQuarterlyCost += Number(item.quarterlyServiceCost) || 0;
summary.totalAnnualDuration += Number(item.annualServiceDuration) || 0;
summary.totalAnnualCost += Number(item.annualServiceCost) || 0;
});
return summary;
});
//
function formatNumber(value) {
if (value === null || value === undefined) return '-';
const num = Number(value);
return isNaN(num) ? '-' : num.toFixed(2);
}
//
function formatCurrency(value) {
if (value === null || value === undefined) return '-';
const num = Number(value);
return isNaN(num) ? '-' : `¥${num.toFixed(2)}`;
}
//
async function handleQuery() {
queryLoading.value = true;
try {
console.log('开始查询律师统计数据...');
const params = {
...localQueryForm,
pageNum: 1,
pageSize: 500 //
};
//
const res = await serviceApplicationsApi.statistics(params);
console.log('律师统计查询结果:', res);
if (res.data && res.data.list && Array.isArray(res.data.list)) {
tableData.value = res.data.list.map(item => ({
...item,
//
lawyerName: item.lawyerName || '-',
certificateNumber: item.certificateNumber || '-',
quarterlyServiceDuration: item.quarterlyServiceDuration || 0,
quarterlyServiceCost: item.quarterlyServiceCost || 0,
annualServiceDuration: item.annualServiceDuration || 0,
annualServiceCost: item.annualServiceCost || 0
}));
//
// message.success(` ${res.data.total} `);
} else {
tableData.value = [];
message.warning('暂无数据');
}
} catch (error) {
message.error('查询失败');
console.error('查询律师统计数据失败:', error);
tableData.value = [];
} finally {
queryLoading.value = false;
}
}
//
function handleReset() {
localQueryForm.quarter = null;
localQueryForm.year = new Date().getFullYear();
localQueryForm.lawyerName = '';
localQueryForm.firmName = '';
tableData.value = [];
}
// Excel
async function handleExport() {
if (tableData.value.length === 0) {
message.warning('暂无数据可导出');
return;
}
exportLoading.value = true;
try {
console.log('开始导出律师统计详情...');
const exportParams = {
quarter: localQueryForm.quarter,
year: localQueryForm.year,
lawyerName: localQueryForm.lawyerName,
firmName: localQueryForm.firmName
};
//
await serviceApplicationsApi.exportLawyer(exportParams);
message.success('导出成功');
console.log('律师统计详情导出成功');
} catch (error) {
message.error('导出失败');
console.error('导出律师统计详情失败:', error);
} finally {
exportLoading.value = false;
}
}
onMounted(() => {
initYearOptions();
//
if (!props.tableData || props.tableData.length === 0) {
handleQuery();
}
console.log('Excel统计详情组件已加载');
});
</script>
<style scoped>
.excel-statistics-detail {
padding: 0 16px;
}
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 16px 0;
}
.query-section {
flex: 1;
}
.export-section {
margin-left: 16px;
}
.statistics-report-container {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.report-header {
background: #f5f5f5;
padding: 20px;
text-align: center;
border-bottom: 1px solid #ddd;
}
.report-header h1 {
margin: 0 0 8px 0;
font-size: 24px;
color: #333;
font-weight: 600;
}
.report-subtitle {
font-size: 14px;
color: #666;
}
.report-table {
width: 100%;
border-collapse: collapse;
}
.report-row {
display: flex;
width: 100%;
}
.report-cell {
flex: 1;
padding: 12px;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
text-align: center;
min-height: 50px;
box-sizing: border-box;
font-size: 14px;
}
.report-cell:last-child {
border-right: none;
}
.header-row {
background-color: #0044cc;
color: white;
font-weight: 600;
}
.data-row {
background-color: white;
color: #333;
}
.summary-row {
background-color: #f0f0f0;
color: #333;
font-weight: 600;
}
/* 最后一行单元格没有下边框 */
.report-row:last-child .report-cell {
border-bottom: none;
}
</style>

157
src/components/system/service-count/firm-statistics-detail.vue

@ -1,15 +1,11 @@
<template>
<div class="firm-statistics-detail">
<!-- 返回按钮和标题 -->
<!-- 返回按钮 -->
<div class="detail-header">
<a-button type="link" @click="handleBack" class="back-btn">
<ArrowLeftOutlined />
返回查询
</a-button>
<div class="detail-title">
<h2>律所统计详情</h2>
<div class="detail-subtitle">统计时间{{ queryParams.year }}<span v-if="queryParams.quarter != null">{{ queryParams.quarter }}季度</span></div>
</div>
</div>
<!-- 导出按钮 -->
@ -20,42 +16,44 @@
</a-button>
</div>
<!-- Excel样式表格 -->
<div class="excel-table-container">
<div class="excel-table-header">
<div class="excel-table-title">律所服务统计报表</div>
<div class="excel-table-subtitle">统计时间{{ queryParams.year }}<span v-if="queryParams.quarter != null">{{ queryParams.quarter }}季度</span></div>
<!-- 统计报表 -->
<div class="statistics-report-container">
<!-- 报表标题 -->
<div class="report-header">
<h1>律所服务统计报表</h1>
<div class="report-subtitle">统计时间{{ queryParams.year }}<span v-if="queryParams.quarter != null">{{ queryParams.quarter }}季度</span></div>
</div>
<div class="excel-table">
<!-- 报表表格 -->
<div class="report-table">
<!-- 表头 -->
<div class="excel-row excel-header">
<div class="excel-cell" style="width: 15%;">序号</div>
<div class="excel-cell" style="width: 25%;">律所名称</div>
<div class="excel-cell" style="width: 15%;">季度累计服务时长</div>
<div class="excel-cell" style="width: 15%;">季度累计服务成本</div>
<div class="excel-cell" style="width: 15%;">年度累计服务时长</div>
<div class="excel-cell" style="width: 15%;">年度累计服务成本</div>
<div class="report-row header-row">
<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>
<!-- 数据行 -->
<div v-for="(item, index) in tableData" :key="index" class="excel-row excel-data">
<div class="excel-cell" style="width: 15%;">{{ index + 1 }}</div>
<div class="excel-cell" style="width: 25%;">{{ item.firmName || '-' }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatNumber(item.quarterlyServiceDuration) }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatCurrency(item.quarterlyServiceCost) }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatNumber(item.annualServiceDuration) }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatCurrency(item.annualServiceCost) }}</div>
<div v-for="(item, index) in tableData" :key="index" class="report-row data-row">
<div class="report-cell">{{ index + 1 }}</div>
<div class="report-cell">{{ item.firmName || '-' }}</div>
<div class="report-cell">{{ formatNumber(item.quarterlyServiceDuration) }}</div>
<div class="report-cell">{{ formatCurrency(item.quarterlyServiceCost) }}</div>
<div class="report-cell">{{ formatNumber(item.annualServiceDuration) }}</div>
<div class="report-cell">{{ formatCurrency(item.annualServiceCost) }}</div>
</div>
<!-- 汇总行 -->
<div v-if="summaryData" class="excel-row excel-summary">
<div class="excel-cell" style="width: 15%;">汇总</div>
<div class="excel-cell" style="width: 25%;">-</div>
<div class="excel-cell" style="width: 15%;">{{ formatNumber(summaryData.totalQuarterlyDuration) }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatCurrency(summaryData.totalQuarterlyCost) }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatNumber(summaryData.totalAnnualDuration) }}</div>
<div class="excel-cell" style="width: 15%;">{{ formatCurrency(summaryData.totalAnnualCost) }}</div>
<div v-if="summaryData" class="report-row summary-row">
<div class="report-cell">汇总</div>
<div class="report-cell">-</div>
<div class="report-cell">{{ formatNumber(summaryData.totalQuarterlyDuration) }}</div>
<div class="report-cell">{{ formatCurrency(summaryData.totalQuarterlyCost) }}</div>
<div class="report-cell">{{ formatNumber(summaryData.totalAnnualDuration) }}</div>
<div class="report-cell">{{ formatCurrency(summaryData.totalAnnualCost) }}</div>
</div>
</div>
</div>
@ -161,117 +159,92 @@ onMounted(() => {
</script>
<style scoped>
.firm-statistics-detail {
padding: 0;
padding: 0 16px;
}
.detail-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding: 16px;
background: #f5f5f5;
border-radius: 4px;
padding: 16px 0;
}
.back-btn {
margin-right: 20px;
color: #1e3a8a;
}
.detail-title h2 {
margin: 0;
font-size: 20px;
color: #333;
}
.detail-subtitle {
margin-top: 4px;
font-size: 14px;
color: #666;
}
.export-section {
margin-bottom: 20px;
text-align: right;
padding: 0 16px;
}
.excel-table-container {
border: 2px solid #d9d9d9;
.statistics-report-container {
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
}
.excel-table-header {
.report-header {
background: #f5f5f5;
padding: 16px;
border-bottom: 1px solid #d9d9d9;
padding: 20px;
text-align: center;
border-bottom: 1px solid #ddd;
}
.excel-table-title {
font-size: 18px;
font-weight: 600;
.report-header h1 {
margin: 0 0 8px 0;
font-size: 24px;
color: #333;
margin-bottom: 4px;
font-weight: 600;
}
.excel-table-subtitle {
.report-subtitle {
font-size: 14px;
color: #666;
}
.excel-table {
.report-table {
width: 100%;
border-collapse: collapse;
}
.excel-row {
.report-row {
display: flex;
border-bottom: 1px solid #d9d9d9;
}
.excel-row:last-child {
border-bottom: none;
}
.excel-header {
background: #e6f7ff;
font-weight: 600;
}
.excel-summary {
background: #f0f0f0;
font-weight: 600;
width: 100%;
}
.excel-cell {
.report-cell {
flex: 1;
padding: 12px;
border-right: 1px solid #d9d9d9;
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid #ddd;
border-bottom: 1px solid #ddd;
text-align: center;
min-height: 50px;
box-sizing: border-box;
font-size: 14px;
}
.excel-cell:last-child {
.report-cell:last-child {
border-right: none;
}
.excel-header .excel-cell {
background: #1e3a8a;
.header-row {
background-color: #0044cc;
color: white;
border-right: 1px solid #40a9ff;
font-weight: 600;
}
.excel-data .excel-cell {
background: white;
.data-row {
background-color: white;
color: #333;
}
.excel-summary .excel-cell {
background: #fafafa;
.summary-row {
background-color: #f0f0f0;
color: #333;
font-weight: 600;
}
/* 最后一行单元格没有下边框 */
.report-row:last-child .report-cell {
border-bottom: none;
}
</style>

4
src/constants/business/erp/cost-const.js

@ -30,11 +30,11 @@ export const REVIEW_STATUS_ENUM = {
},
SUBMITTED: {
value: 1,
desc: '待审',
desc: '待审',
},
APPROVED: {
value: 2,
desc: '审中',
desc: '审中',
},
REJECTED: {
value: 3,

4
src/constants/system/review-const.js

@ -8,11 +8,11 @@ export const REVIEW_ENUM = {
},
APPROVAL: {
value: 1,
desc: '待审',
desc: '待审',
},
REVIEW: {
value: 2,
desc: '审中',
desc: '审中',
},
PASS: {
value: 3,

115
src/views/business/erp/service/service-applications-count.vue

@ -8,66 +8,22 @@
<template>
<div class="service-applications-count">
<div v-if="!loginInfo">加载中...</div>
<div v-else-if="isAdmin" class="admin-excel-view">
<!-- 查询页面只在没有显示结果时显示 -->
<div v-if="!showResultView">
<a-card title="机构数据查询" class="admin-excel-card">
<div class="excel-description">
<p>作为律师协会您可以查看机构级别的统计数据数据以Excel表格样式展示</p>
<p>请选择需要查看的季度和年度然后点击查询按钮</p>
</div>
<a-form-item label="年度" :label-col="{ span: 4 }" :wrapper-col="{ span: 8 }">
<a-select v-model:value="queryForm.year" placeholder="请选择年度" style="width: 200px">
<a-select-option :value="2026">2026</a-select-option>
<a-select-option :value="2027">2027</a-select-option>
<a-select-option :value="2028">2028</a-select-option>
<a-select-option :value="2029">2029</a-select-option>
<a-select-option :value="2030">2030</a-select-option>
</a-select>
</a-form-item>
<a-form :model="queryForm" layout="horizontal" class="query-form">
<a-form-item label="季度" :label-col="{ span: 4 }" :wrapper-col="{ span: 8 }">
<a-select v-model:value="queryForm.quarter" placeholder="请选择季度" style="width: 200px">
<a-select-option :value="1">第一季度</a-select-option>
<a-select-option :value="2">第二季度</a-select-option>
<a-select-option :value="3">第三季度</a-select-option>
<a-select-option :value="4">第四季度</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="机构名称" :label-col="{ span: 4 }" :wrapper-col="{ span: 8 }">
<!--<a-input v-model:value="queryForm.firmName" placeholder="请输入机构名称(可选)" style="width: 300px; margin-bottom: 8px;" />-->
<div class="department-selector">
<DepartmentTreeSelect v-model:value="queryForm.firmId" style="width: 300px;" />
</div>
</a-form-item>
<a-form-item :wrapper-col="{ span: 8, offset: 4 }">
<a-button type="primary" @click="handleQuery" :loading="queryLoading" style="margin-right: 8px;">
<SearchOutlined />
查询数据
</a-button>
<a-button @click="handleReset" style="margin-right: 8px;">
<ReloadOutlined />
重置
</a-button>
<!--<a-button type="primary" @click="handleExport" :loading="exportLoading">
<ExportOutlined />
导出Excel
</a-button>-->
</a-form-item>
</a-form>
<!-- 空状态 -->
<div v-if="hasQueried && !showResultView" class="empty-state">
<a-empty description="暂无数据" />
</div>
</a-card>
<!-- CEO角色直接显示律所统计组件 -->
<div v-else-if="isCeo" class="admin-excel-view">
<LawFirmStatistics />
</div>
<!-- 详情页面查询成功后完全替换查询页面 -->
<div v-else class="result-view">
<FirmStatisticsDetail
<!-- CTO角色直接显示数据 -->
<div v-else-if="isCto" class="admin-excel-view">
<!-- 加载状态 -->
<div v-if="queryLoading" class="loading-container">
<a-spin size="large" />
<div style="margin-top: 16px;">加载数据中...</div>
</div>
<!-- 数据展示 -->
<div v-else>
<ExcelStatisticsDetail
:query-params="queryForm"
:table-data="tableData"
@back="handleBackToQuery"
@ -77,18 +33,19 @@
<a-tabs v-else v-model:activeKey="activeTab" type="card">
<!-- 季度统计 -->
<a-tab-pane key="quarter" tab="季度统计">
<QuarterStatistics :is-admin="isAdmin" />
<QuarterStatistics :is-admin="isCeo" />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue';
import { ref, onMounted, reactive, watch } from 'vue';
import { ExportOutlined, SearchOutlined, ReloadOutlined, ArrowLeftOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import QuarterStatistics from '/@/components/system/service-count/quarter-statistics.vue';
import FirmStatisticsDetail from '/@/components/system/service-count/firm-statistics-detail.vue';
import LawFirmStatistics from '/@/components/system/service-count/law-firm-statistics.vue';
import ExcelStatisticsDetail from '/@/components/system/service-count/excel-statistics-detail.vue';
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
import { loginApi } from '/@/api/system/login-api';
import { serviceApplicationsApi } from '/@/api/business/service-applications/service-applications-api';
@ -96,6 +53,8 @@ import { serviceApplicationsApi } from '/@/api/business/service-applications/ser
const activeTab = ref('quarter');
const loginInfo = ref(null);
const isAdmin = ref(false);
const isCeo = ref(false);
const isCto = ref(false);
const queryLoading = ref(false);
const exportLoading = ref(false);
const hasQueried = ref(false);
@ -118,10 +77,12 @@ async function getLoginInfo() {
loginInfo.value = res.data;
console.log('登录信息:', res.data);
// admin
const loginNameCheck = res.data?.loginName === 'admin';
const userTypeCheck = res.data?.userType === 1;
isAdmin.value = loginNameCheck || userTypeCheck;
// CEOCTO
const role = (res.data?.roleCode || res.data?.roleName || '').toLowerCase();
isCeo.value = role === 'ceo';
isCto.value = role === 'cto';
// isAdmin使isCeoisCto
isAdmin.value = isCeo.value || isCto.value;
console.log('管理员状态判断:');
console.log('- loginName:', res.data?.loginName);
@ -138,6 +99,14 @@ async function getLoginInfo() {
//
getLoginInfo();
// CTO
watch(() => loginInfo.value, (newVal) => {
if (newVal && isCto.value) {
// CTO
handleQuery();
}
}, { deep: true });
onMounted(() => {
//
});
@ -197,7 +166,8 @@ async function handleQuery() {
//
showResultView.value = true;
hasQueried.value = true;
message.success(`查询成功,共 ${res.data.total} 条数据`);
//
// message.success(` ${res.data.total} `);
} else {
tableData.value = [];
summaryData.value = null;
@ -288,10 +258,15 @@ function calculateSummary() {
summaryData.value = summary;
}
//
function handleBackToQuery() {
showResultView.value = false;
}
//
// CTO
watch(() => isCto.value, (newVal) => {
if (newVal && loginInfo.value) {
// CTO
handleQuery();
}
});
</script>
<style scoped>

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

@ -6,6 +6,8 @@
* @Copyright 1.0
-->
<template>
<!-- 默认直接显示服务记录列表所有角色都显示 -->
<div>
<!---------- 查询表单form begin ----------->
<a-form class="smart-query-form">
<a-row class="smart-query-form-row">
@ -39,6 +41,20 @@
<a-form-item label="服务对象负责人姓名" class="smart-query-form-item">
<a-input style="width: 150px" v-model:value="queryForm.managerName" placeholder="服务对象负责人姓名" />
</a-form-item>
<a-form-item label="执业机构审核状态" class="smart-query-form-item">
<a-select style="width: 150px" v-model:value="queryForm.firmAuditStatus" placeholder="请选择审核状态">
<a-select-option v-for="status in Object.values(REVIEW_ENUM)" :key="status.value" :value="status.value">
{{ status.desc }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="协会审核状态" class="smart-query-form-item">
<a-select style="width: 150px" v-model:value="queryForm.associationAuditStatus" placeholder="请选择审核状态">
<a-select-option v-for="status in Object.values(REVIEW_ENUM)" :key="status.value" :value="status.value">
{{ status.desc }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="smart-query-form-item smart-margin-left10">
<a-button v-privilege="'serviceApplications:query'" type="primary" @click="onSearch">
<template #icon>
@ -61,7 +77,8 @@
<!---------- 表格操作行 begin ----------->
<a-row class="smart-table-btn-block">
<div class="smart-table-operate-block">
<a-button v-privilege="'serviceApplications:add'" @click="showForm" type="primary">
<a-button v-if="isNotUser" @click="showForm" type="primary">
<template #icon>
<PlusOutlined />
</template>
@ -73,6 +90,33 @@
</template>
批量提交
</a-button>
<!-- 批量审核按钮不是user角色显示 -->
<a-button
v-if="isNotUser"
@click="batchAudit"
type="primary"
:disabled="selectedRowKeyList.length == 0"
>
<template #icon>
<CheckCircleOutlined />
</template>
批量审核
</a-button>
<!-- 批量上报按钮cto角色显示 -->
<a-button
v-if="isCtoRole"
@click="batchReport"
type="primary"
:disabled="selectedRowKeyList.length == 0"
>
<template #icon>
<UploadOutlined />
</template>
批量上报
</a-button>
<a-button @click="confirmBatchDelete" type="primary" danger :disabled="selectedRowKeyList.length == 0">
<template #icon>
<DeleteOutlined />
@ -96,7 +140,7 @@
<!---------- 表格 begin ----------->
<a-table
size="small"
:scroll="{ y: 800 }"
:scroll="{ x: 1800, y: 800 }"
:dataSource="tableData"
:columns="columns"
rowKey="applicationId"
@ -106,14 +150,13 @@
:row-selection="{ selectedRowKeys: selectedRowKeyList, onChange: onSelectChange }"
>
<template #bodyCell="{ text, record, column }">
<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.firmAuditStatus === 4) && record.userId === loginInfo?.userId" @click="showForm(record)" type="link">编辑</a-button>
<a-button v-if="(record.firmAuditStatus === 0 && record.userId === loginInfo?.userId)" @click="onSubmit(record)" type="link">提交</a-button>
<a-button v-if="(record.firmAuditStatus === 1 || record.firmAuditStatus === 2) && loginInfo?.dataScopeView === 1" @click="showAuditModal(record)" type="link">审核</a-button>
<a-button v-if="canAuditRecord(record)" @click="showAuditModal(record)" type="link">审核</a-button>
<a-button v-if="canRejectRecord(record)" @click="showRejectModal(record)" danger type="link">驳回</a-button>
<a-button v-if="record.firmAuditStatus === 0 && record.userId === loginInfo?.userId" @click="onDelete(record)" danger type="link">删除</a-button>
</div>
</template>
@ -144,6 +187,51 @@
</a-modal>
<!---------- 审核弹框 end ----------->
<!---------- 批量审核弹框 begin ----------->
<a-modal
v-model:visible="batchAuditModalVisible"
title="批量审核服务申报"
:confirm-loading="batchAuditLoading"
@ok="handleBatchAudit"
@cancel="handleBatchAuditCancel"
width="500px"
>
<a-form :model="batchAuditForm" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="审核结果" required>
<a-radio-group v-model:value="batchAuditForm.auditResult">
<a-radio :value="3">同意</a-radio>
<a-radio :value="4">拒绝</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="选中记录">
<span style="color: #1890ff;">{{ selectedRowKeyList.length }} 条记录</span>
</a-form-item>
</a-form>
</a-modal>
<!---------- 批量审核弹框 end ----------->
<!---------- 驳回弹框 begin ----------->
<a-modal
v-model:visible="rejectModalVisible"
title="驳回服务申报"
:confirm-loading="rejectLoading"
@ok="handleReject"
@cancel="handleRejectCancel"
width="500px"
>
<a-form :model="rejectForm" layout="vertical">
<a-form-item label="驳回原因" required>
<a-textarea
v-model:value="rejectForm.rejectReason"
placeholder="请输入驳回原因"
rows="4"
style="resize: vertical;"
/>
</a-form-item>
</a-form>
</a-modal>
<!---------- 驳回弹框 end ----------->
<div class="smart-query-table-page">
<a-pagination
showSizeChanger
@ -195,21 +283,22 @@
</a-modal>
</a-card>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue';
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 { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
import { smartSentry } from '/@/lib/smart-sentry';
import TableOperator from '/@/components/support/table-operator/index.vue';
import ServiceApplicationsForm from './service-applications-form.vue';
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
import { employeeApi } from '/@/api/system/employee-api';
import { REVIEW_ENUM } from '/@/constants/system/review-const';
import { PlusOutlined, DeleteOutlined, SendOutlined, ImportOutlined, ExportOutlined, DownloadOutlined, UploadOutlined } from '@ant-design/icons-vue';
import { loginApi } from '/@/api/system/login-api';
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 { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
import { smartSentry } from '/@/lib/smart-sentry';
import TableOperator from '/@/components/support/table-operator/index.vue';
import ServiceApplicationsForm from './service-applications-form.vue';
import DepartmentTreeSelect from '/@/components/system/department-tree-select/index.vue';
import { employeeApi } from '/@/api/system/employee-api';
import { REVIEW_ENUM } from '/@/constants/system/review-const';
import { PlusOutlined, DeleteOutlined, SendOutlined, ImportOutlined, ExportOutlined, DownloadOutlined, UploadOutlined, CheckCircleOutlined } from '@ant-design/icons-vue';
import { loginApi } from '/@/api/system/login-api';
// ---------------------------- ----------------------------
const columns = ref([
@ -263,11 +352,6 @@
dataIndex: 'organizerPhone',
ellipsis: true,
},
{
title: '工作量换算得分',
dataIndex: 'workloadScore',
ellipsis: true,
},
{
title: '执业机构审核状态',
dataIndex: 'firmAuditStatus',
@ -287,6 +371,30 @@
dataIndex: 'firmAuditTime',
ellipsis: true,
},
{
title: '协会审核状态',
dataIndex: 'associationAuditStatus',
ellipsis: true,
customRender: ({ text }) => {
const status = Object.values(REVIEW_ENUM).find(item => item.value === text);
return status ? status.desc : text;
},
},
{
title: '协会审核意见',
dataIndex: 'associationAuditOpinion',
ellipsis: true,
},
{
title: '协会审核人',
dataIndex: 'associationAuditUserName',
ellipsis: true,
},
{
title: '协会审核时间',
dataIndex: 'associationAuditTime',
ellipsis: true,
},
{
title: '操作',
dataIndex: 'action',
@ -306,6 +414,8 @@
serviceEnd: undefined, //
organizerName: undefined, //
managerName: undefined, //
firmAuditStatus: undefined, //
associationAuditStatus: undefined, //
};
// form
const queryForm = reactive({ ...queryFormState });
@ -322,13 +432,215 @@
const auditLoading = ref(false);
const currentAuditRecord = ref(null);
const auditForm = reactive({
auditResult: 3, //
auditRemark: ''
});
//
const batchAuditModalVisible = ref(false);
const batchAuditLoading = ref(false);
const batchAuditForm = reactive({
applicationIds: '', // ID
});
//
const loginInfo = ref(null);
// useruser
const isNotUser = ref(false);
// ctocto
const isCtoRole = ref(false);
//
const isAssociationRole = ref(false);
// CEOCEO
const isCeo = ref(false);
//
async function getLoginInfo() {
try {
const res = await loginApi.getLoginInfo();
loginInfo.value = res.data;
checkUserRole(); //
console.log('登录信息:', res.data);
} catch (e) {
console.error('获取登录信息失败:', e);
}
}
//
function checkUserRole() {
if (loginInfo.value) {
//
const userRole = loginInfo.value.roleCode || loginInfo.value.roleName || '';
const roleLower = userRole.toLowerCase();
// user
isNotUser.value = roleLower != 'user';
// cto
isCtoRole.value = roleLower === 'cto';
//
//
isAssociationRole.value = roleLower.includes('协会') ||
roleLower.includes('association') ||
roleLower.includes('律协') ||
roleLower.includes('律师协会');
// CEO
isCeo.value = roleLower === 'ceo';
console.log('用户角色:', userRole, '不是user:', isNotUser.value, '是cto:', isCtoRole.value, '是协会角色:', isAssociationRole.value, '是CEO:', isCeo.value);
}
}
//
function batchAudit() {
if (selectedRowKeyList.value.length === 0) {
message.warning('请选择要审核的记录');
return;
}
//
const canAuditRecords = tableData.value.filter(record =>
selectedRowKeyList.value.includes(record.applicationId) && canAuditRecord(record)
);
if (canAuditRecords.length === 0) {
message.warning('选中的记录不符合审核条件,请选择待审核状态的记录');
return;
}
//
batchAuditModalVisible.value = true;
batchAuditForm.applicationIds = selectedRowKeyList.value.join(',');
}
//
function canAuditRecord(record) {
if (!loginInfo.value || !isNotUser.value) return false;
const userRole = (loginInfo.value.roleCode || loginInfo.value.roleName || '').toLowerCase();
// ceo(1)
if (userRole === 'ceo') {
return record.associationAuditStatus === 1;
}
// cto(1)
if (userRole === 'cto') {
return record.firmAuditStatus === 1;
}
return false;
}
//
async function handleBatchAudit() {
if (!batchAuditForm.auditResult) {
message.warning('请选择审核结果');
return;
}
//
const canAuditRecords = tableData.value.filter(record =>
selectedRowKeyList.value.includes(record.applicationId) && canAuditRecord(record)
);
if (canAuditRecords.length !== selectedRowKeyList.value.length) {
message.warning('部分选中的记录不符合审核条件,请重新选择');
return;
}
batchAuditLoading.value = true;
try {
await serviceApplicationsApi.batchReview(batchAuditForm);
message.success('批量审核成功');
batchAuditModalVisible.value = false;
queryData(); //
selectedRowKeyList.value = []; //
//
batchAuditForm.auditResult = undefined;
batchAuditForm.auditRemark = '';
batchAuditForm.applicationIds = '';
} catch (error) {
console.error('批量审核失败:', error);
message.error('批量审核失败');
} finally {
batchAuditLoading.value = false;
}
}
//
function handleBatchAuditCancel() {
batchAuditModalVisible.value = false;
batchAuditForm.auditResult = undefined;
batchAuditForm.auditRemark = '';
batchAuditForm.applicationIds = '';
}
//
function batchReport() {
if (selectedRowKeyList.value.length === 0) {
message.warning('请选择要上报的记录');
return;
}
//
const canReportRecords = tableData.value.filter(record =>
selectedRowKeyList.value.includes(record.applicationId) && canReportRecord(record)
);
if (canReportRecords.length === 0) {
message.warning('选中的记录不符合上报条件');
return;
}
//
Modal.confirm({
title: '批量上报确认',
content: `确定要将选中的 ${selectedRowKeyList.value.length} 条记录上报到协会审核吗?`,
onOk: handleBatchReport,
okText: '确定上报',
cancelText: '取消'
});
}
//
function canReportRecord(record) {
// CTO3
// 04
return record.firmAuditStatus === 3 &&
(record.associationAuditStatus === 0 || record.associationAuditStatus === 4);
}
//
async function handleBatchReport() {
try {
SmartLoading.show();
// ValidateList<Long> idListID
const res = await serviceApplicationsApi.batchSubmitAsFirm(selectedRowKeyList.value);
if (res.data) {
message.success('批量上报成功');
//
selectedRowKeyList.value = [];
//
queryData();
} else {
message.error('批量上报失败');
}
} catch (error) {
console.error('批量上报失败:', error);
message.error('批量上报失败');
smartSentry.captureError(error);
} finally {
SmartLoading.hide();
}
}
//
async function loadAllEmployees() {
try {
@ -351,6 +663,27 @@
queryData();
}
//
function processQueryParams(params) {
const processedParams = { ...params };
//
if (params.firmAuditTimeRange && params.firmAuditTimeRange.length === 2) {
processedParams.firmAuditTimeStart = params.firmAuditTimeRange[0];
processedParams.firmAuditTimeEnd = params.firmAuditTimeRange[1];
delete processedParams.firmAuditTimeRange;
}
//
if (params.associationAuditTimeRange && params.associationAuditTimeRange.length === 2) {
processedParams.associationAuditTimeStart = params.associationAuditTimeRange[0];
processedParams.associationAuditTimeEnd = params.associationAuditTimeRange[1];
delete processedParams.associationAuditTimeRange;
}
return processedParams;
}
//
function onSearch(){
queryForm.pageNum = 1;
@ -361,7 +694,9 @@
async function queryData() {
tableLoading.value = true;
try {
let queryResult = await serviceApplicationsApi.queryPage(queryForm);
//
let query = processQueryParams({ ...queryForm });
let queryResult = await serviceApplicationsApi.queryPage(query);
tableData.value = queryResult.data.list;
total.value = queryResult.data.total;
} catch (e) {
@ -373,20 +708,21 @@
onMounted(async () => {
queryData();
await getLoginInfo();
});
//
async function getLoginInfo() {
try {
const res = await loginApi.getLoginInfo();
loginInfo.value = res.data;
} catch (error) {
console.error('获取登录信息失败:', error);
}
// sessionStorageserviceFirmId
const firmId = sessionStorage.getItem('serviceFirmId');
if (firmId) {
// firmId
queryForm.firmId = firmId;
// sessionStoragefirmId使
sessionStorage.removeItem('serviceFirmId');
}
//
queryData();
});
// ---------------------------- / ----------------------------
const formRef = ref();
@ -509,12 +845,12 @@ function showAuditModal(record) {
}
//
function handleAuditCancel() {
function handleAuditCancel() {
auditModalVisible.value = false;
currentAuditRecord.value = null;
auditForm.auditResult = 3;
auditForm.auditRemark = '';
}
}
// ---------------------------- ----------------------------
@ -660,6 +996,171 @@ function handleAuditCancel() {
}
}
//
//
// ---------------------------- CEO ----------------------------
// CEO
const showFirmStatistics = ref(true);
const selectedFirmData = ref(null);
//
function backToFirmStatistics() {
console.log('返回律所统计,当前showFirmStatistics值:', showFirmStatistics.value);
showFirmStatistics.value = true;
selectedFirmData.value = null;
//
queryForm.firmId = undefined;
queryForm.year = undefined;
queryForm.quarter = undefined;
//
queryForm.pageNum = 1;
console.log('返回后showFirmStatistics值:', showFirmStatistics.value);
//
queryData();
}
//
function handleFirmSelected(firmData) {
console.log('选择的律所:', firmData);
console.log('当前showFirmStatistics值:', showFirmStatistics.value);
//
selectedFirmData.value = firmData;
//
queryForm.firmId = firmData.firmId;
//
if (firmData.year) {
queryForm.year = firmData.year;
}
if (firmData.quarter) {
queryForm.quarter = firmData.quarter;
}
//
queryForm.pageNum = 1;
//
showFirmStatistics.value = false;
console.log('切换后showFirmStatistics值:', showFirmStatistics.value);
//
queryData();
}
// ---------------------------- ----------------------------
//
const rejectModalVisible = ref(false);
const rejectLoading = ref(false);
const currentRejectRecord = ref(null);
const rejectForm = reactive({
rejectReason: '',
approvalRemark: ''
});
//
function canRejectRecord(record) {
// 1. CEOCTO
if (!isAssociationRole.value && !isCeo.value && !isCtoRole.value) {
console.log('驳回按钮不显示:不是协会角色、CEO角色或CTO角色');
return false;
}
// 2.
if (isAssociationRole.value || isCeo.value) {
// CEO318
if (record.associationAuditStatus !== 3) {
console.log('驳回按钮不显示:协会审核状态不是通过', record.associationAuditStatus);
return false;
}
//
if (!record.associationAuditTime) {
console.log('驳回按钮不显示:没有协会审核时间');
return false;
}
// 18
const auditDate = new Date(record.associationAuditTime);
const now = new Date();
const monthsDiff = (now.getFullYear() - auditDate.getFullYear()) * 12 + (now.getMonth() - auditDate.getMonth());
console.log('协会审核时间:', record.associationAuditTime, '月份差:', monthsDiff);
const canReject = monthsDiff <= 18;
if (!canReject) {
console.log('驳回按钮不显示:超过18个月');
}
return canReject;
} else if (isCtoRole.value) {
// CTO4
if (record.associationAuditStatus !== 4) {
console.log('驳回按钮不显示:协会审核状态不是驳回', record.associationAuditStatus);
return false;
}
return true;
}
return false;
}
//
function showRejectModal(record) {
currentRejectRecord.value = record;
rejectForm.rejectReason = '';
rejectForm.approvalRemark = '';
rejectModalVisible.value = true;
}
//
async function handleReject() {
if (!currentRejectRecord.value) {
message.error('未选择驳回记录');
return;
}
if (!rejectForm.rejectReason.trim()) {
message.warning('请输入驳回原因');
return;
}
rejectLoading.value = true;
const rejectData = {
applicationId: currentRejectRecord.value.applicationId,
associationAuditStatus: 4, //
associationAuditOpinion: rejectForm.rejectReason,
approvalRemark: rejectForm.approvalRemark
};
try {
await serviceApplicationsApi.review(rejectData);
message.success('驳回成功');
rejectModalVisible.value = false;
queryData();
} catch (error) {
message.error('驳回失败');
console.error('驳回失败:', error);
} finally {
rejectLoading.value = false;
}
}
//
function handleRejectCancel() {
rejectModalVisible.value = false;
currentRejectRecord.value = null;
rejectForm.rejectReason = '';
rejectForm.approvalRemark = '';
}
</script>

Loading…
Cancel
Save