feat: 新建、删除 PPT 模板,模板分类管理

This commit is contained in:
2025-01-21 17:09:45 +08:00
parent b602f5c00e
commit 87ba5a1685
2 changed files with 409 additions and 17 deletions

View File

@@ -731,7 +731,7 @@ const udpateBalance = (tag: ServiceTag, isActivate: boolean = false) => {
</div> </div>
</template> </template>
<UForm class="flex justify-between gap-4"> <div class="flex justify-between gap-4">
<UFormGroup <UFormGroup
label="到期时间" label="到期时间"
class="flex-1" class="flex-1"
@@ -771,7 +771,7 @@ const udpateBalance = (tag: ServiceTag, isActivate: boolean = false) => {
> >
<UInput v-model="userBalanceState.remain_count" /> <UInput v-model="userBalanceState.remain_count" />
</UFormGroup> </UFormGroup>
</UForm> </div>
<template #footer> <template #footer>
<div class="flex items-center justify-end gap-2"> <div class="flex items-center justify-end gap-2">

View File

@@ -1,7 +1,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import { number, object, string, type InferType } from 'yup'
import type { FormSubmitEvent } from '#ui/types'
import dayjs from 'dayjs'
const toast = useToast()
const loginState = useLoginState() const loginState = useLoginState()
const { data: pptCategories } = useAsyncData('pptCategories', () => const isCreateSlideOpen = ref(false)
const isCatSlideOpen = ref(false)
const { data: pptCategories, refresh: refreshPPTCategories } = useAsyncData(
'pptCategories',
() =>
useFetchWrapped< useFetchWrapped<
PagedDataRequest & AuthedRequest, PagedDataRequest & AuthedRequest,
BaseResponse<PagedData<PPTCategory>> BaseResponse<PagedData<PPTCategory>>
@@ -19,7 +29,7 @@ const pagination = reactive({
perpage: 18, perpage: 18,
}) })
const { data: pptTemplates } = useAsyncData( const { data: pptTemplates, refresh: refreshPptTemplates } = useAsyncData(
'pptTemplates', 'pptTemplates',
() => () =>
useFetchWrapped< useFetchWrapped<
@@ -41,6 +51,204 @@ const onDownloadClick = (ppt: PPTTemplate) => {
const { download } = useDownload(ppt.file_url, `${ppt.title}.pptx`) const { download } = useDownload(ppt.file_url, `${ppt.title}.pptx`)
download() download()
} }
const pptCreateState = reactive({
title: '',
description: '',
type: 0,
preview_url: '',
file_url: '',
})
const pptCreateSchema = object({
title: string().required('模板标题不能为空'),
description: string().required('模板描述不能为空'),
type: number().notOneOf([0], '无效分类').required('请选择模板分类'),
preview_url: string().required('请上传预览图'),
file_url: string().required('请上传 PPT 文件'),
})
type PPTCreateSchema = InferType<typeof pptCreateSchema>
const selectMenuOptions = computed(() => {
return pptCategories.value?.data.items.map((cat) => ({
label: cat.type,
value: cat.id,
}))
})
const onCreateSubmit = (event: FormSubmitEvent<PPTCreateSchema>) => {
useFetchWrapped<
{
title: string
description: string
type: number
preview_url: string
file_url: string
} & AuthedRequest,
BaseResponse<{ powerpoint_id: number }>
>('App.Digital_PowerPoint.Create', {
token: loginState.token!,
user_id: loginState.user.id,
title: event.data.title,
description: event.data.description,
type: event.data.type,
preview_url: event.data.preview_url,
file_url: event.data.file_url,
})
.then(async (res) => {
if (res.data.powerpoint_id) {
await refreshPPTCategories()
toast.add({
title: '创建成功',
description: '已加入模板库',
color: 'green',
icon: 'i-tabler-check',
})
isCreateSlideOpen.value = false
refreshPptTemplates()
Object.assign(pptCreateState, {
title: '',
description: '',
type: 0,
preview_url: '',
file_url: '',
})
}
})
.catch(() => {
toast.add({
title: '创建失败',
description: '请检查输入是否正确',
color: 'red',
icon: 'i-tabler-alert-triangle',
})
})
}
const onFileSelect = async (files: FileList, type: 'preview' | 'ppt') => {
const url = await useFileGo(files[0])
if (type === 'preview') {
pptCreateState.preview_url = url
} else {
pptCreateState.file_url = url
}
toast.add({
title: '上传成功',
description: `已上传 ${type === 'preview' ? '预览图' : 'PPT 文件'}`,
color: 'green',
icon: 'i-tabler-check',
})
}
const onDeletePPT = (ppt: PPTTemplate) => {
useFetchWrapped<
{ powerpoint_id: number } & AuthedRequest,
BaseResponse<{ code: number }>
>('App.Digital_PowerPoint.Delete', {
token: loginState.token!,
user_id: loginState.user.id,
powerpoint_id: ppt.id,
})
.then(async (res) => {
if (res.ret === 200 && res.data.code === 1) {
await refreshPptTemplates()
toast.add({
title: '删除成功',
description: '已删除模板',
color: 'green',
icon: 'i-tabler-check',
})
} else {
toast.add({
title: '删除失败',
description: res.msg || '未知错误',
color: 'red',
icon: 'i-tabler-alert-triangle',
})
}
})
.catch(() => {
toast.add({
title: '删除失败',
description: '未知错误',
color: 'red',
icon: 'i-tabler-alert-triangle',
})
})
}
const createCatInput = ref('')
const onCreateCat = () => {
if (createCatInput.value) {
useFetchWrapped<
{ type: string } & AuthedRequest,
BaseResponse<{ ppt_cat_id: number }>
>('App.Digital_PowerPointCat.Create', {
token: loginState.token!,
user_id: loginState.user.id,
type: createCatInput.value,
})
.then(async (res) => {
if (res.data.ppt_cat_id) {
await refreshPPTCategories()
toast.add({
title: '创建成功',
description: '已加入分类列表',
color: 'green',
icon: 'i-tabler-check',
})
createCatInput.value = ''
}
})
.catch(() => {
toast.add({
title: '创建失败',
description: '请检查输入是否正确',
color: 'red',
icon: 'i-tabler-alert-triangle',
})
})
}
}
const onDeleteCat = (cat: PPTCategory) => {
useFetchWrapped<
{ ppt_cat_id: number } & AuthedRequest,
BaseResponse<{ code: number }>
>('App.Digital_PowerPointCat.Delete', {
token: loginState.token!,
user_id: loginState.user.id,
ppt_cat_id: cat.id,
})
.then(async (res) => {
if (res.ret === 200 && res.data.code === 1) {
await refreshPPTCategories()
toast.add({
title: '删除成功',
description: '已删除分类',
color: 'green',
icon: 'i-tabler-check',
})
} else {
toast.add({
title: '删除失败',
description: res.msg || '未知错误',
color: 'red',
icon: 'i-tabler-alert-triangle',
})
}
})
.catch(() => {
toast.add({
title: '删除失败',
description: '请检查输入是否正确',
color: 'red',
icon: 'i-tabler-alert-triangle',
})
})
}
</script> </script>
<template> <template>
@@ -49,14 +257,32 @@ const onDownloadClick = (ppt: PPTTemplate) => {
<BubbleTitle <BubbleTitle
title="PPT 模板库" title="PPT 模板库"
subtitle="Slide Templates" subtitle="Slide Templates"
>
<template #action>
<UButton
v-if="loginState.user.auth_code === 2"
label="分类管理"
color="amber"
variant="soft"
icon="tabler:grid"
@click="isCatSlideOpen = true"
/> />
<UButton
v-if="loginState.user.auth_code === 2"
label="创建模板"
color="amber"
variant="soft"
icon="tabler:plus"
@click="isCreateSlideOpen = true"
/>
</template>
</BubbleTitle>
<GradientDivider /> <GradientDivider />
</div> </div>
<div class="p-4 pt-0"> <div class="p-4 pt-0">
<!-- cat selector --> <!-- cat selector -->
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<div <div
v-if="pptTemplates?.data.items.length !== 0"
v-for="cat in [ v-for="cat in [
{ id: 0, type: '全部' }, { id: 0, type: '全部' },
...(pptCategories?.data.items || []), ...(pptCategories?.data.items || []),
@@ -105,11 +331,20 @@ const onDownloadClick = (ppt: PPTTemplate) => {
> >
<div class="space-y-0.5"> <div class="space-y-0.5">
<h3 class="text-base font-bold text-white">{{ ppt.title }}</h3> <h3 class="text-base font-bold text-white">{{ ppt.title }}</h3>
<p class="text-xs font-medium text-neutral-400"> <p class="text-xs font-medium text-neutral-200">
{{ ppt.description }} {{ ppt.description }}
</p> </p>
</div> </div>
<div> <div class="flex items-center gap-2">
<!-- delete -->
<UButton
v-if="loginState.user.auth_code === 2"
size="sm"
color="red"
icon="tabler:trash"
variant="soft"
@click="onDeletePPT(ppt)"
/>
<UButton <UButton
label="下载" label="下载"
size="sm" size="sm"
@@ -133,6 +368,163 @@ const onDownloadClick = (ppt: PPTTemplate) => {
</div> </div>
</div> </div>
</div> </div>
<USlideover v-model="isCreateSlideOpen">
<UCard
:ui="{
body: { base: 'flex-1' },
ring: '',
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
}"
class="flex flex-col flex-1"
>
<template #header>
<UButton
class="flex absolute end-5 top-5 z-10"
color="gray"
icon="tabler:x"
padded
size="sm"
square
variant="ghost"
@click="isCreateSlideOpen = false"
/>
创建 PPT 模板
</template>
<UForm
class="space-y-4"
:schema="pptCreateSchema"
:state="pptCreateState"
@submit="onCreateSubmit"
>
<UFormGroup
label="模板标题"
name="title"
>
<UInput v-model="pptCreateState.title" />
</UFormGroup>
<UFormGroup
label="模板描述"
name="description"
>
<UTextarea v-model="pptCreateState.description" />
</UFormGroup>
<UFormGroup
label="模板分类"
name="type"
>
<USelectMenu
v-model="pptCreateState.type"
value-attribute="value"
option-attribute="label"
searchable
searchable-placeholder="搜索现有分类..."
:options="selectMenuOptions"
/>
</UFormGroup>
<UFormGroup
label="预览图"
name="preview_url"
>
<UniFileDnD
@change="onFileSelect($event, 'preview')"
accept="image/png,image/jpeg"
/>
</UFormGroup>
<UFormGroup
label="PPT 文件"
name="file_url"
>
<UniFileDnD
@change="onFileSelect($event, 'ppt')"
accept="application/vnd.openxmlformats-officedocument.presentationml.presentation"
/>
</UFormGroup>
<div class="flex justify-end">
<UButton
label="创建"
color="primary"
type="submit"
/>
</div>
</UForm>
</UCard>
</USlideover>
<USlideover v-model="isCatSlideOpen">
<UCard
:ui="{
body: { base: 'flex-1' },
ring: '',
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
}"
class="flex flex-col flex-1"
>
<template #header>
<UButton
class="flex absolute end-5 top-5 z-10"
color="gray"
icon="tabler:x"
padded
size="sm"
square
variant="ghost"
@click="isCatSlideOpen = false"
/>
PPT 模板分类管理
</template>
<div class="space-y-4">
<UFormGroup label="创建分类">
<UButtonGroup
orientation="horizontal"
class="w-full"
size="lg"
>
<UInput
class="flex-1"
placeholder="分类名称"
v-model="createCatInput"
/>
<UButton
icon="tabler:plus"
color="gray"
label="创建"
:disabled="!createCatInput"
@click="onCreateCat"
/>
</UButtonGroup>
</UFormGroup>
<div class="border dark:border-neutral-700 rounded-md">
<UTable
:columns="[
{ key: 'id', label: 'ID' },
{ key: 'type', label: '分类' },
{ key: 'create_time', label: '创建时间' },
{ key: 'actions' },
]"
:rows="pptCategories?.data.items"
>
<template #create_time-data="{ row }">
{{
dayjs(row.create_time * 1000).format('YYYY-MM-DD HH:mm:ss')
}}
</template>
<template #actions-data="{ row }">
<UButton
color="red"
icon="tabler:trash"
size="xs"
variant="soft"
@click="onDeleteCat(row)"
/>
</template>
</UTable>
</div>
</div>
</UCard>
</USlideover>
</div> </div>
</template> </template>