351 lines
12 KiB
Vue
351 lines
12 KiB
Vue
<template>
|
|
<div class="d-flex flex-column">
|
|
<v-card variant="flat">
|
|
<v-card-title class="d-flex flex-wrap align-center py-0 ga-2">
|
|
<span class="text-h6">可編輯表格維護示範</span>
|
|
<v-chip :color="hasAnyChange ? 'warning' : 'success'" variant="tonal">
|
|
{{ hasAnyChange ? '有未儲存變更' : '已同步' }}
|
|
</v-chip>
|
|
<v-chip color="info" variant="tonal">筆數 {{ filteredStudents.length }}</v-chip>
|
|
<v-spacer />
|
|
<v-btn flat :prepend-icon="mdiMagnify" @click="isSearchVisible = !isSearchVisible"
|
|
>條件搜尋</v-btn
|
|
>
|
|
<v-switch v-model="isBulkEditEnabled" color="primary" hide-details label="啟用編輯" />
|
|
</v-card-title>
|
|
|
|
<v-divider />
|
|
|
|
<v-card-text class="pb-0 pt-2">
|
|
<v-row v-if="isSearchVisible" class="mb-2" density="compact">
|
|
<v-col cols="12" md="3">
|
|
<div class="text-body-1 text-medium-emphasis pl-2">學號</div>
|
|
<v-text-field
|
|
v-model="search.studentId"
|
|
clearable
|
|
density="compact"
|
|
hide-details
|
|
placeholder="例如:S2024001"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
<v-col cols="12" md="3">
|
|
<div class="text-body-1 text-medium-emphasis pl-2">姓名</div>
|
|
<v-text-field
|
|
v-model="search.name"
|
|
clearable
|
|
density="compact"
|
|
hide-details
|
|
placeholder="例如:王小明"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
<v-col cols="12" md="3">
|
|
<div class="text-body-1 text-medium-emphasis pl-2">系所</div>
|
|
<v-select
|
|
v-model="search.department"
|
|
:class="{ 'select-hide-arrow': !isBulkEditEnabled }"
|
|
clearable
|
|
density="compact"
|
|
hide-details
|
|
:items="departments"
|
|
variant="outlined"
|
|
/>
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<v-row v-if="isBulkEditEnabled" align="center" class="mb-2 ga-1" density="compact">
|
|
<v-btn
|
|
:disabled="!hasSelectedRows"
|
|
:prepend-icon="mdiDelete"
|
|
variant="outlined"
|
|
@click="deleteSelectedRows"
|
|
>
|
|
批次刪除
|
|
</v-btn>
|
|
<v-spacer />
|
|
<v-btn
|
|
color="primary"
|
|
:disabled="!hasAnyChange"
|
|
:prepend-icon="mdiContentSave"
|
|
variant="outlined"
|
|
@click="saveAllRows"
|
|
>
|
|
儲存變更
|
|
</v-btn>
|
|
<v-btn
|
|
:disabled="!hasAnyChange"
|
|
:prepend-icon="mdiRestore"
|
|
variant="text"
|
|
@click="resetAllRows"
|
|
>
|
|
取消變更
|
|
</v-btn>
|
|
</v-row>
|
|
|
|
<div ref="tableContainerRef">
|
|
<v-data-table
|
|
density="comfortable"
|
|
fixed-header
|
|
:headers="tableHeaders"
|
|
:height="tableHeight"
|
|
item-value="id"
|
|
:items="filteredStudents"
|
|
:items-per-page="10"
|
|
items-per-page-text="每頁筆數"
|
|
page-text="第 {0}-{1} 筆 / 共 {2} 筆"
|
|
>
|
|
<template #[`header.select`]>
|
|
<v-checkbox-btn
|
|
:disabled="!isBulkEditEnabled"
|
|
:indeterminate="isPartiallyVisibleSelected"
|
|
:model-value="isAllVisibleSelected"
|
|
@update:model-value="toggleSelectAll"
|
|
/>
|
|
</template>
|
|
<template #[`item.select`]="{ item }">
|
|
<v-checkbox-btn
|
|
:disabled="!isBulkEditEnabled"
|
|
:model-value="selectedRowIds.includes(item.id)"
|
|
@update:model-value="(checked) => toggleSingleRowSelection(item.id, checked)"
|
|
/>
|
|
</template>
|
|
<template #[`item.studentId`]="{ item }">
|
|
<v-text-field
|
|
:model-value="getDraftRow(item.id)?.studentId ?? ''"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.studentId = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.name`]="{ item }">
|
|
<v-text-field
|
|
:model-value="getDraftRow(item.id)?.name ?? ''"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.name = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.department`]="{ item }">
|
|
<v-select
|
|
:model-value="getDraftRow(item.id)?.department ?? ''"
|
|
:class="{ 'select-hide-arrow': !isBulkEditEnabled }"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:items="departments"
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.department = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.grade`]="{ item }">
|
|
<v-select
|
|
:model-value="getDraftRow(item.id)?.grade ?? null"
|
|
:class="{ 'select-hide-arrow': !isBulkEditEnabled }"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
item-title="title"
|
|
item-value="value"
|
|
:items="gradeOptions"
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.grade = Number(value) || 0
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.enrollYear`]="{ item }">
|
|
<v-select
|
|
:model-value="getDraftRow(item.id)?.enrollYear ?? null"
|
|
:class="{ 'select-hide-arrow': !isBulkEditEnabled }"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:items="enrollYears"
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.enrollYear = Number(value) || 0
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.credits`]="{ item }">
|
|
<v-text-field
|
|
:model-value="getDraftRow(item.id)?.credits ?? 0"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
min="0"
|
|
:readonly="!isBulkEditEnabled"
|
|
type="number"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.credits = Number(value) || 0
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.advisor`]="{ item }">
|
|
<v-text-field
|
|
:model-value="getDraftRow(item.id)?.advisor ?? ''"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.advisor = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.email`]="{ item }">
|
|
<v-text-field
|
|
:model-value="getDraftRow(item.id)?.email ?? ''"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.email = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.phone`]="{ item }">
|
|
<v-text-field
|
|
:model-value="getDraftRow(item.id)?.phone ?? ''"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.phone = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.status`]="{ item }">
|
|
<v-select
|
|
:model-value="getDraftRow(item.id)?.status ?? ''"
|
|
:class="{ 'select-hide-arrow': !isBulkEditEnabled }"
|
|
density="compact"
|
|
flat
|
|
hide-details
|
|
:items="statuses"
|
|
:readonly="!isBulkEditEnabled"
|
|
:variant="isBulkEditEnabled ? 'outlined' : 'solo'"
|
|
@update:model-value="
|
|
(value) => {
|
|
const row = getDraftRow(item.id)
|
|
if (row) row.status = String(value)
|
|
}
|
|
"
|
|
/>
|
|
</template>
|
|
<template #[`item.actions`]="{ item }">
|
|
<div class="d-flex ga-1 justify-end">
|
|
<v-btn
|
|
color="error"
|
|
:disabled="!isBulkEditEnabled"
|
|
size="small"
|
|
variant="text"
|
|
@click="deleteSingleRow(item.id)"
|
|
>
|
|
刪除
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
</v-data-table>
|
|
</div>
|
|
</v-card-text>
|
|
</v-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mdiContentSave, mdiDelete, mdiMagnify, mdiRestore } from '@mdi/js'
|
|
import { useEditableStudentGrid } from '@/composables/maintenance/useEditableStudentGrid'
|
|
|
|
const {
|
|
departments,
|
|
enrollYears,
|
|
filteredStudents,
|
|
getDraftRow,
|
|
gradeOptions,
|
|
hasAnyChange,
|
|
hasSelectedRows,
|
|
isAllVisibleSelected,
|
|
isBulkEditEnabled,
|
|
isPartiallyVisibleSelected,
|
|
isSearchVisible,
|
|
saveAllRows,
|
|
search,
|
|
selectedRowIds,
|
|
statuses,
|
|
tableContainerRef,
|
|
tableHeaders,
|
|
tableHeight,
|
|
toggleSelectAll,
|
|
toggleSingleRowSelection,
|
|
deleteSelectedRows,
|
|
deleteSingleRow,
|
|
resetAllRows,
|
|
} = useEditableStudentGrid()
|
|
</script>
|
|
|
|
<style scoped>
|
|
:deep(.select-hide-arrow .v-field__append-inner) {
|
|
display: none;
|
|
}
|
|
|
|
:deep(.v-table__wrapper .v-field__input) {
|
|
padding-top: 0 !important;
|
|
padding-bottom: 0 !important;
|
|
min-height: 32px;
|
|
}
|
|
|
|
:deep(.v-data-table-footer) {
|
|
padding: 4px 0 0;
|
|
}
|
|
</style>
|