批量更新功能
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
<!-- 统计周期 -->
|
||||
<div class="stat-period-bar">
|
||||
<el-radio-group v-model="statsPeriod" @change="onPeriodChange">
|
||||
<el-radio-button label="all">全部</el-radio-button>
|
||||
<el-radio-button label="month">本月</el-radio-button>
|
||||
<el-radio-button label="quarter">本季</el-radio-button>
|
||||
<el-radio-button label="year">本年</el-radio-button>
|
||||
@@ -30,10 +31,12 @@
|
||||
:clearable="false"
|
||||
@change="onRangeChange"
|
||||
/>
|
||||
<!-- 当前统计范围:切换周期后卡片按该区间重新统计(区间相同则数字自然相同) -->
|
||||
<span class="stat-scope">统计范围:{{ statsScopeText }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stat-cards">
|
||||
<div class="stat-cards" v-loading="statsLoading">
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">合同总数</span>
|
||||
<span class="stat-value">{{ stats.total ?? 0 }}</span>
|
||||
@@ -47,7 +50,12 @@
|
||||
<span class="stat-value">{{ formatMoney(stats.total_cost) }}</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-label">合同总利润</span>
|
||||
<el-tooltip
|
||||
content="仅统计非甲方(收款方)合同的利润;我方为甲方的合同属于支出,不计入利润"
|
||||
placement="top"
|
||||
>
|
||||
<span class="stat-label">合同总利润</span>
|
||||
</el-tooltip>
|
||||
<span class="stat-value" :style="profitStyle(stats.total_profit)">{{ profitText(stats.total_profit) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,8 +73,8 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="我方角色">
|
||||
<el-select v-model="filters.our_role" clearable placeholder="全部" style="width: 110px">
|
||||
<el-option v-for="i in OUR_ROLE_OPTIONS" :key="i.value" :label="i.label" :value="i.value" />
|
||||
<el-select v-model="filters.our_role" clearable placeholder="全部" style="width: 130px">
|
||||
<el-option v-for="i in ourRoleFilterOptions" :key="i.value" :label="i.label" :value="i.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同分类">
|
||||
@@ -101,8 +109,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contract_no" label="合同编号" width="150" show-overflow-tooltip />
|
||||
<el-table-column label="合同名称" min-width="190" show-overflow-tooltip fixed>
|
||||
<el-table-column label="我方角色" width="90" align="center" fixed>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="ourRoleTag(row.our_role)" size="small" effect="plain">{{ ourRoleText(row.our_role) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="contract_no" label="合同编号" width="150" show-overflow-tooltip /> -->
|
||||
<el-table-column label="合同名称" min-width="190" show-overflow-tooltip >
|
||||
<template #default="{ row }">
|
||||
<span class="name-link" @click="openRow(row)">
|
||||
{{ row.contract_name }}
|
||||
@@ -110,11 +123,6 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="我方角色" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="primary" size="small" effect="plain">{{ ourRoleText(row.our_role) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分类" width="100" align="center">
|
||||
<template #default="{ row }">{{ contractCategoryText(row.contract_category) }}</template>
|
||||
</el-table-column>
|
||||
@@ -128,20 +136,28 @@
|
||||
{{ formatMoney(row.summary?.total_amount ?? row.total_amount) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合同总利润" width="130" align="right">
|
||||
<el-table-column label="合同利润/支出" width="140" align="right">
|
||||
<template #header>
|
||||
<el-tooltip
|
||||
content="我方为甲方(付款方):按负数(绿色)显示合同支出 = 合同总金额;其余角色:显示合同利润 = 总金额 − 产品总成本"
|
||||
placement="top"
|
||||
>
|
||||
<span>合同利润/支出</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<span :style="profitStyle(row.summary?.total_profit ?? row.total_profit)">
|
||||
{{ profitText(row.summary?.total_profit ?? row.total_profit) }}
|
||||
<span :style="profitStyle(contractProfitOrExpense(row))">
|
||||
{{ profitText(contractProfitOrExpense(row)) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签订日期" width="110" align="center">
|
||||
<template #default="{ row }">{{ formatDateOnly(row.sign_date) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="逾期" width="100" align="center">
|
||||
<el-table-column label="逾期" width="130" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="contractOverdue(row).overdue" type="danger" size="small" effect="dark">
|
||||
{{ overdueText(contractOverdue(row).days) }}
|
||||
{{ contractOverdue(row).text }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
@@ -184,7 +200,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, nextTick, watch } from "vue";
|
||||
import { ref, reactive, computed, onMounted, nextTick, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus, Search, Refresh } from "@element-plus/icons-vue";
|
||||
@@ -197,16 +213,17 @@ import ContractCreate from "./components/create.vue";
|
||||
import ContractDetail from "./components/detail.vue";
|
||||
import {
|
||||
CONTRACT_CATEGORY_OPTIONS,
|
||||
OUR_ROLE_OPTIONS,
|
||||
ourRoleFilterOptions,
|
||||
CONTRACT_STATUS_OPTIONS,
|
||||
ourRoleText,
|
||||
ourRoleTag,
|
||||
contractCategoryText,
|
||||
contractStatusText,
|
||||
contractStatusTag,
|
||||
contractOverdue,
|
||||
overdueText,
|
||||
formatDateOnly,
|
||||
formatMoney,
|
||||
isPayerContract,
|
||||
} from "../dict";
|
||||
|
||||
const route = useRoute();
|
||||
@@ -228,15 +245,46 @@ const filters = reactive({
|
||||
});
|
||||
const pagination = reactive({ page: 1, pageSize: 20, total: 0 });
|
||||
|
||||
/** 统计周期:month/quarter/year/custom(自定义选区间),默认本年 */
|
||||
const statsPeriod = ref("year");
|
||||
/** 统计周期:all(全部)/ month / quarter / year / custom(自定义选区间),默认全部 */
|
||||
const statsPeriod = ref("all");
|
||||
const statsRange = ref([]);
|
||||
|
||||
/** 列表页统计:后端按周期汇总(排除已作废),周期参数透传给 Stats */
|
||||
const stats = ref({ total: 0, total_amount: 0, total_cost: 0, total_profit: 0 });
|
||||
/** 卡片加载态:切换周期时给出可见的刷新反馈 */
|
||||
const statsLoading = ref(false);
|
||||
|
||||
/** 周期参数:统计与列表共用,保证两者口径一致 */
|
||||
/** 当前统计范围文案:与后端口径一致(合同有效期与所选周期重叠),便于对照卡片数字 */
|
||||
const statsScopeText = computed(() => {
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
const fmt = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
||||
const now = new Date();
|
||||
const y = now.getFullYear();
|
||||
const m = now.getMonth();
|
||||
switch (statsPeriod.value) {
|
||||
case "month":
|
||||
return `${fmt(new Date(y, m, 1))} ~ ${fmt(new Date(y, m + 1, 0))}`;
|
||||
case "quarter": {
|
||||
const q = Math.floor(m / 3);
|
||||
return `${fmt(new Date(y, q * 3, 1))} ~ ${fmt(new Date(y, q * 3 + 3, 0))}`;
|
||||
}
|
||||
case "year":
|
||||
return `${y}-01-01 ~ ${y}-12-31`;
|
||||
case "custom":
|
||||
return statsRange.value?.length === 2
|
||||
? `${statsRange.value[0]} ~ ${statsRange.value[1]}`
|
||||
: "自定义区间";
|
||||
default:
|
||||
return "全部时间(不限有效期)";
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 周期参数:统计与列表共用,保证两者口径一致。
|
||||
* 「全部」不传周期参数——列表不按有效期过滤,避免有效期不在所选周期内的合同(如历史合同)被隐藏。
|
||||
*/
|
||||
function periodParams() {
|
||||
if (statsPeriod.value === "all") return {};
|
||||
const params = { period: statsPeriod.value };
|
||||
if (statsPeriod.value === "custom" && statsRange.value && statsRange.value.length === 2) {
|
||||
params.start_date = statsRange.value[0];
|
||||
@@ -246,11 +294,14 @@ function periodParams() {
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
statsLoading.value = true;
|
||||
try {
|
||||
const res = await getContractStats(periodParams());
|
||||
stats.value = res?.data || stats.value;
|
||||
} catch {
|
||||
/* 统计失败不影响列表 */
|
||||
} finally {
|
||||
statsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +424,18 @@ function profitStyle(val) {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表展示值:我方为甲方(付款方)的合同是「支出」,取负值 → 显示为 -¥X(绿色);
|
||||
* 其余角色的合同是「利润」(总金额 − 产品总成本)。表头已注明口径,单元格内不写"支出"文字。
|
||||
*/
|
||||
function contractProfitOrExpense(row) {
|
||||
if (!isPayerContract(row.our_role)) {
|
||||
return Number(row.summary?.total_profit ?? row.total_profit) || 0;
|
||||
}
|
||||
const paid = Number(row.summary?.total_amount ?? row.total_amount) || 0;
|
||||
return -paid;
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
currentRow.value = null;
|
||||
initStep.value = 1;
|
||||
@@ -434,6 +497,13 @@ async function handleDelete(row) {
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
/* 当前统计范围:右对齐说明,切换周期后随卡片一起刷新 */
|
||||
.stat-scope {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
|
||||
Reference in New Issue
Block a user