mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 【应用】支持自定义上传应用的logo #54
* feat: 【知识库】本地上传的文档内带的图片能同步到 maxkb 里 #69 * feat: 【应用】支持自定义上传应用的logo #54
This commit is contained in:
parent
73dfcb6fb0
commit
b26265fefd
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-04-23 11:16
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('application', '0002_chat_client_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='application',
|
||||||
|
name='icon',
|
||||||
|
field=models.CharField(default='/ui/favicon.ico', max_length=256, verbose_name='应用icon'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -37,6 +37,7 @@ class Application(AppModelMixin):
|
||||||
dataset_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict)
|
dataset_setting = models.JSONField(verbose_name="数据集参数设置", default=get_dataset_setting_dict)
|
||||||
model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict)
|
model_setting = models.JSONField(verbose_name="模型参数相关设置", default=get_model_setting_dict)
|
||||||
problem_optimization = models.BooleanField(verbose_name="问题优化", default=False)
|
problem_optimization = models.BooleanField(verbose_name="问题优化", default=False)
|
||||||
|
icon = models.CharField(max_length=256, verbose_name="应用icon", default="/ui/favicon.ico")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_default_model_prompt():
|
def get_default_model_prompt():
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ from common.constants.authentication_type import AuthenticationType
|
||||||
from common.db.search import get_dynamics_model, native_search, native_page_search
|
from common.db.search import get_dynamics_model, native_search, native_page_search
|
||||||
from common.db.sql_execute import select_list
|
from common.db.sql_execute import select_list
|
||||||
from common.exception.app_exception import AppApiException, NotFound404
|
from common.exception.app_exception import AppApiException, NotFound404
|
||||||
|
from common.field.common import UploadedImageField
|
||||||
from common.util.field_message import ErrMessage
|
from common.util.field_message import ErrMessage
|
||||||
from common.util.file_util import get_file_content
|
from common.util.file_util import get_file_content
|
||||||
from dataset.models import DataSet, Document
|
from dataset.models import DataSet, Document, Image
|
||||||
from dataset.serializers.common_serializers import list_paragraph
|
from dataset.serializers.common_serializers import list_paragraph
|
||||||
from embedding.models import SearchMode
|
from embedding.models import SearchMode
|
||||||
from setting.models import AuthOperate
|
from setting.models import AuthOperate
|
||||||
|
|
@ -251,6 +252,7 @@ class ApplicationSerializer(serializers.Serializer):
|
||||||
# 问题补全
|
# 问题补全
|
||||||
problem_optimization = serializers.BooleanField(required=False, allow_null=True,
|
problem_optimization = serializers.BooleanField(required=False, allow_null=True,
|
||||||
error_messages=ErrMessage.boolean("问题补全"))
|
error_messages=ErrMessage.boolean("问题补全"))
|
||||||
|
icon = serializers.CharField(required=False, allow_null=True, error_messages=ErrMessage.char("icon图标"))
|
||||||
|
|
||||||
class Create(serializers.Serializer):
|
class Create(serializers.Serializer):
|
||||||
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
||||||
|
|
@ -380,7 +382,8 @@ class ApplicationSerializer(serializers.Serializer):
|
||||||
def reset_application(application: Dict):
|
def reset_application(application: Dict):
|
||||||
application['multiple_rounds_dialogue'] = True if application.get('dialogue_number') > 0 else False
|
application['multiple_rounds_dialogue'] = True if application.get('dialogue_number') > 0 else False
|
||||||
del application['dialogue_number']
|
del application['dialogue_number']
|
||||||
application['dataset_setting'] = {**application['dataset_setting'], 'search_mode': 'embedding'}
|
if 'dataset_setting' in application:
|
||||||
|
application['dataset_setting'] = {**application['dataset_setting'], 'search_mode': 'embedding'}
|
||||||
return application
|
return application
|
||||||
|
|
||||||
def page(self, current_page: int, page_size: int, with_valid=True):
|
def page(self, current_page: int, page_size: int, with_valid=True):
|
||||||
|
|
@ -393,7 +396,25 @@ class ApplicationSerializer(serializers.Serializer):
|
||||||
class ApplicationModel(serializers.ModelSerializer):
|
class ApplicationModel(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Application
|
model = Application
|
||||||
fields = ['id', 'name', 'desc', 'prologue', 'dialogue_number']
|
fields = ['id', 'name', 'desc', 'prologue', 'dialogue_number', 'icon']
|
||||||
|
|
||||||
|
class IconOperate(serializers.Serializer):
|
||||||
|
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
|
||||||
|
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id"))
|
||||||
|
image = UploadedImageField(required=True, error_messages=ErrMessage.image("图片"))
|
||||||
|
|
||||||
|
def edit(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
application = QuerySet(Application).filter(id=self.data.get('application_id')).first()
|
||||||
|
if application is None:
|
||||||
|
raise AppApiException(500, '不存在的应用id')
|
||||||
|
image_id = uuid.uuid1()
|
||||||
|
image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name)
|
||||||
|
image.save()
|
||||||
|
application.icon = f'/api/image/{image_id}'
|
||||||
|
application.save()
|
||||||
|
return {**ApplicationSerializer.Query.reset_application(ApplicationSerializerModel(application).data)}
|
||||||
|
|
||||||
class Operate(serializers.Serializer):
|
class Operate(serializers.Serializer):
|
||||||
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
|
application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("应用id"))
|
||||||
|
|
@ -457,7 +478,7 @@ class ApplicationSerializer(serializers.Serializer):
|
||||||
|
|
||||||
update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',
|
update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',
|
||||||
'dataset_setting', 'model_setting', 'problem_optimization',
|
'dataset_setting', 'model_setting', 'problem_optimization',
|
||||||
'api_key_is_active']
|
'api_key_is_active', 'icon']
|
||||||
for update_key in update_keys:
|
for update_key in update_keys:
|
||||||
if update_key in instance and instance.get(update_key) is not None:
|
if update_key in instance and instance.get(update_key) is not None:
|
||||||
if update_key == 'multiple_rounds_dialogue':
|
if update_key == 'multiple_rounds_dialogue':
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,17 @@ from common.mixins.api_mixin import ApiMixin
|
||||||
|
|
||||||
|
|
||||||
class ApplicationApi(ApiMixin):
|
class ApplicationApi(ApiMixin):
|
||||||
|
class EditApplicationIcon(ApiMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request_params_api():
|
||||||
|
return [
|
||||||
|
openapi.Parameter(name='file',
|
||||||
|
in_=openapi.IN_FORM,
|
||||||
|
type=openapi.TYPE_FILE,
|
||||||
|
required=True,
|
||||||
|
description='上传文件')
|
||||||
|
]
|
||||||
|
|
||||||
class Authentication(ApiMixin):
|
class Authentication(ApiMixin):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_request_body_api():
|
def get_request_body_api():
|
||||||
|
|
@ -143,7 +154,9 @@ class ApplicationApi(ApiMixin):
|
||||||
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
|
'dataset_setting': ApplicationApi.DatasetSetting.get_request_body_api(),
|
||||||
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
|
'model_setting': ApplicationApi.ModelSetting.get_request_body_api(),
|
||||||
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
|
'problem_optimization': openapi.Schema(type=openapi.TYPE_BOOLEAN, title="问题优化",
|
||||||
description="是否开启问题优化", default=True)
|
description="是否开启问题优化", default=True),
|
||||||
|
'icon': openapi.Schema(type=openapi.TYPE_STRING, title="icon",
|
||||||
|
description="icon", default="/ui/favicon.ico")
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ urlpatterns = [
|
||||||
path('application/profile', views.Application.Profile.as_view()),
|
path('application/profile', views.Application.Profile.as_view()),
|
||||||
path('application/embed', views.Application.Embed.as_view()),
|
path('application/embed', views.Application.Embed.as_view()),
|
||||||
path('application/authentication', views.Application.Authentication.as_view()),
|
path('application/authentication', views.Application.Authentication.as_view()),
|
||||||
|
path('application/<str:application_id>/edit_icon', views.Application.EditIcon.as_view()),
|
||||||
path('application/<str:application_id>/statistics/customer_count',
|
path('application/<str:application_id>/statistics/customer_count',
|
||||||
views.ApplicationStatistics.CustomerCount.as_view()),
|
views.ApplicationStatistics.CustomerCount.as_view()),
|
||||||
path('application/<str:application_id>/statistics/customer_count_trend',
|
path('application/<str:application_id>/statistics/customer_count_trend',
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from drf_yasg.utils import swagger_auto_schema
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.parsers import MultiPartParser
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
|
|
@ -131,6 +132,28 @@ class ApplicationStatistics(APIView):
|
||||||
class Application(APIView):
|
class Application(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
class EditIcon(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
parser_classes = [MultiPartParser]
|
||||||
|
|
||||||
|
@action(methods=['PUT'], detail=False)
|
||||||
|
@swagger_auto_schema(operation_summary="修改应用icon",
|
||||||
|
operation_id="修改应用icon",
|
||||||
|
tags=['应用'],
|
||||||
|
manual_parameters=ApplicationApi.EditApplicationIcon.get_request_params_api(),
|
||||||
|
request_body=ApplicationApi.Operate.get_request_body_api())
|
||||||
|
@has_permissions(ViewPermission(
|
||||||
|
[RoleConstants.ADMIN, RoleConstants.USER],
|
||||||
|
[lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.MANAGE,
|
||||||
|
dynamic_tag=keywords.get('application_id'))],
|
||||||
|
compare=CompareConstants.AND), PermissionConstants.APPLICATION_EDIT,
|
||||||
|
compare=CompareConstants.AND)
|
||||||
|
def put(self, request: Request, application_id: str):
|
||||||
|
return result.success(
|
||||||
|
ApplicationSerializer.IconOperate(
|
||||||
|
data={'application_id': application_id, 'user_id': request.user.id,
|
||||||
|
'image': request.FILES.get('file')}).edit(request.data))
|
||||||
|
|
||||||
class Embed(APIView):
|
class Embed(APIView):
|
||||||
@action(methods=["GET"], detail=False)
|
@action(methods=["GET"], detail=False)
|
||||||
@swagger_auto_schema(operation_summary="获取嵌入js",
|
@swagger_auto_schema(operation_summary="获取嵌入js",
|
||||||
|
|
|
||||||
|
|
@ -32,3 +32,11 @@ class FunctionField(serializers.Field):
|
||||||
|
|
||||||
def to_representation(self, value):
|
def to_representation(self, value):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class UploadedImageField(serializers.ImageField):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
def to_representation(self, value):
|
||||||
|
return value
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,14 @@ class StaticHeadersMiddleware(MiddlewareMixin):
|
||||||
if request.path.startswith('/ui/chat/'):
|
if request.path.startswith('/ui/chat/'):
|
||||||
access_token = request.path.replace('/ui/chat/', '')
|
access_token = request.path.replace('/ui/chat/', '')
|
||||||
application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()
|
application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()
|
||||||
if application_access_token is not None and application_access_token.white_active:
|
if application_access_token is not None:
|
||||||
# 添加自定义的响应头
|
if application_access_token.white_active:
|
||||||
response['Content-Security-Policy'] = f'frame-ancestors {" ".join(application_access_token.white_list)}'
|
# 添加自定义的响应头
|
||||||
|
response[
|
||||||
|
'Content-Security-Policy'] = f'frame-ancestors {" ".join(application_access_token.white_list)}'
|
||||||
|
response.content = (response.content.decode('utf-8').replace(
|
||||||
|
'<link rel="icon" href="/ui/favicon.ico" />',
|
||||||
|
f'<link rel="icon" href="{application_access_token.application.icon}" />')
|
||||||
|
.replace('<title>MaxKB</title>', f'<title>{application_access_token.application.name}</title>').encode(
|
||||||
|
"utf-8"))
|
||||||
return response
|
return response
|
||||||
|
|
|
||||||
|
|
@ -95,3 +95,12 @@ class ErrMessage:
|
||||||
'invalid': gettext_lazy('【%s】日期格式错误。请改用以下格式之一: {format}。'),
|
'invalid': gettext_lazy('【%s】日期格式错误。请改用以下格式之一: {format}。'),
|
||||||
'datetime': gettext_lazy('【%s】应为日期,但得到的是日期时间。')
|
'datetime': gettext_lazy('【%s】应为日期,但得到的是日期时间。')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def image(field: str):
|
||||||
|
return {
|
||||||
|
'required': gettext_lazy('【%s】此字段必填。' % field),
|
||||||
|
'null': gettext_lazy('【%s】此字段不能为null。' % field),
|
||||||
|
'invalid_image': gettext_lazy('【%s】上载有效的图像。您上载的文件不是图像或图像已损坏。' % field),
|
||||||
|
'max_length': gettext_lazy('请确保此文件名最多包含 {max_length} 个字符(长度为 {length})。')
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Generated by Django 4.1.13 on 2024-04-22 19:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('dataset', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Image',
|
||||||
|
fields=[
|
||||||
|
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('update_time', models.DateTimeField(auto_now=True, verbose_name='修改时间')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid1, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),
|
||||||
|
('image', models.BinaryField(verbose_name='图片数据')),
|
||||||
|
('image_name', models.CharField(default='', max_length=256, verbose_name='图片名称')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'image',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -105,3 +105,12 @@ class ProblemParagraphMapping(AppModelMixin):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "problem_paragraph_mapping"
|
db_table = "problem_paragraph_mapping"
|
||||||
|
|
||||||
|
|
||||||
|
class Image(AppModelMixin):
|
||||||
|
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
|
||||||
|
image = models.BinaryField(verbose_name="图片数据")
|
||||||
|
image_name = models.CharField(max_length=256, verbose_name="图片名称", default="")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = "image"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: maxkb
|
||||||
|
@Author:虎
|
||||||
|
@file: image_serializers.py
|
||||||
|
@date:2024/4/22 16:36
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db.models import QuerySet
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from common.exception.app_exception import NotFound404
|
||||||
|
from common.field.common import UploadedImageField
|
||||||
|
from common.util.field_message import ErrMessage
|
||||||
|
from dataset.models import Image
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSerializer(serializers.Serializer):
|
||||||
|
image = UploadedImageField(required=True, error_messages=ErrMessage.image("图片"))
|
||||||
|
|
||||||
|
def upload(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
image_id = uuid.uuid1()
|
||||||
|
image = Image(id=image_id, image=self.data.get('image').read(), image_name=self.data.get('image').name)
|
||||||
|
image.save()
|
||||||
|
return f'/api/image/{image_id}'
|
||||||
|
|
||||||
|
class Operate(serializers.Serializer):
|
||||||
|
id = serializers.UUIDField(required=True)
|
||||||
|
|
||||||
|
def get(self, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
image_id = self.data.get('id')
|
||||||
|
image = QuerySet(Image).filter(id=image_id).first()
|
||||||
|
if image is None:
|
||||||
|
raise NotFound404(404, "不存在的图片")
|
||||||
|
return HttpResponse(image.image, status=200, headers={'Content-Type': 'image/png'})
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: maxkb
|
||||||
|
@Author:虎
|
||||||
|
@file: image_api.py
|
||||||
|
@date:2024/4/23 11:23
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from drf_yasg import openapi
|
||||||
|
|
||||||
|
from common.mixins.api_mixin import ApiMixin
|
||||||
|
|
||||||
|
|
||||||
|
class ImageApi(ApiMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request_params_api():
|
||||||
|
return [openapi.Parameter(name='file',
|
||||||
|
in_=openapi.IN_FORM,
|
||||||
|
type=openapi.TYPE_FILE,
|
||||||
|
required=True,
|
||||||
|
description='上传图片文件')
|
||||||
|
]
|
||||||
|
|
@ -40,5 +40,6 @@ urlpatterns = [
|
||||||
path('dataset/<str:dataset_id>/problem/<int:current_page>/<int:page_size>', views.Problem.Page.as_view()),
|
path('dataset/<str:dataset_id>/problem/<int:current_page>/<int:page_size>', views.Problem.Page.as_view()),
|
||||||
path('dataset/<str:dataset_id>/problem/<str:problem_id>', views.Problem.Operate.as_view()),
|
path('dataset/<str:dataset_id>/problem/<str:problem_id>', views.Problem.Operate.as_view()),
|
||||||
path('dataset/<str:dataset_id>/problem/<str:problem_id>/paragraph', views.Problem.Paragraph.as_view()),
|
path('dataset/<str:dataset_id>/problem/<str:problem_id>/paragraph', views.Problem.Paragraph.as_view()),
|
||||||
|
path('image/<str:image_id>', views.Image.Operate.as_view()),
|
||||||
|
path('image', views.Image.as_view())
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ from .dataset import *
|
||||||
from .document import *
|
from .document import *
|
||||||
from .paragraph import *
|
from .paragraph import *
|
||||||
from .problem import *
|
from .problem import *
|
||||||
|
from .image import *
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: maxkb
|
||||||
|
@Author:虎
|
||||||
|
@file: image.py
|
||||||
|
@date:2024/4/22 16:23
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from drf_yasg import openapi
|
||||||
|
from drf_yasg.utils import swagger_auto_schema
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.parsers import MultiPartParser
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.views import Request
|
||||||
|
|
||||||
|
from common.auth import TokenAuth
|
||||||
|
from common.response import result
|
||||||
|
from dataset.serializers.image_serializers import ImageSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class Image(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
parser_classes = [MultiPartParser]
|
||||||
|
|
||||||
|
@action(methods=['POST'], detail=False)
|
||||||
|
@swagger_auto_schema(operation_summary="上传图片",
|
||||||
|
operation_id="上传图片",
|
||||||
|
manual_parameters=[openapi.Parameter(name='file',
|
||||||
|
in_=openapi.IN_FORM,
|
||||||
|
type=openapi.TYPE_FILE,
|
||||||
|
required=True,
|
||||||
|
description='上传文件')],
|
||||||
|
tags=["图片"])
|
||||||
|
def post(self, request: Request):
|
||||||
|
return result.success(ImageSerializer(data={'image': request.FILES.get('file')}).upload())
|
||||||
|
|
||||||
|
class Operate(APIView):
|
||||||
|
@action(methods=['GET'], detail=False)
|
||||||
|
@swagger_auto_schema(operation_summary="获取图片",
|
||||||
|
operation_id="获取图片",
|
||||||
|
tags=["图片"])
|
||||||
|
def get(self, request: Request, image_id: str):
|
||||||
|
return ImageSerializer.Operate(data={'id': image_id}).get()
|
||||||
|
|
@ -67,10 +67,24 @@ const getStatistics: (
|
||||||
return get(`${prefix}/${application_id}/statistics/chat_record_aggregate_trend`, data, loading)
|
return get(`${prefix}/${application_id}/statistics/chat_record_aggregate_trend`, data, loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改应用icon
|
||||||
|
* @param 参数 application_id
|
||||||
|
* data: file
|
||||||
|
*/
|
||||||
|
const putAppIcon: (
|
||||||
|
application_id: string,
|
||||||
|
data: any,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) => Promise<Result<any>> = (application_id, data, loading) => {
|
||||||
|
return put(`${prefix}/${application_id}/edit_icon`, data, undefined, loading)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getAPIKey,
|
getAPIKey,
|
||||||
postAPIKey,
|
postAPIKey,
|
||||||
delAPIKey,
|
delAPIKey,
|
||||||
putAPIKey,
|
putAPIKey,
|
||||||
getStatistics
|
getStatistics,
|
||||||
|
putAppIcon
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Result } from '@/request/Result'
|
||||||
|
import { get, post, del, put } from '@/request/index'
|
||||||
|
|
||||||
|
const prefix = '/image'
|
||||||
|
/**
|
||||||
|
* 上传图片
|
||||||
|
* @param 参数 file:file
|
||||||
|
*/
|
||||||
|
const postImage: (data: any) => Promise<Result<any>> = (data) => {
|
||||||
|
return post(`${prefix}`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
postImage
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ interface ApplicationFormType {
|
||||||
dataset_setting?: any
|
dataset_setting?: any
|
||||||
model_setting?: any
|
model_setting?: any
|
||||||
problem_optimization?: boolean
|
problem_optimization?: boolean
|
||||||
|
icon?: string | undefined
|
||||||
}
|
}
|
||||||
interface chatType {
|
interface chatType {
|
||||||
id: string
|
id: string
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
class="mr-8"
|
class="mr-8"
|
||||||
:size="24"
|
:size="24"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-else-if="isDataset && currentType === '1'"
|
v-else-if="isDataset && currentType === '1'"
|
||||||
class="mr-8 avatar-purple"
|
class="mr-8 avatar-purple"
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,19 @@ const useApplicationStore = defineStore({
|
||||||
},
|
},
|
||||||
async refreshAccessToken(token: string) {
|
async refreshAccessToken(token: string) {
|
||||||
this.asyncAppAuthentication(token)
|
this.asyncAppAuthentication(token)
|
||||||
|
},
|
||||||
|
// 修改应用
|
||||||
|
async asyncPutApplication(id: string, data: any, loading?: Ref<boolean>) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
applicationApi
|
||||||
|
.putApplication(id, data, loading)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,10 @@
|
||||||
.el-popover {
|
.el-popover {
|
||||||
--el-popover-padding: 16px;
|
--el-popover-padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-radio {
|
||||||
|
--el-radio-font-weight: 400;
|
||||||
|
}
|
||||||
.el-radio__input.is-checked + .el-radio__label {
|
.el-radio__input.is-checked + .el-radio__label {
|
||||||
color: var(--app-text-color);
|
color: var(--app-text-color);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
export const defaultIcon = '/ui/favicon.ico'
|
||||||
|
|
||||||
|
// 是否显示字母 / icon
|
||||||
|
export function isAppIcon(url: string | undefined) {
|
||||||
|
return url === defaultIcon ? '' : url
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
<template>
|
||||||
|
<el-dialog title="设置 Logo" v-model="dialogVisible">
|
||||||
|
<el-radio-group v-model="radioType" class="card__block mb-16">
|
||||||
|
<div>
|
||||||
|
<el-radio value="default">
|
||||||
|
<p>默认 Logo</p>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="detail?.name"
|
||||||
|
:name="detail?.name"
|
||||||
|
pinyinColor
|
||||||
|
class="mt-8 mb-8"
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
</el-radio>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<el-radio value="custom">
|
||||||
|
<p>自定义上传</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"
|
||||||
|
:limit="1"
|
||||||
|
accept="image/*"
|
||||||
|
:on-change="onChange"
|
||||||
|
>
|
||||||
|
<el-button icon="Upload">上传</el-button>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
<div class="el-upload__tip info mt-16">
|
||||||
|
建议尺寸 32*32,支持 ico、png , 大小不超过200KB
|
||||||
|
</div>
|
||||||
|
</el-radio>
|
||||||
|
</div>
|
||||||
|
</el-radio-group>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||||
|
<el-button type="primary" @click="submit" :loading="loading"> 保存 </el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import overviewApi from '@/api/application-overview'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { MsgSuccess, MsgError } from '@/utils/message'
|
||||||
|
import { defaultIcon, isAppIcon } from '@/utils/application'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
|
||||||
|
const { application } = useStore()
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { id } //应用id
|
||||||
|
} = route
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
//1、判断文件大小是否合法,文件限制不能大于 200KB
|
||||||
|
const isLimit = file?.size / 1024 < 200
|
||||||
|
if (!isLimit) {
|
||||||
|
MsgError('文件大小超过 200KB')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iconFile.value = file
|
||||||
|
fileURL.value = URL.createObjectURL(file.raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
if (radioType.value === 'default') {
|
||||||
|
application.asyncPutApplication(id as string, { icon: defaultIcon }, loading).then((res) => {
|
||||||
|
emit('refresh')
|
||||||
|
MsgSuccess('设置成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
} else if (radioType.value === 'custom' && iconFile.value) {
|
||||||
|
let fd = new FormData()
|
||||||
|
fd.append('file', iconFile.value.raw)
|
||||||
|
overviewApi.putAppIcon(id as string, fd, loading).then((res: any) => {
|
||||||
|
emit('refresh')
|
||||||
|
MsgSuccess('设置成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError('请上传一张图片')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scope>
|
||||||
|
.card__block {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
.el-radio {
|
||||||
|
align-items: flex-start;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.el-radio__inner {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -5,14 +5,37 @@
|
||||||
<h4 class="title-decoration-1 mb-16">应用信息</h4>
|
<h4 class="title-decoration-1 mb-16">应用信息</h4>
|
||||||
<el-card shadow="never" class="overview-card" v-loading="loading">
|
<el-card shadow="never" class="overview-card" v-loading="loading">
|
||||||
<div class="title flex align-center">
|
<div class="title flex align-center">
|
||||||
<AppAvatar
|
<div
|
||||||
v-if="detail?.name"
|
class="edit-avatar mr-12"
|
||||||
:name="detail?.name"
|
@mouseenter="showEditIcon = true"
|
||||||
pinyinColor
|
@mouseleave="showEditIcon = false"
|
||||||
class="mr-12"
|
>
|
||||||
shape="square"
|
<AppAvatar
|
||||||
:size="32"
|
v-if="isAppIcon(detail?.icon)"
|
||||||
/>
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
>
|
||||||
|
<img :src="detail?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="detail?.name"
|
||||||
|
:name="detail?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
/>
|
||||||
|
<AppAvatar
|
||||||
|
v-if="showEditIcon"
|
||||||
|
shape="square"
|
||||||
|
class="edit-mask"
|
||||||
|
:size="32"
|
||||||
|
@click="openEditAvatar"
|
||||||
|
>
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</AppAvatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4>{{ detail?.name }}</h4>
|
<h4>{{ detail?.name }}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -102,6 +125,7 @@
|
||||||
<EmbedDialog ref="EmbedDialogRef" />
|
<EmbedDialog ref="EmbedDialogRef" />
|
||||||
<APIKeyDialog ref="APIKeyDialogRef" />
|
<APIKeyDialog ref="APIKeyDialogRef" />
|
||||||
<LimitDialog ref="LimitDialogRef" @refresh="refresh" />
|
<LimitDialog ref="LimitDialogRef" @refresh="refresh" />
|
||||||
|
<EditAvatarDialog ref="EditAvatarDialogRef" @refresh="refreshIcon" />
|
||||||
</LayoutContainer>
|
</LayoutContainer>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
@ -110,14 +134,14 @@ import { useRoute } from 'vue-router'
|
||||||
import EmbedDialog from './component/EmbedDialog.vue'
|
import EmbedDialog from './component/EmbedDialog.vue'
|
||||||
import APIKeyDialog from './component/APIKeyDialog.vue'
|
import APIKeyDialog from './component/APIKeyDialog.vue'
|
||||||
import LimitDialog from './component/LimitDialog.vue'
|
import LimitDialog from './component/LimitDialog.vue'
|
||||||
|
import EditAvatarDialog from './component/EditAvatarDialog.vue'
|
||||||
import StatisticsCharts from './component/StatisticsCharts.vue'
|
import StatisticsCharts from './component/StatisticsCharts.vue'
|
||||||
import applicationApi from '@/api/application'
|
import applicationApi from '@/api/application'
|
||||||
import overviewApi from '@/api/application-overview'
|
import overviewApi from '@/api/application-overview'
|
||||||
import { nowDate, beforeDay } from '@/utils/time'
|
import { nowDate, beforeDay } from '@/utils/time'
|
||||||
|
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
|
|
||||||
import { copyClick } from '@/utils/clipboard'
|
import { copyClick } from '@/utils/clipboard'
|
||||||
|
import { isAppIcon } from '@/utils/application'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const { application } = useStore()
|
const { application } = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
@ -127,6 +151,7 @@ const {
|
||||||
|
|
||||||
const apiUrl = window.location.origin + '/doc'
|
const apiUrl = window.location.origin + '/doc'
|
||||||
|
|
||||||
|
const EditAvatarDialogRef = ref()
|
||||||
const LimitDialogRef = ref()
|
const LimitDialogRef = ref()
|
||||||
const APIKeyDialogRef = ref()
|
const APIKeyDialogRef = ref()
|
||||||
const EmbedDialogRef = ref()
|
const EmbedDialogRef = ref()
|
||||||
|
|
@ -175,6 +200,12 @@ const daterange = ref({
|
||||||
const statisticsLoading = ref(false)
|
const statisticsLoading = ref(false)
|
||||||
const statisticsData = ref([])
|
const statisticsData = ref([])
|
||||||
|
|
||||||
|
const showEditIcon = ref(false)
|
||||||
|
|
||||||
|
function openEditAvatar() {
|
||||||
|
EditAvatarDialogRef.value.open(detail.value)
|
||||||
|
}
|
||||||
|
|
||||||
function changeDayHandle(val: number | string) {
|
function changeDayHandle(val: number | string) {
|
||||||
if (val !== 'other') {
|
if (val !== 'other') {
|
||||||
daterange.value.start_time = beforeDay(val)
|
daterange.value.start_time = beforeDay(val)
|
||||||
|
|
@ -252,6 +283,11 @@ function getDetail() {
|
||||||
function refresh() {
|
function refresh() {
|
||||||
getAccessToken()
|
getAccessToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshIcon() {
|
||||||
|
getDetail()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDetail()
|
getDetail()
|
||||||
getAccessToken()
|
getAccessToken()
|
||||||
|
|
@ -266,5 +302,14 @@ onMounted(() => {
|
||||||
right: 16px;
|
right: 16px;
|
||||||
top: 21px;
|
top: 21px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-avatar {
|
||||||
|
position: relative;
|
||||||
|
.edit-mask {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -350,7 +350,7 @@ const submit = async (formEl: FormInstance | undefined) => {
|
||||||
await formEl.validate((valid, fields) => {
|
await formEl.validate((valid, fields) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
if (id) {
|
if (id) {
|
||||||
applicationApi.putApplication(id, applicationForm.value, loading).then((res) => {
|
application.asyncPutApplication(id, applicationForm.value, loading).then((res) => {
|
||||||
MsgSuccess('保存成功')
|
MsgSuccess('保存成功')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,21 @@
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-if="item.name"
|
v-if="isAppIcon(item?.icon)"
|
||||||
:name="item.name"
|
|
||||||
pinyinColor
|
|
||||||
class="mr-12"
|
|
||||||
shape="square"
|
shape="square"
|
||||||
:size="32"
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
class="mr-8"
|
||||||
|
>
|
||||||
|
<img :src="item?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="item?.name"
|
||||||
|
:name="item?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
class="mr-8"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -85,6 +94,7 @@
|
||||||
import { ref, onMounted, reactive, computed } from 'vue'
|
import { ref, onMounted, reactive, computed } from 'vue'
|
||||||
import applicationApi from '@/api/application'
|
import applicationApi from '@/api/application'
|
||||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||||
|
import { isAppIcon } from '@/utils/application'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const { application } = useStore()
|
const { application } = useStore()
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,21 @@
|
||||||
<CardCheckbox value-field="id" :data="item" v-model="application_id_list">
|
<CardCheckbox value-field="id" :data="item" v-model="application_id_list">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<AppAvatar
|
<AppAvatar
|
||||||
v-if="item.name"
|
v-if="isAppIcon(item?.icon)"
|
||||||
:name="item.name"
|
|
||||||
pinyinColor
|
|
||||||
class="mr-12"
|
|
||||||
shape="square"
|
shape="square"
|
||||||
:size="32"
|
:size="32"
|
||||||
|
style="background: none"
|
||||||
|
class="mr-12"
|
||||||
|
>
|
||||||
|
<img :src="item?.icon" alt="" />
|
||||||
|
</AppAvatar>
|
||||||
|
<AppAvatar
|
||||||
|
v-else-if="item?.name"
|
||||||
|
:name="item?.name"
|
||||||
|
pinyinColor
|
||||||
|
shape="square"
|
||||||
|
:size="32"
|
||||||
|
class="mr-12"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
|
|
@ -87,6 +96,7 @@ import BaseForm from '@/views/dataset/component/BaseForm.vue'
|
||||||
import datasetApi from '@/api/dataset'
|
import datasetApi from '@/api/dataset'
|
||||||
import type { ApplicationFormType } from '@/api/type/application'
|
import type { ApplicationFormType } from '@/api/type/application'
|
||||||
import { MsgSuccess } from '@/utils/message'
|
import { MsgSuccess } from '@/utils/message'
|
||||||
|
import { isAppIcon } from '@/utils/application'
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
:preview="false"
|
:preview="false"
|
||||||
:toolbars="toolbars"
|
:toolbars="toolbars"
|
||||||
style="height: 300px"
|
style="height: 300px"
|
||||||
|
@onUploadImg="onUploadImg"
|
||||||
/>
|
/>
|
||||||
<MdPreview
|
<MdPreview
|
||||||
v-else
|
v-else
|
||||||
|
|
@ -46,6 +47,7 @@
|
||||||
import { ref, reactive, onUnmounted, watch, nextTick } from 'vue'
|
import { ref, reactive, onUnmounted, watch, nextTick } from 'vue'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
import { MdEditor, MdPreview } from 'md-editor-v3'
|
import { MdEditor, MdPreview } from 'md-editor-v3'
|
||||||
|
import imageApi from '@/api/image'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -81,7 +83,7 @@ const toolbars = [
|
||||||
'=',
|
'=',
|
||||||
'pageFullscreen',
|
'pageFullscreen',
|
||||||
'preview',
|
'preview',
|
||||||
'htmlPreview',
|
'htmlPreview'
|
||||||
] as any[]
|
] as any[]
|
||||||
|
|
||||||
const editorRef = ref()
|
const editorRef = ref()
|
||||||
|
|
@ -134,20 +136,25 @@ function validate() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// const onHtmlChanged = () => {
|
const onUploadImg = async (files: any, callback: any) => {
|
||||||
// appendTarget()
|
const res = await Promise.all(
|
||||||
// }
|
files.map((file: any) => {
|
||||||
// const appendTarget = () => {
|
return new Promise((rev, rej) => {
|
||||||
// nextTick(() => {
|
const fd = new FormData()
|
||||||
// var item = document.getElementsByClassName('maxkb-md')
|
fd.append('file', file)
|
||||||
// for (var j = 0; j < item.length; j++) {
|
|
||||||
// var aTags = item[j].getElementsByTagName('a')
|
imageApi
|
||||||
// for (var i = 0; i < aTags.length; i++) {
|
.postImage(fd)
|
||||||
// aTags[i].setAttribute('target', '_blank')
|
.then((res: any) => {
|
||||||
// }
|
rev(res)
|
||||||
// }
|
})
|
||||||
// })
|
.catch((error) => rej(error))
|
||||||
// }
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
callback(res.map((item) => item.data))
|
||||||
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
form.value = {
|
form.value = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue