1070 lines
30 KiB
Plaintext
1070 lines
30 KiB
Plaintext
<template>
|
||
<view class="page-wrapper">
|
||
<!-- Top section with language switch -->
|
||
<view class="top-section">
|
||
<view class="language-switch">
|
||
<button class="language-btn" @click="toggleLanguage">
|
||
{{ currentLocale === 'zh-CN' ? 'EN' : '中' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Main content section -->
|
||
<view class="main-section">
|
||
<scroll-view direction="vertical" class="profile-container">
|
||
|
||
<!-- Loading state -->
|
||
<view v-if="isLoading" class="loading-container">
|
||
<text class="loading-text">{{ $t('user.profile.loading') }}</text>
|
||
</view>
|
||
|
||
<!-- Error state -->
|
||
<view v-else-if="profile.email == ''" class="error-container">
|
||
<text class="error-text">{{ $t('user.profile.load_error') }}</text>
|
||
<button class="retry-button" @click="loadProfile">{{ $t('retry') }}</button>
|
||
</view>
|
||
|
||
<!-- Profile content -->
|
||
<view v-else class="profile-content">
|
||
<!-- Avatar section -->
|
||
<view class="avatar-section">
|
||
<image class="avatar" :src="userAvatar" mode="aspectFill" @click="chooseAvatar">></image>
|
||
</view>
|
||
|
||
<!-- Profile form -->
|
||
<form @submit="onSubmit">
|
||
<!-- Username input -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.username') }}</text>
|
||
<input class="input-field" name="username" type="text" v-model="profile.username"
|
||
:placeholder="$t('user.profile.username_placeholder')" />
|
||
</view>
|
||
|
||
<!-- Email input (read-only) -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.email') }}</text>
|
||
<input class="input-field readonly" name="email" type="text" v-model="profile.email"
|
||
disabled />
|
||
<text class="hint-text">{{ $t('user.profile.email_readonly') }}</text>
|
||
</view>
|
||
|
||
<!-- Gender select -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.gender') }}</text>
|
||
<view class="picker-field" @click="showGenderPicker = true">
|
||
<text>{{ getGenderText(profile.gender ?? 'other') }}</text>
|
||
<text class="picker-arrow"></text>
|
||
</view>
|
||
<view v-if="showGenderPicker" class="picker-modal">
|
||
<picker-view class="picker-view" :value="tempGenderIndex"
|
||
:indicator-style="'height: 50px;'" @change="onGenderPickerViewChange">
|
||
<picker-view-column style="width:750rpx;">
|
||
<view v-for="(g, idx) in genderOptions" :key="g" class="picker-item">
|
||
{{ getGenderText(g) }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
<view class="picker-actions">
|
||
<button @click="showGenderPicker = false">{{ $t('cancel') }}</button>
|
||
<button @click="confirmGenderPicker"
|
||
class="picker-actions-button">{{ $t('confirm') }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Birthday picker -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.birthday') }}</text>
|
||
<view class="picker-field" @click="showBirthdayPickernow">
|
||
<text>{{ profile.birthday ?? $t('user.profile.birthday_placeholder') }}</text>
|
||
<text class="picker-arrow"></text>
|
||
</view>
|
||
<view v-if="showBirthdayPicker" class="picker-modal">
|
||
<picker-date :startYear="1970" :endYear="new Date().getFullYear()" :value="tempBirthday"
|
||
@change="onBirthdayDateChange" />
|
||
<view class="picker-actions">
|
||
<button @click="showBirthdayPicker = false">{{ $t('cancel') }}</button>
|
||
<button @click="confirmBirthdayPicker"
|
||
class="picker-actions-button">{{ $t('confirm') }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Height input -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.height') }} (cm)</text>
|
||
<input class="input-field" name="height" type="number" :value="profile.height_cm ?? ''"
|
||
:placeholder="$t('user.profile.height_placeholder')" @input="onHeightInput" />
|
||
</view>
|
||
|
||
<!-- Weight input -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.weight') }} (kg)</text>
|
||
<input class="input-field" name="weight" type="number" :value="profile.weight_kg ?? ''"
|
||
:placeholder="$t('user.profile.weight_placeholder')" @input="onWeightInput" />
|
||
</view> <!-- Language preference -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.language') }}</text>
|
||
<view class="picker-field" @click="showLanguagePickerNow">
|
||
<text>{{ getLanguageName(profile.preferred_language??'') }}</text>
|
||
<text class="picker-arrow">›</text>
|
||
</view>
|
||
<view v-if="showLanguagePicker" class="picker-modal">
|
||
<picker-view class="picker-view" :value="tempLanguageIndexArr"
|
||
:indicator-style="'height: 50px;'" @change="onLanguagePickerViewChange">
|
||
<picker-view-column style="width:750rpx;">
|
||
<view v-for="(lang, idx) in languageOptions" :key="lang" class="picker-item">
|
||
{{ lang }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
<view class="picker-actions">
|
||
<button @click="showLanguagePicker = false">{{ $t('cancel') }}</button>
|
||
<button @click="confirmLanguagePicker"
|
||
class="picker-actions-button">{{ $t('confirm') }}</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Bio/description textarea -->
|
||
<view class="input-group">
|
||
<text class="input-label">{{ $t('user.profile.bio') }}</text>
|
||
<textarea class="textarea-field" name="bio" v-model="profile.bio"
|
||
:placeholder="$t('user.profile.bio_placeholder')"></textarea>
|
||
</view>
|
||
|
||
<!-- Save button -->
|
||
<button form-type="submit" class="save-button" :disabled="isSaving" :loading="isSaving">
|
||
{{ $t('user.profile.save_button') }}
|
||
</button>
|
||
</form>
|
||
|
||
<!-- Success/error messages -->
|
||
<view v-if="saveSuccess" class="success-message">
|
||
<text class="success-text">{{ $t('user.profile.save_success') }}</text>
|
||
</view>
|
||
<view v-else-if="saveError" class="error-message">
|
||
<text class="error-text">{{ saveError }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- Bottom section -->
|
||
<view class="bottom-section">
|
||
<!-- Footer content or spacing -->
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="uts">
|
||
import supa from '@/components/supadb/aksupainstance.uts';
|
||
import { AkSupaSelectOptions } from '@/components/supadb/aksupa.uts'
|
||
import { setUserProfile } from '@/utils/store.uts';
|
||
import type { UserProfile, LanguageOption } from './types.uts';
|
||
import { switchLocale, getCurrentLocale } from '@/utils/utils.uts';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
isLoading: false,
|
||
saveSuccess: '',
|
||
saveError: '',
|
||
isSaving: false,
|
||
userAvatar: '',
|
||
genderOptions: ['male', 'female', 'other'],
|
||
tempGenderIndex: [0], currentLocale: getCurrentLocale(),
|
||
languages: [] as LanguageOption[], // 从数据库获取的语言选项
|
||
languageOptions: [] as string[], // 显示的语言名称列表
|
||
languageIds: [] as string[], // 语言UUID列表
|
||
profile: {
|
||
id: null,
|
||
username: '',
|
||
email: '',
|
||
gender: 'other',
|
||
birthday: '',
|
||
height_cm: null,
|
||
weight_kg: null,
|
||
bio: '',
|
||
avatar_url: '',
|
||
preferred_language: 'zh-CN',
|
||
} as UserProfile,
|
||
showGenderPicker: false,
|
||
showBirthdayPicker: false,
|
||
showLanguagePicker: false,
|
||
tempBirthday: [2000, 1, 1],
|
||
birthdayYears: [] as number[],
|
||
birthdayMonths: [] as number[],
|
||
birthdayDays: [] as number[],
|
||
birthdayPickerValue: [0, 0, 0],
|
||
tempLanguageIndex: 0,
|
||
tempLanguageIndexArr: [0],
|
||
indicatorStyle: 'height: 50px;',
|
||
indicatorClass: '',
|
||
maskStyle: '',
|
||
maskClass: '',
|
||
maskTopStyle: '',
|
||
maskBottomStyle: ''
|
||
};
|
||
},
|
||
onLoad(options) {
|
||
this.loadLanguages();
|
||
this.loadProfile();
|
||
}, methods: {
|
||
|
||
toggleLanguage() {
|
||
const newLocale = this.currentLocale === 'zh-CN' ? 'en-US' : 'zh-CN';
|
||
switchLocale(newLocale);
|
||
this.currentLocale = newLocale;
|
||
|
||
uni.showToast({
|
||
title: this.$t('user.profile.language_switched'),
|
||
icon: 'success'
|
||
});
|
||
}, async loadLanguages() {
|
||
try {
|
||
|
||
const result = await supa.from('ak_languages').select('id,code,name,native_name', {}).execute();
|
||
const error = result.error
|
||
const data = result.data
|
||
if (error == null && Array.isArray(data) && data.length > 0) {
|
||
// Replace map with for loop for UTS compatibility
|
||
const languageList : LanguageOption[] = []
|
||
for (let i = 0; i < data.length; i++) {
|
||
const item = data[i] as UTSJSONObject
|
||
languageList.push({
|
||
id: item.getString("id") ?? '',
|
||
code: item.getString("code") ?? '',
|
||
name: item.getString("name") ?? '',
|
||
native_name: item.getString("native_name") ?? ''
|
||
} as LanguageOption)
|
||
}
|
||
this.languages = languageList
|
||
|
||
// Replace map with for loop for UTS compatibility
|
||
const optionsList : string[] = []
|
||
const idsList : string[] = []
|
||
for (let i = 0; i < this.languages.length; i++) {
|
||
const lang = this.languages[i]
|
||
optionsList.push(lang.native_name)
|
||
idsList.push(lang.id)
|
||
}
|
||
this.languageOptions = optionsList
|
||
this.languageIds = idsList
|
||
} else {
|
||
console.warn('ak_languages 表中没有数据,请先添加语言选项');
|
||
// 如果数据库没有数据,使用空数组,避免UUID错误
|
||
this.languages = [] as LanguageOption[];
|
||
this.languageOptions = [];
|
||
this.languageIds = [];
|
||
}
|
||
} catch (e) {
|
||
console.error('加载语言选项失败:', e);
|
||
// 使用空数组,避免UUID错误
|
||
this.languages = [] as LanguageOption[];
|
||
this.languageOptions = [];
|
||
this.languageIds = [];
|
||
}
|
||
},
|
||
async loadProfile() {
|
||
|
||
const user = supa.user;
|
||
if (user == null || user.getString("email") == null || user.getString("email")?.trim() === '') {
|
||
console.log('null user:', user)
|
||
this.profile.email = '';
|
||
return;
|
||
}
|
||
const filter = `id=eq.${user.id as string}`;
|
||
const options = { single: true } as AkSupaSelectOptions;
|
||
const result = await supa.select('ak_users', filter, options);
|
||
const data = result.data;
|
||
const error = result.error;
|
||
// 判断 data 是否为数组且为空
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
console.log(data)
|
||
let prodata = data[0] as UTSJSONObject; this.profile = {
|
||
id: user.id as string,
|
||
username: prodata.getString("username") ?? "",
|
||
email: prodata.getString("email") ?? "",
|
||
gender: prodata.getString("gender") ?? 'other',
|
||
birthday: prodata.getString("birthday") ?? '',
|
||
height_cm: prodata.getNumber("height_cm"),
|
||
weight_kg: prodata.getNumber("weight_kg"),
|
||
bio: prodata.getString("bio") ?? '',
|
||
avatar_url: prodata.getString("avatar_url") ?? '/static/logo.png',
|
||
preferred_language: prodata.getString("preferred_language") ?? this.getDefaultLanguageId(),
|
||
}
|
||
// UTS兼容:avatar_url 需强转为string,避免smart cast 报错 this.userAvatar = (this.profile.avatar_url && typeof this.profile.avatar_url === 'string') ? (this.profile.avatar_url as string) : '';
|
||
setUserProfile(this.profile);
|
||
}
|
||
else {
|
||
this.profile.id = user.getString("id") ?? null;
|
||
this.profile.username = user.getString("username") ?? "";
|
||
this.profile.email = user.getString("email") ?? "";
|
||
// UTS兼容:判断字符串为空需用trim() === ''
|
||
if (this.profile.username == null || this.profile.username.trim() === '') {
|
||
// UTS兼容:三元表达式替换 ||
|
||
let emailStr = this.profile.email;
|
||
let splitArr = (emailStr != null && emailStr.trim() !== '') ? emailStr.split("@") : [];
|
||
this.profile.username = (splitArr != null && splitArr.length > 0 && splitArr[0] != null && splitArr[0].trim() !== '') ? splitArr[0] : "user";
|
||
} if (this.profile.email == null || this.profile.email.trim() === '') this.profile.email = "";
|
||
// 设置默认语言首选项
|
||
this.profile.preferred_language = this.getDefaultLanguageId();
|
||
let newProfile = new UTSJSONObject(this.profile);
|
||
const insertResult = await supa.from('ak_users').insert(newProfile).execute();
|
||
console.log(insertResult)
|
||
if (insertResult.error == null) {
|
||
setUserProfile(this.profile);
|
||
}
|
||
}
|
||
},
|
||
onSubmit() {
|
||
this.saveProfile();
|
||
}, async saveProfile() {
|
||
this.isSaving = true;
|
||
this.saveSuccess = '';
|
||
this.saveError = '';
|
||
console.log('ak save ...')
|
||
try {
|
||
const updateData = { ...this.profile };
|
||
if (this.userAvatar != '') updateData.avatar_url = this.userAvatar;
|
||
// 验证语言ID,如果不是有效的UUID,则不保存语言首选项
|
||
if (updateData.preferred_language != null && (updateData.preferred_language as string).trim() !== '' && this.languages.length > 0) {
|
||
const isValidLanguage = this.languages.some(lang => lang.id === updateData.preferred_language);
|
||
if (!isValidLanguage) {
|
||
updateData.preferred_language = null;
|
||
}
|
||
}
|
||
|
||
// 只允许更新部分字段
|
||
// UTS兼容:避免'!!',直接类型断言或判断
|
||
const userid : string = this.profile.id != null ? this.profile.id as string : '';
|
||
const result = await supa
|
||
.from('ak_users')
|
||
.update(updateData)
|
||
.eq('id', userid)
|
||
.execute();
|
||
|
||
console.log(result)
|
||
// if (error) {
|
||
// this.saveError = error.message || '保存失败';
|
||
// } else {
|
||
// this.saveSuccess = '保存成功';
|
||
// }
|
||
} catch (e) {
|
||
this.saveError = '保存失败';
|
||
}
|
||
this.isSaving = false;
|
||
},
|
||
// 替换 uuid 依赖,使用本地生成唯一文件名
|
||
getUuid() : string {
|
||
return `${Date.now()}_${Math.floor(Math.random() * 1e8)}`;
|
||
},
|
||
async chooseAvatar() {
|
||
let that = this;
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType: ['compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
// 注意:uni.chooseImage 的success 回调不能是async,需用普通函数
|
||
success(res : ChooseImageSuccess) {
|
||
const upfilepath = res.tempFilePaths[0];
|
||
console.log(that.profile)
|
||
const userId = that.profile.id ?? '';
|
||
// 优先从tempFiles[0] 获取扩展名,否则从路径推断
|
||
let ext = 'png';
|
||
|
||
const tempFiles = res.tempFiles;
|
||
if (Array.isArray(tempFiles) && tempFiles.length > 0) {
|
||
const fileObj : ChooseImageTempFile = tempFiles[0];
|
||
// 兼容 UTS: 直接尝试 fileObj.name,判空和类型
|
||
let fileName = fileObj?.name; if (fileName != null && fileName.length > 0) {
|
||
const idx = fileName.lastIndexOf('.');
|
||
if (idx >= 0) ext = fileName.substring(idx + 1);
|
||
}
|
||
} else if (typeof that.userAvatar === 'string' && that.userAvatar.indexOf('.') > -1) {
|
||
ext = that.userAvatar.split('.').pop() ?? 'png';
|
||
}
|
||
const uuid = that.getUuid();
|
||
const remotePath = `profiles/${userId}_${uuid}.${ext}`;
|
||
// 传递空对象作为 options,兼容upload 的参数要求
|
||
supa.storage.from('zhipao').upload(remotePath, upfilepath, {}).then(({ data, status }) => {
|
||
|
||
if (status === 200 || status === 201) {
|
||
console.log(data)
|
||
// 用host+key 作为头像 URL
|
||
if (data != null && typeof data == 'object') {
|
||
const dataObj = data as UTSJSONObject;
|
||
let avatarUrl = dataObj.getString("Key");
|
||
console.log(avatarUrl)
|
||
if (avatarUrl != null && avatarUrl.trim() !== '') {
|
||
avatarUrl = 'https://ak3.oulog.com/storage/v1/object/public/' + avatarUrl;
|
||
that.userAvatar = avatarUrl;
|
||
that.profile.avatar_url = avatarUrl;
|
||
// 直接更新 profile
|
||
that.saveProfile();
|
||
}
|
||
console.log(that.userAvatar)
|
||
uni.showToast({ title: that.$t('user.profile.avatar_selected'), icon: 'success' });
|
||
} else {
|
||
uni.showToast({ title: that.$t('uploadfail'), icon: 'none', duration: 5000 });
|
||
}
|
||
} else {
|
||
uni.showToast({ title: that.$t('uploadfail'), icon: 'none', duration: 5000 });
|
||
}
|
||
});
|
||
}
|
||
});
|
||
},
|
||
getGenderText(genderCode : string) : string {
|
||
console.log(genderCode)
|
||
switch (genderCode) {
|
||
case 'male':
|
||
return this.$t('user.profile.gender_male');
|
||
case 'female':
|
||
return this.$t('user.profile.gender_female');
|
||
default:
|
||
return this.$t('user.profile.gender_other');
|
||
}
|
||
}, getLanguageIndex() : number {
|
||
const langIndex = this.languageIds.indexOf(this.profile.preferred_language ?? '');
|
||
return langIndex >= 0 ? langIndex : 0;
|
||
}, getDefaultLanguageId() : string {
|
||
// 查找 zh-CN 对应的UUID,如果没找到则返回第一个可用的语言 UUID
|
||
const zhLang = this.languages.find(lang => lang.code === 'zh-CN');
|
||
if (zhLang != null) {
|
||
return zhLang.id;
|
||
}
|
||
// 如果没有中文,返回第一个可用的语言
|
||
return this.languages.length > 0 ? this.languages[0].id : '';
|
||
}, getLanguageName(langId : string) : string { // 通过 langId 查找 native_name,兼容UTS 类型推断
|
||
const lang = this.languages.find(lang => lang.id === langId);
|
||
if (lang != null && typeof lang.native_name === 'string') {
|
||
return lang.native_name;
|
||
} else {
|
||
return this.languageOptions.length > 0 ? this.languageOptions[0] : '';
|
||
}
|
||
},
|
||
goBack() {
|
||
uni.navigateBack();
|
||
},
|
||
onHeightInput(e : UniInputEvent) {
|
||
const val = e.detail.value;
|
||
this.profile.height_cm = val === '' ? null : parseInt(val);
|
||
},
|
||
onWeightInput(e : UniInputEvent) {
|
||
const val = e.detail.value;
|
||
this.profile.weight_kg = val === '' ? null : parseInt(val);
|
||
},
|
||
showGenderPickerNow() {
|
||
// 打开弹窗时同步索引,保证 tempGenderIndex 为number[]
|
||
const idx = this.genderOptions.indexOf(this.profile.gender ?? 'other');
|
||
this.tempGenderIndex = [idx >= 0 ? idx : 0];
|
||
this.showGenderPicker = true;
|
||
},
|
||
onGenderPickerViewChange(e : UniPickerViewChangeEvent) {
|
||
const idx = e.detail.value[0];
|
||
this.tempGenderIndex = [(idx >= 0 && idx < this.genderOptions.length) ? idx : 0];
|
||
},
|
||
confirmGenderPicker() {
|
||
this.profile.gender = this.genderOptions[this.tempGenderIndex[0]];
|
||
this.showGenderPicker = false;
|
||
},
|
||
onBirthdayDateChange([y, m, d] : number[]) {
|
||
this.tempBirthday = [y, m, d];
|
||
},
|
||
showBirthdayPickernow() {
|
||
// 打开弹窗时同步tempBirthday
|
||
this.tempBirthday = this.parseBirthday(this.profile.birthday ?? '');
|
||
this.showBirthdayPicker = true;
|
||
},
|
||
confirmBirthdayPicker() {
|
||
this.showBirthdayPicker = false;
|
||
const [y, m, d] = this.tempBirthday;
|
||
// 兼容 UTS:用 (m < 10 ? '0' + m : m) 拼接字符串
|
||
const mm = m < 10 ? '0' + m : '' + m;
|
||
const dd = d < 10 ? '0' + d : '' + d;
|
||
this.profile.birthday = `${y}-${mm}-${dd}`;
|
||
this.showBirthdayPicker = false;
|
||
}, showLanguagePickerNow() {
|
||
// 打开弹窗时同步索引,保证 tempLanguageIndexArr 为number[]
|
||
const idx = this.languageIds.indexOf(this.profile.preferred_language ?? '');
|
||
this.tempLanguageIndex = idx >= 0 ? idx : 0;
|
||
this.tempLanguageIndexArr = [this.tempLanguageIndex];
|
||
this.showLanguagePicker = true;
|
||
},
|
||
onLanguagePickerViewChange(e : UniPickerViewChangeEvent) {
|
||
const idx = e.detail.value[0];
|
||
this.tempLanguageIndex = (idx >= 0 && idx < this.languageOptions.length) ? idx : 0;
|
||
this.tempLanguageIndexArr = [this.tempLanguageIndex];
|
||
}, confirmLanguagePicker() {
|
||
this.profile.preferred_language = this.languageIds[this.tempLanguageIndex];
|
||
this.showLanguagePicker = false;
|
||
|
||
// 同步更新应用的语言设置 - 使用语言代码而不是UUID
|
||
const selectedLang = this.languages[this.tempLanguageIndex];
|
||
if (selectedLang != null && selectedLang.code != null) {
|
||
const langCode = selectedLang.code;
|
||
uni.setStorageSync('uVueI18nLocale', langCode);
|
||
this.$i18n.global.locale.value = langCode;
|
||
|
||
// 显示语言切换成功提示,使用native_name
|
||
const message = `${this.$t('user.profile.language_switched')} ${selectedLang.native_name}`;
|
||
uni.showToast({
|
||
title: message,
|
||
icon: 'success'
|
||
});
|
||
}
|
||
},
|
||
parseBirthday(birthday : any) : number[] {
|
||
// 兼容 UTS,确保birthday 为string
|
||
let str = '';
|
||
if (typeof birthday === 'string') {
|
||
str = birthday;
|
||
} else if (birthday != null) {
|
||
str = '' + birthday;
|
||
} else {
|
||
// 为空直接用当前日期
|
||
const now = new Date();
|
||
return [now.getFullYear(), now.getMonth() + 1, now.getDate()];
|
||
}
|
||
// UTS split 返回 UTSArray<String?>,需转普通string[]
|
||
const partsRaw = str.split('-');
|
||
// 如果分割后不够段,直接用当前日期
|
||
if (partsRaw != null || typeof partsRaw.length !== 'number' || partsRaw.length !== 3) {
|
||
const now = new Date();
|
||
return [now.getFullYear(), now.getMonth() + 1, now.getDate()];
|
||
}
|
||
const parts : string[] = [];
|
||
for (let i = 0; i < 3; i++) {
|
||
let p : string = '';
|
||
if (typeof partsRaw[i] == 'string') {
|
||
p = partsRaw[i] as string;
|
||
} else {
|
||
p = '' + (partsRaw[i] ?? '');
|
||
}
|
||
parts.push(p);
|
||
}
|
||
// 保证 parseInt 参数为string
|
||
const y = parseInt(parts[0]);
|
||
const m = parseInt(parts[1]);
|
||
const d = parseInt(parts[2]);
|
||
if (isNaN(y) || isNaN(m) || isNaN(d)) {
|
||
const now = new Date();
|
||
return [now.getFullYear(), now.getMonth() + 1, now.getDate()];
|
||
}
|
||
return [y, m, d];
|
||
},
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style>
|
||
/* Page wrapper for full screen utilization */
|
||
.page-wrapper {
|
||
/* #ifdef APP-PLUS */
|
||
display: flex;
|
||
flex-direction: column;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
/* #endif */
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
/* Top section - Fixed header */
|
||
.top-section {
|
||
/* #ifdef APP-PLUS */
|
||
flex: 0 0 auto;
|
||
height: 100rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
height: 120rpx;
|
||
/* #endif */
|
||
position: relative;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
/* Main content section - Scrollable */
|
||
.main-section {
|
||
/* #ifdef APP-PLUS */
|
||
flex: 1;
|
||
overflow: hidden;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
flex-grow: 1;
|
||
/* #endif */
|
||
}
|
||
|
||
/* Bottom section - Fixed footer */
|
||
.bottom-section {
|
||
/* #ifdef APP-PLUS */
|
||
flex: 0 0 auto;
|
||
height: 50rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
height: 60rpx;
|
||
/* #endif */
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.profile-container {
|
||
/* #ifdef APP-PLUS */
|
||
padding: 20rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
padding: 30rpx;
|
||
/* #endif */
|
||
background-color: #f8f9fa;
|
||
display: flex;
|
||
flex-direction: column;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Language switch button */
|
||
.language-switch {
|
||
position: absolute;
|
||
/* #ifdef APP-PLUS */
|
||
top: 20rpx;
|
||
right: 30rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
top: 30rpx;
|
||
right: 40rpx;
|
||
/* #endif */
|
||
z-index: 10;
|
||
}
|
||
|
||
|
||
.header {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-top: 30rpx;
|
||
padding-bottom: 30rpx;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.back-button {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.back-icon {
|
||
font-size: 36rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.page-title {
|
||
flex: 1;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
text-align: center;
|
||
margin-right: 60rpx;
|
||
/* Balance the back button width */
|
||
}
|
||
|
||
/* Loading state */
|
||
.loading-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 80%;
|
||
}
|
||
|
||
.loading-text {
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 22rpx;
|
||
margin-top: 15rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 28rpx;
|
||
margin-top: 20rpx;
|
||
/* #endif */
|
||
color: #666;
|
||
}
|
||
|
||
/* Error state */
|
||
.error-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 80%;
|
||
}
|
||
|
||
.error-text {
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 22rpx;
|
||
margin-bottom: 15rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 28rpx;
|
||
margin-bottom: 20rpx;
|
||
/* #endif */
|
||
color: #f44336;
|
||
}
|
||
|
||
.retry-button {
|
||
/* #ifdef APP-PLUS */
|
||
padding: 15rpx 30rpx;
|
||
font-size: 22rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
padding: 20rpx 40rpx;
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
background-color: #2196f3;
|
||
color: white;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
/* Profile content */
|
||
.profile-content {
|
||
/* #ifdef APP-PLUS */
|
||
padding: 15rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
padding: 20rpx;
|
||
/* #endif */
|
||
background-color: white;
|
||
border-radius: 20rpx;
|
||
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
/* Avatar section */
|
||
.avatar-section {
|
||
position: relative;
|
||
display: flex;
|
||
justify-content: center;
|
||
/* #ifdef APP-PLUS */
|
||
margin-bottom: 30rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
margin-bottom: 40rpx;
|
||
/* #endif */
|
||
}
|
||
|
||
.avatar {
|
||
/* #ifdef APP-PLUS */
|
||
width: 140rpx;
|
||
height: 140rpx;
|
||
border-radius: 70rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
border-radius: 90rpx;
|
||
/* #endif */
|
||
background-color: #eee;
|
||
}
|
||
|
||
.avatar-upload-button {
|
||
position: absolute;
|
||
right: 30rpx;
|
||
bottom: 0;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background-color: #2196f3;
|
||
border-radius: 30rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 5rpx 10rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.avatar-upload-icon {
|
||
font-size: 30rpx;
|
||
color: white;
|
||
}
|
||
|
||
/* Form elements */
|
||
.input-group {
|
||
/* #ifdef APP-PLUS */
|
||
margin-bottom: 20rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
margin-bottom: 30rpx;
|
||
/* #endif */
|
||
}
|
||
|
||
.input-label {
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 24rpx;
|
||
margin-bottom: 8rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 28rpx;
|
||
margin-bottom: 10rpx;
|
||
/* #endif */
|
||
font-weight: normal;
|
||
color: #333;
|
||
}
|
||
|
||
.input-field {
|
||
width: 100%;
|
||
/* #ifdef APP-PLUS */
|
||
height: 60rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 24rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
height: 80rpx;
|
||
padding: 0 30rpx;
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
border-radius: 10rpx;
|
||
border: 2rpx solid #ddd;
|
||
background-color: #fff;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.input-field.readonly {
|
||
background-color: #f5f5f5;
|
||
color: #999;
|
||
}
|
||
|
||
.hint-text {
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 20rpx;
|
||
margin-top: 5rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 24rpx;
|
||
margin-top: 6rpx;
|
||
/* #endif */
|
||
color: #999;
|
||
}
|
||
|
||
/* Picker */
|
||
.picker-field {
|
||
width: 100%;
|
||
/* #ifdef APP-PLUS */
|
||
height: 60rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 24rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
height: 80rpx;
|
||
padding: 0 30rpx;
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
border-radius: 10rpx;
|
||
border: 2rpx solid #ddd;
|
||
background-color: #fff;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-arrow {
|
||
color: #999;
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 20rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 24rpx;
|
||
/* #endif */
|
||
}
|
||
|
||
/* Textarea */
|
||
.textarea-field {
|
||
width: 100%;
|
||
/* #ifdef APP-PLUS */
|
||
height: 150rpx;
|
||
padding: 15rpx 20rpx;
|
||
font-size: 24rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
height: 200rpx;
|
||
padding: 20rpx 30rpx;
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
border-radius: 10rpx;
|
||
border: 2rpx solid #ddd;
|
||
background-color: #fff;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Save button */
|
||
.save-button {
|
||
width: 100%;
|
||
/* #ifdef APP-PLUS */
|
||
height: 70rpx;
|
||
font-size: 26rpx;
|
||
margin: 15rpx 0 25rpx;
|
||
border-radius: 35rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
height: 90rpx;
|
||
font-size: 32rpx;
|
||
margin: 20rpx 0 30rpx;
|
||
border-radius: 45rpx;
|
||
/* #endif */
|
||
background-image: linear-gradient(to right, #2196f3, #03a9f4);
|
||
color: #fff;
|
||
font-weight: normal;
|
||
text-align: center;
|
||
box-shadow: 0 10rpx 20rpx rgba(3, 169, 244, 0.2);
|
||
}
|
||
|
||
.save-button:disabled {
|
||
background: #ccc;
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* Success/error messages */
|
||
.success-message {
|
||
/* #ifdef APP-PLUS */
|
||
padding: 15rpx;
|
||
margin-bottom: 15rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
padding: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
/* #endif */
|
||
background-color: #e8f5e9;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
.success-text {
|
||
color: #43a047;
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 22rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
text-align: center;
|
||
}
|
||
|
||
.error-message {
|
||
/* #ifdef APP-PLUS */
|
||
padding: 15rpx;
|
||
margin-bottom: 15rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
padding: 20rpx;
|
||
margin-bottom: 20rpx;
|
||
/* #endif */
|
||
background-color: #ffebee;
|
||
border-radius: 10rpx;
|
||
}
|
||
|
||
.error-text {
|
||
color: #e53935;
|
||
/* #ifdef APP-PLUS */
|
||
font-size: 22rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
text-align: center;
|
||
}
|
||
|
||
.picker-modal {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: #fff;
|
||
z-index: 1000;
|
||
box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.1);
|
||
padding-bottom: 30rpx;
|
||
width: 750rpx;
|
||
border-top-left-radius: 20rpx;
|
||
border-top-right-radius: 20rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.picker-view {
|
||
width: 750rpx;
|
||
min-width: 240px;
|
||
height: 320px;
|
||
background: #fff;
|
||
display: flex;
|
||
flex-direction: row;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-view-column {
|
||
flex: 1;
|
||
min-width: 80px;
|
||
width: 750rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-item {
|
||
height: 50px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 750rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
width: 750rpx;
|
||
padding: 20rpx 40rpx 0 40rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-actions-button {
|
||
flex: 1;
|
||
margin: 0 10rpx;
|
||
background: #2196f3;
|
||
color: #fff;
|
||
border-radius: 10rpx;
|
||
font-size: 28rpx;
|
||
height: 80rpx;
|
||
border: none;
|
||
}
|
||
|
||
/* Language switch button */
|
||
.language-switch {
|
||
position: absolute;
|
||
/* #ifdef APP-PLUS */
|
||
top: 40rpx;
|
||
right: 30rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
top: 60rpx;
|
||
right: 40rpx;
|
||
/* #endif */
|
||
z-index: 10;
|
||
}
|
||
|
||
.language-btn {
|
||
/* #ifdef APP-PLUS */
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 30rpx;
|
||
font-size: 22rpx;
|
||
/* #endif */
|
||
/* #ifndef APP-PLUS */
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 40rpx;
|
||
font-size: 28rpx;
|
||
/* #endif */
|
||
background-color: rgba(33, 150, 243, 0.8);
|
||
color: #fff;
|
||
font-weight: normal;
|
||
border: 2rpx solid rgba(255, 255, 255, 0.3);
|
||
text-align: center;
|
||
box-shadow: 0 4rpx 12rpx rgba(33, 150, 243, 0.3);
|
||
}
|
||
</style> |