feat(portal): enhance creator dashboard and order management with new layout and features
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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 -->
|
||||
<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>
|
||||
|
||||
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 LayoutAuth from '../layout/LayoutAuth.vue'
|
||||
import LayoutUser from '../layout/LayoutUser.vue'
|
||||
import LayoutCreator from '../layout/LayoutCreator.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -39,6 +40,11 @@ const router = createRouter({
|
||||
path: 'creator/apply',
|
||||
name: 'creator-apply',
|
||||
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',
|
||||
name: 'user-wallet',
|
||||
component: () => import('../views/user/WalletView.vue') // Placeholder
|
||||
component: () => import('../views/user/WalletView.vue')
|
||||
},
|
||||
{
|
||||
path: 'library',
|
||||
name: 'user-library',
|
||||
component: () => import('../views/user/LibraryView.vue') // Placeholder
|
||||
component: () => import('../views/user/LibraryView.vue')
|
||||
},
|
||||
{
|
||||
path: 'notifications',
|
||||
name: 'user-notifications',
|
||||
component: () => import('../views/user/NotificationsView.vue') // Placeholder
|
||||
component: () => import('../views/user/NotificationsView.vue')
|
||||
},
|
||||
{
|
||||
path: 'profile',
|
||||
name: 'user-profile',
|
||||
component: () => import('../views/user/ProfileView.vue') // Placeholder
|
||||
component: () => import('../views/user/ProfileView.vue')
|
||||
},
|
||||
{
|
||||
path: 'security',
|
||||
name: 'user-security',
|
||||
component: () => import('../views/user/SecurityView.vue') // Placeholder
|
||||
component: () => import('../views/user/SecurityView.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/creator',
|
||||
component: LayoutUser, // Initially use LayoutUser, later maybe specialized LayoutCreator
|
||||
component: LayoutCreator,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -127,7 +133,7 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: '/checkout',
|
||||
component: LayoutMain, // Or a simplified checkout layout
|
||||
component: LayoutMain,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<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"
|
||||
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>
|
||||
@@ -11,7 +12,8 @@
|
||||
<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">
|
||||
<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>
|
||||
@@ -21,7 +23,8 @@
|
||||
</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">
|
||||
<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>
|
||||
@@ -30,19 +33,23 @@
|
||||
</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">
|
||||
<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">
|
||||
<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
|
||||
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>
|
||||
|
||||
@@ -52,14 +59,18 @@
|
||||
<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>
|
||||
<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="点击查看原因">
|
||||
<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">
|
||||
<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>
|
||||
@@ -76,10 +87,16 @@
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
@@ -88,7 +105,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const filterStatus = ref('all');
|
||||
const filterGenre = ref('all');
|
||||
|
||||
@@ -1,7 +1,71 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Creator Dashboard</h1>
|
||||
<p class="text-slate-500">Welcome to the Creator Center.</p>
|
||||
<p class="text-slate-400 mt-2">(Implementation pending based on PAGE_TENANT_MANAGEMENT.md)</p>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900">管理概览</h1>
|
||||
<div class="flex gap-4">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Creator Orders</h1>
|
||||
<p class="text-slate-400">(List of orders)</p>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900">订单管理</h1>
|
||||
<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>
|
||||
|
||||
<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