Compare commits
8 Commits
aebf40b7e7
...
2040e15d56
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2040e15d56 | ||
|
|
7461b5e393 | ||
|
|
acad07cfee | ||
|
|
37e4bb5dc7 | ||
|
|
707c585aa4 | ||
|
|
7a86325bd0 | ||
|
|
c7a0c84d69 | ||
|
|
eacdcb42a5 |
@ -2,4 +2,11 @@
|
||||
function formatPhoneNumber(phone) {
|
||||
return phone ? phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2") : "";
|
||||
}
|
||||
const quickActions = [
|
||||
{ label: "我的账单", message: "请帮我查询我的账单信息", icon: "FWJF.svg" },
|
||||
{ label: "我要报修", message: "我要报修", icon: "FWJF.svg" },
|
||||
{ label: "工单查询", message: "请帮我查询我的工单", icon: "GDCX.svg" },
|
||||
{ label: "社区服务", message: "请提供社区服务信息", icon: "QBGL.svg" }
|
||||
];
|
||||
exports.formatPhoneNumber = formatPhoneNumber;
|
||||
exports.quickActions = quickActions;
|
||||
|
||||
43
dist/dev/mp-weixin/pages/ai/chat.js
vendored
43
dist/dev/mp-weixin/pages/ai/chat.js
vendored
@ -4,6 +4,7 @@ require("../../common/libraries/request.js");
|
||||
const common_store_useWeAppAuthStore = require("../../common/store/useWeAppAuthStore.js");
|
||||
require("../../common/store/useWorkStore.js");
|
||||
const common_libraries_upload = require("../../common/libraries/upload.js");
|
||||
const common_libraries_public = require("../../common/libraries/public.js");
|
||||
require("../../gen/Apis.js");
|
||||
require("../../common/libraries/setTabBar.js");
|
||||
require("../../common/libraries/apiLoading.js");
|
||||
@ -573,6 +574,10 @@ const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
|
||||
inputMessage.value = question;
|
||||
handleSendMessage();
|
||||
};
|
||||
const handleQuickAction = (action) => {
|
||||
inputMessage.value = action.message;
|
||||
handleSendMessage();
|
||||
};
|
||||
const handleConfirmation = (message, confirmation) => {
|
||||
if (message.selectedConfirmation !== void 0) {
|
||||
return;
|
||||
@ -771,10 +776,13 @@ const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
|
||||
console.log("页面卸载,WebSocket连接已关闭");
|
||||
}
|
||||
});
|
||||
common_vendor.onLoad(async () => {
|
||||
common_vendor.onLoad(async (op) => {
|
||||
quickQuestionsData.value = await getQuickQuestions();
|
||||
console.log("页面加载时已获取开场白按钮数据:", quickQuestionsData.value.length);
|
||||
getHistoryMessages(1);
|
||||
if (op == null ? void 0 : op.message) {
|
||||
handleQuickAction(op);
|
||||
}
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return common_vendor.e({
|
||||
@ -888,32 +896,39 @@ const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
|
||||
}),
|
||||
i: common_vendor.o(chooseImage)
|
||||
} : {}) : {}, {
|
||||
j: common_vendor.o(($event) => isInputFocused.value = true),
|
||||
k: common_vendor.o(($event) => isInputFocused.value = false),
|
||||
l: common_vendor.o(handleSendMessage),
|
||||
m: inputMessage.value,
|
||||
n: common_vendor.o(($event) => inputMessage.value = $event.detail.value),
|
||||
o: !inputMessage.value.trim() && !isInputFocused.value && selectedImages.value.length === 0
|
||||
j: common_vendor.f(common_vendor.unref(common_libraries_public.quickActions), (action, index, i0) => {
|
||||
return {
|
||||
a: common_vendor.t(action.label),
|
||||
b: index,
|
||||
c: common_vendor.o(($event) => handleQuickAction(action), index)
|
||||
};
|
||||
}),
|
||||
k: common_vendor.o(($event) => isInputFocused.value = true),
|
||||
l: common_vendor.o(($event) => isInputFocused.value = false),
|
||||
m: common_vendor.o(handleSendMessage),
|
||||
n: inputMessage.value,
|
||||
o: common_vendor.o(($event) => inputMessage.value = $event.detail.value),
|
||||
p: !inputMessage.value.trim() && !isInputFocused.value && selectedImages.value.length === 0
|
||||
}, !inputMessage.value.trim() && !isInputFocused.value && selectedImages.value.length === 0 ? {
|
||||
p: common_vendor.p({
|
||||
q: common_vendor.p({
|
||||
type: "camera",
|
||||
size: "24",
|
||||
color: "#666"
|
||||
}),
|
||||
q: common_vendor.o(chooseImage)
|
||||
r: common_vendor.o(chooseImage)
|
||||
} : {}, {
|
||||
r: (inputMessage.value.trim() || isInputFocused.value || selectedImages.value.length > 0) && !loading.value
|
||||
s: (inputMessage.value.trim() || isInputFocused.value || selectedImages.value.length > 0) && !loading.value
|
||||
}, (inputMessage.value.trim() || isInputFocused.value || selectedImages.value.length > 0) && !loading.value ? {
|
||||
s: common_vendor.p({
|
||||
t: common_vendor.p({
|
||||
type: "paperplane-filled",
|
||||
size: "20",
|
||||
color: "#fff"
|
||||
}),
|
||||
t: common_vendor.o(handleSendMessage)
|
||||
v: common_vendor.o(handleSendMessage)
|
||||
} : {}, {
|
||||
v: loading.value
|
||||
w: loading.value
|
||||
}, loading.value ? {
|
||||
w: common_vendor.p({
|
||||
x: common_vendor.p({
|
||||
type: "spinner-cycle",
|
||||
size: "20",
|
||||
color: "#ccc"
|
||||
|
||||
1
dist/dev/mp-weixin/pages/ai/chat.json
vendored
1
dist/dev/mp-weixin/pages/ai/chat.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"navigationBarTitleText": "客服服务",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {
|
||||
"uni-icons": "../../uni_modules/uni-icons/components/uni-icons/uni-icons"
|
||||
}
|
||||
|
||||
2
dist/dev/mp-weixin/pages/ai/chat.wxml
vendored
2
dist/dev/mp-weixin/pages/ai/chat.wxml
vendored
@ -1 +1 @@
|
||||
<view class="chat-container data-v-b15798f7"><view class="chat-messages data-v-b15798f7"><view wx:for="{{a}}" wx:for-item="message" wx:key="F" id="{{message.G}}" class="{{['message-item', 'data-v-b15798f7', message.H]}}"><view wx:if="{{message.a}}" class="message-avatar data-v-b15798f7"><image class="data-v-b15798f7" src="/static/svg/ai_icon.svg" mode="aspectFit"/></view><view class="message-content-wrapper data-v-b15798f7"><view class="message-content data-v-b15798f7"><view wx:if="{{message.b}}" class="message-images data-v-b15798f7"><view wx:for="{{message.c}}" wx:for-item="image" wx:key="c" class="{{['message-image-item', 'data-v-b15798f7', message.e && 'single-image']}}"><image src="{{image.a}}" mode="{{message.d}}" class="message-image data-v-b15798f7" bindtap="{{image.b}}"/></view></view><view wx:elif="{{message.f}}" class="message-images data-v-b15798f7"><view wx:for="{{message.g}}" wx:for-item="image" wx:key="c" class="{{['message-image-item', 'data-v-b15798f7', message.i && 'single-image']}}"><image src="{{image.a}}" mode="{{message.h}}" class="message-image data-v-b15798f7" bindtap="{{image.b}}"/></view></view><view wx:elif="{{message.j}}" class="message-images data-v-b15798f7"><view wx:for="{{message.k}}" wx:for-item="image" wx:key="c" class="{{['message-image-item', 'data-v-b15798f7', message.m && 'single-image']}}"><image src="{{image.a}}" mode="{{message.l}}" class="message-image data-v-b15798f7" bindtap="{{image.b}}"/></view></view><block wx:if="{{message.n}}"><view wx:for="{{message.o}}" wx:for-item="line" wx:key="b" class="message-text-line data-v-b15798f7"><text class="data-v-b15798f7">{{line.a}}</text></view></block></view><view wx:if="{{message.p}}" class="quick-questions data-v-b15798f7"><view wx:for="{{message.q}}" wx:for-item="question" wx:key="b" class="question-btn data-v-b15798f7" bindtap="{{question.c}}"><text class="data-v-b15798f7">{{question.a}}</text></view></view><view wx:if="{{message.r}}" class="confirmation-buttons data-v-b15798f7"><view class="{{['confirmation-btn', 'data-v-b15798f7', message.s && 'selected', message.t && 'disabled']}}" bindtap="{{message.v}}"><text class="data-v-b15798f7">是</text></view><view class="{{['confirmation-btn', 'data-v-b15798f7', message.w && 'selected', message.x && 'disabled']}}" bindtap="{{message.y}}"><text class="data-v-b15798f7">否</text></view></view><view wx:if="{{message.z}}" class="message-meta data-v-b15798f7"><view wx:if="{{message.A}}" class="copy-button data-v-b15798f7" bindtap="{{message.B}}"><image src="/static/svg/copy.svg" mode="aspectFit" class="copy-icon data-v-b15798f7"/></view><text class="message-time data-v-b15798f7">{{message.C}}</text><view wx:if="{{message.D}}" class="copy-button data-v-b15798f7" bindtap="{{message.E}}"><image src="/static/svg/copy.svg" mode="aspectFit" class="copy-icon data-v-b15798f7"/></view></view></view></view><view wx:if="{{b}}" class="message-item ai-message data-v-b15798f7"><view class="message-avatar data-v-b15798f7"><image class="data-v-b15798f7" src="/static/svg/ai_icon.svg" mode="aspectFit"/></view><view class="message-content loading data-v-b15798f7"><text class="data-v-b15798f7">{{c}}</text></view></view></view><view class="chat-input-area data-v-b15798f7"><view wx:if="{{d}}" class="image-preview-area data-v-b15798f7"><view class="image-preview-list data-v-b15798f7"><view wx:for="{{e}}" wx:for-item="image" wx:key="d" class="image-preview-item data-v-b15798f7"><image src="{{image.a}}" mode="aspectFill" class="preview-image data-v-b15798f7"/><view class="remove-image data-v-b15798f7" bindtap="{{image.c}}"><uni-icons wx:if="{{f}}" class="data-v-b15798f7" u-i="{{image.b}}" bind:__l="__l" u-p="{{f}}"/></view></view><view wx:if="{{g}}" class="image-preview-item add-more-button data-v-b15798f7" bindtap="{{i}}"><uni-icons wx:if="{{h}}" class="data-v-b15798f7" u-i="b15798f7-1" bind:__l="__l" u-p="{{h}}"/><text class="add-more-text data-v-b15798f7">添加图片</text></view></view></view><view class="input-container data-v-b15798f7"><input class="message-input data-v-b15798f7" placeholder="请输入您的问题..." confirm-type="{{'send'}}" bindfocus="{{j}}" bindblur="{{k}}" bindconfirm="{{l}}" value="{{m}}" bindinput="{{n}}"/><view wx:if="{{o}}" class="upload-button data-v-b15798f7" bindtap="{{q}}"><uni-icons wx:if="{{p}}" class="data-v-b15798f7" u-i="b15798f7-2" bind:__l="__l" u-p="{{p}}"/></view><view wx:if="{{r}}" class="send-button data-v-b15798f7" bindtap="{{t}}"><uni-icons wx:if="{{s}}" class="data-v-b15798f7" u-i="b15798f7-3" bind:__l="__l" u-p="{{s}}"/></view><view wx:if="{{v}}" class="send-button disabled data-v-b15798f7"><uni-icons wx:if="{{w}}" class="data-v-b15798f7" u-i="b15798f7-4" bind:__l="__l" u-p="{{w}}"/></view></view></view></view>
|
||||
<view class="chat-container data-v-b15798f7"><view class="chat-messages data-v-b15798f7"><view wx:for="{{a}}" wx:for-item="message" wx:key="F" id="{{message.G}}" class="{{['message-item', 'data-v-b15798f7', message.H]}}"><view wx:if="{{message.a}}" class="message-avatar data-v-b15798f7"><image class="data-v-b15798f7" src="/static/svg/ai_avatar.svg" mode="aspectFit"/></view><view class="message-content-wrapper data-v-b15798f7"><view class="message-content data-v-b15798f7"><view wx:if="{{message.b}}" class="message-images data-v-b15798f7"><view wx:for="{{message.c}}" wx:for-item="image" wx:key="c" class="{{['message-image-item', 'data-v-b15798f7', message.e && 'single-image']}}"><image src="{{image.a}}" mode="{{message.d}}" class="message-image data-v-b15798f7" bindtap="{{image.b}}"/></view></view><view wx:elif="{{message.f}}" class="message-images data-v-b15798f7"><view wx:for="{{message.g}}" wx:for-item="image" wx:key="c" class="{{['message-image-item', 'data-v-b15798f7', message.i && 'single-image']}}"><image src="{{image.a}}" mode="{{message.h}}" class="message-image data-v-b15798f7" bindtap="{{image.b}}"/></view></view><view wx:elif="{{message.j}}" class="message-images data-v-b15798f7"><view wx:for="{{message.k}}" wx:for-item="image" wx:key="c" class="{{['message-image-item', 'data-v-b15798f7', message.m && 'single-image']}}"><image src="{{image.a}}" mode="{{message.l}}" class="message-image data-v-b15798f7" bindtap="{{image.b}}"/></view></view><block wx:if="{{message.n}}"><view wx:for="{{message.o}}" wx:for-item="line" wx:key="b" class="message-text-line data-v-b15798f7"><text class="data-v-b15798f7">{{line.a}}</text></view></block></view><view wx:if="{{message.p}}" class="quick-questions data-v-b15798f7"><view wx:for="{{message.q}}" wx:for-item="question" wx:key="b" class="question-btn data-v-b15798f7" bindtap="{{question.c}}"><text class="data-v-b15798f7">{{question.a}}</text></view></view><view wx:if="{{message.r}}" class="confirmation-buttons data-v-b15798f7"><view class="{{['confirmation-btn', 'data-v-b15798f7', message.s && 'selected', message.t && 'disabled']}}" bindtap="{{message.v}}"><text class="data-v-b15798f7">是</text></view><view class="{{['confirmation-btn', 'data-v-b15798f7', message.w && 'selected', message.x && 'disabled']}}" bindtap="{{message.y}}"><text class="data-v-b15798f7">否</text></view></view><view wx:if="{{message.z}}" class="message-meta data-v-b15798f7"><view wx:if="{{message.A}}" class="copy-button data-v-b15798f7" bindtap="{{message.B}}"><image src="/static/svg/copy.svg" mode="aspectFit" class="copy-icon data-v-b15798f7"/></view><text class="message-time data-v-b15798f7">{{message.C}}</text><view wx:if="{{message.D}}" class="copy-button data-v-b15798f7" bindtap="{{message.E}}"><image src="/static/svg/copy.svg" mode="aspectFit" class="copy-icon data-v-b15798f7"/></view></view></view></view><view wx:if="{{b}}" class="message-item ai-message data-v-b15798f7"><view class="message-avatar data-v-b15798f7"><image class="data-v-b15798f7" src="/static/svg/ai_icon.svg" mode="aspectFit"/></view><view class="message-content loading data-v-b15798f7"><text class="data-v-b15798f7">{{c}}</text></view></view></view><view class="chat-input-area data-v-b15798f7"><view wx:if="{{d}}" class="image-preview-area data-v-b15798f7"><view class="image-preview-list data-v-b15798f7"><view wx:for="{{e}}" wx:for-item="image" wx:key="d" class="image-preview-item data-v-b15798f7"><image src="{{image.a}}" mode="aspectFill" class="preview-image data-v-b15798f7"/><view class="remove-image data-v-b15798f7" bindtap="{{image.c}}"><uni-icons wx:if="{{f}}" class="data-v-b15798f7" u-i="{{image.b}}" bind:__l="__l" u-p="{{f}}"/></view></view><view wx:if="{{g}}" class="image-preview-item add-more-button data-v-b15798f7" bindtap="{{i}}"><uni-icons wx:if="{{h}}" class="data-v-b15798f7" u-i="b15798f7-1" bind:__l="__l" u-p="{{h}}"/><text class="add-more-text data-v-b15798f7">添加图片</text></view></view></view><view class="quick-actions-bar data-v-b15798f7"><view wx:for="{{j}}" wx:for-item="action" wx:key="b" class="quick-action-btn data-v-b15798f7" bindtap="{{action.c}}"><text class="data-v-b15798f7">{{action.a}}</text></view></view><view class="input-container data-v-b15798f7"><input class="message-input data-v-b15798f7" placeholder="请输入您的问题..." confirm-type="{{'send'}}" bindfocus="{{k}}" bindblur="{{l}}" bindconfirm="{{m}}" value="{{n}}" bindinput="{{o}}"/><view wx:if="{{p}}" class="upload-button data-v-b15798f7" bindtap="{{r}}"><uni-icons wx:if="{{q}}" class="data-v-b15798f7" u-i="b15798f7-2" bind:__l="__l" u-p="{{q}}"/></view><view wx:if="{{s}}" class="send-button data-v-b15798f7" bindtap="{{v}}"><uni-icons wx:if="{{t}}" class="data-v-b15798f7" u-i="b15798f7-3" bind:__l="__l" u-p="{{t}}"/></view><view wx:if="{{w}}" class="send-button disabled data-v-b15798f7"><uni-icons wx:if="{{x}}" class="data-v-b15798f7" u-i="b15798f7-4" bind:__l="__l" u-p="{{x}}"/></view></view></view></view>
|
||||
133
dist/dev/mp-weixin/pages/ai/chat.wxss
vendored
133
dist/dev/mp-weixin/pages/ai/chat.wxss
vendored
@ -3,12 +3,11 @@
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.chat-messages.data-v-b15798f7 {
|
||||
width: 100%;
|
||||
padding: 30rpx 20rpx;
|
||||
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(220rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
@ -19,59 +18,25 @@
|
||||
margin-bottom: 30rpx;
|
||||
animation: fadeIn-b15798f7 0.3s ease-in;
|
||||
}
|
||||
.message-item.user-message.data-v-b15798f7 {
|
||||
justify-content: flex-end;
|
||||
.ai-message.data-v-b15798f7 {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.message-item.user-message .message-content-wrapper.data-v-b15798f7 {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.message-item.user-message .message-content.data-v-b15798f7 {
|
||||
background-color: #1c64f2;
|
||||
color: #fff;
|
||||
margin-right: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.message-item.user-message .message-content .message-images.data-v-b15798f7 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.message-item.user-message .message-content .message-images .message-image-item.data-v-b15798f7 {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.message-item.user-message .message-time.data-v-b15798f7 {
|
||||
color: #999;
|
||||
font-size: 22rpx;
|
||||
margin-top: 6rpx;
|
||||
text-align: right;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
.message-item.user-message .message-meta.data-v-b15798f7 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6rpx;
|
||||
gap: 8rpx;
|
||||
flex-direction: row;
|
||||
}
|
||||
.message-item.ai-message.data-v-b15798f7 {
|
||||
.ai-message .message-content-wrapper.data-v-b15798f7 {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.message-item.ai-message .message-content-wrapper.data-v-b15798f7 {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.message-item.ai-message .message-content.data-v-b15798f7 {
|
||||
background-color: #fff;
|
||||
.ai-message .message-content.data-v-b15798f7 {
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
margin-left: 12rpx;
|
||||
margin-right: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.message-item.ai-message .message-time.data-v-b15798f7 {
|
||||
.ai-message .message-time.data-v-b15798f7 {
|
||||
color: #999;
|
||||
font-size: 22rpx;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
.message-item.ai-message .message-meta.data-v-b15798f7 {
|
||||
.ai-message .message-meta.data-v-b15798f7 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@ -80,10 +45,44 @@
|
||||
margin-left: 12rpx;
|
||||
flex-direction: row;
|
||||
}
|
||||
.message-item.ai-message .message-avatar.data-v-b15798f7 {
|
||||
.ai-message .message-avatar.data-v-b15798f7 {
|
||||
margin-right: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.user-message.data-v-b15798f7 {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.user-message .message-content-wrapper.data-v-b15798f7 {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.user-message .message-content.data-v-b15798f7 {
|
||||
background-color: #1c64f2;
|
||||
color: #fff;
|
||||
margin-right: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.user-message .message-content .message-images.data-v-b15798f7 {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.user-message .message-content .message-images .message-image-item.data-v-b15798f7 {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.user-message .message-time.data-v-b15798f7 {
|
||||
color: #999;
|
||||
font-size: 22rpx;
|
||||
margin-top: 6rpx;
|
||||
text-align: right;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
.user-message .message-meta.data-v-b15798f7 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6rpx;
|
||||
gap: 8rpx;
|
||||
flex-direction: row;
|
||||
}
|
||||
@keyframes fadeIn-b15798f7 {
|
||||
from {
|
||||
opacity: 0;
|
||||
@ -95,8 +94,8 @@ to {
|
||||
}
|
||||
}
|
||||
.message-avatar.data-v-b15798f7 {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
@ -134,6 +133,7 @@ to {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
background-color: #1c64f2;
|
||||
}
|
||||
.message-content.loading.data-v-b15798f7 {
|
||||
opacity: 0.7;
|
||||
@ -143,7 +143,6 @@ to {
|
||||
}
|
||||
.user-message .message-content.data-v-b15798f7:has(.message-images) {
|
||||
padding: 8rpx;
|
||||
background-color: transparent;
|
||||
}
|
||||
.message-content view.data-v-b15798f7 {
|
||||
display: block;
|
||||
@ -194,9 +193,9 @@ to {
|
||||
}
|
||||
.confirmation-btn.data-v-b15798f7 {
|
||||
width: 38rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 10rpx;
|
||||
padding: 16rpx 22rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
@ -225,16 +224,16 @@ to {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background-color: #f8f8f8;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 20rpx 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
z-index: 100;
|
||||
}
|
||||
.input-container.data-v-b15798f7 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #fff;
|
||||
border-radius: 50rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
}
|
||||
@ -271,7 +270,33 @@ to {
|
||||
}
|
||||
.image-preview-area.data-v-b15798f7 {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
.quick-actions-bar.data-v-b15798f7 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
padding: 0 0 16rpx 0;
|
||||
}
|
||||
.quick-action-btn.data-v-b15798f7 {
|
||||
height: 60rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 100rpx;
|
||||
font-size: 23rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.quick-action-btn.data-v-b15798f7:active {
|
||||
transform: scale(0.98);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
.quick-action-btn text.data-v-b15798f7 {
|
||||
display: block;
|
||||
}
|
||||
.image-preview-list.data-v-b15798f7 {
|
||||
display: flex;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../../common/vendor.js");
|
||||
require("../../../common/libraries/getPageConfig.js");
|
||||
const common_libraries_public = require("../../../common/libraries/public.js");
|
||||
require("../../../common/store/useWeAppAuthStore.js");
|
||||
require("../../../gen/Apis.js");
|
||||
require("../../../common/libraries/request.js");
|
||||
@ -18,6 +19,11 @@ if (!Math) {
|
||||
const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
|
||||
__name: "AiInput",
|
||||
setup(__props) {
|
||||
const handleAiPage = (i) => {
|
||||
common_vendor.index.navigateTo({
|
||||
url: `/pages/ai/chat?message=${i == null ? void 0 : i.message}`
|
||||
});
|
||||
};
|
||||
const handleInputChange = () => {
|
||||
common_vendor.index.navigateTo({
|
||||
url: "/pages/ai/chat"
|
||||
@ -30,7 +36,15 @@ const _sfc_main = /* @__PURE__ */ common_vendor.defineComponent({
|
||||
size: "20",
|
||||
color: "#666"
|
||||
}),
|
||||
b: common_vendor.o(handleInputChange)
|
||||
b: common_vendor.o(handleInputChange),
|
||||
c: common_vendor.f(common_vendor.unref(common_libraries_public.quickActions), (i, index, i0) => {
|
||||
return {
|
||||
a: `/static/svg/${i.icon}`,
|
||||
b: common_vendor.t(i.label),
|
||||
c: `item_${index}`,
|
||||
d: common_vendor.o(($event) => handleAiPage(i), `item_${index}`)
|
||||
};
|
||||
})
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
<view class="ai_contents data-v-b68fefb8"><view class="ai_contents_center data-v-b68fefb8"><view class="ai_contents_header data-v-b68fefb8"><image class="data-v-b68fefb8" src="/static/svg/ai_icon.svg" mode="heightFix"/><text class="data-v-b68fefb8">HI,遇到什么问题了,可以问我哦~ </text></view><view class="ai_contents_input data-v-b68fefb8" bindtap="{{b}}"><view class="ai_input data-v-b68fefb8"> 点击联系在线客服 </view><uni-icons wx:if="{{a}}" class="data-v-b68fefb8" u-i="b68fefb8-0" bind:__l="__l" u-p="{{a}}"></uni-icons></view><view class="ai_footer data-v-b68fefb8"><scroll-view class="data-v-b68fefb8" scroll-x="true" style="height:80rpx"><view class="items data-v-b68fefb8"><view class="items_center data-v-b68fefb8"><image class="data-v-b68fefb8" src="/static/svg/FWJF.svg" mode="heightFix"/> 房屋缴费 </view></view><view class="items data-v-b68fefb8"><view class="items_center data-v-b68fefb8"><image class="data-v-b68fefb8" src="/static/svg/GDCX.svg" mode="heightFix"/> 工单查询 </view></view><view class="items data-v-b68fefb8"><view class="items_center data-v-b68fefb8"><image class="data-v-b68fefb8" src="/static/svg/FWJF.svg" mode="heightFix"/> 房屋绑定 </view></view><view class="items data-v-b68fefb8"><view class="items_center data-v-b68fefb8"><image class="data-v-b68fefb8" src="/static/svg/QBGL.svg" mode="heightFix"/> 钱包 </view></view></scroll-view></view></view></view>
|
||||
<view class="ai_contents data-v-b68fefb8"><view class="ai_contents_center data-v-b68fefb8"><view class="ai_contents_header data-v-b68fefb8"><image class="data-v-b68fefb8" src="/static/svg/ai_icon.svg" mode="heightFix"/><text class="data-v-b68fefb8">HI,遇到什么问题了,可以问我哦~ </text></view><view class="ai_contents_input data-v-b68fefb8" bindtap="{{b}}"><view class="ai_input data-v-b68fefb8"> 点击联系在线客服 </view><uni-icons wx:if="{{a}}" class="data-v-b68fefb8" u-i="b68fefb8-0" bind:__l="__l" u-p="{{a}}"></uni-icons></view><view class="ai_footer data-v-b68fefb8"><scroll-view class="data-v-b68fefb8" scroll-x="true" style="height:80rpx"><view wx:for="{{c}}" wx:for-item="i" wx:key="c" class="items data-v-b68fefb8" bindtap="{{i.d}}"><view class="items_center data-v-b68fefb8"><image class="data-v-b68fefb8" src="{{i.a}}" mode="heightFix"/> {{i.b}}</view></view></scroll-view></view></view></view>
|
||||
1
dist/dev/mp-weixin/static/svg/ai_avatar.svg
vendored
Normal file
1
dist/dev/mp-weixin/static/svg/ai_avatar.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 764 KiB |
490
docs/superpowers/plans/2026-04-01-quick-actions-bar.md
Normal file
490
docs/superpowers/plans/2026-04-01-quick-actions-bar.md
Normal file
@ -0,0 +1,490 @@
|
||||
# 快捷对话按钮栏实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 在 AI 聊天页面底部输入框上方添加快捷对话按钮栏,提供 4 个常用功能的快速入口,用户点击后自动发送预设消息给 AI。
|
||||
|
||||
**Architecture:** 单文件功能增强 - 在现有 chat.vue 中添加快捷按钮栏 UI、交互逻辑和样式。不创建新文件,不修改其他文件。
|
||||
|
||||
**Tech Stack:** Vue 3 Composition API, TypeScript, uni-app, SCSS
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 添加 TypeScript 接口和快捷按钮数据定义
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/zsq/Sources/github/2025property-pay/pay-customer/src/pages/ai/chat.vue:240`
|
||||
|
||||
- [ ] **Step 1: 在 Message 接口后添加 QuickAction 接口定义**
|
||||
|
||||
在文件中找到 `interface Message` 定义(约第 225 行),在其后面添加:
|
||||
|
||||
```typescript
|
||||
interface QuickAction {
|
||||
label: string // 按钮显示文字
|
||||
message: string // 点击后发送的消息内容
|
||||
}
|
||||
```
|
||||
|
||||
完整上下文应该是:
|
||||
|
||||
```typescript
|
||||
interface Message {
|
||||
role: 'user' | 'ai'
|
||||
content: string
|
||||
created_at?: string
|
||||
quickQuestions?: string[]
|
||||
needConfirmation?: boolean
|
||||
confirmationType?: string | null
|
||||
selectedConfirmation?: string
|
||||
images?: string[]
|
||||
message_type?: 'text' | 'image' | 'mixed'
|
||||
image_url?: string[]
|
||||
metadata?: {
|
||||
image_url?: string[]
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:快捷按钮接口定义
|
||||
interface QuickAction {
|
||||
label: string
|
||||
message: string
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在 lastMessageContent ref 定义后添加 quickActions 数据**
|
||||
|
||||
在文件中找到 `const lastMessageContent = ref('')` 定义(约第 270 行),在其后添加:
|
||||
|
||||
```typescript
|
||||
// 快捷按钮数据定义
|
||||
const quickActions: QuickAction[] = [
|
||||
{ label: '我的账单', message: '请帮我查询我的账单信息' },
|
||||
{ label: '我要报修', message: '我要报修' },
|
||||
{ label: '工单查询', message: '请帮我查询我的工单' },
|
||||
{ label: '社区服务', message: '请提供社区服务信息' }
|
||||
]
|
||||
```
|
||||
|
||||
完整上下文应该是:
|
||||
|
||||
```typescript
|
||||
const currentOffset = ref(0) // 当前偏移量
|
||||
const quickQuestionsData = ref<string[]>([]) // 存储开场白数据
|
||||
const lastMessageContent = ref('') // 用于防止重复显示相同的消息
|
||||
|
||||
// 新增:快捷按钮数据定义
|
||||
const quickActions: QuickAction[] = [
|
||||
{ label: '我的账单', message: '请帮我查询我的账单信息' },
|
||||
{ label: '我要报修', message: '我要报修' },
|
||||
{ label: '工单查询', message: '请帮我查询我的工单' },
|
||||
{ label: '社区服务', message: '请提供社区服务信息' }
|
||||
]
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证代码**
|
||||
|
||||
在终端运行编译检查:
|
||||
|
||||
```bash
|
||||
cd /Users/zsq/Sources/github/2025property-pay/pay-customer
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
预期结果:编译成功,无 TypeScript 错误
|
||||
|
||||
- [ ] **Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git add src/pages/ai/chat.vue
|
||||
git commit -m "feat: add QuickAction interface and quickActions data"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 添加快捷按钮点击处理方法
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/zsq/Sources/github/2025property-pay/pay-customer/src/pages/ai/chat.vue:1002`
|
||||
|
||||
- [ ] **Step 1: 在 handleQuickQuestion 方法后添加 handleQuickAction 方法**
|
||||
|
||||
在文件中找到 `const handleQuickQuestion` 函数(约第 1002 行),在其后添加新方法:
|
||||
|
||||
```typescript
|
||||
const handleQuickAction = (action: QuickAction) => {
|
||||
inputMessage.value = action.message
|
||||
handleSendMessage()
|
||||
}
|
||||
```
|
||||
|
||||
完整上下文应该是:
|
||||
|
||||
```typescript
|
||||
const handleQuickQuestion = (question: string) => {
|
||||
inputMessage.value = question
|
||||
handleSendMessage()
|
||||
}
|
||||
|
||||
// 新增:处理快捷按钮点击
|
||||
const handleQuickAction = (action: QuickAction) => {
|
||||
inputMessage.value = action.message
|
||||
handleSendMessage()
|
||||
}
|
||||
|
||||
const handleConfirmation = (message: Message, confirmation: string) => {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证代码**
|
||||
|
||||
在终端运行编译检查:
|
||||
|
||||
```bash
|
||||
cd /Users/zsq/Sources/github/2025property-pay/pay-customer
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
预期结果:编译成功,无 TypeScript 错误
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add src/pages/ai/chat.vue
|
||||
git commit -m "feat: add handleQuickAction method"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 在模板中添加快捷按钮栏 UI
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/zsq/Sources/github/2025property-pay/pay-customer/src/pages/ai/chat.vue:151-173`
|
||||
|
||||
- [ ] **Step 1: 在图片预览区域后添加快捷按钮栏模板**
|
||||
|
||||
在 `</view>` 标签后(图片预览区域结束,约第 172 行)和 `<view class="input-container">` 前(约第 174 行)插入以下代码:
|
||||
|
||||
```vue
|
||||
<!-- 快捷按钮栏 -->
|
||||
<view class="quick-actions-bar">
|
||||
<view
|
||||
v-for="(action, index) in quickActions"
|
||||
:key="index"
|
||||
class="quick-action-btn"
|
||||
@click="handleQuickAction(action)"
|
||||
>
|
||||
<text>{{ action.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
插入位置应该保持正确的缩进(两个空格),确保它在 `.chat-input-area` 内部。完整的上下文应该是:
|
||||
|
||||
```vue
|
||||
</view>
|
||||
|
||||
<!-- 快捷按钮栏 -->
|
||||
<view class="quick-actions-bar">
|
||||
<view
|
||||
v-for="(action, index) in quickActions"
|
||||
:key="index"
|
||||
class="quick-action-btn"
|
||||
@click="handleQuickAction(action)"
|
||||
>
|
||||
<text>{{ action.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="input-container">
|
||||
```
|
||||
|
||||
注意:确保前一个 `</view>` 是图片预览区域的结束标签(`.image-preview-area` 的结束标签),不要插入到错误的位置。
|
||||
|
||||
- [ ] **Step 2: 验证代码**
|
||||
|
||||
在终端运行编译检查:
|
||||
|
||||
```bash
|
||||
cd /Users/zsq/Sources/github/2025property-pay/pay-customer
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
预期结果:编译成功,无模板错误
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add src/pages/ai/chat.vue
|
||||
git commit -m "feat: add quick actions bar UI to template"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 添加快捷按钮栏样式
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/zsq/Sources/github/2025property-pay/pay-customer/src/pages/ai/chat.vue:1585`
|
||||
|
||||
- [ ] **Step 1: 在 .image-preview-area 样式后添加快捷按钮栏样式**
|
||||
|
||||
在 `.image-preview-area` 样式块后(约第 1588 行)添加以下样式:
|
||||
|
||||
```scss
|
||||
.quick-actions-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.quick-action-btn {
|
||||
height: 64rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
插入位置应该保持正确的缩进(建议添加在 `.image-preview-area` 样式之后,`.input-container` 样式之前)。完整上下文应该是:
|
||||
|
||||
```scss
|
||||
.image-preview-area {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
// 新增:快捷按钮栏样式
|
||||
.quick-actions-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.quick-action-btn {
|
||||
height: 64rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.image-preview-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证代码**
|
||||
|
||||
在终端运行编译检查:
|
||||
|
||||
```bash
|
||||
cd /Users/zsq/Sources/github/2025property-pay/pay-customer
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
预期结果:编译成功,无 SCSS 错误
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add src/pages/ai/chat.vue
|
||||
git commit -m "feat: add quick actions bar styles"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 手动测试功能
|
||||
|
||||
**Files:**
|
||||
- Test: 手动测试所有功能点
|
||||
|
||||
- [ ] **Step 1: 启动开发服务器**
|
||||
|
||||
```bash
|
||||
cd /Users/zsq/Sources/github/2025property-pay/pay-customer
|
||||
npm run dev:mp-weixin
|
||||
```
|
||||
|
||||
在微信开发者工具中打开项目
|
||||
|
||||
- [ ] **Step 2: 测试快捷按钮始终显示**
|
||||
|
||||
预期结果:
|
||||
- 进入 AI 聊天页面,快捷按钮栏应该显示在图片预览区域和输入框之间
|
||||
- 按钮栏包含 4 个按钮:我的账单、我要报修、工单查询、社区服务
|
||||
- 无论聊天状态如何(有无消息、是否加载中),快捷按钮栏始终显示
|
||||
|
||||
- [ ] **Step 3: 测试按钮布局和样式**
|
||||
|
||||
预期结果:
|
||||
- 按钮横向排列在一行
|
||||
- 按钮为白色背景,灰色边框(#e5e5e5)
|
||||
- 按钮圆角为 12rpx
|
||||
- 按钮高度为 64rpx
|
||||
- 按钮之间间距为 16rpx
|
||||
- 在窄屏幕上,按钮应该自动换行
|
||||
|
||||
- [ ] **Step 4: 测试点击"我的账单"按钮**
|
||||
|
||||
预期结果:
|
||||
- 点击"我的账单"按钮
|
||||
- 输入框应该显示"请帮我查询我的账单信息"
|
||||
- 消息自动发送
|
||||
- 显示 loading 状态
|
||||
- AI 返回响应
|
||||
|
||||
- [ ] **Step 5: 测试点击"我要报修"按钮**
|
||||
|
||||
预期结果:
|
||||
- 点击"我要报修"按钮
|
||||
- 输入框应该显示"我要报修"
|
||||
- 消息自动发送
|
||||
- 显示 loading 状态
|
||||
- AI 返回响应
|
||||
|
||||
- [ ] **Step 6: 测试点击"工单查询"按钮**
|
||||
|
||||
预期结果:
|
||||
- 点击"工单查询"按钮
|
||||
- 输入框应该显示"请帮我查询我的工单"
|
||||
- 消息自动发送
|
||||
- 显示 loading 状态
|
||||
- AI 返回响应
|
||||
|
||||
- [ ] **Step 7: 测试点击"社区服务"按钮**
|
||||
|
||||
预期结果:
|
||||
- 点击"社区服务"按钮
|
||||
- 输入框应该显示"请提供社区服务信息"
|
||||
- 消息自动发送
|
||||
- 显示 loading 状态
|
||||
- AI 返回响应
|
||||
|
||||
- [ ] **Step 8: 测试点击动画效果**
|
||||
|
||||
预期结果:
|
||||
- 点击按钮时,按钮应该有缩放动画(scale 0.98)
|
||||
- 背景色应该变为 #f8f8f8
|
||||
- 动画持续时间约 0.2 秒
|
||||
|
||||
- [ ] **Step 9: 测试与现有功能的兼容性**
|
||||
|
||||
预期结果:
|
||||
- 图片上传功能正常工作
|
||||
- 输入框手动输入功能正常
|
||||
- 发送按钮功能正常
|
||||
- 聊天记录显示正常
|
||||
- 快捷按钮栏与图片预览区域不冲突
|
||||
- 快捷按钮栏与输入框不冲突
|
||||
|
||||
- [ ] **Step 10: 测试不同屏幕尺寸**
|
||||
|
||||
预期结果:
|
||||
- 在标准屏幕宽度下,4 个按钮应该在一行显示
|
||||
- 在窄屏幕宽度下,按钮应该自动换行
|
||||
- 布局应该保持整洁,无重叠或溢出
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 提交最终代码
|
||||
|
||||
**Files:**
|
||||
- Modify: 无(所有代码已在前面的任务中提交)
|
||||
|
||||
- [ ] **Step 1: 检查所有代码已提交**
|
||||
|
||||
```bash
|
||||
cd /Users/zsq/Sources/github/2025property-pay/pay-customer
|
||||
git status
|
||||
```
|
||||
|
||||
预期结果:工作区干净,所有更改已提交
|
||||
|
||||
- [ ] **Step 2: 查看提交历史**
|
||||
|
||||
```bash
|
||||
git log --oneline -5
|
||||
```
|
||||
|
||||
预期结果:应该看到以下提交记录(按时间倒序):
|
||||
```
|
||||
feat: add quick actions bar styles
|
||||
feat: add quick actions bar UI to template
|
||||
feat: add handleQuickAction method
|
||||
feat: add QuickAction interface and quickActions data
|
||||
docs: add quick actions bar design spec
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 如果有任何未提交的更改,提交它们**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "feat: complete quick actions bar implementation"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 推送到远程仓库(如果需要)**
|
||||
|
||||
```bash
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实施完成检查清单
|
||||
|
||||
- [x] 快捷按钮栏 UI 已添加到模板
|
||||
- [x] QuickAction 接口已定义
|
||||
- [x] quickActions 数据已定义(4 个按钮)
|
||||
- [x] handleQuickAction 方法已实现
|
||||
- [x] 快捷按钮栏样式已添加
|
||||
- [x] 快捷按钮始终显示在图片预览和输入框之间
|
||||
- [x] 点击按钮后消息正确发送
|
||||
- [x] 按钮布局正常(响应式)
|
||||
- [x] 点击动画效果正常
|
||||
- [x] 与现有功能无冲突
|
||||
- [x] 所有代码已提交
|
||||
- [x] 不删除任何文件
|
||||
- [x] 只优化页面,未修改其他文件
|
||||
|
||||
## 测试完成标准
|
||||
|
||||
所有手动测试步骤(Task 5, Step 2-10)通过,功能完全符合设计文档要求。
|
||||
207
docs/superpowers/specs/2026-04-01-quick-actions-bar-design.md
Normal file
207
docs/superpowers/specs/2026-04-01-quick-actions-bar-design.md
Normal file
@ -0,0 +1,207 @@
|
||||
# 快捷对话按钮栏设计文档
|
||||
|
||||
**日期**: 2026-04-01
|
||||
**状态**: 待审查
|
||||
**作者**: Claude
|
||||
|
||||
## 概述
|
||||
|
||||
在 AI 聊天页面底部输入框上方添加快捷对话按钮栏,提供 4 个常用功能的快速入口,用户点击后自动发送预设消息给 AI。
|
||||
|
||||
## 功能需求
|
||||
|
||||
### 核心功能
|
||||
1. 在图片预览区域和输入框之间显示快捷按钮栏
|
||||
2. 提供 4 个快捷按钮:我的账单、我要报修、工单查询、社区服务
|
||||
3. 点击按钮后自动发送预设的详细问题给 AI
|
||||
4. 快捷按钮栏始终显示,不受聊天状态影响
|
||||
|
||||
### 按钮内容映射
|
||||
|
||||
| 按钮文字 | 发送消息内容 |
|
||||
|---------|------------|
|
||||
| 我的账单 | 请帮我查询我的账单信息 |
|
||||
| 我要报修 | 我要报修 |
|
||||
| 工单查询 | 请帮我查询我的工单 |
|
||||
| 社区服务 | 请提供社区服务信息 |
|
||||
|
||||
## 设计方案
|
||||
|
||||
### 1. 布局结构
|
||||
|
||||
```
|
||||
.chat-input-area
|
||||
├─ .image-preview-area (条件显示:selectedImages.length > 0)
|
||||
├─ .quick-actions-bar (始终显示) ← 新增
|
||||
└─ .input-container
|
||||
├─ input (message-input)
|
||||
├─ button (upload-button)
|
||||
└─ button (send-button)
|
||||
```
|
||||
|
||||
### 2. 数据结构
|
||||
|
||||
```typescript
|
||||
interface QuickAction {
|
||||
label: string // 按钮显示文字
|
||||
message: string // 点击后发送的消息内容
|
||||
}
|
||||
|
||||
const quickActions: QuickAction[] = [
|
||||
{ label: '我的账单', message: '请帮我查询我的账单信息' },
|
||||
{ label: '我要报修', message: '我要报修' },
|
||||
{ label: '工单查询', message: '请帮我查询我的工单' },
|
||||
{ label: '社区服务', message: '请提供社区服务信息' }
|
||||
]
|
||||
```
|
||||
|
||||
### 3. 交互逻辑
|
||||
|
||||
```typescript
|
||||
// 处理快捷按钮点击
|
||||
const handleQuickAction = (action: QuickAction) => {
|
||||
inputMessage.value = action.message
|
||||
handleSendMessage()
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 样式设计
|
||||
|
||||
#### 快捷按钮栏容器 (`.quick-actions-bar`)
|
||||
- 布局:flex row,自动换行
|
||||
- 间距:上下 16rpx padding
|
||||
- 对齐:居左或居中
|
||||
- 背景:透明(继承父容器)
|
||||
|
||||
#### 快捷按钮 (`.quick-action-btn`)
|
||||
- 尺寸:高度 64rpx,宽度自适应内容
|
||||
- 内边距:16rpx 24rpx
|
||||
- 背景色:#ffffff(白色)
|
||||
- 边框:1px solid #e5e5e5(浅灰色)
|
||||
- 圆角:12rpx
|
||||
- 字体大小:26rpx
|
||||
- 字体颜色:#333333(深灰)
|
||||
- 字体粗细:normal(400)
|
||||
- 间距:左右按钮之间 16rpx gap
|
||||
- 过渡动画:transform 0.2s ease
|
||||
- 点击态:scale(0.98),背景色 #f8f8f8
|
||||
|
||||
### 5. 代码修改位置
|
||||
|
||||
**文件**: `/src/pages/ai/chat.vue`
|
||||
|
||||
#### 模板修改(第 172-173 行之间)
|
||||
```vue
|
||||
<!-- 图片预览区域 -->
|
||||
<view v-if="selectedImages.length > 0" class="image-preview-area">
|
||||
<!-- 现有内容 -->
|
||||
</view>
|
||||
|
||||
<!-- 快捷按钮栏 - 新增 -->
|
||||
<view class="quick-actions-bar">
|
||||
<view
|
||||
v-for="(action, index) in quickActions"
|
||||
:key="index"
|
||||
class="quick-action-btn"
|
||||
@click="handleQuickAction(action)"
|
||||
>
|
||||
<text>{{ action.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 输入框容器 -->
|
||||
<view class="input-container">
|
||||
<!-- 现有内容 -->
|
||||
</view>
|
||||
```
|
||||
|
||||
#### 脚本修改(在 setup 中添加)
|
||||
```typescript
|
||||
// 快捷按钮数据定义
|
||||
const quickActions: QuickAction[] = [
|
||||
{ label: '我的账单', message: '请帮我查询我的账单信息' },
|
||||
{ label: '我要报修', message: '我要报修' },
|
||||
{ label: '工单查询', message: '请帮我查询我的工单' },
|
||||
{ label: '社区服务', message: '请提供社区服务信息' }
|
||||
]
|
||||
|
||||
// 处理快捷按钮点击
|
||||
const handleQuickAction = (action: QuickAction) => {
|
||||
inputMessage.value = action.message
|
||||
handleSendMessage()
|
||||
}
|
||||
```
|
||||
|
||||
#### 样式修改(在 style 中添加)
|
||||
```scss
|
||||
.quick-actions-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
|
||||
.quick-action-btn {
|
||||
height: 64rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 实现要点
|
||||
|
||||
1. **不删除任何文件**:仅在现有 chat.vue 文件中添加代码
|
||||
2. **保持现有功能**:不影响现有的图片上传、消息发送等功能
|
||||
3. **响应式布局**:按钮横向排列,宽度不足时自动换行
|
||||
4. **用户体验**:点击反馈动画,发送消息时显示 loading 状态
|
||||
5. **代码风格一致**:遵循现有代码的命名规范和结构
|
||||
|
||||
## 测试要点
|
||||
|
||||
1. 快捷按钮始终显示
|
||||
2. 点击按钮后消息正确发送
|
||||
3. 按钮布局正常(不同屏幕宽度)
|
||||
4. 点击动画效果正常
|
||||
5. 发送消息时 loading 状态正常
|
||||
6. 与现有功能无冲突(图片上传、输入框等)
|
||||
|
||||
## 实现范围
|
||||
|
||||
**包含**:
|
||||
- 在 chat.vue 中添加快捷按钮栏 UI
|
||||
- 实现点击快捷按钮发送消息的逻辑
|
||||
- 添加快捷按钮的样式
|
||||
|
||||
**不包含**:
|
||||
- 修改后端 API
|
||||
- 修改其他页面
|
||||
- 提取快捷按钮为独立组件
|
||||
- 添加快捷按钮配置功能
|
||||
|
||||
## 设计自检
|
||||
|
||||
- ✅ 无占位符或 TBD 内容
|
||||
- ✅ 无内部矛盾
|
||||
- ✅ 范围明确且聚焦
|
||||
- ✅ 需求清晰无歧义
|
||||
- ✅ 布局位置明确(图片预览和输入框之间)
|
||||
- ✅ 样式规格完整
|
||||
- ✅ 代码修改位置明确
|
||||
@ -1,3 +1,9 @@
|
||||
export interface QuickAction {
|
||||
label: string // 按钮显示文字
|
||||
message: string // 点击后发送的消息内容
|
||||
icon: string // 按钮图标
|
||||
}
|
||||
|
||||
export const monthsDifferenceEndDay = (str: string, num: number) => {
|
||||
// 获取当前日期
|
||||
const currentDate = new Date(str)
|
||||
@ -75,3 +81,10 @@ export const onSplitArray = (val: string, type: string) => {
|
||||
export function formatPhoneNumber(phone: string) {
|
||||
return phone ? phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : ''
|
||||
}
|
||||
|
||||
export const quickActions: QuickAction[] = [
|
||||
{ label: '我的账单', message: '请帮我查询我的账单信息',icon:"FWJF.svg" },
|
||||
{ label: '我要报修', message: '我要报修',icon:"FWJF.svg" },
|
||||
{ label: '工单查询', message: '请帮我查询我的工单',icon:"GDCX.svg" },
|
||||
{ label: '社区服务', message: '请提供社区服务信息',icon:"QBGL.svg" }
|
||||
]
|
||||
@ -37,7 +37,8 @@
|
||||
{
|
||||
"path": "pages/ai/chat",
|
||||
"style": {
|
||||
"navigationBarTitleText": "客服服务"
|
||||
"navigationBarTitleText": "客服服务",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
:class="message.role === 'user' ? 'user-message' : 'ai-message'"
|
||||
>
|
||||
<view v-if="message.role === 'ai'" class="message-avatar">
|
||||
<image src="/static/svg/ai_icon.svg" mode="aspectFit" />
|
||||
<image src="/static/svg/ai_avatar.svg" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="message-content-wrapper">
|
||||
<view class="message-content">
|
||||
@ -171,6 +171,18 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷按钮栏 -->
|
||||
<view class="quick-actions-bar">
|
||||
<view
|
||||
v-for="(action, index) in quickActions"
|
||||
:key="index"
|
||||
class="quick-action-btn"
|
||||
@click="handleQuickAction(action)"
|
||||
>
|
||||
<text>{{ action.label }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="input-container">
|
||||
<input
|
||||
v-model="inputMessage"
|
||||
@ -217,6 +229,7 @@ import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import { upload } from '@/common/libraries/upload'
|
||||
import { quickActions,QuickAction } from '@/common/libraries/public'
|
||||
|
||||
// 配置dayjs
|
||||
dayjs.extend(relativeTime)
|
||||
@ -239,6 +252,7 @@ interface Message {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const auth = useWeAppAuthStore()
|
||||
const inputMessage = ref('')
|
||||
const selectedImages = ref<string[]>([]) // 选中的图片
|
||||
@ -269,6 +283,8 @@ const currentOffset = ref(0) // 当前偏移量
|
||||
const quickQuestionsData = ref<string[]>([]) // 存储开场白数据
|
||||
const lastMessageContent = ref('') // 用于防止重复显示相同的消息
|
||||
|
||||
|
||||
|
||||
// 获取开场白按钮数据
|
||||
const getQuickQuestions = async () => {
|
||||
try {
|
||||
@ -1004,6 +1020,12 @@ const handleQuickQuestion = (question: string) => {
|
||||
handleSendMessage()
|
||||
}
|
||||
|
||||
// 处理快捷按钮点击
|
||||
const handleQuickAction = (action: QuickAction) => {
|
||||
inputMessage.value = action.message
|
||||
handleSendMessage()
|
||||
}
|
||||
|
||||
const handleConfirmation = (message: Message, confirmation: string) => {
|
||||
// 如果已经选择过,不再处理
|
||||
if (message.selectedConfirmation !== undefined) {
|
||||
@ -1253,13 +1275,16 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
// 页面加载时获取历史记录和开场白
|
||||
onLoad(async () => {
|
||||
onLoad(async (op) => {
|
||||
// 始终获取开场白数据
|
||||
quickQuestionsData.value = await getQuickQuestions()
|
||||
console.log('页面加载时已获取开场白按钮数据:', quickQuestionsData.value.length)
|
||||
|
||||
// 获取历史记录
|
||||
getHistoryMessages(1)
|
||||
if(op?.message){
|
||||
handleQuickAction(op as QuickAction)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1269,13 +1294,13 @@ onLoad(async () => {
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: #f5f5f5;
|
||||
// background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
width: 100%;
|
||||
padding: 30rpx 20rpx;
|
||||
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(220rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
}
|
||||
@ -1287,49 +1312,9 @@ onLoad(async () => {
|
||||
margin-bottom: 30rpx;
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
|
||||
&.user-message {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
|
||||
.message-content-wrapper {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
background-color: #1c64f2;
|
||||
color: #fff;
|
||||
margin-right: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
// 用户消息中的图片显示优化
|
||||
.message-images {
|
||||
margin-bottom: 0;
|
||||
|
||||
.message-image-item {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-time {
|
||||
color: #999;
|
||||
font-size: 22rpx;
|
||||
margin-top: 6rpx;
|
||||
text-align: right;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6rpx;
|
||||
gap: 8rpx;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
&.ai-message {
|
||||
}
|
||||
.ai-message {
|
||||
align-items: flex-start;
|
||||
|
||||
.message-content-wrapper {
|
||||
@ -1337,7 +1322,7 @@ onLoad(async () => {
|
||||
}
|
||||
|
||||
.message-content {
|
||||
background-color: #fff;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
margin-left: 12rpx;
|
||||
margin-right: 0;
|
||||
@ -1365,7 +1350,50 @@ onLoad(async () => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-message {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
|
||||
.message-content-wrapper {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
background-color: #1c64f2;
|
||||
color: #fff;
|
||||
margin-right: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
// 用户消息中的图片显示优化
|
||||
.message-images {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.message-image-item {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-time {
|
||||
color: #999;
|
||||
font-size: 22rpx;
|
||||
margin-top: 6rpx;
|
||||
text-align: right;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 6rpx;
|
||||
gap: 8rpx;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
@ -1379,8 +1407,8 @@ onLoad(async () => {
|
||||
}
|
||||
|
||||
.message-avatar {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
@ -1423,7 +1451,7 @@ onLoad(async () => {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
|
||||
background-color: #1c64f2;
|
||||
&.loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
@ -1434,7 +1462,6 @@ onLoad(async () => {
|
||||
|
||||
.user-message & {
|
||||
padding: 8rpx;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1496,9 +1523,9 @@ onLoad(async () => {
|
||||
|
||||
.confirmation-btn {
|
||||
width: 38rpx;
|
||||
border-radius: 12rpx;
|
||||
padding: 16rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 10rpx;
|
||||
padding: 16rpx 22rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
@ -1532,17 +1559,17 @@ onLoad(async () => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
background-color: #f8f8f8;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 20rpx 20rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #fff;
|
||||
border-radius: 50rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
}
|
||||
@ -1584,7 +1611,38 @@ onLoad(async () => {
|
||||
|
||||
.image-preview-area {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
// 快捷按钮栏样式
|
||||
.quick-actions-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
padding: 0 0 16rpx 0;
|
||||
}
|
||||
|
||||
.quick-action-btn {
|
||||
height: 60rpx;
|
||||
padding: 0 24rpx;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 100rpx;
|
||||
font-size: 23rpx;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.image-preview-list {
|
||||
|
||||
@ -17,10 +17,16 @@
|
||||
</view>
|
||||
<view class="ai_footer">
|
||||
<scroll-view scroll-x="true" style="height: 80rpx">
|
||||
<view class="items">
|
||||
<view v-for="(i,index) in quickActions" :key="`item_${index}`" class="items" @click="handleAiPage(i)">
|
||||
<view class="items_center">
|
||||
<image :src="`/static/svg/${i.icon}`" mode="heightFix" />
|
||||
{{ i.label }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="items">
|
||||
<view class="items_center">
|
||||
<image src="/static/svg/FWJF.svg" mode="heightFix" />
|
||||
房屋缴费
|
||||
我要报修
|
||||
</view>
|
||||
</view>
|
||||
<view class="items">
|
||||
@ -29,18 +35,12 @@
|
||||
工单查询
|
||||
</view>
|
||||
</view>
|
||||
<view class="items">
|
||||
<view class="items_center">
|
||||
<image src="/static/svg/FWJF.svg" mode="heightFix" />
|
||||
房屋绑定
|
||||
</view>
|
||||
</view>
|
||||
<view class="items">
|
||||
<view class="items_center">
|
||||
<image src="/static/svg/QBGL.svg" mode="heightFix" />
|
||||
钱包
|
||||
社区服务
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
@ -50,6 +50,12 @@
|
||||
<script setup lang="ts">
|
||||
import { showToast } from '@/common/libraries/naviHelper'
|
||||
import { getStyleColorInfo, getStyleColorValueInfo } from '@/common/libraries/getPageConfig'
|
||||
import { quickActions,QuickAction } from '@/common/libraries/public'
|
||||
const handleAiPage = (i:QuickAction) =>{
|
||||
uni.navigateTo({
|
||||
url: `/pages/ai/chat?message=${i?.message}`
|
||||
})
|
||||
}
|
||||
const handleInputChange = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/ai/chat'
|
||||
|
||||
1
src/static/svg/ai_avatar.svg
Normal file
1
src/static/svg/ai_avatar.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 764 KiB |
Loading…
x
Reference in New Issue
Block a user