feat(ai): 优化AI聊天界面布局和交互逻辑
- 重构输入框布局,将"新开会话"按钮移至左侧 - 添加ResizeObserver监听输入框高度变化,动态调整内容区域padding - 增加登录状态检查,未登录时提示并限制操作 - 优化文本显示样式,包括AI提示框的圆角和背景色 - 修复输入框自动高度调整逻辑 - 提取logo文字为变量,支持动态配置
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
dist/index.html
vendored
2
dist/index.html
vendored
@@ -1,4 +1,4 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/favicon.ico"/><title>港校租房</title><script defer="defer" src="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/js/chunk-vendors.65a8ec5d.js"></script><script defer="defer" src="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/js/app.ecc66856.js"></script><link href="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/css/chunk-vendors.7885d77e.css" rel="stylesheet"><link href="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/css/app.e162f837.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but zufang doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><script src="https://app.gter.net/bottom?tpl=header&menukey=fang"></script><div id="app"></div><div style="display: none;"><script>var _hmt = _hmt || []
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"/><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="icon" href="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/favicon.ico"/><title>港校租房</title><script defer="defer" src="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/js/chunk-vendors.65a8ec5d.js"></script><script defer="defer" src="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/js/app.b40db703.js"></script><link href="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/css/chunk-vendors.7885d77e.css" rel="stylesheet"><link href="https://ansnid.oss-cn-shenzhen.aliyuncs.com/fang/css/app.e162f837.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but zufang doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><script src="https://app.gter.net/bottom?tpl=header&menukey=fang"></script><div id="app"></div><div style="display: none;"><script>var _hmt = _hmt || []
|
||||
;(function () {
|
||||
var hm = document.createElement("script")
|
||||
hm.src = "//hm.baidu.com/hm.js?4bd66cbe45a640b607fe46c48f658746"
|
||||
|
||||
1
dist/js/app.b40db703.js
vendored
Normal file
1
dist/js/app.b40db703.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/js/app.ecc66856.js
vendored
1
dist/js/app.ecc66856.js
vendored
File diff suppressed because one or more lines are too long
1
dist/js/edit.af9b8f63.js
vendored
1
dist/js/edit.af9b8f63.js
vendored
File diff suppressed because one or more lines are too long
1
dist/js/edit.e600b5f5.js
vendored
Normal file
1
dist/js/edit.e600b5f5.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/js/housing.a7d1dd9f.js
vendored
Normal file
1
dist/js/housing.a7d1dd9f.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/js/housing.f8a513dd.js
vendored
1
dist/js/housing.f8a513dd.js
vendored
File diff suppressed because one or more lines are too long
@@ -78,9 +78,10 @@
|
||||
<div class="ai-hint">内容由AI生成,请审慎参考</div>
|
||||
</template>
|
||||
<div v-else-if="item?.payload?.type == 'text' && item?.payload?.message?.before">
|
||||
{{ item.payload.message.before }}<span class="text link" bind:tap="consultStateCut" v-if="item.payload.message.wechat">
|
||||
<wechat-btn>{{ item.payload.message.wechat }}</wechat-btn>
|
||||
</span>{{ item.payload.message.after }}
|
||||
{{ item.payload.message.before
|
||||
}}<span class="text link" bind:tap="consultStateCut" v-if="item.payload.message.wechat">
|
||||
<wechat-btn>{{ item.payload.message.wechat }}</wechat-btn> </span
|
||||
>{{ item.payload.message.after }}
|
||||
</div>
|
||||
<div v-else>{{ item?.payload?.message || item.message }}</div>
|
||||
</div>
|
||||
@@ -95,7 +96,6 @@ import Contacts from "./contacts.vue";
|
||||
import imageWatch from "@/components/detail/imageWatch.vue";
|
||||
import wechatBtn from "@/components/ai/wechat-btn.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
@@ -123,8 +123,7 @@ const openImageShow = (list, index) => {
|
||||
|
||||
const cloaseImageShow = () => {
|
||||
imageShow.value = false;
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -369,6 +368,10 @@ const cloaseImageShow = () => {
|
||||
color: #aaaaaa;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background-color: rgba(246, 246, 246, 1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.link {
|
||||
|
||||
189
src/views/ai.vue
189
src/views/ai.vue
@@ -10,7 +10,7 @@
|
||||
<div class="content flexflex" ref="contentRef">
|
||||
<template v-if="isLogo">
|
||||
<img class="logo" src="../assets/img/publicImage/round-icon.gif" />
|
||||
<div class="hint">Hi,我是你的租房小助手圆同学。初次见面很开心!目前我还属于初代产品,只能提供房源推荐服务,暂时干不了其他事。来吧,告诉我你想找什么样的房子…</div>
|
||||
<div class="hint">{{ logoText }}</div>
|
||||
</template>
|
||||
|
||||
<template v-if="historyList.length > 0">
|
||||
@@ -26,18 +26,15 @@
|
||||
|
||||
<AiItem v-for="(item, index) in list" :key="item.id" :item="item"></AiItem>
|
||||
|
||||
<!-- fixed -->
|
||||
<div class="input-box" :class="{ fixed: !isLogo }">
|
||||
<div class="input">
|
||||
<div class="input-box flexflex" :class="{ fixed: !isLogo }" ref="inputBoxRef">
|
||||
<div class="restart flexcenter" @click="restartAffirm">
|
||||
<img class="icon" src="@/assets/img/publicImage/reopen-icon.png" />
|
||||
<div>新开会话</div>
|
||||
</div>
|
||||
<div class="input flex1" @click="inputRef.focus()">
|
||||
<textarea class="input-text" placeholder="请输入" @input="handleInput" v-model="input" @keyup.enter.prevent="send()" ref="inputRef" maxlength="100"></textarea>
|
||||
<div class="input-bottom flexacenter">
|
||||
<div class="restart flexcenter" @click="restartAffirm">
|
||||
<img class="icon" src="@/assets/img/publicImage/reopen-icon.png" />
|
||||
<div class="text">新开会话</div>
|
||||
</div>
|
||||
<div class="send flexcenter" :class="{ pitch: input.length > 0 }" @click="send()">
|
||||
<img class="icon" src="../assets/img/publicImage/arrows-deep-white.svg" />
|
||||
</div>
|
||||
<div class="send flexcenter" :class="{ pitch: input.length > 0 }" @click.stop="send()">
|
||||
<img class="icon" src="../assets/img/publicImage/arrows-deep-white.svg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,6 +65,19 @@ const handleScrollToTop = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化input-box高度监听
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
const inputBoxHeight = entries[0].contentRect.height;
|
||||
console.log("entries", entries[0].contentRect);
|
||||
|
||||
if (contentRef.value) {
|
||||
contentRef.value.style.paddingBottom = `${inputBoxHeight + 32 + 40}px`;
|
||||
}
|
||||
});
|
||||
if (inputBoxRef.value) {
|
||||
resizeObserver.observe(inputBoxRef.value);
|
||||
contentRef.value.style.paddingBottom = `${inputBoxRef.value.offsetHeight + 32 + 40}px`;
|
||||
}
|
||||
init();
|
||||
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
@@ -76,6 +86,10 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 移除高度监听
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
// 移除全局滚动事件监听
|
||||
window.removeEventListener("scroll", handleScrollToTop);
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
@@ -94,19 +108,28 @@ let historyObj = {
|
||||
},
|
||||
};
|
||||
|
||||
let isLogin = false; // 登录状态
|
||||
let logoText = ref(""); // logo 文字
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
api.alInit().then((res) => {
|
||||
if (res.code == 401) store.state.showloginmodal = true;
|
||||
if (res.code == 401) {
|
||||
store.state.showloginmodal = true;
|
||||
isLogin = false;
|
||||
}
|
||||
if (res.code != 200) return;
|
||||
|
||||
isLogin = true;
|
||||
const data = res.data;
|
||||
|
||||
let history = data.history || [];
|
||||
|
||||
session_id = data.session_id;
|
||||
initState = true;
|
||||
|
||||
const target = history[0] || {};
|
||||
logoText.value = target["payload"]?.message || "";
|
||||
historyObj["id"] = randomString();
|
||||
historyObj["payload"] = target["payload"];
|
||||
historyObj["timestamp"] = target["timestamp"];
|
||||
historyObj["timeText"] = formatTimestamp(getTimestamp(target["timestamp"])) || "";
|
||||
@@ -116,6 +139,8 @@ const init = () => {
|
||||
};
|
||||
|
||||
const contentRef = ref(null);
|
||||
const inputBoxRef = ref(null); // input-box的ref引用
|
||||
let resizeObserver = null; // ResizeObserver实例
|
||||
|
||||
// 滚动到最后一条
|
||||
const scrollEnd = async () => {
|
||||
@@ -151,6 +176,10 @@ let isLogo = ref(true);
|
||||
|
||||
// 获取历史记录
|
||||
const getHistory = () => {
|
||||
if (!isLogin) {
|
||||
ElMessage.error("请先登录!");
|
||||
return;
|
||||
}
|
||||
if (page == 0 || loading) return;
|
||||
loading = true;
|
||||
|
||||
@@ -164,8 +193,8 @@ const getHistory = () => {
|
||||
.then((res) => {
|
||||
if (res.code != 200) return;
|
||||
let data = res.data || {};
|
||||
// data.count = 0
|
||||
// data.history = []
|
||||
// data.count = 0;
|
||||
// data.history = [];
|
||||
let history = data.history || [];
|
||||
history.reverse();
|
||||
|
||||
@@ -175,6 +204,8 @@ const getHistory = () => {
|
||||
|
||||
if (history.length > 0 && page == 1) {
|
||||
let obj = historyObj || {};
|
||||
obj["id"] = randomString();
|
||||
|
||||
list.value.push(obj);
|
||||
}
|
||||
|
||||
@@ -234,11 +265,21 @@ let generateState = false;
|
||||
let input = ref("");
|
||||
|
||||
const send = () => {
|
||||
if (!isLogin) {
|
||||
ElMessage.error("请先登录!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (generateState) {
|
||||
ElMessage.error("生成中,请等待!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!session_id) {
|
||||
ElMessage.error("出错了,请刷新页面重试");
|
||||
return;
|
||||
}
|
||||
|
||||
const inputValue = input.value;
|
||||
|
||||
if (!inputValue) {
|
||||
@@ -390,7 +431,7 @@ const chatError = (listValue, inputValue, hint) => {
|
||||
|
||||
// 恢复会话
|
||||
const handleResume = () => {
|
||||
if (!session_id || !initState) return;
|
||||
if (!session_id || !initState || !isLogin) return;
|
||||
api.alResume({ session_id }).then((res) => {
|
||||
// console.log("res", res);
|
||||
});
|
||||
@@ -398,6 +439,11 @@ const handleResume = () => {
|
||||
|
||||
// 结束对话 上报历史
|
||||
const endChat = () => {
|
||||
if (!isLogin) {
|
||||
ElMessage.error("请先登录!");
|
||||
return;
|
||||
}
|
||||
|
||||
let listValue = [...list.value];
|
||||
listValue = listValue.filter((item) => {
|
||||
return !("isHistory" in item) && item.role !== "contacts";
|
||||
@@ -415,6 +461,10 @@ let inputRef = ref(null);
|
||||
|
||||
// 自动输入框增高
|
||||
const autoResize = (e) => {
|
||||
if (!input.value) {
|
||||
inputRef.value.style.height = "41px"; // 重置高度
|
||||
return;
|
||||
}
|
||||
inputRef.value.style.height = "auto"; // 重置高度
|
||||
inputRef.value.style.height = `${inputRef.value.scrollHeight + 1}px`; // 设置为内容高度
|
||||
};
|
||||
@@ -521,7 +571,7 @@ const handleVisibilityChange = () => {
|
||||
|
||||
// 根据状态执行不同操作
|
||||
if (document.visibilityState === "visible") handleResume();
|
||||
else endChat(); // 可执行暂停操作:如暂停视频、保存临时数据等
|
||||
else endChat(); // 可执行暂停操作:如暂停视频、保存临时数据等
|
||||
};
|
||||
|
||||
const randomString = () => {
|
||||
@@ -554,6 +604,10 @@ const restartAffirm = () => {
|
||||
|
||||
// 新开会话
|
||||
const restart = () => {
|
||||
if (!isLogin) {
|
||||
ElMessage.error("请先登录!");
|
||||
return;
|
||||
}
|
||||
api.alNew().then((res) => {
|
||||
const data = res.data;
|
||||
let history = data.history || [];
|
||||
@@ -569,7 +623,7 @@ const restart = () => {
|
||||
|
||||
let obj = historyObj || {};
|
||||
const target = history[0] || {};
|
||||
|
||||
obj["id"] = randomString();
|
||||
obj["payload"] = target["payload"];
|
||||
obj["timestamp"] = target["timestamp"];
|
||||
obj["timeText"] = formatTimestamp(getTimestamp(target["timestamp"])) || "";
|
||||
@@ -648,7 +702,7 @@ const restart = () => {
|
||||
font-size: 16px;
|
||||
line-height: 26px;
|
||||
color: #333333;
|
||||
width: 430px;
|
||||
max-width: 430px;
|
||||
margin: 0 auto 86px;
|
||||
}
|
||||
|
||||
@@ -659,8 +713,37 @@ const restart = () => {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
padding-bottom: 38px;
|
||||
padding-bottom: 32px;
|
||||
background: #fff;
|
||||
.restart {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.input {
|
||||
// height: 90px;
|
||||
|
||||
.input-text {
|
||||
height: 41px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.restart {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
width: 90px;
|
||||
// height: 90px;
|
||||
background-color: rgba(246, 246, 246, 1);
|
||||
border-radius: 20px;
|
||||
color: #333333;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
@@ -677,46 +760,50 @@ const restart = () => {
|
||||
resize: none;
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
height: 51px;
|
||||
// height: 51px;
|
||||
max-height: 230px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.input-bottom {
|
||||
margin-left: 10px;
|
||||
// .input-bottom {
|
||||
// margin-left: 10px;
|
||||
// margin-right: 20px;
|
||||
|
||||
// margin-bottom: 15px;
|
||||
// .restart {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// cursor: pointer;
|
||||
// font-size: 10px;
|
||||
|
||||
// .icon {
|
||||
// width: 20px;
|
||||
// height: 20px;
|
||||
// margin-bottom: 3px;
|
||||
// }
|
||||
// }
|
||||
.send {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #d7d7d7;
|
||||
margin-left: auto;
|
||||
margin-right: 20px;
|
||||
|
||||
cursor: not-allowed;
|
||||
margin-bottom: 15px;
|
||||
.restart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.pitch {
|
||||
background-color: #7fddff;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
.send {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #d7d7d7;
|
||||
margin-left: auto;
|
||||
cursor: not-allowed;
|
||||
&.pitch {
|
||||
background-color: #7fddff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user