feat: 百科rag
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
"pages/plant-detail/growth-record/index",
|
||||
"pages/wiki/detail/index",
|
||||
"pages/wiki/identify/index",
|
||||
"pages/wiki/chat/index",
|
||||
"pages/wiki/chat/history/index",
|
||||
"pages/profile/identify-history/index",
|
||||
"pages/profile/badges/index",
|
||||
"pages/profile/badges/level-detail/index",
|
||||
|
||||
@@ -135,11 +135,16 @@
|
||||
|
||||
<!-- Empty State -->
|
||||
<view wx:else class="empty-feed">
|
||||
<view class="empty-icon">
|
||||
<t-icon name="chat" size="80rpx" color="#ccc" />
|
||||
<view class="empty-scene">
|
||||
<view class="empty-glow"></view>
|
||||
<view class="empty-emoji anim-breathe">💬</view>
|
||||
</view>
|
||||
<text class="empty-text">暂无相关动态</text>
|
||||
<text class="empty-title">暂无相关动态</text>
|
||||
<text class="empty-hint">快来发布第一条动态吧</text>
|
||||
<view class="empty-cta" bindtap="goToCreatePost">
|
||||
<t-icon name="add" size="28rpx" color="#fff" />
|
||||
<text>发布动态</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Bottom Spacer -->
|
||||
|
||||
+63
-19
@@ -276,31 +276,75 @@ page {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 120rpx 40rpx;
|
||||
color: #999;
|
||||
padding: 100rpx 40rpx 60rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
.empty-scene {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
.empty-glow {
|
||||
position: absolute;
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(85,139,47,0.12) 0%, transparent 70%);
|
||||
animation: emptyPulse 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes emptyPulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.7; }
|
||||
50% { transform: scale(1.1); opacity: 1; }
|
||||
}
|
||||
|
||||
.empty-emoji {
|
||||
font-size: 96rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.anim-breathe {
|
||||
animation: breathe 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0%, 100% { transform: scale(1) translateY(0); }
|
||||
50% { transform: scale(1.05) translateY(-8rpx); }
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
margin-bottom: 12rpx;
|
||||
font-weight: 700;
|
||||
color: #558B2F;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
color: #90A4AE;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.empty-cta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx 40rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #558B2F, #7CB342);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 20rpx rgba(85,139,47,0.3);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.empty-cta:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Floating Action Button */
|
||||
@@ -308,23 +352,23 @@ page {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 60rpx;
|
||||
background: #558B2F;
|
||||
background: rgba(85, 139, 47, 0.92);
|
||||
backdrop-filter: blur(12px);
|
||||
color: white;
|
||||
padding: 24rpx 40rpx;
|
||||
border-radius: 60rpx;
|
||||
padding: 20rpx 36rpx;
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
box-shadow: 0 12rpx 32rpx rgba(85, 139, 47, 0.4);
|
||||
gap: 10rpx;
|
||||
box-shadow: 0 8rpx 28rpx rgba(85, 139, 47, 0.35);
|
||||
z-index: 100;
|
||||
font-size: 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.floating-add-btn:active {
|
||||
transform: scale(0.92);
|
||||
box-shadow: 0 4rpx 16rpx rgba(85, 139, 47, 0.2);
|
||||
}
|
||||
|
||||
/* WeChat Style Action Container */
|
||||
|
||||
+12
-1
@@ -14,6 +14,7 @@ Page({
|
||||
total: 0,
|
||||
isLastPage: false,
|
||||
isLoading: false,
|
||||
isRefreshing: false,
|
||||
scrollTop: 0
|
||||
},
|
||||
|
||||
@@ -34,13 +35,23 @@ Page({
|
||||
this.loadPlants(true);
|
||||
},
|
||||
|
||||
// Pull to refresh
|
||||
// Pull to refresh (page-level)
|
||||
onPullDownRefresh() {
|
||||
this.loadPlants(true).then(() => {
|
||||
wx.stopPullDownRefresh();
|
||||
});
|
||||
},
|
||||
|
||||
// Pull to refresh (scroll-view)
|
||||
onRefresh() {
|
||||
this.setData({ isRefreshing: true });
|
||||
this.loadPlants(true).then(() => {
|
||||
this.setData({ isRefreshing: false });
|
||||
}).catch(() => {
|
||||
this.setData({ isRefreshing: false });
|
||||
});
|
||||
},
|
||||
|
||||
// Infinite scroll
|
||||
onReachBottom() {
|
||||
if (!this.data.isLastPage && !this.data.isLoading) {
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
</view>
|
||||
|
||||
<view class="banner-container">
|
||||
<image src="https://images.unsplash.com/photo-1585320806297-9794b3e4eeae?w=800" class="garden-banner" mode="aspectFill" />
|
||||
<view class="garden-banner-bg">
|
||||
<text class="banner-deco">🌿🌸🍀🌺</text>
|
||||
</view>
|
||||
<view class="banner-overlay">
|
||||
<text class="count-tag">共养护 {{total}} 盆植物</text>
|
||||
</view>
|
||||
@@ -45,7 +47,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view wx:else scroll-y class="garden-list-container" enhanced show-scrollbar="{{false}}" bindscrolltolower="onScrollLower" scroll-top="{{scrollTop}}">
|
||||
<scroll-view wx:else scroll-y class="garden-list-container" enhanced show-scrollbar="{{false}}" bindscrolltolower="onScrollLower" scroll-top="{{scrollTop}}" refresher-enabled="{{true}}" bindrefresherrefresh="onRefresh" refresher-triggered="{{isRefreshing}}">
|
||||
<view class="plant-grid">
|
||||
<view wx:for="{{plants}}" wx:key="id" class="plant-card" bindtap="navigateToDetail" data-id="{{item.id}}">
|
||||
<view class="plant-image-container">
|
||||
@@ -55,6 +57,7 @@
|
||||
width="100%"
|
||||
height="100%"
|
||||
t-class="uploaded-img"
|
||||
lazy
|
||||
/>
|
||||
<view class="days-badge">{{item.daysPlanted}}天</view>
|
||||
</view>
|
||||
|
||||
+18
-8
@@ -83,9 +83,19 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.garden-banner {
|
||||
.garden-banner-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #558B2F 0%, #7CB342 40%, #AED581 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.banner-deco {
|
||||
font-size: 56rpx;
|
||||
letter-spacing: 24rpx;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.banner-overlay {
|
||||
@@ -207,23 +217,23 @@
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 60rpx;
|
||||
background: #558B2F;
|
||||
background: rgba(85, 139, 47, 0.92);
|
||||
backdrop-filter: blur(12px);
|
||||
color: white;
|
||||
padding: 24rpx 40rpx;
|
||||
border-radius: 60rpx;
|
||||
padding: 20rpx 36rpx;
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
box-shadow: 0 12rpx 32rpx rgba(85, 139, 47, 0.4);
|
||||
gap: 10rpx;
|
||||
box-shadow: 0 8rpx 28rpx rgba(85, 139, 47, 0.35);
|
||||
z-index: 1000;
|
||||
font-size: 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.floating-add-btn:active {
|
||||
transform: scale(0.92);
|
||||
box-shadow: 0 4rpx 16rpx rgba(85, 139, 47, 0.2);
|
||||
}
|
||||
/* List Footer */
|
||||
.list-footer {
|
||||
|
||||
@@ -69,8 +69,10 @@
|
||||
</view>
|
||||
<text class="menu-text">我的收藏</text>
|
||||
</view>
|
||||
<view class="menu-right-info">
|
||||
<t-icon name="chevron-right" size="36rpx" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="goToPosts">
|
||||
<view class="menu-left">
|
||||
@@ -79,8 +81,10 @@
|
||||
</view>
|
||||
<text class="menu-text">我的发布</text>
|
||||
</view>
|
||||
<view class="menu-right-info">
|
||||
<t-icon name="chevron-right" size="36rpx" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-item" bindtap="goToIdentifyHistory">
|
||||
<view class="menu-left">
|
||||
@@ -89,8 +93,10 @@
|
||||
</view>
|
||||
<text class="menu-text">识别记录</text>
|
||||
</view>
|
||||
<view class="menu-right-info">
|
||||
<t-icon name="chevron-right" size="36rpx" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-group-title" style="margin-top: 32rpx;">更多服务</view>
|
||||
|
||||
@@ -101,9 +107,11 @@
|
||||
</view>
|
||||
<text class="menu-text">帮助与关于</text>
|
||||
</view>
|
||||
<view class="menu-right-info">
|
||||
<t-icon name="chevron-right" size="36rpx" color="#ccc" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height: 100rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// pages/wiki/chat/history/index.js
|
||||
import request from '../../../../utils/request';
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
current: 1,
|
||||
pageSize: 15,
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
showClearDialog: false,
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.fetchHistory(true);
|
||||
},
|
||||
|
||||
fetchHistory(reset = false) {
|
||||
if (this.data.loading) return;
|
||||
if (!reset && !this.data.hasMore) return;
|
||||
|
||||
const current = reset ? 1 : this.data.current;
|
||||
this.setData({ loading: true });
|
||||
|
||||
request.get('/plant/chat/history', { current, pageSize: this.data.pageSize })
|
||||
.then(res => {
|
||||
const items = (res.list || []).map(item => ({
|
||||
...item,
|
||||
answerPreview: (item.answer || '').substring(0, 80) + ((item.answer || '').length > 80 ? '...' : ''),
|
||||
}));
|
||||
const total = res.total || 0;
|
||||
|
||||
if (reset) {
|
||||
this.setData({
|
||||
list: items,
|
||||
total,
|
||||
current: 2,
|
||||
hasMore: items.length < total,
|
||||
loading: false,
|
||||
});
|
||||
} else {
|
||||
const old = this.data.list;
|
||||
const update = {};
|
||||
items.forEach((item, i) => {
|
||||
update[`list[${old.length + i}]`] = item;
|
||||
});
|
||||
update.current = current + 1;
|
||||
update.hasMore = (old.length + items.length) < total;
|
||||
update.loading = false;
|
||||
update.total = total;
|
||||
this.setData(update);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({ loading: false });
|
||||
});
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.fetchHistory(false);
|
||||
},
|
||||
|
||||
onTapItem(e) {
|
||||
const item = e.currentTarget.dataset.item;
|
||||
// Navigate to chat page with prefilled Q&A
|
||||
wx.navigateTo({
|
||||
url: '/pages/wiki/chat/index?fromHistory=1',
|
||||
success(res) {
|
||||
res.eventChannel.emit('historyData', {
|
||||
question: item.question,
|
||||
answer: item.answer,
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
onDeleteItem(e) {
|
||||
const id = e.currentTarget.dataset.id;
|
||||
wx.showModal({
|
||||
title: '删除记录',
|
||||
content: '确定删除这条问答记录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
request.post('/plant/chat/history/delete', { id }).then(() => {
|
||||
wx.showToast({ title: '已删除', icon: 'success' });
|
||||
this.fetchHistory(true);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
onClearAll() {
|
||||
this.setData({ showClearDialog: true });
|
||||
},
|
||||
|
||||
closeClearDialog() {
|
||||
this.setData({ showClearDialog: false });
|
||||
},
|
||||
|
||||
doClearAll() {
|
||||
this.setData({ showClearDialog: false });
|
||||
request.post('/plant/chat/history/clear').then(() => {
|
||||
wx.showToast({ title: '已清空', icon: 'success' });
|
||||
this.setData({ list: [], total: 0, hasMore: false });
|
||||
});
|
||||
},
|
||||
|
||||
goToChat() {
|
||||
wx.navigateBack();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"navigationBarTitleText": "问答历史",
|
||||
"navigationBarBackgroundColor": "#558B2F",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading",
|
||||
"t-empty": "tdesign-miniprogram/empty/empty",
|
||||
"t-dialog": "tdesign-miniprogram/dialog/dialog",
|
||||
"t-swipe-cell": "tdesign-miniprogram/swipe-cell/swipe-cell"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<!--pages/wiki/chat/history/index.wxml-->
|
||||
<view class="history-page">
|
||||
<scroll-view
|
||||
class="history-scroll"
|
||||
scroll-y
|
||||
bindscrolltolower="loadMore"
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
>
|
||||
<!-- Header Actions -->
|
||||
<view class="header-bar" wx:if="{{list.length > 0}}">
|
||||
<text class="header-count">共 {{total}} 条记录</text>
|
||||
<view class="clear-btn" bindtap="onClearAll">
|
||||
<t-icon name="delete" size="32rpx" color="#EF4444" />
|
||||
<text>清空全部</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- History List -->
|
||||
<view wx:for="{{list}}" wx:key="id" class="history-card" bindtap="onTapItem" data-item="{{item}}">
|
||||
<view class="card-header">
|
||||
<text class="card-time">{{item.createdAtStr}}</text>
|
||||
<view class="card-del" catchtap="onDeleteItem" data-id="{{item.id}}">
|
||||
<t-icon name="close" size="28rpx" color="#9CA3AF" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-question">
|
||||
<text class="q-label">Q</text>
|
||||
<text class="q-text">{{item.question}}</text>
|
||||
</view>
|
||||
<view class="card-answer">
|
||||
<text class="a-label">A</text>
|
||||
<text class="a-text">{{item.answerPreview}}</text>
|
||||
<view class="card-arrow">
|
||||
<t-icon name="chevron-right" size="28rpx" color="#CCC" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- States -->
|
||||
<view class="footer">
|
||||
<t-loading wx:if="{{loading}}" theme="circular" size="40rpx" text="加载中..." />
|
||||
<text wx:elif="{{!hasMore && list.length > 0}}" class="no-more">没有更多了</text>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!loading && list.length === 0}}" class="empty-wrap">
|
||||
<view class="empty-icon">📝</view>
|
||||
<text class="empty-text">暂无问答记录</text>
|
||||
<text class="empty-sub">去和AI助手聊聊吧</text>
|
||||
<view class="empty-cta" bindtap="goToChat">
|
||||
<text>开始提问</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height: 60rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<t-dialog
|
||||
visible="{{showClearDialog}}"
|
||||
title="清空全部历史"
|
||||
content="确定要清空所有问答记录吗?此操作不可恢复。"
|
||||
confirm-btn="确定清空"
|
||||
cancel-btn="取消"
|
||||
bind:confirm="doClearAll"
|
||||
bind:cancel="closeClearDialog"
|
||||
/>
|
||||
</view>
|
||||
@@ -0,0 +1,195 @@
|
||||
/** pages/wiki/chat/history/index.wxss **/
|
||||
.history-page {
|
||||
height: 100vh;
|
||||
background: linear-gradient(180deg, #EEF3E5 0%, #F4F6F0 100%);
|
||||
}
|
||||
|
||||
.history-scroll {
|
||||
height: 100%;
|
||||
padding: 24rpx 28rpx;
|
||||
}
|
||||
|
||||
.header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.header-count {
|
||||
font-size: 26rpx;
|
||||
color: #78909C;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
font-size: 26rpx;
|
||||
color: #EF4444;
|
||||
font-weight: 600;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.clear-btn:active {
|
||||
background: rgba(239,68,68,0.08);
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.history-card {
|
||||
background: rgba(255,255,255,0.92);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(85,139,47,0.05);
|
||||
border: 1rpx solid rgba(85,139,47,0.04);
|
||||
transition: all 0.15s;
|
||||
animation: cardIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes cardIn {
|
||||
from { opacity: 0; transform: translateY(12rpx); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.history-card:active {
|
||||
transform: scale(0.98);
|
||||
background: #FAFDF7;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.card-time {
|
||||
font-size: 22rpx;
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.card-del {
|
||||
padding: 8rpx;
|
||||
margin: -8rpx;
|
||||
}
|
||||
|
||||
.card-question {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.q-label {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 10rpx;
|
||||
background: linear-gradient(135deg, #558B2F, #7CB342);
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
line-height: 40rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.q-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-answer {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.a-label {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #E8F5E9;
|
||||
color: #2E7D32;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
line-height: 40rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.a-text {
|
||||
font-size: 26rpx;
|
||||
color: #6B7280;
|
||||
line-height: 1.6;
|
||||
flex: 1;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
font-size: 24rpx;
|
||||
color: #CCC;
|
||||
}
|
||||
|
||||
/* Empty */
|
||||
.empty-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 96rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #9CA3AF;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.empty-sub {
|
||||
font-size: 26rpx;
|
||||
color: #CCC;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.empty-cta {
|
||||
padding: 16rpx 48rpx;
|
||||
border-radius: 40rpx;
|
||||
background: linear-gradient(135deg, #558B2F, #7CB342);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 6rpx 20rpx rgba(85,139,47,0.3);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.empty-cta:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
// pages/wiki/chat/index.js
|
||||
|
||||
Page({
|
||||
data: {
|
||||
messages: [],
|
||||
inputValue: '',
|
||||
isTyping: false,
|
||||
scrollAnchor: '',
|
||||
_counter: 0,
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
if (options && options.fromHistory === '1') {
|
||||
const channel = this.getOpenerEventChannel();
|
||||
channel.on('historyData', (data) => {
|
||||
const msgs = [
|
||||
{ id: 'h1', role: 'user', content: data.question },
|
||||
{ id: 'h2', role: 'ai', content: this._cleanMd(data.answer) },
|
||||
];
|
||||
this.setData({ messages: msgs, _counter: 2 }, () => this.scrollToBottom());
|
||||
});
|
||||
} else if (options && options.prefillQuestion) {
|
||||
const q = decodeURIComponent(options.prefillQuestion);
|
||||
this.setData({ inputValue: q }, () => this.onSend());
|
||||
}
|
||||
},
|
||||
|
||||
goToHistory() {
|
||||
wx.navigateTo({ url: '/pages/wiki/chat/history/index' });
|
||||
},
|
||||
|
||||
onQuickAsk(e) {
|
||||
const query = e.currentTarget.dataset.q;
|
||||
this.setData({ inputValue: query }, () => this.onSend());
|
||||
},
|
||||
|
||||
onInput(e) {
|
||||
this.setData({ inputValue: e.detail.value });
|
||||
},
|
||||
|
||||
onSend() {
|
||||
const query = this.data.inputValue.trim();
|
||||
if (!query || this.data.isTyping) return;
|
||||
|
||||
const uid = 'u' + (++this.data._counter);
|
||||
const aid = 'a' + (++this.data._counter);
|
||||
const len = this.data.messages.length;
|
||||
|
||||
// Push user msg + empty AI msg at once
|
||||
this.setData({
|
||||
[`messages[${len}]`]: { id: uid, role: 'user', content: query },
|
||||
[`messages[${len + 1}]`]: { id: aid, role: 'ai', content: '' },
|
||||
inputValue: '',
|
||||
isTyping: true,
|
||||
}, () => {
|
||||
this.scrollToBottom();
|
||||
this._streamRequest(query, aid);
|
||||
});
|
||||
},
|
||||
|
||||
_streamRequest(query, aiMsgId) {
|
||||
const token = wx.getStorageSync('token');
|
||||
const baseUrl = 'http://192.168.0.184:8889';
|
||||
const url = `${baseUrl}/plant/chat/stream?query=${encodeURIComponent(query)}`;
|
||||
let fullText = '';
|
||||
|
||||
const task = wx.request({
|
||||
url,
|
||||
method: 'GET',
|
||||
enableChunked: true,
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Accept': 'text/event-stream',
|
||||
},
|
||||
success: () => {
|
||||
this.setData({ isTyping: false });
|
||||
this.scrollToBottom();
|
||||
},
|
||||
fail: () => {
|
||||
this._updateAiMsg(aiMsgId, '网络连接失败,请稍后重试');
|
||||
this.setData({ isTyping: false });
|
||||
},
|
||||
});
|
||||
|
||||
task.onChunkReceived((res) => {
|
||||
const text = this._decode(res.data);
|
||||
const lines = text.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.startsWith('data: ')) continue;
|
||||
const chunk = line.substring(6);
|
||||
|
||||
if (chunk === '[DONE]') {
|
||||
this.setData({ isTyping: false });
|
||||
return;
|
||||
}
|
||||
if (chunk.startsWith('[ERROR]')) {
|
||||
fullText += '\n⚠️ ' + (chunk.substring(7) || '服务异常');
|
||||
this._updateAiMsg(aiMsgId, fullText);
|
||||
this.setData({ isTyping: false });
|
||||
return;
|
||||
}
|
||||
|
||||
fullText += chunk;
|
||||
this._updateAiMsg(aiMsgId, fullText);
|
||||
}
|
||||
this.scrollToBottom();
|
||||
});
|
||||
},
|
||||
|
||||
_updateAiMsg(id, content) {
|
||||
const idx = this.data.messages.findIndex(m => m.id === id);
|
||||
if (idx !== -1) {
|
||||
this.setData({ [`messages[${idx}].content`]: this._cleanMd(content) });
|
||||
}
|
||||
},
|
||||
|
||||
// Strip residual markdown symbols for clean display
|
||||
_cleanMd(text) {
|
||||
return text
|
||||
.replace(/^#{1,6}\s*/gm, '') // ### headers
|
||||
.replace(/\*\*(.+?)\*\*/g, '【$1】') // **bold** → 【bold】
|
||||
.replace(/\*(.+?)\*/g, '$1') // *italic*
|
||||
.replace(/^[\-\*]\s+/gm, '· ') // - list → · list
|
||||
.replace(/^\d+\.\s+/gm, (m) => m) // keep numbered lists
|
||||
.replace(/`([^`]+)`/g, '$1') // `code`
|
||||
.replace(/^---+$/gm, '————') // --- → ————
|
||||
.replace(/\n{3,}/g, '\n\n'); // collapse blank lines
|
||||
},
|
||||
|
||||
_decode(buffer) {
|
||||
try {
|
||||
return new TextDecoder('utf-8').decode(new Uint8Array(buffer));
|
||||
} catch (e) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let s = '';
|
||||
for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
|
||||
try { return decodeURIComponent(escape(s)); } catch (_) { return s; }
|
||||
}
|
||||
},
|
||||
|
||||
scrollToBottom() {
|
||||
this.setData({ scrollAnchor: 'scroll-bottom' });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"navigationBarTitleText": "植物AI助手",
|
||||
"navigationBarBackgroundColor": "#558B2F",
|
||||
"navigationBarTextStyle": "white",
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon",
|
||||
"t-loading": "tdesign-miniprogram/loading/loading"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<!--pages/wiki/chat/index.wxml-->
|
||||
<view class="chat-page">
|
||||
<!-- Messages Area -->
|
||||
<scroll-view
|
||||
class="chat-messages"
|
||||
scroll-y
|
||||
scroll-into-view="{{scrollAnchor}}"
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
scroll-with-animation
|
||||
>
|
||||
<!-- Welcome -->
|
||||
<view class="welcome" wx:if="{{messages.length === 0}}">
|
||||
<view class="welcome-glow"></view>
|
||||
<view class="welcome-icon anim-float">🌿</view>
|
||||
<view class="welcome-title">植物AI百科</view>
|
||||
<view class="welcome-sub">基于知识库的智能问答助手</view>
|
||||
<view class="history-entry" bindtap="goToHistory">
|
||||
<t-icon name="time" size="32rpx" color="#558B2F" />
|
||||
<text>查看问答历史</text>
|
||||
</view>
|
||||
<view class="quick-grid">
|
||||
<view class="quick-card" bindtap="onQuickAsk" data-q="龟背竹怎么养护?">
|
||||
<text class="qc-emoji">🌱</text>
|
||||
<text class="qc-text">龟背竹怎么养护?</text>
|
||||
</view>
|
||||
<view class="quick-card" bindtap="onQuickAsk" data-q="哪些植物适合室内?">
|
||||
<text class="qc-emoji">🏠</text>
|
||||
<text class="qc-text">哪些植物适合室内?</text>
|
||||
</view>
|
||||
<view class="quick-card" bindtap="onQuickAsk" data-q="多肉浇水注意什么?">
|
||||
<text class="qc-emoji">💧</text>
|
||||
<text class="qc-text">多肉浇水注意什么?</text>
|
||||
</view>
|
||||
<view class="quick-card" bindtap="onQuickAsk" data-q="植物叶子发黄怎么办?">
|
||||
<text class="qc-emoji">🍂</text>
|
||||
<text class="qc-text">叶子发黄怎么办?</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Chat Bubbles -->
|
||||
<block wx:for="{{messages}}" wx:key="id">
|
||||
<view id="msg-{{item.id}}" class="msg-row {{item.role}}">
|
||||
<!-- AI avatar -->
|
||||
<view wx:if="{{item.role === 'ai'}}" class="ai-avatar-wrap">
|
||||
<text class="ai-avatar-emoji">🌱</text>
|
||||
</view>
|
||||
|
||||
<view class="msg-bubble {{item.role}}">
|
||||
<!-- AI: typing state -->
|
||||
<view wx:if="{{item.role === 'ai' && !item.content}}" class="typing-wrap">
|
||||
<view class="typing-dots">
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
<view class="dot"></view>
|
||||
</view>
|
||||
<text class="typing-label">思考中...</text>
|
||||
</view>
|
||||
<text wx:elif="{{item.role === 'ai'}}" class="ai-text" user-select>{{item.content}}</text>
|
||||
<!-- User text -->
|
||||
<text wx:else class="msg-text" user-select>{{item.content}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<view style="height: 32rpx;" id="scroll-bottom"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- Input -->
|
||||
<view class="input-area">
|
||||
<view class="input-row">
|
||||
<input
|
||||
class="chat-input"
|
||||
placeholder="{{isTyping ? 'AI正在回答中...' : '问我任何植物相关的问题...'}}"
|
||||
value="{{inputValue}}"
|
||||
bindinput="onInput"
|
||||
bindconfirm="onSend"
|
||||
confirm-type="send"
|
||||
disabled="{{isTyping}}"
|
||||
adjust-position="{{true}}"
|
||||
cursor-spacing="20"
|
||||
/>
|
||||
<view class="send-btn {{inputValue && !isTyping ? 'active' : ''}}" bindtap="onSend">
|
||||
<t-icon name="arrow-up" size="36rpx" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="safe-bottom"></view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,265 @@
|
||||
/** pages/wiki/chat/index.wxss **/
|
||||
.chat-page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #EEF3E5 0%, #F4F6F0 35%, #F4F6F0 100%);
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
padding: 20rpx 24rpx;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* ── Welcome ── */
|
||||
.welcome {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 40rpx 20rpx 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.welcome-glow {
|
||||
position: absolute;
|
||||
top: -60rpx;
|
||||
width: 480rpx;
|
||||
height: 480rpx;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(85,139,47,0.15) 0%, rgba(85,139,47,0.04) 50%, transparent 75%);
|
||||
pointer-events: none;
|
||||
animation: glowPulse 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes glowPulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.8; }
|
||||
50% { transform: scale(1.08); opacity: 1; }
|
||||
}
|
||||
|
||||
.welcome-icon { font-size: 88rpx; margin-bottom: 12rpx; position: relative; }
|
||||
|
||||
.anim-float { animation: floatUp 3s ease-in-out infinite; }
|
||||
|
||||
@keyframes floatUp {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-12rpx); }
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 42rpx;
|
||||
font-weight: 800;
|
||||
color: #2E7D32;
|
||||
margin-bottom: 8rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.welcome-sub {
|
||||
font-size: 24rpx;
|
||||
color: #90A4AE;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.history-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 32rpx;
|
||||
background: rgba(255,255,255,0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 2rpx 12rpx rgba(85,139,47,0.08);
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: #558B2F;
|
||||
margin-bottom: 32rpx;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.history-entry:active {
|
||||
transform: scale(0.96);
|
||||
background: #F0F7EB;
|
||||
}
|
||||
|
||||
/* Quick Ask Grid */
|
||||
.quick-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.quick-card {
|
||||
background: rgba(255,255,255,0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
box-shadow: 0 2rpx 16rpx rgba(85,139,47,0.06);
|
||||
border: 1rpx solid rgba(85,139,47,0.06);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.quick-card:active {
|
||||
transform: scale(0.96);
|
||||
background: #F0F7EB;
|
||||
border-color: rgba(85,139,47,0.15);
|
||||
}
|
||||
|
||||
.qc-emoji { font-size: 40rpx; }
|
||||
|
||||
.qc-text {
|
||||
font-size: 25rpx;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* ── Message Row ── */
|
||||
.msg-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 24rpx;
|
||||
gap: 12rpx;
|
||||
animation: fadeSlideIn 0.25s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeSlideIn {
|
||||
from { opacity: 0; transform: translateY(16rpx); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.msg-row.user { flex-direction: row-reverse; }
|
||||
|
||||
.ai-avatar-wrap {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #E8F5E9, #C8E6C9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4rpx 12rpx rgba(85,139,47,0.12);
|
||||
}
|
||||
|
||||
.ai-avatar-emoji { font-size: 32rpx; }
|
||||
|
||||
/* ── Bubbles ── */
|
||||
.msg-bubble {
|
||||
max-width: 78%;
|
||||
padding: 22rpx 26rpx;
|
||||
border-radius: 24rpx;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.msg-bubble.ai {
|
||||
max-width: 88%;
|
||||
background: rgba(255,255,255,0.92);
|
||||
backdrop-filter: blur(8px);
|
||||
border-top-left-radius: 6rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.msg-bubble.user {
|
||||
background: linear-gradient(135deg, #558B2F, #7CB342);
|
||||
border-top-right-radius: 6rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(85,139,47,0.2);
|
||||
}
|
||||
|
||||
.msg-text {
|
||||
font-size: 29rpx;
|
||||
line-height: 1.7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ai-text {
|
||||
font-size: 29rpx;
|
||||
line-height: 1.85;
|
||||
color: #1F2937;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* ── Typing ── */
|
||||
.typing-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.typing-dots {
|
||||
display: flex;
|
||||
gap: 8rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #A5D6A7;
|
||||
animation: bounce 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) { animation-delay: 0.16s; }
|
||||
.dot:nth-child(3) { animation-delay: 0.32s; }
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% { transform: scale(0.5); opacity: 0.4; }
|
||||
40% { transform: scale(1); opacity: 1; }
|
||||
}
|
||||
|
||||
.typing-label {
|
||||
font-size: 22rpx;
|
||||
color: #90A4AE;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── Input ── */
|
||||
.input-area {
|
||||
background: rgba(255,255,255,0.95);
|
||||
backdrop-filter: blur(16px);
|
||||
border-top: 1rpx solid rgba(0,0,0,0.03);
|
||||
padding: 14rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
height: 78rpx;
|
||||
background: #F0F4E8;
|
||||
border-radius: 40rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.send-btn {
|
||||
width: 74rpx;
|
||||
height: 74rpx;
|
||||
border-radius: 50%;
|
||||
background: #D1D5DB;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.send-btn.active {
|
||||
background: linear-gradient(135deg, #558B2F, #7CB342);
|
||||
box-shadow: 0 4rpx 20rpx rgba(85,139,47,0.35);
|
||||
}
|
||||
|
||||
.send-btn:active { transform: scale(0.85); }
|
||||
|
||||
.safe-bottom {
|
||||
height: calc(14rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
@@ -142,5 +142,12 @@ Page({
|
||||
title: `植物百科 - ${this.data.plant.name}`,
|
||||
path: `/pages/wiki/detail/index?id=${this.data.plant.id}`
|
||||
};
|
||||
},
|
||||
|
||||
askAiAboutPlant() {
|
||||
const name = this.data.plant ? this.data.plant.name : '';
|
||||
wx.navigateTo({
|
||||
url: `/pages/wiki/chat/index?prefillQuestion=${encodeURIComponent(name + '怎么养护?')}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -195,9 +195,14 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view style="height: 40rpx;"></view>
|
||||
<view style="height: 120rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- Ask AI FAB -->
|
||||
<view class="ask-ai-fab" bindtap="askAiAboutPlant">
|
||||
<text class="fab-emoji">🤖</text>
|
||||
<text>问 AI</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
@@ -286,3 +286,27 @@ page {
|
||||
transform: scale(0.9);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
/* Ask AI FAB */
|
||||
.ask-ai-fab {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
bottom: 48rpx;
|
||||
background: linear-gradient(135deg, rgba(21,101,192,0.92), rgba(25,118,210,0.92));
|
||||
backdrop-filter: blur(12px);
|
||||
color: #fff;
|
||||
padding: 20rpx 32rpx;
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 8rpx 28rpx rgba(21,101,192,0.3);
|
||||
z-index: 100;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.ask-ai-fab:active { transform: scale(0.92); }
|
||||
|
||||
.fab-emoji { font-size: 32rpx; line-height: 1; }
|
||||
|
||||
+16
-4
@@ -21,7 +21,8 @@ Page({
|
||||
scrollTop: 0,
|
||||
|
||||
// Modal State
|
||||
showIdentifyModal: false
|
||||
showIdentifyModal: false,
|
||||
isRefreshing: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -40,6 +41,13 @@ Page({
|
||||
this.setData({ scrollTop: Math.random() * 0.01 });
|
||||
},
|
||||
|
||||
onRefresh() {
|
||||
this.setData({ isRefreshing: true });
|
||||
this.fetchWikiList(true).finally(() => {
|
||||
this.setData({ isRefreshing: false });
|
||||
});
|
||||
},
|
||||
|
||||
// Fetch categories from API
|
||||
fetchCategories() {
|
||||
request.get('/wiki-class/list').then(res => {
|
||||
@@ -52,8 +60,8 @@ Page({
|
||||
|
||||
// Fetch wiki list from API
|
||||
fetchWikiList(reset = false) {
|
||||
if (this.data.isLoading) return;
|
||||
if (!reset && !this.data.hasMore) return;
|
||||
if (this.data.isLoading) return Promise.resolve();
|
||||
if (!reset && !this.data.hasMore) return Promise.resolve();
|
||||
|
||||
const current = reset ? 1 : this.data.current;
|
||||
|
||||
@@ -75,7 +83,7 @@ Page({
|
||||
params.classId = [this.data.activeCategory];
|
||||
}
|
||||
|
||||
request.post('/wiki/page', params).then(res => {
|
||||
return request.post('/wiki/page', params).then(res => {
|
||||
const data = res || {};
|
||||
const list = data.list || [];
|
||||
const total = data.total || 0;
|
||||
@@ -207,6 +215,10 @@ Page({
|
||||
|
||||
openIdentifyModal() { this.setData({ showIdentifyModal: true }); },
|
||||
|
||||
goToAiChat() {
|
||||
wx.navigateTo({ url: '/pages/wiki/chat/index' });
|
||||
},
|
||||
|
||||
onPopupVisibleChange(e) {
|
||||
this.setData({
|
||||
showIdentifyModal: e.detail.visible
|
||||
|
||||
+16
-5
@@ -17,6 +17,9 @@
|
||||
enhanced
|
||||
show-scrollbar="{{false}}"
|
||||
scroll-top="{{scrollTop}}"
|
||||
refresher-enabled="{{true}}"
|
||||
bindrefresherrefresh="onRefresh"
|
||||
refresher-triggered="{{isRefreshing}}"
|
||||
>
|
||||
<view class="search-section">
|
||||
<view class="search-box-card">
|
||||
@@ -111,13 +114,9 @@
|
||||
</view>
|
||||
|
||||
<!-- Spacer -->
|
||||
<view style="height: 160rpx;"></view>
|
||||
<view style="height: 200rpx;"></view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="floating-add-btn" bindtap="openIdentifyModal">
|
||||
<t-icon name="scan" size="40rpx" color="#FFF" />
|
||||
<text>植物识别</text>
|
||||
</view>
|
||||
|
||||
<!-- Identify Popup -->
|
||||
<t-popup visible="{{showIdentifyModal}}" bind:visible-change="onPopupVisibleChange" placement="bottom">
|
||||
@@ -147,4 +146,16 @@
|
||||
</view>
|
||||
</view>
|
||||
</t-popup>
|
||||
|
||||
<!-- Floating Buttons (must be after popup in DOM to stay on top) -->
|
||||
<view class="floating-btns" wx:if="{{!showIdentifyModal}}">
|
||||
<view class="floating-btn chat-btn" bindtap="goToAiChat">
|
||||
<text class="btn-emoji">🤖</text>
|
||||
<text>AI问答</text>
|
||||
</view>
|
||||
<view class="floating-btn scan-btn" bindtap="openIdentifyModal">
|
||||
<t-icon name="scan" size="40rpx" color="#FFF" />
|
||||
<text>植物识别</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
+41
-19
@@ -79,11 +79,12 @@
|
||||
}
|
||||
|
||||
.category-item.active {
|
||||
background: #558B2F;
|
||||
background: linear-gradient(135deg, #558B2F, #689F38);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 8rpx 20rpx rgba(85, 139, 47, 0.3);
|
||||
box-shadow: 0 6rpx 20rpx rgba(85, 139, 47, 0.3);
|
||||
border-color: #558B2F;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.wiki-list {
|
||||
@@ -179,28 +180,49 @@
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* Floating Action Button */
|
||||
.floating-add-btn {
|
||||
/* Floating Action Buttons */
|
||||
.floating-btns {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 60rpx;
|
||||
background: #558B2F;
|
||||
color: white;
|
||||
padding: 24rpx 40rpx;
|
||||
border-radius: 60rpx;
|
||||
bottom: 48rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
box-shadow: 0 12rpx 32rpx rgba(85, 139, 47, 0.4);
|
||||
z-index: 1000;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
transition: all 0.2s ease;
|
||||
flex-direction: row;
|
||||
gap: 24rpx;
|
||||
z-index: 11600;
|
||||
}
|
||||
|
||||
.floating-add-btn:active {
|
||||
.floating-btn {
|
||||
color: white;
|
||||
padding: 18rpx 28rpx;
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.floating-btn:active {
|
||||
transform: scale(0.92);
|
||||
box-shadow: 0 4rpx 16rpx rgba(85, 139, 47, 0.2);
|
||||
}
|
||||
|
||||
.scan-btn {
|
||||
background: rgba(85, 139, 47, 0.92);
|
||||
box-shadow: 0 8rpx 28rpx rgba(85, 139, 47, 0.35);
|
||||
}
|
||||
|
||||
.chat-btn {
|
||||
background: linear-gradient(135deg, rgba(21,101,192,0.92), rgba(25,118,210,0.92));
|
||||
box-shadow: 0 8rpx 28rpx rgba(21, 101, 192, 0.3);
|
||||
}
|
||||
|
||||
.btn-emoji {
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Popup Styles */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"projectname": "plant-mp",
|
||||
"condition": {},
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
|
||||
+2
-2
@@ -270,8 +270,8 @@ class WxRequest {
|
||||
|
||||
// Initialize with default instance
|
||||
const request = new WxRequest({
|
||||
//baseUrl: 'http://192.168.0.184:8889',
|
||||
baseUrl: 'https://go.sundynix.cn/api',
|
||||
baseUrl: 'http://192.168.0.184:8889',
|
||||
//baseUrl: 'https://go.sundynix.cn/api',
|
||||
header: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user