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>
|