feat(portal): enhance creator dashboard and order management with new layout and features
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="fixed top-0 w-full z-50 bg-white border-b border-slate-200 h-16">
|
<nav class="fixed top-0 w-full z-50 bg-white border-b border-slate-200 h-16">
|
||||||
<div class="mx-auto max-w-screen-xl px-4 sm:px-6 lg:px-8 h-full flex items-center justify-between">
|
<div class="mx-auto max-w-screen-xl h-full flex items-center justify-between">
|
||||||
<!-- Left: Logo -->
|
<!-- Left: Logo -->
|
||||||
<router-link to="/" class="flex items-center gap-2">
|
<router-link to="/" class="flex items-center gap-2">
|
||||||
<div class="w-8 h-8 bg-primary-600 rounded-lg flex items-center justify-center text-white font-bold text-xl">Q</div>
|
<div class="w-8 h-8 bg-primary-600 rounded-lg flex items-center justify-center text-white font-bold text-xl">Q</div>
|
||||||
|
|||||||
81
frontend/portal/src/layout/LayoutCreator.vue
Normal file
81
frontend/portal/src/layout/LayoutCreator.vue
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen flex flex-col bg-slate-50">
|
||||||
|
<TopNavbar />
|
||||||
|
<main class="flex-grow pt-16">
|
||||||
|
<div class="mx-auto max-w-screen-xl py-8 flex gap-8">
|
||||||
|
<!-- Creator Sidebar (Dark Theme) -->
|
||||||
|
<aside class="w-[260px] flex-shrink-0 hidden lg:block">
|
||||||
|
<div
|
||||||
|
class="bg-slate-900 rounded-2xl shadow-sm overflow-hidden sticky top-24 text-slate-300 min-h-[600px] flex flex-col">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="p-6 border-b border-slate-800">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
class="w-10 h-10 bg-gradient-to-br from-primary-500 to-primary-700 rounded-lg flex items-center justify-center text-white font-bold text-lg shadow-lg">
|
||||||
|
<i class="pi pi-palette"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-white leading-tight">创作者中心</div>
|
||||||
|
<div class="text-xs text-slate-500 mt-1">Creator Studio</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Menus -->
|
||||||
|
<nav class="p-4 space-y-1 flex-1">
|
||||||
|
<router-link to="/creator"
|
||||||
|
exact-active-class="bg-primary-600 text-white shadow-md shadow-primary-900/20"
|
||||||
|
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 hover:text-white transition-all group">
|
||||||
|
<i class="pi pi-th-large text-lg group-hover:scale-110 transition-transform"></i>
|
||||||
|
<span class="font-medium">管理概览</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="px-4 py-2 text-xs font-bold text-slate-500 uppercase tracking-wider mt-4">内容与交易</div>
|
||||||
|
|
||||||
|
<router-link to="/creator/contents"
|
||||||
|
active-class="bg-primary-600 text-white shadow-md shadow-primary-900/20"
|
||||||
|
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 hover:text-white transition-all group">
|
||||||
|
<i class="pi pi-file-edit text-lg group-hover:scale-110 transition-transform"></i>
|
||||||
|
<span class="font-medium">内容管理</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link to="/creator/orders"
|
||||||
|
active-class="bg-primary-600 text-white shadow-md shadow-primary-900/20"
|
||||||
|
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 hover:text-white transition-all group">
|
||||||
|
<i class="pi pi-shopping-cart text-lg group-hover:scale-110 transition-transform"></i>
|
||||||
|
<span class="font-medium">订单管理</span>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="px-4 py-2 text-xs font-bold text-slate-500 uppercase tracking-wider mt-4">配置</div>
|
||||||
|
|
||||||
|
<router-link to="/creator/settings"
|
||||||
|
active-class="bg-primary-600 text-white shadow-md shadow-primary-900/20"
|
||||||
|
class="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-slate-800 hover:text-white transition-all group">
|
||||||
|
<i class="pi pi-cog text-lg group-hover:scale-110 transition-transform"></i>
|
||||||
|
<span class="font-medium">频道设置</span>
|
||||||
|
</router-link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Footer Link -->
|
||||||
|
<div class="p-4 border-t border-slate-800">
|
||||||
|
<router-link to="/t/1"
|
||||||
|
class="flex items-center gap-2 px-4 py-2 text-sm text-slate-400 hover:text-white transition-colors">
|
||||||
|
<i class="pi pi-external-link"></i> 预览我的主页
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="flex-grow min-w-0">
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<AppFooter />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import AppFooter from '../components/AppFooter.vue';
|
||||||
|
import TopNavbar from '../components/TopNavbar.vue';
|
||||||
|
</script>
|
||||||
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||||||
import LayoutMain from '../layout/LayoutMain.vue'
|
import LayoutMain from '../layout/LayoutMain.vue'
|
||||||
import LayoutAuth from '../layout/LayoutAuth.vue'
|
import LayoutAuth from '../layout/LayoutAuth.vue'
|
||||||
import LayoutUser from '../layout/LayoutUser.vue'
|
import LayoutUser from '../layout/LayoutUser.vue'
|
||||||
|
import LayoutCreator from '../layout/LayoutCreator.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -39,6 +40,11 @@ const router = createRouter({
|
|||||||
path: 'creator/apply',
|
path: 'creator/apply',
|
||||||
name: 'creator-apply',
|
name: 'creator-apply',
|
||||||
component: () => import('../views/creator/ApplyView.vue')
|
component: () => import('../views/creator/ApplyView.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'creator/contents/new',
|
||||||
|
name: 'creator-content-new',
|
||||||
|
component: () => import('../views/creator/ContentsEditView.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -75,33 +81,33 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: 'wallet',
|
path: 'wallet',
|
||||||
name: 'user-wallet',
|
name: 'user-wallet',
|
||||||
component: () => import('../views/user/WalletView.vue') // Placeholder
|
component: () => import('../views/user/WalletView.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'library',
|
path: 'library',
|
||||||
name: 'user-library',
|
name: 'user-library',
|
||||||
component: () => import('../views/user/LibraryView.vue') // Placeholder
|
component: () => import('../views/user/LibraryView.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'notifications',
|
path: 'notifications',
|
||||||
name: 'user-notifications',
|
name: 'user-notifications',
|
||||||
component: () => import('../views/user/NotificationsView.vue') // Placeholder
|
component: () => import('../views/user/NotificationsView.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'profile',
|
path: 'profile',
|
||||||
name: 'user-profile',
|
name: 'user-profile',
|
||||||
component: () => import('../views/user/ProfileView.vue') // Placeholder
|
component: () => import('../views/user/ProfileView.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'security',
|
path: 'security',
|
||||||
name: 'user-security',
|
name: 'user-security',
|
||||||
component: () => import('../views/user/SecurityView.vue') // Placeholder
|
component: () => import('../views/user/SecurityView.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/creator',
|
path: '/creator',
|
||||||
component: LayoutUser, // Initially use LayoutUser, later maybe specialized LayoutCreator
|
component: LayoutCreator,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -127,7 +133,7 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/checkout',
|
path: '/checkout',
|
||||||
component: LayoutMain, // Or a simplified checkout layout
|
component: LayoutMain,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -163,4 +169,4 @@ const router = createRouter({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,161 +1,178 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-8">
|
<div>
|
||||||
<div class="flex items-center justify-between mb-8">
|
<div class="flex items-center justify-between mb-8">
|
||||||
<h1 class="text-2xl font-bold text-slate-900">内容管理</h1>
|
<h1 class="text-2xl font-bold text-slate-900">内容管理</h1>
|
||||||
<router-link to="/creator/contents/new" class="px-6 py-2.5 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200 cursor-pointer active:scale-95 flex items-center gap-2">
|
<router-link to="/creator/contents/new"
|
||||||
<i class="pi pi-plus"></i> 发布新内容
|
class="px-6 py-2.5 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200 cursor-pointer active:scale-95 flex items-center gap-2">
|
||||||
</router-link>
|
<i class="pi pi-plus"></i> 发布新内容
|
||||||
</div>
|
</router-link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-4 mb-6 flex flex-wrap gap-4 items-center">
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-4 mb-6 flex flex-wrap gap-4 items-center">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-sm font-bold text-slate-500">状态:</span>
|
<span class="text-sm font-bold text-slate-500">状态:</span>
|
||||||
<select v-model="filterStatus" class="h-9 px-3 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none bg-white cursor-pointer">
|
<select v-model="filterStatus"
|
||||||
<option value="all">全部</option>
|
class="h-9 px-3 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none bg-white cursor-pointer">
|
||||||
<option value="published">已发布</option>
|
<option value="all">全部</option>
|
||||||
<option value="audit">审核中</option>
|
<option value="published">已发布</option>
|
||||||
<option value="rejected">已驳回</option>
|
<option value="audit">审核中</option>
|
||||||
<option value="draft">草稿箱</option>
|
<option value="rejected">已驳回</option>
|
||||||
</select>
|
<option value="draft">草稿箱</option>
|
||||||
</div>
|
</select>
|
||||||
<div class="flex items-center gap-2">
|
</div>
|
||||||
<span class="text-sm font-bold text-slate-500">曲种:</span>
|
<div class="flex items-center gap-2">
|
||||||
<select v-model="filterGenre" class="h-9 px-3 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none bg-white cursor-pointer">
|
<span class="text-sm font-bold text-slate-500">曲种:</span>
|
||||||
<option value="all">全部</option>
|
<select v-model="filterGenre"
|
||||||
<option value="京剧">京剧</option>
|
class="h-9 px-3 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none bg-white cursor-pointer">
|
||||||
<option value="昆曲">昆曲</option>
|
<option value="all">全部</option>
|
||||||
<option value="越剧">越剧</option>
|
<option value="京剧">京剧</option>
|
||||||
</select>
|
<option value="昆曲">昆曲</option>
|
||||||
</div>
|
<option value="越剧">越剧</option>
|
||||||
<div class="ml-auto relative">
|
</select>
|
||||||
<i class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
</div>
|
||||||
<input type="text" placeholder="搜索标题..." class="h-9 pl-9 pr-4 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none w-48 transition-all focus:w-64">
|
<div class="ml-auto relative">
|
||||||
</div>
|
<i class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||||
</div>
|
<input type="text" placeholder="搜索标题..."
|
||||||
|
class="h-9 pl-9 pr-4 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none w-48 transition-all focus:w-64">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Content List -->
|
<!-- Content List -->
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div v-for="item in filteredList" :key="item.id" class="bg-white rounded-xl shadow-sm border border-slate-100 p-5 flex gap-6 hover:shadow-md transition-shadow group relative">
|
<div v-for="item in filteredList" :key="item.id"
|
||||||
|
class="bg-white rounded-xl shadow-sm border border-slate-100 p-5 flex gap-6 hover:shadow-md transition-shadow group relative">
|
||||||
<!-- Cover -->
|
|
||||||
<div class="w-40 h-[90px] bg-slate-100 rounded-lg flex-shrink-0 overflow-hidden relative">
|
|
||||||
<img :src="item.cover" class="w-full h-full object-cover">
|
|
||||||
<div class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
|
||||||
<router-link :to="`/creator/contents/new`" class="text-white text-xs font-bold border border-white px-3 py-1 rounded hover:bg-white hover:text-black transition-colors">编辑</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Info -->
|
<!-- Cover -->
|
||||||
<div class="flex-1 min-w-0 flex flex-col justify-between">
|
<div class="w-40 h-[90px] bg-slate-100 rounded-lg flex-shrink-0 overflow-hidden relative">
|
||||||
<div>
|
<img :src="item.cover" class="w-full h-full object-cover">
|
||||||
<div class="flex items-center justify-between mb-2">
|
<div
|
||||||
<div class="flex items-center gap-2">
|
class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
<span class="text-xs px-1.5 py-0.5 border rounded text-slate-500">[{{ item.genre }}]</span>
|
<router-link :to="`/creator/contents/new`"
|
||||||
<h3 class="font-bold text-slate-900 text-lg truncate hover:text-primary-600 cursor-pointer transition-colors">{{ item.title }}</h3>
|
class="text-white text-xs font-bold border border-white px-3 py-1 rounded hover:bg-white hover:text-black transition-colors">编辑</router-link>
|
||||||
</div>
|
</div>
|
||||||
<!-- Status Badge -->
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<span v-if="item.status === 'rejected'" class="text-red-500 text-xs flex items-center gap-1 cursor-help" title="点击查看原因">
|
|
||||||
<i class="pi pi-info-circle"></i> {{ item.rejectReason }}
|
|
||||||
</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 }}</span>
|
|
||||||
<span v-else class="text-green-600 font-bold">免费</span>
|
|
||||||
<span><i class="pi pi-eye mr-1"></i> {{ item.views }}</span>
|
|
||||||
<span><i class="pi pi-thumbs-up mr-1"></i> {{ item.likes }}</span>
|
|
||||||
<span>{{ item.date }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Info -->
|
||||||
<div class="flex items-center gap-4 pt-3 border-t border-slate-50 mt-2">
|
<div class="flex-1 min-w-0 flex flex-col justify-between">
|
||||||
<button class="text-sm text-slate-500 hover:text-primary-600 font-medium cursor-pointer"><i class="pi pi-file-edit mr-1"></i> 编辑</button>
|
<div>
|
||||||
<button v-if="item.status === 'published'" class="text-sm text-slate-500 hover:text-orange-600 font-medium cursor-pointer"><i class="pi pi-arrow-down mr-1"></i> 下架</button>
|
<div class="flex items-center justify-between mb-2">
|
||||||
<button v-if="item.status === 'offline'" class="text-sm text-slate-500 hover:text-green-600 font-medium cursor-pointer"><i class="pi pi-arrow-up mr-1"></i> 上架</button>
|
<div class="flex items-center gap-2">
|
||||||
<button class="text-sm text-slate-500 hover:text-red-600 font-medium ml-auto cursor-pointer"><i class="pi pi-trash mr-1"></i> 删除</button>
|
<span class="text-xs px-1.5 py-0.5 border rounded text-slate-500">[{{ item.genre }}]</span>
|
||||||
</div>
|
<h3
|
||||||
</div>
|
class="font-bold text-slate-900 text-lg truncate hover:text-primary-600 cursor-pointer transition-colors">
|
||||||
</div>
|
{{ item.title }}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- Status Badge -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span v-if="item.status === 'rejected'"
|
||||||
|
class="text-red-500 text-xs flex items-center gap-1 cursor-help" title="点击查看原因">
|
||||||
|
<i class="pi pi-info-circle"></i> {{ item.rejectReason }}
|
||||||
|
</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 }}</span>
|
||||||
|
<span v-else class="text-green-600 font-bold">免费</span>
|
||||||
|
<span><i class="pi pi-eye mr-1"></i> {{ item.views }}</span>
|
||||||
|
<span><i class="pi pi-thumbs-up mr-1"></i> {{ item.likes }}</span>
|
||||||
|
<span>{{ item.date }}</span>
|
||||||
|
</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"><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"><i
|
||||||
|
class="pi pi-arrow-down mr-1"></i> 下架</button>
|
||||||
|
<button v-if="item.status === 'offline'"
|
||||||
|
class="text-sm text-slate-500 hover:text-green-600 font-medium cursor-pointer"><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"><i
|
||||||
|
class="pi pi-trash mr-1"></i> 删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
const filterStatus = ref('all');
|
const filterStatus = ref('all');
|
||||||
const filterGenre = ref('all');
|
const filterGenre = ref('all');
|
||||||
|
|
||||||
const list = ref([
|
const list = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: '《锁麟囊》春秋亭 (程砚秋)',
|
title: '《锁麟囊》春秋亭 (程砚秋)',
|
||||||
genre: '京剧',
|
genre: '京剧',
|
||||||
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||||
status: 'published',
|
status: 'published',
|
||||||
price: 9.9,
|
price: 9.9,
|
||||||
views: '12.5k',
|
views: '12.5k',
|
||||||
likes: 850,
|
likes: 850,
|
||||||
date: '2025-12-24'
|
date: '2025-12-24'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: '昆曲《牡丹亭》游园惊梦',
|
title: '昆曲《牡丹亭》游园惊梦',
|
||||||
genre: '昆曲',
|
genre: '昆曲',
|
||||||
cover: 'https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
cover: 'https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||||
status: 'audit',
|
status: 'audit',
|
||||||
price: 0,
|
price: 0,
|
||||||
views: '-',
|
views: '-',
|
||||||
likes: '-',
|
likes: '-',
|
||||||
date: '2025-12-25'
|
date: '2025-12-25'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: '越剧《红楼梦》葬花',
|
title: '越剧《红楼梦》葬花',
|
||||||
genre: '越剧',
|
genre: '越剧',
|
||||||
cover: 'https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
cover: 'https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||||
status: 'rejected',
|
status: 'rejected',
|
||||||
rejectReason: '封面图清晰度不足',
|
rejectReason: '封面图清晰度不足',
|
||||||
price: 19.9,
|
price: 19.9,
|
||||||
views: '-',
|
views: '-',
|
||||||
likes: '-',
|
likes: '-',
|
||||||
date: '2025-12-23'
|
date: '2025-12-23'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
title: '未命名的草稿',
|
title: '未命名的草稿',
|
||||||
genre: '京剧',
|
genre: '京剧',
|
||||||
cover: '',
|
cover: '',
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
price: 0,
|
price: 0,
|
||||||
views: '-',
|
views: '-',
|
||||||
likes: '-',
|
likes: '-',
|
||||||
date: '2025-12-26'
|
date: '2025-12-26'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const filteredList = computed(() => {
|
const filteredList = computed(() => {
|
||||||
return list.value.filter(item => {
|
return list.value.filter(item => {
|
||||||
const matchStatus = filterStatus.value === 'all' || item.status === filterStatus.value;
|
const matchStatus = filterStatus.value === 'all' || item.status === filterStatus.value;
|
||||||
const matchGenre = filterGenre.value === 'all' || item.genre === filterGenre.value;
|
const matchGenre = filterGenre.value === 'all' || item.genre === filterGenre.value;
|
||||||
return matchStatus && matchGenre;
|
return matchStatus && matchGenre;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusStyle = (status) => {
|
const statusStyle = (status) => {
|
||||||
switch(status) {
|
switch (status) {
|
||||||
case 'published': return { bg: 'bg-green-50', text: 'text-green-600', label: '已发布' };
|
case 'published': return { bg: 'bg-green-50', text: 'text-green-600', label: '已发布' };
|
||||||
case 'audit': return { bg: 'bg-orange-50', text: 'text-orange-600', label: '审核中' };
|
case 'audit': return { bg: 'bg-orange-50', text: 'text-orange-600', label: '审核中' };
|
||||||
case 'rejected': return { bg: 'bg-red-50', text: 'text-red-600', label: '已驳回' };
|
case 'rejected': return { bg: 'bg-red-50', text: 'text-red-600', label: '已驳回' };
|
||||||
case 'draft': return { bg: 'bg-slate-100', text: 'text-slate-500', label: '草稿' };
|
case 'draft': return { bg: 'bg-slate-100', text: 'text-slate-500', label: '草稿' };
|
||||||
default: return { bg: 'bg-slate-100', text: 'text-slate-500', label: '未知' };
|
default: return { bg: 'bg-slate-100', text: 'text-slate-500', label: '未知' };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,7 +1,71 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-8">
|
<div>
|
||||||
<h1 class="text-2xl font-bold mb-4">Creator Dashboard</h1>
|
<div class="flex items-center justify-between mb-8">
|
||||||
<p class="text-slate-500">Welcome to the Creator Center.</p>
|
<h1 class="text-2xl font-bold text-slate-900">管理概览</h1>
|
||||||
<p class="text-slate-400 mt-2">(Implementation pending based on PAGE_TENANT_MANAGEMENT.md)</p>
|
<div class="flex gap-4">
|
||||||
</div>
|
<router-link to="/creator/contents/new"
|
||||||
|
class="px-6 py-2.5 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200 cursor-pointer active:scale-95 flex items-center gap-2">
|
||||||
|
<i class="pi pi-plus"></i> 发布新内容
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Key Metrics -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
<div v-for="metric in metrics" :key="metric.label"
|
||||||
|
class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 hover:shadow-md transition-shadow">
|
||||||
|
<div class="text-sm text-slate-500 mb-2">{{ metric.label }}</div>
|
||||||
|
<div class="flex items-baseline gap-2">
|
||||||
|
<span class="text-3xl font-bold text-slate-900">{{ metric.value }}</span>
|
||||||
|
<span class="text-xs font-bold" :class="metric.trend > 0 ? 'text-green-600' : 'text-red-600'">
|
||||||
|
<i class="pi" :class="metric.trend > 0 ? 'pi-arrow-up' : 'pi-arrow-down'"
|
||||||
|
style="font-size: 0.7rem"></i>
|
||||||
|
{{ Math.abs(metric.trend) }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-slate-400 mt-2">{{ metric.subtext }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pending Orders (Quick Access) -->
|
||||||
|
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100">
|
||||||
|
<h3 class="font-bold text-slate-900 mb-4">待处理事项</h3>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div
|
||||||
|
class="flex-1 p-4 bg-orange-50 border border-orange-100 rounded-xl flex items-center justify-between cursor-pointer hover:bg-orange-100 transition-colors"
|
||||||
|
@click="$router.push('/creator/orders')">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-10 h-10 rounded-full bg-orange-200 text-orange-700 flex items-center justify-center"><i
|
||||||
|
class="pi pi-refresh"></i></div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-slate-900">退款申请</div>
|
||||||
|
<div class="text-xs text-orange-600">需要处理</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-bold text-orange-700">2</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex-1 p-4 bg-blue-50 border border-blue-100 rounded-xl flex items-center justify-between cursor-pointer hover:bg-blue-100 transition-colors">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-10 h-10 rounded-full bg-blue-200 text-blue-700 flex items-center justify-center"><i
|
||||||
|
class="pi pi-comments"></i></div>
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-slate-900">新私信</div>
|
||||||
|
<div class="text-xs text-blue-600">粉丝互动</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-2xl font-bold text-blue-700">5</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const metrics = ref([
|
||||||
|
{ label: '总关注用户', value: '12,548', trend: 1.2, subtext: '昨日 +125' },
|
||||||
|
{ label: '累计总收益', value: '¥ 8,920.50', trend: 5.4, subtext: '近30天 +2,300' },
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,241 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-8">
|
<div>
|
||||||
<h1 class="text-2xl font-bold mb-4">Creator Orders</h1>
|
<div class="flex items-center justify-between mb-8">
|
||||||
<p class="text-slate-400">(List of orders)</p>
|
<h1 class="text-2xl font-bold text-slate-900">订单管理</h1>
|
||||||
</div>
|
<div class="flex gap-4">
|
||||||
|
<button
|
||||||
|
class="px-4 py-2 border border-slate-200 rounded-lg text-sm font-bold text-slate-600 hover:bg-slate-50 cursor-pointer">
|
||||||
|
<i class="pi pi-download mr-1"></i> 导出报表
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filters -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-4 mb-6 flex flex-wrap gap-4 items-center">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm font-bold text-slate-500">状态:</span>
|
||||||
|
<select v-model="filterStatus"
|
||||||
|
class="h-9 px-3 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none bg-white cursor-pointer">
|
||||||
|
<option value="all">全部</option>
|
||||||
|
<option value="completed">已完成</option>
|
||||||
|
<option value="refunding">退款申请中</option>
|
||||||
|
<option value="refunded">已退款</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto relative w-64">
|
||||||
|
<i class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||||
|
<input type="text" placeholder="搜索订单号或买家..."
|
||||||
|
class="w-full h-9 pl-9 pr-4 rounded border border-slate-200 text-sm focus:border-primary-500 outline-none transition-all">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Order Table -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
|
||||||
|
<table class="w-full text-left text-sm">
|
||||||
|
<thead class="bg-slate-50 text-slate-500 font-bold border-b border-slate-200">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-4 whitespace-nowrap">订单号</th>
|
||||||
|
<th class="px-6 py-4 w-[30%]">内容信息</th>
|
||||||
|
<th class="px-6 py-4 whitespace-nowrap">买家</th>
|
||||||
|
<th class="px-6 py-4 text-right whitespace-nowrap">实付金额</th>
|
||||||
|
<th class="px-6 py-4 whitespace-nowrap">下单时间</th>
|
||||||
|
<th class="px-6 py-4 whitespace-nowrap">状态</th>
|
||||||
|
<th class="px-6 py-4 text-right whitespace-nowrap">操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100">
|
||||||
|
<tr v-for="order in filteredOrders" :key="order.id" class="hover:bg-slate-50 transition-colors">
|
||||||
|
<td class="px-6 py-4 font-mono text-slate-600 align-middle">{{ order.id }}</td>
|
||||||
|
<td class="px-6 py-4 align-middle">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<img :src="order.cover"
|
||||||
|
class="w-16 h-10 object-cover rounded bg-slate-100 flex-shrink-0">
|
||||||
|
<span class="font-bold text-slate-900 truncate max-w-[240px]" :title="order.title">{{
|
||||||
|
order.title }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 align-middle">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<img :src="order.buyerAvatar" class="w-8 h-8 rounded-full flex-shrink-0">
|
||||||
|
<span class="text-slate-700 truncate max-w-[100px]">{{ order.buyerName }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-right font-bold text-slate-900 align-middle">¥ {{ order.amount }}</td>
|
||||||
|
<td class="px-6 py-4 text-slate-500 whitespace-nowrap align-middle">{{ order.date }}</td>
|
||||||
|
<td class="px-6 py-4 align-middle">
|
||||||
|
<span class="inline-block px-2.5 py-1 rounded text-xs font-bold whitespace-nowrap"
|
||||||
|
:class="statusStyle(order.status).bg + ' ' + statusStyle(order.status).text">
|
||||||
|
{{ statusStyle(order.status).label }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-right align-middle whitespace-nowrap">
|
||||||
|
<button @click="viewDetail(order)"
|
||||||
|
class="text-primary-600 hover:text-primary-700 font-medium mr-4 cursor-pointer hover:bg-primary-50 px-2 py-1 rounded transition-colors">详情</button>
|
||||||
|
<button v-if="order.status === 'refunding'" @click="handleRefund(order)"
|
||||||
|
class="text-red-600 hover:text-red-700 font-medium cursor-pointer hover:bg-red-50 px-2 py-1 rounded transition-colors">处理退款</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div v-if="filteredOrders.length === 0" class="text-center py-12 text-slate-400">
|
||||||
|
暂无相关订单
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detail Dialog -->
|
||||||
|
<Dialog v-model:visible="detailDialog" modal header="订单详情" :style="{ width: '30rem' }">
|
||||||
|
<div v-if="selectedOrder" class="space-y-6">
|
||||||
|
<div class="flex justify-between items-center pb-4 border-b border-slate-100">
|
||||||
|
<span class="text-sm text-slate-500">订单号</span>
|
||||||
|
<span class="font-mono font-bold">{{ selectedOrder.id }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<img :src="selectedOrder.cover" class="w-20 h-14 object-cover rounded">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold text-slate-900">{{ selectedOrder.title }}</h3>
|
||||||
|
<p class="text-sm text-slate-500 mt-1">类型: {{ selectedOrder.type }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-50 p-4 rounded-lg space-y-2 text-sm">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-slate-500">买家</span>
|
||||||
|
<span class="font-medium">{{ selectedOrder.buyerName }} (ID: {{ selectedOrder.buyerId }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-slate-500">支付方式</span>
|
||||||
|
<span>微信支付</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between pt-2 border-t border-slate-200">
|
||||||
|
<span class="font-bold text-slate-900">实付</span>
|
||||||
|
<span class="font-bold text-red-600 text-lg">¥ {{ selectedOrder.amount }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedOrder.status === 'refunding'" class="bg-red-50 p-4 rounded-lg border border-red-100">
|
||||||
|
<h4 class="text-red-700 font-bold text-sm mb-2">退款申请信息</h4>
|
||||||
|
<p class="text-sm text-red-600">申请原因:内容无法播放/质量问题</p>
|
||||||
|
<p class="text-sm text-red-600 mt-1">申请说明:视频一直加载不出来。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<button @click="detailDialog = false"
|
||||||
|
class="px-4 py-2 border border-slate-200 rounded text-sm hover:bg-slate-50">关闭</button>
|
||||||
|
<button
|
||||||
|
class="px-4 py-2 border border-primary-200 text-primary-600 rounded text-sm hover:bg-primary-50 ml-2">联系买家</button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Refund Dialog -->
|
||||||
|
<Dialog v-model:visible="refundDialog" modal header="处理退款申请" :style="{ width: '25rem' }">
|
||||||
|
<div class="text-sm text-slate-600 mb-6">
|
||||||
|
您正在处理订单 <span class="font-mono font-bold">{{ selectedOrder?.id }}</span> 的退款申请。
|
||||||
|
<br>同意后,金额将原路退回给买家。
|
||||||
|
</div>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<label class="flex items-center gap-3 p-3 border rounded-lg cursor-pointer hover:bg-slate-50"
|
||||||
|
:class="refundAction === 'accept' ? 'border-green-500 bg-green-50' : 'border-slate-200'">
|
||||||
|
<RadioButton v-model="refundAction" value="accept" />
|
||||||
|
<span>同意退款</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-3 p-3 border rounded-lg cursor-pointer hover:bg-slate-50"
|
||||||
|
:class="refundAction === 'reject' ? 'border-red-500 bg-red-50' : 'border-slate-200'">
|
||||||
|
<RadioButton v-model="refundAction" value="reject" />
|
||||||
|
<span>拒绝退款</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div v-if="refundAction === 'reject'" class="mt-4">
|
||||||
|
<textarea class="w-full p-2 border border-slate-200 rounded text-sm focus:border-red-500 outline-none"
|
||||||
|
rows="2" placeholder="请输入拒绝理由..."></textarea>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<button @click="refundDialog = false"
|
||||||
|
class="px-4 py-2 text-slate-500 hover:text-slate-700 text-sm">取消</button>
|
||||||
|
<button @click="confirmRefund"
|
||||||
|
class="px-4 py-2 bg-slate-900 text-white rounded text-sm hover:bg-slate-800">确认处理</button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Toast />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
|
import RadioButton from 'primevue/radiobutton';
|
||||||
|
import Toast from 'primevue/toast';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const filterStatus = ref('all');
|
||||||
|
const detailDialog = ref(false);
|
||||||
|
const refundDialog = ref(false);
|
||||||
|
const selectedOrder = ref(null);
|
||||||
|
const refundAction = ref('accept');
|
||||||
|
|
||||||
|
const orders = ref([
|
||||||
|
{
|
||||||
|
id: '82934712',
|
||||||
|
title: '《霸王别姬》全本实录珍藏版',
|
||||||
|
type: '视频',
|
||||||
|
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=100&q=60',
|
||||||
|
buyerName: '戏迷小张',
|
||||||
|
buyerAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zhang',
|
||||||
|
buyerId: '9527',
|
||||||
|
amount: '9.90',
|
||||||
|
date: '2025-12-24 14:30',
|
||||||
|
status: 'completed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '82934715',
|
||||||
|
title: '京剧打击乐基础教程',
|
||||||
|
type: '视频',
|
||||||
|
cover: 'https://images.unsplash.com/photo-1533174072545-e8d4aa97edf9?ixlib=rb-1.2.1&auto=format&fit=crop&w=100&q=60',
|
||||||
|
buyerName: '票友老李',
|
||||||
|
buyerAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Li',
|
||||||
|
buyerId: '8848',
|
||||||
|
amount: '19.90',
|
||||||
|
date: '2025-12-25 09:15',
|
||||||
|
status: 'refunding'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filteredOrders = computed(() => {
|
||||||
|
if (filterStatus.value === 'all') return orders.value;
|
||||||
|
return orders.value.filter(o => o.status === filterStatus.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusStyle = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'completed': return { bg: 'bg-green-50', text: 'text-green-600', label: '已完成' };
|
||||||
|
case 'refunding': return { bg: 'bg-orange-50', text: 'text-orange-600', label: '退款申请中' };
|
||||||
|
case 'refunded': return { bg: 'bg-slate-100', text: 'text-slate-500', label: '已退款' };
|
||||||
|
default: return { bg: 'bg-slate-100', text: 'text-slate-500', label: '未知' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const viewDetail = (order) => {
|
||||||
|
selectedOrder.value = order;
|
||||||
|
detailDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefund = (order) => {
|
||||||
|
selectedOrder.value = order;
|
||||||
|
refundAction.value = 'accept';
|
||||||
|
refundDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmRefund = () => {
|
||||||
|
// Mock API
|
||||||
|
refundDialog.value = false;
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: '处理完成',
|
||||||
|
detail: refundAction.value === 'accept' ? '已同意退款' : '已拒绝退款申请',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
// In real app, refresh list
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user