From 1d44f1083701f0978baa2f1a0e1256dfa69c369c Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期五, 28 八月 2026 23:37:42 +0800
Subject: [PATCH] 天津_建诚恒商贸 1.备件管理-新增独立备件出库功能入口,支持直接完成备件领用出库操作。 2.设备保养-支持按日期分组折叠展示,分组条目可展开查看明细;单条明细行末尾增加【保养完成】操作按钮,可单独办结单条保养任务;折叠汇总状态下,新增【一键保养完成】批量操作按钮,快速办结当前分组全部保养项。 3.设备巡检-按日期分组折叠展示,分组支持展开查看明细;每条巡检明细后增设【巡检完成】按钮,支持单独办结单条巡检记录;分组折叠未展开状态下,提供【一键巡检完成】批量操作,统一办结该分组下所有巡检任务。 4.增值税对比-优化数据展示逻辑,列表展示各订单增值税明细;页面右侧新增柱状可视化图表,直观对比进销项增值税数据。 5.管理驾驶舱-指标修改:将顶部「销售产品数」调整为「销售订单数」;时间筛选:页面左上角新增日期范围下拉筛选控件,所有统计指标标注当前筛选时段;
---
src/views/equipmentManagement/upkeep/index.vue | 214 ++++++++---
src/views/equipmentManagement/spareParts/index.vue | 147 ++++++++
src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue | 4
src/api/procurementManagement/taxComparison.js | 20 +
src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue | 4
src/api/equipmentManagement/upkeep.js | 17
src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue | 4
src/views/equipmentManagement/inspectionManagement/index.vue | 2
src/views/reportAnalysis/PSIDataAnalysis/index.vue | 66 +++
src/api/equipmentManagement/sparePartsUsage.js | 9
src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue | 4
src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue | 5
src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue | 7
src/api/inspectionManagement/index.js | 17
src/views/reportAnalysis/taxComparison/index.vue | 483 +++++++++++++++++++++-----
src/api/viewIndex.js | 20
src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue | 4
17 files changed, 839 insertions(+), 188 deletions(-)
diff --git a/src/api/equipmentManagement/sparePartsUsage.js b/src/api/equipmentManagement/sparePartsUsage.js
index e9384aa..9befc6c 100644
--- a/src/api/equipmentManagement/sparePartsUsage.js
+++ b/src/api/equipmentManagement/sparePartsUsage.js
@@ -34,3 +34,12 @@
});
};
+// 澶囦欢鐩存帴鍑哄簱
+export function outboundSpareParts(data) {
+ return request({
+ url: '/sparePartsRequisitionRecord/outbound',
+ method: 'post',
+ data
+ })
+}
+
diff --git a/src/api/equipmentManagement/upkeep.js b/src/api/equipmentManagement/upkeep.js
index 234d2a5..5fa070a 100644
--- a/src/api/equipmentManagement/upkeep.js
+++ b/src/api/equipmentManagement/upkeep.js
@@ -94,6 +94,23 @@
params: params,
});
};
+// 鍗曟潯淇濆吇瀹屾垚
+export function completeMaintenance(id) {
+ return request({
+ url: '/device/maintenance/complete/' + id,
+ method: 'put'
+ })
+}
+
+// 涓�閿繚鍏诲畬鎴�
+export function batchCompleteMaintenance(dateStr) {
+ return request({
+ url: '/device/maintenance/batchComplete',
+ method: 'put',
+ params: { dateStr }
+ })
+}
+
// 璁惧淇濆吇瀹氭椂浠诲姟鍒楄〃
export const deviceMaintenanceTaskDel = (params) => {
return request({
diff --git a/src/api/inspectionManagement/index.js b/src/api/inspectionManagement/index.js
index d0c444a..13d31f9 100644
--- a/src/api/inspectionManagement/index.js
+++ b/src/api/inspectionManagement/index.js
@@ -58,4 +58,21 @@
method: 'post',
data: query
})
+}
+
+// 鍗曟潯宸℃瀹屾垚
+export function completeInspection(id) {
+ return request({
+ url: '/inspectionTask/complete/' + id,
+ method: 'put'
+ })
+}
+
+// 涓�閿贰妫�瀹屾垚
+export function batchCompleteInspection(dateStr) {
+ return request({
+ url: '/inspectionTask/batchComplete',
+ method: 'put',
+ params: { dateStr }
+ })
}
\ No newline at end of file
diff --git a/src/api/procurementManagement/taxComparison.js b/src/api/procurementManagement/taxComparison.js
index 726a27f..750bccb 100644
--- a/src/api/procurementManagement/taxComparison.js
+++ b/src/api/procurementManagement/taxComparison.js
@@ -1,6 +1,6 @@
import request from "@/utils/request";
-// 鍒嗛〉鏌ヨ
+// 鍒嗛〉鏌ヨ锛堟柊澧� type 鍙傛暟锛�
export function getTaxList(query) {
return request({
url: "/purchase/report/listVat",
@@ -8,3 +8,21 @@
params: query,
});
}
+
+// 澧炲�肩◣杩涢攢椤瑰浘琛ㄦ暟鎹�
+export function getVatChart(year) {
+ return request({
+ url: '/purchase/report/vatChart',
+ method: 'get',
+ params: { year }
+ })
+}
+
+// 澧炲�肩◣鏄庣粏
+export function getVatDetail(query) {
+ return request({
+ url: '/purchase/report/listVatDetail',
+ method: 'get',
+ params: query
+ })
+}
diff --git a/src/api/viewIndex.js b/src/api/viewIndex.js
index 0087d66..8b36d2e 100644
--- a/src/api/viewIndex.js
+++ b/src/api/viewIndex.js
@@ -202,20 +202,20 @@
};
// 渚涘簲鍟嗛噰璐帓鍚�
-export const supplierPurchaseRanking = (query) => {
+export const supplierPurchaseRanking = (params) => {
return request({
url: "/home/supplierPurchaseRanking",
method: "get",
- params: query,
+ params,
});
};
// 瀹㈡埛閲戦璐$尞鎺掑悕
-export const customerContributionRanking = (query) => {
+export const customerContributionRanking = (params) => {
return request({
url: "/home/customerContributionRanking",
method: "get",
- params: query,
+ params,
});
};
@@ -228,10 +228,11 @@
};
// 浜у搧閿�鍞噾棰濆垎鏋�
-export const productSalesAnalysis = () => {
+export const productSalesAnalysis = (params) => {
return request({
url: "/home/productSalesAnalysis",
method: "get",
+ params,
});
};
@@ -245,10 +246,11 @@
};
// 鍘熸潗鏂欓噰璐噾棰濆崰姣�
-export const rawMaterialPurchaseAmountRatio = () => {
+export const rawMaterialPurchaseAmountRatio = (params) => {
return request({
url: "/home/rawMaterialPurchaseAmountRatio",
method: "get",
+ params,
});
};
@@ -262,10 +264,11 @@
};
// 閿�鍞�/閲囪喘/鍌ㄥ瓨浜у搧鏁�
-export const salesPurchaseStorageProductCount = () => {
+export const salesPurchaseStorageProductCount = (startDate, endDate) => {
return request({
url: "/home/salesPurchaseStorageProductCount",
method: "get",
+ params: { startDate, endDate },
});
};
@@ -288,10 +291,11 @@
};
// 浜у搧鍛ㄨ浆澶╂暟
-export const productTurnoverDays = () => {
+export const productTurnoverDays = (params) => {
return request({
url: "/home/productTurnoverDays",
method: "get",
+ params,
});
};
diff --git a/src/views/equipmentManagement/inspectionManagement/index.vue b/src/views/equipmentManagement/inspectionManagement/index.vue
index e254d0d..da8ac99 100644
--- a/src/views/equipmentManagement/inspectionManagement/index.vue
+++ b/src/views/equipmentManagement/inspectionManagement/index.vue
@@ -224,7 +224,7 @@
{ prop: "registrant", label: "鐧昏浜�", minWidth: 120 },
{
prop: "createTime", label: "鐧昏鏃ユ湡", minWidth: 180,
- formatData: cell => { if (!cell) return "-"; try { return dayjs(cell).format("YYYY-MM-DD"); } catch { return cell; } },
+ formatData: cell => { if (!cell) return "-"; try { return dayjs(cell).format("YYYY-MM-DD HH:mm:ss"); } catch { return cell; } },
},
{ prop: "abnormalDescription", label: "寮傚父鎻忚堪", minWidth: 150 },
]);
diff --git a/src/views/equipmentManagement/spareParts/index.vue b/src/views/equipmentManagement/spareParts/index.vue
index 2fa6f7d..b663187 100644
--- a/src/views/equipmentManagement/spareParts/index.vue
+++ b/src/views/equipmentManagement/spareParts/index.vue
@@ -105,6 +105,7 @@
<el-select v-model="usageQuery.sourceType" placeholder="璇烽�夋嫨" clearable style="width: 200px">
<el-option label="缁翠慨" :value="0" />
<el-option label="淇濆吇" :value="1" />
+ <el-option label="鐩存帴鍑哄簱" :value="2" />
</el-select>
</el-form-item>
<el-form-item>
@@ -112,6 +113,9 @@
<el-button @click="resetUsageQuery">閲嶇疆</el-button>
</el-form-item>
</el-form>
+ <div>
+ <el-button type="primary" @click="openRequisitionDialog">鐩存帴鍑哄簱</el-button>
+ </div>
</div>
<div class="table_list">
<PIMTable
@@ -126,6 +130,60 @@
</div>
</el-tab-pane>
</el-tabs>
+
+ <!-- 鐩存帴鍑哄簱寮圭獥 -->
+ <el-dialog title="鐩存帴鍑哄簱" v-model="requisitionDialogVisible" width="500px">
+ <el-form :model="requisitionForm" :rules="requisitionRules" ref="requisitionFormRef" label-width="100px">
+ <el-form-item label="璁惧閫夋嫨" prop="deviceLedgerId">
+ <el-select
+ v-model="requisitionForm.deviceLedgerId"
+ placeholder="璇烽�夋嫨璁惧"
+ filterable
+ clearable
+ style="width: 100%"
+ >
+ <el-option
+ v-for="item in requisitionDeviceOptions"
+ :key="item.id"
+ :label="item.deviceName"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="澶囦欢閫夋嫨" prop="sparePartsId">
+ <el-select
+ v-model="requisitionForm.sparePartsId"
+ placeholder="璇烽�夋嫨澶囦欢"
+ filterable
+ clearable
+ style="width: 100%"
+ @change="onRequisitionSparePartChange"
+ >
+ <el-option
+ v-for="item in requisitionSparePartsOptions"
+ :key="item.id"
+ :label="`${item.name}锛堝簱瀛�: ${item.quantity}锛塦"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="棰嗙敤鏁伴噺" prop="quantity">
+ <el-input-number
+ v-model="requisitionForm.quantity"
+ :min="1"
+ :max="selectedSparePartMaxQty"
+ style="width: 100%"
+ />
+ </el-form-item>
+ <el-form-item label="澶囨敞" prop="remark">
+ <el-input v-model="requisitionForm.remark" placeholder="璇疯緭鍏ュ娉�" />
+ </el-form-item>
+ </el-form>
+ <template #footer>
+ <el-button type="primary" @click="submitRequisition" :loading="requisitionLoading">纭畾</el-button>
+ <el-button @click="requisitionDialogVisible = false">鍙栨秷</el-button>
+ </template>
+ </el-dialog>
</div>
</template>
@@ -135,7 +193,7 @@
import { getSparePartsList, addSparePart, editSparePart, delSparePart } from "@/api/equipmentManagement/spareParts";
import { getDeviceLedger } from "@/api/equipmentManagement/ledger";
import PIMTable from "@/components/PIMTable/PIMTable.vue";
-import { getSparePartsUsagePage } from "@/api/equipmentManagement/sparePartsUsage";
+import { getSparePartsUsagePage, outboundSpareParts } from "@/api/equipmentManagement/sparePartsUsage";
// 鍔犺浇鐘舵��
const loading = ref(false);
@@ -511,6 +569,93 @@
}
};
+// 鐩存帴鍑哄簱
+const requisitionDialogVisible = ref(false);
+const requisitionLoading = ref(false);
+const requisitionFormRef = ref(null);
+const requisitionDeviceOptions = ref([]);
+const requisitionSparePartsOptions = ref([]);
+const selectedSparePartMaxQty = ref(1);
+const requisitionForm = reactive({
+ deviceLedgerId: null,
+ sparePartsId: null,
+ quantity: 1,
+ remark: '',
+});
+const requisitionRules = {
+ sparePartsId: [{ required: true, message: '璇烽�夋嫨澶囦欢', trigger: 'change' }],
+ quantity: [{ required: true, message: '璇疯緭鍏ラ鐢ㄦ暟閲�', trigger: 'blur' }],
+};
+
+const loadRequisitionOptions = async () => {
+ try {
+ const [deviceRes, spareRes] = await Promise.all([
+ getDeviceLedger(),
+ getSparePartsList({ current: 1, size: 9999 }),
+ ]);
+ requisitionDeviceOptions.value = deviceRes.data || [];
+ requisitionSparePartsOptions.value = spareRes.data?.records || [];
+ } catch (e) {
+ ElMessage.error('鍔犺浇閫夐」澶辫触');
+ }
+};
+
+const openRequisitionDialog = async () => {
+ requisitionForm.deviceLedgerId = null;
+ requisitionForm.sparePartsId = null;
+ requisitionForm.quantity = 1;
+ requisitionForm.remark = '';
+ selectedSparePartMaxQty.value = 1;
+ await loadRequisitionOptions();
+ requisitionDialogVisible.value = true;
+};
+
+const onRequisitionSparePartChange = (val) => {
+ const selected = requisitionSparePartsOptions.value.find(item => item.id === val);
+ selectedSparePartMaxQty.value = selected ? selected.quantity : 1;
+ if (requisitionForm.quantity > selectedSparePartMaxQty.value) {
+ requisitionForm.quantity = selectedSparePartMaxQty.value;
+ }
+};
+
+const submitRequisition = async () => {
+ if (!requisitionFormRef.value) return;
+ try {
+ await requisitionFormRef.value.validate();
+ } catch {
+ return;
+ }
+ if (requisitionForm.quantity <= 0) {
+ ElMessage.warning('棰嗙敤鏁伴噺蹇呴』澶т簬0');
+ return;
+ }
+ const selected = requisitionSparePartsOptions.value.find(item => item.id === requisitionForm.sparePartsId);
+ if (selected && requisitionForm.quantity > selected.quantity) {
+ ElMessage.warning(`搴撳瓨涓嶈冻锛屽綋鍓嶅簱瀛樹负 ${selected.quantity}`);
+ return;
+ }
+ requisitionLoading.value = true;
+ try {
+ const res = await outboundSpareParts({
+ deviceLedgerId: requisitionForm.deviceLedgerId || undefined,
+ sparePartsId: requisitionForm.sparePartsId,
+ quantity: requisitionForm.quantity,
+ remark: requisitionForm.remark || undefined,
+ });
+ if (res.code === 200) {
+ ElMessage.success('棰嗙敤鎴愬姛');
+ requisitionDialogVisible.value = false;
+ fetchUsageData();
+ } else {
+ ElMessage.error(res.msg || '棰嗙敤澶辫触');
+ }
+ } catch {
+ ElMessage.error('棰嗙敤澶辫触');
+ } finally {
+ requisitionLoading.value = false;
+ }
+};
+
// 缁勪欢鎸傝浇鏃惰幏鍙栧垪琛ㄦ暟鎹�
onMounted(() => {
fetchListData();
diff --git a/src/views/equipmentManagement/upkeep/index.vue b/src/views/equipmentManagement/upkeep/index.vue
index 7d1cdd2..61fe1a6 100644
--- a/src/views/equipmentManagement/upkeep/index.vue
+++ b/src/views/equipmentManagement/upkeep/index.vue
@@ -142,68 +142,91 @@
</el-button>
<el-button type="danger"
icon="Delete"
- :disabled="multipleList.length <= 0 || hasFinishedStatus"
+ :disabled="multipleList.length <= 0"
@click="delRepairByIds(multipleList.map((item) => item.id))">
鎵归噺鍒犻櫎
</el-button>
</div>
</div>
- <PIMTable rowKey="id"
- isSelection
- :column="columns"
- :tableData="dataList"
- :page="{
- current: pagination.currentPage,
- size: pagination.pageSize,
- total: pagination.total,
- }"
- @selection-change="handleSelectionChange"
- @pagination="changePage">
- <template #maintenanceResultRef="{ row }">
- <div>{{ row.maintenanceResult || '-' }}</div>
- </template>
- <template #statusRef="{ row }">
- <el-tag v-if="row.status === 2"
- type="danger">澶辫触</el-tag>
- <el-tag v-if="row.status === 1"
- type="success">瀹岀粨</el-tag>
- <el-tag v-if="row.status === 0"
- type="warning">寰呬繚鍏�</el-tag>
- </template>
- <template #operation="{ row }">
- <!-- 杩欎釜鍔熻兘璺熸柊澧炰繚鍏诲姛鑳戒竴妯′竴鏍凤紝鏈夊暐鎰忎箟锛� -->
- <!-- <el-button
- type="primary"
- text
- @click="addMaintain(row)"
+ <el-table
+ :data="groupedDataList"
+ border
+ v-loading="tableLoading"
+ @selection-change="handleSelectionChange"
+ :expand-row-keys="expandedRowKeys"
+ :row-key="(row) => row.date"
+ @expand-change="onExpandChange"
>
- 鏂板淇濆吇
- </el-button> -->
- <el-button type="primary"
- link
- :disabled="row.status === 1"
- @click="editPlan(row.id)">
- 缂栬緫
- </el-button>
- <el-button type="success"
- link
- :disabled="row.status === 1"
- @click="addMaintain(row)">
- 淇濆吇
- </el-button>
- <el-button type="danger"
- link
- :disabled="row.status === 1"
- @click="delRepairByIds(row.id)">
- 鍒犻櫎
- </el-button>
- <el-button type="primary"
- link
- @click="openFileDialog(row)">
- 闄勪欢
- </el-button>
- </template>
- </PIMTable>
+ <el-table-column type="selection" width="55" />
+ <el-table-column type="expand">
+ <template #default="props">
+ <el-table :data="props.row.children" border row-key="id">
+ <el-table-column label="璁惧鍚嶇О" prop="deviceName" align="center" />
+ <el-table-column label="瑙勬牸鍨嬪彿" prop="deviceModel" align="center" />
+ <el-table-column label="褰曞叆浜�" prop="createUserName" align="center" />
+ <el-table-column label="淇濆吇椤圭洰" prop="machineryCategory" align="center">
+ <template #default="{ row }">
+ {{ row.machineryCategory || '--' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="瀹為檯淇濆吇浜�" prop="maintenanceActuallyName" align="center" />
+ <el-table-column label="瀹為檯淇濆吇鏃ユ湡" align="center">
+ <template #default="{ row }">
+ {{ row.maintenanceActuallyTime ? dayjs(row.maintenanceActuallyTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="淇濆吇缁撴灉" align="center">
+ <template #default="{ row }">
+ {{ row.maintenanceResult || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鐘舵��" align="center">
+ <template #default="{ row }">
+ <el-tag v-if="row.status === 2" type="danger">澶辫触</el-tag>
+ <el-tag v-if="row.status === 1" type="success">瀹岀粨</el-tag>
+ <el-tag v-if="row.status === 0" type="warning">寰呬繚鍏�</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" width="300">
+ <template #default="{ row }">
+ <el-button type="primary" link :disabled="row.status === 1" @click="editPlan(row.id)">缂栬緫</el-button>
+ <el-button type="success" link :disabled="row.status !== 0" @click="handleSingleComplete(row.id)">淇濆吇瀹屾垚</el-button>
+ <el-button type="success" link :disabled="row.status === 1" @click="addMaintain(row)">淇濆吇</el-button>
+ <el-button type="danger" link :disabled="row.status === 1" @click="delRepairByIds(row.id)">鍒犻櫎</el-button>
+ <el-button type="primary" link @click="openFileDialog(row)">闄勪欢</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </template>
+ </el-table-column>
+ <el-table-column label="璁″垝淇濆吇鏃ユ湡" prop="date" align="center" />
+ <el-table-column label="淇濆吇璁板綍鏁�" prop="count" align="center" />
+ <el-table-column label="寰呬繚鍏绘暟" align="center" width="120">
+ <template #default="{ row }">
+ {{ row.children.filter(c => c.status === 0).length }}
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔" align="center" width="200">
+ <template #default="{ row }">
+ <el-button
+ type="primary"
+ size="small"
+ :disabled="!row.children.some(c => c.status === 0)"
+ @click="handleBatchComplete(row.date)"
+ >
+ 涓�閿繚鍏诲畬鎴�
+ </el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <Pagination
+ v-show="pagination.total > 0"
+ :total="pagination.total"
+ layout="total, sizes, prev, pager, next, jumper"
+ :page="pagination.currentPage"
+ :limit="pagination.pageSize"
+ @pagination="changePage"
+ />
</div>
</el-tab-pane>
</el-tabs>
@@ -217,6 +240,7 @@
v-model:visible="fileDialogVisible"
:record-type="'device_maintenance'"
:record-id="currentMaintenanceTaskId" />
+
</div>
</template>
@@ -240,8 +264,11 @@
delUpkeep,
deviceMaintenanceTaskList,
deviceMaintenanceTaskDel,
+ batchCompleteMaintenance,
+ completeMaintenance,
} from "@/api/equipmentManagement/upkeep";
import dayjs from "dayjs";
+ import Pagination from "@/components/PIMTable/Pagination.vue";
const { proxy } = getCurrentInstance();
const FileList = defineAsyncComponent(() =>
@@ -273,10 +300,56 @@
const dataList = ref([]);
const pagination = ref({
currentPage: 1,
- pageSize: 10,
+ pageSize: 100,
total: 0,
});
const multipleList = ref([]);
+ const tableLoading = ref(false);
+ const expandedRowKeys = ref([]);
+
+ const groupByDate = (list) => {
+ const map = {};
+ list.forEach(item => {
+ const date = item.dateStr || (item.maintenancePlanTime ? dayjs(item.maintenancePlanTime).format('YYYY-MM-DD') : (item.createTime ? dayjs(item.createTime).format('YYYY-MM-DD') : '鏈煡鏃ユ湡'));
+ if (!map[date]) map[date] = [];
+ map[date].push(item);
+ });
+ return Object.entries(map).map(([date, children]) => ({ date, children, count: children.length }));
+ };
+
+ const groupedDataList = computed(() => groupByDate(dataList.value));
+
+ // 鍗曟潯淇濆吇瀹屾垚
+ const handleSingleComplete = async (id) => {
+ try {
+ await ElMessageBox.confirm('纭灏嗚淇濆吇璁板綍鏍囪涓哄畬鎴愶紵', '鎻愮ず', { type: 'warning' });
+ const res = await completeMaintenance(id);
+ if (res.code === 200) {
+ ElMessage.success('淇濆吇瀹屾垚');
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || '鎿嶄綔澶辫触');
+ }
+ } catch { /* 鍙栨秷 */ }
+ };
+
+ // 涓�閿繚鍏诲畬鎴�
+ const handleBatchComplete = async (dateStr) => {
+ try {
+ await ElMessageBox.confirm(`纭灏� ${dateStr} 鐨勬墍鏈夊緟淇濆吇璁板綍鏍囪涓哄畬鎴愶紵`, '鎻愮ず', { type: 'warning' });
+ const res = await batchCompleteMaintenance(dateStr);
+ if (res.code === 200) {
+ ElMessage.success(res.msg || '鎵归噺淇濆吇瀹屾垚鎴愬姛');
+ getTableData();
+ } else {
+ ElMessage.error(res.msg || '鎵归噺淇濆吇瀹屾垚澶辫触');
+ }
+ } catch { /* 鍙栨秷 */ }
+ };
+
+ const onExpandChange = (row, expandedRows) => {
+ expandedRowKeys.value = expandedRows.map(r => r.date);
+ };
// 淇濆吇浠诲姟tab鐩稿叧鍙橀噺
const scheduledFilters = reactive({
@@ -513,6 +586,7 @@
// 淇濆吇璁板綍鐩稿叧鏂规硶锛堝師璁惧淇濆吇椤甸潰鏂规硶锛�
const getTableData = async () => {
+ tableLoading.value = true;
try {
const params = {
current: pagination.value.currentPage,
@@ -534,6 +608,8 @@
}
} catch (error) {
console.log(error);
+ } finally {
+ tableLoading.value = false;
}
};
@@ -546,13 +622,15 @@
};
const handleSelectionChange = selection => {
- multipleList.value = selection;
+ // 浠庨�変腑鐨勫垎缁勮涓敹闆嗘墍鏈夊瓙璁板綍ID
+ const ids = [];
+ selection.forEach(group => {
+ if (group.children) {
+ group.children.forEach(child => ids.push(child.id));
+ }
+ });
+ multipleList.value = ids;
};
-
- // 妫�鏌ラ�変腑鐨勮褰曚腑鏄惁鏈夊畬缁撶姸鎬佺殑
- const hasFinishedStatus = computed(() => {
- return multipleList.value.some(item => item.status === 1);
- });
const changePage = page => {
pagination.value.currentPage = page.page;
@@ -573,12 +651,8 @@
};
const delRepairByIds = async ids => {
- // 妫�鏌ユ槸鍚︽湁瀹岀粨鐘舵�佺殑璁板綍
- const hasFinished = multipleList.value.some(item => item.status === 1);
- if (hasFinished) {
- ElMessage.warning("涓嶈兘鍒犻櫎鐘舵�佷负瀹岀粨鐨勮褰�");
- return;
- }
+ const idList = Array.isArray(ids) ? ids : [ids];
+ if (idList.length === 0) return;
try {
await ElMessageBox.confirm("纭鍒犻櫎淇濆吇鏁版嵁, 姝ゆ搷浣滀笉鍙��?", "璀﹀憡", {
@@ -587,7 +661,7 @@
type: "warning",
});
- const { code } = await delUpkeep(ids);
+ const { code } = await delUpkeep(idList);
if (code === 200) {
ElMessage.success("鍒犻櫎鎴愬姛");
getTableData();
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
index f4e49b6..3a06632 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-bottom.vue
@@ -132,8 +132,11 @@
},
}
+const dateRange = inject('psiDateRange', null)
+
const fetchData = () => {
- productInOutAnalysis({ type: productType.value })
+ const params = { type: productType.value, ...(dateRange?.value || {}) }
+ productInOutAnalysis(params)
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
const list = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
index 6e39eb1..916c089 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-center.vue
@@ -31,6 +31,8 @@
import Echarts from '@/components/Echarts/echarts.vue'
import { productTurnoverDays } from '@/api/viewIndex.js'
+const dateRange = inject('psiDateRange', null)
+
const chartStyle = { width: '100%', height: '100%' }
const grid = { left: '3%', right: '4%', bottom: '3%', top: '4%', containLabel: true }
const barLegend = { show: false, textStyle: { color: '#B8C8E0' }, data: ['鍛ㄨ浆澶╂暟'] }
@@ -69,7 +71,7 @@
const yAxis1 = [{ type: 'value', axisLabel: { color: '#B8C8E0' } }]
const fetchData = () => {
- productTurnoverDays()
+ productTurnoverDays(dateRange?.value || undefined)
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
const list = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
index 055fb66..d461f4f 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/center-top.vue
@@ -36,12 +36,15 @@
const compareClass = (val) => (val >= 0 ? 'compare-up' : 'compare-down')
+const dateRange = inject('psiDateRange', null)
+
const fetchData = () => {
- salesPurchaseStorageProductCount()
+ const params = dateRange?.value || {}
+ salesPurchaseStorageProductCount(params.startDate, params.endDate)
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
statItems.value = res.data.map((item) => ({
- name: item.name,
+ name: item.name === '閿�鍞骇鍝佹暟' ? '閿�鍞鍗曟暟' : item.name,
value: item.value,
rate: item.rate,
}))
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
index 65a72fe..b48f96a 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/left-bottom.vue
@@ -29,6 +29,8 @@
import { rawMaterialPurchaseAmountRatio } from '@/api/viewIndex.js'
import { useChartBackground } from '@/hooks/useChartBackground.js'
+const dateRange = inject('psiDateRange', null)
+
const pieWrapperRef = ref(null)
const pieBackgroundRef = ref(null)
@@ -181,7 +183,7 @@
})
const fetchData = () => {
- rawMaterialPurchaseAmountRatio()
+ rawMaterialPurchaseAmountRatio(dateRange?.value || undefined)
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
const items = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue b/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
index f5dac31..4fa1062 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/components/left-top.vue
@@ -24,6 +24,8 @@
import { ref, onMounted, onBeforeUnmount, computed, inject, watch } from 'vue'
import { productSalesAnalysis } from '@/api/viewIndex.js'
import PanelHeader from './PanelHeader.vue'
+
+const dateRange = inject('psiDateRange', null)
import CarouselCards from './CarouselCards.vue'
import Echarts from '@/components/Echarts/echarts.vue'
import { useChartBackground } from '@/hooks/useChartBackground.js'
@@ -153,7 +155,7 @@
})
const fetchData = () => {
- productSalesAnalysis()
+ productSalesAnalysis(dateRange?.value || undefined)
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
const items = res.data
diff --git a/src/views/reportAnalysis/PSIDataAnalysis/index.vue b/src/views/reportAnalysis/PSIDataAnalysis/index.vue
index f81e482..33a8b92 100644
--- a/src/views/reportAnalysis/PSIDataAnalysis/index.vue
+++ b/src/views/reportAnalysis/PSIDataAnalysis/index.vue
@@ -13,7 +13,22 @@
<!-- 椤堕儴鏍囬鏍� -->
<div class="dashboard-header">
+ <div class="header-left"></div>
<div class="factory-name">PSI 鏁版嵁鍒嗘瀽</div>
+ <div class="header-right">
+ <div class="date-picker-wrapper">
+ <el-date-picker
+ v-model="psiDateRange"
+ type="daterange"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ value-format="YYYY-MM-DD"
+ :shortcuts="dateShortcuts"
+ @change="onDateRangeChange"
+ />
+ </div>
+ </div>
</div>
<!-- 涓昏鍐呭鍖哄煙 -->
@@ -43,8 +58,9 @@
</template>
<script setup>
-import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue'
+import { ref, onMounted, onBeforeUnmount, nextTick, provide, reactive } from 'vue'
import autofit from 'autofit.js'
+import dayjs from 'dayjs'
import LeftBottom from './components/left-bottom.vue'
import CenterCenter from './components/center-center.vue'
import RightTop from '../dataDashboard/components/basic/right-top.vue'
@@ -65,6 +81,27 @@
// 鐢ㄦ埛store
const userStore = useUserStore()
+
+// 鏃ユ湡鑼冨洿绛涢��
+const psiDateRange = ref([])
+const psiDateRangeParams = ref({})
+const dateShortcuts = [
+ { text: '鏈湀', value: () => { const start = dayjs().startOf('month').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+ { text: '杩�7澶�', value: () => { const start = dayjs().subtract(6, 'day').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+ { text: '杩�30澶�', value: () => { const start = dayjs().subtract(29, 'day').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+ { text: '杩�90澶�', value: () => { const start = dayjs().subtract(89, 'day').format('YYYY-MM-DD'); const end = dayjs().format('YYYY-MM-DD'); return [start, end] } },
+]
+
+const onDateRangeChange = (val) => {
+ if (val && val.length === 2) {
+ psiDateRangeParams.value = { startDate: val[0], endDate: val[1] }
+ } else {
+ psiDateRangeParams.value = {}
+ }
+ dataDashboardRefreshTick.value++
+}
+
+provide('psiDateRange', psiDateRangeParams)
/** 涓� dataDashboard 鍏辩敤娉ㄥ叆鍚嶏紝瀛愮粍浠讹紙鍚鐢ㄧ殑 right-top/right-bottom锛夋瘡鍒嗛挓鍒锋柊 */
const DASHBOARD_REFRESH_MS = 60 * 1000
@@ -237,15 +274,36 @@
background-repeat: no-repeat;
display: flex;
align-items: center;
-justify-content: center;
+justify-content: space-between;
+}
+
+.header-left {
+width: 300px;
+padding-left: 20px;
+display: flex;
+align-items: center;
+}
+
+.header-right {
+width: 300px;
+display: flex;
+align-items: center;
+justify-content: flex-end;
+padding-right: 20px;
+}
+
+.date-picker-wrapper {
+/* 鏃ユ湡閫夋嫨鍣ㄥ湪澶у睆涓婄殑鏍峰紡 */
+--el-fill-color-blank: rgba(0, 20, 60, 0.8);
+--el-border-color: rgba(0, 212, 255, 0.3);
+--el-text-color-regular: #00d4ff;
+--el-color-primary: #00d4ff;
}
.factory-name {
font-weight: 600;
font-size: 52px;
color: #FFFFFF;
-top: 16px;
-position: absolute;
}
.fullscreen-btn {
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue b/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
index 6fcd80f..7786c9a 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/right-bottom.vue
@@ -28,6 +28,8 @@
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { customerContributionRanking } from '@/api/viewIndex.js'
+const dateRange = inject('psiDateRange', null)
+
const chartStyle = {
width: '100%',
height: '100%',
@@ -287,7 +289,7 @@
}
const fetchCustomerRanking = () => {
- customerContributionRanking({ type: dateType.value })
+ customerContributionRanking({ type: dateType.value, ...(dateRange?.value || {}) })
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
dataArr.value = res.data.map(item => ({
diff --git a/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue b/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
index 96e4548..bfc0dea 100644
--- a/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
+++ b/src/views/reportAnalysis/dataDashboard/components/basic/right-top.vue
@@ -27,6 +27,8 @@
import DateTypeSwitch from '../DateTypeSwitch.vue'
import { supplierPurchaseRanking } from '@/api/viewIndex.js'
+const dateRange = inject('psiDateRange', null)
+
const chartStyle = {
width: '100%',
height: '100%',
@@ -295,7 +297,7 @@
// 渚涘簲鍟嗛噰璐帓鍚�
const fetchSupplierRanking = () => {
- supplierPurchaseRanking({ type: radio1.value })
+ supplierPurchaseRanking({ type: radio1.value, ...(dateRange?.value || {}) })
.then((res) => {
if (res.code === 200 && Array.isArray(res.data)) {
dataArr.value = res.data.map(item => ({
diff --git a/src/views/reportAnalysis/taxComparison/index.vue b/src/views/reportAnalysis/taxComparison/index.vue
index d27ab07..c28d2f6 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 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: "澧炲�肩◣姣斿",
});
-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");
+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());
+ }
})
- .catch(() => {
- proxy.$modal.msg("宸插彇娑�");
+ .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