7 changed files with 1048 additions and 213 deletions
@ -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> |
||||
Loading…
Reference in new issue