添加首页加载skeleton
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<!-- pages/main/index.uvue -->
|
||||
<template>
|
||||
<view class="medic-home">
|
||||
<view :class="['home-main-stage', pageLoading ? 'home-main-stage-hidden' : 'home-main-stage-visible']">
|
||||
<view class="jd-header-fixed">
|
||||
<JdLikeHomeHeader
|
||||
:status-bar-height="statusBarHeight"
|
||||
@@ -59,6 +60,7 @@
|
||||
<HomeMallContent
|
||||
v-if="activeTopModule == 'home'"
|
||||
:current-category="currentCategory"
|
||||
:page-loading="pageLoading"
|
||||
:selected-sub-category-id="selectedSubCategoryId"
|
||||
:secondary-category-display="secondaryCategoryDisplay"
|
||||
:marketing-channels="marketingChannels"
|
||||
@@ -220,6 +222,22 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showHomeSkeleton"
|
||||
:class="['home-skeleton-overlay', pageLoading ? 'home-skeleton-overlay-visible' : 'home-skeleton-overlay-leaving']"
|
||||
>
|
||||
<HomeSkeleton
|
||||
:status-bar-height="statusBarHeight"
|
||||
:capsule-right="navBarRight"
|
||||
:bottom-safe-area="bottomSafeArea"
|
||||
:show-category-bar="true"
|
||||
:category-count="7"
|
||||
:channel-count="4"
|
||||
:product-count="8"
|
||||
></HomeSkeleton>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -228,6 +246,7 @@ import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { onShow, onLoad, onHide } from '@dcloudio/uni-app'
|
||||
import HomeMallContent from '@/components/home/HomeMallContent.uvue'
|
||||
import JdLikeHomeHeader from '@/components/home/JdLikeHomeHeader.uvue'
|
||||
import HomeSkeleton from '@/components/home-skeleton/home-skeleton.uvue'
|
||||
import { fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
|
||||
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
|
||||
import supabaseService from '@/utils/supabaseService.uts'
|
||||
@@ -246,6 +265,11 @@ const refreshing = ref(false)
|
||||
const loading = ref(false)
|
||||
const isFirstShow = ref(true)
|
||||
const hasMore = ref(true)
|
||||
const pageLoading = ref(true)
|
||||
const goodsLoaded = ref(false)
|
||||
const categoryLoaded = ref(false)
|
||||
const bannerLoaded = ref(false)
|
||||
const skeletonExiting = ref(false)
|
||||
const activeSort = ref('recommend') // 默认展示智能推荐
|
||||
const activeFilter = ref('recommend')
|
||||
const currentPage = ref(1)
|
||||
@@ -598,6 +622,11 @@ const nextPlaceholderKeyword = ref(placeholderKeywords.value.length > 1 ? placeh
|
||||
const placeholderAnimating = ref(false)
|
||||
let placeholderTimer: number = 0
|
||||
let placeholderAnimationTimer: number = 0
|
||||
let skeletonTimer: number = 0
|
||||
|
||||
const showHomeSkeleton = computed((): boolean => {
|
||||
return pageLoading.value || skeletonExiting.value
|
||||
})
|
||||
|
||||
const homeEmptyStateTitle = computed((): string => {
|
||||
if (currentFeedCategoryId.value == 'recommend') {
|
||||
@@ -660,6 +689,42 @@ function stopPlaceholderScroll(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function resetInitialHomeLoadFlags(): void {
|
||||
goodsLoaded.value = false
|
||||
categoryLoaded.value = false
|
||||
bannerLoaded.value = false
|
||||
}
|
||||
|
||||
function clearSkeletonTimer(): void {
|
||||
if (skeletonTimer != 0) {
|
||||
clearTimeout(skeletonTimer)
|
||||
skeletonTimer = 0
|
||||
}
|
||||
}
|
||||
|
||||
function beginHomeSkeleton(): void {
|
||||
clearSkeletonTimer()
|
||||
pageLoading.value = true
|
||||
skeletonExiting.value = false
|
||||
}
|
||||
|
||||
function finishHomeSkeleton(): void {
|
||||
if (!pageLoading.value) {
|
||||
return
|
||||
}
|
||||
pageLoading.value = false
|
||||
skeletonExiting.value = true
|
||||
clearSkeletonTimer()
|
||||
skeletonTimer = setTimeout(() => {
|
||||
skeletonExiting.value = false
|
||||
skeletonTimer = 0
|
||||
}, 280)
|
||||
}
|
||||
|
||||
async function loadHomeBannerData(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
function startPlaceholderScroll(): void {
|
||||
try {
|
||||
if (placeholderTimer != 0) {
|
||||
@@ -1910,9 +1975,17 @@ const searchByKeyword = (keyword: string): void => {
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
type InitDataOptions = {
|
||||
showSkeleton: boolean
|
||||
}
|
||||
|
||||
const initData = async (options: InitDataOptions = { showSkeleton: false }) => {
|
||||
logSupaConfig()
|
||||
console.log('[consumer-db] 首页开始加载数据')
|
||||
resetInitialHomeLoadFlags()
|
||||
if (options.showSkeleton) {
|
||||
beginHomeSkeleton()
|
||||
}
|
||||
// 首先确保用户资料已加载
|
||||
try {
|
||||
await getCurrentUser()
|
||||
@@ -1920,16 +1993,50 @@ const initData = async () => {
|
||||
} catch (error) {
|
||||
console.error('加载用户资料失败:', error)
|
||||
}
|
||||
await loadMedicalMallCategories()
|
||||
await loadBrands()
|
||||
await loadHotKeywords()
|
||||
await loadServiceHomeData()
|
||||
if (await consumeSelectedCategoryFromStorage()) {
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
return
|
||||
const categoryTask = (async (): Promise<void> => {
|
||||
try {
|
||||
await loadMedicalMallCategories()
|
||||
} finally {
|
||||
categoryLoaded.value = true
|
||||
}
|
||||
})()
|
||||
const bannerTask = (async (): Promise<void> => {
|
||||
try {
|
||||
await loadHomeBannerData()
|
||||
} finally {
|
||||
bannerLoaded.value = true
|
||||
}
|
||||
})()
|
||||
const goodsTask = (async (): Promise<void> => {
|
||||
try {
|
||||
await categoryTask
|
||||
if (!(await consumeSelectedCategoryFromStorage())) {
|
||||
await loadCategoryGoods(currentCategory.value)
|
||||
}
|
||||
} finally {
|
||||
goodsLoaded.value = true
|
||||
}
|
||||
})()
|
||||
const supplementTask = (async (): Promise<void> => {
|
||||
await Promise.all([
|
||||
loadBrands(),
|
||||
loadHotKeywords(),
|
||||
loadServiceHomeData()
|
||||
])
|
||||
try {
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
} catch (error) {
|
||||
console.error('加载推荐频道商品失败:', error)
|
||||
recommendedProducts.value = []
|
||||
}
|
||||
})()
|
||||
try {
|
||||
await Promise.all([categoryTask, goodsTask, bannerTask, supplementTask])
|
||||
} finally {
|
||||
if (options.showSkeleton && categoryLoaded.value && goodsLoaded.value && bannerLoaded.value) {
|
||||
finishHomeSkeleton()
|
||||
}
|
||||
}
|
||||
await loadCategoryGoods(currentCategory.value)
|
||||
await loadRecommendedProducts(defaultLoadLimit)
|
||||
}
|
||||
|
||||
|
||||
@@ -2035,7 +2142,7 @@ const initPage = () => {
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
initPage()
|
||||
initData()
|
||||
void initData({ showSkeleton: true })
|
||||
startPlaceholderScroll()
|
||||
})
|
||||
|
||||
@@ -2085,6 +2192,7 @@ onHide(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
stopPlaceholderScroll()
|
||||
clearSkeletonTimer()
|
||||
})
|
||||
|
||||
// 处理滚动事件
|
||||
@@ -2202,7 +2310,7 @@ const onRefresh = async () => {
|
||||
|
||||
try {
|
||||
// 重新加载数据
|
||||
await initData()
|
||||
await initData({ showSkeleton: false })
|
||||
} catch (e) {
|
||||
console.error('刷新数据失败:', e)
|
||||
} finally {
|
||||
@@ -2432,6 +2540,41 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.home-main-stage {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
transition: opacity 260ms ease;
|
||||
}
|
||||
|
||||
.home-main-stage-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.home-main-stage-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.home-skeleton-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1200;
|
||||
background: #f6f8fb;
|
||||
transition: opacity 260ms ease;
|
||||
}
|
||||
|
||||
.home-skeleton-overlay-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.home-skeleton-overlay-leaving {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.jd-header-fixed {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<view class="info-row last-row" @click="openLanguagePicker">
|
||||
<text class="info-label">语言偏好</text>
|
||||
<view class="info-value-wrap">
|
||||
<text class="info-value">{{ getLanguageText(profile.preferred_language) }}</text>
|
||||
<text class="info-value">{{ getPreferredLanguageText() }}</text>
|
||||
</view>
|
||||
<text class="info-arrow">›</text>
|
||||
</view>
|
||||
@@ -225,8 +225,8 @@
|
||||
<view v-if="showLanguagePicker" class="picker-modal">
|
||||
<picker-view class="picker-view" :value="tempLanguageIndex" :indicator-style="'height: 50px;'" @change="onLanguagePickerViewChange">
|
||||
<picker-view-column style="width:750rpx;">
|
||||
<view v-for="(language, idx) in languageOptions" :key="language" class="picker-item">
|
||||
{{ getLanguageText(language) }}
|
||||
<view v-for="(language, idx) in languageOptions" :key="language.id" class="picker-item">
|
||||
{{ language.label }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
@@ -317,19 +317,41 @@
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { AkSupaSelectOptions } from '@/components/supadb/aksupa.uts'
|
||||
import { setUserProfile } from '@/utils/store.uts'
|
||||
import { PROFILE_REGION_OPTIONS, PROFILE_COMMON_ADDRESS_SUGGESTIONS } from '@/utils/profileRegionData.uts'
|
||||
import type { UserProfile } from '@/types/mall-types.uts'
|
||||
|
||||
type AkLanguageRow = {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
native_name: string
|
||||
is_active: boolean
|
||||
is_default: boolean
|
||||
sort_order: number
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
type LanguageOption = {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
nativeName: string
|
||||
label: string
|
||||
isDefault: boolean
|
||||
sortOrder: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
const isLoading = ref<boolean>(false)
|
||||
const isSaving = ref<boolean>(false)
|
||||
const userAvatar = ref<string>('/static/logo.png')
|
||||
const hasLoadError = ref<boolean>(false)
|
||||
const registeredDate = ref<string>('暂未记录')
|
||||
const profileRowId = ref<string>('')
|
||||
const originalProfile = ref<UserProfile | null>(null)
|
||||
const genderOptions: Array<string> = ['male', 'female', 'other']
|
||||
const languageOptions: Array<string> = ['zh-CN', 'en-US']
|
||||
const languageOptions = ref<Array<LanguageOption>>([])
|
||||
const customOptionLabel = '自定义其他'
|
||||
const healthGoalOptions: Array<string> = ['改善睡眠', '康复护理', '日常保健', '慢病管理', '营养调理', customOptionLabel]
|
||||
const carePreferenceOptions: Array<string> = ['上门护理', '陪诊陪护', '康复训练', '定期回访', '电话提醒', customOptionLabel]
|
||||
@@ -419,7 +441,7 @@ const profile = ref<UserProfile>({
|
||||
weight_kg: 0,
|
||||
bio: '',
|
||||
avatar_url: '/static/logo.png',
|
||||
preferred_language: 'zh-CN',
|
||||
preferred_language: '',
|
||||
health_goal: '',
|
||||
service_address: '',
|
||||
emergency_contact: '',
|
||||
@@ -536,11 +558,167 @@ const getBmiText = (): string => {
|
||||
return '' + rounded
|
||||
}
|
||||
|
||||
const getLanguageText = (languageCode: string | null): string => {
|
||||
if (languageCode == 'en-US') {
|
||||
return 'English'
|
||||
const normalizeString = (value: string | null | undefined): string | null => {
|
||||
if (value == null) {
|
||||
return null
|
||||
}
|
||||
return '中文'
|
||||
const trimmed = value.trim()
|
||||
return trimmed == '' ? null : trimmed
|
||||
}
|
||||
|
||||
const normalizeNumber = (value: number | null | undefined): number | null => {
|
||||
if (value == null) {
|
||||
return null
|
||||
}
|
||||
const numericValue = Number(value)
|
||||
return numericValue == numericValue ? numericValue : null
|
||||
}
|
||||
|
||||
const normalizeDateString = (value: string | null | undefined): string | null => {
|
||||
const normalized = normalizeString(value)
|
||||
if (normalized == null) {
|
||||
return null
|
||||
}
|
||||
const parts = normalized.split('T')
|
||||
return parts.length > 0 ? parts[0] : normalized
|
||||
}
|
||||
|
||||
const deepCloneProfile = (source: UserProfile): UserProfile => {
|
||||
return {
|
||||
id: source.id ?? '',
|
||||
username: source.username ?? '',
|
||||
email: source.email ?? '',
|
||||
gender: source.gender ?? '',
|
||||
birthday: source.birthday ?? '',
|
||||
height_cm: source.height_cm ?? 0,
|
||||
weight_kg: source.weight_kg ?? 0,
|
||||
bio: source.bio ?? '',
|
||||
avatar_url: source.avatar_url ?? '',
|
||||
preferred_language: source.preferred_language ?? '',
|
||||
health_goal: source.health_goal ?? '',
|
||||
service_address: source.service_address ?? '',
|
||||
emergency_contact: source.emergency_contact ?? '',
|
||||
chronic_notes: source.chronic_notes ?? '',
|
||||
care_preference: source.care_preference ?? '',
|
||||
role: source.role ?? '',
|
||||
school_id: source.school_id ?? '',
|
||||
grade_id: source.grade_id ?? '',
|
||||
class_id: source.class_id ?? '',
|
||||
created_at: source.created_at ?? '',
|
||||
updated_at: source.updated_at ?? ''
|
||||
} as UserProfile
|
||||
}
|
||||
|
||||
const getLanguageOptionById = (languageId: string | null | undefined): LanguageOption | null => {
|
||||
const normalizedId = normalizeString(languageId)
|
||||
if (normalizedId == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||
const option = languageOptions.value[i]
|
||||
if (option.id == normalizedId) {
|
||||
return option
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const getDefaultLanguageOption = (): LanguageOption | null => {
|
||||
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||
if (languageOptions.value[i].isDefault == true) {
|
||||
return languageOptions.value[i]
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||
if (languageOptions.value[i].code == 'zh-CN') {
|
||||
return languageOptions.value[i]
|
||||
}
|
||||
}
|
||||
|
||||
return languageOptions.value.length > 0 ? languageOptions.value[0] : null
|
||||
}
|
||||
|
||||
const getLanguageLabel = (languageId: string | null): string => {
|
||||
const language = getLanguageOptionById(languageId)
|
||||
return language != null ? language.label : '未设置'
|
||||
}
|
||||
|
||||
const getPreferredLanguageText = (): string => {
|
||||
const currentLanguageId = normalizeString(profile.value.preferred_language)
|
||||
if (currentLanguageId != null) {
|
||||
return getLanguageLabel(currentLanguageId)
|
||||
}
|
||||
|
||||
const defaultLanguage = getDefaultLanguageOption()
|
||||
return defaultLanguage != null ? defaultLanguage.label : '未设置'
|
||||
}
|
||||
|
||||
const loadLanguageOptions = async (): Promise<void> => {
|
||||
const result = await supa
|
||||
.from('ak_languages')
|
||||
.select('id, code, name, native_name, is_active, is_default, sort_order, created_at', {} as UTSJSONObject)
|
||||
.eq('is_active', true)
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (result.error != null) {
|
||||
console.error('加载语言列表失败:', JSON.stringify(result.error))
|
||||
languageOptions.value = []
|
||||
return
|
||||
}
|
||||
|
||||
const rows = result.data
|
||||
const options: Array<LanguageOption> = []
|
||||
if (Array.isArray(rows)) {
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const item = rows[i] as UTSJSONObject
|
||||
const row: AkLanguageRow = {
|
||||
id: item.getString('id') ?? '',
|
||||
code: item.getString('code') ?? '',
|
||||
name: item.getString('name') ?? '',
|
||||
native_name: item.getString('native_name') ?? '',
|
||||
is_active: item.getBoolean('is_active') ?? false,
|
||||
is_default: item.getBoolean('is_default') ?? false,
|
||||
sort_order: item.getNumber('sort_order') ?? 0,
|
||||
created_at: item.getString('created_at') ?? ''
|
||||
}
|
||||
if (row.id == '') {
|
||||
continue
|
||||
}
|
||||
const label = row.native_name != '' ? row.native_name : (row.name != '' ? row.name : row.code)
|
||||
options.push({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
nativeName: row.native_name,
|
||||
label,
|
||||
isDefault: row.is_default,
|
||||
sortOrder: row.sort_order,
|
||||
createdAt: row.created_at ?? ''
|
||||
} as LanguageOption)
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
for (let j = i + 1; j < options.length; j++) {
|
||||
let shouldSwap = false
|
||||
if (options[j].sortOrder < options[i].sortOrder) {
|
||||
shouldSwap = true
|
||||
} else if (options[j].sortOrder == options[i].sortOrder && options[j].createdAt != '' && options[i].createdAt != '' && options[j].createdAt < options[i].createdAt) {
|
||||
shouldSwap = true
|
||||
}
|
||||
if (shouldSwap) {
|
||||
const temp = options[i]
|
||||
options[i] = options[j]
|
||||
options[j] = temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
languageOptions.value = options
|
||||
}
|
||||
|
||||
const getTextOrPlaceholder = (fieldValue: string | null | undefined, placeholder: string): string => {
|
||||
@@ -878,19 +1056,37 @@ const confirmAddressPicker = (): void => {
|
||||
}
|
||||
|
||||
const openLanguagePicker = (): void => {
|
||||
const languageValue = profile.value.preferred_language
|
||||
const idx = languageValue != null ? languageOptions.indexOf(languageValue) : -1
|
||||
tempLanguageIndex.value = [idx >= 0 ? idx : 0]
|
||||
if (languageOptions.value.length == 0) {
|
||||
uni.showToast({
|
||||
title: '语言列表加载中',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const currentLanguageId = normalizeString(profile.value.preferred_language)
|
||||
const defaultLanguage = getDefaultLanguageOption()
|
||||
const targetLanguageId = currentLanguageId != null ? currentLanguageId : (defaultLanguage != null ? defaultLanguage.id : '')
|
||||
let idx = 0
|
||||
for (let i = 0; i < languageOptions.value.length; i++) {
|
||||
if (languageOptions.value[i].id == targetLanguageId) {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
tempLanguageIndex.value = [idx]
|
||||
showLanguagePicker.value = true
|
||||
}
|
||||
|
||||
const onLanguagePickerViewChange = (e: UniPickerViewChangeEvent): void => {
|
||||
const idx = e.detail.value[0]
|
||||
tempLanguageIndex.value = [(idx >= 0 && idx < languageOptions.length) ? idx : 0]
|
||||
tempLanguageIndex.value = [(idx >= 0 && idx < languageOptions.value.length) ? idx : 0]
|
||||
}
|
||||
|
||||
const confirmLanguagePicker = (): void => {
|
||||
profile.value.preferred_language = languageOptions[tempLanguageIndex.value[0]]
|
||||
if (languageOptions.value.length > 0) {
|
||||
profile.value.preferred_language = languageOptions.value[tempLanguageIndex.value[0]].id
|
||||
}
|
||||
showLanguagePicker.value = false
|
||||
}
|
||||
|
||||
@@ -938,7 +1134,7 @@ const loadProfile = async (): Promise<void> => {
|
||||
weight_kg: prodata.getNumber('weight_kg') ?? 0,
|
||||
bio: prodata.getString('bio') ?? '',
|
||||
avatar_url: prodata.getString('avatar_url') ?? '/static/logo.png',
|
||||
preferred_language: prodata.getString('preferred_language') ?? 'zh-CN',
|
||||
preferred_language: prodata.getString('preferred_language') ?? '',
|
||||
health_goal: prodata.getString('health_goal') ?? '',
|
||||
service_address: prodata.getString('service_address') ?? '',
|
||||
emergency_contact: prodata.getString('emergency_contact') ?? '',
|
||||
@@ -947,6 +1143,7 @@ const loadProfile = async (): Promise<void> => {
|
||||
} as UserProfile
|
||||
p.service_address = normalizeServiceAddress(p.service_address)
|
||||
profile.value = p
|
||||
originalProfile.value = deepCloneProfile(p)
|
||||
const createdAt = prodata.getString('created_at')
|
||||
if (createdAt != null && createdAt != '') {
|
||||
registeredDate.value = formatDate(createdAt)
|
||||
@@ -1005,54 +1202,88 @@ const loadProfile = async (): Promise<void> => {
|
||||
} as UserProfile
|
||||
setUserProfile(newProfileData)
|
||||
}
|
||||
originalProfile.value = deepCloneProfile(profile.value)
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
const saveProfile = async (): Promise<void> => {
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
const userid: string = profileRowId.value != '' ? profileRowId.value : (profile.value.id ?? '')
|
||||
const birthdayValue = profile.value.birthday != null ? profile.value.birthday.trim() : ''
|
||||
console.log('saveProfile context:', JSON.stringify({
|
||||
profileRowId: profileRowId.value,
|
||||
profileId: profile.value.id,
|
||||
birthday: birthdayValue != '' ? birthdayValue : null,
|
||||
hasHealthGoal: (profile.value.health_goal ?? '') != '',
|
||||
hasServiceAddress: (profile.value.service_address ?? '') != ''
|
||||
}))
|
||||
const baseUpdateData = new UTSJSONObject()
|
||||
baseUpdateData.set('username', profile.value.username)
|
||||
baseUpdateData.set('gender', profile.value.gender)
|
||||
baseUpdateData.set('birthday', birthdayValue != '' ? birthdayValue : null)
|
||||
baseUpdateData.set('height_cm', profile.value.height_cm)
|
||||
baseUpdateData.set('weight_kg', profile.value.weight_kg)
|
||||
baseUpdateData.set('bio', profile.value.bio)
|
||||
baseUpdateData.set('avatar_url', profile.value.avatar_url)
|
||||
const buildProfileUpdatePayload = (): UTSJSONObject => {
|
||||
const payload = new UTSJSONObject()
|
||||
const oldProfile = originalProfile.value
|
||||
const newProfile = profile.value
|
||||
|
||||
const updateData = new UTSJSONObject()
|
||||
updateData.set('username', profile.value.username)
|
||||
updateData.set('gender', profile.value.gender)
|
||||
updateData.set('birthday', birthdayValue != '' ? birthdayValue : null)
|
||||
updateData.set('height_cm', profile.value.height_cm)
|
||||
updateData.set('weight_kg', profile.value.weight_kg)
|
||||
updateData.set('bio', profile.value.bio)
|
||||
updateData.set('avatar_url', profile.value.avatar_url)
|
||||
updateData.set('health_goal', profile.value.health_goal)
|
||||
updateData.set('service_address', profile.value.service_address)
|
||||
updateData.set('emergency_contact', profile.value.emergency_contact)
|
||||
updateData.set('chronic_notes', profile.value.chronic_notes)
|
||||
updateData.set('care_preference', profile.value.care_preference)
|
||||
|
||||
let result = await supa
|
||||
if (oldProfile == null) {
|
||||
return payload
|
||||
}
|
||||
|
||||
if (normalizeString(oldProfile.username) != normalizeString(newProfile.username)) {
|
||||
payload.set('username', normalizeString(newProfile.username))
|
||||
}
|
||||
if (normalizeString(oldProfile.avatar_url) != normalizeString(newProfile.avatar_url)) {
|
||||
payload.set('avatar_url', normalizeString(newProfile.avatar_url))
|
||||
}
|
||||
if (normalizeString(oldProfile.gender) != normalizeString(newProfile.gender)) {
|
||||
payload.set('gender', normalizeString(newProfile.gender))
|
||||
}
|
||||
if (normalizeDateString(oldProfile.birthday) != normalizeDateString(newProfile.birthday)) {
|
||||
payload.set('birthday', normalizeDateString(newProfile.birthday))
|
||||
}
|
||||
if (normalizeString(oldProfile.service_address) != normalizeString(newProfile.service_address)) {
|
||||
payload.set('service_address', normalizeString(newProfile.service_address))
|
||||
}
|
||||
if (normalizeString(oldProfile.bio) != normalizeString(newProfile.bio)) {
|
||||
payload.set('bio', normalizeString(newProfile.bio))
|
||||
}
|
||||
if (normalizeNumber(oldProfile.height_cm) != normalizeNumber(newProfile.height_cm)) {
|
||||
payload.set('height_cm', normalizeNumber(newProfile.height_cm))
|
||||
}
|
||||
if (normalizeNumber(oldProfile.weight_kg) != normalizeNumber(newProfile.weight_kg)) {
|
||||
payload.set('weight_kg', normalizeNumber(newProfile.weight_kg))
|
||||
}
|
||||
if (normalizeString(oldProfile.preferred_language) != normalizeString(newProfile.preferred_language)) {
|
||||
payload.set('preferred_language', normalizeString(newProfile.preferred_language))
|
||||
}
|
||||
if (normalizeString(oldProfile.health_goal) != normalizeString(newProfile.health_goal)) {
|
||||
payload.set('health_goal', normalizeString(newProfile.health_goal))
|
||||
}
|
||||
if (normalizeString(oldProfile.emergency_contact) != normalizeString(newProfile.emergency_contact)) {
|
||||
payload.set('emergency_contact', normalizeString(newProfile.emergency_contact))
|
||||
}
|
||||
if (normalizeString(oldProfile.chronic_notes) != normalizeString(newProfile.chronic_notes)) {
|
||||
payload.set('chronic_notes', normalizeString(newProfile.chronic_notes))
|
||||
}
|
||||
if (normalizeString(oldProfile.care_preference) != normalizeString(newProfile.care_preference)) {
|
||||
payload.set('care_preference', normalizeString(newProfile.care_preference))
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
const saveProfile = async (): Promise<void> => {
|
||||
const userid: string = profileRowId.value != '' ? profileRowId.value : (profile.value.id ?? '')
|
||||
const updatePayload = buildProfileUpdatePayload()
|
||||
if (UTSJSONObject.keys(updatePayload).length == 0) {
|
||||
uni.showToast({
|
||||
title: '没有修改内容',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
updatePayload.set('updated_at', new Date().toISOString())
|
||||
|
||||
const result = await supa
|
||||
.from('ak_users')
|
||||
.update(updateData)
|
||||
.update(updatePayload)
|
||||
.eq('id', userid)
|
||||
.execute()
|
||||
|
||||
if (result.error == null) {
|
||||
originalProfile.value = deepCloneProfile(profile.value)
|
||||
setUserProfile(profile.value)
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
@@ -1060,25 +1291,10 @@ const saveProfile = async (): Promise<void> => {
|
||||
})
|
||||
} else {
|
||||
console.log('saveProfile update ak_users error:', JSON.stringify(result.error))
|
||||
result = await supa
|
||||
.from('ak_users')
|
||||
.update(baseUpdateData)
|
||||
.eq('id', userid)
|
||||
.execute()
|
||||
|
||||
if (result.error == null) {
|
||||
setUserProfile(profile.value)
|
||||
uni.showToast({
|
||||
title: '基础资料已保存,康养字段待后端升级',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
console.log('saveProfile fallback update ak_users error:', JSON.stringify(result.error))
|
||||
uni.showToast({
|
||||
title: '保存失败,请稍后重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
uni.showToast({
|
||||
title: '保存失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('saveProfile exception:', e)
|
||||
@@ -1221,6 +1437,7 @@ const confirmBirthdayPicker = (): void => {
|
||||
|
||||
onMounted(() => {
|
||||
loadRecentAddressSuggestions()
|
||||
loadLanguageOptions()
|
||||
loadProfile()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user