MaxKB/ui/src/views/tool/DataSourceToolFormDrawer.vue

538 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-drawer v-model="visible" size="60%" :before-close="close">
<template #header>
<h4>{{ title }}</h4>
</template>
<div>
<h4 class="title-decoration-1 mb-16">
{{ $t('views.model.modelForm.title.baseInfo') }}
</h4>
<el-form
ref="FormRef"
:model="form"
:rules="rules"
label-position="top"
require-asterisk-position="right"
v-loading="loading"
@submit.prevent
>
<el-form-item :label="$t('common.name')" prop="name">
<div class="flex w-full">
<div
v-if="form.id"
class="edit-avatar mr-12"
@mouseenter="showEditIcon = true"
@mouseleave="showEditIcon = false"
>
<el-Avatar
v-if="isAppIcon(form.icon)"
:id="form.id"
shape="square"
:size="32"
style="background: none"
>
<img :src="String(form.icon)" alt="" />
</el-Avatar>
<el-avatar v-else class="avatar-purple" shape="square" :size="32">
<img src="@/assets/tool/icon_datasource.svg" style="width: 58%" alt="" />
</el-avatar>
<el-Avatar
v-if="showEditIcon"
:id="form.id"
shape="square"
class="edit-mask"
:size="32"
@click="openEditAvatar"
>
<AppIcon iconName="app-edit"></AppIcon>
</el-Avatar>
</div>
<el-avatar v-else class="avatar-purple mr-12" shape="square" :size="32">
<img src="@/assets/tool/icon_datasource.svg" style="width: 58%" alt="" />
</el-avatar>
<el-input
v-model="form.name"
:placeholder="$t('views.tool.form.toolName.placeholder')"
maxlength="64"
show-word-limit
@blur="form.name = form.name?.trim()"
/>
</div>
</el-form-item>
<el-form-item :label="$t('common.desc')">
<el-input
v-model="form.desc"
type="textarea"
:placeholder="$t('views.tool.form.toolDescription.placeholder')"
maxlength="128"
show-word-limit
:autosize="{ minRows: 3 }"
@blur="form.desc = form.desc?.trim()"
/>
</el-form-item>
</el-form>
<div class="flex-between">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.initParam') }}
</h4>
<el-button link type="primary" @click="openAddInitDialog()">
<AppIcon iconName="app-add-outlined" class="mr-4"></AppIcon>
{{ $t('common.add') }}
</el-button>
</div>
<el-table ref="initFieldTableRef" :data="form.init_field_list" class="mb-16">
<el-table-column prop="field" :label="$t('dynamicsForm.paramForm.field.label')">
<template #default="{ row }">
<span :title="row.field" class="ellipsis-1">{{ row.field }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('dynamicsForm.paramForm.input_type.label')">
<template #default="{ row }">
<el-tag type="info" class="info-tag" v-if="row.input_type === 'TextInput'"
>{{ $t('dynamicsForm.input_type_list.TextInput') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'PasswordInput'"
>{{ $t('dynamicsForm.input_type_list.PasswordInput') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'Slider'"
>{{ $t('dynamicsForm.input_type_list.Slider') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'SwitchInput'"
>{{ $t('dynamicsForm.input_type_list.SwitchInput') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'SingleSelect'"
>{{ $t('dynamicsForm.input_type_list.SingleSelect') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'MultiSelect'"
>{{ $t('dynamicsForm.input_type_list.MultiSelect') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'RadioCard'"
>{{ $t('dynamicsForm.input_type_list.RadioCard') }}
</el-tag>
<el-tag type="info" class="info-tag" v-if="row.input_type === 'DatePicker'"
>{{ $t('dynamicsForm.input_type_list.DatePicker') }}
</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('common.required')">
<template #default="{ row }">
<div @click.stop>
<el-switch disabled size="small" v-model="row.required" />
</div>
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="90">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button type="primary" text @click.stop="openAddInitDialog(row, $index)">
<AppIcon iconName="app-edit"></AppIcon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteInitField($index)">
<AppIcon iconName="app-delete"></AppIcon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="flex-between">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.inputParam') }}
<el-text type="info" class="color-secondary">
{{ $t('views.tool.form.param.paramInfo1') }}
</el-text>
</h4>
<el-button link type="primary" @click="openAddDialog()">
<AppIcon iconName="app-add-outlined" class="mr-4"></AppIcon>
{{ $t('common.add') }}
</el-button>
</div>
<el-table ref="inputFieldTableRef" :data="form.input_field_list" class="mb-16">
<el-table-column prop="name" :label="$t('views.tool.form.paramName.label')" />
<el-table-column :label="$t('views.tool.form.dataType.label')">
<template #default="{ row }">
<el-tag type="info" class="info-tag">{{ row.type }}</el-tag>
</template>
</el-table-column>
<el-table-column :label="$t('common.required')">
<template #default="{ row }">
<div @click.stop>
<el-switch size="small" v-model="row.is_required" />
</div>
</template>
</el-table-column>
<el-table-column prop="source" :label="$t('views.tool.form.source.label')">
<template #default="{ row }">
{{
row.source === 'custom' ? $t('common.custom') : $t('views.tool.form.source.reference')
}}
</template>
</el-table-column>
<el-table-column :label="$t('common.operation')" align="left" width="90">
<template #default="{ row, $index }">
<span class="mr-4">
<el-tooltip effect="dark" :content="$t('common.modify')" placement="top">
<el-button type="primary" text @click.stop="openAddDialog(row, $index)">
<AppIcon iconName="app-edit"></AppIcon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteField($index)">
<AppIcon iconName="app-delete"></AppIcon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<h4 class="title-decoration-1 mb-16">
{{ $t('views.tool.form.param.code') }}
<span class="color-danger" style="margin-left: -10px">*</span>
<el-text type="info" class="color-secondary">
{{ $t('views.tool.form.param.paramInfo2') }}
</el-text>
</h4>
<div class="mb-8" v-if="showEditor">
<CodemirrorEditor
:title="$t('views.tool.form.param.code')"
v-model="form.code"
@submitDialog="submitCodemirrorEditor"
/>
</div>
</div>
<template #footer>
<div>
<el-button :loading="loading" @click="visible = false">{{ $t('common.cancel') }}</el-button>
<el-button
type="primary"
@click="submit(FormRef)"
:loading="loading"
v-if="isEdit ? permissionPrecise.edit(form?.id as string) : permissionPrecise.create()"
>
{{ isEdit ? $t('common.save') : $t('common.create') }}
</el-button>
</div>
</template>
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
<UserFieldFormDialog ref="UserFieldFormDialogRef" @refresh="refreshInitFieldList" />
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshTool" />
</el-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch, nextTick, computed } from 'vue'
import FieldFormDialog from '@/views/tool/component/FieldFormDialog.vue'
import UserFieldFormDialog from '@/views/tool/component/UserFieldFormDialog.vue'
import EditAvatarDialog from '@/views/tool/component/EditAvatarDialog.vue'
import type { toolData } from '@/api/type/tool'
import type { FormInstance } from 'element-plus'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { cloneDeep } from 'lodash'
import { t } from '@/locales'
import { isAppIcon } from '@/utils/common'
import { useRoute } from 'vue-router'
import useStore from '@/stores'
import permissionMap from '@/permission'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
const route = useRoute()
const props = defineProps({
title: String,
})
const { folder, user } = useStore()
const apiType = computed(() => {
if (route.path.includes('shared')) {
return 'systemShare'
} else if (route.path.includes('resource-management')) {
return 'systemManage'
} else {
return 'workspace'
}
})
const permissionPrecise = computed(() => {
return permissionMap['tool'][apiType.value]
})
const emit = defineEmits(['refresh'])
const FieldFormDialogRef = ref()
const ToolDebugDrawerRef = ref()
const UserFieldFormDialogRef = ref()
const EditAvatarDialogRef = ref()
const initFieldTableRef = ref()
const inputFieldTableRef = ref()
const FormRef = ref()
const isEdit = ref(false)
const loading = ref(false)
const visible = ref(false)
const showEditor = ref(false)
const currentIndex = ref<any>(null)
const showEditIcon = ref(false)
const codeTemplate = `
from typing import Dict, List
def get_form_list(node, **kwargs) -> List[Dict[str, object]]:
"""获取文件列表表单配置
生成一个树形选择器的表单配置,用于展示和选择文件列表。
Args:
node: 节点对象用于构造API调用URL
**kwargs: 其他可选参数
Returns:
list: 表单配置列表,包含树形选择器的配置项
"""
return [{
"field": 'file_list',
"text_field": 'name',
"value_field": 'token',
"input_type": 'Tree',
"attrs": {
"lazy": True,
"fetch_list_function": "get_file_list",
},
"label": '',
}]
def get_file_list(app_id=None, app_secret=None, folder_token=None, **kwargs) -> List[Dict[str, str]]:
"""获取指定文件夹下的文件列表
Args:
app_id: 应用ID用于身份验证
app_secret: 应用密钥,用于身份验证
folder_token: 文件夹标识符,不传则获取根目录文件
**kwargs: 其他可选参数
Returns:
list: 文件列表,每个文件对象包含以下字段:
- name (str): 文件名称
- token (str): 文件唯一标识符
- type (str): 文件类型,如 "docx"、"xlsx" 或 "folder"
- 其他元数据字段
Example:
[
{
"name": "示例文档.docx",
"token": "abc123",
"type": "docx"
},
{
"name": "子文件夹",
"token": "def456",
"type": "folder"
}
]
"""
pass
def get_raw_file(app_id=None, app_secret=None, **kwargs) -> Dict[str, object]:
"""下载文件的原始内容
Args:
app_id: 应用ID用于身份验证
app_secret: 应用密钥,用于身份验证
**kwargs: 其他可选参数
Returns:
[
{
"name": "示例文档.docx",
"file_bytes": b"文件的二进制内容"
}
]
"""
pass
`
const form = ref<toolData>({
name: '',
desc: '',
code: codeTemplate,
icon: '',
input_field_list: [],
init_field_list: [],
tool_type: 'DATA_SOURCE',
})
watch(visible, (bool) => {
if (!bool) {
isEdit.value = false
showEditor.value = false
currentIndex.value = null
form.value = {
name: '',
desc: '',
code: codeTemplate,
icon: '',
input_field_list: [],
init_field_list: [],
tool_type: 'DATA_SOURCE',
}
FormRef.value?.clearValidate()
}
})
const rules = reactive({
name: [
{
required: true,
message: t('views.tool.form.toolName.requiredMessage'),
trigger: 'blur',
},
],
})
function submitCodemirrorEditor(val: string) {
form.value.code = val
}
function close() {
if (!areAllValuesNonEmpty(form.value)) {
visible.value = false
} else {
MsgConfirm(t('common.tip'), t('views.tool.tip.saveMessage'), {
confirmButtonText: t('common.confirm'),
})
.then(() => {
visible.value = false
})
.catch(() => {})
}
}
function areAllValuesNonEmpty(obj: any) {
return Object.values(obj).some((value) => {
return Array.isArray(value)
? value.length !== 0
: value !== null && value !== undefined && value !== ''
})
}
function deleteField(index: any) {
form.value.input_field_list?.splice(index, 1)
}
function openAddDialog(data?: any, index?: any) {
if (typeof index !== 'undefined') {
currentIndex.value = index
}
FieldFormDialogRef.value.open(data)
}
function refreshFieldList(data: any) {
if (currentIndex.value !== null) {
form.value.input_field_list?.splice(currentIndex.value, 1, data)
} else {
form.value.input_field_list?.push(data)
}
currentIndex.value = null
}
function openAddInitDialog(data?: any, index?: any) {
if (typeof index !== 'undefined') {
currentIndex.value = index
}
UserFieldFormDialogRef.value.open(data)
}
function refreshInitFieldList(data: any) {
if (currentIndex.value !== null) {
form.value.init_field_list?.splice(currentIndex.value, 1, data)
} else {
form.value.init_field_list?.push(data)
}
currentIndex.value = null
UserFieldFormDialogRef.value.close()
}
function refreshTool(data: any) {
form.value.icon = data
}
function deleteInitField(index: any) {
form.value.init_field_list?.splice(index, 1)
}
function openEditAvatar() {
EditAvatarDialogRef.value.open(form.value)
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid: any) => {
if (valid) {
loading.value = true
if (isEdit.value) {
loadSharedApi({ type: 'tool', systemType: apiType.value })
.putTool(form.value?.id as string, form.value)
.then((res: any) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh', res.data)
return user.profile()
})
.then(() => {
visible.value = false
})
.finally(() => {
loading.value = false
})
} else {
const obj = {
folder_id: folder.currentFolder?.id,
...form.value,
}
loadSharedApi({ type: 'tool', systemType: apiType.value })
.postTool(obj)
.then((res: any) => {
MsgSuccess(t('common.createSuccess'))
emit('refresh')
return user.profile()
})
.then(() => {
visible.value = false
})
.finally(() => {
loading.value = false
})
}
}
})
}
const open = (data: any) => {
if (data) {
isEdit.value = data?.id ? true : false
form.value = cloneDeep(data)
}
visible.value = true
setTimeout(() => {
showEditor.value = true
}, 100)
}
defineExpose({
open,
})
</script>
<style lang="scss" scoped></style>