|
|
|
@ -1,15 +1,32 @@
|
|
|
|
<template>
|
|
|
|
<template>
|
|
|
|
<div v-if="state.isShowSearch" class="ai-float-window ai-float-fullscreen">
|
|
|
|
<div v-if="state.isShowSearch" :class="['ai-float-window', currentSizeClass]"
|
|
|
|
|
|
|
|
:style="currentSize === 'custom' ? {
|
|
|
|
|
|
|
|
width: windowSize.width,
|
|
|
|
|
|
|
|
height: windowSize.height,
|
|
|
|
|
|
|
|
left: typeof windowPosition.x === 'string' ? 'auto' : windowPosition.x + 'px',
|
|
|
|
|
|
|
|
right: typeof windowPosition.x === 'string' ? '0' : 'auto',
|
|
|
|
|
|
|
|
top: windowPosition.y + 'px'
|
|
|
|
|
|
|
|
} : {}">
|
|
|
|
<div class="ai-float-main">
|
|
|
|
<div class="ai-float-main">
|
|
|
|
<div class="ai-float-left" v-if="isShowLeft">
|
|
|
|
<div class="ai-float-left" v-if="isShowLeft">
|
|
|
|
<component :is="currentComponent" v-bind="currentProps" v-if="currentComponent"/>
|
|
|
|
<component :is="currentComponent" v-bind="currentProps" v-if="currentComponent"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="ai-float-right" v-loading="aiLoading">
|
|
|
|
<div class="ai-float-right" v-loading="aiLoading">
|
|
|
|
<div class="ai-float-header" @mousedown="onDragStart">
|
|
|
|
<div class="ai-float-header" @mousedown="startDrag">
|
|
|
|
<span>AI智能填报助手</span>
|
|
|
|
<span>AI智能填报助手</span>
|
|
|
|
|
|
|
|
<!-- 将两个图标按钮放在一个容器内 -->
|
|
|
|
|
|
|
|
<div class="ai-float-header-actions">
|
|
|
|
|
|
|
|
<el-icon v-if="currentSize==='minimize'" class="ai-float-minimize" @click.stop="toggleSize">
|
|
|
|
|
|
|
|
<Plus />
|
|
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
|
|
<el-icon v-if="currentSize!=='minimize'" class="ai-float-minimize" @click.stop="toggleSize">
|
|
|
|
|
|
|
|
<Minus />
|
|
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
|
|
|
|
|
|
<el-icon class="ai-float-close" @click="close">
|
|
|
|
<el-icon class="ai-float-close" @click="close">
|
|
|
|
<Close/>
|
|
|
|
<Close/>
|
|
|
|
</el-icon>
|
|
|
|
</el-icon>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="ai-float-form-list ai-float-scroll">
|
|
|
|
<div class="ai-float-form-list ai-float-scroll">
|
|
|
|
<div class="ai-float-welcome">👋 欢迎使用AI智能填报助手</div>
|
|
|
|
<div class="ai-float-welcome">👋 欢迎使用AI智能填报助手</div>
|
|
|
|
@ -73,11 +90,11 @@
|
|
|
|
<div class="ai-select-list-title">{{ list.title }}</div>
|
|
|
|
<div class="ai-select-list-title">{{ list.title }}</div>
|
|
|
|
<div class="ai-select-options">
|
|
|
|
<div class="ai-select-options">
|
|
|
|
<el-button v-for="opt in list.options.slice(0,4)" :key="opt" size="small"
|
|
|
|
<el-button v-for="opt in list.options.slice(0,4)" :key="opt" size="small"
|
|
|
|
@click="() => handleSelectOption(opt)"
|
|
|
|
@click="() => handleSelectOption(opt,lidx)"
|
|
|
|
:type="selectedOption === opt ? 'primary' : 'default'">{{ opt }}
|
|
|
|
:type="selectedOption === opt ? 'primary' : 'default'">{{ opt.fieldValue }}
|
|
|
|
</el-button>
|
|
|
|
</el-button>
|
|
|
|
<el-button v-if="list.options.length > 4" size="small" type="primary" link
|
|
|
|
<el-button v-if="list.options.length > 4" size="small" type="primary" link
|
|
|
|
@click="() => openSelectDialog(list)">更多
|
|
|
|
@click="() => openSelectDialog(list,lidx)">更多
|
|
|
|
</el-button>
|
|
|
|
</el-button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -105,8 +122,9 @@
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
<div class="ai-select-options">
|
|
|
|
<div class="ai-select-options">
|
|
|
|
<el-button v-for="opt in filteredSelectDialogOptions" :key="opt" size="small"
|
|
|
|
<el-button v-for="opt in filteredSelectDialogOptions" :key="opt" size="small"
|
|
|
|
@click="() => handleSelectOption(opt)" :type="selectedOption === opt ? 'primary' : 'default'">
|
|
|
|
@click="() => handleSelectOption(opt)"
|
|
|
|
{{ opt }}
|
|
|
|
:type="selectedOption === opt ? 'primary' : 'default'">
|
|
|
|
|
|
|
|
{{ opt.fieldValue }}
|
|
|
|
</el-button>
|
|
|
|
</el-button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@ -117,9 +135,22 @@
|
|
|
|
当前表单:{{ aiFormSettingList.find(f => f.formSettingId === selectedFormSettingId)?.formName }}
|
|
|
|
当前表单:{{ aiFormSettingList.find(f => f.formSettingId === selectedFormSettingId)?.formName }}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="form-info-tip">请根据下方示例填写内容,AI将自动为您智能填报。</div>
|
|
|
|
<div class="form-info-tip">请根据下方示例填写内容,AI将自动为您智能填报。</div>
|
|
|
|
<div class="form-info-example">示例:如"申请10个物料A用于生产线1,计划交付日期为2024-07-01"</div>
|
|
|
|
<div class="form-info-example">{{ aiFormSettingList.find(f => f.formSettingId === selectedFormSettingId)?.remark ?? '如:申请10个物料A用于生产线1,计划交付日期为2024-07-01'}}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="ai-float-input-row">
|
|
|
|
<div class="ai-float-input-row">
|
|
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
@click="toggleRecording"
|
|
|
|
|
|
|
|
:class="['recording-btn', { 'recording': isRecording }]"
|
|
|
|
|
|
|
|
v-loading="audioAsrLoading"
|
|
|
|
|
|
|
|
:disabled="!canSend || aiThinking"
|
|
|
|
|
|
|
|
circle
|
|
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<template #icon>
|
|
|
|
|
|
|
|
<el-icon v-if="isRecording"><VideoPause /></el-icon>
|
|
|
|
|
|
|
|
<el-icon v-else><Microphone /></el-icon>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
<el-input
|
|
|
|
<el-input
|
|
|
|
v-model="inputValue"
|
|
|
|
v-model="inputValue"
|
|
|
|
type="textarea"
|
|
|
|
type="textarea"
|
|
|
|
@ -136,22 +167,19 @@
|
|
|
|
<el-button
|
|
|
|
<el-button
|
|
|
|
class="ai-float-send-btn"
|
|
|
|
class="ai-float-send-btn"
|
|
|
|
:disabled="!canSend || aiThinking"
|
|
|
|
:disabled="!canSend || aiThinking"
|
|
|
|
icon="el-icon-s-promotion"
|
|
|
|
icon="Top"
|
|
|
|
@click="() => handleSend()"
|
|
|
|
@click="() => handleSend()"
|
|
|
|
circle
|
|
|
|
circle
|
|
|
|
type="primary"
|
|
|
|
type="success"
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
<el-button
|
|
|
|
|
|
|
|
@click="toggleRecording"
|
|
|
|
|
|
|
|
:class="{ 'recording': isRecording }"
|
|
|
|
|
|
|
|
v-loading="audioAsrLoading"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{{ isRecording ? '停止录音' : '开始录音' }}
|
|
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 新增:调整大小的句柄 -->
|
|
|
|
|
|
|
|
<div class="ai-float-resize-handle" @mousedown="startResize"></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
@ -164,15 +192,25 @@ import {getAiFormSettingList, getAiFormSettingDetailList} from '@/api/ai/base/ai
|
|
|
|
import {AiFormSettingVO, AiFormSettingDetailVO} from '@/api/ai/base/aiFormSetting/types';
|
|
|
|
import {AiFormSettingVO, AiFormSettingDetailVO} from '@/api/ai/base/aiFormSetting/types';
|
|
|
|
import {aiFillForm, recognizeSpeechByUrl, uploadFile} from '@/api/ai/skill/aiAssistant';
|
|
|
|
import {aiFillForm, recognizeSpeechByUrl, uploadFile} from '@/api/ai/skill/aiAssistant';
|
|
|
|
import router from "@/router"
|
|
|
|
import router from "@/router"
|
|
|
|
|
|
|
|
import {useSharedDataStore} from '@/api/monitorData'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const sharedStore = useSharedDataStore()
|
|
|
|
|
|
|
|
|
|
|
|
const {proxy} = getCurrentInstance() as ComponentInternalInstance
|
|
|
|
const {proxy} = getCurrentInstance() as ComponentInternalInstance
|
|
|
|
|
|
|
|
|
|
|
|
const aiFormSettingList = ref<AiFormSettingVO[]>([])
|
|
|
|
const aiFormSettingList = ref<AiFormSettingVO[]>([])
|
|
|
|
const aiFormSettingDetailListMap = ref<Record<number, AiFormSettingDetailVO[]>>({});
|
|
|
|
const aiFormSettingDetailListMap = ref<Record<number, AiFormSettingDetailVO[]>>({});
|
|
|
|
const selectedFormSettingId = ref<number | null>(null)
|
|
|
|
const selectedFormSettingId = ref<number | null>(null)
|
|
|
|
|
|
|
|
const selectedForm = ref();
|
|
|
|
const formsLoading = ref(false)
|
|
|
|
const formsLoading = ref(false)
|
|
|
|
const isShowLeft = ref(false);
|
|
|
|
const isShowLeft = ref(false);
|
|
|
|
const aiLoading = ref(false);
|
|
|
|
const aiLoading = ref(false);
|
|
|
|
|
|
|
|
const currentSize = ref("menu");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const currentSizeClass = computed(() =>
|
|
|
|
|
|
|
|
`size-${currentSize.value}`
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 用reactive对象直接定义state
|
|
|
|
// 用reactive对象直接定义state
|
|
|
|
const state = reactive({
|
|
|
|
const state = reactive({
|
|
|
|
@ -272,9 +310,12 @@ const getFormProps = (form: AiFormSettingVO) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectForm = async (aiFormSetting: AiFormSettingVO) => {
|
|
|
|
const selectForm = async (aiFormSetting: AiFormSettingVO) => {
|
|
|
|
|
|
|
|
if( selectedFormSettingId.value === aiFormSetting.formSettingId) return;
|
|
|
|
selectedFormSettingId.value = aiFormSetting.formSettingId
|
|
|
|
selectedFormSettingId.value = aiFormSetting.formSettingId
|
|
|
|
|
|
|
|
selectedForm.value = aiFormSetting;
|
|
|
|
aiLoading.value = true
|
|
|
|
aiLoading.value = true
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
|
|
|
|
resetAiAssistant();
|
|
|
|
if (!aiFormSettingDetailListMap.value[aiFormSetting.formSettingId]) {
|
|
|
|
if (!aiFormSettingDetailListMap.value[aiFormSetting.formSettingId]) {
|
|
|
|
const res = await getAiFormSettingDetailList(
|
|
|
|
const res = await getAiFormSettingDetailList(
|
|
|
|
{formSettingId: aiFormSetting.formSettingId, settingFlag: '1'})
|
|
|
|
{formSettingId: aiFormSetting.formSettingId, settingFlag: '1'})
|
|
|
|
@ -288,6 +329,10 @@ const selectForm = async (aiFormSetting: AiFormSettingVO) => {
|
|
|
|
console.log(aiFormSettingDetailListMap.value[aiFormSetting.formSettingId])
|
|
|
|
console.log(aiFormSettingDetailListMap.value[aiFormSetting.formSettingId])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const resetAiAssistant = () =>{
|
|
|
|
|
|
|
|
chatList.value = [];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// function selectForm(aiFormSetting: AiFormSettingVO) {
|
|
|
|
// function selectForm(aiFormSetting: AiFormSettingVO) {
|
|
|
|
// selectedFormSettingId.value = aiFormSetting.formSettingId
|
|
|
|
// selectedFormSettingId.value = aiFormSetting.formSettingId
|
|
|
|
// if(!aiFormSettingDetailListMap[aiFormSetting.formSettingId]){
|
|
|
|
// if(!aiFormSettingDetailListMap[aiFormSetting.formSettingId]){
|
|
|
|
@ -306,11 +351,14 @@ watch(selectedFormSettingId, async (formSettingId) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if (formPageMap.value[formSettingId].props && formPageMap.value[formSettingId].props.dialogVisibleVariable && formPageMap.value[formSettingId].props.dialogVisibleVariable !== '') {
|
|
|
|
if (formPageMap.value[formSettingId].props && formPageMap.value[formSettingId].props.dialogVisibleVariable && formPageMap.value[formSettingId].props.dialogVisibleVariable !== '') {
|
|
|
|
isShowLeft.value = false
|
|
|
|
isShowLeft.value = false
|
|
|
|
router.push("/" + formPageMap.value[formSettingId].props.formPath)
|
|
|
|
currentSize.value = "menu";
|
|
|
|
|
|
|
|
router.push({path: "/" + formPageMap.value[formSettingId].props.formPath, query: {from: "ai"}})
|
|
|
|
|
|
|
|
|
|
|
|
// proxy.$store.commit('aiFormSettingId', formSettingId)
|
|
|
|
// proxy.$store.commit('aiFormSettingId', formSettingId)
|
|
|
|
// proxy.$store.commit('aiFormSetting', formPageMap.value[formSettingId])
|
|
|
|
// proxy.$store.commit('aiFormSetting', formPageMap.value[formSettingId])
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
isShowLeft.value = true
|
|
|
|
isShowLeft.value = true
|
|
|
|
|
|
|
|
currentSize.value = "form";
|
|
|
|
const mod = await formPageMap.value[formSettingId].loader()
|
|
|
|
const mod = await formPageMap.value[formSettingId].loader()
|
|
|
|
console.log(formPageMap.value[formSettingId].props)
|
|
|
|
console.log(formPageMap.value[formSettingId].props)
|
|
|
|
currentComponent.value = mod.default
|
|
|
|
currentComponent.value = mod.default
|
|
|
|
@ -330,6 +378,7 @@ watch(selectedFormSettingId, async (formSettingId) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 暴露变量
|
|
|
|
// 暴露变量
|
|
|
|
defineExpose({
|
|
|
|
defineExpose({
|
|
|
|
openSearch
|
|
|
|
openSearch
|
|
|
|
@ -342,7 +391,50 @@ watch(state, (newVal) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const selectLists = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 存储fieldType为1的元素及其位置信息,显示使用
|
|
|
|
|
|
|
|
const type1Elements = ref([]);
|
|
|
|
|
|
|
|
// 存储fieldType为2的元素及其位置信息,赋值使用
|
|
|
|
|
|
|
|
const type2Elements = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 存储选中后对应的fieldType为2的元素
|
|
|
|
|
|
|
|
const selectedType2Elements = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 处理数据,找出所有fieldType为1的元素
|
|
|
|
|
|
|
|
const processData = (data) => {
|
|
|
|
|
|
|
|
type1Elements.value = [];
|
|
|
|
|
|
|
|
type2Elements.value = [];
|
|
|
|
|
|
|
|
let title = "";
|
|
|
|
|
|
|
|
data.forEach((subArray, subArrayIndex) => {
|
|
|
|
|
|
|
|
if (Array.isArray(subArray)) {
|
|
|
|
|
|
|
|
subArray.forEach(item => {
|
|
|
|
|
|
|
|
if (item.fieldType === '3' || item.fieldType === 3) {//请选择关联表的表名title信息
|
|
|
|
|
|
|
|
if (title == '') title = item.fieldValue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (item.fieldType === '1' || item.fieldType === 1) {//赋值
|
|
|
|
|
|
|
|
type1Elements.value.push({
|
|
|
|
|
|
|
|
...item,
|
|
|
|
|
|
|
|
subArrayIndex
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (item.fieldType === '2' || item.fieldType === 2) {//显示的名称
|
|
|
|
|
|
|
|
type2Elements.value.push({
|
|
|
|
|
|
|
|
...item,
|
|
|
|
|
|
|
|
subArrayIndex
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
selectLists.value.push({title: title, options: type2Elements.value, formOptions: type1Elements.value});
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const sendForm = ref({});
|
|
|
|
const handleSend = async (selectedText?: string) => {
|
|
|
|
const handleSend = async (selectedText?: string) => {
|
|
|
|
if ((!canSend.value || aiThinking.value) && !selectedText) return
|
|
|
|
if ((!canSend.value || aiThinking.value) && !selectedText) return
|
|
|
|
const userMsg = selectedText || inputValue.value
|
|
|
|
const userMsg = selectedText || inputValue.value
|
|
|
|
@ -361,38 +453,144 @@ const handleSend = async (selectedText?: string) => {
|
|
|
|
platformId: 1
|
|
|
|
platformId: 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// currentProps.value = {completeAmount:10,productionTime:16.6};
|
|
|
|
// chatList.value.push({
|
|
|
|
|
|
|
|
// text: '操作成功,请选择相关信息:',
|
|
|
|
|
|
|
|
// from: 'ai',
|
|
|
|
|
|
|
|
// type: 'select',
|
|
|
|
|
|
|
|
// selectLists: selectLists.value
|
|
|
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// sharedStore.updateDynamicValue({
|
|
|
|
|
|
|
|
// message: {deptId:100},
|
|
|
|
|
|
|
|
// timestamp: new Date().toLocaleString()
|
|
|
|
|
|
|
|
// })
|
|
|
|
// return;
|
|
|
|
// return;
|
|
|
|
|
|
|
|
|
|
|
|
const response = await aiFillForm(params)
|
|
|
|
const response = await aiFillForm(params)
|
|
|
|
currentProps.value = response;
|
|
|
|
sendForm.value = response;
|
|
|
|
inputValue.value = ''
|
|
|
|
inputValue.value = ''
|
|
|
|
aiThinking.value = false
|
|
|
|
aiThinking.value = false
|
|
|
|
|
|
|
|
traverseJSON();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedForm.value && selectedForm.value.dialogVisibleVariable && selectedForm.value.dialogVisibleVariable !== '') {
|
|
|
|
|
|
|
|
//打开菜单时赋值使用
|
|
|
|
|
|
|
|
sharedStore.updateDynamicValue({
|
|
|
|
|
|
|
|
message: sendForm.value,
|
|
|
|
|
|
|
|
timestamp: new Date().toLocaleString()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
//直接打开表单赋值使用
|
|
|
|
|
|
|
|
currentProps.value = sendForm.value;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 固定AI回复+多个选择列表
|
|
|
|
// 固定AI回复+多个选择列表
|
|
|
|
chatList.value.push({
|
|
|
|
// chatList.value.push({
|
|
|
|
text: '操作成功,请选择相关信息:',
|
|
|
|
// text: '操作成功,请选择相关信息:',
|
|
|
|
from: 'ai',
|
|
|
|
// from: 'ai',
|
|
|
|
type: 'select',
|
|
|
|
// type: 'select',
|
|
|
|
selectLists: [
|
|
|
|
// selectLists: [
|
|
|
|
{title: '物料信息', options: ['物料A', '物料B', '物料C', '物料D', '物料E', '物料F']},
|
|
|
|
// {title: '物料信息', options: ['物料A', '物料B', '物料C', '物料D', '物料E', '物料F']},
|
|
|
|
{title: 'BOM信息', options: ['BOM-001', 'BOM-002', 'BOM-003', 'BOM-004', 'BOM-005']}
|
|
|
|
// {title: 'BOM信息', options: ['BOM-001', 'BOM-002', 'BOM-003', 'BOM-004', 'BOM-005']}
|
|
|
|
]
|
|
|
|
// ]
|
|
|
|
})
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (selectLists.value.length > 0) {
|
|
|
|
|
|
|
|
chatList.value.push({
|
|
|
|
|
|
|
|
text: '操作成功,请选择相关信息:',
|
|
|
|
|
|
|
|
from: 'ai',
|
|
|
|
|
|
|
|
type: 'select',
|
|
|
|
|
|
|
|
selectLists: selectLists.value
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
chatList.value.push({
|
|
|
|
|
|
|
|
text: '操作成功',
|
|
|
|
|
|
|
|
from: 'ai',
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scrollToBottom()
|
|
|
|
scrollToBottom()
|
|
|
|
|
|
|
|
|
|
|
|
console.log(1)
|
|
|
|
console.log(1)
|
|
|
|
console.log(response)
|
|
|
|
console.log(response)
|
|
|
|
console.log(2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
proxy?.$modal.msgError(error);
|
|
|
|
proxy?.$modal.msgError(error);
|
|
|
|
console.log("Error:" + error)
|
|
|
|
console.log("Error:" + error)
|
|
|
|
|
|
|
|
chatList.value.push({
|
|
|
|
|
|
|
|
text: '请重试:'+error,
|
|
|
|
|
|
|
|
from: 'ai',
|
|
|
|
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
aiThinking.value = false
|
|
|
|
|
|
|
|
inputValue.value = ''
|
|
|
|
aiLoading.value = false
|
|
|
|
aiLoading.value = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 递归遍历JSON对象的函数
|
|
|
|
|
|
|
|
function traverseJSON() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否为数组
|
|
|
|
|
|
|
|
if (Array.isArray(sendForm.value)) {
|
|
|
|
|
|
|
|
console.log('这是一个数组,包含 ' + sendForm.value.length + ' 个元素:');
|
|
|
|
|
|
|
|
// 遍历数组中的每个元素
|
|
|
|
|
|
|
|
// obj.forEach((item, index) => {
|
|
|
|
|
|
|
|
// console.log(' '.repeat(indent) + '索引 ' + index + ':');
|
|
|
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// 遍历对象的所有key值
|
|
|
|
|
|
|
|
for (const key in sendForm.value) {
|
|
|
|
|
|
|
|
// 确保key是对象自身的属性(非继承)
|
|
|
|
|
|
|
|
if (sendForm.value.hasOwnProperty(key)) {
|
|
|
|
|
|
|
|
const value = sendForm.value[key];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 判断value是否为数组
|
|
|
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
|
|
|
// selectLists: [
|
|
|
|
|
|
|
|
// {title: '物料信息', options: ['物料A', '物料B', '物料C', '物料D', '物料E', '物料F']},
|
|
|
|
|
|
|
|
// {title: 'BOM信息', options: ['BOM-001', 'BOM-002', 'BOM-003', 'BOM-004', 'BOM-005']}
|
|
|
|
|
|
|
|
// ]
|
|
|
|
|
|
|
|
sendForm.value[key] = '';
|
|
|
|
|
|
|
|
if (value.length == 1) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const firstValue = value[0];
|
|
|
|
|
|
|
|
firstValue.forEach(item => {
|
|
|
|
|
|
|
|
if(item.fieldType ==='1' || item.fieldType===1){
|
|
|
|
|
|
|
|
sendForm.value[key] = item.fieldValue
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// let selectList = {title: value[0].relateTableDesc, options: value}
|
|
|
|
|
|
|
|
// selectLists.value.push(selectList);
|
|
|
|
|
|
|
|
}else if (value.length > 1) {
|
|
|
|
|
|
|
|
processData(value)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历数组中的每个元素
|
|
|
|
|
|
|
|
// value.forEach((item, index) => {
|
|
|
|
|
|
|
|
// console.log(' '.repeat(indent + 1) + '数组元素 ' + index + ':');
|
|
|
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 如果value是对象但不是数组,递归遍历
|
|
|
|
|
|
|
|
// else if (value !== null && typeof value === 'object') {
|
|
|
|
|
|
|
|
// console.log(' '.repeat(indent) + '值是一个对象:');
|
|
|
|
|
|
|
|
// traverseJSON(value, indent + 1);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// 基本类型值直接输出
|
|
|
|
|
|
|
|
// else {
|
|
|
|
|
|
|
|
// console.log(' '.repeat(indent) + '值:', value);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 录音相关对象
|
|
|
|
// 录音相关对象
|
|
|
|
const isRecording = ref(false);
|
|
|
|
const isRecording = ref(false);
|
|
|
|
let mediaRecorder = null
|
|
|
|
let mediaRecorder = null
|
|
|
|
@ -418,8 +616,6 @@ const toggleRecording = () => {
|
|
|
|
|
|
|
|
|
|
|
|
// 开始录音
|
|
|
|
// 开始录音
|
|
|
|
async function startRecording() {
|
|
|
|
async function startRecording() {
|
|
|
|
alert('您的浏览器不支持录音功能1')
|
|
|
|
|
|
|
|
proxy.$modal.msgError('您的浏览器不支持录音功能')
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
// 检查浏览器支持
|
|
|
|
// 检查浏览器支持
|
|
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
|
|
@ -529,8 +725,8 @@ function stopRecording() {
|
|
|
|
console.error('处理录音文件失败:', error)
|
|
|
|
console.error('处理录音文件失败:', error)
|
|
|
|
proxy.$modal.msgError('处理录音文件失败,请重试', error)
|
|
|
|
proxy.$modal.msgError('处理录音文件失败,请重试', error)
|
|
|
|
// addLog(`处理录音文件失败: ${error.message}`)
|
|
|
|
// addLog(`处理录音文件失败: ${error.message}`)
|
|
|
|
}finally{
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
audioAsrLoading.value = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -699,13 +895,11 @@ async function uploadAudioFile(blob, fileName) {
|
|
|
|
if (response.code === 200) {
|
|
|
|
if (response.code === 200) {
|
|
|
|
// addLog('音频上传成功,正在识别')
|
|
|
|
// addLog('音频上传成功,正在识别')
|
|
|
|
|
|
|
|
|
|
|
|
// alert(response.data.url)
|
|
|
|
|
|
|
|
const response1 = await recognizeSpeechByUrl(response.data.url);
|
|
|
|
const response1 = await recognizeSpeechByUrl(response.data.url);
|
|
|
|
if (response1.code === 200) {
|
|
|
|
if (response1.code === 200) {
|
|
|
|
console.log("--" + response1);
|
|
|
|
console.log("--" + response1);
|
|
|
|
inputValue.value += response1.data.text
|
|
|
|
inputValue.value += response1.data.text
|
|
|
|
// recognitionResult.value = response1.data.text || '识别成功,但未返回文本结果'
|
|
|
|
// recognitionResult.value = response1.data.text || '识别成功,但未返回文本结果'
|
|
|
|
// alert(recognitionResult.value)
|
|
|
|
|
|
|
|
// addLog('识别成功,结果为: ' + recognitionResult.value)
|
|
|
|
// addLog('识别成功,结果为: ' + recognitionResult.value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
@ -767,6 +961,7 @@ type ChatMsg = { text: string, from: 'user' | 'ai', type?: 'text' | 'select', se
|
|
|
|
const chatList = ref<ChatMsg[]>([])
|
|
|
|
const chatList = ref<ChatMsg[]>([])
|
|
|
|
const selectDialog = ref(false)
|
|
|
|
const selectDialog = ref(false)
|
|
|
|
const selectDialogList = ref<ChatSelectList | null>(null)
|
|
|
|
const selectDialogList = ref<ChatSelectList | null>(null)
|
|
|
|
|
|
|
|
const selectDialogListIndex = ref();
|
|
|
|
const aiThinking = ref(false)
|
|
|
|
const aiThinking = ref(false)
|
|
|
|
const chatListRef = ref<HTMLElement | null>(null)
|
|
|
|
const chatListRef = ref<HTMLElement | null>(null)
|
|
|
|
const selectedOption = ref('')
|
|
|
|
const selectedOption = ref('')
|
|
|
|
@ -776,61 +971,48 @@ const iframeRef = ref<HTMLIFrameElement | null>(null)
|
|
|
|
const selectDialogFilterText = ref('')
|
|
|
|
const selectDialogFilterText = ref('')
|
|
|
|
const filteredSelectDialogOptions = computed(() =>
|
|
|
|
const filteredSelectDialogOptions = computed(() =>
|
|
|
|
selectDialogList.value
|
|
|
|
selectDialogList.value
|
|
|
|
? selectDialogList.value.options.filter(opt => opt.includes(selectDialogFilterText.value))
|
|
|
|
? selectDialogList.value.options.filter(opt => {
|
|
|
|
|
|
|
|
// 检查opt是否为对象,如果是则访问其字符串属性
|
|
|
|
|
|
|
|
// 这里假设对象有label或value属性,根据实际情况修改
|
|
|
|
|
|
|
|
const fieldValue = typeof opt === 'string' ? opt :
|
|
|
|
|
|
|
|
opt.fieldValue
|
|
|
|
|
|
|
|
return fieldValue.toLowerCase().includes(selectDialogFilterText.value.toLowerCase());
|
|
|
|
|
|
|
|
})
|
|
|
|
: []
|
|
|
|
: []
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 悬浮窗拖拽
|
|
|
|
|
|
|
|
const floatStyle = reactive({
|
|
|
|
|
|
|
|
left: '',
|
|
|
|
|
|
|
|
top: '0px',
|
|
|
|
|
|
|
|
width: '440px',
|
|
|
|
|
|
|
|
height: '100vh',
|
|
|
|
|
|
|
|
zIndex: 9999,
|
|
|
|
|
|
|
|
position: 'fixed' as const,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 默认显示在最右侧
|
|
|
|
// 默认显示在最右侧
|
|
|
|
onMounted(() => {
|
|
|
|
onMounted(() => {
|
|
|
|
floatStyle.left = (window.innerWidth - 440) + 'px';
|
|
|
|
|
|
|
|
floatStyle.top = '0px';
|
|
|
|
|
|
|
|
// 加载表单数据
|
|
|
|
// 加载表单数据
|
|
|
|
// loadForms()
|
|
|
|
// loadForms()
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// const testData = [[{"fieldKey":"relateTableDesc","fieldValue":"部门1","fieldType":"3"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptName","fieldValue":"测试部门1","fieldType":"2"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptId","fieldValue":105,"fieldType":"1"}],
|
|
|
|
|
|
|
|
// [{"fieldKey":"relateTableDesc","fieldValue":null,"fieldType":"3"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptName","fieldValue":"测试部门2","fieldType":"2"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptId","fieldValue":"1952572898270183426","fieldType":"1"}]
|
|
|
|
|
|
|
|
// ]
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// processData(testData);
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// const testData1 = [
|
|
|
|
|
|
|
|
// [{"fieldKey":"relateTableDesc","fieldValue":"部门2","fieldType":"3"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptName","fieldValue":"测试部门11","fieldType":"2"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptId","fieldValue":1051,"fieldType":"1"}],
|
|
|
|
|
|
|
|
// [{"fieldKey":"relateTableDesc","fieldValue":null,"fieldType":"3"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptName","fieldValue":"测试部门22","fieldType":"2"},
|
|
|
|
|
|
|
|
// {"fieldKey":"deptId","fieldValue":"19525728982701834261","fieldType":"1"}]
|
|
|
|
|
|
|
|
// ]
|
|
|
|
|
|
|
|
// processData(testData1);
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
let drag = false
|
|
|
|
|
|
|
|
let offsetX = 0
|
|
|
|
|
|
|
|
let offsetY = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onDragStart(e: MouseEvent) {
|
|
|
|
|
|
|
|
drag = true
|
|
|
|
|
|
|
|
offsetX = e.clientX - parseInt(floatStyle.left)
|
|
|
|
|
|
|
|
offsetY = e.clientY - parseInt(floatStyle.top)
|
|
|
|
|
|
|
|
document.addEventListener('mousemove', onDragging)
|
|
|
|
|
|
|
|
document.addEventListener('mouseup', onDragEnd)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onDragging(e: MouseEvent) {
|
|
|
|
|
|
|
|
if (!drag) return
|
|
|
|
|
|
|
|
let newLeft = e.clientX - offsetX
|
|
|
|
|
|
|
|
let newTop = e.clientY - offsetY
|
|
|
|
|
|
|
|
// 限制在屏幕内
|
|
|
|
|
|
|
|
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - 440))
|
|
|
|
|
|
|
|
newTop = Math.max(0, Math.min(newTop, window.innerHeight - 100))
|
|
|
|
|
|
|
|
floatStyle.left = newLeft + 'px'
|
|
|
|
|
|
|
|
floatStyle.top = newTop + 'px'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onDragEnd() {
|
|
|
|
|
|
|
|
drag = false
|
|
|
|
|
|
|
|
document.removeEventListener('mousemove', onDragging)
|
|
|
|
|
|
|
|
document.removeEventListener('mouseup', onDragEnd)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
document.removeEventListener('mousemove', onDragging)
|
|
|
|
|
|
|
|
document.removeEventListener('mouseup', onDragEnd)
|
|
|
|
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 组件卸载时清理资源
|
|
|
|
// 组件卸载时清理资源
|
|
|
|
@ -865,22 +1047,40 @@ function scrollToBottom() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保证所有方法直接在<script setup>中定义
|
|
|
|
// 保证所有方法直接在<script setup>中定义
|
|
|
|
function handleSelectOption(opt: string) {
|
|
|
|
function handleSelectOption(opt, index) {
|
|
|
|
|
|
|
|
if (index === undefined || index === null) {
|
|
|
|
|
|
|
|
index = selectDialogListIndex.value
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let selectList = selectLists.value[index];
|
|
|
|
selectedOption.value = opt
|
|
|
|
selectedOption.value = opt
|
|
|
|
|
|
|
|
let selectFormOptions = selectList.formOptions;
|
|
|
|
|
|
|
|
const subArray = selectFormOptions[opt.subArrayIndex];
|
|
|
|
|
|
|
|
const newValueJson = {[subArray.fieldKey]: subArray.fieldValue}
|
|
|
|
|
|
|
|
sharedStore.updateDynamicValue({
|
|
|
|
|
|
|
|
message: newValueJson,
|
|
|
|
|
|
|
|
timestamp: new Date().toLocaleString()
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// currentProps.value= newValueJson;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openSelectDialog(list: ChatSelectList) {
|
|
|
|
function openSelectDialog(list: ChatSelectList,listIndex:number) {
|
|
|
|
selectDialogList.value = list
|
|
|
|
selectDialogList.value = list
|
|
|
|
selectDialog.value = true
|
|
|
|
selectDialog.value = true
|
|
|
|
|
|
|
|
selectDialogListIndex.value = listIndex;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatMsg(text: string) {
|
|
|
|
function formatMsg(text: string) {
|
|
|
|
// 转换换行符为<br>,并做简单转义防XSS
|
|
|
|
// 转换换行符为<br>,并做简单转义防XSS
|
|
|
|
return text
|
|
|
|
if (text) {
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
return text
|
|
|
|
.replace(/</g, '<')
|
|
|
|
.replace(/&/g, '&')
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
.replace(/</g, '<')
|
|
|
|
.replace(/\n/g, '<br>')
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
|
|
|
.replace(/\n/g, '<br>')
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onInputKeydown(e: KeyboardEvent) {
|
|
|
|
function onInputKeydown(e: KeyboardEvent) {
|
|
|
|
@ -895,19 +1095,172 @@ function close() {
|
|
|
|
state.isShowSearch = false
|
|
|
|
state.isShowSearch = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索弹窗关闭
|
|
|
|
// 新增:拖拽相关状态
|
|
|
|
const closeSearch = () => {
|
|
|
|
const isDragging = ref(false);
|
|
|
|
state.isShowSearch = false;
|
|
|
|
const isResizing = ref(false);
|
|
|
|
};
|
|
|
|
const windowPosition = ref({ x: 'auto', y: 0 });
|
|
|
|
|
|
|
|
const isSmallSize = ref(false);
|
|
|
|
|
|
|
|
const windowSize = ref({ width: '24%', height: '100vh' });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const startPos = ref({ x: 0, y: 0 });
|
|
|
|
|
|
|
|
const startSize = ref({ width: 0, height: 0 });
|
|
|
|
|
|
|
|
const isCustomPosition = ref(false); // 新增:标记是否为自定义位置
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:拖拽功能实现
|
|
|
|
|
|
|
|
function startDrag(e: MouseEvent) {
|
|
|
|
|
|
|
|
if (isResizing.value) return;
|
|
|
|
|
|
|
|
isDragging.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取窗口当前实际位置
|
|
|
|
|
|
|
|
const windowElement = document.querySelector('.ai-float-window') as HTMLElement;
|
|
|
|
|
|
|
|
if (windowElement) {
|
|
|
|
|
|
|
|
const rect = windowElement.getBoundingClientRect();
|
|
|
|
|
|
|
|
startPos.value = {
|
|
|
|
|
|
|
|
x: e.clientX - rect.left,
|
|
|
|
|
|
|
|
y: e.clientY - rect.top
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 标记为自定义位置
|
|
|
|
|
|
|
|
isCustomPosition.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('mousemove', onDrag);
|
|
|
|
|
|
|
|
document.addEventListener('mouseup', stopDrag);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onDrag(e: MouseEvent) {
|
|
|
|
|
|
|
|
if (!isDragging.value) return;
|
|
|
|
|
|
|
|
const newX = e.clientX - startPos.value.x;
|
|
|
|
|
|
|
|
const newY = e.clientY - startPos.value.y;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 确保窗口不会超出视口边界(留出一些边距)
|
|
|
|
|
|
|
|
const maxX = window.innerWidth - 200; // 最小宽度200px
|
|
|
|
|
|
|
|
const maxY = window.innerHeight - 100; // 最小高度100px
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
windowPosition.value = {
|
|
|
|
|
|
|
|
x: Math.max(0, Math.min(newX, maxX)),
|
|
|
|
|
|
|
|
y: Math.max(0, Math.min(newY, maxY))
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 当窗口被拖动时,不再固定在右侧
|
|
|
|
|
|
|
|
if (currentSize.value === 'menu' || currentSize.value === 'form' || currentSize.value === 'minimize') {
|
|
|
|
|
|
|
|
currentSize.value = 'custom';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function stopDrag() {
|
|
|
|
|
|
|
|
isDragging.value = false;
|
|
|
|
|
|
|
|
document.removeEventListener('mousemove', onDrag);
|
|
|
|
|
|
|
|
document.removeEventListener('mouseup', stopDrag);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ... existing code ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:调整大小功能实现
|
|
|
|
|
|
|
|
function startResize(e: MouseEvent) {
|
|
|
|
|
|
|
|
isResizing.value = true;
|
|
|
|
|
|
|
|
startPos.value = { x: e.clientX, y: e.clientY };
|
|
|
|
|
|
|
|
startSize.value = {
|
|
|
|
|
|
|
|
width: window.innerWidth * parseFloat(windowSize.value.width) / 100,
|
|
|
|
|
|
|
|
height: window.innerHeight * parseFloat(windowSize.value.height) / 100
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('mousemove', onResize);
|
|
|
|
|
|
|
|
document.addEventListener('mouseup', stopResize);
|
|
|
|
|
|
|
|
e.preventDefault(); // 防止拖动时选中文本
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onResize(e: MouseEvent) {
|
|
|
|
|
|
|
|
if (!isResizing.value) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const deltaX = e.clientX - startPos.value.x;
|
|
|
|
|
|
|
|
const deltaY = e.clientY - startPos.value.y;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算新的宽度和高度,确保最小尺寸
|
|
|
|
|
|
|
|
let newWidth = startSize.value.width + deltaX;
|
|
|
|
|
|
|
|
let newHeight = startSize.value.height + deltaY;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
newWidth = Math.max(200, newWidth); // 最小宽度200px
|
|
|
|
|
|
|
|
newHeight = Math.max(300, newHeight); // 最小高度300px
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为视口百分比
|
|
|
|
|
|
|
|
windowSize.value = {
|
|
|
|
|
|
|
|
width: `${(newWidth / window.innerWidth) * 100}%`,
|
|
|
|
|
|
|
|
height: `${(newHeight / window.innerHeight) * 100}%`
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 当窗口大小被调整时,不再使用预设尺寸
|
|
|
|
|
|
|
|
if (currentSize.value !== 'custom') {
|
|
|
|
|
|
|
|
currentSize.value = 'custom';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function stopResize() {
|
|
|
|
|
|
|
|
isResizing.value = false;
|
|
|
|
|
|
|
|
document.removeEventListener('mousemove', onResize);
|
|
|
|
|
|
|
|
document.removeEventListener('mouseup', stopResize);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:切换预设尺寸时更新窗口大小
|
|
|
|
|
|
|
|
watch(currentSize, (newSize) => {
|
|
|
|
|
|
|
|
if (newSize === 'menu') {
|
|
|
|
|
|
|
|
windowSize.value = { width: '24%', height: '100vh' };
|
|
|
|
|
|
|
|
windowPosition.value = { x: 'auto', y: 0 };
|
|
|
|
|
|
|
|
} else if (newSize === 'form') {
|
|
|
|
|
|
|
|
windowSize.value = { width: '80%', height: '100vh' };
|
|
|
|
|
|
|
|
windowPosition.value = { x: 'auto', y: 0 };
|
|
|
|
|
|
|
|
} else if(newSize === 'minimize'){
|
|
|
|
|
|
|
|
windowSize.value = { width: '24%', height: '360px' };
|
|
|
|
|
|
|
|
windowPosition.value = { x: 'auto', y: 0 };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// const windowSize = computed(() => {
|
|
|
|
|
|
|
|
// if (isSmallSize.value) {
|
|
|
|
|
|
|
|
// return {
|
|
|
|
|
|
|
|
// width: '100px',
|
|
|
|
|
|
|
|
// height: '100px',
|
|
|
|
|
|
|
|
// top: 'auto',
|
|
|
|
|
|
|
|
// bottom: '20px',
|
|
|
|
|
|
|
|
// left: 'auto',
|
|
|
|
|
|
|
|
// right: '20px',
|
|
|
|
|
|
|
|
// borderRadius: '8px',
|
|
|
|
|
|
|
|
// overflow: 'hidden'
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }else{
|
|
|
|
|
|
|
|
// return {
|
|
|
|
|
|
|
|
// width: '24%', height: '100vh'
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// return {}
|
|
|
|
|
|
|
|
// })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:调整窗口大小
|
|
|
|
|
|
|
|
function toggleSize() {
|
|
|
|
|
|
|
|
if(currentSize.value === 'minimize'){
|
|
|
|
|
|
|
|
if (!selectedForm.value) {//没有选择form时,原始大小
|
|
|
|
|
|
|
|
currentSize.value = 'menu';
|
|
|
|
|
|
|
|
}else{
|
|
|
|
|
|
|
|
if(selectedForm.value.dialogVisibleVariable && selectedForm.value.dialogVisibleVariable !== ''){
|
|
|
|
|
|
|
|
currentSize.value = 'menu'
|
|
|
|
|
|
|
|
} else{
|
|
|
|
|
|
|
|
currentSize.value = 'form'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}else{
|
|
|
|
|
|
|
|
currentSize.value = 'minimize'
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="less">
|
|
|
|
<style scoped lang="less">
|
|
|
|
.el-message {
|
|
|
|
|
|
|
|
z-index:8006 !important;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.ai-float-fullscreen {
|
|
|
|
.ai-float-fullscreen {
|
|
|
|
position: fixed;
|
|
|
|
position: fixed;
|
|
|
|
left: 0;
|
|
|
|
left: 0;
|
|
|
|
@ -929,13 +1282,121 @@ const closeSearch = () => {
|
|
|
|
user-select: none;
|
|
|
|
user-select: none;
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
flex-direction: column;
|
|
|
|
height: 100vh;
|
|
|
|
height: 99vh;
|
|
|
|
width: 60vw;
|
|
|
|
max-height:99vh;
|
|
|
|
right: 0;
|
|
|
|
right: 0;
|
|
|
|
left: auto;
|
|
|
|
left: auto;
|
|
|
|
top: 0;
|
|
|
|
top: 0;
|
|
|
|
position: fixed;
|
|
|
|
position: fixed;
|
|
|
|
z-index: 7005;
|
|
|
|
z-index: 7005;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 修复动态样式
|
|
|
|
|
|
|
|
&.size-custom {
|
|
|
|
|
|
|
|
right: v-bind('isCustomPosition ? "auto" : "0"');
|
|
|
|
|
|
|
|
left: v-bind('isCustomPosition ? windowPosition.x + "px" : "auto"');
|
|
|
|
|
|
|
|
top: v-bind('isCustomPosition ? windowPosition.y + "px" : "0"');
|
|
|
|
|
|
|
|
width: v-bind('windowSize.width');
|
|
|
|
|
|
|
|
height: v-bind('windowSize.height');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
&.size-minimized {
|
|
|
|
|
|
|
|
right: auto !important;
|
|
|
|
|
|
|
|
left: v-bind('windowPosition.x + "px"') !important;
|
|
|
|
|
|
|
|
top: v-bind('windowPosition.y + "px"') !important;
|
|
|
|
|
|
|
|
width: v-bind('windowSize.width + "px"') !important;
|
|
|
|
|
|
|
|
height: v-bind('windowSize.height + "px"') !important;
|
|
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:最小化状态下隐藏内容
|
|
|
|
|
|
|
|
.size-minimized .ai-float-main,
|
|
|
|
|
|
|
|
.size-minimized .ai-float-form-list,
|
|
|
|
|
|
|
|
.size-minimized .ai-float-header > div:first-child {
|
|
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 新增:最小化按钮样式
|
|
|
|
|
|
|
|
.size-minimized .ai-float-header {
|
|
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
|
|
cursor: default;
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 确保头部右侧按钮组正确显示 */
|
|
|
|
|
|
|
|
.ai-float-header-actions {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 优化最小化按钮样式
|
|
|
|
|
|
|
|
.ai-float-minimize {
|
|
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
color: #888;
|
|
|
|
|
|
|
|
transition: color 0.2s;
|
|
|
|
|
|
|
|
margin-right: 12px;
|
|
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.size-form {
|
|
|
|
|
|
|
|
width: 80vw;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.size-menu {
|
|
|
|
|
|
|
|
width: 24%; //80vw的30%。跟下面al-float-right的flex定义匹配宽度
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.size-minimize {
|
|
|
|
|
|
|
|
width: 24%;
|
|
|
|
|
|
|
|
height:360px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.ai-float-minimize:hover {
|
|
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 最小化状态下隐藏关闭按钮
|
|
|
|
|
|
|
|
.size-minimized .ai-float-close {
|
|
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 优化调整大小的句柄,使用通用图标样式
|
|
|
|
|
|
|
|
.ai-float-resize-handle {
|
|
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
|
|
right: 4px;
|
|
|
|
|
|
|
|
bottom: 4px;
|
|
|
|
|
|
|
|
width: 16px;
|
|
|
|
|
|
|
|
height: 16px;
|
|
|
|
|
|
|
|
border-radius: 2px;
|
|
|
|
|
|
|
|
cursor: se-resize;
|
|
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
|
|
transition: opacity 0.2s;
|
|
|
|
|
|
|
|
z-index: 10;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用CSS创建通用的调整大小图标
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
|
|
right: -2px;
|
|
|
|
|
|
|
|
bottom: -2px;
|
|
|
|
|
|
|
|
width: 0;
|
|
|
|
|
|
|
|
height: 0;
|
|
|
|
|
|
|
|
border-style: solid;
|
|
|
|
|
|
|
|
border-width: 0 8px 8px 0;
|
|
|
|
|
|
|
|
border-color: transparent #fff transparent transparent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.ai-float-resize-handle:hover {
|
|
|
|
|
|
|
|
opacity: 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-float-main {
|
|
|
|
.ai-float-main {
|
|
|
|
@ -945,7 +1406,6 @@ const closeSearch = () => {
|
|
|
|
|
|
|
|
|
|
|
|
.ai-float-left {
|
|
|
|
.ai-float-left {
|
|
|
|
flex: 7 7 0;
|
|
|
|
flex: 7 7 0;
|
|
|
|
min-width: 400px;
|
|
|
|
|
|
|
|
background: #f7f8fa;
|
|
|
|
background: #f7f8fa;
|
|
|
|
border-right: 1px solid #e0e7ef;
|
|
|
|
border-right: 1px solid #e0e7ef;
|
|
|
|
overflow: auto;
|
|
|
|
overflow: auto;
|
|
|
|
@ -955,11 +1415,13 @@ const closeSearch = () => {
|
|
|
|
.ai-float-right {
|
|
|
|
.ai-float-right {
|
|
|
|
flex: 3 3 0;
|
|
|
|
flex: 3 3 0;
|
|
|
|
min-width: 260px;
|
|
|
|
min-width: 260px;
|
|
|
|
|
|
|
|
width: 500px;
|
|
|
|
height: 100%;
|
|
|
|
height: 100%;
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
flex-direction: column;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调整头部按钮区域布局
|
|
|
|
.ai-float-header {
|
|
|
|
.ai-float-header {
|
|
|
|
display: flex;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
align-items: center;
|
|
|
|
@ -971,6 +1433,12 @@ const closeSearch = () => {
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调整头部右侧按钮组
|
|
|
|
|
|
|
|
.ai-float-header-actions {
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ai-float-welcome {
|
|
|
|
.ai-float-welcome {
|
|
|
|
font-size: 16px;
|
|
|
|
font-size: 16px;
|
|
|
|
color: #409eff;
|
|
|
|
color: #409eff;
|
|
|
|
@ -1201,5 +1669,14 @@ const closeSearch = () => {
|
|
|
|
background: linear-gradient(135deg, #e0e7ef 0%, #d0eaff 100%);
|
|
|
|
background: linear-gradient(135deg, #e0e7ef 0%, #d0eaff 100%);
|
|
|
|
color: #409eff;
|
|
|
|
color: #409eff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.recording-btn {
|
|
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.recording-btn.recording {
|
|
|
|
|
|
|
|
color: #f56c6c;
|
|
|
|
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|