Files
trade-assistant/uni-app/src/pages/profile/profile.vue
T

490 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<view class="user-card">
<view class="avatar">{{ initials }}</view>
<view class="user-info">
<text class="user-name">{{ user.username || '未设置' }}</text>
<text class="user-phone">{{ user.phone || '未绑定手机' }}</text>
<view class="tier-badge" :class="user.tier">{{ tierLabel }}</view>
</view>
</view>
<view class="credit-card" v-if="user.tier !== 'guest'" @click="goCredits">
<view class="credit-left">
<text class="credit-label">可用次数</text>
<text class="credit-value">{{ creditBalance }}</text>
</view>
<view class="credit-right">
<text class="credit-buy">购买次数 </text>
</view>
</view>
<view class="section">
<view class="section-title">账号设置</view>
<view class="menu-item" v-if="user.tier !== 'guest'" @click="showProfileEdit = true">
<text class="menu-icon">📝</text>
<text class="menu-text">编辑资料</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" v-if="user.tier !== 'guest'" @click="showPassword = true">
<text class="menu-icon">🔒</text>
<text class="menu-text">修改密码</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goCredits">
<text class="menu-icon"></text>
<text class="menu-text">购买次数</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="section" v-if="user.tier !== 'guest'">
<view class="section-title">认证与发票</view>
<view class="menu-item" @click="goCertification">
<text class="menu-icon">🪪</text>
<text class="menu-text">实名认证</text>
<text class="menu-arrow"></text>
</view>
<view class="menu-item" @click="goInvoice">
<text class="menu-icon">🧾</text>
<text class="menu-text">发票管理</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="section">
<view class="section-title">其他</view>
<view class="menu-item" @click="goFeedback">
<text class="menu-icon">💬</text>
<text class="menu-text">意见反馈</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="section">
<view class="section-title">关于我们</view>
<view class="about-item">
<text class="about-label">版本</text>
<text class="about-value">1.0.0</text>
</view>
<view class="about-item" @click="goAgreement('privacy')">
<text class="about-label">隐私政策</text>
<text class="about-arrow"></text>
</view>
<view class="about-item" @click="goAgreement('terms')">
<text class="about-label">用户协议</text>
<text class="about-arrow"></text>
</view>
<view class="about-item">
<text class="about-label">ICP 备案</text>
<text class="about-value">京ICP备2026007249号-1</text>
</view>
<view class="about-item">
<text class="about-label">公安备案</text>
<text class="about-value">京公网安备11011502039545号</text>
</view>
<view class="about-copyright">© 2026 北京宇之然科技中心. 保留所有权利.</view>
</view>
<view class="logout-btn" v-if="user.tier !== 'guest'" @click="logout">退出登录</view>
<view class="login-btn-bottom" v-else @click="goLogin">登录 / 注册</view>
<!-- 编辑资料弹窗 -->
<view class="modal-overlay" v-if="showProfileEdit" @click="showProfileEdit = false">
<view class="popup-card" @click.stop>
<text class="popup-title">编辑资料</text>
<view class="form-field">
<text class="form-label">用户名</text>
<input class="form-input" v-model="editForm.username" placeholder="输入用户名" />
</view>
<view class="form-field">
<text class="form-label">邮箱</text>
<input class="form-input" v-model="editForm.email" placeholder="输入邮箱" type="email" />
</view>
<view class="popup-btns">
<text class="popup-btn cancel" @click="showProfileEdit = false">取消</text>
<text class="popup-btn confirm" @click="saveProfile">保存</text>
</view>
</view>
</view>
<!-- 修改密码弹窗 -->
<view class="modal-overlay" v-if="showPassword" @click="showPassword = false">
<view class="popup-card" @click.stop>
<text class="popup-title">修改密码</text>
<view class="form-field">
<text class="form-label">旧密码</text>
<input class="form-input" v-model="pwdForm.old" placeholder="输入旧密码" password />
</view>
<view class="form-field">
<text class="form-label">新密码</text>
<input class="form-input" v-model="pwdForm.new1" placeholder="输入新密码" password />
</view>
<view class="form-field">
<text class="form-label">确认新密码</text>
<input class="form-input" v-model="pwdForm.new2" placeholder="再次输入新密码" password />
</view>
<view class="popup-btns">
<text class="popup-btn cancel" @click="showPassword = false">取消</text>
<text class="popup-btn confirm" @click="changePwd">确认修改</text>
</view>
</view>
</view>
<AiAssistant />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { authApi, creditApi } from '@/utils/api.js'
import AiAssistant from '@/components/ai-assistant.vue'
import { STORAGE_KEYS, PAGES, TIER_LABELS } from '@/config.js'
const user = ref({})
const creditBalance = ref(0)
const showProfileEdit = ref(false)
const showPassword = ref(false)
const editForm = ref({ username: '', email: '' })
const pwdForm = ref({ old: '', new1: '', new2: '' })
const initials = computed(() => {
const name = user.value.username || user.value.phone || 'U'
return name.charAt(0).toUpperCase()
})
const tierLabel = computed(() => TIER_LABELS[user.value.tier] || '免费版')
const loadUser = async () => {
const token = uni.getStorageSync(STORAGE_KEYS.TOKEN)
if (!token) {
user.value = { tier: 'guest' }
return
}
try {
const res = await authApi.getUserInfo()
user.value = res
editForm.value = { username: res.username || '', email: res.email || '' }
} catch {
user.value = { tier: 'guest' }
}
try {
const cb = await creditApi.balance()
creditBalance.value = cb.balance || 0
} catch {
creditBalance.value = 0
}
}
const saveProfile = async () => {
try {
await authApi.updateProfile(editForm.value)
uni.showToast({ title: '保存成功', icon: 'success' })
showProfileEdit.value = false
loadUser()
} catch (e) {
uni.showToast({ title: e.message || '保存失败', icon: 'none' })
}
}
const changePwd = async () => {
if (!pwdForm.value.old || !pwdForm.value.new1) {
uni.showToast({ title: '请填写完整', icon: 'none' })
return
}
if (pwdForm.value.new1 !== pwdForm.value.new2) {
uni.showToast({ title: '两次新密码不一致', icon: 'none' })
return
}
if (pwdForm.value.new1.length < 6) {
uni.showToast({ title: '密码长度不少于6位', icon: 'none' })
return
}
try {
await authApi.changePassword(pwdForm.value.old, pwdForm.value.new1)
uni.showToast({ title: '密码修改成功', icon: 'success' })
showPassword.value = false
pwdForm.value = { old: '', new1: '', new2: '' }
} catch (e) {
uni.showToast({ title: e.message || '修改失败', icon: 'none' })
}
}
const goLogin = () => uni.navigateTo({ url: PAGES.LOGIN })
const goCredits = () => uni.navigateTo({ url: PAGES.CREDITS })
const goFeedback = () => uni.navigateTo({ url: PAGES.FEEDBACK })
const goAgreement = (type) => uni.navigateTo({ url: `/pages/agreement/${type}` })
const goCertification = () => uni.navigateTo({ url: PAGES.CERTIFICATION })
const goInvoice = () => uni.navigateTo({ url: PAGES.INVOICE })
const logout = () => {
uni.showModal({
title: '提示',
content: '确定退出登录?',
success: (res) => {
if (res.confirm) {
uni.removeStorageSync(STORAGE_KEYS.TOKEN)
uni.removeStorageSync(STORAGE_KEYS.REFRESH_TOKEN)
user.value = { tier: 'guest' }
}
},
})
}
onMounted(loadUser)
onShow(loadUser)
</script>
<style scoped>
.page {
padding: 20rpx;
background: #f5f5f5;
min-height: 100vh;
}
.user-card {
display: flex;
align-items: center;
background: linear-gradient(135deg, #1890ff, #096dd9);
border-radius: 16rpx;
padding: 40rpx;
margin-bottom: 20rpx;
}
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: rgba(255,255,255,0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #fff;
font-weight: bold;
margin-right: 24rpx;
}
.user-info {
flex: 1;
}
.user-name {
font-size: 32rpx;
color: #fff;
font-weight: 600;
display: block;
}
.user-phone {
font-size: 24rpx;
color: rgba(255,255,255,0.8);
display: block;
margin-top: 6rpx;
}
.tier-badge {
display: inline-block;
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 20rpx;
margin-top: 10rpx;
}
.tier-badge.free { background: #e8f5e9; color: #2e7d32; }
.tier-badge.pro { background: #fff3e0; color: #e65100; }
.tier-badge.enterprise { background: #e3f2fd; color: #1565c0; }
.tier-badge.guest { background: #fce4ec; color: #c62828; }
.credit-card {
background: linear-gradient(135deg, #f97316, #ea580c);
border-radius: 16rpx;
padding: 28rpx 32rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.credit-left { display: flex; flex-direction: column; }
.credit-label { font-size: 24rpx; color: rgba(255,255,255,0.8); }
.credit-value { font-size: 48rpx; color: #fff; font-weight: bold; }
.credit-right { }
.credit-buy { font-size: 28rpx; color: #fff; font-weight: 500; }
.section {
background: #fff;
border-radius: 16rpx;
margin-bottom: 20rpx;
overflow: hidden;
}
.section-title {
padding: 24rpx 30rpx 0;
font-size: 26rpx;
color: #999;
font-weight: 500;
}
.menu-item {
display: flex;
align-items: center;
padding: 28rpx 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.menu-item:last-child { border-bottom: none; }
.menu-icon {
font-size: 32rpx;
margin-right: 20rpx;
}
.menu-text {
flex: 1;
font-size: 28rpx;
color: #333;
}
.menu-arrow {
font-size: 36rpx;
color: #ccc;
}
.menu-value {
font-size: 26rpx;
color: #999;
}
.logout-btn {
margin: 40rpx 0;
height: 88rpx;
line-height: 88rpx;
text-align: center;
background: #fff;
border-radius: 16rpx;
font-size: 30rpx;
color: #1890ff;
font-weight: 500;
border: 2rpx solid #e0e0e0;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
}
.login-btn-bottom {
margin: 40rpx 0;
height: 88rpx;
line-height: 88rpx;
text-align: center;
background: linear-gradient(135deg, #1890ff, #096dd9);
border-radius: 16rpx;
font-size: 30rpx;
color: #fff;
font-weight: 500;
box-shadow: 0 4rpx 12rpx rgba(24,144,255,0.3);
}
.modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
}
.popup-card {
width: 560rpx;
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
}
.popup-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
display: block;
margin-bottom: 30rpx;
text-align: center;
}
.form-field {
margin-bottom: 24rpx;
}
.form-label {
font-size: 26rpx;
color: #666;
display: block;
margin-bottom: 8rpx;
}
.form-input {
height: 72rpx;
background: #f5f5f5;
border-radius: 12rpx;
padding: 0 20rpx;
font-size: 28rpx;
width: 100%;
box-sizing: border-box;
}
.popup-btns {
display: flex;
gap: 20rpx;
margin-top: 30rpx;
}
.popup-btn {
flex: 1;
height: 72rpx;
line-height: 72rpx;
text-align: center;
border-radius: 12rpx;
font-size: 28rpx;
}
.popup-btn.cancel {
background: #f5f5f5;
color: #666;
}
.popup-btn.confirm {
background: #1890ff;
color: #fff;
}
.about-item {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.about-item:last-child { border-bottom: none; }
.about-label {
flex: 1;
font-size: 26rpx;
color: #666;
}
.about-value {
font-size: 24rpx;
color: #999;
}
.about-arrow {
font-size: 32rpx;
color: #ccc;
}
.about-copyright {
text-align: center;
padding: 24rpx 30rpx;
font-size: 22rpx;
color: #bbb;
}
</style>