管理后台前后端逻辑、UI美化
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<p class="page-subtitle">{{ TEXT.modules.feeContracts.detailSubtitle }}</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" :disabled="!canWrite" @click="goEdit" class="header-action-btn copy-btn">
|
||||
<el-button type="primary" :disabled="!canWrite || isReadOnly" @click="goEdit" class="header-action-btn copy-btn">
|
||||
<el-icon class="el-icon--left"><Edit /></el-icon>
|
||||
{{ TEXT.common.actions.edit }}
|
||||
</el-button>
|
||||
@@ -106,6 +106,7 @@
|
||||
:entity-type="'contract_fee'"
|
||||
:entity-id="contractId"
|
||||
:groups="attachmentGroups"
|
||||
:readonly="isReadOnly"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -115,6 +116,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Edit } from "@element-plus/icons-vue";
|
||||
import { getContractFee } from "../../api/feeContracts";
|
||||
import { fetchSites } from "../../api/sites";
|
||||
@@ -145,6 +147,14 @@ const detail = reactive<any>({
|
||||
payments: [],
|
||||
});
|
||||
const sites = ref<any[]>([]);
|
||||
const siteActiveMap = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
sites.value.forEach((site) => {
|
||||
if (site?.id) map[site.id] = !!site.is_active;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
const isReadOnly = computed(() => !!detail.center_id && siteActiveMap.value[detail.center_id] === false);
|
||||
|
||||
const attachmentGroups = [
|
||||
{
|
||||
@@ -204,7 +214,13 @@ const formatAmountWan = (value: any) => {
|
||||
return (numberValue / 10000).toFixed(2);
|
||||
};
|
||||
|
||||
const goEdit = () => router.push(`/fees/contracts/${contractId}/edit`);
|
||||
const goEdit = () => {
|
||||
if (isReadOnly.value) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
router.push(`/fees/contracts/${contractId}/edit`);
|
||||
};
|
||||
const goBack = () => router.push("/fees/contracts");
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -29,39 +29,69 @@
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.common.fields.site" prop="center_id" required>
|
||||
<el-select v-model="form.center_id" :disabled="isEdit" :placeholder="TEXT.common.placeholders.select" class="full-width">
|
||||
<el-select
|
||||
v-model="form.center_id"
|
||||
:disabled="isEdit || isReadOnly"
|
||||
:placeholder="TEXT.common.placeholders.select"
|
||||
class="full-width"
|
||||
>
|
||||
<el-option
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
:label="site.name || TEXT.common.fallback"
|
||||
:value="site.id"
|
||||
:disabled="!site.is_active"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.contractAmount" prop="contract_amount" required>
|
||||
<el-input-number v-model="form.contract_amount" :min="0" :precision="2" :step="1000" class="full-width" controls-position="right" />
|
||||
<el-input-number
|
||||
v-model="form.contract_amount"
|
||||
:disabled="isReadOnly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1000"
|
||||
class="full-width"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.contractCases" prop="contract_cases" required>
|
||||
<el-input-number v-model="form.contract_cases" :min="0" :step="1" class="full-width" controls-position="right" />
|
||||
<el-input-number v-model="form.contract_cases" :disabled="isReadOnly" :min="0" :step="1" class="full-width" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.actualCases" prop="actual_cases">
|
||||
<el-input-number v-model="form.actual_cases" :min="0" :step="1" class="full-width" controls-position="right" />
|
||||
<el-input-number v-model="form.actual_cases" :disabled="isReadOnly" :min="0" :step="1" class="full-width" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.settlementAmount" prop="settlement_amount">
|
||||
<el-input-number v-model="form.settlement_amount" :min="0" :precision="2" :step="1000" class="full-width" controls-position="right" />
|
||||
<el-input-number
|
||||
v-model="form.settlement_amount"
|
||||
:disabled="isReadOnly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1000"
|
||||
class="full-width"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.finalPaymentAmount" prop="final_payment_amount">
|
||||
<el-input-number v-model="form.final_payment_amount" :min="0" :precision="2" :step="1000" class="full-width" controls-position="right" />
|
||||
<el-input-number
|
||||
v-model="form.final_payment_amount"
|
||||
:disabled="isReadOnly"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1000"
|
||||
class="full-width"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -71,7 +101,7 @@
|
||||
<template #header>
|
||||
<div class="card-header actions-header">
|
||||
<span class="header-title">{{ TEXT.modules.feeContracts.paymentTitle }}</span>
|
||||
<el-button type="primary" @click="addPayment">
|
||||
<el-button type="primary" :disabled="isReadOnly" @click="addPayment">
|
||||
<el-icon class="el-icon--left"><Plus /></el-icon>
|
||||
{{ TEXT.common.actions.add }}
|
||||
</el-button>
|
||||
@@ -90,7 +120,7 @@
|
||||
<span class="seq-circle">{{ index + 1 }}</span>
|
||||
<span class="seq-text">{{ TEXT.modules.feeContracts.paymentSeqUnit }}</span>
|
||||
</div>
|
||||
<el-button link type="danger" @click="removePayment(index)">
|
||||
<el-button link type="danger" :disabled="isReadOnly" @click="removePayment(index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -98,13 +128,13 @@
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="TEXT.common.fields.amount" :error="paymentErrors[index]?.amount">
|
||||
<el-input-number v-model="payment.amount" :min="0" :precision="2" class="full-width" controls-position="right" />
|
||||
<el-input-number v-model="payment.amount" :disabled="isReadOnly" :min="0" :precision="2" class="full-width" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.paidFlag" :error="paymentErrors[index]?.paid_date">
|
||||
<div class="status-control">
|
||||
<el-checkbox v-model="payment.is_paid" @change="() => onPaidToggle(payment)">
|
||||
<el-checkbox v-model="payment.is_paid" :disabled="isReadOnly" @change="() => onPaidToggle(payment)">
|
||||
{{ TEXT.modules.feeContracts.isPaid }}
|
||||
</el-checkbox>
|
||||
<el-date-picker
|
||||
@@ -113,6 +143,7 @@
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="TEXT.common.placeholders.select"
|
||||
:disabled="isReadOnly"
|
||||
class="status-picker"
|
||||
size="small"
|
||||
/>
|
||||
@@ -122,7 +153,7 @@
|
||||
<el-col :span="8">
|
||||
<el-form-item :label="TEXT.modules.feeContracts.verifiedFlag" :error="paymentErrors[index]?.verified_date">
|
||||
<div class="status-control">
|
||||
<el-checkbox v-model="payment.is_verified" @change="() => onVerifiedToggle(payment)">
|
||||
<el-checkbox v-model="payment.is_verified" :disabled="isReadOnly" @change="() => onVerifiedToggle(payment)">
|
||||
{{ TEXT.modules.feeContracts.isVerified }}
|
||||
</el-checkbox>
|
||||
<el-date-picker
|
||||
@@ -131,6 +162,7 @@
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="TEXT.common.placeholders.select"
|
||||
:disabled="isReadOnly"
|
||||
class="status-picker"
|
||||
size="small"
|
||||
/>
|
||||
@@ -140,7 +172,7 @@
|
||||
</el-row>
|
||||
|
||||
<el-form-item :label="TEXT.common.fields.remark" class="mb-0">
|
||||
<el-input v-model="payment.remark" type="textarea" :rows="2" :placeholder="TEXT.common.placeholders.input" />
|
||||
<el-input v-model="payment.remark" :disabled="isReadOnly" type="textarea" :rows="2" :placeholder="TEXT.common.placeholders.input" />
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="paymentErrors[index]?.verification" class="payment-error">
|
||||
@@ -162,6 +194,7 @@
|
||||
:entity-type="'contract_fee'"
|
||||
:entity-id="form.id"
|
||||
:groups="attachmentGroups"
|
||||
:readonly="isReadOnly"
|
||||
/>
|
||||
|
||||
<StateEmpty v-else :description="TEXT.modules.feeContracts.uploadHint" class="compact-empty" />
|
||||
@@ -169,7 +202,7 @@
|
||||
|
||||
<div class="footer-actions">
|
||||
<el-button @click="goBack">{{ TEXT.common.actions.cancel }}</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="submit" class="save-btn">
|
||||
<el-button type="primary" :loading="saving" :disabled="isReadOnly" @click="submit" class="save-btn">
|
||||
{{ TEXT.common.actions.save }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -207,6 +240,14 @@ const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const sites = ref<any[]>([]);
|
||||
const siteActiveMap = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
sites.value.forEach((site) => {
|
||||
if (site?.id) map[site.id] = !!site.is_active;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
const isReadOnly = computed(() => isEdit.value && !!form.center_id && siteActiveMap.value[form.center_id] === false);
|
||||
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
@@ -302,6 +343,10 @@ const loadDetail = async () => {
|
||||
};
|
||||
|
||||
const addPayment = () => {
|
||||
if (isReadOnly.value) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
payments.value.push({
|
||||
tempId: `${Date.now()}-${Math.random()}`,
|
||||
amount: 0,
|
||||
@@ -314,6 +359,10 @@ const addPayment = () => {
|
||||
};
|
||||
|
||||
const removePayment = (index: number) => {
|
||||
if (isReadOnly.value) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
const payment = payments.value[index];
|
||||
if (payment?.id) {
|
||||
removedPaymentIds.value.push(payment.id);
|
||||
@@ -322,6 +371,7 @@ const removePayment = (index: number) => {
|
||||
};
|
||||
|
||||
const onPaidToggle = (payment: any) => {
|
||||
if (isReadOnly.value) return;
|
||||
if (!payment.is_paid) {
|
||||
payment.paid_date = "";
|
||||
payment.is_verified = false;
|
||||
@@ -330,6 +380,7 @@ const onPaidToggle = (payment: any) => {
|
||||
};
|
||||
|
||||
const onVerifiedToggle = (payment: any) => {
|
||||
if (isReadOnly.value) return;
|
||||
if (payment.is_verified && !payment.is_paid) {
|
||||
payment.is_paid = true;
|
||||
}
|
||||
@@ -367,6 +418,14 @@ const validatePayments = () => {
|
||||
|
||||
const submit = async () => {
|
||||
if (!study.currentStudy?.id) return;
|
||||
if (isReadOnly.value) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
if (form.center_id && siteActiveMap.value[form.center_id] === false) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
const formOk = await formRef.value?.validate?.().catch(() => false);
|
||||
if (!formOk) return;
|
||||
if (!validatePayments()) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">{{ TEXT.modules.feeContracts.title }}</h1>
|
||||
<p class="page-subtitle">{{ TEXT.modules.feeContracts.subtitle }}</p>
|
||||
</div>
|
||||
<el-button type="primary" :disabled="!canWrite" @click="goNew" class="header-action-btn">
|
||||
<el-icon class="el-icon--left"><Plus /></el-icon>
|
||||
@@ -94,7 +93,13 @@
|
||||
<StateLoading v-else-if="loading" :rows="6" />
|
||||
|
||||
<el-card v-else class="table-card" shadow="never">
|
||||
<el-table :data="contracts" style="width: 100%" @row-click="onRowClick" :header-cell-style="{ background: '#f8f9fb' }">
|
||||
<el-table
|
||||
:data="sortedContracts"
|
||||
style="width: 100%"
|
||||
@row-click="onRowClick"
|
||||
:row-class-name="contractRowClass"
|
||||
:header-cell-style="{ background: '#f8f9fb' }"
|
||||
>
|
||||
<el-table-column prop="center_name" :label="TEXT.common.fields.site" min-width="180">
|
||||
<template #default="scope">
|
||||
<div class="site-cell">
|
||||
@@ -163,7 +168,7 @@
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
:disabled="!canWrite"
|
||||
:disabled="!canWrite || isInactiveSite(scope.row.center_id)"
|
||||
@click.stop="remove(scope.row)"
|
||||
>
|
||||
{{ TEXT.common.actions.delete }}
|
||||
@@ -171,7 +176,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<StateEmpty v-if="contracts.length === 0" :description="TEXT.modules.feeContracts.empty" />
|
||||
<StateEmpty v-if="sortedContracts.length === 0" :description="TEXT.modules.feeContracts.empty" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -201,6 +206,19 @@ const loading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const contracts = ref<any[]>([]);
|
||||
const sites = ref<any[]>([]);
|
||||
const siteActiveMap = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
sites.value.forEach((site) => {
|
||||
if (site?.id) map[site.id] = !!site.is_active;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
const isInactiveSite = (siteId?: string) => !!siteId && siteActiveMap.value[siteId] === false;
|
||||
const sortedContracts = computed(() =>
|
||||
[...contracts.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: "",
|
||||
@@ -279,6 +297,10 @@ const onRowClick = (row: any) => {
|
||||
|
||||
const remove = async (row: any) => {
|
||||
if (!row?.id || !canWrite.value) return;
|
||||
if (isInactiveSite(row?.center_id)) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
|
||||
if (!ok) return;
|
||||
try {
|
||||
@@ -438,4 +460,16 @@ onMounted(async () => {
|
||||
color: var(--ctms-text-regular);
|
||||
font-family: var(--el-font-family);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.row-inactive td {
|
||||
color: var(--ctms-text-secondary);
|
||||
background-color: #fff7d6 !important;
|
||||
}
|
||||
|
||||
.row-inactive .el-tag {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<p class="page-subtitle">{{ TEXT.modules.feeSpecials.detailSubtitle }}</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" :disabled="!canWrite" @click="goEdit" class="header-action-btn copy-btn">
|
||||
<el-button type="primary" :disabled="!canWrite || isReadOnly" @click="goEdit" class="header-action-btn copy-btn">
|
||||
<el-icon class="el-icon--left"><Edit /></el-icon>
|
||||
{{ TEXT.common.actions.edit }}
|
||||
</el-button>
|
||||
@@ -82,6 +82,7 @@
|
||||
:entity-type="'special_expense'"
|
||||
:entity-id="expenseId"
|
||||
:groups="attachmentGroups"
|
||||
:readonly="isReadOnly"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
||||
@@ -91,6 +92,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Edit } from "@element-plus/icons-vue";
|
||||
import { getSpecialExpense } from "../../api/feeSpecials";
|
||||
import { fetchSites } from "../../api/sites";
|
||||
@@ -123,6 +125,14 @@ const detail = reactive<any>({
|
||||
verified_date: "",
|
||||
});
|
||||
const sites = ref<any[]>([]);
|
||||
const siteActiveMap = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
sites.value.forEach((site) => {
|
||||
if (site?.id) map[site.id] = !!site.is_active;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
const isReadOnly = computed(() => !!detail.center_id && siteActiveMap.value[detail.center_id] === false);
|
||||
|
||||
const attachmentGroups = [
|
||||
{
|
||||
@@ -188,7 +198,13 @@ const getCategoryType = (category: string) => {
|
||||
return 'info';
|
||||
};
|
||||
|
||||
const goEdit = () => router.push(`/fees/special/${expenseId}/edit`);
|
||||
const goEdit = () => {
|
||||
if (isReadOnly.value) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
router.push(`/fees/special/${expenseId}/edit`);
|
||||
};
|
||||
const goBack = () => router.push("/fees/special");
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -29,36 +29,37 @@
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.common.fields.site">
|
||||
<el-select v-model="form.center_id" clearable :placeholder="TEXT.common.placeholders.select" class="full-width">
|
||||
<el-select v-model="form.center_id" clearable :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.select" class="full-width">
|
||||
<el-option
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
:label="site.name || TEXT.common.fallback"
|
||||
:value="site.id"
|
||||
:disabled="!site.is_active"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.common.fields.occurDate" prop="happen_date">
|
||||
<el-date-picker v-model="form.happen_date" type="date" value-format="YYYY-MM-DD" class="full-width" />
|
||||
<el-date-picker v-model="form.happen_date" :disabled="isReadOnly" type="date" value-format="YYYY-MM-DD" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.common.fields.category" prop="category" required>
|
||||
<el-select v-model="form.category" :placeholder="TEXT.common.placeholders.select" class="full-width">
|
||||
<el-select v-model="form.category" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.select" class="full-width">
|
||||
<el-option v-for="option in categoryOptions" :key="option.value" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="TEXT.common.fields.amount" prop="amount" required>
|
||||
<el-input-number v-model="form.amount" :min="0" :precision="2" class="full-width" controls-position="right" />
|
||||
<el-input-number v-model="form.amount" :disabled="isReadOnly" :min="0" :precision="2" class="full-width" controls-position="right" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item :label="TEXT.common.fields.description">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" />
|
||||
<el-input v-model="form.description" :disabled="isReadOnly" type="textarea" :rows="3" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -76,7 +77,7 @@
|
||||
<div class="status-box">
|
||||
<div class="status-header">
|
||||
<span class="status-title">{{ TEXT.modules.feeSpecials.paidFlag }}</span>
|
||||
<el-switch v-model="form.is_paid" @change="onPaidToggle" />
|
||||
<el-switch v-model="form.is_paid" :disabled="isReadOnly" @change="onPaidToggle" />
|
||||
</div>
|
||||
<el-form-item :label="TEXT.common.fields.occurDate" :error="specialErrors.paid_date" class="status-form-item">
|
||||
<el-date-picker
|
||||
@@ -84,7 +85,7 @@
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="TEXT.common.placeholders.select"
|
||||
:disabled="!form.is_paid"
|
||||
:disabled="isReadOnly || !form.is_paid"
|
||||
class="full-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -94,7 +95,7 @@
|
||||
<div class="status-box">
|
||||
<div class="status-header">
|
||||
<span class="status-title">{{ TEXT.modules.feeSpecials.verifiedFlag }}</span>
|
||||
<el-switch v-model="form.is_verified" @change="onVerifiedToggle" />
|
||||
<el-switch v-model="form.is_verified" :disabled="isReadOnly" @change="onVerifiedToggle" />
|
||||
</div>
|
||||
<el-form-item :label="TEXT.common.fields.occurDate" :error="specialErrors.verified_date" class="status-form-item">
|
||||
<el-date-picker
|
||||
@@ -102,7 +103,7 @@
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="TEXT.common.placeholders.select"
|
||||
:disabled="!form.is_verified"
|
||||
:disabled="isReadOnly || !form.is_verified"
|
||||
class="full-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -126,6 +127,7 @@
|
||||
:entity-type="'special_expense'"
|
||||
:entity-id="form.id"
|
||||
:groups="attachmentGroups"
|
||||
:readonly="isReadOnly"
|
||||
/>
|
||||
|
||||
<StateEmpty v-else :description="TEXT.modules.feeSpecials.uploadHint" class="compact-empty" />
|
||||
@@ -133,7 +135,7 @@
|
||||
|
||||
<div class="footer-actions">
|
||||
<el-button @click="goBack">{{ TEXT.common.actions.cancel }}</el-button>
|
||||
<el-button type="primary" :loading="saving" @click="submit" class="save-btn">
|
||||
<el-button type="primary" :loading="saving" :disabled="isReadOnly" @click="submit" class="save-btn">
|
||||
{{ TEXT.common.actions.save }}
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -163,6 +165,14 @@ const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const sites = ref<any[]>([]);
|
||||
const siteActiveMap = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
sites.value.forEach((site) => {
|
||||
if (site?.id) map[site.id] = !!site.is_active;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
const isReadOnly = computed(() => isEdit.value && !!form.center_id && siteActiveMap.value[form.center_id] === false);
|
||||
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
@@ -301,6 +311,14 @@ const validateSpecial = () => {
|
||||
|
||||
const submit = async () => {
|
||||
if (!study.currentStudy?.id) return;
|
||||
if (isReadOnly.value) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
if (form.center_id && siteActiveMap.value[form.center_id] === false) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
const formOk = await formRef.value?.validate?.().catch(() => false);
|
||||
if (!formOk) return;
|
||||
if (!validateSpecial()) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h1 class="page-title">{{ TEXT.modules.feeSpecials.title }}</h1>
|
||||
<p class="page-subtitle">{{ TEXT.modules.feeSpecials.subtitle }}</p>
|
||||
</div>
|
||||
<el-button type="primary" :disabled="!canWrite" @click="goNew" class="header-action-btn">
|
||||
<el-icon class="el-icon--left"><Plus /></el-icon>
|
||||
@@ -109,7 +108,13 @@
|
||||
<StateLoading v-else-if="loading" :rows="6" />
|
||||
|
||||
<el-card v-else class="table-card" shadow="never">
|
||||
<el-table :data="expenses" style="width: 100%" @row-click="onRowClick" :header-cell-style="{ background: '#f8f9fb' }">
|
||||
<el-table
|
||||
:data="sortedExpenses"
|
||||
style="width: 100%"
|
||||
@row-click="onRowClick"
|
||||
:header-cell-style="{ background: '#f8f9fb' }"
|
||||
:row-class-name="expenseRowClass"
|
||||
>
|
||||
<el-table-column :label="TEXT.common.fields.occurDate" width="140">
|
||||
<template #default="scope">
|
||||
<span class="date-text">{{ displayDate(scope.row.happen_date) }}</span>
|
||||
@@ -161,7 +166,7 @@
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
:disabled="!canWrite"
|
||||
:disabled="!canWrite || isInactiveSite(scope.row.center_id)"
|
||||
@click.stop="remove(scope.row)"
|
||||
>
|
||||
{{ TEXT.common.actions.delete }}
|
||||
@@ -169,7 +174,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<StateEmpty v-if="expenses.length === 0" :description="TEXT.modules.feeSpecials.empty" />
|
||||
<StateEmpty v-if="sortedExpenses.length === 0" :description="TEXT.modules.feeSpecials.empty" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
@@ -199,6 +204,19 @@ const loading = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const expenses = ref<any[]>([]);
|
||||
const sites = ref<any[]>([]);
|
||||
const siteActiveMap = computed(() => {
|
||||
const map: Record<string, boolean> = {};
|
||||
sites.value.forEach((site) => {
|
||||
if (site?.id) map[site.id] = !!site.is_active;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
const isInactiveSite = (siteId?: string) => !!siteId && siteActiveMap.value[siteId] === false;
|
||||
const expenseRowClass = ({ row }: { row: any }) =>
|
||||
`${row?.id ? "clickable-row" : ""}${isInactiveSite(row?.center_id) ? " row-inactive" : ""}`.trim();
|
||||
const sortedExpenses = computed(() =>
|
||||
[...expenses.value].sort((a, b) => Number(isInactiveSite(a?.center_id)) - Number(isInactiveSite(b?.center_id)))
|
||||
);
|
||||
|
||||
const filters = reactive({
|
||||
centerId: "",
|
||||
@@ -287,6 +305,10 @@ const onRowClick = (row: any) => {
|
||||
|
||||
const remove = async (row: any) => {
|
||||
if (!row?.id || !canWrite.value) return;
|
||||
if (isInactiveSite(row?.center_id)) {
|
||||
ElMessage.warning("中心已停用");
|
||||
return;
|
||||
}
|
||||
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
|
||||
if (!ok) return;
|
||||
try {
|
||||
@@ -444,4 +466,16 @@ onMounted(async () => {
|
||||
.text-muted {
|
||||
color: var(--ctms-text-placeholder);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.row-inactive td {
|
||||
color: var(--ctms-text-secondary);
|
||||
background-color: #fff7d6 !important;
|
||||
}
|
||||
|
||||
.row-inactive .el-tag {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user