|
- <template>
- <view class="page">
- <!-- 头部 -->
- <view class="profile-header">
- <image class="header-bg" src="https://cdn.csybhelp.com/images/cytx/profile-bg.jpg" mode="aspectFill" />
- <view class="user-row">
- <view class="avatar-wrap" @tap="isLoggedIn && changeAvatar()">
- <image class="avatar" :src="userInfo?.avatar || 'https://cdn.csybhelp.com/images/cytx/default-avatar.jpg'"
- mode="aspectFill" />
- <view v-if="isLoggedIn" class="camera-icon">
- <u-icon name="camera-fill" size="20" color="#666" />
- </view>
- </view>
- <view class="info" @tap="!isLoggedIn && goLogin()">
- <view class="name">{{ displayName }}</view>
- <view v-if="isLoggedIn && userInfo?.patient?.patient_no" class="patient-no">{{ userInfo.patient.patient_no }}</view>
- </view>
- </view>
- </view>
-
- <!-- 驳回提示 -->
- <view v-if="isRejected" class="reject-tip" @tap="goRejectDetail">
- <u-icon name="warning-fill" size="20" color="#fa8c16" />
- <text class="reject-text">您提交的资料未通过审核,请点击查看详情并重新提交。</text>
- <u-icon name="arrow-right" size="16" color="#f5222d" />
- </view>
-
- <!-- 菜单区域 -->
- <view class="menu-section" :class="{ 'no-overlap': isRejected }">
- <view class="menu-item" @tap="goMyInfo">
- <view class="menu-icon">
- <u-icon name="file-text-fill" size="20" color="#0e63e3" />
- </view>
- <text class="text">我的资料</text>
- <text v-if="patientStatus === -1" class="extra draft">待提交</text>
- <text v-else-if="patientStatus === 0" class="extra pending">待审核</text>
- <text v-else-if="patientStatus === 1" class="extra authed">已通过</text>
- <text v-else-if="patientStatus === 2" class="extra rejected">已驳回</text>
- <u-icon name="arrow-right" size="16" color="#c0c4cc" />
- </view>
- <view class="menu-item" @tap="goTo('/pages/message/message')">
- <view class="menu-icon">
- <u-icon name="chat-fill" size="20" color="#fa8c16" />
- </view>
- <text class="text">消息中心</text>
- <u-badge v-if="unreadCount > 0" :value="unreadCount" :max="99" type="error" />
- <u-icon name="arrow-right" size="16" color="#c0c4cc" />
- </view>
- <view class="menu-item" @tap="goTo('/pages/verify/verify')">
- <view class="menu-icon">
- <u-icon name="account-fill" size="20" color="#52c41a" />
- </view>
- <text class="text">实名认证</text>
- <text v-if="isAuthed" class="extra authed">已认证</text>
- <text v-else class="extra link">去认证</text>
- <u-icon name="arrow-right" size="16" color="#c0c4cc" />
- </view>
- </view>
-
- <view class="menu-section">
- <view class="menu-item" @tap="goChangePhone">
- <view class="menu-icon">
- <u-icon name="phone-fill" size="20" color="#0e63e3" />
- </view>
- <text class="text">修改手机号</text>
- <u-icon name="arrow-right" size="16" color="#c0c4cc" />
- </view>
- <view class="menu-item" @tap="goTo('/pages/content/content?key=privacy_policy')">
- <view class="menu-icon">
- <u-icon name="lock-fill" size="20" color="#909399" />
- </view>
- <text class="text">隐私协议</text>
- <u-icon name="arrow-right" size="16" color="#c0c4cc" />
- </view>
- <view class="menu-item" @tap="goTo('/pages/content/content?key=about_us')">
- <view class="menu-icon">
- <u-icon name="info-circle-fill" size="20" color="#909399" />
- </view>
- <text class="text">关于我们</text>
- <u-icon name="arrow-right" size="16" color="#c0c4cc" />
- </view>
- </view>
-
- <button v-if="isLoggedIn" class="logout-btn" @tap="handleLogout">退出登录</button>
- <button v-else class="login-btn" @tap="goLogin">去登录</button>
- </view>
- </template>
-
- <script setup>
- import { ref, computed } from 'vue'
- import { onShow } from '@dcloudio/uni-app'
- import { get, post, upload } from '@/utils/request.js'
- import { getToken, getUserInfo, setUserInfo, clearAll } from '@/utils/cache.js'
-
- const userInfo = ref(getUserInfo())
- const isLoggedIn = ref(!!getToken())
- const unreadCount = ref(0)
-
- const isAuthed = computed(() => userInfo.value?.patient?.auth_status === 1)
- const patientStatus = computed(() => {
- const p = userInfo.value?.patient
- if (!p) return null
- return p.status
- })
- const isRejected = computed(() => patientStatus.value === 2)
-
- const displayName = computed(() => {
- if (!isLoggedIn.value) return '点击登录'
- if (isAuthed.value && userInfo.value?.patient?.name) return userInfo.value.patient.name
- return userInfo.value?.nickname || '微信用户'
- })
-
- onShow(() => {
- isLoggedIn.value = !!getToken()
- userInfo.value = getUserInfo()
- if (isLoggedIn.value) {
- fetchUserInfo()
- fetchUnreadCount()
- } else {
- unreadCount.value = 0
- }
- })
-
- const fetchUserInfo = async () => {
- try {
- const res = await get('/api/mp/userinfo')
- userInfo.value = res.data
- setUserInfo(res.data)
- } catch (e) {
- if (e?.code === 1009) {
- clearAll()
- userInfo.value = null
- }
- }
- }
-
- const fetchUnreadCount = async () => {
- try {
- const res = await get('/api/mp/unreadCount')
- unreadCount.value = res.data?.count || 0
- } catch (e) {}
- }
-
- const goTo = (url) => {
- uni.navigateTo({ url })
- }
-
- const goMyInfo = () => {
- if (!isAuthed.value) {
- uni.showToast({ title: '请先完成实名认证', icon: 'none' })
- return
- }
- uni.navigateTo({ url: '/pages/myinfo/myinfo' })
- }
-
- const goChangePhone = () => {
- if (!isAuthed.value) {
- uni.showToast({ title: '请先完成实名认证', icon: 'none' })
- return
- }
- uni.navigateTo({ url: '/pages/change-phone/change-phone' })
- }
-
- const goLogin = () => {
- uni.navigateTo({ url: '/pages/login/index' })
- }
-
- const goRejectDetail = () => {
- uni.navigateTo({ url: '/pages/myinfo/myinfo' })
- }
-
- const changeAvatar = () => {
- uni.chooseImage({
- count: 1,
- sizeType: ['compressed'],
- sourceType: ['album', 'camera'],
- success: async (chooseRes) => {
- let filePath = chooseRes.tempFilePaths[0]
- // 小程序端使用 cropImage 裁剪
- // #ifdef MP-WEIXIN
- try {
- const cropRes = await new Promise((resolve, reject) => {
- wx.cropImage({
- src: filePath,
- cropScale: '1:1',
- success: resolve,
- fail: reject
- })
- })
- filePath = cropRes.tempFilePath
- } catch (e) {
- // 用户取消裁剪
- return
- }
- // #endif
- try {
- uni.showLoading({ title: '上传中...' })
- const uploadRes = await upload('/api/mp/upload', { filePath, name: 'file' })
- if (!uploadRes.data?.url) throw { msg: '上传失败' }
- const avatarUrl = uploadRes.data.url
- await post('/api/mp/updateAvatar', { avatar: avatarUrl })
- if (userInfo.value) {
- userInfo.value.avatar = avatarUrl
- setUserInfo(userInfo.value)
- }
- uni.showToast({ title: '头像已更新', icon: 'success' })
- } catch (e) {
- if (e?.msg) uni.showToast({ title: e.msg, icon: 'none' })
- } finally {
- uni.hideLoading()
- }
- }
- })
- }
-
- const handleLogout = () => {
- uni.showModal({
- title: '提示',
- content: '确定退出登录吗?',
- success: (res) => {
- if (res.confirm) {
- clearAll()
- userInfo.value = null
- isLoggedIn.value = false
- uni.showToast({ title: '已退出', icon: 'success' })
- }
- }
- })
- }
- </script>
-
- <style lang="scss" scoped>
- .page {
- min-height: 100vh;
- background: #f4f4f5;
- }
-
- .profile-header {
- position: relative;
- padding: 220rpx 40rpx 60rpx;
- overflow: hidden;
- }
-
- .header-bg {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- }
-
- .user-row {
- display: flex;
- align-items: center;
- gap: 28rpx;
- position: relative;
- z-index: 1;
- }
-
- .avatar-wrap {
- position: relative;
- width: 120rpx;
- height: 120rpx;
- flex-shrink: 0;
- }
-
- .avatar {
- width: 120rpx;
- height: 120rpx;
- border-radius: 50%;
- background: rgba(255, 255, 255, 0.3);
- }
-
- .camera-icon {
- position: absolute;
- right: -4rpx;
- bottom: -4rpx;
- width: 40rpx;
- height: 40rpx;
- background: #fff;
- border-radius: 50%;
- font-size: 22rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
- }
-
- .info {
- .name {
- font-size: 40rpx;
- font-weight: 600;
- color: #fff;
- }
-
- .patient-no {
- font-size: 24rpx;
- color: rgba(255, 255, 255, 0.6);
- margin-top: 8rpx;
- }
- }
-
- .menu-section {
- margin: 24rpx 32rpx;
- background: #fff;
- border-radius: 8rpx;
- overflow: hidden;
- border: 1rpx solid #ebeef5;
-
- &:first-of-type {
- margin-top: -32rpx;
- position: relative;
- z-index: 1;
- }
- }
-
- .reject-tip {
- display: flex;
- align-items: center;
- margin: 0 32rpx;
- margin-top: 24rpx;
- margin-bottom: 24rpx;
- padding: 24rpx 28rpx;
- background: #fff2f0;
- border: 1rpx solid #ffccc7;
- border-radius: 8rpx;
- position: relative;
- z-index: 1;
-
- .reject-text {
- flex: 1;
- font-size: 26rpx;
- color: #f5222d;
- margin-left: 16rpx;
- line-height: 1.5;
- }
- }
-
- .reject-tip + .menu-section {
- margin-top: 0;
- }
-
- .menu-section.no-overlap {
- margin-top: 0 !important;
- }
-
- .menu-item {
- display: flex;
- align-items: center;
- padding: 28rpx 32rpx;
- border-bottom: 1rpx solid #ebeef5;
-
- &:last-child {
- border-bottom: none;
- }
-
- &:active {
- background: #f5f7fa;
- }
-
- .menu-icon {
- width: 40rpx;
- height: 40rpx;
- margin-right: 24rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
-
- .text {
- flex: 1;
- font-size: 30rpx;
- color: #303133;
- }
-
- .extra {
- font-size: 26rpx;
- color: #909399;
- margin-right: 16rpx;
-
- &.link {
- color: #0e63e3;
- }
-
- &.authed {
- color: #67c23a;
- }
-
- &.pending {
- color: #fa8c16;
- }
-
- &.rejected {
- color: #f5222d;
- }
-
- &.draft {
- color: #909399;
- }
- }
- }
-
- .login-btn {
- margin: 48rpx 32rpx;
- height: 80rpx;
- line-height: 80rpx;
- background: #fff;
- border: 2rpx solid #0e63e3;
- color: #0e63e3;
- border-radius: 8rpx;
- font-size: 30rpx;
-
- &::after {
- border: none;
- }
-
- &:active {
- background: #e8f0fe;
- }
- }
-
- .logout-btn {
- margin: 48rpx 32rpx;
- height: 80rpx;
- line-height: 80rpx;
- background: #fff;
- border: 2rpx solid #e6e6e6;
- color: #909399;
- border-radius: 8rpx;
- font-size: 30rpx;
-
- &::after {
- border: none;
- }
-
- &:active {
- background: #f5f5f5;
- }
- }
- </style>
|