feat: add SingleRecordMnt component for student record maintenance with search, add, edit, view, and delete functionalities

This commit is contained in:
skytek_xinliang
2026-03-26 11:24:37 +08:00
parent 507afcc99c
commit 069141794e
116 changed files with 15247 additions and 107 deletions
+161
View File
@@ -0,0 +1,161 @@
<template>
<v-row align="center" class="pa-4" no-gutters v-bind="$attrs">
<span v-if="title">{{ title }}</span>
<v-spacer></v-spacer>
<v-btn
v-if="showCreate"
class="mr-4"
color="primary"
prepend-icon="mdi-plus"
@click="$emit('create')"
>
{{ createBtnText }}
</v-btn>
<v-tooltip :disabled="!searchToggleTooltipText" location="top" :text="searchToggleTooltipText">
<template #activator="{ props }">
<v-btn
v-if="showSearchToggle"
v-bind="props"
density="comfortable"
icon
variant="text"
@click="$emit('toggle-search')"
>
<v-icon :color="searchVisible ? 'primary-variant' : undefined"> mdi-magnify </v-icon>
</v-btn>
</template>
</v-tooltip>
<v-tooltip :disabled="!refreshTooltipText" location="top" :text="refreshTooltipText">
<template #activator="{ props }">
<v-btn v-bind="props" density="comfortable" icon variant="text" @click="$emit('refresh')">
<v-icon>mdi-refresh</v-icon>
</v-btn>
</template>
</v-tooltip>
<v-menu v-if="settingsItems && settingsItems.length > 0">
<template #activator="{ props: menuProps }">
<v-tooltip :disabled="!settingsTooltipText" location="top" :text="settingsTooltipText">
<template #activator="{ props: tooltipProps }">
<v-btn
v-bind="{ ...menuProps, ...tooltipProps }"
density="comfortable"
icon
variant="text"
@click="$emit('settings')"
>
<v-icon>mdi-cog</v-icon>
</v-btn>
</template>
</v-tooltip>
</template>
<v-list density="compact">
<v-list-item class="py-0">
<v-checkbox
color="primary"
density="compact"
:disabled="selectAllChecked"
hide-details
:indeterminate="selectAllIndeterminate"
label="全選"
:model-value="selectAllChecked"
@update:model-value="toggleSelectAll"
/>
</v-list-item>
<v-list-item v-for="item in settingsItems" :key="item.key" class="py-0">
<v-checkbox
color="primary"
density="compact"
hide-details
:label="item.label"
:model-value="settingsSelectedKeys"
:value="item.key"
@update:model-value="updateSettingsSelectedKeys"
/>
</v-list-item>
</v-list>
</v-menu>
</v-row>
</template>
<script setup lang="ts">
import { computed, toRefs } from 'vue'
interface SettingsItem {
key: string
label: string
}
interface Props {
title?: string
createBtnText?: string
showCreate?: boolean
showSearchToggle?: boolean
searchVisible?: boolean
searchToggleTooltipText?: string
refreshTooltipText?: string
settingsTooltipText?: string
settingsItems?: SettingsItem[]
settingsSelectedKeys?: string[]
}
const props = withDefaults(defineProps<Props>(), {
createBtnText: '新增',
showCreate: true,
showSearchToggle: false,
searchVisible: true,
searchToggleTooltipText: '顯示/隱藏搜尋條件',
refreshTooltipText: '更新',
settingsTooltipText: '欄位設定',
settingsItems: () => [],
settingsSelectedKeys: () => [],
})
const { settingsItems, settingsSelectedKeys } = toRefs(props)
const emit = defineEmits([
'create',
'refresh',
'settings',
'toggle-search',
'update:settingsSelectedKeys',
])
const allSettingsKeys = computed(() => settingsItems.value.map((i) => i.key))
const selectAllChecked = computed(() => {
if (allSettingsKeys.value.length === 0) {
return false
}
return allSettingsKeys.value.every((k) => settingsSelectedKeys.value.includes(k))
})
const selectAllIndeterminate = computed(() => {
if (allSettingsKeys.value.length === 0) {
return false
}
const selectedCount = allSettingsKeys.value.filter((k) =>
settingsSelectedKeys.value.includes(k)
).length
return selectedCount > 0 && selectedCount < allSettingsKeys.value.length
})
function toggleSelectAll (checked: unknown) {
const current = Array.isArray(settingsSelectedKeys.value) ? settingsSelectedKeys.value : []
const nonSettingsKeys = current.filter((k) => !allSettingsKeys.value.includes(k))
emit(
'update:settingsSelectedKeys',
checked ? [...nonSettingsKeys, ...allSettingsKeys.value] : nonSettingsKeys
)
}
function updateSettingsSelectedKeys (value: unknown) {
emit('update:settingsSelectedKeys', Array.isArray(value) ? value : [])
}
</script>