Files
skt-vuetify-templates/src/views/maint/MasterDetailA.vue
T
skytek_xinliang f61432ad8a fixing adn docing
2026-06-01 14:44:39 +08:00

372 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import DetailNavigation from '@/components/maint/master-detail/DetailNavigation.vue'
import DetailSidePanel from '@/components/maint/master-detail/DetailSidePanel.vue'
import MasterFileFormFields from '@/components/maint/MasterFileFormFields.vue'
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
import MaintShell from '@/components/maint/MaintShell.vue'
import SectionDataTable from '@/components/sections/SectionDataTable.vue'
import SectionSearchPanel from '@/components/sections/SectionSearchPanel.vue'
import { useMasterDetailAMaintenancePage } from '@/composables/page-drivers/useMasterDetailAMaintenancePage'
const {
confirmSave, currentPage, departments, detailForm, flow, formState,
gradeOptions, itemsPerPage, masterDetailEvents, masterDetailProps,
openAddDialog, openEditDialog, openViewDialog, pageCount, pageModel,
pageSummary, requestSaveConfirmation, resetSearch, scrollToField,
search, searchPanelOpen, snackbarVisible, statuses, students, tableHeaders,
} = useMasterDetailAMaintenancePage()
</script>
<template>
<MaintShell
:search-panel-open="searchPanelOpen"
:title="pageModel.title"
@toggle-search="searchPanelOpen = !searchPanelOpen"
@create="openAddDialog"
>
<template #search-fields>
<SectionSearchPanel
v-model="search"
:departments="masterDetailProps.departments"
:grade-options="masterDetailProps.gradeOptions"
:statuses="masterDetailProps.statuses"
@reset="resetSearch"
/>
</template>
<template #table>
<SectionDataTable
:current-page="currentPage"
:grade-label="formState.gradeLabel"
:headers="tableHeaders"
:items="students"
:items-per-page="itemsPerPage"
:page-count="pageCount"
:page-summary="pageSummary"
:row-props="formState.rowProps"
:status-color="formState.statusColor"
@delete="flow.requestDeleteConfirmation($event)"
@edit="openEditDialog($event)"
@update:current-page="currentPage = $event"
@view="openViewDialog($event)"
/>
</template>
</MaintShell>
<teleport to="body">
<v-overlay
class="dialog-overlay"
:close-on-content-click="false"
:model-value="masterDetailProps.dialogVisible"
scrim="rgba(0, 0, 0, 0.45)"
scroll-strategy="block"
@update:model-value="flow.handleDialogVisibility($event)"
>
<div class="dialog-panel" :class="{ 'is-mobile': masterDetailProps.isMobile }">
<div
v-if="!masterDetailProps.isMobile || masterDetailProps.activeMobilePanel === 'detail'"
class="detail-panel-wrapper"
:class="{ 'is-active': !!masterDetailProps.selectedSemesterId, 'is-mobile': masterDetailProps.isMobile }"
>
<DetailSidePanel
v-model:detail-form="detailForm"
:is-detail-editing="masterDetailProps.isDetailEditing"
:is-mobile="masterDetailProps.isMobile"
:is-view-mode="masterDetailProps.isViewMode"
:selected-semester="masterDetailProps.selectedSemester"
v-on="masterDetailEvents"
/>
</div>
<MntDialogCard
v-if="!masterDetailProps.isMobile || masterDetailProps.activeMobilePanel === 'master'"
:dialog-subtitle="masterDetailProps.dialogSubtitle"
:dialog-title="masterDetailProps.dialogTitle"
:is-edit-mode="masterDetailProps.isEditMode"
:is-view-mode="masterDetailProps.isViewMode"
:width="masterDetailProps.isMobile ? '100%' : 760"
>
<template #toolbar>
<MntRecordNavToolbar
:has-next-record="masterDetailProps.hasNextRecord"
:has-prev-record="masterDetailProps.hasPrevRecord"
:is-edit-mode="masterDetailProps.isEditMode"
:is-view-mode="masterDetailProps.isViewMode"
:mobile="masterDetailProps.isMobile"
v-on="masterDetailEvents"
/>
</template>
<template #content>
<v-alert
v-if="masterDetailProps.errorSummary.length > 0 && !masterDetailProps.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 masterDetailProps.errorSummary"
:key="error.field"
color="error"
size="small"
variant="text"
@click="scrollToField(error.field)"
>
{{ error.message }}
</v-btn>
</div>
</v-alert>
<v-skeleton-loader
v-if="masterDetailProps.isLoading"
class="mt-4"
type="subtitle,paragraph"
width="100%"
/>
<v-form
v-else
:class="{ 'form-readonly': masterDetailProps.isFormReadonly }"
@submit.prevent="requestSaveConfirmation()"
>
<MasterFileFormFields
:departments="masterDetailProps.departments"
:enroll-years="masterDetailProps.enrollYears"
:field-errors="masterDetailProps.fieldErrors"
:form="formState.form.value"
:grade-options="masterDetailProps.gradeOptions"
:is-form-locked="masterDetailProps.isFormLocked"
:is-form-readonly="masterDetailProps.isFormReadonly"
:statuses="masterDetailProps.statuses"
v-on="masterDetailEvents"
/>
<v-divider />
<DetailNavigation
:is-mobile="masterDetailProps.isMobile"
:is-view-mode="masterDetailProps.isViewMode"
:selected-semester-id="masterDetailProps.selectedSemesterId"
:semesters="masterDetailProps.semesters"
v-on="masterDetailEvents"
/>
</v-form>
</template>
<template #actions>
<template v-if="masterDetailProps.isMobile">
<v-btn :disabled="masterDetailProps.isSaving" variant="text" @click="flow.requestCloseDialog()">取消</v-btn>
<v-btn
v-if="masterDetailProps.isEditMode"
color="error"
:disabled="masterDetailProps.isSaving"
variant="tonal"
@click="flow.requestDeleteCurrent()"
>
刪除
</v-btn>
<v-btn
v-if="!masterDetailProps.isViewMode"
color="primary"
:disabled="!masterDetailProps.isDirty || masterDetailProps.isLoading"
:loading="masterDetailProps.isSaving"
variant="flat"
@click="requestSaveConfirmation()"
>
儲存
</v-btn>
<v-btn v-else color="primary" variant="flat" @click="flow.requestCloseDialog()">關閉</v-btn>
</template>
<template v-else>
<v-spacer />
<v-btn :disabled="masterDetailProps.isSaving" variant="text" @click="flow.requestCloseDialog()">取消</v-btn>
<v-btn
v-if="masterDetailProps.isEditMode"
color="error"
:disabled="masterDetailProps.isSaving"
variant="tonal"
@click="flow.requestDeleteCurrent()"
>
刪除
</v-btn>
<v-btn
v-if="!masterDetailProps.isViewMode"
color="primary"
:disabled="!masterDetailProps.isDirty || masterDetailProps.isLoading"
:loading="masterDetailProps.isSaving"
variant="flat"
@click="requestSaveConfirmation()"
>
儲存
</v-btn>
<v-btn v-else color="primary" variant="flat" @click="flow.requestCloseDialog()">關閉</v-btn>
</template>
</template>
</MntDialogCard>
</div>
</v-overlay>
</teleport>
<ConfirmDialog
:model-value="masterDetailProps.confirmCloseVisible"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="flow.confirmClose()"
@update:model-value="flow.confirmCloseVisible.value = $event"
/>
<ConfirmDialog
:model-value="masterDetailProps.confirmSaveVisible"
:confirm-loading="masterDetailProps.isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="confirmSave()"
@update:model-value="flow.confirmSaveVisible.value = $event"
>
<div v-if="masterDetailProps.saveSummary.length > 0" class="d-flex flex-column ga-2">
<div v-for="item in masterDetailProps.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="masterDetailProps.confirmDeleteVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${masterDetailProps.pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="flow.confirmDelete()"
@update:model-value="flow.confirmDeleteVisible.value = $event"
/>
<ConfirmDialog
:model-value="masterDetailProps.confirmSwitchVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="flow.confirmSwitch()"
@update:model-value="flow.confirmSwitchVisible.value = $event"
/>
<ConfirmDialog
:model-value="masterDetailProps.confirmNavigateVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="flow.confirmNavigate()"
@update:model-value="flow.confirmNavigateVisible.value = $event"
/>
<v-snackbar v-model="snackbarVisible" color="success" location="bottom right" :timeout="2200">
儲存成功
</v-snackbar>
</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: auto;
max-width: 100%;
height: 100vh;
background: transparent;
padding: 0;
display: flex;
}
.dialog-panel > .v-card {
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.12);
}
.detail-panel-wrapper {
width: 0;
opacity: 0;
overflow: hidden;
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
margin-right: 0;
}
.detail-panel-wrapper.is-active {
width: 600px;
opacity: 1;
margin-right: 0;
}
.dialog-panel.is-mobile {
width: 100%;
}
.dialog-panel.is-mobile :deep(.dialog-title) {
padding: 16px 20px 12px;
}
.dialog-panel.is-mobile :deep(.dialog-toolbar) {
padding: 8px 12px;
}
.dialog-panel.is-mobile :deep(.dialog-actions) {
gap: 8px;
padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
}
.dialog-panel.is-mobile :deep(.dialog-actions .v-btn) {
flex: 1 1 0;
min-width: 0;
}
.dialog-panel.is-mobile :deep(.v-card-text) {
padding-bottom: 88px;
}
.detail-panel-wrapper.is-mobile {
width: 100%;
opacity: 1;
overflow: visible;
}
.detail-panel-wrapper.is-mobile.is-active {
width: 100%;
}
.form-readonly :deep(.v-field) {
pointer-events: none;
}
@media (max-width: 600px) {
.dialog-panel {
width: 100%;
}
.dialog-panel > .v-card {
width: 100%;
box-shadow: none;
}
}
</style>