feat(PageHeader): 新增 PageHeader 组件并应用于多个配置页面

新增了 PageHeader 通用组件,用于统一页面标题和操作按钮区域的布局。
该组件包含 title 属性以及 buttons 和 alerts 两个具名插槽,便于复用和维护。
已在以下页面中集成使用:
- 图片管理页(ImageConfig.vue)
- 音乐管理页(MusicConfig.vue)
- 人员管理页(PersonAll/index.vue)
- 中奖者管理页(PersonAlready.vue)
- 奖品管理页(PrizeConfig.vue)
This commit is contained in:
log1997
2025-12-02 16:07:48 +08:00
parent 5c8347ff62
commit d776017306
7 changed files with 163 additions and 111 deletions

1
src/components.d.ts vendored
View File

@@ -14,6 +14,7 @@ declare module 'vue' {
HelloWorld: typeof import('./components/HelloWorld.vue')['default'] HelloWorld: typeof import('./components/HelloWorld.vue')['default']
ImageSync: typeof import('./components/ImageSync/index.vue')['default'] ImageSync: typeof import('./components/ImageSync/index.vue')['default']
Loading: typeof import('./components/Loading/index.vue')['default'] Loading: typeof import('./components/Loading/index.vue')['default']
PageHeader: typeof import('./components/PageHeader/index.vue')['default']
PlayMusic: typeof import('./components/PlayMusic/index.vue')['default'] PlayMusic: typeof import('./components/PlayMusic/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

View File

@@ -0,0 +1,24 @@
<script setup lang='ts'>
const props = defineProps<{
title: string
}>()
</script>
<template>
<header>
<h1 class="text-lg leading-10">
{{ props.title }}
</h1>
<div class="button-group my-4">
<slot name="buttons" />
</div>
<div class="divider mt-0" />
<div>
<slot name="alerts" />
</div>
</header>
</template>
<style scoped>
</style>

View File

@@ -1,12 +1,13 @@
<script setup lang='ts'> <script setup lang='ts'>
import type { IImage } from '@/types/storeType' import type { IImage } from '@/types/storeType'
import ImageSync from '@/components/ImageSync/index.vue'
import useStore from '@/store'
import { readFileData } from '@/utils/file'
import localforage from 'localforage' import localforage from 'localforage'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { onMounted, ref, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import ImageSync from '@/components/ImageSync/index.vue'
import PageHeader from '@/components/PageHeader/index.vue'
import useStore from '@/store'
import { readFileData } from '@/utils/file'
const { t } = useI18n() const { t } = useI18n()
const globalConfig = useStore().globalConfig const globalConfig = useStore().globalConfig
@@ -81,6 +82,8 @@ watch(() => imgUploadToast.value, (val) => {
</div> </div>
<div> <div>
<PageHeader title="图片管理">
<template #buttons>
<div class=""> <div class="">
<label for="explore"> <label for="explore">
<input <input
@@ -90,6 +93,9 @@ watch(() => imgUploadToast.value, (val) => {
<span class="btn btn-primary btn-sm">{{ t('button.upload') }}</span> <span class="btn btn-primary btn-sm">{{ t('button.upload') }}</span>
</label> </label>
</div> </div>
</template>
</PageHeader>
<ul class="p-0"> <ul class="p-0">
<li v-for="item in localImageList" :key="item.id" class="mb-3"> <li v-for="item in localImageList" :key="item.id" class="mb-3">
<div class="flex items-center gap-8"> <div class="flex items-center gap-8">

View File

@@ -1,12 +1,12 @@
<script setup lang='ts'> <script setup lang='ts'>
import type { IMusic } from '@/types/storeType' import type { IMusic } from '@/types/storeType'
import useStore from '@/store'
import { readFileData } from '@/utils/file'
import localforage from 'localforage' import localforage from 'localforage'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PageHeader from '@/components/PageHeader/index.vue'
import useStore from '@/store'
import { readFileData } from '@/utils/file'
const { t } = useI18n() const { t } = useI18n()
const audioUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片 const audioUploadToast = ref(0) // 0是不显示1是成功2是失败,3是不是图片
@@ -74,6 +74,8 @@ onMounted(() => {
<template> <template>
<div> <div>
<PageHeader title="音乐管理">
<template #buttons>
<div class="flex gap-3"> <div class="flex gap-3">
<button class="btn btn-primary btn-sm" @click="resetMusic"> <button class="btn btn-primary btn-sm" @click="resetMusic">
{{ t('button.reset') }} {{ t('button.reset') }}
@@ -89,6 +91,9 @@ onMounted(() => {
{{ t('button.allDelete') }} {{ t('button.allDelete') }}
</button> </button>
</div> </div>
</template>
</PageHeader>
<div> <div>
<ul class="p-0"> <ul class="p-0">
<li v-for="item in localMusicListValue" :key="item.id" class="flex items-center gap-6 pb-2 mb-3 divide-y"> <li v-for="item in localMusicListValue" :key="item.id" class="flex items-center gap-6 pb-2 mb-3 divide-y">

View File

@@ -4,6 +4,7 @@ import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import DaiysuiTable from '@/components/DaiysuiTable/index.vue' import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
import CustomDialog from '@/components/Dialog/index.vue' import CustomDialog from '@/components/Dialog/index.vue'
import PageHeader from '@/components/PageHeader/index.vue'
import { useViewModel } from './useViewModel' import { useViewModel } from './useViewModel'
const resetDataDialogRef = ref() const resetDataDialogRef = ref()
@@ -29,7 +30,8 @@ const limitType = '.xlsx,.xls'
/> />
<div class="min-w-1000px"> <div class="min-w-1000px">
<h2>{{ t('viewTitle.personManagement') }}</h2> <PageHeader :title="t('viewTitle.personManagement')">
<template #buttons>
<div class="flex gap-3"> <div class="flex gap-3">
<button class="btn btn-error btn-sm" @click="delAllDataDialogRef.showDialog()"> <button class="btn btn-error btn-sm" @click="delAllDataDialogRef.showDialog()">
{{ t('button.allDelete') }} {{ t('button.allDelete') }}
@@ -42,7 +44,6 @@ const limitType = '.xlsx,.xls'
</div> </div>
<div class=""> <div class="">
<label for="explore"> <label for="explore">
<div class="tooltip tooltip-bottom" :data-tip="t('tooltip.uploadExcelTip')"> <div class="tooltip tooltip-bottom" :data-tip="t('tooltip.uploadExcelTip')">
<input <input
id="explore" ref="exportInputFileRef" type="file" class="" style="display: none" id="explore" ref="exportInputFileRef" type="file" class="" style="display: none"
@@ -66,6 +67,9 @@ const limitType = '.xlsx,.xls'
<span>{{ allPersonList.length }}</span> <span>{{ allPersonList.length }}</span>
</div> </div>
</div> </div>
</template>
</PageHeader>
<DaiysuiTable :table-columns="tableColumns" :data="allPersonList" /> <DaiysuiTable :table-columns="tableColumns" :data="allPersonList" />
</div> </div>
</template> </template>

View File

@@ -1,12 +1,13 @@
<!-- eslint-disable vue/no-parsing-error --> <!-- eslint-disable vue/no-parsing-error -->
<script setup lang='ts'> <script setup lang='ts'>
import type { IPersonConfig } from '@/types/storeType' import type { IPersonConfig } from '@/types/storeType'
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
import i18n from '@/locales/i18n'
import useStore from '@/store'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import DaiysuiTable from '@/components/DaiysuiTable/index.vue'
import PageHeader from '@/components/PageHeader/index.vue'
import i18n from '@/locales/i18n'
import useStore from '@/store'
const { t } = useI18n() const { t } = useI18n()
const personConfig = useStore().personConfig const personConfig = useStore().personConfig
@@ -39,8 +40,8 @@ const tableColumnsList = [
label: i18n.global.t('data.avatar'), label: i18n.global.t('data.avatar'),
props: 'avatar', props: 'avatar',
formatValue(row: any) { formatValue(row: any) {
return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-'; return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-'
} },
}, },
{ {
label: i18n.global.t('data.department'), label: i18n.global.t('data.department'),
@@ -82,8 +83,8 @@ const tableColumnsDetail = [
label: i18n.global.t('data.avatar'), label: i18n.global.t('data.avatar'),
props: 'avatar', props: 'avatar',
formatValue(row: any) { formatValue(row: any) {
return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-'; return row.avatar ? `<img src="${row.avatar}" alt="avatar" style="width: 50px; height: 50px;"/>` : '-'
} },
}, },
{ {
label: i18n.global.t('data.department'), label: i18n.global.t('data.department'),
@@ -121,7 +122,8 @@ const tableColumnsDetail = [
<template> <template>
<div class="overflow-y-auto"> <div class="overflow-y-auto">
<h2>{{ t('viewTitle.winnerManagement') }}</h2> <PageHeader :title="t('viewTitle.winnerManagement')">
<template #buttons>
<div class="flex items-center justify-start gap-10"> <div class="flex items-center justify-start gap-10">
<div> <div>
<span>{{ t('table.luckyPeopleNumber') }}</span> <span>{{ t('table.luckyPeopleNumber') }}</span>
@@ -136,6 +138,9 @@ const tableColumnsDetail = [
</div> </div>
</div> </div>
</div> </div>
</template>
</PageHeader>
<DaiysuiTable v-if="!isDetail" :table-columns="tableColumnsList" :data="alreadyPersonList" /> <DaiysuiTable v-if="!isDetail" :table-columns="tableColumnsList" :data="alreadyPersonList" />
<DaiysuiTable v-if="isDetail" :table-columns="tableColumnsDetail" :data="alreadyPersonDetail" /> <DaiysuiTable v-if="isDetail" :table-columns="tableColumnsDetail" :data="alreadyPersonDetail" />

View File

@@ -1,12 +1,13 @@
<script setup lang='ts'> <script setup lang='ts'>
import type { IPrizeConfig } from '@/types/storeType' import type { IPrizeConfig } from '@/types/storeType'
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
import i18n from '@/locales/i18n'
import useStore from '@/store'
import localforage from 'localforage' import localforage from 'localforage'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { onMounted, ref, watch } from 'vue' import { onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import EditSeparateDialog from '@/components/NumberSeparate/EditSeparateDialog.vue'
import PageHeader from '@/components/PageHeader/index.vue'
import i18n from '@/locales/i18n'
import useStore from '@/store'
const { t } = useI18n() const { t } = useI18n()
const imageDbStore = localforage.createInstance({ const imageDbStore = localforage.createInstance({
@@ -149,7 +150,8 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
<template> <template>
<div> <div>
<h2>{{ t('viewTitle.prizeManagement') }}</h2> <PageHeader :title="t('viewTitle.prizeManagement')">
<template #buttons>
<div class="flex w-full gap-3"> <div class="flex w-full gap-3">
<button class="btn btn-info btn-sm" @click="addPrize"> <button class="btn btn-info btn-sm" @click="addPrize">
{{ t('button.add') }} {{ t('button.add') }}
@@ -161,6 +163,8 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
{{ t('button.allDelete') }} {{ t('button.allDelete') }}
</button> </button>
</div> </div>
</template>
<template #alerts>
<div role="alert" class="w-full my-4 alert alert-info"> <div role="alert" class="w-full my-4 alert alert-info">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-6 h-6 stroke-current shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-6 h-6 stroke-current shrink-0">
<path <path
@@ -170,6 +174,9 @@ watch(() => prizeList.value, (val: IPrizeConfig[]) => {
</svg> </svg>
<span>{{ t('dialog.tipResetPrize') }}</span> <span>{{ t('dialog.tipResetPrize') }}</span>
</div> </div>
</template>
</PageHeader>
<ul class="p-0 m-0"> <ul class="p-0 m-0">
<li <li
v-for="item in prizeList" :key="item.id" class="flex gap-10" v-for="item in prizeList" :key="item.id" class="flex gap-10"