20260325稳定版

This commit is contained in:
2026-03-25 15:59:24 +08:00
commit 0decd78604
470 changed files with 53100 additions and 0 deletions
+265
View File
@@ -0,0 +1,265 @@
<template>
<view class="min-height bg-main-light">
<HeaderItem title="AI智慧助手"/>
<view style="line-height: 35rpx"
class="flex flex-col justify-start items-start text-25 p-30">
<view class="text-25 text-gray text-center my-15 w-100p">该内容由人工智能生成</view>
<view v-for="(item, index) in list" :key="index" class="mb-25 w-100p">
<view v-if="!item.isMine" class="flex justify-start items-start">
<u-icon name="/static/robot.png" size="60rpx"/>
<view class="shadow bg-white ml-15" style="border-radius: 0 15rpx 15rpx 15rpx; max-width: 80%">
<view v-if="item.loading" class="p-15">
<u-loading-icon mode="semicircle" :color="MAIN()"></u-loading-icon>
</view>
</view>
</view>
<view v-else class="flex justify-end items-start w-100p">
<view class="shadow p-15 text-white"
style="border-radius: 15rpx 0 15rpx 15rpx; max-width: 80%; background-color: #006DFF">{{
item.content
}}
</view>
</view>
<!-- <view v-if="index === 0" class="mt-25">-->
<!-- <view class="font-bold text-28 mb-25 text-center">猜你想问</view>-->
<!-- <view class="font-bold text-28 mb-25 text-center" @click="send(quest.dictDataCode)"-->
<!-- style="color: #006DFF"-->
<!-- v-for="(quest, questIndex) in questionList" :key="questIndex">-->
<!-- {{ quest.dictDataCode }}-->
<!-- </view>-->
<!-- </view>-->
</view>
<u-gap height="140rpx"></u-gap>
</view>
<view class="stop" v-if="answering" @click="stopAI">
<u-icon name="/static/stop.png" width="80rpx" height="80rpx"/>
</view>
<view class="footer">
<view class="p-20 bg-white shadow-top flex justify-center items-center">
<view class="flex-1 mr-15">
<u-textarea maxlength="-1" auto-height v-model="content" border="none" placeholder="点此开始提问"/>
</view>
<view class="mr-25">
<u-icon name="/static/send.png" size="60rpx" @click="send()"/>
</view>
</view>
</view>
<Login ref="Login" @done="getUserData"/>
</view>
</template>
<script>
import HeaderItem from "@/components/HeaderItem.vue";
import {WS_API} from "@/config";
import {sendChatReq, stopChatReq} from "@/api/chat";
import {getUserInfo} from "@/util/user";
import {MAIN} from "@/config/color";
import {dictDataReq} from "@/api/common";
import Login from "@/components/Login.vue";
import {userInfoReq} from "@/api/user";
export default {
name: "index",
components: {Login, HeaderItem},
data() {
return {
list: [],
questionList: [],
content: '',
taskId: null,
roomList: null,
userData: null,
answering: false,
isLogin: false,
billList: [],
socket: null,
timer: null
}
},
methods: {
MAIN() {
return MAIN
},
async getUserData() {
const {data} = await userInfoReq()
this.userData = data
this.isLogin = true
this.ws()
},
async getWelcomeWord() {
const {data} = await dictDataReq({dictId: '1435'})
this.list.push({
content: data[0].dictDataCode,
isMine: false,
loading: false,
done: true,
type: 'text'
})
console.log(this.list)
},
async send(content = null) {
if (!this.isLogin) return this.$refs.Login.open()
const contentItem = content || this.content
// console.log(contentItem, this.content)
if (!contentItem.trim()) return this.$toast('请输入提问内容')
if (!this.list[this.list.length - 1].done) return this.$toast('请等待AI回答完毕再继续提问')
this.list.push({
content: contentItem,
isMine: true,
loading: false,
done: true,
type: 'text'
})
this.scrollToBottom()
this.answering = true
this.list.push({
content: '',
isMine: false,
loading: true,
done: false,
})
this.taskId = null
sendChatReq({
query: contentItem,
user: this.userData.userId,
}).then(() => {
this.list[this.list.length - 1].done = true
this.answering = false
})
this.content = ''
},
scrollToBottom() {
setTimeout(() => {
this.$nextTick(() => {
uni.pageScrollTo({
scrollTop: 99999,
duration: 0
})
})
}, 100)
},
async stopAI() {
await stopChatReq({
taskId: this.taskId
})
this.answering = false
this.list.splice(this.list.length - 1, 1)
// uni.closeSocket()
},
ws() {
const _this = this
this.socket = uni.connectSocket({
url: `${WS_API}/${this.userData.userId}`,
success: () => {
_this.ping()
},
})
console.log(this.socket)
uni.onSocketMessage(async res => {
// console.log(res.data)
if (res.data.includes('连接成功')) return
const aiData = JSON.parse(res.data)
this.list[this.list.length - 1].loading = false
if (aiData.answer !== '__END__' && !this.list[this.list.length - 1].isMine) {
this.list[this.list.length - 1].type = 'text'
this.list[this.list.length - 1].content += aiData.answer
} else {
this.list[this.list.length - 1].done = true
const lastContent = this.list[this.list.length - 1].content
console.log('check url', lastContent, lastContent.includes(`<openUrl url="/pages/user/property-bill">`))
if (lastContent.includes(`pages/user/property-bill`)) {
await this.getRoomList()
this.list.push({
content: '',
isMine: false,
loading: false,
done: true,
type: 'bill'
})
} else if (lastContent.includes(`servicePages/pages/suggest`) || lastContent.includes(`servicePages/pages/suuggest`)) {
this.list.push({
content: '点击跳转',
isMine: false,
loading: false,
done: true,
type: 'link',
url: '/servicePages/pages/suggest'
})
} else if (lastContent.includes(`servicePages/pages/survey`)) {
this.list.push({
content: '点击跳转',
isMine: false,
loading: false,
done: true,
type: 'link',
url: '/servicePages/pages/survey'
})
} else if (lastContent.includes(`servicePages/pages/fix`)) {
this.list.push({
content: '点击跳转',
isMine: false,
loading: false,
done: true,
type: 'link',
url: '/servicePages/pages/fix'
})
}
}
this.taskId = aiData.taskId
this.scrollToBottom()
// console.log(this.list)
})
},
ping() {
this.timer = setInterval(() => {
if (this.socket) {
this.socket.send({
data: 'ping'
})
}
}, 10000)
}
},
onLoad() {
this.getWelcomeWord()
},
onShow() {
if (getUserInfo().token) this.getUserData()
},
onUnload() {
if (this.timer) clearInterval(this.timer)
uni.closeSocket()
this.stopAI()
},
onHide() {
if (this.timer) clearInterval(this.timer)
uni.closeSocket()
this.stopAI()
},
}
</script>
<style scoped>
page {
background-color: #E0F2FE;
}
.bg {
background: url("https://sifa-api.wsdns.cn/api/file/20250420/bg.jpg") no-repeat 100% 100%;
}
.stop {
position: fixed;
right: 10rpx;
bottom: 160rpx;
width: 100rpx;
height: 100rpx;
border-radius: 100rpx;
background: rgba(255, 255, 255, 0.4);
box-shadow: 1px 1px 5px #3c9cff;
display: flex;
align-items: center;
justify-content: center;
}
</style>