From ad0b0323845398d312054b5b5231184ab2fe78a4 Mon Sep 17 00:00:00 2001 From: CaptainB Date: Mon, 10 Mar 2025 11:12:49 +0800 Subject: [PATCH] feat: Support functionlib icon and init_fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --story=1017947 --user=刘瑞斌 【函数库】- 函数支持配置参数及操作优化 https://www.tapd.cn/57709429/s/1664936 --- ...ionlib_icon_functionlib_init_field_list.py | 23 +++ apps/function_lib/models/function.py | 2 + .../serializers/function_lib_serializer.py | 41 ++++- apps/function_lib/urls.py | 1 + apps/function_lib/views/function_lib_views.py | 14 +- ui/src/api/function-lib.ts | 11 +- ui/src/api/type/function-lib.ts | 2 + ui/src/locales/lang/en-US/common.ts | 3 +- ui/src/locales/lang/zh-CN/common.ts | 3 +- ui/src/locales/lang/zh-Hant/common.ts | 3 +- .../component/EditAvatarDialog.vue | 134 ++++++++++++++++ .../component/FunctionDebugDrawer.vue | 25 ++- .../component/FunctionFormDrawer.vue | 148 ++++++++++++++++++ .../component/InitParamDrawer.vue | 108 +++++++++++++ .../component/PermissionDialog.vue | 104 ++++++++++++ ui/src/views/function-lib/index.vue | 86 ++++++---- 16 files changed, 668 insertions(+), 40 deletions(-) create mode 100644 apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py create mode 100644 ui/src/views/function-lib/component/EditAvatarDialog.vue create mode 100644 ui/src/views/function-lib/component/InitParamDrawer.vue create mode 100644 ui/src/views/function-lib/component/PermissionDialog.vue diff --git a/apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py b/apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py new file mode 100644 index 000000000..3ea3a4637 --- /dev/null +++ b/apps/function_lib/migrations/0003_functionlib_icon_functionlib_init_field_list.py @@ -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='启动字段列表'), + ), + ] diff --git a/apps/function_lib/models/function.py b/apps/function_lib/models/function.py index 49a0e981b..a95339545 100644 --- a/apps/function_lib/models/function.py +++ b/apps/function_lib/models/function.py @@ -29,6 +29,8 @@ class FunctionLib(AppModelMixin): input_field_list = ArrayField(verbose_name="输入字段列表", base_field=models.JSONField(verbose_name="输入字段", default=dict) , 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) permission_type = models.CharField(max_length=20, verbose_name='权限类型', choices=PermissionType.choices, default=PermissionType.PRIVATE) diff --git a/apps/function_lib/serializers/function_lib_serializer.py b/apps/function_lib/serializers/function_lib_serializer.py index cccdfcdc3..a007cc287 100644 --- a/apps/function_lib/serializers/function_lib_serializer.py +++ b/apps/function_lib/serializers/function_lib_serializer.py @@ -10,23 +10,23 @@ import json import pickle import re import uuid -from typing import List from django.core import validators from django.db import transaction from django.db.models import QuerySet, Q from django.http import HttpResponse +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers, status from common.db.search import page_search 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.util.field_message import ErrMessage from common.util.function_code import FunctionExecutor +from dataset.models import Image from function_lib.models.function import FunctionLib from smartdoc.const import CONFIG -from django.utils.translation import gettext_lazy as _ function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) @@ -39,7 +39,7 @@ class FlibInstance: class FunctionLibModelSerializer(serializers.ModelSerializer): class Meta: 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'] @@ -65,6 +65,7 @@ class DebugField(serializers.Serializer): class DebugInstance(serializers.Serializer): debug_field_list = DebugField(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'))) @@ -80,6 +81,8 @@ class EditFunctionLib(serializers.Serializer): 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'))) @@ -93,6 +96,8 @@ class CreateFunctionLib(serializers.Serializer): 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=[ validators.RegexValidator(regex=re.compile("^PUBLIC|PRIVATE$"), message="权限只支持PUBLIC|PRIVATE", code=500) @@ -148,6 +153,7 @@ class FunctionLibSerializer(serializers.Serializer): code=instance.get('code'), user_id=self.data.get('user_id'), input_field_list=instance.get('input_field_list'), + init_field_list=instance.get('init_field_list'), permission_type=instance.get('permission_type'), is_active=instance.get('is_active', True)) function_lib.save() @@ -163,12 +169,16 @@ class FunctionLibSerializer(serializers.Serializer): input_field_list = debug_instance.get('input_field_list') code = debug_instance.get('code') 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'), field.get('is_required')) for field in [{'value': self.get_field_value(debug_field_list, field.get('name'), field.get('is_required')), **field} for field in input_field_list]} + # 合并初始化参数 + params = init_params | params return function_executor.exec_code(code, params) @staticmethod @@ -224,7 +234,7 @@ class FunctionLibSerializer(serializers.Serializer): if with_valid: self.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 ( field in instance and instance.get(field) is not None)} QuerySet(FunctionLib).filter(id=self.data.get('id')).update(**edit_dict) @@ -277,4 +287,23 @@ class FunctionLibSerializer(serializers.Serializer): permission_type='PRIVATE', is_active=function_lib.get('is_active')) function_lib_model.save() - return True \ No newline at end of file + 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 diff --git a/apps/function_lib/urls.py b/apps/function_lib/urls.py index 5bcf70bd9..d0c89f17a 100644 --- a/apps/function_lib/urls.py +++ b/apps/function_lib/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ path('function_lib/debug', views.FunctionLibView.Debug.as_view()), path('function_lib//export', views.FunctionLibView.Export.as_view()), path('function_lib/import', views.FunctionLibView.Import.as_view()), + path('function_lib//edit_icon', views.FunctionLibView.EditIcon.as_view()), path('function_lib/pylint', views.PyLintView.as_view()), path('function_lib/', views.FunctionLibView.Operate.as_view()), path("function_lib//", views.FunctionLibView.Page.as_view(), diff --git a/apps/function_lib/views/function_lib_views.py b/apps/function_lib/views/function_lib_views.py index 40d1b1084..51c15ea02 100644 --- a/apps/function_lib/views/function_lib_views.py +++ b/apps/function_lib/views/function_lib_views.py @@ -136,4 +136,16 @@ class FunctionLibView(APIView): @has_permissions(RoleConstants.ADMIN, RoleConstants.USER) def get(self, request: Request, id: str): return FunctionLibSerializer.Operate( - data={'id': id, 'user_id': request.user.id}).export() \ No newline at end of file + 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)) \ No newline at end of file diff --git a/ui/src/api/function-lib.ts b/ui/src/api/function-lib.ts index 41a90a96c..7fdbad8b0 100644 --- a/ui/src/api/function-lib.ts +++ b/ui/src/api/function-lib.ts @@ -112,6 +112,14 @@ const exportFunctionLib = ( ) } +const putFunctionLibIcon: ( + id: string, + data: any, + loading?: Ref +) => Promise> = (id, data, loading) => { + return put(`${prefix}/${id}/edit_icon`, data, undefined, loading) +} + const importFunctionLib: (data: any, loading?: Ref) => Promise> = ( data, loading @@ -128,5 +136,6 @@ export default { getFunctionLibById, exportFunctionLib, importFunctionLib, - pylint + pylint, + putFunctionLibIcon } diff --git a/ui/src/api/type/function-lib.ts b/ui/src/api/type/function-lib.ts index 2c5efe254..0f51764e0 100644 --- a/ui/src/api/type/function-lib.ts +++ b/ui/src/api/type/function-lib.ts @@ -1,10 +1,12 @@ interface functionLibData { id?: String name?: String + icon?: String desc?: String code?: String permission_type?: 'PRIVATE' | 'PUBLIC' input_field_list?: Array + init_field_list?: Array is_active?: Boolean } diff --git a/ui/src/locales/lang/en-US/common.ts b/ui/src/locales/lang/en-US/common.ts index e8fbb87e9..12f83c7fe 100644 --- a/ui/src/locales/lang/en-US/common.ts +++ b/ui/src/locales/lang/en-US/common.ts @@ -52,7 +52,8 @@ export default { }, param: { outputParam: 'Output Parameters', - inputParam: 'Input Parameters' + inputParam: 'Input Parameters', + initParam: 'Startup Parameters', }, inputPlaceholder: 'Please input', diff --git a/ui/src/locales/lang/zh-CN/common.ts b/ui/src/locales/lang/zh-CN/common.ts index cdcde3d60..da89789ee 100644 --- a/ui/src/locales/lang/zh-CN/common.ts +++ b/ui/src/locales/lang/zh-CN/common.ts @@ -55,7 +55,8 @@ export default { content: '内容', param: { outputParam: '输出参数', - inputParam:'输入参数' + inputParam: '输入参数', + initParam: '启动参数', }, rename:'重命名' } diff --git a/ui/src/locales/lang/zh-Hant/common.ts b/ui/src/locales/lang/zh-Hant/common.ts index 3281456de..ea4bbe118 100644 --- a/ui/src/locales/lang/zh-Hant/common.ts +++ b/ui/src/locales/lang/zh-Hant/common.ts @@ -55,7 +55,8 @@ export default { content: '内容', param: { outputParam: '輸出參數', - inputParam: '輸入參數' + inputParam: '輸入參數', + initParam: '啟動參數', }, rename: '重命名' } diff --git a/ui/src/views/function-lib/component/EditAvatarDialog.vue b/ui/src/views/function-lib/component/EditAvatarDialog.vue new file mode 100644 index 000000000..0dd3189eb --- /dev/null +++ b/ui/src/views/function-lib/component/EditAvatarDialog.vue @@ -0,0 +1,134 @@ + + + diff --git a/ui/src/views/function-lib/component/FunctionDebugDrawer.vue b/ui/src/views/function-lib/component/FunctionDebugDrawer.vue index 44856ac82..84f81aff3 100644 --- a/ui/src/views/function-lib/component/FunctionDebugDrawer.vue +++ b/ui/src/views/function-lib/component/FunctionDebugDrawer.vue @@ -11,6 +11,22 @@
+
+

+ {{ $t('common.param.initParam') }} +

+ + + + +

{{ $t('common.param.inputParam') }} @@ -95,6 +111,7 @@ import { ref, reactive, watch } from 'vue' import functionLibApi from '@/api/function-lib' import type { FormInstance } from 'element-plus' +import DynamicsForm from '@/components/dynamics-form/index.vue' const FormRef = ref() const loading = ref(false) @@ -102,11 +119,13 @@ const debugVisible = ref(false) const showResult = ref(false) const isSuccess = ref(false) const result = ref('') +const init_form_data = ref({}) const form = ref({ debug_field_list: [], code: '', - input_field_list: [] + input_field_list: [], + init_field_list: [] }) watch(debugVisible, (bool) => { @@ -117,7 +136,8 @@ watch(debugVisible, (bool) => { form.value = { debug_field_list: [], 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.input_field_list = data.input_field_list + form.value.init_field_list = data.init_field_list debugVisible.value = true } diff --git a/ui/src/views/function-lib/component/FunctionFormDrawer.vue b/ui/src/views/function-lib/component/FunctionFormDrawer.vue index 1435aca93..95d748b00 100644 --- a/ui/src/views/function-lib/component/FunctionFormDrawer.vue +++ b/ui/src/views/function-lib/component/FunctionFormDrawer.vue @@ -20,6 +20,39 @@ :label="$t('views.functionLib.functionForm.form.functionName.label')" prop="name" > +
+ + + + + + + +
+
+

+ {{ $t('common.param.initParam') }} +

+ + {{ $t('common.add') }} + +
+ + + + + + + + + + + + + +

{{ $t('common.param.inputParam') }} @@ -163,6 +264,8 @@ + + @@ -177,6 +280,10 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message' import { cloneDeep } from 'lodash' import { PermissionType, PermissionDesc } from '@/enums/model' 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({ title: String }) @@ -184,6 +291,8 @@ const props = defineProps({ const emit = defineEmits(['refresh']) const FieldFormDialogRef = ref() const FunctionDebugDrawerRef = ref() +const UserFieldFormDialogRef = ref() +const EditAvatarDialogRef = ref() const FormRef = ref() @@ -192,12 +301,15 @@ const loading = ref(false) const visible = ref(false) const showEditor = ref(false) const currentIndex = ref(null) +const showEditIcon = ref(false) const form = ref({ name: '', desc: '', code: '', + icon: '', input_field_list: [], + init_field_list: [], permission_type: 'PRIVATE' }) @@ -210,7 +322,9 @@ watch(visible, (bool) => { name: '', desc: '', code: '', + icon: '', input_field_list: [], + init_field_list: [], permission_type: 'PRIVATE' } FormRef.value?.clearValidate() @@ -286,10 +400,44 @@ function refreshFieldList(data: any) { 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) => { if (!formEl) return await formEl.validate((valid: any) => { if (valid) { + // console.log(form.value) if (isEdit.value) { functionLibApi.putFunctionLib(form.value?.id as string, form.value, loading).then((res) => { MsgSuccess(t('common.editSuccess')) diff --git a/ui/src/views/function-lib/component/InitParamDrawer.vue b/ui/src/views/function-lib/component/InitParamDrawer.vue new file mode 100644 index 000000000..f5923ca00 --- /dev/null +++ b/ui/src/views/function-lib/component/InitParamDrawer.vue @@ -0,0 +1,108 @@ + + + + diff --git a/ui/src/views/function-lib/component/PermissionDialog.vue b/ui/src/views/function-lib/component/PermissionDialog.vue new file mode 100644 index 000000000..ca9104e9a --- /dev/null +++ b/ui/src/views/function-lib/component/PermissionDialog.vue @@ -0,0 +1,104 @@ + + + diff --git a/ui/src/views/function-lib/index.vue b/ui/src/views/function-lib/index.vue index 732b2c9de..25ba3a03a 100644 --- a/ui/src/views/function-lib/index.vue +++ b/ui/src/views/function-lib/index.vue @@ -113,33 +113,7 @@

@@ -156,6 +174,8 @@

+ +