Browse Source

专家列表,聊天

master
ZhaoYang 1 month ago
parent
commit
35cff55bb8
  1. BIN
      pagesA/images/add.png
  2. BIN
      pagesA/images/cuo.png
  3. BIN
      pagesA/images/ps.png
  4. BIN
      pagesA/images/wj.png
  5. BIN
      pagesA/images/xjp.png
  6. BIN
      pagesA/images/yy.png
  7. BIN
      pagesA/images/zp.png
  8. 151
      pagesA/pages/expert/expert.js
  9. 51
      pagesA/pages/expert/expert.wxml
  10. 2
      pagesA/pages/expert/expert.wxss
  11. 435
      pagesA/pages/expertChat/expertChat.js
  12. 125
      pagesA/pages/expertChat/expertChat.wxml
  13. 228
      pagesA/pages/expertChat/expertChat.wxss
  14. 2
      project.config.json
  15. 2
      utils/baseUrl.js

BIN
pagesA/images/add.png

After

Width: 200  |  Height: 200  |  Size: 4.4 KiB

BIN
pagesA/images/cuo.png

After

Width: 200  |  Height: 200  |  Size: 4.6 KiB

BIN
pagesA/images/ps.png

After

Width: 200  |  Height: 200  |  Size: 3.3 KiB

BIN
pagesA/images/wj.png

After

Width: 200  |  Height: 200  |  Size: 2.0 KiB

BIN
pagesA/images/xjp.png

After

Width: 200  |  Height: 200  |  Size: 4.6 KiB

BIN
pagesA/images/yy.png

After

Width: 200  |  Height: 200  |  Size: 4.9 KiB

BIN
pagesA/images/zp.png

After

Width: 200  |  Height: 200  |  Size: 2.7 KiB

151
pagesA/pages/expert/expert.js

@ -1,16 +1,15 @@
import http from '../../../utils/api'
const baseUr = require('../../../utils/baseUrl')
Page({
data: {
// 搜索关键词
searchKeyword: '',
// 当前筛选条件
currentFilter: 'all',
currentFilter: '全部',
// 所有专家数据
allExperts: [
{
allExperts: [{
id: 1,
name: '张建国',
title: '畜牧学博士',
@ -155,54 +154,50 @@ Page({
avatar: '/pagesA/images/3.png'
}
],
baseUr: baseUr,
formzj: {
isOnline: null,
realName: null,
expert: null,
expertiseArea: null
},
// 筛选后的专家列表
filteredExperts: [],
// 在线专家数量
onlineCount: 0,
// 当前选中的专家
currentExpert: null,
// 是否显示联系方式弹窗
showContactDialog: false
},
onLoad() {
this.sortAndFilterExperts()
this.getexpertsList()
},
onShow() {
// 页面显示时随机更新一些专家的在线状态(模拟实时状态)
this.updateOnlineStatus();
},
// 专家列表
getexpertsList(){
http.expertsList({
data:{},
success:res=>{
console.log(111,res);
}
})
getexpertsList() {
http.expertsList({
data: this.data.formzj,
success: res => {
console.log(111, res);
this.setData({
allExperts: res.rows
})
}
})
},
// 更新在线状态
updateOnlineStatus() {
const experts = this.data.allExperts.map(expert => ({
...expert,
online: Math.random() > 0.4 // 60%的概率在线
}));
this.setData({
allExperts: experts
}, () => {
this.sortAndFilterExperts();
});
},
// 处理搜索输入
onSearchInput(e) {
@ -223,81 +218,28 @@ Page({
// 更改筛选条件
changeFilter(e) {
console.log(2222, e);
const filter = e.currentTarget.dataset.filter;
this.setData({
currentFilter: filter
});
this.sortAndFilterExperts();
},
// 排序和筛选专家
sortAndFilterExperts() {
const { allExperts, searchKeyword, currentFilter } = this.data;
let filtered = [...allExperts];
// 关键词搜索
if (searchKeyword.trim()) {
const keyword = searchKeyword.toLowerCase();
filtered = filtered.filter(expert =>
expert.name.toLowerCase().includes(keyword) ||
expert.expertise.toLowerCase().includes(keyword) ||
expert.tags.some(tag => tag.toLowerCase().includes(keyword)) ||
expert.institution.toLowerCase().includes(keyword)
);
if (filter == '全部') {
this.data.formzj.isOnline = null
this.getexpertsList()
} else {
this.data.formzj.isOnline = filter
this.getexpertsList()
}
// 筛选条件
switch (currentFilter) {
case 'online':
filtered = filtered.filter(expert => expert.online);
break;
case 'offline':
filtered = filtered.filter(expert => !expert.online);
break;
case 'senior':
filtered = filtered.filter(expert => expert.senior);
break;
case 'veterinary':
filtered = filtered.filter(expert =>
expert.expertise.includes('病') ||
expert.tags.some(tag => tag.includes('病') || tag.includes('医'))
);
break;
}
// 排序:在线优先 > 资深专家 > 经验丰富 > 解决率高
filtered.sort((a, b) => {
// 在线状态优先
if (a.online !== b.online) {
return a.online ? -1 : 1;
}
// 资深专家优先
if (a.senior !== b.senior) {
return a.senior ? -1 : 1;
}
// 经验丰富优先
if (a.experience !== b.experience) {
return b.experience - a.experience;
}
// 解决率高优先
return (b.successRate || 0) - (a.successRate || 0);
});
// 计算在线人数
const onlineCount = filtered.filter(expert => expert.online).length;
this.setData({
filteredExperts: filtered,
onlineCount: onlineCount
});
},
// 显示专家联系方式
showContactInfo(e) {
console.log(234,e);
const index = e.currentTarget.dataset.index;
const expert = this.data.filteredExperts[index];
const expert = this.data.allExperts[index];
console.log(33333,expert);
this.setData({
currentExpert: expert,
showContactDialog: true
@ -320,7 +262,7 @@ Page({
makePhoneCall(e) {
const phone = e.currentTarget.dataset.phone;
const cleanPhone = phone.replace(/-/g, '');
wx.showModal({
title: '拨打电话',
content: `确定要拨打 ${phone} 吗?`,
@ -350,7 +292,7 @@ Page({
// 复制邮箱
copyEmail(e) {
const email = e.currentTarget.dataset.email;
wx.setClipboardData({
data: email,
success: () => {
@ -382,12 +324,11 @@ Page({
// 开始咨询
startConsultation() {
const expert = this.data.currentExpert;
wx.showModal({
title: '咨询确认',
content: expert.online
? `确定要立即咨询 ${expert.name} 专家吗?`
: `确定要预约咨询 ${expert.name} 专家吗?`,
content: expert.online ?
`确定要立即咨询 ${expert.name} 专家吗?` : `确定要预约咨询 ${expert.name} 专家吗?`,
success: (res) => {
if (res.confirm) {
if (expert.online) {
@ -396,7 +337,7 @@ Page({
icon: 'loading',
duration: 2000
});
setTimeout(() => {
// 跳转一对一咨询专家
wx.navigateTo({

51
pagesA/pages/expert/expert.wxml

@ -54,29 +54,20 @@
<!-- 筛选标签 -->
<scroll-view class="filter-scroll" scroll-x>
<view class="filter-tags">
<view class="filter-tag {{currentFilter === 'all' ? 'active' : ''}}" bindtap="changeFilter" data-filter="all">
<view class="filter-tag {{currentFilter === '全部' ? 'active' : ''}}" bindtap="changeFilter" data-filter="全部">
<text>全部专家</text>
<view class="tag-count">{{allExperts.length}}</view>
</view>
<view class="filter-tag {{currentFilter === 'online' ? 'active' : ''}}" bindtap="changeFilter" data-filter="online">
<view class="filter-tag {{currentFilter === '在线' ? 'active' : ''}}" bindtap="changeFilter" data-filter="在线">
<text>在线专家</text>
<view class="tag-count online-count">{{onlineCount}}</view>
</view>
<view class="filter-tag {{currentFilter === 'offline' ? 'active' : ''}}" bindtap="changeFilter" data-filter="offline">
<view class="filter-tag {{currentFilter === '离线' ? 'active' : ''}}" bindtap="changeFilter" data-filter="离线">
<text>离线专家</text>
<view class="tag-count">{{allExperts.length - onlineCount}}</view>
</view>
<view class="filter-tag {{currentFilter === 'senior' ? 'active' : ''}}" bindtap="changeFilter" data-filter="senior">
<text>资深专家</text>
</view>
<view class="filter-tag {{currentFilter === 'veterinary' ? 'active' : ''}}" bindtap="changeFilter" data-filter="veterinary">
<text>兽医专家</text>
</view>
</view>
</scroll-view>
</view>
@ -86,13 +77,13 @@
<!-- 列表标题 -->
<view class="list-header">
<text class="list-title">专家列表</text>
<text class="list-count">已为您找到 {{filteredExperts.length}} 位专家</text>
<text class="list-count">已为您找到 {{allExperts.length}} 位专家</text>
</view>
<!-- 专家卡片列表 -->
<scroll-view class="expert-cards" scroll-y>
<!-- 空状态 -->
<view wx:if="{{filteredExperts.length === 0}}" class="empty-state">
<view wx:if="{{allExperts.length === 0}}" class="empty-state">
<image src="/pagesA/images/kzt.png" class="empty-image"></image>
<text class="empty-title">暂无相关专家</text>
<text class="empty-desc">换个关键词试试,或联系客服为您推荐</text>
@ -100,14 +91,14 @@
<!-- 专家卡片 -->
<view wx:else>
<block wx:for="{{filteredExperts}}" wx:key="id">
<block wx:for="{{allExperts}}" wx:key="id">
<view class="expert-card">
<!-- 专家头像和在线状态 -->
<view class="card-left">
<view class="avatar-container">
<image src="{{item.avatar}}" class="expert-avatar" mode="aspectFill"></image>
<view class="online-badge {{item.online ? 'online' : 'offline'}}">
{{item.online ? '在线' : '离线'}}
<image src="{{baseUr+item.avatar}}" class="expert-avatar" mode="aspectFill"></image>
<view class="online-badge {{item.isOnline=='在线' ? 'online' : 'offline'}}">
{{item.isOnline}}
</view>
</view>
</view>
@ -115,27 +106,27 @@
<!-- 专家信息 -->
<view class="card-middle">
<view class="name-title-row">
<text class="expert-name">{{item.name}}</text>
<text class="expert-title">{{item.title}}</text>
<text class="expert-name">{{item.realName}}</text>
<text class="expert-title">{{item.expert}}</text>
</view>
<!-- 专门的信息展示区域 -->
<view class="info-display">
<view class="info-item">
<text class="info-label">擅长领域:</text>
<text class="info-value">{{item.expertise}}</text>
<text class="info-value">{{item.expertiseArea}}</text>
</view>
<view class="info-item">
<text class="info-label">从业经验:</text>
<text class="info-value">{{item.experience}}年</text>
<text class="info-value">{{item.workExperience}}</text>
</view>
</view>
</view>
<!-- 点击按钮 -->
<view class="contact-btn {{item.online ? 'online-btn' : 'offline-btn'}}" bindtap="showContactInfo" data-index="{{index}}">
{{item.online ? '立即咨询' : '查看联系'}}
<view class="contact-btn {{item.isOnline=='在线' ? 'online-btn' : 'offline-btn'}}" bindtap="showContactInfo" data-index="{{index}}">
{{item.isOnline=='在线' ? '立即咨询' : '查看联系'}}
</view>
</view>
</block>
@ -154,13 +145,13 @@
<image src="/pages/images/tx.png" class="modal-avatar"></image>
<view class="expert-intro">
<view class="modallei">
<view class="modal-name">{{currentExpert.name}}</view>
<view class="modal-title">{{currentExpert.title}}</view>
<view class="modal-name">{{currentExpert.realName}}</view>
<view class="modal-title">{{currentExpert.expert}}</view>
</view>
<view class="title-status">
<view class="modal-status {{currentExpert.online ? 'online' : 'offline'}}">
<view class="modal-status {{currentExpert.isOnline='在线' ? 'online' : 'offline'}}">
<view class="status-dot"></view>
{{currentExpert.online ? '在线可咨询' : '暂时离线'}}
{{currentExpert.isOnline=='在线' ? '在线可咨询' : '暂时离线'}}
</view>
</view>
</view>
@ -184,10 +175,10 @@
</view>
<view class="contact-info">
<text class="contact-label">联系电话</text>
<text class="contact-value">{{currentExpert.phone}}</text>
<text class="contact-value">{{currentExpert.iphone}}</text>
<text class="contact-desc">可直接拨打电话咨询</text>
</view>
<button class="action-btn call-btn" bindtap="makePhoneCall" data-phone="{{currentExpert.phone}}">
<button class="action-btn call-btn" bindtap="makePhoneCall" data-phone="{{currentExpert.iphone}}">
拨打
</button>
</view>

2
pagesA/pages/expert/expert.wxss

@ -198,7 +198,7 @@
.filter-tag {
display: inline-flex;
align-items: center;
padding: 16rpx 28rpx;
padding: 16rpx 24rpx;
margin-right: 20rpx;
background: #f8faf9;
border-radius: 30rpx;

435
pagesA/pages/expertChat/expertChat.js

@ -1,6 +1,4 @@
// pages/consult/consult.js
// import http from '../../../utils/api' // 您的注释代码保留
// import http from '../../../utils/api'
Page({
data: {
// 专家信息
@ -48,6 +46,7 @@ Page({
showDateDivider: true,
todayDate: '',
loading: false,
loadingMore: false,
// 滚动相关
isScrolling: false,
@ -64,8 +63,11 @@ Page({
pageSize: 20,
hasMore: true,
// 时间显示间隔(分钟)
timeInterval: 5
// 时间显示间隔(分钟) - 微信默认为5分钟
timeInterval: 5,
// 用于存储最后一条显示时间的消息的时间戳
lastShowTimeStamp: 0
},
onLoad: function(options) {
@ -258,6 +260,51 @@ Page({
// this.loadDefaultExpertInfo(expertId);
// }
// });
// 模拟数据
setTimeout(() => {
const experts = [
{
id: 1,
name: '张明专家',
title: '资深畜牧兽医',
expertise: '牛羊疾病防治',
avatar: '/images/avatars/expert1.png',
online: true,
phone: '13800138000'
},
{
id: 2,
name: '李华专家',
title: '高级畜牧师',
expertise: '饲料营养',
avatar: '/images/avatars/expert2.png',
online: false,
phone: '13800138001'
},
{
id: 3,
name: '王强专家',
title: '兽医专家',
expertise: '疾病防治',
avatar: '/images/avatars/expert3.png',
online: true,
phone: '13800138002'
}
];
const expertInfo = experts.find(e => e.id == expertId) || experts[0];
this.setData({
expertInfo,
loading: false
});
wx.hideLoading();
// 加载聊天记录
this.loadChatHistory();
}, 500);
},
// 加载默认专家信息(当接口失败时使用)
@ -331,9 +378,17 @@ Page({
// const messages = res.data.list || [];
// if (messages.length > 0) {
// // 处理消息时间显示
// // 处理消息时间显示 - 使用完全修复的时间处理逻辑
// const processedMessages = this.processMessageTimes(messages);
// // 调试:查看处理后的消息
// console.log('处理后的消息数据:', processedMessages.map(msg => ({
// id: msg.id,
// showTime: msg.showTime,
// time: this.formatTime(msg.timestamp),
// timestamp: msg.timestamp
// })));
// this.setData({
// messageList: processedMessages,
// loading: false,
@ -359,6 +414,114 @@ Page({
// this.loadMockChatHistory();
// }
// });
// 模拟数据 - 修复时间戳问题
setTimeout(() => {
const now = Date.now();
let mockMessages = [];
if (page === 1) {
// 第一页数据 - 测试不同时间间隔的消息
mockMessages = [
{
id: 'msg-1',
sender: 'expert',
type: 'text',
content: '您好,我是张明专家,有什么可以帮您?',
timestamp: now - 10 * 60 * 1000, // 10分钟前 - 应该显示时间
status: 'success'
},
{
id: 'msg-2',
sender: 'user',
type: 'text',
content: '您好,我养的牛最近食欲不振,请问是什么原因?',
timestamp: now - 9 * 60 * 1000, // 9分钟前 - 不显示时间(与上条间隔1分钟)
status: 'success'
},
{
id: 'msg-3',
sender: 'expert',
type: 'text',
content: '可能是饲料问题或环境变化引起的,请描述一下具体情况。',
timestamp: now - 7 * 60 * 1000, // 7分钟前 - 显示时间(与上条间隔2分钟,但与第一条间隔3分钟)
status: 'success'
},
{
id: 'msg-4',
sender: 'user',
type: 'text',
content: '具体症状是拉稀,体温偏高,精神状态不好。',
timestamp: now - 2 * 60 * 1000, // 2分钟前 - 显示时间(与上条间隔5分钟)
status: 'success'
},
{
id: 'msg-5',
sender: 'expert',
type: 'text',
content: '明白了,建议您调整饲料配方,添加一些益生菌。',
timestamp: now - 1 * 60 * 1000, // 1分钟前 - 不显示时间(与上条间隔1分钟)
status: 'success'
}
];
} else {
// 更多数据
mockMessages = [
{
id: 'msg-6',
sender: 'user',
type: 'text',
content: '之前喂的是玉米秸秆,需要换饲料吗?',
timestamp: now - 30 * 60 * 1000, // 30分钟前
status: 'success'
},
{
id: 'msg-7',
sender: 'expert',
type: 'text',
content: '可以尝试添加一些豆粕和麦麸,改善营养结构。',
timestamp: now - 25 * 60 * 1000, // 25分钟前
status: 'success'
}
];
}
if (mockMessages.length > 0) {
// 处理消息时间显示 - 使用完全修复的时间处理逻辑
const processedMessages = this.processMessageTimes(mockMessages);
// 调试:查看处理后的消息
console.log('处理后的消息数据:', processedMessages.map(msg => ({
id: msg.id,
showTime: msg.showTime,
time: this.formatTime(msg.timestamp),
timestamp: msg.timestamp,
sender: msg.sender
})));
let newMessageList = [];
if (page === 1) {
newMessageList = processedMessages;
} else {
newMessageList = [...processedMessages, ...this.data.messageList];
}
this.setData({
messageList: newMessageList,
loading: false,
loadingMore: false,
hasMore: mockMessages.length >= pageSize
}, () => {
if (page === 1) {
// 滚动到底部
this.scrollToBottom(true);
}
});
} else {
// 如果没有历史记录,添加一条欢迎消息
this.addWelcomeMessage();
}
}, 800);
},
// 加载模拟聊天记录(当接口失败时使用)
@ -371,42 +534,40 @@ Page({
sender: 'expert',
type: 'text',
content: '您好,我是张明专家,有什么可以帮您?',
timestamp: now - 3600000, // 1小时
showTime: true
timestamp: now - 10 * 60 * 1000, // 10分钟
status: 'success'
},
{
id: 'msg-2',
sender: 'user',
type: 'text',
content: '您好,我养的牛最近食欲不振,请问是什么原因?',
timestamp: now - 1800000, // 30分钟前
status: 'success',
showTime: false
timestamp: now - 8 * 60 * 1000, // 8分钟前
status: 'success'
},
{
id: 'msg-3',
sender: 'expert',
type: 'text',
content: '可能是饲料问题或环境变化引起的,请描述一下具体情况。',
timestamp: now - 1200000, // 20分钟前
showTime: false
timestamp: now - 6 * 60 * 1000, // 6分钟前
status: 'success'
},
{
id: 'msg-4',
sender: 'user',
type: 'text',
content: '具体症状是...',
timestamp: now - 600000, // 10分钟前
status: 'success',
showTime: true
timestamp: now - 4 * 60 * 1000, // 4分钟前
status: 'success'
},
{
id: 'msg-5',
sender: 'expert',
type: 'text',
content: '明白了,建议您调整饲料配方。',
timestamp: now - 300000, // 5分钟前
showTime: false
timestamp: now - 2 * 60 * 1000, // 2分钟前
status: 'success'
}
];
@ -430,11 +591,13 @@ Page({
type: 'text',
content: `您好,我是${this.data.expertInfo.name},有什么可以帮您?`,
timestamp: Date.now(),
showTime: true
status: 'success'
};
const processedMessage = this.processSingleMessageTime(welcomeMessage, []);
this.setData({
messageList: [welcomeMessage],
messageList: [processedMessage],
loading: false
}, () => {
// 滚动到底部
@ -442,15 +605,15 @@ Page({
});
},
// 处理消息时间显示 - 优化微信样式
// 完全修复:处理消息时间显示逻辑(类似微信)
processMessageTimes: function(messages) {
if (!messages || messages.length === 0) return [];
// 按时间排序(从早到晚)
const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp);
let lastShowTime = 0;
const processedMessages = [];
let lastShowTime = null; // 最后一条显示时间消息的时间戳
for (let i = 0; i < sortedMessages.length; i++) {
const msg = { ...sortedMessages[i] };
@ -465,23 +628,62 @@ Page({
msg.showTime = true;
lastShowTime = msg.timestamp;
} else {
// 计算与上一条显示时间的时间差(分钟)
// 计算与最后一条显示时间的消息的时间差(分钟)
const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
// 超过5分钟显示时间(微信默认5分钟)
msg.showTime = timeDiffMinutes >= this.data.timeInterval;
if (msg.showTime) {
// 超过5分钟显示时间
if (timeDiffMinutes >= this.data.timeInterval) {
msg.showTime = true;
lastShowTime = msg.timestamp;
} else {
msg.showTime = false;
}
}
processedMessages.push(msg);
}
// 存储最后一条显示时间的消息的时间戳
if (lastShowTime) {
this.setData({ lastShowTimeStamp: lastShowTime });
}
return processedMessages;
},
// 处理单条消息的时间显示(添加新消息时调用)
processSingleMessageTime: function(message, messageList) {
const msg = { ...message };
// 确保时间戳是有效数字
if (!msg.timestamp || isNaN(msg.timestamp) || msg.timestamp <= 0) {
msg.timestamp = Date.now();
}
if (messageList.length === 0) {
// 第一条消息
msg.showTime = true;
this.setData({ lastShowTimeStamp: msg.timestamp });
return msg;
}
// 获取最后一条显示时间的消息
const lastShowTime = this.data.lastShowTimeStamp;
// 计算时间差(分钟)
const timeDiffMinutes = (msg.timestamp - lastShowTime) / (1000 * 60);
// 超过5分钟显示时间
if (timeDiffMinutes >= this.data.timeInterval) {
msg.showTime = true;
this.setData({ lastShowTimeStamp: msg.timestamp });
} else {
msg.showTime = false;
}
return msg;
},
// 返回上一页
goBack: function() {
wx.navigateBack();
@ -555,11 +757,10 @@ Page({
type: 'text',
content: content,
timestamp: Date.now(),
status: 'sending',
showTime: this.shouldShowTime()
status: 'sending'
};
// 添加到消息列表
// 处理时间显示并添加到消息列表
this.addMessageToList(newMessage);
// 清空输入框
@ -605,46 +806,27 @@ Page({
// });
// }
// });
},
// 判断是否显示时间(基于时间间隔)
shouldShowTime: function() {
const { messageList, timeInterval } = this.data;
if (messageList.length === 0) return true;
const lastMessage = messageList[messageList.length - 1];
// 如果上一条消息显示了时间,检查时间间隔
if (lastMessage.showTime) {
const timeDiff = Date.now() - lastMessage.timestamp;
const timeDiffMinutes = timeDiff / (1000 * 60);
return timeDiffMinutes >= timeInterval;
}
return true;
// 模拟发送成功
setTimeout(() => {
this.updateMessageStatus(newMessage.id, 'success');
// 模拟专家回复
setTimeout(() => {
this.receiveExpertReply();
}, 1000 + Math.random() * 1000);
}, 500);
},
// 添加消息到列表
addMessageToList: function(message) {
const { messageList, timeInterval } = this.data;
// 确保时间戳有效
if (!message.timestamp || isNaN(message.timestamp) || message.timestamp <= 0) {
message.timestamp = Date.now();
}
const { messageList } = this.data;
if (messageList.length > 0) {
const lastMessage = messageList[messageList.length - 1];
const timeDiff = message.timestamp - lastMessage.timestamp;
const timeDiffMinutes = timeDiff / (1000 * 60);
// 根据时间间隔判断是否显示时间
message.showTime = timeDiffMinutes >= timeInterval;
} else {
message.showTime = true;
}
// 处理消息时间显示
const processedMessage = this.processSingleMessageTime(message, messageList);
messageList.push(message);
// 添加到列表
messageList.push(processedMessage);
this.setData({
messageList
@ -685,7 +867,7 @@ Page({
type: 'text',
content: randomReply,
timestamp: Date.now(),
showTime: this.shouldShowTime()
status: 'success'
};
this.addMessageToList(newMessage);
@ -735,22 +917,27 @@ Page({
}, 200);
// 检查是否需要加载更多
if (scrollTop <= 100 && !this.data.loading && this.data.hasMore) {
if (scrollTop <= 100 && !this.data.loadingMore && this.data.hasMore && this.data.page > 1) {
this.loadMoreMessages();
}
},
// 加载更多消息
loadMoreMessages: function() {
if (this.data.loading || !this.data.hasMore) return;
if (this.data.loadingMore || !this.data.hasMore) return;
this.setData({
page: this.data.page + 1,
loading: true
loadingMore: true
});
// 加载更多聊天记录
this.loadChatHistory();
setTimeout(() => {
this.setData({
page: this.data.page + 1
}, () => {
this.loadChatHistory();
});
}, 500);
},
// 滚动到底部
@ -761,9 +948,11 @@ Page({
scrollAnimate: animate
}, () => {
// 设置一个足够大的值确保滚动到底部
this.setData({
scrollTop: 999999
});
setTimeout(() => {
this.setData({
scrollTop: 999999
});
}, 100);
});
},
@ -818,32 +1007,11 @@ Page({
});
},
// 拍照
takePhoto: function() {
this.hideMediaActionSheet();
wx.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['camera'],
success: (res) => {
console.log('拍照成功:', res.tempFilePaths);
this.uploadImages(res.tempFilePaths);
},
fail: (err) => {
console.error('拍照失败:', err);
wx.showToast({
title: '拍照失败',
icon: 'none'
});
}
});
},
// 选择视频
chooseVideo: function() {
this.hideMediaActionSheet();
wx.chooseVideo({
sourceType: ['album'],
compressed: true,
@ -862,11 +1030,6 @@ Page({
});
},
// 录制语音
recordAudio: function() {
this.hideMediaActionSheet();
this.startVoiceRecord();
},
// 选择文件
chooseFile: function() {
@ -924,8 +1087,7 @@ Page({
extension: extension,
timestamp: Date.now(),
status: 'uploading',
progress: 0,
showTime: this.shouldShowTime()
progress: 0
};
this.addMessageToList(message);
@ -997,7 +1159,7 @@ Page({
type: 'text',
content: randomReply,
timestamp: Date.now(),
showTime: this.shouldShowTime()
status: 'success'
};
this.addMessageToList(newMessage);
@ -1069,8 +1231,7 @@ Page({
content: tempFilePath,
duration: duration,
timestamp: Date.now(),
status: 'sending',
showTime: this.shouldShowTime()
status: 'sending'
};
this.addMessageToList(message);
@ -1087,7 +1248,7 @@ Page({
type: 'text',
content: '语音收到了,我会仔细听取分析。',
timestamp: Date.now(),
showTime: this.shouldShowTime()
status: 'success'
};
this.addMessageToList(reply);
}, 1500);
@ -1151,71 +1312,35 @@ Page({
});
},
// 优化:微信样式时间格式化
// 修复:时间格式化函数 - 确保正确显示
formatTime: function(timestamp) {
// 调试日志
console.log('formatTime 接收到的timestamp:', timestamp, '类型:', typeof timestamp);
if (!timestamp || timestamp <= 0) {
console.warn('无效的时间戳:', timestamp);
return '';
return '未知时间';
}
const timeNum = Number(timestamp);
if (isNaN(timeNum)) {
return '';
console.warn('时间戳不是有效数字:', timestamp);
return '未知时间';
}
const date = new Date(timeNum);
if (isNaN(date.getTime())) {
return '';
console.warn('无法创建有效日期对象:', timeNum);
return '未知时间';
}
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(today.getTime() - 86400000);
const msgDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
// 只显示小时和分钟
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const timeStr = `${hours}:${minutes}`;
// 今天
if (msgDate.getTime() === today.getTime()) {
// 小于1分钟显示"刚刚"
const diffMinutes = (now.getTime() - date.getTime()) / (1000 * 60);
if (diffMinutes < 1) {
return '刚刚';
}
// 小于1小时显示"X分钟前"
else if (diffMinutes < 60) {
return `${Math.floor(diffMinutes)}分钟前`;
}
// 今天超过1小时显示时间
else {
return timeStr;
}
}
// 昨天
else if (msgDate.getTime() === yesterday.getTime()) {
return `昨天 ${timeStr}`;
}
// 本周内(7天内)
else {
const diffDays = (today.getTime() - msgDate.getTime()) / (1000 * 60 * 60 * 24);
if (diffDays < 7) {
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return `${weekDays[date.getDay()]} ${timeStr}`;
}
// 今年内
else if (date.getFullYear() === now.getFullYear()) {
const month = date.getMonth() + 1;
const day = date.getDate();
return `${month}${day}${timeStr}`;
}
// 更早
else {
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${timeStr}`;
}
}
const result = `${hours}:${minutes}`;
console.log('formatTime 结果:', result);
return result;
},
// 格式化文件大小

125
pagesA/pages/expertChat/expertChat.wxml

@ -1,14 +1,7 @@
<!-- pages/consult/consult.wxml -->
<view class="consult-page">
<!-- 头部专家信息 -->
<view class="consult-header">
<view class="header-content">
<view class="header-left">
<button class="back-btn" bindtap="goBack">
<image src="/images/icons/back.png" class="back-icon"></image>
</button>
</view>
<view class="header-center">
<view class="expert-info">
<text class="expert-name">{{expertInfo.name}}</text>
@ -18,12 +11,6 @@
</view>
</view>
</view>
<view class="header-right">
<button class="header-action-btn" bindtap="makePhoneCall">
<image src="/images/icons/phone.png" class="header-action-icon"></image>
</button>
</view>
</view>
</view>
@ -45,24 +32,20 @@
<!-- 消息列表 -->
<block wx:for="{{messageList}}" wx:key="id">
<!-- 微信样式时间分隔 -->
<view class="time-divider" wx:if="{{item.showTime}}">
<view class="time-line"></view>
<text class="time-text">{{formatTime(item.timestamp)}}</text>
<view class="time-line"></view>
</view>
<!-- 对方消息 -->
<view class="message-item message-left" wx:if="{{item.sender === 'expert'}}">
<view class="message-avatar">
<image src="{{expertInfo.avatar}}" class="avatar-img"></image>
<image src="/pagesA/images/1.png" class="avatar-img"></image>
</view>
<view class="message-content-wrapper">
<!-- 气泡箭头 -->
<view class="message-arrow arrow-left"></view>
<!-- 文本消息 -->
<view class="message-bubble bubble-left" wx:if="{{item.type === 'text'}}">
<text class="message-text">{{item.content}}</text>
<view class="message-arrow arrow-left"></view>
</view>
<!-- 图片消息 -->
@ -74,7 +57,6 @@
bindtap="previewImage"
data-url="{{item.content}}"
></image>
<view class="message-arrow arrow-left"></view>
</view>
<!-- 视频消息 -->
@ -89,7 +71,6 @@
<view class="video-play-overlay">
<image src="/images/icons/play.png" class="play-icon"></image>
</view>
<view class="message-arrow arrow-left"></view>
</view>
<!-- 语音消息 -->
@ -103,7 +84,6 @@
</view>
<text class="audio-duration">{{item.duration || 0}}''</text>
</view>
<view class="message-arrow arrow-left"></view>
</view>
<!-- 文件消息 -->
@ -115,18 +95,19 @@
<text class="file-name">{{item.fileName}}</text>
<text class="file-size">{{formatFileSize(item.fileSize)}}</text>
</view>
<view class="message-arrow arrow-left"></view>
</view>
</view>
</view>
<!-- 我的消息 -->
<!-- 我的消息 -->
<view class="message-item message-right" wx:else>
<view class="message-content-wrapper">
<view class="message-content-wrapper">
<!-- 气泡箭头 -->
<view class="message-arrow arrow-right"></view>
<!-- 文本消息 -->
<view class="message-bubble bubble-right" wx:if="{{item.type === 'text'}}">
<text class="message-text">{{item.content}}</text>
<view class="message-arrow arrow-right"></view>
</view>
<!-- 图片消息 -->
@ -143,7 +124,6 @@
<text class="progress-text">{{item.progress}}%</text>
</view>
</view>
<view class="message-arrow arrow-right"></view>
</view>
<!-- 视频消息 -->
@ -163,7 +143,6 @@
<text class="progress-text">{{item.progress}}%</text>
</view>
</view>
<view class="message-arrow arrow-right"></view>
</view>
<!-- 语音消息 -->
@ -177,7 +156,6 @@
<text class="audio-duration">{{item.duration || 0}}''</text>
</view>
<image src="/images/icons/voice_right.png" class="audio-icon-right"></image>
<view class="message-arrow arrow-right"></view>
</view>
<!-- 文件消息 -->
@ -194,16 +172,18 @@
<text class="progress-text">{{item.progress}}%</text>
</view>
</view>
<view class="message-arrow arrow-right"></view>
</view>
</view>
<view class="message-avatar">
<image src="{{userInfo.avatar}}" class="avatar-img"></image>
<image src="/pagesA/images/2.png" class="avatar-img"></image>
</view>
</view>
</block>
<!-- 底部留白区域 -->
<view class="chat-bottom-space"></view>
<!-- 加载更多提示 -->
<view class="load-more-tip" wx:if="{{loadingMore}}">
<text>加载中...</text>
@ -221,36 +201,35 @@
<!-- 语音输入模式 -->
<view class="voice-input-panel" wx:if="{{inputMode === 'voice'}}">
<!-- 切换到文字输入按钮 -->
<button class="voice-input-btn" bindtap="switchInputMode">
<image src="/images/icons/keyboard.png" class="voice-btn-icon"></image>
</button>
<view class="voice-input-btn" bindtap="switchInputMode">
<image src="/pagesA/images/xjp.png" class="voice-btn-icon"></image>
</view>
<!-- 语音输入按钮 -->
<view class="input-wrapper">
<button
<view
class="voice-record-btn"
bindtouchstart="startVoiceRecord"
bindtouchmove="onVoiceTouchMove"
bindtouchend="endVoiceRecord"
bindtouchcancel="cancelVoiceRecord"
>
<image src="/images/icons/microphone.png" class="mic-icon"></image>
<text class="voice-tip">{{voiceTip}}</text>
</button>
</view>
</view>
<view class="more-btn" bindtap="showMediaActionSheet" >
<image src="/pagesA/images/add.png" class="more-icon"></image>
</view>
<!-- 更多按钮 -->
<button class="more-btn" bindtap="showMediaActionSheet">
<image src="/images/icons/plus.png" class="more-icon"></image>
</button>
</view>
<!-- 文字输入模式 -->
<view class="text-input-panel" wx:else>
<!-- 切换到语音输入按钮 -->
<button class="voice-input-btn" bindtap="switchInputMode">
<image src="/images/icons/voice.png" class="voice-btn-icon"></image>
</button>
<view class="voice-input-btn" bindtap="switchInputMode">
<image src="/pagesA/images/yy.png" class="voice-btn-icon"></image>
</view>
<!-- 文字输入框 -->
<view class="input-wrapper">
@ -279,17 +258,17 @@
</view>
<!-- 发送/更多按钮 -->
<button
<!-- <button
class="send-btn"
bindtap="sendTextMessage"
wx:if="{{inputValue.trim()}}"
>
<text class="send-text">发送</text>
</button>
</button> -->
<button class="more-btn" bindtap="showMediaActionSheet" wx:else>
<image src="/images/icons/plus.png" class="more-icon"></image>
</button>
<view class="more-btn" bindtap="showMediaActionSheet" >
<image src="/pagesA/images/add.png" class="more-icon"></image>
</view>
</view>
</view>
@ -298,46 +277,32 @@
<view class="media-sheet-content" catchtap="stopPropagation">
<view class="media-sheet-header">
<text class="sheet-title">发送内容</text>
<button class="close-sheet-btn" bindtap="hideMediaActionSheet">
<image src="/images/icons/close_round.png"></image>
</button>
<view class="close-sheet-btn" bindtap="hideMediaActionSheet">
<image src="/pagesA/images/cuo.png"></image>
</view>
</view>
<view class="media-options-grid">
<button class="media-option" bindtap="chooseImage">
<view class="option-icon-box photo-icon">
<image src="/images/icons/photo.png"></image>
<view class="media-option" bindtap="chooseImage">
<view class="option-icon-box">
<image src="/pagesA/images/zp.png"></image>
</view>
<text class="option-text">照片</text>
</button>
<button class="media-option" bindtap="takePhoto">
<view class="option-icon-box camera-icon">
<image src="/images/icons/camera.png"></image>
</view>
<text class="option-text">拍摄</text>
</button>
</view>
<button class="media-option" bindtap="chooseVideo">
<view class="option-icon-box video-icon">
<image src="/images/icons/video.png"></image>
<view class="media-option" bindtap="chooseVideo">
<view class="option-icon-box">
<image src="/pagesA/images/ps.png"></image>
</view>
<text class="option-text">视频</text>
</button>
<button class="media-option" bindtap="recordAudio">
<view class="option-icon-box audio-icon">
<image src="/images/icons/audio_msg.png"></image>
</view>
<text class="option-text">语音</text>
</button>
</view>
<button class="media-option" bindtap="chooseFile">
<view class="option-icon-box file-icon">
<image src="/images/icons/file.png"></image>
<view class="media-option" bindtap="chooseFile">
<view class="option-icon-box">
<image src="/pagesA/images/wj.png"></image>
</view>
<text class="option-text">文件</text>
</button>
</view>
</view>
<view class="sheet-bottom">

228
pagesA/pages/expertChat/expertChat.wxss

@ -1,4 +1,3 @@
/* pages/consult/consult.wxss */
/* 页面整体样式 */
.consult-page {
@ -7,16 +6,15 @@
background: #f5f5f5;
display: flex;
flex-direction: column;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;
}
/* ========== 头部样式 ========== */
/* 头部样式 */
.consult-header {
background: #ffffff;
border-bottom: 1px solid #e5e5e5;
border-bottom: 1rpx solid #e5e5e5;
position: relative;
z-index: 1000;
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.05);
}
.header-content {
@ -26,9 +24,6 @@
height: 96rpx;
}
.header-left {
width: 80rpx;
}
.header-center {
flex: 1;
@ -36,30 +31,8 @@
justify-content: center;
}
.header-right {
width: 80rpx;
}
.back-btn {
width: 72rpx;
height: 72rpx;
border: none;
background: transparent;
padding: 0;
margin: 0;
line-height: 1;
border-radius: 50%;
transition: background-color 0.2s;
}
.back-btn:active {
background-color: rgba(0, 0, 0, 0.05);
}
.back-icon {
width: 36rpx;
height: 36rpx;
}
.expert-info {
display: flex;
@ -115,31 +88,12 @@
color: #666666;
}
.header-action-btn {
width: 72rpx;
height: 72rpx;
border: none;
background: transparent;
padding: 0;
margin: 0;
line-height: 1;
border-radius: 50%;
transition: background-color 0.2s;
}
.header-action-btn:active {
background-color: rgba(0, 0, 0, 0.05);
}
.header-action-icon {
width: 40rpx;
height: 40rpx;
}
/* ========== 聊天容器 ========== */
/* 聊天容器 */
.chat-container {
flex: 1;
padding: 20rpx 0 0;
padding: 20rpx 0;
background: #f5f5f5;
overflow-y: auto;
position: relative;
@ -162,36 +116,9 @@
background-color: #d8d8d8;
}
/* 微信样式时间分隔 */
.time-divider {
display: flex;
justify-content: center;
align-items: center;
margin: 40rpx 0;
padding: 0 30rpx;
}
.time-line {
flex: 1;
height: 1rpx;
background: rgba(0, 0, 0, 0.1);
margin: 0 20rpx;
}
.time-text {
display: inline-block;
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 22rpx;
color: #999999;
background-color: rgba(0, 0, 0, 0.05);
line-height: 1.5;
text-align: center;
word-break: keep-all;
white-space: nowrap;
}
/* 消息项 - 修复对齐 */
/* 消息项 */
.message-item {
display: flex;
margin-bottom: 24rpx;
@ -221,7 +148,7 @@
justify-content: flex-end;
}
/* 头像 - 修复对齐 */
/* 头像 */
.message-avatar {
width: 80rpx;
height: 80rpx;
@ -248,12 +175,13 @@
object-fit: cover;
}
/* 消息内容包装器 */
/* 消息内容包装器 */
.message-content-wrapper {
max-width: 480rpx;
position: relative;
display: flex;
flex-direction: column;
z-index: 2;
}
.message-left .message-content-wrapper {
@ -264,16 +192,38 @@
align-items: flex-end;
}
/* 气泡箭头*/
.message-arrow {
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 12rpx;
top: 30rpx; /* 固定在头像中间位置 (80rpx/2 = 40rpx) */
z-index: 1;
}
/* 左侧箭头(专家) */
.arrow-left {
left: -24rpx;
border-color: transparent #ffffff transparent transparent;
}
/* 右侧箭头(用户) */
.arrow-right {
right: -24rpx;
border-color: transparent transparent transparent #95ec69;
}
/* 消息气泡通用样式 */
.message-bubble {
position: relative;
padding: 16rpx 20rpx;
word-break: break-word;
box-sizing: border-box;
min-height: 80rpx; /* 与头像高度一致 */
min-height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 左侧气泡(专家) */
@ -292,28 +242,6 @@
box-shadow: 0 2rpx 8rpx rgba(149, 236, 105, 0.2);
}
/* 气泡箭头 - 固定在头像中间位置 */
.message-arrow {
position: absolute;
width: 0;
height: 0;
border-style: solid;
border-width: 12rpx;
top: 50%;
margin-top: -12rpx; /* 箭头高度的一半 */
z-index: 2;
}
.arrow-left {
left: -24rpx;
border-color: transparent #ffffff transparent transparent;
}
.arrow-right {
right: -24rpx;
border-color: transparent transparent transparent #95ec69;
}
/* 文本消息 */
.message-text {
font-size: 32rpx;
@ -332,7 +260,7 @@
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
background: #ffffff;
min-height: 80rpx; /* 与头像高度一致 */
min-height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
@ -386,7 +314,7 @@
padding: 20rpx;
display: flex;
align-items: center;
min-height: 80rpx;
min-height: 60rpx;
}
.audio-icon-left,
@ -447,7 +375,7 @@
padding: 20rpx;
display: flex;
align-items: center;
min-height: 80rpx;
min-height: 60rpx;
}
.file-icon-box {
@ -527,6 +455,11 @@
font-weight: 600;
}
/* 底部留白区域 */
.chat-bottom-space {
height: 40rpx;
}
/* 加载更多提示 */
.load-more-tip {
display: flex;
@ -559,7 +492,7 @@
color: #999999;
}
/* ========== 输入区域 ========== */
/* 输入区域 */
.input-section {
background: #ffffff;
border-top: 1rpx solid #e5e5e5;
@ -589,26 +522,15 @@
.voice-input-btn {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
border: none;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
padding: 0;
margin: 0;
line-height: 1;
flex-shrink: 0;
}
.voice-input-btn:active {
background: rgba(0, 0, 0, 0.05);
}
.voice-btn-icon {
width: 40rpx;
height: 40rpx;
width: 70rpx;
height: 70rpx;
}
/* 输入框包装器 */
@ -625,13 +547,13 @@
min-width: 0;
}
/* 语音输入模式下的输入框包装器 */
/* 语音输入模式下的输入框 */
.voice-input-panel .input-wrapper {
padding: 0;
background: #f5f5f5;
}
/* 文字输入模式下的输入框包装器 */
/* 文字输入模式下的输入框 */
.text-input-panel .input-wrapper {
padding: 0 30rpx;
}
@ -644,27 +566,18 @@
.voice-record-btn {
width: 100%;
height: 100%;
background: transparent;
border: none;
border-radius: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: all 0.2s;
padding: 0;
margin: 0;
}
.voice-record-btn:active {
background: #e0e0e0;
}
.mic-icon {
width: 36rpx;
height: 36rpx;
margin-bottom: 6rpx;
}
.voice-tip {
font-size: 24rpx;
@ -683,7 +596,7 @@
.input-placeholder {
color: #999999;
font-size: 30rpx;
font-size: 24rpx;
}
.input-actions {
@ -738,8 +651,8 @@
}
.more-icon {
width: 36rpx;
height: 36rpx;
width: 70rpx;
height: 70rpx;
}
/* 发送按钮 */
@ -770,7 +683,7 @@
font-weight: 500;
}
/* ========== 多媒体选择面板 ========== */
/* 多媒体选择面板 */
.media-action-sheet {
position: fixed;
top: 0;
@ -787,9 +700,9 @@
.media-sheet-content {
width: 100%;
background: #ffffff;
background: #F7F7F7;
border-radius: 40rpx 40rpx 0 0;
padding: 40rpx 30rpx calc(80rpx + env(safe-area-inset-bottom));
padding: 40rpx 30rpx calc(10rpx + env(safe-area-inset-bottom));
animation: slideUp 0.3s ease;
box-sizing: border-box;
}
@ -808,7 +721,8 @@
align-items: center;
justify-content: space-between;
margin-bottom: 40rpx;
padding: 0 10rpx;
padding: 0 10rpx 20rpx;
border-bottom: 1px solid #E4E4E4;
}
.sheet-title {
@ -818,22 +732,14 @@
}
.close-sheet-btn {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
border: none;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
line-height: 1;
}
.close-sheet-btn image {
width: 24rpx;
height: 24rpx;
width: 60rpx;
height: 60rpx;
}
.media-options-grid {
@ -863,31 +769,13 @@
justify-content: center;
margin-bottom: 16rpx;
transition: transform 0.2s;
background-color: #fff;
}
.media-option:active .option-icon-box {
transform: scale(0.95);
}
.photo-icon {
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
}
.camera-icon {
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%);
}
.video-icon {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
}
.audio-icon {
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
}
.file-icon {
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
}
.option-icon-box image {
width: 60rpx;
@ -908,7 +796,7 @@
color: #999999;
}
/* ========== 录音模态框 ========== */
/* 录音模态框 */
.recording-modal {
position: fixed;
top: 0;

2
project.config.json

@ -1,5 +1,5 @@
{
"appid": "wxb5becc8d6d8123a6",
"appid": "wx9bf7f4e81a1b2d6b",
"compileType": "miniprogram",
"libVersion": "3.13.0",
"packOptions": {

2
utils/baseUrl.js

@ -1,4 +1,4 @@
// var baseUrl = 'https://wx.chenhaitech.com/guoziwei-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.111:8081'
module.exports = baseUrl
Loading…
Cancel
Save