-
4app.json
-
7pages/home/home.js
-
2pages/home/home.wxml
-
BINpagesA/images/1.png
-
BINpagesA/images/2.png
-
BINpagesA/images/3.png
-
BINpagesA/images/dzyx.png
-
BINpagesA/images/gzdw.png
-
BINpagesA/images/kzt.png
-
BINpagesA/images/phone.png
-
BINpagesA/images/ss.png
-
91pagesA/pages/askingSy/askingSy.js
-
45pagesA/pages/askingSy/askingSy.wxml
-
381pagesA/pages/askingSy/askingSy.wxss
-
192pagesA/pages/askingSyAdd/askingSyAdd.js
-
75pagesA/pages/askingSyAdd/askingSyAdd.wxml
-
11pagesA/pages/askingSyAdd/askingSyAdd.wxss
-
166pagesA/pages/askingSyDetails/askingSyDetails.js
-
49pagesA/pages/askingSyDetails/askingSyDetails.wxml
-
40pagesA/pages/askingSyDetails/askingSyDetails.wxss
-
431pagesA/pages/expert/expert.js
-
4pagesA/pages/expert/expert.json
-
248pagesA/pages/expert/expert.wxml
-
769pagesA/pages/expert/expert.wxss
-
753pagesA/pages/expertChat/expertChat.js
-
4pagesA/pages/expertChat/expertChat.json
-
296pagesA/pages/expertChat/expertChat.wxml
-
928pagesA/pages/expertChat/expertChat.wxss
-
23utils/api.js
|
After Width: 556 | Height: 674 | Size: 368 KiB |
|
After Width: 466 | Height: 678 | Size: 374 KiB |
|
After Width: 459 | Height: 682 | Size: 285 KiB |
|
After Width: 200 | Height: 200 | Size: 2.4 KiB |
|
After Width: 200 | Height: 200 | Size: 2.4 KiB |
|
After Width: 320 | Height: 320 | Size: 8.5 KiB |
|
After Width: 200 | Height: 200 | Size: 4.0 KiB |
|
After Width: 200 | Height: 200 | Size: 4.1 KiB |
@ -0,0 +1,431 @@ |
|||||
|
import http from '../../../utils/api' |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
// 搜索关键词
|
||||
|
searchKeyword: '', |
||||
|
|
||||
|
// 当前筛选条件
|
||||
|
currentFilter: 'all', |
||||
|
|
||||
|
// 所有专家数据
|
||||
|
allExperts: [ |
||||
|
{ |
||||
|
id: 1, |
||||
|
name: '张建国', |
||||
|
title: '畜牧学博士', |
||||
|
expertise: '牛类养殖与疾病防治', |
||||
|
experience: 15, |
||||
|
online: true, |
||||
|
senior: true, |
||||
|
phone: '138-0013-8001', |
||||
|
email: 'zhangjg@agri-expert.com', |
||||
|
institution: '国家畜牧业研究所', |
||||
|
address: '北京市朝阳区农业科技大厦', |
||||
|
bio: '15年牛类养殖研究经验,发表SCI论文20余篇,擅长规模化养殖场管理与疾病防控。', |
||||
|
tags: ['肉牛养殖', '疾病预防', '饲料配方', '规模化养殖'], |
||||
|
successRate: 98, |
||||
|
responseTime: '15分钟内响应', |
||||
|
avatar: '/pagesA/images/1.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
name: '李秀英', |
||||
|
title: '兽医学硕士', |
||||
|
expertise: '猪病诊断与治疗', |
||||
|
experience: 12, |
||||
|
online: true, |
||||
|
senior: true, |
||||
|
phone: '139-0013-9002', |
||||
|
email: 'lixy@vet-hospital.cn', |
||||
|
institution: '农业大学动物医学院', |
||||
|
address: '上海市徐汇区农业路58号', |
||||
|
bio: '资深猪病防治专家,参与多项国家级科研项目,成功解决多个大规模猪场疫情。', |
||||
|
tags: ['猪病诊疗', '疫苗管理', '养殖场规划', '传染病防治'], |
||||
|
successRate: 96, |
||||
|
responseTime: '20分钟内响应', |
||||
|
avatar: '/pagesA/images/2.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 3, |
||||
|
name: '王伟民', |
||||
|
title: '高级畜牧师', |
||||
|
expertise: '羊类繁殖与饲养管理', |
||||
|
experience: 20, |
||||
|
online: false, |
||||
|
senior: true, |
||||
|
phone: '137-0013-7003', |
||||
|
email: 'wangwm@livestock.cn', |
||||
|
institution: '草原畜牧业研究中心', |
||||
|
address: '内蒙古呼和浩特市牧业科技园', |
||||
|
bio: '羊类养殖领域权威专家,拥有20年草原牧区养殖经验,精通各类羊只繁殖技术。', |
||||
|
tags: ['绵羊养殖', '山羊繁殖', '牧草种植', '草原管理'], |
||||
|
successRate: 95, |
||||
|
responseTime: '1小时内响应', |
||||
|
avatar: '/pagesA/images/3.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 4, |
||||
|
name: '陈秀兰', |
||||
|
title: '家禽养殖专家', |
||||
|
expertise: '鸡鸭养殖技术', |
||||
|
experience: 18, |
||||
|
online: true, |
||||
|
senior: false, |
||||
|
phone: '136-0013-6004', |
||||
|
email: 'chenxl@poultry-expert.com', |
||||
|
institution: '家禽养殖技术中心', |
||||
|
address: '广东省广州市禽业科技园', |
||||
|
bio: '家禽养殖技术推广专家,帮助300+养殖户实现科学化、标准化养殖管理。', |
||||
|
tags: ['蛋鸡养殖', '肉鸭饲养', '疾病防控', '禽舍设计'], |
||||
|
successRate: 94, |
||||
|
responseTime: '30分钟内响应', |
||||
|
avatar: '/pagesA/images/1.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 5, |
||||
|
name: '刘志强', |
||||
|
title: '特种养殖专家', |
||||
|
expertise: '鹿、兔等特种动物养殖', |
||||
|
experience: 10, |
||||
|
online: false, |
||||
|
senior: false, |
||||
|
phone: '135-0013-5005', |
||||
|
email: 'liuzq@special-livestock.cn', |
||||
|
institution: '特种动物养殖研究所', |
||||
|
address: '四川省成都市农业科技园', |
||||
|
bio: '特种动物养殖新兴力量,专注于梅花鹿、肉兔等特种动物的现代化养殖技术。', |
||||
|
tags: ['梅花鹿养殖', '肉兔饲养', '市场分析', '特色养殖'], |
||||
|
successRate: 92, |
||||
|
responseTime: '2小时内响应', |
||||
|
avatar: '/pagesA/images/2.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 6, |
||||
|
name: '赵雪梅', |
||||
|
title: '畜牧营养学博士', |
||||
|
expertise: '牲畜饲料配方与营养', |
||||
|
experience: 14, |
||||
|
online: true, |
||||
|
senior: true, |
||||
|
phone: '134-0013-4006', |
||||
|
email: 'zhaoxm@feed-research.cn', |
||||
|
institution: '农业科学院饲料研究所', |
||||
|
address: '江苏省南京市科研路88号', |
||||
|
bio: '饲料营养学专家,研发多个高效饲料配方,帮助养殖户降低30%饲料成本。', |
||||
|
tags: ['饲料配方', '营养管理', '成本控制', '饲料安全'], |
||||
|
successRate: 97, |
||||
|
responseTime: '25分钟内响应', |
||||
|
avatar: '/pagesA/images/3.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 7, |
||||
|
name: '孙建国', |
||||
|
title: '养殖场管理专家', |
||||
|
expertise: '现代化养殖场设计与运营', |
||||
|
experience: 22, |
||||
|
online: true, |
||||
|
senior: true, |
||||
|
phone: '133-0013-3007', |
||||
|
email: 'sunjg@farm-management.com', |
||||
|
institution: '现代农业发展中心', |
||||
|
address: '浙江省杭州市创新产业园', |
||||
|
bio: '养殖场规划设计专家,参与设计200+现代化养殖场,精通自动化养殖设备应用。', |
||||
|
tags: ['场舍设计', '设备管理', '成本核算', '环保处理'], |
||||
|
successRate: 99, |
||||
|
responseTime: '10分钟内响应', |
||||
|
avatar: '/pagesA/images/2.png' |
||||
|
}, |
||||
|
{ |
||||
|
id: 8, |
||||
|
name: '周小芳', |
||||
|
title: '兽医病理学硕士', |
||||
|
expertise: '牲畜常见病与传染病防治', |
||||
|
experience: 11, |
||||
|
online: false, |
||||
|
senior: false, |
||||
|
phone: '132-0013-2008', |
||||
|
email: 'zhouxf@vet-pathology.cn', |
||||
|
institution: '动物疫病防控中心', |
||||
|
address: '湖北省武汉市动物防疫站', |
||||
|
bio: '专攻牲畜病理诊断与传染病防控,在快速诊断和精准治疗方面有丰富经验。', |
||||
|
tags: ['传染病防治', '病理诊断', '用药指导', '疫情处理'], |
||||
|
successRate: 93, |
||||
|
responseTime: '1.5小时内响应', |
||||
|
avatar: '/pagesA/images/3.png' |
||||
|
} |
||||
|
], |
||||
|
|
||||
|
// 筛选后的专家列表
|
||||
|
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); |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 更新在线状态
|
||||
|
updateOnlineStatus() { |
||||
|
const experts = this.data.allExperts.map(expert => ({ |
||||
|
...expert, |
||||
|
online: Math.random() > 0.4 // 60%的概率在线
|
||||
|
})); |
||||
|
|
||||
|
this.setData({ |
||||
|
allExperts: experts |
||||
|
}, () => { |
||||
|
this.sortAndFilterExperts(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 处理搜索输入
|
||||
|
onSearchInput(e) { |
||||
|
this.setData({ |
||||
|
searchKeyword: e.detail.value |
||||
|
}); |
||||
|
this.sortAndFilterExperts(); |
||||
|
}, |
||||
|
|
||||
|
// 清空搜索
|
||||
|
clearSearch() { |
||||
|
this.setData({ |
||||
|
searchKeyword: '', |
||||
|
currentFilter: 'all' |
||||
|
}); |
||||
|
this.sortAndFilterExperts(); |
||||
|
}, |
||||
|
|
||||
|
// 更改筛选条件
|
||||
|
changeFilter(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) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 筛选条件
|
||||
|
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) { |
||||
|
const index = e.currentTarget.dataset.index; |
||||
|
const expert = this.data.filteredExperts[index]; |
||||
|
|
||||
|
this.setData({ |
||||
|
currentExpert: expert, |
||||
|
showContactDialog: true |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 隐藏联系方式弹窗
|
||||
|
hideContactDialog() { |
||||
|
this.setData({ |
||||
|
showContactDialog: false |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 阻止事件冒泡
|
||||
|
stopPropagation() { |
||||
|
// 阻止事件冒泡
|
||||
|
}, |
||||
|
|
||||
|
// 拨打电话
|
||||
|
makePhoneCall(e) { |
||||
|
const phone = e.currentTarget.dataset.phone; |
||||
|
const cleanPhone = phone.replace(/-/g, ''); |
||||
|
|
||||
|
wx.showModal({ |
||||
|
title: '拨打电话', |
||||
|
content: `确定要拨打 ${phone} 吗?`, |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
wx.makePhoneCall({ |
||||
|
phoneNumber: cleanPhone, |
||||
|
success: () => { |
||||
|
wx.showToast({ |
||||
|
title: '拨号成功', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('拨打电话失败:', err); |
||||
|
wx.showToast({ |
||||
|
title: '拨号失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 复制邮箱
|
||||
|
copyEmail(e) { |
||||
|
const email = e.currentTarget.dataset.email; |
||||
|
|
||||
|
wx.setClipboardData({ |
||||
|
data: email, |
||||
|
success: () => { |
||||
|
wx.showToast({ |
||||
|
title: '邮箱已复制', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
}, |
||||
|
fail: () => { |
||||
|
wx.showToast({ |
||||
|
title: '复制失败', |
||||
|
icon: 'none' |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 查看位置
|
||||
|
viewLocation(e) { |
||||
|
const address = e.currentTarget.dataset.address |
||||
|
wx.showModal({ |
||||
|
title: '单位地址', |
||||
|
content: address, |
||||
|
showCancel: false, |
||||
|
confirmText: '知道了' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 开始咨询
|
||||
|
startConsultation() { |
||||
|
const expert = this.data.currentExpert; |
||||
|
|
||||
|
wx.showModal({ |
||||
|
title: '咨询确认', |
||||
|
content: expert.online |
||||
|
? `确定要立即咨询 ${expert.name} 专家吗?` |
||||
|
: `确定要预约咨询 ${expert.name} 专家吗?`, |
||||
|
success: (res) => { |
||||
|
if (res.confirm) { |
||||
|
if (expert.online) { |
||||
|
wx.showToast({ |
||||
|
title: '正在为您连接专家...', |
||||
|
icon: 'loading', |
||||
|
duration: 2000 |
||||
|
}); |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
// 跳转一对一咨询专家
|
||||
|
wx.navigateTo({ |
||||
|
url: '/pagesA/pages/expertChat/expertChat', |
||||
|
}) |
||||
|
|
||||
|
this.hideContactDialog(); |
||||
|
}, 2000); |
||||
|
|
||||
|
|
||||
|
|
||||
|
} else { |
||||
|
wx.showToast({ |
||||
|
title: '预约成功,专家将尽快回复', |
||||
|
icon: 'success' |
||||
|
}); |
||||
|
this.hideContactDialog(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 分享专家
|
||||
|
onShareAppMessage() { |
||||
|
return { |
||||
|
title: '牲畜专家咨询平台', |
||||
|
path: '/pages/expert/expert', |
||||
|
imageUrl: '/images/share-cover.jpg' |
||||
|
}; |
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"专家列表", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,248 @@ |
|||||
|
<view class="expert-page"> |
||||
|
<!-- 顶部区域 --> |
||||
|
<view class="header-section"> |
||||
|
<!-- 背景装饰 --> |
||||
|
<view class="header-bg"> |
||||
|
<view class="bg-circle bg-circle-1"></view> |
||||
|
<view class="bg-circle bg-circle-2"></view> |
||||
|
<view class="bg-circle bg-circle-3"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 标题内容 --> |
||||
|
<view class="header-content"> |
||||
|
<view class="title-row"> |
||||
|
<text class="main-title">牲畜专家</text> |
||||
|
<view class="expert-badge"> |
||||
|
<text class="badge-text">专业咨询</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
<text class="sub-title">连接行业专家 · 解决养殖难题</text> |
||||
|
|
||||
|
<!-- 统计信息 --> |
||||
|
<view class="stats-row"> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-number">{{allExperts.length}}</text> |
||||
|
<text class="stat-label">总专家数</text> |
||||
|
</view> |
||||
|
<view class="stat-divider"></view> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-number">{{onlineCount}}</text> |
||||
|
<text class="stat-label">在线专家</text> |
||||
|
</view> |
||||
|
<view class="stat-divider"></view> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-number">24h</text> |
||||
|
<text class="stat-label">快速响应</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 搜索和筛选区域 --> |
||||
|
<view class="search-filter-section"> |
||||
|
<!-- 搜索框 --> |
||||
|
<view class="search-wrapper"> |
||||
|
<view class="search-box"> |
||||
|
<image src="/pagesA/images/ss.png" class="search-icon"></image> |
||||
|
<input type="text" placeholder="搜索专家姓名、擅长领域..." placeholder-class="placeholder" bindinput="onSearchInput" value="{{searchKeyword}}" /> |
||||
|
<view wx:if="{{searchKeyword}}" class="clear-btn" bindtap="clearSearch"> |
||||
|
<image src="/pagesA/images/ch.png" class="clear-icon"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 筛选标签 --> |
||||
|
<scroll-view class="filter-scroll" scroll-x> |
||||
|
<view class="filter-tags"> |
||||
|
<view class="filter-tag {{currentFilter === 'all' ? 'active' : ''}}" bindtap="changeFilter" data-filter="all"> |
||||
|
<text>全部专家</text> |
||||
|
<view class="tag-count">{{allExperts.length}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="filter-tag {{currentFilter === 'online' ? 'active' : ''}}" bindtap="changeFilter" data-filter="online"> |
||||
|
<text>在线专家</text> |
||||
|
<view class="tag-count online-count">{{onlineCount}}</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="filter-tag {{currentFilter === 'offline' ? 'active' : ''}}" bindtap="changeFilter" data-filter="offline"> |
||||
|
<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> |
||||
|
|
||||
|
<!-- 专家列表 --> |
||||
|
<view class="expert-list-section"> |
||||
|
<!-- 列表标题 --> |
||||
|
<view class="list-header"> |
||||
|
<text class="list-title">专家列表</text> |
||||
|
<text class="list-count">已为您找到 {{filteredExperts.length}} 位专家</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 专家卡片列表 --> |
||||
|
<scroll-view class="expert-cards" scroll-y> |
||||
|
<!-- 空状态 --> |
||||
|
<view wx:if="{{filteredExperts.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> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 专家卡片 --> |
||||
|
<view wx:else> |
||||
|
<block wx:for="{{filteredExperts}}" 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 ? '在线' : '离线'}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 专家信息 --> |
||||
|
<view class="card-middle"> |
||||
|
<view class="name-title-row"> |
||||
|
<text class="expert-name">{{item.name}}</text> |
||||
|
<text class="expert-title">{{item.title}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 专门的信息展示区域 --> |
||||
|
<view class="info-display"> |
||||
|
<view class="info-item"> |
||||
|
<text class="info-label">擅长领域:</text> |
||||
|
<text class="info-value">{{item.expertise}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="info-item"> |
||||
|
<text class="info-label">从业经验:</text> |
||||
|
<text class="info-value">{{item.experience}}年</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 点击按钮 --> |
||||
|
<view class="contact-btn {{item.online ? 'online-btn' : 'offline-btn'}}" bindtap="showContactInfo" data-index="{{index}}"> |
||||
|
{{item.online ? '立即咨询' : '查看联系'}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</block> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 联系方式弹窗 --> |
||||
|
<view wx:if="{{showContactDialog}}" class="dialog-overlay" bindtap="hideContactDialog"> |
||||
|
<view class="contact-modal" catchtap="stopPropagation"> |
||||
|
<!-- 可滚动的内容区域 --> |
||||
|
<scroll-view class="modal-content" scroll-y> |
||||
|
<!-- 模态框头部 --> |
||||
|
<view class="modal-header"> |
||||
|
<view class="header-left"> |
||||
|
<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> |
||||
|
<view class="title-status"> |
||||
|
<view class="modal-status {{currentExpert.online ? 'online' : 'offline'}}"> |
||||
|
<view class="status-dot"></view> |
||||
|
{{currentExpert.online ? '在线可咨询' : '暂时离线'}} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 专家简介 --> |
||||
|
<view class="expert-bio"> |
||||
|
<view class="bio-header"> |
||||
|
<text class="bio-title">专家简介</text> |
||||
|
</view> |
||||
|
<text class="bio-content">{{currentExpert.bio || '资深牲畜养殖专家,拥有丰富的实践经验和理论知识,擅长解决各类养殖难题'}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 联系方式 --> |
||||
|
<view class="contact-section"> |
||||
|
<!-- 电话 --> |
||||
|
<view class="contact-item phone-item"> |
||||
|
<view class="contact-icon-container"> |
||||
|
<image src="/pagesA/images/phone.png" class="contact-item-icon"></image> |
||||
|
</view> |
||||
|
<view class="contact-info"> |
||||
|
<text class="contact-label">联系电话</text> |
||||
|
<text class="contact-value">{{currentExpert.phone}}</text> |
||||
|
<text class="contact-desc">可直接拨打电话咨询</text> |
||||
|
</view> |
||||
|
<button class="action-btn call-btn" bindtap="makePhoneCall" data-phone="{{currentExpert.phone}}"> |
||||
|
拨打 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 邮箱 --> |
||||
|
<view class="contact-item email-item"> |
||||
|
<view class="contact-icon-container"> |
||||
|
<image src="/pagesA/images/dzyx.png" class="contact-item-icon"></image> |
||||
|
</view> |
||||
|
<view class="contact-info"> |
||||
|
<text class="contact-label">电子邮箱</text> |
||||
|
<text class="contact-value">{{currentExpert.email}}</text> |
||||
|
<text class="contact-desc">发送邮件可获得详细回复</text> |
||||
|
</view> |
||||
|
<button class="action-btn copy-btn" bindtap="copyEmail" data-email="{{currentExpert.email}}"> |
||||
|
复制 |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 工作单位 --> |
||||
|
<view class="contact-item institution-item"> |
||||
|
<view class="contact-icon-container"> |
||||
|
<image src="/pagesA/images/gzdw.png" class="contact-item-icon"></image> |
||||
|
</view> |
||||
|
<view class="contact-info"> |
||||
|
<text class="contact-label">工作单位</text> |
||||
|
<text class="contact-value">{{currentExpert.institution}}</text> |
||||
|
<text class="contact-desc">专业机构认证专家</text> |
||||
|
</view> |
||||
|
<button class="action-btn address-btn" bindtap="viewLocation" data-address="{{currentExpert.address}}"> |
||||
|
位置 |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 咨询时间 --> |
||||
|
<view class="consultation-time"> |
||||
|
<view class="time-header"> |
||||
|
<text class="time-title">咨询时间建议</text> |
||||
|
</view> |
||||
|
<text class="time-content">{{currentExpert.online ? '专家当前在线,可直接联系咨询' : '专家当前不在线,建议在工作时间联系或发送邮件咨询'}}</text> |
||||
|
<view class="time-slots"> |
||||
|
<text class="time-slot">工作日 9:00-18:00</text> |
||||
|
<text class="time-slot">周末 10:00-16:00</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 操作按钮(固定在底部) --> |
||||
|
<view class="modal-actions"> |
||||
|
<button class="secondary-btn" bindtap="hideContactDialog">稍后联系</button> |
||||
|
<button class="primary-btn" bindtap="startConsultation"> |
||||
|
{{currentExpert.online ? '立即咨询' : '预约咨询'}} |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,769 @@ |
|||||
|
.expert-page { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(180deg, #f5f7fa 0%, #ffffff 100%); |
||||
|
} |
||||
|
|
||||
|
/* 顶部区域样式 */ |
||||
|
.header-section { |
||||
|
background: linear-gradient(135deg, #2c8c34 0%, #4caf50 100%); |
||||
|
padding: 40rpx 40rpx 50rpx; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.header-bg { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.bg-circle { |
||||
|
position: absolute; |
||||
|
border-radius: 50%; |
||||
|
background: rgba(255, 255, 255, 0.1); |
||||
|
} |
||||
|
|
||||
|
.bg-circle-1 { |
||||
|
width: 300rpx; |
||||
|
height: 300rpx; |
||||
|
top: -150rpx; |
||||
|
right: -100rpx; |
||||
|
} |
||||
|
|
||||
|
.bg-circle-2 { |
||||
|
width: 200rpx; |
||||
|
height: 200rpx; |
||||
|
bottom: -80rpx; |
||||
|
left: -80rpx; |
||||
|
} |
||||
|
|
||||
|
.bg-circle-3 { |
||||
|
width: 150rpx; |
||||
|
height: 150rpx; |
||||
|
top: 50%; |
||||
|
right: 20%; |
||||
|
} |
||||
|
|
||||
|
.header-content { |
||||
|
position: relative; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
|
||||
|
.title-row { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.main-title { |
||||
|
font-size: 52rpx; |
||||
|
font-weight: bold; |
||||
|
color: white; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.expert-badge { |
||||
|
background: rgba(255, 255, 255, 0.2); |
||||
|
border: 2rpx solid rgba(255, 255, 255, 0.3); |
||||
|
border-radius: 30rpx; |
||||
|
padding: 6rpx 20rpx; |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
} |
||||
|
|
||||
|
.badge-text { |
||||
|
font-size: 24rpx; |
||||
|
color: white; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.sub-title { |
||||
|
font-size: 28rpx; |
||||
|
color: rgba(255, 255, 255, 0.9); |
||||
|
margin-bottom: 40rpx; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
.stats-row { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background: rgba(255, 255, 255, 0.15); |
||||
|
border-radius: 24rpx; |
||||
|
padding: 20rpx; |
||||
|
backdrop-filter: blur(10rpx); |
||||
|
} |
||||
|
|
||||
|
.stat-item { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.stat-number { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: white; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.stat-label { |
||||
|
font-size: 24rpx; |
||||
|
color: rgba(255, 255, 255, 0.85); |
||||
|
} |
||||
|
|
||||
|
.stat-divider { |
||||
|
width: 2rpx; |
||||
|
height: 40rpx; |
||||
|
background: rgba(255, 255, 255, 0.3); |
||||
|
} |
||||
|
|
||||
|
/* 搜索和筛选区域 */ |
||||
|
.search-filter-section { |
||||
|
background: white; |
||||
|
border-radius: 40rpx 40rpx 0 0; |
||||
|
margin-top: -30rpx; |
||||
|
position: relative; |
||||
|
z-index: 10; |
||||
|
padding: 30rpx 40rpx 0; |
||||
|
box-shadow: 0 -10rpx 30rpx rgba(44, 140, 52, 0.1); |
||||
|
} |
||||
|
|
||||
|
.search-wrapper { |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.search-box { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background: #f8faf9; |
||||
|
border-radius: 50rpx; |
||||
|
padding: 20rpx 30rpx; |
||||
|
border: 2rpx solid #e8f5e9; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.search-box:active { |
||||
|
background: #f0f9f1; |
||||
|
border-color: #2c8c34; |
||||
|
} |
||||
|
|
||||
|
.search-icon { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
margin-right: 20rpx; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
.search-box input { |
||||
|
flex: 1; |
||||
|
font-size: 30rpx; |
||||
|
color: #333; |
||||
|
height: 40rpx; |
||||
|
line-height: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.placeholder { |
||||
|
color: #999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.clear-btn { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background: #EAEBED; |
||||
|
border-radius: 50%; |
||||
|
margin-left: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.clear-icon { |
||||
|
width: 28rpx; |
||||
|
height: 28rpx; |
||||
|
} |
||||
|
|
||||
|
/* 筛选标签 */ |
||||
|
.filter-scroll { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
|
||||
|
.filter-tags { |
||||
|
display: inline-flex; |
||||
|
padding-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.filter-tag { |
||||
|
display: inline-flex; |
||||
|
align-items: center; |
||||
|
padding: 16rpx 28rpx; |
||||
|
margin-right: 20rpx; |
||||
|
background: #f8faf9; |
||||
|
border-radius: 30rpx; |
||||
|
border: 2rpx solid #e8f5e9; |
||||
|
color: #666; |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.filter-tag.active { |
||||
|
background: #2c8c34; |
||||
|
border-color: #2c8c34; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.tag-count { |
||||
|
background: rgba(255, 255, 255, 0.2); |
||||
|
color: #2c8c34; |
||||
|
font-size: 22rpx; |
||||
|
padding: 2rpx 10rpx; |
||||
|
border-radius: 20rpx; |
||||
|
margin-left: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.filter-tag.active .tag-count { |
||||
|
background: rgba(255, 255, 255, 0.3); |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.online-count { |
||||
|
color: #4caf50; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* 专家列表区域 */ |
||||
|
.expert-list-section { |
||||
|
padding: 0 30rpx 30rpx; |
||||
|
} |
||||
|
|
||||
|
.list-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 30rpx 0 20rpx; |
||||
|
border-bottom: 2rpx solid #f0f5f1; |
||||
|
} |
||||
|
|
||||
|
.list-title { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.list-count { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.expert-cards { |
||||
|
height: calc(100vh - 700rpx); |
||||
|
padding-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.empty-state { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 120rpx 0; |
||||
|
} |
||||
|
|
||||
|
.empty-image { |
||||
|
width: 240rpx; |
||||
|
height: 240rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.empty-title { |
||||
|
font-size: 34rpx; |
||||
|
color: #999; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/* 专家卡片 */ |
||||
|
.expert-card { |
||||
|
background: white; |
||||
|
border-radius: 30rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 2fr; |
||||
|
align-items: center; |
||||
|
box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.05); |
||||
|
border: 2rpx solid #f0f5f1; |
||||
|
transition: all 0.3s; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.expert-card:active { |
||||
|
transform: translateY(-4rpx); |
||||
|
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.1); |
||||
|
border-color: #e8f5e9; |
||||
|
} |
||||
|
|
||||
|
.card-left { |
||||
|
margin-right: 25rpx; |
||||
|
} |
||||
|
|
||||
|
.avatar-container { |
||||
|
position: relative; |
||||
|
width: 150rpx; |
||||
|
height: 180rpx; |
||||
|
border-radius: 16rpx; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.expert-avatar { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: #f5f7fa; |
||||
|
} |
||||
|
|
||||
|
.online-badge { |
||||
|
position: absolute; |
||||
|
bottom: 0rpx; |
||||
|
right: 0rpx; |
||||
|
padding: 5rpx 15rpx; |
||||
|
border-radius: 20rpx; |
||||
|
font-size: 18rpx; |
||||
|
font-weight: bold; |
||||
|
color: white; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.online-badge.online { |
||||
|
background: linear-gradient(135deg, #4caf50 0%, #2c8c34 100%); |
||||
|
} |
||||
|
|
||||
|
.online-badge.offline { |
||||
|
background: linear-gradient(135deg, #9e9e9e 0%, #757575 100%); |
||||
|
} |
||||
|
|
||||
|
.card-middle { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.name-title-row { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.expert-name { |
||||
|
font-size: 36rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-right: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.expert-title { |
||||
|
font-size: 26rpx; |
||||
|
color: #2c8c34; |
||||
|
background: #f0f9f1; |
||||
|
padding: 4rpx 12rpx; |
||||
|
border-radius: 8rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 信息展示区域 */ |
||||
|
.info-display { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 12rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.info-item { |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
font-size: 28rpx; |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
.info-label { |
||||
|
color: #666; |
||||
|
min-width: 150rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.info-value { |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.contact-btn { |
||||
|
font-size: 22rpx; |
||||
|
padding: 6rpx 20rpx; |
||||
|
border-radius: 30rpx; |
||||
|
border: none; |
||||
|
color: white; |
||||
|
font-weight: bold; |
||||
|
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1); |
||||
|
transition: all 0.3s; |
||||
|
display: inline-block; |
||||
|
position: absolute; |
||||
|
right: 20rpx; |
||||
|
bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.contact-btn.online-btn { |
||||
|
background: linear-gradient(135deg, #4caf50 0%, #2c8c34 100%); |
||||
|
} |
||||
|
|
||||
|
.contact-btn.offline-btn { |
||||
|
background: linear-gradient(135deg, #9e9e9e 0%, #757575 100%); |
||||
|
} |
||||
|
|
||||
|
.contact-btn:active { |
||||
|
transform: scale(0.95); |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
/* 联系方式弹窗 */ |
||||
|
.dialog-overlay { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 1000; |
||||
|
animation: fadeIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeIn { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.contact-modal { |
||||
|
width: 86vw; |
||||
|
max-height: 80vh; |
||||
|
background: white; |
||||
|
border-radius: 20rpx; |
||||
|
overflow: hidden; |
||||
|
animation: slideUp 0.4s cubic-bezier(0.22, 0.61, 0.36, 1); |
||||
|
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
@keyframes slideUp { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(100rpx) scale(0.95); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0) scale(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 可滚动的内容区域 */ |
||||
|
.modal-content { |
||||
|
flex: 1; |
||||
|
overflow-y: auto; |
||||
|
max-height: calc(80vh - 150rpx); |
||||
|
} |
||||
|
|
||||
|
/* 模态框头部 */ |
||||
|
.modal-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: flex-start; |
||||
|
padding: 40rpx 40rpx 30rpx; |
||||
|
background: linear-gradient(135deg, #f0f9f1 0%, #ffffff 100%); |
||||
|
border-bottom: 2rpx solid #f0f5f1; |
||||
|
} |
||||
|
|
||||
|
.header-left { |
||||
|
display: flex; |
||||
|
align-items: flex-start; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.modal-avatar { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
border-radius: 50%; |
||||
|
margin-right: 25rpx; |
||||
|
border: 4rpx solid white; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.expert-intro { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.modallei { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.modal-name { |
||||
|
display: block; |
||||
|
font-size: 38rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.title-status { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin: 10rpx 0; |
||||
|
} |
||||
|
|
||||
|
.modal-title { |
||||
|
font-size: 28rpx; |
||||
|
color: #2c8c34; |
||||
|
background: #f0f9f1; |
||||
|
padding: 0rpx 10rpx; |
||||
|
border-radius: 10rpx; |
||||
|
font-weight: 500; |
||||
|
margin: 0 20rpx; |
||||
|
} |
||||
|
|
||||
|
.modal-status { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
font-size: 26rpx; |
||||
|
padding: 6rpx 15rpx; |
||||
|
border-radius: 10rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.modal-status.online { |
||||
|
background: #e8f5e9; |
||||
|
color: #2c8c34; |
||||
|
} |
||||
|
|
||||
|
.modal-status.offline { |
||||
|
background: #f5f5f5; |
||||
|
color: #757575; |
||||
|
} |
||||
|
|
||||
|
.status-dot { |
||||
|
width: 12rpx; |
||||
|
height: 12rpx; |
||||
|
border-radius: 50%; |
||||
|
margin-right: 8rpx; |
||||
|
background: currentColor; |
||||
|
} |
||||
|
|
||||
|
/* 专家简介 */ |
||||
|
.expert-bio { |
||||
|
padding: 30rpx 40rpx; |
||||
|
border-bottom: 2rpx solid #f0f5f1; |
||||
|
} |
||||
|
|
||||
|
.bio-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.bio-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.bio-content { |
||||
|
font-size: 28rpx; |
||||
|
color: #555; |
||||
|
line-height: 1.6; |
||||
|
} |
||||
|
|
||||
|
/* 联系方式区域 */ |
||||
|
.contact-section { |
||||
|
padding: 30rpx 40rpx; |
||||
|
border-bottom: 2rpx solid #f0f5f1; |
||||
|
} |
||||
|
|
||||
|
.contact-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 25rpx 0; |
||||
|
} |
||||
|
|
||||
|
.contact-item:not(:last-child) { |
||||
|
border-bottom: 1rpx solid #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.contact-icon-container { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
border-radius: 18rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.phone-item .contact-icon-container { |
||||
|
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%); |
||||
|
} |
||||
|
|
||||
|
.email-item .contact-icon-container { |
||||
|
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); |
||||
|
} |
||||
|
|
||||
|
.institution-item .contact-icon-container { |
||||
|
background: linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%); |
||||
|
} |
||||
|
|
||||
|
.contact-item-icon { |
||||
|
width: 32rpx; |
||||
|
height: 32rpx; |
||||
|
} |
||||
|
|
||||
|
.contact-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.contact-label { |
||||
|
display: block; |
||||
|
font-size: 26rpx; |
||||
|
color: #888; |
||||
|
margin-bottom: 6rpx; |
||||
|
} |
||||
|
|
||||
|
.contact-value { |
||||
|
display: block; |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
font-weight: 600; |
||||
|
margin-bottom: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.contact-desc { |
||||
|
display: block; |
||||
|
font-size: 24rpx; |
||||
|
color: #aaa; |
||||
|
} |
||||
|
|
||||
|
.action-btn { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
border-radius: 50rpx; |
||||
|
font-size: 22rpx; |
||||
|
font-weight: 600; |
||||
|
border: none; |
||||
|
margin-left: 20rpx; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.call-btn { |
||||
|
background: linear-gradient(135deg, #4caf50 0%, #2c8c34 100%); |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.copy-btn { |
||||
|
background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%); |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.address-btn { |
||||
|
background: linear-gradient(135deg, #9c27b0 0%, #7b1fa2 100%); |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.action-btn:active { |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
/* 咨询时间 */ |
||||
|
.consultation-time { |
||||
|
padding: 30rpx 40rpx; |
||||
|
border-bottom: 2rpx solid #f0f5f1; |
||||
|
} |
||||
|
|
||||
|
.time-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
.time-title { |
||||
|
font-size: 30rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.time-content { |
||||
|
display: block; |
||||
|
font-size: 28rpx; |
||||
|
color: #555; |
||||
|
line-height: 1.5; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.time-slots { |
||||
|
display: flex; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.time-slot { |
||||
|
background: #f8faf9; |
||||
|
padding: 10rpx 20rpx; |
||||
|
border-radius: 15rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
border: 1rpx solid #e8f5e9; |
||||
|
} |
||||
|
|
||||
|
/* 操作按钮(固定在底部) */ |
||||
|
.modal-actions { |
||||
|
display: flex; |
||||
|
padding: 30rpx 40rpx; |
||||
|
gap: 20rpx; |
||||
|
border-top: 2rpx solid #f0f5f1; |
||||
|
background: white; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.secondary-btn, |
||||
|
.primary-btn { |
||||
|
flex: 1; |
||||
|
border-radius: 50rpx; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.secondary-btn { |
||||
|
background: #f8faf9; |
||||
|
color: #666; |
||||
|
border: 2rpx solid #e8f5e9; |
||||
|
} |
||||
|
|
||||
|
.primary-btn { |
||||
|
background: linear-gradient(135deg, #4caf50 0%, #2c8c34 100%); |
||||
|
color: white; |
||||
|
box-shadow: 0 6rpx 20rpx rgba(44, 140, 52, 0.3); |
||||
|
} |
||||
|
|
||||
|
.secondary-btn:active, |
||||
|
.primary-btn:active { |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
@ -0,0 +1,753 @@ |
|||||
|
// 获取应用实例
|
||||
|
const app = getApp() |
||||
|
|
||||
|
Page({ |
||||
|
data: { |
||||
|
// 专家信息
|
||||
|
expertInfo: { |
||||
|
id: 1, |
||||
|
name: '张明专家', |
||||
|
title: '资深畜牧兽医', |
||||
|
expertise: '牛羊疾病防治', |
||||
|
avatar: '/images/avatars/expert1.png', |
||||
|
online: true, |
||||
|
phone: '13800138000' |
||||
|
}, |
||||
|
|
||||
|
// 用户信息
|
||||
|
userInfo: { |
||||
|
id: 1001, |
||||
|
name: '养殖户', |
||||
|
avatar: '/images/avatars/user.png' |
||||
|
}, |
||||
|
|
||||
|
// 消息列表
|
||||
|
messageList: [], |
||||
|
scrollToView: '', |
||||
|
|
||||
|
// 输入相关
|
||||
|
inputValue: '', |
||||
|
inputFocus: false, |
||||
|
inputMode: 'keyboard', // keyboard or voice
|
||||
|
inputPlaceholder: '输入消息...', |
||||
|
|
||||
|
// 多媒体
|
||||
|
showMediaSheet: false, |
||||
|
|
||||
|
// 录音相关
|
||||
|
isRecording: false, |
||||
|
recordingTime: 0, |
||||
|
recordingTip: '上滑取消录音', |
||||
|
recordingTimer: null, |
||||
|
recordManager: null, |
||||
|
|
||||
|
// 页面状态
|
||||
|
isFirstLoad: true, |
||||
|
showDateDivider: true, |
||||
|
todayDate: '', |
||||
|
|
||||
|
// 消息ID计数器
|
||||
|
messageId: 1000, |
||||
|
|
||||
|
// 存储键名
|
||||
|
storageKey: 'consult_messages_' |
||||
|
}, |
||||
|
|
||||
|
onLoad: function(options) { |
||||
|
// 初始化录音管理器
|
||||
|
this.initRecordManager() |
||||
|
|
||||
|
// 获取今天日期
|
||||
|
this.setTodayDate() |
||||
|
|
||||
|
// 加载用户信息
|
||||
|
this.loadUserInfo() |
||||
|
|
||||
|
// 加载专家信息
|
||||
|
if (options.expertId) { |
||||
|
this.loadExpertInfo(options.expertId) |
||||
|
} |
||||
|
|
||||
|
// 加载聊天记录
|
||||
|
this.loadChatHistory() |
||||
|
|
||||
|
// 设置键盘监听
|
||||
|
wx.onKeyboardHeightChange(this.onKeyboardHeightChange) |
||||
|
|
||||
|
// 模拟首次进入时的欢迎消息
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ isFirstLoad: false }) |
||||
|
}, 2000) |
||||
|
}, |
||||
|
|
||||
|
onUnload: function() { |
||||
|
// 清理定时器
|
||||
|
if (this.data.recordingTimer) { |
||||
|
clearInterval(this.data.recordingTimer) |
||||
|
} |
||||
|
|
||||
|
// 移除监听器
|
||||
|
wx.offKeyboardHeightChange() |
||||
|
|
||||
|
// 保存聊天记录
|
||||
|
this.saveChatHistory() |
||||
|
}, |
||||
|
|
||||
|
onShow: function() { |
||||
|
// 页面显示时自动滚动到底部
|
||||
|
setTimeout(() => { |
||||
|
this.scrollToBottom() |
||||
|
}, 300) |
||||
|
}, |
||||
|
|
||||
|
// 初始化录音管理器
|
||||
|
initRecordManager: function() { |
||||
|
this.recordManager = wx.getRecorderManager() |
||||
|
|
||||
|
this.recordManager.onStart(() => { |
||||
|
console.log('录音开始') |
||||
|
}) |
||||
|
|
||||
|
this.recordManager.onStop((res) => { |
||||
|
const { tempFilePath, duration } = res |
||||
|
if (tempFilePath) { |
||||
|
this.sendAudioMessage(tempFilePath, Math.floor(duration / 1000)) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
this.recordManager.onError((err) => { |
||||
|
console.error('录音失败:', err) |
||||
|
wx.showToast({ |
||||
|
title: '录音失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
this.setData({ isRecording: false }) |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 设置今天日期
|
||||
|
setTodayDate: function() { |
||||
|
const now = new Date() |
||||
|
const month = now.getMonth() + 1 |
||||
|
const date = now.getDate() |
||||
|
const week = ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] |
||||
|
this.setData({ |
||||
|
todayDate: `${month}月${date}日 星期${week}` |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 加载用户信息
|
||||
|
loadUserInfo: function() { |
||||
|
const userInfo = wx.getStorageSync('userInfo') || this.data.userInfo |
||||
|
this.setData({ userInfo }) |
||||
|
}, |
||||
|
|
||||
|
// 加载专家信息
|
||||
|
loadExpertInfo: function(expertId) { |
||||
|
// 这里应该是API请求,暂时使用模拟数据
|
||||
|
wx.showLoading({ title: '加载中...' }) |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
const expertInfo = { |
||||
|
id: expertId, |
||||
|
name: ['张明', '李华', '王强', '赵刚'][expertId - 1] + '专家', |
||||
|
title: '资深畜牧兽医', |
||||
|
expertise: '牛羊疾病防治', |
||||
|
avatar: `/images/avatars/expert${expertId}.png`, |
||||
|
online: Math.random() > 0.3, |
||||
|
phone: '138' + Math.floor(Math.random() * 100000000).toString().padStart(8, '0') |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
expertInfo, |
||||
|
storageKey: `consult_messages_${expertId}_${this.data.userInfo.id}` |
||||
|
}) |
||||
|
|
||||
|
wx.hideLoading() |
||||
|
}, 500) |
||||
|
}, |
||||
|
|
||||
|
// 加载聊天记录
|
||||
|
loadChatHistory: function() { |
||||
|
const storageKey = this.data.storageKey |
||||
|
const savedMessages = wx.getStorageSync(storageKey) || [] |
||||
|
|
||||
|
if (savedMessages.length > 0) { |
||||
|
// 处理消息时间显示
|
||||
|
const processedMessages = this.processMessageTimes(savedMessages) |
||||
|
this.setData({ |
||||
|
messageList: processedMessages, |
||||
|
isFirstLoad: false |
||||
|
}) |
||||
|
|
||||
|
// 滚动到底部
|
||||
|
setTimeout(() => { |
||||
|
this.scrollToBottom() |
||||
|
}, 200) |
||||
|
} else { |
||||
|
// 初始化第一条欢迎消息
|
||||
|
this.initWelcomeMessage() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 处理消息时间显示
|
||||
|
processMessageTimes: function(messages) { |
||||
|
if (!messages || messages.length === 0) return messages |
||||
|
|
||||
|
let lastTimestamp = 0 |
||||
|
|
||||
|
return messages.map((msg, index) => { |
||||
|
const currentTimestamp = msg.timestamp |
||||
|
|
||||
|
// 如果两条消息间隔超过5分钟,显示时间
|
||||
|
const timeDiff = currentTimestamp - lastTimestamp |
||||
|
msg.showTime = timeDiff > 5 * 60 * 1000 || index === 0 |
||||
|
|
||||
|
if (msg.showTime) { |
||||
|
lastTimestamp = currentTimestamp |
||||
|
} |
||||
|
|
||||
|
return msg |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 初始化欢迎消息
|
||||
|
initWelcomeMessage: function() { |
||||
|
const welcomeMessage = { |
||||
|
id: 'welcome-' + Date.now(), |
||||
|
sender: 'expert', |
||||
|
type: 'text', |
||||
|
content: '您好,我是' + this.data.expertInfo.name + ',有什么可以帮您?', |
||||
|
timestamp: Date.now() - 60000, |
||||
|
showTime: true |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
messageList: [welcomeMessage] |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 保存聊天记录
|
||||
|
saveChatHistory: function() { |
||||
|
const { storageKey, messageList } = this.data |
||||
|
if (messageList.length > 0) { |
||||
|
try { |
||||
|
wx.setStorageSync(storageKey, messageList) |
||||
|
} catch (e) { |
||||
|
console.error('保存聊天记录失败:', e) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 返回上一页
|
||||
|
goBack: function() { |
||||
|
wx.navigateBack() |
||||
|
}, |
||||
|
|
||||
|
// 打电话
|
||||
|
makePhoneCall: function() { |
||||
|
const phone = this.data.expertInfo.phone |
||||
|
wx.makePhoneCall({ |
||||
|
phoneNumber: phone, |
||||
|
fail: () => { |
||||
|
wx.showToast({ |
||||
|
title: '拨打失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 输入处理
|
||||
|
onInput: function(e) { |
||||
|
this.setData({ |
||||
|
inputValue: e.detail.value |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 发送文本消息
|
||||
|
sendTextMessage: function() { |
||||
|
const content = this.data.inputValue.trim() |
||||
|
if (!content) return |
||||
|
|
||||
|
const newMessage = { |
||||
|
id: 'msg-' + (++this.data.messageId), |
||||
|
sender: 'user', |
||||
|
type: 'text', |
||||
|
content: content, |
||||
|
timestamp: Date.now(), |
||||
|
status: 'sending' |
||||
|
} |
||||
|
|
||||
|
// 添加到消息列表
|
||||
|
this.addMessageToList(newMessage) |
||||
|
|
||||
|
// 清空输入框
|
||||
|
this.setData({ |
||||
|
inputValue: '', |
||||
|
inputFocus: false |
||||
|
}) |
||||
|
|
||||
|
// 模拟发送成功
|
||||
|
setTimeout(() => { |
||||
|
this.updateMessageStatus(newMessage.id, 'success') |
||||
|
|
||||
|
// 模拟专家回复
|
||||
|
setTimeout(() => { |
||||
|
this.receiveExpertReply() |
||||
|
}, 1000) |
||||
|
}, 500) |
||||
|
}, |
||||
|
|
||||
|
// 添加消息到列表
|
||||
|
addMessageToList: function(message) { |
||||
|
const { messageList } = this.data |
||||
|
|
||||
|
// 确定是否需要显示时间
|
||||
|
const lastMessage = messageList[messageList.length - 1] |
||||
|
const timeDiff = lastMessage ? message.timestamp - lastMessage.timestamp : 0 |
||||
|
message.showTime = timeDiff > 5 * 60 * 1000 || !lastMessage |
||||
|
|
||||
|
messageList.push(message) |
||||
|
|
||||
|
this.setData({ |
||||
|
messageList, |
||||
|
scrollToView: 'msg-' + message.id |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 更新消息状态
|
||||
|
updateMessageStatus: function(messageId, status) { |
||||
|
const { messageList } = this.data |
||||
|
const index = messageList.findIndex(msg => msg.id === messageId) |
||||
|
|
||||
|
if (index !== -1) { |
||||
|
messageList[index].status = status |
||||
|
this.setData({ messageList }) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 接收专家回复
|
||||
|
receiveExpertReply: function() { |
||||
|
const replies = [ |
||||
|
'收到您的消息,让我分析一下您说的情况。', |
||||
|
'建议您提供更多细节,比如发病时间、具体症状等。', |
||||
|
'根据描述,可能是饲料问题引起的,建议调整饲料配方。', |
||||
|
'可以考虑添加一些维生素补充剂,改善食欲问题。', |
||||
|
'最好能提供照片,这样我可以更准确地判断情况。' |
||||
|
] |
||||
|
|
||||
|
const randomReply = replies[Math.floor(Math.random() * replies.length)] |
||||
|
|
||||
|
const newMessage = { |
||||
|
id: 'exp-' + Date.now(), |
||||
|
sender: 'expert', |
||||
|
type: 'text', |
||||
|
content: randomReply, |
||||
|
timestamp: Date.now(), |
||||
|
showTime: false |
||||
|
} |
||||
|
|
||||
|
this.addMessageToList(newMessage) |
||||
|
}, |
||||
|
|
||||
|
// 切换输入模式
|
||||
|
switchInputMode: function() { |
||||
|
const newMode = this.data.inputMode === 'keyboard' ? 'voice' : 'keyboard' |
||||
|
const placeholder = newMode === 'voice' ? '按住说话' : '输入消息...' |
||||
|
|
||||
|
this.setData({ |
||||
|
inputMode: newMode, |
||||
|
inputPlaceholder: placeholder, |
||||
|
inputFocus: newMode === 'keyboard' |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 显示多媒体选择面板
|
||||
|
showMediaActionSheet: function() { |
||||
|
this.setData({ |
||||
|
showMediaSheet: true, |
||||
|
inputFocus: false |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 隐藏多媒体选择面板
|
||||
|
hideMediaActionSheet: function() { |
||||
|
this.setData({ |
||||
|
showMediaSheet: false |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 选择图片
|
||||
|
chooseImage: function() { |
||||
|
this.hideMediaActionSheet() |
||||
|
|
||||
|
wx.chooseImage({ |
||||
|
count: 9, |
||||
|
sizeType: ['compressed'], |
||||
|
sourceType: ['album'], |
||||
|
success: (res) => { |
||||
|
this.uploadImages(res.tempFilePaths) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 拍照
|
||||
|
takePhoto: function() { |
||||
|
this.hideMediaActionSheet() |
||||
|
|
||||
|
wx.chooseImage({ |
||||
|
count: 1, |
||||
|
sizeType: ['compressed'], |
||||
|
sourceType: ['camera'], |
||||
|
success: (res) => { |
||||
|
this.uploadImages(res.tempFilePaths) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 选择视频
|
||||
|
chooseVideo: function() { |
||||
|
this.hideMediaActionSheet() |
||||
|
|
||||
|
wx.chooseVideo({ |
||||
|
sourceType: ['album'], |
||||
|
compressed: true, |
||||
|
maxDuration: 60, |
||||
|
success: (res) => { |
||||
|
this.uploadVideo(res.tempFilePath, res.thumbTempFilePath) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 录制语音
|
||||
|
recordAudio: function() { |
||||
|
this.hideMediaActionSheet() |
||||
|
this.startVoiceRecord() |
||||
|
}, |
||||
|
|
||||
|
// 选择文件
|
||||
|
chooseFile: function() { |
||||
|
this.hideMediaActionSheet() |
||||
|
|
||||
|
wx.chooseMessageFile({ |
||||
|
count: 1, |
||||
|
type: 'all', |
||||
|
success: (res) => { |
||||
|
const file = res.tempFiles[0] |
||||
|
this.uploadFile(file.path, file.name, file.size) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 上传图片
|
||||
|
uploadImages: function(tempFilePaths) { |
||||
|
tempFilePaths.forEach((tempFilePath, index) => { |
||||
|
const fileName = 'image_' + Date.now() + '_' + index + '.jpg' |
||||
|
this.uploadFile(tempFilePath, fileName, 0, 'image') |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 上传视频
|
||||
|
uploadVideo: function(tempFilePath, thumbPath) { |
||||
|
const fileName = 'video_' + Date.now() + '.mp4' |
||||
|
this.uploadFile(tempFilePath, fileName, 0, 'video', thumbPath) |
||||
|
}, |
||||
|
|
||||
|
// 通用文件上传
|
||||
|
uploadFile: function(tempFilePath, fileName, fileSize = 0, type = 'file', thumbPath = '') { |
||||
|
// 获取文件扩展名
|
||||
|
const extension = fileName.split('.').pop().toLowerCase() |
||||
|
|
||||
|
// 创建消息
|
||||
|
const messageId = 'file-' + Date.now() |
||||
|
const message = { |
||||
|
id: messageId, |
||||
|
sender: 'user', |
||||
|
type: type, |
||||
|
content: tempFilePath, |
||||
|
thumb: thumbPath, |
||||
|
fileName: fileName, |
||||
|
fileSize: fileSize, |
||||
|
extension: extension, |
||||
|
timestamp: Date.now(), |
||||
|
status: 'uploading', |
||||
|
progress: 0 |
||||
|
} |
||||
|
|
||||
|
this.addMessageToList(message) |
||||
|
|
||||
|
// 模拟上传过程
|
||||
|
let progress = 0 |
||||
|
const uploadInterval = setInterval(() => { |
||||
|
progress += Math.random() * 20 + 10 |
||||
|
|
||||
|
if (progress >= 100) { |
||||
|
progress = 100 |
||||
|
clearInterval(uploadInterval) |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
this.updateMessageStatus(messageId, 'success') |
||||
|
|
||||
|
// 清除进度信息
|
||||
|
const { messageList } = this.data |
||||
|
const index = messageList.findIndex(msg => msg.id === messageId) |
||||
|
if (index !== -1) { |
||||
|
delete messageList[index].progress |
||||
|
this.setData({ messageList }) |
||||
|
|
||||
|
// 模拟专家回复
|
||||
|
if (type === 'image' || type === 'video') { |
||||
|
setTimeout(() => { |
||||
|
this.receiveMediaReply(type) |
||||
|
}, 800) |
||||
|
} |
||||
|
} |
||||
|
}, 200) |
||||
|
} |
||||
|
|
||||
|
// 更新进度
|
||||
|
const { messageList } = this.data |
||||
|
const index = messageList.findIndex(msg => msg.id === messageId) |
||||
|
if (index !== -1) { |
||||
|
messageList[index].progress = Math.min(progress, 100) |
||||
|
this.setData({ messageList }) |
||||
|
} |
||||
|
}, 100) |
||||
|
}, |
||||
|
|
||||
|
// 接收媒体回复
|
||||
|
receiveMediaReply: function(type) { |
||||
|
const imageReplies = [ |
||||
|
'照片收到了,牛的状况看起来确实不太理想。', |
||||
|
'从照片看,饲养环境需要改善一下。', |
||||
|
'图片清晰,我可以更准确地判断问题了。' |
||||
|
] |
||||
|
|
||||
|
const videoReplies = [ |
||||
|
'视频看到了,动物的精神状态需要关注。', |
||||
|
'从视频可以观察到更多细节,这很有帮助。', |
||||
|
'视频内容很有价值,让我了解了具体情况。' |
||||
|
] |
||||
|
|
||||
|
const replies = type === 'image' ? imageReplies : videoReplies |
||||
|
const randomReply = replies[Math.floor(Math.random() * replies.length)] |
||||
|
|
||||
|
const newMessage = { |
||||
|
id: 'exp-media-' + Date.now(), |
||||
|
sender: 'expert', |
||||
|
type: 'text', |
||||
|
content: randomReply, |
||||
|
timestamp: Date.now(), |
||||
|
showTime: false |
||||
|
} |
||||
|
|
||||
|
this.addMessageToList(newMessage) |
||||
|
}, |
||||
|
|
||||
|
// 开始语音录制
|
||||
|
startVoiceRecord: function(e) { |
||||
|
this.setData({ |
||||
|
isRecording: true, |
||||
|
recordingTime: 0, |
||||
|
recordingTip: '上滑取消录音' |
||||
|
}) |
||||
|
|
||||
|
// 开始录音
|
||||
|
this.recordManager.start({ |
||||
|
duration: 60000, // 最长60秒
|
||||
|
sampleRate: 44100, |
||||
|
numberOfChannels: 1, |
||||
|
encodeBitRate: 192000, |
||||
|
format: 'mp3' |
||||
|
}) |
||||
|
|
||||
|
// 开始计时
|
||||
|
const timer = setInterval(() => { |
||||
|
const time = this.data.recordingTime + 1 |
||||
|
this.setData({ recordingTime: time }) |
||||
|
}, 1000) |
||||
|
|
||||
|
this.setData({ recordingTimer: timer }) |
||||
|
}, |
||||
|
|
||||
|
// 结束语音录制
|
||||
|
endVoiceRecord: function() { |
||||
|
if (this.data.isRecording) { |
||||
|
this.recordManager.stop() |
||||
|
|
||||
|
if (this.data.recordingTimer) { |
||||
|
clearInterval(this.data.recordingTimer) |
||||
|
} |
||||
|
|
||||
|
this.setData({ |
||||
|
isRecording: false, |
||||
|
recordingTime: 0 |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 发送语音消息
|
||||
|
sendAudioMessage: function(tempFilePath, duration) { |
||||
|
const message = { |
||||
|
id: 'audio-' + Date.now(), |
||||
|
sender: 'user', |
||||
|
type: 'audio', |
||||
|
content: tempFilePath, |
||||
|
duration: duration, |
||||
|
timestamp: Date.now(), |
||||
|
status: 'sending' |
||||
|
} |
||||
|
|
||||
|
this.addMessageToList(message) |
||||
|
|
||||
|
// 模拟发送成功
|
||||
|
setTimeout(() => { |
||||
|
this.updateMessageStatus(message.id, 'success') |
||||
|
|
||||
|
// 模拟专家回复
|
||||
|
setTimeout(() => { |
||||
|
const reply = { |
||||
|
id: 'exp-audio-' + Date.now(), |
||||
|
sender: 'expert', |
||||
|
type: 'text', |
||||
|
content: '语音收到了,我会仔细听取分析。', |
||||
|
timestamp: Date.now(), |
||||
|
showTime: false |
||||
|
} |
||||
|
this.addMessageToList(reply) |
||||
|
}, 1500) |
||||
|
}, 800) |
||||
|
}, |
||||
|
|
||||
|
// 预览图片
|
||||
|
previewImage: function(e) { |
||||
|
const url = e.currentTarget.dataset.url |
||||
|
wx.previewImage({ |
||||
|
current: url, |
||||
|
urls: [url] |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 下载文件
|
||||
|
downloadFile: function(e) { |
||||
|
const url = e.currentTarget.dataset.url |
||||
|
wx.showLoading({ title: '下载中...' }) |
||||
|
|
||||
|
wx.downloadFile({ |
||||
|
url: url, |
||||
|
success: (res) => { |
||||
|
wx.hideLoading() |
||||
|
wx.showToast({ |
||||
|
title: '下载成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
|
||||
|
// 保存到相册(如果是图片)
|
||||
|
if (url.match(/\.(jpg|jpeg|png|gif)$/i)) { |
||||
|
wx.saveImageToPhotosAlbum({ |
||||
|
filePath: res.tempFilePath, |
||||
|
success: () => { |
||||
|
wx.showToast({ |
||||
|
title: '已保存到相册', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
fail: () => { |
||||
|
wx.hideLoading() |
||||
|
wx.showToast({ |
||||
|
title: '下载失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
// 键盘高度变化
|
||||
|
onKeyboardHeightChange: function(res) { |
||||
|
if (res.height > 0) { |
||||
|
// 键盘弹出时隐藏多媒体面板
|
||||
|
this.setData({ showMediaSheet: false }) |
||||
|
|
||||
|
// 滚动到底部
|
||||
|
setTimeout(() => { |
||||
|
this.scrollToBottom() |
||||
|
}, 100) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 滚动到底部
|
||||
|
scrollToBottom: function() { |
||||
|
if (this.data.messageList.length > 0) { |
||||
|
const lastMessage = this.data.messageList[this.data.messageList.length - 1] |
||||
|
this.setData({ |
||||
|
scrollToView: lastMessage.id |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 格式化时间(微信样式)
|
||||
|
formatTime: function(timestamp) { |
||||
|
const now = new Date() |
||||
|
const date = new Date(timestamp) |
||||
|
const diff = now - date |
||||
|
|
||||
|
// 今天
|
||||
|
if (date.toDateString() === now.toDateString()) { |
||||
|
return this.formatMessageTime(timestamp) |
||||
|
} |
||||
|
|
||||
|
// 昨天
|
||||
|
const yesterday = new Date(now) |
||||
|
yesterday.setDate(yesterday.getDate() - 1) |
||||
|
if (date.toDateString() === yesterday.toDateString()) { |
||||
|
return '昨天 ' + this.formatMessageTime(timestamp) |
||||
|
} |
||||
|
|
||||
|
// 一周内
|
||||
|
if (diff < 7 * 24 * 60 * 60 * 1000) { |
||||
|
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] |
||||
|
return weekDays[date.getDay()] + ' ' + this.formatMessageTime(timestamp) |
||||
|
} |
||||
|
|
||||
|
// 今年内
|
||||
|
if (date.getFullYear() === now.getFullYear()) { |
||||
|
return `${date.getMonth() + 1}月${date.getDate()}日 ${this.formatMessageTime(timestamp)}` |
||||
|
} |
||||
|
|
||||
|
// 更早
|
||||
|
return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${this.formatMessageTime(timestamp)}` |
||||
|
}, |
||||
|
|
||||
|
// 格式化消息时间(HH:mm)
|
||||
|
formatMessageTime: function(timestamp) { |
||||
|
const date = new Date(timestamp) |
||||
|
const hours = date.getHours().toString().padStart(2, '0') |
||||
|
const minutes = date.getMinutes().toString().padStart(2, '0') |
||||
|
return `${hours}:${minutes}` |
||||
|
}, |
||||
|
|
||||
|
// 格式化文件大小
|
||||
|
formatFileSize: function(bytes) { |
||||
|
if (bytes === 0) return '未知大小' |
||||
|
|
||||
|
const units = ['B', 'KB', 'MB', 'GB'] |
||||
|
let size = bytes |
||||
|
let unitIndex = 0 |
||||
|
|
||||
|
while (size >= 1024 && unitIndex < units.length - 1) { |
||||
|
size /= 1024 |
||||
|
unitIndex++ |
||||
|
} |
||||
|
|
||||
|
return size.toFixed(1) + units[unitIndex] |
||||
|
}, |
||||
|
|
||||
|
// 阻止事件冒泡
|
||||
|
stopPropagation: function() { |
||||
|
// 空函数,仅用于阻止事件冒泡
|
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,4 @@ |
|||||
|
{ |
||||
|
"navigationBarTitleText":"问专家", |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,296 @@ |
|||||
|
<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"> |
||||
|
<text class="expert-name">{{expertInfo.name}}</text> |
||||
|
<view class="expert-status"> |
||||
|
<view class="status-dot {{expertInfo.online ? 'online' : 'offline'}}"></view> |
||||
|
<text class="status-text">{{expertInfo.online ? '在线' : '离线'}}</text> |
||||
|
</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> |
||||
|
|
||||
|
<!-- 聊天内容区域 --> |
||||
|
<scroll-view class="chat-container" scroll-y scroll-into-view="{{scrollToView}}" scroll-with-animation="true"> |
||||
|
<!-- 首次咨询欢迎语 --> |
||||
|
<view class="system-welcome" wx:if="{{isFirstLoad}}"> |
||||
|
<view class="welcome-avatar"> |
||||
|
<image src="{{expertInfo.avatar}}"></image> |
||||
|
</view> |
||||
|
<text class="welcome-name">{{expertInfo.name}}</text> |
||||
|
<text class="welcome-title">{{expertInfo.title}} · {{expertInfo.expertise}}</text> |
||||
|
<text class="welcome-tip">您好,我是{{expertInfo.name}},很高兴为您服务!</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 日期分隔线 --> |
||||
|
<view class="date-divider" wx:if="{{showDateDivider}}"> |
||||
|
<text class="date-text">{{todayDate}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 消息列表 --> |
||||
|
<block wx:for="{{messageList}}" wx:key="id"> |
||||
|
<!-- 时间分隔 --> |
||||
|
<view class="time-divider" wx:if="{{item.showTime}}"> |
||||
|
<text>{{formatTime(item.timestamp)}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 对方消息 --> |
||||
|
<view class="message-item message-left" wx:if="{{item.sender === 'expert'}}"> |
||||
|
<view class="message-avatar"> |
||||
|
<image src="{{expertInfo.avatar}}"></image> |
||||
|
</view> |
||||
|
|
||||
|
<view class="message-content-wrapper"> |
||||
|
<!-- 文本消息 --> |
||||
|
<view class="message-bubble message-bubble-left" wx:if="{{item.type === 'text'}}"> |
||||
|
<text class="message-text">{{item.content}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 图片消息 --> |
||||
|
<view class="message-bubble message-bubble-left" wx:elif="{{item.type === 'image'}}"> |
||||
|
<image src="{{item.content}}" class="message-image" mode="widthFix" bindtap="previewImage" data-url="{{item.content}}"></image> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频消息 --> |
||||
|
<view class="message-bubble message-bubble-left" wx:elif="{{item.type === 'video'}}"> |
||||
|
<video src="{{item.content}}" class="message-video" controls poster="{{item.thumb}}"></video> |
||||
|
<view class="video-play-btn"> |
||||
|
<image src="/images/icons/play.png"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 语音消息 --> |
||||
|
<view class="message-bubble message-bubble-left message-audio" wx:elif="{{item.type === 'audio'}}"> |
||||
|
<image src="/images/icons/audio-left.png" class="audio-icon-left"></image> |
||||
|
<view class="audio-content"> |
||||
|
<view class="audio-wave"> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
</view> |
||||
|
<text class="audio-duration">{{item.duration}}''</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文件消息 --> |
||||
|
<view class="message-bubble message-bubble-left message-file" wx:elif="{{item.type === 'file'}}"> |
||||
|
<view class="file-icon-box"> |
||||
|
<image src="/images/icons/file.png" class="file-icon"></image> |
||||
|
<text class="file-extension">{{item.extension}}</text> |
||||
|
</view> |
||||
|
<view class="file-info"> |
||||
|
<text class="file-name">{{item.fileName}}</text> |
||||
|
<text class="file-size">{{formatFileSize(item.fileSize)}}</text> |
||||
|
</view> |
||||
|
<button class="file-download-btn" bindtap="downloadFile" data-url="{{item.content}}">下载</button> |
||||
|
</view> |
||||
|
|
||||
|
<text class="message-time">{{formatMessageTime(item.timestamp)}}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 我的消息 --> |
||||
|
<view class="message-item message-right" wx:else> |
||||
|
<view class="message-content-wrapper"> |
||||
|
<!-- 消息状态 --> |
||||
|
<view class="message-status"> |
||||
|
<image wx:if="{{item.status === 'sending'}}" src="/images/icons/sending.png" class="status-icon"></image> |
||||
|
<image wx:if="{{item.status === 'success'}}" src="/images/icons/success.png" class="status-icon"></image> |
||||
|
<image wx:if="{{item.status === 'error'}}" src="/images/icons/error.png" class="status-icon"></image> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文本消息 --> |
||||
|
<view class="message-bubble message-bubble-right" wx:if="{{item.type === 'text'}}"> |
||||
|
<text class="message-text">{{item.content}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 图片消息 --> |
||||
|
<view class="message-bubble message-bubble-right" wx:elif="{{item.type === 'image'}}"> |
||||
|
<image src="{{item.content}}" class="message-image" mode="widthFix" bindtap="previewImage" data-url="{{item.content}}"></image> |
||||
|
<view class="upload-progress" wx:if="{{item.status === 'uploading'}}"> |
||||
|
<view class="progress-circle"> |
||||
|
<view class="progress-fill" style="transform: rotate({{item.progress * 3.6}}deg);"></view> |
||||
|
<text class="progress-text">{{item.progress}}%</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 视频消息 --> |
||||
|
<view class="message-bubble message-bubble-right" wx:elif="{{item.type === 'video'}}"> |
||||
|
<video src="{{item.content}}" class="message-video" controls poster="{{item.thumb}}"></video> |
||||
|
<view class="video-play-btn"> |
||||
|
<image src="/images/icons/play.png"></image> |
||||
|
</view> |
||||
|
<view class="upload-progress" wx:if="{{item.status === 'uploading'}}"> |
||||
|
<view class="progress-circle"> |
||||
|
<view class="progress-fill" style="transform: rotate({{item.progress * 3.6}}deg);"></view> |
||||
|
<text class="progress-text">{{item.progress}}%</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 语音消息 --> |
||||
|
<view class="message-bubble message-bubble-right message-audio" wx:elif="{{item.type === 'audio'}}"> |
||||
|
<view class="audio-content"> |
||||
|
<view class="audio-wave"> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
<view class="wave-bar"></view> |
||||
|
</view> |
||||
|
<text class="audio-duration">{{item.duration}}''</text> |
||||
|
</view> |
||||
|
<image src="/images/icons/audio-right.png" class="audio-icon-right"></image> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文件消息 --> |
||||
|
<view class="message-bubble message-bubble-right message-file" wx:elif="{{item.type === 'file'}}"> |
||||
|
<view class="file-icon-box"> |
||||
|
<image src="/images/icons/file.png" class="file-icon"></image> |
||||
|
<text class="file-extension">{{item.extension}}</text> |
||||
|
</view> |
||||
|
<view class="file-info"> |
||||
|
<text class="file-name">{{item.fileName}}</text> |
||||
|
<text class="file-size">{{formatFileSize(item.fileSize)}}</text> |
||||
|
</view> |
||||
|
<view class="upload-progress" wx:if="{{item.status === 'uploading'}}"> |
||||
|
<view class="progress-circle"> |
||||
|
<view class="progress-fill" style="transform: rotate({{item.progress * 3.6}}deg);"></view> |
||||
|
<text class="progress-text">{{item.progress}}%</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<text class="message-time">{{formatMessageTime(item.timestamp)}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="message-avatar"> |
||||
|
<image src="{{userInfo.avatar}}"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
</block> |
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 输入区域 --> |
||||
|
<view class="input-section"> |
||||
|
<!-- 录音面板 --> |
||||
|
<view class="voice-panel" wx:if="{{inputMode === 'voice'}}"> |
||||
|
<button class="voice-record-btn" bindtouchstart="startVoiceRecord" bindtouchend="endVoiceRecord"> |
||||
|
<image src="/images/icons/mic.png" class="mic-icon"></image> |
||||
|
<text class="voice-tip">按住说话</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 正常输入面板 --> |
||||
|
<view class="input-panel" wx:else> |
||||
|
<!-- 语音/键盘切换按钮 --> |
||||
|
<button class="input-mode-btn" bindtap="switchInputMode"> |
||||
|
<image src="{{inputMode === 'keyboard' ? '/images/icons/voice.png' : '/images/icons/keyboard.png'}}" class="mode-icon"></image> |
||||
|
</button> |
||||
|
|
||||
|
<!-- 输入框 --> |
||||
|
<view class="input-box-wrapper"> |
||||
|
<input |
||||
|
type="text" |
||||
|
class="chat-input" |
||||
|
placeholder="{{inputPlaceholder}}" |
||||
|
placeholder-class="placeholder" |
||||
|
value="{{inputValue}}" |
||||
|
bindinput="onInput" |
||||
|
bindconfirm="sendTextMessage" |
||||
|
confirm-type="send" |
||||
|
focus="{{inputFocus}}" |
||||
|
adjust-position="{{false}}" |
||||
|
/> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 多媒体按钮 --> |
||||
|
<button class="media-btn" bindtap="showMediaActionSheet"> |
||||
|
<image src="/images/icons/add.png" class="media-icon"></image> |
||||
|
</button> |
||||
|
|
||||
|
<!-- 发送按钮 --> |
||||
|
<button class="send-btn" bindtap="sendTextMessage" wx:if="{{inputValue.trim()}}"> |
||||
|
<image src="/images/icons/send.png" class="send-icon"></image> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 多媒体选择面板 --> |
||||
|
<view class="media-action-sheet" wx:if="{{showMediaSheet}}" catchtap="hideMediaActionSheet"> |
||||
|
<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.png"></image> |
||||
|
</button> |
||||
|
</view> |
||||
|
|
||||
|
<view class="media-options"> |
||||
|
<button class="media-option" bindtap="chooseImage"> |
||||
|
<view class="option-icon photo-icon"> |
||||
|
<image src="/images/icons/photo.png"></image> |
||||
|
</view> |
||||
|
<text class="option-text">照片</text> |
||||
|
</button> |
||||
|
|
||||
|
<button class="media-option" bindtap="takePhoto"> |
||||
|
<view class="option-icon camera-icon"> |
||||
|
<image src="/images/icons/camera.png"></image> |
||||
|
</view> |
||||
|
<text class="option-text">拍摄</text> |
||||
|
</button> |
||||
|
|
||||
|
<button class="media-option" bindtap="chooseVideo"> |
||||
|
<view class="option-icon video-icon"> |
||||
|
<image src="/images/icons/video.png"></image> |
||||
|
</view> |
||||
|
<text class="option-text">视频</text> |
||||
|
</button> |
||||
|
|
||||
|
<button class="media-option" bindtap="recordAudio"> |
||||
|
<view class="option-icon audio-icon"> |
||||
|
<image src="/images/icons/voice.png"></image> |
||||
|
</view> |
||||
|
<text class="option-text">语音</text> |
||||
|
</button> |
||||
|
|
||||
|
<button class="media-option" bindtap="chooseFile"> |
||||
|
<view class="option-icon file-icon"> |
||||
|
<image src="/images/icons/file2.png"></image> |
||||
|
</view> |
||||
|
<text class="option-text">文件</text> |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 录音提示 --> |
||||
|
<view class="recording-modal" wx:if="{{isRecording}}"> |
||||
|
<view class="recording-box"> |
||||
|
<view class="recording-wave-box"> |
||||
|
<view class="recording-wave"></view> |
||||
|
<image src="/images/icons/mic2.png" class="recording-mic-icon"></image> |
||||
|
</view> |
||||
|
<text class="recording-tip">{{recordingTip}}</text> |
||||
|
<text class="recording-time">{{recordingTime}}s</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
@ -0,0 +1,928 @@ |
|||||
|
/* 页面整体样式 */ |
||||
|
.consult-page { |
||||
|
width: 100vw; |
||||
|
height: 100vh; |
||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; |
||||
|
} |
||||
|
|
||||
|
/* 头部样式 */ |
||||
|
.consult-header { |
||||
|
background: rgba(255, 255, 255, 0.95); |
||||
|
backdrop-filter: blur(20px); |
||||
|
padding: 15rpx 30rpx; |
||||
|
border-bottom: 1rpx solid rgba(0, 0, 0, 0.1); |
||||
|
position: relative; |
||||
|
z-index: 100; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
.header-content { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
height: 90rpx; |
||||
|
} |
||||
|
|
||||
|
.header-left { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.header-center { |
||||
|
flex: 2; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.header-right { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
justify-content: flex-end; |
||||
|
} |
||||
|
|
||||
|
.back-btn { |
||||
|
width: 70rpx; |
||||
|
height: 70rpx; |
||||
|
border-radius: 50%; |
||||
|
border: none; |
||||
|
background: rgba(0, 0, 0, 0.05); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.back-btn:active { |
||||
|
background: rgba(0, 0, 0, 0.1); |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
.back-icon { |
||||
|
width: 30rpx; |
||||
|
height: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.expert-name { |
||||
|
font-size: 34rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 5rpx; |
||||
|
} |
||||
|
|
||||
|
.expert-status { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.status-dot { |
||||
|
width: 14rpx; |
||||
|
height: 14rpx; |
||||
|
border-radius: 50%; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.status-dot.online { |
||||
|
background: #07c160; |
||||
|
box-shadow: 0 0 10rpx rgba(7, 193, 96, 0.5); |
||||
|
} |
||||
|
|
||||
|
.status-dot.offline { |
||||
|
background: #999; |
||||
|
} |
||||
|
|
||||
|
.status-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.header-action-btn { |
||||
|
width: 70rpx; |
||||
|
height: 70rpx; |
||||
|
border-radius: 50%; |
||||
|
border: none; |
||||
|
background: rgba(0, 0, 0, 0.05); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.header-action-btn:active { |
||||
|
background: rgba(0, 0, 0, 0.1); |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
.header-action-icon { |
||||
|
width: 32rpx; |
||||
|
height: 32rpx; |
||||
|
} |
||||
|
|
||||
|
/* 聊天容器 */ |
||||
|
.chat-container { |
||||
|
flex: 1; |
||||
|
padding: 30rpx 20rpx 0; |
||||
|
background: linear-gradient(180deg, #f5f7fa 0%, #f0f2f5 100%); |
||||
|
overflow-y: auto; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* 欢迎语 */ |
||||
|
.system-welcome { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 60rpx 40rpx; |
||||
|
text-align: center; |
||||
|
background: white; |
||||
|
border-radius: 30rpx; |
||||
|
margin: 0 auto 40rpx; |
||||
|
max-width: 80%; |
||||
|
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.08); |
||||
|
animation: fadeInUp 0.6s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeInUp { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(50rpx); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.welcome-avatar { |
||||
|
width: 120rpx; |
||||
|
height: 120rpx; |
||||
|
border-radius: 50%; |
||||
|
overflow: hidden; |
||||
|
margin-bottom: 20rpx; |
||||
|
border: 4rpx solid white; |
||||
|
box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.welcome-avatar image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.welcome-name { |
||||
|
font-size: 34rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.welcome-title { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 25rpx; |
||||
|
} |
||||
|
|
||||
|
.welcome-tip { |
||||
|
font-size: 28rpx; |
||||
|
color: #4caf50; |
||||
|
line-height: 1.4; |
||||
|
} |
||||
|
|
||||
|
/* 日期分隔线 */ |
||||
|
.date-divider { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
margin: 40rpx 0; |
||||
|
} |
||||
|
|
||||
|
.date-text { |
||||
|
background: rgba(0, 0, 0, 0.05); |
||||
|
padding: 8rpx 24rpx; |
||||
|
border-radius: 20rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 时间分隔 */ |
||||
|
.time-divider { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
margin: 30rpx 0; |
||||
|
animation: fadeIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes fadeIn { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.time-divider text { |
||||
|
background: rgba(0, 0, 0, 0.05); |
||||
|
padding: 6rpx 20rpx; |
||||
|
border-radius: 15rpx; |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 消息项 */ |
||||
|
.message-item { |
||||
|
display: flex; |
||||
|
margin-bottom: 40rpx; |
||||
|
animation: slideIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes slideIn { |
||||
|
from { |
||||
|
opacity: 0; |
||||
|
transform: translateY(20rpx); |
||||
|
} |
||||
|
to { |
||||
|
opacity: 1; |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.message-left { |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.message-right { |
||||
|
justify-content: flex-end; |
||||
|
} |
||||
|
|
||||
|
.message-avatar { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 10rpx; |
||||
|
overflow: hidden; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.message-left .message-avatar { |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.message-right .message-avatar { |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.message-avatar image { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.message-content-wrapper { |
||||
|
max-width: 65%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.message-left .message-content-wrapper { |
||||
|
align-items: flex-start; |
||||
|
} |
||||
|
|
||||
|
.message-right .message-content-wrapper { |
||||
|
align-items: flex-end; |
||||
|
} |
||||
|
|
||||
|
/* 消息气泡 */ |
||||
|
.message-bubble { |
||||
|
padding: 20rpx 25rpx; |
||||
|
border-radius: 20rpx; |
||||
|
position: relative; |
||||
|
word-break: break-word; |
||||
|
line-height: 1.5; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-left { |
||||
|
background: white; |
||||
|
border-radius: 0 20rpx 20rpx 20rpx; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08); |
||||
|
} |
||||
|
|
||||
|
.message-bubble-right { |
||||
|
background: linear-gradient(135deg, #95ec69 0%, #5ac725 100%); |
||||
|
color: white; |
||||
|
border-radius: 20rpx 0 20rpx 20rpx; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(90, 199, 37, 0.2); |
||||
|
} |
||||
|
|
||||
|
.message-text { |
||||
|
font-size: 30rpx; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-left .message-text { |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-right .message-text { |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
/* 消息时间 */ |
||||
|
.message-time { |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
margin-top: 8rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
/* 消息状态 */ |
||||
|
.message-status { |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.status-icon { |
||||
|
width: 28rpx; |
||||
|
height: 28rpx; |
||||
|
} |
||||
|
|
||||
|
/* 图片消息 */ |
||||
|
.message-image { |
||||
|
max-width: 300rpx; |
||||
|
max-height: 400rpx; |
||||
|
border-radius: 12rpx; |
||||
|
display: block; |
||||
|
} |
||||
|
|
||||
|
/* 视频消息 */ |
||||
|
.message-video { |
||||
|
width: 300rpx; |
||||
|
height: 200rpx; |
||||
|
border-radius: 12rpx; |
||||
|
background: #000; |
||||
|
} |
||||
|
|
||||
|
.video-play-btn { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
background: rgba(0, 0, 0, 0.6); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.video-play-btn image { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
} |
||||
|
|
||||
|
/* 语音消息 */ |
||||
|
.message-audio { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 25rpx; |
||||
|
} |
||||
|
|
||||
|
.audio-icon-left { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.audio-icon-right { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
margin-left: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.audio-content { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.audio-wave { |
||||
|
display: flex; |
||||
|
align-items: flex-end; |
||||
|
height: 40rpx; |
||||
|
margin-right: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.wave-bar { |
||||
|
width: 4rpx; |
||||
|
margin: 0 2rpx; |
||||
|
background: currentColor; |
||||
|
animation: wave 1.2s ease-in-out infinite; |
||||
|
} |
||||
|
|
||||
|
.wave-bar:nth-child(1) { |
||||
|
height: 20rpx; |
||||
|
animation-delay: 0s; |
||||
|
} |
||||
|
|
||||
|
.wave-bar:nth-child(2) { |
||||
|
height: 30rpx; |
||||
|
animation-delay: 0.2s; |
||||
|
} |
||||
|
|
||||
|
.wave-bar:nth-child(3) { |
||||
|
height: 40rpx; |
||||
|
animation-delay: 0.4s; |
||||
|
} |
||||
|
|
||||
|
.wave-bar:nth-child(4) { |
||||
|
height: 30rpx; |
||||
|
animation-delay: 0.6s; |
||||
|
} |
||||
|
|
||||
|
.wave-bar:nth-child(5) { |
||||
|
height: 20rpx; |
||||
|
animation-delay: 0.8s; |
||||
|
} |
||||
|
|
||||
|
@keyframes wave { |
||||
|
0%, 100% { |
||||
|
transform: scaleY(0.5); |
||||
|
} |
||||
|
50% { |
||||
|
transform: scaleY(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.audio-duration { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-left .audio-duration { |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-right .audio-duration { |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
/* 文件消息 */ |
||||
|
.message-file { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 25rpx; |
||||
|
min-width: 300rpx; |
||||
|
} |
||||
|
|
||||
|
.file-icon-box { |
||||
|
position: relative; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.file-icon { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.file-extension { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
font-size: 18rpx; |
||||
|
font-weight: 600; |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.file-info { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.file-name { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 500; |
||||
|
margin-bottom: 8rpx; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
max-width: 200rpx; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-left .file-name { |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-right .file-name { |
||||
|
color: white; |
||||
|
} |
||||
|
|
||||
|
.file-size { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-left .file-size { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.message-bubble-right .file-size { |
||||
|
color: rgba(255, 255, 255, 0.9); |
||||
|
} |
||||
|
|
||||
|
.file-download-btn { |
||||
|
background: rgba(0, 0, 0, 0.1); |
||||
|
border: none; |
||||
|
padding: 10rpx 20rpx; |
||||
|
border-radius: 15rpx; |
||||
|
font-size: 24rpx; |
||||
|
color: white; |
||||
|
margin-left: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.file-download-btn:active { |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
/* 上传进度 */ |
||||
|
.upload-progress { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
border-radius: 12rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.progress-circle { |
||||
|
position: relative; |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
} |
||||
|
|
||||
|
.progress-fill { |
||||
|
position: absolute; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
border: 6rpx solid #4caf50; |
||||
|
border-radius: 50%; |
||||
|
border-top-color: transparent; |
||||
|
border-right-color: transparent; |
||||
|
} |
||||
|
|
||||
|
.progress-text { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
font-size: 22rpx; |
||||
|
color: white; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
/* 输入区域 */ |
||||
|
.input-section { |
||||
|
background: white; |
||||
|
border-top: 1rpx solid #e0e0e0; |
||||
|
padding: 20rpx 30rpx; |
||||
|
position: relative; |
||||
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
/* 语音面板 */ |
||||
|
.voice-panel { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
.voice-record-btn { |
||||
|
width: 100%; |
||||
|
height: 100rpx; |
||||
|
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%); |
||||
|
border: none; |
||||
|
border-radius: 50rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.voice-record-btn:active { |
||||
|
background: linear-gradient(135deg, #e0e0e0 0%, #d0d0d0 100%); |
||||
|
transform: scale(0.98); |
||||
|
} |
||||
|
|
||||
|
.mic-icon { |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.voice-tip { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
/* 输入面板 */ |
||||
|
.input-panel { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.input-mode-btn { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
border: none; |
||||
|
background: #f5f5f5; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.input-mode-btn:active { |
||||
|
background: #e0e0e0; |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
.mode-icon { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
} |
||||
|
|
||||
|
.input-box-wrapper { |
||||
|
flex: 1; |
||||
|
background: #f5f5f5; |
||||
|
border-radius: 40rpx; |
||||
|
padding: 0 30rpx; |
||||
|
height: 80rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.input-box-wrapper:active { |
||||
|
background: #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.chat-input { |
||||
|
flex: 1; |
||||
|
height: 100%; |
||||
|
font-size: 30rpx; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.placeholder { |
||||
|
color: #999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.media-btn, |
||||
|
.send-btn { |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
border-radius: 50%; |
||||
|
border: none; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.media-btn { |
||||
|
background: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.media-btn:active { |
||||
|
background: #e0e0e0; |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
.send-btn { |
||||
|
background: linear-gradient(135deg, #95ec69 0%, #5ac725 100%); |
||||
|
} |
||||
|
|
||||
|
.send-btn:active { |
||||
|
background: linear-gradient(135deg, #5ac725 0%, #4caf50 100%); |
||||
|
transform: scale(0.95); |
||||
|
} |
||||
|
|
||||
|
.media-icon, |
||||
|
.send-icon { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
} |
||||
|
|
||||
|
.send-icon { |
||||
|
filter: brightness(0) invert(1); |
||||
|
} |
||||
|
|
||||
|
/* 多媒体选择面板 */ |
||||
|
.media-action-sheet { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.5); |
||||
|
display: flex; |
||||
|
align-items: flex-end; |
||||
|
justify-content: center; |
||||
|
z-index: 1000; |
||||
|
animation: fadeIn 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
.media-sheet-content { |
||||
|
width: 100%; |
||||
|
background: white; |
||||
|
border-radius: 40rpx 40rpx 0 0; |
||||
|
padding: 40rpx 30rpx 60rpx; |
||||
|
animation: slideUp 0.3s ease; |
||||
|
} |
||||
|
|
||||
|
@keyframes slideUp { |
||||
|
from { |
||||
|
transform: translateY(100%); |
||||
|
} |
||||
|
to { |
||||
|
transform: translateY(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.media-sheet-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.sheet-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.close-sheet-btn { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
border-radius: 50%; |
||||
|
border: none; |
||||
|
background: #f5f5f5; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.close-sheet-btn image { |
||||
|
width: 24rpx; |
||||
|
height: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.media-options { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(4, 1fr); |
||||
|
gap: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.media-option { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
border: none; |
||||
|
background: none; |
||||
|
padding: 20rpx 10rpx; |
||||
|
border-radius: 20rpx; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.media-option:active { |
||||
|
background: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
.option-icon { |
||||
|
width: 100rpx; |
||||
|
height: 100rpx; |
||||
|
border-radius: 25rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.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 image { |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
} |
||||
|
|
||||
|
.option-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
/* 录音模态框 */ |
||||
|
.recording-modal { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.7); |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
z-index: 1001; |
||||
|
animation: fadeIn 0.2s ease; |
||||
|
} |
||||
|
|
||||
|
.recording-box { |
||||
|
background: white; |
||||
|
border-radius: 40rpx; |
||||
|
padding: 60rpx; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3); |
||||
|
} |
||||
|
|
||||
|
.recording-wave-box { |
||||
|
position: relative; |
||||
|
width: 200rpx; |
||||
|
height: 200rpx; |
||||
|
margin-bottom: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.recording-wave { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
border: 8rpx solid #5ac725; |
||||
|
border-radius: 50%; |
||||
|
animation: pulse 1.5s ease-in-out infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes pulse { |
||||
|
0% { |
||||
|
transform: scale(1); |
||||
|
opacity: 1; |
||||
|
} |
||||
|
100% { |
||||
|
transform: scale(1.2); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.recording-mic-icon { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 80rpx; |
||||
|
height: 80rpx; |
||||
|
} |
||||
|
|
||||
|
.recording-tip { |
||||
|
font-size: 32rpx; |
||||
|
color: #333; |
||||
|
font-weight: 500; |
||||
|
margin-bottom: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.recording-time { |
||||
|
font-size: 36rpx; |
||||
|
color: #5ac725; |
||||
|
font-weight: bold; |
||||
|
} |
||||