You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

376 lines
12 KiB
Vue

<template>
<div class="p-2">
<el-form :model="form" label-width="100px" class="dataset-form" style="margin-bottom: 0;">
<div class="form-row">
<el-form-item label="数据集名称">
<el-input v-model="form.name" placeholder="请输入数据集名称"/>
</el-form-item>
<el-form-item label="数据源">
<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-select>
</el-form-item>
<el-form-item label="是否分页">
<el-switch v-model="form.pagination"/>
<el-tooltip content="开启分页后,查询结果将按页返回,适用于大数据量场景。" placement="top">
<el-icon style="margin-left:4px;cursor:pointer;">
<QuestionFilled/>
</el-icon>
</el-tooltip>
</el-form-item>
</div>
<el-form-item label="查询SQL" class="sql-form-item">
<div style="display: flex; align-items: flex-start; width: 100%;">
<el-input
type="textarea"
v-model="form.sql"
:rows="3"
placeholder="请输入查询SQL"
style="flex:1"
/>
<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>
</el-button>
</div>
<div class="sql-desc">
<div class="sql-tips">
<div> 请填写标准SQL支持参数占位符</div>
<div> 参数占位符格式#{参数名}#{year}</div>
<div> 支持SELECTFROMWHERE等标准SQL语法</div>
</div>
<el-button type="primary" size="small" @click="parseSql" style="margin-left:8px;">查询解析</el-button>
</div>
</el-form-item>
</el-form>
<el-tabs v-model="activeTab" style="margin: 16px 0 0 10px;">
<el-tab-pane label="列表字段" name="fields">
<div style="margin-bottom:8px;display:flex;align-items:center;">
<el-button type="danger" size="small" :disabled="!multipleSelection.length" @click="batchRemoveFields">
批量删除
</el-button>
</div>
<el-table
:data="fields"
style="width: 100%"
border
ref="fieldsTableRef"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50"/>
<el-table-column prop="name" label="字段名" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.name" size="small"/>
</template>
</el-table-column>
<el-table-column prop="text" label="字段文本" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.text" size="small"/>
</template>
</el-table-column>
<el-table-column prop="type" label="字段类型" min-width="100">
<template #default="scope">
<el-select v-model="scope.row.type" size="small" style="width:100px">
<el-option label="文本" value="varchar"/>
<el-option label="数值" value="int"/>
<el-option label="日期" value="date"/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="order" label="排序" min-width="80">
<template #default="scope">
<el-input-number v-model="scope.row.order" :min="1" size="small" style="width:80px"/>
</template>
</el-table-column>
<el-table-column prop="dictCode" label="字典编码" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.dictCode" size="small"/>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="参数字段" name="params">
<div style="margin-bottom:8px;display:flex;align-items:center;">
<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>
</div>
<el-table
:data="params"
style="width: 100%"
border
ref="paramsTableRef"
@selection-change="handleParamSelectionChange"
>
<el-table-column type="selection" width="50"/>
<el-table-column prop="param" label="参数" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.param" size="small"/>
</template>
</el-table-column>
<el-table-column prop="text" label="参数文本" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.text" size="small"/>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" min-width="100">
<template #default="scope">
<el-select v-model="scope.row.type" size="small" style="width:100px">
<el-option label="文本" value="varchar"/>
<el-option label="数值" value="int"/>
<el-option label="日期" value="date"/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="defaultValue" label="默认值" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.defaultValue" size="small"/>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="数据预览" name="preview">
<div style="display:flex;align-items:center;margin-bottom:8px;">
<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>
</div>
<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>
<div v-else style="color:#888;text-align:center;padding:24px 0;">暂无数据</div>
</el-tab-pane>
</el-tabs>
<div style="text-align:right;margin-top:24px;">
<el-button @click="onClose"></el-button>
<el-button type="primary" @click="onSave"></el-button>
</div>
<!-- AI生成SQL弹窗 -->
<el-dialog v-model="showAiDialog" title="AI生成SQL" width="800px">
<el-input
v-model="aiPrompt"
type="textarea"
placeholder="请输入需求描述查询年龄大于35的用户信息包括所在部门的名称"
style="margin-bottom:12px;"
/>
<el-button type="primary" @click="getSql" :loading="aiLoading">生成SQL</el-button>
<el-input
v-model="aiSql"
type="textarea"
:rows="4"
readonly
style="margin-top:12px;"
/>
<div style="text-align:right;margin-top:8px;">
<el-button @click="showAiDialog=false"></el-button>
<el-button type="primary" @click="replaceSql">SQL</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import {ref, reactive, watch} from 'vue'
import {ElMessage} from 'element-plus'
import {QuestionFilled} from '@element-plus/icons-vue'
import {generateSql} from "@/api/ai/skill/aiChat/index";
const {proxy} = getCurrentInstance() as ComponentInternalInstance;
const form = reactive({
name: '',
datasource: '',
sql: '',
pagination: true
})
const datasourceList = ref([
{label: '数据源1', value: 'ds1'},
{label: '数据源2', value: 'ds2'}
])
const showAiDialog = ref(false)
const aiPrompt = ref('')
const aiSql = ref('')
const aiLoading = ref(false)
const activeTab = ref('fields')
const fields = ref<any[]>([])
const params = ref<any[]>([])
const multipleSelection = ref<any[]>([])
const fieldsTableRef = ref()
const multipleParamSelection = ref<any[]>([])
const paramsTableRef = ref()
const previewData = ref<any[]>([])
const previewColumns = ref<any[]>([])
const getSql = async () => {
if (!aiPrompt.value.trim() || aiLoading.value) return
aiLoading.value = true
// let sql1 = "```sql\nSELECT \n user_id,\n tenant_id,\n dept_id,\n user_name,\n nick_name,\n user_type,\n email,\n phonenumber,\n sex,\n avatar,\n password,\n status,\n del_flag,\n login_ip,\n login_date,\n create_dept,\n create_by,\n create_time,\n update_by,\n update_time,\n remark\nFROM \n sys_user WITH(NOLOCK)\nWHERE \n del_flag = '0'\n```";
// sql1 =sql1.replace(/\s+/g, ' ');
// aiSql.value = sql1;
// return;
try {
const response = await generateSql({text: aiPrompt.value,modelId:1,platformId:1})
// sql = sql.replaceAll("\"","");
// sql = sql.substring(String.prototype.toLowerCase(sql).indexOf("select"));
// 检查首尾是否是双引号或单引号
// if ((sql.startsWith("\"") && sql.endsWith("\"")) ||
// (sql.startsWith("'") && sql.endsWith("'"))) {
// sql = sql.substring(1, sql.length - 1);
// }
aiSql.value = response;
console.log(1)
console.log(response)
console.log(2)
} catch (error) {
proxy?.$modal.msgError(error);
console.log("Error:" + error)
} finally {
aiLoading.value = false
}
}
function replaceSql() {
form.sql = aiSql.value
showAiDialog.value = false
}
function parseSql() {
if (!form.sql) return ElMessage.warning('请先填写SQL')
fields.value = [
{name: 'id', text: '编号', type: 'int', order: 1, dictCode: ''},
{name: 'name', text: '名称', type: 'varchar', order: 2, dictCode: ''}
]
params.value = [
{param: 'year', text: '年份', type: 'int', defaultValue: 2023}
]
ElMessage.success('解析成功')
}
function handleSelectionChange(val: any[]) {
multipleSelection.value = val
}
function batchRemoveFields() {
if (!multipleSelection.value.length) return
fields.value = fields.value.filter(row => !multipleSelection.value.includes(row))
multipleSelection.value = []
}
function handleParamSelectionChange(val: any[]) {
multipleParamSelection.value = val
}
function batchRemoveParams() {
if (!multipleParamSelection.value.length) return
params.value = params.value.filter(row => !multipleParamSelection.value.includes(row))
multipleParamSelection.value = []
}
function addParam() {
params.value.push({param: '', text: '', type: '', defaultValue: ''})
}
function onSave() {
ElMessage.success('保存成功')
}
function onClose() {
ElMessage.info('已关闭')
}
function fetchPreviewData() {
// 模拟数据预览实际可根据SQL和参数请求后端
if (!fields.value.length) {
previewData.value = []
previewColumns.value = []
return
}
// 构造表头
previewColumns.value = fields.value.map(f => ({prop: f.name, label: f.text}))
// 构造10条模拟数据
previewData.value = Array.from({length: 10}, (_, i) => {
const row: any = {}
fields.value.forEach(f => {
if (f.type === 'int') row[f.name] = i + 1
else if (f.type === 'date') row[f.name] = '2023-01-01'
else row[f.name] = f.text + (i + 1)
})
return row
})
}
// 默认进入时预览
if (activeTab.value === 'preview') fetchPreviewData()
watch(activeTab, (val) => {
if (val === 'preview') fetchPreviewData()
})
</script>
<style scoped>
.dataset-form {
max-width: 100%;
margin: 0 auto;
background: #fff;
padding: 16px 24px 0 24px;
border-radius: 8px;
}
.form-row {
display: flex;
gap: 24px;
align-items: center;
margin-bottom: 0;
}
.sql-form-item {
margin-bottom: 0;
}
.sql-desc {
color: #888;
font-size: 12px;
margin-top: 4px;
display: flex;
align-items: flex-start;
justify-content: space-between;
}
.sql-tips {
flex: 1;
}
.sql-tips div {
line-height: 1.5;
margin-bottom: 2px;
}
.sql-desc .el-button {
margin-left: 8px;
padding: 0 10px;
font-size: 12px;
}
</style>