Files
akmon/pages/user/profile.uvue
2026-01-20 08:04:15 +08:00

1070 lines
30 KiB
Plaintext
Raw Permalink 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-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>