feat: Support functionlib icon and init_fields

--story=1017947 --user=刘瑞斌 【函数库】- 函数支持配置参数及操作优化 https://www.tapd.cn/57709429/s/1664936
This commit is contained in:
CaptainB 2025-03-10 11:12:49 +08:00 committed by 刘瑞斌
parent a09f5c0577
commit ad0b032384
16 changed files with 668 additions and 40 deletions

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.15 on 2025-03-07 10:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('function_lib', '0002_functionlib_is_active_functionlib_permission_type'),
]
operations = [
migrations.AddField(
model_name='functionlib',
name='icon',
field=models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='函数库icon'),
),
migrations.AddField(
model_name='functionlib',
name='init_field_list',
field=models.JSONField(default=list, verbose_name='启动字段列表'),
),
]

View File

@ -29,6 +29,8 @@ class FunctionLib(AppModelMixin):
input_field_list = ArrayField(verbose_name="输入字段列表", input_field_list = ArrayField(verbose_name="输入字段列表",
base_field=models.JSONField(verbose_name="输入字段", default=dict) base_field=models.JSONField(verbose_name="输入字段", default=dict)
, default=list) , default=list)
init_field_list = models.JSONField(verbose_name="启动字段列表", default=list)
icon = models.CharField(max_length=256, verbose_name="函数库icon", default="/ui/favicon.ico")
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
permission_type = models.CharField(max_length=20, verbose_name='权限类型', choices=PermissionType.choices, permission_type = models.CharField(max_length=20, verbose_name='权限类型', choices=PermissionType.choices,
default=PermissionType.PRIVATE) default=PermissionType.PRIVATE)

View File

@ -10,23 +10,23 @@ import json
import pickle import pickle
import re import re
import uuid import uuid
from typing import List
from django.core import validators from django.core import validators
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet, Q from django.db.models import QuerySet, Q
from django.http import HttpResponse from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers, status from rest_framework import serializers, status
from common.db.search import page_search from common.db.search import page_search
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.field.common import UploadedFileField from common.field.common import UploadedFileField, UploadedImageField
from common.response import result from common.response import result
from common.util.field_message import ErrMessage from common.util.field_message import ErrMessage
from common.util.function_code import FunctionExecutor from common.util.function_code import FunctionExecutor
from dataset.models import Image
from function_lib.models.function import FunctionLib from function_lib.models.function import FunctionLib
from smartdoc.const import CONFIG from smartdoc.const import CONFIG
from django.utils.translation import gettext_lazy as _
function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
@ -39,7 +39,7 @@ class FlibInstance:
class FunctionLibModelSerializer(serializers.ModelSerializer): class FunctionLibModelSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = FunctionLib model = FunctionLib
fields = ['id', 'name', 'desc', 'code', 'input_field_list', 'permission_type', 'is_active', 'user_id', fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list','init_field_list', 'permission_type', 'is_active', 'user_id',
'create_time', 'update_time'] 'create_time', 'update_time']
@ -65,6 +65,7 @@ class DebugField(serializers.Serializer):
class DebugInstance(serializers.Serializer): class DebugInstance(serializers.Serializer):
debug_field_list = DebugField(required=True, many=True) debug_field_list = DebugField(required=True, many=True)
input_field_list = FunctionLibInputField(required=True, many=True) input_field_list = FunctionLibInputField(required=True, many=True)
init_field_list = serializers.ListField(required=False, default=list)
code = serializers.CharField(required=True, error_messages=ErrMessage.char(_('function content'))) code = serializers.CharField(required=True, error_messages=ErrMessage.char(_('function content')))
@ -80,6 +81,8 @@ class EditFunctionLib(serializers.Serializer):
input_field_list = FunctionLibInputField(required=False, many=True) input_field_list = FunctionLibInputField(required=False, many=True)
init_field_list = serializers.ListField(required=False, default=list)
is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char(_('Is active'))) is_active = serializers.BooleanField(required=False, error_messages=ErrMessage.char(_('Is active')))
@ -93,6 +96,8 @@ class CreateFunctionLib(serializers.Serializer):
input_field_list = FunctionLibInputField(required=True, many=True) input_field_list = FunctionLibInputField(required=True, many=True)
init_field_list = serializers.ListField(required=False, default=list)
permission_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_('permission')), validators=[ permission_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_('permission')), validators=[
validators.RegexValidator(regex=re.compile("^PUBLIC|PRIVATE$"), validators.RegexValidator(regex=re.compile("^PUBLIC|PRIVATE$"),
message="权限只支持PUBLIC|PRIVATE", code=500) message="权限只支持PUBLIC|PRIVATE", code=500)
@ -148,6 +153,7 @@ class FunctionLibSerializer(serializers.Serializer):
code=instance.get('code'), code=instance.get('code'),
user_id=self.data.get('user_id'), user_id=self.data.get('user_id'),
input_field_list=instance.get('input_field_list'), input_field_list=instance.get('input_field_list'),
init_field_list=instance.get('init_field_list'),
permission_type=instance.get('permission_type'), permission_type=instance.get('permission_type'),
is_active=instance.get('is_active', True)) is_active=instance.get('is_active', True))
function_lib.save() function_lib.save()
@ -163,12 +169,16 @@ class FunctionLibSerializer(serializers.Serializer):
input_field_list = debug_instance.get('input_field_list') input_field_list = debug_instance.get('input_field_list')
code = debug_instance.get('code') code = debug_instance.get('code')
debug_field_list = debug_instance.get('debug_field_list') debug_field_list = debug_instance.get('debug_field_list')
init_field_list = debug_instance.get('init_field_list')
init_params = {field.get('field'): field.get('value') if field.get('value', None) is not None else field.get('default_value') for field in init_field_list}
params = {field.get('name'): self.convert_value(field.get('name'), field.get('value'), field.get('type'), params = {field.get('name'): self.convert_value(field.get('name'), field.get('value'), field.get('type'),
field.get('is_required')) field.get('is_required'))
for field in for field in
[{'value': self.get_field_value(debug_field_list, field.get('name'), field.get('is_required')), [{'value': self.get_field_value(debug_field_list, field.get('name'), field.get('is_required')),
**field} for field in **field} for field in
input_field_list]} input_field_list]}
# 合并初始化参数
params = init_params | params
return function_executor.exec_code(code, params) return function_executor.exec_code(code, params)
@staticmethod @staticmethod
@ -224,7 +234,7 @@ class FunctionLibSerializer(serializers.Serializer):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
EditFunctionLib(data=instance).is_valid(raise_exception=True) EditFunctionLib(data=instance).is_valid(raise_exception=True)
edit_field_list = ['name', 'desc', 'code', 'input_field_list', 'permission_type', 'is_active'] edit_field_list = ['name', 'desc', 'code', 'input_field_list', 'init_field_list', 'permission_type', 'is_active']
edit_dict = {field: instance.get(field) for field in edit_field_list if ( edit_dict = {field: instance.get(field) for field in edit_field_list if (
field in instance and instance.get(field) is not None)} field in instance and instance.get(field) is not None)}
QuerySet(FunctionLib).filter(id=self.data.get('id')).update(**edit_dict) QuerySet(FunctionLib).filter(id=self.data.get('id')).update(**edit_dict)
@ -277,4 +287,23 @@ class FunctionLibSerializer(serializers.Serializer):
permission_type='PRIVATE', permission_type='PRIVATE',
is_active=function_lib.get('is_active')) is_active=function_lib.get('is_active'))
function_lib_model.save() function_lib_model.save()
return True return True
class IconOperate(serializers.Serializer):
id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("function ID")))
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_("User ID")))
image = UploadedImageField(required=True, error_messages=ErrMessage.image(_("picture")))
def edit(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
functionLib = QuerySet(FunctionLib).filter(id=self.data.get('id')).first()
if functionLib is None:
raise AppApiException(500, _('Function does not exist'))
image_id = uuid.uuid1()
image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name)
image.save()
functionLib.icon = f'/api/image/{image_id}'
functionLib.save()
return functionLib.icon

View File

@ -8,6 +8,7 @@ urlpatterns = [
path('function_lib/debug', views.FunctionLibView.Debug.as_view()), path('function_lib/debug', views.FunctionLibView.Debug.as_view()),
path('function_lib/<str:id>/export', views.FunctionLibView.Export.as_view()), path('function_lib/<str:id>/export', views.FunctionLibView.Export.as_view()),
path('function_lib/import', views.FunctionLibView.Import.as_view()), path('function_lib/import', views.FunctionLibView.Import.as_view()),
path('function_lib/<str:id>/edit_icon', views.FunctionLibView.EditIcon.as_view()),
path('function_lib/pylint', views.PyLintView.as_view()), path('function_lib/pylint', views.PyLintView.as_view()),
path('function_lib/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()), path('function_lib/<str:function_lib_id>', views.FunctionLibView.Operate.as_view()),
path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(), path("function_lib/<int:current_page>/<int:page_size>", views.FunctionLibView.Page.as_view(),

View File

@ -136,4 +136,16 @@ class FunctionLibView(APIView):
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER) @has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
def get(self, request: Request, id: str): def get(self, request: Request, id: str):
return FunctionLibSerializer.Operate( return FunctionLibSerializer.Operate(
data={'id': id, 'user_id': request.user.id}).export() data={'id': id, 'user_id': request.user.id}).export()
class EditIcon(APIView):
authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser]
@action(methods=['PUT'], detail=False)
@has_permissions(RoleConstants.ADMIN, RoleConstants.USER)
def put(self, request: Request, id: str):
return result.success(
FunctionLibSerializer.IconOperate(
data={'id': id, 'user_id': request.user.id,
'image': request.FILES.get('file')}).edit(request.data))

View File

@ -112,6 +112,14 @@ const exportFunctionLib = (
) )
} }
const putFunctionLibIcon: (
id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (id, data, loading) => {
return put(`${prefix}/${id}/edit_icon`, data, undefined, loading)
}
const importFunctionLib: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = ( const importFunctionLib: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data, data,
loading loading
@ -128,5 +136,6 @@ export default {
getFunctionLibById, getFunctionLibById,
exportFunctionLib, exportFunctionLib,
importFunctionLib, importFunctionLib,
pylint pylint,
putFunctionLibIcon
} }

View File

@ -1,10 +1,12 @@
interface functionLibData { interface functionLibData {
id?: String id?: String
name?: String name?: String
icon?: String
desc?: String desc?: String
code?: String code?: String
permission_type?: 'PRIVATE' | 'PUBLIC' permission_type?: 'PRIVATE' | 'PUBLIC'
input_field_list?: Array<any> input_field_list?: Array<any>
init_field_list?: Array<any>
is_active?: Boolean is_active?: Boolean
} }

View File

@ -52,7 +52,8 @@ export default {
}, },
param: { param: {
outputParam: 'Output Parameters', outputParam: 'Output Parameters',
inputParam: 'Input Parameters' inputParam: 'Input Parameters',
initParam: 'Startup Parameters',
}, },
inputPlaceholder: 'Please input', inputPlaceholder: 'Please input',

View File

@ -55,7 +55,8 @@ export default {
content: '内容', content: '内容',
param: { param: {
outputParam: '输出参数', outputParam: '输出参数',
inputParam:'输入参数' inputParam: '输入参数',
initParam: '启动参数',
}, },
rename:'重命名' rename:'重命名'
} }

View File

@ -55,7 +55,8 @@ export default {
content: '内容', content: '内容',
param: { param: {
outputParam: '輸出參數', outputParam: '輸出參數',
inputParam: '輸入參數' inputParam: '輸入參數',
initParam: '啟動參數',
}, },
rename: '重命名' rename: '重命名'
} }

View File

@ -0,0 +1,134 @@
<template>
<el-dialog
:title="$t('views.applicationOverview.appInfo.EditAvatarDialog.title')"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-radio-group v-model="radioType" class="radio-block mb-16">
<div>
<el-radio value="default">
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.default') }}</p>
<AppAvatar
v-if="detail?.name"
:name="detail?.name"
pinyinColor
class="mt-8 mb-8"
shape="square"
:size="32"
/>
</el-radio>
</div>
<div class="mt-8">
<el-radio value="custom">
<p>{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.customizeUpload') }}</p>
<div class="flex mt-8">
<AppAvatar
v-if="fileURL"
shape="square"
:size="32"
style="background: none"
class="mr-16"
>
<img :src="fileURL" alt=""/>
</AppAvatar>
<el-upload
ref="uploadRef"
action="#"
:auto-upload="false"
:show-file-list="false"
accept="image/jpeg, image/png, image/gif"
:on-change="onChange"
>
<el-button icon="Upload" :disabled="radioType !== 'custom'">{{
$t('views.applicationOverview.appInfo.EditAvatarDialog.upload')
}}
</el-button>
</el-upload>
</div>
<div class="el-upload__tip info mt-16">
{{ $t('views.applicationOverview.appInfo.EditAvatarDialog.sizeTip') }}
</div>
</el-radio>
</div>
</el-radio-group>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false">
{{ $t('common.cancel') }}</el-button
>
<el-button type="primary" @click="submit" :loading="loading">
{{ $t('common.save') }}</el-button
>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {ref, watch} from 'vue'
import functionLibApi from '@/api/function-lib'
import {cloneDeep} from 'lodash'
import {MsgError, MsgSuccess} from '@/utils/message'
import {defaultIcon, isAppIcon} from '@/utils/application'
import {t} from '@/locales'
const emit = defineEmits(['refresh'])
const iconFile = ref<any>(null)
const fileURL = ref<any>(null)
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const detail = ref<any>(null)
const radioType = ref('default')
watch(dialogVisible, (bool) => {
if (!bool) {
iconFile.value = null
fileURL.value = null
}
})
const open = (data: any) => {
radioType.value = isAppIcon(data.icon) ? 'custom' : 'default'
fileURL.value = isAppIcon(data.icon) ? data.icon : null
detail.value = cloneDeep(data)
dialogVisible.value = true
}
const onChange = (file: any) => {
//110MB
const isLimit = file?.size / 1024 / 1024 < 10
if (!isLimit) {
// @ts-ignore
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.fileSizeExceeded'))
return false
} else {
iconFile.value = file
fileURL.value = URL.createObjectURL(file.raw)
}
}
function submit() {
if (radioType.value === 'default') {
// application.asyncPutApplication(props.id as string, { icon: defaultIcon }, loading).then((res: any) => {
// emit('refresh')
// MsgSuccess(t('views.applicationOverview.appInfo.EditAvatarDialog.setSuccess'))
// dialogVisible.value = false
// })
} else if (radioType.value === 'custom' && iconFile.value) {
let fd = new FormData()
fd.append('file', iconFile.value.raw)
functionLibApi.putFunctionLibIcon(detail.value.id, fd, loading)
.then((res: any) => {
emit('refresh', res.data)
dialogVisible.value = false
})
} else {
MsgError(t('views.applicationOverview.appInfo.EditAvatarDialog.uploadImagePrompt'))
}
}
defineExpose({open})
</script>
<style lang="scss" scope></style>

View File

@ -11,6 +11,22 @@
</div> </div>
</template> </template>
<div> <div>
<div v-if="form.init_field_list.length > 0">
<h4 class="title-decoration-1 mb-16">
{{ $t('common.param.initParam') }}
</h4>
<el-card shadow="never" class="card-never" style="--el-card-padding: 12px">
<DynamicsForm
v-model="init_form_data"
:model="init_form_data"
label-position="top"
require-asterisk-position="right"
:render_data="form.init_field_list"
ref="dynamicsFormRef"
>
</DynamicsForm>
</el-card>
</div>
<div v-if="form.debug_field_list.length > 0" class="mb-16"> <div v-if="form.debug_field_list.length > 0" class="mb-16">
<h4 class="title-decoration-1 mb-16"> <h4 class="title-decoration-1 mb-16">
{{ $t('common.param.inputParam') }} {{ $t('common.param.inputParam') }}
@ -95,6 +111,7 @@
import { ref, reactive, watch } from 'vue' import { ref, reactive, watch } from 'vue'
import functionLibApi from '@/api/function-lib' import functionLibApi from '@/api/function-lib'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import DynamicsForm from '@/components/dynamics-form/index.vue'
const FormRef = ref() const FormRef = ref()
const loading = ref(false) const loading = ref(false)
@ -102,11 +119,13 @@ const debugVisible = ref(false)
const showResult = ref(false) const showResult = ref(false)
const isSuccess = ref(false) const isSuccess = ref(false)
const result = ref('') const result = ref('')
const init_form_data = ref<any>({})
const form = ref<any>({ const form = ref<any>({
debug_field_list: [], debug_field_list: [],
code: '', code: '',
input_field_list: [] input_field_list: [],
init_field_list: []
}) })
watch(debugVisible, (bool) => { watch(debugVisible, (bool) => {
@ -117,7 +136,8 @@ watch(debugVisible, (bool) => {
form.value = { form.value = {
debug_field_list: [], debug_field_list: [],
code: '', code: '',
input_field_list: [] input_field_list: [],
init_field_list: []
} }
} }
}) })
@ -150,6 +170,7 @@ const open = (data: any) => {
} }
form.value.code = data.code form.value.code = data.code
form.value.input_field_list = data.input_field_list form.value.input_field_list = data.input_field_list
form.value.init_field_list = data.init_field_list
debugVisible.value = true debugVisible.value = true
} }

View File

@ -20,6 +20,39 @@
:label="$t('views.functionLib.functionForm.form.functionName.label')" :label="$t('views.functionLib.functionForm.form.functionName.label')"
prop="name" prop="name"
> >
<div v-if="form.id"
class="edit-avatar mr-12"
@mouseenter="showEditIcon = true"
@mouseleave="showEditIcon = false"
>
<AppAvatar
v-if="isAppIcon(form.icon)"
:id="form.id"
shape="square"
:size="32"
style="background: none"
>
<img :src="form.icon" alt="" />
</AppAvatar>
<AppAvatar
v-else-if="form.name"
:id="form.id"
:name="form.name"
pinyinColor
shape="square"
:size="32"
/>
<AppAvatar
v-if="showEditIcon"
:id="form.id"
shape="square"
class="edit-mask"
:size="32"
@click="openEditAvatar"
>
<el-icon><EditPen /></el-icon>
</AppAvatar>
</div>
<el-input <el-input
v-model="form.name" v-model="form.name"
:placeholder="$t('views.functionLib.functionForm.form.functionName.placeholder')" :placeholder="$t('views.functionLib.functionForm.form.functionName.placeholder')"
@ -66,6 +99,74 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-form> </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()">
<el-icon class="mr-4"><Plus /></el-icon> {{ $t('common.add') }}
</el-button>
</div>
<el-table :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)">
<el-icon><EditPen /></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button type="primary" text @click="deleteInitField($index)">
<el-icon>
<Delete />
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="flex-between"> <div class="flex-between">
<h4 class="title-decoration-1 mb-16"> <h4 class="title-decoration-1 mb-16">
{{ $t('common.param.inputParam') }} {{ $t('common.param.inputParam') }}
@ -163,6 +264,8 @@
<FunctionDebugDrawer ref="FunctionDebugDrawerRef" /> <FunctionDebugDrawer ref="FunctionDebugDrawerRef" />
<FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" /> <FieldFormDialog ref="FieldFormDialogRef" @refresh="refreshFieldList" />
<UserFieldFormDialog ref="UserFieldFormDialogRef" @refresh="refreshInitFieldList"/>
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshFunctionLib" />
</el-drawer> </el-drawer>
</template> </template>
@ -177,6 +280,10 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { PermissionType, PermissionDesc } from '@/enums/model' import { PermissionType, PermissionDesc } from '@/enums/model'
import { t } from '@/locales' import { t } from '@/locales'
import UserFieldFormDialog from '@/workflow/nodes/base-node/component/UserFieldFormDialog.vue'
import {isAppIcon} from "@/utils/application";
import EditAvatarDialog from "./EditAvatarDialog.vue";
const props = defineProps({ const props = defineProps({
title: String title: String
}) })
@ -184,6 +291,8 @@ const props = defineProps({
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
const FieldFormDialogRef = ref() const FieldFormDialogRef = ref()
const FunctionDebugDrawerRef = ref() const FunctionDebugDrawerRef = ref()
const UserFieldFormDialogRef = ref()
const EditAvatarDialogRef = ref()
const FormRef = ref() const FormRef = ref()
@ -192,12 +301,15 @@ const loading = ref(false)
const visible = ref(false) const visible = ref(false)
const showEditor = ref(false) const showEditor = ref(false)
const currentIndex = ref<any>(null) const currentIndex = ref<any>(null)
const showEditIcon = ref(false)
const form = ref<functionLibData>({ const form = ref<functionLibData>({
name: '', name: '',
desc: '', desc: '',
code: '', code: '',
icon: '',
input_field_list: [], input_field_list: [],
init_field_list: [],
permission_type: 'PRIVATE' permission_type: 'PRIVATE'
}) })
@ -210,7 +322,9 @@ watch(visible, (bool) => {
name: '', name: '',
desc: '', desc: '',
code: '', code: '',
icon: '',
input_field_list: [], input_field_list: [],
init_field_list: [],
permission_type: 'PRIVATE' permission_type: 'PRIVATE'
} }
FormRef.value?.clearValidate() FormRef.value?.clearValidate()
@ -286,10 +400,44 @@ function refreshFieldList(data: any) {
currentIndex.value = null 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 refreshFunctionLib(data: any) {
form.value.icon = data
// console.log(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) => { const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
await formEl.validate((valid: any) => { await formEl.validate((valid: any) => {
if (valid) { if (valid) {
// console.log(form.value)
if (isEdit.value) { if (isEdit.value) {
functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading).then((res) => { functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading).then((res) => {
MsgSuccess(t('common.editSuccess')) MsgSuccess(t('common.editSuccess'))

View File

@ -0,0 +1,108 @@
<template>
<el-drawer v-model="debugVisible" size="60%" :append-to-body="true">
<template #header>
<div class="flex align-center" style="margin-left: -8px">
<el-button class="cursor mr-4" link @click.prevent="debugVisible = false">
<el-icon :size="20">
<Back />
</el-icon>
</el-button>
<h4>{{ $t('common.param.initParam') }}</h4>
</div>
</template>
<div>
<div v-if="form.init_field_list.length > 0">
<DynamicsForm
v-model="init_form_data"
:model="init_form_data"
label-position="top"
require-asterisk-position="right"
:render_data="form.init_field_list"
ref="dynamicsFormRef"
>
</DynamicsForm>
</div>
</div>
<template #footer>
<div>
<el-button type="primary" @click="submit()" :loading="loading">
{{ $t('common.save') }}
</el-button
>
</div>
</template>
</el-drawer>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import functionLibApi from '@/api/function-lib'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import { cloneDeep } from 'lodash'
const emit = defineEmits(['refresh'])
const dynamicsFormRef = ref()
const loading = ref(false)
const debugVisible = ref(false)
const showResult = ref(false)
const isSuccess = ref(false)
const result = ref('')
const init_form_data = ref<any>({})
const form = ref<any>({
init_field_list: []
})
watch(debugVisible, (bool) => {
if (!bool) {
showResult.value = false
isSuccess.value = false
result.value = ''
form.value = {
init_field_list: []
}
}
})
const submit = async () => {
dynamicsFormRef.value.validate().then(() => {
form.value.init_field_list.forEach((item: any) => {
item.value = init_form_data.value[item.field]
})
// console.log(init_form_data.value)
functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading)
.then((res) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh')
debugVisible.value = false
})
})
}
const open = (data: any) => {
if (data) {
form.value = cloneDeep(data)
}
init_form_data.value = form.value.init_field_list
.map((item: any) => {
if (item.value) {
return { [item.field]: item.value }
}
if (item.show_default_value === false) {
return { [item.field]: undefined }
}
return { [item.field]: item.default_value }
})
.reduce((x: any, y: any) => ({ ...x, ...y }), {})
debugVisible.value = true
}
defineExpose({
open
})
</script>
<style lang="scss"></style>

View File

@ -0,0 +1,104 @@
<template>
<el-dialog
:title="$t('views.functionLib.functionForm.form.permission_type.label')"
v-model="dialogVisible"
:close-on-click-modal="false"
:close-on-press-escape="false"
:destroy-on-close="true"
append-to-body
>
<el-form
label-position="top"
ref="fieldFormRef"
:rules="rules"
:model="form"
require-asterisk-position="right"
>
<el-radio-group v-model="form.permission_type">
<el-radio value="PRIVATE">
{{ $t('common.private') }}
{{ $t('views.template.templateForm.form.permissionType.privateDesc') }}
</el-radio>
<el-radio value="PUBLIC">
{{ $t('common.public') }}
{{ $t('views.template.templateForm.form.permissionType.publicDesc') }}
</el-radio>
</el-radio-group>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(fieldFormRef)" :loading="loading">
{{ isEdit ? $t('common.save') : $t('common.add') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import { cloneDeep } from 'lodash'
import { t } from '@/locales'
import functionLibApi from '@/api/function-lib'
import { MsgSuccess } from '@/utils/message'
const emit = defineEmits(['refresh'])
const fieldFormRef = ref()
const loading = ref<boolean>(false)
const isEdit = ref(false)
const form = ref<any>({
permission_type: 'PRIVATE'
})
const rules = reactive({
permission_type: [
{
required: true,
message: t('views.functionLib.functionForm.form.paramName.placeholder'),
trigger: 'blur'
}
]
})
const dialogVisible = ref<boolean>(false)
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
permission_type: 'PRIVATE'
}
isEdit.value = false
}
})
const open = (row: any) => {
if (row) {
form.value = cloneDeep(row)
isEdit.value = true
}
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading)
.then((res) => {
MsgSuccess(t('common.editSuccess'))
emit('refresh')
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@ -113,33 +113,7 @@
</div> </div>
<template #footer> <template #footer>
<div class="footer-content flex-between"> <div class="footer-content flex-between">
<div> <div></div>
<el-tooltip effect="dark" :content="$t('common.copy')" placement="top">
<el-button text @click.stop="copyFunctionLib(item)"
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
>
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" :content="$t('common.export')" placement="top">
<el-button text @click.stop="exportFunctionLib(item)"
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
>
<AppIcon iconName="app-export"></AppIcon>
</el-button>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
text
@click.stop="deleteFunctionLib(item)"
>
<el-icon><Delete /></el-icon>
</el-button>
</el-tooltip>
</div>
<div @click.stop> <div @click.stop>
<el-switch <el-switch
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)" :disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@ -147,6 +121,50 @@
@change="changeState($event, item)" @change="changeState($event, item)"
size="small" size="small"
/> />
<el-dropdown trigger="click">
<el-button text @click.stop>
<el-icon><MoreFilled /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="copyFunctionLib(item)"
>
<AppIcon iconName="app-copy"></AppIcon>
{{$t('common.copy')}}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="configInitParams(item)"
>
<AppIcon iconName="app-operation" class="mr-4"></AppIcon>
{{ $t('common.param.initParam') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="configPermission(item)"
>
<AppIcon iconName="app-copy"></AppIcon>
{{ $t('views.functionLib.functionForm.form.permission_type.label') }}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="exportFunctionLib(item)"
>
<AppIcon iconName="app-export"></AppIcon>
{{$t('common.export')}}
</el-dropdown-item>
<el-dropdown-item
:disabled="item.permission_type === 'PUBLIC' && !canEdit(item)"
@click.stop="deleteFunctionLib(item)"
>
<el-icon><Delete /></el-icon>
{{ $t('common.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div> </div>
</div> </div>
</template> </template>
@ -156,6 +174,8 @@
</InfiniteScroll> </InfiniteScroll>
</div> </div>
<FunctionFormDrawer ref="FunctionFormDrawerRef" @refresh="refresh" :title="title" /> <FunctionFormDrawer ref="FunctionFormDrawerRef" @refresh="refresh" :title="title" />
<PermissionDialog ref="PermissionDialogRef" @refresh="refresh" />
<InitParamDrawer ref="InitParamDrawerRef" @refresh="refresh" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -167,11 +187,15 @@ import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
import useStore from '@/stores' import useStore from '@/stores'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import { t } from '@/locales' import { t } from '@/locales'
import PermissionDialog from '@/views/function-lib/component/PermissionDialog.vue'
import InitParamDrawer from '@/views/function-lib/component/InitParamDrawer.vue'
const { user } = useStore() const { user } = useStore()
const loading = ref(false) const loading = ref(false)
const FunctionFormDrawerRef = ref() const FunctionFormDrawerRef = ref()
const PermissionDialogRef = ref()
const InitParamDrawerRef = ref()
const functionLibList = ref<any[]>([]) const functionLibList = ref<any[]>([])
@ -286,6 +310,14 @@ function exportFunctionLib(row: any) {
}) })
} }
function configPermission(item: any) {
PermissionDialogRef.value.open(item)
}
function configInitParams(item: any) {
InitParamDrawerRef.value.open(item)
}
function importFunctionLib(file: any) { function importFunctionLib(file: any) {
const formData = new FormData() const formData = new FormData()
formData.append('file', file.raw, file.name) formData.append('file', file.raw, file.name)