修改个人中心页面图片引用
This commit is contained in:
parent
24698f580d
commit
46fe97c589
|
|
@ -200,3 +200,12 @@
|
|||
[2026-07-25 09:57:25,049] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 09:57:25,504] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 09:57:26,180] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 10:06:37,515] [INFO] [unknow:0] GET 200 /
|
||||
[2026-07-25 10:06:37,710] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 10:06:37,803] [INFO] [D:\AAA-Code\flaunt-web\service\core\router\index.cjs:28] GET 200 /api/user?nonce=bf0ed8359fb3d1e4b17dcd8861d638f1&signature=060bef939839eb3c0e46eb4daf2c6f5b×tamp=1784945197
|
||||
[2026-07-25 10:06:37,805] [INFO] [D:\AAA-Code\flaunt-web\service\core\router\index.cjs:28] GET 200 /api/user/config?nonce=3764528cea8bdaf98099bd459a3e9d36&signature=34b517db3b7df01a9c7349c7a46ff02d×tamp=1784945197
|
||||
[2026-07-25 10:06:38,986] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 10:06:40,453] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 10:06:42,035] [INFO] [D:\AAA-Code\flaunt-web\service\core\router\index.cjs:28] GET 200 /api/user?nonce=57df84ed41a34157f3de139fa046d5f4&signature=c580ad55611e2949914ce98f0e4b68df×tamp=1784945202
|
||||
[2026-07-25 10:06:42,037] [INFO] [unknow:0] GET 200 /favicon.ico
|
||||
[2026-07-25 10:06:42,179] [INFO] [D:\AAA-Code\flaunt-web\service\core\router\index.cjs:28] GET 200 /api/order?nonce=180858c062a7d37f8a3bc5addc1f7c5f&signature=ed4d810b96feff3c2bcb8354b9396516&status=2×tamp=1784945202
|
||||
|
|
|
|||
|
|
@ -1,646 +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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
line-height: 37px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,64 +1,618 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="about-box">
|
||||
<div class="title">关于我们</div>
|
||||
<p>我们是一群爱搞怪、也爱帮忙的年轻人。日常最爱做的事,就是用微信模拟器帮朋友解决“场面尴尬”</p>
|
||||
<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="@/public/profile/hasworks.png" class="folder-icon" />
|
||||
<img v-else src="@/public/profile/folder.png" class="folder-icon" />
|
||||
<span>我的作品</span>
|
||||
</div>
|
||||
</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="@/public/profile/empty.png" class="empty-img" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<button class="logout-btn" @click="handleLogout">退出登录</button>
|
||||
</div>
|
||||
|
||||
<AppFooter />
|
||||
<!-- 全部订单弹窗 -->
|
||||
<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>
|
||||
</div>
|
||||
<WarningFooter />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppFooter from '@/components/common/AppFooter.vue'
|
||||
import { ref, reactive, onMounted, inject } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
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) { }
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.about-box {
|
||||
width: 1200px;
|
||||
min-width: 1200px;
|
||||
.profile-page {
|
||||
min-width: 800px;
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: url('/src/public/about-bg.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
padding: 100px 56px 200px;
|
||||
padding: 38px 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
font-weight: 700;
|
||||
font-size: 40px;
|
||||
color: #1A1A1A;
|
||||
line-height: 46px;
|
||||
margin-bottom: 88px;
|
||||
.profile-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 16px;
|
||||
padding: 18px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
|
||||
|
||||
&::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%;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
width: 745px;
|
||||
margin-top: 50px;
|
||||
font-family: 'Source Han Sans', 'Microsoft YaHei', sans-serif;
|
||||
font-size: 25px;
|
||||
.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;
|
||||
color: #1A1A1A;
|
||||
line-height: 37px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
margin-bottom: 8px;
|
||||
margin-left: 30px;
|
||||
|
||||
.bar {
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: linear-gradient(215deg, #A679F4 7.14%, #778CF5 100%);
|
||||
/* 紫色竖条 */
|
||||
border-radius: 129px 129px 129px 129px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
Loading…
Reference in New Issue