|
@@ -0,0 +1,412 @@
|
|
|
|
+<template>
|
|
|
|
+ <el-container class="report-view panel-block">
|
|
|
|
+ <el-header class="sa-header">
|
|
|
|
+ <!-- 简化搜索组件 -->
|
|
|
|
+ <div class="search-container">
|
|
|
|
+ <sa-search-simple
|
|
|
|
+ :searchFields="searchFields"
|
|
|
|
+ :defaultValues="defaultSearchValues"
|
|
|
|
+ @search="(val) => getData(1, val)"
|
|
|
|
+ @reset="refreshData"
|
|
|
|
+ >
|
|
|
|
+ <template #custom="{ data }">
|
|
|
|
+ <el-form-item label="">
|
|
|
|
+ <el-date-picker
|
|
|
|
+ v-model="data.createTime"
|
|
|
|
+ type="daterange"
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
+ format="YYYY-MM-DD"
|
|
|
|
+ range-separator="至"
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
+ :editable="false"
|
|
|
|
+ :disabled-date="disabledDate"
|
|
|
|
+ style="width: 300px"
|
|
|
|
+ />
|
|
|
|
+ </el-form-item>
|
|
|
|
+ </template>
|
|
|
|
+ </sa-search-simple>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="sa-title sa-flex sa-row-between">
|
|
|
|
+ <div class="label sa-flex">财务报表</div>
|
|
|
|
+ <div>
|
|
|
|
+ <el-button class="sa-button-refresh" icon="RefreshRight" @click="refreshData"></el-button>
|
|
|
|
+ <el-button
|
|
|
|
+ icon="Download"
|
|
|
|
+ type="primary"
|
|
|
|
+ :loading="exportLoading"
|
|
|
|
+ :disabled="exportLoading"
|
|
|
|
+ @click="exportReport"
|
|
|
|
+ >
|
|
|
|
+ {{ exportLoading ? '导出中...' : '导出报表' }}
|
|
|
|
+ </el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </el-header>
|
|
|
|
+ <el-main class="sa-p-0">
|
|
|
|
+ <div class="sa-table-wrap panel-block panel-block--bottom" v-loading="loading">
|
|
|
|
+ <el-table
|
|
|
|
+ height="100%"
|
|
|
|
+ class="sa-table"
|
|
|
|
+ :data="flatTableData"
|
|
|
|
+ @sort-change="fieldFilter"
|
|
|
|
+ stripe
|
|
|
|
+ border
|
|
|
|
+ >
|
|
|
|
+ <template #empty>
|
|
|
|
+ <sa-empty />
|
|
|
|
+ </template>
|
|
|
|
+ <el-table-column label="序号" width="120" align="left">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <div class="subject-id-cell" :style="{ paddingLeft: scope.row.level * 20 + 'px' }">
|
|
|
|
+ <!-- 展开/收起按钮 -->
|
|
|
|
+ <el-icon
|
|
|
|
+ v-if="scope.row.isParent"
|
|
|
|
+ class="expand-icon"
|
|
|
|
+ @click="toggleExpand(scope.row)"
|
|
|
|
+ >
|
|
|
|
+ <ArrowRight v-if="!expandedRows.has(scope.row.subjectId)" />
|
|
|
|
+ <ArrowDown v-else />
|
|
|
|
+ </el-icon>
|
|
|
|
+ <span v-else class="expand-placeholder"></span>
|
|
|
|
+ <span class="subject-id">
|
|
|
|
+ {{ scope.row.subjectId }}
|
|
|
|
+ </span>
|
|
|
|
+ </div>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="name" label="科目名称" min-width="200" sortable="custom">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <span class="sa-table-line-1">
|
|
|
|
+ {{ scope.row.name || '-' }}
|
|
|
|
+ </span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column
|
|
|
|
+ prop="aliases"
|
|
|
|
+ label="别名"
|
|
|
|
+ min-width="100"
|
|
|
|
+ align="center"
|
|
|
|
+ sortable="custom"
|
|
|
|
+ >
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <span>{{ scope.row.aliases || '-' }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="direction" label="余额方向" min-width="100" align="center">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <el-tag :type="getDirectionType(scope.row.direction)" size="small">
|
|
|
|
+ {{ getDirectionText(scope.row.direction) }}
|
|
|
|
+ </el-tag>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="initAmount" label="初始日余额" min-width="120" align="right">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <span class="amount-text">{{ formatAmount(scope.row.initAmount) }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="debitAmount" label="借方发生额" min-width="120" align="right">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <span class="amount-text debit-amount">{{
|
|
|
|
+ formatAmount(scope.row.debitAmount)
|
|
|
|
+ }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="creditAmount" label="贷方发生额" min-width="120" align="right">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <span class="amount-text credit-amount">{{
|
|
|
|
+ formatAmount(scope.row.creditAmount)
|
|
|
|
+ }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ <el-table-column prop="endAmount" label="日余额" min-width="120" align="right">
|
|
|
|
+ <template #default="scope">
|
|
|
|
+ <span class="amount-text end-amount">{{ formatAmount(scope.row.endAmount) }}</span>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ </div>
|
|
|
|
+ </el-main>
|
|
|
|
+ <sa-view-bar>
|
|
|
|
+ <!-- 移除统计信息 -->
|
|
|
|
+ </sa-view-bar>
|
|
|
|
+ </el-container>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script setup>
|
|
|
|
+ import { onMounted, reactive, ref, computed } from 'vue';
|
|
|
|
+ import { ArrowRight, ArrowDown } from '@element-plus/icons-vue';
|
|
|
|
+ import { api } from '../finance.service';
|
|
|
|
+
|
|
|
|
+ // 当前搜索条件
|
|
|
|
+ const currentSearchParams = ref({});
|
|
|
|
+
|
|
|
|
+ // 导出loading状态
|
|
|
|
+ const exportLoading = ref(false);
|
|
|
|
+
|
|
|
|
+ // 列表
|
|
|
|
+ const table = reactive({
|
|
|
|
+ data: [],
|
|
|
|
+ order: '',
|
|
|
|
+ sort: '',
|
|
|
|
+ selected: [],
|
|
|
|
+ });
|
|
|
|
+ const loading = ref(true);
|
|
|
|
+
|
|
|
|
+ // 搜索字段配置
|
|
|
|
+ const searchFields = reactive({
|
|
|
|
+ createTime: {
|
|
|
|
+ type: 'custom', // 标记为自定义字段
|
|
|
|
+ label: '时间范围',
|
|
|
|
+ },
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 获取默认时间范围(昨天一天)
|
|
|
|
+ const getDefaultDateRange = () => {
|
|
|
|
+ const yesterday = new Date();
|
|
|
|
+ yesterday.setDate(yesterday.getDate() - 1); // 昨天的日期
|
|
|
|
+ const yesterdayStr = yesterday.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
|
|
+ return [
|
|
|
|
+ yesterdayStr, // 开始日期:昨天
|
|
|
|
+ yesterdayStr, // 结束日期:昨天
|
|
|
|
+ ];
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 禁用日期函数 - 禁用今天及以后的日期
|
|
|
|
+ const disabledDate = (time) => {
|
|
|
|
+ const today = new Date();
|
|
|
|
+ today.setHours(0, 0, 0, 0); // 设置为今天的开始时间
|
|
|
|
+ return time.getTime() >= today.getTime(); // 禁用今天及以后的日期
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 默认搜索值
|
|
|
|
+ const defaultSearchValues = reactive({
|
|
|
|
+ createTime: getDefaultDateRange(),
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 展开状态管理
|
|
|
|
+ const expandedRows = ref(new Set());
|
|
|
|
+
|
|
|
|
+ // 切换展开状态
|
|
|
|
+ const toggleExpand = (row) => {
|
|
|
|
+ if (expandedRows.value.has(row.subjectId)) {
|
|
|
|
+ expandedRows.value.delete(row.subjectId);
|
|
|
|
+ } else {
|
|
|
|
+ expandedRows.value.add(row.subjectId);
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 扁平化数据处理 - 根据展开状态决定是否显示子科目
|
|
|
|
+ const flattenData = (data, level = 0) => {
|
|
|
|
+ const result = [];
|
|
|
|
+ data.forEach((item) => {
|
|
|
|
+ // 添加当前项,标记层级
|
|
|
|
+ result.push({
|
|
|
|
+ ...item,
|
|
|
|
+ level: level,
|
|
|
|
+ isParent: !!(item.children && item.children.length > 0),
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 如果有子项且当前项已展开,则显示子项(默认展开所有主科目)
|
|
|
|
+ if (item.children && item.children.length > 0) {
|
|
|
|
+ // 如果是主科目(level 0)且不在展开列表中,默认展开
|
|
|
|
+ if (level === 0 && !expandedRows.value.has(item.subjectId)) {
|
|
|
|
+ expandedRows.value.add(item.subjectId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (expandedRows.value.has(item.subjectId)) {
|
|
|
|
+ result.push(...flattenData(item.children, level + 1));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ return result;
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 计算属性 - 扁平化的表格数据
|
|
|
|
+ const flatTableData = computed(() => {
|
|
|
|
+ return flattenData(table.data);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 工具函数
|
|
|
|
+ function getDirectionText(direction) {
|
|
|
|
+ const directionMap = {
|
|
|
|
+ D: '借',
|
|
|
|
+ C: '贷',
|
|
|
|
+ };
|
|
|
|
+ return directionMap[direction] || '未知';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function getDirectionType(direction) {
|
|
|
|
+ const typeMap = {
|
|
|
|
+ D: 'success', // 借方用绿色
|
|
|
|
+ C: 'warning', // 贷方用橙色
|
|
|
|
+ };
|
|
|
|
+ return typeMap[direction] || 'info';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function formatAmount(amount) {
|
|
|
|
+ if (amount === null || amount === undefined) return '0.00';
|
|
|
|
+ return Number(amount).toLocaleString('zh-CN', {
|
|
|
|
+ minimumFractionDigits: 2,
|
|
|
|
+ maximumFractionDigits: 2,
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 刷新数据 - 使用默认时间范围
|
|
|
|
+ const refreshData = () => {
|
|
|
|
+ // 重置搜索参数为默认值
|
|
|
|
+ currentSearchParams.value = { ...defaultSearchValues };
|
|
|
|
+ // 使用默认时间范围刷新数据
|
|
|
|
+ getData(1, defaultSearchValues);
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // table 字段排序
|
|
|
|
+ function fieldFilter({ prop, order }) {
|
|
|
|
+ table.order = order == 'ascending' ? 'asc' : 'desc';
|
|
|
|
+ table.sort = prop;
|
|
|
|
+ getData();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 获取数据
|
|
|
|
+ async function getData(page = 1, searchParams = {}) {
|
|
|
|
+ // 保存搜索条件供导出使用
|
|
|
|
+ if (Object.keys(searchParams).length > 0) {
|
|
|
|
+ currentSearchParams.value = { ...searchParams };
|
|
|
|
+ }
|
|
|
|
+ loading.value = true;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 构建请求参数
|
|
|
|
+ const params = {
|
|
|
|
+ ...searchParams,
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 添加排序参数
|
|
|
|
+ if (table.order && table.sort) {
|
|
|
|
+ params.order = table.order;
|
|
|
|
+ params.sort = table.sort;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理时间范围搜索,直接使用年月日格式
|
|
|
|
+ if (searchParams.createTime && searchParams.createTime.length === 2) {
|
|
|
|
+ params.startTime = searchParams.createTime[0];
|
|
|
|
+ params.endTime = searchParams.createTime[1];
|
|
|
|
+ delete params.createTime;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 调用API
|
|
|
|
+ const { code, data } = await api.report.list(params);
|
|
|
|
+
|
|
|
|
+ if (code == '200') {
|
|
|
|
+ table.data = data || [];
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ table.data = [];
|
|
|
|
+ } finally {
|
|
|
|
+ loading.value = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 导出报表
|
|
|
|
+ async function exportReport() {
|
|
|
|
+ if (exportLoading.value) return; // 防止重复点击
|
|
|
|
+
|
|
|
|
+ exportLoading.value = true;
|
|
|
|
+ try {
|
|
|
|
+ // 构建导出参数,与列表请求参数保持一致,但排除分页相关数据
|
|
|
|
+ const exportParams = {
|
|
|
|
+ ...currentSearchParams.value, // 当前搜索参数
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 处理时间范围搜索(与 getData 函数保持一致),直接使用年月日格式
|
|
|
|
+ if (exportParams.createTime && exportParams.createTime.length === 2) {
|
|
|
|
+ exportParams.startTime = exportParams.createTime[0];
|
|
|
|
+ exportParams.endTime = exportParams.createTime[1];
|
|
|
|
+ delete exportParams.createTime;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 生成包含时间范围的文件名作为兜底方案
|
|
|
|
+ let fileName = '财务报表';
|
|
|
|
+ if (exportParams.startTime && exportParams.endTime) {
|
|
|
|
+ if (exportParams.startTime === exportParams.endTime) {
|
|
|
|
+ // 单日报表
|
|
|
|
+ fileName = `财务报表_${exportParams.startTime}`;
|
|
|
|
+ } else {
|
|
|
|
+ // 时间范围报表
|
|
|
|
+ fileName = `财务报表_${exportParams.startTime}_至_${exportParams.endTime}`;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 调用导出API,优先使用服务器返回的文件名,备用使用生成的文件名
|
|
|
|
+ await api.report.export(exportParams, fileName);
|
|
|
|
+ } finally {
|
|
|
|
+ exportLoading.value = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ onMounted(() => {
|
|
|
|
+ // 设置当前搜索参数为默认值,确保搜索组件显示默认时间
|
|
|
|
+ currentSearchParams.value = { ...defaultSearchValues };
|
|
|
|
+ // 使用默认时间范围进行初始查询
|
|
|
|
+ getData(1, defaultSearchValues);
|
|
|
|
+ });
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+ /* 表格样式优化 */
|
|
|
|
+ :deep(.el-table) {
|
|
|
|
+ .el-table__row {
|
|
|
|
+ /* 主科目行样式 */
|
|
|
|
+ &[data-level='0'] {
|
|
|
|
+ background-color: #fafafa;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 子科目行样式 */
|
|
|
|
+ &[data-level='1'] {
|
|
|
|
+ background-color: #ffffff;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 序号单元格样式 */
|
|
|
|
+ .subject-id-cell {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 展开按钮样式 */
|
|
|
|
+ .expand-icon {
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ width: 16px;
|
|
|
|
+ height: 16px;
|
|
|
|
+ color: #606266;
|
|
|
|
+ transition: color 0.3s;
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .expand-icon:hover {
|
|
|
|
+ color: #409eff;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 展开按钮占位符 */
|
|
|
|
+ .expand-placeholder {
|
|
|
|
+ width: 24px;
|
|
|
|
+ height: 16px;
|
|
|
|
+ margin-right: 8px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 序号样式 */
|
|
|
|
+ .subject-id {
|
|
|
|
+ flex: 1;
|
|
|
|
+ font-weight: 500;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 为子科目序号添加连接线效果 */
|
|
|
|
+ .subject-id-cell[style*='padding-left: 20px'] .subject-id::before {
|
|
|
|
+ content: '├─';
|
|
|
|
+ color: #dcdfe6;
|
|
|
|
+ margin-right: 4px;
|
|
|
|
+ }
|
|
|
|
+</style>
|