feat: 聊天室组件抽离

main
许宏杰 4 weeks ago
parent ef86b9c093
commit d3bb5df35b

5
package-lock.json generated

@ -7981,6 +7981,11 @@
"vscode-vue-languageservice": "0.28.10"
}
},
"vue3-draggable-resizable": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/vue3-draggable-resizable/-/vue3-draggable-resizable-1.6.5.tgz",
"integrity": "sha512-31142E31fGNnq3HKqvmFLSsqIbhck7TyGuQWhUKrDw6DOcGAuRx4ddRjaxvT6fe7dgeKH53qAh+i0ZlWtPLl2g=="
},
"vue3-lazyload": {
"version": "0.2.5-beta",
"resolved": "https://registry.npmjs.org/vue3-lazyload/-/vue3-lazyload-0.2.5-beta.tgz",

@ -48,6 +48,7 @@
"vue-demi": "^0.13.1",
"vue-i18n": "9.2.2",
"vue-router": "4.0.12",
"vue3-draggable-resizable": "^1.6.5",
"vue3-lazyload": "^0.2.5-beta",
"vue3-sketch-ruler": "^1.3.3",
"vuedraggable": "^4.1.0"

@ -40,6 +40,14 @@ export const getIssue = async(data: object)=>{
httpErrorHandle()
}
}
export const delIssue = async(id:any)=>{
try{
const res = await http(RequestHttpEnum.DELETE)(`${ModuleTypeEnum.ISSUE}/${id}`, )
return res
}catch{
httpErrorHandle()
}
}
// 新增聊天室历史记录

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

@ -1,4 +1,3 @@
@import url('./font.css');
//
@ -16,6 +15,19 @@
background-color: #18181c;
}
}
.custom-card{
background: #272A31 !important;
}
.custom-card .n-card-header {
font-size: 16px;
color: #ffffff;
font-weight: 500;
padding: 14px 25px;
font-family: 'AlibabaPuHuiTi-Medium';
border-bottom: 1px solid #000000 ;
}
.chat-container {
box-sizing: border-box;
@ -79,13 +91,13 @@
font-size: 12px;
color: #c3cad9;
gap: 10px;
.upload-text{
.upload-text {
cursor: pointer;
display: flex;
align-items: center;
}
.upload-text:hover{
color: #3F7BF8;
.upload-text:hover {
color: #3f7bf8;
}
}
}
@ -128,37 +140,37 @@
}
}
.echarts-box {
height: 220px;
width: 400px;
}
.echatrs-theme {
margin-top: 10px;
display: flex;
align-items: center;
gap: 6px;
.theme-title {
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
height: 220px;
width: 400px;
}
.theme-list {
.echatrs-theme {
margin-top: 10px;
display: flex;
align-items: center;
gap: 10px;
.theme-item {
cursor: pointer;
background: #31363e;
border-radius: 2px;
gap: 6px;
.theme-title {
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
box-sizing: border-box;
padding: 2px 10px;
}
.activeTheme{
color: #3F7BF8;
background: rgba(63,123,248,0.1);
.theme-list {
display: flex;
align-items: center;
gap: 10px;
.theme-item {
cursor: pointer;
background: #31363e;
border-radius: 2px;
font-size: 14px;
color: #cfd5e5;
font-weight: 400;
box-sizing: border-box;
padding: 2px 10px;
}
.activeTheme {
color: #3f7bf8;
background: rgba(63, 123, 248, 0.1);
}
}
}
}
}

@ -49,7 +49,7 @@ import { addIssue } from '@/api/path'
import { useMessage } from 'naive-ui'
import { ref, reactive } from 'vue'
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { httpErrorHandle } from '@/utils'
const props = defineProps({
list: {
type: Array,
@ -73,8 +73,14 @@ const handlePositiveClick = async () => {
form.content = props.issueText
form.userId = systemStore.userInfo.userId || 1
try {
await addIssue(form)
const res = await addIssue(form)
if (res.code === 200) {
window['$message'].success(window['$t']('添加成功'))
currentIndex.value = null
return
}
httpErrorHandle()
} catch {
message.warning('保存出错!请联系管理员')
}

@ -0,0 +1,318 @@
<template>
<div>
<suspensionButton>
<img src="~@/assets/images/ai/ai-botton.png" alt="" class="ai-botton" @dblclick="openAi()" />
</suspensionButton>
<n-modal
v-model:show="showModal"
class="custom-card"
preset="card"
:block-scroll="false"
:style="bodyStyle"
title="AI助手"
size="huge"
:bordered="false"
:segmented="segmented"
>
<div class="ai-container">
<div class="chat-container">
<div class="chat-list" ref="scrollContainer">
<AiHint v-model="keyWord" @sendMessage="handleSend"></AiHint>
<div
v-for="(item, index) in messageList"
:key="index"
:class="['chat-item', item.from == 'user' ? 'user-message' : 'ai-message']"
>
<!-- 头像 -->
<img :src="item.from == 'user' ? userIcon : aiIcon" class="user-icon" alt="" />
<!-- 内容 -->
<div class="message-content">
<div class="message-time">{{ item.time }}</div>
<div class="message-text" :id="`box${index}`">
<!-- 纯文本 -->
<div class="text-ros">
<span v-show="item.type == 'text'">{{ item.text }}</span>
<AddIssue :issueText="item.text" :list="dictList" v-show="item.add"></AddIssue>
</div>
<!-- 图表 -->
<initEcharts :item="item" :currentIndex="index" @changeTheme="handlerTheme"></initEcharts>
<!-- 表格 -->
<AiTable :obj="item"></AiTable>
<!-- 思考 -->
<inThinking :item="item"></inThinking>
<!-- 免责 -->
<disclaimer :item="item" :currentIndex="index"></disclaimer>
</div>
</div>
</div>
</div>
<!-- 搜索 -->
<n-input
class="chat-input"
round
v-model:value="keyWord"
type="textarea"
placeholder="请输入需要统计的数据"
:autosize="{ minRows: 5 }"
>
<template #suffix>
<div class="chat-suffix">
<IssueQuey :list="dictList" v-model="keyWord" @sendMessage="handleSend"></IssueQuey>
<n-button :loading="loading" size="small" :class="['chat-send']" :bordered="false" @click="handleSend">
</n-button>
</div>
</template>
</n-input>
</div>
<div class="chat-history"></div>
</div>
</n-modal>
</div>
</template>
<script setup>
import { onMounted, ref, reactive, nextTick, watch } from 'vue'
import { AiHint, AiTable, AddIssue, IssueQuey, disclaimer, inThinking,initEcharts } from '@/views/AI/components'
import userIcon from '@/assets/images/ai/user-icon.png'
import aiIcon from '@/assets/images/ai/ai-icon.png'
import { LineChart, BarChart, PieChart } from '@/views/AI/chat/class/index'
import suspensionButton from '../../components/suspensionButton/index.vue'
import { historyMessageRoomById, getDict } from '@/api/path'
import moment from 'moment'
import { getAiMsg } from '@/api/ai.js'
//
let loading = ref(false)
//
const scrollContainer = ref(null)
let stopWatch = null
let isWatching = ref(false)
let showModal = ref(true)
let messageList = ref([])
//
let lastIndex = ref(0)
let keyWord = ref('')
let dictList = ref([])
const bodyStyle = {
width: '55%'
}
onMounted(() => {
// getDictListist()
})
/**
* 字典
*/
const getDictListist = async () => {
const res = await getDict()
dictList.value = res.data.records
}
/**
* 问答
*/
const handleSend = async () => {
if (!keyWord.value.trim() && loading.value) {
return
}
loading.value = true
//
setMessageItem({
from: 'user',
text: keyWord.value,
type: 'text',
time: moment().format('YYYY/MM/DD HH:mm:ss'),
add: true
})
//AI
setMessageItem(
{
from: 'ai',
text: '',
time: moment().format('YYYY/MM/DD HH:mm:ss'),
loading: true, //ai
chartInstanceItem: null,
chartInstanceActiveIndex: 0,
type: 'text',
xData: [],
yData: []
},
false
)
try {
const res = await getAiMsg({ prompt: keyWord.value })
//
updataMessageItem({
from: 'ai',
text: res.data,
time: moment().format('YYYY/MM/DD HH:mm:ss'),
loading: false, //ai
chartInstanceItem: null,
chartInstanceActiveIndex: 0,
type: res.type,
xData: res.xData,
data: res.data,
title: res.title,
unit: res.unit
})
nextTick(() => {
const itemData = messageList.value[lastIndex.value]
//
if (res.type === 'pie') {
itemData.chartInstanceItem = new PieChart(`${res.type}${lastIndex.value}`, {
title: res.title,
unit: res.unit,
data: res.data
})
} else if (res.type === 'bar') {
itemData.chartInstanceItem = new BarChart(`${res.type}${lastIndex.value}`, {
title: res.title,
unit: res.unit,
xData: res.xData,
data: res.data
})
} else if (res.type === 'line') {
itemData.chartInstanceItem = new LineChart(`${res.type}${lastIndex.value}`, {
title: res.title,
unit: res.unit,
xData: res.xData,
data: res.data
})
}
})
} catch {
updataMessageItem({
from: 'ai',
type: 'text',
text: '服务器繁忙,请稍后再试。',
time: moment().format('YYYY/MM/DD HH:mm:ss'),
loading: false
})
}
}
/**
* 存储聊天室数据
* @param obj
*/
const setMessageItem = (obj, set = true) => {
messageList.value.push(obj)
lastIndex.value = messageList.value.length - 1
}
/**
* ai回答后更新对应数据
* @param data
*/
const updataMessageItem = data => {
messageList.value[lastIndex.value] = data
keyWord.value = ''
loading.value = false
}
/**
* 图表切换主题
* @param index
*/
const handlerTheme = (item, index) => {
if (isWatching.value) {
pauseWatching()
isWatching.value = false
console.log('暂停')
}
item.chartInstanceActiveIndex = index
item.chartInstanceItem.changeColorStyle(index)
setTimeout(() => {
startWatching()
console.log('监听')
}, 5000)
}
const pauseWatching = () => {
if (stopWatch) {
stopWatch() //
stopWatch = null
}
}
//
const startWatching = () => {
stopWatch = watch(
messageList,
newVal => {
nextTick(() => {
if (scrollContainer.value) {
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
}
})
},
{ deep: true }
)
isWatching.value = true
}
const segmented = {
content: 'soft',
footer: 'soft'
}
const openAi = () => {
messageList.value = []
getDictListist()
startWatching()
showModal.value = true
}
</script>
<style lang="scss" scoped>
.ai-botton {
cursor: grab;
width: 160px;
height: 80px;
}
.ai-botton:active {
cursor: grabbing;
}
.ai-container {
height: 70vh;
width: 100%;
display: flex;
align-items: center;
& > div {
height: 100%;
}
.chat-container {
box-sizing: border-box;
padding: 0 100px 0 80px;
width: 80%;
border-right: 1px solid #000000;
.chat-list,
.chat-input {
width: 100%;
}
.chat-input {
border: 1px solid #3c434d !important;
background: rgba(57, 58, 68, 0.5) !important;
}
}
.chat-history {
width: 20%;
}
}
.message-text {
background: #2f323b;
border-radius: 10px;
border: 1px solid #3c434d;
}
/* 设置滚动条整体样式 */
::-webkit-scrollbar {
width: 0px;
}
</style>

@ -73,13 +73,8 @@ const currentTime = moment().format('YYYY/MM/DD HH:mm:ss')
*/
const handlerChange = () => {
const totalPage = total.value / queryParams.size
if (totalPage === queryParams.current) {
message.warning('没有更多了!')
return
} else {
queryParams.current = getNonZeroRandom(totalPage)
getList()
}
}
const handlerQuerParmas = content => {
@ -109,6 +104,10 @@ onMounted(() => {
<style lang="scss" scoped>
.message-text {
background: #2f323b;
border-radius: 10px;
border: 1px solid #3c434d;
span {
font-weight: bold;
}

@ -1,5 +1,5 @@
<template>
<div class="ai-table">
<div class="ai-table" v-if="obj.type === 'table'">
<div class="table-title">{{ obj.title }}</div>
<div class="table-list">
<div class="list-item" v-for="(item, index) in obj.text" :key="index">
@ -7,7 +7,7 @@
<div class="header-item">{{ item.column }}</div>
</div>
<div class="item-boby" v-for="(valueItem, valueIndex) in item.list" :key="valueIndex">
{{ valueItem }}{{obj.unit }}
{{ valueItem }}{{ obj.unit }}
</div>
</div>
</div>
@ -15,38 +15,33 @@
</template>
<script setup>
import { ref, reactive } from 'vue'
const props = defineProps({
obj: {
type: Object,
default: () => {}
},
obj: {
type: Object,
default: () => {}
}
})
</script>
<style lang="scss" scoped>
.ai-table {
// width: fit-content;
// width: fit-content;
}
.table-title{
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
color: #fff;
.table-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 8px;
color: #fff;
}
.table-list {
min-width: 400px;
min-width: 400px;
display: flex;
align-items: center;
background-color: rgba(195, 202, 217, 0.5);
gap: 1px;
.list-item{
.list-item {
flex: 1;
}
.item-header {
background-color: #507afc;
@ -55,15 +50,21 @@ const props = defineProps({
text-align: center;
font-size: 15px;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.item-boby{
.item-boby {
margin-top: 1px;
background-color: #3D424D;
background-color: #3d424d;
text-align: center;
font-size: 14px;
font-weight: 400;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
</style>

@ -0,0 +1,47 @@
<template>
<div class="ai-hint" v-show="item.from === 'ai' && !item.loading">
<span>本回答由AI生成内容仅供参考</span>
<div class="upload-text" @click="uploadImage(index)">
<n-icon size="14">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16">
<g fill="none">
<path
d="M3.5 13h9a.75.75 0 0 1 .102 1.493l-.102.007h-9a.75.75 0 0 1-.102-1.493L3.5 13h9h-9zM7.898 1.007L8 1a.75.75 0 0 1 .743.648l.007.102v7.688l2.255-2.254a.75.75 0 0 1 .977-.072l.084.072a.75.75 0 0 1 .072.977l-.072.084L8.53 11.78a.75.75 0 0 1-.976.073l-.084-.073l-3.536-3.535a.75.75 0 0 1 .977-1.133l.084.072L7.25 9.44V1.75a.75.75 0 0 1 .648-.743L8 1l-.102.007z"
fill="currentColor"
></path>
</g>
</svg>
</n-icon>
<span>下载图片</span>
</div>
</div>
</template>
<script setup>
import { canvasCut } from '@/utils'
const props = defineProps({
item: {
type: Object,
default: () => {}
},
currentIndex: {
type: Number,
default: 0
}
})
/**
* 下载AI回答为图片
*/
const uploadImage = () => {
const range = document.getElementById(`box${props.currentIndex}`)
// 线
if (!range) {
window['$message'].error('下载失败!')
return
}
canvasCut(range)
}
</script>
<style scoped></style>

@ -0,0 +1,16 @@
<template>
<div v-show="item.from === 'ai' && item.loading" class="ai-loading">
<span>思考中</span> <n-spin size="small" content-class="spin-class" />
</div>
</template>
<script setup>
const props = defineProps({
item: {
type: Object,
default: () => {}
}
})
</script>
<style scoped></style>

@ -5,4 +5,11 @@ export { default as AiTable } from './aiTable/index.vue'
export { default as AddIssue } from './addIssue/index.vue'
export { default as IssueQuey } from './issueQuey/index.vue'
export { default as AiChart } from './aiChart/index.vue'
export { default as IssueQuey } from './issueQuey/index.vue'
export { default as disclaimer } from './disclaimer/index.vue'
export { default as inThinking } from './inThinking/index.vue'
export { default as initEcharts } from './initEcharts/index.vue'

@ -0,0 +1,43 @@
<template>
<section v-if="ehcatrsType.includes(item.type)">
<div class="echarts-box" :id="item.type + currentIndex"></div>
<div class="echatrs-theme">
<div class="theme-title">风格切换</div>
<div class="theme-list">
<div
class="theme-item"
@click="handlerTheme(item, themeIndex)"
v-for="(themeItem, themeIndex) in echartsTheme"
:key="themeIndex"
:class="item.chartInstanceActiveIndex == themeIndex ? 'activeTheme' : ''"
>
{{ themeItem }}
</div>
</div>
</div>
</section>
</template>
<script setup>
const props = defineProps({
item: {
type: Object,
default: () => {}
},
currentIndex: {
type: Number,
default: 0
}
})
const emits = defineEmits(['changeTheme'])
//
const echartsTheme = ['简约风', '商务风', '科技风']
const ehcatrsType = ['pie', 'line', 'bar', 'number']
const handlerTheme = (item,index)=>{
emits('changeTheme',item,index)
}
</script>
<style scoped></style>

@ -36,9 +36,12 @@
:key="item.id"
v-show="queryList.length > 0"
>
{{ item.content }}
</div>
<div class="item-name">
{{ item.content }}
</div>
<img class="item-del-icon" src="~@/assets/images/ai/icon-delet.png" alt="" @click.stop="handlerDel(item.id)" />
</div>
<n-empty description="你什么也找不到" v-show="queryList.length === 0"> </n-empty>
</div>
</n-popconfirm>
@ -46,9 +49,10 @@
<script setup>
import { ref } from 'vue'
import { getIssue } from '@/api/path'
import { getIssue, delIssue } from '@/api/path'
import { useMessage } from 'naive-ui'
import { useSystemStore } from '@/store/modules/systemStore/systemStore'
import { httpErrorHandle } from '@/utils'
const message = useMessage()
const props = defineProps({
@ -70,7 +74,7 @@ let queryList = ref([])
const handlerQuerParmas = content => {
emit('update:modelValue', content)
emit('sendMessage')
// emit('sendMessage')
popconfirm.value.setShow(false)
setTimeout(() => {
handlerBack()
@ -80,6 +84,11 @@ const handlerBack = () => {
currentKey.value = null
currentValue.value = ''
}
/**
* 查询类别问题
* @param dictKey
* @param dictValue
*/
const handlerClick = (dictKey, dictValue) => {
if (currentKey.value != dictKey) {
currentKey.value = dictKey
@ -87,14 +96,27 @@ const handlerClick = (dictKey, dictValue) => {
getList()
}
}
/**
* 删除
* @param id
*/
const handlerDel = async id => {
const res = await delIssue(id)
if (res.code === 200) {
window['$message'].success(window['$t']('global.r_delete_success'))
getList()
return
}
httpErrorHandle()
}
const getList = async () => {
try {
const res = await getIssue({
questionType: currentKey.value,
userId:systemStore.userInfo.userId,
current:1,
size:100
userId: systemStore.userInfo.userId,
current: 1,
size: 100
})
if (res && res.data) {
queryList.value = res.data.records
@ -132,6 +154,29 @@ const getList = async () => {
font-weight: 400;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
.item-name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-del-icon {
height: 14px;
width: 14px;
display: none;
}
}
.list-item:hover {
color: #507afc;
border: 1px solid #507afc;
background: rgba(80, 122, 252, 0.2);
.item-del-icon {
display: block;
}
}
}
.issue-list {

@ -0,0 +1,276 @@
<template>
<div
ref="floatDrag"
id="floatDrag"
class="float-position"
:style="{ top: data.top + 'px', right: data.right + 'px !important' }"
@touchmove.prevent
@mousemove.prevent
@mousedown="mouseDown"
@mouseup="mouseUp"
>
<div class="content">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, reactive, nextTick, ref, onBeforeMount } from 'vue';
interface dataOptionItem {
clientWidth: null | number;
clientHeight: null | number;
left: number;
top: number;
right: number;
timer: null | number;
currentTop: 0 | number;
mousedownX: 0 | number;
mousedownY: 0 | number;
}
export default defineComponent({
name: 'DragBall',
props: {
distanceRight: {
type: Number,
default: 36,
},
distanceBottom: {
type: Number,
default: 100,
},
isScrollHidden: {
type: Boolean,
default: false,
},
isCanDraggable: {
type: Boolean,
default: true,
},
},
emits: ['handlepaly'],
setup(props, { emit }) {
const data = reactive<dataOptionItem>({
clientWidth: null,
clientHeight: null,
left: 0,
top: 0,
right: 0,
timer: null,
currentTop: 0,
mousedownX: 0,
mousedownY: 0,
});
// const floatDrag = ref();
const floatDragDom = ref();
data.clientWidth = document.documentElement.clientWidth;
data.clientHeight = document.documentElement.clientHeight;
onMounted(() => {
props.isCanDraggable &&
nextTick(() => {
//
floatDragDom.value = document.getElementById('floatDrag');
//
data.right = props.distanceRight;
data.top =
Number(data.clientHeight) - floatDragDom.value?.offsetHeight - props.distanceBottom;
initDraggable();
});
// this.isScrollHidden && window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', handleResize);
});
onBeforeMount(() => {
// window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', handleResize);
});
/**
* 初始化draggable
*/
const initDraggable = () => {
floatDragDom.value.addEventListener('touchstart', toucheStart);
floatDragDom.value.addEventListener('touchmove', (e) => touchMove(e));
floatDragDom.value.addEventListener('touchend', touchEnd);
};
const handleResize = () => {
checkDraggablePosition();
};
/**
* 判断元素显示位置
* 在窗口改变和move end时调用
*/
const checkDraggablePosition = () => {
data.clientWidth = document.documentElement.clientWidth;
data.clientHeight = document.documentElement.clientHeight;
if (data.right + floatDragDom.value.offsetWidth / 2 >= data.clientWidth / 2) {
//
data.right = data.clientWidth - floatDragDom.value.offsetWidth;
} else {
data.right = 36;
}
if (data.top < 0) {
// 沿
data.top = 0;
}
if (data.top + floatDragDom.value.offsetHeight >= data.clientHeight) {
// 沿
data.top = data.clientHeight - floatDragDom.value.offsetHeight;
}
};
const canClick = ref(false);
const mouseDown = (e) => {
const event = e || window.event;
data.mousedownX = event.screenX;
data.mousedownY = event.screenY;
let floatDragWidth = floatDragDom.value.offsetWidth / 2;
let floatDragHeight = floatDragDom.value.offsetHeight / 2;
if (event.preventDefault) {
event.preventDefault();
}
canClick.value = false;
floatDragDom.value.style.transition = 'none';
document.onmousemove = function (e) {
var event = e || window.event;
console.log(event.clientY);
//
data.right = Number(data.clientWidth) - event.clientX - floatDragWidth;
data.top = event.clientY - floatDragHeight;
if (data.right < 0) data.right = 0;
if (data.top < 0) data.top = 0;
//
if (
event.clientY < 0 ||
event.clientY > Number(data.clientHeight) ||
event.clientX > Number(data.clientWidth) ||
event.clientX < 0
) {
data.right = 0;
data.top =
Number(data.clientHeight) - floatDragDom.value?.offsetHeight - props.distanceBottom;
document.onmousemove = null;
floatDragDom.value.style.transition = 'all 0.3s';
return;
}
if (data.right >= document.documentElement.clientWidth - floatDragWidth * 2) {
data.right = document.documentElement.clientWidth - floatDragWidth * 2;
}
if (data.top >= Number(data.clientHeight) - floatDragHeight * 2) {
data.top = Number(data.clientHeight) - floatDragHeight * 2;
}
};
};
const mouseUp = (e) => {
const event = e || window.event;
//
if (data.mousedownY == event.screenY && data.mousedownX == event.screenX) {
emit('handlepaly');
}
document.onmousemove = null;
checkDraggablePosition();
floatDragDom.value.style.transition = 'all 0.3s';
};
const toucheStart = () => {
canClick.value = false;
floatDragDom.value.style.transition = 'none';
};
const touchMove = (e) => {
canClick.value = true;
if (e.targetTouches.length === 1) {
//
let touch = e.targetTouches[0];
data.right =
Number(data.clientWidth) - touch.clientX - floatDragDom.value.offsetWidth / 2;
data.top = touch.clientY - floatDragDom.value.offsetHeight / 2;
}
};
const touchEnd = () => {
if (!canClick.value) return; // touch
floatDragDom.value.style.transition = 'all 0.3s';
checkDraggablePosition();
};
return {
data,
handleResize,
checkDraggablePosition,
mouseUp,
mouseDown,
};
},
});
</script>
<style>
html,
body {
overflow: hidden;
}
</style>
<style scoped lang="scss">
.float-position {
position: fixed;
z-index: 100 !important;
right: 0;
top: 70%;
width: 70px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
.content {
// width: 50px;
//height: 50px;
// background: #716af2;
//box-shadow: 0px 0px 10px 2px #a299ff;
// border-radius: 50%;
// position: relative;
// padding: 0.8em;
// display: flex;
// align-content: center;
//justify-content: center;
}
.close {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: rgba(0, 0, 0, 0.6);
position: absolute;
right: -10px;
top: -12px;
cursor: pointer;
}
}
.cart {
border-radius: 50%;
width: 5em;
height: 5em;
display: flex;
align-items: center;
justify-content: center;
}
.header-notice {
display: inline-block;
transition: all 0.3s;
span {
vertical-align: initial;
}
.notice-badge {
color: inherit;
.header-notice-icon {
font-size: 16px;
padding: 4px;
}
}
}
.drag-ball .drag-content {
overflow-wrap: break-word;
font-size: 14px;
color: #fff;
letter-spacing: 2px;
}
</style>

@ -39,6 +39,7 @@
<div class="right-container">
<router-view :key="route.fullPath"></router-view>
</div>
<AiChart></AiChart>
</div>
</template>
@ -47,7 +48,7 @@ import { useRoute, useRouter } from 'vue-router'
import { createProjectApi, historyMessageRoom, historyMessageRoomUpdata } from '@/api/path'
import { getUUID } from '@/utils'
import { ResultEnum } from '@/enums/httpEnum'
import { AiLogo } from './components'
import { AiLogo , AiChart} from './components'
import { useChartEditStore } from '@/store/modules/chartEditStore/chartEditStore'
const chartEditStore = useChartEditStore()

@ -4,8 +4,12 @@
<div class="list">
<div class="list-item" v-for="item in boardLsit" :key="item.id" @click="previewHandle(item)">
<div class="item-operation">
<img @click="deleteHandle(item)" src="~@/assets/images/ai/icon-delet.png" class="operation-item" alt="" />
<img @click="editHandle(item)" src="~@/assets/images/ai/icon-edit.png" class="operation-item" alt="" />
<div class="operation-item">
<img @click.stop="deleteHandle(item)" src="~@/assets/images/ai/icon-delet.png" alt="" />
</div>
<div class="operation-item">
<img @click.stop="editHandle(item)" src="~@/assets/images/ai/icon-edit.png" alt="" />
</div>
</div>
<img :src="item.indexImage" class="item-cover" alt="" />
<div class="item-name">{{ item.projectName }}</div>
@ -32,7 +36,9 @@ import { openNewWindow, previewPath } from '@/utils'
import { useRouter } from 'vue-router'
import { projectListApi, deleteProjectApi } from '@/api/path'
let boardLsit = ref([])
const router = useRouter()
const paginat = reactive({
//
page: 1,
@ -91,7 +97,7 @@ const previewHandle = cardData => {
openNewWindow(previewPath(cardData.id))
}
onMounted(() => {
fetchList()
// fetchList()
})
</script>
@ -121,10 +127,18 @@ onMounted(() => {
.item-operation {
display: flex;
flex-direction: row-reverse;
gap: 11px;
gap: 6px;
.operation-item {
height: 13px;
width: 13px;
padding: 3px 6px;
border-radius: 6px;
img {
height: 13px;
width: 13px;
}
}
.operation-item:hover{
background-color: #18181c;
}
}
.item-cover {

Loading…
Cancel
Save