docs: reorganize architecture strategy documentation

Split current project diagnostics into a dedicated analysis document and
trim the main architecture strategy to focus on core guidance. This makes
the documentation easier to navigate and separates observed issues from
recommended architectural principles.docs: reorganize architecture strategy documentation

Split current project diagnostics into a dedicated analysis document and
trim the main architecture strategy to focus on core guidance. This makes
the documentation easier to navigate and separates observed issues from
recommended architectural principles.
This commit is contained in:
skytek_xinliang
2026-05-19 14:13:10 +08:00
parent 9ae91418e0
commit 96b96bcaaa
12 changed files with 1373 additions and 1077 deletions
@@ -0,0 +1,282 @@
<script setup lang="ts">
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
import ItemFormFieldGroup from '@/components/items/ItemFormFieldGroup.vue'
import type {
SaveSummaryItem,
StudentFormState,
} from '@/composables/maint/useStudentMaintenanceForm'
interface FieldErrorItem {
field: string
message: string
}
interface GradeOption {
title: string
value: number
}
defineProps<{
confirmCloseVisible: boolean
confirmDeleteVisible: boolean
confirmNavigateVisible: boolean
confirmSaveVisible: boolean
confirmSwitchVisible: boolean
departments: string[]
dialogSubtitle: string
dialogTitle: string
dialogVisible: boolean
enrollYears: number[]
errorSummary: FieldErrorItem[]
fieldErrors: Record<keyof StudentFormState, string[]>
gradeOptions: GradeOption[]
hasNextRecord: boolean
hasPrevRecord: boolean
isDirty: boolean
isEditMode: boolean
isFormLocked: boolean
isFormReadonly: boolean
isLoading: boolean
isSaving: boolean
isViewMode: boolean
pendingDeleteLabel: string
saveSummary: SaveSummaryItem[]
statuses: string[]
}>()
const form = defineModel<StudentFormState>('form', { required: true })
const emit = defineEmits<{
(e: 'update:confirmCloseVisible', value: boolean): void
(e: 'update:confirmDeleteVisible', value: boolean): void
(e: 'update:confirmNavigateVisible', value: boolean): void
(e: 'update:confirmSaveVisible', value: boolean): void
(e: 'update:confirmSwitchVisible', value: boolean): void
(e: 'dialog-visible-change', value: boolean): void
(e: 'clear-field-error', field: keyof StudentFormState): void
(e: 'close'): void
(e: 'confirm-close'): void
(e: 'confirm-delete'): void
(e: 'confirm-navigate'): void
(e: 'confirm-save'): void
(e: 'confirm-switch'): void
(e: 'delete-current'): void
(e: 'first'): void
(e: 'last'): void
(e: 'next'): void
(e: 'prev'): void
(e: 'save'): void
(e: 'scroll-to-field', field: string): void
(e: 'switch-to-edit'): void
(e: 'switch-to-view'): void
}>()
</script>
<template>
<teleport to="body">
<v-overlay
class="dialog-overlay"
:close-on-content-click="false"
:model-value="dialogVisible"
scrim="rgba(0, 0, 0, 0.45)"
scroll-strategy="block"
@update:model-value="emit('dialog-visible-change', $event)"
>
<div class="dialog-panel">
<MntDialogCard
content-class="pa-2 flex-grow-1 overflow-y-auto"
:dialog-subtitle="dialogSubtitle"
:dialog-title="dialogTitle"
:is-edit-mode="isEditMode"
:is-view-mode="isViewMode"
width="100%"
>
<template #toolbar>
<MntRecordNavToolbar
edit-label="進入編輯"
first-label="第一筆"
:has-next-record="hasNextRecord"
:has-prev-record="hasPrevRecord"
:is-edit-mode="isEditMode"
:is-view-mode="isViewMode"
last-label="最後一筆"
view-label="回到檢視"
@first="emit('first')"
@last="emit('last')"
@next="emit('next')"
@prev="emit('prev')"
@switch-to-edit="emit('switch-to-edit')"
@switch-to-view="emit('switch-to-view')"
/>
</template>
<template #content>
<v-alert
v-if="errorSummary.length > 0 && !isLoading"
class="mb-4"
type="error"
variant="tonal"
>
<div class="text-subtitle-2 mb-2">請先修正以下問題</div>
<div class="d-flex flex-column ga-1">
<v-btn
v-for="error in errorSummary"
:key="error.field"
color="error"
size="small"
variant="text"
@click="emit('scroll-to-field', error.field)"
>
{{ error.message }}
</v-btn>
</div>
</v-alert>
<v-skeleton-loader
v-if="isLoading"
class="mt-4"
type="subtitle,paragraph"
width="100%"
/>
<v-form
v-else
:class="{ 'form-readonly': isFormReadonly }"
@submit.prevent="emit('save')"
>
<ItemFormFieldGroup
v-model="form"
:departments="departments"
:enroll-years="enrollYears"
:field-errors="fieldErrors"
:grade-options="gradeOptions"
:is-form-locked="isFormLocked"
:is-form-readonly="isFormReadonly"
:statuses="statuses"
@clear-field-error="emit('clear-field-error', $event)"
/>
</v-form>
</template>
<template #actions>
<v-spacer />
<v-btn :disabled="isSaving" variant="text" @click="emit('close')">取消</v-btn>
<v-btn
v-if="isEditMode"
color="error"
:disabled="isSaving"
variant="tonal"
@click="emit('delete-current')"
>
刪除
</v-btn>
<v-btn
v-if="!isViewMode"
color="primary"
:disabled="!isDirty || isLoading"
:loading="isSaving"
variant="flat"
@click="emit('save')"
>
儲存
</v-btn>
<v-btn v-else color="primary" variant="flat" @click="emit('close')">關閉</v-btn>
</template>
</MntDialogCard>
</div>
</v-overlay>
</teleport>
<ConfirmDialog
:model-value="confirmCloseVisible"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="emit('confirm-close')"
@update:model-value="emit('update:confirmCloseVisible', $event)"
/>
<ConfirmDialog
:model-value="confirmSaveVisible"
:confirm-loading="isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="emit('confirm-save')"
@update:model-value="emit('update:confirmSaveVisible', $event)"
>
<div v-if="saveSummary.length > 0" class="d-flex flex-column ga-2">
<div v-for="item in saveSummary" :key="item.label" class="d-flex flex-column">
<div class="text-caption text-medium-emphasis">{{ item.label }}</div>
<div v-if="item.before !== null" class="text-body-2">
<span class="text-medium-emphasis"></span>
<span :class="{ 'text-medium-emphasis': item.before === '—' }">
{{ item.before }}
</span>
</div>
<div class="text-body-2">
<span class="text-medium-emphasis"></span>
<span :class="{ 'text-medium-emphasis': item.after === '—' }">
{{ item.after }}
</span>
</div>
</div>
</div>
<div v-else class="text-body-2">目前沒有可儲存的變更</div>
</ConfirmDialog>
<ConfirmDialog
:model-value="confirmDeleteVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="emit('confirm-delete')"
@update:model-value="emit('update:confirmDeleteVisible', $event)"
/>
<ConfirmDialog
:model-value="confirmSwitchVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="emit('confirm-switch')"
@update:model-value="emit('update:confirmSwitchVisible', $event)"
/>
<ConfirmDialog
:model-value="confirmNavigateVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="emit('confirm-navigate')"
@update:model-value="emit('update:confirmNavigateVisible', $event)"
/>
</template>
<style scoped>
.dialog-overlay :deep(.v-overlay__content) {
height: 100vh;
display: flex;
justify-content: flex-end;
width: 100vw;
max-width: 100vw;
}
.dialog-panel {
width: 760px;
max-width: 100%;
height: 100vh;
background: rgb(var(--v-theme-surface));
padding: 12px;
box-shadow: -12px 0 24px rgba(0, 0, 0, 0.18);
display: flex;
}
.form-readonly :deep(.v-field) {
pointer-events: none;
}
</style>