feat: Implement editable student grid and refactor maintenance dialogs

This commit is contained in:
skytek_xinliang
2026-03-30 11:44:04 +08:00
parent 20b093ff73
commit edf664fbb8
9 changed files with 372 additions and 237 deletions
@@ -59,7 +59,7 @@
:disabled="!hasSelectedRows"
:prepend-icon="mdiDelete"
variant="outlined"
@click="deleteSelectedRows"
@click="requestDeleteSelectedRows"
>
批次刪除
</v-btn>
@@ -69,7 +69,7 @@
:disabled="!hasAnyChange"
:prepend-icon="mdiContentSave"
variant="outlined"
@click="saveAllRows"
@click="requestSaveAllRows"
>
儲存變更
</v-btn>
@@ -289,7 +289,7 @@
:disabled="!isBulkEditEnabled"
size="small"
variant="text"
@click="deleteSingleRow(item.id)"
@click="requestDeleteSingleRow(item.id)"
>
刪除
</v-btn>
@@ -300,10 +300,38 @@
</v-card-text>
</v-card>
</div>
<ConfirmDialog
v-model="confirmDeleteSingleVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="singleDeleteMessage"
title="確認刪除"
@confirm="confirmDeleteSingleRow"
/>
<ConfirmDialog
v-model="confirmDeleteSelectedVisible"
confirm-color="error"
confirm-text="確定批次刪除"
:message="selectedDeleteMessage"
title="確認批次刪除"
@confirm="confirmDeleteSelectedRows"
/>
<ConfirmDialog
v-model="confirmSaveVisible"
confirm-text="確認儲存"
message="確定要儲存目前所有變更嗎?"
title="確認儲存變更"
@confirm="confirmSaveAllRows"
/>
</template>
<script setup lang="ts">
import { mdiContentSave, mdiDelete, mdiMagnify, mdiRestore } from '@mdi/js'
import { computed, ref } from 'vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import { useEditableStudentGrid } from '@/composables/maint/useEditableStudentGrid'
const {
@@ -331,6 +359,62 @@ const {
deleteSingleRow,
resetAllRows,
} = useEditableStudentGrid()
const confirmDeleteSingleVisible = ref(false)
const confirmDeleteSelectedVisible = ref(false)
const confirmSaveVisible = ref(false)
const pendingDeleteRowId = ref<number | null>(null)
const pendingDeleteRow = computed(() =>
pendingDeleteRowId.value === null ? null : getDraftRow(pendingDeleteRowId.value)
)
const singleDeleteMessage = computed(() => {
const row = pendingDeleteRow.value
if (!row) {
return '確定要刪除此筆資料嗎?此操作會在儲存後正式生效。'
}
return `確定要刪除 ${row.name}${row.studentId})嗎?此操作會在儲存後正式生效。`
})
const selectedDeleteMessage = computed(
() =>
`確定要刪除目前選取的 ${selectedRowIds.value.length} 筆資料嗎?此操作會在儲存後正式生效。`
)
function requestDeleteSingleRow(id: number) {
pendingDeleteRowId.value = id
confirmDeleteSingleVisible.value = true
}
function confirmDeleteSingleRow() {
if (pendingDeleteRowId.value === null) return
deleteSingleRow(pendingDeleteRowId.value)
pendingDeleteRowId.value = null
confirmDeleteSingleVisible.value = false
}
function requestDeleteSelectedRows() {
if (!hasSelectedRows.value) return
confirmDeleteSelectedVisible.value = true
}
function confirmDeleteSelectedRows() {
deleteSelectedRows()
confirmDeleteSelectedVisible.value = false
}
function requestSaveAllRows() {
if (!hasAnyChange.value) return
confirmSaveVisible.value = true
}
function confirmSaveAllRows() {
saveAllRows()
confirmSaveVisible.value = false
}
</script>
<style scoped>
@@ -1,120 +0,0 @@
<template>
<common-confirm-dialog
v-model="closeVisibleModel"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="emit('confirm-close')"
/>
<common-confirm-dialog
v-model="saveVisibleModel"
:confirm-loading="props.isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="emit('confirm-save')"
>
<div v-if="props.saveSummary.length > 0" class="d-flex flex-column ga-2">
<div v-for="item in props.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>
</common-confirm-dialog>
<common-confirm-dialog
v-model="deleteVisibleModel"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${props.pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="emit('confirm-delete')"
/>
<common-confirm-dialog
v-model="switchVisibleModel"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="emit('confirm-switch')"
/>
<common-confirm-dialog
v-model="navigateVisibleModel"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="emit('confirm-navigate')"
/>
</template>
<script setup lang="ts">
import type { SaveSummaryItem } from '@/composables/maint/useStudentMaintenanceForm'
import { computed } from 'vue'
import CommonConfirmDialog from './CommonConfirmDialog.vue'
const props = defineProps<{
closeVisible: boolean
deleteVisible: boolean
isSaving: boolean
navigateVisible: boolean
pendingDeleteLabel: string
saveSummary: SaveSummaryItem[]
saveVisible: boolean
switchVisible: boolean
}>()
const emit = defineEmits<{
(event: 'confirm-close'): void
(event: 'confirm-delete'): void
(event: 'confirm-navigate'): void
(event: 'confirm-save'): void
(event: 'confirm-switch'): void
(event: 'update:close-visible', value: boolean): void
(event: 'update:delete-visible', value: boolean): void
(event: 'update:navigate-visible', value: boolean): void
(event: 'update:save-visible', value: boolean): void
(event: 'update:switch-visible', value: boolean): void
}>()
const closeVisibleModel = computed({
get: () => props.closeVisible,
set: (value: boolean) => emit('update:close-visible', value),
})
const saveVisibleModel = computed({
get: () => props.saveVisible,
set: (value: boolean) => emit('update:save-visible', value),
})
const deleteVisibleModel = computed({
get: () => props.deleteVisible,
set: (value: boolean) => emit('update:delete-visible', value),
})
const switchVisibleModel = computed({
get: () => props.switchVisible,
set: (value: boolean) => emit('update:switch-visible', value),
})
const navigateVisibleModel = computed({
get: () => props.navigateVisible,
set: (value: boolean) => emit('update:navigate-visible', value),
})
</script>
+1 -1
View File
@@ -3,5 +3,5 @@
</template>
<script setup lang="ts">
import EditableStudentGrid from '@/components/maint/EditableStudentGrid.vue'
import EditableStudentGrid from '@/components/maint/EditableGrid.vue'
</script>
+65 -22
View File
@@ -268,7 +268,7 @@
:class="{ 'form-readonly': isFormReadonly }"
@submit.prevent="requestSaveConfirmation"
>
<maintenance-student-form-fields
<master-form-fields
:departments="departments"
:enroll-years="enrollYears"
:field-errors="fieldErrors"
@@ -348,25 +348,68 @@
</v-overlay>
</teleport>
<maintenance-crud-dialogs
:close-visible="confirmCloseVisible"
:delete-visible="confirmDeleteVisible"
:is-saving="isSaving"
:navigate-visible="confirmNavigateVisible"
:pending-delete-label="pendingDeleteLabel"
:save-summary="saveSummary"
:save-visible="confirmSaveVisible"
:switch-visible="confirmSwitchVisible"
@confirm-close="confirmClose"
@confirm-delete="confirmDelete"
@confirm-navigate="confirmNavigate"
@confirm-save="confirmSave"
@confirm-switch="confirmSwitch"
@update:close-visible="confirmCloseVisible = $event"
@update:delete-visible="confirmDeleteVisible = $event"
@update:navigate-visible="confirmNavigateVisible = $event"
@update:save-visible="confirmSaveVisible = $event"
@update:switch-visible="confirmSwitchVisible = $event"
<ConfirmDialog
v-model="confirmCloseVisible"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="confirmClose"
/>
<ConfirmDialog
v-model="confirmSaveVisible"
:confirm-loading="isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="confirmSave"
>
<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
v-model="confirmDeleteVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="confirmDelete"
/>
<ConfirmDialog
v-model="confirmSwitchVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmSwitch"
/>
<ConfirmDialog
v-model="confirmNavigateVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmNavigate"
/>
<!-- 成功提示 -->
@@ -380,8 +423,8 @@ import { mdiBroom, mdiDelete, mdiEye, mdiMagnify, mdiPencil } from '@mdi/js'
import { computed, nextTick, ref, watch } from 'vue'
import { useDisplay } from 'vuetify'
import MaintenanceCrudDialogs from '@/components/maint/MaintenanceCrudDialogs.vue'
import MaintenanceStudentFormFields from '@/components/maint/MaintenanceStudentFormFields.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MasterFormFields from '@/components/maint/MasterFormFields.vue'
import MasterDetailSemesterList from '@/components/maint/master-detail/SemesterList.vue'
import MasterDetailSemesterPanel from '@/components/maint/master-detail/SemesterPanel.vue'
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
+66 -24
View File
@@ -267,7 +267,7 @@
]"
@submit.prevent="requestSaveConfirmation"
>
<maintenance-student-form-fields
<master-form-fields
:departments="departments"
:enroll-years="enrollYears"
:field-errors="fieldErrors"
@@ -363,29 +363,72 @@
</v-overlay>
</teleport>
<maintenance-crud-dialogs
:close-visible="confirmCloseVisible"
:delete-visible="confirmDeleteVisible"
:is-saving="isSaving"
:navigate-visible="confirmNavigateVisible"
:pending-delete-label="pendingDeleteLabel"
:save-summary="saveSummary"
:save-visible="confirmSaveVisible"
:switch-visible="confirmSwitchVisible"
@confirm-close="confirmClose"
@confirm-delete="confirmDelete"
@confirm-navigate="confirmNavigate"
@confirm-save="confirmSave"
@confirm-switch="confirmSwitch"
@update:close-visible="confirmCloseVisible = $event"
@update:delete-visible="confirmDeleteVisible = $event"
@update:navigate-visible="confirmNavigateVisible = $event"
@update:save-visible="confirmSaveVisible = $event"
@update:switch-visible="confirmSwitchVisible = $event"
<ConfirmDialog
v-model="confirmCloseVisible"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="confirmClose"
/>
<ConfirmDialog
v-model="confirmSaveVisible"
:confirm-loading="isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="confirmSave"
>
<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
v-model="confirmDeleteVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="confirmDelete"
/>
<ConfirmDialog
v-model="confirmSwitchVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmSwitch"
/>
<ConfirmDialog
v-model="confirmNavigateVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmNavigate"
/>
<!-- 刪除課程確認 -->
<common-confirm-dialog
<ConfirmDialog
v-model="confirmDeleteCourseVisible"
confirm-color="error"
confirm-text="確定移除"
@@ -459,9 +502,8 @@ import { mdiBookPlus, mdiBroom, mdiDelete, mdiEye, mdiMagnify, mdiPencil } from
import { computed, nextTick, ref, watch } from 'vue'
import { useDisplay } from 'vuetify'
import CommonConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MaintenanceCrudDialogs from '@/components/maint/MaintenanceCrudDialogs.vue'
import MaintenanceStudentFormFields from '@/components/maint/MaintenanceStudentFormFields.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MasterFormFields from '@/components/maint/MasterFormFields.vue'
import MasterDetailBSemesterMobilePanel from '@/components/maint/master-detail/SemesterMobilePanel.vue'
import MasterDetailBSemesterSection from '@/components/maint/master-detail/SemesterSection.vue'
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
+65 -22
View File
@@ -266,7 +266,7 @@
]"
@submit.prevent="requestSaveConfirmation"
>
<maintenance-student-form-fields
<master-form-fields
:departments="departments"
:enroll-years="enrollYears"
:field-errors="fieldErrors"
@@ -361,25 +361,68 @@
</v-overlay>
</teleport>
<maintenance-crud-dialogs
:close-visible="confirmCloseVisible"
:delete-visible="confirmDeleteVisible"
:is-saving="isSaving"
:navigate-visible="confirmNavigateVisible"
:pending-delete-label="pendingDeleteLabel"
:save-summary="saveSummary"
:save-visible="confirmSaveVisible"
:switch-visible="confirmSwitchVisible"
@confirm-close="confirmClose"
@confirm-delete="confirmDelete"
@confirm-navigate="confirmNavigate"
@confirm-save="confirmSave"
@confirm-switch="confirmSwitch"
@update:close-visible="confirmCloseVisible = $event"
@update:delete-visible="confirmDeleteVisible = $event"
@update:navigate-visible="confirmNavigateVisible = $event"
@update:save-visible="confirmSaveVisible = $event"
@update:switch-visible="confirmSwitchVisible = $event"
<ConfirmDialog
v-model="confirmCloseVisible"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="confirmClose"
/>
<ConfirmDialog
v-model="confirmSaveVisible"
:confirm-loading="isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="confirmSave"
>
<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
v-model="confirmDeleteVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="confirmDelete"
/>
<ConfirmDialog
v-model="confirmSwitchVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmSwitch"
/>
<ConfirmDialog
v-model="confirmNavigateVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmNavigate"
/>
<!-- 成功提示 -->
@@ -446,8 +489,8 @@ import { mdiBroom, mdiDelete, mdiEye, mdiMagnify, mdiPencil, mdiSchool } from '@
import { computed, nextTick, ref, watch } from 'vue'
import { useDisplay } from 'vuetify'
import MaintenanceCrudDialogs from '@/components/maint/MaintenanceCrudDialogs.vue'
import MaintenanceStudentFormFields from '@/components/maint/MaintenanceStudentFormFields.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MasterFormFields from '@/components/maint/MasterFormFields.vue'
import MasterDetailCCourseMobilePanel from '@/components/maint/master-detail/CourseMobilePanel.vue'
import MasterDetailCCourseSection from '@/components/maint/master-detail/CourseSection.vue'
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
+63 -20
View File
@@ -427,25 +427,68 @@
</v-overlay>
</teleport>
<maintenance-crud-dialogs
:close-visible="confirmCloseVisible"
:delete-visible="confirmDeleteVisible"
:is-saving="isSaving"
:navigate-visible="confirmNavigateVisible"
:pending-delete-label="pendingDeleteLabel"
:save-summary="saveSummary"
:save-visible="confirmSaveVisible"
:switch-visible="confirmSwitchVisible"
@confirm-close="confirmClose"
@confirm-delete="confirmDelete"
@confirm-navigate="confirmNavigate"
@confirm-save="confirmSave"
@confirm-switch="confirmSwitch"
@update:close-visible="confirmCloseVisible = $event"
@update:delete-visible="confirmDeleteVisible = $event"
@update:navigate-visible="confirmNavigateVisible = $event"
@update:save-visible="confirmSaveVisible = $event"
@update:switch-visible="confirmSwitchVisible = $event"
<ConfirmDialog
v-model="confirmCloseVisible"
confirm-color="error"
confirm-text="關閉不儲存"
message="目前有尚未儲存的內容,確定要關閉嗎?"
title="未儲存變更"
@confirm="confirmClose"
/>
<ConfirmDialog
v-model="confirmSaveVisible"
:confirm-loading="isSaving"
confirm-text="確認儲存"
max-width="520"
title="確認儲存變更"
@confirm="confirmSave"
>
<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
v-model="confirmDeleteVisible"
confirm-color="error"
confirm-text="確定刪除"
:message="`確定要刪除 ${pendingDeleteLabel} 嗎?此操作無法復原。`"
title="確認刪除"
@confirm="confirmDelete"
/>
<ConfirmDialog
v-model="confirmSwitchVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換為檢視模式將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmSwitch"
/>
<ConfirmDialog
v-model="confirmNavigateVisible"
confirm-text="確定切換"
max-width="480"
message="目前有尚未儲存的內容,切換到其他資料將會捨棄變更,確定要切換嗎?"
title="未儲存變更"
@confirm="confirmNavigate"
/>
<!-- 成功提示 -->
@@ -458,7 +501,7 @@
import { mdiBroom, mdiDelete, mdiEye, mdiMagnify, mdiPencil } from '@mdi/js'
import { computed, nextTick, ref, watch } from 'vue'
import { useDisplay } from 'vuetify'
import MaintenanceCrudDialogs from '@/components/maint/MaintenanceCrudDialogs.vue'
import ConfirmDialog from '@/components/maint/CommonConfirmDialog.vue'
import MntDialogCard from '@/components/maint/MntDialogCard.vue'
import MntRecordNavToolbar from '@/components/maint/MntRecordNavToolbar.vue'
import PageMaint from '@/components/maint/PageMaint.vue'