This commit is contained in:
tangxinyue 2026-07-25 10:01:43 +08:00
parent 92bd9cce14
commit 24698f580d
1 changed files with 43 additions and 621 deletions

View File

@ -1,642 +1,64 @@
<template>
<div class="profile-page">
<div class="profile-card">
<div class="userInfo-box">
<!-- 头部信息区 -->
<div class="profile-header">
<div class="left-info">
<div class="avatar-box">
<el-avatar :size="80"
:src="userInfo.avater || userInfo.avatar || userInfo.avatar_url || userStore.avatarUrl || undefined">
{{ (userInfo.name || userInfo.username || userStore.username || 'U').charAt(0).toUpperCase() }}
</el-avatar>
</div>
<div class="info-content">
<div class="name-row">
<span class="name">{{ userInfo.name || userInfo.username || userStore.username || '用户' }}</span>
<div v-if="userStore.isVip" class="vip-tag">尊贵会员</div>
<div v-else class="normal-tag">普通用户</div>
</div>
<div class="email">
<span v-if="userStore.isVip">会员到期:{{ userInfo.vip_expire || userStore.membershipExpireAt || '生效中'
}}</span>
<span v-else>开通会员解锁全部功能</span>
</div>
</div>
</div>
<div class="right-works" @click="$router.push('/works')">
<img v-if="worksCount > 0" src="/src/public/profile/hasWorks.png" class="folder-icon" />
<img v-else src="/src/public/profile/folder.png" class="folder-icon" />
<span>我的作品</span>
</div>
</div>
<div>
<div class="about-box">
<div class="title">关于我们</div>
<p>我们是一群爱搞怪也爱帮忙的年轻人日常最爱做的事就是用微信模拟器帮朋友解决场面尴尬</p>
<!-- 数据统计区块 -->
<div class="stats-block">
<div class="stat-item" @click="$router.push('/works')">
<div class="stat-num text-gradient">{{ worksCount }}</div>
<div class="stat-label">作品数</div>
</div>
<div class="divider"></div>
<div class="stat-item" @click="dialogVisible = true">
<div class="stat-num text-gradient">{{ orders.length }}</div>
<div class="stat-label">订单数</div>
</div>
</div>
<p>我们不鼓励撒谎但我们支持用幽默化解小尴尬 每一个功能都做到真实不穿帮只为在你需要的时候帮你圆一个小小的场开一个无伤大雅的玩笑或者逗笑一个不开心的人</p>
</div>
<!-- 最近订单区块 -->
<div class="section-container">
<div class="section-title">
<div class="bar"></div>最近订单
</div>
<div class="order-content">
<div v-if="orders.length > 0" class="order-list">
<div v-for="o in orders.slice(0, 3)" :key="o.id" class="order-item">
<div class="order-left">
<span class="order-plan">{{ planLabel(o.goods_name) }}</span>
<span class="order-time">{{ formatDate(o.pay_time) }}</span>
</div>
<div class="order-right">
<span class="order-amount"><span style="font-size: 12px;">¥</span>{{ o.total_fee }}</span>
<!-- <el-tag :type="o.status === '已过期' ? 'info' : 'success'" size="small">
{{ statusLabel(o.status) }}
</el-tag> -->
</div>
</div>
</div>
<div v-else class="empty-state">
<img src="/src/public/profile/empty.png" class="empty-img" />
</div>
</div>
</div>
<!-- 退出登录按钮 -->
<button class="logout-btn" @click="handleLogout">退出登录</button>
<p>有点皮有点暖你的面子急救队随时在线</p>
</div>
<!-- 全部订单弹窗 -->
<BaseDialog class="order-dialog" v-model="dialogVisible" width="580px">
<div class="all-orders-dialog">
<div class="dialog-title">
<div class="bar"></div>全部订单
</div>
<div class="orders-list">
<div v-for="(o, index) in orders" :key="index" class="order-item">
<div class="order-left">
<span class="order-plan">{{ planLabel(o.goods_name) }}</span>
<span class="order-time">{{ formatDate(o.pay_time) }}</span>
</div>
<div class="order-right">
<span class="order-amount"><span class="symbol">¥</span>{{ o.total_fee }}</span>
<!-- <el-tag :type="o.status === '已过期' ? 'info' : 'success'" size="small">
{{ statusLabel(o.status) }}
</el-tag> -->
</div>
</div>
</div>
</div>
</BaseDialog>
<AppFooter />
</div>
<WarningFooter />
</template>
<script setup>
import { ref, reactive, onMounted, inject } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import api from '@/api'
import { useUserStore } from '@/stores/user'
import { checkUserVip } from '@/utils/appUtils'
import BaseDialog from '@/components/common/BaseDialog.vue'
import WarningFooter from '@/components/common/WarningFooter.vue'
import authService from '@/api/authService.js'
const router = useRouter()
const userStore = useUserStore()
const showPayment = inject('showPayment')
const userInfo = reactive({
id: null, username: '', email: '', avatar_url: '',
membership_expire_at: null, created_at: ''
})
const defaultOrder = []
const isVip = ref(false)
const dialogVisible = ref(false)
const worksCount = ref(1)
const ordersCount = ref(0)
const orders = ref(defaultOrder)
const productMap = ref({}) // productId -> productName
onMounted(async () => {
try {
const data = await userStore.fetchProfile()
if (data) {
Object.assign(userInfo, data)
}
} catch (e) { }
// userInfo
try {
const local = JSON.parse(localStorage.getItem('user_info') || '{}')
Object.assign(userInfo, local)
} catch (e) { }
loadStats();
getOrders();
})
async function getOrders() {
try {
const res = await authService.getOrder({ status: 2 })
if (res.code == 0 && res.data) {
console.log("订单列表", res.data)
orders.value = res.data
ordersCount.value = res.data.length
}
} catch (error) { }
}
async function loadStats() {
// try {
// const [wRes, oRes, pRes] = await Promise.all([
// api.works.list({ per_page: 1 }),
// api.payment.orders(),
// api.product.list().catch(() => []),
// ])
// // works
// if (Array.isArray(wRes)) worksCount.value = wRes.length
// else if (wRes.items) worksCount.value = wRes.total || wRes.items.length
// if (Array.isArray(oRes)) { orders.value = oRes; ordersCount.value = oRes.length }
// else if (oRes && oRes.data) { orders.value = oRes.data; ordersCount.value = oRes.data.length }
// // ID->
// if (Array.isArray(pRes)) {
// const map = {}
// pRes.forEach(item => { map[item.id] = item.name })
// productMap.value = map
// }
// } catch { }
}
function formatDate(d) {
if (!d) return '-'
// return new Date(d).toLocaleString('zh-CN')
return d
}
function planLabel(t) {
//
if (t && !isNaN(Number(t))) {
return productMap.value[t] || `商品#${t}`
}
//
return { monthly: '月付', quarterly: '季付', yearly: '年付' }[t] || t
}
function statusLabel(s) {
return { paid: '已支付', pending: '待支付', expired: '已过期', refunded: '已退款' }[s] || s
}
async function handleLogout() {
try { await api.auth.logout() } catch { }
localStorage.removeItem('token')
ElMessage.success('已退出登录')
router.push('/')
}
import AppFooter from '@/components/common/AppFooter.vue'
</script>
<style lang="scss" scoped>
.profile-page {
min-width: 800px;
width: 800px;
.about-box {
width: 1200px;
min-width: 1200px;
margin: 0 auto;
padding: 38px 0;
}
padding: 20px;
background: url('/src/public/about-bg.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
padding: 100px 56px 200px;
.profile-card {
background: #FFFFFF;
border-radius: 16px;
padding: 18px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
.userInfo-box {
background-color: #FAFAFA;
border-radius: 12px;
padding: 22px 30px 0;
margin-bottom: 16px;
}
}
.profile-header {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 24px;
&::after {
content: '';
background-color: #DFD9EA;
width: 100%;
height: 1px;
position: absolute;
bottom: 0;
left: 0;
z-index: 1;
transform: scaleY(0.5);
}
.left-info {
display: flex;
align-items: center;
gap: 30px;
.avatar-box {
width: 80px;
height: 80px;
border-radius: 50%;
overflow: hidden;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
.el-avatar {
font-size: 24px;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.info-content {
display: flex;
flex-direction: column;
.name-row {
display: flex;
align-items: center;
gap: 8px;
.name {
font-weight: 500;
font-size: 24px;
color: #1A1A1A;
line-height: 18px;
}
.vip-tag {
/* 尊贵会员黄色背景 */
font-weight: 400;
font-size: 14px;
color: #926900;
line-height: 14px;
font-family: Source Han Sans, Source Han Sans;
padding: 4px 6px;
border-radius: 4px;
background: #FFE8AA;
border: 1px solid #DAA00B;
}
.normal-tag {
/* 尊贵会员黄色背景 */
font-weight: 400;
font-size: 14px;
color: #7741D5;
line-height: 14px;
font-family: Source Han Sans, Source Han Sans;
padding: 4px 6px;
border-radius: 4px;
background: #E3D4FF;
border: 1px solid #B08EEC;
}
}
.email {
font-weight: 400;
font-size: 18px;
color: #767676;
line-height: 18px;
margin-top: 18px;
}
}
}
.right-works {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
cursor: pointer;
.folder-icon {
width: 30px;
height: 30px;
object-fit: contain;
}
span {
font-weight: 400;
font-size: 16px;
color: #767676;
line-height: 16px;
}
&:hover {
opacity: 0.8;
}
}
}
.stats-block {
display: flex;
align-items: center;
background: #F8F9FB;
border-radius: 8px;
height: 150px;
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
height: 122px;
border-radius: 11px;
gap: 6px;
&:hover {
background-color: #EDEDFE;
}
.stat-num {
font-size: 50px;
line-height: 60px;
font-weight: 500;
}
.stat-label {
font-weight: 400;
font-size: 18px;
color: #767676;
line-height: 18px;
}
}
.text-gradient {
background: linear-gradient(186deg, #A679F4 0%, #808AFC 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
.divider {
background: #DFD9EA;
width: 1px;
height: 100px;
transform: scalex(0.5);
margin: 0 12px;
}
}
.section-container {
margin-bottom: 13px;
.section-title {
display: flex;
align-items: center;
font-weight: 600;
font-size: 18px;
.title {
position: relative;
font-weight: 700;
font-size: 40px;
color: #1A1A1A;
line-height: 18px;
margin-bottom: 8px;
margin-left: 30px;
line-height: 46px;
margin-bottom: 88px;
.bar {
width: 4px;
height: 18px;
background: linear-gradient(215deg, #A679F4 7.14%, #778CF5 100%);
/* 紫色竖条 */
border-radius: 129px 129px 129px 129px;
margin-right: 12px;
&::after {
content: '';
display: inline-block;
width: 120px;
height: 33px;
background-image: url('@/public/index/decoration1.png');
background-size: 100%;
background-repeat: no-repeat;
position: absolute;
left: 26px;
bottom: -34px;
opacity: 50%;
}
}
.order-list {
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 7px 12px 45px;
&:last-child {
border-bottom: none;
}
.order-left {
display: flex;
flex-direction: column;
gap: 4px;
.order-plan {
font-weight: 400;
font-size: 16px;
color: #1A1A1A;
font-weight: 500;
line-height: 16px;
}
.order-time {
font-weight: 400;
font-size: 16px;
color: #AAAAAA;
line-height: 16px;
margin-top: 8px;
}
}
.order-right {
display: flex;
align-items: center;
gap: 12px;
.order-amount {
font-weight: 500;
color: #FF4D4F;
font-size: 18px;
}
}
}
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: 30px 0 10px;
.empty-img {
width: 150px;
object-fit: contain;
}
}
}
.logout-btn {
width: 100%;
height: 52px;
background: #FFECEC;
border: 1px solid #FF454E;
border-radius: 6px;
font-weight: 400;
font-size: 18px;
color: #FF454E;
line-height: 18px;
cursor: pointer;
outline: none;
transition: all 0.2s;
&:hover {
background: #FF454E;
color: #fff;
border: none;
}
}
.all-orders-dialog {
padding: 10px 3px;
.dialog-title {
display: flex;
align-items: center;
font-weight: 600;
font-size: 18px;
p {
width: 745px;
margin-top: 50px;
font-family: 'Source Han Sans', 'Microsoft YaHei', sans-serif;
font-size: 25px;
color: #1A1A1A;
line-height: 18px;
margin-bottom: 18px;
.bar {
width: 4px;
height: 18px;
background: linear-gradient(215deg, #A679F4 7.14%, #778CF5 100%);
border-radius: 2px;
margin-right: 12px;
}
}
.orders-list {
max-height: 500px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #E0E0E0;
border-radius: 4px;
}
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #F0F0F0;
&:last-child {
border-bottom: none;
}
.order-left {
display: flex;
flex-direction: column;
gap: 8px;
.order-plan {
font-weight: 500;
font-size: 16px;
color: #1A1A1A;
line-height: 16px;
}
.order-time {
font-weight: 400;
font-size: 14px;
color: #AAAAAA;
line-height: 14px;
}
}
.order-right {
display: flex;
align-items: center;
gap: 20px;
.order-amount {
font-weight: 600;
color: #FF4D4F;
font-size: 20px;
.symbol {
font-size: 14px;
margin-right: 2px;
}
}
.status-tag {
font-size: 13px;
padding: 4px 10px;
border-radius: 4px;
white-space: nowrap;
&.active {
color: #52C41A;
background: #F6FFED;
border: 1px solid #B7EB8F;
}
&.expired {
color: #BFBFBF;
background: #FAFAFA;
border: 1px solid #D9D9D9;
}
}
}
}
line-height: 37px;
font-weight: 500;
}
}
.all-orders-dialog {
height: 420px;
overflow: hidden;
overflow-y: auto;
:deep(.el-dialog) {
border-radius: 16px !important;
}
.order-item {
height: 60px;
border-bottom: none !important;
padding-left: 15px !important;
.order-time {
font-size: 16px !important;
}
.order-amount {
font-size: 18px !important;
.symbol {
font-size: 12px !important;
}
}
}
}
</style>
</style>