38 changed files with 6212 additions and 80 deletions
-
11app.json
-
42pages/home/home.js
-
2pages/home/home.wxml
-
BINpages/images/bj.png
-
362pages/personal/personal.js
-
1pages/personal/personal.json
-
136pages/personal/personal.wxml
-
612pages/personal/personal.wxss
-
BINpagesA/images/ch.png
-
159pagesA/pages/advisory/advisory.js
-
4pagesA/pages/advisory/advisory.json
-
62pagesA/pages/advisory/advisory.wxml
-
360pagesA/pages/advisory/advisory.wxss
-
66pagesA/pages/precept/precept.js
-
3pagesA/pages/precept/precept.json
-
95pagesA/pages/precept/precept.wxml
-
428pagesA/pages/precept/precept.wxss
-
191pagesA/pages/releaseSuffer/releaseSuffer.js
-
4pagesA/pages/releaseSuffer/releaseSuffer.json
-
109pagesA/pages/releaseSuffer/releaseSuffer.wxml
-
278pagesA/pages/releaseSuffer/releaseSuffer.wxss
-
37pagesB/pages/experienceDetails/experienceDetails.js
-
4pagesB/pages/experienceDetails/experienceDetails.json
-
52pagesB/pages/experienceDetails/experienceDetails.wxml
-
158pagesB/pages/experienceDetails/experienceDetails.wxss
-
217pagesB/pages/experienceList/experienceList.js
-
4pagesB/pages/experienceList/experienceList.json
-
89pagesB/pages/experienceList/experienceList.wxml
-
390pagesB/pages/experienceList/experienceList.wxss
-
477pagesB/pages/forumlist/forumlist.js
-
4pagesB/pages/forumlist/forumlist.json
-
208pagesB/pages/forumlist/forumlist.wxml
-
758pagesB/pages/forumlist/forumlist.wxss
-
328pagesB/pages/onlineAsk/onlineAsk.js
-
4pagesB/pages/onlineAsk/onlineAsk.json
-
152pagesB/pages/onlineAsk/onlineAsk.wxml
-
434pagesB/pages/onlineAsk/onlineAsk.wxss
-
21utils/api.js
|
After Width: 200 | Height: 200 | Size: 3.3 KiB |
@ -1,66 +1,356 @@ |
|||
// pages/personal/personal.js
|
|||
Page({ |
|||
import http from '../../utils/api' |
|||
const baseUrl = require('../../utils/baseUrl') |
|||
|
|||
/** |
|||
* 页面的初始数据 |
|||
*/ |
|||
Page({ |
|||
data: { |
|||
// 用户信息
|
|||
avatarUrl: '', // 原始头像路径(相对路径)
|
|||
avatarFullUrl: '', // 完整的头像URL(用于显示)
|
|||
userInfo: { |
|||
user: {} |
|||
}, |
|||
baseUrl: baseUrl, |
|||
displayNickName: '', // 显示用的昵称
|
|||
|
|||
// 弹窗状态
|
|||
showFeedbackModal: false, |
|||
showNicknameModal: false, |
|||
showLogoutModal: false, |
|||
showToast: false, |
|||
|
|||
// 反馈相关
|
|||
feedbackContent: '', |
|||
canSubmit: false, |
|||
isSubmitting: false, |
|||
|
|||
// 编辑相关
|
|||
newNickname: '', |
|||
|
|||
// 提示信息
|
|||
toastText: '', |
|||
|
|||
// 表单数据
|
|||
formData: { |
|||
avatar: null, |
|||
nickName: null |
|||
}, |
|||
|
|||
/** |
|||
* 生命周期函数--监听页面加载 |
|||
*/ |
|||
onLoad(options) { |
|||
// 上传状态
|
|||
isUploadingAvatar: false, |
|||
isUpdatingNickname: false, |
|||
|
|||
// 消息数字
|
|||
totalToday: 0 |
|||
}, |
|||
|
|||
/** |
|||
* 生命周期函数--监听页面初次渲染完成 |
|||
*/ |
|||
onReady() { |
|||
onLoad() { |
|||
this.gettoday() |
|||
|
|||
}, |
|||
|
|||
/** |
|||
* 生命周期函数--监听页面显示 |
|||
*/ |
|||
onShow() { |
|||
console.log('个人中心页面显示,重新获取用户信息') |
|||
// 每次显示页面都从服务器获取最新数据
|
|||
this.getUserInfo() |
|||
// 刷新消息数
|
|||
this.gettoday() |
|||
}, |
|||
|
|||
|
|||
// 获取用户信息
|
|||
getUserInfo() { |
|||
http.UserInfo({ |
|||
data: {}, |
|||
success: (res) => { |
|||
console.log('获取用户信息成功', res) |
|||
// 更新数据
|
|||
this.setData({ |
|||
userInfo: res.data, |
|||
displayNickName: res.data.user.nickName, |
|||
avatarUrl: baseUrl + res.data.user.avatar |
|||
}) |
|||
// 更新缓存
|
|||
wx.setStorageSync('userInfo', res.data) |
|||
|
|||
}, |
|||
fail: (err) => { |
|||
console.error('获取用户信息失败:', err) |
|||
|
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 问诊消息 - 获取今日消息数
|
|||
gettoday() { |
|||
http.today({ |
|||
data: {}, |
|||
success: res => { |
|||
if (res.rows && res.rows[0]) { |
|||
const num = res.rows[0].totalTodayReplyCount || 0 |
|||
this.setData({ |
|||
totalToday: num |
|||
}) |
|||
} |
|||
}, |
|||
fail: err => { |
|||
console.error('获取消息数失败', err) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 选择头像
|
|||
onChooseAvatar(e) { |
|||
console.log(7788, e); |
|||
// 防止重复上传
|
|||
if (this.data.isUploadingAvatar) { |
|||
this.showToast('正在上传中...') |
|||
return |
|||
} |
|||
|
|||
const { |
|||
avatarUrl |
|||
} = e.detail |
|||
if (!avatarUrl) { |
|||
this.showToast('选择头像失败') |
|||
return |
|||
} |
|||
|
|||
this.setData({ |
|||
isUploadingAvatar: true |
|||
}) |
|||
|
|||
wx.showLoading({ |
|||
title: '上传中...', |
|||
mask: true |
|||
}) |
|||
|
|||
// 上传头像到服务器
|
|||
wx.uploadFile({ |
|||
url: baseUrl + '/common/upload', |
|||
header: { |
|||
'Authorization': 'Bearer ' + wx.getStorageSync('token') |
|||
}, |
|||
filePath: avatarUrl, |
|||
name: 'file', |
|||
success: (uploadRes) => { |
|||
|
|||
/** |
|||
* 生命周期函数--监听页面隐藏 |
|||
*/ |
|||
onHide() { |
|||
const result = JSON.parse(uploadRes.data) |
|||
console.log('上传结果', result) |
|||
|
|||
if (result && result.fileName) { |
|||
// 获取上传后的文件路径
|
|||
const uploadedFilePath = result.fileName |
|||
this.setData({ |
|||
avatarUrl: uploadedFilePath, |
|||
}) |
|||
|
|||
// 更新缓存中的用户信息
|
|||
const cachedUserInfo = wx.getStorageSync('userInfo') || {} |
|||
if (!cachedUserInfo.user) { |
|||
cachedUserInfo.user = {} |
|||
} |
|||
cachedUserInfo.user.avatar = uploadedFilePath |
|||
wx.setStorageSync('userInfo', cachedUserInfo) |
|||
// 更新头像的API
|
|||
http.revise({ |
|||
data: { |
|||
avatar: uploadedFilePath |
|||
}, |
|||
success: (res) => { |
|||
console.log('头像更新成功') |
|||
wx.hideLoading() |
|||
this.showToast('头像更新成功') |
|||
// 4. 重新获取用户信息以确保数据同步
|
|||
setTimeout(() => { |
|||
this.getUserInfo() |
|||
}, 500) |
|||
}, |
|||
fail: (err) => { |
|||
console.error('头像更新失败:', err) |
|||
wx.hideLoading() |
|||
this.showToast('头像保存失败,请重试') |
|||
} |
|||
}) |
|||
} else { |
|||
throw new Error('上传失败:返回数据格式错误') |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
wx.hideLoading() |
|||
console.error('上传失败:', err) |
|||
this.showToast('上传失败,请检查网络') |
|||
}, |
|||
complete: () => { |
|||
this.setData({ |
|||
isUploadingAvatar: false |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 生命周期函数--监听页面卸载 |
|||
*/ |
|||
onUnload() { |
|||
// 编辑昵称
|
|||
editNickname() { |
|||
this.setData({ |
|||
showNicknameModal: true, |
|||
newNickname: this.data.userInfo.user?.nickName || this.data.displayNickName || '' |
|||
}) |
|||
}, |
|||
|
|||
hideNicknameModal() { |
|||
this.setData({ |
|||
showNicknameModal: false |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 页面相关事件处理函数--监听用户下拉动作 |
|||
*/ |
|||
onPullDownRefresh() { |
|||
onNicknameInput(e) { |
|||
this.setData({ |
|||
newNickname: e.detail.value |
|||
}) |
|||
}, |
|||
|
|||
saveNickname() { |
|||
const newNickname = this.data.newNickname.trim() |
|||
|
|||
if (!newNickname) { |
|||
this.showToast('昵称不能为空') |
|||
return |
|||
} |
|||
|
|||
if (newNickname.length > 10) { |
|||
this.showToast('昵称不能超过10个字符') |
|||
return |
|||
} |
|||
|
|||
// 如果昵称没有变化,直接关闭弹窗
|
|||
const currentNickName = this.data.userInfo.user?.nickName || this.data.displayNickName |
|||
if (newNickname === currentNickName) { |
|||
this.hideNicknameModal() |
|||
return |
|||
} |
|||
|
|||
this.setData({ |
|||
isUpdatingNickname: true |
|||
}) |
|||
|
|||
// 立即更新本地显示
|
|||
this.setData({ |
|||
'userInfo.user.nickName': newNickname, |
|||
displayNickName: newNickname, |
|||
'formData.nickName': newNickname |
|||
}) |
|||
|
|||
// 更新缓存
|
|||
const cachedUserInfo = wx.getStorageSync('userInfo') || {} |
|||
if (!cachedUserInfo.user) { |
|||
cachedUserInfo.user = {} |
|||
} |
|||
cachedUserInfo.user.nickName = newNickname |
|||
wx.setStorageSync('userInfo', cachedUserInfo) |
|||
|
|||
// 更新到服务器
|
|||
http.revise({ |
|||
data: { |
|||
nickName: newNickname |
|||
}, |
|||
success: (res) => { |
|||
console.log('昵称更新成功') |
|||
this.setData({ |
|||
showNicknameModal: false, |
|||
isUpdatingNickname: false, |
|||
'formData.nickName': null |
|||
}) |
|||
this.showToast('昵称修改成功') |
|||
|
|||
/** |
|||
* 页面上拉触底事件的处理函数 |
|||
*/ |
|||
onReachBottom() { |
|||
// 重新获取用户信息以确保数据同步
|
|||
setTimeout(() => { |
|||
this.getUserInfo() |
|||
}, 500) |
|||
}, |
|||
fail: (err) => { |
|||
console.error('昵称更新失败:', err) |
|||
this.setData({ |
|||
isUpdatingNickname: false |
|||
}) |
|||
this.showToast('修改失败,请重试') |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 查看问诊消息
|
|||
goToConsultation() { |
|||
wx.navigateTo({ |
|||
url: '/pagesA/pages/todayInquiry/todayInquiry' |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 用户点击右上角分享 |
|||
*/ |
|||
onShareAppMessage() { |
|||
// 查看问答消息
|
|||
goToQA() { |
|||
wx.navigateTo({ |
|||
url: '' // 请填写实际的问答消息页面路径
|
|||
}) |
|||
}, |
|||
|
|||
// 实名认证
|
|||
goToAuth() { |
|||
if (this.data.userInfo.authStatus == '已认证') { |
|||
this.showToast('您已完成实名认证') |
|||
} else { |
|||
wx.navigateTo({ |
|||
url: '/pagesA/pages/attestation/attestation' |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
|
|||
|
|||
|
|||
// 退出登录相关
|
|||
showLogoutConfirm() { |
|||
this.setData({ |
|||
showLogoutModal: true |
|||
}) |
|||
}, |
|||
|
|||
hideLogoutModal() { |
|||
this.setData({ |
|||
showLogoutModal: false |
|||
}) |
|||
}, |
|||
|
|||
doLogout() { |
|||
// 清除本地存储
|
|||
wx.clearStorageSync() |
|||
|
|||
// 跳转到登录页
|
|||
wx.reLaunch({ |
|||
url: '/pages/login/login' |
|||
}) |
|||
this.showToast('已退出登录') |
|||
}, |
|||
|
|||
// 显示提示
|
|||
showToast(text) { |
|||
this.setData({ |
|||
toastText: text, |
|||
showToast: true |
|||
}) |
|||
|
|||
setTimeout(() => { |
|||
this.setData({ |
|||
showToast: false |
|||
}) |
|||
}, 2000) |
|||
}, |
|||
|
|||
// 下拉刷新
|
|||
onPullDownRefresh() { |
|||
this.getUserInfo() |
|||
this.gettoday() // 刷新消息数
|
|||
|
|||
setTimeout(() => { |
|||
wx.stopPullDownRefresh() |
|||
this.showToast('刷新成功') |
|||
}, 1000) |
|||
}, |
|||
|
|||
}) |
|||
@ -1,3 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText":"个人中心", |
|||
"usingComponents": {} |
|||
} |
|||
@ -1,2 +1,134 @@ |
|||
<!--pages/personal/personal.wxml--> |
|||
<text>pages/personal/personal.wxml</text> |
|||
<view class="personal-center"> |
|||
<!-- 用户信息区域 --> |
|||
<view class="user-section fade-in"> |
|||
<view class="user-card"> |
|||
<!-- 头像 --> |
|||
<button class="avatar-btn" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar"> |
|||
<image class="avatar" src="{{avatarUrl || '/pages/images/tx.png'}}" mode="aspectFill"></image> |
|||
<view class="avatar-edit-tip">点击修改</view> |
|||
</button> |
|||
|
|||
<!-- 用户信息 --> |
|||
<view class="user-info"> |
|||
<view class="nickname-section" bindtap="editNickname"> |
|||
<text class="nickname">{{displayNickName || '微信用户'}}</text> |
|||
<image src="/pages/images/bj.png"></image> |
|||
</view> |
|||
|
|||
<view class="user-meta"> |
|||
<view class="auth-tag {{userInfo.isVerified ? 'verified' : ''}}" bindtap="goToAuth"> |
|||
<text>{{userInfo.authStatus}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 消息通知 --> |
|||
<!-- <view class="section-card fade-in-delay-1"> |
|||
<view class="section-header"> |
|||
<text class="section-title">消息通知</text> |
|||
</view> |
|||
<view class="message-row"> |
|||
<view class="message-item" bindtap="goToConsultation"> |
|||
<view class="message-icon-wrapper"> |
|||
<image class="message-icon" src="/pages/images/wz.png" mode=""></image> |
|||
|
|||
<view class="badge" wx:if="{{totalToday > 0}}"> |
|||
<view class="badge-dot"></view> |
|||
<view class="badge-num animation-badge">{{totalToday}}</view> |
|||
</view> |
|||
</view> |
|||
<text class="message-label">问诊消息</text> |
|||
</view> |
|||
<view class="divider"></view> |
|||
<view class="message-item" bindtap="goToQA"> |
|||
<view class="message-icon-wrapper"> |
|||
<image class="message-icon" src="/pages/images/wdxx.png" mode=""></image> |
|||
</view> |
|||
<text class="message-label">问答消息</text> |
|||
</view> |
|||
</view> |
|||
</view> --> |
|||
|
|||
<!-- 功能列表 --> |
|||
<view class="section-card fade-in-delay-2"> |
|||
<view class="function-list"> |
|||
<!-- 实名认证 --> |
|||
<view class="function-item" bindtap="goToAuth"> |
|||
<view class="item-left"> |
|||
<image class="item-icon" src="/pages/images/smrz.png"></image> |
|||
<text class="item-title">实名认证</text> |
|||
</view> |
|||
<view class="item-status {{userInfo.isVerified ? 'verified' : ''}}"> |
|||
{{userInfo.authStatus}} |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 反馈建议 --> |
|||
<view class="function-item" bindtap="showFeedback"> |
|||
<view class="item-left"> |
|||
<image class="item-icon" src="/pages/images/fkjy.png"></image> |
|||
<text class="item-title">服务评价</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 退出登录 --> |
|||
<view class="function-item" bindtap="showLogoutConfirm"> |
|||
<view class="item-left"> |
|||
<image class="item-icon" src="/pages/images/logout.png"></image> |
|||
<text class="item-title logout-title">退出登录</text> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
<!-- 修改昵称弹窗 --> |
|||
<view class="nickname-modal {{showNicknameModal ? 'show' : ''}}"> |
|||
<view class="modal-mask" bindtap="hideNicknameModal"></view> |
|||
<view class="modal-content"> |
|||
<view class="modal-header"> |
|||
<text class="modal-title">修改昵称</text> |
|||
</view> |
|||
<view class="modal-body"> |
|||
<input |
|||
class="nickname-input" |
|||
type="text" |
|||
placeholder="请输入昵称" |
|||
value="{{newNickname}}" |
|||
bindinput="onNicknameInput" |
|||
maxlength="10" |
|||
focus="{{showNicknameModal}}" |
|||
confirm-type="done" |
|||
/> |
|||
<text class="input-tip">最多10个字符</text> |
|||
</view> |
|||
<view class="modal-footer"> |
|||
<button class="cancel-btn" bindtap="hideNicknameModal">取消</button> |
|||
<button class="confirm-btn" bindtap="saveNickname">确定</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 退出登录确认弹窗 --> |
|||
<view class="logout-modal {{showLogoutModal ? 'show' : ''}}"> |
|||
<view class="modal-mask" bindtap="hideLogoutModal"></view> |
|||
<view class="modal-content"> |
|||
<view class="logout-modal-body"> |
|||
<text class="logout-title">确认退出登录?</text> |
|||
<text class="logout-desc">退出后需要重新登录才能使用完整功能</text> |
|||
</view> |
|||
<view class="logout-modal-footer"> |
|||
<button class="logout-cancel-btn" bindtap="hideLogoutModal">取消</button> |
|||
<button class="logout-confirm-btn" bindtap="doLogout">退出登录</button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 提示信息 --> |
|||
<view class="toast {{showToast ? 'show' : ''}}"> |
|||
<text>{{toastText}}</text> |
|||
</view> |
|||
</view> |
|||
@ -1 +1,611 @@ |
|||
/* pages/personal/personal.wxss */ |
|||
.personal-center { |
|||
min-height: 100vh; |
|||
background: linear-gradient(180deg, #86D8D0 0%, #a9dfda 30%, #cfe9e7 60%, #ECF8F7 90%); |
|||
padding-bottom: 40rpx; |
|||
} |
|||
|
|||
/* 淡入动画 */ |
|||
.fade-in { |
|||
animation: fadeIn 0.6s ease forwards; |
|||
opacity: 0; |
|||
transform: translateY(20rpx); |
|||
} |
|||
|
|||
.fade-in-delay-1 { |
|||
animation: fadeIn 0.6s 0.2s ease forwards; |
|||
opacity: 0; |
|||
transform: translateY(20rpx); |
|||
} |
|||
|
|||
.fade-in-delay-2 { |
|||
animation: fadeIn 0.6s 0.4s ease forwards; |
|||
opacity: 0; |
|||
transform: translateY(20rpx); |
|||
} |
|||
|
|||
@keyframes fadeIn { |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
/* 用户信息区域 */ |
|||
.user-section { |
|||
padding: 40rpx 30rpx 30rpx; |
|||
} |
|||
|
|||
.user-card { |
|||
background: white; |
|||
border-radius: 24rpx; |
|||
padding: 40rpx 30rpx; |
|||
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.08); |
|||
display: flex; |
|||
align-items: center; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.user-card:active { |
|||
transform: scale(0.99); |
|||
box-shadow: 0 5rpx 20rpx rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.avatar-btn { |
|||
position: relative; |
|||
width: 140rpx; |
|||
height: 140rpx; |
|||
margin-right: 30rpx; |
|||
flex-shrink: 0; |
|||
padding: 0; |
|||
background: transparent; |
|||
border: none; |
|||
border-radius: 50%; |
|||
overflow: visible; |
|||
} |
|||
|
|||
.avatar-btn::after { |
|||
border: none; |
|||
} |
|||
|
|||
.avatar { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 50%; |
|||
border: 4rpx solid white; |
|||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.avatar-btn:active .avatar { |
|||
transform: scale(0.95); |
|||
} |
|||
|
|||
.avatar-edit-tip { |
|||
position: absolute; |
|||
bottom: -10rpx; |
|||
left: 50%; |
|||
transform: translateX(-50%); |
|||
background: rgba(0, 0, 0, 0.7); |
|||
color: white; |
|||
font-size: 20rpx; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 20rpx; |
|||
white-space: nowrap; |
|||
opacity: 0; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.avatar-btn:hover .avatar-edit-tip { |
|||
opacity: 1; |
|||
bottom: -20rpx; |
|||
} |
|||
|
|||
.user-info { |
|||
flex: 1; |
|||
} |
|||
|
|||
.nickname-section { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
padding: 8rpx 0; |
|||
border-radius: 8rpx; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.nickname-section:active { |
|||
background: rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.nickname { |
|||
font-size: 36rpx; |
|||
font-weight: 700; |
|||
color: #1e293b; |
|||
margin-right: 20rpx; |
|||
max-width: 300rpx; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.nickname-section image { |
|||
width: 26rpx; |
|||
height: 26rpx; |
|||
opacity: 0.6; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.nickname-section:active image { |
|||
opacity: 0.8; |
|||
transform: scale(1.1); |
|||
} |
|||
|
|||
.user-meta { |
|||
display: flex; |
|||
align-items: center; |
|||
flex-wrap: wrap; |
|||
gap: 16rpx; |
|||
} |
|||
|
|||
.auth-tag { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 6rpx 16rpx; |
|||
background: #fef3c7; |
|||
border-radius: 20rpx; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.auth-tag.verified { |
|||
background: #d1fae5; |
|||
} |
|||
|
|||
.auth-tag:active { |
|||
transform: scale(0.95); |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.auth-tag text { |
|||
font-size: 24rpx; |
|||
color: #92400e; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.auth-tag.verified text { |
|||
color: #065f46; |
|||
} |
|||
|
|||
/* 卡片样式 */ |
|||
.section-card { |
|||
background: white; |
|||
margin: 0 30rpx 30rpx; |
|||
border-radius: 20rpx; |
|||
overflow: hidden; |
|||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05); |
|||
} |
|||
|
|||
.section-header { |
|||
padding: 30rpx 30rpx 20rpx; |
|||
border-bottom: 1rpx solid #f1f5f9; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #1e293b; |
|||
position: relative; |
|||
padding-left: 20rpx; |
|||
} |
|||
|
|||
.section-title::before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
width: 6rpx; |
|||
height: 24rpx; |
|||
background: linear-gradient(135deg, #667eea, #764ba2); |
|||
border-radius: 3rpx; |
|||
} |
|||
|
|||
/* 消息行 */ |
|||
.message-row { |
|||
display: flex; |
|||
align-items: center; |
|||
padding: 30rpx; |
|||
} |
|||
|
|||
.message-item { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.message-item:active { |
|||
transform: scale(0.95); |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.message-icon-wrapper { |
|||
position: relative; |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
margin-bottom: 16rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background: #f8fafc; |
|||
border-radius: 50%; |
|||
border: 1rpx solid #e2e8f0; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.message-item:active .message-icon-wrapper { |
|||
background: #e2e8f0; |
|||
} |
|||
|
|||
.message-icon { |
|||
width: 60rpx; |
|||
height: 60rpx; |
|||
} |
|||
|
|||
/* 数字气泡 - 定位在顶部 */ |
|||
.badge { |
|||
position: absolute; |
|||
top: -10rpx; |
|||
right: -10rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.badge-num { |
|||
background: linear-gradient(135deg, #ff6b6b, #ee5253); |
|||
border-radius: 36rpx; |
|||
color: white; |
|||
font-size: 22rpx; |
|||
font-weight: 600; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 0 10rpx; |
|||
box-shadow: 0 4rpx 8rpx rgba(238, 82, 83, 0.3); |
|||
} |
|||
|
|||
/* 动画效果:跳动+呼吸 */ |
|||
.animation-badge { |
|||
animation: badgeBounce 2.5s ease-in-out infinite; |
|||
} |
|||
|
|||
@keyframes badgeBounce { |
|||
0%, 100% { |
|||
transform: scale(1); |
|||
} |
|||
30% { |
|||
transform: scale(1.2); |
|||
} |
|||
50% { |
|||
transform: scale(1.1); |
|||
} |
|||
70% { |
|||
transform: scale(1.15); |
|||
} |
|||
} |
|||
|
|||
/* 可选的小红点效果(如果有需要可以保留,当前用的是数字) */ |
|||
.badge-dot { |
|||
display: none; /* 隐藏,用数字替代 */ |
|||
} |
|||
|
|||
.message-label { |
|||
font-size: 26rpx; |
|||
color: #475569; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.divider { |
|||
width: 1rpx; |
|||
height: 60rpx; |
|||
background: #e2e8f0; |
|||
margin: 0 40rpx; |
|||
} |
|||
|
|||
/* 功能列表 */ |
|||
.function-list { |
|||
padding: 0 30rpx 20rpx; |
|||
} |
|||
|
|||
.function-item { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 30rpx 0; |
|||
border-bottom: 1rpx solid #f1f5f9; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.function-item:last-child { |
|||
border-bottom: none; |
|||
} |
|||
|
|||
.function-item:active { |
|||
background: #f8fafc; |
|||
border-radius: 12rpx; |
|||
} |
|||
|
|||
.item-left { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.item-icon { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin-right: 20rpx; |
|||
} |
|||
|
|||
.item-title { |
|||
font-size: 30rpx; |
|||
color: #1e293b; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.logout-title { |
|||
color: #ef4444; |
|||
} |
|||
|
|||
.item-status { |
|||
font-size: 26rpx; |
|||
color: #f59e0b; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.item-status.verified { |
|||
color: #10b981; |
|||
} |
|||
|
|||
|
|||
|
|||
/* 昵称修改弹窗 */ |
|||
.nickname-modal { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1000; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
opacity: 0; |
|||
visibility: hidden; |
|||
transition: all 0.3s ease; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.nickname-modal.show { |
|||
opacity: 1; |
|||
visibility: visible; |
|||
pointer-events: auto; |
|||
} |
|||
|
|||
.nickname-modal .modal-content { |
|||
width: 600rpx; |
|||
background: white; |
|||
border-radius: 20rpx; |
|||
overflow: hidden; |
|||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2); |
|||
transform: scale(0.8); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.nickname-modal.show .modal-content { |
|||
transform: scale(1); |
|||
} |
|||
|
|||
.nickname-modal .modal-header { |
|||
padding: 40rpx 40rpx 20rpx; |
|||
border-bottom: 1rpx solid #f1f5f9; |
|||
} |
|||
|
|||
.nickname-modal .modal-body { |
|||
padding: 40rpx; |
|||
} |
|||
|
|||
.nickname-input { |
|||
width: 100%; |
|||
height: 80rpx; |
|||
background: #f8fafc; |
|||
border: 2rpx solid #e2e8f0; |
|||
border-radius: 12rpx; |
|||
font-size: 28rpx; |
|||
color: #1e293b; |
|||
text-align: center; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.nickname-input:focus { |
|||
border-color: #667eea; |
|||
background: white; |
|||
box-shadow: 0 0 0 4rpx rgba(102, 126, 234, 0.1); |
|||
} |
|||
|
|||
.input-tip { |
|||
display: block; |
|||
font-size: 24rpx; |
|||
color: #94a3b8; |
|||
text-align: center; |
|||
margin-top: 16rpx; |
|||
} |
|||
|
|||
.nickname-modal .modal-footer { |
|||
padding: 0 40rpx 40rpx; |
|||
display: flex; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.cancel-btn, |
|||
.confirm-btn { |
|||
flex: 1; |
|||
padding: 5rpx 0; |
|||
border-radius: 40rpx; |
|||
font-size: 28rpx; |
|||
font-weight: 500; |
|||
transition: all 0.3s ease; |
|||
border: none; |
|||
} |
|||
|
|||
.cancel-btn { |
|||
background: #f1f5f9; |
|||
color: #64748b; |
|||
} |
|||
|
|||
.cancel-btn::after{ |
|||
border: none; |
|||
} |
|||
|
|||
.cancel-btn:active { |
|||
background: #e2e8f0; |
|||
} |
|||
|
|||
.confirm-btn { |
|||
background: linear-gradient(135deg, #667eea, #764ba2); |
|||
color: white; |
|||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3); |
|||
} |
|||
|
|||
.confirm-btn:active { |
|||
transform: scale(0.98); |
|||
opacity: 0.9; |
|||
} |
|||
|
|||
/* 退出登录确认弹窗 */ |
|||
.logout-modal { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
z-index: 1000; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
opacity: 0; |
|||
visibility: hidden; |
|||
transition: all 0.3s ease; |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.logout-modal.show { |
|||
opacity: 1; |
|||
visibility: visible; |
|||
pointer-events: auto; |
|||
} |
|||
|
|||
.logout-modal .modal-content { |
|||
width: 600rpx; |
|||
background: white; |
|||
border-radius: 24rpx; |
|||
overflow: hidden; |
|||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2); |
|||
transform: scale(0.8); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.logout-modal.show .modal-content { |
|||
transform: scale(1); |
|||
} |
|||
|
|||
.logout-modal-body { |
|||
padding: 60rpx 40rpx 40rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.logout-title { |
|||
font-weight: 700; |
|||
color: #1e293b; |
|||
display: block; |
|||
} |
|||
|
|||
.logout-desc { |
|||
font-size: 28rpx; |
|||
color: #64748b; |
|||
line-height: 1.5; |
|||
} |
|||
|
|||
.logout-modal-footer { |
|||
padding: 0 40rpx 40rpx; |
|||
display: flex; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.logout-cancel-btn, |
|||
.logout-confirm-btn { |
|||
flex: 1; |
|||
border-radius: 44rpx; |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
transition: all 0.3s ease; |
|||
border: none; |
|||
} |
|||
|
|||
.logout-cancel-btn::after{ |
|||
border: none; |
|||
} |
|||
|
|||
.logout-cancel-btn { |
|||
background: #f1f5f9; |
|||
color: #64748b; |
|||
} |
|||
|
|||
.logout-cancel-btn:active { |
|||
background: #e2e8f0; |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
.logout-confirm-btn { |
|||
background: linear-gradient(135deg, #ef4444, #dc2626); |
|||
color: white; |
|||
box-shadow: 0 8rpx 24rpx rgba(239, 68, 68, 0.3); |
|||
} |
|||
|
|||
.logout-confirm-btn:active { |
|||
transform: scale(0.98); |
|||
opacity: 0.9; |
|||
} |
|||
|
|||
/* 提示信息 */ |
|||
.toast { |
|||
position: fixed; |
|||
top: 150rpx; |
|||
left: 50%; |
|||
transform: translateX(-50%) translateY(-100rpx); |
|||
background: rgba(30, 41, 59, 0.95); |
|||
backdrop-filter: blur(20rpx); |
|||
color: white; |
|||
padding: 24rpx 48rpx; |
|||
border-radius: 16rpx; |
|||
font-size: 28rpx; |
|||
font-weight: 500; |
|||
opacity: 0; |
|||
visibility: hidden; |
|||
transition: all 0.3s ease; |
|||
z-index: 1001; |
|||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2); |
|||
max-width: 80%; |
|||
text-align: center; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.toast.show { |
|||
opacity: 1; |
|||
visibility: visible; |
|||
transform: translateX(-50%) translateY(0); |
|||
} |
|||
|
After Width: 200 | Height: 200 | Size: 2.4 KiB |
@ -0,0 +1,159 @@ |
|||
Page({ |
|||
data: { |
|||
// 专家信息(新增在线状态)
|
|||
expertInfo: { |
|||
avatar: 'https://api.dicebear.com/7.x/avataaars/png?seed=expert_pro&size=140', |
|||
name: '张振农', |
|||
specialty: '作物栽培 · 土壤改良', |
|||
experience: 15, |
|||
online: true // 在线状态
|
|||
}, |
|||
// 聊天申请列表数据
|
|||
applyList: [], |
|||
// 分页相关
|
|||
page: 1, |
|||
pageSize: 10, |
|||
hasMore: true, |
|||
total: 0, |
|||
}, |
|||
|
|||
onLoad() { |
|||
this.loadApplyList(1); |
|||
}, |
|||
|
|||
// 加载申请列表(增强模拟数据)
|
|||
loadApplyList(page) { |
|||
const { pageSize } = this.data; |
|||
wx.showLoading({ title: '加载中...', mask: true }); |
|||
|
|||
setTimeout(() => { |
|||
const mockData = this.generateMockData(page, pageSize); |
|||
|
|||
if (page === 1) { |
|||
this.setData({ |
|||
applyList: mockData.list, |
|||
total: mockData.total, |
|||
hasMore: mockData.hasMore, |
|||
page: page |
|||
}); |
|||
} else { |
|||
this.setData({ |
|||
applyList: this.data.applyList.concat(mockData.list), |
|||
total: mockData.total, |
|||
hasMore: mockData.hasMore, |
|||
page: page |
|||
}); |
|||
} |
|||
wx.hideLoading(); |
|||
}, 600); |
|||
}, |
|||
|
|||
// 生成更丰富的模拟数据(含标签)
|
|||
generateMockData(page, pageSize) { |
|||
const total = 23; |
|||
const start = (page - 1) * pageSize; |
|||
const end = start + pageSize; |
|||
const hasMore = end < total; |
|||
const list = []; |
|||
|
|||
const seeds = ['farmer1', 'sheep', 'cattle', 'wheat', 'tractor', 'green', 'sun', 'rain', 'soil', 'seed']; |
|||
const firstNames = ['李', '王', '张', '刘', '赵', '陈', '杨', '周', '吴', '黄']; |
|||
const farmTypes = ['水稻种植', '牛羊养殖', '果树栽培', '蔬菜大棚', '有机农场', '蜂蜜养殖', '食用菌', '药材种植']; |
|||
const tagPool = [ |
|||
['玉米', '虫害'], |
|||
['羊羔', '腹泻'], |
|||
['施肥', '建议'], |
|||
['土壤', '检测'], |
|||
['大棚', '温度'], |
|||
['猪病', '防治'], |
|||
['果树', '修剪'], |
|||
['蜂蜜', '取蜜'] |
|||
]; |
|||
|
|||
for (let i = start; i < Math.min(end, total); i++) { |
|||
const id = `apply_${i + 1}`; |
|||
const userId = `user_${(i % 10) + 1}`; |
|||
const seedIndex = i % seeds.length; |
|||
const nameIndex = i % firstNames.length; |
|||
const typeIndex = (i * 3) % farmTypes.length; |
|||
const unread = i % 4 === 0 ? 2 : (i % 7 === 0 ? 1 : 0); |
|||
const statuses = ['pending', 'accepted', 'completed']; |
|||
const status = statuses[i % 3]; |
|||
|
|||
// 随机标签
|
|||
const tags = tagPool[(i * 2) % tagPool.length]; |
|||
|
|||
const time = new Date(Date.now() - (i * 7200000) - Math.floor(Math.random() * 5000000)); |
|||
const timeStr = `${time.getMonth()+1}.${time.getDate()} ${time.getHours()}:${time.getMinutes().toString().padStart(2,'0')}`; |
|||
|
|||
// 更真实的预览消息
|
|||
let lastMessage = ''; |
|||
if (i % 5 === 0) lastMessage = '专家您好,我家玉米出现黄叶,能帮看看吗?'; |
|||
else if (i % 3 === 0) lastMessage = '咨询一下羊羔腹泻的问题,急!'; |
|||
else if (i % 4 === 0) lastMessage = '请问什么时候方便通话?'; |
|||
else lastMessage = '请求添加您为咨询顾问'; |
|||
|
|||
list.push({ |
|||
id: id, |
|||
user: { |
|||
id: userId, |
|||
name: firstNames[nameIndex] + (i % 2 === 0 ? '建国' : '秀英') + (i + 1), |
|||
avatar: `https://api.dicebear.com/7.x/avataaars/png?seed=${seeds[seedIndex]}_${i}&size=96`, |
|||
farmType: farmTypes[typeIndex], |
|||
}, |
|||
applyTime: timeStr, |
|||
lastMessage: lastMessage, |
|||
unreadCount: unread, |
|||
status: status, |
|||
tags: tags, |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
list, |
|||
total, |
|||
hasMore, |
|||
}; |
|||
}, |
|||
|
|||
// 处理点击申请项
|
|||
handleApply(e) { |
|||
const { id, user } = e.currentTarget.dataset; |
|||
wx.showActionSheet({ |
|||
itemList: ['接受并回复', '标记为已读', '稍后处理'], |
|||
success: (res) => { |
|||
if (res.tapIndex === 0) { |
|||
wx.showToast({ title: '已接受申请', icon: 'success' }); |
|||
// 模拟更新状态
|
|||
const newList = this.data.applyList.map(item => |
|||
item.id === id ? {...item, status: 'accepted'} : item |
|||
); |
|||
this.setData({ applyList: newList }); |
|||
} else if (res.tapIndex === 1) { |
|||
const newList = this.data.applyList.map(item => |
|||
item.id === id ? {...item, unreadCount: 0} : item |
|||
); |
|||
this.setData({ applyList: newList }); |
|||
wx.showToast({ title: '已标记已读', icon: 'none' }); |
|||
} else { |
|||
wx.showToast({ title: '已稍后处理', icon: 'none' }); |
|||
} |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
|
|||
|
|||
loadMore() { |
|||
const { hasMore, page } = this.data; |
|||
if (hasMore) { |
|||
this.loadApplyList(page + 1); |
|||
} |
|||
}, |
|||
|
|||
onPullDownRefresh() { |
|||
this.setData({ page: 1, hasMore: true }); |
|||
this.loadApplyList(1); |
|||
wx.stopPullDownRefresh(); |
|||
} |
|||
}); |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText":"咨询列表", |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
<view class="container-box"> |
|||
<!-- 头部:专家信息卡片 --> |
|||
<view class="expert-card"> |
|||
<view class="avatar-wrapper"> |
|||
<image class="avatar" src="{{expertInfo.avatar}}" mode="aspectFill"></image> |
|||
<view class="online-status {{expertInfo.online ? 'online' : 'offline'}}"></view> |
|||
</view> |
|||
<view class="info"> |
|||
<view class="name-row"> |
|||
<text class="name">{{expertInfo.name}}</text> |
|||
<text class="online-text">{{expertInfo.online ? '在线' : '离线'}}</text> |
|||
</view> |
|||
<view class="specialty">{{expertInfo.specialty}}</view> |
|||
<view class="experience-tag"> |
|||
<text class="tag-text">从业 {{expertInfo.experience}} 年</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 聊天申请列表区域 --> |
|||
<view class="list-header"> |
|||
<view class="list-title-wrap"> |
|||
<text class="list-title">咨询申请</text> |
|||
<text class="list-count">{{applyList.length}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<scroll-view scroll-y class="apply-scroll" bindscrolltolower="loadMore" enhanced show-scrollbar="{{false}}"> |
|||
<view class="apply-list"> |
|||
<block wx:for="{{applyList}}" wx:key="id"> |
|||
<view class="apply-item" bindtap="handleApply" data-id="{{item.id}}" data-user="{{item.user}}"> |
|||
<image class="user-avatar" src="{{item.user.avatar}}" mode="aspectFill"></image> |
|||
<view class="apply-content"> |
|||
<view class="apply-header"> |
|||
<view class="user-info"> |
|||
<text class="user-name">{{item.user.name}}</text> |
|||
</view> |
|||
<text class="apply-time">{{item.applyTime}}</text> |
|||
</view> |
|||
<view class="message-area"> |
|||
<text class="message-preview">{{item.lastMessage || '请求咨询...'}}</text> |
|||
<view wx:if="{{item.unreadCount > 0}}" class="unread-badge">{{item.unreadCount > 99 ? '99+' : item.unreadCount}}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</block> |
|||
|
|||
<!-- 空状态提示 --> |
|||
<view wx:if="{{applyList.length === 0}}" class="empty-state"> |
|||
<text class="empty-text">暂无新的咨询申请</text> |
|||
<text class="empty-subtext">稍后刷新试试</text> |
|||
</view> |
|||
|
|||
<!-- 加载更多指示 --> |
|||
<view wx:if="{{hasMore && applyList.length > 0}}" class="loading-more"> |
|||
<view class="loading-spinner"></view> |
|||
<text>正在加载更多...</text> |
|||
</view> |
|||
<view wx:if="{{!hasMore && applyList.length > 0}}" class="no-more">没有更多了</view> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
@ -0,0 +1,360 @@ |
|||
.container-box { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100vh; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
/* 专家卡片*/ |
|||
.expert-card { |
|||
background-image: linear-gradient(to top, #48c6ef 0%, #6f86d6 100%); |
|||
padding: 36rpx 32rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
color: white; |
|||
box-shadow: 0 8rpx 24rpx rgba(0, 30, 10, 0.25); |
|||
flex-shrink: 0; |
|||
position: relative; |
|||
} |
|||
|
|||
.avatar-wrapper { |
|||
position: relative; |
|||
margin-right: 28rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.avatar { |
|||
width: 140rpx; |
|||
height: 140rpx; |
|||
border-radius: 70rpx; |
|||
border: 4rpx solid rgba(255, 255, 255, 0.25); |
|||
background-color: #e6f0ea; |
|||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.15); |
|||
transition: transform 0.2s; |
|||
} |
|||
|
|||
.avatar:active { |
|||
transform: scale(0.98); |
|||
} |
|||
|
|||
.online-status { |
|||
position: absolute; |
|||
bottom: 6rpx; |
|||
right: 6rpx; |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
border-radius: 14rpx; |
|||
border: 4rpx solid #1a4b2e; |
|||
background-color: #a0a0a0; |
|||
transition: background-color 0.2s; |
|||
} |
|||
|
|||
.online-status.online { |
|||
background-color: #4caf50; |
|||
box-shadow: 0 0 0 2rpx rgba(76, 175, 80, 0.3); |
|||
} |
|||
|
|||
.online-status.offline { |
|||
background-color: #9e9e9e; |
|||
} |
|||
|
|||
.info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
min-width: 0; |
|||
} |
|||
|
|||
.name-row { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 12rpx; |
|||
flex-wrap: wrap; |
|||
gap: 16rpx; |
|||
} |
|||
|
|||
.name { |
|||
font-size: 44rpx; |
|||
font-weight: 600; |
|||
line-height: 1.2; |
|||
letter-spacing: 1rpx; |
|||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2); |
|||
} |
|||
|
|||
.online-text { |
|||
font-size: 26rpx; |
|||
background-color: rgba(255, 255, 255, 0.2); |
|||
padding: 3rpx 20rpx; |
|||
border-radius: 30rpx; |
|||
backdrop-filter: blur(4px); |
|||
border: 1rpx solid rgba(255, 255, 255, 0.15); |
|||
font-weight: 400; |
|||
color: #ffecb3; |
|||
} |
|||
|
|||
.specialty { |
|||
font-size: 26rpx; |
|||
opacity: 0.95; |
|||
margin-bottom: 16rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
background-color: rgba(255, 255, 255, 0.1); |
|||
padding: 8rpx 20rpx; |
|||
border-radius: 40rpx; |
|||
width: fit-content; |
|||
backdrop-filter: blur(4px); |
|||
border: 1rpx solid rgba(255, 255, 255, 0.1); |
|||
} |
|||
|
|||
.experience-tag { |
|||
display: flex; |
|||
align-items: center; |
|||
background-color: rgba(255, 255, 255, 0.1); |
|||
padding: 8rpx 22rpx 8rpx 18rpx; |
|||
border-radius: 60rpx; |
|||
width: fit-content; |
|||
} |
|||
|
|||
|
|||
.tag-text { |
|||
font-size: 26rpx; |
|||
font-weight: 400; |
|||
} |
|||
|
|||
/* 列表头部 */ |
|||
.list-header { |
|||
padding: 28rpx 32rpx 20rpx; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
background-color: transparent; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.list-title-wrap { |
|||
display: flex; |
|||
align-items: baseline; |
|||
gap: 12rpx; |
|||
} |
|||
|
|||
.list-title { |
|||
font-size: 34rpx; |
|||
font-weight: 600; |
|||
color: #1f2d3d; |
|||
letter-spacing: 0.5rpx; |
|||
} |
|||
|
|||
.list-count { |
|||
font-size: 28rpx; |
|||
color: #5f6b7a; |
|||
background-color: #e9ecf0; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 30rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
|
|||
/* 滚动区域 */ |
|||
.apply-scroll { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
padding: 0 32rpx; |
|||
background-color: transparent; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.apply-list { |
|||
padding: 8rpx 0 30rpx; |
|||
} |
|||
|
|||
/* 申请项卡片 */ |
|||
.apply-item { |
|||
background-color: #ffffff; |
|||
border-radius: 32rpx; |
|||
padding: 32rpx 28rpx; |
|||
margin-bottom: 20rpx; |
|||
display: flex; |
|||
align-items: flex-start; |
|||
box-shadow: 0 8rpx 24rpx rgba(0, 20, 10, 0.04); |
|||
transition: all 0.25s ease; |
|||
border: 1rpx solid #edf2f7; |
|||
position: relative; |
|||
backdrop-filter: blur(2px); |
|||
} |
|||
|
|||
.apply-item:active { |
|||
background-color: #fafdff; |
|||
transform: translateY(-4rpx); |
|||
box-shadow: 0 16rpx 32rpx rgba(30, 60, 40, 0.08); |
|||
border-color: #cbd5e0; |
|||
} |
|||
|
|||
.user-avatar { |
|||
width: 96rpx; |
|||
height: 96rpx; |
|||
border-radius: 10rpx; |
|||
background-color: #dde5ed; |
|||
margin-right: 28rpx; |
|||
flex-shrink: 0; |
|||
border: 2rpx solid #e2e8f0; |
|||
transition: border-color 0.2s; |
|||
} |
|||
|
|||
.apply-item:active .user-avatar { |
|||
border-color: #2b6c4e; |
|||
} |
|||
|
|||
.apply-content { |
|||
flex: 1; |
|||
min-width: 0; |
|||
} |
|||
|
|||
.apply-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.user-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 16rpx; |
|||
flex-wrap: wrap; |
|||
flex: 1; |
|||
min-width: 0; |
|||
} |
|||
|
|||
.user-name { |
|||
font-size: 34rpx; |
|||
font-weight: 600; |
|||
color: #1e293b; |
|||
max-width: 240rpx; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
|
|||
.apply-time { |
|||
font-size: 24rpx; |
|||
color: #94a3b8; |
|||
flex-shrink: 0; |
|||
margin-left: 16rpx; |
|||
background-color: #f8fafc; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 30rpx; |
|||
} |
|||
|
|||
.message-area { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin-bottom: 22rpx; |
|||
} |
|||
|
|||
.message-preview { |
|||
font-size: 28rpx; |
|||
color: #475569; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
flex: 1; |
|||
min-width: 0; |
|||
margin-right: 16rpx; |
|||
line-height: 1.4; |
|||
} |
|||
|
|||
.unread-badge { |
|||
background: linear-gradient(145deg, #ff5f6d, #ff7b89); |
|||
color: white; |
|||
font-size: 22rpx; |
|||
font-weight: 600; |
|||
min-width: 40rpx; |
|||
height: 40rpx; |
|||
line-height: 40rpx; |
|||
text-align: center; |
|||
border-radius: 40rpx; |
|||
padding: 0 12rpx; |
|||
flex-shrink: 0; |
|||
box-shadow: 0 4rpx 8rpx rgba(255, 95, 109, 0.25); |
|||
border: 2rpx solid rgba(255, 255, 255, 0.5); |
|||
} |
|||
|
|||
|
|||
/* 空状态 */ |
|||
.empty-state { |
|||
text-align: center; |
|||
padding: 80rpx 0; |
|||
color: #94a3b8; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
} |
|||
|
|||
|
|||
|
|||
.empty-text { |
|||
font-size: 32rpx; |
|||
font-weight: 500; |
|||
color: #64748b; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.empty-subtext { |
|||
font-size: 26rpx; |
|||
color: #a0afbe; |
|||
} |
|||
|
|||
/* 加载更多 */ |
|||
.loading-more { |
|||
text-align: center; |
|||
padding: 30rpx 0 40rpx; |
|||
color: #5f7d9c; |
|||
font-size: 26rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
gap: 16rpx; |
|||
} |
|||
|
|||
.loading-spinner { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
border: 4rpx solid #d1d9e6; |
|||
border-top-color: #2b6c4e; |
|||
border-radius: 50%; |
|||
animation: spin 0.8s linear infinite; |
|||
} |
|||
|
|||
@keyframes spin { |
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
|
|||
.no-more { |
|||
text-align: center; |
|||
padding: 30rpx 0; |
|||
color: #9aa9b9; |
|||
font-size: 26rpx; |
|||
position: relative; |
|||
} |
|||
|
|||
.no-more::before, |
|||
.no-more::after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 50%; |
|||
width: 60rpx; |
|||
height: 1rpx; |
|||
background-color: #dce3ec; |
|||
} |
|||
|
|||
.no-more::before { |
|||
left: 60rpx; |
|||
} |
|||
|
|||
.no-more::after { |
|||
right: 60rpx; |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
import http from '../../../utils/api' |
|||
Page({ |
|||
data: { |
|||
diagnosisList: [] |
|||
}, |
|||
|
|||
onLoad: function () { |
|||
|
|||
}, |
|||
|
|||
onShow:function(){ |
|||
this.getwzd() |
|||
}, |
|||
|
|||
|
|||
// 问诊单
|
|||
getwzd() { |
|||
http.wzd({ |
|||
data: {}, |
|||
success: res => { |
|||
console.log(1111, res); |
|||
this.setData({ |
|||
diagnosisList: res.rows |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
|
|||
|
|||
// 格式化日期显示
|
|||
formatDate: function (dateString) { |
|||
const date = new Date(dateString); |
|||
const now = new Date(); |
|||
const diff = now - date; |
|||
const diffDays = Math.floor(diff / (1000 * 60 * 60 * 24)); |
|||
const diffHours = Math.floor(diff / (1000 * 60 * 60)); |
|||
const diffMinutes = Math.floor(diff / (1000 * 60)); |
|||
|
|||
if (diffMinutes < 60) { |
|||
return `${diffMinutes}分钟前`; |
|||
} else if (diffHours < 24) { |
|||
return `${diffHours}小时前`; |
|||
} else if (diffDays === 1) { |
|||
return '昨天'; |
|||
} else if (diffDays === 2) { |
|||
return '前天'; |
|||
} else if (diffDays < 7) { |
|||
return `${diffDays}天前`; |
|||
} else { |
|||
const month = date.getMonth() + 1; |
|||
const day = date.getDate(); |
|||
return `${month}月${day}日`; |
|||
} |
|||
}, |
|||
|
|||
|
|||
// 查看详情
|
|||
viewDetail: function (e) { |
|||
const data = e.currentTarget.dataset.value |
|||
wx.navigateTo({ |
|||
url: `/pagesA/pages/askingSyDetails/askingSyDetails?data=${encodeURIComponent(JSON.stringify(data))}`, |
|||
}); |
|||
}, |
|||
|
|||
}); |
|||
@ -0,0 +1,3 @@ |
|||
{ |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
<view class="wzbox"> |
|||
<!-- 顶部标题栏 --> |
|||
<view class="header"> |
|||
<view class="header-content"> |
|||
<view class="title-section"> |
|||
<text class="title">远程诊疗</text> |
|||
<text class="subtitle">专业兽医在线解答</text> |
|||
</view> |
|||
</view> |
|||
<view class="header-decoration"> |
|||
<view class="decoration-circle circle-1"></view> |
|||
<view class="decoration-circle circle-2"></view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 问诊记录列表 --> |
|||
<scroll-view class="record-list" scroll-y enable-back-to-top> |
|||
<!-- 空状态 --> |
|||
<view wx:if="{{diagnosisList.length === 0}}" class="empty-state"> |
|||
<image class="empty-icon" src="/pagesA/images/kzt.png" mode="widthFix"></image> |
|||
<text class="empty-text">暂无问诊记录</text> |
|||
<text class="empty-tip">开始您的第一次问诊吧</text> |
|||
</view> |
|||
|
|||
<!-- 问诊记录卡片 --> |
|||
<view wx:else class="records-container"> |
|||
|
|||
|
|||
<view wx:for="{{diagnosisList}}" wx:key="id" class="record-card" bindtap="viewDetail" data-value="{{item}}"> |
|||
<!-- 卡片头部:用户信息 + 状态 --> |
|||
<view class="card-header"> |
|||
<view class="user-section"> |
|||
<image class="user-avatar" src="{{item.userInfo.avatar || '/pages/images/tx.png'}}"></image> |
|||
<text class="user-name">{{item.farmerName || '用户'}}</text> |
|||
</view> |
|||
<view class="status-tag {{item.status === '已回复' ? 'status-replied' : 'status-pending'}}"> |
|||
{{item.status}} |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 牲畜信息 --> |
|||
<view class="livestock-section"> |
|||
<view class="livestock-title-wrapper"> |
|||
<view class="livestock-title-line"></view> |
|||
<view class="livestock-title-content"> |
|||
<text class="livestock-title-text">牲畜信息</text> |
|||
</view> |
|||
<view class="livestock-title-line"></view> |
|||
</view> |
|||
|
|||
<view class="livestock-tags"> |
|||
<view class="livestock-tag type-tag"> |
|||
<text class="tag-text">{{item.animalType}}</text> |
|||
</view> |
|||
<view class="livestock-tag age-tag"> |
|||
<text class="tag-text">{{item.animalAge}}</text> |
|||
</view> |
|||
<view class="livestock-tag gender-tag"> |
|||
<text class="tag-text">{{item.animalGender}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 症状描述 --> |
|||
<view class="symptom-section"> |
|||
<view class="symptom-content"> |
|||
<text class="symptom-text">{{item.description}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 卡片底部 --> |
|||
<view class="card-footer"> |
|||
<view class="footer-left"> |
|||
<view class="time-info"> |
|||
<text class="time-text">{{item.createdTime}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="footer-right"> |
|||
<view class="reply-info"> |
|||
<text class="reply-count">制定方案</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 列表底部提示 --> |
|||
<view wx:if="{{diagnosisList.length > 0}}" class="list-footer"> |
|||
<text class="footer-text">已显示全部记录</text> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
|
|||
</view> |
|||
@ -0,0 +1,428 @@ |
|||
/* 全局样式 */ |
|||
.wzbox{ |
|||
min-height: 100vh; |
|||
position: relative; |
|||
background: linear-gradient(180deg, #F8FBFF 0%, #F0F7FF 100%); |
|||
} |
|||
|
|||
/* 头部样式优化 */ |
|||
.header { |
|||
padding: 20rpx 40rpx 20rpx; |
|||
background: linear-gradient(135deg, #6D9EFF 0%, #4A7CFF 100%); |
|||
border-radius: 0 0 36rpx 36rpx; |
|||
box-shadow: 0 4rpx 20rpx rgba(74, 144, 226, 0.15); |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.header-content { |
|||
position: relative; |
|||
z-index: 2; |
|||
} |
|||
|
|||
.title-section { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.title { |
|||
font-size: 44rpx; |
|||
font-weight: 700; |
|||
color: #FFFFFF; |
|||
letter-spacing: 0.5rpx; |
|||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.subtitle { |
|||
font-size: 28rpx; |
|||
color: rgba(255, 255, 255, 0.9); |
|||
font-weight: 400; |
|||
} |
|||
|
|||
/* 头部装饰元素 */ |
|||
.header-decoration { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
overflow: hidden; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.decoration-circle { |
|||
position: absolute; |
|||
border-radius: 50%; |
|||
background: rgba(255, 255, 255, 0.08); |
|||
} |
|||
|
|||
.circle-1 { |
|||
width: 200rpx; |
|||
height: 200rpx; |
|||
top: -80rpx; |
|||
right: -40rpx; |
|||
} |
|||
|
|||
.circle-2 { |
|||
width: 120rpx; |
|||
height: 120rpx; |
|||
bottom: -40rpx; |
|||
left: -20rpx; |
|||
} |
|||
|
|||
/* 问诊记录列表 */ |
|||
.record-list { |
|||
height: calc(100vh - 200rpx); |
|||
padding: 0 32rpx 20rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.records-container { |
|||
padding-top: 24rpx; |
|||
} |
|||
|
|||
/* 列表头部 */ |
|||
.section-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 24rpx; |
|||
padding: 0 4rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 32rpx; |
|||
font-weight: 700; |
|||
color: #1A1A1A; |
|||
position: relative; |
|||
padding-left: 16rpx; |
|||
} |
|||
|
|||
.section-title::before { |
|||
content: ''; |
|||
position: absolute; |
|||
left: 0; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
width: 4rpx; |
|||
height: 20rpx; |
|||
background: linear-gradient(180deg, #6D9EFF 0%, #4A7CFF 100%); |
|||
border-radius: 2rpx; |
|||
} |
|||
|
|||
.section-count { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
background: #F0F7FF; |
|||
padding: 6rpx 16rpx; |
|||
border-radius: 16rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
/* 问诊记录卡片 */ |
|||
.record-card { |
|||
background: #FFFFFF; |
|||
border-radius: 24rpx; |
|||
margin-bottom: 24rpx; |
|||
box-shadow: 0 6rpx 24rpx rgba(74, 144, 226, 0.08); |
|||
position: relative; |
|||
overflow: hidden; |
|||
transition: all 0.3s cubic-bezier(0.2, 0, 0.2, 1); |
|||
border: 1rpx solid #F0F7FF; |
|||
} |
|||
|
|||
.record-card:active { |
|||
transform: translateY(-2rpx); |
|||
box-shadow: 0 10rpx 30rpx rgba(74, 144, 226, 0.15); |
|||
} |
|||
|
|||
/* 卡片头部 */ |
|||
.card-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 24rpx 28rpx; |
|||
border-bottom: 1rpx solid #F5F9FF; |
|||
} |
|||
|
|||
.user-section { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 16rpx; |
|||
flex: 1; |
|||
} |
|||
|
|||
.user-avatar { |
|||
width: 60rpx; |
|||
height: 60rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid rgba(255, 255, 255, 0.8); |
|||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
|||
} |
|||
|
|||
.user-name { |
|||
font-size: 28rpx; |
|||
color: #1A1A1A; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
/* 状态标签 */ |
|||
.status-tag { |
|||
padding: 8rpx 20rpx; |
|||
border-radius: 20rpx; |
|||
font-size: 22rpx; |
|||
font-weight: 600; |
|||
min-width: 80rpx; |
|||
text-align: center; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.status-replied { |
|||
background: linear-gradient(135deg, rgba(76, 217, 100, 0.12) 0%, rgba(46, 204, 113, 0.12) 100%); |
|||
color: #2ECC71; |
|||
border: 1rpx solid rgba(46, 204, 113, 0.2); |
|||
} |
|||
|
|||
.status-pending { |
|||
background: linear-gradient(135deg, rgba(255, 149, 0, 0.12) 0%, rgba(255, 127, 0, 0.12) 100%); |
|||
color: #FF9500; |
|||
border: 1rpx solid rgba(255, 149, 0, 0.2); |
|||
} |
|||
|
|||
/* 牲畜信息 */ |
|||
.livestock-section { |
|||
padding: 20rpx 28rpx; |
|||
} |
|||
|
|||
/* 牲畜信息标题优化 */ |
|||
.livestock-title-wrapper { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-bottom: 20rpx; |
|||
position: relative; |
|||
} |
|||
|
|||
.livestock-title-line { |
|||
flex: 1; |
|||
height: 2rpx; |
|||
background: linear-gradient(90deg, transparent, #4A7CFF 50%, transparent); |
|||
opacity: 0.3; |
|||
} |
|||
|
|||
.livestock-title-content { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12rpx; |
|||
padding: 0 20rpx; |
|||
position: relative; |
|||
} |
|||
|
|||
.livestock-title-text { |
|||
font-size: 30rpx; |
|||
font-weight: 700; |
|||
color: #333; |
|||
background: linear-gradient(135deg, #4A7CFF 0%, #8CB4FF 100%); |
|||
-webkit-background-clip: text; |
|||
-webkit-text-fill-color: transparent; |
|||
background-clip: text; |
|||
text-shadow: 0 2rpx 4rpx rgba(74, 124, 255, 0.1); |
|||
letter-spacing: 1rpx; |
|||
} |
|||
|
|||
|
|||
@keyframes cowPulse { |
|||
0%, 100% { |
|||
transform: scale(1) rotate(0deg); |
|||
} |
|||
50% { |
|||
transform: scale(1.1) rotate(5deg); |
|||
} |
|||
} |
|||
|
|||
.livestock-tags { |
|||
display: flex; |
|||
gap: 16rpx; |
|||
flex-wrap: wrap; |
|||
} |
|||
|
|||
.livestock-tag { |
|||
padding: 5rpx 20rpx; |
|||
border-radius: 20rpx; |
|||
font-size: 26rpx; |
|||
font-weight: 600; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.type-tag { |
|||
background: linear-gradient(135deg, #6D9EFF 0%, #4A7CFF 100%); |
|||
color: #FFFFFF; |
|||
border: 2rpx solid rgba(255, 255, 255, 0.3); |
|||
} |
|||
|
|||
.age-tag { |
|||
background: linear-gradient(135deg, #FFB347 0%, #FFA033 100%); |
|||
color: #FFFFFF; |
|||
border: 2rpx solid rgba(255, 255, 255, 0.3); |
|||
} |
|||
|
|||
.gender-tag { |
|||
background: linear-gradient(135deg, #42E695 0%, #3BB2B8 100%); |
|||
color: #FFFFFF; |
|||
border: 2rpx solid rgba(255, 255, 255, 0.3); |
|||
} |
|||
|
|||
.tag-text { |
|||
font-size: 26rpx; |
|||
font-weight: 600; |
|||
color: #FFFFFF; |
|||
} |
|||
|
|||
/* 症状描述 */ |
|||
.symptom-section { |
|||
padding: 24rpx 28rpx 28rpx; |
|||
} |
|||
|
|||
.symptom-content { |
|||
background: #F9FAFF; |
|||
padding: 20rpx 24rpx; |
|||
border-radius: 16rpx; |
|||
border: 1rpx solid #E6ECFF; |
|||
position: relative; |
|||
} |
|||
|
|||
.symptom-content::before { |
|||
content: '症状描述'; |
|||
position: absolute; |
|||
top: -12rpx; |
|||
left: 24rpx; |
|||
background: #FFFFFF; |
|||
padding: 0 12rpx; |
|||
font-size: 24rpx; |
|||
font-weight: 600; |
|||
color: #4A7CFF; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.symptom-text { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
line-height: 1.6; |
|||
font-weight: 400; |
|||
} |
|||
|
|||
/* 卡片底部 */ |
|||
.card-footer { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 20rpx 28rpx; |
|||
border-top: 1rpx solid #F5F9FF; |
|||
} |
|||
|
|||
.time-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.time-text { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
font-weight: 400; |
|||
} |
|||
|
|||
.reply-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
padding: 8rpx 16rpx; |
|||
background: #F0F7FF; |
|||
border-radius: 16rpx; |
|||
border: 1rpx solid #E5EFFF; |
|||
} |
|||
|
|||
.reply-count { |
|||
font-size: 24rpx; |
|||
font-weight: 500; |
|||
color: #4A7CFF; |
|||
} |
|||
|
|||
/* 空状态优化 */ |
|||
.empty-state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
padding-top: 120rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.empty-icon { |
|||
width: 400rpx; |
|||
margin-bottom: 32rpx; |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
.empty-text { |
|||
font-size: 32rpx; |
|||
color: #666; |
|||
margin-bottom: 12rpx; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
.empty-tip { |
|||
font-size: 26rpx; |
|||
color: #999; |
|||
margin-bottom: 40rpx; |
|||
line-height: 1.4; |
|||
} |
|||
|
|||
/* 列表底部 */ |
|||
.list-footer { |
|||
text-align: center; |
|||
padding: 40rpx 0; |
|||
color: #999; |
|||
font-size: 24rpx; |
|||
} |
|||
|
|||
.footer-text { |
|||
opacity: 0.6; |
|||
letter-spacing: 1rpx; |
|||
} |
|||
|
|||
|
|||
/* 卡片入场动画 */ |
|||
@keyframes cardSlideIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(30rpx); |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
.record-card { |
|||
animation: cardSlideIn 0.4s ease-out forwards; |
|||
opacity: 0; |
|||
} |
|||
|
|||
.record-card:nth-child(1) { animation-delay: 0.1s; } |
|||
.record-card:nth-child(2) { animation-delay: 0.15s; } |
|||
.record-card:nth-child(3) { animation-delay: 0.2s; } |
|||
.record-card:nth-child(4) { animation-delay: 0.25s; } |
|||
.record-card:nth-child(5) { animation-delay: 0.3s; } |
|||
|
|||
/* 响应式适配 */ |
|||
@media screen and (min-width: 768px) { |
|||
.record-card { |
|||
max-width: 600rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
} |
|||
@ -0,0 +1,191 @@ |
|||
import http from '../../../utils/api'; |
|||
const baseUrl = require('../../../utils/baseUrl'); |
|||
|
|||
Page({ |
|||
data: { |
|||
baseUrl: baseUrl, |
|||
// 表单数据
|
|||
formData: { |
|||
title: '', |
|||
summary: '', |
|||
categoryName: '', |
|||
tags: '', // 改为字符串格式,如 "传染病,疫苗"
|
|||
content: '' |
|||
}, |
|||
// 分类列表 - 直接从接口返回的 rows 赋值
|
|||
categoryList: [], |
|||
categoryIndex: -1, |
|||
// 标签列表 - 直接从接口返回的 rows 赋值
|
|||
tagList: [], |
|||
// 已选中的标签对象数组
|
|||
selectedTags: [], |
|||
// 提交状态
|
|||
submitting: false |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
this.getCategoryList(); |
|||
this.getTagList(); |
|||
}, |
|||
|
|||
// 输入处理
|
|||
onInput(e) { |
|||
const { field } = e.currentTarget.dataset; |
|||
const { value } = e.detail; |
|||
this.setData({ |
|||
[`formData.${field}`]: value |
|||
}); |
|||
}, |
|||
|
|||
// 获取文章分类 - 直接使用 res.rows
|
|||
getCategoryList() { |
|||
http.articleZd({ |
|||
data: { |
|||
dictType: 'vet_experience_category' |
|||
}, |
|||
success: (res) => { |
|||
// 直接使用 res.rows 赋值
|
|||
if (res.rows && Array.isArray(res.rows)) { |
|||
this.setData({ categoryList: res.rows }); |
|||
} else { |
|||
console.error('分类数据格式错误', res); |
|||
wx.showToast({ title: '分类加载失败', icon: 'none' }); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
console.error('分类接口异常', err); |
|||
wx.showToast({ title: '网络错误', icon: 'none' }); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 获取文章标签 - 直接使用 res.rows
|
|||
getTagList() { |
|||
http.videoZd({ |
|||
data: { |
|||
dictType: 'vet_experience_tag' |
|||
}, |
|||
success: (res) => { |
|||
// 直接使用 res.rows 赋值
|
|||
if (res.rows && Array.isArray(res.rows)) { |
|||
this.setData({ tagList: res.rows }); |
|||
} else { |
|||
console.error('标签数据格式错误', res); |
|||
wx.showToast({ title: '标签加载失败', icon: 'none' }); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
console.error('标签接口异常', err); |
|||
wx.showToast({ title: '网络错误', icon: 'none' }); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 分类选择
|
|||
onCategoryChange(e) { |
|||
const index = parseInt(e.detail.value, 10); |
|||
const selectedCategory = this.data.categoryList[index]; |
|||
this.setData({ |
|||
categoryIndex: index, |
|||
[`formData.categoryName`]: selectedCategory.dictLabel |
|||
}); |
|||
}, |
|||
|
|||
// 标签点击切换
|
|||
toggleTag(e) { |
|||
const tagItem = e.currentTarget.dataset.item; |
|||
let selectedTags = [...this.data.selectedTags]; |
|||
const index = selectedTags.findIndex(t => t.dictValue === tagItem.dictValue); |
|||
|
|||
if (index > -1) { |
|||
selectedTags.splice(index, 1); |
|||
} else { |
|||
selectedTags.push(tagItem); |
|||
} |
|||
|
|||
// 生成逗号分隔的标签字符串,如 "传染病,疫苗"
|
|||
const tagString = selectedTags.map(item => item.dictLabel).join(','); |
|||
|
|||
this.setData({ |
|||
selectedTags: selectedTags, |
|||
[`formData.tags`]: tagString |
|||
}); |
|||
}, |
|||
|
|||
// 表单提交
|
|||
formSubmit(e) { |
|||
const { title, categoryName, content } = this.data.formData; |
|||
|
|||
// 表单验证
|
|||
if (!title || title.trim() === '') { |
|||
this.showError('请填写文章标题'); |
|||
return; |
|||
} |
|||
if (!categoryName) { |
|||
this.showError('请选择文章分类'); |
|||
return; |
|||
} |
|||
if (this.data.selectedTags.length === 0) { |
|||
this.showError('请至少选择一个标签'); |
|||
return; |
|||
} |
|||
if (!content || content.trim() === '') { |
|||
this.showError('请填写文章内容'); |
|||
return; |
|||
} |
|||
|
|||
this.setData({ submitting: true }); |
|||
|
|||
// 构建提交数据 - tags 已经是字符串格式
|
|||
const postData = { |
|||
title: this.data.formData.title, |
|||
summary: this.data.formData.summary || '', |
|||
categoryName: this.data.formData.categoryName, |
|||
tags: this.data.formData.tags, // 已经是 "传染病,疫苗" 格式
|
|||
content: this.data.formData.content |
|||
}; |
|||
|
|||
console.log('提交数据:', postData); // 调试用
|
|||
|
|||
http.shareAdd({ |
|||
data: postData, |
|||
success: (res) => { |
|||
if(res.code == 200){ |
|||
wx.showToast({ |
|||
title: '发布成功', |
|||
icon: 'success', |
|||
duration: 2000, |
|||
success: () => { |
|||
setTimeout(() => { |
|||
wx.navigateBack(); |
|||
}, 1500); |
|||
} |
|||
}) |
|||
}else{ |
|||
console.error('发布失败', err); |
|||
this.showError('发布失败,请重试'); |
|||
this.setData({ submitting: false }); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
console.error('发布失败', err); |
|||
this.showError('发布失败,请重试'); |
|||
this.setData({ submitting: false }); |
|||
}, |
|||
complete: () => { |
|||
setTimeout(() => { |
|||
this.setData({ submitting: false }); |
|||
}, 3000); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 错误提示
|
|||
showError(msg) { |
|||
wx.showToast({ |
|||
title: msg, |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
} |
|||
}); |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText":"发布文章", |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
<view class="page-wrapper"> |
|||
<form catchsubmit="formSubmit"> |
|||
<!-- 标题卡片--> |
|||
<view class="form-card"> |
|||
<view class="card-header"> |
|||
<text class="header-icon">📝</text> |
|||
<text class="header-title">文章标题</text> |
|||
<text class="required-badge">必填</text> |
|||
</view> |
|||
<view class="card-content no-padding"> |
|||
<view class="input-wrapper"> |
|||
<input type="text" placeholder="请输入吸引人的标题(最多50字)" |
|||
name="title" maxlength="50" value="{{formData.title}}" |
|||
bindinput="onInput" data-field="title" |
|||
placeholder-class="placeholder-style" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 摘要卡片 --> |
|||
<view class="form-card"> |
|||
<view class="card-header"> |
|||
<text class="header-icon">📋</text> |
|||
<text class="header-title">文章摘要</text> |
|||
<text class="optional-badge">选填</text> |
|||
</view> |
|||
<view class="card-content no-padding"> |
|||
<view class="textarea-wrapper"> |
|||
<textarea placeholder="简单描述文章要点,让读者快速了解内容(最多200字)" |
|||
name="summary" maxlength="200" auto-height |
|||
value="{{formData.summary}}" bindinput="onInput" |
|||
data-field="summary" placeholder-class="placeholder-style" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 分类卡片 --> |
|||
<view class="form-card"> |
|||
<view class="card-header"> |
|||
<text class="header-icon">📂</text> |
|||
<text class="header-title">文章分类</text> |
|||
<text class="required-badge">必填</text> |
|||
</view> |
|||
<view class="card-content"> |
|||
<picker mode="selector" range="{{categoryList}}" range-key="dictLabel" |
|||
bindchange="onCategoryChange" value="{{categoryIndex}}"> |
|||
<view class="picker-trigger {{categoryIndex > -1 ? 'selected' : ''}}"> |
|||
<text>{{categoryIndex > -1 ? categoryList[categoryIndex].dictLabel : '请选择文章分类'}}</text> |
|||
<text class="picker-arrow">▼</text> |
|||
</view> |
|||
</picker> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 标签卡片 --> |
|||
<view class="form-card"> |
|||
<view class="card-header"> |
|||
<text class="header-icon">🏷️</text> |
|||
<text class="header-title">文章标签</text> |
|||
<text class="required-badge">必填</text> |
|||
</view> |
|||
<view class="card-content"> |
|||
<view class="tag-group"> |
|||
<block wx:for="{{tagList}}" wx:key="dictValue"> |
|||
<view class="tag-item {{selectedTags.includes(item) ? 'active' : ''}}" |
|||
bindtap="toggleTag" data-item="{{item}}"> |
|||
{{item.dictLabel}} |
|||
</view> |
|||
</block> |
|||
</view> |
|||
<view class="selected-tags" wx:if="{{selectedTags.length}}"> |
|||
<text class="selected-label">已选标签:</text> |
|||
<view class="selected-tag-list"> |
|||
<view class="selected-tag-item" wx:for="{{selectedTags}}" wx:key="index"> |
|||
{{item.dictLabel}} |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 内容卡片 --> |
|||
<view class="form-card content-card"> |
|||
<view class="card-header"> |
|||
<text class="header-icon">📄</text> |
|||
<text class="header-title">文章内容</text> |
|||
<text class="required-badge">必填</text> |
|||
</view> |
|||
<view class="card-content no-padding"> |
|||
<view class="textarea-wrapper"> |
|||
<textarea placeholder="开始撰写您的文章吧...(支持最多5000字)" |
|||
name="content" maxlength="5000" auto-height |
|||
value="{{formData.content}}" bindinput="onInput" |
|||
data-field="content" placeholder-class="placeholder-style" |
|||
class="content-textarea" /> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 提交按钮区域 --> |
|||
<view class="submit-area"> |
|||
<button form-type="submit" class="submit-btn" loading="{{submitting}}" disabled="{{submitting}}"> |
|||
<text wx:if="{{!submitting}}">✨ 发布文章</text> |
|||
<text wx:else>发布中...</text> |
|||
</button> |
|||
<view class="tip-text">好的内容值得被更多人看见</view> |
|||
</view> |
|||
</form> |
|||
</view> |
|||
@ -0,0 +1,278 @@ |
|||
.page-wrapper { |
|||
min-height: 100vh; |
|||
background: linear-gradient(135deg, #f5f7fa 0%, #e9edf5 100%); |
|||
padding: 30rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 卡片样式 */ |
|||
.form-card { |
|||
background: rgba(255, 255, 255, 0.95); |
|||
backdrop-filter: blur(10px); |
|||
border-radius: 32rpx; |
|||
margin-bottom: 30rpx; |
|||
box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.05), 0 4rpx 12rpx rgba(0, 0, 0, 0.03); |
|||
overflow: hidden; |
|||
border: 2rpx solid rgba(255, 255, 255, 0.8); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
/* 卡片头部 */ |
|||
.card-header { |
|||
padding: 28rpx 32rpx; |
|||
background: linear-gradient(90deg, #ffffff, #fafcff); |
|||
border-bottom: 2rpx solid rgba(7, 193, 96, 0.1); |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 12rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.header-icon { |
|||
font-size: 36rpx; |
|||
line-height: 1; |
|||
filter: drop-shadow(0 4rpx 6rpx rgba(7, 193, 96, 0.2)); |
|||
} |
|||
|
|||
.header-title { |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
color: #1a2b3c; |
|||
letter-spacing: 1rpx; |
|||
} |
|||
|
|||
/* 徽章样式 */ |
|||
.required-badge { |
|||
background: linear-gradient(135deg, #ff6b6b, #ff4757); |
|||
color: white; |
|||
font-size: 22rpx; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 30rpx; |
|||
margin-left: 16rpx; |
|||
box-shadow: 0 4rpx 10rpx rgba(255, 71, 87, 0.3); |
|||
} |
|||
|
|||
.optional-badge { |
|||
background: linear-gradient(135deg, #a0a0a0, #808080); |
|||
color: white; |
|||
font-size: 22rpx; |
|||
padding: 4rpx 16rpx; |
|||
border-radius: 30rpx; |
|||
margin-left: 16rpx; |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
/* 卡片内容 */ |
|||
.card-content { |
|||
padding: 0 32rpx 32rpx 32rpx; |
|||
} |
|||
|
|||
.card-content.no-padding { |
|||
padding: 0; |
|||
} |
|||
|
|||
/* 输入框包装器 */ |
|||
.input-wrapper, |
|||
.textarea-wrapper { |
|||
background: #f8fafd; |
|||
border-radius: 24rpx; |
|||
margin: 0 32rpx 32rpx 32rpx; |
|||
border: 2rpx solid transparent; |
|||
transition: all 0.3s ease; |
|||
box-shadow: inset 0 2rpx 6rpx rgba(0, 0, 0, 0.02); |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.input-wrapper:focus-within, |
|||
.textarea-wrapper:focus-within { |
|||
border-color: #07c160; |
|||
background: #ffffff; |
|||
box-shadow: 0 0 0 6rpx rgba(7, 193, 96, 0.1), inset 0 2rpx 6rpx rgba(0, 0, 0, 0.02); |
|||
} |
|||
|
|||
/* 输入框样式 */ |
|||
input, textarea { |
|||
width: 100%; |
|||
font-size: 30rpx; |
|||
color: #1e293b; |
|||
padding: 24rpx 28rpx; |
|||
background: transparent; |
|||
border: none; |
|||
outline: none; |
|||
} |
|||
|
|||
textarea { |
|||
min-height: 120rpx; |
|||
line-height: 1.6; |
|||
} |
|||
|
|||
.placeholder-style { |
|||
color: #aab8c5; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
/* 分类选择器 */ |
|||
.picker-trigger { |
|||
background: #f8fafd; |
|||
padding: 24rpx 28rpx; |
|||
border-radius: 24rpx; |
|||
font-size: 30rpx; |
|||
color: #aab8c5; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
border: 2rpx solid transparent; |
|||
transition: all 0.3s ease; |
|||
box-shadow: inset 0 2rpx 6rpx rgba(0, 0, 0, 0.02); |
|||
} |
|||
|
|||
.picker-trigger.selected { |
|||
color: #1e293b; |
|||
border-color: #07c160; |
|||
background: #ffffff; |
|||
} |
|||
|
|||
.picker-arrow { |
|||
font-size: 28rpx; |
|||
color: #94a3b8; |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
/* 标签组 */ |
|||
.tag-group { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
gap: 20rpx; |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.tag-item { |
|||
padding: 16rpx 36rpx; |
|||
background: #f1f5f9; |
|||
border-radius: 60rpx; |
|||
font-size: 28rpx; |
|||
color: #475569; |
|||
border: 2rpx solid #e2e8f0; |
|||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|||
box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.02); |
|||
} |
|||
|
|||
.tag-item.active { |
|||
background: linear-gradient(135deg, #07c160, #059669); |
|||
color: white; |
|||
border-color: #07c160; |
|||
transform: translateY(-2rpx); |
|||
box-shadow: 0 10rpx 20rpx rgba(7, 193, 96, 0.3); |
|||
} |
|||
|
|||
/* 已选标签区域 */ |
|||
.selected-tags { |
|||
margin-top: 24rpx; |
|||
padding: 24rpx; |
|||
background: linear-gradient(135deg, #f0fdf4, #dcfce7); |
|||
border-radius: 24rpx; |
|||
border: 2rpx solid rgba(7, 193, 96, 0.2); |
|||
} |
|||
|
|||
.selected-label { |
|||
font-size: 26rpx; |
|||
color: #059669; |
|||
font-weight: 500; |
|||
display: block; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.selected-tag-list { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
gap: 16rpx; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.selected-tag-item { |
|||
padding: 10rpx 28rpx; |
|||
background: white; |
|||
border-radius: 40rpx; |
|||
font-size: 26rpx; |
|||
color: #07c160; |
|||
border: 2rpx solid #07c160; |
|||
box-shadow: 0 4rpx 10rpx rgba(7, 193, 96, 0.1); |
|||
} |
|||
|
|||
/* 标签字符串预览 */ |
|||
.tag-string-preview { |
|||
font-size: 26rpx; |
|||
color: #475569; |
|||
padding: 16rpx; |
|||
background: rgba(255, 255, 255, 0.7); |
|||
border-radius: 16rpx; |
|||
border: 2rpx dashed #07c160; |
|||
} |
|||
|
|||
.tag-string-value { |
|||
color: #07c160; |
|||
font-weight: 500; |
|||
word-break: break-all; |
|||
} |
|||
|
|||
/* 内容区域特殊样式 */ |
|||
.content-card .content-textarea { |
|||
min-height: 300rpx; |
|||
} |
|||
|
|||
/* 提交区域 */ |
|||
.submit-area { |
|||
margin-top: 60rpx; |
|||
padding-bottom: 40rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.submit-btn { |
|||
width: 100%; |
|||
height: 96rpx; |
|||
background: linear-gradient(135deg, #07c160, #059669); |
|||
border-radius: 60rpx; |
|||
color: white; |
|||
font-size: 36rpx; |
|||
font-weight: 600; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
border: none; |
|||
box-shadow: 0 20rpx 40rpx rgba(7, 193, 96, 0.3); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.submit-btn:active { |
|||
transform: translateY(-4rpx); |
|||
box-shadow: 0 30rpx 50rpx rgba(7, 193, 96, 0.4); |
|||
} |
|||
|
|||
.submit-btn[disabled] { |
|||
opacity: 0.7; |
|||
transform: none; |
|||
box-shadow: 0 10rpx 20rpx rgba(7, 193, 96, 0.2); |
|||
} |
|||
|
|||
.tip-text { |
|||
margin-top: 24rpx; |
|||
font-size: 26rpx; |
|||
color: #94a3b8; |
|||
letter-spacing: 2rpx; |
|||
} |
|||
|
|||
/* 动画效果 */ |
|||
@keyframes fadeIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(20rpx); |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
.form-card { |
|||
animation: fadeIn 0.4s ease-out forwards; |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
import http from '../../../utils/api' |
|||
const baseUrl = require('../../../utils/baseUrl') |
|||
Page({ |
|||
data: { |
|||
id: '', // 文章ID
|
|||
baseUrl:baseUrl, |
|||
detail: {}, // 文章详情数据
|
|||
tags:[] |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
this.getexperienceDetails(options) |
|||
}, |
|||
|
|||
// 经验分享详情
|
|||
getexperienceDetails(options){ |
|||
http.experienceDetails({ |
|||
data:{ |
|||
id:options.id |
|||
}, |
|||
success:res=>{ |
|||
console.log(1111,res); |
|||
const tag = res.data.tags.split(',') |
|||
var ch ='<img src="/dev-api' |
|||
const mmg = res.data.content.replace(new RegExp(ch, 'g'), '<img src="' + baseUrl) |
|||
const images = mmg.replace(/\<img/g, '<img style="width:100%;display:block; border-radius:3px;"'); |
|||
this.setData({ |
|||
detail:res.data, |
|||
content:images, |
|||
tags:tag |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
|
|||
}) |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText": "分享详情", |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
<view class="page-container"> |
|||
|
|||
<!-- 内容区域 --> |
|||
<scroll-view class="content-scroll" scroll-y="true"> |
|||
<!-- 文章头部 --> |
|||
<view class="article-header"> |
|||
<!-- 分类标签 --> |
|||
<view class="category-tag"> |
|||
<text class="category-text">{{detail.categoryName}}</text> |
|||
</view> |
|||
|
|||
<!-- 标题 --> |
|||
<view class="article-title">{{detail.title}}</view> |
|||
|
|||
<!-- 作者信息和发布时间 --> |
|||
<view class="article-meta"> |
|||
<view class="author-info"> |
|||
<image src="{{baseUrl+detail.vetAvatar}}" class="author-avatar"></image> |
|||
<view class="author-details"> |
|||
<text class="author-name">{{detail.vetName}}</text> |
|||
<text class="publish-time">{{detail.publishTime}}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 浏览量 --> |
|||
<view class="view-info"> |
|||
<image src="/pagesB/images/lll.png" class="view-icon"></image> |
|||
<text class="view-count">{{detail.viewCount}}次浏览</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 分类 --> |
|||
<view class="fl"> |
|||
<view wx:for="{{tags}}">{{item}}</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<!-- 文章内容 --> |
|||
<view class="article-content"> |
|||
<!-- 摘要 --> |
|||
<view wx:if="{{detail.summary}}" class="article-summary"> |
|||
{{detail.summary}} |
|||
</view> |
|||
|
|||
<!-- 正文内容 --> |
|||
<view class="article-body"> |
|||
<rich-text class="content-text" space="emsp" nodes="{{content}}"></rich-text> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
@ -0,0 +1,158 @@ |
|||
.page-container { |
|||
background-color: #f5f7fa; |
|||
min-height: 100vh; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 内容区域 */ |
|||
.content-scroll { |
|||
height: 100vh; |
|||
padding-top: calc(88rpx + var(--status-bar-height)); |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 文章头部 */ |
|||
.article-header { |
|||
background-color: white; |
|||
padding: 40rpx 32rpx 32rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.category-tag { |
|||
display: inline-block; |
|||
background-color: #E8F3FF; |
|||
padding: 8rpx 20rpx; |
|||
border-radius: 24rpx; |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.category-text { |
|||
font-size: 24rpx; |
|||
color: #4A90E2; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.article-title { |
|||
font-size: 40rpx; |
|||
color: #1a1a1a; |
|||
font-weight: 700; |
|||
line-height: 1.4; |
|||
margin-bottom: 32rpx; |
|||
} |
|||
|
|||
.article-meta { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.author-info { |
|||
display: flex; |
|||
align-items: center; |
|||
flex: 1; |
|||
} |
|||
|
|||
.author-avatar { |
|||
width: 64rpx; |
|||
height: 64rpx; |
|||
border-radius: 50%; |
|||
margin-right: 16rpx; |
|||
border: 2rpx solid #f0f0f0; |
|||
} |
|||
|
|||
.author-details { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.author-name { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
font-weight: 500; |
|||
margin-bottom: 4rpx; |
|||
} |
|||
|
|||
.publish-time { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.view-info { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.view-icon { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
margin-right: 8rpx; |
|||
opacity: 0.7; |
|||
} |
|||
|
|||
.view-count { |
|||
font-size: 26rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.fl{ |
|||
margin-top: 30rpx; |
|||
display: flex; |
|||
gap: 20rpx; |
|||
} |
|||
.fl view{ |
|||
background-color: #F5F7FA; |
|||
font-size: 24rpx; |
|||
padding: 10rpx 20rpx; |
|||
border-radius: 10rpx; |
|||
} |
|||
|
|||
/* 文章内容 */ |
|||
.article-content { |
|||
background-color: white; |
|||
padding: 32rpx; |
|||
} |
|||
|
|||
.article-summary { |
|||
font-size: 30rpx; |
|||
color: #666; |
|||
line-height: 1.6; |
|||
margin-bottom: 32rpx; |
|||
padding: 24rpx; |
|||
background-color: #f9f9f9; |
|||
border-radius: 12rpx; |
|||
border-left: 4rpx solid #4A90E2; |
|||
} |
|||
|
|||
.article-body { |
|||
font-size: 30rpx; |
|||
line-height: 1.8; |
|||
color: #333; |
|||
} |
|||
|
|||
.content-text { |
|||
font-size: 30rpx; |
|||
line-height: 1.8; |
|||
color: #333; |
|||
white-space: pre-line; |
|||
} |
|||
|
|||
.content-text p { |
|||
margin-bottom: 32rpx; |
|||
} |
|||
|
|||
|
|||
/* 响应式调整 */ |
|||
@media (max-width: 375px) { |
|||
.article-header, |
|||
.article-content { |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.article-title { |
|||
font-size: 36rpx; |
|||
} |
|||
|
|||
.content-text { |
|||
font-size: 28rpx; |
|||
} |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
import http from '../../../utils/api' |
|||
const baseUrl = require('../../../utils/baseUrl') |
|||
|
|||
Page({ |
|||
data: { |
|||
// 搜索文本
|
|||
searchText: '', |
|||
baseUrl: baseUrl, |
|||
// 当前选中的分类
|
|||
activeCategory: '', |
|||
// 分类数据
|
|||
categories: [], |
|||
// 经验分享列表
|
|||
experienceList: [], |
|||
// 分页参数
|
|||
pageNum: 1, |
|||
pageSize: 10, |
|||
total: 0, |
|||
// 加载状态
|
|||
loading: false, |
|||
hasMore: true, |
|||
// 搜索防抖定时器
|
|||
searchTimer: null |
|||
}, |
|||
|
|||
onLoad() { |
|||
// 获取分类数据
|
|||
this.getCategories() |
|||
}, |
|||
onShow(){ |
|||
// 获取经验分享列表
|
|||
this.getExperienceList(true) |
|||
}, |
|||
|
|||
// 获取经验分享列表
|
|||
getExperienceList(isRefresh = false) { |
|||
if (this.data.loading) return |
|||
|
|||
const params = { |
|||
pageNum: isRefresh ? 1 : this.data.pageNum, |
|||
pageSize: this.data.pageSize |
|||
} |
|||
|
|||
// 添加搜索关键词
|
|||
if (this.data.searchText) { |
|||
params.searchKey = this.data.searchText.trim() |
|||
} |
|||
|
|||
// 添加分类筛选
|
|||
if (this.data.activeCategory) { |
|||
params.categoryName = this.data.activeCategory |
|||
} |
|||
|
|||
this.setData({ loading: true }) |
|||
|
|||
http.experience({ |
|||
data: params, |
|||
success: res => { |
|||
console.log('经验列表数据:', res) |
|||
|
|||
if (res.code === 200) { |
|||
const newList = isRefresh ? res.rows : [...this.data.experienceList, ...res.rows] |
|||
const total = res.total || 0 |
|||
const hasMore = newList.length < total |
|||
|
|||
this.setData({ |
|||
experienceList: newList, |
|||
total: total, |
|||
hasMore: hasMore, |
|||
pageNum: isRefresh ? 2 : this.data.pageNum + 1, |
|||
loading: false |
|||
}) |
|||
} else { |
|||
wx.showToast({ |
|||
title: res.msg || '加载失败', |
|||
icon: 'none' |
|||
}) |
|||
this.setData({ loading: false }) |
|||
} |
|||
}, |
|||
fail: err => { |
|||
console.error('请求失败:', err) |
|||
wx.showToast({ |
|||
title: '网络错误,请重试', |
|||
icon: 'none' |
|||
}) |
|||
this.setData({ loading: false }) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 获取分类数据
|
|||
getCategories() { |
|||
http.experiencezd({ |
|||
data: { |
|||
categoryName: 'category_id' |
|||
}, |
|||
success: res => { |
|||
console.log('分类数据:', res) |
|||
if (res.code === 200) { |
|||
this.setData({ |
|||
categories: res.data || [] |
|||
}) |
|||
} |
|||
}, |
|||
fail: err => { |
|||
console.error('获取分类失败:', err) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 处理搜索输入(带防抖)
|
|||
onSearchInput(e) { |
|||
const searchText = e.detail.value |
|||
|
|||
this.setData({ |
|||
searchText: searchText |
|||
}) |
|||
|
|||
// 清除之前的定时器
|
|||
if (this.data.searchTimer) { |
|||
clearTimeout(this.data.searchTimer) |
|||
} |
|||
|
|||
// 设置新的定时器,500ms后执行搜索
|
|||
const timer = setTimeout(() => { |
|||
this.handleSearch() |
|||
}, 500) |
|||
|
|||
this.setData({ |
|||
searchTimer: timer |
|||
}) |
|||
}, |
|||
|
|||
// 搜索确认
|
|||
onSearchConfirm() { |
|||
if (this.data.searchTimer) { |
|||
clearTimeout(this.data.searchTimer) |
|||
} |
|||
this.handleSearch() |
|||
}, |
|||
|
|||
// 执行搜索
|
|||
handleSearch() { |
|||
this.setData({ |
|||
pageNum: 1, |
|||
hasMore: true |
|||
}) |
|||
this.getExperienceList(true) |
|||
}, |
|||
|
|||
// 清空搜索
|
|||
clearSearch() { |
|||
this.setData({ |
|||
searchText: '', |
|||
pageNum: 1, |
|||
hasMore: true |
|||
}) |
|||
this.getExperienceList(true) |
|||
}, |
|||
|
|||
// 分类点击事件
|
|||
onCategoryTap(e) { |
|||
const categoryId = e.currentTarget.dataset.id |
|||
|
|||
if (this.data.activeCategory === categoryId) { |
|||
return |
|||
} |
|||
|
|||
this.setData({ |
|||
activeCategory: categoryId, |
|||
pageNum: 1, |
|||
hasMore: true |
|||
}) |
|||
|
|||
this.getExperienceList(true) |
|||
}, |
|||
|
|||
|
|||
//发布文章
|
|||
bindPublish(){ |
|||
wx.navigateTo({ |
|||
url: '/pagesA/pages/releaseSuffer/releaseSuffer', |
|||
}) |
|||
}, |
|||
|
|||
|
|||
// 滚动到底部加载更多
|
|||
onScrollToLower() { |
|||
if (!this.data.loading && this.data.hasMore) { |
|||
this.getExperienceList() |
|||
} |
|||
}, |
|||
|
|||
// 点击加载更多
|
|||
loadMoreData() { |
|||
if (!this.data.loading && this.data.hasMore) { |
|||
this.getExperienceList() |
|||
} |
|||
}, |
|||
|
|||
// 经验分享点击事件
|
|||
onExperienceTap(e) { |
|||
const id = e.currentTarget.dataset.id |
|||
|
|||
wx.navigateTo({ |
|||
url: `/pagesB/pages/experienceDetails/experienceDetails?id=${id}`, |
|||
}) |
|||
}, |
|||
|
|||
onUnload() { |
|||
// 清理定时器
|
|||
if (this.data.searchTimer) { |
|||
clearTimeout(this.data.searchTimer) |
|||
} |
|||
} |
|||
}) |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText": "经验分享", |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,89 @@ |
|||
<view class="page-container"> |
|||
<!-- 搜索栏 --> |
|||
<view class="search-container"> |
|||
<view class="search-box"> |
|||
<image src="/pagesB/images/sou.png" class="search-icon"></image> |
|||
<input placeholder="搜索经验分享..." placeholder-class="placeholder" bindinput="onSearchInput" bindconfirm="onSearchConfirm" value="{{searchText}}" class="search-input" /> |
|||
<view wx:if="{{searchText}}" bindtap="clearSearch" class="clear-btn"> |
|||
<image src="/pagesA/images/ch.png" class="clear-icon"></image> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 分类标签 --> |
|||
<scroll-view class="category-container" scroll-x="true"> |
|||
<view class="category-list"> |
|||
<view class="category-item {{activeCategory === '' ? 'active' : ''}}" bindtap="onCategoryTap" data-id=""> |
|||
<text class="category-text">全部</text> |
|||
</view> |
|||
<view wx:for="{{categories}}" wx:key="id" class="category-item {{activeCategory === item.label ? 'active' : ''}}" bindtap="onCategoryTap" data-id="{{item.label}}"> |
|||
<text class="category-text">{{item.label}}</text> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
|
|||
<!-- 经验分享列表 --> |
|||
<scroll-view class="experience-scroll" scroll-y="true" bindscrolltolower="onScrollToLower" style="height: 100vh;"> |
|||
<view class="experience-list"> |
|||
<view wx:for="{{experienceList}}" wx:key="id" class="experience-item" bindtap="onExperienceTap" data-id="{{item.id}}"> |
|||
<!-- 分类标签 --> |
|||
<view class="tag"> |
|||
<view class="item-category-tag"> |
|||
<text class="tag-text">{{item.categoryName}}</text> |
|||
</view> |
|||
<!-- 发布时间 --> |
|||
<text class="publish-time">{{item.publishTime}}</text> |
|||
</view> |
|||
|
|||
<!-- 标题 --> |
|||
<view class="item-title">{{item.title}}</view> |
|||
|
|||
<!-- 摘要 --> |
|||
<view class="item-summary">{{item.summary}}</view> |
|||
|
|||
<!-- 底部信息 --> |
|||
<view class="item-footer"> |
|||
<!-- 作者信息 --> |
|||
<view class="author-info"> |
|||
<image src="{{baseUrl+item.vetAvatar}}" class="author-avatar"></image> |
|||
<text class="author-name">{{item.vetName}}</text> |
|||
</view> |
|||
|
|||
<!-- 浏览量 --> |
|||
<view class="view-info"> |
|||
<image src="/pagesB/images/lll.png" class="view-icon"></image> |
|||
<text class="view-count">{{item.viewCount}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
<!-- 新增气泡按钮 --> |
|||
<view class="create-btn-container" bind:tap="bindPublish"> |
|||
<view class="create-btn" bindtap="showCreateModal"> |
|||
<image class="btn-icon" src="/pagesA/images/jh.png"></image> |
|||
<text class="btn-text">发布</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 加载更多 --> |
|||
<view wx:if="{{!loading && hasMore}}" class="load-more" bindtap="loadMoreData"> |
|||
<text class="load-more-text">点击加载更多</text> |
|||
</view> |
|||
|
|||
<view wx:if="{{loading}}" class="loading"> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
|
|||
<view wx:if="{{!hasMore && experienceList.length > 0}}" class="no-more"> |
|||
<text class="no-more-text">没有更多了</text> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view wx:if="{{experienceList.length === 0 && !loading}}" class="empty-state"> |
|||
<text class="empty-text">暂无相关经验分享</text> |
|||
<text class="empty-hint">{{searchText || activeCategory ? '换个关键词试试吧' : '快去分享你的经验吧'}}</text> |
|||
</view> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
@ -0,0 +1,390 @@ |
|||
.page-container { |
|||
background-color: #f5f7fa; |
|||
min-height: 100vh; |
|||
} |
|||
|
|||
/* 搜索区域 */ |
|||
.search-container { |
|||
background: linear-gradient(135deg, #4A90E2 0%, #6AC5F8 100%); |
|||
padding: 20rpx 32rpx 32rpx; |
|||
} |
|||
|
|||
.search-box { |
|||
background-color: white; |
|||
border-radius: 50rpx; |
|||
padding: 20rpx 32rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
box-shadow: 0 4rpx 16rpx rgba(74, 144, 226, 0.2); |
|||
} |
|||
|
|||
.search-icon { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
margin-right: 16rpx; |
|||
} |
|||
|
|||
.search-input { |
|||
width: 95%; |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
} |
|||
|
|||
.placeholder { |
|||
color: #999; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.clear-btn { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.clear-icon { |
|||
width: 30rpx; |
|||
height: 30rpx; |
|||
filter: brightness(0) contrast(100%); |
|||
} |
|||
|
|||
/* 分类区域 */ |
|||
.category-container { |
|||
background-color: white; |
|||
padding: 20rpx 32rpx; |
|||
white-space: nowrap; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.category-list { |
|||
display: inline-flex; |
|||
} |
|||
|
|||
.category-item { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
padding: 12rpx 28rpx; |
|||
margin-right: 20rpx; |
|||
border-radius: 40rpx; |
|||
background-color: #f5f7fa; |
|||
border: 2rpx solid #f5f7fa; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.category-item.active { |
|||
background-color: #E8F3FF; |
|||
border-color: #4A90E2; |
|||
} |
|||
|
|||
.category-text { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
font-weight: 400; |
|||
} |
|||
|
|||
.category-item.active .category-text { |
|||
color: #4A90E2; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
|
|||
|
|||
.category-item.active .category-count { |
|||
background-color: rgba(74, 144, 226, 0.1); |
|||
color: #4A90E2; |
|||
} |
|||
|
|||
/* 经验列表 */ |
|||
.experience-list { |
|||
padding: 32rpx; |
|||
} |
|||
|
|||
.experience-item { |
|||
background-color: white; |
|||
border-radius: 20rpx; |
|||
padding: 32rpx; |
|||
margin-bottom: 24rpx; |
|||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04); |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.experience-item:active { |
|||
transform: translateY(-2rpx); |
|||
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.08); |
|||
} |
|||
|
|||
.tag{ |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding-bottom: 20rpx; |
|||
} |
|||
|
|||
.item-category-tag { |
|||
display: inline-block; |
|||
background-color: #E8F3FF; |
|||
padding: 6rpx 16rpx; |
|||
border-radius: 20rpx; |
|||
} |
|||
|
|||
.tag-text { |
|||
font-size: 22rpx; |
|||
color: #4A90E2; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.item-title { |
|||
font-size: 34rpx; |
|||
color: #1a1a1a; |
|||
font-weight: 600; |
|||
line-height: 1.4; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.item-summary { |
|||
font-size: 28rpx; |
|||
color: #666; |
|||
line-height: 1.5; |
|||
margin-bottom: 24rpx; |
|||
display: -webkit-box; |
|||
-webkit-box-orient: vertical; |
|||
-webkit-line-clamp: 2; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.item-footer { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding-top: 20rpx; |
|||
border-top: 1rpx solid #f0f0f0; |
|||
} |
|||
|
|||
.author-info { |
|||
display: flex; |
|||
align-items: center; |
|||
flex: 1; |
|||
} |
|||
|
|||
.author-avatar { |
|||
width: 48rpx; |
|||
height: 48rpx; |
|||
border-radius: 50%; |
|||
margin-right: 12rpx; |
|||
} |
|||
|
|||
.author-name { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.view-info { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-right: 32rpx; |
|||
} |
|||
|
|||
.view-icon { |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
margin-right: 8rpx; |
|||
opacity: 0.6; |
|||
} |
|||
|
|||
.view-count { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.publish-time { |
|||
font-size: 24rpx; |
|||
color: #ccc; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
|
|||
/* 新增视频气泡按钮 */ |
|||
.create-btn-container { |
|||
position: fixed; |
|||
bottom: 40rpx; |
|||
right: 15rpx; |
|||
z-index: 100; |
|||
} |
|||
|
|||
.create-btn { |
|||
width: 120rpx; |
|||
height: 120rpx; |
|||
background: linear-gradient(135deg, #6D9EFF 0%, #4A7CFF 100%); |
|||
border-radius: 50%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-shadow: 0 8rpx 32rpx rgba(74, 144, 226, 0.3); |
|||
transition: all 0.3s cubic-bezier(0.2, 0, 0.2, 1); |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.create-btn::after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(255, 255, 255, 0.1); |
|||
opacity: 0; |
|||
transition: opacity 0.2s ease; |
|||
} |
|||
|
|||
.create-btn:active { |
|||
transform: scale(0.95); |
|||
box-shadow: 0 4rpx 20rpx rgba(74, 144, 226, 0.4); |
|||
} |
|||
|
|||
.create-btn:active::after { |
|||
opacity: 1; |
|||
} |
|||
|
|||
.btn-icon { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
.btn-text { |
|||
font-size: 26rpx; |
|||
color: white; |
|||
font-weight: 600; |
|||
letter-spacing: 0.5rpx; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/* 空状态 */ |
|||
.empty-state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 100rpx 0; |
|||
} |
|||
|
|||
|
|||
.empty-text { |
|||
font-size: 32rpx; |
|||
color: #999; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.empty-hint { |
|||
font-size: 26rpx; |
|||
color: #ccc; |
|||
} |
|||
|
|||
|
|||
.experience-scroll { |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 加载更多样式 */ |
|||
.load-more { |
|||
text-align: center; |
|||
padding: 40rpx 0; |
|||
color: #4A90E2; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.load-more-text { |
|||
padding: 16rpx 32rpx; |
|||
background-color: #E8F3FF; |
|||
border-radius: 24rpx; |
|||
display: inline-block; |
|||
} |
|||
|
|||
.loading { |
|||
text-align: center; |
|||
padding: 40rpx 0; |
|||
color: #999; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.loading-text { |
|||
display: inline-block; |
|||
padding: 16rpx 32rpx; |
|||
} |
|||
|
|||
.no-more { |
|||
text-align: center; |
|||
padding: 40rpx 0; |
|||
color: #ccc; |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.no-more-text { |
|||
display: inline-block; |
|||
padding: 16rpx 32rpx; |
|||
} |
|||
|
|||
/* 优化空状态样式 */ |
|||
.empty-state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 120rpx 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.empty-text { |
|||
font-size: 32rpx; |
|||
color: #999; |
|||
margin-bottom: 20rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.empty-hint { |
|||
font-size: 28rpx; |
|||
color: #ccc; |
|||
} |
|||
|
|||
/* 优化列表项样式 */ |
|||
.experience-item { |
|||
position: relative; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.experience-item:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
/* 响应式调整 */ |
|||
@media (max-width: 375px) { |
|||
.load-more, |
|||
.loading, |
|||
.no-more { |
|||
padding: 30rpx 0; |
|||
} |
|||
|
|||
.empty-state { |
|||
padding: 80rpx 0; |
|||
} |
|||
} |
|||
|
|||
/* 响应式调整 */ |
|||
@media (max-width: 375px) { |
|||
.search-container { |
|||
padding: 16rpx 24rpx 24rpx; |
|||
} |
|||
|
|||
.experience-list { |
|||
padding: 24rpx; |
|||
} |
|||
|
|||
.experience-item { |
|||
padding: 24rpx; |
|||
} |
|||
} |
|||
@ -0,0 +1,477 @@ |
|||
import http from '../../../utils/api' |
|||
const baseUrl = require('../../../utils/baseUrl') |
|||
|
|||
Page({ |
|||
data: { |
|||
// 帖子列表相关
|
|||
posts: [], |
|||
baseUrl: baseUrl, |
|||
loading: false, |
|||
searchLoading: false, |
|||
initialLoading: true, |
|||
loadingMore: false, |
|||
refreshing: false, |
|||
hasMore: true, |
|||
page: 1, |
|||
pageSize: 10, |
|||
searchKeyword: '', |
|||
lastSearchKeyword: '', // 新增:记录上次搜索关键词
|
|||
|
|||
scrollTop: 0, |
|||
showBackToTop: false, |
|||
scrollThreshold: 300, |
|||
|
|||
// 发布帖子相关
|
|||
showPostModal: false, |
|||
postTitle: '', |
|||
postContent: '', |
|||
postImages: [], |
|||
isSubmitting: false |
|||
}, |
|||
|
|||
onLoad: function () { |
|||
this.loadPosts(true); |
|||
}, |
|||
|
|||
onShow: function () { |
|||
// 每次显示页面时刷新数据
|
|||
this.refreshData(); |
|||
}, |
|||
|
|||
// 加载帖子列表
|
|||
loadPosts: function (reset = false) { |
|||
// 防止重复请求
|
|||
if (reset) { |
|||
this.setData({ |
|||
page: 1, |
|||
hasMore: true, |
|||
loading: true, |
|||
searchLoading: !!this.data.searchKeyword |
|||
}); |
|||
} else if (this.data.loadingMore) { |
|||
return; |
|||
} |
|||
|
|||
this.setData({ |
|||
loading: reset || this.data.page === 1, |
|||
loadingMore: !reset && this.data.page > 1 |
|||
}); |
|||
|
|||
// 准备请求参数
|
|||
const params = { |
|||
page: this.data.page, |
|||
pageSize: this.data.pageSize |
|||
}; |
|||
|
|||
// 如果有搜索关键词,添加搜索参数
|
|||
const searchKeyword = this.data.searchKeyword.trim(); |
|||
if (searchKeyword) { |
|||
params.searchKey = searchKeyword; |
|||
} |
|||
|
|||
// 记录当前搜索关键词
|
|||
this.setData({ |
|||
lastSearchKeyword: searchKeyword |
|||
}); |
|||
|
|||
// 调用接口获取数据
|
|||
http.forumList({ |
|||
data: params, |
|||
success: (res) => { |
|||
if (res.code === 200) { |
|||
let postsData = res.rows || []; |
|||
|
|||
// 处理图片字段(分割字符串为数组)
|
|||
postsData = postsData.map(item => { |
|||
if (item.images && typeof item.images === 'string') { |
|||
item.images = item.images.split(',').filter(img => img.trim() !== ''); |
|||
} else { |
|||
item.images = []; |
|||
} |
|||
return item; |
|||
}); |
|||
|
|||
if (reset) { |
|||
this.setData({ |
|||
posts: postsData, |
|||
loading: false, |
|||
searchLoading: false, |
|||
initialLoading: false, |
|||
hasMore: postsData.length === this.data.pageSize |
|||
}); |
|||
} else { |
|||
this.setData({ |
|||
posts: [...this.data.posts, ...postsData], |
|||
loading: false, |
|||
initialLoading: false, |
|||
loadingMore: false, |
|||
hasMore: postsData.length === this.data.pageSize |
|||
}); |
|||
} |
|||
} else { |
|||
wx.showToast({ |
|||
title: res.msg || '加载失败', |
|||
icon: 'none' |
|||
}); |
|||
this.setData({ |
|||
loading: false, |
|||
searchLoading: false, |
|||
initialLoading: false, |
|||
loadingMore: false |
|||
}); |
|||
} |
|||
|
|||
if (this.data.refreshing) { |
|||
wx.stopPullDownRefresh(); |
|||
this.setData({ |
|||
refreshing: false |
|||
}); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
wx.showToast({ |
|||
title: '网络错误', |
|||
icon: 'none' |
|||
}); |
|||
this.setData({ |
|||
loading: false, |
|||
searchLoading: false, |
|||
initialLoading: false, |
|||
loadingMore: false, |
|||
refreshing: false |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 下拉刷新
|
|||
onRefresh: function () { |
|||
this.setData({ |
|||
refreshing: true |
|||
}); |
|||
this.loadPosts(true); |
|||
}, |
|||
|
|||
// 加载更多
|
|||
loadMore: function () { |
|||
if (!this.data.hasMore || this.data.loadingMore) return; |
|||
|
|||
this.setData({ |
|||
page: this.data.page + 1 |
|||
}, () => { |
|||
this.loadPosts(); |
|||
}); |
|||
}, |
|||
|
|||
// 搜索输入(防抖)
|
|||
onSearchInput: function (e) { |
|||
const keyword = e.detail.value; |
|||
this.setData({ |
|||
searchKeyword: keyword |
|||
}); |
|||
|
|||
// 防抖搜索
|
|||
clearTimeout(this.searchTimer); |
|||
|
|||
// 如果输入框为空,立即重置搜索
|
|||
if (!keyword.trim()) { |
|||
this.setData({ |
|||
searchKeyword: '' |
|||
}); |
|||
// 清空搜索时立即重新加载数据
|
|||
this.loadPosts(true); |
|||
return; |
|||
} |
|||
|
|||
this.searchTimer = setTimeout(() => { |
|||
// 只有当有搜索词且与上次不同时才搜索
|
|||
if (keyword.trim() && keyword.trim() !== this.data.lastSearchKeyword) { |
|||
this.loadPosts(true); |
|||
} |
|||
}, 500); |
|||
}, |
|||
|
|||
// 搜索确认
|
|||
onSearchConfirm: function (e) { |
|||
const keyword = e.detail.value.trim(); |
|||
this.setData({ |
|||
searchKeyword: keyword |
|||
}); |
|||
|
|||
// 如果搜索词为空,加载所有帖子
|
|||
if (!keyword) { |
|||
this.loadPosts(true); |
|||
} else { |
|||
// 只有当搜索词与上次不同时才搜索
|
|||
if (keyword !== this.data.lastSearchKeyword) { |
|||
this.loadPosts(true); |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 清空搜索 - 优化版
|
|||
clearSearch: function () { |
|||
// 如果当前已经有搜索词,才执行清空操作
|
|||
if (this.data.searchKeyword) { |
|||
this.setData({ |
|||
searchKeyword: '' |
|||
}, () => { |
|||
// 清空后立即加载所有帖子
|
|||
this.loadPosts(true); |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
// 跳转到详情页
|
|||
goToDetail: function (e) { |
|||
const postId = e.currentTarget.dataset.id; |
|||
wx.navigateTo({ |
|||
url: `/pagesB/pages/onlineAsk/onlineAsk?id=${postId}` |
|||
}); |
|||
}, |
|||
|
|||
// 图片预览功能
|
|||
previewImage: function (e) { |
|||
const postIndex = e.currentTarget.dataset.postindex; |
|||
const imageIndex = e.currentTarget.dataset.imageindex; |
|||
const post = this.data.posts[postIndex]; |
|||
|
|||
if (!post || !post.images || post.images.length === 0) return; |
|||
|
|||
// 构建完整的图片URL数组
|
|||
const urls = post.images.map(img => this.data.baseUrl + img); |
|||
|
|||
wx.previewImage({ |
|||
current: urls[imageIndex], // 当前显示图片的链接
|
|||
urls: urls // 需要预览的图片链接列表
|
|||
}); |
|||
}, |
|||
|
|||
// 显示发布模态框
|
|||
createPost: function () { |
|||
// 检查登录状态(如果需要)
|
|||
// 这里可以添加登录检查逻辑
|
|||
|
|||
this.setData({ |
|||
showPostModal: true |
|||
}); |
|||
}, |
|||
|
|||
// 隐藏发布模态框
|
|||
hidePostModal: function () { |
|||
if (this.data.isSubmitting) return; |
|||
this.setData({ |
|||
showPostModal: false, |
|||
postTitle: '', |
|||
postContent: '', |
|||
postImages: [] |
|||
}); |
|||
}, |
|||
|
|||
// 阻止事件冒泡
|
|||
stopPropagation: function () {}, |
|||
|
|||
// 标题输入
|
|||
onPostTitleInput: function (e) { |
|||
this.setData({ |
|||
postTitle: e.detail.value |
|||
}); |
|||
}, |
|||
|
|||
// 内容输入
|
|||
onPostContentInput: function (e) { |
|||
this.setData({ |
|||
postContent: e.detail.value |
|||
}); |
|||
}, |
|||
|
|||
// 选择图片
|
|||
chooseImage: function () { |
|||
const remaining = 3 - this.data.postImages.length; |
|||
if (remaining <= 0) { |
|||
wx.showToast({ |
|||
title: '最多上传3张图片', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
wx.chooseMedia({ |
|||
count: remaining, |
|||
mediaType: ['image'], |
|||
sizeType: ['compressed'], |
|||
sourceType: ['album', 'camera'], |
|||
success: (res) => { |
|||
const tempFiles = res.tempFiles; |
|||
const newImages = tempFiles.map(file => file.tempFilePath); |
|||
const postImages = [...this.data.postImages, ...newImages]; |
|||
|
|||
this.setData({ |
|||
postImages: postImages.slice(0, 3) // 确保不超过3张
|
|||
}); |
|||
}, |
|||
fail: (err) => { |
|||
console.error('选择图片失败:', err); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 移除图片
|
|||
removeImage: function (e) { |
|||
const index = e.currentTarget.dataset.index; |
|||
const postImages = [...this.data.postImages]; |
|||
postImages.splice(index, 1); |
|||
this.setData({ |
|||
postImages |
|||
}); |
|||
}, |
|||
|
|||
// 提交帖子
|
|||
submitPost: function () { |
|||
const { postTitle, postContent, postImages } = this.data; |
|||
|
|||
// 验证输入
|
|||
if (!postTitle.trim()) { |
|||
wx.showToast({ |
|||
title: '请输入标题', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (!postContent.trim()) { |
|||
wx.showToast({ |
|||
title: '请输入内容', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
this.setData({ |
|||
isSubmitting: true |
|||
}); |
|||
|
|||
// 如果有图片,先上传图片
|
|||
const uploadPromises = postImages.map(imagePath => { |
|||
return new Promise((resolve, reject) => { |
|||
wx.uploadFile({ |
|||
url: baseUrl+'/common/upload', |
|||
header: { |
|||
'Authorization': 'Bearer ' + wx.getStorageSync('token') |
|||
}, |
|||
filePath: imagePath, |
|||
name: 'file', |
|||
success: (res) => { |
|||
const data = JSON.parse(res.data); |
|||
console.log(data); |
|||
if (data.code === 200) { |
|||
resolve(data.fileName); // 假设返回的图片路径在data.url中
|
|||
} else { |
|||
reject(new Error('图片上传失败')); |
|||
} |
|||
}, |
|||
fail: (err) => { |
|||
reject(err); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
// 处理图片上传
|
|||
Promise.all(uploadPromises) |
|||
.then((imageUrls) => { |
|||
// 所有图片上传成功,提交帖子数据
|
|||
const postData = { |
|||
title: postTitle, |
|||
content: postContent, |
|||
images: imageUrls.join(',') // 将图片URL用逗号拼接
|
|||
}; |
|||
|
|||
return http.forumAdd({ |
|||
data: postData, |
|||
success:res=>{ |
|||
console.log(1111,res); |
|||
if (res.code === 200) { |
|||
// 发布成功
|
|||
this.setData({ |
|||
showPostModal: false, |
|||
postTitle: '', |
|||
postContent: '', |
|||
postImages: [], |
|||
isSubmitting: false |
|||
}); |
|||
|
|||
wx.showToast({ |
|||
title: '发布成功', |
|||
icon: 'success', |
|||
duration: 2000 |
|||
}); |
|||
|
|||
// 刷新帖子列表
|
|||
setTimeout(() => { |
|||
this.loadPosts(true); |
|||
}, 500); |
|||
} else { |
|||
throw new Error(res.msg || '发布失败'); |
|||
} |
|||
} |
|||
}); |
|||
}) |
|||
}, |
|||
|
|||
// 滚动事件监听
|
|||
onScroll: function (e) { |
|||
const scrollTop = e.detail.scrollTop; |
|||
const showBackToTop = scrollTop > this.data.scrollThreshold; |
|||
|
|||
if (showBackToTop !== this.data.showBackToTop) { |
|||
this.setData({ |
|||
scrollTop: scrollTop, |
|||
showBackToTop: showBackToTop |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
// 返回顶部
|
|||
backToTop: function () { |
|||
this.setData({ |
|||
showBackToTop: false |
|||
}); |
|||
|
|||
wx.pageScrollTo({ |
|||
scrollTop: 0, |
|||
duration: 400, |
|||
success: () => { |
|||
this.setData({ |
|||
scrollTop: 0 |
|||
}); |
|||
}, |
|||
fail: (err) => { |
|||
console.log('滚动失败:', err); |
|||
this.setData({ |
|||
scrollTop: 0 |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 刷新数据
|
|||
refreshData: function () { |
|||
const pages = getCurrentPages(); |
|||
const currentPage = pages[pages.length - 1]; |
|||
if (currentPage.data && currentPage.data.refresh) { |
|||
this.loadPosts(true); |
|||
currentPage.setData({ |
|||
refresh: false |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
onPullDownRefresh: function () { |
|||
this.onRefresh(); |
|||
}, |
|||
|
|||
onReachBottom: function () { |
|||
this.loadMore(); |
|||
} |
|||
}); |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText": "问答论坛", |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,208 @@ |
|||
<view class="forum-page"> |
|||
|
|||
<!-- 顶部栏 --> |
|||
<view class="header"> |
|||
<view class="header-content"> |
|||
<view class="header-main"> |
|||
<text class="title-text">问答社区</text> |
|||
<text class="title-sub">互帮互助,共同成长</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 主内容区 --> |
|||
<view class="main-content"> |
|||
|
|||
<!-- 搜索框 --> |
|||
<view class="search-section"> |
|||
<view class="search-box"> |
|||
<image class="search-icon" src="/pagesB/images/sou.png" mode="aspectFit"></image> |
|||
<input class="search-input" placeholder="搜索问题或关键词..." value="{{searchKeyword}}" bindinput="onSearchInput" bindconfirm="onSearchConfirm" confirm-type="search" /> |
|||
<view class="search-clear" wx:if="{{searchKeyword}}" bindtap="clearSearch"> |
|||
<text class="clear-text">×</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 帖子列表 --> |
|||
<scroll-view class="post-list-section" scroll-y enable-back-to-top scroll-top="{{scrollTop}}" bindscrolltolower="loadMore" bindscroll="onScroll" refresher-enabled="{{true}}" refresher-triggered="{{refreshing}}" bindrefresherrefresh="onRefresh"> |
|||
|
|||
<!-- 搜索加载提示 --> |
|||
<view class="search-loading" wx:if="{{searchLoading && posts.length === 0}}"> |
|||
<view class="search-spinner"></view> |
|||
<text class="search-loading-text">搜索中...</text> |
|||
</view> |
|||
|
|||
<!-- 空状态 --> |
|||
<view class="empty-state" wx:if="{{!loading && !searchLoading && posts.length === 0}}"> |
|||
<view class="empty-text" wx:if="{{searchKeyword}}"> |
|||
没有找到"{{searchKeyword}}"相关的问题 |
|||
</view> |
|||
<view class="empty-text" wx:else> |
|||
这里还没有问题,快来发布第一个吧! |
|||
</view> |
|||
<button class="empty-btn" bindtap="createPost" wx:if="{{!searchKeyword}}"> |
|||
发布问题 |
|||
</button> |
|||
</view> |
|||
|
|||
<!-- 帖子列表 --> |
|||
<view class="post-list" wx:if="{{posts.length > 0}}"> |
|||
<block wx:for="{{posts}}" wx:key="id"> |
|||
<view class="post-card" data-id="{{item.id}}" bindtap="goToDetail"> |
|||
<view class="post-content"> |
|||
<view class="post-type"> |
|||
<!-- 头像 --> |
|||
<view class="user-info"> |
|||
<image class="user-avatar" src="{{baseUrl+item.avatar}}" mode="aspectFill"></image> |
|||
<text class="username">{{item.nickName}}</text> |
|||
</view> |
|||
<!-- 时间 --> |
|||
<text class="post-time">{{item.createdAt}}</text> |
|||
</view> |
|||
<!-- 标题 --> |
|||
<view class="post-title-wrapper"> |
|||
<text class="post-title">{{item.title}}</text> |
|||
</view> |
|||
|
|||
<!-- 内容摘要 --> |
|||
<view class="post-summary"> |
|||
{{item.content}} |
|||
</view> |
|||
|
|||
<!-- 图片预览 --> |
|||
<view class="post-images" wx:if="{{item.images && item.images.length>0}}"> |
|||
<view class="images-grid"> |
|||
<block wx:for="{{item.images}}" wx:key="index" wx:for-index="imgIndex"> |
|||
<image class="post-image" src="{{baseUrl+item}}" mode="aspectFill" data-postindex="{{index}}" data-imageindex="{{imgIndex}}" bindtap="previewImage"></image> |
|||
</block> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部信息 --> |
|||
<view class="post-footer"> |
|||
<view class="post-meta"> |
|||
<view class="meta-item"> |
|||
<image class="meta-icon" src="/pagesB/images/hf.png" mode="aspectFit"></image> |
|||
<text class="meta-count">{{item.answerCount || 0}}</text> |
|||
</view> |
|||
<view class="meta-item"> |
|||
<image class="meta-icon" src="/pagesB/images/lll.png" mode="aspectFit"></image> |
|||
<text class="meta-count">{{item.viewCount || 0}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
</block> |
|||
|
|||
<!-- 加载更多 --> |
|||
<view class="load-more" wx:if="{{hasMore && posts.length > 0}}"> |
|||
<view class="loading-spinner" wx:if="{{!loadingMore}}"> |
|||
<text class="loading-text">上拉加载更多</text> |
|||
</view> |
|||
<view class="loading-spinner" wx:else> |
|||
<view class="spinner"></view> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 没有更多了 --> |
|||
<view class="no-more" wx:if="{{!hasMore && posts.length > 0}}"> |
|||
<view class="no-more-line"></view> |
|||
<text class="no-more-text">已经到底了</text> |
|||
<view class="no-more-line"></view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
</scroll-view> |
|||
|
|||
</view> |
|||
|
|||
<!-- 固定发布按钮(左下角) --> |
|||
<!-- <view class="floating-create-btn" bindtap="createPost"> |
|||
<image class="create-icon" src="/pagesA/images/jh.png" mode="aspectFit"></image> |
|||
</view> --> |
|||
|
|||
<!-- 返回顶部按钮(右下角) --> |
|||
<view class="back-to-top-btn {{showBackToTop ? 'show' : ''}}" bindtap="backToTop"> |
|||
<view class="back-top-icon">↑</view> |
|||
</view> |
|||
|
|||
<!-- 发布模态框 --> |
|||
<view class="post-modal-overlay" wx:if="{{showPostModal}}" bindtap="hidePostModal"> |
|||
<view class="post-modal-content" catchtap="stopPropagation"> |
|||
|
|||
<view class="modal-header"> |
|||
<text class="modal-title">发布问题</text> |
|||
<view class="modal-close" bindtap="hidePostModal"> |
|||
<text class="close-icon">×</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="modal-body"> |
|||
<view class="form-field"> |
|||
<view class="field-header"> |
|||
<text class="field-label">问题标题</text> |
|||
<text class="field-counter">{{postTitle.length}}/50</text> |
|||
</view> |
|||
<input class="title-input" placeholder="请输入问题标题" value="{{postTitle}}" bindinput="onPostTitleInput" maxlength="50" /> |
|||
</view> |
|||
|
|||
<view class="form-field"> |
|||
<view class="field-header"> |
|||
<text class="field-label">详细描述</text> |
|||
<text class="field-counter">{{postContent.length}}/500</text> |
|||
</view> |
|||
<textarea class="content-input" placeholder="请详细描述您的问题..." value="{{postContent}}" bindinput="onPostContentInput" maxlength="500" auto-height /> |
|||
</view> |
|||
|
|||
<view class="form-field"> |
|||
<view class="field-header"> |
|||
<text class="field-label">添加图片</text> |
|||
<text class="field-hint">最多3张</text> |
|||
</view> |
|||
<view class="image-upload-area"> |
|||
|
|||
<view class="uploaded-images" wx:if="{{postImages.length > 0}}"> |
|||
<block wx:for="{{postImages}}" wx:key="index"> |
|||
<view class="image-item"> |
|||
<image class="preview-image" src="{{item}}" mode="aspectFill"></image> |
|||
<view class="image-remove" bindtap="removeImage" data-index="{{index}}"> |
|||
<text class="remove-icon">×</text> |
|||
</view> |
|||
</view> |
|||
</block> |
|||
</view> |
|||
|
|||
<view class="upload-btn" wx:if="{{postImages.length < 3}}" bindtap="chooseImage"> |
|||
<image class="camera-icon" src="/pagesA/images/jh.png" mode="aspectFit"></image> |
|||
<text class="upload-text">添加图片</text> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<view class="modal-footer"> |
|||
<button class="cancel-btn" bindtap="hidePostModal">取消</button> |
|||
<button class="submit-btn" bindtap="submitPost" disabled="{{!postTitle || !postContent || isSubmitting}}"> |
|||
{{isSubmitting ? '发布中...' : '发布'}} |
|||
</button> |
|||
</view> |
|||
|
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 首次加载遮罩 --> |
|||
<view class="loading-overlay" wx:if="{{initialLoading && posts.length === 0}}"> |
|||
<view class="loading-content"> |
|||
<view class="spinner-large"></view> |
|||
<text class="loading-tip">加载中...</text> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
@ -0,0 +1,758 @@ |
|||
.forum-page { |
|||
min-height: 100vh; |
|||
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%); |
|||
position: relative; |
|||
font-family: '思源宋体 Light' !important; |
|||
} |
|||
|
|||
/* 顶部栏 */ |
|||
.header { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
padding: 40rpx 30rpx; |
|||
border-radius: 0 0 40rpx 40rpx; |
|||
box-shadow: 0 10rpx 40rpx rgba(102, 126, 234, 0.15); |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.header-content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 30rpx; |
|||
} |
|||
|
|||
.header-main { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 10rpx; |
|||
} |
|||
|
|||
.title-text { |
|||
font-size: 44rpx; |
|||
font-weight: 800; |
|||
color: white; |
|||
letter-spacing: -0.5rpx; |
|||
} |
|||
|
|||
.title-sub { |
|||
font-size: 26rpx; |
|||
color: rgba(255, 255, 255, 0.9); |
|||
font-weight: 400; |
|||
} |
|||
|
|||
/* 搜索框 */ |
|||
.search-section { |
|||
padding: 0 30rpx 25rpx; |
|||
} |
|||
|
|||
.search-box { |
|||
display: flex; |
|||
align-items: center; |
|||
background: white; |
|||
border-radius: 50rpx; |
|||
padding: 0 30rpx; |
|||
height: 88rpx; |
|||
box-shadow: 0 15rpx 50rpx rgba(0, 0, 0, 0.05); |
|||
border: 2rpx solid #e2e8f0; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.search-box:focus-within { |
|||
border-color: #667eea; |
|||
box-shadow: 0 15rpx 50rpx rgba(102, 126, 234, 0.15); |
|||
} |
|||
|
|||
.search-icon { |
|||
width: 36rpx; |
|||
height: 36rpx; |
|||
margin-right: 20rpx; |
|||
} |
|||
|
|||
.search-input { |
|||
flex: 1; |
|||
font-size: 30rpx; |
|||
height: 88rpx; |
|||
line-height: 88rpx; |
|||
color: #1e293b; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.search-clear { |
|||
width: 44rpx; |
|||
height: 44rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background: #f1f5f9; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.clear-text { |
|||
font-size: 32rpx; |
|||
color: #94a3b8; |
|||
line-height: 1; |
|||
} |
|||
|
|||
/* 搜索加载提示 */ |
|||
.search-loading { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 100rpx 30rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.search-spinner { |
|||
width: 60rpx; |
|||
height: 60rpx; |
|||
border: 4rpx solid rgba(102, 126, 234, 0.1); |
|||
border-top-color: #667eea; |
|||
border-radius: 50%; |
|||
animation: spin 1s linear infinite; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.search-loading-text { |
|||
font-size: 30rpx; |
|||
color: #64748b; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
/* 帖子列表区域 */ |
|||
.post-list-section { |
|||
height: calc(100vh - 320rpx); |
|||
padding: 0 20rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 空状态 */ |
|||
.empty-state { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 150rpx 30rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
|
|||
.empty-text { |
|||
font-size: 32rpx; |
|||
color: #94a3b8; |
|||
margin-bottom: 50rpx; |
|||
line-height: 1.5; |
|||
max-width: 400rpx; |
|||
} |
|||
|
|||
.empty-btn { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
color: white; |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
padding: 24rpx 60rpx; |
|||
border-radius: 50rpx; |
|||
box-shadow: 0 10rpx 30rpx rgba(102, 126, 234, 0.3); |
|||
border: none; |
|||
margin: 0; |
|||
} |
|||
|
|||
.empty-btn::after { |
|||
border: none; |
|||
} |
|||
|
|||
/* 帖子卡片 */ |
|||
.post-list { |
|||
padding: 10rpx; |
|||
} |
|||
|
|||
.post-card { |
|||
background: white; |
|||
border-radius: 24rpx; |
|||
margin-bottom: 25rpx; |
|||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.04); |
|||
border: 1rpx solid #f1f5f9; |
|||
position: relative; |
|||
overflow: hidden; |
|||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|||
} |
|||
|
|||
.post-card:active { |
|||
transform: translateY(-4rpx); |
|||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
/* 帖子内容 */ |
|||
.post-content { |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.post-type { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
border-bottom: 1rpx solid #f1f5f9; |
|||
padding-bottom: 20rpx; |
|||
} |
|||
|
|||
/* 帖子标题 */ |
|||
.post-title-wrapper { |
|||
margin: 20rpx 0; |
|||
} |
|||
|
|||
.post-title { |
|||
font-size: 34rpx; |
|||
font-weight: 700; |
|||
color: #1e293b; |
|||
line-height: 1.4; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
/* 帖子摘要 */ |
|||
.post-summary { |
|||
font-size: 28rpx; |
|||
color: #64748b; |
|||
line-height: 1.6; |
|||
margin-bottom: 25rpx; |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 3; |
|||
-webkit-box-orient: vertical; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
/* 图片区域 */ |
|||
.post-images { |
|||
margin-bottom: 25rpx; |
|||
} |
|||
|
|||
.images-grid { |
|||
width: 100%; |
|||
display: grid; |
|||
grid-template-columns: repeat(3,1fr); |
|||
position: relative; |
|||
} |
|||
|
|||
.post-image { |
|||
width: 180rpx; |
|||
height: 180rpx; |
|||
border-radius: 16rpx; |
|||
flex-shrink: 0; |
|||
object-fit: cover; |
|||
} |
|||
|
|||
|
|||
|
|||
/* 帖子底部信息 */ |
|||
.post-footer { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: flex-end; |
|||
padding-top: 25rpx; |
|||
border-top: 1rpx solid #f1f5f9; |
|||
} |
|||
|
|||
/* 用户信息 */ |
|||
.user-info { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 15rpx; |
|||
} |
|||
|
|||
.user-avatar { |
|||
width: 64rpx; |
|||
height: 64rpx; |
|||
border-radius: 50%; |
|||
border: 2rpx solid white; |
|||
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08); |
|||
} |
|||
|
|||
.username { |
|||
font-size: 28rpx; |
|||
font-weight: 600; |
|||
color: #334155; |
|||
} |
|||
|
|||
/* 元信息 */ |
|||
.post-meta { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 25rpx; |
|||
} |
|||
|
|||
.meta-item { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8rpx; |
|||
} |
|||
|
|||
.meta-icon { |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
opacity: 0.5; |
|||
} |
|||
|
|||
.meta-count { |
|||
font-size: 26rpx; |
|||
font-weight: 600; |
|||
color: #64748b; |
|||
} |
|||
|
|||
.post-time { |
|||
font-size: 24rpx; |
|||
color: #94a3b8; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
/* 加载更多 */ |
|||
.load-more { |
|||
padding: 50rpx 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.loading-spinner { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.spinner { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
border: 4rpx solid rgba(102, 126, 234, 0.1); |
|||
border-top-color: #667eea; |
|||
border-radius: 50%; |
|||
animation: spin 1s linear infinite; |
|||
} |
|||
|
|||
@keyframes spin { |
|||
0% { transform: rotate(0deg); } |
|||
100% { transform: rotate(360deg); } |
|||
} |
|||
|
|||
.loading-text { |
|||
font-size: 28rpx; |
|||
color: #94a3b8; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
/* 没有更多了 */ |
|||
.no-more { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
gap: 30rpx; |
|||
padding: 50rpx 0; |
|||
} |
|||
|
|||
.no-more-line { |
|||
flex: 1; |
|||
height: 1rpx; |
|||
background: linear-gradient(90deg, transparent, #e2e8f0, transparent); |
|||
} |
|||
|
|||
.no-more-text { |
|||
font-size: 26rpx; |
|||
color: #cbd5e1; |
|||
font-weight: 500; |
|||
padding: 0 20rpx; |
|||
} |
|||
|
|||
/* 固定发布按钮(左下角) */ |
|||
.floating-create-btn { |
|||
position: fixed; |
|||
left: 40rpx; |
|||
bottom: 40rpx; |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-shadow: 0 15rpx 50rpx rgba(102, 126, 234, 0.4); |
|||
z-index: 100; |
|||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|||
} |
|||
|
|||
.floating-create-btn:active { |
|||
transform: scale(0.95); |
|||
box-shadow: 0 10rpx 30rpx rgba(102, 126, 234, 0.3); |
|||
} |
|||
|
|||
.create-icon { |
|||
width: 44rpx; |
|||
height: 44rpx; |
|||
} |
|||
|
|||
/* 返回顶部按钮 - 修复图标显示 */ |
|||
.back-to-top-btn { |
|||
position: fixed; |
|||
right: 40rpx; |
|||
bottom: 40rpx; |
|||
width: 100rpx; |
|||
height: 100rpx; |
|||
background: white; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.1); |
|||
z-index: 100; |
|||
opacity: 0; |
|||
transform: translateY(50rpx) scale(0.8); |
|||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.back-to-top-btn.show { |
|||
opacity: 1; |
|||
transform: translateY(0) scale(1); |
|||
pointer-events: auto; |
|||
} |
|||
|
|||
.back-to-top-btn:active { |
|||
transform: translateY(2rpx) scale(0.98); |
|||
background: #f8fafc; |
|||
transition: transform 0.1s ease; |
|||
} |
|||
|
|||
.back-top-icon { |
|||
font-size: 40rpx; |
|||
color: #667eea; |
|||
font-weight: bold; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.back-to-top-btn:active .back-top-icon { |
|||
transform: translateY(-2rpx); |
|||
} |
|||
|
|||
/* 发布模态框 */ |
|||
.post-modal-overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
backdrop-filter: blur(10rpx); |
|||
display: flex; |
|||
align-items: flex-end; |
|||
justify-content: center; |
|||
z-index: 1000; |
|||
animation: fadeIn 0.3s ease; |
|||
} |
|||
|
|||
.post-modal-content { |
|||
background: white; |
|||
width: 100%; |
|||
border-radius: 40rpx 40rpx 0 0; |
|||
max-height: 85vh; |
|||
overflow-y: auto; |
|||
animation: slideUp 0.3s ease; |
|||
} |
|||
|
|||
.modal-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 40rpx 30rpx 30rpx; |
|||
border-bottom: 1rpx solid #f1f5f9; |
|||
} |
|||
|
|||
.modal-title { |
|||
font-size: 38rpx; |
|||
font-weight: 700; |
|||
color: #1e293b; |
|||
} |
|||
|
|||
.modal-close { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background: #f1f5f9; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.close-icon { |
|||
font-size: 36rpx; |
|||
color: #64748b; |
|||
line-height: 1; |
|||
} |
|||
|
|||
.modal-body { |
|||
padding: 30rpx; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.form-field { |
|||
margin-bottom: 40rpx; |
|||
} |
|||
|
|||
.field-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 16rpx; |
|||
} |
|||
|
|||
.field-label { |
|||
font-size: 30rpx; |
|||
font-weight: 600; |
|||
color: #1e293b; |
|||
} |
|||
|
|||
.field-counter, .field-hint { |
|||
font-size: 26rpx; |
|||
color: #94a3b8; |
|||
} |
|||
|
|||
.title-input { |
|||
font-family: '思源宋体 Light' !important; |
|||
background: #f8fafc; |
|||
border-radius: 16rpx; |
|||
font-size: 30rpx; |
|||
width: 100%; |
|||
height: 80rpx; |
|||
line-height: 80rpx; |
|||
padding: 0 20rpx; |
|||
box-sizing: border-box; |
|||
transition: all 0.3s ease; |
|||
border: 2rpx solid transparent; |
|||
} |
|||
|
|||
.title-input:focus { |
|||
border-color: #667eea; |
|||
background: white; |
|||
box-shadow: 0 0 0 4rpx rgba(102, 126, 234, 0.1); |
|||
outline: none; |
|||
} |
|||
|
|||
.content-input { |
|||
background: #f8fafc; |
|||
border-radius: 16rpx; |
|||
font-size: 30rpx; |
|||
min-height: 200rpx; |
|||
line-height: 1.6; |
|||
padding: 20rpx; |
|||
box-sizing: border-box; |
|||
width: 100%; |
|||
transition: all 0.3s ease; |
|||
border: 2rpx solid transparent; |
|||
} |
|||
|
|||
.content-input:focus { |
|||
border-color: #667eea; |
|||
background: white; |
|||
box-shadow: 0 0 0 4rpx rgba(102, 126, 234, 0.1); |
|||
outline: none; |
|||
} |
|||
|
|||
.image-upload-area { |
|||
margin-top: 10rpx; |
|||
} |
|||
|
|||
.uploaded-images { |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
gap: 20rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.image-item { |
|||
position: relative; |
|||
width: 180rpx; |
|||
height: 180rpx; |
|||
} |
|||
|
|||
.preview-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
border-radius: 16rpx; |
|||
object-fit: cover; |
|||
} |
|||
|
|||
.image-remove { |
|||
position: absolute; |
|||
top: -12rpx; |
|||
right: -12rpx; |
|||
width: 44rpx; |
|||
height: 44rpx; |
|||
background: #ef4444; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
box-shadow: 0 6rpx 20rpx rgba(239, 68, 68, 0.3); |
|||
} |
|||
|
|||
.remove-icon { |
|||
font-size: 28rpx; |
|||
color: white; |
|||
line-height: 1; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.upload-btn { |
|||
width: 180rpx; |
|||
height: 180rpx; |
|||
border: 2rpx dashed #cbd5e1; |
|||
border-radius: 16rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background: #f8fafc; |
|||
transition: all 0.3s ease; |
|||
gap: 15rpx; |
|||
} |
|||
|
|||
.upload-btn:active { |
|||
border-color: #667eea; |
|||
background: rgba(102, 126, 234, 0.05); |
|||
} |
|||
|
|||
.camera-icon { |
|||
width: 48rpx; |
|||
height: 48rpx; |
|||
filter: brightness(0); |
|||
} |
|||
|
|||
.upload-text { |
|||
font-size: 26rpx; |
|||
color: #64748b; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.modal-footer { |
|||
display: flex; |
|||
gap: 20rpx; |
|||
padding: 30rpx; |
|||
background: #f8fafc; |
|||
border-top: 1rpx solid #f1f5f9; |
|||
} |
|||
|
|||
.cancel-btn, .submit-btn { |
|||
flex: 1; |
|||
border-radius: 16rpx; |
|||
font-size: 32rpx; |
|||
font-weight: 600; |
|||
border: none; |
|||
margin: 0; |
|||
transition: all 0.3s ease; |
|||
} |
|||
|
|||
.cancel-btn::after, .submit-btn::after { |
|||
border: none; |
|||
} |
|||
|
|||
.cancel-btn { |
|||
background: white; |
|||
color: #64748b; |
|||
border: 1rpx solid #e2e8f0; |
|||
} |
|||
|
|||
.cancel-btn:active { |
|||
background: #f8fafc; |
|||
} |
|||
|
|||
.submit-btn { |
|||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|||
color: white; |
|||
box-shadow: 0 10rpx 30rpx rgba(102, 126, 234, 0.3); |
|||
} |
|||
|
|||
.submit-btn:active:not([disabled]) { |
|||
transform: translateY(-2rpx); |
|||
box-shadow: 0 15rpx 40rpx rgba(102, 126, 234, 0.4); |
|||
} |
|||
|
|||
.submit-btn[disabled] { |
|||
background: #cbd5e1; |
|||
box-shadow: none; |
|||
color: #94a3b8; |
|||
} |
|||
|
|||
/* 首次加载遮罩 */ |
|||
.loading-overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background: rgba(255, 255, 255, 0.9); |
|||
backdrop-filter: blur(10rpx); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 999; |
|||
} |
|||
|
|||
.loading-content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
gap: 30rpx; |
|||
} |
|||
|
|||
.spinner-large { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border: 6rpx solid rgba(102, 126, 234, 0.1); |
|||
border-top-color: #667eea; |
|||
border-radius: 50%; |
|||
animation: spin 1s linear infinite; |
|||
} |
|||
|
|||
.loading-tip { |
|||
font-size: 32rpx; |
|||
color: #64748b; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
/* 动画 */ |
|||
@keyframes fadeIn { |
|||
from { opacity: 0; } |
|||
to { opacity: 1; } |
|||
} |
|||
|
|||
@keyframes slideUp { |
|||
from { transform: translateY(100%); } |
|||
to { transform: translateY(0); } |
|||
} |
|||
|
|||
/* 添加返回顶部按钮的呼吸动画 */ |
|||
@keyframes breathe { |
|||
0%, 100% { |
|||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.1); |
|||
} |
|||
50% { |
|||
box-shadow: 0 10rpx 40rpx rgba(102, 126, 234, 0.3); |
|||
} |
|||
} |
|||
|
|||
.back-to-top-btn.show { |
|||
animation: breathe 3s ease-in-out infinite; |
|||
} |
|||
|
|||
/* 响应式调整 */ |
|||
@media (min-width: 768px) { |
|||
.post-card { |
|||
max-width: 600rpx; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
} |
|||
|
|||
.floating-create-btn { |
|||
left: calc(50% - 250rpx); |
|||
} |
|||
|
|||
.back-to-top-btn { |
|||
right: calc(50% - 250rpx); |
|||
} |
|||
} |
|||
@ -0,0 +1,328 @@ |
|||
import http from '../../../utils/api' |
|||
const baseUrl = require('../../../utils/baseUrl') |
|||
|
|||
Page({ |
|||
data: { |
|||
postId: null, // 存储帖子ID
|
|||
post: null, |
|||
postHf: [], // 帖子回复列表
|
|||
replyContent: '', |
|||
baseUrl: baseUrl, |
|||
replyTarget: { |
|||
type: '', // 'post'或'reply'或'comment'
|
|||
id: '', // 回复或评论的ID
|
|||
nickName: '', |
|||
replyIndex: null, |
|||
commentIndex: null, |
|||
parentId: null // 父级ID(用于二级评论)
|
|||
}, |
|||
replyPlaceholder: '输入您的回复...', |
|||
isInputFocused: false, |
|||
inputTransformY: '0', |
|||
isSubmitting: false, |
|||
showPreview: false, |
|||
previewImages: [], |
|||
previewIndex: 0, |
|||
loading: false, |
|||
scrollToId: '', |
|||
keyboardHeight: 0 |
|||
}, |
|||
|
|||
onLoad: function (options) { |
|||
const postId = options.id |
|||
this.setData({ |
|||
postId: postId |
|||
}) |
|||
|
|||
this.loadPostDetail(postId); |
|||
this.getforumReply(postId) |
|||
|
|||
// 监听键盘高度变化
|
|||
wx.onKeyboardHeightChange(res => { |
|||
if (res.height > 0) { |
|||
this.setData({ |
|||
keyboardHeight: res.height |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 加载帖子详情
|
|||
loadPostDetail: function (postId) { |
|||
this.setData({ |
|||
loading: true |
|||
}); |
|||
|
|||
http.forumDetails({ |
|||
data: { |
|||
id: postId |
|||
}, |
|||
success: res => { |
|||
console.log('帖子详情:', res); |
|||
const postData = res.data |
|||
const images = postData.images ? postData.images.split(',') : [] |
|||
this.setData({ |
|||
post: postData, |
|||
images: images, |
|||
loading: false |
|||
}); |
|||
}, |
|||
fail: err => { |
|||
console.error('加载帖子详情失败:', err); |
|||
wx.showToast({ |
|||
title: '加载失败', |
|||
icon: 'none' |
|||
}); |
|||
this.setData({ |
|||
loading: false |
|||
}); |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 帖子回复列表
|
|||
getforumReply(postId) { |
|||
http.forumReply({ |
|||
data: { |
|||
questionId: postId |
|||
}, |
|||
success: res => { |
|||
console.log('回复列表:', res); |
|||
this.setData({ |
|||
postHf: res.rows || [] |
|||
}) |
|||
}, |
|||
fail: err => { |
|||
console.error('加载回复列表失败:', err); |
|||
this.setData({ |
|||
postHf: [] |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
// 滚动监听
|
|||
onScroll: function (e) { |
|||
// 可以在这里实现滚动相关逻辑
|
|||
}, |
|||
|
|||
// 输入框获取焦点
|
|||
onInputFocus: function (e) { |
|||
this.setData({ |
|||
isInputFocused: true, |
|||
inputTransformY: `-${e.detail.height}px` |
|||
}); |
|||
}, |
|||
|
|||
// 输入框失去焦点
|
|||
onInputBlur: function () { |
|||
this.setData({ |
|||
isInputFocused: false, |
|||
inputTransformY: '0' |
|||
}); |
|||
}, |
|||
|
|||
// 回复输入
|
|||
onReplyInput: function (e) { |
|||
this.setData({ |
|||
replyContent: e.detail.value |
|||
}); |
|||
}, |
|||
|
|||
// 回复一级评论
|
|||
replyToUser: function (e) { |
|||
console.log(567,e); |
|||
const { |
|||
type, |
|||
index, |
|||
username |
|||
} = e.currentTarget.dataset; |
|||
|
|||
const replyItem = this.data.postHf[index]; |
|||
|
|||
this.setData({ |
|||
replyTarget: { |
|||
type: 'reply', // 回复一级评论
|
|||
id: replyItem.id, |
|||
nickName: username, |
|||
replyIndex: parseInt(index), |
|||
parentId: null // 一级评论没有父级ID
|
|||
}, |
|||
replyPlaceholder: `回复 @${username}...`, |
|||
replyContent: '', |
|||
isInputFocused: true |
|||
}); |
|||
|
|||
// 滚动到对应回复位置
|
|||
setTimeout(() => { |
|||
this.setData({ |
|||
scrollToId: `reply-${replyItem.id}` |
|||
}); |
|||
}, 100); |
|||
}, |
|||
|
|||
|
|||
// 清除回复目标
|
|||
clearReplyTarget: function () { |
|||
this.setData({ |
|||
replyTarget: { |
|||
type: '', |
|||
id: '', |
|||
nickName: '', |
|||
replyIndex: null, |
|||
commentIndex: null, |
|||
parentId: null |
|||
}, |
|||
replyPlaceholder: '输入您的回复...', |
|||
replyContent: '' |
|||
}); |
|||
}, |
|||
|
|||
// 提交回复
|
|||
submitReply: function () { |
|||
const { |
|||
replyContent, |
|||
replyTarget, |
|||
postId |
|||
} = this.data; |
|||
const content = replyContent.trim(); |
|||
|
|||
if (!content) { |
|||
wx.showToast({ |
|||
title: '请输入内容', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
if (content.length > 500) { |
|||
wx.showToast({ |
|||
title: '回复内容不能超过500字', |
|||
icon: 'none' |
|||
}); |
|||
return; |
|||
} |
|||
|
|||
this.setData({ |
|||
isSubmitting: true |
|||
}); |
|||
|
|||
// 准备提交数据
|
|||
const submitData = { |
|||
questionId: postId, // 帖子ID
|
|||
content: content // 回复内容
|
|||
}; |
|||
|
|||
// 如果有回复目标,添加父级ID
|
|||
if (replyTarget.type === 'reply' && replyTarget.id) { |
|||
submitData.parentId = replyTarget.id; // 回复一级评论
|
|||
} else if (replyTarget.type === 'comment' && replyTarget.id) { |
|||
submitData.parentId = replyTarget.id; // 回复二级评论
|
|||
} |
|||
// 如果type为'post'或为空,则表示直接回复帖子,parentId为空
|
|||
|
|||
console.log('提交数据:', submitData); |
|||
|
|||
// 调用提交接口
|
|||
http.commentReply({ |
|||
data: submitData, |
|||
success: res => { |
|||
console.log('回复成功:', res); |
|||
// 提交成功后的处理
|
|||
this.handleReplySuccess(content, replyTarget); |
|||
|
|||
// 刷新回复列表
|
|||
setTimeout(() => { |
|||
this.getforumReply(postId); |
|||
}, 500); |
|||
}, |
|||
fail: err => { |
|||
console.error('回复失败:', err); |
|||
wx.showToast({ |
|||
title: '回复失败,请重试', |
|||
icon: 'none', |
|||
duration: 2000 |
|||
}); |
|||
this.setData({ |
|||
isSubmitting: false |
|||
}); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
// 处理回复成功后的UI更新
|
|||
handleReplySuccess: function (content, replyTarget) { |
|||
// 清空输入框和回复目标
|
|||
this.setData({ |
|||
replyContent: '', |
|||
replyTarget: { |
|||
type: '', |
|||
id: '', |
|||
nickName: '', |
|||
replyIndex: null, |
|||
commentIndex: null, |
|||
parentId: null |
|||
}, |
|||
replyPlaceholder: '输入您的回复...', |
|||
isInputFocused: false, |
|||
isSubmitting: false |
|||
}); |
|||
|
|||
// 如果是直接回复帖子,滚动到底部
|
|||
if (!replyTarget.type || replyTarget.type === 'post') { |
|||
setTimeout(() => { |
|||
wx.pageScrollTo({ |
|||
scrollTop: 9999, |
|||
duration: 300 |
|||
}); |
|||
}, 300); |
|||
} |
|||
}, |
|||
|
|||
// 图片预览
|
|||
previewImage: function (e) { |
|||
const imgIndex = e.currentTarget.dataset.imgIndex; |
|||
const images = this.data.images; |
|||
|
|||
this.setData({ |
|||
showPreview: true, |
|||
previewImages: images, |
|||
previewIndex: imgIndex |
|||
}); |
|||
}, |
|||
|
|||
// 图片预览滑动
|
|||
onSwiperChange: function (e) { |
|||
this.setData({ |
|||
previewIndex: e.detail.current |
|||
}); |
|||
}, |
|||
|
|||
// 隐藏预览
|
|||
hidePreview: function () { |
|||
this.setData({ |
|||
showPreview: false |
|||
}); |
|||
}, |
|||
|
|||
// 下拉刷新
|
|||
onPullDownRefresh: function () { |
|||
if (this.data.postId) { |
|||
this.loadPostDetail(this.data.postId); |
|||
this.getforumReply(this.data.postId); |
|||
} |
|||
wx.stopPullDownRefresh(); |
|||
}, |
|||
|
|||
// 页面上拉触底
|
|||
onReachBottom: function () { |
|||
// 这里可以实现加载更多回复的逻辑
|
|||
console.log('加载更多回复'); |
|||
}, |
|||
|
|||
// 页面卸载
|
|||
onUnload: function () { |
|||
// 移除键盘高度监听
|
|||
wx.offKeyboardHeightChange(); |
|||
} |
|||
}); |
|||
@ -0,0 +1,4 @@ |
|||
{ |
|||
"navigationBarTitleText": "问答论坛", |
|||
"usingComponents": {} |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
<view class="forum-page"> |
|||
<!-- 帖子详情 --> |
|||
<scroll-view |
|||
class="post-detail-container" |
|||
scroll-y |
|||
scroll-with-animation |
|||
scroll-into-view="{{scrollToId}}" |
|||
bindscroll="onScroll" |
|||
> |
|||
|
|||
<!-- 帖子主体 --> |
|||
<view class="post-main" id="post-main"> |
|||
<!-- 帖子头部 --> |
|||
<view class="post-header"> |
|||
<view class="user-info"> |
|||
<image class="avatar" src="{{baseUrl+post.avatar}}" mode="aspectFill"></image> |
|||
<view class="user-detail"> |
|||
<view class="username">{{post.nickName}}</view> |
|||
<view class="post-time">{{post.createdAt}}</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 帖子内容 --> |
|||
<view class="post-content"> |
|||
<view class="post-title">{{post.title}}</view> |
|||
<view class="post-desc">{{post.content}}</view> |
|||
|
|||
<!-- 帖子图片 --> |
|||
<view class="post-images" wx:if="{{post.images && post.images.length > 0}}"> |
|||
<view class="images-container"> |
|||
<block wx:for="{{images}}" wx:for-item="image" wx:for-index="imgIndex" wx:key="index"> |
|||
<image |
|||
class="post-image" |
|||
src="{{baseUrl+image}}" |
|||
mode="aspectFill" |
|||
data-img-index="{{imgIndex}}" |
|||
bindtap="previewImage" |
|||
></image> |
|||
</block> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
|||
|
|||
<!-- 回复列表 --> |
|||
<view class="replies-section" id="replies-section"> |
|||
<view class="section-title"> |
|||
<text class="title-text">全部回复</text> |
|||
<text class="reply-count">{{postHf.length}}条</text> |
|||
</view> |
|||
|
|||
<view class="reply-list" wx:if="{{postHf.length > 0}}"> |
|||
<block wx:for="{{postHf}}" wx:key="id" wx:for-index="replyIndex"> |
|||
<view class="reply-item" id="reply-{{item.id}}"> |
|||
<view class="reply-user"> |
|||
<image class="reply-avatar" src="{{baseUrl+item.avatar}}" mode="aspectFill"></image> |
|||
<view class="reply-user-info"> |
|||
<view class="reply-username">{{item.nickName}}</view> |
|||
<view class="reply-time">{{item.createdAt}}</view> |
|||
</view> |
|||
</view> |
|||
<view class="reply-content">{{item.content}}</view> |
|||
|
|||
<!-- 回复操作 --> |
|||
<view class="reply-actions"> |
|||
<view class="reply-action" bindtap="replyToUser" data-type="reply" data-index="{{replyIndex}}" data-username="{{item.nickName}}"> |
|||
<image class="reply-action-icon" src="/pagesB/images/hf.png" mode="aspectFit"></image> |
|||
<text class="reply-action-text">回复</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 二级评论列表 --> |
|||
<view class="comment-list" wx:if="{{item.children.length > 0}}"> |
|||
<block wx:for="{{item.children}}" wx:key="id" wx:for-index="commentIndex"> |
|||
<view class="comment-item"> |
|||
<view class="comment-user"> |
|||
<image class="comment-avatar" src="{{baseUrl+item.avatar}}" mode="aspectFill"></image> |
|||
<view class="comment-user-info"> |
|||
<view class="comment-username">{{item.nickName}}</view> |
|||
<view class="comment-time">{{item.createdAt}}</view> |
|||
</view> |
|||
</view> |
|||
<view class="comment-content"> |
|||
{{item.content}} |
|||
</view> |
|||
</view> |
|||
</block> |
|||
</view> |
|||
</view> |
|||
</block> |
|||
</view> |
|||
|
|||
<!-- 空回复状态 --> |
|||
<view class="empty-replies" wx:else> |
|||
<view class="empty-reply-text">还没有回复,快来第一个回复吧!</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 底部占位 --> |
|||
<view class="bottom-placeholder" style="height: 160rpx;"></view> |
|||
</scroll-view> |
|||
|
|||
<!-- 底部回复输入栏 --> |
|||
<view class="bottom-input-container" style="transform: translateY({{inputTransformY}});"> |
|||
<view class="input-wrapper"> |
|||
<input |
|||
class="reply-input" |
|||
placeholder="{{replyPlaceholder}}" |
|||
value="{{replyContent}}" |
|||
bindinput="onReplyInput" |
|||
bindfocus="onInputFocus" |
|||
bindblur="onInputBlur" |
|||
bindconfirm="submitReply" |
|||
adjust-position="{{false}}" |
|||
focus="{{isInputFocused}}" |
|||
maxlength="500" |
|||
/> |
|||
<button |
|||
class="send-btn" |
|||
bindtap="submitReply" |
|||
disabled="{{!replyContent}}" |
|||
> |
|||
{{isSubmitting ? '发送中...' : '发送'}} |
|||
</button> |
|||
</view> |
|||
|
|||
<!-- 回复对象显示 --> |
|||
<view class="reply-target" wx:if="{{replyTarget.nickName}}"> |
|||
<text class="target-text">回复 {{replyTarget.nickName}}</text> |
|||
<text class="clear-target" bindtap="clearReplyTarget">×</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 图片预览模态框 --> |
|||
<view class="preview-overlay" wx:if="{{showPreview}}" catchtap="hidePreview"> |
|||
<swiper class="preview-swiper" current="{{previewIndex}}" bindchange="onSwiperChange"> |
|||
<block wx:for="{{previewImages}}" wx:key="index"> |
|||
<swiper-item> |
|||
<image class="preview-image" src="{{baseUrl+item}}" mode="aspectFit"></image> |
|||
</swiper-item> |
|||
</block> |
|||
</swiper> |
|||
<view class="preview-indicator">{{previewIndex + 1}}/{{previewImages.length}}</view> |
|||
</view> |
|||
|
|||
<!-- 加载提示 --> |
|||
<view class="loading" wx:if="{{loading}}"> |
|||
加载中... |
|||
</view> |
|||
</view> |
|||
@ -0,0 +1,434 @@ |
|||
.forum-page { |
|||
min-height: 100vh; |
|||
background-color: #f8f8f8; |
|||
position: relative; |
|||
} |
|||
|
|||
|
|||
/* 帖子详情容器 */ |
|||
.post-detail-container { |
|||
height: 100vh; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
/* 帖子主体 */ |
|||
.post-main { |
|||
background-color: white; |
|||
padding: 40rpx 30rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.post-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.user-info { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 50%; |
|||
margin-right: 20rpx; |
|||
} |
|||
|
|||
.user-detail { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.username { |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #333; |
|||
margin-bottom: 8rpx; |
|||
} |
|||
|
|||
.post-time { |
|||
font-size: 26rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
/* 帖子内容 */ |
|||
.post-content { |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.post-title { |
|||
font-size: 36rpx; |
|||
font-weight: bold; |
|||
color: #333; |
|||
margin-bottom: 24rpx; |
|||
line-height: 1.4; |
|||
} |
|||
|
|||
.post-desc { |
|||
font-size: 30rpx; |
|||
color: #555; |
|||
line-height: 1.6; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
/* 帖子图片 */ |
|||
.post-images { |
|||
margin: 30rpx 0; |
|||
} |
|||
|
|||
.images-container { |
|||
width: 100%; |
|||
display: grid; |
|||
grid-template-columns: repeat(3,1fr); |
|||
} |
|||
|
|||
.post-image { |
|||
width: 200rpx; |
|||
height: 200rpx; |
|||
border-radius: 12rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
/* 回复区域 */ |
|||
.replies-section { |
|||
background-color: white; |
|||
border-radius: 20rpx 20rpx 0 0; |
|||
padding: 40rpx 30rpx; |
|||
margin: 0; |
|||
} |
|||
|
|||
.section-title { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 30rpx; |
|||
padding-bottom: 20rpx; |
|||
border-bottom: 2rpx solid #f0f0f0; |
|||
} |
|||
|
|||
.title-text { |
|||
font-size: 34rpx; |
|||
font-weight: bold; |
|||
color: #333; |
|||
} |
|||
|
|||
.reply-count { |
|||
font-size: 28rpx; |
|||
color: #07c160; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
/* 回复列表 */ |
|||
.reply-list { |
|||
margin-top: 10rpx; |
|||
} |
|||
|
|||
.reply-item { |
|||
padding: 30rpx 0; |
|||
border-bottom: 1rpx solid #f8f8f8; |
|||
} |
|||
|
|||
.reply-item:last-child { |
|||
border-bottom: none; |
|||
} |
|||
|
|||
.reply-user { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.reply-avatar { |
|||
width: 64rpx; |
|||
height: 64rpx; |
|||
border-radius: 50%; |
|||
margin-right: 20rpx; |
|||
} |
|||
|
|||
.reply-user-info { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.reply-username { |
|||
font-size: 28rpx; |
|||
color: #333; |
|||
margin-bottom: 6rpx; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.reply-time { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.reply-content { |
|||
font-size: 30rpx; |
|||
color: #444; |
|||
line-height: 1.5; |
|||
margin-left: 84rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
/* 回复操作 */ |
|||
.reply-actions { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-left: 84rpx; |
|||
margin-top: 20rpx; |
|||
gap: 40rpx; |
|||
} |
|||
|
|||
.reply-action { |
|||
display: flex; |
|||
align-items: center; |
|||
color: #999; |
|||
font-size: 24rpx; |
|||
padding: 4rpx 8rpx; |
|||
} |
|||
|
|||
.reply-action-icon { |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
margin-right: 8rpx; |
|||
} |
|||
|
|||
.reply-action-text { |
|||
font-size: 24rpx; |
|||
color: #666; |
|||
} |
|||
|
|||
/* 评论列表 */ |
|||
.comment-list { |
|||
margin-left: 84rpx; |
|||
margin-top: 20rpx; |
|||
background-color: #fafafa; |
|||
border-radius: 12rpx; |
|||
padding: 20rpx; |
|||
} |
|||
|
|||
.comment-item { |
|||
padding: 20rpx 0; |
|||
} |
|||
|
|||
.comment-item:not(:last-child) { |
|||
border-bottom: 1rpx solid #eee; |
|||
} |
|||
|
|||
.comment-user { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 12rpx; |
|||
} |
|||
|
|||
.comment-avatar { |
|||
width: 48rpx; |
|||
height: 48rpx; |
|||
border-radius: 50%; |
|||
margin-right: 15rpx; |
|||
} |
|||
|
|||
.comment-user-info { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.comment-username { |
|||
font-size: 26rpx; |
|||
color: #666; |
|||
margin-bottom: 4rpx; |
|||
} |
|||
|
|||
.comment-time { |
|||
font-size: 22rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.comment-content { |
|||
font-size: 28rpx; |
|||
color: #555; |
|||
line-height: 1.4; |
|||
margin-left: 63rpx; |
|||
} |
|||
|
|||
.comment-to { |
|||
color: #07c160; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
/* 评论操作 */ |
|||
.comment-actions { |
|||
margin-left: 63rpx; |
|||
margin-top: 12rpx; |
|||
} |
|||
|
|||
.comment-action { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
padding: 4rpx 8rpx; |
|||
} |
|||
|
|||
/* 空回复状态 */ |
|||
.empty-replies { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 100rpx 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
|
|||
|
|||
.empty-reply-text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.bottom-placeholder { |
|||
height: 160rpx; |
|||
} |
|||
|
|||
/* 底部输入栏 */ |
|||
.bottom-input-container { |
|||
position: fixed; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background-color: white; |
|||
border-top: 1rpx solid #eee; |
|||
padding: 20rpx 30rpx; |
|||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05); |
|||
z-index: 100; |
|||
transition: transform 0.3s ease; |
|||
} |
|||
|
|||
.input-wrapper { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 20rpx; |
|||
} |
|||
|
|||
.reply-input { |
|||
flex: 1; |
|||
background-color: #f8f8f8; |
|||
border-radius: 40rpx; |
|||
padding: 10rpx 30rpx; |
|||
font-size: 30rpx; |
|||
} |
|||
|
|||
.send-btn { |
|||
background-color: #07c160; |
|||
color: white; |
|||
font-size: 28rpx; |
|||
padding: 20rpx 40rpx; |
|||
line-height: normal; |
|||
border-radius: 40rpx; |
|||
margin: 0; |
|||
min-width: 120rpx; |
|||
transition: background-color 0.3s; |
|||
} |
|||
|
|||
.send-btn[disabled] { |
|||
background-color: #cccccc; |
|||
color: white; |
|||
} |
|||
|
|||
/* 回复对象显示 */ |
|||
.reply-target { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
background-color: #f0f0f0; |
|||
border-radius: 20rpx; |
|||
padding: 16rpx 24rpx; |
|||
margin-top: 15rpx; |
|||
animation: slideIn 0.3s ease; |
|||
} |
|||
|
|||
@keyframes slideIn { |
|||
from { |
|||
opacity: 0; |
|||
transform: translateY(-10rpx); |
|||
} |
|||
to { |
|||
opacity: 1; |
|||
transform: translateY(0); |
|||
} |
|||
} |
|||
|
|||
.target-text { |
|||
font-size: 26rpx; |
|||
color: #07c160; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.clear-target { |
|||
font-size: 36rpx; |
|||
color: #999; |
|||
padding: 0 10rpx 4rpx 20rpx; |
|||
} |
|||
|
|||
/* 图片预览 */ |
|||
.preview-overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: rgba(0, 0, 0, 0.95); |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 1000; |
|||
} |
|||
|
|||
.preview-swiper { |
|||
width: 100%; |
|||
height: 70vh; |
|||
} |
|||
|
|||
.preview-image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.preview-indicator { |
|||
position: absolute; |
|||
bottom: 60rpx; |
|||
color: white; |
|||
font-size: 28rpx; |
|||
background-color: rgba(0, 0, 0, 0.5); |
|||
padding: 10rpx 24rpx; |
|||
border-radius: 20rpx; |
|||
} |
|||
|
|||
/* 加载提示 */ |
|||
.loading { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 60rpx 0; |
|||
color: #999; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
/* 移除按钮边框 */ |
|||
button::after { |
|||
border: none; |
|||
} |
|||
|
|||
/* 滚动条样式 */ |
|||
::-webkit-scrollbar { |
|||
width: 0; |
|||
height: 0; |
|||
color: transparent; |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue