Compare commits
5 Commits
6ad195b360
...
35b46386c7
| Author | SHA1 | Date | |
|---|---|---|---|
| 35b46386c7 | |||
| b40c529cd8 | |||
| 8b75c80d1a | |||
| db72ce84da | |||
| 2232a46c9f |
@@ -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: '',
|
||||||
|
|||||||
@@ -1,10 +1,219 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 mx-auto max-w-screen-xl px-4 sm:px-6 lg:px-8 py-8 items-start">
|
||||||
<!-- Placeholder for Content Detail View -->
|
|
||||||
<div class="bg-white p-8 rounded-xl shadow-sm border border-slate-100 mx-auto max-w-screen-xl my-8 text-center">
|
<!-- Main Content (Left 9) -->
|
||||||
<h1 class="text-2xl font-bold mb-4">Content Detail Page</h1>
|
<div class="lg:col-span-9 space-y-6">
|
||||||
<p class="text-slate-500">ID: {{ $route.params.id }}</p>
|
<!-- Article Body -->
|
||||||
<p class="text-slate-400 mt-2">(Implementation pending based on PAGE_CONTENT_DETAIL.md)</p>
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-8 md:p-12 relative overflow-hidden">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mb-8 border-b border-slate-100 pb-8">
|
||||||
|
<div class="flex items-start justify-between gap-4 mb-4">
|
||||||
|
<h1 class="text-3xl md:text-4xl font-bold text-slate-900 leading-tight">
|
||||||
|
<span class="text-primary-600 mr-2 text-2xl align-middle">[京剧]</span>
|
||||||
|
《锁麟囊》春秋亭 · 二六
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Opera Meta -->
|
||||||
|
<div class="flex flex-wrap gap-3 mb-6">
|
||||||
|
<span class="px-3 py-1 bg-slate-100 text-slate-600 rounded-full text-sm font-bold">行当: 青衣</span>
|
||||||
|
<span class="px-3 py-1 bg-slate-100 text-slate-600 rounded-full text-sm font-bold">定调: F大调</span>
|
||||||
|
<span class="px-3 py-1 bg-slate-100 text-slate-600 rounded-full text-sm font-bold">板式: 二六</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Author & Tools -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Master1" class="w-10 h-10 rounded-full border border-slate-200">
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-slate-900 text-sm">梅派传人小林 <i class="pi pi-check-circle text-blue-500 text-xs"></i></div>
|
||||||
|
<div class="text-xs text-slate-400">发布于 2025-12-24</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button @click="fontSize++" class="w-9 h-9 flex items-center justify-center rounded hover:bg-slate-100 text-slate-600" title="放大字体"><i class="pi pi-plus" style="font-size: 0.8rem"></i>A</button>
|
||||||
|
<button @click="fontSize--" class="w-9 h-9 flex items-center justify-center rounded hover:bg-slate-100 text-slate-600" title="缩小字体"><i class="pi pi-minus" style="font-size: 0.8rem"></i>A</button>
|
||||||
|
<div class="w-px h-4 bg-slate-200 mx-1"></div>
|
||||||
|
<button class="w-9 h-9 flex items-center justify-center rounded hover:bg-slate-100 text-slate-600"><i class="pi pi-share-alt"></i></button>
|
||||||
|
<button class="w-9 h-9 flex items-center justify-center rounded hover:bg-slate-100 text-slate-600"><i class="pi pi-bookmark"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Render -->
|
||||||
|
<div class="prose prose-slate max-w-none text-slate-800" :style="{ fontSize: fontSize + 'px' }">
|
||||||
|
<!-- Video Player (Trial) -->
|
||||||
|
<div class="not-prose mb-8 rounded-xl overflow-hidden bg-black relative group aspect-video">
|
||||||
|
<div class="absolute inset-0 flex items-center justify-center text-white" v-if="!isPlaying">
|
||||||
|
<button @click="isPlaying = true" class="w-16 h-16 bg-white/20 hover:bg-white/30 backdrop-blur rounded-full flex items-center justify-center transition-all">
|
||||||
|
<i class="pi pi-play text-3xl ml-1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Mock Video UI -->
|
||||||
|
<div class="absolute bottom-0 left-0 w-full p-4 bg-gradient-to-t from-black/80 to-transparent flex items-center gap-4 text-white">
|
||||||
|
<button class="text-white hover:text-primary-400"><i class="pi pi-play-circle text-2xl"></i></button>
|
||||||
|
<div class="flex-1 h-1.5 bg-white/30 rounded-full relative overflow-hidden group/bar cursor-pointer">
|
||||||
|
<!-- Trial Segment (Highlight) -->
|
||||||
|
<div class="absolute top-0 left-0 h-full bg-primary-500 w-[30%]"></div>
|
||||||
|
<!-- Locked Segment -->
|
||||||
|
<div class="absolute top-0 right-0 h-full bg-slate-500/50 w-[70%] flex items-center justify-center">
|
||||||
|
<i class="pi pi-lock text-[10px]"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-mono">01:30 / 05:00</span>
|
||||||
|
</div>
|
||||||
|
<!-- Paywall Overlay -->
|
||||||
|
<div v-if="showPaywall" class="absolute inset-0 bg-black/80 backdrop-blur-sm flex flex-col items-center justify-center text-center p-8 z-20">
|
||||||
|
<h3 class="text-white text-xl font-bold mb-2">试看已结束</h3>
|
||||||
|
<p class="text-slate-300 text-sm mb-6">购买后可观看完整内容 (包含高清视频 + 完整剧本)</p>
|
||||||
|
<button class="px-8 py-3 bg-primary-600 text-white rounded-full font-bold hover:bg-primary-700 shadow-lg shadow-primary-900/50 transition-transform hover:scale-105 active:scale-95">
|
||||||
|
¥ 9.90 立即解锁
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>(白)听薛良一语,一似那金钟猛撞。</p>
|
||||||
|
<p>(唱)春秋亭外风雨暴,何处悲声破寂寥。</p>
|
||||||
|
|
||||||
|
<!-- Script Block -->
|
||||||
|
<div class="pl-6 border-l-4 border-primary-200 bg-slate-50 p-4 rounded-r-lg my-6 not-prose">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<span class="font-bold text-slate-900 flex-shrink-0">薛湘灵:</span>
|
||||||
|
<span class="text-slate-700 leading-relaxed">
|
||||||
|
隔帘只见一花轿,想必是新婚渡鹊桥。<br>
|
||||||
|
吉日良辰当欢笑,为什么被珠泪抛?<br>
|
||||||
|
此时却又明白了,<br>
|
||||||
|
(白) 世上何尝尽富豪。<br>
|
||||||
|
(唱) 也有饥寒悲怀抱,也有失意痛哭嚎啕。<br>
|
||||||
|
轿内的人儿,命薄如纸,<br>
|
||||||
|
苦煞了严亲,累煞了做娘的。
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aria Block -->
|
||||||
|
<div class="flex items-center gap-4 p-4 border border-purple-100 bg-purple-50 rounded-xl my-6 not-prose cursor-pointer hover:bg-purple-100 transition-colors">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-purple-600 text-white flex items-center justify-center flex-shrink-0 shadow-sm"><i class="pi pi-play"></i></div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="font-bold text-slate-900 truncate">名家示范:流水板式要点</div>
|
||||||
|
<div class="text-xs text-purple-600 flex gap-2 mt-1 font-mono">
|
||||||
|
<span class="bg-white px-1.5 rounded border border-purple-200">Key: F</span>
|
||||||
|
<span class="bg-white px-1.5 rounded border border-purple-200">Beat: 1/4</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-slate-500 font-mono">00:45</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>在这里,程派独特的“嗽音”运用得淋漓尽致,表现了湘灵对贫富无常的深刻感悟...</p>
|
||||||
|
|
||||||
|
<!-- Paywall Mask for Text -->
|
||||||
|
<div class="relative mt-8 pt-20 pb-8 text-center" v-if="!isPurchased">
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-b from-transparent via-white/90 to-white z-10"></div>
|
||||||
|
<div class="relative z-20">
|
||||||
|
<button class="px-10 py-3.5 bg-primary-600 text-white rounded-full font-bold text-lg hover:bg-primary-700 shadow-xl shadow-primary-100 transition-transform hover:scale-105 active:scale-95 flex items-center gap-2 mx-auto">
|
||||||
|
<i class="pi pi-lock"></i> 购买解锁全文 ¥ 9.90
|
||||||
|
</button>
|
||||||
|
<p class="text-xs text-slate-400 mt-3">支持微信 / 支付宝</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comments Section -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-8">
|
||||||
|
<div class="flex items-center justify-between mb-6">
|
||||||
|
<h2 class="text-xl font-bold text-slate-900">全部评论 (24)</h2>
|
||||||
|
<div class="flex text-sm font-bold text-slate-400">
|
||||||
|
<button class="text-slate-900 mr-4">最新</button>
|
||||||
|
<button class="hover:text-slate-900">热门</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4 mb-8">
|
||||||
|
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=User" class="w-10 h-10 rounded-full bg-slate-100">
|
||||||
|
<div class="flex-1">
|
||||||
|
<textarea rows="2" class="w-full p-3 rounded-lg border border-slate-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-100 outline-none resize-none transition-colors" placeholder="写下你的想法..."></textarea>
|
||||||
|
<div class="flex justify-end mt-2">
|
||||||
|
<button class="px-6 py-2 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 text-sm">发布</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div v-for="i in 3" :key="i" class="flex gap-4">
|
||||||
|
<img :src="`https://api.dicebear.com/7.x/avataaars/svg?seed=${i}`" class="w-10 h-10 rounded-full bg-slate-100">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 mb-1">
|
||||||
|
<span class="font-bold text-slate-900 text-sm">戏迷_{{ i*92 }}</span>
|
||||||
|
<span class="text-xs text-slate-400">1小时前</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-slate-700 text-sm leading-relaxed mb-2">这段唱腔真是经典,张火丁老师的演绎太有味道了,反复听了十几遍!</p>
|
||||||
|
<div class="flex gap-4 text-xs text-slate-500">
|
||||||
|
<button class="hover:text-primary-600 flex items-center gap-1"><i class="pi pi-thumbs-up"></i> 12</button>
|
||||||
|
<button class="hover:text-primary-600">回复</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sidebar (Right 3) - Sticky -->
|
||||||
|
<div class="lg:col-span-3 space-y-6 sticky top-24 h-fit">
|
||||||
|
<!-- Author Card -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-6 text-center">
|
||||||
|
<div class="w-20 h-20 rounded-full border-4 border-slate-50 mx-auto -mt-10 mb-4 bg-white shadow-sm overflow-hidden">
|
||||||
|
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Master1" class="w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
<h3 class="font-bold text-slate-900 text-lg">梅派传人小林</h3>
|
||||||
|
<p class="text-xs text-slate-500 mt-1 mb-4">专注京剧程派艺术传承与推广</p>
|
||||||
|
<div class="flex justify-center gap-4 text-sm font-bold text-slate-900 mb-6">
|
||||||
|
<div>125 <span class="text-xs text-slate-400 font-normal block">内容</span></div>
|
||||||
|
<div>1.2w <span class="text-xs text-slate-400 font-normal block">关注</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-3">
|
||||||
|
<button class="py-2 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 shadow-sm shadow-primary-200">关注</button>
|
||||||
|
<button class="py-2 border border-slate-200 text-slate-700 rounded-lg font-bold hover:bg-slate-50">私信</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||||
|
<h3 class="font-bold text-slate-900 mb-4">目录</h3>
|
||||||
|
<div class="space-y-3 text-sm relative">
|
||||||
|
<!-- Active Line -->
|
||||||
|
<div class="absolute left-0 top-2 bottom-2 w-0.5 bg-slate-100"></div>
|
||||||
|
|
||||||
|
<a href="#" class="block pl-4 border-l-2 border-primary-600 text-primary-600 font-bold -ml-[1px]">唱段背景介绍</a>
|
||||||
|
<a href="#" class="block pl-4 border-l-2 border-transparent text-slate-600 hover:text-slate-900 hover:border-slate-300">发音技巧解析</a>
|
||||||
|
<a href="#" class="block pl-4 border-l-2 border-transparent text-slate-600 hover:text-slate-900 hover:border-slate-300">逐句示范 (00:45)</a>
|
||||||
|
<a href="#" class="block pl-4 border-l-2 border-transparent text-slate-600 hover:text-slate-900 hover:border-slate-300">完整伴奏下载</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Related -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||||
|
<h3 class="font-bold text-slate-900 mb-4">相关推荐</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div v-for="i in 3" :key="i" class="flex gap-3 group cursor-pointer">
|
||||||
|
<img src="https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=100&q=60" class="w-20 h-14 object-cover rounded bg-slate-100 flex-shrink-0">
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-bold text-slate-900 line-clamp-2 group-hover:text-primary-600 leading-snug">程派《荒山泪》夜织选段</h4>
|
||||||
|
<span class="text-xs text-slate-400 mt-1 block">8.5k 阅读</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const fontSize = ref(18); // Default 18px
|
||||||
|
const isPlaying = ref(false);
|
||||||
|
const showPaywall = ref(true); // Simulate trial ended
|
||||||
|
const isPurchased = ref(false); // Simulate paid content
|
||||||
|
</script>
|
||||||
297
frontend/portal/src/views/creator/ContentsEditView.vue
Normal file
297
frontend/portal/src/views/creator/ContentsEditView.vue
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-full bg-white rounded-xl overflow-hidden">
|
||||||
|
<!-- Header -->
|
||||||
|
<div
|
||||||
|
class="px-8 py-4 border-b border-slate-100 flex items-center justify-between bg-white z-10 shadow-sm sticky top-0">
|
||||||
|
<div class="flex items-center gap-4 w-1/3">
|
||||||
|
<button @click="$router.back()"
|
||||||
|
class="text-slate-400 hover:text-slate-600 transition-colors cursor-pointer"><i
|
||||||
|
class="pi pi-arrow-left text-xl"></i></button>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 px-3 py-1 bg-green-50 text-green-700 text-xs font-medium rounded-full animate-pulse">
|
||||||
|
<i class="pi pi-cloud-upload"></i> {{ autoSaveStatus }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-1 flex justify-center">
|
||||||
|
<div class="text-lg font-bold text-slate-900 truncate max-w-md" :class="!fullTitle ? 'text-slate-400' : ''">
|
||||||
|
{{ fullTitle || '新内容标题预览' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 w-1/3 justify-end">
|
||||||
|
<button
|
||||||
|
class="px-6 py-2 border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-bold transition-colors cursor-pointer">存草稿</button>
|
||||||
|
<button @click="submit"
|
||||||
|
class="px-8 py-2 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 shadow-lg shadow-primary-200 transition-all active:scale-95 cursor-pointer">发布</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content Form -->
|
||||||
|
<div class="flex-1 overflow-y-auto bg-slate-50/50 p-8 md:p-12">
|
||||||
|
<div class="max-w-screen-xl mx-auto bg-white p-10 rounded-2xl border border-slate-200 shadow-sm space-y-10">
|
||||||
|
|
||||||
|
<!-- Row 1: Genre & Key -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">曲种 <span
|
||||||
|
class="text-red-500">*</span></label>
|
||||||
|
<Select v-model="form.genre" :options="genres" placeholder="选择曲种" class="w-full h-12" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">主定调</label>
|
||||||
|
<Select v-model="form.key" :options="keys" placeholder="选择定调" class="w-full h-12" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: Titles -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">剧目名 <span
|
||||||
|
class="text-red-500">*</span></label>
|
||||||
|
<input v-model="form.playName" type="text" placeholder="如《锁麟囊》"
|
||||||
|
class="w-full h-12 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">选段名</label>
|
||||||
|
<input v-model="form.selectionName" type="text" placeholder="如“春秋亭”"
|
||||||
|
class="w-full h-12 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">附加信息</label>
|
||||||
|
<input v-model="form.extraInfo" type="text" placeholder="如“程砚秋”"
|
||||||
|
class="w-full h-12 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 3: Price -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-3">价格策略</label>
|
||||||
|
<div class="p-6 bg-slate-50 rounded-xl border border-slate-200">
|
||||||
|
<div class="flex gap-4 mb-6">
|
||||||
|
<button @click="form.priceType = 'free'"
|
||||||
|
class="flex-1 py-3 font-bold rounded-lg border-2 transition-all cursor-pointer"
|
||||||
|
:class="form.priceType === 'free' ? 'border-primary-600 bg-white text-primary-600' : 'border-transparent bg-white text-slate-500 hover:bg-slate-100'">免费</button>
|
||||||
|
<button @click="form.priceType = 'paid'"
|
||||||
|
class="flex-1 py-3 font-bold rounded-lg border-2 transition-all cursor-pointer"
|
||||||
|
:class="form.priceType === 'paid' ? 'border-primary-600 bg-white text-primary-600' : 'border-transparent bg-white text-slate-500 hover:bg-slate-100'">付费</button>
|
||||||
|
<button @click="form.priceType = 'member'"
|
||||||
|
class="flex-1 py-3 font-bold rounded-lg border-2 transition-all cursor-pointer"
|
||||||
|
:class="form.priceType === 'member' ? 'border-amber-500 bg-white text-amber-600' : 'border-transparent bg-white text-slate-500 hover:bg-slate-100'">会员专享</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="form.priceType === 'paid'"
|
||||||
|
class="flex flex-wrap items-center gap-8 animate-in fade-in slide-in-from-top-2">
|
||||||
|
<div class="relative w-48">
|
||||||
|
<span class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 font-bold">¥</span>
|
||||||
|
<input v-model="form.price" type="number"
|
||||||
|
class="w-full h-12 pl-8 pr-4 border border-slate-200 rounded-lg focus:border-primary-500 font-bold text-lg outline-none"
|
||||||
|
placeholder="0.00">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<input type="checkbox" v-model="form.enableTrial" id="trial"
|
||||||
|
class="w-5 h-5 rounded border-slate-300 text-primary-600 focus:ring-primary-500 cursor-pointer">
|
||||||
|
<label for="trial" class="text-sm font-bold text-slate-700 cursor-pointer">开启试看</label>
|
||||||
|
</div>
|
||||||
|
<div v-if="form.enableTrial" class="flex items-center gap-2">
|
||||||
|
<input v-model="form.trialTime" type="number"
|
||||||
|
class="w-20 h-10 px-2 border border-slate-200 rounded-lg text-center outline-none">
|
||||||
|
<span class="text-sm text-slate-500">秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 4: Abstract -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">摘要</label>
|
||||||
|
<textarea v-model="form.abstract" rows="4"
|
||||||
|
class="w-full p-4 rounded-xl border border-slate-200 focus:border-primary-500 outline-none resize-none transition-colors"
|
||||||
|
placeholder="请输入内容简介..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 5: Covers (Max 3) -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-3">封面 (最多3张)</label>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
<div v-for="(img, idx) in form.covers" :key="idx"
|
||||||
|
class="relative group aspect-video rounded-lg overflow-hidden bg-slate-100 border border-slate-200">
|
||||||
|
<img :src="img" class="w-full h-full object-cover">
|
||||||
|
<button @click="removeCover(idx)"
|
||||||
|
class="absolute top-1 right-1 w-6 h-6 bg-black/50 hover:bg-red-500 text-white rounded-full flex items-center justify-center transition-colors cursor-pointer"><i
|
||||||
|
class="pi pi-times text-xs"></i></button>
|
||||||
|
</div>
|
||||||
|
<div v-if="form.covers.length < 3" @click="triggerUpload('cover')"
|
||||||
|
class="aspect-video rounded-lg border-2 border-dashed border-slate-300 flex flex-col items-center justify-center text-slate-400 hover:border-primary-500 hover:bg-primary-50 hover:text-primary-600 cursor-pointer transition-all">
|
||||||
|
<i class="pi pi-plus text-2xl mb-1"></i>
|
||||||
|
<span class="text-xs font-bold">上传封面</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 6: Video -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-3">视频列表</label>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div v-for="(file, idx) in form.videos" :key="idx"
|
||||||
|
class="flex items-center gap-4 p-3 border border-slate-200 rounded-lg bg-slate-50">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 text-blue-600 rounded flex items-center justify-center"><i
|
||||||
|
class="pi pi-video"></i></div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="font-bold text-sm text-slate-900 truncate">{{ file.name }}</div>
|
||||||
|
<div class="text-xs text-slate-500">{{ file.size }}</div>
|
||||||
|
</div>
|
||||||
|
<button @click="removeMedia('videos', idx)"
|
||||||
|
class="text-slate-400 hover:text-red-500 px-2 cursor-pointer"><i
|
||||||
|
class="pi pi-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
<div @click="triggerUpload('video')"
|
||||||
|
class="h-14 border-2 border-dashed border-slate-300 rounded-lg flex items-center justify-center text-slate-500 hover:border-blue-500 hover:bg-blue-50 hover:text-blue-600 cursor-pointer transition-all font-bold gap-2">
|
||||||
|
<i class="pi pi-video"></i> 添加视频
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 7: Audio -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-3">音频列表</label>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div v-for="(file, idx) in form.audios" :key="idx"
|
||||||
|
class="flex items-center gap-4 p-3 border border-slate-200 rounded-lg bg-slate-50">
|
||||||
|
<div class="w-10 h-10 bg-purple-100 text-purple-600 rounded flex items-center justify-center"><i
|
||||||
|
class="pi pi-microphone"></i></div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="font-bold text-sm text-slate-900 truncate">{{ file.name }}</div>
|
||||||
|
<div class="text-xs text-slate-500">{{ file.size }}</div>
|
||||||
|
</div>
|
||||||
|
<button @click="removeMedia('audios', idx)"
|
||||||
|
class="text-slate-400 hover:text-red-500 px-2 cursor-pointer"><i
|
||||||
|
class="pi pi-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
<div @click="triggerUpload('audio')"
|
||||||
|
class="h-14 border-2 border-dashed border-slate-300 rounded-lg flex items-center justify-center text-slate-500 hover:border-purple-500 hover:bg-purple-50 hover:text-purple-600 cursor-pointer transition-all font-bold gap-2">
|
||||||
|
<i class="pi pi-microphone"></i> 添加音频
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 8: Images -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-3">图片列表</label>
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
|
<div v-for="(file, idx) in form.images" :key="idx"
|
||||||
|
class="relative group aspect-square rounded-lg overflow-hidden border border-slate-200">
|
||||||
|
<img :src="file.url" class="w-full h-full object-cover">
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 left-0 w-full bg-black/60 text-white text-xs p-1 truncate text-center">
|
||||||
|
{{ file.name }}</div>
|
||||||
|
<button @click="removeMedia('images', idx)"
|
||||||
|
class="absolute top-1 right-1 w-6 h-6 bg-red-500 text-white rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer"><i
|
||||||
|
class="pi pi-times text-xs"></i></button>
|
||||||
|
</div>
|
||||||
|
<div @click="triggerUpload('image')"
|
||||||
|
class="aspect-square border-2 border-dashed border-slate-300 rounded-lg flex flex-col items-center justify-center text-slate-500 hover:border-orange-500 hover:bg-orange-50 hover:text-orange-600 cursor-pointer transition-all">
|
||||||
|
<i class="pi pi-image text-2xl mb-1"></i>
|
||||||
|
<span class="text-xs font-bold">添加图片</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="file" ref="fileInput" class="hidden" @change="handleFileChange" multiple>
|
||||||
|
<Toast />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Select from 'primevue/select';
|
||||||
|
import Toast from 'primevue/toast';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const toast = useToast();
|
||||||
|
const fileInput = ref(null);
|
||||||
|
const currentUploadType = ref('');
|
||||||
|
|
||||||
|
const autoSaveStatus = ref('已自动保存');
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
genre: null,
|
||||||
|
playName: '',
|
||||||
|
selectionName: '',
|
||||||
|
extraInfo: '',
|
||||||
|
abstract: '',
|
||||||
|
priceType: 'free',
|
||||||
|
price: 9.9,
|
||||||
|
enableTrial: true,
|
||||||
|
trialTime: 60,
|
||||||
|
key: null,
|
||||||
|
covers: [],
|
||||||
|
videos: [],
|
||||||
|
audios: [],
|
||||||
|
images: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const genres = ['京剧', '昆曲', '越剧', '黄梅戏', '豫剧', '评剧'];
|
||||||
|
const keys = ['C大调', 'D大调', 'E大调', 'F大调', 'G大调', 'A大调', 'B大调', '降E大调'];
|
||||||
|
|
||||||
|
const fullTitle = computed(() => {
|
||||||
|
let title = '';
|
||||||
|
if (form.playName) title += `《${form.playName}》`;
|
||||||
|
if (form.selectionName) title += ` ${form.selectionName}`;
|
||||||
|
if (form.extraInfo) title += ` (${form.extraInfo})`;
|
||||||
|
return title;
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerUpload = (type) => {
|
||||||
|
currentUploadType.value = type;
|
||||||
|
fileInput.value.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (event) => {
|
||||||
|
const files = event.target.files;
|
||||||
|
if (!files.length) return;
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = files[i];
|
||||||
|
const mockUrl = URL.createObjectURL(file); // For preview
|
||||||
|
|
||||||
|
if (currentUploadType.value === 'cover') {
|
||||||
|
if (form.covers.length < 3) form.covers.push(mockUrl);
|
||||||
|
} else if (currentUploadType.value === 'video') {
|
||||||
|
form.videos.push({ name: file.name, size: '25MB', url: mockUrl });
|
||||||
|
} else if (currentUploadType.value === 'audio') {
|
||||||
|
form.audios.push({ name: file.name, size: '5MB', url: mockUrl });
|
||||||
|
} else if (currentUploadType.value === 'image') {
|
||||||
|
form.images.push({ name: file.name, size: '1MB', url: mockUrl });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset input to allow re-uploading same file
|
||||||
|
event.target.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeCover = (idx) => form.covers.splice(idx, 1);
|
||||||
|
const removeMedia = (type, idx) => form[type].splice(idx, 1);
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
toast.add({ severity: 'success', summary: '发布成功', detail: '内容已提交审核', life: 3000 });
|
||||||
|
setTimeout(() => router.push('/creator/contents'), 1500);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@reference "../../assets/main.css";
|
||||||
|
|
||||||
|
:deep(.p-select) {
|
||||||
|
@apply border-slate-200 rounded-lg shadow-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.p-select:not(.p-disabled).p-focus) {
|
||||||
|
@apply border-primary-500 ring-2 ring-primary-100 shadow-none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,178 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-8">
|
<div>
|
||||||
<h1 class="text-2xl font-bold mb-4">Creator Contents</h1>
|
<div class="flex items-center justify-between mb-8">
|
||||||
<p class="text-slate-400">(List of contents)</p>
|
<h1 class="text-2xl font-bold text-slate-900">内容管理</h1>
|
||||||
</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>
|
||||||
|
|
||||||
|
<!-- 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="published">已发布</option>
|
||||||
|
<option value="audit">审核中</option>
|
||||||
|
<option value="rejected">已驳回</option>
|
||||||
|
<option value="draft">草稿箱</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm font-bold text-slate-500">曲种:</span>
|
||||||
|
<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">
|
||||||
|
<option value="all">全部</option>
|
||||||
|
<option value="京剧">京剧</option>
|
||||||
|
<option value="昆曲">昆曲</option>
|
||||||
|
<option value="越剧">越剧</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto relative">
|
||||||
|
<i class="pi pi-search absolute left-3 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||||
|
<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 -->
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!-- 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 -->
|
||||||
|
<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">[{{ item.genre }}]</span>
|
||||||
|
<h3
|
||||||
|
class="font-bold text-slate-900 text-lg truncate hover:text-primary-600 cursor-pointer transition-colors">
|
||||||
|
{{ item.title }}</h3>
|
||||||
|
</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>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const filterStatus = ref('all');
|
||||||
|
const filterGenre = ref('all');
|
||||||
|
|
||||||
|
const list = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: '《锁麟囊》春秋亭 (程砚秋)',
|
||||||
|
genre: '京剧',
|
||||||
|
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||||
|
status: 'published',
|
||||||
|
price: 9.9,
|
||||||
|
views: '12.5k',
|
||||||
|
likes: 850,
|
||||||
|
date: '2025-12-24'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: '昆曲《牡丹亭》游园惊梦',
|
||||||
|
genre: '昆曲',
|
||||||
|
cover: 'https://images.unsplash.com/photo-1557683316-973673baf926?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||||
|
status: 'audit',
|
||||||
|
price: 0,
|
||||||
|
views: '-',
|
||||||
|
likes: '-',
|
||||||
|
date: '2025-12-25'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: '越剧《红楼梦》葬花',
|
||||||
|
genre: '越剧',
|
||||||
|
cover: 'https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||||
|
status: 'rejected',
|
||||||
|
rejectReason: '封面图清晰度不足',
|
||||||
|
price: 19.9,
|
||||||
|
views: '-',
|
||||||
|
likes: '-',
|
||||||
|
date: '2025-12-23'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: '未命名的草稿',
|
||||||
|
genre: '京剧',
|
||||||
|
cover: '',
|
||||||
|
status: 'draft',
|
||||||
|
price: 0,
|
||||||
|
views: '-',
|
||||||
|
likes: '-',
|
||||||
|
date: '2025-12-26'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filteredList = computed(() => {
|
||||||
|
return list.value.filter(item => {
|
||||||
|
const matchStatus = filterStatus.value === 'all' || item.status === filterStatus.value;
|
||||||
|
const matchGenre = filterGenre.value === 'all' || item.genre === filterGenre.value;
|
||||||
|
return matchStatus && matchGenre;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusStyle = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
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 'rejected': return { bg: 'bg-red-50', text: 'text-red-600', label: '已驳回' };
|
||||||
|
case 'draft': return { bg: 'bg-slate-100', text: 'text-slate-500', label: '草稿' };
|
||||||
|
default: return { bg: 'bg-slate-100', text: 'text-slate-500', label: '未知' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</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>
|
||||||
@@ -1,6 +1,129 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-8">
|
<div class="p-8 max-w-5xl mx-auto">
|
||||||
<h1 class="text-2xl font-bold mb-4">Creator Settings</h1>
|
<div class="flex items-center justify-between mb-8">
|
||||||
<p class="text-slate-400">(Tenant settings)</p>
|
<h1 class="text-2xl font-bold text-slate-900">频道设置</h1>
|
||||||
|
<button @click="saveSettings" class="px-8 py-2.5 bg-primary-600 text-white rounded-lg font-bold hover:bg-primary-700 shadow-sm shadow-primary-200 cursor-pointer active:scale-95 flex items-center gap-2">
|
||||||
|
<i class="pi pi-check"></i> 保存修改
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-w-4xl mx-auto space-y-8">
|
||||||
|
<!-- 0. Tips -->
|
||||||
|
<div class="bg-blue-50 p-6 rounded-xl border border-blue-100 text-sm text-blue-700 flex items-start gap-4">
|
||||||
|
<i class="pi pi-info-circle text-xl mt-0.5"></i>
|
||||||
|
<div>
|
||||||
|
<h4 class="font-bold mb-1">频道设置须知</h4>
|
||||||
|
<ul class="list-disc list-inside space-y-1 opacity-90">
|
||||||
|
<li>建议封面图尺寸 1280x320px,重点内容居中。</li>
|
||||||
|
<li>频道名称修改后需人工审核,审核期间原名称仍可见。</li>
|
||||||
|
<li>优质的频道介绍和精美的视觉设计有助于大幅提升关注转化率。</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 1. Basic Info -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-8">
|
||||||
|
<h2 class="text-lg font-bold text-slate-900 mb-6 pb-4 border-b border-slate-100">基本信息</h2>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Avatar & Name -->
|
||||||
|
<div class="flex items-start gap-6">
|
||||||
|
<div class="relative group cursor-pointer flex-shrink-0" @click="triggerUpload('avatar')">
|
||||||
|
<div class="w-24 h-24 rounded-full border-4 border-slate-50 shadow-sm overflow-hidden bg-slate-100">
|
||||||
|
<img :src="form.avatar" class="w-full h-full object-cover">
|
||||||
|
<div class="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<i class="pi pi-camera text-white text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">频道名称</label>
|
||||||
|
<input v-model="form.name" type="text" class="w-full h-11 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors" placeholder="给您的频道起个名字">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">一句话简介</label>
|
||||||
|
<input v-model="form.bio" type="text" class="w-full h-11 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors" placeholder="介绍一下您的频道特色...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Intro Detail -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-2">详细介绍</label>
|
||||||
|
<textarea v-model="form.description" rows="4" class="w-full p-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors resize-none" placeholder="详细描述您的履历、师承或频道内容规划..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 2. Visual Style -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-8">
|
||||||
|
<h2 class="text-lg font-bold text-slate-900 mb-6 pb-4 border-b border-slate-100">视觉风格</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold text-slate-700 mb-3">频道头图 (Cover)</label>
|
||||||
|
<div class="aspect-[4/1] rounded-xl bg-slate-50 border-2 border-dashed border-slate-300 relative overflow-hidden group cursor-pointer hover:border-primary-400 transition-all" @click="triggerUpload('cover')">
|
||||||
|
<img v-if="form.cover" :src="form.cover" class="w-full h-full object-cover">
|
||||||
|
<div class="absolute inset-0 flex flex-col items-center justify-center text-slate-400 bg-white/50 opacity-100 group-hover:bg-white/80 transition-all" v-else>
|
||||||
|
<i class="pi pi-image text-3xl mb-2"></i>
|
||||||
|
<span class="text-sm font-medium">点击上传 (建议尺寸 1280x320)</span>
|
||||||
|
</div>
|
||||||
|
<!-- Hover Overlay for replace -->
|
||||||
|
<div v-if="form.cover" class="absolute inset-0 bg-black/40 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
<span class="text-white font-bold"><i class="pi pi-refresh mr-2"></i>更换封面</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="file" ref="fileInput" class="hidden" @change="handleFileChange">
|
||||||
|
<Toast />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import Toast from 'primevue/toast';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const fileInput = ref(null);
|
||||||
|
const currentUploadType = ref('');
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
name: '梅派传人小林',
|
||||||
|
bio: '专注京剧程派艺术传承与推广',
|
||||||
|
description: '',
|
||||||
|
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Master1',
|
||||||
|
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=1200&q=80'
|
||||||
|
});
|
||||||
|
|
||||||
|
const triggerUpload = (type) => {
|
||||||
|
currentUploadType.value = type;
|
||||||
|
fileInput.value.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileChange = (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
if (currentUploadType.value === 'avatar') {
|
||||||
|
form.avatar = e.target.result;
|
||||||
|
} else {
|
||||||
|
form.cover = e.target.result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveSettings = () => {
|
||||||
|
if (!form.name) {
|
||||||
|
toast.add({ severity: 'error', summary: '错误', detail: '频道名称不能为空', life: 3000 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast.add({ severity: 'success', summary: '保存成功', detail: '部分信息正在审核中', life: 3000 });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user