立项配置数据入库、审计日志数据入库、编辑页UI界面美化、设备管理内容补充、审计日志显示优化
This commit is contained in:
@@ -196,11 +196,10 @@
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useStudyStore } from "../../store/study";
|
||||
import { fetchStudySetupConfig } from "../../api/studies";
|
||||
import { listProjectMilestones, updateProjectMilestone } from "../../api/projectMilestones";
|
||||
import { TEXT } from "../../locales";
|
||||
import StateEmpty from "../../components/StateEmpty.vue";
|
||||
import { displayDateTime } from "../../utils/display";
|
||||
import type { ProjectMilestoneDraft, StudySetupConfigResponse } from "../../types/setupConfig";
|
||||
|
||||
interface MilestoneRow {
|
||||
id: string;
|
||||
@@ -234,11 +233,10 @@ type MilestoneLocalEdit = {
|
||||
const study = useStudyStore();
|
||||
const rows = ref<MilestoneRow[]>([]);
|
||||
const loading = ref(false);
|
||||
const dataSourceLabel = ref<string>(TEXT.modules.projectMilestones.sourceDraft);
|
||||
const dataSourceLabel = ref<string>(TEXT.modules.projectMilestones.sourcePublished);
|
||||
const updatedAtLabel = ref<string>(TEXT.common.fallback);
|
||||
const editorVisible = ref(false);
|
||||
const editingRowId = ref("");
|
||||
const localEdits = ref<Record<string, MilestoneLocalEdit>>({});
|
||||
const editorForm = reactive<MilestoneLocalEdit>({
|
||||
adjustedStartDate: "",
|
||||
adjustedEndDate: "",
|
||||
@@ -249,8 +247,6 @@ const editorForm = reactive<MilestoneLocalEdit>({
|
||||
|
||||
const doneCount = computed(() => rows.value.filter((item) => getStatusView(item).completed).length);
|
||||
|
||||
const getLocalEditKey = (studyId: string) => `ctms_project_milestones_edits_${studyId}`;
|
||||
|
||||
const toDateOnly = (value?: string): Date | null => {
|
||||
if (!value) return null;
|
||||
const d = new Date(`${value}T00:00:00`);
|
||||
@@ -267,22 +263,24 @@ const calcDurationDays = (start?: string, end?: string): number | null => {
|
||||
};
|
||||
|
||||
const toDurationText = (days: number | null) => (days && days > 0 ? `${days}天` : TEXT.common.fallback);
|
||||
const toPositiveInt = (value: unknown): number | null => {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n) || n <= 0) return null;
|
||||
return Math.floor(n);
|
||||
};
|
||||
|
||||
const applyEditToRow = (row: MilestoneRow): MilestoneRow => {
|
||||
const patch = localEdits.value[row.id] || {};
|
||||
const adjustedStartDate = String(patch.adjustedStartDate ?? row.adjustedStartDate ?? "").trim();
|
||||
const adjustedEndDate = String(patch.adjustedEndDate ?? row.adjustedEndDate ?? "").trim();
|
||||
const actualStartDate = String(patch.actualStartDate ?? row.actualStartDate ?? "").trim();
|
||||
const actualEndDate = String(patch.actualEndDate ?? row.actualEndDate ?? "").trim();
|
||||
const normalizeRow = (row: any): MilestoneRow => {
|
||||
const startDate = String(row?.planned_date || "").trim();
|
||||
const endDate = String(row?.planned_date || "").trim();
|
||||
const adjustedStartDate = String(row?.adjusted_start_date || "").trim();
|
||||
const adjustedEndDate = String(row?.adjusted_end_date || "").trim();
|
||||
const actualStartDate = String(row?.actual_start_date || "").trim();
|
||||
const actualEndDate = String(row?.actual_end_date || "").trim();
|
||||
const adjustedDurationDays = calcDurationDays(adjustedStartDate, adjustedEndDate);
|
||||
const actualDurationDays = calcDurationDays(actualStartDate, actualEndDate);
|
||||
return {
|
||||
...row,
|
||||
id: row?.id || "",
|
||||
name: String(row?.name || "").trim(),
|
||||
planDate: startDate,
|
||||
startDate,
|
||||
endDate,
|
||||
durationDays: startDate ? 1 : null,
|
||||
durationText: startDate ? "1天" : TEXT.common.fallback,
|
||||
adjustedStartDate,
|
||||
adjustedEndDate,
|
||||
adjustedDurationDays,
|
||||
@@ -291,85 +289,26 @@ const applyEditToRow = (row: MilestoneRow): MilestoneRow => {
|
||||
actualEndDate,
|
||||
actualDurationDays,
|
||||
actualDurationText: toDurationText(actualDurationDays),
|
||||
remark: String(patch.remark ?? row.remark ?? "").trim(),
|
||||
owner: String(row?.owner_name || "").trim(),
|
||||
status: String(row?.status || "NOT_STARTED").trim(),
|
||||
remark: String(row?.notes || "").trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const loadLocalEdits = () => {
|
||||
const studyId = study.currentStudy?.id;
|
||||
if (!studyId) return;
|
||||
try {
|
||||
const raw = localStorage.getItem(getLocalEditKey(studyId));
|
||||
localEdits.value = raw ? (JSON.parse(raw) as Record<string, MilestoneLocalEdit>) : {};
|
||||
} catch {
|
||||
localEdits.value = {};
|
||||
}
|
||||
};
|
||||
|
||||
const persistLocalEdits = () => {
|
||||
const studyId = study.currentStudy?.id;
|
||||
if (!studyId) return;
|
||||
localStorage.setItem(getLocalEditKey(studyId), JSON.stringify(localEdits.value));
|
||||
};
|
||||
|
||||
const normalizeRows = (items: ProjectMilestoneDraft[]) =>
|
||||
items
|
||||
.map((item, index) => {
|
||||
const planDate = String(item.planDate || "").trim();
|
||||
const startDate = String(item.startDate || planDate).trim();
|
||||
const endDate = String(item.endDate || planDate).trim();
|
||||
const adjustedStartDate = String((item as any).adjustedStartDate || "").trim();
|
||||
const adjustedEndDate = String((item as any).adjustedEndDate || "").trim();
|
||||
const actualStartDate = String((item as any).actualStartDate || "").trim();
|
||||
const actualEndDate = String((item as any).actualEndDate || "").trim();
|
||||
const rawDays = toPositiveInt(item.durationDays);
|
||||
const adjustedRawDays = toPositiveInt((item as any).adjustedDurationDays) ?? calcDurationDays(adjustedStartDate, adjustedEndDate);
|
||||
const actualRawDays = toPositiveInt((item as any).actualDurationDays) ?? calcDurationDays(actualStartDate, actualEndDate);
|
||||
const durationDays = rawDays ?? (startDate || endDate ? 1 : null);
|
||||
const adjustedDurationDays = adjustedRawDays ?? (adjustedStartDate || adjustedEndDate ? 1 : null);
|
||||
const actualDurationDays = actualRawDays ?? (actualStartDate || actualEndDate ? 1 : null);
|
||||
return {
|
||||
id: item.id || `setup-project-milestone-${index + 1}`,
|
||||
name: String(item.name || "").trim(),
|
||||
planDate,
|
||||
startDate,
|
||||
endDate,
|
||||
durationDays,
|
||||
durationText: durationDays ? `${durationDays}天` : TEXT.common.fallback,
|
||||
adjustedStartDate,
|
||||
adjustedEndDate,
|
||||
adjustedDurationDays,
|
||||
adjustedDurationText: adjustedDurationDays ? `${adjustedDurationDays}天` : TEXT.common.fallback,
|
||||
actualStartDate,
|
||||
actualEndDate,
|
||||
actualDurationDays,
|
||||
actualDurationText: actualDurationDays ? `${actualDurationDays}天` : TEXT.common.fallback,
|
||||
owner: String(item.owner || "").trim(),
|
||||
status: String(item.status || "未开始").trim(),
|
||||
remark: String(item.remark || "").trim(),
|
||||
};
|
||||
})
|
||||
.filter((item) => item.name || item.startDate || item.endDate || item.owner || item.remark);
|
||||
|
||||
const pickProjectMilestones = (setup: StudySetupConfigResponse): ProjectMilestoneDraft[] => {
|
||||
const publishedRows = normalizeRows(setup.published_data?.projectMilestones || []);
|
||||
if (publishedRows.length > 0) {
|
||||
dataSourceLabel.value = TEXT.modules.projectMilestones.sourcePublished;
|
||||
return setup.published_data?.projectMilestones || [];
|
||||
}
|
||||
dataSourceLabel.value = setup.published_data ? TEXT.modules.projectMilestones.sourceDraftFallback : TEXT.modules.projectMilestones.sourceDraft;
|
||||
return setup.data?.projectMilestones || [];
|
||||
};
|
||||
|
||||
const loadMilestones = async () => {
|
||||
const studyId = study.currentStudy?.id;
|
||||
if (!studyId) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await fetchStudySetupConfig(studyId);
|
||||
loadLocalEdits();
|
||||
rows.value = normalizeRows(pickProjectMilestones(data)).map(applyEditToRow);
|
||||
updatedAtLabel.value = displayDateTime(data.published_at || data.updated_at);
|
||||
const { data } = (await listProjectMilestones(studyId)) as any;
|
||||
const list = Array.isArray(data) ? data : data?.items || [];
|
||||
rows.value = list.map(normalizeRow);
|
||||
const updatedAt = list
|
||||
.map((item: any) => String(item?.updated_at || ""))
|
||||
.filter(Boolean)
|
||||
.sort()
|
||||
.pop();
|
||||
updatedAtLabel.value = updatedAt ? displayDateTime(updatedAt) : TEXT.common.fallback;
|
||||
} catch (error: any) {
|
||||
rows.value = [];
|
||||
updatedAtLabel.value = TEXT.common.fallback;
|
||||
@@ -468,25 +407,26 @@ const openEditor = (row: MilestoneRow) => {
|
||||
editorVisible.value = true;
|
||||
};
|
||||
|
||||
const saveEditor = () => {
|
||||
if (!editingRowId.value) return;
|
||||
const saveEditor = async () => {
|
||||
const studyId = study.currentStudy?.id;
|
||||
if (!studyId || !editingRowId.value) return;
|
||||
if (!validateDateRange(editorForm.adjustedStartDate, editorForm.adjustedEndDate, "调整计划时间")) return;
|
||||
if (!validateDateRange(editorForm.actualStartDate, editorForm.actualEndDate, "实际时间")) return;
|
||||
const patch: MilestoneLocalEdit = {
|
||||
adjustedStartDate: editorForm.adjustedStartDate || "",
|
||||
adjustedEndDate: editorForm.adjustedEndDate || "",
|
||||
actualStartDate: editorForm.actualStartDate || "",
|
||||
actualEndDate: editorForm.actualEndDate || "",
|
||||
remark: (editorForm.remark || "").trim(),
|
||||
const patch = {
|
||||
adjusted_start_date: editorForm.adjustedStartDate || null,
|
||||
adjusted_end_date: editorForm.adjustedEndDate || null,
|
||||
actual_start_date: editorForm.actualStartDate || null,
|
||||
actual_end_date: editorForm.actualEndDate || null,
|
||||
notes: (editorForm.remark || "").trim() || null,
|
||||
};
|
||||
localEdits.value = {
|
||||
...localEdits.value,
|
||||
[editingRowId.value]: patch,
|
||||
};
|
||||
persistLocalEdits();
|
||||
rows.value = rows.value.map((item) => (item.id === editingRowId.value ? applyEditToRow(item) : item));
|
||||
editorVisible.value = false;
|
||||
ElMessage.success("里程碑时间已更新");
|
||||
try {
|
||||
await updateProjectMilestone(studyId, editingRowId.value, patch);
|
||||
await loadMilestones();
|
||||
editorVisible.value = false;
|
||||
ElMessage.success("里程碑时间已更新");
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.response?.data?.detail || TEXT.common.messages.saveFailed);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
@@ -497,7 +437,6 @@ watch(
|
||||
() => study.currentStudy?.id,
|
||||
() => {
|
||||
rows.value = [];
|
||||
localEdits.value = {};
|
||||
updatedAtLabel.value = TEXT.common.fallback;
|
||||
loadMilestones();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user