1.5.7前端

AI表单快捷填报完善:
用户管理填报支持,可拖拽调整大小和位置,可最小化和恢复初始大小;
AI表单设置完善
master
xs 3 months ago
parent a0c49b7175
commit 835b1f31ca

@ -44,6 +44,8 @@ const openAiAssistantMenu = () => {
right: 10px; right: 10px;
width: 24px; width: 24px;
height: 24px; height: 24px;
cursor: pointer;
z-index:7000;
} }
.el-message { .el-message {

@ -93,6 +93,11 @@ export interface AiFormSettingForm extends BaseEntity {
*/ */
formPath?: string; formPath?: string;
/**
* (12)
*/
formLoadType?: string;
/** /**
* *
*/ */

@ -0,0 +1,33 @@
import { defineStore } from 'pinia'
export const useSharedDataStore = defineStore('sharedData', {
state: () => ({
// 定义需要共享的数据
dynamicValue: null,
// 可以定义多个共享状态
otherData: '',
// 可以添加时间戳以便监听更新
lastUpdated: null
}),
actions: {
// 更新数据的方法
updateDynamicValue(value) {
this.dynamicValue = value
this.lastUpdated = new Date().toISOString()
},
// 清空数据
clearData() {
this.dynamicValue = null
this.otherData = ''
}
},
getters: {
// 可以添加计算属性
formattedValue() {
return this.dynamicValue ? JSON.stringify(this.dynamicValue) : '无数据'
}
}
})

@ -0,0 +1,115 @@
/**
*
* @param sourceObject
* @returns
*
* @description
* nullundefined('')
*
*/
export function filterEmptyValues<T extends object>(sourceObject: T): Partial<T> {
const result: Partial<T> = {};
// 遍历源对象的所有属性
Object.keys(sourceObject).forEach(key => {
const value = (sourceObject as any)[key];
// 检查值是否不为空
if (value !== null && value !== undefined && value !== '') {
// 如果是对象且不是数组,递归处理
if (typeof value === 'object' && !Array.isArray(value)) {
// 递归过滤嵌套对象
const filteredNestedObject = filterEmptyValues(value);
// 只有当过滤后的嵌套对象有属性时才添加
if (Object.keys(filteredNestedObject).length > 0) {
(result as any)[key] = filteredNestedObject;
}
} else {
// 对于非对象类型或数组,直接添加
(result as any)[key] = value;
}
}
});
return result;
}
/**
*
* @param sourceObject
* @param options
* @returns
*
* @description
*/
export function filterEmptyValuesAdvanced<T extends object>(
sourceObject: T,
options: {
// 是否将空数组视为空值
treatEmptyArrayAsEmpty?: boolean;
// 是否将空对象视为空值
treatEmptyObjectAsEmpty?: boolean;
// 是否将0视为空值
treatZeroAsEmpty?: boolean;
// 是否将false视为空值
treatFalseAsEmpty?: boolean;
} = {}
): Partial<T> {
const {
treatEmptyArrayAsEmpty = false,
treatEmptyObjectAsEmpty = false,
treatZeroAsEmpty = false,
treatFalseAsEmpty = false
} = options;
const result: Partial<T> = {};
Object.keys(sourceObject).forEach(key => {
const value = (sourceObject as any)[key];
// 基本空值检查
let isEmpty = value === null || value === undefined || value === '';
// 根据选项进行额外的空值检查
if (!isEmpty && treatZeroAsEmpty && value === 0) {
isEmpty = true;
}
if (!isEmpty && treatFalseAsEmpty && value === false) {
isEmpty = true;
}
if (!isEmpty && treatEmptyArrayAsEmpty && Array.isArray(value) && value.length === 0) {
isEmpty = true;
}
if (!isEmpty && treatEmptyObjectAsEmpty && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) {
isEmpty = true;
}
// 如果值不为空,处理并添加到结果中
if (!isEmpty) {
if (typeof value === 'object' && !Array.isArray(value)) {
const filteredNestedObject = filterEmptyValuesAdvanced(value, options);
if (Object.keys(filteredNestedObject).length > 0) {
(result as any)[key] = filteredNestedObject;
}
} else {
(result as any)[key] = value;
}
}
});
return result;
}
/**
*
* @param source
* @param target
* @returns
*/
export function copyNonEmptyValues<T extends object, U extends object>(source: T, target: U): U & Partial<T> {
const filteredValues = filterEmptyValues(source);
return Object.assign(target, filteredValues) as U & Partial<T>;
}

@ -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}`
);
// reactivestate // reactivestate
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([]);
// fieldType1,使
const type1Elements = ref([]);
// fieldType2使
const type2Elements = ref([]);
// fieldType2
const selectedType2Elements = ref([]);
// fieldType1
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访
// labelvalue
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, '&amp;') return text
.replace(/</g, '&lt;') .replace(/&/g, '&amp;')
.replace(/>/g, '&gt;') .replace(/</g, '&lt;')
.replace(/\n/g, '<br>') .replace(/>/g, '&gt;')
.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%; //80vw30%al-float-rightflex
}
.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>

@ -52,7 +52,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="路径" align="center" prop="formPath" :show-overflow-tooltip="true"/> <el-table-column label="路径" align="center" prop="formPath" :show-overflow-tooltip="true"/>
<el-table-column label="弹窗变量" align="center" prop="dialogVisibleVariable" :show-overflow-tooltip="true"/> <el-table-column label="加载类型" align="center" prop="formLoadType" :show-overflow-tooltip="true">
<template #default="scope">
<dict-tag :options="ai_form_setting_load_type" :value="scope.row.formLoadType"/>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200"> <el-table-column label="操作" align="center" width="200">
<template #default="scope"> <template #default="scope">
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top">
@ -70,7 +74,7 @@
</el-card> </el-card>
<!-- 设置弹窗 --> <!-- 设置弹窗 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="60%" top="5vh" append-to-body> <el-dialog :title="dialog.title" v-model="dialog.visible" width="80vw" top="5vh" append-to-body>
<el-form :model="form" ref="aiFormSettingFormRef" :rules="rules" label-width="100px"> <el-form :model="form" ref="aiFormSettingFormRef" :rules="rules" label-width="100px">
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
@ -115,8 +119,15 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="弹窗变量" prop="dialogVisibleVariable"> <el-form-item label="加载类型" prop="formLoadType">
<el-input v-model="form.dialogVisibleVariable" placeholder="请输入表单弹窗变量名称"/> <el-select v-model="form.formLoadType" placeholder="请选择加载类型" clearable>
<el-option
v-for="dict in ai_form_setting_load_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -131,9 +142,9 @@
<el-table :data="form.aiFormSettingDetailList" border height="40vh" v-loading="columnLoading"> <el-table :data="form.aiFormSettingDetailList" border height="40vh" v-loading="columnLoading">
<el-table-column label="字段名称" prop="fieldName"/> <el-table-column label="字段名称" prop="fieldName"/>
<el-table-column label="字段注释" prop="fieldDesc" type="textarea"> <el-table-column label="字段注释" prop="fieldDesc" >
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.fieldDesc"/> <el-input v-model="scope.row.fieldDesc" type="textarea"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="表单prop" prop="propName"> <el-table-column label="表单prop" prop="propName">
@ -149,6 +160,43 @@
</el-radio-group> </el-radio-group>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="字段类型">
<template #default="scope">
<el-select v-model="scope.row.fieldType" placeholder="请选择字段类型" clearable>
<el-option
v-for="dict in ai_form_setting_field_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="字段样式">
<template #default="scope">
<el-select v-model="scope.row.fieldStyle" placeholder="请选择字段样式" clearable>
<el-option
v-for="dict in ai_form_setting_field_style"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="获取字段" prop="relateTableFields" >
<template #default="scope">
<el-input v-model="scope.row.relateTableFields" type="textarea" placeholder="filedType是1的代表赋值filedType是2的代表显示例:[{fieldName:'dept_id',formProp:'deptId',fieldType:'1'},{fieldName:'dept_name',formProp:'deptName',fieldType:'2'}]"/>
</template>
</el-table-column>
<el-table-column label="过滤条件" prop="relateFilterCondition" >
<template #default="scope">
<el-input v-model="scope.row.relateFilterCondition" type="textarea" placeholder="例:[{fieldKey:'delFlag',fieldValue:'0',conditionSymbol:'='}]"/>
</template>
</el-table-column>
</el-table> </el-table>
<template #footer> <template #footer>
@ -197,8 +245,8 @@ import {listAiFormSetting, getAiFormSetting, delAiFormSetting, addAiFormSetting,
import {AiFormSettingForm,AiFormSettingVO,AiFormSettingDetailVO,AiFormSettingQuery} from '@/api/ai/base/aiFormSetting/types'; import {AiFormSettingForm,AiFormSettingVO,AiFormSettingDetailVO,AiFormSettingQuery} from '@/api/ai/base/aiFormSetting/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { const {
ai_form_setting_flag,ai_form_setting_client_type ai_form_setting_flag,ai_form_setting_client_type,ai_form_setting_field_type,ai_form_setting_field_style,ai_form_setting_load_type
} = toRefs<any>(proxy?.useDict('ai_form_setting_flag','ai_form_setting_client_type')); } = toRefs<any>(proxy?.useDict('ai_form_setting_flag','ai_form_setting_client_type','ai_form_setting_field_type','ai_form_setting_field_style','ai_form_setting_load_type'));
const aiFormSettingList = ref<AiFormSettingVO[]>([]); const aiFormSettingList = ref<AiFormSettingVO[]>([]);
const loading = ref(false); const loading = ref(false);
@ -229,6 +277,7 @@ const initFormData: AiFormSettingForm = {
tableName: undefined, tableName: undefined,
formName: undefined, formName: undefined,
formPath: undefined, formPath: undefined,
formLoadType:undefined,
dialogVisibleVariable: undefined, dialogVisibleVariable: undefined,
subTableName: undefined, subTableName: undefined,
subTableFkName: undefined, subTableFkName: undefined,
@ -266,6 +315,9 @@ const data = reactive<PageData<AiFormSettingForm, AiFormSettingQuery>>({
clientType: [ clientType: [
{ required: true, message: "客户端类型不能为空", trigger: "change" } { required: true, message: "客户端类型不能为空", trigger: "change" }
], ],
loadType: [
{ required: true, message: "加载类型不能为空", trigger: "change" }
],
} }
}); });

@ -1,18 +1,21 @@
<template> <template>
<div class="p-2">
<el-form :model="form" label-width="100px" class="dataset-form" style="margin-bottom: 0;"> <el-form :model="form" label-width="100px" class="dataset-form" style="margin-bottom: 0;">
<div class="form-row"> <div class="form-row">
<el-form-item label="数据集名称"> <el-form-item label="数据集名称">
<el-input v-model="form.name" placeholder="请输入数据集名称" /> <el-input v-model="form.name" placeholder="请输入数据集名称"/>
</el-form-item> </el-form-item>
<el-form-item label="数据源"> <el-form-item label="数据源">
<el-select v-model="form.datasource" placeholder="请选择数据源" style="width: 240px;"> <el-select v-model="form.datasource" placeholder="请选择数据源" style="width: 240px;">
<el-option v-for="ds in datasourceList" :key="ds.value" :label="ds.label" :value="ds.value" /> <el-option v-for="ds in datasourceList" :key="ds.value" :label="ds.label" :value="ds.value"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="是否分页"> <el-form-item label="是否分页">
<el-switch v-model="form.pagination" /> <el-switch v-model="form.pagination"/>
<el-tooltip content="开启分页后,查询结果将按页返回,适用于大数据量场景。" placement="top"> <el-tooltip content="开启分页后,查询结果将按页返回,适用于大数据量场景。" placement="top">
<el-icon style="margin-left:4px;cursor:pointer;"><QuestionFilled /></el-icon> <el-icon style="margin-left:4px;cursor:pointer;">
<QuestionFilled/>
</el-icon>
</el-tooltip> </el-tooltip>
</el-form-item> </el-form-item>
</div> </div>
@ -26,7 +29,11 @@
style="flex:1" style="flex:1"
/> />
<el-button type="primary" @click="showAiDialog = true" style="margin-left:8px;"> <el-button type="primary" @click="showAiDialog = true" style="margin-left:8px;">
<svg width="20" height="20" viewBox="0 0 1024 1024"><circle cx="512" cy="512" r="512" fill="#409EFF"/><text x="50%" y="60%" text-anchor="middle" fill="#fff" font-size="400" font-family="Arial" dy=".3em">AI</text></svg> <svg width="20" height="20" viewBox="0 0 1024 1024">
<circle cx="512" cy="512" r="512" fill="#409EFF"/>
<text x="50%" y="60%" text-anchor="middle" fill="#fff" font-size="400" font-family="Arial" dy=".3em">AI
</text>
</svg>
</el-button> </el-button>
</div> </div>
<div class="sql-desc"> <div class="sql-desc">
@ -43,7 +50,9 @@
<el-tabs v-model="activeTab" style="margin: 16px 0 0 10px;"> <el-tabs v-model="activeTab" style="margin: 16px 0 0 10px;">
<el-tab-pane label="列表字段" name="fields"> <el-tab-pane label="列表字段" name="fields">
<div style="margin-bottom:8px;display:flex;align-items:center;"> <div style="margin-bottom:8px;display:flex;align-items:center;">
<el-button type="danger" size="small" :disabled="!multipleSelection.length" @click="batchRemoveFields"></el-button> <el-button type="danger" size="small" :disabled="!multipleSelection.length" @click="batchRemoveFields">
批量删除
</el-button>
</div> </div>
<el-table <el-table
:data="fields" :data="fields"
@ -52,34 +61,34 @@
ref="fieldsTableRef" ref="fieldsTableRef"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
> >
<el-table-column type="selection" width="50" /> <el-table-column type="selection" width="50"/>
<el-table-column prop="name" label="字段名" min-width="120"> <el-table-column prop="name" label="字段名" min-width="120">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.name" size="small" /> <el-input v-model="scope.row.name" size="small"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="text" label="字段文本" min-width="120"> <el-table-column prop="text" label="字段文本" min-width="120">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.text" size="small" /> <el-input v-model="scope.row.text" size="small"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="type" label="字段类型" min-width="100"> <el-table-column prop="type" label="字段类型" min-width="100">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.type" size="small" style="width:100px"> <el-select v-model="scope.row.type" size="small" style="width:100px">
<el-option label="文本" value="varchar" /> <el-option label="文本" value="varchar"/>
<el-option label="数值" value="int" /> <el-option label="数值" value="int"/>
<el-option label="日期" value="date" /> <el-option label="日期" value="date"/>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="order" label="排序" min-width="80"> <el-table-column prop="order" label="排序" min-width="80">
<template #default="scope"> <template #default="scope">
<el-input-number v-model="scope.row.order" :min="1" size="small" style="width:80px" /> <el-input-number v-model="scope.row.order" :min="1" size="small" style="width:80px"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="dictCode" label="字典编码" min-width="120"> <el-table-column prop="dictCode" label="字典编码" min-width="120">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.dictCode" size="small" /> <el-input v-model="scope.row.dictCode" size="small"/>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -87,7 +96,9 @@
<el-tab-pane label="参数字段" name="params"> <el-tab-pane label="参数字段" name="params">
<div style="margin-bottom:8px;display:flex;align-items:center;"> <div style="margin-bottom:8px;display:flex;align-items:center;">
<el-button type="primary" size="small" @click="addParam"></el-button> <el-button type="primary" size="small" @click="addParam"></el-button>
<el-button type="danger" size="small" :disabled="!multipleParamSelection.length" @click="batchRemoveParams" style="margin-left:8px;">批量删除</el-button> <el-button type="danger" size="small" :disabled="!multipleParamSelection.length" @click="batchRemoveParams"
style="margin-left:8px;">批量删除
</el-button>
</div> </div>
<el-table <el-table
:data="params" :data="params"
@ -96,29 +107,29 @@
ref="paramsTableRef" ref="paramsTableRef"
@selection-change="handleParamSelectionChange" @selection-change="handleParamSelectionChange"
> >
<el-table-column type="selection" width="50" /> <el-table-column type="selection" width="50"/>
<el-table-column prop="param" label="参数" min-width="120"> <el-table-column prop="param" label="参数" min-width="120">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.param" size="small" /> <el-input v-model="scope.row.param" size="small"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="text" label="参数文本" min-width="120"> <el-table-column prop="text" label="参数文本" min-width="120">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.text" size="small" /> <el-input v-model="scope.row.text" size="small"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="type" label="类型" min-width="100"> <el-table-column prop="type" label="类型" min-width="100">
<template #default="scope"> <template #default="scope">
<el-select v-model="scope.row.type" size="small" style="width:100px"> <el-select v-model="scope.row.type" size="small" style="width:100px">
<el-option label="文本" value="varchar" /> <el-option label="文本" value="varchar"/>
<el-option label="数值" value="int" /> <el-option label="数值" value="int"/>
<el-option label="日期" value="date" /> <el-option label="日期" value="date"/>
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="defaultValue" label="默认值" min-width="120"> <el-table-column prop="defaultValue" label="默认值" min-width="120">
<template #default="scope"> <template #default="scope">
<el-input v-model="scope.row.defaultValue" size="small" /> <el-input v-model="scope.row.defaultValue" size="small"/>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -126,10 +137,11 @@
<el-tab-pane label="数据预览" name="preview"> <el-tab-pane label="数据预览" name="preview">
<div style="display:flex;align-items:center;margin-bottom:8px;"> <div style="display:flex;align-items:center;margin-bottom:8px;">
<el-button type="primary" size="small" @click="fetchPreviewData"></el-button> <el-button type="primary" size="small" @click="fetchPreviewData"></el-button>
<span style="margin-left:8px;padding:2px 14px;background:#ecf5ff;color:#409EFF;font-weight:bold;border-radius:4px;font-size:14px;">仅预览前10条数据</span> <span
style="margin-left:8px;padding:2px 14px;background:#ecf5ff;color:#409EFF;font-weight:bold;border-radius:4px;font-size:14px;">仅预览前10条数据</span>
</div> </div>
<el-table :data="previewData" style="width: 100%" border v-if="previewData.length"> <el-table :data="previewData" style="width: 100%" border v-if="previewData.length">
<el-table-column v-for="col in previewColumns" :key="col.prop" :prop="col.prop" :label="col.label" /> <el-table-column v-for="col in previewColumns" :key="col.prop" :prop="col.prop" :label="col.label"/>
</el-table> </el-table>
<div v-else style="color:#888;text-align:center;padding:24px 0;">暂无数据</div> <div v-else style="color:#888;text-align:center;padding:24px 0;">暂无数据</div>
</el-tab-pane> </el-tab-pane>
@ -148,7 +160,7 @@
placeholder="请输入需求描述查询年龄大于35的用户信息包括所在部门的名称" placeholder="请输入需求描述查询年龄大于35的用户信息包括所在部门的名称"
style="margin-bottom:12px;" style="margin-bottom:12px;"
/> />
<el-button type="primary" @click="generateSql" :loading="aiLoading">生成SQL</el-button> <el-button type="primary" @click="getSql" :loading="aiLoading">生成SQL</el-button>
<el-input <el-input
v-model="aiSql" v-model="aiSql"
type="textarea" type="textarea"
@ -161,13 +173,18 @@
<el-button type="primary" @click="replaceSql">SQL</el-button> <el-button type="primary" @click="replaceSql">SQL</el-button>
</div> </div>
</el-dialog> </el-dialog>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, watch } from 'vue' import {ref, reactive, watch} from 'vue'
import { ElMessage } from 'element-plus' import {ElMessage} from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue' import {QuestionFilled} from '@element-plus/icons-vue'
import {aiSqlAsk} from "@/api/ai/skill/aiChat/index"; import {generateSql} from "@/api/ai/skill/aiChat/index";
const {proxy} = getCurrentInstance() as ComponentInternalInstance;
const form = reactive({ const form = reactive({
name: '', name: '',
@ -176,8 +193,8 @@ const form = reactive({
pagination: true pagination: true
}) })
const datasourceList = ref([ const datasourceList = ref([
{ label: '数据源1', value: 'ds1' }, {label: '数据源1', value: 'ds1'},
{ label: '数据源2', value: 'ds2' } {label: '数据源2', value: 'ds2'}
]) ])
const showAiDialog = ref(false) const showAiDialog = ref(false)
@ -196,7 +213,7 @@ const paramsTableRef = ref()
const previewData = ref<any[]>([]) const previewData = ref<any[]>([])
const previewColumns = ref<any[]>([]) const previewColumns = ref<any[]>([])
const generateSql = async () => { const getSql = async () => {
if (!aiPrompt.value.trim() || aiLoading.value) return if (!aiPrompt.value.trim() || aiLoading.value) return
aiLoading.value = true aiLoading.value = true
@ -208,23 +225,26 @@ const generateSql = async () => {
// return; // return;
try { try {
const response = await aiSqlAsk({prompt:aiPrompt.value}) const response = await generateSql({text: aiPrompt.value,modelId:1,platformId:1})
let sql = JSON.stringify(response)
// sql = sql.replaceAll("\"",""); // sql = sql.replaceAll("\"","");
// sql = sql.substring(String.prototype.toLowerCase(sql).indexOf("select")); // sql = sql.substring(String.prototype.toLowerCase(sql).indexOf("select"));
// //
if ((sql.startsWith("\"") && sql.endsWith("\"")) || // if ((sql.startsWith("\"") && sql.endsWith("\"")) ||
(sql.startsWith("'") && sql.endsWith("'"))) { // (sql.startsWith("'") && sql.endsWith("'"))) {
sql = sql.substring(1, sql.length - 1); // sql = sql.substring(1, sql.length - 1);
} // }
aiSql.value = response;
aiSql.value = sql;
console.log(1) console.log(1)
console.log(response) console.log(response)
console.log(2) console.log(2)
} catch (error) { } catch (error) {
console.log("Error:"+error) proxy?.$modal.msgError(error);
console.log("Error:" + error)
} finally { } finally {
aiLoading.value = false aiLoading.value = false
} }
@ -235,39 +255,47 @@ function replaceSql() {
form.sql = aiSql.value form.sql = aiSql.value
showAiDialog.value = false showAiDialog.value = false
} }
function parseSql() { function parseSql() {
if (!form.sql) return ElMessage.warning('请先填写SQL') if (!form.sql) return ElMessage.warning('请先填写SQL')
fields.value = [ fields.value = [
{ name: 'id', text: '编号', type: 'int', order: 1, dictCode: '' }, {name: 'id', text: '编号', type: 'int', order: 1, dictCode: ''},
{ name: 'name', text: '名称', type: 'varchar', order: 2, dictCode: '' } {name: 'name', text: '名称', type: 'varchar', order: 2, dictCode: ''}
] ]
params.value = [ params.value = [
{ param: 'year', text: '年份', type: 'int', defaultValue: 2023 } {param: 'year', text: '年份', type: 'int', defaultValue: 2023}
] ]
ElMessage.success('解析成功') ElMessage.success('解析成功')
} }
function handleSelectionChange(val: any[]) { function handleSelectionChange(val: any[]) {
multipleSelection.value = val multipleSelection.value = val
} }
function batchRemoveFields() { function batchRemoveFields() {
if (!multipleSelection.value.length) return if (!multipleSelection.value.length) return
fields.value = fields.value.filter(row => !multipleSelection.value.includes(row)) fields.value = fields.value.filter(row => !multipleSelection.value.includes(row))
multipleSelection.value = [] multipleSelection.value = []
} }
function handleParamSelectionChange(val: any[]) { function handleParamSelectionChange(val: any[]) {
multipleParamSelection.value = val multipleParamSelection.value = val
} }
function batchRemoveParams() { function batchRemoveParams() {
if (!multipleParamSelection.value.length) return if (!multipleParamSelection.value.length) return
params.value = params.value.filter(row => !multipleParamSelection.value.includes(row)) params.value = params.value.filter(row => !multipleParamSelection.value.includes(row))
multipleParamSelection.value = [] multipleParamSelection.value = []
} }
function addParam() { function addParam() {
params.value.push({ param: '', text: '', type: '', defaultValue: '' }) params.value.push({param: '', text: '', type: '', defaultValue: ''})
} }
function onSave() { function onSave() {
ElMessage.success('保存成功') ElMessage.success('保存成功')
} }
function onClose() { function onClose() {
ElMessage.info('已关闭') ElMessage.info('已关闭')
} }
@ -280,9 +308,9 @@ function fetchPreviewData() {
return return
} }
// //
previewColumns.value = fields.value.map(f => ({ prop: f.name, label: f.text })) previewColumns.value = fields.value.map(f => ({prop: f.name, label: f.text}))
// 10 // 10
previewData.value = Array.from({ length: 10 }, (_, i) => { previewData.value = Array.from({length: 10}, (_, i) => {
const row: any = {} const row: any = {}
fields.value.forEach(f => { fields.value.forEach(f => {
if (f.type === 'int') row[f.name] = i + 1 if (f.type === 'int') row[f.name] = i + 1
@ -309,15 +337,18 @@ watch(activeTab, (val) => {
padding: 16px 24px 0 24px; padding: 16px 24px 0 24px;
border-radius: 8px; border-radius: 8px;
} }
.form-row { .form-row {
display: flex; display: flex;
gap: 24px; gap: 24px;
align-items: center; align-items: center;
margin-bottom: 0; margin-bottom: 0;
} }
.sql-form-item { .sql-form-item {
margin-bottom: 0; margin-bottom: 0;
} }
.sql-desc { .sql-desc {
color: #888; color: #888;
font-size: 12px; font-size: 12px;
@ -326,13 +357,16 @@ watch(activeTab, (val) => {
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
} }
.sql-tips { .sql-tips {
flex: 1; flex: 1;
} }
.sql-tips div { .sql-tips div {
line-height: 1.5; line-height: 1.5;
margin-bottom: 2px; margin-bottom: 2px;
} }
.sql-desc .el-button { .sql-desc .el-button {
margin-left: 8px; margin-left: 8px;
padding: 0 10px; padding: 0 10px;

@ -324,6 +324,10 @@ import { treeselect } from '@/api/system/dept';
import { globalHeaders } from '@/utils/request'; import { globalHeaders } from '@/utils/request';
import { to } from 'await-to-js'; import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post'; import { optionselect } from '@/api/system/post';
import { useSharedDataStore } from '@/api/monitorData'
import { filterEmptyValues, copyNonEmptyValues } from '@/api/objectUtil';
const sharedStore = useSharedDataStore()
const router = useRouter(); const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@ -672,14 +676,55 @@ const resetForm = () => {
form.value.id = undefined; form.value.id = undefined;
form.value.status = '1'; form.value.status = '1';
}; };
onMounted(() => { onMounted(() => {
getTreeSelect(); // getTreeSelect(); //
getList(); // getList(); //
proxy?.getConfigKey('sys.user.initPassword').then((response) => { proxy?.getConfigKey('sys.user.initPassword').then((response) => {
initPassword.value = response.data; initPassword.value = response.data;
}); });
getSourceInfo();
}); });
const getSourceInfo = () =>{
const from = proxy.$route.query.from;
if(form && from==="ai"){
handleAdd();
}
// previousRoute.value = localStorage.getItem("previousRoute");
// if(previousRoute.value && previousRoute.value == "ai"){
// alert(1)
// localStorage.removeItem("previousRoute");
//
// }
}
//
const sharedData = computed(() => sharedStore.dynamicValue)
//
watch(
() => sharedStore.dynamicValue,
(newValue, oldValue) => {
if (newValue !== oldValue) {
ElMessage.info('收到新数据更新')
console.log('数据更新:', newValue)
//
// handleDataUpdate(newValue)
copyNonEmptyValues(newValue.message,form.value);
if(form.value.deptId && form.value.deptId!==''){
handleDeptChange(form.value.deptId)
}
}
},
{ deep: true } //
)
async function handleDeptChange(value: number | string) { async function handleDeptChange(value: number | string) {
const response = await optionselect(value); const response = await optionselect(value);
postOptions.value = response.data; postOptions.value = response.data;

Loading…
Cancel
Save