From 73d112ddc9bdfeb7eaaf3b7308cfb4dbeb470c89 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 07 八月 2026 17:28:10 +0800
Subject: [PATCH] 天津_意博凯信 1.备件管理-新增独立备件出库功能入口,支持直接完成备件领用出库操作。 2.设备保养-支持按日期分组折叠展示,分组条目可展开查看明细;单条明细行末尾增加【保养完成】操作按钮,可单独办结单条保养任务;折叠汇总状态下,新增【一键保养完成】批量操作按钮,快速办结当前分组全部保养项。 3.设备巡检-按日期分组折叠展示,分组支持展开查看明细;每条巡检明细后增设【巡检完成】按钮,支持单独办结单条巡检记录;分组折叠未展开状态下,提供【一键巡检完成】批量操作,统一办结该分组下所有巡检任务。 4.增值税对比-优化数据展示逻辑,列表展示各订单增值税明细;页面右侧新增柱状可视化图表,直观对比进销项增值税数据。 5.管理驾驶舱-指标修改:将顶部「销售产品数」调整为「销售订单数」;时间筛选:页面左上角新增日期范围下拉筛选控件,所有统计指标标注当前筛选时段;

---
 src/views/reportAnalysis/taxComparison/index.vue |  491 +++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 392 insertions(+), 99 deletions(-)

diff --git a/src/views/reportAnalysis/taxComparison/index.vue b/src/views/reportAnalysis/taxComparison/index.vue
index d27ab07..25f3827 100644
--- a/src/views/reportAnalysis/taxComparison/index.vue
+++ b/src/views/reportAnalysis/taxComparison/index.vue
@@ -1,118 +1,411 @@
 <template>
-  <div class="app-container">
-    <el-form :model="filters" :inline="true">
-      <el-form-item label="鏃ユ湡">
-        <el-date-picker
-          style="width: 240px"
-          v-model="filters.month"
-          value-format="YYYY-MM"
-          format="YYYY-MM"
-          type="month"
-          placeholder="閫夋嫨鏈堜唤"
-          clearable
-          @change="getTableData"
-        />
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" @click="getTableData"> 鎼滅储 </el-button>
-        <el-button @click="resetFilters"> 閲嶇疆 </el-button>
-        <el-button @click="handleOut"> 瀵煎嚭 </el-button>
-      </el-form-item>
-    </el-form>
-    <div class="table_list">
-      <PIMTable
-        rowKey="id"
-        :column="columns"
-        :tableData="dataList"
-        :page="{
-          current: pagination.currentPage,
-          size: pagination.pageSize,
-          total: pagination.total,
-        }"
-        @pagination="changePage"
-      />
-    </div>
-  </div>
+	<div class="app-container vat-page">
+		<el-row :gutter="20" class="vat-row">
+			<!-- 宸︿晶锛氬鍊肩◣鏄庣粏鍒楄〃 -->
+			<el-col :span="15" class="vat-col">
+				<el-card shadow="never" class="vat-card">
+					<template #header>
+						<div class="card-header">
+							<span class="card-title">澧炲�肩◣鏄庣粏</span>
+							<el-select v-model="queryMonth" placeholder="閫夋嫨鏈堜唤" clearable style="width: 150px;" @change="handleMonthChange">
+								<el-option v-for="m in monthOptions" :key="m" :label="m" :value="m" />
+							</el-select>
+						</div>
+					</template>
+					<template v-if="!loading && vatDetailList.length === 0">
+						<div class="empty-full">
+							<el-empty description="鏆傛棤澧炲�肩◣鏄庣粏鏁版嵁" :image-size="160" />
+						</div>
+					</template>
+					<template v-else>
+						<div class="table-wrapper">
+							<el-table :data="vatDetailList" border v-loading="loading" stripe height="100%">
+								<el-table-column prop="orderType" label="绫诲瀷" width="65" align="center">
+									<template #default="scope">
+										<el-tag :type="scope.row.orderType === '杩涢」' ? '' : 'warning'" size="small" effect="plain">
+											{{ scope.row.orderType }}
+										</el-tag>
+									</template>
+								</el-table-column>
+								<el-table-column prop="invoiceNo" label="鍙戠エ鍙�" min-width="140" show-overflow-tooltip />
+								<el-table-column prop="salesContractNo" label="鍚堝悓鍙�" min-width="140" show-overflow-tooltip />
+								<el-table-column prop="supplierName" label="渚涘簲鍟�" min-width="100" show-overflow-tooltip />
+								<el-table-column prop="customerName" label="瀹㈡埛" min-width="100" show-overflow-tooltip />
+								<el-table-column prop="invoiceDate" label="寮�绁ㄦ棩鏈�" width="110" align="center" />
+								<el-table-column prop="taxRate" label="绋庣巼" width="75" align="center">
+									<template #default="scope">
+										{{ scope.row.taxRate }}%
+									</template>
+								</el-table-column>
+								<el-table-column prop="taxAmount" label="绋庨" width="110" align="right">
+									<template #default="scope">
+                    <span class="tax-amount" :class="scope.row.orderType === '杩涢」' ? 'input-tax' : 'output-tax'">
+                      楼{{ formatNumber(scope.row.taxAmount) }}
+                    </span>
+									</template>
+								</el-table-column>
+							</el-table>
+						</div>
+						<el-pagination
+							v-if="page.total > 0"
+							class="vat-pagination"
+							background
+							layout="total, sizes, prev, pager, next, jumper"
+							:current-page="page.current"
+							:page-sizes="[10, 20, 50]"
+							:page-size="page.size"
+							:total="page.total"
+							@size-change="handleSizeChange"
+							@current-change="handleCurrentChange" />
+					</template>
+				</el-card>
+			</el-col>
+			
+			<!-- 鍙充晶锛氭煴鐘跺姣斿浘 -->
+			<el-col :span="9" class="vat-col">
+				<el-card shadow="never" class="vat-card">
+					<template #header>
+						<span class="card-title">杩涢攢椤瑰鍊肩◣瀵规瘮</span>
+					</template>
+					<div class="summary-row">
+						<div class="summary-item input-bg">
+							<div class="summary-label">杩涢」绋庨鍚堣</div>
+							<div class="summary-value">楼{{ formatNumber(inputTotal) }}</div>
+						</div>
+						<div class="summary-item output-bg">
+							<div class="summary-label">閿�椤圭◣棰濆悎璁�</div>
+							<div class="summary-value">楼{{ formatNumber(outputTotal) }}</div>
+						</div>
+						<div class="summary-item diff-bg">
+							<div class="summary-label">宸</div>
+							<div class="summary-value" :class="diffValue >= 0 ? 'positive' : 'negative'">
+								{{ diffValue >= 0 ? '+' : '' }}楼{{ formatNumber(diffValue) }}
+							</div>
+						</div>
+					</div>
+					<div v-if="vatDetailList.length > 0" ref="vatChart" class="chart-container"></div>
+					<div v-else class="chart-empty">鏆傛棤鏁版嵁</div>
+				</el-card>
+			</el-col>
+		</el-row>
+	</div>
 </template>
 
 <script setup>
-import { usePaginationApi } from "@/hooks/usePaginationApi";
-import { onMounted, getCurrentInstance } from "vue";
-import { getTaxList } from "@/api/procurementManagement/taxComparison";
-import { ElMessageBox } from "element-plus";
-
-const { proxy } = getCurrentInstance();
+import { ref, reactive, computed, onMounted, nextTick, onBeforeUnmount } from "vue";
+import * as echarts from 'echarts';
+import { getVatDetail } from "@/api/procurementManagement/taxComparison";
 
 defineOptions({
-  name: "澧炲�肩◣姣斿",
+	name: "澧炲�肩◣姣斿",
 });
 
-const {
-  loading,
-  filters,
-  columns,
-  dataList,
-  pagination,
-  getTableData,
-  resetFilters,
-  onCurrentChange,
-} = usePaginationApi(
-  getTaxList,
-  {
-    month: [], // 鏉ョエ鏃ユ湡
-  },
-  [
-    {
-      label: "鏈堜唤",
-      prop: "month",
-      align: "center",
-    },
-    {
-      label: "閿�椤圭◣棰�",
-      prop: "jtaxAmount",
-      align: "center",
-    },
-    {
-      label: "杩涢」绋庨",
-      prop: "xtaxAmount",
-      align: "center",
-    },
-    {
-      label: "閿�-杩�",
-      prop: "taxAmount",
-      align: "center",
-    },
-  ],
-  {}
-);
+const vatChart = ref(null);
+let chartInstance = null;
 
-const changePage = ({ page }) => {
-  pagination.currentPage = page;
-  onCurrentChange(page);
+const queryMonth = ref(new Date().toISOString().slice(0, 7));
+const monthOptions = ref([]);
+const vatDetailList = ref([]);
+const loading = ref(false);
+const page = reactive({ current: 1, size: 20, total: 0 });
+
+// 姹囨�婚
+const inputTotal = computed(() => {
+	return vatDetailList.value
+		.filter(i => i.orderType === '杩涢」')
+		.reduce((sum, i) => sum + (Number(i.taxAmount) || 0), 0);
+});
+const outputTotal = computed(() => {
+	return vatDetailList.value
+		.filter(i => i.orderType === '閿�椤�')
+		.reduce((sum, i) => sum + (Number(i.taxAmount) || 0), 0);
+});
+const diffValue = computed(() => outputTotal.value - inputTotal.value);
+
+const formatNumber = (val) => {
+	const num = Number(val);
+	if (isNaN(num)) return '0.00';
+	return num.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
 };
 
-// 瀵煎嚭
-const handleOut = () => {
-  ElMessageBox.confirm("閫変腑鐨勫唴瀹瑰皢琚鍑猴紝鏄惁纭瀵煎嚭锛�", "瀵煎嚭", {
-    confirmButtonText: "纭",
-    cancelButtonText: "鍙栨秷",
-    type: "warning",
-  })
-    .then(() => {
-      proxy.download("/purchase/report/exportTwo", {}, "澧炲�肩◣姣斿.xlsx");
-    })
-    .catch(() => {
-      proxy.$modal.msg("宸插彇娑�");
-    });
+const loadVatDetail = () => {
+	loading.value = true;
+	const params = {
+		current: page.current,
+		size: page.size,
+		month: queryMonth.value || undefined,
+	};
+	getVatDetail(params)
+		.then((res) => {
+			if (res.code === 200) {
+				vatDetailList.value = res.data.records || [];
+				page.total = res.data.total || 0;
+				nextTick(() => renderChart());
+			}
+		})
+		.finally(() => {
+			loading.value = false;
+		});
+};
+
+const renderChart = () => {
+	if (!vatChart.value) return;
+	if (chartInstance) {
+		chartInstance.dispose();
+	}
+	chartInstance = echarts.init(vatChart.value);
+	
+	const data = vatDetailList.value;
+	const invoices = data.map(i => i.invoiceNo);
+	
+	chartInstance.setOption({
+		tooltip: {
+			trigger: 'axis',
+			backgroundColor: 'rgba(255,255,255,0.95)',
+			borderColor: '#e0e0e0',
+			borderWidth: 1,
+			textStyle: { color: '#333', fontSize: 13 },
+			formatter: function (params) {
+				let html = `<b>${params[0].axisValue}</b><br/>`;
+				params.forEach(p => {
+					if (p.value > 0) {
+						html += `${p.marker} ${p.seriesName}: 楼${formatNumber(p.value)}<br/>`;
+					}
+				});
+				return html;
+			},
+		},
+		legend: {
+			data: ['杩涢」绋庨', '閿�椤圭◣棰�'],
+			bottom: 0,
+			textStyle: { fontSize: 12 },
+		},
+		grid: {
+			left: '10%',
+			right: '8%',
+			top: '8%',
+			bottom: '12%',
+		},
+		xAxis: {
+			type: 'category',
+			data: invoices,
+			axisLabel: {
+				rotate: 45,
+				fontSize: 10,
+				interval: 0,
+			},
+			axisTick: { alignWithLabel: true },
+		},
+		yAxis: {
+			type: 'value',
+			name: '绋庨(鍏�)',
+			nameTextStyle: { fontSize: 11 },
+			axisLabel: {
+				formatter: (val) => val >= 10000 ? `${(val / 10000).toFixed(1)}涓嘸 : val,
+			},
+		},
+		series: [
+			{
+				name: '杩涢」绋庨',
+				type: 'bar',
+				barWidth: '35%',
+				data: data.map(i => i.orderType === '杩涢」' ? Number(i.taxAmount) || 0 : null),
+				itemStyle: {
+					color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+						{ offset: 0, color: '#667eea' },
+						{ offset: 1, color: '#764ba2' },
+					]),
+					borderRadius: [4, 4, 0, 0],
+				},
+				emphasis: {
+					itemStyle: { color: '#667eea' },
+				},
+			},
+			{
+				name: '閿�椤圭◣棰�',
+				type: 'bar',
+				barWidth: '35%',
+				data: data.map(i => i.orderType === '閿�椤�' ? Number(i.taxAmount) || 0 : null),
+				itemStyle: {
+					color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+						{ offset: 0, color: '#f093fb' },
+						{ offset: 1, color: '#f5576c' },
+					]),
+					borderRadius: [4, 4, 0, 0],
+				},
+				emphasis: {
+					itemStyle: { color: '#f093fb' },
+				},
+			},
+		],
+	});
+};
+
+const handleMonthChange = () => {
+	page.current = 1;
+	loadVatDetail();
+};
+
+const handleSizeChange = (val) => {
+	page.size = val;
+	loadVatDetail();
+};
+
+const handleCurrentChange = (val) => {
+	page.current = val;
+	loadVatDetail();
+};
+
+const generateMonthOptions = () => {
+	const now = new Date();
+	const options = [];
+	for (let i = 11; i >= 0; i--) {
+		const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
+		const y = d.getFullYear();
+		const m = String(d.getMonth() + 1).padStart(2, '0');
+		options.push(`${y}-${m}`);
+	}
+	monthOptions.value = options;
+};
+
+const handleResize = () => {
+	if (chartInstance) {
+		chartInstance.resize();
+	}
 };
 
 onMounted(() => {
-  getTableData();
+	generateMonthOptions();
+	loadVatDetail();
+	window.addEventListener('resize', handleResize);
+});
+
+onBeforeUnmount(() => {
+	window.removeEventListener('resize', handleResize);
+	if (chartInstance) {
+		chartInstance.dispose();
+	}
 });
 </script>
 
 <style lang="scss" scoped>
-.table_list {
-  margin-top: unset;
+.vat-page {
+	height: calc(100vh - 84px);
 }
-</style>
+
+.vat-row {
+	height: 100%;
+}
+
+.vat-col {
+	height: 100%;
+}
+
+.vat-card {
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	
+	:deep(.el-card__header) {
+		flex-shrink: 0;
+	}
+	:deep(.el-card__body) {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		overflow: hidden;
+	}
+}
+
+.card-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+
+.card-title {
+	font-size: 15px;
+	font-weight: 600;
+	color: #303133;
+}
+
+.table-wrapper {
+	flex: 1;
+	overflow: hidden;
+}
+
+.vat-pagination {
+	flex-shrink: 0;
+	margin-top: 12px;
+	justify-content: flex-end;
+}
+
+.tax-amount {
+	font-weight: 600;
+	font-family: 'Monaco', 'Menlo', monospace;
+	&.input-tax { color: #667eea; }
+	&.output-tax { color: #f5576c; }
+}
+
+.summary-row {
+	display: flex;
+	gap: 12px;
+	margin-bottom: 16px;
+	flex-shrink: 0;
+	
+	.summary-item {
+		flex: 1;
+		padding: 12px 14px;
+		border-radius: 8px;
+		text-align: center;
+		
+		&.input-bg {
+			background: linear-gradient(135deg, #f3f0ff 0%, #e8e5ff 100%);
+			.summary-value { color: #667eea; }
+		}
+		&.output-bg {
+			background: linear-gradient(135deg, #fff0f3 0%, #ffe0e6 100%);
+			.summary-value { color: #f5576c; }
+		}
+		&.diff-bg {
+			background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+			.summary-value {
+				&.positive { color: #f5576c; }
+				&.negative { color: #22c55e; }
+			}
+		}
+		
+		.summary-label {
+			font-size: 12px;
+			color: #909399;
+			margin-bottom: 4px;
+		}
+		.summary-value {
+			font-size: 18px;
+			font-weight: 700;
+			font-family: 'Monaco', 'Menlo', monospace;
+		}
+	}
+}
+
+.chart-container {
+	flex: 1;
+	min-height: 0;
+}
+
+.empty-full {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.chart-empty {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	color: #c0c4cc;
+	font-size: 13px;
+}
+</style>
\ No newline at end of file

--
Gitblit v1.9.3