未知(继上次中断)

This commit is contained in:
Cheng Zhou
2026-02-04 10:52:34 +08:00
parent 8e258d21a7
commit 737f84bf54
99 changed files with 5497 additions and 2143 deletions
+95 -107
View File
@@ -1,14 +1,5 @@
<template>
<div class="page">
<div class="page-header">
<div>
<h1 class="page-title">{{ TEXT.modules.feeContracts.title }}</h1>
</div>
<el-button type="primary" :disabled="!canWrite" @click="goNew" class="header-action-btn">
<el-icon class="el-icon--left"><Plus /></el-icon>
{{ TEXT.modules.feeContracts.newTitle }}
</el-button>
</div>
<div class="overview">
<el-row :gutter="20">
@@ -55,34 +46,39 @@
</el-row>
</div>
<el-card class="filter-card" shadow="never">
<el-form :inline="true" :model="filters" class="filter-form">
<el-form-item label="" class="filter-item">
<el-select v-model="filters.centerId" clearable :placeholder="TEXT.common.fields.site" class="filter-select">
<template #prefix>
<el-icon><Location /></el-icon>
</template>
<el-option
v-for="site in sites"
:key="site.id"
:label="site.name || TEXT.common.fallback"
:value="site.id"
/>
</el-select>
</el-form-item>
<el-form-item label="" class="filter-item">
<el-input v-model="filters.q" :placeholder="TEXT.common.placeholders.keyword" clearable class="filter-input">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item class="filter-actions">
<el-button type="primary" @click="load">{{ TEXT.common.actions.search }}</el-button>
<el-button @click="resetFilters">{{ TEXT.common.actions.reset }}</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="main-content-card" v-if="!errorMessage && !loading">
<div class="filter-container">
<el-form :inline="true" :model="filters" class="filter-form">
<el-form-item label="" class="filter-item">
<el-select v-model="filters.centerId" clearable :placeholder="TEXT.common.fields.site" class="filter-select">
<template #prefix>
<el-icon><Location /></el-icon>
</template>
<el-option
v-for="site in sites"
:key="site.id"
:label="site.name || TEXT.common.fallback"
:value="site.id"
/>
</el-select>
</el-form-item>
<el-form-item label="" class="filter-item">
<el-input v-model="filters.q" :placeholder="TEXT.common.placeholders.keyword" clearable class="filter-input">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item class="filter-actions">
<el-button @click="resetFilters">{{ TEXT.common.actions.reset }}</el-button>
</el-form-item>
<div class="filter-spacer"></div>
<el-button type="primary" :disabled="!canWrite" @click="goNew" class="header-action-btn">
<el-icon class="el-icon--left"><Plus /></el-icon>
{{ TEXT.modules.feeContracts.newTitle }}
</el-button>
</el-form>
</div>
<StateError v-if="errorMessage" :description="errorMessage">
<template #action>
@@ -92,7 +88,6 @@
<StateLoading v-else-if="loading" :rows="6" />
<el-card v-else class="table-card" shadow="never">
<el-table
:data="sortedContracts"
style="width: 100%"
@@ -100,26 +95,26 @@
:row-class-name="contractRowClass"
:header-cell-style="{ background: '#f8f9fb' }"
>
<el-table-column prop="center_name" :label="TEXT.common.fields.site" min-width="180">
<el-table-column prop="center_name" :label="TEXT.common.fields.site" min-width="160">
<template #default="scope">
<div class="site-cell">
<span class="site-name">{{ scope.row.center_name || TEXT.common.fallback }}</span>
</div>
</template>
</el-table-column>
<el-table-column :label="TEXT.modules.feeContracts.contractAmount" width="160" align="right">
<el-table-column :label="TEXT.modules.feeContracts.contractAmount" width="140" align="right">
<template #default="scope">
<span class="amount-text">{{ formatAmountWan(scope.row.contract_amount) }}</span>
</template>
</el-table-column>
<el-table-column :label="TEXT.modules.feeContracts.caseProgress" min-width="150" align="center">
<el-table-column :label="TEXT.modules.feeContracts.caseProgress" min-width="140" align="center">
<template #default="scope">
<el-tag effect="plain" type="info">
{{ displayCases(scope.row.contract_cases, scope.row.actual_cases) }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="TEXT.modules.feeContracts.paidSummary" min-width="180">
<el-table-column :label="TEXT.modules.feeContracts.paidSummary" min-width="200">
<template #default="scope">
<div class="summary-line">
<span>{{ TEXT.modules.feeContracts.paidTotal }}</span>
@@ -132,7 +127,7 @@
</div>
</template>
</el-table-column>
<el-table-column :label="TEXT.modules.feeContracts.verifySummary" min-width="180">
<el-table-column :label="TEXT.modules.feeContracts.verifySummary" min-width="200">
<template #default="scope">
<div class="summary-line">
<span>{{ TEXT.modules.feeContracts.verifiedTotal }}</span>
@@ -145,7 +140,7 @@
</div>
</template>
</el-table-column>
<el-table-column :label="TEXT.modules.feeContracts.recentDates" min-width="200">
<el-table-column :label="TEXT.modules.feeContracts.recentDates" min-width="180">
<template #default="scope">
<div class="date-line">
<span class="date-label">{{ TEXT.modules.feeContracts.lastPaid }}</span>
@@ -157,12 +152,7 @@
</div>
</template>
</el-table-column>
<el-table-column :label="TEXT.common.fields.remark" min-width="180" show-overflow-tooltip>
<template #default="scope">
{{ scope.row.remark || TEXT.common.fallback }}
</template>
</el-table-column>
<el-table-column :label="TEXT.common.labels.actions" width="120">
<el-table-column :label="TEXT.common.labels.actions" width="100">
<template #default="scope">
<el-button
link
@@ -182,7 +172,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref, watch } from "vue";
import { useRouter } from "vue-router";
import { ElMessage, ElMessageBox } from "element-plus";
import { Coin, Wallet, CircleCheck, CircleClose, Plus, Location, Search } from "@element-plus/icons-vue";
@@ -214,14 +204,26 @@ const siteActiveMap = computed(() => {
return map;
});
const isInactiveSite = (siteId?: string) => !!siteId && siteActiveMap.value[siteId] === false;
const filteredContracts = computed(() => {
const keyword = filters.q.trim().toLowerCase();
return contracts.value.filter((item) => {
if (filters.centerId && item?.center_id !== filters.centerId) return false;
if (keyword) {
const fields = [item?.center_name, item?.contract_no, item?.contract_name];
const matched = fields.some((field) => String(field || "").toLowerCase().includes(keyword));
if (!matched) return false;
}
return true;
});
});
const sortedContracts = computed(() =>
[...contracts.value].sort((a, b) => Number(isInactiveSite(a?.center_id)) - Number(isInactiveSite(b?.center_id)))
[...filteredContracts.value].sort((a, b) => Number(isInactiveSite(a?.center_id)) - Number(isInactiveSite(b?.center_id)))
);
const contractRowClass = ({ row }: { row: any }) =>
`${row?.id ? "clickable-row" : ""}${isInactiveSite(row?.center_id) ? " row-inactive" : ""}`.trim();
const filters = reactive({
centerId: "",
centerId: study.currentSite?.id || "",
q: "",
});
@@ -231,7 +233,7 @@ const toNumber = (value: any) => {
};
const overview = computed(() => {
const totals = contracts.value.reduce(
const totals = filteredContracts.value.reduce(
(acc, item) => {
acc.totalContractAmount += toNumber(item.contract_amount);
acc.totalPaidAmount += toNumber(item.paid_total);
@@ -248,7 +250,7 @@ const overview = computed(() => {
);
return {
...totals,
centerCount: contracts.value.length,
centerCount: filteredContracts.value.length,
};
});
@@ -269,11 +271,7 @@ const load = async () => {
loading.value = true;
errorMessage.value = "";
try {
const { data } = await listContractFees({
projectId,
centerId: filters.centerId || undefined,
q: filters.q || undefined,
});
const { data } = await listContractFees({ projectId });
contracts.value = data?.data || [];
} catch (e: any) {
errorMessage.value = e?.response?.data?.message || TEXT.common.messages.loadFailed;
@@ -285,7 +283,6 @@ const load = async () => {
const resetFilters = () => {
filters.centerId = "";
filters.q = "";
load();
};
const goNew = () => router.push("/fees/contracts/new");
@@ -327,6 +324,19 @@ const formatAmountWan = (value: any) => {
return (numberValue / 10000).toFixed(2);
};
watch(() => filters.centerId, (val: string) => {
if (val) {
const matched = sites.value.find(s => s.id === val);
if (matched) study.setCurrentSite(matched);
} else {
study.setCurrentSite(null);
}
});
watch(() => study.currentSite, (newSite: any) => {
filters.centerId = newSite?.id || "";
});
onMounted(async () => {
await loadSites();
load();
@@ -337,58 +347,21 @@ onMounted(async () => {
.page {
display: flex;
flex-direction: column;
gap: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
.main-content-card :deep(.el-card__body) {
padding: 20px;
}
.page-title {
margin: 0;
font-size: 24px;
font-weight: 700;
color: var(--ctms-text-main);
letter-spacing: -0.5px;
}
.page-subtitle {
margin: 6px 0 0;
font-size: 14px;
color: var(--ctms-text-secondary);
}
.header-action-btn {
height: 36px;
padding: 0 20px;
border-radius: 8px;
}
.overview {
margin-bottom: 4px;
}
.filter-card {
border: none;
background-color: transparent;
padding: 0;
}
.filter-card :deep(.el-card__body) {
padding: 0;
.filter-container {
margin-bottom: 20px;
}
.filter-form {
display: flex;
width: 100%;
gap: 12px;
align-items: center;
background: white;
padding: 16px;
border-radius: 12px;
border: 1px solid var(--ctms-border-color);
}
.filter-item {
@@ -398,14 +371,17 @@ onMounted(async () => {
.filter-select,
.filter-input {
width: 240px;
width: 140px;
}
.filter-actions {
margin-left: auto !important;
margin-bottom: 0 !important;
}
.filter-spacer {
flex: 1;
}
.table-card {
border-radius: 12px;
border: 1px solid var(--ctms-border-color);
@@ -424,22 +400,32 @@ onMounted(async () => {
.summary-line {
display: flex;
justify-content: space-between;
justify-content: flex-start;
align-items: center;
font-size: 13px;
margin-bottom: 4px;
color: var(--ctms-text-regular);
gap: 8px;
}
.summary-line:last-child {
margin-bottom: 0;
}
.summary-line span:first-child {
color: var(--ctms-text-secondary);
}
.amount-highlight {
font-weight: 600;
font-family: var(--el-font-family);
}
.summary-line .amount-highlight,
.summary-line .amount-muted {
text-align: left;
}
.amount-highlight.success { color: var(--el-color-success); }
.amount-highlight.warning { color: var(--el-color-warning); }
.amount-highlight.info { color: var(--el-color-info); }
@@ -447,9 +433,11 @@ onMounted(async () => {
.date-line {
display: flex;
justify-content: space-between;
justify-content: flex-start;
align-items: center;
font-size: 12px;
margin-bottom: 2px;
gap: 8px;
}
.date-label {