upload project
This commit is contained in:
commit
06961cae04
422 changed files with 110626 additions and 0 deletions
1567
subpackages/media/camera/camera.js
Normal file
1567
subpackages/media/camera/camera.js
Normal file
File diff suppressed because it is too large
Load diff
8
subpackages/media/camera/camera.json
Normal file
8
subpackages/media/camera/camera.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"navigationBarTitleText": "",
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTextStyle": "white",
|
||||
"disableScroll": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
144
subpackages/media/camera/camera.wxml
Normal file
144
subpackages/media/camera/camera.wxml
Normal 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>
|
||||
379
subpackages/media/camera/camera.wxss
Normal file
379
subpackages/media/camera/camera.wxss
Normal 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;
|
||||
}
|
||||
614
subpackages/media/edit/edit.js
Normal file
614
subpackages/media/edit/edit.js
Normal 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
7
subpackages/media/edit/edit.json
Normal file
7
subpackages/media/edit/edit.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"navigationBarTitleText": "编辑资料",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#000000",
|
||||
"disableScroll": true
|
||||
}
|
||||
310
subpackages/media/edit/edit.wxml
Normal file
310
subpackages/media/edit/edit.wxml
Normal 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>
|
||||
651
subpackages/media/edit/edit.wxss
Normal file
651
subpackages/media/edit/edit.wxss
Normal 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;
|
||||
}
|
||||
1121
subpackages/media/edits/edits.js
Normal file
1121
subpackages/media/edits/edits.js
Normal file
File diff suppressed because it is too large
Load diff
8
subpackages/media/edits/edits.json
Normal file
8
subpackages/media/edits/edits.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"navigationBarTitleText": "文字动态",
|
||||
"navigationBarBackgroundColor": "#667eea",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#f8f9fa",
|
||||
"disableScroll": true,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
132
subpackages/media/edits/edits.wxml
Normal file
132
subpackages/media/edits/edits.wxml
Normal 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>
|
||||
|
||||
685
subpackages/media/edits/edits.wxss
Normal file
685
subpackages/media/edits/edits.wxss
Normal 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;
|
||||
}
|
||||
837
subpackages/media/utils/media-manager.js
Normal file
837
subpackages/media/utils/media-manager.js
Normal 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;
|
||||
|
||||
241
subpackages/media/visibility-selector/visibility-selector.js
Normal file
241
subpackages/media/visibility-selector/visibility-selector.js
Normal 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();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"navigationBarTitleText": "谁可以看",
|
||||
"navigationBarBackgroundColor": "#000000",
|
||||
"navigationBarTextStyle": "white",
|
||||
"backgroundColor": "#000000",
|
||||
"disableScroll": false
|
||||
}
|
||||
|
|
@ -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>
|
||||
287
subpackages/media/visibility-selector/visibility-selector.wxss
Normal file
287
subpackages/media/visibility-selector/visibility-selector.wxss
Normal 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue