管理后台前后端逻辑、UI美化
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user