Compare commits
7 Commits
9ebb7e5452
...
6ad195b360
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ad195b360 | |||
| eefcfc79a5 | |||
| f8c312752e | |||
| caaa8654b3 | |||
| c2c9621e64 | |||
| 6b71ac47d1 | |||
| 648e3f7c21 |
@@ -106,6 +106,12 @@
|
||||
- **样式**: `text-lg px-4 border-slate-300 focus:border-primary-600 focus:ring-2`。
|
||||
- **Label**: 必须显示 Label,字号 `text-base` 或 `text-lg`,不建议仅用 placeholder。
|
||||
|
||||
### 4.5 交互反馈 (Interactive Feedback)
|
||||
- **鼠标手势**: 所有可点击元素(卡片、按钮、链接、自定义交互区)必须明确指定 `cursor: pointer`。
|
||||
- **视觉反馈**:
|
||||
- Hover 时应伴随背景色微调、阴影加深或缩放效果 (`transition-all`)。
|
||||
- Active 时应有轻微的按下缩放效果 (`active:scale-[0.98]`),增强操作确认感。
|
||||
|
||||
---
|
||||
|
||||
## 5. 响应式与可访问性 (Responsive & Accessibility)
|
||||
|
||||
@@ -2,6 +2,8 @@ import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import PrimeVue from 'primevue/config';
|
||||
import Aura from '@primevue/themes/aura';
|
||||
import ToastService from 'primevue/toastservice';
|
||||
import ConfirmationService from 'primevue/confirmationservice';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
|
||||
@@ -12,6 +14,8 @@ const app = createApp(App);
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.use(ToastService);
|
||||
app.use(ConfirmationService);
|
||||
app.use(PrimeVue, {
|
||||
theme: {
|
||||
preset: Aura,
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
</router-link>
|
||||
|
||||
<!-- Card Variant 2: No Image (Text Only) -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5 hover:shadow-md transition-shadow group">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5 hover:shadow-md transition-all hover:border-primary-100 cursor-pointer active:scale-[0.99] group">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="px-1.5 py-0.5 rounded text-xs font-medium bg-green-50 text-green-600 border border-green-100">限免</span>
|
||||
<span class="text-xs text-slate-500 border border-slate-200 px-1 rounded">[昆曲]</span>
|
||||
@@ -102,17 +102,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Card Variant 3: 3 Images -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5 hover:shadow-md transition-shadow group">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 p-5 hover:shadow-md transition-all hover:border-primary-100 cursor-pointer active:scale-[0.99] group">
|
||||
<h3 class="text-lg font-bold text-slate-900 mb-3 group-hover:text-primary-600 transition-colors">[图集] 2024 新年京剧晚会后台探班:名角云集</h3>
|
||||
<div class="grid grid-cols-3 gap-2 mb-4">
|
||||
<div class="aspect-[4/3] rounded-lg overflow-hidden bg-slate-100">
|
||||
<img src="https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" class="w-full h-full object-cover hover:scale-105 transition-transform duration-500">
|
||||
<img src="https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
|
||||
</div>
|
||||
<div class="aspect-[4/3] rounded-lg overflow-hidden bg-slate-100">
|
||||
<img src="https://images.unsplash.com/photo-1533174072545-e8d4aa97edf9?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" class="w-full h-full object-cover hover:scale-105 transition-transform duration-500">
|
||||
<img src="https://images.unsplash.com/photo-1533174072545-e8d4aa97edf9?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
|
||||
</div>
|
||||
<div class="aspect-[4/3] rounded-lg overflow-hidden bg-slate-100 relative">
|
||||
<img src="https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" class="w-full h-full object-cover hover:scale-105 transition-transform duration-500">
|
||||
<img src="https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500">
|
||||
<div class="absolute bottom-2 right-2 px-1.5 py-0.5 bg-black/60 text-white text-xs rounded">+9</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,155 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-3xl my-12 bg-white rounded-xl shadow-sm border border-slate-100 p-8">
|
||||
<h1 class="text-2xl font-bold mb-4 text-center">Creator Application</h1>
|
||||
<p class="text-slate-500 text-center mb-8">Join us and start your creative journey.</p>
|
||||
<p class="text-slate-400 text-center">(Implementation pending based on PAGE_TENANT_APPLY.md)</p>
|
||||
<!-- Main Container -->
|
||||
<div class="mx-auto max-w-4xl my-8 bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden min-h-[600px]">
|
||||
|
||||
<!-- Step 1: Landing -->
|
||||
<div v-if="step === 1">
|
||||
<div class="relative h-[400px] w-full overflow-hidden">
|
||||
<img src="https://images.unsplash.com/photo-1522202176988-66273c2fd55f?ixlib=rb-1.2.1&auto=format&fit=crop&w=1200&q=80" class="w-full h-full object-cover">
|
||||
<div class="absolute inset-0 bg-black/40 flex items-center justify-center p-12">
|
||||
<div class="text-center text-white">
|
||||
<h1 class="text-4xl md:text-5xl font-bold mb-4 drop-shadow-lg">开启您的内容创作之旅</h1>
|
||||
<p class="text-xl md:text-2xl opacity-90 max-w-2xl mx-auto drop-shadow-md">加入我们,获得专属频道主页,通过优质戏曲内容获取收益,与百万戏迷互动。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-12">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
|
||||
<div class="p-6 bg-slate-50 rounded-xl text-center">
|
||||
<div class="w-12 h-12 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-2xl mx-auto mb-4"><i class="pi pi-wallet"></i></div>
|
||||
<h3 class="font-bold text-slate-900 mb-2">内容变现</h3>
|
||||
<p class="text-sm text-slate-500">灵活设置付费阅读与会员专栏,收益直接结算。</p>
|
||||
</div>
|
||||
<div class="p-6 bg-slate-50 rounded-xl text-center">
|
||||
<div class="w-12 h-12 bg-purple-100 text-purple-600 rounded-full flex items-center justify-center text-2xl mx-auto mb-4"><i class="pi pi-home"></i></div>
|
||||
<h3 class="font-bold text-slate-900 mb-2">专属频道</h3>
|
||||
<p class="text-sm text-slate-500">拥有独立的品牌展示空间,积累私域粉丝。</p>
|
||||
</div>
|
||||
<div class="p-6 bg-slate-50 rounded-xl text-center">
|
||||
<div class="w-12 h-12 bg-orange-100 text-orange-600 rounded-full flex items-center justify-center text-2xl mx-auto mb-4"><i class="pi pi-chart-bar"></i></div>
|
||||
<h3 class="font-bold text-slate-900 mb-2">数据管理</h3>
|
||||
<p class="text-sm text-slate-500">全方位的作品数据分析,助您优化创作方向。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<button @click="step = 2" class="px-12 py-4 bg-primary-600 text-white rounded-full font-bold text-xl hover:bg-primary-700 transition-colors shadow-lg shadow-primary-200 cursor-pointer active:scale-95">
|
||||
立即申请入驻
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Application Form -->
|
||||
<div v-else-if="step === 2" class="p-8 md:p-12">
|
||||
<h2 class="text-2xl font-bold text-slate-900 mb-8">填写申请资料</h2>
|
||||
|
||||
<div class="space-y-8 max-w-2xl mx-auto">
|
||||
<!-- Avatar -->
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="w-24 h-24 rounded-full bg-slate-100 flex items-center justify-center border-2 border-dashed border-slate-300 hover:border-primary-400 cursor-pointer transition-colors" @click="triggerUpload">
|
||||
<img v-if="form.avatar" :src="form.avatar" class="w-full h-full rounded-full object-cover">
|
||||
<i v-else class="pi pi-camera text-3xl text-slate-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900 mb-1">频道头像 <span class="text-red-500">*</span></div>
|
||||
<p class="text-sm text-slate-500">建议使用清晰的个人照或 Logo</p>
|
||||
</div>
|
||||
<input type="file" ref="fileInput" class="hidden" @change="handleFileChange">
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label class="block font-bold text-slate-900 mb-2">频道名称 <span class="text-red-500">*</span></label>
|
||||
<input v-model="form.name" type="text" class="w-full h-12 px-4 rounded-lg border border-slate-200 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all text-lg" placeholder="给您的频道起个名字">
|
||||
</div>
|
||||
|
||||
<!-- Bio -->
|
||||
<div>
|
||||
<label class="block font-bold text-slate-900 mb-2">频道介绍 <span class="text-red-500">*</span></label>
|
||||
<textarea v-model="form.bio" rows="3" class="w-full p-4 rounded-lg border border-slate-200 focus:border-primary-500 focus:ring-4 focus:ring-primary-100 outline-none transition-all text-base resize-none" placeholder="一句话介绍您的频道特色..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Agreement -->
|
||||
<div class="flex items-center pt-4">
|
||||
<input type="checkbox" id="agree" v-model="form.agreed" class="w-5 h-5 rounded border-slate-300 text-primary-600 focus:ring-primary-500 cursor-pointer">
|
||||
<label for="agree" class="ml-3 text-slate-600 cursor-pointer">
|
||||
我已阅读并同意 <a href="#" class="text-primary-600 hover:underline">《创作者入驻协议》</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="pt-6">
|
||||
<button
|
||||
@click="submitForm"
|
||||
class="w-full py-4 bg-primary-600 text-white rounded-xl font-bold text-xl hover:bg-primary-700 transition-all shadow-lg disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer active:scale-95"
|
||||
:disabled="!isValid"
|
||||
>
|
||||
{{ submitting ? '正在提交...' : '提交审核' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Result -->
|
||||
<div v-else-if="step === 3" class="p-20 text-center">
|
||||
<div class="w-20 h-20 bg-green-100 text-green-600 rounded-full flex items-center justify-center text-4xl mx-auto mb-6 animate-in zoom-in duration-300">
|
||||
<i class="pi pi-check"></i>
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold text-slate-900 mb-4">申请已提交</h2>
|
||||
<p class="text-lg text-slate-600 mb-12 max-w-lg mx-auto">您的入驻申请已成功提交,平台将在 1-3 个工作日内完成审核。审核结果将通过短信和系统通知发送给您。</p>
|
||||
|
||||
<div class="flex justify-center gap-4">
|
||||
<router-link to="/" class="px-8 py-3 bg-white border border-slate-300 text-slate-700 rounded-lg hover:bg-slate-50 font-medium">返回首页</router-link>
|
||||
<router-link to="/me" class="px-8 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 font-medium">查看个人中心</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
|
||||
const step = ref(1);
|
||||
const submitting = ref(false);
|
||||
const fileInput = ref(null);
|
||||
|
||||
const form = reactive({
|
||||
avatar: '',
|
||||
name: '',
|
||||
bio: '',
|
||||
agreed: false
|
||||
});
|
||||
|
||||
const isValid = computed(() => {
|
||||
return form.avatar && form.name && form.bio && form.agreed;
|
||||
});
|
||||
|
||||
const triggerUpload = () => {
|
||||
fileInput.value.click();
|
||||
};
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
form.avatar = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const submitForm = () => {
|
||||
if (!isValid.value) return;
|
||||
|
||||
submitting.value = true;
|
||||
// Simulate API
|
||||
setTimeout(() => {
|
||||
submitting.value = false;
|
||||
step.value = 3;
|
||||
}, 1500);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<button @click="$router.back()" class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-slate-100 text-slate-500 transition-colors">
|
||||
<button @click="$router.back()" class="w-10 h-10 flex items-center justify-center rounded-full hover:bg-slate-100 text-slate-500 transition-colors cursor-pointer active:scale-90">
|
||||
<i class="pi pi-arrow-left text-lg"></i>
|
||||
</button>
|
||||
<div>
|
||||
@@ -107,7 +107,7 @@
|
||||
<div>
|
||||
<h3 class="font-bold text-slate-900 text-lg mb-4 border-l-4 border-primary-500 pl-3">订单信息</h3>
|
||||
<div class="space-y-3 text-slate-600">
|
||||
<p>订单编号: <span class="text-slate-900 select-all">{{ order.id }}</span> <button class="text-primary-600 ml-2 hover:underline font-medium">复制</button></p>
|
||||
<p>订单编号: <span class="text-slate-900 select-all">{{ order.id }}</span> <button class="text-primary-600 ml-2 hover:underline font-medium cursor-pointer">复制</button></p>
|
||||
<p>创建时间: {{ order.createTime }}</p>
|
||||
<p v-if="order.payTime">付款时间: {{ order.payTime }}</p>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@
|
||||
<img :src="order.tenantAvatar" class="w-12 h-12 rounded-full">
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">{{ order.tenantName }}</div>
|
||||
<button class="text-primary-600 text-sm mt-1 hover:underline">联系商家</button>
|
||||
<button class="text-primary-600 text-sm mt-1 hover:underline cursor-pointer">联系商家</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,27 +2,29 @@
|
||||
<div class="mx-auto max-w-screen-xl px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<!-- Stat Cards -->
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4">
|
||||
<router-link to="/me/wallet" class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4 hover:shadow-md hover:border-primary-100 transition-all cursor-pointer">
|
||||
<div class="w-12 h-12 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center text-xl"><i class="pi pi-wallet"></i></div>
|
||||
<div>
|
||||
<div class="text-sm text-slate-500">账户余额</div>
|
||||
<div class="text-2xl font-bold text-slate-900">¥ 128.50</div>
|
||||
</div>
|
||||
<!-- <button class="ml-auto text-sm font-medium text-primary-600 hover:bg-primary-50 px-3 py-1.5 rounded transition-colors">充值</button> -->
|
||||
</div>
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-full bg-yellow-50 text-yellow-600 flex items-center justify-center text-xl"><i class="pi pi-star"></i></div>
|
||||
<i class="pi pi-chevron-right ml-auto text-slate-300 group-hover:text-primary-400"></i>
|
||||
</router-link>
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4 hover:shadow-md hover:border-primary-100 transition-all cursor-pointer group active:scale-[0.98]">
|
||||
<div class="w-12 h-12 rounded-full bg-yellow-50 text-yellow-600 flex items-center justify-center text-xl transition-transform group-hover:scale-110"><i class="pi pi-star"></i></div>
|
||||
<div>
|
||||
<div class="text-sm text-slate-500">我的积分</div>
|
||||
<div class="text-2xl font-bold text-slate-900">2,450</div>
|
||||
</div>
|
||||
<i class="pi pi-chevron-right ml-auto text-slate-300 group-hover:text-primary-400"></i>
|
||||
</div>
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-full bg-red-50 text-red-600 flex items-center justify-center text-xl"><i class="pi pi-ticket"></i></div>
|
||||
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-100 flex items-center gap-4 hover:shadow-md hover:border-primary-100 transition-all cursor-pointer group active:scale-[0.98]">
|
||||
<div class="w-12 h-12 rounded-full bg-red-50 text-red-600 flex items-center justify-center text-xl transition-transform group-hover:scale-110"><i class="pi pi-ticket"></i></div>
|
||||
<div>
|
||||
<div class="text-sm text-slate-500">优惠券</div>
|
||||
<div class="text-2xl font-bold text-slate-900">3 张</div>
|
||||
</div>
|
||||
<i class="pi pi-chevron-right ml-auto text-slate-300 group-hover:text-primary-400"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,16 +32,16 @@
|
||||
<div class="mt-8 bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-bold text-slate-900">最近订单</h2>
|
||||
<router-link to="/me/orders" class="text-sm text-primary-600 hover:text-primary-700 font-medium">查看全部 <i class="pi pi-angle-right"></i></router-link>
|
||||
<router-link to="/me/orders" class="text-sm text-primary-600 hover:text-primary-700 font-medium px-2 py-1 rounded hover:bg-primary-50 transition-colors">查看全部 <i class="pi pi-angle-right"></i></router-link>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-4 p-4 border border-slate-100 rounded-lg hover:border-slate-300 transition-colors cursor-pointer">
|
||||
<div @click="$router.push('/me/orders/82934712')" class="flex items-center gap-4 p-4 border border-slate-100 rounded-lg hover:border-primary-100 hover:shadow-sm transition-all cursor-pointer active:scale-[0.99] group">
|
||||
<div class="w-16 h-16 bg-slate-100 rounded object-cover flex-shrink-0">
|
||||
<img src="https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=100&q=60" class="w-full h-full object-cover rounded">
|
||||
<img src="https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=100&q=60" class="w-full h-full object-cover rounded transition-transform group-hover:scale-105">
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-bold text-slate-900 truncate">《霸王别姬》全本实录珍藏版</h3>
|
||||
<h3 class="font-bold text-slate-900 truncate group-hover:text-primary-600 transition-colors">《霸王别姬》全本实录珍藏版</h3>
|
||||
<div class="text-sm text-slate-500 mt-1">2025-12-24 14:30 · 订单号: 82934712</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
|
||||
@@ -1,6 +1,133 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">My Library</h1>
|
||||
<p class="text-slate-400">(Purchased content)</p>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900">已购内容</h1>
|
||||
<div class="flex gap-4">
|
||||
<select class="h-10 pl-3 pr-8 rounded-lg border border-slate-200 bg-white text-sm focus:border-primary-500 focus:outline-none cursor-pointer hover:border-primary-300 transition-colors">
|
||||
<option>全部类型</option>
|
||||
<option>视频</option>
|
||||
<option>音频</option>
|
||||
<option>图文专栏</option>
|
||||
</select>
|
||||
<div class="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-10 pl-9 pr-4 rounded-lg border border-slate-200 text-sm focus:border-primary-500 focus:outline-none w-48 transition-all focus:w-64">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content List (Vertical Stack) -->
|
||||
<div class="space-y-6">
|
||||
<div
|
||||
v-for="item in libraryItems"
|
||||
:key="item.id"
|
||||
@click="item.status !== 'offline' ? $router.push(`/contents/${item.id}`) : null"
|
||||
class="group relative bg-white border border-slate-200 rounded-xl overflow-hidden hover:shadow-md transition-all hover:border-primary-200 flex flex-col sm:flex-row"
|
||||
:class="item.status !== 'offline' ? 'cursor-pointer active:scale-[0.99]' : 'opacity-75 cursor-not-allowed'"
|
||||
>
|
||||
|
||||
<!-- Cover -->
|
||||
<div class="w-full sm:w-64 aspect-video bg-slate-100 relative overflow-hidden flex-shrink-0">
|
||||
<img :src="item.cover" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105" :class="{ 'grayscale opacity-70': item.status === 'expired' || item.status === 'offline' }">
|
||||
|
||||
<!-- Type Icon -->
|
||||
<div class="absolute bottom-2 left-2 w-8 h-8 bg-black/60 rounded-full flex items-center justify-center text-white backdrop-blur-sm">
|
||||
<i class="pi" :class="getTypeIcon(item.type)"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
<div class="p-6 flex flex-col flex-1 min-w-0">
|
||||
<div class="flex items-start justify-between gap-4 mb-2">
|
||||
<h3 class="font-bold text-slate-900 text-lg sm:text-xl line-clamp-2 group-hover:text-primary-600 transition-colors">{{ item.title }}</h3>
|
||||
<!-- Status Badges (Moved here for list layout) -->
|
||||
<span v-if="item.status === 'active'" class="flex-shrink-0 px-2 py-1 bg-green-100 text-green-700 text-xs font-bold rounded">永久有效</span>
|
||||
<span v-else-if="item.status === 'expiring'" class="flex-shrink-0 px-2 py-1 bg-orange-100 text-orange-700 text-xs font-bold rounded">剩余 3 天</span>
|
||||
<span v-else-if="item.status === 'expired'" class="flex-shrink-0 px-2 py-1 bg-slate-100 text-slate-500 text-xs font-bold rounded">已过期</span>
|
||||
<span v-else-if="item.status === 'offline'" class="flex-shrink-0 px-2 py-1 bg-red-100 text-red-600 text-xs font-bold rounded">已下架</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 text-sm text-slate-500 mb-4">
|
||||
<img :src="item.tenantAvatar" class="w-6 h-6 rounded-full">
|
||||
<span class="font-medium text-slate-700">{{ item.tenantName }}</span>
|
||||
<span class="w-1 h-1 bg-slate-300 rounded-full"></span>
|
||||
<span>{{ item.purchaseDate }} 购买</span>
|
||||
</div>
|
||||
|
||||
<!-- Action -->
|
||||
<div class="mt-auto pt-4 border-t border-slate-50 flex items-center justify-end">
|
||||
<button v-if="item.status === 'active' || item.status === 'expiring'" class="px-6 py-2 bg-primary-600 text-white text-base font-bold rounded-lg hover:bg-primary-700 transition-colors shadow-sm shadow-primary-200">
|
||||
立即阅读
|
||||
</button>
|
||||
<button v-else class="px-6 py-2 bg-slate-100 text-slate-400 text-base font-bold rounded-lg cursor-not-allowed">
|
||||
不可查看
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State (Hidden) -->
|
||||
<!-- <div class="flex flex-col items-center justify-center py-20">
|
||||
<div class="w-20 h-20 bg-slate-50 rounded-full flex items-center justify-center mb-4"><i class="pi pi-book text-3xl text-slate-300"></i></div>
|
||||
<p class="text-slate-500">暂无已购内容</p>
|
||||
<button class="mt-4 text-primary-600 font-medium hover:underline">去发现好内容</button>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
const getTypeIcon = (type) => {
|
||||
switch(type) {
|
||||
case 'video': return 'pi-video';
|
||||
case 'audio': return 'pi-volume-up';
|
||||
default: return 'pi-file';
|
||||
}
|
||||
};
|
||||
|
||||
// Mock Data
|
||||
const libraryItems = ref([
|
||||
{
|
||||
id: 1,
|
||||
title: '《锁麟囊》程派艺术解析专栏',
|
||||
cover: 'https://images.unsplash.com/photo-1514306191717-452ec28c7f31?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||
tenantName: '梅派传人小林',
|
||||
tenantAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Master1',
|
||||
purchaseDate: '2025-12-20',
|
||||
type: 'article',
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '京剧打击乐基础教程 (视频课)',
|
||||
cover: 'https://images.unsplash.com/photo-1533174072545-e8d4aa97edf9?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||
tenantName: '戏曲学院官方',
|
||||
tenantAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=School',
|
||||
purchaseDate: '2025-11-15',
|
||||
type: 'video',
|
||||
status: 'expiring'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '【已过期】2024 新年戏曲晚会直播回放',
|
||||
cover: 'https://images.unsplash.com/photo-1469571486292-0ba58a3f068b?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||
tenantName: 'CCTV 戏曲',
|
||||
tenantAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=TV',
|
||||
purchaseDate: '2025-01-01',
|
||||
type: 'video',
|
||||
status: 'expired'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '【已下架】某某名家访谈录',
|
||||
cover: 'https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?ixlib=rb-1.2.1&auto=format&fit=crop&w=300&q=60',
|
||||
tenantName: '未知用户',
|
||||
tenantAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Unknown',
|
||||
purchaseDate: '2024-12-10',
|
||||
type: 'audio',
|
||||
status: 'offline'
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
@@ -1,6 +1,175 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Notifications</h1>
|
||||
<p class="text-slate-400">(Message center)</p>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px]">
|
||||
<!-- Header & Tabs -->
|
||||
<div class="px-6 pt-6 border-b border-slate-100 flex items-center justify-between">
|
||||
<div class="flex items-center gap-8">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
@click="currentTab = tab.value"
|
||||
class="pb-4 text-sm font-medium transition-colors border-b-2 cursor-pointer focus:outline-none relative"
|
||||
:class="currentTab === tab.value ? 'text-primary-600 border-primary-600' : 'text-slate-500 border-transparent hover:text-slate-700'"
|
||||
>
|
||||
{{ tab.label }}
|
||||
<span v-if="tab.count > 0" class="absolute -top-1 -right-3 min-w-[1.25rem] h-5 px-1 bg-red-500 text-white text-xs rounded-full flex items-center justify-center scale-75">{{ tab.count }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<button class="mb-4 text-sm text-slate-500 hover:text-primary-600 cursor-pointer flex items-center gap-1">
|
||||
<i class="pi pi-check-circle"></i> 全部已读
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Notification List -->
|
||||
<div class="p-0">
|
||||
<div v-if="filteredNotifications.length > 0">
|
||||
<div
|
||||
v-for="item in filteredNotifications"
|
||||
:key="item.id"
|
||||
@click="handleNotificationClick(item)"
|
||||
class="flex items-start gap-4 p-5 border-b border-slate-50 hover:bg-slate-50 transition-colors cursor-pointer group"
|
||||
:class="{ 'bg-blue-50/30': !item.read }"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0" :class="getIconStyle(item.type).bg">
|
||||
<i class="pi" :class="[getIconStyle(item.type).icon, getIconStyle(item.type).color]"></i>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<h3 class="font-bold text-slate-900 text-sm group-hover:text-primary-600 transition-colors flex items-center gap-2">
|
||||
<span v-if="!item.read" class="w-2 h-2 bg-blue-600 rounded-full inline-block"></span>
|
||||
{{ item.title }}
|
||||
</h3>
|
||||
<span class="text-xs text-slate-400 whitespace-nowrap">{{ item.time }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-600 line-clamp-2">{{ item.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="text-center py-20">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-slate-50 mb-4">
|
||||
<i class="pi pi-bell-slash text-2xl text-slate-300"></i>
|
||||
</div>
|
||||
<p class="text-slate-500 text-lg">暂无消息通知</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Message Modal -->
|
||||
<Dialog v-model:visible="dialogVisible" modal :header="selectedNotification?.title" :style="{ width: '50rem' }" :breakpoints="{ '960px': '75vw', '641px': '90vw' }">
|
||||
<div class="p-4">
|
||||
<div class="text-slate-500 text-sm mb-4">{{ selectedNotification?.time }}</div>
|
||||
<div class="text-slate-700 leading-relaxed whitespace-pre-wrap">{{ selectedNotification?.content }}</div>
|
||||
<!-- Mock rich content / image -->
|
||||
<div v-if="selectedNotification?.id === 1" class="mt-4 p-4 bg-slate-50 rounded text-sm text-slate-500">
|
||||
(此处为富文本内容展示区,可能包含图片、链接等)
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="关闭" icon="pi pi-check" @click="dialogVisible = false" autofocus />
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import Button from 'primevue/button';
|
||||
|
||||
const router = useRouter();
|
||||
const currentTab = ref('all');
|
||||
const dialogVisible = ref(false);
|
||||
const selectedNotification = ref(null);
|
||||
|
||||
const tabs = [
|
||||
{ label: '全部', value: 'all', count: 3 },
|
||||
{ label: '系统通知', value: 'system', count: 1 },
|
||||
{ label: '订单通知', value: 'order', count: 1 },
|
||||
{ label: '审核通知', value: 'audit', count: 0 },
|
||||
{ label: '互动消息', value: 'interaction', count: 1 }
|
||||
];
|
||||
|
||||
const notifications = ref([
|
||||
{
|
||||
id: 1,
|
||||
type: 'system',
|
||||
title: '平台服务协议更新通知',
|
||||
content: '为了更好地保障您的权益,我们更新了《用户服务协议》和《隐私政策》,主要变更涉及账户安全与数据保护。\n\n具体变更内容如下:\n1. 明确了数据采集范围...\n2. 优化了账号注销流程...',
|
||||
time: '10分钟前',
|
||||
read: false,
|
||||
link: null
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'order',
|
||||
title: '订单支付成功',
|
||||
content: '您购买的《霸王别姬》全本实录珍藏版已支付成功,订单号:82934712,请前往已购内容查看。',
|
||||
time: '2小时前',
|
||||
read: false,
|
||||
link: '/me/orders/82934712'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'interaction',
|
||||
title: '收到新的评论回复',
|
||||
content: '梅派传人小林 回复了您的评论:“感谢您的支持,这版录音确实非常珍贵...”。',
|
||||
time: '昨天 14:30',
|
||||
read: false,
|
||||
link: '/contents/1'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'audit',
|
||||
title: '内容审核通过',
|
||||
content: '恭喜!您发布的文章《京剧脸谱赏析》已通过审核并发布上线。',
|
||||
time: '3天前',
|
||||
read: true,
|
||||
link: '/creator/contents'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'system',
|
||||
title: '春节期间服务调整公告',
|
||||
content: '春节期间(2月9日-2月17日),提现申请处理时效将有所延迟,敬请谅解。',
|
||||
time: '5天前',
|
||||
read: true,
|
||||
link: null
|
||||
}
|
||||
]);
|
||||
|
||||
const filteredNotifications = computed(() => {
|
||||
if (currentTab.value === 'all') return notifications.value;
|
||||
return notifications.value.filter(n => n.type === currentTab.value);
|
||||
});
|
||||
|
||||
const getIconStyle = (type) => {
|
||||
switch(type) {
|
||||
case 'system': return { bg: 'bg-blue-50', color: 'text-blue-600', icon: 'pi-megaphone' };
|
||||
case 'order': return { bg: 'bg-green-50', color: 'text-green-600', icon: 'pi-shopping-bag' };
|
||||
case 'audit': return { bg: 'bg-orange-50', color: 'text-orange-600', icon: 'pi-file-edit' };
|
||||
case 'interaction': return { bg: 'bg-purple-50', color: 'text-purple-600', icon: 'pi-comments' };
|
||||
default: return { bg: 'bg-slate-100', color: 'text-slate-500', icon: 'pi-bell' };
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotificationClick = (item) => {
|
||||
// 1. Mark as read
|
||||
item.read = true;
|
||||
|
||||
// 2. Handle System type separately
|
||||
if (item.type === 'system') {
|
||||
selectedNotification.value = item;
|
||||
dialogVisible.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Navigate if link exists
|
||||
if (item.link) {
|
||||
router.push(item.link);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
@click="currentTab = tab.value"
|
||||
class="pb-4 text-sm font-medium transition-colors border-b-2"
|
||||
class="pb-4 text-sm font-medium transition-colors border-b-2 cursor-pointer focus:outline-none"
|
||||
:class="currentTab === tab.value ? 'text-primary-600 border-primary-600' : 'text-slate-500 border-transparent hover:text-slate-700'"
|
||||
>
|
||||
{{ tab.label }}
|
||||
@@ -22,33 +22,33 @@
|
||||
<div class="flex justify-end mb-4">
|
||||
<div class="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 pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:border-primary-500">
|
||||
<input type="text" placeholder="搜索订单号或商品..." class="w-full pl-10 pr-4 py-2 border border-slate-200 rounded-lg text-sm focus:outline-none focus:border-primary-500 transition-colors">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List Items -->
|
||||
<div v-if="filteredOrders.length > 0" class="space-y-6">
|
||||
<div v-for="order in filteredOrders" :key="order.id" class="border border-slate-200 rounded-lg overflow-hidden hover:border-slate-300 transition-colors">
|
||||
<div v-for="order in filteredOrders" :key="order.id" class="border border-slate-200 rounded-lg overflow-hidden hover:border-slate-300 hover:shadow-sm transition-all group">
|
||||
<!-- Order Header -->
|
||||
<div class="bg-slate-50 px-4 py-3 flex items-center justify-between text-sm text-slate-500">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="font-medium text-slate-900">{{ order.date }}</span>
|
||||
<span>订单号: {{ order.id }}</span>
|
||||
<span>{{ order.tenantName }}</span>
|
||||
<span class="select-all">订单号: {{ order.id }}</span>
|
||||
<span class="hover:text-primary-600 cursor-pointer transition-colors">{{ order.tenantName }} <i class="pi pi-angle-right text-xs"></i></span>
|
||||
</div>
|
||||
<div class="font-bold" :class="statusColor(order.status)">{{ statusText(order.status) }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Body -->
|
||||
<div class="p-4 flex flex-col sm:flex-row gap-6">
|
||||
<!-- Product Info -->
|
||||
<div class="flex-1 flex gap-4">
|
||||
<div class="w-24 h-16 bg-slate-100 rounded object-cover flex-shrink-0 relative overflow-hidden">
|
||||
<!-- Product Info (Clickable Area) -->
|
||||
<div class="flex-1 flex gap-4 cursor-pointer" @click="$router.push(`/me/orders/${order.id}`)">
|
||||
<div class="w-24 h-16 bg-slate-100 rounded object-cover flex-shrink-0 relative overflow-hidden group-hover:opacity-90 transition-opacity">
|
||||
<img :src="order.cover" class="w-full h-full object-cover">
|
||||
<div v-if="order.type === 'video'" class="absolute inset-0 flex items-center justify-center bg-black/20 text-white"><i class="pi pi-play-circle"></i></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-slate-900 line-clamp-1 mb-1">{{ order.title }}</h3>
|
||||
<h3 class="font-bold text-slate-900 line-clamp-1 mb-1 group-hover:text-primary-600 transition-colors">{{ order.title }}</h3>
|
||||
<div class="text-xs text-slate-500 mb-2">{{ order.typeLabel }}</div>
|
||||
<div v-if="order.isVirtual" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 text-blue-600">虚拟发货</div>
|
||||
</div>
|
||||
@@ -61,10 +61,10 @@
|
||||
<div class="text-xs text-slate-400">在线支付</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button v-if="order.status === 'unpaid'" class="px-4 py-1.5 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700">去支付</button>
|
||||
<router-link :to="`/me/orders/${order.id}`" v-if="order.status === 'paid' || order.status === 'completed'" class="px-4 py-1.5 border border-slate-300 text-slate-700 text-sm font-medium rounded-lg hover:bg-slate-50 inline-block text-center">查看详情</router-link>
|
||||
<button v-if="order.status === 'completed'" class="px-4 py-1.5 text-primary-600 text-sm hover:underline">申请售后</button>
|
||||
<button v-if="order.status === 'unpaid'" class="text-xs text-slate-400 hover:text-slate-600">取消订单</button>
|
||||
<button v-if="order.status === 'unpaid'" class="px-4 py-1.5 bg-primary-600 text-white text-sm font-medium rounded-lg hover:bg-primary-700 transition-colors shadow-sm active:scale-95 cursor-pointer">去支付</button>
|
||||
<router-link :to="`/me/orders/${order.id}`" v-if="order.status === 'paid' || order.status === 'completed'" class="px-4 py-1.5 border border-slate-300 text-slate-700 text-sm font-medium rounded-lg hover:bg-slate-50 hover:border-slate-400 transition-colors inline-block text-center cursor-pointer active:scale-95">查看详情</router-link>
|
||||
<button v-if="order.status === 'completed'" class="px-4 py-1.5 text-primary-600 text-sm hover:underline cursor-pointer">申请售后</button>
|
||||
<button v-if="order.status === 'unpaid'" class="text-xs text-slate-400 hover:text-slate-600 cursor-pointer">取消订单</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,188 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Profile Settings</h1>
|
||||
<p class="text-slate-400">(Edit profile)</p>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900 mb-8">个人资料</h1>
|
||||
|
||||
<div class="max-w-2xl">
|
||||
<!-- Avatar Section -->
|
||||
<div class="flex items-center gap-6 mb-10">
|
||||
<div class="relative group cursor-pointer" @click="triggerUpload">
|
||||
<div class="w-24 h-24 rounded-full overflow-hidden border-2 border-slate-200 group-hover:border-primary-200 transition-colors">
|
||||
<img :src="form.avatar" class="w-full h-full object-cover">
|
||||
<!-- Overlay -->
|
||||
<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>
|
||||
<!-- Mock File Input -->
|
||||
<input type="file" ref="fileInput" class="hidden" @change="handleFileChange">
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900 text-lg mb-1">修改头像</div>
|
||||
<p class="text-sm text-slate-500">支持 JPG, PNG 格式,文件小于 5MB</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Section -->
|
||||
<div class="space-y-6">
|
||||
<!-- Nickname -->
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">昵称 <span class="text-red-500">*</span></label>
|
||||
<input
|
||||
v-model="form.nickname"
|
||||
type="text"
|
||||
class="w-full h-12 px-4 rounded-lg border border-slate-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-100 outline-none transition-all"
|
||||
placeholder="请输入昵称"
|
||||
>
|
||||
<p class="text-xs text-slate-400 mt-2">修改后需通过审核方可生效</p>
|
||||
</div>
|
||||
|
||||
<!-- Bio -->
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">个人简介</label>
|
||||
<textarea
|
||||
v-model="form.bio"
|
||||
rows="3"
|
||||
class="w-full p-4 rounded-lg border border-slate-200 focus:border-primary-500 focus:ring-2 focus:ring-primary-100 outline-none transition-all resize-none"
|
||||
placeholder="介绍一下你自己..."
|
||||
maxlength="100"
|
||||
></textarea>
|
||||
<div class="text-right text-xs text-slate-400 mt-1">{{ form.bio.length }}/100</div>
|
||||
</div>
|
||||
|
||||
<!-- Gender -->
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-3">性别</label>
|
||||
<div class="flex gap-6">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<RadioButton v-model="form.gender" value="male" />
|
||||
<span class="text-slate-700">男</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<RadioButton v-model="form.gender" value="female" />
|
||||
<span class="text-slate-700">女</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<RadioButton v-model="form.gender" value="secret" />
|
||||
<span class="text-slate-700">保密</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Birthday -->
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">生日</label>
|
||||
<DatePicker v-model="form.birthday" showIcon fluid iconDisplay="input" dateFormat="yy-mm-dd" placeholder="选择日期" />
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">所在省份</label>
|
||||
<Select v-model="form.province" :options="provinces" optionLabel="name" placeholder="选择省份" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">所在城市</label>
|
||||
<Select v-model="form.city" :options="cities" optionLabel="name" placeholder="选择城市" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Footer -->
|
||||
<div class="mt-10 pt-6 border-t border-slate-100">
|
||||
<Button
|
||||
label="保存修改"
|
||||
icon="pi pi-check"
|
||||
:loading="saving"
|
||||
@click="saveProfile"
|
||||
class="w-full sm:w-auto px-8 h-12 text-lg font-bold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Toast />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import RadioButton from 'primevue/radiobutton';
|
||||
import DatePicker from 'primevue/datepicker';
|
||||
import Select from 'primevue/select';
|
||||
import Button from 'primevue/button';
|
||||
import Toast from 'primevue/toast';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
const toast = useToast();
|
||||
const fileInput = ref(null);
|
||||
const saving = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix',
|
||||
nickname: 'Felix Demo',
|
||||
bio: '热爱戏曲,喜欢京剧程派艺术。',
|
||||
gender: 'male',
|
||||
birthday: null,
|
||||
province: null,
|
||||
city: null
|
||||
});
|
||||
|
||||
// Mock Location Data
|
||||
const provinces = [
|
||||
{ name: '北京', code: 'BJ' },
|
||||
{ name: '上海', code: 'SH' },
|
||||
{ name: '广东', code: 'GD' }
|
||||
];
|
||||
const cities = [
|
||||
{ name: '朝阳区', code: 'CY' },
|
||||
{ name: '海淀区', code: 'HD' }
|
||||
];
|
||||
|
||||
const triggerUpload = () => {
|
||||
fileInput.value.click();
|
||||
};
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
// Mock upload logic: Read as DataURL for preview
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
form.avatar = e.target.result;
|
||||
toast.add({ severity: 'success', summary: '头像已更新', detail: '新头像已预览,请点击保存生效', life: 3000 });
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
const saveProfile = () => {
|
||||
if (!form.nickname.trim()) {
|
||||
toast.add({ severity: 'error', summary: '错误', detail: '昵称不能为空', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true;
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
saving.value = false;
|
||||
toast.add({ severity: 'success', summary: '保存成功', detail: '个人资料已更新,部分信息正在审核中', life: 3000 });
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference "../../assets/main.css";
|
||||
|
||||
:deep(.p-select) {
|
||||
@apply h-12 flex items-center 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;
|
||||
}
|
||||
:deep(.p-datepicker-input) {
|
||||
@apply h-12 border-slate-200 rounded-lg shadow-none w-full;
|
||||
}
|
||||
:deep(.p-datepicker-input:focus) {
|
||||
@apply border-primary-500 ring-2 ring-primary-100;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,147 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">Account Security</h1>
|
||||
<p class="text-slate-400">(Password and bindings)</p>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900 mb-8">账号安全</h1>
|
||||
|
||||
<!-- Security Level (Optional visual) -->
|
||||
<div class="mb-10 p-6 bg-slate-50 rounded-xl border border-slate-100 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-lg font-bold text-slate-900 mb-1">安全等级:中</div>
|
||||
<p class="text-sm text-slate-500">建议绑定邮箱并完成实名认证,提升账号安全性。</p>
|
||||
</div>
|
||||
<div class="w-32 h-2 bg-slate-200 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-orange-500 w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Items -->
|
||||
<div class="space-y-6">
|
||||
<!-- Password -->
|
||||
<div class="flex items-center justify-between py-4 border-b border-slate-50">
|
||||
<div class="flex items-center gap-4">
|
||||
<i class="pi pi-lock text-xl text-slate-400"></i>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">登录密码</div>
|
||||
<div class="text-sm text-slate-500">已设置,建议定期更换</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="openVerify('password')" class="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg hover:bg-slate-50 text-sm font-medium transition-colors">修改</button>
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="flex items-center justify-between py-4 border-b border-slate-50">
|
||||
<div class="flex items-center gap-4">
|
||||
<i class="pi pi-mobile text-xl text-slate-400"></i>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">手机绑定</div>
|
||||
<div class="text-sm text-slate-500">已绑定:138****8888</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="openVerify('phone')" class="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg hover:bg-slate-50 text-sm font-medium transition-colors">更换</button>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="flex items-center justify-between py-4 border-b border-slate-50">
|
||||
<div class="flex items-center gap-4">
|
||||
<i class="pi pi-envelope text-xl text-slate-400"></i>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">邮箱绑定</div>
|
||||
<div class="text-sm text-slate-500">未绑定</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="openVerify('email')" class="px-4 py-2 text-primary-600 font-medium hover:text-primary-700 text-sm transition-colors">立即绑定</button>
|
||||
</div>
|
||||
|
||||
<!-- Real-name Auth -->
|
||||
<div class="flex items-center justify-between py-4 border-b border-slate-50">
|
||||
<div class="flex items-center gap-4">
|
||||
<i class="pi pi-id-card text-xl text-slate-400"></i>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">实名认证</div>
|
||||
<div class="text-sm text-slate-500">未认证,发布内容前需完成认证</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="$router.push('/creator/apply')" class="px-4 py-2 text-primary-600 font-medium hover:text-primary-700 text-sm transition-colors">去认证</button>
|
||||
</div>
|
||||
|
||||
<!-- Delete Account -->
|
||||
<div class="flex items-center justify-between py-4 pt-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<i class="pi pi-exclamation-circle text-xl text-red-400"></i>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">注销账号</div>
|
||||
<div class="text-sm text-slate-500">注销后账号及资产将无法找回,请谨慎操作</div>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="confirmDelete" class="px-4 py-2 bg-red-50 text-red-600 rounded-lg hover:bg-red-100 text-sm font-medium transition-colors">注销</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Verification Dialog -->
|
||||
<Dialog v-model:visible="verifyDialog" modal header="安全验证" :style="{ width: '25rem' }">
|
||||
<div class="text-sm text-slate-600 mb-6">为了您的账号安全,请先进行身份验证。</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-1">手机号</label>
|
||||
<div class="text-lg font-bold text-slate-900">138****8888</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-slate-500 mb-2">验证码</label>
|
||||
<div class="flex gap-2">
|
||||
<input type="text" class="flex-1 h-10 px-3 rounded border border-slate-200 focus:border-primary-500 focus:outline-none" placeholder="6位数字">
|
||||
<button class="px-3 h-10 border border-slate-200 rounded text-sm text-slate-600 hover:bg-slate-50">获取验证码</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-8">
|
||||
<button @click="verifyDialog = false" class="px-4 py-2 text-slate-500 hover:text-slate-700 text-sm">取消</button>
|
||||
<button @click="handleVerifySuccess" class="px-4 py-2 bg-primary-600 text-white rounded hover:bg-primary-700 text-sm">下一步</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog></ConfirmDialog>
|
||||
<Toast />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import Dialog from 'primevue/dialog';
|
||||
import ConfirmDialog from 'primevue/confirmdialog';
|
||||
import Toast from 'primevue/toast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
const verifyDialog = ref(false);
|
||||
const currentAction = ref('');
|
||||
|
||||
const openVerify = (action) => {
|
||||
currentAction.value = action;
|
||||
verifyDialog.value = true;
|
||||
};
|
||||
|
||||
const handleVerifySuccess = () => {
|
||||
verifyDialog.value = false;
|
||||
toast.add({ severity: 'success', summary: '验证通过', detail: '即将跳转至操作页面...', life: 2000 });
|
||||
// In real app, redirect to specific edit page
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
confirm.require({
|
||||
message: '注销后,您的所有数据、资产、购买记录将永久丢失且无法找回。请输入“注销”以确认。',
|
||||
header: '高风险操作确认',
|
||||
icon: 'pi pi-info-circle',
|
||||
rejectLabel: '取消',
|
||||
acceptLabel: '确认注销',
|
||||
rejectClass: 'p-button-secondary p-button-outlined',
|
||||
acceptClass: 'p-button-danger',
|
||||
accept: () => {
|
||||
toast.add({ severity: 'error', summary: '已提交注销申请', detail: '账号将在 7 天后正式注销', life: 3000 });
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +1,107 @@
|
||||
<template>
|
||||
<div class="p-8">
|
||||
<h1 class="text-2xl font-bold mb-4">My Wallet</h1>
|
||||
<p class="text-slate-400">(Balance and transactions)</p>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-slate-100 min-h-[600px] p-8">
|
||||
<h1 class="text-2xl font-bold text-slate-900 mb-8">我的钱包</h1>
|
||||
|
||||
<!-- Balance Card -->
|
||||
<div class="bg-gradient-to-r from-blue-600 to-blue-500 rounded-2xl p-8 text-white shadow-lg mb-10 relative overflow-hidden">
|
||||
<!-- Background Decor -->
|
||||
<div class="absolute -right-10 -bottom-10 w-48 h-48 bg-white/10 rounded-full blur-2xl"></div>
|
||||
|
||||
<div class="relative z-10 flex flex-col md:flex-row justify-between items-center gap-6">
|
||||
<div>
|
||||
<div class="text-blue-100 text-sm font-medium mb-1">账户余额 (元)</div>
|
||||
<div class="text-5xl font-bold font-mono">128.50</div>
|
||||
</div>
|
||||
<button @click="showRecharge = !showRecharge" class="px-8 py-3 bg-white text-blue-600 font-bold rounded-full shadow-sm hover:bg-blue-50 transition-all active:scale-95 cursor-pointer">
|
||||
立即充值
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recharge Section (Collapsible) -->
|
||||
<div v-if="showRecharge" class="mb-10 p-6 bg-slate-50 rounded-xl border border-slate-200 animate-in fade-in slide-in-from-top-4 duration-300">
|
||||
<h3 class="font-bold text-slate-900 mb-4">充值金额</h3>
|
||||
<div class="grid grid-cols-3 sm:grid-cols-4 gap-4 mb-6">
|
||||
<button
|
||||
v-for="amount in [10, 30, 50, 100, 200, 500]"
|
||||
:key="amount"
|
||||
@click="selectedAmount = amount; customAmount = ''"
|
||||
class="h-14 rounded-lg border-2 font-bold text-lg transition-all cursor-pointer active:scale-95"
|
||||
:class="selectedAmount === amount ? 'border-blue-600 bg-blue-50 text-blue-600' : 'border-slate-200 bg-white text-slate-600 hover:border-blue-300'"
|
||||
>
|
||||
{{ amount }}元
|
||||
</button>
|
||||
<div class="col-span-2 sm:col-span-2 relative">
|
||||
<input
|
||||
v-model="customAmount"
|
||||
@focus="selectedAmount = null"
|
||||
type="number"
|
||||
placeholder="自定义金额"
|
||||
class="w-full h-14 pl-4 pr-4 rounded-lg border-2 border-slate-200 focus:border-blue-600 focus:outline-none text-lg font-bold transition-colors"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="font-bold text-slate-900 mb-4">支付方式</h3>
|
||||
<div class="flex gap-4 mb-8">
|
||||
<button class="flex items-center gap-2 px-6 py-3 border-2 border-blue-600 bg-blue-50 text-blue-700 rounded-lg font-medium cursor-pointer active:scale-95 transition-transform">
|
||||
<i class="pi pi-wechat text-xl text-green-600"></i> 微信支付
|
||||
</button>
|
||||
<button class="flex items-center gap-2 px-6 py-3 border-2 border-slate-200 text-slate-600 rounded-lg hover:bg-white hover:border-slate-300 font-medium cursor-pointer active:scale-95 transition-all">
|
||||
<i class="pi pi-alipay text-xl text-blue-500"></i> 支付宝
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="w-full py-4 bg-blue-600 text-white rounded-xl font-bold text-lg hover:bg-blue-700 transition-all shadow-lg shadow-blue-200 cursor-pointer active:scale-[0.98]">
|
||||
确认支付 ¥ {{ displayAmount }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Transaction History -->
|
||||
<div>
|
||||
<h3 class="text-xl font-bold text-slate-900 mb-6 flex items-center gap-2">
|
||||
<i class="pi pi-history text-slate-400"></i> 交易明细
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div v-for="item in transactions" :key="item.id" class="flex items-center justify-between p-4 border-b border-slate-50 hover:bg-slate-50 rounded-lg transition-colors">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center" :class="item.type === 'income' ? 'bg-green-100 text-green-600' : 'bg-slate-100 text-slate-500'">
|
||||
<i class="pi" :class="item.type === 'income' ? 'pi-plus' : 'pi-minus'"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-slate-900">{{ item.title }}</div>
|
||||
<div class="text-xs text-slate-400">{{ item.date }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="font-mono font-bold text-lg" :class="item.type === 'income' ? 'text-green-600' : 'text-slate-900'">
|
||||
{{ item.type === 'income' ? '+' : '-' }} {{ item.amount.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State (Hidden for mock) -->
|
||||
<!-- <div class="text-center py-12 text-slate-400">暂无交易记录</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const showRecharge = ref(false);
|
||||
const selectedAmount = ref(50);
|
||||
const customAmount = ref('');
|
||||
|
||||
const displayAmount = computed(() => {
|
||||
return customAmount.value ? Number(customAmount.value).toFixed(2) : selectedAmount.value.toFixed(2);
|
||||
});
|
||||
|
||||
// Mock Data
|
||||
const transactions = ref([
|
||||
{ id: 1, title: '购买《霸王别姬》全本', date: '2025-12-24 14:30', amount: 9.90, type: 'expense' },
|
||||
{ id: 2, title: '账户充值', date: '2025-12-20 10:00', amount: 100.00, type: 'income' },
|
||||
{ id: 3, title: '购买周边商品', date: '2025-12-15 18:20', amount: 45.00, type: 'expense' },
|
||||
{ id: 4, title: '付费专栏订阅', date: '2025-12-10 09:15', amount: 19.90, type: 'expense' },
|
||||
]);
|
||||
</script>
|
||||
Reference in New Issue
Block a user