175 lines
5.9 KiB
Vue
175 lines
5.9 KiB
Vue
<script setup lang="ts">
|
|
/**
|
|
* 视频表单:分类/专辑多选/封面/播放地址/发布状态。
|
|
*/
|
|
import { computed, reactive, ref } from 'vue'
|
|
import { onLoad, onShow } from '@dcloudio/uni-app'
|
|
import { useToast } from '@wot-ui/ui'
|
|
import AppNavbar from '@/components/layout/AppNavbar.vue'
|
|
import AppPageShell from '@/components/layout/AppPageShell.vue'
|
|
import AdminMediaPicker from '../../components/AdminMediaPicker.vue'
|
|
import { useAdminGuard } from '../../composables/useAdminGuard'
|
|
import { adminCreate, adminGet, adminList, adminUpdate, normalizeAdminList, type VideoItem } from '@/api'
|
|
|
|
const toast = useToast()
|
|
const { guardAdmin } = useAdminGuard()
|
|
|
|
const mode = ref<'create' | 'edit'>('create')
|
|
const id = ref('')
|
|
const loading = ref(false)
|
|
const submitting = ref(false)
|
|
const categories = ref<{ id: number, name: string }[]>([])
|
|
const albums = ref<{ id: number, name: string }[]>([])
|
|
const selectedAlbums = ref<number[]>([])
|
|
|
|
const form = reactive({
|
|
title: '',
|
|
categoryId: '' as string | number,
|
|
videoUrl: '',
|
|
cover: '',
|
|
description: '',
|
|
isPublished: 1,
|
|
})
|
|
|
|
const pageTitle = computed(() => (mode.value === 'create' ? '新建视频' : '编辑视频'))
|
|
|
|
async function load() {
|
|
if (!guardAdmin()) return
|
|
loading.value = true
|
|
try {
|
|
categories.value = normalizeAdminList(await adminList('video-categories')) as { id: number, name: string }[]
|
|
albums.value = normalizeAdminList(await adminList('video-albums')) as { id: number, name: string }[]
|
|
if (mode.value === 'edit' && id.value) {
|
|
const data = await adminGet('videos', id.value) as VideoItem
|
|
form.title = String(data.title || '')
|
|
form.categoryId = data.categoryId || ''
|
|
form.videoUrl = String(data.videoUrl || data.url || '')
|
|
form.cover = String(data.cover || data.poster || '')
|
|
form.description = String(data.description || '')
|
|
form.isPublished = data.isPublished === 0 || data.isPublished === false ? 0 : 1
|
|
selectedAlbums.value = Array.isArray(data.albumIds) ? data.albumIds.map(Number) : []
|
|
}
|
|
}
|
|
catch (e: unknown) {
|
|
toast.error(e instanceof Error ? e.message : '加载失败')
|
|
}
|
|
finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onLoad((q) => {
|
|
mode.value = q?.mode === 'edit' ? 'edit' : 'create'
|
|
id.value = String(q?.id || '')
|
|
})
|
|
onShow(load)
|
|
|
|
function toggleAlbum(aid: number) {
|
|
const i = selectedAlbums.value.indexOf(aid)
|
|
if (i >= 0) selectedAlbums.value.splice(i, 1)
|
|
else selectedAlbums.value.push(aid)
|
|
}
|
|
|
|
async function onSubmit() {
|
|
if (!form.title.trim()) {
|
|
toast.show('请填写标题')
|
|
return
|
|
}
|
|
submitting.value = true
|
|
try {
|
|
const payload = {
|
|
title: form.title,
|
|
categoryId: form.categoryId ? Number(form.categoryId) : undefined,
|
|
videoUrl: form.videoUrl,
|
|
cover: form.cover,
|
|
poster: form.cover,
|
|
description: form.description,
|
|
isPublished: form.isPublished,
|
|
albumIds: selectedAlbums.value,
|
|
}
|
|
if (mode.value === 'edit' && id.value) await adminUpdate('videos', id.value, payload)
|
|
else await adminCreate('videos', payload)
|
|
toast.success('已保存')
|
|
setTimeout(() => uni.navigateBack(), 400)
|
|
}
|
|
catch (e: unknown) {
|
|
toast.error(e instanceof Error ? e.message : '保存失败')
|
|
}
|
|
finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
|
|
function catLabel() {
|
|
return categories.value.find(c => String(c.id) === String(form.categoryId))?.name || '选择分类'
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<AppPageShell admin>
|
|
<AppNavbar :title="pageTitle" show-back />
|
|
<view class="page-pad">
|
|
<view class="admin-card form-card">
|
|
<wd-input v-model="form.title" label="标题" clearable />
|
|
<wd-picker
|
|
:columns="[categories.map(c => ({ label: c.name, value: c.id }))]"
|
|
@confirm="({ value }: { value: unknown[] }) => { form.categoryId = (value?.[0] as { value?: number })?.value ?? value?.[0] as number }"
|
|
>
|
|
<view class="picker-row"><text>分类</text><text class="val">{{ catLabel() }}</text></view>
|
|
</wd-picker>
|
|
<AdminMediaPicker v-model="form.cover" label="封面" />
|
|
<AdminMediaPicker v-model="form.videoUrl" label="视频文件" media-type="video" />
|
|
<wd-input v-model="form.videoUrl" label="或填写视频 URL" clearable />
|
|
<wd-textarea v-model="form.description" label="描述" />
|
|
<view class="switch-row">
|
|
<text>发布</text>
|
|
<wd-switch :model-value="!!form.isPublished" @change="(v: boolean) => form.isPublished = v ? 1 : 0" />
|
|
</view>
|
|
<view class="section">
|
|
<text class="label text-art-muted">所属专辑</text>
|
|
<view class="tag-wrap">
|
|
<view
|
|
v-for="a in albums"
|
|
:key="a.id"
|
|
class="tag"
|
|
:class="{ on: selectedAlbums.includes(a.id) }"
|
|
@click="toggleAlbum(a.id)"
|
|
>
|
|
{{ a.name }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<wd-button
|
|
block
|
|
type="primary"
|
|
:loading="submitting"
|
|
custom-style="margin-top:24rpx;background:#d4b383;border-color:#d4b383;"
|
|
@click="onSubmit"
|
|
>
|
|
保存
|
|
</wd-button>
|
|
</view>
|
|
<wd-loading v-if="loading" />
|
|
</view>
|
|
</AppPageShell>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.page-pad { padding: 24rpx 32rpx 80rpx; }
|
|
.form-card { padding: 24rpx; }
|
|
.picker-row, .switch-row {
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
padding: 20rpx 0; color: rgb(var(--art-text));
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
}
|
|
.val { color: rgba(255, 255, 255, 0.6); font-size: 26rpx; }
|
|
.section { margin: 16rpx 0; }
|
|
.label { font-size: 24rpx; }
|
|
.tag-wrap { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 12rpx; }
|
|
.tag {
|
|
padding: 8rpx 18rpx; border-radius: 8rpx; font-size: 24rpx;
|
|
background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.65);
|
|
}
|
|
.tag.on { background: rgba(212, 179, 131, 0.25); color: rgb(var(--art-accent)); }
|
|
</style>
|