upload project

This commit is contained in:
unknown 2025-12-27 17:16:03 +08:00
commit 06961cae04
422 changed files with 110626 additions and 0 deletions

View file

@ -0,0 +1,658 @@
const apiClient = require('../../../utils/api-client.js');
const nimUserManager = require('../../../utils/nim-user-manager.js');
const { calculateConstellation, calculateConstellationFromBirthday } = require('../../../utils/formatDate.js');
const cosManager = require('../../../utils/cos-manager.js');
const occupationOptions = [
"初中生", "高中生", "大学生", "研究生", "留学生", "科研", "警察", "医生", "护士",
"程序员", "老师", "化妆师", "摄影师", "音乐", "美术", "金融", "厨师", "工程师",
"公务员", "互联网", "产品经理", "模特", "演员", "导演", "律师", "创业者", "其他"
];
const schoolOptions = ["博士", "硕士", "本科", "专科", "中专", "高中", "初中", "小学", "其他"];
const genderOptions = ["未知", "男", "女", "其他"];
const genderMap = {
"未知": 0,
"男": 1,
"女": 2,
"其他": 3
};
// 反向映射:数字转文字
const genderReverseMap = {
0: "未知",
1: "男",
2: "女",
3: "其他"
};
const zodiacSignOptions = ["双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座"];
const mbtiTypeOptions = ["INTJ", "INTP", "ENTJ", "INFP", "ENTP", "INFJ", "ENFP", "ENFJ", "ISTJ", "ISFJ", "ISTP", "ISFP", "ESTJ", "ESFJ", "ESTP", "ESFP"];
const sleepHabitOptions = ["早起鸟儿", "夜猫子", "规律型", "深度睡眠追求者", "碎片化睡眠者", "失眠困扰者", "咖啡因敏感型", "数字戒断者", "运动调节型", "挑战打卡型", "鼾声监测者", "生物钟调节者", "社区分享型"];
const socialActivityOptions = ["内容创作者", "观察者", "吃瓜者", "潜水者", "机器人", "社群型用户", "KOL", "KOC", "普通用户", "算法依赖型用户", "事件驱动型用户", "季节性活跃用户", "社交维系型用户", "兴趣社群型用户", "职业网络型用户", "娱乐消遣型用户", "购物种草型用户", "互动型用户"];
const heightOptions = Array.from({ length: 71 }, (_, i) => (140 + i));
Page({
data: {
occupationOptions,
schoolOptions,
genderOptions,
zodiacSignOptions,
mbtiTypeOptions,
sleepHabitOptions,
socialActivityOptions,
heightOptions,
user: {
nickname: "",
avatar: "",
bio: "",
gender: 0, // 0=未知 1=男 2=女 3=其他
birthday: "",
ethnicity: "",
occupation: "",
school: "",
hometown: [],
zodiacSign: "",
height: 0,
mbtiType: "",
sleepHabit: "",
socialActivity: ""
},
// 性别显示文字(用于 WXML 显示)
genderDisplayText: "请选择",
// sheet 控制
showSheet: false,
sheetOptions: [],
sheetTitle: "",
sheetKey: "",
selectedValue: "",
// 下拉刷新
refreshing: false
},
getKeyByValue(obj, value) {
return Object.keys(obj).find(key => obj[key] === value);
},
// 转换用户数据格式:从旧格式转换为新格式
transformUserData(data) {
if (!data) return this.clearObjectValues(this.data.user);
const transformed = {
nickname: data.nickname || "",
avatar: data.avatar || "",
bio: data.bio || "",
gender: typeof data.gender === 'number' ? data.gender : (genderMap[data.gender] !== undefined ? genderMap[data.gender] : 0),
birthday: data.birthday || "",
ethnicity: data.ethnicity || "",
occupation: data.occupation || "",
school: data.school || "", // 兼容旧字段 education
hometown: Array.isArray(data.hometown) ? data.hometown : [],
zodiacSign: data.zodiacSign || "", // 兼容旧字段 constellation
height: typeof data.height === 'number' ? data.height : (data.height ? parseInt(data.height) || 0 : 0),
mbtiType: data.mbtiType || "", // 兼容旧字段 personality
sleepHabit: data.sleepHabit || "", // 兼容旧字段 sleep
socialActivity: data.socialActivity || "" // 兼容旧字段 social
};
// 如果有生日但没有星座,根据生日自动生成星座
if (transformed.birthday && !transformed.zodiacSign) {
const generatedZodiacSign = calculateConstellationFromBirthday(transformed.birthday);
if (generatedZodiacSign) {
transformed.zodiacSign = generatedZodiacSign;
console.log('根据生日自动生成星座:', transformed.birthday, '->', generatedZodiacSign);
}
}
// 更新性别显示文字
this.updateGenderDisplay(transformed.gender);
return transformed;
},
// 更新性别显示文字
updateGenderDisplay(gender) {
let displayText = "请选择";
if (gender !== undefined && gender !== null) {
if (typeof gender === 'number' && genderReverseMap[gender] !== undefined) {
displayText = genderReverseMap[gender];
} else if (typeof gender === 'string') {
displayText = gender;
}
}
this.setData({ genderDisplayText: displayText });
},
async onLoad() {
try {
// 获取 app 实例
const app = getApp();
// 初始化屏幕适配
await app.initScreen(this);
const pages = getCurrentPages();
const currPage = pages[pages.length - 1]; // 当前页面实例
const profile = pages[pages.length - 2]; // 上一个页面实例profile
if (profile && profile.initSystemInfo) {
profile.initSystemInfo();
}
if (profile && profile.loadUserData) {
profile.loadUserData();
}
const clearData = this.clearObjectValues(this.data.user);
// 获取用户信息
try {
const appData = await apiClient.getUserInfo();
if (appData && appData.code === 0 && appData.data) {
// 转换数据格式以匹配新的数据结构
const userData = this.transformUserData(appData.data);
this.setData({ user: userData });
// app.globalData.userInfo = userData;
// wx.setStorageSync('userInfo', userData);
} else {
// 如果获取失败,使用默认数据
this.setData({ user: clearData });
this.updateGenderDisplay(clearData.gender);
}
} catch (error) {
console.error('获取用户信息失败:', error);
// 使用默认数据
this.setData({ user: clearData });
this.updateGenderDisplay(clearData.gender);
}
} catch (error) {
console.error('页面加载失败:', error);
}
},
onUnload() {
// 移除自动保存功能,改为点击保存按钮才保存
// 用户可以通过保存按钮主动保存,或者直接返回(不保存)
},
clearObjectValues(obj) {
Object.keys(obj).forEach(key => {
const value = obj[key];
if (Array.isArray(value)) {
// 清空为新数组
obj[key] = [];
} else {
const map = {
'object': {},
'string': '',
'number': 0,
'boolean': false,
}
const type = typeof value;
obj[key] = map[type] ?? null;
}
});
return obj
},
/* ---------- 头像 -------------- */
async chooseAvatar() {
try {
// 选择图片
const res = await new Promise((resolve, reject) => {
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: resolve,
fail: reject
});
});
if (!res.tempFilePaths || res.tempFilePaths.length === 0) {
return;
}
const filePath = res.tempFilePaths[0];
// 显示上传中提示
wx.showLoading({ title: '上传中...', mask: true });
// 使用 COS 上传头像
// const uploadResult = await cosManager.upload(filePath, {
// fileType: 'avatar',
// enableDedup: false, // 启用去重,节省存储空间
// onProgress: (percent) => {
// console.log('上传进度:', percent + '%');
// }
// });
// 使用 COS 上传头像
const uploadResult = await cosManager.upload(filePath, {
fileType: 'avatar',
enableDedup: true, // 启用去重,节省存储空间
onProgress: (percent) => {
console.log('上传进度:', percent + '%');
},
enableCompress: false,
compressOptions: {
maxWidth: 400,
maxHeight: 400,
targetSize: 30 * 1024
}
});
console.log('上传结果:', uploadResult);
// 检查上传结果
if (!uploadResult) {
throw new Error('上传失败:未获取到上传结果');
}
if (uploadResult.success !== true) {
const errorMsg = uploadResult?.error || uploadResult?.message || '上传失败';
console.error('上传失败:', errorMsg, uploadResult);
throw new Error(errorMsg);
}
// 获取上传后的文件URL
const avatarUrl = uploadResult.fileUrl || uploadResult.originalUrl;
if (!avatarUrl) {
console.error('上传返回数据:', uploadResult);
throw new Error('上传失败未获取到文件URL');
}
// 更新本地头像
const user = Object.assign({}, this.data.user, { avatar: avatarUrl });
this.setData({ user });
// app.globalData.userInfo.user= {
// ...app.globalData.userInfo.user,
// avatar:avatarUrl
// };
// 立即同步头像到 NIM
try {
await nimUserManager.updateSelfUserInfo({
avatar: avatarUrl
});
console.log('✅ 头像已同步到 NIM');
} catch (nimError) {
console.error('⚠️ 同步头像到 NIM 失败:', nimError);
// 不影响主流程,静默失败
}
// 立即同步头像到业务后端
try {
await apiClient.updateUserProfile({ avatar: avatarUrl });
console.log('✅ 头像已同步到后端');
} catch (apiError) {
console.error('⚠️ 同步头像到后端失败:', apiError);
// 不影响主流程,静默失败
}
// 通知 profile 页面更新头像
try {
const pages = getCurrentPages();
const profilePage = pages[pages.length - 2];
if (profilePage) {
profilePage.onLoad();
}
} catch (error) {
console.error('通知 profile 页面更新头像失败:', error);
// 不影响主流程,静默失败
}
wx.hideLoading();
wx.showToast({
title: '上传成功',
icon: 'success',
duration: 2000
});
} catch (error) {
wx.hideLoading();
console.error('上传头像失败:', error);
wx.showToast({
title: error.message || '上传失败,请重试',
icon: 'none',
duration: 2000
});
}
},
// chooseImage(useCamera) {
// const self = this;
// wx.chooseImage({
// count: 1,
// sizeType: ['compressed'],
// sourceType: useCamera ? ['camera'] : ['album'],
// success(res) {
// const tempPath = res.tempFilePaths[0];
// const user = Object.assign({}, self.data.user, { avatar: tempPath });
// self.setData({ user });
// wx.setStorageSync('user_profile_v1', user);
// }
// });
// },
// 昵称输入
onNicknameInput(e) {
const user = Object.assign({}, this.data.user, { nickname: e.detail.value });
this.setData({ user });
},
/* ---------- 个人简介 ---------- */
onIntroInput(e) {
// 直接更新 user.bio
const user = Object.assign({}, this.data.user, { bio: e.detail.value });
this.setData({ user });
},
/* ---------- 种族 ---------- */
onEthnicityInput(e) {
const user = Object.assign({}, this.data.user, { ethnicity: e.detail.value });
this.setData({ user });
},
/* ---------- 毕业院校 ---------- */
onSchoolInput(e) {
const user = Object.assign({}, this.data.user, { school: e.detail.value });
this.setData({ user });
},
/* ---------- bottom sheet 通用打开 ---------- */
openSheet(e) {
// data-options 会以字符串形式传入,如果直接传对象需要自定义组件
// 这里我们支持两种写法:如果元素上有 data-options 属性且为 JSON 字符串则尝试解析注意wxml 中直接传数组会被字符串化)
const dataset = e.currentTarget.dataset || {};
const key = dataset.key;
const title = dataset.title || '请选择';
let options = dataset.options;
// dataset.options 在 wxml 传递数组时通常会变成字符串 "[object Object]" —— 我们优先根据 key 在定义好的数组中取
if (!options || typeof options === 'string') {
const map = {
occupation: occupationOptions,
school: schoolOptions,
gender: genderOptions,
zodiacSign: zodiacSignOptions,
mbtiType: mbtiTypeOptions,
sleepHabit: sleepHabitOptions,
socialActivity: socialActivityOptions,
height: heightOptions,
}
options = map[key] || [];
}
// 获取当前选中值,如果是性别需要转换为文字显示
let selectedValue = "";
if (key === 'gender') {
const genderNum = this.data.user[key];
if (typeof genderNum === 'number' && genderReverseMap[genderNum] !== undefined) {
selectedValue = genderReverseMap[genderNum];
} else if (typeof genderNum === 'string') {
selectedValue = genderNum;
}
} else {
selectedValue = this.data.user[key] || "";
}
this.setData({
showSheet: true,
sheetOptions: options,
sheetTitle: title,
sheetKey: key,
selectedValue
});
},
closeSheet() {
this.setData({ showSheet: false, sheetOptions: [], sheetTitle: "", sheetKey: "", selectedValue: "" });
},
onSheetSelect(e) {
const idx = e.currentTarget.dataset.index;
const val = this.data.sheetOptions[idx];
const key = this.data.sheetKey;
if (!key) return this.closeSheet();
const user = Object.assign({}, this.data.user);
// 如果是性别选择,需要转换为数字
if (key === 'gender') {
user[key] = genderMap[val] !== undefined ? genderMap[val] : 0;
// 更新性别显示文字
this.updateGenderDisplay(user[key]);
} else {
user[key] = val;
}
this.setData({ user, selectedValue: val }, () => {
this.closeSheet();
});
},
/* ---------- 生日 ---------- */
onBirthdayChange(e) {
const val = e.detail.value;
const user = Object.assign({}, this.data.user, { birthday: val });
// 更新星座(使用公共方法)
user.zodiacSign = calculateConstellationFromBirthday(val);
this.setData({ user });
},
/* ---------- 家乡 ---------- */
onHometownChange(e) {
const nick = e.detail?.value ?? this.data.user.hometown;
const user = Object.assign({}, this.data.user, { hometown: nick });
this.setData({ user });
// 更新用户信息
// apiClient.updateUserProfile(user)
// .then(res => {
// console.log('更新用户信息成功', res);
// wx.showToast({ title: '更新用户信息成功', icon: 'success' });
// this.setData({ user });
// })
// .catch(err => {
// console.error('更新用户信息失败', err);
// wx.showToast({ title: '更新用户信息失败', icon: 'none' });
// });
},
/* ---------- 下拉刷新 ---------- */
onRefresh() {
this.setData({ refreshing: true });
// 重新加载用户信息
setTimeout(async () => {
try {
const clearData = this.clearObjectValues(this.data.user);
const appData = await apiClient.getUserInfo();
if (appData && appData.code === 0 && appData.data) {
// 转换数据格式以匹配新的数据结构
const userData = this.transformUserData(appData.data);
this.setData({ user: userData });
} else {
this.setData({ user: clearData });
this.updateGenderDisplay(clearData.gender);
}
} catch (error) {
console.error('刷新用户信息失败:', error);
} finally {
this.setData({ refreshing: false });
}
}, 500);
},
/* ---------- 保存按钮 ---------- */
async handleSave() {
try {
// 延迟一下,确保失焦完成
await new Promise(resolve => setTimeout(resolve, 100));
wx.showLoading({ title: '保存中...', mask: true });
// 更新用户信息
const user = Object.assign({}, this.data.user);
// 确保性别是数字格式0=未知 1=男 2=女 3=其他)
if (typeof user.gender === 'string') {
user.gender = genderMap[user.gender] !== undefined ? genderMap[user.gender] : 0;
} else if (typeof user.gender !== 'number') {
user.gender = 0;
}
// 确保身高是数字
if (typeof user.height === 'string') {
user.height = parseInt(user.height) || 0;
}
if (user.height === 0) {//==0提交会报错 所以删除
delete user.height;
}
const res = await apiClient.updateUserProfile(user);
// 同步昵称和头像到 NIM
if (user.nickname) {
try {
await nimUserManager.updateSelfUserInfo({
nickname: user.nickname,
avatar: user.avatar || undefined
});
console.log('✅ 用户资料已同步到 NIM');
} catch (nimError) {
console.error('⚠️ 同步用户资料到 NIM 失败:', nimError);
// 不影响主流程,静默失败
}
}
wx.hideLoading();
wx.showToast({
title: '保存成功',
icon: 'success',
duration: 2000
});
// 刷新全局用户信息缓存
try {
await apiClient.refreshUserInfoCache();
} catch (error) {
console.error('刷新全局用户信息缓存失败:', error);
}
// 更新全局用户信息缓存
try {
const app = getApp();
if (app.globalData.userInfo) {
// 更新全局用户信息,包括所有字段
const globalUserInfo = app.globalData.userInfo;
if (globalUserInfo.user) {
// 更新所有字段到全局用户信息(使用新字段名)
Object.assign(globalUserInfo.user, {
nickname: user.nickname,
avatar: user.avatar,
bio: user.bio,
gender: user.gender,
birthday: user.birthday,
ethnicity: user.ethnicity,
occupation: user.occupation,
school: user.school,
hometown: user.hometown,
zodiacSign: user.zodiacSign,
height: user.height,
mbtiType: user.mbtiType,
sleepHabit: user.sleepHabit,
socialActivity: user.socialActivity
});
}
}
} catch (error) {
console.error('更新全局用户信息失败:', error);
}
// 通知 profile 页面更新用户信息
try {
const pages = getCurrentPages();
// 查找 profile 页面(上一个页面)
const profilePage = pages[pages.length - 2];
if (profilePage) {
// 重新加载用户数据(会从全局缓存读取最新数据)
if (typeof profilePage.loadUserData === 'function') {
profilePage.loadUserData();
}
// 直接更新 profile 页面的 userInfo包括所有字段
const profileUserInfo = profilePage.data.userInfo || {};
if (!profileUserInfo.user) {
profileUserInfo.user = {};
}
// 重新计算年龄(根据生日)
let calculatedAge = null;
if (user.birthday && typeof profilePage.calculateAge === 'function') {
calculatedAge = profilePage.calculateAge(user.birthday);
}
// 更新所有字段
const updateData = {
'userInfo.user.nickname': user.nickname,
'userInfo.user.avatar': user.avatar,
'userInfo.user.bio': user.bio,
'userInfo.user.gender': user.gender,
'userInfo.user.birthday': user.birthday,
'userInfo.user.ethnicity': user.ethnicity,
'userInfo.user.occupation': user.occupation,
'userInfo.user.school': user.school,
'userInfo.user.hometown': user.hometown,
'userInfo.user.zodiacSign': user.zodiacSign,
'userInfo.user.height': user.height,
'userInfo.user.mbtiType': user.mbtiType,
'userInfo.user.sleepHabit': user.sleepHabit,
'userInfo.user.socialActivity': user.socialActivity,
'userInfo.avatar': user.avatar,
'userInfo.avatarUrl': user.avatar,
'userInfo.nickname': user.nickname,
'userInfo.bio': user.bio,
'userInfo.gender': user.gender,
'userInfo.birthday': user.birthday,
'userInfo.ethnicity': user.ethnicity,
'userInfo.occupation': user.occupation,
'userInfo.school': user.school,
'userInfo.hometown': user.hometown,
'userInfo.zodiacSign': user.zodiacSign,
'userInfo.height': user.height,
'userInfo.mbtiType': user.mbtiType,
'userInfo.sleepHabit': user.sleepHabit,
'userInfo.socialActivity': user.socialActivity
};
// 如果有计算出的年龄,也更新
if (calculatedAge !== null) {
updateData.calculatedAge = calculatedAge;
}
profilePage.setData(updateData);
}
} catch (error) {
console.error('通知 profile 页面更新失败:', error);
// 不影响主流程,静默失败
}
// 保存成功后,延迟返回上一页
setTimeout(() => {
wx.navigateBack();
}, 1500);
} catch (err) {
wx.hideLoading();
console.error('保存用户信息失败', err);
wx.showToast({
title: err.message || '保存失败,请重试',
icon: 'none',
duration: 2000
});
}
}
});

View file

@ -0,0 +1,7 @@
{
"navigationBarTitleText": "个人资料",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"backgroundColor": "#000000",
"backgroundTextStyle": "light"
}

View file

@ -0,0 +1,160 @@
<view class="personal">
<scroll-view class="personal-container"
scroll-y="true"
enhanced="true"
bounces="true"
show-scrollbar="false"
refresher-enabled="true"
refresher-triggered="{{refreshing}}"
bindrefresherrefresh="onRefresh">
<!-- 头像 + customId + 昵称 -->
<view class="card intro-card">
<view class="row">
<text class="left">头像</text>
<view class="right">
<image src="{{user.avatar}}" class="avatar-img" mode="aspectFill" bindtap="chooseAvatar"/>
</view>
</view>
<view class="row">
<text class="left">昵称</text>
<view class="right">
<input
class="input"
placeholder="请输入昵称"
value="{{user.nickname}}"
maxlength="15"
bindinput="onNicknameInput"
/>
</view>
</view>
</view>
<!-- 个人简介 -->
<text class="left" style="position:relative;top:-12rpx;">个人简介</text>
<view class="card intro-card">
<textarea
class="textarea"
value="{{user.bio}}"
bindinput="onIntroInput"
maxlength="150"
placeholder="请填写个人简介最多150字"
></textarea>
</view>
<!-- 关于我 -->
<text class="left">关于我</text>
<view class="card intro-card">
<view class="row" bindtap="openSheet" data-key="occupation" data-title="选择职业" data-options="{{occupationOptions}}">
<text class="left">职业</text>
<view class="right">
<text class="value">{{user.occupation || '请选择'}}</text>
<text class="chev"></text>
</view>
</view>
<view class="row">
<text class="left">毕业院校</text>
<view class="right">
<input
class="input"
placeholder="请输入毕业院校"
value="{{user.school}}"
maxlength="100"
bindinput="onSchoolInput"
/>
</view>
</view>
<view class="row" bindtap="openSheet" data-key="gender" data-title="选择性别" data-options="{{genderOptions}}">
<text class="left">性别</text>
<view class="right">
<text class="value">{{genderDisplayText}}</text>
<text class="chev"></text>
</view>
</view>
<view class="row">
<text class="left">民族</text>
<view class="right">
<input
class="input"
placeholder="请输入民族"
value="{{user.ethnicity}}"
maxlength="50"
bindinput="onEthnicityInput"
/>
</view>
</view>
<view class="row">
<text class="left">生日</text>
<view class="right">
<picker mode="date" value="{{user.birthday}}" start="1900-01-01" end="2100-12-31" bindchange="onBirthdayChange">
<text class="value">{{user.birthday || '请选择'}}</text>
</picker>
<text class="chev"></text>
</view>
</view>
<view class="row">
<text class="left">家乡</text>
<view class="right">
<picker mode="region" value="{{user.hometown}}" bindchange="onHometownChange">
<text class="value" wx:if="{{!user.hometown || user.hometown.length === 0}}">请选择</text>
<view class="value">{{user.hometown[0]}} {{user.hometown[1]}} {{user.hometown[2]}}</view>
</picker>
<text class="chev"></text>
</view>
</view>
</view>
<!-- 更多 -->
<text class="left">更多</text>
<view class="card intro-card">
<view class="row">
<text class="left">星座</text>
<view class="right"><text style="color:#bfbfbf;font-size: 28rpx;">{{user.zodiacSign || '选择生日后会自动试算哦😘'}}</text></view>
</view>
<view class="row" bindtap="openSheet" data-key="height" data-title="选择身高" data-options="{{heightOptions}}">
<text class="left">身高</text>
<view class="right"><text class="value">{{user.height || '请选择'}} cm</text><text class="chev"></text></view>
</view>
<view class="row" bindtap="openSheet" data-key="mbtiType" data-title="选择MBTI类型" data-options="{{mbtiTypeOptions}}">
<text class="left">MBTI类型</text>
<view class="right"><text class="value">{{user.mbtiType || '请选择'}}</text><text class="chev"></text></view>
</view>
<view class="row" bindtap="openSheet" data-key="sleepHabit" data-title="选择睡眠习惯" data-options="{{sleepHabitOptions}}">
<text class="left">睡眠习惯</text>
<view class="right"><text class="value">{{user.sleepHabit || '请选择'}}</text><text class="chev"></text></view>
</view>
<view class="row" bindtap="openSheet" data-key="socialActivity" data-title="选择社交活跃度" data-options="{{socialActivityOptions}}">
<text class="left">社交活跃度</text>
<view class="right"><text class="value">{{user.socialActivity || '请选择'}}</text><text class="chev"></text></view>
</view>
</view>
</scroll-view>
<!-- 保存按钮 -->
<view class="save-button-container">
<view class="save-button" bind:tap="handleSave">保存</view>
</view>
<!-- 底部内联选择面板sheet -->
<view wx:if="{{showSheet}}">
<view class="mask" bindtap="closeSheet"></view>
<view class="sheet">
<view class="sheet-handle"></view>
<view class="sheet-title">
<text>{{sheetTitle}}</text>
<!-- <button class="sheet-done" bindtap="closeSheet">完成</button> -->
</view>
<scroll-view class="sheet-list" scroll-y="true">
<view wx:for="{{sheetOptions}}" wx:key="index" wx:for-item="item" data-index="{{index}}" bindtap="onSheetSelect" class="sheet-item {{item==selectedValue? 'active' : ''}}">
<text class="sheet-item-text">{{item}}</text>
<text wx:if="{{item==selectedValue}}" class="sheet-check">✓</text>
</view>
</scroll-view>
</view>
</view>
</view>

View file

@ -0,0 +1,282 @@
/* 全局背景与字体 */
page, .personal {
height: 100%;
background: #000000;
padding: 30rpx;
display: flex;
flex-direction: column;
position: relative;
}
/* 容器 */
.personal-container {
/* flex: 1; */
background: transparent;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
/* 卡片基础 */
.card {
background: rgb(105 105 105 / 30%);
border-radius: 18rpx;
/* 减小整体内边距,给行内元素更多可用宽度 */
padding: 12rpx 14rpx;
margin-bottom: 20rpx;
box-shadow: 0 6rpx 18rpx rgba(0,0,0,0.6);
color: #e8e8e8;
}
/* 头像区域 */
.avatar-area {
display: flex;
align-items: center;
justify-content: space-between;
}
.avatar-area .left .label {
font-size: 26rpx;
color: #cfcfcf;
}
/* .avatar-right {
display: flex;
align-items: center;
} */
.avatar-img {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
background: #000000;
margin-left: 18rpx;
margin-right: 18rpx;
display: block;
}
/* 头像外层容器(如有使用) */
.avatar-wrap {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
overflow: hidden;
background: #000;
margin-left: 18rpx;
margin-right: 18rpx;
box-shadow: 0 6rpx 18rpx rgba(0,0,0,0.4);
}
/* 头像占位样式(无头像时) */
.avatar-placeholder {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
background: linear-gradient(135deg, #151516 0%, #0F0F11 100%);
display: flex;
align-items: center;
justify-content: center;
color: #e8e8e8;
font-size: 40rpx;
margin-left: 18rpx;
margin-right: 18rpx;
box-shadow: 0 6rpx 18rpx rgba(0,0,0,0.4);
}
.chev {
color: #9b9b9b;
font-size: 30rpx;
margin-left: 8rpx;
}
/* 小按钮样式 */
.icon-btn {
/* 小按钮,移除强制宽度,允许在小屏上收缩 */
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 28rpx;
width: 120rpx;
padding: 4rpx 8rpx;
font-size: 26rpx;
background-color: rgb(143 49 255 / 32%);
box-sizing: border-box;
flex: 0 0 auto;
line-height: 1;
}
/* 输入框样式 */
.input {
font-size: 24rpx;
/* 可见的文本颜色 */
color: #ffffff;
/* 右对齐输入 */
text-align: right;
flex: 1 1 auto; /* 主动占满剩余空间 */
width: auto;
min-width: 0; /* 允许在 flex 容器中收缩,避免换行 */
border: none; /* 移除默认边框 */
background: transparent;
padding: 0 0 0 8rpx;
}
/* 个人简介 */
.intro-box {
background: rgb(105 105 105 / 30%);
border-radius: 12rpx;
padding: 16rpx;
min-height: 140rpx;
}
.intro-text {
color: #bfbfbf;
line-height: 1.6;
font-size: 24rpx;
white-space: normal;
word-wrap: break-word;
word-break: break-word;
}
/* 编辑态 textarea */
.textarea {
width: 95%;
min-height: 80rpx;
max-height: 200rpx;
background: #0b0d0e;
color: #ddd;
border-radius: 12rpx;
padding: 16rpx;
font-size: 24rpx;
border: 1rpx solid rgba(255,255,255,0.02);
}
.intro-actions {
display: flex;
justify-content: flex-end;
margin-top: 12rpx;
}
.btn {
padding: 10rpx 20rpx;
margin-left: 12rpx;
border-radius: 12rpx;
font-size: 24rpx;
border: none;
}
.cancel {
background: transparent;
color: #9b9b9b;
border: 1rpx solid rgba(255,255,255,0.03);
}
.save {
background: linear-gradient(90deg,#00c2a8,#00a3ff);
color: #fff;
}
/* 卡片样式 */
.intro-card .row {
display: flex;
flex-wrap: nowrap; /* 禁止换行,保证左侧标签与右侧内容在同一行 */
justify-content: space-between;
align-items: center;
/* 减小行内上下与左右内边距,释放水平空间 */
padding: 22rpx 8rpx;
border-bottom: 1rpx solid rgba(255,255,255,0.02);
}
.intro-card .row:last-child { border-bottom: none; }
.left { color: #cfcfcf; font-size: 26rpx; /* 固定或最小宽度,避免被压缩换行 */
/* 将左侧最小宽度适度减小,给输入留出更多空间 */
min-width: 88rpx;
flex: 0 0 auto;
white-space: nowrap; /* 防止标签内换行 */
}
.right { display:flex; align-items:center; flex: 1 1 auto; justify-content: flex-end; gap: 6rpx; }
.value { color: #e3e3e3; font-size: 26rpx; margin-right: 10rpx; }
/* 底部选择面板sheet与遮罩 */
.mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 50;
}
.sheet {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 60;
background: #0f1112;
border-top-left-radius: 28rpx;
border-top-right-radius: 28rpx;
padding: 18rpx;
box-shadow: 0 -8rpx 30rpx rgba(0,0,0,0.6);
}
.sheet-handle {
width: 80rpx;
height: 6rpx;
background: rgba(255,255,255,0.06);
border-radius: 6rpx;
margin: 6rpx auto 12rpx;
}
.sheet-title {
display:flex;
justify-content:space-between;
align-items:center;
padding-bottom: 12rpx;
border-bottom: 1rpx solid rgba(255,255,255,0.03);
}
.sheet-title text { color: white; font-size: 28rpx; }
.sheet-done {
background: transparent;
color: #9aa0a6;
border: none;
font-size: 24rpx;
}
.sheet-list {
max-height: 420rpx;
margin-top: 12rpx;
padding-bottom: 12rpx;
}
.sheet-item {
padding: 16rpx 12rpx;
display:flex;
justify-content:space-between;
align-items:center;
border-bottom: 1rpx solid rgba(255,255,255,0.02);
font-size: 26rpx;
color: #d7d7d7;
}
.sheet-item.active {
background: rgba(0,160,255,0.06);
color: #00a3ff;
}
.sheet-check { color: #00a3ff; font-size: 28rpx; }
/* 保存按钮容器 - 参考 phone-next 样式 */
.save-button-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 40rpx;
box-sizing: border-box;
padding: 0 30rpx;
}
.save-button {
height: 100rpx;
width: 240rpx;
background: linear-gradient(135deg, #000000, #232323);
color: white;
text-align: center;
line-height: 100rpx;
border: 1rpx solid #4e4e4e;
border-radius: 60rpx;
font-size: 40rpx;
font-weight: 500;
box-shadow:
4rpx 4rpx 30rpx rgba(91, 196, 245, 0.1),
0 4rpx 8rpx rgba(255, 255, 255, 0.2) inset;
transition: all 0.2s ease;
}
.save-button:active {
box-shadow:
0 -16rpx 32rpx rgba(18, 158, 240, 0.1) inset, /* 内层上阴影(凹陷) */
0 4rpx 8rpx rgba(0, 0, 0, 0.1) inset; /* 内层下阴影 */
transform: translateY(4rpx); /* 向下偏移,强化"按下"感 */
}