yaowanxin
4 天以前 8fd61b5b8e138b8151c644b612b92ca0fa0fe840
src/views/financialManagement/accounting/index.vue
@@ -1,231 +1,579 @@
<template>
  <div class="app-container">
    <el-row :gutter="16" class="mb-16">
      <el-col :span="12">
        <el-card shadow="hover">
          <div class="section-title">统一会计科目体系</div>
          <el-tree
            :data="accountTree"
            node-key="code"
            :props="{ label: 'label', children: 'children' }"
            highlight-current
            default-expand-all
          />
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card shadow="hover">
          <div class="section-title">凭证模板</div>
          <el-table :data="voucherTemplates" border size="small">
            <el-table-column prop="name" label="模板名称" min-width="140" />
            <el-table-column prop="bizScene" label="业务场景" min-width="140" />
            <el-table-column prop="debit" label="借方科目" min-width="160" />
            <el-table-column prop="credit" label="贷方科目" min-width="160" />
            <el-table-column prop="auxDims" label="辅助核算维度" min-width="180">
              <template #default="scope">
                <el-space wrap>
                  <el-tag v-for="dim in scope.row.auxDims" :key="dim" size="small" type="info">{{ dim }}</el-tag>
                </el-space>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>
    </el-row>
  <div style="padding: 20px;">
    <!-- 页面标题和筛选条件 -->
    <div class="w-full md:w-auto flex items-center gap-3" style="margin-bottom: 20px;">
      <el-date-picker
        v-model="dateRange"
        type="daterange"
        format="YYYY-MM-DD"
        value-format="YYYY-MM-DD"
        range-separator="至"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        clearable
        @change="handleDateChange"
        class="w-full md:w-auto"
        style="margin-right: 30px;"
      />
    <el-row :gutter="16" class="mb-16">
      <el-col :span="12">
        <el-card shadow="hover">
          <div class="section-title">业务流程 → 凭证自动生成</div>
          <div class="toolbar">
            <el-text type="info">演示数据仅用于展示结构与字段</el-text>
          </div>
          <el-table :data="generatedVouchers" border size="small">
            <el-table-column prop="date" label="日期" width="110" />
            <el-table-column prop="bizScene" label="业务场景" min-width="120" />
            <el-table-column prop="summary" label="摘要" min-width="160" />
            <el-table-column prop="amount" label="金额(¥)" width="110" />
            <el-table-column prop="status" label="状态" width="100">
              <template #default="scope">
                <el-tag :type="scope.row.status === '已生成' ? 'success' : 'warning'">{{ scope.row.status }}</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card shadow="hover">
          <div class="section-title">多维辅助核算</div>
          <div class="dims">
            <el-tag type="success">客户</el-tag>
            <el-tag type="warning">项目</el-tag>
            <el-tag type="info">部门</el-tag>
            <el-tag type="primary">管理员</el-tag>
          </div>
          <el-table :data="auxSummary" size="small" border>
            <el-table-column prop="dimension" label="维度" width="100" />
            <el-table-column prop="category" label="类别" min-width="140" />
            <el-table-column prop="debit" label="借方(本期)" width="110" />
            <el-table-column prop="credit" label="贷方(本期)" width="110" />
            <el-table-column prop="balance" label="余额" width="100" />
          </el-table>
        </el-card>
      </el-col>
    </el-row>
      <!-- 设备类型筛选 -->
      <el-select
        v-model="equipmentType"
        placeholder="设备类型"
        clearable
        @change="handleFilterChange"
        style="margin-right: 20px; width: 150px;"
      >
        <el-option
          v-for="item in equipmentTypeOptions"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        />
      </el-select>
    <el-row :gutter="16" class="mb-16">
      <el-col :span="12">
        <el-card shadow="hover">
          <div class="section-title">结账任务(月/季/年)</div>
          <div class="toolbar">
            <el-space>
              <el-button size="small" @click="runClose('月结')">执行月结</el-button>
              <el-button size="small" @click="runClose('季报')">执行季报</el-button>
              <el-button size="small" @click="runClose('年度结账')">执行年度结账</el-button>
            </el-space>
          </div>
          <el-timeline style="margin-top: 6px;">
            <el-timeline-item
              v-for="item in closingTasks"
              :key="item.id"
              :type="item.type"
              :timestamp="item.time"
              placement="top"
            >
              <div class="close-item">
                <div class="title">{{ item.name }}</div>
                <el-tag :type="item.status === '完成' ? 'success' : 'info'" size="small">{{ item.status }}</el-tag>
      <el-button
        type="primary"
        icon="Refresh"
        @click="resetFilters"
        size="default"
      >
        重置
      </el-button>
    </div>
    <main class="container mx-auto px-4 pb-10">
      <!-- 固定资产指标卡片 -->
      <div class="grid-container">
        <!-- 设备总数 -->
        <el-card class="bg2">
          <p>设备总数</p>
          <h3>
            {{ assetInfo.totalEquipment }}
          </h3>
        </el-card>
        <!-- 资产原值 -->
        <el-card class="bg3">
          <p>资产原值</p>
          <h3>
            ¥{{ assetInfo.totalOriginalValue }}
          </h3>
        </el-card>
        <!-- 累计折旧 -->
        <el-card class="bg4">
          <p>累计折旧</p>
          <h3>
            ¥{{ assetInfo.totalDepreciation }}
          </h3>
        </el-card>
        <!-- 净值 -->
        <el-card class="bg5">
          <p>净值</p>
          <h3>
            ¥{{ assetInfo.totalNetValue }}
          </h3>
        </el-card>
      </div>
      <!-- 固定资产统计图表 -->
      <div class="grid-layout">
        <!-- 按设备类型统计 -->
        <el-card style="margin-bottom: 20px;">
          <h2 class="section-title">设备类型分布</h2>
          <div class="echarts">
            <Echarts
                :legend="typeDistributionLegend"
                :chartStyle="chartStylePie"
                :series="typeDistributionSeries"
                :tooltip="pieTooltip"
                style="height: 260px; width: 35%;">
              <div class="chart-num">
                <span style="font-size: 22px;">设备类型</span>
                <span style="font-size: 36px; font-weight: 500; font-family: 'MyCustomFont', sans-serif;">{{ assetInfo.totalEquipment }}</span>
              </div>
            </el-timeline-item>
          </el-timeline>
            </Echarts>
            <Echarts
                ref="chart"
                :chartStyle="chartStyle"
                :grid="grid"
                :legend="lineLegend"
                :series="typeDistributionLineSeries"
                :tooltip="tooltip"
                :xAxis="xAxis"
                :yAxis="yAxis"
                style="height: 260px; width: 64%;"></Echarts>
          </div>
        </el-card>
      </el-col>
      <el-col :span="12">
        <el-card shadow="hover">
          <div class="section-title">审计留痕与审批权限</div>
          <el-table :data="auditTrail" border size="small">
            <el-table-column prop="time" label="时间" width="160" />
            <el-table-column prop="action" label="动作" min-width="160" />
            <el-table-column prop="bizScene" label="关联业务" min-width="140" />
            <el-table-column prop="role" label="执行角色" width="120" />
            <el-table-column prop="result" label="结果" width="100">
              <template #default="scope">
                <el-tag :type="scope.row.result === '通过' ? 'success' : (scope.row.result === '驳回' ? 'danger' : 'info')">
                  {{ scope.row.result }}
                </el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>
    </el-row>
      </div>
      <!-- 设备台账表格 -->
      <el-card style="margin-bottom: 20px;">
        <el-table
          :data="equipmentList"
          stripe
          style="width: 100%"
          :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
        >
          <el-table-column prop="id" label="资产编号" width="120" />
          <el-table-column prop="deviceName" label="设备名称" width="150" />
          <el-table-column prop="deviceModel" label="型号规格" width="150" />
          <el-table-column prop="supplierName" label="供应商" width="120" />
          <el-table-column prop="unit" label="单位" width="120" />
          <el-table-column prop="number" label="数量" width="120" />
          <el-table-column prop="originalValue" label="原值(元)" width="120">
            <template #default="{ row }">
              ¥{{ formatCurrency(row.taxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="depreciation" label="累计折旧(元)" width="140">
            <template #default="{ row }">
              ¥{{ formatCurrency(row.taxIncludingPriceTotal-row.unTaxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="netValue" label="净值(元)" width="120">
            <template #default="{ row }">
              ¥{{ formatCurrency(row.unTaxIncludingPriceTotal) }}
            </template>
          </el-table-column>
          <el-table-column prop="status" label="状态" width="100">
            <template #default="{ row }">
              <el-tag
                :type="getStatusTagType(row.status)"
                size="small"
              >
                {{ row.status }}
              </el-tag>
            </template>
          </el-table-column>
        </el-table>
        <!-- 分页 -->
        <div class="pagination-container">
          <el-pagination
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
            :current-page="pagination.currentPage"
            :page-sizes="[10, 20, 50, 100]"
            :page-size="pagination.pageSize"
            layout="total, sizes, prev, pager, next, jumper"
            :total="pagination.total"
          />
        </div>
      </el-card>
    </main>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import { ref, computed, onMounted, reactive } from 'vue';
import 'element-plus/dist/index.css';
import Echarts from "@/components/Echarts/echarts.vue";
import { getLedgerPage, getAssetInfo } from "@/api/equipmentManagement/ledger";
import dayjs from "dayjs";
// 科目树(示例)
const accountTree = ref([
  { code: '1001', label: '资产', children: [
    { code: '100101', label: '库存现金' },
    { code: '100102', label: '银行存款' },
    { code: '1122', label: '应收账款' },
    { code: '1601', label: '固定资产' },
  ]},
  { code: '2001', label: '负债', children: [
    { code: '2202', label: '应付账款' },
    { code: '2241', label: '其他应付款' },
  ]},
  { code: '3001', label: '所有者权益', children: [
    { code: '3103', label: '本年利润' },
  ]},
  { code: '4001', label: '成本费用', children: [
    { code: '5601', label: '制造费用' },
    { code: '6602', label: '管理费用' },
  ]},
])
// 筛选条件
const dateRange = ref(null);
const equipmentType = ref('');
// 凭证模板(示例)
const voucherTemplates = ref([
  { name: '销售收入确认', bizScene: '销售出库', debit: '1122 应收账款', credit: '6001 主营业务收入', auxDims: ['客户','项目'] },
  { name: '采购应付确认', bizScene: '采购入库', debit: '1403 在途物资', credit: '2202 应付账款', auxDims: ['项目','部门'] },
  { name: '费用报销', bizScene: '费用单', debit: '6602 管理费用', credit: '1002 银行存款', auxDims: ['部门'] },
  { name: '固定资产折旧', bizScene: '月末折旧', debit: '6602 管理费用', credit: '1602 累计折旧', auxDims: ['部门'] },
])
// 自动生成的凭证(示例)
const generatedVouchers = ref([
  { date: '2025-10-01', bizScene: '销售出库', summary: '确认应收与收入', amount: 128000, status: '已生成' },
  { date: '2025-10-03', bizScene: '采购入库', summary: '确认到货应付', amount: 56000, status: '已生成' },
  { date: '2025-10-05', bizScene: '费用单', summary: '办公费用报销', amount: 3200, status: '已生成' },
])
// 固定资产信息
const assetInfo = ref({
  totalEquipment: 0,
  totalOriginalValue: 0,
  totalDepreciation: 0,
  totalNetValue: 0
});
// 无模拟生成操作,仅展示静态示例数据
// 设备列表
const equipmentList = ref([]);
const pagination = ref({
  currentPage: 1,
  pageSize: 10,
  total: 0
});
// 辅助核算示例汇总(无个人姓名,仅维度类别)
const auxSummary = ref([
  { dimension: '客户', category: '重点客户集合', debit: 320000, credit: 210000, balance: 110000 },
  { dimension: '项目', category: '项目A', debit: 150000, credit: 120000, balance: 30000 },
  { dimension: '部门', category: '运营中心', debit: 42000, credit: 18000, balance: 24000 },
  { dimension: '管理员', category: '系统角色', debit: 0, credit: 0, balance: 0 },
])
// 图表配置
const chartStyle = {
  width: '100%',
  height: '100%',
  position: 'relative',
};
// 结账任务
const closingTasks = ref([
  { id: 1, name: '2025年10月 月结', time: '2025-10-31 18:00', status: '完成', type: 'success' },
  { id: 2, name: '2025年Q4 季报', time: '2025-12-31 18:00', status: '计划', type: 'info' },
  { id: 3, name: '2025年度 年度结账', time: '2025-12-31 23:00', status: '计划', type: 'info' },
])
const grid = {
  left: '3%',
  right: '4%',
  bottom: '3%',
  containLabel: true
};
function runClose(kind) {
  closingTasks.value.unshift({
    id: Date.now(),
    name: `${new Date().getFullYear()}年${kind}`,
    time: new Date().toISOString().replace('T',' ').slice(0,16),
    status: '完成',
    type: 'success',
  })
}
const lineLegend = {
  show: false,
};
// 审计留痕(不含个人姓名,仅角色/机制)
const auditTrail = ref([
  { time: '2025-10-01 09:12', action: '销售出库触发凭证生成', bizScene: '销售出库', role: '系统自动化', result: '通过' },
  { time: '2025-10-03 14:20', action: '采购入库触发应付确认', bizScene: '采购入库', role: '系统自动化', result: '通过' },
  { time: '2025-10-05 10:03', action: '费用单审批', bizScene: '费用单', role: '财务审批', result: '通过' },
  { time: '2025-10-08 16:45', action: '凭证过账', bizScene: '总账', role: '会计审核', result: '通过' },
  { time: '2025-10-31 18:05', action: '月结完成并锁账', bizScene: '总账', role: '系统自动化', result: '通过' },
])
// 折线图提示框
const tooltip = reactive({
  trigger: 'axis',
  axisPointer: {
    type: 'line',
    lineStyle: { color: '#aaa' }
  },
  // 自定义内容
  formatter: function (params) {
    if (!params || !params.length) return '';
    const axisLabel = params[0].axisValueLabel || params[0].axisValue || '';
    const rows = params
      .map(p => {
        const colorDot = `<span style="display:inline-block;margin-right:6px;width:8px;height:8px;border-radius:50%;background:${p.color}"></span>`;
        return `${colorDot}${p.seriesName}: ${p.value}`;
      })
      .join('<br/>');
    return `<div>${axisLabel}</div><div>${rows}</div>`;
  }
});
const xAxis = ref([
  {
    type: 'category',
    axisTick: { show: true, alignWithLabel: true },
    data: [],
  },
]);
const yAxis = [
  {
    type: 'value',
    name: '数量/金额', // 左侧y轴
    position: 'left',
    min: 0,
    // 坐标轴名称样式
    nameTextStyle: {
      color: '#000',
      fontSize: 14,
    },
  }
];
const chartStylePie = {
  width: '100%',
  height: '100%' // 设置图表容器的高度
};
const pieColors = ['#F04864', '#FACC14', '#8543E0', '#1890FF', '#13C2C2', '#2FC25B']; // 可根据实际调整
// 饼图数据
const typeDistributionData = ref([]);
const departmentDistributionData = ref([]);
// 饼图图例
const typeDistributionLegend = computed(() => ({
  show: true,
  top: 'center',
  left: '60%',
  orient: 'vertical',
  icon: 'circle',
  data: typeDistributionData.value.map(item => item.name),
  formatter: function(name) {
    const item = typeDistributionData.value.find(i => i.name === name);
    if (!item) return name;
    return `${name} | ${item.count} 台 | ${item.amount}`;
  },
  textStyle: {
    color: '#333',
    fontSize: 14,
    lineHeight: 26,
  }
}));
// 饼图系列
const typeDistributionSeries = computed(() => [
  {
    type: 'pie',
    radius: ['50%', '65%'],
    center: ['25%', '50%'],
    avoidLabelOverlap: false,
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 2
    },
    label: {
      show: false
    },
    data: typeDistributionData.value,
    color: pieColors
  }
]);
// 折线图数据
const typeDistributionLineSeries = ref([]);
// 饼图提示框
const pieTooltip = reactive({
  trigger: 'item',
  formatter: function(params) {
    // 检查数据是否存在
    if (!params.data) return params.name;
    // 拼接完整内容
    return `
      <div>
        <div style="color:${params.color};font-size:16px;">●</div>
        <div>${params.name}</div>
        <div>数量:${params.data.count} 台</div>
        <div>金额:${params.data.amount}</div>
      </div>
    `;
  }
});
// 选项数据
const equipmentTypeOptions = ref([]);
// 获取数据
const fetchData = async () => {
  try {
    // 获取固定资产汇总信息
    const assetInfoRes = await getAssetInfo({
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
    });
    if (assetInfoRes.code === 200) {
      assetInfo.value = assetInfoRes.data;
    }
    // 获取设备列表
    const equipmentListRes = await getLedgerPage({
      current: pagination.value.currentPage,
      size: pagination.value.pageSize,
      startDate: dateRange.value ? dateRange.value[0] : null,
      endDate: dateRange.value ? dateRange.value[1] : null,
      equipmentType: equipmentType.value
    });
    if (equipmentListRes.code === 200) {
      equipmentList.value = equipmentListRes.data.records;
      pagination.value.total = equipmentListRes.data.total;
    }
    // // 获取设备类型分布数据
    // const typeDistributionRes = await getEquipmentTypes({
    //   startDate: dateRange.value ? dateRange.value[0] : null,
    //   endDate: dateRange.value ? dateRange.value[1] : null,
    //   equipmentType: equipmentType.value
    // });
    // if (typeDistributionRes.code === 200) {
    //   typeDistributionData.value = typeDistributionRes.data.map(item => ({
    //     name: item.typeName,
    //     value: item.count,
    //     count: item.count,
    //     amount: `¥${formatCurrency(item.totalValue)}`
    //   }));
    //
    //   // 构建折线图数据
    //   typeDistributionLineSeries.value = [
    //     {
    //       name: '设备数量',
    //       type: 'line',
    //       data: typeDistributionRes.data.map(item => item.count)
    //     }
    //   ];
    //   // 更新x轴数据
    //   xAxis.value[0].data = typeDistributionRes.data.map(item => item.typeName);
    // }
  } catch (error) {
    console.error('获取固定资产数据失败:', error);
  }
};
// 初始化
onMounted(() => {
  // 获取列表数据
  fetchData();
});
// 格式化货币
const formatCurrency = (value) => {
  if (!value) return '0.00';
  return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
};
// 获取状态标签类型
const getStatusTagType = (status) => {
  switch (status) {
    case '在用':
      return 'success';
    case '闲置':
      return 'info';
    case '维修中':
      return 'warning';
    case '报废':
      return 'danger';
    default:
      return 'info';
  }
};
// 处理日期范围变化
const handleDateChange = (newRange) => {
  dateRange.value = newRange;
  fetchData();
};
// 处理筛选条件变化
const handleFilterChange = () => {
  fetchData();
};
// 重置筛选条件
const resetFilters = () => {
  dateRange.value = null;
  equipmentType.value = '';
  fetchData();
};
// 分页处理
const handleSizeChange = (size) => {
  pagination.value.pageSize = size;
  fetchData();
};
const handleCurrentChange = (page) => {
  pagination.value.currentPage = page;
  fetchData();
};
</script>
<style scoped lang="scss">
.app-container {
  padding: 16px;
/* 基础样式补充 */
:root {
  --el-color-primary: #4f46e5;
}
.page-header {
  margin-bottom: 12px;
  h2 { margin: 0 0 6px 0; font-weight: 600; }
  p { margin: 0; color: #666; }
.el-card {
  position: relative;
  border-radius: 12px;
  padding: 14px 10px 10px 10px;
  box-shadow: 0 2px 8px #eee;
  :deep(.el-card__body) {
    padding: 10px 20px !important;
  }
  &.bg1 {
    background: url(@/assets/icons/png/1.png) no-repeat 100% 100% !important;
  }
  &.bg2 {
    background: url(@/assets/icons/png/2.png) no-repeat 100% 100% !important;
  }
  &.bg3 {
    background: url(@/assets/icons/png/3.png) no-repeat 100% 100% !important;
  }
  &.bg4 {
    background: url(@/assets/icons/png/4.png) no-repeat 100% 100% !important;
  }
  &.bg5 {
    background: url(@/assets/icons/png/5.png) no-repeat 100% 100% !important;
  }
}
.grid-container {
  /* grid 容器基础样式 */
  display: grid;
  gap: 1rem; /* gap-4 对应 1rem (16px) */
  margin-bottom: 2rem; /* mb-8 对应 2rem (32px) */
  p {
    font-size: 22px;
    margin-top: 0px;
    color: #fff;
  }
  h3 {
    font-size: 36px;
    font-weight: 500;
    font-family: 'MyCustomFont', sans-serif;
    margin: 10px 0;
    color: #fff;
  }
}
/* 移动端默认样式 (grid-cols-1) */
.grid-container {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
/* 小屏幕及以上 (sm:grid-cols-2) */
@media (min-width: 640px) {
  .grid-container {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
/* 大屏幕及以上 (lg:grid-cols-5) */
@media (min-width: 1024px) {
  .grid-container {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}
/* 卡片悬停效果增强 */
.el-card:hover {
  transform: translateY(-2px);
}
.echarts {
  display: flex;
  justify-content: space-between;
}
/* 图表容器样式 */
.el-chart {
  width: 100%;
  height: 100%;
}
.section-title {
  position: relative;
  font-size: 18px;
  color: #333;
  padding-left: 10px;
  margin-bottom: 10px;
  font-weight: 700;
}
.section-title::before {
  content: '';
  position: absolute;
  left: 0; top: 0.2em;
  width: 4px; height: 1.2em;
  background: #002FA7;
  left: 0;
  top: 0px;
  content: '';
  width: 4px;
  height: 18px;
  background-color: #002FA7;
  border-radius: 2px;
}
.mb-16 { margin-bottom: 16px; }
.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.dims { display: flex; gap: 8px; margin-bottom: 10px; }
.close-item { display: flex; align-items: center; gap: 8px; }
.chart-num {
  position: absolute;
  z-index: 3;
  top: 92px;
  left: 92px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: center;
}
</style>