Files
medical-mall/pages/mall/consumer/settings.uvue

698 lines
15 KiB
Plaintext
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-root">
<scroll-view class="settings-scroll" direction="vertical" :scroll-y="true">
<view class="page-inner">
<view class="section-wrap account-section">
<text class="section-title">管理我的账户</text>
<view class="account-card" @click="goToProfile">
<image class="account-avatar" :src="userAvatar" mode="aspectFill" />
<view class="account-main">
<view class="account-name-row">
<text class="account-name">{{ getDisplayName() }}</text>
<text class="account-badge">当前登录</text>
</view>
<text class="account-subline">{{ getAccountSubtitle() }}</text>
</view>
<view class="account-edit" @click.stop="goToProfile">
<text class="edit-icon">✎</text>
</view>
</view>
</view>
<view class="section-wrap">
<text class="section-title">用户设置</text>
<view class="menu-card">
<view class="menu-row" @click="goToAddressList">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-address">
<image class="menu-icon-image icon-address" src="/static/consumer/location.png" mode="aspectFit" />
</view>
<text class="menu-title">我的收货地址</text>
</view>
<view class="menu-right">
<text class="menu-right-desc">管理我的地址</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="menu-row" @click="openAccountSecurity">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-security">
<image class="menu-icon-image icon-security" src="/static/consumer/setting.png" mode="aspectFit" />
</view>
<text class="menu-title">账户与安全</text>
</view>
<view class="menu-right">
<text class="menu-arrow"></text>
</view>
</view>
</view>
</view>
<view class="section-wrap">
<text class="section-title">功能设置</text>
<view class="menu-card">
<view class="menu-row" @click="openPrivacySettings">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-privacy">
<image class="menu-icon-image icon-privacy" src="/static/consumer/privacy_setting.png" mode="aspectFit" />
</view>
<text class="menu-title">隐私设置</text>
</view>
<view class="menu-right">
<text class="menu-arrow"></text>
</view>
</view>
<view class="menu-row" @click="openDefaultHomeSettings">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-home">
<image class="menu-icon-image icon-home" src="/static/consumer/default_index.png" mode="aspectFit" />
</view>
<text class="menu-title">默认主页</text>
</view>
<view class="menu-right">
<text class="menu-arrow"></text>
</view>
</view>
<view class="menu-row" @click="openSeniorVersionSettings">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-senior">
<image class="menu-icon-image icon-senior" src="/static/consumer/change.png" mode="aspectFit" />
</view>
<text class="menu-title">长辈版本</text>
</view>
<view class="menu-right">
<text class="menu-right-desc">未开启</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="menu-row" @click="contactService">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-service">
<image class="menu-icon-image icon-service" src="/static/consumer/bell.png" mode="aspectFit" />
</view>
<text class="menu-title">联系客服</text>
</view>
<view class="menu-right">
<text class="menu-right-desc">在线客服</text>
<text class="menu-arrow"></text>
</view>
</view>
<view class="menu-row menu-row-last" @click="feedback">
<view class="menu-left">
<view class="menu-icon-shell menu-icon-shell-feedback">
<image class="menu-icon-image icon-feedback" src="/static/consumer/feedback.png" mode="aspectFit" />
</view>
<text class="menu-title">建议反馈</text>
</view>
<view class="menu-right">
<text class="menu-arrow"></text>
</view>
</view>
</view>
</view>
<view class="logout-wrap">
<button class="logout-button" @click="showLogoutConfirm">退出登录</button>
</view>
<view class="bottom-safe"></view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import { onBackPress, onShow } from '@dcloudio/uni-app'
import supa from '@/components/supadb/aksupainstance.uts'
import { goToLogin } from '@/utils/utils.uts'
import { logout as logoutStore } from '@/utils/store.uts'
type UserInfoLite = {
id: string
phone: string | null
email: string | null
nickname: string | null
avatar_url: string | null
}
const userInfo = ref<UserInfoLite>({
id: '',
phone: null,
email: null,
nickname: null,
avatar_url: null
})
const userAvatar = ref<string>('/static/consumer/defaul_picture.png')
const isLoggingOut = ref<boolean>(false)
onBackPress((_options): boolean => {
uni.switchTab({
url: '/pages/main/profile'
})
return true
})
const createEmptyUserInfo = (): UserInfoLite => {
return {
id: '',
phone: null,
email: null,
nickname: null,
avatar_url: null
}
}
const normalizeAvatar = (avatarUrl: string | null): string => {
if (avatarUrl == null || avatarUrl == '') {
return '/static/consumer/defaul_picture.png'
}
return avatarUrl
}
const loadUserInfo = (): void => {
const storedUser = uni.getStorageSync('userInfo')
if (storedUser != null && storedUser != '') {
let rawUser: UTSJSONObject | null = null
if (storedUser instanceof UTSJSONObject) {
rawUser = storedUser as UTSJSONObject
} else if (typeof storedUser == 'string') {
const storedText = storedUser as string
if (storedText != '') {
try {
rawUser = JSON.parse(storedText) as UTSJSONObject
} catch (e) {
console.error('[settings] 解析 userInfo 失败', e)
}
}
} else {
rawUser = JSON.parse(JSON.stringify(storedUser)) as UTSJSONObject
}
if (rawUser != null) {
const nextInfo: UserInfoLite = {
id: rawUser.getString('id') ?? '',
phone: rawUser.getString('phone'),
email: rawUser.getString('email'),
nickname: rawUser.getString('nickname'),
avatar_url: rawUser.getString('avatar_url')
}
userInfo.value = nextInfo
userAvatar.value = normalizeAvatar(nextInfo.avatar_url)
return
}
}
userInfo.value = createEmptyUserInfo()
userAvatar.value = '/static/consumer/defaul_picture.png'
}
const clearAuthStorage = (): void => {
const keys: Array<string> = [
'userInfo',
'user_id',
'access_token',
'refresh_token',
'token',
'currentUser',
'current_user',
'user',
'auth_user',
'supabase.auth.token'
]
for (let i = 0; i < keys.length; i++) {
try {
uni.removeStorageSync(keys[i])
} catch (e) {
console.error('[settings] 清理登录态失败', keys[i], e)
}
}
}
const resetLocalUserInfo = (): void => {
userInfo.value = createEmptyUserInfo()
userAvatar.value = '/static/consumer/defaul_picture.png'
}
const getStoredUserId = (): string => {
if (userInfo.value.id != null && userInfo.value.id != '') {
return userInfo.value.id
}
const cachedId = uni.getStorageSync('user_id') as string | null
if (cachedId != null && cachedId != '') {
return cachedId
}
return ''
}
const maskPhone = (phone: string): string => {
if (phone == null || phone == '') {
return ''
}
if (phone.length < 7) {
return phone
}
return phone.substring(0, 3) + '****' + phone.substring(phone.length - 4)
}
const shortUserId = (id: string): string => {
if (id == null || id == '') {
return ''
}
if (id.length <= 12) {
return id
}
return id.substring(0, 7) + '...' + id.substring(id.length - 5)
}
const getDisplayName = (): string => {
const nickname = userInfo.value.nickname
if (nickname != null && nickname.trim() != '') {
return nickname.trim()
}
return '用户'
}
const getAccountSubtitle = (): string => {
const phone = userInfo.value.phone
if (phone != null && phone != '') {
return '手机号:' + maskPhone(phone)
}
const userId = userInfo.value.id
if (userId != null && userId != '') {
return '账号ID' + shortUserId(userId)
}
return '账号信息待完善'
}
const showComingSoonToast = (): void => {
uni.showToast({
title: '功能建设中',
icon: 'none'
})
}
const goToProfile = (): void => {
uni.navigateTo({
url: '/pages/user/profile'
})
}
const goToAddressList = (): void => {
const userId = getStoredUserId()
if (userId == '') {
goToLogin('/pages/mall/consumer/address-list')
return
}
uni.navigateTo({
url: '/pages/mall/consumer/address-list'
})
}
const openAccountSecurity = (): void => {
showComingSoonToast()
}
const openPrivacySettings = (): void => {
showComingSoonToast()
}
const openDefaultHomeSettings = (): void => {
showComingSoonToast()
}
const openSeniorVersionSettings = (): void => {
showComingSoonToast()
}
const contactService = (): void => {
const userId = getStoredUserId()
if (userId == '') {
goToLogin('/pages/mall/consumer/chat')
return
}
uni.navigateTo({
url: '/pages/mall/consumer/chat'
})
}
const feedback = (): void => {
showComingSoonToast()
}
const executeLogout = async (): Promise<void> => {
if (isLoggingOut.value) {
return
}
isLoggingOut.value = true
uni.showLoading({
title: '正在退出登录...'
})
try {
logoutStore()
try {
await supa.signOut()
} catch (signOutError) {
console.error('[settings] supa.signOut failed:', signOutError)
}
clearAuthStorage()
resetLocalUserInfo()
uni.hideLoading()
uni.showToast({
title: '退出成功',
icon: 'success',
duration: 1200
})
uni.$emit('authChanged', { loggedIn: false })
setTimeout((): void => {
uni.switchTab({
url: '/pages/main/profile'
})
}, 1000)
} catch (e) {
console.error('[settings] 退出登录失败', e)
uni.hideLoading()
uni.showToast({
title: '退出失败,请稍后重试',
icon: 'none',
duration: 1500
})
} finally {
isLoggingOut.value = false
}
}
const showLogoutConfirm = (): void => {
if (isLoggingOut.value) {
return
}
uni.showModal({
title: '退出登录',
content: '确定要退出当前账号吗?',
confirmText: '退出',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
executeLogout()
}
}
})
}
onMounted(() => {
loadUserInfo()
})
onShow(() => {
loadUserInfo()
})
</script>
<style scoped>
.page-root {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
background-color: #f5f5f7;
}
.settings-scroll {
flex: 1;
height: 0;
width: 100%;
background-color: #f5f5f7;
}
.page-inner {
width: 100%;
min-height: 100%;
padding: 14rpx 0 24rpx 0;
box-sizing: border-box;
}
.section-wrap {
margin-bottom: 14rpx;
}
.account-section {
margin-bottom: 18rpx;
}
.section-title {
display: block;
padding: 0 16px;
margin-bottom: 7px;
font-size: 13px;
color: #999999;
line-height: 18px;
}
.account-card {
margin: 0 16px;
padding: 14px;
border-radius: 14px;
background-color: #ffffff;
display: flex;
flex-direction: row;
align-items: center;
box-sizing: border-box;
overflow: hidden;
}
.account-avatar {
width: 52px;
height: 52px;
border-radius: 26px;
background-color: #edf0f3;
flex-shrink: 0;
}
.account-main {
flex: 1;
min-width: 0;
margin-left: 12px;
display: flex;
flex-direction: column;
justify-content: center;
}
.account-name-row {
display: flex;
flex-direction: row;
align-items: center;
min-width: 0;
}
.account-name {
max-width: 100%;
font-size: 16px;
color: #222222;
font-weight: 600;
lines: 1;
text-overflow: ellipsis;
}
.account-badge {
margin-left: 8px;
padding: 0 8px;
height: 20px;
line-height: 20px;
border-radius: 999px;
background-color: #eef1f4;
color: #6f7682;
font-size: 11px;
flex-shrink: 0;
}
.account-subline {
margin-top: 4px;
font-size: 12px;
color: #999999;
lines: 1;
text-overflow: ellipsis;
}
.account-edit {
margin-left: 12px;
flex-shrink: 0;
}
.edit-icon {
width: 20px;
height: 20px;
font-size: 18px;
color: #b5b5b5;
line-height: 20px;
text-align: center;
}
.menu-card {
margin: 0 16px;
background-color: #ffffff;
border-radius: 14px;
overflow: hidden;
}
.menu-row {
height: 54px;
padding: 0 14px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #f1f2f4;
}
.menu-row-last {
border-bottom-width: 0;
}
.menu-left {
flex: 1;
min-width: 0;
display: flex;
flex-direction: row;
align-items: center;
}
.menu-icon-shell {
width: 26px;
height: 26px;
background-color: transparent;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.menu-icon-image {
width: 20px;
height: 20px;
}
.menu-title {
margin-left: 12px;
flex: 1;
min-width: 0;
font-size: 15px;
color: #222222;
line-height: 22px;
font-weight: 400;
lines: 1;
text-overflow: ellipsis;
}
.menu-right {
margin-left: 12px;
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0;
}
.menu-right-desc {
font-size: 13px;
color: #999999;
line-height: 20px;
font-weight: 400;
}
.menu-arrow {
margin-left: 6px;
font-size: 16px;
color: #b5b5b5;
line-height: 20px;
}
.icon-address {
width: 21px;
height: 21px;
}
.icon-security {
width: 20px;
height: 20px;
}
.icon-privacy {
width: 20px;
height: 20px;
}
.icon-home {
width: 20px;
height: 20px;
}
.icon-senior {
width: 20px;
height: 20px;
}
.icon-service {
width: 19px;
height: 19px;
}
.icon-feedback {
width: 20px;
height: 20px;
}
.logout-wrap {
margin: 12px 16px 0 16px;
padding-bottom: 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
}
.logout-button {
width: 100%;
height: 52px;
line-height: 52px;
border-radius: 12px;
background-color: #ffffff;
color: #333333;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.logout-button::after {
border: none;
}
.bottom-safe {
height: 12px;
}
</style>