feat: add SingleRecordMnt component for student record maintenance with search, add, edit, view, and delete functionalities
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<v-sheet class="bg-background" v-bind="$attrs">
|
||||
<!-- Search Filter Section -->
|
||||
<SKSearchFilter
|
||||
v-if="showSearchFilter"
|
||||
:collapse-btn-text="props.collapseBtnText"
|
||||
:expand-btn-text="props.expandBtnText"
|
||||
:fields="searchFields"
|
||||
:reset-btn-text="props.resetBtnText"
|
||||
:search-btn-text="props.searchBtnText"
|
||||
:show-expand="true"
|
||||
:visible-when-collapsed="['roleName', 'roleId']"
|
||||
@reset="$emit('reset')"
|
||||
@search="$emit('search', $event)"
|
||||
/>
|
||||
|
||||
<!-- Data Table Section -->
|
||||
<v-card class="bg-surface">
|
||||
<SKTableActionBar
|
||||
v-model:settings-selected-keys="visibleHeaderKeys"
|
||||
:create-btn-text="props.createBtnText"
|
||||
:search-visible="showSearchFilter"
|
||||
:settings-items="headerSettingsItems"
|
||||
:show-search-toggle="true"
|
||||
:title="props.listTitle"
|
||||
@create="emit('create')"
|
||||
@refresh="emit('refresh')"
|
||||
@settings="emit('settings')"
|
||||
@toggle-search="showSearchFilter = !showSearchFilter"
|
||||
/>
|
||||
<v-divider></v-divider>
|
||||
<v-data-table
|
||||
class="role-table"
|
||||
:headers="filteredHeaders"
|
||||
hover
|
||||
:items="props.roles"
|
||||
:items-per-page="10"
|
||||
:items-per-page-options="itemsPerPageOptions"
|
||||
:items-per-page-text="props.itemsPerPageText"
|
||||
:loading="props.loading"
|
||||
>
|
||||
<!-- Status Slot -->
|
||||
<template #[`item.status`]="{ item }">
|
||||
<v-switch
|
||||
color="primary"
|
||||
density="compact"
|
||||
hide-details
|
||||
:model-value="item.status"
|
||||
@update:model-value="emit('update:status', item, $event)"
|
||||
></v-switch>
|
||||
</template>
|
||||
|
||||
<!-- Actions Slot -->
|
||||
<template #[`item.actions`]="{ item }">
|
||||
<v-btn
|
||||
class="px-1"
|
||||
color="primary"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('edit', item)"
|
||||
>
|
||||
{{ props.editActionText }}
|
||||
</v-btn>
|
||||
<v-divider
|
||||
class="mx-1 d-inline-block"
|
||||
style="height: 12px; vertical-align: middle"
|
||||
vertical
|
||||
></v-divider>
|
||||
<v-btn
|
||||
class="px-1"
|
||||
color="error"
|
||||
size="small"
|
||||
variant="text"
|
||||
@click="emit('delete', item)"
|
||||
>
|
||||
{{ props.deleteActionText }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-sheet>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watchEffect } from 'vue'
|
||||
import SKSearchFilter from './base/SKSearchFilter.vue'
|
||||
import SKTableActionBar from './base/SKTableActionBar.vue'
|
||||
|
||||
export interface RoleItem {
|
||||
name: string
|
||||
id: string
|
||||
status: boolean
|
||||
note: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
const showSearchFilter = ref(true)
|
||||
|
||||
const props = defineProps({
|
||||
// Filter Labels & Placeholders
|
||||
roleNameLabel: { type: String, default: '角色名稱' },
|
||||
roleNamePlaceholder: { type: String, default: '請輸入' },
|
||||
roleIdLabel: { type: String, default: '角色ID' },
|
||||
roleIdPlaceholder: { type: String, default: '請輸入' },
|
||||
statusLabel: { type: String, default: '狀態' },
|
||||
statusPlaceholder: { type: String, default: '請選擇' },
|
||||
statusOptions: { type: Array, default: () => ['已啟用', '已禁用'] },
|
||||
noteLabel: { type: String, default: '備註' },
|
||||
notePlaceholder: { type: String, default: '請輸入' },
|
||||
createTimeLabel: { type: String, default: '建立時間' },
|
||||
startDateLabel: { type: String, default: '開始日期' },
|
||||
endDateLabel: { type: String, default: '結束日期' },
|
||||
datePlaceholder: { type: String, default: '請選擇' },
|
||||
|
||||
// Button Texts
|
||||
searchBtnText: { type: String, default: '搜尋' },
|
||||
resetBtnText: { type: String, default: '重置' },
|
||||
expandBtnText: { type: String, default: '展開' },
|
||||
collapseBtnText: { type: String, default: '收起' },
|
||||
createBtnText: { type: String, default: '新增角色' },
|
||||
|
||||
// Table Texts
|
||||
listTitle: { type: String, default: '權限列表' },
|
||||
roleNameHeader: { type: String, default: '角色名稱' },
|
||||
roleIdHeader: { type: String, default: '角色ID' },
|
||||
statusHeader: { type: String, default: '狀態' },
|
||||
noteHeader: { type: String, default: '備註' },
|
||||
createTimeHeader: { type: String, default: '建立時間' },
|
||||
actionsHeader: { type: String, default: '操作' },
|
||||
editActionText: { type: String, default: '修改' },
|
||||
deleteActionText: { type: String, default: '刪除' },
|
||||
|
||||
// Data Table Footer Texts
|
||||
itemsPerPageText: { type: String, default: '每頁筆數:' },
|
||||
itemsPerPageAllText: { type: String, default: '全部' },
|
||||
|
||||
// Data
|
||||
roles: {
|
||||
type: Array as () => RoleItem[],
|
||||
default: () => [],
|
||||
},
|
||||
loading: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'search',
|
||||
'reset',
|
||||
'create',
|
||||
'edit',
|
||||
'delete',
|
||||
'update:status',
|
||||
'refresh',
|
||||
'settings',
|
||||
])
|
||||
|
||||
// --- Search Fields Configuration ---
|
||||
const searchFields = computed(() => [
|
||||
{
|
||||
key: 'roleName',
|
||||
type: 'text' as const,
|
||||
label: props.roleNameLabel,
|
||||
placeholder: props.roleNamePlaceholder,
|
||||
meta: {
|
||||
cols: 12,
|
||||
md: 4,
|
||||
lg: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'roleId',
|
||||
type: 'text' as const,
|
||||
label: props.roleIdLabel,
|
||||
placeholder: props.roleIdPlaceholder,
|
||||
meta: {
|
||||
cols: 12,
|
||||
md: 4,
|
||||
lg: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
type: 'select' as const,
|
||||
label: props.statusLabel,
|
||||
placeholder: props.statusPlaceholder,
|
||||
items: props.statusOptions,
|
||||
meta: {
|
||||
cols: 12,
|
||||
md: 4,
|
||||
lg: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'note',
|
||||
type: 'text' as const,
|
||||
label: props.noteLabel,
|
||||
placeholder: props.notePlaceholder,
|
||||
meta: {
|
||||
cols: 12,
|
||||
md: 4,
|
||||
lg: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'date' as const,
|
||||
key: 'startDate',
|
||||
label: props.startDateLabel,
|
||||
placeholder: props.datePlaceholder,
|
||||
meta: {
|
||||
cols: 12,
|
||||
md: 4,
|
||||
lg: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'date' as const,
|
||||
key: 'endDate',
|
||||
label: props.endDateLabel,
|
||||
placeholder: props.datePlaceholder,
|
||||
meta: {
|
||||
cols: 12,
|
||||
md: 4,
|
||||
lg: 3,
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
// --- Table Config ---
|
||||
|
||||
const formattedHeaders = computed(() => [
|
||||
{ title: props.roleNameHeader, key: 'name', align: 'start' as const },
|
||||
{ title: props.roleIdHeader, key: 'id', align: 'start' as const },
|
||||
{ title: props.statusHeader, key: 'status', align: 'start' as const },
|
||||
{ title: props.noteHeader, key: 'note', align: 'start' as const },
|
||||
{ title: props.createTimeHeader, key: 'createTime', align: 'start' as const },
|
||||
{ title: props.actionsHeader, key: 'actions', align: 'end' as const, sortable: false },
|
||||
])
|
||||
|
||||
const visibleHeaderKeys = ref<string[]>([])
|
||||
|
||||
watchEffect(() => {
|
||||
if (visibleHeaderKeys.value.length > 0) {
|
||||
return
|
||||
}
|
||||
visibleHeaderKeys.value = formattedHeaders.value.map((h) => String(h.key))
|
||||
})
|
||||
|
||||
const headerSettingsItems = computed(() =>
|
||||
formattedHeaders.value
|
||||
.filter((h) => h.key !== 'actions')
|
||||
.map((h) => ({ key: String(h.key), label: String(h.title) }))
|
||||
)
|
||||
|
||||
const filteredHeaders = computed(() =>
|
||||
formattedHeaders.value.filter((h) => visibleHeaderKeys.value.includes(String(h.key)))
|
||||
)
|
||||
|
||||
const itemsPerPageOptions = computed(() =>
|
||||
[10, 25, 50, 100, -1].map((value) => ({
|
||||
value,
|
||||
title: value === -1 ? props.itemsPerPageAllText : String(value),
|
||||
}))
|
||||
)
|
||||
</script>
|
||||
Reference in New Issue
Block a user