27 changed files with 3672 additions and 1222 deletions
-
7app.json
-
55pages/home/home.js
-
66pages/home/home.wxml
-
83pages/home/home.wxss
-
7pages/personal/personal.js
-
2pages/personal/personal.wxml
-
317pagesA/pages/attestation/attestation.js
-
4pagesA/pages/attestation/attestation.json
-
185pagesA/pages/attestation/attestation.wxml
-
546pagesA/pages/attestation/attestation.wxss
-
34pagesB/pages/experienceDetails/experienceDetails.js
-
4pagesB/pages/experienceDetails/experienceDetails.json
-
46pagesB/pages/experienceDetails/experienceDetails.wxml
-
146pagesB/pages/experienceDetails/experienceDetails.wxss
-
206pagesB/pages/experienceList/experienceList.js
-
4pagesB/pages/experienceList/experienceList.json
-
80pagesB/pages/experienceList/experienceList.wxml
-
332pagesB/pages/experienceList/experienceList.wxss
-
619pagesB/pages/forumlist/forumlist.js
-
1pagesB/pages/forumlist/forumlist.json
-
298pagesB/pages/forumlist/forumlist.wxml
-
1200pagesB/pages/forumlist/forumlist.wxss
-
439pagesB/pages/onlineAsk/onlineAsk.js
-
66pagesB/pages/onlineAsk/onlineAsk.wxml
-
30pagesB/pages/onlineAsk/onlineAsk.wxss
-
44utils/api.js
-
3utils/baseUrl.js
@ -0,0 +1,317 @@ |
|||||
|
// pages/real-name-auth/real-name-auth.js
|
||||
|
Page({ |
||||
|
data: { |
||||
|
// 表单数据
|
||||
|
name: '', |
||||
|
phone: '', |
||||
|
smsCode: '', |
||||
|
|
||||
|
// 焦点状态
|
||||
|
nameFocus: false, |
||||
|
phoneFocus: false, |
||||
|
codeFocus: false, |
||||
|
|
||||
|
// 错误提示
|
||||
|
nameError: '', |
||||
|
phoneError: '', |
||||
|
smsCodeError: '', |
||||
|
|
||||
|
// 验证码相关
|
||||
|
canSendCode: false, |
||||
|
countdown: 0, |
||||
|
countdownTimer: null, |
||||
|
|
||||
|
// 协议状态
|
||||
|
agreed: false, |
||||
|
|
||||
|
// 提交状态
|
||||
|
canSubmit: false, |
||||
|
isSubmitting: false, |
||||
|
|
||||
|
// 弹窗
|
||||
|
showSuccessModal: false |
||||
|
}, |
||||
|
|
||||
|
onLoad() { |
||||
|
this.checkForm(); |
||||
|
}, |
||||
|
|
||||
|
onUnload() { |
||||
|
// 清除定时器
|
||||
|
if (this.data.countdownTimer) { |
||||
|
clearInterval(this.data.countdownTimer); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 姓名输入处理
|
||||
|
onNameInput(e) { |
||||
|
const name = e.detail.value.trim(); |
||||
|
let nameError = ''; |
||||
|
|
||||
|
if (name && !/^[\u4e00-\u9fa5]{2,10}$/.test(name)) { |
||||
|
nameError = '姓名应为2-10个汉字'; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
name: name, |
||||
|
nameError: nameError |
||||
|
}, () => { |
||||
|
this.checkForm(); |
||||
|
this.checkCanSendCode(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
onNameFocus() { |
||||
|
this.setData({ nameFocus: true }); |
||||
|
}, |
||||
|
|
||||
|
onNameBlur() { |
||||
|
this.setData({ nameFocus: false }); |
||||
|
}, |
||||
|
|
||||
|
// 手机号输入处理
|
||||
|
onPhoneInput(e) { |
||||
|
const phone = e.detail.value.trim(); |
||||
|
let phoneError = ''; |
||||
|
|
||||
|
if (phone && !/^1[3-9]\d{9}$/.test(phone)) { |
||||
|
phoneError = '请输入正确的手机号码'; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
phone: phone, |
||||
|
phoneError: phoneError |
||||
|
}, () => { |
||||
|
this.checkForm(); |
||||
|
this.checkCanSendCode(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
onPhoneFocus() { |
||||
|
this.setData({ phoneFocus: true }); |
||||
|
}, |
||||
|
|
||||
|
onPhoneBlur() { |
||||
|
this.setData({ phoneFocus: false }); |
||||
|
}, |
||||
|
|
||||
|
// 验证码输入处理
|
||||
|
onSmsCodeInput(e) { |
||||
|
const smsCode = e.detail.value.trim(); |
||||
|
let smsCodeError = ''; |
||||
|
|
||||
|
if (smsCode && !/^\d{6}$/.test(smsCode)) { |
||||
|
smsCodeError = '请输入6位数字验证码'; |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
smsCode: smsCode, |
||||
|
smsCodeError: smsCodeError |
||||
|
}, () => { |
||||
|
this.checkForm(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
onCodeFocus() { |
||||
|
this.setData({ codeFocus: true }); |
||||
|
}, |
||||
|
|
||||
|
onCodeBlur() { |
||||
|
this.setData({ codeFocus: false }); |
||||
|
}, |
||||
|
|
||||
|
// 检查是否可以发送验证码
|
||||
|
checkCanSendCode() { |
||||
|
const { name, phone, nameError, phoneError } = this.data; |
||||
|
const canSendCode = name && phone && !nameError && !phoneError; |
||||
|
this.setData({ canSendCode }); |
||||
|
}, |
||||
|
|
||||
|
// 发送验证码
|
||||
|
async sendSmsCode() { |
||||
|
const { name, phone, countdown } = this.data; |
||||
|
|
||||
|
if (countdown > 0) return; |
||||
|
|
||||
|
// 验证手机号格式
|
||||
|
if (!/^1[3-9]\d{9}$/.test(phone)) { |
||||
|
this.setData({ phoneError: '请输入正确的手机号码' }); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// 显示加载
|
||||
|
wx.showLoading({ |
||||
|
title: '发送中...', |
||||
|
mask: true |
||||
|
}); |
||||
|
|
||||
|
try { |
||||
|
// 模拟发送验证码请求
|
||||
|
await new Promise(resolve => setTimeout(resolve, 1500)); |
||||
|
|
||||
|
wx.hideLoading(); |
||||
|
|
||||
|
// 发送成功
|
||||
|
wx.showToast({ |
||||
|
title: '验证码已发送', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
|
||||
|
// 开始倒计时
|
||||
|
this.startCountdown(); |
||||
|
|
||||
|
} catch (error) { |
||||
|
wx.hideLoading(); |
||||
|
wx.showToast({ |
||||
|
title: '发送失败,请重试', |
||||
|
icon: 'error', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 开始倒计时
|
||||
|
startCountdown() { |
||||
|
this.setData({ countdown: 60 }); |
||||
|
|
||||
|
const timer = setInterval(() => { |
||||
|
let { countdown } = this.data; |
||||
|
countdown--; |
||||
|
|
||||
|
if (countdown <= 0) { |
||||
|
clearInterval(timer); |
||||
|
this.setData({ |
||||
|
countdown: 0, |
||||
|
countdownTimer: null |
||||
|
}); |
||||
|
} else { |
||||
|
this.setData({ |
||||
|
countdown: countdown, |
||||
|
countdownTimer: timer |
||||
|
}); |
||||
|
} |
||||
|
}, 1000); |
||||
|
}, |
||||
|
|
||||
|
// 协议处理
|
||||
|
toggleAgreement() { |
||||
|
this.setData({ |
||||
|
agreed: !this.data.agreed |
||||
|
}, () => { |
||||
|
this.checkForm(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
viewAgreement() { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/web-view/web-view?url=' + encodeURIComponent('https://your-domain.com/agreement') |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
viewPrivacy() { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/web-view/web-view?url=' + encodeURIComponent('https://your-domain.com/privacy') |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 检查表单
|
||||
|
checkForm() { |
||||
|
const { |
||||
|
name, |
||||
|
phone, |
||||
|
smsCode, |
||||
|
nameError, |
||||
|
phoneError, |
||||
|
smsCodeError, |
||||
|
agreed |
||||
|
} = this.data; |
||||
|
|
||||
|
const isValid = name && |
||||
|
phone && |
||||
|
smsCode && |
||||
|
!nameError && |
||||
|
!phoneError && |
||||
|
!smsCodeError && |
||||
|
agreed; |
||||
|
|
||||
|
this.setData({ |
||||
|
canSubmit: isValid |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 提交认证
|
||||
|
async submitAuth() { |
||||
|
if (!this.data.canSubmit || this.data.isSubmitting) return; |
||||
|
|
||||
|
this.setData({ isSubmitting: true }); |
||||
|
|
||||
|
// 验证数据
|
||||
|
const { name, phone, smsCode } = this.data; |
||||
|
|
||||
|
// 最终验证
|
||||
|
if (!/^[\u4e00-\u9fa5]{2,10}$/.test(name)) { |
||||
|
this.setData({ |
||||
|
nameError: '姓名应为2-10个汉字', |
||||
|
isSubmitting: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!/^1[3-9]\d{9}$/.test(phone)) { |
||||
|
this.setData({ |
||||
|
phoneError: '请输入正确的手机号码', |
||||
|
isSubmitting: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!/^\d{6}$/.test(smsCode)) { |
||||
|
this.setData({ |
||||
|
smsCodeError: '请输入6位数字验证码', |
||||
|
isSubmitting: false |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// 模拟API请求
|
||||
|
await new Promise(resolve => setTimeout(resolve, 2000)); |
||||
|
|
||||
|
// 提交成功
|
||||
|
this.setData({ |
||||
|
isSubmitting: false, |
||||
|
showSuccessModal: true |
||||
|
}); |
||||
|
|
||||
|
// 保存认证信息到本地
|
||||
|
wx.setStorageSync('realNameAuth', { |
||||
|
name: name, |
||||
|
phone: phone, |
||||
|
certified: true, |
||||
|
certifiedTime: new Date().getTime() |
||||
|
}); |
||||
|
|
||||
|
} catch (error) { |
||||
|
this.setData({ isSubmitting: false }); |
||||
|
wx.showToast({ |
||||
|
title: '认证失败,请重试', |
||||
|
icon: 'error', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 成功弹窗处理
|
||||
|
closeSuccessModal() { |
||||
|
this.setData({ showSuccessModal: false }); |
||||
|
}, |
||||
|
|
||||
|
// 前往首页
|
||||
|
goToHome() { |
||||
|
this.closeSuccessModal(); |
||||
|
wx.switchTab({ |
||||
|
url: '/pages/index/index' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"实名认证", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,185 @@ |
|||||
|
<!-- pages/real-name-auth/real-name-auth.wxml --> |
||||
|
<view class="auth-container"> |
||||
|
<!-- 顶部装饰 --> |
||||
|
<view class="top-decoration"> |
||||
|
<view class="decoration-circle circle-1"></view> |
||||
|
<view class="decoration-circle circle-2"></view> |
||||
|
<view class="decoration-circle circle-3"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 头部 --> |
||||
|
<view class="header"> |
||||
|
<image class="logo" src="/assets/icons/sheep-logo.svg" mode="aspectFit"></image> |
||||
|
<view class="brand-name">与牧同行</view> |
||||
|
<view class="page-title">实名认证</view> |
||||
|
<view class="page-subtitle">开启您的牧业伙伴之旅</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 表单卡片 --> |
||||
|
<view class="form-card"> |
||||
|
<!-- 表单标题 --> |
||||
|
<view class="card-header"> |
||||
|
<view class="card-title"> |
||||
|
<image class="title-icon" src="/assets/icons/id-card.svg" mode="aspectFit"></image> |
||||
|
<text>身份信息</text> |
||||
|
</view> |
||||
|
<view class="card-hint">请填写您的真实信息</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 姓名输入 --> |
||||
|
<view class="input-group"> |
||||
|
<view class="input-label"> |
||||
|
<image class="label-icon" src="/assets/icons/user.svg" mode="aspectFit"></image> |
||||
|
<text>真实姓名</text> |
||||
|
</view> |
||||
|
<view class="input-wrapper"> |
||||
|
<input |
||||
|
class="name-input" |
||||
|
placeholder="请输入您的真实姓名" |
||||
|
placeholder-class="placeholder" |
||||
|
value="{{name}}" |
||||
|
bindinput="onNameInput" |
||||
|
focus="{{nameFocus}}" |
||||
|
bindfocus="onNameFocus" |
||||
|
bindblur="onNameBlur" |
||||
|
/> |
||||
|
<view class="input-border"></view> |
||||
|
<view class="input-focus-border {{nameFocus ? 'active' : ''}}"></view> |
||||
|
</view> |
||||
|
<view class="input-hint {{nameError ? 'error' : ''}}"> |
||||
|
{{nameError || '请务必使用真实姓名'}} |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 手机号输入 --> |
||||
|
<view class="input-group"> |
||||
|
<view class="input-label"> |
||||
|
<image class="label-icon" src="/assets/icons/phone.svg" mode="aspectFit"></image> |
||||
|
<text>手机号码</text> |
||||
|
</view> |
||||
|
<view class="input-wrapper"> |
||||
|
<input |
||||
|
class="phone-input" |
||||
|
placeholder="请输入您的手机号码" |
||||
|
placeholder-class="placeholder" |
||||
|
value="{{phone}}" |
||||
|
bindinput="onPhoneInput" |
||||
|
type="number" |
||||
|
maxlength="11" |
||||
|
focus="{{phoneFocus}}" |
||||
|
bindfocus="onPhoneFocus" |
||||
|
bindblur="onPhoneBlur" |
||||
|
/> |
||||
|
<view class="input-border"></view> |
||||
|
<view class="input-focus-border {{phoneFocus ? 'active' : ''}}"></view> |
||||
|
</view> |
||||
|
<view class="input-hint {{phoneError ? 'error' : ''}}"> |
||||
|
{{phoneError || '用于接收重要通知'}} |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 验证码 --> |
||||
|
<view class="input-group"> |
||||
|
<view class="input-label"> |
||||
|
<image class="label-icon" src="/assets/icons/sms.svg" mode="aspectFit"></image> |
||||
|
<text>验证码</text> |
||||
|
</view> |
||||
|
<view class="code-input-wrapper"> |
||||
|
<view class="code-input-container"> |
||||
|
<input |
||||
|
class="code-input" |
||||
|
placeholder="请输入验证码" |
||||
|
placeholder-class="placeholder" |
||||
|
value="{{smsCode}}" |
||||
|
bindinput="onSmsCodeInput" |
||||
|
type="number" |
||||
|
maxlength="6" |
||||
|
focus="{{codeFocus}}" |
||||
|
bindfocus="onCodeFocus" |
||||
|
bindblur="onCodeBlur" |
||||
|
/> |
||||
|
<view class="input-border"></view> |
||||
|
<view class="input-focus-border {{codeFocus ? 'active' : ''}}"></view> |
||||
|
</view> |
||||
|
<button |
||||
|
class="send-code-btn {{!canSendCode ? 'disabled' : ''}} {{countdown > 0 ? 'counting' : ''}}" |
||||
|
bindtap="sendSmsCode" |
||||
|
disabled="{{!canSendCode || countdown > 0}}" |
||||
|
hover-class="btn-hover" |
||||
|
> |
||||
|
<view class="btn-content"> |
||||
|
<image wx:if="{{countdown === 0}}" class="sms-icon" src="/assets/icons/send.svg" mode="aspectFit"></image> |
||||
|
<!-- <text>{{countdown > 0 ? `${countdown}s后重发` : '发送验证码'}}</text> --> |
||||
|
</view> |
||||
|
</button> |
||||
|
</view> |
||||
|
<view class="input-hint {{smsCodeError ? 'error' : ''}}"> |
||||
|
{{smsCodeError || '输入6位数字验证码'}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 协议 --> |
||||
|
<view class="agreement-card"> |
||||
|
<label class="agreement-item" bindtap="toggleAgreement"> |
||||
|
<view class="checkbox {{agreed ? 'checked' : ''}}"> |
||||
|
<image wx:if="{{agreed}}" class="check-icon" src="/assets/icons/check.svg" mode="aspectFit"></image> |
||||
|
</view> |
||||
|
<view class="agreement-text"> |
||||
|
我已阅读并同意 |
||||
|
<text class="link" bindtap="viewAgreement">《用户服务协议》</text> |
||||
|
和 |
||||
|
<text class="link" bindtap="viewPrivacy">《隐私政策》</text> |
||||
|
</view> |
||||
|
</label> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="submit-section"> |
||||
|
<button |
||||
|
class="submit-btn {{canSubmit ? 'active' : 'disabled'}}" |
||||
|
bindtap="submitAuth" |
||||
|
disabled="{{!canSubmit}}" |
||||
|
hover-class="submit-btn-hover" |
||||
|
loading="{{isSubmitting}}" |
||||
|
> |
||||
|
<view class="btn-inner"> |
||||
|
<text>{{isSubmitting ? '提交中...' : '完成认证'}}</text> |
||||
|
<image wx:if="{{!isSubmitting}}" class="arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image> |
||||
|
</view> |
||||
|
</button> |
||||
|
|
||||
|
<view class="submit-hint"> |
||||
|
认证成功后,您将享受与牧同行的专属服务 |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 底部装饰 --> |
||||
|
<view class="bottom-decoration"> |
||||
|
<image class="sheep-illustration" src="/assets/illustrations/sheep.svg" mode="aspectFit"></image> |
||||
|
<view class="decoration-text"> |
||||
|
<text class="highlight">与牧同行</text>,伴您每一次成长 |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 成功弹窗 --> |
||||
|
<view class="success-modal {{showSuccessModal ? 'show' : ''}}"> |
||||
|
<view class="modal-mask" bindtap="closeSuccessModal"></view> |
||||
|
<view class="modal-content"> |
||||
|
<view class="modal-icon"> |
||||
|
<image class="success-icon" src="/assets/icons/success.svg" mode="aspectFit"></image> |
||||
|
</view> |
||||
|
<view class="modal-title">认证成功!</view> |
||||
|
<view class="modal-message"> |
||||
|
欢迎加入<text class="brand-highlight">与牧同行</text>大家庭 |
||||
|
</view> |
||||
|
<view class="modal-subtitle"> |
||||
|
您已成功完成实名认证,开始享受专属服务吧! |
||||
|
</view> |
||||
|
<button class="modal-btn" bindtap="goToHome"> |
||||
|
<image class="home-icon" src="/assets/icons/home.svg" mode="aspectFit"></image> |
||||
|
<text>前往首页</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,546 @@ |
|||||
|
/* pages/real-name-auth/real-name-auth.wxss */ |
||||
|
/* 基础样式 */ |
||||
|
.auth-container { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
padding: 40rpx 0 100rpx; |
||||
|
} |
||||
|
|
||||
|
/* 顶部装饰 */ |
||||
|
.top-decoration { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 300rpx; |
||||
|
overflow: hidden; |
||||
|
z-index: 0; |
||||
|
} |
||||
|
|
||||
|
.decoration-circle { |
||||
|
position: absolute; |
||||
|
border-radius: 50%; |
||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.1) 100%); |
||||
|
} |
||||
|
|
||||
|
.circle-1 { |
||||
|
width: 400rpx; |
||||
|
height: 400rpx; |
||||
|
top: -200rpx; |
||||
|
right: -100rpx; |
||||
|
} |
||||
|
|
||||
|
.circle-2 { |
||||
|
width: 300rpx; |
||||
|
height: 300rpx; |
||||
|
top: -150rpx; |
||||
|
left: -100rpx; |
||||
|
} |
||||
|
|
||||
|
.circle-3 { |
||||
|
width: 200rpx; |
||||
|
height: 200rpx; |
||||
|
top: 100rpx; |
||||
|
left: 50%; |
||||
|
margin-left: -100rpx; |
||||
|
} |
||||
|
|
||||
|
/* 头部 */ |
||||
|
.header { |
||||
|
text-align: center; |
||||
|
padding: 40rpx 0 60rpx; |
||||
|
position: relative; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
margin: 0 auto 24rpx; |
||||
|
} |
||||
|
|
||||
|
.brand-name { |
||||
|
font-size: 48rpx; |
||||
|
font-weight: bold; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 16rpx; |
||||
|
letter-spacing: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.page-title { |
||||
|
font-size: 36rpx; |
||||
|
color: #2c3e50; |
||||
|
font-weight: 600; |
||||
|
margin-bottom: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.page-subtitle { |
||||
|
font-size: 28rpx; |
||||
|
color: #7f8c8d; |
||||
|
} |
||||
|
|
||||
|
/* 表单卡片 */ |
||||
|
.form-card { |
||||
|
background: white; |
||||
|
border-radius: 32rpx; |
||||
|
padding: 48rpx 40rpx; |
||||
|
margin: 0 40rpx 30rpx; |
||||
|
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.08); |
||||
|
position: relative; |
||||
|
z-index: 1; |
||||
|
animation: slideUp 0.6s ease-out; |
||||
|
} |
||||
|
|
||||
|
.card-header { |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.card-title { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.title-icon { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
margin-right: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.card-hint { |
||||
|
font-size: 26rpx; |
||||
|
color: #95a5a6; |
||||
|
} |
||||
|
|
||||
|
/* 输入组 */ |
||||
|
.input-group { |
||||
|
margin-bottom: 48rpx; |
||||
|
} |
||||
|
|
||||
|
.input-label { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #2c3e50; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.label-icon { |
||||
|
width: 32rpx; |
||||
|
height: 32rpx; |
||||
|
margin-right: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.input-wrapper, |
||||
|
.code-input-container { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.name-input, |
||||
|
.phone-input, |
||||
|
.code-input { |
||||
|
width: 100%; |
||||
|
height: 88rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #2c3e50; |
||||
|
padding: 0 24rpx; |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 16rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.placeholder { |
||||
|
color: #bdc3c7; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.input-border { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
border: 2rpx solid transparent; |
||||
|
border-radius: 16rpx; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.input-focus-border { |
||||
|
position: absolute; |
||||
|
top: -2rpx; |
||||
|
left: -2rpx; |
||||
|
right: -2rpx; |
||||
|
bottom: -2rpx; |
||||
|
border: 4rpx solid transparent; |
||||
|
border-radius: 18rpx; |
||||
|
opacity: 0; |
||||
|
transition: all 0.3s ease; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.input-focus-border.active { |
||||
|
opacity: 1; |
||||
|
border-color: rgba(52, 152, 219, 0.3); |
||||
|
} |
||||
|
|
||||
|
.input-hint { |
||||
|
font-size: 24rpx; |
||||
|
color: #95a5a6; |
||||
|
margin-top: 12rpx; |
||||
|
min-height: 36rpx; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.input-hint.error { |
||||
|
color: #e74c3c; |
||||
|
} |
||||
|
|
||||
|
/* 验证码区域 */ |
||||
|
.code-input-wrapper { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.code-input-container { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.send-code-btn { |
||||
|
min-width: 200rpx; |
||||
|
height: 88rpx; |
||||
|
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); |
||||
|
border-radius: 16rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: white; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s ease; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(52, 152, 219, 0.3); |
||||
|
} |
||||
|
|
||||
|
.send-code-btn::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.btn-content { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.sms-icon { |
||||
|
width: 24rpx; |
||||
|
height: 24rpx; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.send-code-btn.disabled { |
||||
|
background: #bdc3c7; |
||||
|
box-shadow: none; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.send-code-btn.counting { |
||||
|
background: #95a5a6; |
||||
|
} |
||||
|
|
||||
|
.btn-hover { |
||||
|
opacity: 0.9; |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
|
|
||||
|
/* 协议 */ |
||||
|
.agreement-card { |
||||
|
background: white; |
||||
|
border-radius: 24rpx; |
||||
|
padding: 32rpx 40rpx; |
||||
|
margin: 0 40rpx 40rpx; |
||||
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06); |
||||
|
} |
||||
|
|
||||
|
.agreement-item { |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
} |
||||
|
|
||||
|
.checkbox { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
border: 2rpx solid #bdc3c7; |
||||
|
border-radius: 8rpx; |
||||
|
margin-right: 20rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
flex-shrink: 0; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.checkbox.checked { |
||||
|
background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%); |
||||
|
border-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.check-icon { |
||||
|
width: 20rpx; |
||||
|
height: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.agreement-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #2c3e50; |
||||
|
line-height: 1.4; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.link { |
||||
|
color: #3498db; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
/* 提交按钮 */ |
||||
|
.submit-section { |
||||
|
padding: 0 40rpx; |
||||
|
position: relative; |
||||
|
z-index: 1; |
||||
|
} |
||||
|
|
||||
|
.submit-btn { |
||||
|
width: 100%; |
||||
|
height: 100rpx; |
||||
|
background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%); |
||||
|
border-radius: 50rpx; |
||||
|
font-size: 32rpx; |
||||
|
color: white; |
||||
|
font-weight: 600; |
||||
|
padding: 0; |
||||
|
margin: 0; |
||||
|
transition: all 0.3s ease; |
||||
|
box-shadow: 0 10rpx 40rpx rgba(46, 204, 113, 0.4); |
||||
|
} |
||||
|
|
||||
|
.submit-btn::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.submit-btn.disabled { |
||||
|
background: #bdc3c7; |
||||
|
box-shadow: none; |
||||
|
opacity: 0.7; |
||||
|
} |
||||
|
|
||||
|
.btn-inner { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.arrow-icon { |
||||
|
width: 32rpx; |
||||
|
height: 32rpx; |
||||
|
margin-left: 12rpx; |
||||
|
opacity: 0.9; |
||||
|
} |
||||
|
|
||||
|
.submit-btn-hover { |
||||
|
opacity: 0.9; |
||||
|
transform: translateY(-2rpx); |
||||
|
} |
||||
|
|
||||
|
.submit-hint { |
||||
|
text-align: center; |
||||
|
font-size: 26rpx; |
||||
|
color: #7f8c8d; |
||||
|
margin-top: 24rpx; |
||||
|
} |
||||
|
|
||||
|
/* 底部装饰 */ |
||||
|
.bottom-decoration { |
||||
|
text-align: center; |
||||
|
margin-top: 60rpx; |
||||
|
position: relative; |
||||
|
z-index: 1; |
||||
|
animation: fadeInUp 0.8s ease-out 0.3s both; |
||||
|
} |
||||
|
|
||||
|
.sheep-illustration { |
||||
|
width: 200rpx; |
||||
|
height: 150rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
|
||||
|
.decoration-text { |
||||
|
font-size: 28rpx; |
||||
|
color: #7f8c8d; |
||||
|
} |
||||
|
|
||||
|
.highlight { |
||||
|
color: #3498db; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
/* 成功弹窗 */ |
||||
|
.success-modal { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
z-index: 1000; |
||||
|
opacity: 0; |
||||
|
visibility: hidden; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.success-modal.show { |
||||
|
opacity: 1; |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
.modal-mask { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
} |
||||
|
|
||||
|
.modal-content { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%) scale(0.9); |
||||
|
width: 600rpx; |
||||
|
background: white; |
||||
|
border-radius: 32rpx; |
||||
|
padding: 60rpx 40rpx; |
||||
|
text-align: center; |
||||
|
opacity: 0; |
||||
|
transition: all 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.success-modal.show .modal-content { |
||||
|
opacity: 1; |
||||
|
transform: translate(-50%, -50%) scale(1); |
||||
|
} |
||||
|
|
||||
|
.modal-icon { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
background: linear-gradient(135deg, #2ecc71 0%, #27ae60 100%); |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin: 0 auto 32rpx; |
||||
|
} |
||||
|
|
||||
|
.success-icon { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-title { |
||||
|
font-size: 40rpx; |
||||
|
font-weight: bold; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-message { |
||||
|
font-size: 32rpx; |
||||
|
color: #2c3e50; |
||||
|
margin-bottom: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.brand-highlight { |
||||
|
color: #3498db; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.modal-subtitle { |
||||
|
font-size: 26rpx; |
||||
|
color: #7f8c8d; |
||||
|
margin-bottom: 40rpx; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.modal-btn { |
||||
|
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); |
||||
|
border-radius: 50rpx; |
||||
|
height: 88rpx; |
||||
|
font-size: 30rpx; |
||||
|
color: white; |
||||
|
font-weight: 600; |
||||
|
padding: 0 60rpx; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-btn::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
.home-icon { |
||||
|
width: 32rpx; |
||||
|
height: 32rpx; |
||||
|
} |
||||
|
|
||||
|
/* 动画 */ |
||||
|
@keyframes slideUp { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(50rpx); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeInUp { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(30rpx); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 响应式 */ |
||||
|
@media (max-width: 480px) { |
||||
|
.form-card, |
||||
|
.agreement-card, |
||||
|
.submit-section { |
||||
|
margin-left: 30rpx; |
||||
|
margin-right: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.code-input-wrapper { |
||||
|
flex-direction: column; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.send-code-btn { |
||||
|
min-width: 100%; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
const baseUrl = require('../../../utils/baseUrl') |
||||
|
Page({ |
||||
|
data: { |
||||
|
id: '', // 文章ID
|
||||
|
baseUrl:baseUrl, |
||||
|
detail: {} // 文章详情数据
|
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
this.getexperienceDetails(options) |
||||
|
}, |
||||
|
|
||||
|
// 经验分享详情
|
||||
|
getexperienceDetails(options){ |
||||
|
http.experienceDetails({ |
||||
|
data:{ |
||||
|
id:options.id |
||||
|
}, |
||||
|
success:res=>{ |
||||
|
console.log(1111,res); |
||||
|
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 |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
|
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText": "分享详情", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
<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> |
||||
|
|
||||
|
<!-- 文章内容 --> |
||||
|
<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,146 @@ |
|||||
|
.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; |
||||
|
} |
||||
|
|
||||
|
/* 文章内容 */ |
||||
|
.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,206 @@ |
|||||
|
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() |
||||
|
// 获取经验分享列表
|
||||
|
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) |
||||
|
}, |
||||
|
|
||||
|
// 滚动到底部加载更多
|
||||
|
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,80 @@ |
|||||
|
<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 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,332 @@ |
|||||
|
/* pages/experience/experience.wxss */ |
||||
|
|
||||
|
.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; |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.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; |
||||
|
} |
||||
|
} |
||||
@ -1,346 +1,477 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
const baseUrl = require('../../../utils/baseUrl') |
||||
|
|
||||
Page({ |
Page({ |
||||
data: { |
data: { |
||||
|
// 帖子列表相关
|
||||
posts: [], |
posts: [], |
||||
|
baseUrl: baseUrl, |
||||
loading: false, |
loading: false, |
||||
|
searchLoading: false, |
||||
|
initialLoading: true, |
||||
loadingMore: false, |
loadingMore: false, |
||||
refreshing: false, |
refreshing: false, |
||||
hasMore: true, |
hasMore: true, |
||||
page: 1, |
page: 1, |
||||
pageSize: 10, |
pageSize: 10, |
||||
currentFilter: 'all', |
|
||||
searchKeyword: '', |
searchKeyword: '', |
||||
currentUser: '当前用户' |
|
||||
|
lastSearchKeyword: '', // 新增:记录上次搜索关键词
|
||||
|
|
||||
|
scrollTop: 0, |
||||
|
showBackToTop: false, |
||||
|
scrollThreshold: 300, |
||||
|
|
||||
|
// 发布帖子相关
|
||||
|
showPostModal: false, |
||||
|
postTitle: '', |
||||
|
postContent: '', |
||||
|
postImages: [], |
||||
|
isSubmitting: false |
||||
}, |
}, |
||||
|
|
||||
onLoad: function() { |
|
||||
this.loadPosts(); |
|
||||
|
|
||||
// 监听页面显示,用于刷新数据
|
|
||||
wx.onAppShow(() => { |
|
||||
this.refreshData(); |
|
||||
}); |
|
||||
|
onLoad: function () { |
||||
|
this.loadPosts(true); |
||||
}, |
}, |
||||
|
|
||||
onShow: function() { |
|
||||
|
onShow: function () { |
||||
|
// 每次显示页面时刷新数据
|
||||
this.refreshData(); |
this.refreshData(); |
||||
}, |
}, |
||||
|
|
||||
// 加载帖子列表
|
// 加载帖子列表
|
||||
loadPosts: function(reset = false) { |
|
||||
|
loadPosts: function (reset = false) { |
||||
|
// 防止重复请求
|
||||
if (reset) { |
if (reset) { |
||||
this.setData({ |
this.setData({ |
||||
page: 1, |
page: 1, |
||||
hasMore: true, |
hasMore: true, |
||||
posts: [], |
|
||||
loading: true |
|
||||
|
loading: true, |
||||
|
searchLoading: !!this.data.searchKeyword |
||||
}); |
}); |
||||
} else if (this.data.loadingMore) { |
} else if (this.data.loadingMore) { |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
|
this.setData({ |
||||
|
loading: reset || this.data.page === 1, |
||||
|
loadingMore: !reset && this.data.page > 1 |
||||
|
}); |
||||
|
|
||||
|
// 准备请求参数
|
||||
const params = { |
const params = { |
||||
page: this.data.page, |
page: this.data.page, |
||||
pageSize: this.data.pageSize, |
|
||||
filter: this.data.currentFilter, |
|
||||
search: this.data.searchKeyword |
|
||||
|
pageSize: this.data.pageSize |
||||
}; |
}; |
||||
|
|
||||
|
// 如果有搜索关键词,添加搜索参数
|
||||
|
const searchKeyword = this.data.searchKeyword.trim(); |
||||
|
if (searchKeyword) { |
||||
|
params.searchKey = searchKeyword; |
||||
|
} |
||||
|
|
||||
|
// 记录当前搜索关键词
|
||||
this.setData({ |
this.setData({ |
||||
loading: reset || this.data.page === 1, |
|
||||
loadingMore: !reset && this.data.page > 1 |
|
||||
|
lastSearchKeyword: searchKeyword |
||||
}); |
}); |
||||
|
|
||||
// 模拟API请求
|
|
||||
setTimeout(() => { |
|
||||
const mockPosts = this.generateMockPosts(params); |
|
||||
|
// 调用接口获取数据
|
||||
|
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 (reset) { |
|
||||
this.setData({ |
|
||||
posts: mockPosts, |
|
||||
loading: false, |
|
||||
hasMore: mockPosts.length === params.pageSize |
|
||||
|
if (this.data.refreshing) { |
||||
|
wx.stopPullDownRefresh(); |
||||
|
this.setData({ |
||||
|
refreshing: false |
||||
|
}); |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
wx.showToast({ |
||||
|
title: '网络错误', |
||||
|
icon: 'none' |
||||
}); |
}); |
||||
} else { |
|
||||
this.setData({ |
this.setData({ |
||||
posts: [...this.data.posts, ...mockPosts], |
|
||||
loading: false, |
loading: false, |
||||
|
searchLoading: false, |
||||
|
initialLoading: false, |
||||
loadingMore: false, |
loadingMore: false, |
||||
hasMore: mockPosts.length === params.pageSize |
|
||||
|
refreshing: false |
||||
}); |
}); |
||||
} |
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
if (this.data.refreshing) { |
|
||||
wx.stopPullDownRefresh(); |
|
||||
this.setData({ refreshing: false }); |
|
||||
} |
|
||||
}, 800); |
|
||||
|
// 下拉刷新
|
||||
|
onRefresh: function () { |
||||
|
this.setData({ |
||||
|
refreshing: true |
||||
|
}); |
||||
|
this.loadPosts(true); |
||||
}, |
}, |
||||
|
|
||||
// 生成模拟数据
|
|
||||
generateMockPosts: function(params) { |
|
||||
const posts = []; |
|
||||
const currentUser = this.data.currentUser; |
|
||||
const baseId = (params.page - 1) * params.pageSize; |
|
||||
|
|
||||
for (let i = 0; i < params.pageSize; i++) { |
|
||||
const id = baseId + i + 1; |
|
||||
const solved = i % 4 === 0; |
|
||||
const hot = i % 3 === 0 && i % 2 === 0; |
|
||||
const isMine = i % 5 === 0; |
|
||||
|
|
||||
// 根据筛选条件过滤
|
|
||||
if (params.filter === 'solved' && !solved) continue; |
|
||||
if (params.filter === 'unsolved' && solved) continue; |
|
||||
if (params.filter === 'mine' && !isMine) continue; |
|
||||
|
|
||||
const tags = this.getRandomTags(); |
|
||||
const post = { |
|
||||
id: id, |
|
||||
title: this.getRandomTitle(id), |
|
||||
summary: this.getRandomSummary(id), |
|
||||
username: isMine ? currentUser : this.getRandomUsername(), |
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
|
||||
time: this.getRandomTime(), |
|
||||
likeCount: Math.floor(Math.random() * 50), |
|
||||
replyCount: Math.floor(Math.random() * 30), |
|
||||
viewCount: Math.floor(Math.random() * 300), |
|
||||
solved: solved, |
|
||||
hot: hot, |
|
||||
tags: tags, |
|
||||
lastReply: Math.random() > 0.3 ? { |
|
||||
username: this.getRandomUsername(), |
|
||||
time: this.getRandomTime('short') |
|
||||
} : null |
|
||||
}; |
|
||||
|
|
||||
// 搜索过滤
|
|
||||
if (params.search) { |
|
||||
const keyword = params.search.toLowerCase(); |
|
||||
const titleMatch = post.title.toLowerCase().includes(keyword); |
|
||||
const summaryMatch = post.summary.toLowerCase().includes(keyword); |
|
||||
const tagMatch = post.tags.some(tag => tag.toLowerCase().includes(keyword)); |
|
||||
|
|
||||
if (!titleMatch && !summaryMatch && !tagMatch) { |
|
||||
continue; |
|
||||
} |
|
||||
} |
|
||||
|
// 加载更多
|
||||
|
loadMore: function () { |
||||
|
if (!this.data.hasMore || this.data.loadingMore) return; |
||||
|
|
||||
posts.push(post); |
|
||||
} |
|
||||
|
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 (params.filter === 'hot') { |
|
||||
posts.sort((a, b) => { |
|
||||
const aScore = a.likeCount * 2 + a.replyCount * 3 + a.viewCount; |
|
||||
const bScore = b.likeCount * 2 + b.replyCount * 3 + b.viewCount; |
|
||||
return bScore - aScore; |
|
||||
|
// 如果输入框为空,立即重置搜索
|
||||
|
if (!keyword.trim()) { |
||||
|
this.setData({ |
||||
|
searchKeyword: '' |
||||
}); |
}); |
||||
|
// 清空搜索时立即重新加载数据
|
||||
|
this.loadPosts(true); |
||||
|
return; |
||||
} |
} |
||||
|
|
||||
return posts; |
|
||||
|
this.searchTimer = setTimeout(() => { |
||||
|
// 只有当有搜索词且与上次不同时才搜索
|
||||
|
if (keyword.trim() && keyword.trim() !== this.data.lastSearchKeyword) { |
||||
|
this.loadPosts(true); |
||||
|
} |
||||
|
}, 500); |
||||
}, |
}, |
||||
|
|
||||
// 随机生成标题
|
|
||||
getRandomTitle: function(id) { |
|
||||
const titles = [ |
|
||||
'微信小程序如何实现图片上传和预览功能?', |
|
||||
'uni-app开发中如何处理不同平台的兼容性问题?', |
|
||||
'JavaScript闭包的使用场景有哪些?', |
|
||||
'Vue3组合式API和选项式API该如何选择?', |
|
||||
'React Hooks在项目中的最佳实践', |
|
||||
'Node.js高并发场景下的性能优化方案', |
|
||||
'TypeScript在大型项目中的类型设计经验分享', |
|
||||
'微信小程序云开发数据库查询性能优化', |
|
||||
'前端工程化建设:从零搭建Webpack配置', |
|
||||
'移动端H5页面适配的最佳方案是什么?', |
|
||||
'如何优雅地处理前端错误监控和上报?', |
|
||||
'微前端架构在实际项目中的应用经验', |
|
||||
'Webpack5 Module Federation实战分享', |
|
||||
'前端代码质量保证:ESLint + Prettier + Husky', |
|
||||
'跨端开发框架选型:Flutter vs React Native vs uni-app' |
|
||||
]; |
|
||||
return titles[id % titles.length] || titles[0]; |
|
||||
}, |
|
||||
|
// 搜索确认
|
||||
|
onSearchConfirm: function (e) { |
||||
|
const keyword = e.detail.value.trim(); |
||||
|
this.setData({ |
||||
|
searchKeyword: keyword |
||||
|
}); |
||||
|
|
||||
// 随机生成摘要
|
|
||||
getRandomSummary: function(id) { |
|
||||
const summaries = [ |
|
||||
'我正在开发一个微信小程序,需要实现图片上传功能,并且能够在上传前预览图片。请问有什么好的实现方案吗?上传的图片大小限制和格式有什么建议?', |
|
||||
'最近在做一个uni-app项目,需要同时兼容微信小程序和H5,遇到了一些样式和API兼容性问题,大家有什么好的解决方案吗?', |
|
||||
'在实际项目中经常使用闭包,但对其原理和应用场景理解还不够深入,想请教一下大家在项目中都是如何使用闭包的?', |
|
||||
'公司新项目准备使用Vue3,对于组合式API和选项式API的选择有些纠结,大家有什么建议吗?各自的使用场景是什么?', |
|
||||
'React Hooks确实很方便,但在大型项目中如何合理组织Hooks,避免过度使用导致代码难以维护?', |
|
||||
'我们的Node.js服务在高并发场景下性能表现不佳,有哪些常见的性能优化方案可以参考?', |
|
||||
'项目准备从JavaScript迁移到TypeScript,在类型设计方面有什么经验可以分享吗?如何设计合理的泛型和接口?' |
|
||||
]; |
|
||||
return summaries[id % summaries.length] || summaries[0]; |
|
||||
|
// 如果搜索词为空,加载所有帖子
|
||||
|
if (!keyword) { |
||||
|
this.loadPosts(true); |
||||
|
} else { |
||||
|
// 只有当搜索词与上次不同时才搜索
|
||||
|
if (keyword !== this.data.lastSearchKeyword) { |
||||
|
this.loadPosts(true); |
||||
|
} |
||||
|
} |
||||
}, |
}, |
||||
|
|
||||
// 随机生成用户名
|
|
||||
getRandomUsername: function() { |
|
||||
const usernames = [ |
|
||||
'前端工程师', '技术爱好者', '小程序开发', '全栈程序员', |
|
||||
'架构师老王', '代码艺术家', '算法工程师', '产品经理', |
|
||||
'UI设计师', '测试工程师', '运维小哥', '数据分析师' |
|
||||
]; |
|
||||
return usernames[Math.floor(Math.random() * usernames.length)]; |
|
||||
|
// 清空搜索 - 优化版
|
||||
|
clearSearch: function () { |
||||
|
// 如果当前已经有搜索词,才执行清空操作
|
||||
|
if (this.data.searchKeyword) { |
||||
|
this.setData({ |
||||
|
searchKeyword: '' |
||||
|
}, () => { |
||||
|
// 清空后立即加载所有帖子
|
||||
|
this.loadPosts(true); |
||||
|
}); |
||||
|
} |
||||
}, |
}, |
||||
|
|
||||
// 随机生成时间
|
|
||||
getRandomTime: function(type = 'normal') { |
|
||||
const times = type === 'short' |
|
||||
? ['5分钟前', '10分钟前', '半小时前', '1小时前'] |
|
||||
: ['2小时前', '5小时前', '昨天', '2天前', '3天前', '一周前']; |
|
||||
return times[Math.floor(Math.random() * times.length)]; |
|
||||
|
// 跳转到详情页
|
||||
|
goToDetail: function (e) { |
||||
|
const postId = e.currentTarget.dataset.id; |
||||
|
wx.navigateTo({ |
||||
|
url: `/pagesB/pages/onlineAsk/onlineAsk?id=${postId}` |
||||
|
}); |
||||
}, |
}, |
||||
|
|
||||
// 随机生成标签
|
|
||||
getRandomTags: function() { |
|
||||
const allTags = [ |
|
||||
'微信小程序', '前端开发', 'JavaScript', 'Vue.js', 'React', |
|
||||
'Node.js', 'TypeScript', 'uni-app', '性能优化', '工程化', |
|
||||
'移动端', 'H5', 'CSS', 'Webpack', 'Git' |
|
||||
]; |
|
||||
|
|
||||
const count = Math.floor(Math.random() * 3) + 1; |
|
||||
const tags = []; |
|
||||
const usedIndices = new Set(); |
|
||||
|
|
||||
for (let i = 0; i < count; i++) { |
|
||||
let index; |
|
||||
do { |
|
||||
index = Math.floor(Math.random() * allTags.length); |
|
||||
} while (usedIndices.has(index)); |
|
||||
|
|
||||
usedIndices.add(index); |
|
||||
tags.push(allTags[index]); |
|
||||
} |
|
||||
|
// 图片预览功能
|
||||
|
previewImage: function (e) { |
||||
|
const postIndex = e.currentTarget.dataset.postindex; |
||||
|
const imageIndex = e.currentTarget.dataset.imageindex; |
||||
|
const post = this.data.posts[postIndex]; |
||||
|
|
||||
return tags; |
|
||||
}, |
|
||||
|
if (!post || !post.images || post.images.length === 0) return; |
||||
|
|
||||
// 下拉刷新
|
|
||||
onRefresh: function() { |
|
||||
this.setData({ refreshing: true }); |
|
||||
this.loadPosts(true); |
|
||||
|
// 构建完整的图片URL数组
|
||||
|
const urls = post.images.map(img => this.data.baseUrl + img); |
||||
|
|
||||
|
wx.previewImage({ |
||||
|
current: urls[imageIndex], // 当前显示图片的链接
|
||||
|
urls: urls // 需要预览的图片链接列表
|
||||
|
}); |
||||
}, |
}, |
||||
|
|
||||
// 滚动到底部加载更多
|
|
||||
loadMore: function() { |
|
||||
if (!this.data.hasMore || this.data.loadingMore) return; |
|
||||
|
// 显示发布模态框
|
||||
|
createPost: function () { |
||||
|
// 检查登录状态(如果需要)
|
||||
|
// 这里可以添加登录检查逻辑
|
||||
|
|
||||
this.setData({ |
this.setData({ |
||||
page: this.data.page + 1 |
|
||||
}, () => { |
|
||||
this.loadPosts(); |
|
||||
|
showPostModal: true |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 筛选切换
|
|
||||
changeFilter: function(e) { |
|
||||
const filterType = e.currentTarget.dataset.type; |
|
||||
if (this.data.currentFilter === filterType) return; |
|
||||
|
|
||||
|
// 隐藏发布模态框
|
||||
|
hidePostModal: function () { |
||||
|
if (this.data.isSubmitting) return; |
||||
this.setData({ |
this.setData({ |
||||
currentFilter: filterType, |
|
||||
searchKeyword: '' // 切换筛选时清空搜索
|
|
||||
}, () => { |
|
||||
this.loadPosts(true); |
|
||||
|
showPostModal: false, |
||||
|
postTitle: '', |
||||
|
postContent: '', |
||||
|
postImages: [] |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 搜索输入
|
|
||||
onSearchInput: function(e) { |
|
||||
this.setData({ searchKeyword: e.detail.value }); |
|
||||
|
|
||||
// 防抖搜索
|
|
||||
clearTimeout(this.searchTimer); |
|
||||
this.searchTimer = setTimeout(() => { |
|
||||
if (e.detail.value.trim()) { |
|
||||
this.loadPosts(true); |
|
||||
} |
|
||||
}, 300); |
|
||||
}, |
|
||||
|
// 阻止事件冒泡
|
||||
|
stopPropagation: function () {}, |
||||
|
|
||||
// 搜索确认
|
|
||||
onSearchConfirm: function(e) { |
|
||||
const keyword = e.detail.value.trim(); |
|
||||
if (keyword) { |
|
||||
this.setData({ searchKeyword: keyword }); |
|
||||
this.loadPosts(true); |
|
||||
} |
|
||||
|
// 标题输入
|
||||
|
onPostTitleInput: function (e) { |
||||
|
this.setData({ |
||||
|
postTitle: e.detail.value |
||||
|
}); |
||||
}, |
}, |
||||
|
|
||||
// 清空搜索
|
|
||||
clearSearch: function() { |
|
||||
|
// 内容输入
|
||||
|
onPostContentInput: function (e) { |
||||
this.setData({ |
this.setData({ |
||||
searchKeyword: '', |
|
||||
currentFilter: 'all' |
|
||||
}, () => { |
|
||||
this.loadPosts(true); |
|
||||
|
postContent: e.detail.value |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 跳转到详情页
|
|
||||
goToDetail: function(e) { |
|
||||
const postId = e.currentTarget.dataset.id; |
|
||||
wx.navigateTo({ |
|
||||
url: `/pages/forum/detail/detail?id=${postId}`, |
|
||||
success: () => { |
|
||||
// 记录浏览历史
|
|
||||
this.recordViewHistory(postId); |
|
||||
|
// 选择图片
|
||||
|
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); |
||||
} |
} |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 创建新帖子
|
|
||||
createPost: function() { |
|
||||
wx.navigateTo({ |
|
||||
url: '/pages/forum/create/create' |
|
||||
|
// 移除图片
|
||||
|
removeImage: function (e) { |
||||
|
const index = e.currentTarget.dataset.index; |
||||
|
const postImages = [...this.data.postImages]; |
||||
|
postImages.splice(index, 1); |
||||
|
this.setData({ |
||||
|
postImages |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 记录浏览历史
|
|
||||
recordViewHistory: function(postId) { |
|
||||
try { |
|
||||
const history = wx.getStorageSync('forumViewHistory') || []; |
|
||||
const index = history.findIndex(item => item.id === postId); |
|
||||
|
// 提交帖子
|
||||
|
submitPost: function () { |
||||
|
const { postTitle, postContent, postImages } = this.data; |
||||
|
|
||||
if (index !== -1) { |
|
||||
history.splice(index, 1); |
|
||||
} |
|
||||
|
// 验证输入
|
||||
|
if (!postTitle.trim()) { |
||||
|
wx.showToast({ |
||||
|
title: '请输入标题', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
history.unshift({ |
|
||||
id: postId, |
|
||||
timestamp: Date.now() |
|
||||
|
if (!postContent.trim()) { |
||||
|
wx.showToast({ |
||||
|
title: '请输入内容', |
||||
|
icon: 'none' |
||||
}); |
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
// 只保留最近50条记录
|
|
||||
if (history.length > 50) { |
|
||||
history.pop(); |
|
||||
} |
|
||||
|
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); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
wx.setStorageSync('forumViewHistory', history); |
|
||||
} catch (error) { |
|
||||
console.error('记录浏览历史失败:', error); |
|
||||
|
// 处理图片上传
|
||||
|
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() { |
|
||||
// 这里可以检查是否有新数据需要刷新
|
|
||||
// 例如:从详情页返回时刷新点赞状态等
|
|
||||
|
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() { |
|
||||
|
onPullDownRefresh: function () { |
||||
this.onRefresh(); |
this.onRefresh(); |
||||
}, |
}, |
||||
|
|
||||
onReachBottom: function() { |
|
||||
|
onReachBottom: function () { |
||||
this.loadMore(); |
this.loadMore(); |
||||
}, |
|
||||
|
|
||||
onPageScroll: function(e) { |
|
||||
// 可以在这里处理页面滚动时的效果
|
|
||||
} |
} |
||||
}); |
}); |
||||
@ -1,3 +1,4 @@ |
|||||
{ |
{ |
||||
|
"navigationBarTitleText": "问答论坛", |
||||
"usingComponents": {} |
"usingComponents": {} |
||||
} |
} |
||||
@ -1,202 +1,208 @@ |
|||||
<view class="forum-list-page"> |
|
||||
|
|
||||
<!-- 顶部栏 --> |
|
||||
<view class="header"> |
|
||||
<view class="title">问答论坛</view> |
|
||||
<button class="add-btn" bindtap="createPost">提问</button> |
|
||||
|
<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> |
||||
|
|
||||
<!-- 筛选栏 --> |
|
||||
<view class="filter-bar"> |
|
||||
<scroll-view class="filter-scroll" scroll-x> |
|
||||
<view class="filter-list"> |
|
||||
<view |
|
||||
class="filter-item {{currentFilter === 'all' ? 'active' : ''}}" |
|
||||
bindtap="changeFilter" |
|
||||
data-type="all" |
|
||||
> |
|
||||
<text>全部</text> |
|
||||
</view> |
|
||||
<view |
|
||||
class="filter-item {{currentFilter === 'unsolved' ? 'active' : ''}}" |
|
||||
bindtap="changeFilter" |
|
||||
data-type="unsolved" |
|
||||
> |
|
||||
<text>待解决</text> |
|
||||
</view> |
|
||||
<view |
|
||||
class="filter-item {{currentFilter === 'solved' ? 'active' : ''}}" |
|
||||
bindtap="changeFilter" |
|
||||
data-type="solved" |
|
||||
> |
|
||||
<text>已解决</text> |
|
||||
</view> |
|
||||
<view |
|
||||
class="filter-item {{currentFilter === 'hot' ? 'active' : ''}}" |
|
||||
bindtap="changeFilter" |
|
||||
data-type="hot" |
|
||||
> |
|
||||
<text>热门</text> |
|
||||
</view> |
|
||||
<view |
|
||||
class="filter-item {{currentFilter === 'mine' ? 'active' : ''}}" |
|
||||
bindtap="changeFilter" |
|
||||
data-type="mine" |
|
||||
> |
|
||||
<text>我的帖子</text> |
|
||||
</view> |
|
||||
</view> |
|
||||
</scroll-view> |
|
||||
</view> |
|
||||
|
<!-- 主内容区 --> |
||||
|
<view class="main-content"> |
||||
|
|
||||
<!-- 搜索框 --> |
<!-- 搜索框 --> |
||||
<view class="search-container"> |
|
||||
<view class="search-input-wrapper"> |
|
||||
<image class="search-icon" src="/images/search.png" mode="aspectFit"></image> |
|
||||
<input |
|
||||
class="search-input" |
|
||||
placeholder="搜索问题或关键词..." |
|
||||
value="{{searchKeyword}}" |
|
||||
bindinput="onSearchInput" |
|
||||
bindconfirm="onSearchConfirm" |
|
||||
confirm-type="search" |
|
||||
/> |
|
||||
|
<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"> |
<view class="search-clear" wx:if="{{searchKeyword}}" bindtap="clearSearch"> |
||||
× |
|
||||
|
<text class="clear-text">×</text> |
||||
</view> |
</view> |
||||
</view> |
</view> |
||||
</view> |
</view> |
||||
|
|
||||
<!-- 帖子列表 --> |
<!-- 帖子列表 --> |
||||
<scroll-view |
|
||||
class="post-list-container" |
|
||||
scroll-y |
|
||||
enable-back-to-top |
|
||||
bindscrolltolower="loadMore" |
|
||||
refresher-enabled="{{true}}" |
|
||||
refresher-triggered="{{refreshing}}" |
|
||||
bindrefresherrefresh="onRefresh" |
|
||||
> |
|
||||
|
<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 && posts.length === 0}}"> |
|
||||
<image class="empty-image" src="/images/empty-forum.png" mode="aspectFit"></image> |
|
||||
|
<view class="empty-state" wx:if="{{!loading && !searchLoading && posts.length === 0}}"> |
||||
<view class="empty-text" wx:if="{{searchKeyword}}"> |
<view class="empty-text" wx:if="{{searchKeyword}}"> |
||||
没有找到"{{searchKeyword}}"相关的问题 |
没有找到"{{searchKeyword}}"相关的问题 |
||||
</view> |
</view> |
||||
<view class="empty-text" wx:else> |
<view class="empty-text" wx:else> |
||||
暂无帖子,快来发布第一个问题吧! |
|
||||
|
这里还没有问题,快来发布第一个吧! |
||||
</view> |
</view> |
||||
<button class="empty-btn" bindtap="createPost" wx:if="{{!searchKeyword}}">发布问题</button> |
|
||||
<button class="empty-btn" bindtap="clearSearch" wx:else>清空搜索</button> |
|
||||
|
<button class="empty-btn" bindtap="createPost" wx:if="{{!searchKeyword}}"> |
||||
|
发布问题 |
||||
|
</button> |
||||
</view> |
</view> |
||||
|
|
||||
<!-- 帖子列表 --> |
<!-- 帖子列表 --> |
||||
<view class="post-list" wx:if="{{posts.length > 0}}"> |
<view class="post-list" wx:if="{{posts.length > 0}}"> |
||||
<block wx:for="{{posts}}" wx:key="id"> |
<block wx:for="{{posts}}" wx:key="id"> |
||||
<view class="post-item" data-id="{{item.id}}" bindtap="goToDetail"> |
|
||||
|
|
||||
<!-- 左侧状态栏 --> |
|
||||
<view class="post-status-side"> |
|
||||
<view class="vote-count"> |
|
||||
<view class="vote-number">{{item.likeCount}}</view> |
|
||||
<view class="vote-label">点赞</view> |
|
||||
</view> |
|
||||
<view class="reply-count"> |
|
||||
<view class="reply-number">{{item.replyCount}}</view> |
|
||||
<view class="reply-label">回答</view> |
|
||||
|
<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> |
||||
<view class="view-count"> |
|
||||
<view class="view-number">{{item.viewCount}}</view> |
|
||||
<view class="view-label">浏览</view> |
|
||||
|
<!-- 标题 --> |
||||
|
<view class="post-title-wrapper"> |
||||
|
<text class="post-title">{{item.title}}</text> |
||||
</view> |
</view> |
||||
</view> |
|
||||
|
|
||||
<!-- 右侧内容区 --> |
|
||||
<view class="post-content-main"> |
|
||||
|
|
||||
<!-- 帖子标题和状态 --> |
|
||||
<view class="post-header"> |
|
||||
<view class="post-title">{{item.title}}</view> |
|
||||
<view class="status-badge" wx:if="{{item.solved}}"> |
|
||||
<text class="solved-badge">已解决</text> |
|
||||
</view> |
|
||||
<view class="status-badge" wx:if="{{item.hot}}"> |
|
||||
<text class="hot-badge">热门</text> |
|
||||
</view> |
|
||||
|
<!-- 内容摘要 --> |
||||
|
<view class="post-summary"> |
||||
|
{{item.content}} |
||||
</view> |
</view> |
||||
|
|
||||
<!-- 帖子内容摘要 --> |
|
||||
<view class="post-summary">{{item.summary}}</view> |
|
||||
|
|
||||
<!-- 帖子元信息 --> |
|
||||
<view class="post-meta"> |
|
||||
|
|
||||
<!-- 用户信息 --> |
|
||||
<view class="user-info"> |
|
||||
<image class="user-avatar" src="{{item.avatar}}" mode="aspectFill"></image> |
|
||||
<text class="username">{{item.username}}</text> |
|
||||
<text class="separator">·</text> |
|
||||
<text class="post-time">{{item.time}}</text> |
|
||||
</view> |
|
||||
|
|
||||
<!-- 标签 --> |
|
||||
<view class="post-tags" wx:if="{{item.tags && item.tags.length > 0}}"> |
|
||||
<block wx:for="{{item.tags.slice(0, 2)}}" wx:key="index"> |
|
||||
<text class="tag">{{item}}</text> |
|
||||
|
<!-- 图片预览 --> |
||||
|
<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> |
</block> |
||||
<text class="more-tags" wx:if="{{item.tags.length > 2}}">+{{item.tags.length - 2}}</text> |
|
||||
</view> |
</view> |
||||
|
|
||||
</view> |
</view> |
||||
|
|
||||
<!-- 最后回复信息 --> |
|
||||
<view class="last-reply" wx:if="{{item.lastReply}}"> |
|
||||
<text class="last-reply-label">最后回复:</text> |
|
||||
<text class="last-reply-user">{{item.lastReply.username}}</text> |
|
||||
<text class="separator">·</text> |
|
||||
<text class="last-reply-time">{{item.lastReply.time}}</text> |
|
||||
|
<!-- 底部信息 --> |
||||
|
<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> |
</view> |
||||
|
|
||||
</view> |
</view> |
||||
</block> |
</block> |
||||
|
|
||||
<!-- 加载更多 --> |
<!-- 加载更多 --> |
||||
<view class="load-more" wx:if="{{hasMore && posts.length > 0}}"> |
<view class="load-more" wx:if="{{hasMore && posts.length > 0}}"> |
||||
<view class="loading-text" wx:if="{{!loadingMore}}"> |
|
||||
上拉加载更多 |
|
||||
|
<view class="loading-spinner" wx:if="{{!loadingMore}}"> |
||||
|
<text class="loading-text">上拉加载更多</text> |
||||
</view> |
</view> |
||||
<view class="loading-more" wx:else> |
|
||||
<image class="loading-icon" src="/images/loading.png" mode="aspectFit"></image> |
|
||||
加载中... |
|
||||
|
<view class="loading-spinner" wx:else> |
||||
|
<view class="spinner"></view> |
||||
|
<text class="loading-text">加载中...</text> |
||||
</view> |
</view> |
||||
</view> |
</view> |
||||
|
|
||||
<!-- 没有更多了 --> |
<!-- 没有更多了 --> |
||||
<view class="no-more" wx:if="{{!hasMore && posts.length > 0}}"> |
<view class="no-more" wx:if="{{!hasMore && posts.length > 0}}"> |
||||
<text>没有更多了</text> |
|
||||
|
<view class="no-more-line"></view> |
||||
|
<text class="no-more-text">已经到底了</text> |
||||
|
<view class="no-more-line"></view> |
||||
</view> |
</view> |
||||
|
|
||||
</view> |
</view> |
||||
|
|
||||
<!-- 底部占位 --> |
|
||||
<view class="bottom-placeholder"></view> |
|
||||
|
|
||||
</scroll-view> |
</scroll-view> |
||||
|
|
||||
<!-- 加载提示 --> |
|
||||
<view class="loading" wx:if="{{loading && posts.length === 0}}"> |
|
||||
<image class="loading-icon" src="/images/loading.png" mode="aspectFit"></image> |
|
||||
加载中... |
|
||||
</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 class="fab-container" wx:if="{{!searchKeyword}}"> |
|
||||
<view class="fab" bindtap="createPost"> |
|
||||
<image class="fab-icon" src="/images/add.png" mode="aspectFit"></image> |
|
||||
</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> |
||||
|
|
||||
|
<!-- 首次加载遮罩 --> |
||||
|
<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> |
</view> |
||||
1200
pagesB/pages/forumlist/forumlist.wxss
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,4 +1,5 @@ |
|||||
// var baseUrl = 'https://wx.chenhaitech.com/ymtx-prod-api'
|
// var baseUrl = 'https://wx.chenhaitech.com/ymtx-prod-api'
|
||||
var baseUrl = 'http://192.168.101.109:8080' |
|
||||
|
// var baseUrl = 'http://192.168.101.109:8080'
|
||||
|
var baseUrl = 'http://192.168.101.105:8082' |
||||
// var baseUrl = 'http://192.168.101.111:8081'
|
// var baseUrl = 'http://192.168.101.111:8081'
|
||||
module.exports = baseUrl |
module.exports = baseUrl |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue