管理后台前后端逻辑、UI美化

This commit is contained in:
Cheng Zhou
2026-01-16 13:50:08 +08:00
parent 05c1f9579a
commit 7fdcfdaadd
82 changed files with 3210 additions and 549 deletions
+13 -2
View File
@@ -6,7 +6,9 @@
<p class="ctms-page-subtitle">{{ TEXT.modules.startupFeasibilityEthics.ethicsDetailSubtitle }}</p>
</div>
<div class="ctms-page-actions">
<el-button type="primary" @click="goEdit">{{ TEXT.common.actions.edit }}</el-button>
<el-button type="primary" :disabled="isInactiveSite(detail.site_id)" @click="goEdit">
{{ TEXT.common.actions.edit }}
</el-button>
<el-button @click="goBack">
<el-icon class="el-icon--left"><ArrowLeft /></el-icon>
{{ TEXT.common.actions.back }}
@@ -33,6 +35,7 @@
:study-id="studyId"
entity-type="startup_ethics"
:entity-id="recordId"
:readonly="isInactiveSite(detail.site_id)"
/>
</div>
</template>
@@ -61,7 +64,9 @@ const studyId = study.currentStudy?.id || "";
const sites = ref<Site[]>([]);
const siteMap = computed(() => new Map(sites.value.map((site) => [site.id, site.name])));
const siteActiveMap = computed(() => new Map(sites.value.map((site) => [site.id, !!site.is_active])));
const displaySite = (siteId?: string) => siteMap.value.get(siteId || "") || TEXT.common.fallback;
const isInactiveSite = (siteId?: string) => siteId && siteActiveMap.value.get(siteId) === false;
const detail = reactive<any>({
site_id: "",
@@ -137,7 +142,13 @@ const load = async () => {
}
};
const goEdit = () => router.push(`/startup/ethics/${recordId}/edit`);
const goEdit = () => {
if (isInactiveSite(detail.site_id)) {
ElMessage.warning("中心已停用");
return;
}
router.push(`/startup/ethics/${recordId}/edit`);
};
const goBack = () => router.push("/startup/feasibility-ethics");
onMounted(() => {
+54 -9
View File
@@ -18,14 +18,14 @@
<el-row :gutter="24">
<el-col :span="12">
<el-form-item :label="TEXT.common.fields.site">
<el-select v-model="form.site_id" :placeholder="TEXT.common.placeholders.select" clearable style="width: 100%">
<el-option v-for="site in siteOptions" :key="site.value" :label="site.label" :value="site.value" />
<el-select v-model="form.site_id" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.select" clearable style="width: 100%">
<el-option v-for="site in siteOptions" :key="site.value" :label="site.label" :value="site.value" :disabled="site.disabled" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="TEXT.common.fields.approvalNo">
<el-input v-model="form.approval_no" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.approvalNo" />
<el-input v-model="form.approval_no" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.approvalNo" />
</el-form-item>
</el-col>
</el-row>
@@ -33,28 +33,56 @@
<el-row :gutter="24">
<el-col :span="6">
<el-form-item :label="TEXT.common.fields.submitDate">
<el-date-picker v-model="form.submit_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.submit_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="TEXT.common.fields.acceptDate">
<el-date-picker v-model="form.accept_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.accept_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="TEXT.common.fields.meetingDate">
<el-date-picker v-model="form.meeting_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.meeting_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item :label="TEXT.common.fields.approvedDate">
<el-date-picker v-model="form.approved_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.approved_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item class="form-actions">
<el-button type="primary" :loading="saving" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button type="primary" :loading="saving" :disabled="isReadOnly" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.cancel }}</el-button>
</el-form-item>
</el-form>
@@ -68,6 +96,7 @@
:study-id="studyId"
entity-type="startup_ethics"
:entity-id="recordId"
:readonly="isReadOnly"
/>
</div>
<el-card v-else class="hint-card">
@@ -100,8 +129,16 @@ const studyId = computed(() => study.currentStudy?.id || "");
const sites = ref<Site[]>([]);
const siteOptions = computed(() =>
sites.value.map((site) => ({ value: site.id, label: site.name || TEXT.common.fallback }))
sites.value.map((site) => ({ value: site.id, label: site.name || TEXT.common.fallback, disabled: !site.is_active }))
);
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.site_id && siteActiveMap.value[form.site_id] === false);
const form = reactive({
site_id: "",
@@ -141,6 +178,14 @@ const load = async () => {
const submit = async () => {
if (!studyId.value) return;
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (form.site_id && siteActiveMap.value[form.site_id] === false) {
ElMessage.warning("中心已停用");
return;
}
saving.value = true;
try {
const payload = {
@@ -6,7 +6,9 @@
<p class="ctms-page-subtitle">{{ TEXT.modules.startupFeasibilityEthics.feasibilityDetailSubtitle }}</p>
</div>
<div class="ctms-page-actions">
<el-button type="primary" @click="goEdit">{{ TEXT.common.actions.edit }}</el-button>
<el-button type="primary" :disabled="isInactiveSite(detail.site_id)" @click="goEdit">
{{ TEXT.common.actions.edit }}
</el-button>
<el-button @click="goBack">
<el-icon class="el-icon--left"><ArrowLeft /></el-icon>
{{ TEXT.common.actions.back }}
@@ -32,6 +34,7 @@
:study-id="studyId"
entity-type="startup_feasibility"
:entity-id="recordId"
:readonly="isInactiveSite(detail.site_id)"
/>
</div>
</template>
@@ -60,7 +63,9 @@ const studyId = study.currentStudy?.id || "";
const sites = ref<Site[]>([]);
const siteMap = computed(() => new Map(sites.value.map((site) => [site.id, site.name])));
const siteActiveMap = computed(() => new Map(sites.value.map((site) => [site.id, !!site.is_active])));
const displaySite = (siteId?: string) => siteMap.value.get(siteId || "") || TEXT.common.fallback;
const isInactiveSite = (siteId?: string) => siteId && siteActiveMap.value.get(siteId) === false;
const detail = reactive<any>({
site_id: "",
@@ -135,7 +140,13 @@ const load = async () => {
}
};
const goEdit = () => router.push(`/startup/feasibility/${recordId}/edit`);
const goEdit = () => {
if (isInactiveSite(detail.site_id)) {
ElMessage.warning("中心已停用");
return;
}
router.push(`/startup/feasibility/${recordId}/edit`);
};
const goBack = () => router.push("/startup/feasibility-ethics");
onMounted(() => {
+46 -8
View File
@@ -18,14 +18,14 @@
<el-row :gutter="24">
<el-col :span="12">
<el-form-item :label="TEXT.common.fields.site">
<el-select v-model="form.site_id" :placeholder="TEXT.common.placeholders.select" clearable style="width: 100%">
<el-option v-for="site in siteOptions" :key="site.value" :label="site.label" :value="site.value" />
<el-select v-model="form.site_id" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.select" clearable style="width: 100%">
<el-option v-for="site in siteOptions" :key="site.value" :label="site.label" :value="site.value" :disabled="site.disabled" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="TEXT.common.fields.projectNo">
<el-input v-model="form.project_no" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.projectNo" />
<el-input v-model="form.project_no" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.projectNo" />
</el-form-item>
</el-col>
</el-row>
@@ -33,23 +33,44 @@
<el-row :gutter="24">
<el-col :span="8">
<el-form-item :label="TEXT.common.fields.submitDate">
<el-date-picker v-model="form.submit_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.submit_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="TEXT.common.fields.acceptDate">
<el-date-picker v-model="form.accept_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.accept_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item :label="TEXT.common.fields.approvedDate">
<el-date-picker v-model="form.approved_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" style="width: 100%" />
<el-date-picker
v-model="form.approved_date"
:disabled="isReadOnly"
type="date"
value-format="YYYY-MM-DD"
:placeholder="TEXT.common.placeholders.select"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item class="form-actions">
<el-button type="primary" :loading="saving" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button type="primary" :loading="saving" :disabled="isReadOnly" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.cancel }}</el-button>
</el-form-item>
</el-form>
@@ -63,6 +84,7 @@
:study-id="studyId"
entity-type="startup_feasibility"
:entity-id="recordId"
:readonly="isReadOnly"
/>
</div>
<el-card v-else class="hint-card">
@@ -95,8 +117,16 @@ const studyId = computed(() => study.currentStudy?.id || "");
const sites = ref<Site[]>([]);
const siteOptions = computed(() =>
sites.value.map((site) => ({ value: site.id, label: site.name || TEXT.common.fallback }))
sites.value.map((site) => ({ value: site.id, label: site.name || TEXT.common.fallback, disabled: !site.is_active }))
);
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.site_id && siteActiveMap.value[form.site_id] === false);
const form = reactive({
site_id: "",
@@ -134,6 +164,14 @@ const load = async () => {
const submit = async () => {
if (!studyId.value) return;
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (form.site_id && siteActiveMap.value[form.site_id] === false) {
ElMessage.warning("中心已停用");
return;
}
saving.value = true;
try {
const payload = {
+53 -9
View File
@@ -6,7 +6,14 @@
<p class="page-subtitle">{{ TEXT.modules.startupMeetingAuth.kickoffDetailSubtitle }}</p>
</div>
<div class="actions">
<el-button v-if="!editMode" type="primary" :disabled="loading" @click="startEdit">{{ TEXT.common.actions.edit }}</el-button>
<el-button
v-if="!editMode"
type="primary"
:disabled="loading || isReadOnly"
@click="startEdit"
>
{{ TEXT.common.actions.edit }}
</el-button>
<el-button v-else type="primary" :loading="saving" @click="saveEdit">{{ TEXT.common.actions.save }}</el-button>
<el-button v-if="editMode" :disabled="saving" @click="cancelEdit">{{ TEXT.common.actions.cancel }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.back }}</el-button>
@@ -59,9 +66,16 @@
<el-checkbox
v-if="isTrainingEditing(scope.row)"
v-model="trainingRowDraft.trained"
:disabled="isReadOnly"
@click.stop
/>
<el-checkbox
v-else
v-model="scope.row.trained"
:disabled="isReadOnly"
@change="toggleTraining(scope.row)"
@click.stop
/>
<el-checkbox v-else v-model="scope.row.trained" @change="toggleTraining(scope.row)" @click.stop />
</template>
</el-table-column>
<el-table-column :label="TEXT.common.fields.authorized" width="120">
@@ -69,9 +83,16 @@
<el-checkbox
v-if="isTrainingEditing(scope.row)"
v-model="trainingRowDraft.authorized"
:disabled="isReadOnly"
@click.stop
/>
<el-checkbox
v-else
v-model="scope.row.authorized"
:disabled="isReadOnly"
@change="toggleTraining(scope.row)"
@click.stop
/>
<el-checkbox v-else v-model="scope.row.authorized" @change="toggleTraining(scope.row)" @click.stop />
</template>
</el-table-column>
<el-table-column :label="TEXT.common.labels.actions" width="120">
@@ -85,17 +106,19 @@
</el-button>
</template>
<template v-else>
<el-button link type="primary" size="small" @click.stop="startTrainingEdit(scope.row)">
<el-button link type="primary" size="small" :disabled="isReadOnly" @click.stop="startTrainingEdit(scope.row)">
{{ TEXT.common.actions.edit }}
</el-button>
<el-button link type="danger" size="small" @click.stop="removeTraining(scope.row)">{{ TEXT.common.actions.delete }}</el-button>
<el-button link type="danger" size="small" :disabled="isReadOnly" @click.stop="removeTraining(scope.row)">
{{ TEXT.common.actions.delete }}
</el-button>
</template>
</template>
</el-table-column>
</el-table>
<div
class="training-add-row"
:class="{ disabled: newTrainingActive || savingTraining }"
:class="{ disabled: newTrainingActive || savingTraining || isReadOnly }"
@click="startTrainingAdd"
>
<span class="training-add-icon">+</span>
@@ -149,7 +172,7 @@ const saving = ref(false);
const editMode = ref(false);
const meetingId = route.params.meetingId as string;
const studyId = study.currentStudy?.id || "";
const siteInfo = reactive<{ name: string; contact?: string | null }>({ name: "" });
const siteInfo = reactive<{ name: string; contact?: string | null; is_active?: boolean }>({ name: "" });
const users = ref<any[]>([]);
const members = ref<any[]>([]);
const kickoffAttachmentGroups = [
@@ -217,6 +240,7 @@ const statusLabel = computed(() =>
detail.kickoff_date ? TEXT.modules.startupMeetingAuth.statusCompleted : TEXT.modules.startupMeetingAuth.statusPending
);
const statusTagType = computed(() => (detail.kickoff_date ? "success" : "info"));
const isReadOnly = computed(() => siteInfo.is_active === false);
const load = async () => {
if (!studyId || !meetingId) return;
@@ -256,6 +280,10 @@ const loadTraining = async () => {
};
const toggleTraining = async (row: any) => {
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (!studyId) return;
try {
await updateTrainingAuthorization(studyId, row.id, {
@@ -282,14 +310,14 @@ const isTrainingEditing = (row: any) => {
};
const startTrainingEdit = (row: any) => {
if (savingTraining.value) return;
if (savingTraining.value || isReadOnly.value) return;
newTrainingActive.value = false;
trainingEditingRowId.value = row?.id || null;
resetTrainingDraft(row);
};
const startTrainingAdd = () => {
if (newTrainingActive.value || savingTraining.value) return;
if (newTrainingActive.value || savingTraining.value || isReadOnly.value) return;
trainingEditingRowId.value = null;
newTrainingActive.value = true;
resetTrainingDraft();
@@ -302,6 +330,10 @@ const cancelTrainingEdit = () => {
};
const saveTraining = async (row: any) => {
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (!studyId) return;
if (!trainingRowDraft.name.trim()) {
ElMessage.error(requiredMessage(TEXT.common.fields.name));
@@ -332,6 +364,10 @@ const saveTraining = async (row: any) => {
};
const removeTraining = async (row: any) => {
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (!studyId) return;
const ok = await ElMessageBox.confirm(TEXT.common.confirm.delete, TEXT.common.labels.tips).catch(() => null);
if (!ok) return;
@@ -345,6 +381,10 @@ const removeTraining = async (row: any) => {
};
const startEdit = () => {
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
editMode.value = true;
draft.kickoff_date = detail.kickoff_date || "";
};
@@ -355,6 +395,10 @@ const cancelEdit = () => {
};
const saveEdit = async () => {
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (!studyId || !meetingId) return;
saving.value = true;
try {
+11 -4
View File
@@ -17,18 +17,19 @@
<el-input :model-value="ownerLabel" disabled />
</el-form-item>
<el-form-item :label="TEXT.common.fields.kickoffDate">
<el-date-picker v-model="form.kickoff_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" />
<el-date-picker v-model="form.kickoff_date" :disabled="isReadOnly" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.attendees">
<el-input
v-model="form.attendees"
:disabled="isReadOnly"
type="textarea"
:rows="4"
:placeholder="TEXT.modules.startupMeetingAuth.attendeesPlaceholder"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button type="primary" :loading="saving" :disabled="isReadOnly" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.cancel }}</el-button>
</el-form-item>
</el-form>
@@ -42,6 +43,7 @@
:entity-type="group.entityType"
:entity-id="meetingId"
:title="group.title"
:readonly="isReadOnly"
/>
</div>
<el-card v-else class="hint-card">
@@ -71,7 +73,7 @@ const saving = ref(false);
const meetingId = computed(() => route.params.meetingId as string | undefined);
const isEdit = computed(() => !!meetingId.value);
const studyId = computed(() => study.currentStudy?.id || "");
const siteInfo = reactive<{ name: string; contact?: string | null }>({ name: "" });
const siteInfo = reactive<{ name: string; contact?: string | null; is_active?: boolean }>({ name: "", is_active: true });
const siteId = ref("");
const users = ref<any[]>([]);
const members = ref<any[]>([]);
@@ -96,6 +98,7 @@ const ownerLabel = computed(() => {
.map((c) => memberNameMap.value[c] || c)
.join("、") || TEXT.common.fallback;
});
const isReadOnly = computed(() => siteInfo.is_active === false);
const kickoffAttachmentGroups = [
{ entityType: "startup_kickoff_minutes", title: TEXT.modules.startupMeetingAuth.kickoffMinutesLabel },
{ entityType: "startup_kickoff_signin", title: TEXT.modules.startupMeetingAuth.kickoffSignInLabel },
@@ -132,7 +135,7 @@ const load = async () => {
const { data: sitesData } = await fetchSites(studyId.value, { limit: 500 });
const list = Array.isArray(sitesData) ? sitesData : sitesData.items || [];
const matched = list.find((site: any) => site.id === siteId.value);
Object.assign(siteInfo, matched || { name: "", contact: "" });
Object.assign(siteInfo, matched || { name: "", contact: "", is_active: true });
}
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || TEXT.common.messages.loadFailed);
@@ -141,6 +144,10 @@ const load = async () => {
const submit = async () => {
if (!studyId.value) return;
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
saving.value = true;
try {
const payload = {
+35 -4
View File
@@ -6,7 +6,7 @@
<p class="page-subtitle">{{ TEXT.modules.startupMeetingAuth.trainingDetailSubtitle }}</p>
</div>
<div class="actions">
<el-button type="primary" @click="goEdit">{{ TEXT.common.actions.edit }}</el-button>
<el-button type="primary" :disabled="isReadOnly" @click="goEdit">{{ TEXT.common.actions.edit }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.back }}</el-button>
</div>
</div>
@@ -29,16 +29,18 @@
:study-id="studyId"
entity-type="training_authorization"
:entity-id="recordId"
:readonly="isReadOnly"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from "vue";
import { computed, onMounted, reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { useStudyStore } from "../../store/study";
import { getTrainingAuthorization } from "../../api/startup";
import { fetchSites } from "../../api/sites";
import { displayDate, displayEnum } from "../../utils/display";
import AttachmentList from "../../components/attachments/AttachmentList.vue";
import { TEXT } from "../../locales";
@@ -49,6 +51,7 @@ const study = useStudyStore();
const loading = ref(false);
const recordId = route.params.recordId as string;
const studyId = study.currentStudy?.id || "";
const sites = ref<any[]>([]);
const detail = reactive<any>({
name: "",
@@ -61,6 +64,25 @@ const detail = reactive<any>({
remark: "",
});
const siteActiveMap = computed(() => {
const map: Record<string, boolean> = {};
sites.value.forEach((site) => {
if (site?.name) map[site.name] = !!site.is_active;
});
return map;
});
const isReadOnly = computed(() => !!detail.site_name && siteActiveMap.value[detail.site_name] === false);
const loadSites = async () => {
if (!studyId) return;
try {
const { data } = await fetchSites(studyId, { limit: 500 });
sites.value = Array.isArray(data) ? data : data.items || [];
} catch {
sites.value = [];
}
};
const load = async () => {
if (!studyId || !recordId) return;
loading.value = true;
@@ -74,10 +96,19 @@ const load = async () => {
}
};
const goEdit = () => router.push(`/startup/training/${recordId}/edit`);
const goEdit = () => {
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
router.push(`/startup/training/${recordId}/edit`);
};
const goBack = () => router.push("/startup/meeting-auth");
onMounted(load);
onMounted(async () => {
await loadSites();
load();
});
</script>
<style scoped>
+43 -10
View File
@@ -11,31 +11,31 @@
<el-card>
<el-form :model="form" label-width="120px">
<el-form-item :label="TEXT.common.fields.name" required>
<el-input v-model="form.name" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.name" />
<el-input v-model="form.name" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.name" />
</el-form-item>
<el-form-item :label="TEXT.common.labels.role">
<el-input v-model="form.role" :placeholder="TEXT.common.placeholders.input + TEXT.common.labels.role" />
<el-input v-model="form.role" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.input + TEXT.common.labels.role" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.site">
<el-input v-model="form.site_name" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.site" />
<el-input v-model="form.site_name" :disabled="isReadOnly" :placeholder="TEXT.common.placeholders.input + TEXT.common.fields.site" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.trained">
<el-switch v-model="form.trained" />
<el-switch v-model="form.trained" :disabled="isReadOnly" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.trainedDate">
<el-date-picker v-model="form.trained_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" />
<el-date-picker v-model="form.trained_date" :disabled="isReadOnly" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.authorized">
<el-switch v-model="form.authorized" />
<el-switch v-model="form.authorized" :disabled="isReadOnly" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.authorizedDate">
<el-date-picker v-model="form.authorized_date" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" />
<el-date-picker v-model="form.authorized_date" :disabled="isReadOnly" type="date" value-format="YYYY-MM-DD" :placeholder="TEXT.common.placeholders.select" />
</el-form-item>
<el-form-item :label="TEXT.common.fields.remark">
<el-input v-model="form.remark" type="textarea" :rows="3" :placeholder="TEXT.common.placeholders.input" />
<el-input v-model="form.remark" :disabled="isReadOnly" type="textarea" :rows="3" :placeholder="TEXT.common.placeholders.input" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="saving" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button type="primary" :loading="saving" :disabled="isReadOnly" @click="submit">{{ TEXT.common.actions.save }}</el-button>
<el-button @click="goBack">{{ TEXT.common.actions.cancel }}</el-button>
</el-form-item>
</el-form>
@@ -46,6 +46,7 @@
:study-id="studyId"
entity-type="training_authorization"
:entity-id="recordId"
:readonly="isReadOnly"
/>
<el-card v-else class="hint-card">
<StateEmpty :description="TEXT.modules.startupMeetingAuth.trainingUploadHint" />
@@ -59,6 +60,7 @@ import { useRoute, useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { useStudyStore } from "../../store/study";
import { createTrainingAuthorization, getTrainingAuthorization, updateTrainingAuthorization } from "../../api/startup";
import { fetchSites } from "../../api/sites";
import AttachmentList from "../../components/attachments/AttachmentList.vue";
import StateEmpty from "../../components/StateEmpty.vue";
import { TEXT } from "../../locales";
@@ -67,6 +69,7 @@ const route = useRoute();
const router = useRouter();
const study = useStudyStore();
const saving = ref(false);
const sites = ref<any[]>([]);
const recordId = computed(() => route.params.recordId as string | undefined);
const isEdit = computed(() => !!recordId.value);
@@ -83,6 +86,25 @@ const form = reactive({
remark: "",
});
const siteActiveMap = computed(() => {
const map: Record<string, boolean> = {};
sites.value.forEach((site) => {
if (site?.name) map[site.name] = !!site.is_active;
});
return map;
});
const isReadOnly = computed(() => isEdit.value && !!form.site_name && siteActiveMap.value[form.site_name] === false);
const loadSites = async () => {
if (!studyId.value) return;
try {
const { data } = await fetchSites(studyId.value, { limit: 500 });
sites.value = Array.isArray(data) ? data : data.items || [];
} catch {
sites.value = [];
}
};
const load = async () => {
if (!isEdit.value || !studyId.value || !recordId.value) return;
try {
@@ -104,6 +126,14 @@ const load = async () => {
const submit = async () => {
if (!studyId.value) return;
if (isReadOnly.value) {
ElMessage.warning("中心已停用");
return;
}
if (form.site_name && siteActiveMap.value[form.site_name] === false) {
ElMessage.warning("中心已停用");
return;
}
saving.value = true;
try {
const payload = {
@@ -134,7 +164,10 @@ const submit = async () => {
const goBack = () => router.push("/startup/meeting-auth");
onMounted(load);
onMounted(async () => {
await loadSites();
load();
});
</script>
<style scoped>