feat: 添加内容置顶功能,更新相关数据模型和前端视图
This commit is contained in:
@@ -61,64 +61,73 @@
|
||||
<!-- Info -->
|
||||
<div class="flex-1 min-w-0 flex flex-col justify-between">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs px-1.5 py-0.5 border rounded text-slate-500" v-if="item.genre">[{{ getGenreLabel(item.genre) }}]</span>
|
||||
<span class="text-xs px-1.5 py-0.5 border rounded text-slate-500" v-if="item.key">[{{ item.key }}]</span>
|
||||
<h3
|
||||
class="font-bold text-slate-900 text-lg truncate hover:text-primary-600 cursor-pointer transition-colors"
|
||||
@click="$router.push(`/creator/contents/${item.id}`)">
|
||||
{{ item.title }}</h3>
|
||||
</div>
|
||||
<!-- Status Badge -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="item.status === 'blocked'"
|
||||
class="text-red-500 text-xs flex items-center gap-1 cursor-help" title="已被封禁">
|
||||
<i class="pi pi-info-circle"></i> 封禁
|
||||
</span>
|
||||
<span class="px-2.5 py-1 rounded text-xs font-bold"
|
||||
:class="statusStyle(item.status).bg + ' ' + statusStyle(item.status).text">
|
||||
{{ statusStyle(item.status).label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6 text-sm text-slate-500">
|
||||
<span v-if="item.price > 0" class="text-red-600 font-bold">¥ {{ item.price.toFixed(2) }}</span>
|
||||
<span v-else class="text-green-600 font-bold">免费</span>
|
||||
|
||||
<span class="flex items-center gap-1" title="图片" v-if="item.image_count > 0">
|
||||
<i class="pi pi-image"></i> {{ item.image_count }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1" title="视频" v-if="item.video_count > 0">
|
||||
<i class="pi pi-video"></i> {{ item.video_count }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1" title="音频" v-if="item.audio_count > 0">
|
||||
<i class="pi pi-microphone"></i> {{ item.audio_count }}
|
||||
</span>
|
||||
|
||||
<span title="浏览量"><i class="pi pi-eye mr-1"></i> {{ item.views }}</span>
|
||||
<span title="点赞数"><i class="pi pi-thumbs-up mr-1"></i> {{ item.likes }}</span>
|
||||
<!-- Date field missing in DTO, using hardcoded or omitting -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-4 pt-3 border-t border-slate-50 mt-2">
|
||||
<button class="text-sm text-slate-500 hover:text-primary-600 font-medium cursor-pointer"
|
||||
@click="$router.push(`/creator/contents/${item.id}`)"><i class="pi pi-file-edit mr-1"></i>
|
||||
编辑</button>
|
||||
<button v-if="item.status === 'published'"
|
||||
class="text-sm text-slate-500 hover:text-orange-600 font-medium cursor-pointer"
|
||||
@click="handleStatusChange(item.id, 'unpublished')"><i
|
||||
class="pi pi-arrow-down mr-1"></i> 下架</button>
|
||||
<button v-if="item.status === 'unpublished'"
|
||||
class="text-sm text-slate-500 hover:text-green-600 font-medium cursor-pointer"
|
||||
@click="handleStatusChange(item.id, 'published')"><i
|
||||
class="pi pi-arrow-up mr-1"></i> 上架</button>
|
||||
<button class="text-sm text-slate-500 hover:text-red-600 font-medium ml-auto cursor-pointer"
|
||||
@click="handleDelete(item.id)"><i class="pi pi-trash mr-1"></i> 删除</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="item.is_pinned" class="bg-red-600 text-white text-xs px-1.5 py-0.5 rounded font-bold">置顶</span>
|
||||
<span class="text-xs px-1.5 py-0.5 border rounded text-slate-500" v-if="item.genre">[{{ getGenreLabel(item.genre) }}]</span>
|
||||
<span class="text-xs px-1.5 py-0.5 border rounded text-slate-500" v-if="item.key">[{{ item.key }}]</span>
|
||||
<h3
|
||||
class="font-bold text-slate-900 text-lg truncate hover:text-primary-600 cursor-pointer transition-colors"
|
||||
@click="$router.push(`/creator/contents/${item.id}`)">
|
||||
{{ item.title }}</h3>
|
||||
</div>
|
||||
<!-- Status Badge -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="item.status === 'blocked'"
|
||||
class="text-red-500 text-xs flex items-center gap-1 cursor-help" title="已被封禁">
|
||||
<i class="pi pi-info-circle"></i> 封禁
|
||||
</span>
|
||||
<span class="px-2.5 py-1 rounded text-xs font-bold"
|
||||
:class="statusStyle(item.status).bg + ' ' + statusStyle(item.status).text">
|
||||
{{ statusStyle(item.status).label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6 text-sm text-slate-500">
|
||||
<span v-if="item.price > 0" class="text-red-600 font-bold">¥ {{ item.price.toFixed(2) }}</span>
|
||||
<span v-else class="text-green-600 font-bold">免费</span>
|
||||
|
||||
<span class="flex items-center gap-1" title="图片" v-if="item.image_count > 0">
|
||||
<i class="pi pi-image"></i> {{ item.image_count }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1" title="视频" v-if="item.video_count > 0">
|
||||
<i class="pi pi-video"></i> {{ item.video_count }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1" title="音频" v-if="item.audio_count > 0">
|
||||
<i class="pi pi-microphone"></i> {{ item.audio_count }}
|
||||
</span>
|
||||
|
||||
<span title="浏览量"><i class="pi pi-eye mr-1"></i> {{ item.views }}</span>
|
||||
<span title="点赞数"><i class="pi pi-thumbs-up mr-1"></i> {{ item.likes }}</span>
|
||||
<!-- Date field missing in DTO, using hardcoded or omitting -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-4 pt-3 border-t border-slate-50 mt-2">
|
||||
<button class="text-sm text-slate-500 hover:text-primary-600 font-medium cursor-pointer" @click="$router.push(`/creator/contents/${item.id}`)"><i
|
||||
class="pi pi-file-edit mr-1"></i> 编辑</button>
|
||||
<button v-if="item.status === 'published'"
|
||||
class="text-sm text-slate-500 hover:text-orange-600 font-medium cursor-pointer"
|
||||
@click="handleStatusChange(item.id, 'unpublished')"><i
|
||||
class="pi pi-arrow-down mr-1"></i> 下架</button>
|
||||
<button v-if="item.status === 'unpublished'"
|
||||
class="text-sm text-slate-500 hover:text-green-600 font-medium cursor-pointer"
|
||||
@click="handleStatusChange(item.id, 'published')"><i
|
||||
class="pi pi-arrow-up mr-1"></i> 上架</button>
|
||||
<template v-if="item.status === 'published'">
|
||||
<button v-if="!item.is_pinned"
|
||||
class="text-sm text-slate-500 hover:text-blue-600 font-medium cursor-pointer"
|
||||
@click="handlePin(item.id, true)"><i
|
||||
class="pi pi-bookmark mr-1"></i> 置顶</button>
|
||||
<button v-else
|
||||
class="text-sm text-blue-600 font-medium cursor-pointer"
|
||||
@click="handlePin(item.id, false)"><i
|
||||
class="pi pi-bookmark-fill mr-1"></i> 取消置顶</button>
|
||||
</template> <button class="text-sm text-slate-500 hover:text-red-600 font-medium ml-auto cursor-pointer" @click="handleDelete(item.id)"><i
|
||||
class="pi pi-trash mr-1"></i> 删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,11 +138,14 @@
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import ConfirmDialog from 'primevue/confirmdialog';
|
||||
import { commonApi } from '../../api/common';
|
||||
import { creatorApi } from '../../api/creator';
|
||||
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
const contents = ref([]);
|
||||
const filterStatus = ref('all');
|
||||
const filterGenre = ref('all');
|
||||
@@ -206,24 +218,61 @@ const statusStyle = (status) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusChange = async (id, status) => {
|
||||
try {
|
||||
await creatorApi.updateContent(id, { status });
|
||||
toast.add({ severity: 'success', summary: '更新成功', life: 2000 });
|
||||
fetchContents();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.add({ severity: 'error', summary: '更新失败', detail: e.message, life: 3000 });
|
||||
}
|
||||
const handleStatusChange = (id, status) => {
|
||||
const action = status === 'published' ? '上架' : '下架';
|
||||
confirm.require({
|
||||
message: `确定要${action}该内容吗?`,
|
||||
header: '操作确认',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
acceptClass: status === 'unpublished' ? 'p-button-danger' : '',
|
||||
accept: async () => {
|
||||
try {
|
||||
await creatorApi.updateContent(id, { status });
|
||||
toast.add({ severity: 'success', summary: '更新成功', life: 2000 });
|
||||
fetchContents();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.add({ severity: 'error', summary: '更新失败', detail: e.message, life: 3000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = async (id) => {
|
||||
if (!confirm('确定要删除吗?')) return;
|
||||
try {
|
||||
await creatorApi.deleteContent(id);
|
||||
fetchContents();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
const handlePin = (id, isPinned) => {
|
||||
const action = isPinned ? '置顶' : '取消置顶';
|
||||
confirm.require({
|
||||
message: `确定要${action}该内容吗?`,
|
||||
header: '操作确认',
|
||||
icon: 'pi pi-info-circle',
|
||||
accept: async () => {
|
||||
try {
|
||||
await creatorApi.updateContent(id, { is_pinned: isPinned });
|
||||
toast.add({ severity: 'success', summary: isPinned ? '已置顶' : '已取消置顶', life: 2000 });
|
||||
fetchContents();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.add({ severity: 'error', summary: '操作失败', detail: e.message, life: 3000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (id) => {
|
||||
confirm.require({
|
||||
message: '确定要删除该内容吗?此操作不可恢复。',
|
||||
header: '删除确认',
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: async () => {
|
||||
try {
|
||||
await creatorApi.deleteContent(id);
|
||||
fetchContents();
|
||||
toast.add({ severity: 'success', summary: '删除成功', life: 2000 });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.add({ severity: 'error', summary: '删除失败', detail: e.message, life: 3000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user