339 lines
9.1 KiB
Vue
339 lines
9.1 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="slide-wrap">
|
|||
|
|
<div class="header">
|
|||
|
|
<img class="header-image" :src="getImage()" />
|
|||
|
|
<div class="header-name">{{ appData.name || 'AI助手' }}</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="createArea">
|
|||
|
|
<a-button type="dashed" @click="handleCreate">新建聊天</a-button>
|
|||
|
|
</div>
|
|||
|
|
<div class="historyArea">
|
|||
|
|
<ul>
|
|||
|
|
<li
|
|||
|
|
v-for="(item, index) in dataSource.history"
|
|||
|
|
:key="item.id"
|
|||
|
|
class="list"
|
|||
|
|
:class="[item.id == dataSource.active ? 'active' : 'normal', dataSource.history.length == 1 ? 'last' : '']"
|
|||
|
|
@click="handleToggleChat(item, index)"
|
|||
|
|
>
|
|||
|
|
<i class="icon message">
|
|||
|
|
<svg
|
|||
|
|
xmlns="http://www.w3.org/2000/svg"
|
|||
|
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|||
|
|
aria-hidden="true"
|
|||
|
|
role="img"
|
|||
|
|
class="iconify iconify--ri"
|
|||
|
|
width="1em"
|
|||
|
|
height="1em"
|
|||
|
|
viewBox="0 0 24 24"
|
|||
|
|
>
|
|||
|
|
<path
|
|||
|
|
fill="currentColor"
|
|||
|
|
d="M2 8.994A5.99 5.99 0 0 1 8 3h8c3.313 0 6 2.695 6 5.994V21H8c-3.313 0-6-2.695-6-5.994zM20 19V8.994A4.004 4.004 0 0 0 16 5H8a3.99 3.99 0 0 0-4 3.994v6.012A4.004 4.004 0 0 0 8 19zm-6-8h2v2h-2zm-6 0h2v2H8z"
|
|||
|
|
></path>
|
|||
|
|
</svg>
|
|||
|
|
</i>
|
|||
|
|
<a-input
|
|||
|
|
class="title"
|
|||
|
|
ref="inputRef"
|
|||
|
|
v-if="item.isEdit"
|
|||
|
|
:defaultValue="item.title"
|
|||
|
|
placeholder="请输入标题"
|
|||
|
|
@change="handleInputChange"
|
|||
|
|
@keyup.enter="inputBlur(item)"
|
|||
|
|
/>
|
|||
|
|
<span class="title" v-else>{{ item.title }}</span>
|
|||
|
|
<span class="icon edit" @click.stop="handleEdit(item)" v-if="!item.isEdit && !item.disabled">
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" role="img" class="iconify iconify--ri" width="1em" height="1em" viewBox="0 0 24 24">
|
|||
|
|
<path
|
|||
|
|
fill="currentColor"
|
|||
|
|
d="M6.414 15.89L16.556 5.748l-1.414-1.414L5 14.476v1.414zm.829 2H3v-4.243L14.435 2.212a1 1 0 0 1 1.414 0l2.829 2.829a1 1 0 0 1 0 1.414zM3 19.89h18v2H3z"
|
|||
|
|
></path>
|
|||
|
|
</svg>
|
|||
|
|
</span>
|
|||
|
|
<span class="icon del">
|
|||
|
|
<a-popconfirm
|
|||
|
|
:overlayStyle="{ 'z-index': 9999 }"
|
|||
|
|
title="确定删除此记录?"
|
|||
|
|
placement="bottom"
|
|||
|
|
ok-text="确定"
|
|||
|
|
cancel-text="取消"
|
|||
|
|
@confirm.stop="handleDel(item)"
|
|||
|
|
>
|
|||
|
|
<svg xmlns="http://www.w3.org/2000/svg" role="img" class="iconify iconify--ri" width="1em" height="1em" viewBox="0 0 24 24">
|
|||
|
|
<path
|
|||
|
|
fill="currentColor"
|
|||
|
|
d="M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1zm1 2H6v12h12zm-9 3h2v6H9zm4 0h2v6h-2zM9 4v2h6V4z"
|
|||
|
|
></path>
|
|||
|
|
</svg>
|
|||
|
|
</a-popconfirm>
|
|||
|
|
</span>
|
|||
|
|
</li>
|
|||
|
|
</ul>
|
|||
|
|
</div>
|
|||
|
|
<div class="left-footer" v-if="source!='chatJs'">
|
|||
|
|
AI客服由
|
|||
|
|
<a style="color: #4183c4;margin-left: 2px;margin-right: 2px" href="https://www.qiaoqiaoyun.com/aiCustomerService" target="_blank">
|
|||
|
|
JEECG AI
|
|||
|
|
</a>
|
|||
|
|
提供
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { ref, watch } from 'vue';
|
|||
|
|
import { useRouter } from 'vue-router';
|
|||
|
|
import { defHttp } from '@/utils/http/axios';
|
|||
|
|
import { getFileAccessHttpUrl } from '@/utils/common/compUtils';
|
|||
|
|
import defaultImg from '../img/ailogo.png';
|
|||
|
|
const props = defineProps(['dataSource', 'appData','source']);
|
|||
|
|
const emit = defineEmits(['save', 'click', 'reloadRight', 'prologue']);
|
|||
|
|
const inputRef = ref(null);
|
|||
|
|
const router = useRouter();
|
|||
|
|
let inputValue = '';
|
|||
|
|
//新建聊天
|
|||
|
|
const handleCreate = () => {
|
|||
|
|
const uuid = getUuid();
|
|||
|
|
props.dataSource.history.unshift({ title: '新建聊天', id: uuid, isEdit: false, disabled: true });
|
|||
|
|
// 新建第一个(需要高亮选中)
|
|||
|
|
props.dataSource.active = uuid;
|
|||
|
|
emit('click', "新建聊天", 0);
|
|||
|
|
};
|
|||
|
|
// 切换聊天
|
|||
|
|
const handleToggleChat = (item, index) => {
|
|||
|
|
if (item.id != props.dataSource.active) {
|
|||
|
|
props.dataSource.active = item.id;
|
|||
|
|
emit('click', item.title, index);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const handleInputChange = (e) => {
|
|||
|
|
inputValue = e.target.value.trim();
|
|||
|
|
};
|
|||
|
|
// 失去焦点
|
|||
|
|
const inputBlur = (item) => {
|
|||
|
|
item.isEdit = false;
|
|||
|
|
item.title = inputValue;
|
|||
|
|
defHttp
|
|||
|
|
.put(
|
|||
|
|
{
|
|||
|
|
url: '/airag/chat/conversation/update/title',
|
|||
|
|
params: { id: item.id, title: inputValue },
|
|||
|
|
},
|
|||
|
|
{ joinParamsToUrl: true }
|
|||
|
|
)
|
|||
|
|
.then((res) => {});
|
|||
|
|
};
|
|||
|
|
// 编辑
|
|||
|
|
const handleEdit = (item) => {
|
|||
|
|
console.log(item);
|
|||
|
|
item.isEdit = true;
|
|||
|
|
inputValue = item.title;
|
|||
|
|
};
|
|||
|
|
// 保存
|
|||
|
|
const handleSave = (item) => {
|
|||
|
|
item.isEdit = false;
|
|||
|
|
item.title = inputValue;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除
|
|||
|
|
* @param data
|
|||
|
|
*/
|
|||
|
|
function handleDel(data) {
|
|||
|
|
const findIndex = props.dataSource.history.findIndex((item) => item.id == data.id);
|
|||
|
|
if (findIndex != -1) {
|
|||
|
|
props.dataSource.history.splice(findIndex, 1);
|
|||
|
|
// 删除的是当前active的,active往前移,前面没了往后移。
|
|||
|
|
if (props.dataSource.history.length) {
|
|||
|
|
if (props.dataSource.active == data.id) {
|
|||
|
|
if (findIndex > 0) {
|
|||
|
|
props.dataSource.active = props.dataSource.history[findIndex - 1].id;
|
|||
|
|
} else {
|
|||
|
|
props.dataSource.active = props.dataSource.history[0].id;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
emit('click', props.dataSource.history[0].title, findIndex);
|
|||
|
|
} else {
|
|||
|
|
// 删没了(删除了最后一个)
|
|||
|
|
handleCreate();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
//update-begin---author:wangshuai---date:2025-03-12---for:【QQYUN-11560】新建聊天内容为空,无法删除---
|
|||
|
|
if(data.disabled){
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
//update-end---author:wangshuai---date:2025-03-12---for:【QQYUN-11560】新建聊天内容为空,无法删除---
|
|||
|
|
defHttp.delete({
|
|||
|
|
url: '/airag/chat/conversation/' + data.id,
|
|||
|
|
},{ isTransformResponse: false });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取图片
|
|||
|
|
*/
|
|||
|
|
function getImage() {
|
|||
|
|
return props.appData.icon ? getFileAccessHttpUrl(props.appData.icon) : defaultImg;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => inputRef.value,
|
|||
|
|
(newVal: any) => {
|
|||
|
|
if (newVal?.length) {
|
|||
|
|
newVal[0].focus();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{ deep: true }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 指定长度和基数
|
|||
|
|
const getUuid = (len = 10, radix = 16) => {
|
|||
|
|
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
|
|||
|
|
var uuid: any = [],
|
|||
|
|
i;
|
|||
|
|
radix = radix || chars.length;
|
|||
|
|
|
|||
|
|
if (len) {
|
|||
|
|
for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
|
|||
|
|
} else {
|
|||
|
|
var r;
|
|||
|
|
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
|||
|
|
uuid[14] = '4';
|
|||
|
|
for (i = 0; i < 36; i++) {
|
|||
|
|
if (!uuid[i]) {
|
|||
|
|
r = 0 | (Math.random() * 16);
|
|||
|
|
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return uuid.join('');
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped lang="less">
|
|||
|
|
.slide-wrap {
|
|||
|
|
border-right: 1px solid #e5e7eb;
|
|||
|
|
height: 100%;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
.historyArea {
|
|||
|
|
padding: 20px;
|
|||
|
|
padding-top: 0;
|
|||
|
|
flex: 1;
|
|||
|
|
min-height: 0;
|
|||
|
|
overflow: auto;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
&::-webkit-scrollbar {
|
|||
|
|
width: 8px;
|
|||
|
|
height: 8px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.historyArea ul li:hover {
|
|||
|
|
.del {
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.createArea {
|
|||
|
|
padding: 20px;
|
|||
|
|
padding-bottom: 0;
|
|||
|
|
}
|
|||
|
|
.ant-btn {
|
|||
|
|
width: 100%;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
ul {
|
|||
|
|
margin-bottom: 0;
|
|||
|
|
}
|
|||
|
|
.list {
|
|||
|
|
width: 100%;
|
|||
|
|
padding-top: 0.75rem;
|
|||
|
|
padding-bottom: 0.75rem;
|
|||
|
|
padding-left: 0.75rem;
|
|||
|
|
padding-right: 0.75rem;
|
|||
|
|
border-radius: 0.375rem;
|
|||
|
|
border-width: 1px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
color: #333;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-start;
|
|||
|
|
align-items: center;
|
|||
|
|
&:hover,
|
|||
|
|
&.active {
|
|||
|
|
border-color: @primary-color;
|
|||
|
|
color: @primary-color;
|
|||
|
|
}
|
|||
|
|
.edit,
|
|||
|
|
.save,
|
|||
|
|
.del {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
&.active {
|
|||
|
|
.edit,
|
|||
|
|
.save,
|
|||
|
|
.del {
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
&.last {
|
|||
|
|
.del {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.message {
|
|||
|
|
margin-right: 8px;
|
|||
|
|
}
|
|||
|
|
.edit {
|
|||
|
|
margin-right: 8px;
|
|||
|
|
}
|
|||
|
|
.title {
|
|||
|
|
flex: 1;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
font-size: 14px;
|
|||
|
|
position: relative;
|
|||
|
|
top: 2px;
|
|||
|
|
&.ant-input {
|
|||
|
|
margin-right: 20px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
svg {
|
|||
|
|
vertical-align: middle;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
:deep(.ant-popover) {
|
|||
|
|
z-index: 9999 !important;
|
|||
|
|
}
|
|||
|
|
:deep(.ant-popconfirm) {
|
|||
|
|
z-index: 9999 !important;
|
|||
|
|
}
|
|||
|
|
.header {
|
|||
|
|
display: flex;
|
|||
|
|
padding: 20px 4px 0 4px;
|
|||
|
|
margin-left: 16px;
|
|||
|
|
.header-image {
|
|||
|
|
height: 35px;
|
|||
|
|
width: 35px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
margin-right: 10px;
|
|||
|
|
}
|
|||
|
|
.header-name {
|
|||
|
|
align-self: center;
|
|||
|
|
color: #1d2939;
|
|||
|
|
font-weight: 600;
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.left-footer{
|
|||
|
|
display:flex;
|
|||
|
|
margin-right: 20px;
|
|||
|
|
font-size: 12px;
|
|||
|
|
position: absolute;
|
|||
|
|
bottom: 4px;
|
|||
|
|
left: 50px;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
</style>
|