This commit is contained in:
Your Name
2026-03-11 09:49:47 +08:00
parent 02ae537b4c
commit 38ad60f4bb
290 changed files with 36917 additions and 123 deletions
+414
View File
@@ -0,0 +1,414 @@
<template>
<div class="mine-page">
<!-- 顶部用户信息卡片 -->
<div class="user-card" @click="navigateTo('/pages/user/userinfo')">
<div class="user-header">
<image class="avatar" :src="userInfo.avatar" mode="aspectFill" />
<div class="user-info">
<text class="nickname">{{ userInfo.nickname || '未登录' }}</text>
<text class="vip-tag">VIP会员</text>
</div>
<div class="qr-code" @click.stop="showQRCode">
<uni-icons type="scan" size="20" color="#333"></uni-icons>
</div>
</div>
<div class="stat-row">
<div class="stat-item">
<text class="value">{{ userInfo.balance ||0}}</text>
<text class="label">钱包()</text>
</div>
<div class="stat-item">
<text class="value">{{ userInfo.points ||0}}</text>
<text class="label">积分</text>
</div>
<div class="stat-item">
<text class="value">{{ userInfo.coupons ||0}}</text>
<text class="label">优惠券</text>
</div>
</div>
</div>
<!-- 订单卡片 -->
<div class="order-card">
<div class="card-header">
<text class="title">我的订单</text>
<div class="more" @click="navigateTo('/pages/order/list')">
<text>查看全部</text>
<uni-icons type="right" size="14" color="#999"></uni-icons>
</div>
</div>
<div class="order-list">
<div
class="order-item"
v-for="(item, index) in orderTypes"
:key="index"
@click="navigateTo(item.path)"
>
<div class="icon-wrapper">
<image :src="item.image" mode="aspectFit" class="order-icon" />
<text v-if="item.count" class="count-badge">{{ item.count }}</text>
</div>
<text class="order-name">{{ item.name }}</text>
</div>
</div>
</div>
<!-- 快捷功能区 -->
<!-- <div class="quick-actions">
<div
class="action-item"
v-for="(item, index) in allServices.slice(0, 4)"
:key="index"
@click="navigateTo(item.path)"
>
<div class="action-icon" :class="item.class">
<uni-icons :type="item.icon" size="24" color="#fff"></uni-icons>
</div>
<text class="action-name">{{ item.name }}</text>
</div>
</div> -->
<!-- 服务菜单列表 -->
<div class="service-list">
<div
class="service-item"
v-for="(item, index) in allServices.slice(4)"
:key="index"
@click="navigateTo(item.path)"
>
<div class="left">
<div class="service-icon" :class="item.class">
<uni-icons :type="item.icon" size="20" color="#fff"></uni-icons>
</div>
<text class="service-name">{{ item.name }}</text>
</div>
<div class="right">
<text v-if="item.desc" class="desc">{{ item.desc }}</text>
<uni-icons type="right" size="14" color="#999"></uni-icons>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'profileB',
data() {
return {
userInfo: {
avatar: '',
nickname: '',
userId: '',
balance: '0',
points: '0',
coupons: '5'
},
orderTypes: [
{
name: '待付款',
image: '/static/images/my/payment.png',
path: '/pages/order/list?type=1',
count: 2
},
{
name: '待发货',
image: '/static/images/my/delivery.png',
path: '/pages/order/list?type=2',
count: 1
},
{
name: '待收货',
image: '/static/images/my/shipping.png',
path: '/pages/order/list?type=3',
count: 0
},
{
name: '待评价',
image: '/static/images/my/review.png',
path: '/pages/order/list?type=4',
count: 3
},
{
name: '退换/售后',
image: '/static/images/my/after-sale.png',
path: '/pages/order/list?type=5',
count: 0
}
],
allServices: [
{ name: '收藏夹', icon: 'star', path: '/pages/favorite/index', class: 'bg-red' },
{ name: '浏览记录', icon: 'eye', path: '/pages/history/index', class: 'bg-blue' },
{ name: '收货地址', icon: 'location', path: '/pages/address/list', class: 'bg-green' },
{ name: '优惠券', icon: 'gift', path: '/pages/coupon/list', class: 'bg-purple' },
{ name: '就诊卡', icon: 'folder-add', path: '/pages/Card/Card', class: 'bg-cyan' },
{ name: '设置', icon: 'gear', path: '/pages/user/userinfo', class: 'bg-gray' }// ,
// { name: '客服中心', icon: 'headphones', path: '/pages/service/index', class: 'bg-orange', desc: '在线客服' }
]
}
},
onShow() {
this.userInfo=uni.getStorageSync('userData')
},
methods: {
navigateTo(path) {
uni.navigateTo({ url: path })
},
showQRCode() {
// 显示二维码
}
}
}
</script>
<style lang="scss" scoped>
.mine-page {
min-height: 100vh;
background: #f5f6fa;
padding: 30rpx;
}
.user-card {
background: #fff;
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
.user-header {
display: flex;
align-items: center;
margin-bottom: 40rpx;
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
margin-right: 30rpx;
}
.user-info {
flex: 1;
.nickname {
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
display: block;
}
.vip-tag {
background: linear-gradient(90deg, #FFD700 0%, #FFA500 100%);
color: #fff;
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
display: inline-block;
}
}
.qr-code {
width: 72rpx;
height: 72rpx;
border-radius: 36rpx;
background: #f5f6fa;
display: flex;
align-items: center;
justify-content: center;
}
}
.stat-row {
display: flex;
padding-top: 30rpx;
border-top: 2rpx solid #f5f6fa;
.stat-item {
flex: 1;
text-align: center;
.value {
font-size: 40rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
display: block;
}
.label {
font-size: 24rpx;
color: #999;
}
}
}
}
.order-card {
background: #fff;
border-radius: 24rpx;
padding: 40rpx;
margin-bottom: 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.more {
display: flex;
align-items: center;
text {
font-size: 26rpx;
color: #999;
margin-right: 8rpx;
}
}
}
.order-list {
display: flex;
justify-content: space-between;
.order-item {
flex: 1;
text-align: center;
.icon-wrapper {
width: 100rpx;
height: 100rpx;
margin: 0 auto 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
.count-badge {
position: absolute;
top: -10rpx;
right: -10rpx;
background: #ff4d4f;
color: #fff;
font-size: 20rpx;
min-width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
border-radius: 16rpx;
padding: 0 8rpx;
}
.order-icon {
width: 56rpx;
height: 56rpx;
}
}
.order-name {
font-size: 26rpx;
color: #666;
}
}
}
}
.quick-actions {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 30rpx;
margin-bottom: 30rpx;
.action-item {
background: #fff;
border-radius: 24rpx;
padding: 30rpx 20rpx;
text-align: center;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
.action-icon {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin: 0 auto 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.action-name {
font-size: 26rpx;
color: #666;
}
}
}
.service-list {
background: #fff;
border-radius: 24rpx;
padding: 10rpx 30rpx;
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
.service-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 0;
border-bottom: 2rpx solid #f5f6fa;
&:last-child {
border-bottom: none;
}
.left {
display: flex;
align-items: center;
.service-icon {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
margin-right: 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
.service-name {
font-size: 28rpx;
color: #333;
}
}
.right {
display: flex;
align-items: center;
.desc {
font-size: 24rpx;
color: #999;
margin-right: 16rpx;
}
}
}
}
// 背景色
.bg-red { background: #ff4d4f; }
.bg-blue { background: #1890ff; }
.bg-green { background: #52c41a; }
.bg-purple { background: #722ed1; }
.bg-orange { background: #fa8c16; }
.bg-cyan { background: #13c2c2; }
.bg-pink { background: #eb2f96; }
.bg-gray { background: #666666; }
</style>
+490
View File
@@ -0,0 +1,490 @@
<template>
<view class="userinfo-container">
<!-- 顶部标题栏 -->
<!-- 加载状态 -->
<view v-if="loading" class="loading-state">
<text>加载中...</text>
</view>
<!-- 编辑表单 -->
<view v-else class="form-wrapper">
<!-- 头像 -->
<view class="form-section">
<view class="form-group">
<text class="form-label">头像</text>
<view class="avatar-container">
<image
:src="formData.avatar"
class="avatar-image"
mode="aspectFill"
/>
<view class="avatar-upload" @click="uploadAvatar">
<uni-icons type="upload-filled" size="14" color="#999" class="upload-icon"></uni-icons>
<text class="upload-text">更换头像</text>
</view>
</view>
</view>
</view>
<!-- 基本信息 -->
<view class="form-section">
<text class="section-title">基本信息</text>
<view class="form-group">
<text class="form-label">昵称</text>
<input
v-model="formData.nickname"
class="form-input"
placeholder="请输入昵称"
maxlength="50"
/>
</view>
<view class="form-group">
<text class="form-label">性别</text>
<view class="sex-group">
<view
class="sex-btn"
:class="{ active: formData.sex === 1}"
@click="formData.sex = 1"
>
</view>
<view
class="sex-btn"
:class="{ active: formData.sex === 2}"
@click="formData.sex = 2"
>
</view>
</view>
</view>
<view class="form-group">
<text class="form-label">年龄</text>
<input
v-model.number="formData.age"
class="form-input"
type="number"
placeholder="请输入年龄"
min="0"
max="150"
/>
</view>
</view>
<!-- 操作按钮 -->
<view class="button-group">
<button class="btn btn-submit" @click="submitForm" :disabled="submitting">
{{ submitting ? '保存中...' : '保存修改' }}
</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
const instance = getCurrentInstance();
// 2. 从实例中获取全局 $url
const $url = instance.appContext.config.globalProperties.$url;
// 数据
const loading = ref(false)
const submitting = ref(false)
// 表单数据
const formData = ref({
avatar: '',
patient_name: '',
phone: '',
nickname: '',
sex: 1,
age: ''
})
// 页面加载
onMounted(() => {
loadUserInfo()
})
// 加载用户信息
const loadUserInfo = async () => {
loading.value = true
try {
const res = await proxy.apiUrl({
url: '/api/user/info',
method: 'GET',
data: {}
})
if (res.code === 1) {
const userInfo = res.data
formData.value = {
avatar: userInfo.avatar || '',
patient_name: userInfo.patient_name || userInfo.nickname || '',
phone: userInfo.phone || '',
nickname: userInfo.nickname || '',
sex: userInfo.sex =='男'?1:2,
age: userInfo.age || ''
}
} else {
uni.showToast({ title: res.msg || '加载失败', icon: 'none' })
}
} catch (err) {
console.error('加载用户信息失败:', err)
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
}
}
// 上传头像
const uploadAvatar = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0]
try {
uni.showLoading({ title: '上传中...' })
// 获取token
const token = uni.getStorageSync('token')
// 调用后端上传接口
const uploadRes = await new Promise((resolve, reject) => {
uni.uploadFile({
url: $url+'/api/upload/image',
filePath: tempFilePath,
name: 'file',
header: {
token: token,
'content-type': 'multipart/form-data'
},
success: (res) => {
const data = JSON.parse(res.data)
resolve(data)
},
fail: (err) => {
reject(err)
}
})
})
uni.hideLoading()
if (uploadRes.code === 1) {
// 上传成功,更新头像
formData.value.avatar = uploadRes.data.uri || uploadRes.data
uni.showToast({ title: '头像上传成功', icon: 'success' })
} else {
uni.showToast({ title: uploadRes.msg || '上传失败', icon: 'none' })
}
} catch (err) {
uni.hideLoading()
console.error('上传头像失败:', err)
uni.showToast({ title: '上传失败', icon: 'none' })
}
},
fail: (err) => {
console.error('选择图片失败:', err)
uni.showToast({ title: '选择图片失败', icon: 'none' })
}
})
}
// 验证手机号
const validatePhone = (phone) => {
if (!phone) return true // 可选字段
return /^1[3-9]\d{9}$/.test(phone)
}
// 提交表单
const submitForm = async () => {
// 验证必填字段
if (!formData.value.patient_name) {
uni.showToast({ title: '姓名不能为空', icon: 'none' })
return
}
// 验证手机号格式
if (formData.value.phone && !validatePhone(formData.value.phone)) {
uni.showToast({ title: '手机号格式不正确', icon: 'none' })
return
}
submitting.value = true
try {
// 调用后端API更新用户信息
const res = await proxy.apiUrl({
url: '/api/user/setInfo',
method: 'POST',
data: {
avatar: formData.value.avatar,
phone: formData.value.phone,
nickname: formData.value.nickname,
sex: formData.value.sex,
age: formData.value.age,
patient_name: formData.value.patient_name
}
})
if (res.code === 1) {
// 更新本地存储
const userData = uni.getStorageSync('userData')
const updatedData = {
...userData,
avatar: formData.value.avatar,
phone: formData.value.phone,
nickname: formData.value.nickname,
sex: formData.value.sex,
age: formData.value.age,
patient_name: formData.value.patient_name,
diagnosis: {
...userData.diagnosis,
patient_name: formData.value.patient_name
}
}
uni.setStorageSync('userData', updatedData)
uni.showToast({ title: '保存成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({ title: res.msg || '保存失败', icon: 'none' })
}
} catch (err) {
console.error('保存失败:', err)
uni.showToast({ title: '保存失败', icon: 'none' })
} finally {
submitting.value = false
}
}
// 返回上一页
const goBack = () => {
uni.navigateBack()
}
</script>
<style scoped>
.userinfo-container {
min-height: 100vh;
background: #f5f5f5;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
background: #ffffff;
padding: 20rpx 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.header-back {
font-size: 40rpx;
color: #FFA500;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header-title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
flex: 1;
text-align: center;
}
.header-placeholder {
width: 60rpx;
}
.loading-state {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-size: 28rpx;
color: #999999;
}
.form-wrapper {
padding: 30rpx;
}
.form-section {
background: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #FFA500;
margin-bottom: 24rpx;
display: block;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.form-group {
margin-bottom: 24rpx;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 28rpx;
color: #333333;
font-weight: 500;
margin-bottom: 16rpx;
line-height: 1.5;
}
.form-input {
width: 100%;
padding: 20rpx;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
font-size: 28rpx;
line-height: 1.5;
color: #333333;
background: #ffffff;
box-sizing: border-box;
display: block;
height: auto;
min-height: 60rpx;
}
.form-input::placeholder {
color: #cccccc;
}
.avatar-container {
display: flex;
align-items: center;
gap: 20rpx;
}
.avatar-image {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: #f0f0f0;
}
.avatar-upload {
flex: 1;
padding: 20rpx;
border: 2rpx dashed #FFA500;
border-radius: 12rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
}
.upload-icon {
font-size: 40rpx;
}
.upload-text {
font-size: 24rpx;
color: #FFA500;
}
.sex-group {
display: flex;
gap: 20rpx;
}
.sex-btn {
flex: 1;
padding: 20rpx;
border: 2rpx solid #e8e8e8;
border-radius: 12rpx;
text-align: center;
font-size: 28rpx;
line-height: 1.5;
color: #666666;
background: #ffffff;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
min-height: 60rpx;
}
.sex-btn.active {
border-color: #FFA500;
background: #e6f7ff;
color: #FFA500;
font-weight: bold;
}
.button-group {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
margin-bottom: 40rpx;
}
.btn {
flex: 1;
padding: 20rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: bold;
border: none;
cursor: pointer;
transition: all 0.3s;
}
.btn:active {
transform: scale(0.98);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-cancel {
background: #f0f0f0;
color: #666666;
}
.btn-submit {
background: #FFA500;
color: #ffffff;
}
.btn-submit:active:not(:disabled) {
background: #0050b3;
}
</style>