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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
{
"navigationBarTitleText": "",
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"disableScroll": true,
"usingComponents": {}
}

View file

@ -0,0 +1,144 @@
<view class="camera-page">
<!-- 拍摄模式视图 -->
<view class="shoot-mode" wx:if="{{!hasTakenPhoto}}">
<view class="top-controls">
<view class="flash-btn" bindtap="toggleFlash">
<image src="{{flashState === 'off' ? '/images/cross.svg' : '/images/lightning.svg'}}" mode="aspectFit" style="width: 100%; height: 100%;"></image>
</view>
</view>
<view class="camera-container">
<camera
device-position="{{cameraPosition}}"
flash="{{flashState}}"
binderror="handleCameraError"
class="camera-preview"
></camera>
<!-- 双击检测覆盖层 -->
<view class="camera-tap-overlay" catchtap="onCameraDoubleTap"></view>
</view>
<view class="shoot-tip">轻触拍照</view>
<view class="shoot-controls">
<view class="close-btn" bindtap="navigateBack">取消</view>
<view class="shoot-button" bindtap="takePhoto"></view>
<view class="switch-camera-btn" bindtap="switchCamera">
<image src="/images/refresh.svg" mode="aspectFit" style="width: 100%; height: 100%;"></image>
</view>
</view>
</view>
<!-- 编辑模式视图 -->
<view class="edit-mode" wx:if="{{hasTakenPhoto}}">
<view class="edit-container">
<!-- 图片容器 -->
<view class="image-container" style="position: relative; transform: rotate({{rotateAngle}}deg)scale({{imageScale}});">
<image
src="{{currentPhotoPath}}"
mode="widthFix"
class="edit-image"
bindload="onImageLoaded"
></image>
<canvas
id="mosaicCanvas"
type="2d"
hidden="{{!(currentTool ==='mosaic' || currentTool === 'eraser')}}"
style="position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 999; pointer-events: auto;"
bindtouchstart="onCanvasTouchStart"
bindtouchmove="onCanvasTouchMove"
bindtouchend="onCanvasTouchEnd"
></canvas>
<canvas
id="mergeCanvas"
type="2d"
style="position: absolute; left: -9999rpx; top: -9999rpx; width: {{canvasWidth}}px; height: {{canvasHeight}}px;"
></canvas>
<!-- 裁剪框 -->
<view wx:if="{{isCropping}}" class="crop-overlay"
catchtouchstart="startCropMove"
catchtouchmove="onCropTouchMove"
catchtouchend="onCropTouchEnd">
<!-- 遮罩层 -->
<view class="crop-mask">
<!-- 上遮罩 -->
<view class="crop-mask-top" style="height: {{cropBox.top}}px;"></view>
<!-- 中间区域 -->
<view class="crop-mask-middle" style="height: {{cropBox.height}}px;">
<!-- 左遮罩 -->
<view class="crop-mask-left" style="width: {{cropBox.left}}px;"></view>
<!-- 裁剪区域(透明,不可触摸) -->
<view class="crop-area" style="width: {{cropBox.width}}px; height: 100%;"></view>
<!-- 右遮罩 -->
<view class="crop-mask-right"></view>
</view>
<!-- 下遮罩 -->
<view class="crop-mask-bottom"></view>
</view>
<!-- 裁剪框(可拖动和调整) -->
<view class="crop-box"
style="left: {{cropBox.left}}px; top: {{cropBox.top}}px; width: {{cropBox.width}}px; height: {{cropBox.height}}px;">
<!-- 裁剪框边框 -->
<view class="crop-border">
<!-- 四个角的控制点 -->
<view class="crop-handle crop-handle-tl" data-handle="top-left" catchtouchstart="startCropDrag" catchtouchmove="onCropTouchMove" catchtouchend="onCropTouchEnd"></view>
<view class="crop-handle crop-handle-tr" data-handle="top-right" catchtouchstart="startCropDrag" catchtouchmove="onCropTouchMove" catchtouchend="onCropTouchEnd"></view>
<view class="crop-handle crop-handle-bl" data-handle="bottom-left" catchtouchstart="startCropDrag" catchtouchmove="onCropTouchMove" catchtouchend="onCropTouchEnd"></view>
<view class="crop-handle crop-handle-br" data-handle="bottom-right" catchtouchstart="startCropDrag" catchtouchmove="onCropTouchMove" catchtouchend="onCropTouchEnd"></view>
</view>
</view>
</view>
</view>
</view>
<!-- 工具按钮栏 -->
<view class="edit-toolbar">
<view class="edit-tool-group">
<!-- 旋转按钮 -->
<view class="tool-btn {{isRotated ? 'active' : ''}}" bindtap="rotateImage" title="旋转">
<text>🔄</text>
</view>
<!-- 还原按钮 -->
<view class="tool-btn restore-btn {{hasEdits ? 'active' : ''}}" bindtap="restoreOriginal" title="还原">
<text>↩️</text>
</view>
<!-- 裁剪按钮 -->
<view class="tool-btn {{isCropping ? 'active' : ''}}" bindtap="selectTool" data-tool="crop" title="裁剪">
<text>✂️</text>
</view>
<!-- 马赛克按钮 -->
<view class="tool-btn {{currentTool === 'mosaic' ? 'active' : ''}}" bindtap="selectTool" data-tool="mosaic" title="马赛克">
<text>░</text>
</view>
<!-- 橡皮按钮 -->
<view class="tool-btn {{currentTool === 'eraser' ? 'active' : ''}}" bindtap="selectTool" data-tool="eraser" title="橡皮">
<text>🧽</text>
</view>
</view>
</view>
<!-- 底部控制栏-->
<view class="edit-controls">
<view class="close-btn" bindtap="goBackToShoot">重拍</view>
<!-- 裁剪模式下的确认/取消按钮 -->
<view wx:if="{{isCropping}}" class="crop-controls">
<button
type="text"
class="edit-control-btn cancel"
bindtap="cancelCrop"
>取消</button>
<button
type="text"
class="edit-control-btn confirm"
bindtap="applyCrop"
>确定</button>
</view>
<!-- 非裁剪模式下的使用照片按钮 -->
<button
wx:else
type="text"
class="edit-control-btn confirm"
bindtap="confirmPhoto"
>使用照片</button>
</view>
</view>
</view>

View file

@ -0,0 +1,379 @@
.camera-page {
width: 100%;
height: 100vh;
background-color: #000;
color: #fff;
overflow: hidden;
position: relative;
}
.shoot-mode {
width: 100%;
height: 100%;
position: relative;
}
.top-controls {
position: absolute;
top: 80rpx;
left: 20rpx;
z-index: 10;
}
.flash-btn {
width: 60rpx;
height: 60rpx;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
.camera-container {
position: absolute;
top: 150rpx;
left: 0;
width: 100%;
height: 70%;
background-color: #000;
z-index: 1;
/* 确保可以接收点击事件 */
pointer-events: auto;
}
.camera-preview {
width: 100%;
height: 100%;
object-fit: cover;
}
.camera-tap-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
/* 透明覆盖层,用于捕获双击事件 */
background-color: transparent;
pointer-events: auto;
}
.shoot-tip {
position: absolute;
bottom: 200rpx;
left: 50%;
transform: translateX(-50%);
font-size: 28rpx;
color: rgba(255, 255, 255, 0.8);
z-index: 5;
}
.shoot-controls {
position: absolute;
bottom: 40rpx;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
gap: 150rpx;
z-index: 10;
}
.switch-camera-btn {
width: 60rpx;
height: 60rpx;
font-size: 30rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 重置shoot-controls内部按钮的绝对定位 */
.shoot-controls .close-btn,
.shoot-controls .switch-camera-btn {
position: static;
top: auto;
left: auto;
transform: none;
}
/* 关闭按钮样式 */
.close-btn {
width: 120rpx;
height: 60rpx;
font-size: 36rpx;
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
color: #fff;
background-color: transparent;
}
.shoot-button {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
border: 6rpx solid #fff;
display: flex;
align-items: center;
justify-content: center;
margin-right: 30px;
}
.empty-btn {
width: 0;
height: 0;
}
.edit-mode {
width: 100%;
height: 100%;
position: relative;
}
.edit-container {
position: absolute;
top: 150rpx;
left: 0;
width: 100%;
height: 70%;
background-color: #000;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
z-index: 1;
}
.image-container {
transition: transform 0.3s ease;
position: relative;
z-index: 5;
width: 100%;
height: 100%;
}
.edit-image {
width: 100%;
height: 100%;
object-fit: contain;
background-color: transparent;
}
.mosaic-controls {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
z-index: 20;
background-color: rgba(0, 0, 0, 0.5);
padding: 10rpx;
box-sizing: border-box;
}
.mosaic-controls text {
font-size: 26rpx;
}
.edit-toolbar {
position: absolute;
bottom: 100rpx;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx 0;
box-sizing: border-box;
z-index: 15;
background-color: rgba(0, 0, 0, 0.5);
}
.edit-controls {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 40rpx;
box-sizing: border-box;
z-index: 20;
background-color: #333;
}
.edit-tool-group {
display: flex;
gap: 15rpx;
align-items: center;
margin-left: 0;
}
.edit-tool-group .tool-btn {
background-color: rgba(0, 0, 0, 0.5);
padding: 15rpx;
border-radius: 50%;
font-size: 28rpx;
color: #fff;
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.edit-tool-group .tool-btn.active {
background-color: #007aff;
}
.edit-tool-group .restore-btn {
opacity: 0.5;
}
.edit-tool-group .restore-btn.active {
opacity: 1;
}
.edit-tool-group .clear-mosaic-btn {
opacity: 0.5;
}
.edit-tool-group .clear-mosaic-btn.active {
opacity: 1;
}
.edit-control-btn.confirm {
color: #fff;
font-size: 32rpx;
padding: 10rpx 40rpx;
border-radius: 0;
background-color: transparent;
margin-right: 5rpx;
}
.edit-control-btn.cancel {
color: #999;
font-size: 32rpx;
padding: 10rpx 40rpx;
border-radius: 0;
background-color: transparent;
margin-right: 20rpx;
}
.crop-controls {
display: flex;
align-items: center;
}
/* 裁剪相关样式 */
.crop-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: auto;
}
.crop-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.crop-mask-top {
width: 100%;
background-color: rgba(0, 0, 0, 0.6);
flex: 0 0 auto;
}
.crop-mask-bottom {
width: 100%;
background-color: rgba(0, 0, 0, 0.6);
flex: 1;
}
.crop-mask-middle {
display: flex;
width: 100%;
flex-direction: row;
flex: 0 0 auto;
}
.crop-mask-left {
background-color: rgba(0, 0, 0, 0.6);
flex: 0 0 auto;
}
.crop-mask-right {
background-color: rgba(0, 0, 0, 0.6);
flex: 1;
}
.crop-area {
background-color: transparent;
pointer-events: none;
}
.crop-box {
position: absolute;
border: 2rpx solid #fff;
box-sizing: border-box;
pointer-events: none;
z-index: 11;
}
.crop-border {
position: relative;
width: 100%;
height: 100%;
}
.crop-handle {
position: absolute;
width: 50rpx;
height: 50rpx;
background-color: #fff;
border: 3rpx solid #007aff;
border-radius: 50%;
box-sizing: border-box;
z-index: 12;
pointer-events: auto;
/* 使用transform居中定位在裁剪框的角上确保始终可见 */
transform: translate(-50%, -50%);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.crop-handle-tl {
top: 0;
left: 0;
}
.crop-handle-tr {
top: 0;
right: 0;
}
.crop-handle-bl {
bottom: 0;
left: 0;
}
.crop-handle-br {
bottom: 0;
right: 0;
}

View file

@ -0,0 +1,614 @@
const app = getApp();
const apiClient = require('../../../utils/api-client.js');
const imageCacheManager = require('../../../utils/image-cache-manager.js');
Page({
data: {
userInfo: {
avatar: '',
nickname: '未设置昵称',
customId: '123456789',
signature: '',
career: '',
education: '',
gender: '',
birthday: '',
hometown: '',
constellation: '',
height: '',
personalityType: '',
sleepHabit: '',
socialActivity: ''
},
isEditingNickname: false,
isEditingSignature: false,
tempNickname: '',
tempSignature: '',
showConstellationPicker: false,
showPersonalityPicker: false,
showCareerPicker: false,
showEducationPicker: false,
showHometownPicker: false,
showBirthdayPicker: false,
showHeightPicker: false,
showGenderPicker: false,
showSleepHabitPicker: false,
showSocialActivityPicker: false,
constellations: ['水瓶座', '双鱼座', '白羊座', '金牛座', '双子座', '巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座', '摩羯座'],
personalityTypes: ['INTJ', 'INTP', 'ENTJ', 'INFP', 'ENTP', 'INFJ', 'ENFP', 'ENFJ', 'ISTJ', 'ISFJ', 'ISTP', 'ISFP', 'ESTJ', 'ESFJ', 'ESTP', 'ESFP'],
careers: ['初中生', '高中生', '大学生', '研究生', '留学生', '科研', '警察', '医生', '护士', '程序员', '老师', '化妆师', '摄影师', '音乐', '美术', '金融', '厨师', '工程师', '公务员', '互联网', '产品经理', '模特', '演员', '导演', '律师', '创业者', '其他'],
educations: ['北京大学', '清华大学', '复旦大学', '上海交通大学', '浙江大学', '南京大学', '武汉大学', '中山大学', '四川大学', '哈尔滨工业大学', '大专', '中专', '高职', '高中'],
genders: ['男', '女'],
sleepHabits: ['早起鸟儿', '夜猫子', '规律型', '深度睡眠追求者', '碎片化睡眠者', '失眠困扰者', '咖啡因敏感型', '数字戒断者', '运动调节型', '挑战打卡型', '鼾声监测者', '生物钟调节者', '社区分享型'],
socialActivities: ['内容创作者', '观察者', '吃瓜者', '潜水者', '机器人', '社群型用户', 'KOL', 'KOC', '普通用户', '算法依赖型用户', '事件驱动型用户', '季节性活跃用户', '社交维系型用户', '兴趣社群型用户', '职业网络型用户', '娱乐消遣型用户', '购物种草型用户', '互动型用户'],
selectedConstellation: '',
selectedPersonality: '',
selectedCareer: '',
selectedEducation: '',
selectedGender: '',
selectedHeight: 170,
selectedSleepHabit: '',
selectedSocialActivity: '',
searchCareerText: '',
searchEducationText: '',
filteredCareers: [],
filteredEducations: [],
provinces: [],
cities: [],
selectedProvince: '',
selectedCity: '',
selectedYear: '',
selectedMonth: '',
selectedDay: '',
years: [],
months: [],
days: []
},
onAvatarUpdated(payload = {}) {
const displayUrl = payload.cachedUrl || payload.avatarUrl || payload.serverUrl || '';
const latestProfile = payload.userInfo?.user || {};
const baseProfile = this.data.userInfo || {};
const mergedProfile = Object.assign({}, baseProfile, latestProfile);
if (displayUrl) {
mergedProfile.avatar = displayUrl;
mergedProfile.avatarUrl = displayUrl;
}
const updates = {};
if (displayUrl) {
updates.avatar = displayUrl;
}
if (Object.keys(mergedProfile).length > 0) {
updates.userInfo = mergedProfile;
}
if (Object.keys(updates).length > 0) {
this.setData(updates);
}
},
onLoad: function() {
this.loadUserData();
this.initDatePicker();
this.initLocationData();
},
loadUserData: function() {
const authInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {};
const profile = authInfo.user || {};
const normalizedProfile = {
avatar: profile.avatar || '',
nickname: profile.nickname || '未设置昵称',
customId: profile.customId || (profile.id ? 'findme_' + profile.id : '123456789'),
signature: profile.signature || '',
career: profile.career || '',
education: profile.education || '',
gender: profile.gender || '',
birthday: profile.birthday || '',
hometown: profile.hometown || '',
constellation: profile.constellation || '',
height: profile.height || '',
personalityType: profile.personalityType || '',
sleepHabit: profile.sleepHabit || '',
socialActivity: profile.socialActivity || ''
};
this.setData({
userInfo: normalizedProfile,
tempNickname: normalizedProfile.nickname,
tempSignature: normalizedProfile.signature
});
},
// 头像相关功能
changeAvatar: function() {
wx.showActionSheet({
itemList: ['拍照', '从相册选择'],
success: (res) => {
const sourceType = res.tapIndex === 0 ? ['camera'] : ['album'];
this.chooseImage(sourceType);
}
});
},
chooseImage: function(sourceType) {
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: sourceType,
success: (res) => {
if (sourceType[0] === 'camera') {
this.setData({
tempAvatarPath: res.tempFilePaths[0],
showCameraPreview: true
});
} else {
this.uploadAvatar(res.tempFilePaths[0]);
}
}
});
},
retakePhoto: function() {
this.setData({ showCameraPreview: false });
this.chooseImage(['camera']);
},
usePhoto: function() {
this.uploadAvatar(this.data.tempAvatarPath);
this.setData({ showCameraPreview: false });
},
uploadAvatar: async function(tempFilePath) {
wx.showLoading({ title: '上传中...' });
try {
const uploadResult = await apiClient.uploadAvatar(tempFilePath);
if (!uploadResult?.success) {
throw new Error(uploadResult?.message || '上传失败');
}
const displayAvatar = uploadResult.cachedUrl || uploadResult.fileUrl;
if (displayAvatar) {
this.persistProfile({
avatar: displayAvatar,
avatarUrl: displayAvatar
});
}
wx.showToast({ title: '头像更新成功', icon: 'success' });
} catch (error) {
console.error('上传头像失败:', error);
wx.showToast({ title: error.message || '上传失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
// 昵称编辑
startEditNickname: function() {
this.setData({
isEditingNickname: true,
tempNickname: this.data.userInfo.nickname
});
},
confirmEditNickname: function() {
if (this.data.tempNickname.length > 30) {
wx.showToast({ title: '昵称不能超过30字节', icon: 'none' });
return;
}
this.setData({
isEditingNickname: false
});
this.persistProfile({ nickname: this.data.tempNickname });
},
cancelEditNickname: function() {
this.setData({ isEditingNickname: false });
},
// 签名编辑
startEditSignature: function() {
this.setData({
isEditingSignature: true,
tempSignature: this.data.userInfo.signature
});
},
confirmEditSignature: function() {
if (this.data.tempSignature.length > 200) {
wx.showToast({ title: '简介不能超过200字节', icon: 'none' });
return;
}
this.setData({
isEditingSignature: false
});
this.persistProfile({ signature: this.data.tempSignature });
},
cancelEditSignature: function() {
this.setData({ isEditingSignature: false });
},
// 星座选择
openConstellationPicker: function() {
this.setData({
showConstellationPicker: true,
selectedConstellation: this.data.userInfo.constellation
});
},
selectConstellation: function(e) {
const constellation = e.currentTarget.dataset.value;
this.setData({
selectedConstellation: constellation
});
},
confirmConstellation: function() {
this.persistProfile({ constellation: this.data.selectedConstellation });
this.setData({
showConstellationPicker: false
});
},
// 人格类型选择
openPersonalityPicker: function() {
this.setData({
showPersonalityPicker: true,
selectedPersonality: this.data.userInfo.personalityType
});
},
selectPersonality: function(e) {
const personality = e.currentTarget.dataset.value;
this.setData({
selectedPersonality: personality
});
},
confirmPersonality: function() {
this.persistProfile({ personalityType: this.data.selectedPersonality });
this.setData({
showPersonalityPicker: false
});
},
// 职业选择
openCareerPicker: function() {
this.setData({
showCareerPicker: true,
searchCareerText: '',
filteredCareers: this.data.careers
});
},
searchCareer: function(e) {
const text = e.detail.value;
const filtered = this.data.careers.filter(career =>
career.includes(text)
);
this.setData({
searchCareerText: text,
filteredCareers: filtered
});
},
selectCareer: function(e) {
const career = e.currentTarget.dataset.value;
this.persistProfile({ career });
this.setData({
showCareerPicker: false
});
},
// 教育背景选择
openEducationPicker: function() {
this.setData({
showEducationPicker: true,
searchEducationText: '',
filteredEducations: this.data.educations
});
},
searchEducation: function(e) {
const text = e.detail.value;
const filtered = this.data.educations.filter(edu =>
edu.includes(text)
);
this.setData({
searchEducationText: text,
filteredEducations: filtered
});
},
selectEducation: function(e) {
const education = e.currentTarget.dataset.value;
this.persistProfile({ education });
this.setData({
showEducationPicker: false
});
},
// 家乡选择
openHometownPicker: function() {
this.setData({
showHometownPicker: true,
selectedProvince: this.data.userInfo.hometown ? this.data.userInfo.hometown.split(' ')[0] : '',
selectedCity: this.data.userInfo.hometown ? this.data.userInfo.hometown.split(' ')[1] : ''
});
},
confirmHometown: function() {
this.persistProfile({
hometown: `${this.data.selectedProvince} ${this.data.selectedCity}`
});
this.setData({
showHometownPicker: false
});
},
// 生日选择
openBirthdayPicker: function() {
this.setData({
showBirthdayPicker: true
});
},
confirmBirthday: function() {
const formatted = `${this.data.selectedYear}-${this.data.selectedMonth.toString().padStart(2, '0')}-${this.data.selectedDay.toString().padStart(2, '0')}`;
this.persistProfile({ birthday: formatted });
this.setData({
showBirthdayPicker: false
});
},
// 身高选择
openHeightPicker: function() {
this.setData({
showHeightPicker: true,
selectedHeight: this.data.userInfo.height ? parseInt(this.data.userInfo.height) : 170
});
},
// 性别选择
openGenderPicker: function() {
this.setData({
showGenderPicker: true,
selectedGender: this.data.userInfo.gender
});
},
// 睡眠习惯选择
openSleepHabitPicker: function() {
this.setData({
showSleepHabitPicker: true,
selectedSleepHabit: this.data.userInfo.sleepHabit
});
},
// 社交活跃度选择
openSocialActivityPicker: function() {
this.setData({
showSocialActivityPicker: true,
selectedSocialActivity: this.data.userInfo.socialActivity
});
},
// 初始化位置数据
initLocationData: function() {
// 模拟省市数据
this.setData({
provinces: ['北京市', '上海市', '广东省', '江苏省', '浙江省'],
cities: {
'北京市': ['北京市'],
'上海市': ['上海市'],
'广东省': ['广州市', '深圳市', '珠海市'],
'江苏省': ['南京市', '苏州市', '无锡市'],
'浙江省': ['杭州市', '宁波市', '温州市']
},
selectedProvince: this.data.userInfo.hometown ? this.data.userInfo.hometown.split(' ')[0] : '',
selectedCity: this.data.userInfo.hometown ? this.data.userInfo.hometown.split(' ')[1] : '',
hometownValue: [0, 0] // 默认选中第一项
});
},
// 家乡选择变化处理
onHometownChange: function(e) {
const value = e.detail.value;
const province = this.data.provinces[value[0]];
const city = this.data.cities[province][value[1]];
this.setData({
selectedProvince: province,
selectedCity: city,
hometownValue: value
});
},
initDatePicker: function() {
const years = [];
const currentYear = new Date().getFullYear();
for (let i = currentYear; i >= 1950; i--) {
years.push(i);
}
const months = [];
for (let i = 1; i <= 12; i++) {
months.push(i);
}
const days = [];
for (let i = 1; i <= 31; i++) {
days.push(i);
}
// 设置默认日期
let defaultYear = currentYear - 20;
let defaultMonth = 1;
let defaultDay = 1;
if (this.data.userInfo.birthday) {
const parts = this.data.userInfo.birthday.split('-');
if (parts.length === 3) {
defaultYear = parseInt(parts[0]);
defaultMonth = parseInt(parts[1]);
defaultDay = parseInt(parts[2]);
}
}
// 计算默认值的索引
const yearIndex = years.indexOf(defaultYear);
const monthIndex = months.indexOf(defaultMonth);
const dayIndex = days.indexOf(defaultDay);
this.setData({
years: years,
months: months,
days: days,
selectedYear: defaultYear,
selectedMonth: defaultMonth,
selectedDay: defaultDay,
birthdayValue: [yearIndex, monthIndex, dayIndex]
});
},
// 生日选择变化处理
onBirthdayChange: function(e) {
const value = e.detail.value;
const year = this.data.years[value[0]];
const month = this.data.months[value[1]];
const day = this.data.days[value[2]];
this.setData({
selectedYear: year,
selectedMonth: month,
selectedDay: day,
birthdayValue: value
});
}, bindProvinceChange: function(e) {
const province = this.data.provinces[e.detail.value];
this.setData({
selectedProvince: province,
selectedCity: this.data.cities[province][0]
});
},
bindCityChange: function(e) {
this.setData({
selectedCity: this.data.cities[this.data.selectedProvince][e.detail.value]
});
},
bindYearChange: function(e) {
this.setData({
selectedYear: this.data.years[e.detail.value]
});
},
bindMonthChange: function(e) {
this.setData({
selectedMonth: this.data.months[e.detail.value]
});
},
bindDayChange: function(e) {
this.setData({
selectedDay: this.data.days[e.detail.value]
});
}, adjustHeight: function(e) {
this.setData({
selectedHeight: e.detail.value
});
},
// 睡眠习惯选择处理
// 社交活跃度选择处理
selectSleepHabit: function(e) {
const sleepHabit = e.currentTarget.dataset.value;
this.setData({
showSleepHabitPicker: false
});
this.persistProfile({ sleepHabit });
},
selectSocialActivity: function(e) {
const socialActivity = e.currentTarget.dataset.value;
this.setData({
showSocialActivityPicker: false
});
this.persistProfile({ socialActivity });
},
// 身高选择确认
confirmHeight: function() {
this.setData({
showHeightPicker: false
});
this.persistProfile({ height: this.data.selectedHeight });
},
// 性别选择确认
selectGender: function(e) {
const gender = e.currentTarget.dataset.value;
this.setData({
showGenderPicker: false,
showHeightGenderPicker: false
});
this.persistProfile({ gender });
},
// 关闭所有弹出层
closeAllPickers: function() {
this.setData({
showConstellationPicker: false,
showPersonalityPicker: false,
showCareerPicker: false,
showEducationPicker: false,
showHometownPicker: false,
showBirthdayPicker: false,
showHeightPicker: false,
showGenderPicker: false,
showSleepHabitPicker: false,
showSocialActivityPicker: false,
showCameraPreview: false
});
},
persistProfile(patch = {}) {
const mergedProfile = Object.assign({}, this.data.userInfo || {}, patch);
this.setData({ userInfo: mergedProfile });
const authInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {};
const updatedAuth = Object.assign({}, authInfo, {
user: Object.assign({}, authInfo.user || {}, mergedProfile)
});
app.globalData.userInfo = updatedAuth;
if (updatedAuth.token) {
app.globalData.isLoggedIn = true;
}
wx.setStorageSync('userInfo', updatedAuth);
},
// 保存所有修改
saveChanges: async function() {
wx.showLoading({ title: '保存中...' });
try {
const profilePayload = Object.assign({}, this.data.userInfo);
await apiClient.updateUserProfile(profilePayload);
await apiClient.refreshUserInfoCache();
wx.showToast({ title: '资料保存成功', icon: 'success' });
wx.navigateBack();
} catch (error) {
console.error('保存资料失败:', error);
wx.showToast({
title: error.message || '保存失败,请稍后再试',
icon: 'none'
});
} finally {
wx.hideLoading();
}
}
});

View file

@ -0,0 +1,7 @@
{
"navigationBarTitleText": "编辑资料",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"backgroundColor": "#000000",
"disableScroll": true
}

View file

@ -0,0 +1,310 @@
<view class="profile-edit-container">
<!-- 顶部导航栏 -->
<view class="nav-bar">
<view class="nav-back" bindtap="closeAllPickers">
</view>
<view class="nav-save" bindtap="saveChanges">
<text class="save-text">保存</text>
</view>
</view>
<!-- 滑动内容容器 -->
<view class="scroll-container">
<!-- 头像区域 -->
<view class="avatar-section">
<view class="avatar-container">
<image class="avatar" src="{{userInfo.avatar || '/images/placeholder.txt'}}" mode="aspectFill"></image>
<view class="avatar-upload" bindtap="changeAvatar">
<text class="upload-icon">+</text>
</view>
</view>
</view>
<!-- 用户信息区域 -->
<view class="info-section">
<!-- 用户ID -->
<view class="info-item">
<text class="info-label">用户ID</text>
<text class="info-value">{{userInfo.customId}}</text>
</view>
<!-- 昵称 -->
<view class="info-item">
<text class="info-label">昵称</text>
<view class="nickname-container" bindtap="startEditNickname">
<text class="info-value">{{userInfo.nickname}}</text>
<text class="edit-icon">✎</text>
</view>
</view>
</view>
<!-- 个人简历区域 -->
<view class="resume-section">
<view class="section-title">个人简历</view>
<view class="resume-item" bindtap="startEditSignature">
<text class="resume-value">{{userInfo.signature || '点击添加个人简历'}}</text>
<text class="edit-icon">✎</text>
</view>
</view>
<!-- 关于我区域 -->
<view class="about-section">
<view class="section-title">关于我</view>
<!-- 职业 -->
<view class="about-item" bindtap="openCareerPicker">
<text class="about-label">职业</text>
<view class="about-content">
<text class="about-value">{{userInfo.career || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<!-- 教育背景 -->
<view class="about-item" bindtap="openEducationPicker">
<text class="about-label">教育</text>
<view class="about-content">
<text class="about-value">{{userInfo.education || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<!-- 基本信息 -->
<view class="basic-info">
<!-- 性别 -->
<view class="basic-info-item" bindtap="openGenderPicker">
<text class="info-label">性别</text>
<text class="info-value">{{userInfo.gender || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
<!-- 生日 -->
<view class="basic-info-item" bindtap="openBirthdayPicker">
<text class="info-label">生日</text>
<text class="info-value">{{userInfo.birthday || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
<!-- 家乡 -->
<view class="basic-info-item" bindtap="openHometownPicker">
<text class="info-label">家乡</text>
<text class="info-value">{{userInfo.hometown || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
</view>
<!-- 更多区域 -->
<view class="more-section">
<view class="section-title">更多</view>
<!-- 星座 -->
<view class="more-item" bindtap="openConstellationPicker">
<text class="more-label">星座</text>
<view class="more-content">
<text class="more-value">{{userInfo.constellation || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<!-- 身高 -->
<view class="more-item" bindtap="openHeightPicker">
<text class="more-label">身高</text>
<view class="more-content">
<text class="more-value">{{userInfo.height ? userInfo.height + 'cm' : ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<!-- 人格类型 -->
<view class="more-item" bindtap="openPersonalityPicker">
<text class="more-label">人格类型</text>
<view class="more-content">
<text class="more-value">{{userInfo.personalityType || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<!-- 睡眠习惯 -->
<view class="more-item" bindtap="openSleepHabitPicker">
<text class="more-label">睡眠习惯</text>
<view class="more-content">
<text class="more-value">{{userInfo.sleepHabit || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
<!-- 社交活跃度 -->
<view class="more-item" bindtap="openSocialActivityPicker">
<text class="more-label">社交活跃度</text>
<view class="more-content">
<text class="more-value">{{userInfo.socialActivity || ' '}}</text>
<text class="arrow-icon">→</text>
</view>
</view>
</view>
<!-- 编辑昵称弹窗 -->
<view class="modal-mask" wx:if="{{isEditingNickname}}" bindtap="cancelEditNickname"></view>
<view class="modal-container" wx:if="{{isEditingNickname}}">
<view class="modal-title">编辑昵称</view>
<input class="modal-input" value="{{tempNickname}}" bindinput="inputNickname" maxlength="30" placeholder="请输入昵称"></input>
<view class="modal-buttons">
<view class="modal-button cancel" bindtap="cancelEditNickname">取消</view>
<view class="modal-button confirm" bindtap="confirmEditNickname">确定</view>
</view>
</view>
<!-- 编辑签名弹窗 -->
<view class="modal-mask" wx:if="{{isEditingSignature}}" bindtap="cancelEditSignature"></view>
<view class="modal-container" wx:if="{{isEditingSignature}}">
<view class="modal-title">编辑个人简介</view>
<textarea class="modal-textarea" value="{{tempSignature}}" bindinput="inputSignature" maxlength="200" placeholder="请输入个人简介"></textarea>
<view class="modal-buttons">
<view class="modal-button cancel" bindtap="cancelEditSignature">取消</view>
<view class="modal-button confirm" bindtap="confirmEditSignature">确定</view>
</view>
</view>
<!-- 星座选择器 -->
<view class="picker-mask" wx:if="{{showConstellationPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showConstellationPicker}}">
<view class="picker-title">选择星座</view>
<view class="constellation-grid">
<view class="constellation-item {{selectedConstellation === item ? 'selected' : ''}}" wx:for="{{constellations}}" wx:key="index" data-value="{{item}}" bindtap="selectConstellation">{{item}}</view>
</view>
<view class="picker-button confirm" bindtap="confirmConstellation">确定</view>
</view>
<!-- 人格类型选择器 -->
<view class="picker-mask" wx:if="{{showPersonalityPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showPersonalityPicker}}">
<view class="picker-title">选择人格类型</view>
<view class="personality-grid">
<view class="personality-item {{selectedPersonality === item ? 'selected' : ''}}" wx:for="{{personalityTypes}}" wx:key="index" data-value="{{item}}" bindtap="selectPersonality">{{item}}</view>
</view>
<view class="picker-button confirm" bindtap="confirmPersonality">确定</view>
</view>
<!-- 职业选择器 -->
<view class="picker-mask" wx:if="{{showCareerPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showCareerPicker}}">
<view class="picker-title">选择职业</view>
<view class="search-container">
<input class="search-input" placeholder="搜索职业" value="{{searchCareerText}}" bindinput="searchCareer"></input>
<text class="search-icon">🔍</text>
</view>
<scroll-view class="career-scroll" scroll-y>
<view class="career-item" wx:for="{{filteredCareers}}" wx:key="index" data-value="{{item}}" bindtap="selectCareer">{{item}}</view>
</scroll-view>
</view>
<!-- 教育背景选择器 -->
<view class="picker-mask" wx:if="{{showEducationPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showEducationPicker}}">
<view class="picker-title">选择教育背景</view>
<view class="search-container">
<input class="search-input" placeholder="搜索院校" value="{{searchEducationText}}" bindinput="searchEducation"></input>
<text class="search-icon">🔍</text>
</view>
<scroll-view class="education-scroll" scroll-y>
<view class="education-item" wx:for="{{filteredEducations}}" wx:key="index" data-value="{{item}}" bindtap="selectEducation">{{item}}</view>
</scroll-view>
</view>
<!-- 家乡选择器 - 滑动选择方式 -->
<view class="picker-mask" wx:if="{{showHometownPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showHometownPicker}}">
<view class="picker-title">选择家乡</view>
<view class="hometown-picker-view">
<picker-view indicator-style="height: 50rpx;" style="width: 100%; height: 400rpx;" bindchange="onHometownChange" value="{{hometownValue}}">
<picker-view-column>
<view wx:for="{{cities[selectedProvince] || []}}" wx:key="index" style="line-height: 50rpx;">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{provinces}}" wx:key="index" style="line-height: 50rpx;">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
<view class="picker-button confirm" bindtap="confirmHometown">确定</view>
</view>
<!-- 生日选择器 - 滑动选择方式 -->
<view class="picker-mask" wx:if="{{showBirthdayPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showBirthdayPicker}}">
<view class="picker-title">选择生日</view>
<view class="birthday-picker-view">
<picker-view indicator-style="height: 50rpx;" style="width: 100%; height: 400rpx;" bindchange="onBirthdayChange">
<picker-view-column>
<view wx:for="{{years}}" wx:key="index" style="line-height: 50rpx;">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{months}}" wx:key="index" style="line-height: 50rpx;">{{item}}月</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{days}}" wx:key="index" style="line-height: 50rpx;">{{item}}日</view>
</picker-view-column>
</picker-view>
</view>
<view class="picker-button confirm" bindtap="confirmBirthday">确定</view>
</view>
<!-- 身高选择器 -->
<view class="picker-mask" wx:if="{{showHeightPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showHeightPicker}}">
<view class="picker-title">选择身高</view>
<view class="height-picker">
<text class="picker-label">身高</text>
<slider min="130" max="220" value="{{selectedHeight}}" show-value bindchange="adjustHeight"></slider>
<text class="height-value">{{selectedHeight}}cm</text>
</view>
<view class="picker-button confirm" bindtap="confirmHeight">确定</view>
</view>
<!-- 性别选择器 -->
<view class="picker-mask" wx:if="{{showGenderPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showGenderPicker}}">
<view class="picker-title">选择性别</view>
<view class="gender-picker">
<text class="picker-label">性别</text>
<view class="gender-options">
<view class="gender-option {{selectedGender === '男' ? 'selected' : ''}}" data-value="男" bindtap="selectGender">男</view>
<view class="gender-option {{selectedGender === '女' ? 'selected' : ''}}" data-value="女" bindtap="selectGender">女</view>
</view>
</view>
</view>
<!-- 睡眠习惯选择器 -->
<view class="picker-mask" wx:if="{{showSleepHabitPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showSleepHabitPicker}}">
<view class="picker-title">选择睡眠习惯</view>
<view class="sleep-habit-section">
<scroll-view class="sleep-habit-scroll" scroll-y>
<view class="habit-item" wx:for="{{sleepHabits}}" wx:key="index" data-value="{{item}}" bindtap="selectSleepHabit">{{item}}</view>
</scroll-view>
</view>
</view>
<!-- 社交活跃度选择器 -->
<view class="picker-mask" wx:if="{{showSocialActivityPicker}}" bindtap="closeAllPickers"></view>
<view class="picker-container" wx:if="{{showSocialActivityPicker}}">
<view class="picker-title">选择社交活跃度</view>
<view class="social-activity-section">
<scroll-view class="social-activity-scroll" scroll-y>
<view class="activity-item" wx:for="{{socialActivities}}" wx:key="index" data-value="{{item}}" bindtap="selectSocialActivity">{{item}}</view>
</scroll-view>
</view>
</view>
</view>
<!-- 相机预览 -->
<view class="camera-mask" wx:if="{{showCameraPreview}}" bindtap="retakePhoto"></view>
<view class="camera-preview" wx:if="{{showCameraPreview}}">
<image src="{{tempAvatarPath}}" mode="aspectFit"></image>
<view class="preview-buttons">
<view class="preview-button retake" bindtap="retakePhoto">重拍</view>
<view class="preview-button use" bindtap="usePhoto">使用照片</view>
</view>
</view>
</view>

View file

@ -0,0 +1,651 @@
.profile-edit-container {
min-height: 100vh;
background: #000000;
color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
padding-bottom: 60rpx;
}
/* 滑动容器样式 */
.scroll-container {
height: calc(100vh - 88rpx);
overflow-y: auto;
padding-bottom: 60rpx;
}
/* 导航栏样式 */
.nav-bar {
display: flex;
justify-content: space-between;
align-items: center;
height: 88rpx;
padding: 0 30rpx;
/* background-color: #6e0000; */
position: sticky;
top: 0;
z-index: 100;
}
.nav-back {
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: flex-start;
}
.back-icon {
font-size: 40rpx;
color: #ffffff;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #ffffff;
}
.nav-save {
width: 88rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: flex-end;
}
.save-text {
font-size: 32rpx;
color: #07c160;
font-weight: 500;
}
/* 头像区域样式 */
.avatar-section {
display: flex;
justify-content: center;
padding: 60rpx 0;
}
.avatar-container {
position: relative;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
overflow: hidden;
border: 4rpx solid #333333;
}
.avatar {
width: 100%;
height: 100%;
}
.avatar-upload {
position: absolute;
bottom: 0;
right: 0;
width: 60rpx;
height: 60rpx;
background-color: #555455;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid #1a1a1a;
}
.upload-icon {
font-size: 36rpx;
color: #ffffff;
font-weight: bold;
}
/* 信息区域样式 */
.info-section {
margin: 0 40rpx 50rpx;
background-color: #242424;
border-radius: 20rpx;
padding: 40rpx;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 2rpx solid #333333;
}
.info-item:last-child {
border-bottom: none;
}
.info-label {
font-size: 32rpx;
color: #cccccc;
width: 140rpx;
}
.info-value {
font-size: 32rpx;
color: #ffffff;
flex: 1;
text-align: right;
}
.nickname-container,
.signature-container {
display: flex;
justify-content: flex-end;
align-items: center;
flex: 1;
}
.edit-icon {
font-size: 28rpx;
color: #07c160;
margin-left: 10rpx;
}
.signature {
text-align: right;
color: #999999;
}
/* 关于我区域样式 */
.about-section {
padding: 40rpx 32rpx;
margin: 0 40rpx 50rpx;
background-color: #242424;
border-radius: 24rpx;
}
.resume-section {
margin: 0 40rpx 50rpx;
padding: 40rpx;
background-color: #242424;
border-radius: 20rpx;
}
.resume-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
}
.resume-value {
color: #f5f5f5;
font-size: 28rpx;
line-height: 1.5;
}
/* 更多 */
.more-section {
margin: 0 40rpx 50rpx;
background-color: #242424;
border-radius: 20rpx;
padding: 40rpx;
}
.section-title {
font-size: 34rpx;
font-weight: 600;
margin-bottom: 30rpx;
color: #ffffff;
}
.section-subtitle {
font-size: 30rpx;
color: #cccccc;
margin: 20rpx 0;
}
.about-item,
.more-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 2rpx solid #333333;
}
.about-item:last-child,
.more-item:last-child {
border-bottom: none;
}
.about-label,
.more-label {
font-size: 32rpx;
color: #cccccc;
width: 160rpx;
}
.about-content,
.more-content {
display: flex;
justify-content: flex-end;
align-items: center;
flex: 1;
}
.about-value,
.more-value {
font-size: 32rpx;
color: linear-gradient(123deg, #8361FB 15.54%, #70AAFC 39.58%, #F0F8FB 62.43%, #F07BFF 90.28%);
text-align: right;
margin-right: 10rpx;
}
.arrow-icon {
font-size: 32rpx;
color: #666666;
}
.basic-info {
margin-top: 30rpx;
}
.basic-info-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx 0;
border-bottom: 2rpx solid #333333;
}
.basic-info-item:last-child {
border-bottom: none;
}
/* 弹窗样式 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #242424;
border-radius: 30rpx 30rpx 0 0;
padding: 30rpx;
z-index: 1001;
}
.modal-title {
font-size: 36rpx;
font-weight: 600;
text-align: center;
margin-bottom: 30rpx;
color: #ffffff;
}
.modal-input {
width: 100%;
height: 80rpx;
background-color: #333333;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 32rpx;
color: #ffffff;
margin-bottom: 30rpx;
}
.modal-textarea {
width: 100%;
height: 200rpx;
background-color: #333333;
border-radius: 10rpx;
padding: 20rpx;
font-size: 32rpx;
color: #ffffff;
margin-bottom: 30rpx;
line-height: 1.5;
}
.modal-buttons {
display: flex;
justify-content: space-between;
}
.modal-button {
width: 300rpx;
height: 80rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
}
.cancel {
background-color: #333333;
color: #999999;
}
.confirm {
background-color: #07c160;
color: #ffffff;
}
/* 选择器样式 */
.picker-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.picker-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #242424;
border-radius: 30rpx 30rpx 0 0;
padding: 30rpx;
z-index: 1001;
max-height: 80vh;
overflow-y: auto;
}
.picker-title {
font-size: 36rpx;
font-weight: 600;
text-align: center;
margin-bottom: 30rpx;
color: #ffffff;
}
.picker-button {
width: 100%;
height: 80rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
margin-top: 30rpx;
}
/* 星座选择器样式 */
.constellation-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.constellation-item {
width: 140rpx;
height: 60rpx;
background-color: #333333;
border-radius: 30rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #ffffff;
margin-bottom: 20rpx;
}
.constellation-item.selected {
background-color: #07c160;
}
/* 人格类型选择器样式 */
.personality-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.personality-item {
width: 140rpx;
height: 60rpx;
background-color: #333333;
border-radius: 30rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
color: #ffffff;
margin-bottom: 20rpx;
}
.personality-item.selected {
background-color: #07c160;
}
/* 职业和教育选择器样式 */
.search-container {
display: flex;
align-items: center;
background-color: #333333;
border-radius: 10rpx;
padding: 0 20rpx;
margin-bottom: 20rpx;
}
.search-input {
flex: 1;
height: 70rpx;
font-size: 30rpx;
color: #ffffff;
}
.search-icon {
font-size: 32rpx;
color: #999999;
}
.career-scroll,
.education-scroll {
height: 400rpx;
}
.career-item,
.education-item {
height: 80rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
border-bottom: 2rpx solid #333333;
font-size: 30rpx;
color: #ffffff;
}
/* 家乡和生日选择器样式 */
.hometown-picker-view,
.birthday-picker-view {
background-color: #333333;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 30rpx;
}
.birthday-picker-view picker-view {
border-radius: 12rpx;
overflow: hidden;
}
.birthday-picker-view picker-view-column view {
font-size: 32rpx;
color: #ffffff;
text-align: center;
}
.birthday-picker-view picker-view ::-webkit-scrollbar {
width: 0;
height: 0;
}
.hometown-picker,
.birthday-picker {
margin-bottom: 30rpx;
}
.picker-label {
font-size: 32rpx;
color: #cccccc;
margin-bottom: 10rpx;
display: block;
}
.picker-text {
height: 70rpx;
background-color: #333333;
border-radius: 10rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
font-size: 30rpx;
color: #ffffff;
margin-bottom: 20rpx;
}
.birthday-picker {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.birthday-picker .picker {
width: 30%;
}
/* 身高和性别选择器样式 */
.height-picker,
.gender-picker {
margin-bottom: 30rpx;
}
.slider {
width: 100%;
margin: 20rpx 0;
}
.height-value {
font-size: 32rpx;
color: #07c160;
text-align: center;
margin-top: 10rpx;
}
.gender-options {
display: flex;
justify-content: space-around;
margin-top: 20rpx;
}
.gender-option {
width: 150rpx;
height: 70rpx;
background-color: #333333;
border-radius: 35rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #ffffff;
}
.gender-option.selected {
background-color: #07c160;
}
/* 睡眠习惯和社交活跃度选择器样式 */
.sleep-habit-section,
.social-activity-section {
margin-bottom: 30rpx;
}
.sleep-habit-scroll,
.social-activity-scroll {
height: 250rpx;
}
.habit-item,
.activity-item {
height: 70rpx;
display: flex;
align-items: center;
padding: 0 20rpx;
border-bottom: 2rpx solid #333333;
font-size: 30rpx;
color: #ffffff;
}
/* 相机预览样式 */
.camera-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 2000;
}
.camera-preview {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2001;
display: flex;
flex-direction: column;
align-items: center;
}
.camera-preview image {
width: 600rpx;
height: 600rpx;
object-fit: contain;
}
.preview-buttons {
display: flex;
justify-content: space-between;
width: 600rpx;
margin-top: 40rpx;
}
.preview-button {
width: 250rpx;
height: 80rpx;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
font-weight: 500;
}
.retake {
background-color: #333333;
color: #999999;
}
.use {
background-color: #07c160;
color: #ffffff;
}
/* 适配底部安全区域 */
.bottom-space {
height: 34rpx;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
{
"navigationBarTitleText": "文字动态",
"navigationBarBackgroundColor": "#667eea",
"navigationBarTextStyle": "white",
"backgroundColor": "#f8f9fa",
"disableScroll": true,
"navigationStyle": "custom"
}

View file

@ -0,0 +1,132 @@
<!-- 导航栏 -->
<view class="custom-nav" style="height: {{totalNavBarHeight}}px; padding-top: {{statusBarHeight}}px;">
<view class="nav-content">
<image class="back-btn-img" src="/images/back_arrow.svg" bindtap="onNavBack"></image>
<view class="nav-title">文字动态</view>
<view class="nav-placeholder"></view>
</view>
</view>
<!-- 外部容器框 -->
<view class="outer-container">
<!-- 标题区域 -->
<view class="title-area">
<text class="page-title">我的动态</text>
<button
class="publish-btn {{!canPublish ? 'disabled' : ''}}"
bindtap="publishDynamic"
>发布</button>
</view>
<!-- 文字编辑区域 -->
<view class="text-area">
<textarea
class="content-textarea"
id="contentInput"
bindinput="onTextInput"
placeholder="What's happening..."
placeholder-class="placeholder-gray"
maxlength="120"
value="{{inputText}}"
/>
<view class="text-count">{{textLength}}/120</view>
</view>
<!-- 已选图片显示 -->
<view wx:if="{{selectedImage}}" class="image-wrapper">
<image
src="{{selectedImage}}"
mode="widthFix"
class="selected-image"
bindtap="previewImage"
/>
<view class="delete-icon" bindtap="removeImage">×</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<!-- 功能区 -->
<view class="features-area">
<view class="visibility-area" bindtap="showVisibilityModal">
<image src="/images/User.svg" class="visibility-icon"></image>
<text class="feature-text">{{visibility === '' ? '谁可以看' :
(visibility === 'public' ? '公开' : '私密')}}</text>
</view>
<view wx:if="{{!selectedImage}}" class="upload-btn" bindtap="showImageAction">
<image src="/images/Image.svg" class="upload-icon"></image>
<text class="feature-text">上传图片</text>
</view>
</view>
</view>
<!-- 当前位置 -->
<view class="location-area" bindtap="chooseLocation">
<image src="/images/loca.svg" class="location-icon"></image>
<text class="location-text">{{currentLocation || '未选择位置'}}</text>
</view>
</view>
<!-- 返回弹窗遮罩 -->
<view class="quit-mask" wx:if="{{showQuitModal}}">
<view class="quit-modal">
<view class="quit-modal-content">退出后此次编辑将不会保留,是否退出?</view>
<view class="quit-modal-btns">
<button class="modal-btn" bindtap="quitEdit">退出</button>
<button class="modal-btn" bindtap="cancelQuit">取消</button>
</view>
</view>
</view>
<!-- 图片选择弹窗-->
<view class="camera-action-sheet {{showImageAction ? 'show' : ''}}">
<view class="action-sheet-mask" bindtap="hideImageAction"></view>
<view class="action-sheet-content">
<view class="action-sheet-item primary" bindtap="takePhoto">
<text class="action-text">拍摄</text>
</view>
<view class="action-sheet-divider-thin"></view>
<view class="action-sheet-item primary" bindtap="chooseImageFromAlbum">
<text class="action-text">从相册选择</text>
</view>
<view class="action-sheet-divider"></view>
<view class="action-sheet-item cancel" bindtap="hideImageAction">
<text class="action-text">取消</text>
</view>
</view>
</view>
<!-- 审核失败弹窗 -->
<view class="audit-modal" wx:if="{{showAuditModal}}">
<view class="modal-mask" bindtap="hideAuditModal"></view>
<view class="modal-content">
<view class="modal-title">发布失败</view>
<rich-text class="modal-content-text" nodes="{{auditReason}}"></rich-text>
<view class="modal-btn" bindtap="hideAuditModal">我知道了</view>
</view>
</view>
<!-- 定位失败弹窗 -->
<view class="audit-modal" wx:if="{{showLocationErrorModal}}">
<view class="modal-mask" bindtap="hideLocationErrorModal"></view>
<view class="modal-content">
<view class="modal-title">定位失败</view>
<view class="modal-content-text">未找到你的定位,需要开启定位权限,请重试。</view>
<view class="modal-btns">
<view class="modal-btn cancel-btn" bindtap="hideLocationErrorModal">取消</view>
<view class="modal-btn confirm-btn" bindtap="retryLocation">重试</view>
</view>
</view>
</view>
<!-- Loading 动态背景 -->
<view class="loading-background" wx:if="{{isLoading}}">
<view class="loading-animated-bg">
<view class="loading-wave"></view>
<view class="loading-wave"></view>
<view class="loading-wave"></view>
</view>
<view class="loading-content">
<view class="loading-spinner"></view>
<text class="loading-text">{{loadingText}}</text>
</view>
</view>

View file

@ -0,0 +1,685 @@
/* 图片选择弹窗样式 */
.camera-action-sheet {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000;
transform: none !important;
-webkit-transform: none !important;
}
.camera-action-sheet.show {
display: block;
}
.action-sheet-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
background-color: rgba(0, 0, 0, 0.4);
}
.action-sheet-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
background: #222;
border-radius: 16px 16px 0 0;
padding: 8px 0;
}
.action-sheet-item {
padding: 20px 0;
text-align: center;
background: #222;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
position: relative;
}
.action-sheet-item::after {
display: none;
}
.action-sheet-item.primary {
background: #222;
color: #fff;
}
.action-sheet-item.cancel {
background: #222;
margin-top: 8px;
color: #fff;
margin: 0;
height: auto;
border-radius: 0;
}
.action-sheet-divider {
height: 8px;
background-color: #111;
margin: 8px 0;
}
.action-sheet-divider-thin {
height: 2px;
background-color: #111;
margin: 4px 0;
}
.action-icon {
font-size: 20px;
color: #fff;
}
.action-text {
font-size: 16px;
color: #fff;
}
/* 页面背景样式 */
page {
background-color: #000000;
color: #ffffff;
padding-top: 0;
box-sizing: border-box;
}
/* 确保在不同尺寸设备上的适配 */
@media (min-width: 768px) {
.custom-nav {
padding: 0 24px;
}
.outer-container {
margin: 30rpx;
padding: 30rpx;
}
}
/* 顶部导航栏样式 */
.custom-nav {
display: flex;
align-items: center;
padding: 0 16px;
background-color: #000000;
color: white;
border-bottom: 1px solid #333;
box-sizing: border-box;
position: relative;
width: 100%;
}
/* 返回弹窗样式 */
.quit-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
}
.quit-modal {
background: #333;
padding: 30rpx;
border-radius: 12rpx;
width: 80%;
text-align: center;
color: #fff;
}
.quit-modal-content {
margin-bottom: 30rpx;
}
.quit-modal-btns {
display: flex;
justify-content: space-around;
}
.modal-btn {
width: 30%;
background: #444;
color: #fff;
}
/* 外部容器样式 */
.outer-container {
margin: 20rpx;
padding: 20rpx;
background-color: #111;
border-radius: 16rpx;
border: 1px solid #555;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
/* 标题区域样式 */
.title-area {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 0 20rpx;
background-color: transparent;
}
.page-title {
font-size: 28rpx;
font-weight: bold;
color: #ffffff;
margin-left: 20px;
}
/* 发布按钮样式 */
.publish-btn {
background: #3399F2;
color: #fff;
border-radius: 20rpx;
font-size: 28rpx;
height: 56rpx;
padding: 0 60rpx;
margin: 0;
text-align: center;
line-height: 56rpx;
}
/* 文字区域样式 */
.text-area {
margin: 20rpx 0;
padding: 20rpx;
background-color: #111;
border-radius: 12rpx;
border: 1px solid #555;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.content-textarea {
width: 100%;
min-height: 200rpx;
padding: 0;
border: none;
outline: none;
font-size: 28rpx;
line-height: 1.5;
background: transparent;
color: #ffffff;
resize: none;
}
.placeholder-gray {
color: #888;
}
.text-count {
text-align: right;
margin-top: 10rpx;
font-size: 24rpx;
color: #888;
}
/* 图片区域样式 */
.image-area {
margin-bottom: 30rpx;
}
.upload-btn {
width: 200rpx;
height: 200rpx;
border: 2rpx dashed #555;
display: flex;
align-items: center;
justify-content: center;
color: #888;
}
.image-wrapper {
position: relative;
display: block;
width: 50%;
margin: 20rpx 0;
}
.selected-image {
width: 100%;
height: auto;
max-height: 500rpx;
border-radius: 8rpx;
object-fit: contain;
display: block;
}
.delete-icon {
position: absolute;
top: -10rpx;
right: -10rpx;
background-color: #ff4500;
color: white;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
font-weight: bold;
}
/* 位置区域样式 */
.location-area {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 10rpx;
background-color: #111;
border-radius: 8rpx;
margin: 0;
width: 100%;
flex-shrink: 0;
box-sizing: border-box;
}
.location-icon {
width: 40rpx;
height: 40rpx;
}
.location-info {
flex: 1;
}
.location-name {
font-size: 28rpx;
color: #ffffff;
margin-bottom: 6rpx;
}
.location-address {
font-size: 24rpx;
color: #888;
line-height: 1.4;
}
.choose-btn {
display: none;
}
/* 谁可以看区域样式 */
.visibility-area {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 20rpx;
background-color: #111;
border-radius: 8rpx;
margin-bottom: 20rpx;
white-space: nowrap;
}
.arrow-icon {
color: #888;
}
/* 底部操作栏 */
.bottom-actions {
display: flex;
align-items: center;
justify-content: space-between;
margin: 40rpx 0 20rpx 0;
padding: 0;
}
/* 功能区样式 */
.features-area {
display: flex;
align-items: center;
margin: 0;
padding: 0;
}
.feature-text {
font-size: 28rpx;
color: #fff;
white-space: nowrap;
}
/* 可见性图标样式 */
.visibility-icon {
width: 48rpx;
height: 48rpx;
margin-right: 12rpx;
margin-left: 5px;
vertical-align: middle;
}
/* 上传图标样式 */
.upload-icon {
width: 36rpx;
height: 36rpx;
margin-right: 10rpx;
margin-left: 5px;
vertical-align: middle;
}
/* 地址图标样式 */
.location-icon {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
vertical-align: middle;
}
/* 位置文本样式 */
.location-text {
font-size: 28rpx;
color: #ffffff;
line-height: 1.5;
width: 100%;
flex: 1;
overflow: visible;
white-space: normal;
word-wrap: break-word;
word-break: break-all;
display: block;
}
.features-area .visibility-area {
margin: 0 20rpx 0 0;
padding: 0;
background: transparent;
border-radius: 0;
display: flex;
align-items: center;
}
.features-area .upload-btn {
margin: 0;
padding: 0;
background: transparent;
border: none;
width: auto;
height: auto;
display: inline-flex;
}
/* 导航内容容器 */
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
/* 返回按钮图片样式 */
.back-btn-img {
width: 20px;
height: 20px;
flex-shrink: 0;
}
/* 导航占位元素 */
.nav-placeholder {
width: 20px;
}
/* 导航标题样式 */
.nav-title {
font-size: 17px;
font-weight: bold;
text-align: center;
margin: 0;
flex: 1;
}
/* 审核失败弹窗样式 */
.audit-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.modal-mask {
position: absolute;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(5rpx);
}
.modal-content {
position: relative;
width: 700rpx;
background: #111;
border-radius: 24rpx;
padding: 50rpx;
box-sizing: border-box;
color: #fff;
border: 1px solid #333;
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.5);
transform: scale(1);
animation: modalShow 0.3s ease-out;
overflow: hidden;
}
@keyframes modalShow {
0% {
opacity: 0;
transform: scale(0.95) translateY(20rpx);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
/* 顶部装饰条 */
.modal-content::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 6rpx;
background: linear-gradient(90deg, #ff4757, #ff6b81);
border-radius: 3rpx 3rpx 0 0;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #fff;
margin-bottom: 10rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
}
/* 标题图标 */
.modal-title::before {
content: '⚠️';
font-size: 36rpx;
}
.modal-content-text {
font-size: 32rpx;
color: #ddd;
line-height: 2.2;
margin-bottom: 50rpx;
text-align: center;
word-break: break-word;
white-space: pre-wrap;
padding: 0;
}
.modal-btn {
width: 100%;
height: 90rpx;
line-height: 90rpx;
background: linear-gradient(135deg, #007aff, #0056cc);
color: #fff;
border-radius: 45rpx;
text-align: center;
font-size: 32rpx;
font-weight: 600;
transition: all 0.3s ease;
}
.modal-btn:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.4);
}
/* 定位失败弹窗按钮容器 */
.modal-btns {
display: flex;
gap: 20rpx;
margin-top: 40rpx;
}
.modal-btns .modal-btn {
flex: 1;
}
.modal-btns .cancel-btn {
background: #666;
color: #fff;
}
.modal-btns .cancel-btn:active {
background: #555;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2);
}
.modal-btns .confirm-btn {
background: linear-gradient(135deg, #007aff, #0056cc);
color: #fff;
}
/* Loading 动态背景样式 */
.loading-background {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9998;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(10rpx);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* 动态背景层 */
.loading-animated-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0.3;
}
.loading-wave {
position: absolute;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(51, 153, 242, 0.2) 0%, transparent 70%);
animation: waveAnimation 4s ease-in-out infinite;
border-radius: 50%;
}
.loading-wave:nth-child(1) {
top: -50%;
left: -50%;
animation-delay: 0s;
background: radial-gradient(circle, rgba(51, 153, 242, 0.25) 0%, transparent 70%);
}
.loading-wave:nth-child(2) {
top: -30%;
left: -30%;
animation-delay: 1.3s;
background: radial-gradient(circle, rgba(0, 122, 255, 0.2) 0%, transparent 70%);
}
.loading-wave:nth-child(3) {
top: -10%;
left: -10%;
animation-delay: 2.6s;
background: radial-gradient(circle, rgba(51, 153, 242, 0.15) 0%, transparent 70%);
}
@keyframes waveAnimation {
0%, 100% {
transform: translate(0, 0) scale(1);
opacity: 0.3;
}
50% {
transform: translate(10%, 10%) scale(1.1);
opacity: 0.5;
}
}
/* Loading 内容区域 */
.loading-content {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 30rpx;
}
/* Loading 旋转动画 */
.loading-spinner {
width: 80rpx;
height: 80rpx;
border: 6rpx solid rgba(255, 255, 255, 0.2);
border-top-color: #3399F2;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Loading 文字样式 */
.loading-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
text-align: center;
letter-spacing: 2rpx;
}

View file

@ -0,0 +1,837 @@
// 媒体文件管理器 - 微信小程序专用
// 处理图片、视频、音频、文件的上传、下载、预览、缓存等
const apiClient = require('../../../utils/api-client.js');
/**
* 媒体文件管理器
* 功能
* 1. 文件选择和上传
* 2. 媒体文件预览
* 3. 文件下载和缓存
* 4. 文件压缩和优化
* 5. 云存储管理
* 6. 文件类型检测
*/
class MediaManager {
constructor() {
this.isInitialized = false;
// 媒体配置
this.mediaConfig = {
// 图片配置
image: {
maxSize: 10 * 1024 * 1024, // 10MB
maxCount: 9, // 最多选择9张
quality: 80, // 压缩质量
formats: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
compressWidth: 1080 // 压缩宽度
},
// 视频配置
video: {
maxSize: 100 * 1024 * 1024, // 100MB
maxDuration: 300, // 最长5分钟
formats: ['mp4', 'mov', 'avi'],
compressQuality: 'medium'
},
// 音频配置
audio: {
maxSize: 20 * 1024 * 1024, // 20MB
maxDuration: 600, // 最长10分钟
formats: ['mp3', 'wav', 'aac', 'm4a'],
sampleRate: 16000
},
// 文件配置
file: {
maxSize: 50 * 1024 * 1024, // 50MB
allowedTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'zip', 'rar'],
maxCount: 5
},
// 缓存配置
cache: {
maxSize: 200 * 1024 * 1024, // 200MB
expireTime: 7 * 24 * 60 * 60 * 1000, // 7天
cleanupInterval: 24 * 60 * 60 * 1000 // 24小时清理一次
}
};
// 文件缓存
this.fileCache = new Map();
// 上传队列
this.uploadQueue = [];
// 下载队列
this.downloadQueue = [];
// 当前上传任务
this.currentUploads = new Map();
// 缓存统计
this.cacheStats = {
totalSize: 0,
fileCount: 0,
lastCleanup: 0
};
this.init();
}
// 初始化媒体管理器
async init() {
if (this.isInitialized) return;
try {
// 加载文件缓存信息
await this.loadCacheInfo();
// 检查存储权限
await this.checkStoragePermission();
// 启动缓存清理
this.startCacheCleanup();
this.isInitialized = true;
} catch (error) {
console.error('❌ 媒体文件管理器初始化失败:', error);
}
}
// 📷 ===== 图片处理 =====
// 选择图片
async chooseImages(options = {}) {
try {
const {
count = this.mediaConfig.image.maxCount,
sizeType = ['compressed', 'original'],
sourceType = ['album', 'camera']
} = options;
const result = await new Promise((resolve, reject) => {
wx.chooseImage({
count: count,
sizeType: sizeType,
sourceType: sourceType,
success: resolve,
fail: reject
});
});
// 验证和处理图片
const processedImages = await this.processImages(result.tempFilePaths);
return {
success: true,
images: processedImages
};
} catch (error) {
console.error('❌ 选择图片失败:', error);
return {
success: false,
error: error.errMsg || '选择图片失败'
};
}
}
// 处理图片
async processImages(tempFilePaths) {
const processedImages = [];
for (const tempPath of tempFilePaths) {
try {
// 获取图片信息
const imageInfo = await this.getImageInfo(tempPath);
// 检查文件大小
if (imageInfo.size > this.mediaConfig.image.maxSize) {
console.warn('⚠️ 图片过大,需要压缩:', imageInfo.size);
// 压缩图片
const compressedPath = await this.compressImage(tempPath);
imageInfo.tempFilePath = compressedPath;
}
// 生成缩略图
const thumbnailPath = await this.generateThumbnail(imageInfo.tempFilePath);
processedImages.push({
...imageInfo,
thumbnailPath: thumbnailPath,
type: 'image',
status: 'ready'
});
} catch (error) {
console.error('❌ 处理图片失败:', error);
}
}
return processedImages;
}
// 获取图片信息
async getImageInfo(src) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: src,
success: (res) => {
// 获取文件大小
wx.getFileInfo({
filePath: src,
success: (fileInfo) => {
resolve({
...res,
size: fileInfo.size,
tempFilePath: src
});
},
fail: () => {
resolve({
...res,
size: 0,
tempFilePath: src
});
}
});
},
fail: reject
});
});
}
// 压缩图片
// 图片压缩处理函数
compressImage(tempFilePath) {
return new Promise((resolve, reject) => {
// 先获取图片信息,用于计算压缩比例
wx.getImageInfo({
src: tempFilePath,
success: (info) => {
// 根据图片尺寸计算合适的压缩比例
let quality = 0.6; // 初始质量
let width = info.width;
let height = info.height;
// 对于过大的图片先缩小尺寸
if (width > 1000 || height > 1000) {
const scale = 1000 / Math.max(width, height);
width = Math.floor(width * scale);
height = Math.floor(height * scale);
quality = 0.5; // 大图片适当降低质量
} else if (width > 500 || height > 500) {
quality = 0.55; // 中等图片适度压缩
}
// 执行压缩
wx.compressImage({
src: tempFilePath,
quality: quality, // 质量0-100这里用0.6表示60%质量
width: width, // 压缩后的宽度
height: height, // 压缩后的高度
success: (res) => {
resolve(res.tempFilePath);
},
fail: (err) => {
reject(err);
}
});
},
fail: (err) => {
reject(err);
}
});
});
}
// 生成缩略图
async generateThumbnail(src) {
try {
// 创建canvas生成缩略图
const canvas = wx.createOffscreenCanvas({ type: '2d' });
const ctx = canvas.getContext('2d');
// 设置缩略图尺寸
const thumbnailSize = 200;
canvas.width = thumbnailSize;
canvas.height = thumbnailSize;
// 加载图片
const image = canvas.createImage();
return new Promise((resolve) => {
image.onload = () => {
// 计算绘制尺寸
const { drawWidth, drawHeight, drawX, drawY } = this.calculateDrawSize(
image.width,
image.height,
thumbnailSize,
thumbnailSize
);
// 绘制缩略图
ctx.drawImage(image, drawX, drawY, drawWidth, drawHeight);
// 导出为临时文件
wx.canvasToTempFilePath({
canvas: canvas,
success: (res) => {
resolve(res.tempFilePath);
},
fail: () => {
resolve(src); // 生成失败返回原图
}
});
};
image.onerror = () => {
resolve(src); // 加载失败返回原图
};
image.src = src;
});
} catch (error) {
console.error('❌ 生成缩略图失败:', error);
return src;
}
}
// 计算绘制尺寸
calculateDrawSize(imageWidth, imageHeight, canvasWidth, canvasHeight) {
const imageRatio = imageWidth / imageHeight;
const canvasRatio = canvasWidth / canvasHeight;
let drawWidth, drawHeight, drawX, drawY;
if (imageRatio > canvasRatio) {
// 图片更宽,以高度为准
drawHeight = canvasHeight;
drawWidth = drawHeight * imageRatio;
drawX = (canvasWidth - drawWidth) / 2;
drawY = 0;
} else {
// 图片更高,以宽度为准
drawWidth = canvasWidth;
drawHeight = drawWidth / imageRatio;
drawX = 0;
drawY = (canvasHeight - drawHeight) / 2;
}
return { drawWidth, drawHeight, drawX, drawY };
}
// 🎬 ===== 视频处理 =====
// 选择视频
async chooseVideo(options = {}) {
try {
const {
sourceType = ['album', 'camera'],
maxDuration = this.mediaConfig.video.maxDuration,
camera = 'back'
} = options;
const result = await new Promise((resolve, reject) => {
wx.chooseVideo({
sourceType: sourceType,
maxDuration: maxDuration,
camera: camera,
success: resolve,
fail: reject
});
});
// 验证和处理视频
const processedVideo = await this.processVideo(result);
return {
success: true,
video: processedVideo
};
} catch (error) {
console.error('❌ 选择视频失败:', error);
return {
success: false,
error: error.errMsg || '选择视频失败'
};
}
}
// 处理视频
async processVideo(videoResult) {
try {
// 检查文件大小
if (videoResult.size > this.mediaConfig.video.maxSize) {
throw new Error('视频文件过大');
}
// 检查时长
if (videoResult.duration > this.mediaConfig.video.maxDuration) {
throw new Error('视频时长超出限制');
}
// 生成视频缩略图
const thumbnailPath = await this.generateVideoThumbnail(videoResult.tempFilePath);
return {
tempFilePath: videoResult.tempFilePath,
duration: videoResult.duration,
size: videoResult.size,
width: videoResult.width,
height: videoResult.height,
thumbnailPath: thumbnailPath,
type: 'video',
status: 'ready'
};
} catch (error) {
console.error('❌ 处理视频失败:', error);
throw error;
}
}
// 生成视频缩略图
async generateVideoThumbnail(videoPath) {
try {
// 微信小程序暂不支持视频帧提取,使用默认图标
return null;
} catch (error) {
console.error('❌ 生成视频缩略图失败:', error);
return null;
}
}
// 📄 ===== 文件处理 =====
// 选择文件
async chooseFile(options = {}) {
try {
const {
count = this.mediaConfig.file.maxCount,
type = 'all'
} = options;
const result = await new Promise((resolve, reject) => {
wx.chooseMessageFile({
count: count,
type: type,
success: resolve,
fail: reject
});
});
// 验证和处理文件
const processedFiles = await this.processFiles(result.tempFiles);
return {
success: true,
files: processedFiles
};
} catch (error) {
console.error('❌ 选择文件失败:', error);
return {
success: false,
error: error.errMsg || '选择文件失败'
};
}
}
// 处理文件
async processFiles(tempFiles) {
const processedFiles = [];
for (const file of tempFiles) {
try {
// 检查文件大小
if (file.size > this.mediaConfig.file.maxSize) {
console.warn('⚠️ 文件过大:', file.name, file.size);
continue;
}
// 检查文件类型
const fileExtension = this.getFileExtension(file.name);
if (!this.isAllowedFileType(fileExtension)) {
console.warn('⚠️ 不支持的文件类型:', fileExtension);
continue;
}
processedFiles.push({
tempFilePath: file.path,
name: file.name,
size: file.size,
type: 'file',
extension: fileExtension,
status: 'ready'
});
} catch (error) {
console.error('❌ 处理文件失败:', error);
}
}
return processedFiles;
}
// 获取文件扩展名
getFileExtension(fileName) {
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex === -1) return '';
return fileName.substring(lastDotIndex + 1).toLowerCase();
}
// 检查文件类型是否允许
isAllowedFileType(extension) {
return this.mediaConfig.file.allowedTypes.includes(extension);
}
// 📤 ===== 文件上传 =====
// 上传文件
async uploadFile(file, options = {}) {
try {
const {
onProgress,
onSuccess,
onError
} = options;
// 生成上传ID
const uploadId = this.generateUploadId();
// 添加到上传队列
const uploadTask = {
id: uploadId,
file: file,
status: 'uploading',
progress: 0,
onProgress: onProgress,
onSuccess: onSuccess,
onError: onError
};
this.currentUploads.set(uploadId, uploadTask);
// 执行上传
const result = await this.performUpload(uploadTask);
// 移除上传任务
this.currentUploads.delete(uploadId);
return result;
} catch (error) {
console.error('❌ 上传文件失败:', error);
return {
success: false,
error: error.message
};
}
}
// 执行上传
async performUpload(uploadTask) {
return new Promise((resolve, reject) => {
const uploadTask_wx = wx.uploadFile({
url: `${apiClient.baseURL}/api/v1/files/upload`,
filePath: uploadTask.file.tempFilePath,
name: 'file',
header: {
'Authorization': `Bearer ${wx.getStorageSync('token')}`
},
formData: {
type: uploadTask.file.type,
name: uploadTask.file.name || 'unknown'
},
success: (res) => {
try {
const data = JSON.parse(res.data);
if (data.success) {
const result = {
success: true,
data: {
url: data.data.url,
fileId: data.data.fileId,
fileName: uploadTask.file.name,
fileSize: uploadTask.file.size,
fileType: uploadTask.file.type
}
};
if (uploadTask.onSuccess) {
uploadTask.onSuccess(result);
}
resolve(result);
} else {
throw new Error(data.error || '上传失败');
}
} catch (error) {
reject(error);
}
},
fail: (error) => {
console.error('❌ 上传请求失败:', error);
if (uploadTask.onError) {
uploadTask.onError(error);
}
reject(error);
}
});
// 监听上传进度
uploadTask_wx.onProgressUpdate((res) => {
uploadTask.progress = res.progress;
if (uploadTask.onProgress) {
uploadTask.onProgress({
progress: res.progress,
totalBytesSent: res.totalBytesSent,
totalBytesExpectedToSend: res.totalBytesExpectedToSend
});
}
});
// 保存上传任务引用
uploadTask.wxTask = uploadTask_wx;
});
}
// 取消上传
cancelUpload(uploadId) {
const uploadTask = this.currentUploads.get(uploadId);
if (uploadTask && uploadTask.wxTask) {
uploadTask.wxTask.abort();
this.currentUploads.delete(uploadId);
}
}
// 生成上传ID
generateUploadId() {
return `upload_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 📥 ===== 文件下载和缓存 =====
// 下载文件
async downloadFile(url, options = {}) {
try {
const {
fileName,
onProgress,
useCache = true
} = options;
// 检查缓存
if (useCache) {
const cachedPath = this.getCachedFilePath(url);
if (cachedPath) {
return {
success: true,
tempFilePath: cachedPath,
cached: true
};
}
}
// 执行下载
const result = await this.performDownload(url, { fileName, onProgress });
// 缓存文件
if (result.success && useCache) {
this.cacheFile(url, result.tempFilePath);
}
return result;
} catch (error) {
console.error('❌ 下载文件失败:', error);
return {
success: false,
error: error.message
};
}
}
// 执行下载
async performDownload(url, options = {}) {
return new Promise((resolve, reject) => {
const downloadTask = wx.downloadFile({
url: url,
success: (res) => {
if (res.statusCode === 200) {
resolve({
success: true,
tempFilePath: res.tempFilePath,
cached: false
});
} else {
reject(new Error(`下载失败: ${res.statusCode}`));
}
},
fail: reject
});
// 监听下载进度
if (options.onProgress) {
downloadTask.onProgressUpdate((res) => {
options.onProgress({
progress: res.progress,
totalBytesWritten: res.totalBytesWritten,
totalBytesExpectedToWrite: res.totalBytesExpectedToWrite
});
});
}
});
}
// 检查存储权限
async checkStoragePermission() {
try {
const storageInfo = wx.getStorageInfoSync();
return true;
} catch (error) {
console.error('❌ 检查存储权限失败:', error);
return false;
}
}
// 加载缓存信息
async loadCacheInfo() {
try {
const cacheInfo = wx.getStorageSync('mediaCacheInfo') || {};
this.cacheStats = {
totalSize: cacheInfo.totalSize || 0,
fileCount: cacheInfo.fileCount || 0,
lastCleanup: cacheInfo.lastCleanup || 0
};
} catch (error) {
console.error('❌ 加载缓存信息失败:', error);
}
}
// 保存缓存信息
async saveCacheInfo() {
try {
wx.setStorageSync('mediaCacheInfo', this.cacheStats);
} catch (error) {
console.error('❌ 保存缓存信息失败:', error);
}
}
// 获取缓存文件路径
getCachedFilePath(url) {
const cacheKey = this.generateCacheKey(url);
return this.fileCache.get(cacheKey);
}
// 缓存文件
cacheFile(url, filePath) {
const cacheKey = this.generateCacheKey(url);
this.fileCache.set(cacheKey, filePath);
// 更新缓存统计
this.cacheStats.fileCount++;
this.saveCacheInfo();
}
// 生成缓存键
generateCacheKey(url) {
// 使用URL的hash作为缓存键
let hash = 0;
for (let i = 0; i < url.length; i++) {
const char = url.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // 转换为32位整数
}
return `cache_${Math.abs(hash)}`;
}
// 启动缓存清理
startCacheCleanup() {
setInterval(() => {
this.performCacheCleanup();
}, this.mediaConfig.cache.cleanupInterval);
}
// 执行缓存清理
performCacheCleanup() {
try {
const now = Date.now();
const expireTime = this.mediaConfig.cache.expireTime;
// 清理过期缓存
for (const [key, filePath] of this.fileCache) {
try {
const stats = wx.getFileInfo({ filePath });
if (now - stats.createTime > expireTime) {
this.fileCache.delete(key);
// 删除文件
wx.removeSavedFile({ filePath });
}
} catch (error) {
// 文件不存在,从缓存中移除
this.fileCache.delete(key);
}
}
// 更新清理时间
this.cacheStats.lastCleanup = now;
this.saveCacheInfo();
} catch (error) {
console.error('❌ 缓存清理失败:', error);
}
}
// 获取媒体管理器状态
getStatus() {
return {
isInitialized: this.isInitialized,
uploadCount: this.currentUploads.size,
cacheStats: { ...this.cacheStats },
config: this.mediaConfig
};
}
// 清除所有缓存
clearAllCache() {
this.fileCache.clear();
this.cacheStats = {
totalSize: 0,
fileCount: 0,
lastCleanup: Date.now()
};
this.saveCacheInfo();
}
// 重置管理器
reset() {
// 取消所有上传任务
for (const [uploadId] of this.currentUploads) {
this.cancelUpload(uploadId);
}
this.clearAllCache();
}
}
// 创建全局实例
const mediaManager = new MediaManager();
module.exports = mediaManager;

View file

@ -0,0 +1,241 @@
Page({
data: {
currentVisibility: '',
selectedTags: [], // 选中的标签列表
selectedTagsFriendsCount: 0, // 标签里的好友数量
selectedTagsDisplayText: '', // 标签显示文本
selectedFriends: [], // 选中的好友列表
selectedFriendsCount: 0, // 选中的好友数量
selectedFriendsDisplayText: '', // 选中的好友显示文本
excludeTags: [], // 排除的标签列表
excludeTagsFriendsCount: 0, // 排除标签里的好友数量
excludeTagsDisplayText: '', // 排除标签显示文本
excludeFriends: [], // 排除的好友列表
excludeFriendsCount: 0, // 排除的好友数量
excludeFriendsDisplayText: '' // 排除的好友显示文本
},
onLoad(options) {
let visibility = options.currentVisibility || '';
// 如果当前选中的是隐藏的选项,重置为公开
if (visibility === 'friends_only' || visibility === 'partial_visible' || visibility === 'exclude_friends') {
visibility = 'public';
}
if (visibility) {
this.setData({
currentVisibility: visibility
});
}
},
onShow() {
// 从标签或好友选择页面返回时,更新数据
// 这里可以通过全局变量或事件总线来获取更新的数据
// TODO: 实现从选择页面返回时的数据同步
},
// 加载部分可见的数据
loadPartialVisibleData() {
// TODO: 从上一页获取已选中的标签和好友
// 暂时使用空数据
this.setData({
selectedTags: [],
selectedTagsFriendsCount: 0,
selectedFriends: [],
selectedFriendsCount: 0
});
},
// 选择标签
selectTags() {
const selectedTagsStr = encodeURIComponent(JSON.stringify(this.data.selectedTags || []));
wx.navigateTo({
url: `/subpackages/social/tag-friends/tag-friends?selectedTags=${selectedTagsStr}&mode=partial_visible`,
success: (res) => {
console.log('跳转到标签选择页面成功');
},
fail: (err) => {
console.error('跳转失败:', err);
wx.showToast({
title: '跳转失败',
icon: 'none'
});
}
});
},
// 选择好友
selectFriends() {
const selectedFriendsStr = encodeURIComponent(JSON.stringify(this.data.selectedFriends || []));
wx.navigateTo({
url: `/subpackages/social/friend-selector/friend-selector?selectedFriends=${selectedFriendsStr}&mode=partial_visible`,
success: (res) => {
console.log('跳转到好友选择页面成功');
},
fail: (err) => {
console.error('跳转失败:', err);
wx.showToast({
title: '跳转失败',
icon: 'none'
});
}
});
},
// 更新标签数据(从标签选择页面返回时调用)
updateSelectedTags(tags, friendsCount, displayText) {
this.setData({
selectedTags: tags,
selectedTagsFriendsCount: friendsCount,
selectedTagsDisplayText: displayText || ''
});
},
// 更新好友数据(从好友选择页面返回时调用)
updateSelectedFriends(friends, friendsCount, displayText) {
this.setData({
selectedFriends: friends,
selectedFriendsCount: friendsCount,
selectedFriendsDisplayText: displayText || ''
});
},
// 加载不给谁看的数据
loadExcludeFriendsData() {
// TODO: 从上一页获取已选中的排除标签和好友
// 暂时使用空数据
this.setData({
excludeTags: [],
excludeTagsFriendsCount: 0,
excludeFriends: [],
excludeFriendsCount: 0
});
},
// 选择排除标签
selectExcludeTags() {
const excludeTagsStr = encodeURIComponent(JSON.stringify(this.data.excludeTags || []));
wx.navigateTo({
url: `/subpackages/social/tag-friends/tag-friends?selectedTags=${excludeTagsStr}&mode=exclude_friends`,
success: (res) => {
console.log('跳转到排除标签选择页面成功');
},
fail: (err) => {
console.error('跳转失败:', err);
wx.showToast({
title: '跳转失败',
icon: 'none'
});
}
});
},
// 选择排除好友
selectExcludeFriends() {
const excludeFriendsStr = encodeURIComponent(JSON.stringify(this.data.excludeFriends || []));
wx.navigateTo({
url: `/subpackages/social/friend-selector/friend-selector?selectedFriends=${excludeFriendsStr}&mode=exclude_friends`,
success: (res) => {
console.log('跳转到排除好友选择页面成功');
},
fail: (err) => {
console.error('跳转失败:', err);
wx.showToast({
title: '跳转失败',
icon: 'none'
});
}
});
},
// 更新排除标签数据(从标签选择页面返回时调用)
updateExcludeTags(tags, friendsCount, displayText) {
this.setData({
excludeTags: tags,
excludeTagsFriendsCount: friendsCount,
excludeTagsDisplayText: displayText || ''
});
},
// 更新排除好友数据(从好友选择页面返回时调用)
updateExcludeFriends(friends, friendsCount, displayText) {
this.setData({
excludeFriends: friends,
excludeFriendsCount: friendsCount,
excludeFriendsDisplayText: displayText || ''
});
},
// 选择可见性选项
selectVisibility(e) {
const type = e.currentTarget.dataset.type;
// 禁止选择隐藏的选项
if (type === 'friends_only' || type === 'partial_visible' || type === 'exclude_friends') {
return;
}
this.setData({
currentVisibility: type
});
},
// 完成选择
completeSelection() {
// 获取当前选择的可见性状态
const { currentVisibility, selectedTags, selectedFriends, excludeTags, excludeFriends } = this.data;
console.log('completeSelection 被调用currentVisibility:', currentVisibility);
// 检查是否选择了可见性
if (!currentVisibility || currentVisibility.trim() === '') {
wx.showToast({
title: '请选择可见性',
icon: 'none'
});
return;
}
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
console.log('上一页是否存在:', !!prevPage);
console.log('updateVisibility 方法是否存在:', prevPage && typeof prevPage.updateVisibility === 'function');
if (prevPage && typeof prevPage.updateVisibility === 'function') {
// 如果是部分可见,传递标签和好友数据
if (currentVisibility === 'partial_visible') {
console.log('传递部分可见数据');
prevPage.updateVisibility(currentVisibility, {
selectedTags: selectedTags,
selectedFriends: selectedFriends
});
} else if (currentVisibility === 'exclude_friends') {
// 如果是不给谁看,传递排除标签和好友数据
console.log('传递排除好友数据');
prevPage.updateVisibility(currentVisibility, {
excludeTags: excludeTags,
excludeFriends: excludeFriends
});
} else {
console.log('传递普通可见性:', currentVisibility);
prevPage.updateVisibility(currentVisibility);
}
// 延迟返回,确保 updateVisibility 执行完成
setTimeout(() => {
console.log('准备返回上一页');
wx.navigateBack();
}, 100);
} else {
console.error('无法获取上一页或 updateVisibility 方法不存在');
// 如果无法获取上一页,直接返回
wx.navigateBack();
}
},
navigateBack() {
wx.navigateBack();
}
});

View file

@ -0,0 +1,7 @@
{
"navigationBarTitleText": "谁可以看",
"navigationBarBackgroundColor": "#000000",
"navigationBarTextStyle": "white",
"backgroundColor": "#000000",
"disableScroll": false
}

View file

@ -0,0 +1,53 @@
<view class="visibility-selector-container">
<!-- 主内容区 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 可见性选项列表 -->
<view class="options-list">
<view
class="visibility-option {{currentVisibility === 'public' ? 'selected' : ''}}"
bindtap="selectVisibility"
data-type="public"
>
<view class="option-checkbox">
<view class="checkbox {{currentVisibility === 'public' ? 'checked' : ''}}">
<image wx:if="{{currentVisibility === 'public'}}" src="/images/Selected.svg" mode="aspectFit" class="check-image"></image>
<image wx:else src="/images/fram.svg" mode="aspectFit" class="uncheck-image"></image>
</view>
</view>
<view class="option-content">
<view class="option-main">
<text class="option-title">公开</text>
<text class="option-desc"> 所有人可见</text>
</view>
</view>
</view>
<view
class="visibility-option {{currentVisibility === 'private' ? 'selected' : ''}}"
bindtap="selectVisibility"
data-type="private"
>
<view class="option-checkbox">
<view class="checkbox {{currentVisibility === 'private' ? 'checked' : ''}}">
<image wx:if="{{currentVisibility === 'private'}}" src="/images/Selected.svg" mode="aspectFit" class="check-image"></image>
<image wx:else src="/images/fram.svg" mode="aspectFit" class="uncheck-image"></image>
</view>
</view>
<view class="option-content">
<view class="option-main">
<text class="option-title">私密</text>
<text class="option-desc"> 仅自己可见</text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部完成按钮 -->
<view class="bottom-action">
<button class="complete-btn" bindtap="completeSelection">完成</button>
</view>
</view>

View file

@ -0,0 +1,287 @@
/* 页面根容器 */
.page {
background-color: #000000;
color: #ffffff;
height: 100vh;
}
/* 主容器 */
.visibility-selector-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #000000;
}
/* 内容滚动区域 */
.content-scroll {
flex: 1;
overflow-y: scroll;
padding-bottom: 40rpx;
}
/* 选项列表 */
.options-list {
padding: 20rpx 0;
}
/* 可见性选项 */
.visibility-option {
display: flex;
align-items: center;
padding: 30rpx;
background-color: #111111;
margin-bottom: 1px;
}
.visibility-option.selected {
background-color: #111111;
}
/* 部分可见选项的头部(复选框和标题)- 默认保持水平布局 */
.partial-visible-header {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
/* 部分可见选项的特殊样式 - 按列排列 */
.visibility-option.partial-visible-expanded {
flex-direction: column;
align-items: stretch;
padding: 30rpx; /* 保持与默认一致的 padding */
}
/* 部分可见选项头部在选中时的对齐 */
.visibility-option.partial-visible-expanded .partial-visible-header {
width: 100%;
margin-bottom: 0; /* 确保没有额外间距 */
}
/* 不给谁看选项的特殊样式 - 按列排列 */
.visibility-option.exclude-friends-expanded {
flex-direction: column;
align-items: stretch;
padding: 30rpx; /* 保持与默认一致的 padding */
}
/* 不给谁看选项的头部(复选框和标题)- 默认保持水平布局 */
.exclude-friends-header {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
/* 不给谁看选项头部在选中时的对齐 */
.visibility-option.exclude-friends-expanded .exclude-friends-header {
width: 100%;
margin-bottom: 0; /* 确保没有额外间距 */
}
/* 复选框样式 */
.option-checkbox {
margin-right: 30rpx;
margin-left: 20rpx;
}
.checkbox {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
}
.checkbox.checked {
background-color: transparent;
}
/* 选中图标样式 */
.check-image {
width: 40rpx;
height: 40rpx;
display: inline-block;
}
.uncheck-image {
width: 40rpx;
height: 40rpx;
display: inline-block;
}
/* 选项内容 */
.option-content {
flex: 1;
}
.option-main {
display: flex;
align-items: center;
}
.option-title {
font-size: 32rpx;
font-weight: bold;
color: #ffffff;
margin-right: 20rpx;
}
.option-desc {
font-size: 24rpx;
color: #ffffff;
}
/* 标签区域 */
.tags-section {
padding: 30rpx;
}
.section-title {
font-size: 28rpx;
color: #ffffff;
margin-bottom: 20rpx;
}
.tags-list {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 20rpx;
}
.tag-item {
padding: 16rpx 30rpx;
background-color: #333333;
border-radius: 40rpx;
font-size: 26rpx;
color: #ffffff;
}
.tags-description {
font-size: 24rpx;
color: #888888;
line-height: 1.5;
}
/* 好友选择区域 */
.friends-selection-section {
margin: 0 30rpx;
background-color: #111111;
border-radius: 16rpx;
overflow: hidden;
}
.friend-option {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid #333333;
}
.friend-option:last-child {
border-bottom: none;
}
.friend-option-label {
font-size: 30rpx;
color: #ffffff;
}
.friend-option-value {
display: flex;
align-items: center;
font-size: 26rpx;
color: #888888;
}
.arrow-icon {
margin-left: 10rpx;
font-size: 24rpx;
color: #555555;
}
/* 底部操作按钮 */
.bottom-action {
position: fixed;
bottom: 100rpx;
left: 30rpx;
right: 30rpx;
padding: 10rpx;
background-color: transparent;
}
.complete-btn {
width: 50%;
height: 80rpx;
line-height: 80rpx;
background-image: linear-gradient(73deg, #EF6460, #EC42C8, #435CFF, #00D5FF);
color: #ffffff;
border-radius: 28rpx;
font-size: 24rpx;
font-weight: 600;
border: none;
margin: 0 auto;
letter-spacing: 12rpx;
}
/* 部分可见详细设置 */
.partial-visible-details {
margin-top: 20rpx;
padding-left: 90rpx; /* 与复选框对齐 */
padding-right: 0;
width: 100%;
box-sizing: border-box;
}
/* 不给谁看详细设置 */
.exclude-friends-details {
margin-top: 20rpx;
padding-left: 90rpx; /* 与复选框对齐 */
padding-right: 0;
width: 100%;
box-sizing: border-box;
}
.detail-row {
display: flex;
flex-direction: row;
align-items: flex-start;
padding: 20rpx 0;
background-color: transparent;
margin-bottom: 1px;
margin-left: 20rpx
}
.detail-title {
flex-shrink: 0;
margin-right: 30rpx;
}
.detail-title-text {
font-size: 32rpx;
background: linear-gradient(90deg, #e90abb, #fbcb09);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: bold;
white-space: nowrap;
}
.detail-value {
flex: 1;
display: flex;
align-items: flex-start;
}
.detail-value-text {
font-size: 26rpx;
color: #888888;
line-height: 1.6;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
}