Merge branch 'dev'
@@ -0,0 +1,61 @@
|
||||
"""019_upgrade_model
|
||||
|
||||
Revision ID: dcaecd481715
|
||||
Revises: 863105882eba
|
||||
Create Date: 2025-07-04 14:45:56.927188
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'dcaecd481715'
|
||||
down_revision = '863105882eba'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.drop_index(op.f('ix_ai_model_id'), table_name='ai_model')
|
||||
op.drop_table('ai_model')
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('ai_model',
|
||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
|
||||
sa.Column('api_domain', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('protocol', sa.Integer(), nullable=False),
|
||||
sa.Column('supplier', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('model_type', sa.Integer(), nullable=False),
|
||||
sa.Column('base_model', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('default_model', sa.Boolean(), nullable=False),
|
||||
sa.Column('config', sa.Text(), nullable=False),
|
||||
sa.Column('status', sa.Integer(), nullable=False),
|
||||
sa.Column('create_time', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_ai_model_id'), 'ai_model', ['id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_ai_model_id'), table_name='ai_model')
|
||||
op.drop_table('ai_model')
|
||||
|
||||
op.create_table('ai_model',
|
||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('type', sa.Integer(), nullable=False),
|
||||
sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
|
||||
sa.Column('endpoint', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('max_context_window', sa.Integer(), nullable=False),
|
||||
sa.Column('temperature', sa.Float(), nullable=False),
|
||||
sa.Column('status', sa.Boolean(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
|
||||
sa.Column('create_time', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_ai_model_id'), 'ai_model', ['id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,9 +1,12 @@
|
||||
import json
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, Dict, Any, Type
|
||||
from abc import ABC, abstractmethod
|
||||
from langchain_core.language_models import BaseLLM as LangchainBaseLLM
|
||||
from langchain_openai import ChatOpenAI
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from common.core.db import engine
|
||||
from apps.system.models.system_model import AiModelDetail
|
||||
|
||||
|
||||
@@ -11,6 +14,7 @@ from apps.system.models.system_model import AiModelDetail
|
||||
|
||||
class LLMConfig(BaseModel):
|
||||
"""Base configuration class for large language models"""
|
||||
model_id: Optional[int] = None
|
||||
model_type: str # Model type: openai/tongyi/vllm etc.
|
||||
model_name: str # Specific model name
|
||||
api_key: Optional[str] = None
|
||||
@@ -93,7 +97,7 @@ class LLMFactory:
|
||||
|
||||
|
||||
# todo
|
||||
def get_llm_config(aimodel: AiModelDetail) -> LLMConfig:
|
||||
""" def get_llm_config(aimodel: AiModelDetail) -> LLMConfig:
|
||||
config = LLMConfig(
|
||||
model_type="openai",
|
||||
model_name=aimodel.name,
|
||||
@@ -101,4 +105,31 @@ def get_llm_config(aimodel: AiModelDetail) -> LLMConfig:
|
||||
api_base_url=aimodel.endpoint,
|
||||
additional_params={"temperature": aimodel.temperature}
|
||||
)
|
||||
return config
|
||||
return config """
|
||||
|
||||
def get_default_config() -> LLMConfig:
|
||||
with Session(engine) as session:
|
||||
db_model = session.exec(
|
||||
select(AiModelDetail).where(AiModelDetail.default_model == True)
|
||||
).first()
|
||||
if not db_model:
|
||||
raise ValueError("The system default model has not been set")
|
||||
|
||||
additional_params = {}
|
||||
if db_model.config:
|
||||
try:
|
||||
config_raw = json.loads(db_model.config)
|
||||
additional_params = {item["key"]: item["val"] for item in config_raw if "key" in item and "val" in item}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 构造 LLMConfig
|
||||
return LLMConfig(
|
||||
model_id=db_model.id,
|
||||
model_type="openai",
|
||||
model_name=db_model.base_model,
|
||||
api_key=db_model.api_key,
|
||||
api_base_url=db_model.api_domain,
|
||||
additional_params=additional_params
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage, AI
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import load_only
|
||||
|
||||
from apps.ai_model.model_factory import LLMConfig, LLMFactory, get_llm_config
|
||||
from apps.ai_model.model_factory import LLMConfig, LLMFactory, get_default_config
|
||||
from apps.chat.curd.chat import save_question, save_full_sql_message, save_full_sql_message_and_answer, save_sql, \
|
||||
save_error_message, save_sql_exec_data, save_full_chart_message, save_full_chart_message_and_answer, save_chart, \
|
||||
finish_record, save_full_analysis_message_and_answer, save_full_predict_message_and_answer, save_predict_data, \
|
||||
@@ -23,7 +23,6 @@ from apps.datasource.crud.datasource import get_table_schema
|
||||
from apps.datasource.models.datasource import CoreDatasource
|
||||
from apps.db.db import exec_sql
|
||||
from apps.system.crud.user import get_user_info
|
||||
from apps.system.models.system_model import AiModelDetail
|
||||
from common.core.config import settings
|
||||
from common.core.deps import SessionDep, CurrentUser
|
||||
|
||||
@@ -61,13 +60,6 @@ class LLMService:
|
||||
|
||||
chat_question.engine = ds.type_name if ds.type != 'excel' else 'PostgreSQL'
|
||||
|
||||
# Get available AI model
|
||||
aimodel = self.session.exec(select(AiModelDetail).where(
|
||||
AiModelDetail.status == True,
|
||||
AiModelDetail.api_key.is_not(None)
|
||||
)).first()
|
||||
if not aimodel and aimodel[0]:
|
||||
raise Exception("No available AI model configuration found")
|
||||
|
||||
history_records: List[ChatRecord] = list(
|
||||
map(lambda x: ChatRecord(**x.model_dump()), filter(lambda r: True if r.first_chat != True else False,
|
||||
@@ -83,8 +75,9 @@ class LLMService:
|
||||
|
||||
self.ds = CoreDatasource(**ds.model_dump()) if ds else None
|
||||
self.chat_question = chat_question
|
||||
self.chat_question.ai_modal_id = aimodel[0].id
|
||||
self.config = get_llm_config(aimodel[0])
|
||||
self.config = get_default_config()
|
||||
self.chat_question.ai_modal_id = self.config.model_id
|
||||
|
||||
|
||||
# Create LLM instance through factory
|
||||
llm_instance = LLMFactory.create_llm(self.config)
|
||||
|
||||
@@ -1,90 +1,90 @@
|
||||
from fastapi import APIRouter
|
||||
from sqlmodel import select
|
||||
import json
|
||||
from typing import List, Union
|
||||
from apps.system.schemas.ai_model_schema import AiModelConfigItem, AiModelCreator, AiModelEditor, AiModelGridItem
|
||||
from fastapi import APIRouter, Query
|
||||
from sqlmodel import func, select
|
||||
|
||||
from apps.system.models.system_model import AiModelDetail
|
||||
from apps.system.schemas.system_schema import model_status
|
||||
from common.core.deps import SessionDep
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
from common.utils.time import get_timestamp
|
||||
|
||||
router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
|
||||
|
||||
|
||||
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[AiModelDetail])
|
||||
async def pager(
|
||||
@router.get("", response_model=list[AiModelGridItem])
|
||||
async def query(
|
||||
session: SessionDep,
|
||||
pageNum: int,
|
||||
pageSize: int
|
||||
keyword: Union[str, None] = Query(default=None, max_length=255)
|
||||
):
|
||||
pagination = PaginationParams(page=pageNum, size=pageSize)
|
||||
paginator = Paginator(session)
|
||||
filters = {}
|
||||
return await paginator.get_paginated_response(
|
||||
model=AiModelDetail,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
statement = select(AiModelDetail.id,
|
||||
AiModelDetail.name,
|
||||
AiModelDetail.model_type,
|
||||
AiModelDetail.base_model,
|
||||
AiModelDetail.supplier,
|
||||
AiModelDetail.default_model)
|
||||
if keyword is not None:
|
||||
statement = statement.where(AiModelDetail.name.like(f"%{keyword}%"))
|
||||
|
||||
items = session.exec(statement).all()
|
||||
return items
|
||||
|
||||
|
||||
@router.get("/{id}", response_model=AiModelDetail)
|
||||
@router.get("/{id}", response_model=AiModelEditor)
|
||||
async def get_model_by_id(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.get(AiModelDetail, id)
|
||||
return term
|
||||
db_model = session.get(AiModelDetail, id)
|
||||
if not db_model:
|
||||
raise ValueError(f"AiModelDetail with id {id} not found")
|
||||
|
||||
config_list: List[AiModelConfigItem] = []
|
||||
if db_model.config:
|
||||
try:
|
||||
raw = json.loads(db_model.config)
|
||||
config_list = [AiModelConfigItem(**item) for item in raw]
|
||||
except Exception:
|
||||
pass
|
||||
data = AiModelDetail.model_validate(db_model).model_dump(exclude_unset=True)
|
||||
data.pop("config", None)
|
||||
data["config_list"] = config_list
|
||||
return AiModelEditor(**data)
|
||||
|
||||
@router.post("", response_model=AiModelDetail)
|
||||
@router.post("")
|
||||
async def add_model(
|
||||
session: SessionDep,
|
||||
creator: AiModelDetail
|
||||
creator: AiModelCreator
|
||||
):
|
||||
data = AiModelDetail.model_validate(creator)
|
||||
data.create_time = get_timestamp()
|
||||
session.add(data)
|
||||
data = creator.model_dump(exclude_unset=True)
|
||||
data["config"] = json.dumps([item.model_dump(exclude_unset=True) for item in creator.config_list])
|
||||
data.pop("config_list", None)
|
||||
detail = AiModelDetail.model_validate(data)
|
||||
detail.create_time = get_timestamp()
|
||||
count = session.exec(select(func.count(AiModelDetail.id))).one()
|
||||
if count == 0:
|
||||
detail.default_model = True
|
||||
session.add(detail)
|
||||
session.commit()
|
||||
return creator
|
||||
|
||||
|
||||
@router.put("", response_model=AiModelDetail)
|
||||
async def update_terminology(
|
||||
@router.put("")
|
||||
async def update_model(
|
||||
session: SessionDep,
|
||||
model: AiModelDetail
|
||||
editor: AiModelEditor
|
||||
):
|
||||
model.id = int(model.id)
|
||||
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == model.id)).first()
|
||||
update_data = model.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(term, field, value)
|
||||
session.add(term)
|
||||
id = int(editor.id)
|
||||
data = editor.model_dump(exclude_unset=True)
|
||||
data["config"] = json.dumps([item.model_dump(exclude_unset=True) for item in editor.config_list])
|
||||
data.pop("config_list", None)
|
||||
db_model = session.get(AiModelDetail, id)
|
||||
update_data = AiModelDetail.model_validate(data)
|
||||
db_model.sqlmodel_update(update_data)
|
||||
session.add(db_model)
|
||||
session.commit()
|
||||
return model
|
||||
|
||||
|
||||
@router.delete("/{id}", response_model=AiModelDetail)
|
||||
async def delete_terminology(
|
||||
@router.delete("/{id}")
|
||||
async def delete_model(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == id)).first()
|
||||
session.delete(term)
|
||||
item = session.get(AiModelDetail, id)
|
||||
session.delete(item)
|
||||
session.commit()
|
||||
return {
|
||||
"message": f"AiModel with ID {id} deleted successfully."
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/status", response_model=dict)
|
||||
async def status(session: SessionDep, dto: model_status):
|
||||
ids = dto.ids
|
||||
status = dto.status
|
||||
if not ids:
|
||||
return {"message": "ids is empty"}
|
||||
statement = select(AiModelDetail).where(AiModelDetail.id.in_(ids))
|
||||
terms = session.exec(statement).all()
|
||||
for term in terms:
|
||||
term.status = status
|
||||
session.add(term)
|
||||
session.commit()
|
||||
return {"message": f"AiModel with IDs {ids} updated successfully."}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
|
||||
from sqlmodel import BigInteger, Field, SQLModel
|
||||
|
||||
from sqlmodel import BigInteger, Field, Text, SQLModel
|
||||
from common.core.models import SnowflakeBase
|
||||
|
||||
|
||||
class AiModelBase:
|
||||
supplier: int = Field(nullable=False)
|
||||
name: str = Field(max_length=255, nullable=False)
|
||||
type: int = Field(nullable=False)
|
||||
model_type: int = Field(nullable=False)
|
||||
base_model: str = Field(max_length = 255, nullable=False)
|
||||
default_model: bool = Field(default=False, nullable=False)
|
||||
|
||||
class AiModelDetail(AiModelBase, SnowflakeBase, table=True):
|
||||
class AiModelDetail(SnowflakeBase, AiModelBase, table=True):
|
||||
__tablename__ = "ai_model"
|
||||
api_key: str | None = Field(max_length=255, nullable=True)
|
||||
endpoint: str = Field(max_length=255, nullable=False)
|
||||
max_context_window: int = Field(default=0)
|
||||
temperature: float = Field(default=0.0)
|
||||
status: bool = Field(default=True)
|
||||
description: str | None = Field(max_length=255, nullable=True)
|
||||
api_domain: str = Field(max_length=255, nullable=False)
|
||||
protocol: int = Field(nullable=False, default = 1)
|
||||
config: str = Field(sa_type = Text())
|
||||
status: int = Field(nullable=False, default = 1)
|
||||
create_time: int = Field(default=0, sa_type=BigInteger())
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
|
||||
from common.core.schemas import BaseCreatorDTO
|
||||
|
||||
class AiModelItem(BaseModel):
|
||||
name: str
|
||||
model_type: int
|
||||
base_model: str
|
||||
supplier: int
|
||||
default_model: bool = False
|
||||
|
||||
class AiModelGridItem(AiModelItem, BaseCreatorDTO):
|
||||
pass
|
||||
|
||||
class AiModelConfigItem(BaseModel):
|
||||
key: str
|
||||
val: object
|
||||
name: str
|
||||
|
||||
class AiModelCreator(AiModelItem):
|
||||
api_domain: str
|
||||
api_key: str
|
||||
config_list: List[AiModelConfigItem]
|
||||
|
||||
class AiModelEditor(AiModelCreator, BaseCreatorDTO):
|
||||
pass
|
||||
@@ -4,7 +4,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
from starlette.exceptions import HTTPException
|
||||
from starlette.requests import Request
|
||||
|
||||
from common.core.config import settings
|
||||
class ResponseMiddleware(BaseHTTPMiddleware):
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
@@ -12,7 +12,7 @@ class ResponseMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request, call_next):
|
||||
response = await call_next(request)
|
||||
|
||||
if isinstance(response, JSONResponse):
|
||||
if isinstance(response, JSONResponse) or request.url.path == f"{settings.API_V1_STR}/openapi.json":
|
||||
return response
|
||||
|
||||
if response.headers.get("content-type") == "application/json":
|
||||
|
||||
@@ -46,4 +46,12 @@ class PaginatedResponse(BaseModel, Generic[T]):
|
||||
total: int
|
||||
page: int
|
||||
size: int
|
||||
total_pages: int
|
||||
total_pages: int
|
||||
|
||||
|
||||
class BaseCreatorDTO(BaseModel):
|
||||
id: int
|
||||
class Config:
|
||||
json_encoders = {
|
||||
int: lambda v: str(v) if isinstance(v, int) and v > (2**53 - 1) else v
|
||||
}
|
||||
@@ -28,6 +28,7 @@ module.exports = typescriptEslint.config(
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/no-required-prop-with-default': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'warn',
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const modelApi = {
|
||||
pager: (pageNumber: number, pageSize: number) =>
|
||||
request.get(`/system/aimodel/pager/${pageNumber}/${pageSize}`),
|
||||
queryAll: (keyword?: string) =>
|
||||
request.get('/system/aimodel', { params: keyword ? { keyword } : {} }),
|
||||
add: (data: any) => request.post('/system/aimodel', data),
|
||||
edit: (data: any) => request.put('/system/aimodel', data),
|
||||
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28.3328 13.669H28.1019C28.2398 14.4271 28.3088 15.1961 28.3083 15.9667C28.3132 16.9131 28.2005 17.8564 27.9729 18.775H28.6236C28.9021 18.775 29.1691 18.6644 29.3661 18.4674C29.563 18.2705 29.6736 18.0035 29.6736 17.725V15.01C29.6736 14.8339 29.6389 14.6595 29.5716 14.4968C29.5042 14.3341 29.4054 14.1863 29.2809 14.0618C29.1564 13.9373 29.0086 13.8385 28.8459 13.7711C28.6832 13.7037 28.5088 13.669 28.3328 13.669Z" fill="#149CC5"/>
|
||||
<path d="M1.69164 15.9667C1.69114 15.1961 1.76022 14.4271 1.89804 13.669H1.66727C1.31164 13.669 0.970593 13.8103 0.719137 14.0617C0.467681 14.3132 0.326416 14.6543 0.326416 15.0099V17.7249C0.326416 18.0034 0.437041 18.2704 0.633954 18.4674C0.830867 18.6643 1.09794 18.7749 1.37642 18.7749H2.02712C1.79945 17.8563 1.68676 16.913 1.69164 15.9667Z" fill="#69CAA4"/>
|
||||
<path d="M15 3.795C8.11089 3.795 2.52625 9.07732 2.52625 15.9664C2.52625 22.8556 8.11097 26.205 15 26.205C21.8891 26.205 27.4737 22.8558 27.4737 15.9667C27.4737 9.0777 21.8891 3.795 15 3.795ZM18.0265 21.1253H11.9735C11.1168 21.1265 10.2697 20.944 9.48954 20.59C8.92592 20.3341 7.28177 20.8716 6.82704 20.4631C6.27602 19.9681 6.94405 18.4942 6.6058 17.827C6.1792 16.9853 5.95758 16.0546 5.959 15.1109C5.959 13.5158 6.59265 11.9861 7.72055 10.8582C8.84845 9.73025 10.3782 9.0966 11.9733 9.0966H18.0263C19.6214 9.0966 21.1512 9.73025 22.2791 10.8582C23.407 11.9861 24.0407 13.5158 24.0407 15.1109C24.0407 15.9007 23.8852 16.6828 23.583 17.4126C23.2808 18.1423 22.8378 18.8053 22.2793 19.3638C21.7208 19.9223 21.0578 20.3654 20.3282 20.6677C19.5985 20.97 18.8164 21.1256 18.0266 21.1256L18.0265 21.1253Z" fill="url(#paint0_linear_477_9132)"/>
|
||||
<path d="M15 3.795C8.11089 3.795 2.52625 9.07732 2.52625 15.9664C2.52625 22.8556 8.11097 26.205 15 26.205C21.8891 26.205 27.4737 22.8558 27.4737 15.9667C27.4737 9.0777 21.8891 3.795 15 3.795ZM18.0265 21.1253H11.9735C11.1168 21.1265 10.2697 20.944 9.48954 20.59C8.92592 20.3341 7.28177 20.8716 6.82704 20.4631C6.27602 19.9681 6.94405 18.4942 6.6058 17.827C6.1792 16.9853 5.95758 16.0546 5.959 15.1109C5.959 13.5158 6.59265 11.9861 7.72055 10.8582C8.84845 9.73025 10.3782 9.0966 11.9733 9.0966H18.0263C19.6214 9.0966 21.1512 9.73025 22.2791 10.8582C23.407 11.9861 24.0407 13.5158 24.0407 15.1109C24.0407 15.9007 23.8852 16.6828 23.583 17.4126C23.2808 18.1423 22.8378 18.8053 22.2793 19.3638C21.7208 19.9223 21.0578 20.3654 20.3282 20.6677C19.5985 20.97 18.8164 21.1256 18.0266 21.1256L18.0265 21.1253Z" fill="url(#paint1_linear_477_9132)"/>
|
||||
<path d="M10.5456 14.915H8.76648V18.7946H10.5456V14.915Z" fill="#75CCCC"/>
|
||||
<path d="M14.1198 13.1932H12.3407V18.7946H14.1198V13.1932Z" fill="#75CCCC"/>
|
||||
<path d="M17.6939 15.4381H15.9148V18.7946H17.6939V15.4381Z" fill="#75CCCC"/>
|
||||
<path d="M21.2334 12.4522H19.4543V18.7946H21.2334V12.4522Z" fill="#75CCCC"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_477_9132" x1="6.6502" y1="4.44097" x2="25.0992" y2="29.2491" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B2F288"/>
|
||||
<stop offset="0.181" stop-color="#80D79C"/>
|
||||
<stop offset="0.371" stop-color="#52BEAD"/>
|
||||
<stop offset="0.554" stop-color="#2FAABB"/>
|
||||
<stop offset="0.724" stop-color="#159CC5"/>
|
||||
<stop offset="0.878" stop-color="#0594CB"/>
|
||||
<stop offset="1" stop-color="#0091CD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_477_9132" x1="6.6502" y1="4.44097" x2="25.0992" y2="29.2491" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B2F288"/>
|
||||
<stop offset="0.181" stop-color="#80D79C"/>
|
||||
<stop offset="0.371" stop-color="#52BEAD"/>
|
||||
<stop offset="0.554" stop-color="#2FAABB"/>
|
||||
<stop offset="0.724" stop-color="#159CC5"/>
|
||||
<stop offset="0.878" stop-color="#0594CB"/>
|
||||
<stop offset="1" stop-color="#0091CD"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,42 @@
|
||||
<svg width="131" height="30" viewBox="0 0 131 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_477_6686)">
|
||||
<path d="M28.3328 13.669H28.1019C28.2398 14.4271 28.3088 15.1961 28.3083 15.9667C28.3132 16.9131 28.2005 17.8564 27.9729 18.775H28.6236C28.9021 18.775 29.1691 18.6644 29.3661 18.4674C29.563 18.2705 29.6736 18.0035 29.6736 17.725V15.01C29.6736 14.8339 29.6389 14.6595 29.5716 14.4968C29.5042 14.3341 29.4054 14.1863 29.2809 14.0618C29.1564 13.9373 29.0086 13.8385 28.8459 13.7711C28.6832 13.7037 28.5088 13.669 28.3328 13.669Z" fill="#149CC5"/>
|
||||
<path d="M1.69163 15.9667C1.69112 15.1961 1.7602 14.4271 1.89803 13.669H1.66725C1.31163 13.669 0.970578 13.8103 0.719122 14.0617C0.467666 14.3132 0.326401 14.6543 0.326401 15.0099V17.7249C0.326401 18.0034 0.437025 18.2704 0.633939 18.4674C0.830852 18.6643 1.09792 18.7749 1.3764 18.7749H2.0271C1.79944 17.8563 1.68675 16.913 1.69163 15.9667Z" fill="#69CAA4"/>
|
||||
<path d="M15 3.795C8.11088 3.795 2.52623 9.07732 2.52623 15.9664C2.52623 22.8556 8.11095 26.205 15 26.205C21.889 26.205 27.4737 22.8558 27.4737 15.9667C27.4737 9.0777 21.889 3.795 15 3.795ZM18.0265 21.1253H11.9735C11.1168 21.1265 10.2697 20.944 9.48953 20.59C8.9259 20.3341 7.28175 20.8716 6.82703 20.4631C6.276 19.9681 6.94403 18.4942 6.60578 17.827C6.17919 16.9853 5.95756 16.0546 5.95898 15.1109C5.95898 13.5158 6.59263 11.9861 7.72053 10.8582C8.84844 9.73025 10.3782 9.0966 11.9733 9.0966H18.0263C19.6214 9.0966 21.1512 9.73025 22.2791 10.8582C23.407 11.9861 24.0407 13.5158 24.0407 15.1109C24.0407 15.9007 23.8852 16.6828 23.5829 17.4126C23.2807 18.1423 22.8377 18.8053 22.2793 19.3638C21.7208 19.9223 21.0578 20.3654 20.3281 20.6677C19.5985 20.97 18.8164 21.1256 18.0266 21.1256L18.0265 21.1253Z" fill="url(#paint0_linear_477_6686)"/>
|
||||
<path d="M15 3.795C8.11088 3.795 2.52623 9.07732 2.52623 15.9664C2.52623 22.8556 8.11095 26.205 15 26.205C21.889 26.205 27.4737 22.8558 27.4737 15.9667C27.4737 9.0777 21.889 3.795 15 3.795ZM18.0265 21.1253H11.9735C11.1168 21.1265 10.2697 20.944 9.48953 20.59C8.9259 20.3341 7.28175 20.8716 6.82703 20.4631C6.276 19.9681 6.94403 18.4942 6.60578 17.827C6.17919 16.9853 5.95756 16.0546 5.95898 15.1109C5.95898 13.5158 6.59263 11.9861 7.72053 10.8582C8.84844 9.73025 10.3782 9.0966 11.9733 9.0966H18.0263C19.6214 9.0966 21.1512 9.73025 22.2791 10.8582C23.407 11.9861 24.0407 13.5158 24.0407 15.1109C24.0407 15.9007 23.8852 16.6828 23.5829 17.4126C23.2807 18.1423 22.8377 18.8053 22.2793 19.3638C21.7208 19.9223 21.0578 20.3654 20.3281 20.6677C19.5985 20.97 18.8164 21.1256 18.0266 21.1256L18.0265 21.1253Z" fill="url(#paint1_linear_477_6686)"/>
|
||||
<path d="M10.5456 14.915H8.76653V18.7946H10.5456V14.915Z" fill="#75CCCC"/>
|
||||
<path d="M14.1197 13.1932H12.3407V18.7946H14.1197V13.1932Z" fill="#75CCCC"/>
|
||||
<path d="M17.6938 15.4381H15.9148V18.7946H17.6938V15.4381Z" fill="#75CCCC"/>
|
||||
<path d="M21.2335 12.4522H19.4544V18.7946H21.2335V12.4522Z" fill="#75CCCC"/>
|
||||
<path d="M49.9855 14.2255C49.5648 13.7479 48.9791 13.3311 48.2457 12.9865C47.5234 12.648 46.5864 12.338 45.46 12.065C44.7797 11.904 44.2248 11.75 43.8106 11.6085C43.4064 11.4708 43.0957 11.3205 42.887 11.1612C42.7118 11.039 42.5779 10.8663 42.5032 10.666C42.4319 10.4299 42.3986 10.184 42.4043 9.93742C42.4043 9.20599 42.6086 8.73468 43.0291 8.49633C43.4773 8.24209 44.2263 8.11322 45.2553 8.11322C45.6439 8.11322 46.0627 8.13121 46.5001 8.16684C46.9356 8.20275 47.3727 8.25693 47.799 8.32784C48.2288 8.39973 48.6265 8.47666 48.9812 8.55653C49.3329 8.63584 49.6291 8.71221 49.8618 8.78361L50.0994 8.85648V5.08593L49.9775 5.04239C49.4622 4.85836 48.7815 4.69582 47.9542 4.55939C47.0074 4.41387 46.0505 4.34497 45.0928 4.35338C42.8788 4.35338 41.1341 4.78913 39.9072 5.64838C38.6596 6.52191 38.0271 8.0015 38.0271 10.0461C38.0271 10.9094 38.1114 11.6474 38.2778 12.2396C38.4364 12.8257 38.7411 13.3617 39.1632 13.7974C39.5756 14.2203 40.1291 14.5809 40.8077 14.8693C41.4737 15.1529 42.3272 15.4165 43.3413 15.6519C43.9259 15.7923 44.503 15.9624 45.0703 16.1617C45.4914 16.3167 45.8201 16.4892 46.0471 16.6743C46.2471 16.829 46.4008 17.0357 46.4914 17.2719C46.5811 17.5499 46.6233 17.841 46.6163 18.1329C46.625 18.4706 46.5739 18.8071 46.4654 19.1269C46.3721 19.3796 46.2014 19.5962 45.9776 19.7457C45.7388 19.9107 45.4171 20.0284 45.0215 20.0957C44.5162 20.1728 44.0054 20.2083 43.4943 20.2017C43.0129 20.2017 42.5217 20.179 42.0341 20.1345C41.5428 20.0901 41.0578 20.0356 40.5925 19.9726C40.1271 19.9096 39.6864 19.8334 39.2793 19.7445C38.9292 19.6721 38.5846 19.5748 38.2482 19.4535L38 19.3602V23.3743L38.1339 23.412C38.7177 23.5765 39.5139 23.7334 40.5008 23.8783C41.5461 24.0276 42.6009 24.1008 43.6569 24.0974C46.2731 24.0974 48.1823 23.5939 49.3314 22.6008C50.4884 21.6011 51.075 20.1345 51.075 18.2417C51.075 17.3461 51.0005 16.5698 50.8534 15.9342C50.7116 15.3008 50.4132 14.7132 49.9855 14.2255Z" fill="#485559"/>
|
||||
<path d="M68.9312 6.37533C68.189 5.6229 67.2588 5.09076 66.1662 4.79382C65.0885 4.5015 63.8416 4.35324 62.4603 4.35324C61.0965 4.35324 59.8587 4.5015 58.7815 4.79382C57.6885 5.09076 56.7582 5.6229 56.0164 6.37533C55.2762 7.1258 54.705 8.15102 54.3185 9.42257C53.9357 10.6826 53.7416 12.2982 53.7416 14.2251C53.7416 16.134 53.9311 17.7403 54.3046 18.9991C54.6815 20.2704 55.2435 21.2958 55.9751 22.0466C56.7261 22.8099 57.6656 23.3604 58.6981 23.6419C59.9655 24.0125 61.2802 24.1956 62.6004 24.1854C64.2721 24.1854 67.182 23.705 68.4129 22.5242C68.9675 21.9922 69.5299 21.4276 69.9375 20.6762C70.3419 19.9301 70.6536 19.0133 70.864 17.9512C71.0731 16.8942 71.1791 15.6402 71.1791 14.2248C71.1791 12.2993 70.9896 10.6839 70.616 9.42341C70.2386 8.15221 69.6717 7.12657 68.9312 6.37533ZM60.4175 8.41121C60.9347 8.19547 61.622 8.08613 62.4603 8.08613C63.2986 8.08613 63.991 8.19561 64.5175 8.41156C65.0183 8.61289 65.4396 8.97264 65.7172 9.43601C66.0097 9.91901 66.2157 10.5576 66.3293 11.3346C66.4553 12.2929 66.5135 13.259 66.5035 14.2256C66.5035 15.3682 66.4449 16.3452 66.3294 17.1294C66.216 17.8966 66.0106 18.5209 65.7189 18.985C65.4369 19.4352 65.016 19.7811 64.52 19.9702C63.9929 20.1779 63.2999 20.2831 62.4603 20.2831C61.6208 20.2831 60.9327 20.1781 60.4151 19.9706C59.9239 19.7788 59.5077 19.4331 59.2288 18.985C58.9367 18.5208 58.7313 17.8965 58.6183 17.1294C58.5028 16.3481 58.4442 15.3711 58.4442 14.2256C58.434 13.259 58.4922 12.2929 58.6184 11.3346C58.7316 10.5576 58.9375 9.91894 59.2304 9.43601C59.5051 8.97487 59.9216 8.61531 60.4175 8.41121Z" fill="#485559"/>
|
||||
<path d="M78.9823 4.62463H74.4965V23.8256H86.4606V19.8755H78.9823V4.62463Z" fill="#485559"/>
|
||||
<path d="M100.062 13.785C100.92 13.4782 101.539 13.0206 101.905 12.4214C102.318 11.7434 102.528 10.8894 102.528 9.88317C102.528 9.01678 102.415 8.24804 102.193 7.59823C101.968 6.93804 101.557 6.35734 101.009 5.92649C100.457 5.48619 99.7074 5.15474 98.7818 4.94138C97.8679 4.73138 96.7054 4.62463 95.3267 4.62463H88.8027V23.8256H95.8957C97.278 23.8256 98.4427 23.7002 99.3575 23.453C100.283 23.2026 101.032 22.8335 101.584 22.3561C102.136 21.8804 102.545 21.2594 102.763 20.5634C102.995 19.8023 103.107 19.0099 103.097 18.2143C103.097 15.9286 102.076 14.4405 100.062 13.785ZM97.8991 11.375C97.7688 11.6337 97.5588 11.8435 97.3 11.9732C97.0131 12.1252 96.6374 12.2327 96.1831 12.2927C95.6202 12.3602 95.0536 12.3914 94.4867 12.3861H93.2886V8.27604H94.4867C95.1432 8.27604 95.721 8.29865 96.2039 8.3431C96.6647 8.38601 97.0373 8.48373 97.3115 8.63353C97.5654 8.76636 97.771 8.97599 97.899 9.23259C98.03 9.49544 98.0964 9.86042 98.0964 10.3176C98.0964 10.7567 98.03 11.1124 97.8991 11.375ZM93.2886 15.739H95.0016C95.5989 15.7311 96.1958 15.7711 96.7868 15.8587C97.2456 15.9352 97.6085 16.0624 97.8654 16.2367C98.1065 16.3951 98.2912 16.626 98.393 16.8962C98.5101 17.2325 98.5657 17.5872 98.557 17.9433C98.557 18.4067 98.5019 18.7863 98.393 19.0715C98.2944 19.3363 98.1062 19.5581 97.8611 19.6983C97.5938 19.8574 97.2288 19.9727 96.7759 20.0409C96.1878 20.1192 95.5948 20.1548 95.0016 20.1473H93.2886V15.739Z" fill="#485559"/>
|
||||
<path d="M118.254 10.157C117.602 9.5447 116.806 9.109 115.939 8.89113C115.041 8.65313 114.005 8.53217 112.857 8.53217C111.71 8.53217 110.669 8.65292 109.762 8.89099C108.842 9.13326 108.058 9.55928 107.433 10.1571C106.808 10.7549 106.324 11.5646 105.99 12.5657C105.661 13.5569 105.493 14.8182 105.493 16.3145C105.493 17.8108 105.661 19.0721 105.99 20.0633C106.324 21.065 106.809 21.8754 107.433 22.4718C108.058 23.0701 108.842 23.4961 109.762 23.738C110.669 23.976 111.71 24.0968 112.857 24.0968C114.005 24.0968 115.041 23.9761 115.939 23.7378C116.806 23.5203 117.603 23.0845 118.254 22.4719C118.878 21.8748 119.364 21.0644 119.697 20.0633C120.027 19.0721 120.194 17.8109 120.194 16.3145C120.194 14.8181 120.027 13.5565 119.697 12.5657C119.364 11.5653 118.878 10.7549 118.254 10.157ZM114.299 20.5374C113.915 20.6744 113.43 20.7438 112.857 20.7438C112.284 20.7438 111.804 20.6745 111.43 20.5377C111.073 20.4113 110.767 20.1737 110.556 19.8597C110.328 19.5303 110.161 19.0751 110.057 18.5068C109.951 17.9235 109.898 17.1859 109.898 16.3145C109.888 15.58 109.937 14.846 110.045 14.1194C110.139 13.5524 110.302 13.0929 110.53 12.7539C110.743 12.4302 111.055 12.1834 111.418 12.0497C111.802 11.9045 112.287 11.8308 112.857 11.8308C113.427 11.8308 113.906 11.9043 114.281 12.0491C114.639 12.1871 114.945 12.4335 115.157 12.754C115.386 13.0932 115.554 13.5536 115.657 14.1223C115.763 14.7057 115.817 15.4433 115.817 16.3146C115.826 17.049 115.777 17.783 115.67 18.5096C115.575 19.0771 115.412 19.5316 115.185 19.8598C114.971 20.176 114.66 20.4138 114.299 20.5375V20.5374Z" fill="#485559"/>
|
||||
<path d="M130.652 12.2652V8.80368H127.618V5.33014H123.322V8.80368H121.398V12.2652H123.322V18.9469C123.322 19.9187 123.41 20.7384 123.585 21.3829C123.765 22.0459 124.06 22.5767 124.462 22.9604C124.865 23.3441 125.399 23.6148 126.051 23.7663C126.686 23.9132 127.47 23.9881 128.382 23.9881C128.711 23.9881 129.044 23.9741 129.373 23.9468C129.696 23.9198 130.008 23.8876 130.302 23.851L130.463 23.831V20.3971L130.237 20.4504C129.867 20.5267 129.491 20.5615 129.114 20.5539C128.848 20.5611 128.583 20.5316 128.325 20.4663C128.16 20.4239 128.011 20.3309 127.901 20.2003C127.781 20.0388 127.706 19.8489 127.683 19.6494C127.634 19.309 127.612 18.9653 127.618 18.6214V12.2654L130.652 12.2652Z" fill="#485559"/>
|
||||
<path d="M69.9428 25.1193L68.0201 26.5826C67.9148 26.6628 67.7948 26.7214 67.6669 26.755C67.5391 26.7887 67.4058 26.7969 67.2748 26.779C67.1438 26.7611 67.0176 26.7175 66.9034 26.6508C66.7892 26.584 66.6892 26.4954 66.6092 26.39L64.3819 23.4544C64.2203 23.2415 64.1498 22.973 64.1859 22.708C64.222 22.443 64.3618 22.2032 64.5744 22.0414L66.4971 20.5784C66.7097 20.4166 66.9778 20.346 67.2424 20.3822C67.507 20.4183 67.7464 20.5582 67.908 20.7712L70.1353 23.7067C70.2153 23.8122 70.2738 23.9324 70.3074 24.0604C70.3411 24.1885 70.3492 24.322 70.3313 24.4532C70.3134 24.5844 70.2699 24.7108 70.2033 24.8252C70.1366 24.9395 70.0481 25.0397 69.9428 25.1198V25.1193Z" fill="#485559"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_477_6686" x1="6.65018" y1="4.44097" x2="25.0992" y2="29.2491" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B2F288"/>
|
||||
<stop offset="0.181" stop-color="#80D79C"/>
|
||||
<stop offset="0.371" stop-color="#52BEAD"/>
|
||||
<stop offset="0.554" stop-color="#2FAABB"/>
|
||||
<stop offset="0.724" stop-color="#159CC5"/>
|
||||
<stop offset="0.878" stop-color="#0594CB"/>
|
||||
<stop offset="1" stop-color="#0091CD"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_477_6686" x1="6.65018" y1="4.44097" x2="25.0992" y2="29.2491" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#B2F288"/>
|
||||
<stop offset="0.181" stop-color="#80D79C"/>
|
||||
<stop offset="0.371" stop-color="#52BEAD"/>
|
||||
<stop offset="0.554" stop-color="#2FAABB"/>
|
||||
<stop offset="0.724" stop-color="#159CC5"/>
|
||||
<stop offset="0.878" stop-color="#0594CB"/>
|
||||
<stop offset="1" stop-color="#0091CD"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_477_6686">
|
||||
<rect width="131" height="30" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 715 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 8.00004C8.73638 8.00004 9.33334 8.59699 9.33334 9.33337C9.33334 9.82689 9.0652 10.2578 8.66667 10.4883V11.6667C8.66667 11.8508 8.51743 12 8.33334 12H7.66667C7.48257 12 7.33334 11.8508 7.33334 11.6667V10.4883C6.9348 10.2578 6.66667 9.82689 6.66667 9.33337C6.66667 8.59699 7.26362 8.00004 8 8.00004Z" fill="#646A73"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1.33337C10.025 1.33337 11.6667 2.97337 11.6667 5.00004V5.33337H14C14.3683 5.33337 14.6667 5.63337 14.6667 6.00004V14C14.6667 14.3667 14.3683 14.6667 14 14.6667H2C1.63167 14.6667 1.33334 14.3667 1.33334 14V6.00004C1.33334 5.63337 1.63167 5.33337 2 5.33337H4.33334V5.00004C4.33334 2.97337 5.975 1.33337 8 1.33337ZM2.66667 13.3334H13.3333V6.66671H2.66667V13.3334ZM8 2.66671C6.71134 2.66671 5.66667 3.71004 5.66667 5.00004V5.33337H10.3333V5.00004C10.3333 3.71004 9.28867 2.66671 8 2.66671Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 987 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 14C11.3137 14 14 11.3137 14 7.99996C14 4.68625 11.3137 1.99996 8 1.99996C4.68629 1.99996 2 4.68625 2 7.99996C2 11.3137 4.68629 14 8 14ZM8 15.3333C3.94991 15.3333 0.666664 12.05 0.666664 7.99996C0.666664 3.94987 3.94991 0.666626 8 0.666626C12.0501 0.666626 15.3333 3.94987 15.3333 7.99996C15.3333 12.05 12.0501 15.3333 8 15.3333ZM7.66666 10.6666H8.33333C8.51742 10.6666 8.66666 10.8159 8.66666 11V11.6666C8.66666 11.8507 8.51742 12 8.33333 12H7.66666C7.48257 12 7.33333 11.8507 7.33333 11.6666V11C7.33333 10.8159 7.48257 10.6666 7.66666 10.6666ZM5.36657 6.18777C5.37504 6.11325 5.38372 6.05127 5.3926 6.0018C5.52256 5.27808 5.85169 4.74585 6.38001 4.4051C6.79377 4.135 7.30221 3.99996 7.90532 3.99996C8.69779 3.99996 9.35612 4.19187 9.88034 4.57569C10.4046 4.95951 10.6667 5.52812 10.6667 6.28155C10.6667 6.74356 10.5527 7.1327 10.3248 7.449C10.1915 7.64091 9.93557 7.88613 9.55687 8.18466L9.18343 8.47785C8.98005 8.63778 8.84505 8.82435 8.77843 9.03759C8.7603 9.09578 8.74436 9.20396 8.73063 9.36212C8.71568 9.5344 8.57147 9.66663 8.39855 9.66663H7.65227C7.46818 9.66662 7.31894 9.51739 7.31894 9.33329C7.31894 9.32304 7.31941 9.31279 7.32036 9.30258C7.36328 8.83865 7.40925 8.54508 7.45825 8.42188C7.54941 8.19265 7.78435 7.92878 8.16305 7.63025L8.547 7.32639C8.67324 7.23044 9.32371 6.77239 9.32371 6.36684C9.32371 5.9613 9.25306 5.81213 9.04944 5.58956C8.84582 5.36699 8.38746 5.29535 7.9737 5.29535C7.56694 5.29535 7.21936 5.40337 7.0493 5.67702C6.97053 5.80376 6.90612 5.93897 6.85967 6.07692C6.84139 6.13119 6.8252 6.19947 6.81109 6.28176C6.78365 6.44178 6.64491 6.55875 6.48255 6.55875H5.69777C5.51368 6.55876 5.36443 6.40952 5.36443 6.22542C5.36443 6.21284 5.36515 6.20027 5.36657 6.18777Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="16" cy="16" r="16" fill="#1CBA90"/>
|
||||
<path d="M18.6667 16.4445H13.3333C10.8787 16.4445 8 18.1613 8 21.2445V23.6445C8 24.0863 8.39797 24.4445 8.88889 24.4445H23.1111C23.602 24.4445 24 24.0863 24 23.6445V21.2445C24 18.1587 21.1213 16.4445 18.6667 16.4445Z" fill="url(#paint0_linear_477_6741)"/>
|
||||
<path d="M11.5556 11.1111C11.5556 13.5657 13.5454 15.5556 16 15.5556C18.4546 15.5556 20.4444 13.5657 20.4444 11.1111C20.4444 8.65653 18.4546 6.66669 16 6.66669C13.5454 6.66669 11.5556 8.65653 11.5556 11.1111Z" fill="url(#paint1_linear_477_6741)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_477_6741" x1="16" y1="6.66669" x2="16" y2="24.8889" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_477_6741" x1="16" y1="6.66669" x2="16" y2="24.8889" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0098 4.08305C10.7508 5.08943 9.8372 5.83305 8.74996 5.83305C7.66272 5.83305 6.74916 5.08943 6.49014 4.08305H1.54053C1.41051 4.08305 1.36337 4.06951 1.31584 4.04409C1.26831 4.01867 1.231 3.98137 1.20558 3.93384C1.18016 3.88631 1.16663 3.83916 1.16663 3.70915V3.29028C1.16663 3.16027 1.18016 3.11312 1.20558 3.06559C1.231 3.01806 1.26831 2.98076 1.31584 2.95534C1.36337 2.92992 1.41051 2.91638 1.54053 2.91638H6.49014C6.74916 1.91 7.66272 1.16638 8.74996 1.16638C9.8372 1.16638 10.7508 1.91 11.0098 2.91638H12.4594C12.5894 2.91638 12.6366 2.92992 12.6841 2.95534C12.7316 2.98076 12.7689 3.01806 12.7943 3.06559C12.8198 3.11312 12.8333 3.16027 12.8333 3.29028V3.70915C12.8333 3.83916 12.8198 3.88631 12.7943 3.93384C12.7689 3.98137 12.7316 4.01867 12.6841 4.04409C12.6366 4.06951 12.5894 4.08305 12.4594 4.08305H11.0098ZM8.74996 4.66638C9.39429 4.66638 9.91663 4.14405 9.91663 3.49972C9.91663 2.85538 9.39429 2.33305 8.74996 2.33305C8.10563 2.33305 7.58329 2.85538 7.58329 3.49972C7.58329 4.14405 8.10563 4.66638 8.74996 4.66638Z" fill="#1F2329"/>
|
||||
<path d="M7.50978 11.083C7.25076 12.0894 6.3372 12.833 5.24996 12.833C4.16272 12.833 3.24916 12.0894 2.99014 11.083H1.54053C1.41051 11.083 1.36337 11.0695 1.31584 11.0441C1.26831 11.0187 1.231 10.9814 1.20558 10.9338C1.18016 10.8863 1.16663 10.8392 1.16663 10.7091V10.2903C1.16663 10.1603 1.18016 10.1131 1.20558 10.0656C1.231 10.0181 1.26831 9.98076 1.31584 9.95534C1.36337 9.92992 1.41051 9.91638 1.54053 9.91638H2.99014C3.24916 8.91 4.16272 8.16638 5.24996 8.16638C6.3372 8.16638 7.25076 8.91 7.50978 9.91638H12.4594C12.5894 9.91638 12.6366 9.92992 12.6841 9.95534C12.7316 9.98076 12.7689 10.0181 12.7943 10.0656C12.8198 10.1131 12.8333 10.1603 12.8333 10.2903V10.7091C12.8333 10.8392 12.8198 10.8863 12.7943 10.9338C12.7689 10.9814 12.7316 11.0187 12.6841 11.0441C12.6366 11.0695 12.5894 11.083 12.4594 11.083H7.50978ZM5.24996 11.6664C5.89429 11.6664 6.41663 11.144 6.41663 10.4997C6.41663 9.85538 5.89429 9.33305 5.24996 9.33305C4.60563 9.33305 4.08329 9.85538 4.08329 10.4997C4.08329 11.144 4.60563 11.6664 5.24996 11.6664Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg t="1724827784525" class="icon" viewBox="0 0 1083 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5331" width="100%" height="100%"><path d="M540.115607 419.535168L460.236208 3.644671 376.260809 1.620347c-31.825387-5.244301-54.020439 8.390289-66.588115 40.908208C297.107977 75.046474 194.101272 379.938035 0.654058 957.203237-1.245965 999.731792 12.341272 1020.99311 41.418728 1020.99311h275.015029c14.599399-4.010173 25.104277-17.02178 31.514636-39.031861 6.410358-22.010081 70.46659-209.486428 192.168694-562.427561z" fill="#0D559D" p-id="5332"></path><path d="M669.463676 658.929202l-351.23052 6.189873c-29.527306 6.969711-32.546035 22.615306-9.054705 46.941226 35.237734 36.486659 357.010497 317.661965 372.719722 311.580115 9.100578-3.52185 16.537896-4.005734 26.473064-9.14941 24.994775-12.939098 21.644578-70.568694-10.05059-172.887307l-28.855491-182.674497z" fill="#0078D4" p-id="5333"></path><path d="M371.417526 0.331468l350.216879-0.325549a47.405873 47.405873 0 0 1 31.042589 11.556994c7.219792 6.252023 13.292763 14.842081 18.220393 25.765734 9.603699 21.293873 112.287815 325.368601 308.052347 912.225665a54.729249 54.729249 0 0 1 1.284439 30.169526c-6.225387 25.780532-19.432324 39.509827-39.623768 41.181965-24.348116 2.018405-144.088046 2.962497-359.221272 2.833758 30.551306-9.66141 44.410821-28.275422 41.578543-55.843515C720.153156 940.511445 618.336185 634.30289 417.516763 49.264462h0.00296a84.346821 84.346821 0 0 0-13.869873-25.255213C392.112092 9.559306 382.194682 2.04652 373.893179 1.47237c-10.553711-0.728046-11.379422-1.109827-2.475653-1.140902z" fill="#2FA7E7" p-id="5334"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="13" height="12" viewBox="0 0 13 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.66663 0.75C9.56612 0.75 11.9166 3.10051 11.9166 6C11.9166 8.8995 9.56612 11.25 6.66663 11.25H1.91663C1.64048 11.25 1.41663 11.0261 1.41663 10.75V6C1.41663 3.10051 3.76713 0.75 6.66663 0.75ZM2.41663 10.25H6.66663C9.01384 10.25 10.9166 8.3472 10.9166 5.99999C10.9166 3.65278 9.01384 1.74999 6.66663 1.74999C4.31942 1.74999 2.41663 3.65278 2.41663 5.99999V10.25ZM4.41663 6.75H6.91663C7.0547 6.75 7.16663 6.86193 7.16663 7V7.5C7.16663 7.63807 7.0547 7.75 6.91663 7.75H4.41663C4.27855 7.75 4.16663 7.63807 4.16663 7.5V7C4.16663 6.86193 4.27855 6.75 4.41663 6.75ZM4.41663 4.74999H8.91663C9.0547 4.74999 9.16663 4.86192 9.16663 4.99999V5.49999C9.16663 5.63806 9.0547 5.74999 8.91663 5.74999H4.41663C4.27855 5.74999 4.16663 5.63806 4.16663 5.49999V4.99999C4.16663 4.86192 4.27855 4.74999 4.41663 4.74999Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 928 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.833332 7.40037C0.833332 7.04018 1.20268 6.79815 1.53293 6.94194L8.77366 10.0945C8.95609 10.1739 9.07406 10.3539 9.07406 10.5529V18.339C9.07406 18.7151 8.67437 18.9566 8.34143 18.7816L1.14374 14.9982C1.05097 14.9474 0.973518 14.8737 0.919075 14.7847C0.864631 14.6956 0.835098 14.5943 0.833414 14.4907L0.833332 14.4814V7.40037ZM18.4645 6.9322C18.795 6.78604 19.1667 7.02807 19.1667 7.38949V14.4814C19.1666 14.5866 19.1379 14.6899 19.0834 14.7807C19.0288 14.8715 18.9504 14.9466 18.8563 14.9982L11.6586 18.7816C11.3256 18.9566 10.9259 18.7151 10.9259 18.339L10.9259 10.5912C10.9259 10.3932 11.0427 10.2139 11.2237 10.1339L18.4645 6.9322ZM9.6999 1.74337C9.79149 1.69312 9.89485 1.66672 10 1.66672C10.1051 1.66672 10.2085 1.69312 10.3001 1.74337L17.1981 4.80724C17.5963 4.9841 17.5934 5.55021 17.1935 5.72313L10.1984 8.74754C10.0718 8.80229 9.92819 8.80229 9.80157 8.74754L2.80646 5.72313C2.40653 5.55021 2.40373 4.9841 2.80193 4.80724L9.6999 1.74337Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.14507 11.6067L13.4518 4.2999C13.582 4.16973 13.7931 4.16973 13.9232 4.2999L14.3946 4.77131C14.5248 4.90148 14.5248 5.11254 14.3946 5.24271L6.38077 13.2566C6.25059 13.3868 6.03954 13.3868 5.90936 13.2566L1.66672 9.01395C1.53655 8.88377 1.53655 8.67272 1.66672 8.54254L2.13813 8.07114C2.2683 7.94096 2.47936 7.94096 2.60953 8.07114L6.14507 11.6067Z" fill="#1CBA90"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 480 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.35493 1.6883C2.58222 1.46101 2.8905 1.33331 3.21194 1.33331H14.1213C14.4427 1.33331 14.751 1.46101 14.9783 1.6883C15.2056 1.91559 15.3333 2.22387 15.3333 2.54531V13.4546C15.3333 13.7761 15.2056 14.0844 14.9783 14.3117C14.751 14.539 14.4427 14.6666 14.1213 14.6666H3.21194C2.54261 14.6666 1.99994 14.124 1.99994 13.4546V2.54531C1.99994 2.22387 2.12763 1.91559 2.35493 1.6883ZM3.33327 6.66665V9.33331H5.99994V6.66665H3.33327ZM3.33327 10.6666V13.3333H5.99994V10.6666H3.33327ZM7.33327 13.3333H9.99994V10.6666H7.33327V13.3333ZM11.3333 13.3333H13.9999V10.6666H11.3333V13.3333ZM13.9999 9.33331V6.66665H11.3333V9.33331H13.9999ZM9.99994 6.66665H7.33327V9.33331H9.99994V6.66665ZM13.9999 2.66665H3.33327V5.33331H13.9999V2.66665Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 864 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.66667 1.99996V14H8V12H9.33334V14.6666C9.33334 15.0348 9.03486 15.3333 8.66667 15.3333H2C1.63181 15.3333 1.33334 15.0348 1.33334 14.6666V1.33329C1.33334 0.965103 1.63181 0.666626 2 0.666626H8.66667C9.03486 0.666626 9.33334 0.965103 9.33334 1.33329V3.99996H8V1.99996H2.66667Z" fill="#646A73"/>
|
||||
<path d="M12.2482 8.69455L7 8.69455C6.95623 8.69455 6.91288 8.68593 6.87244 8.66918C6.832 8.65243 6.79525 8.62788 6.7643 8.59692C6.73335 8.56597 6.7088 8.52922 6.69204 8.48878C6.67529 8.44834 6.66667 8.40499 6.66667 8.36122L6.66667 7.69455C6.66667 7.65078 6.67529 7.60744 6.69204 7.56699C6.7088 7.52655 6.73335 7.48981 6.7643 7.45885C6.79525 7.4279 6.832 7.40335 6.87244 7.38659C6.91288 7.36984 6.95623 7.36122 7 7.36122L12.2569 7.36122L10.6026 5.70689C10.5401 5.64438 10.505 5.55961 10.505 5.47122C10.505 5.38283 10.5401 5.29806 10.6026 5.23556L11.0742 4.76422C11.1367 4.70173 11.2215 4.66663 11.3099 4.66663C11.3983 4.66663 11.4831 4.70173 11.5456 4.76422L14.5689 7.78756C14.5999 7.81851 14.6245 7.85528 14.6413 7.89574C14.658 7.93621 14.6667 7.97958 14.6667 8.02339C14.6667 8.06719 14.658 8.11057 14.6413 8.15103C14.6245 8.1915 14.5999 8.22826 14.5689 8.25922L11.5456 11.2829C11.4831 11.3454 11.3983 11.3805 11.3099 11.3805C11.2215 11.3805 11.1367 11.3454 11.0742 11.2829L10.6026 10.8116C10.5401 10.749 10.505 10.6643 10.505 10.5759C10.505 10.4875 10.5401 10.4027 10.6026 10.3402L12.2482 8.69455Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.17896 1.78057L14.8914 6.05213C15.0972 6.18312 15.0972 6.48359 14.8914 6.61457L8.17896 10.8861C8.06977 10.9556 7.93023 10.9556 7.82104 10.8861L1.10858 6.61457C0.902751 6.48359 0.90275 6.18312 1.10858 6.05213L7.82104 1.78057C7.93023 1.71108 8.06977 1.71108 8.17896 1.78057ZM3.15017 6.33335L8 9.41961L12.8498 6.33335L8 3.2471L3.15017 6.33335Z"/>
|
||||
<path d="M1.73096 9.25743C1.57401 9.1612 1.36878 9.21042 1.27255 9.36736L0.924074 9.9357C0.827845 10.0926 0.877063 10.2979 1.03401 10.3941L7.13744 14.1364C7.56569 14.399 8.10095 14.399 8.52919 14.1364L14.6326 10.3941C14.7896 10.2979 14.8388 10.0926 14.7426 9.9357L14.3941 9.36736C14.2979 9.21042 14.0926 9.1612 13.9357 9.25743L7.92044 12.9457C7.86698 12.9784 7.79965 12.9784 7.7462 12.9457L1.73096 9.25743Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 856 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.13909 6L4.07322 1.93414C3.97559 1.83651 3.97559 1.67822 4.07322 1.58059L4.42678 1.22703C4.52441 1.1294 4.6827 1.1294 4.78033 1.22703L9.19975 5.64645C9.39501 5.84171 9.39501 6.15829 9.19975 6.35356L4.78033 10.773C4.6827 10.8706 4.52441 10.8706 4.42678 10.773L4.07322 10.4194C3.97559 10.3218 3.97559 10.1635 4.07322 10.0659L8.13909 6Z" fill="#8F959E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 466 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.08329 3.33331H16.25C16.4801 3.33331 16.6666 3.51986 16.6666 3.74998V4.58331C16.6666 4.81343 16.4801 4.99998 16.25 4.99998H2.08329C1.85317 4.99998 1.66663 4.81343 1.66663 4.58331V3.74998C1.66663 3.51986 1.85317 3.33331 2.08329 3.33331ZM2.08329 15H16.25C16.4801 15 16.6666 15.1865 16.6666 15.4166V16.25C16.6666 16.4801 16.4801 16.6666 16.25 16.6666H2.08329C1.85317 16.6666 1.66663 16.4801 1.66663 16.25V15.4166C1.66663 15.1865 1.85317 15 2.08329 15ZM2.08329 9.16665H11.25C11.4801 9.16665 11.6666 9.35319 11.6666 9.58331V10.4166C11.6666 10.6468 11.4801 10.8333 11.25 10.8333H2.08329C1.85317 10.8333 1.66663 10.6468 1.66663 10.4166V9.58331C1.66663 9.35319 1.85317 9.16665 2.08329 9.16665Z" fill="#1F2329"/>
|
||||
<path d="M17.8422 10.256C17.8006 10.3251 17.7427 10.383 17.6736 10.4246L14.1369 12.5566C13.9024 12.6979 13.5977 12.6224 13.4564 12.3879C13.4098 12.3107 13.3852 12.2222 13.3852 12.132L13.3851 7.86795C13.3851 7.59416 13.6071 7.37221 13.8808 7.3722C13.971 7.3722 14.0595 7.39681 14.1368 7.44338L17.6736 9.57549C17.9081 9.71684 17.9836 10.0215 17.8422 10.256Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.69818 3.33331H17.8648C18.095 3.33331 18.2815 3.51986 18.2815 3.74998V4.58331C18.2815 4.81343 18.095 4.99998 17.8648 4.99998H3.69818C3.46806 4.99998 3.28151 4.81343 3.28151 4.58331V3.74998C3.28151 3.51986 3.46806 3.33331 3.69818 3.33331ZM3.69818 15H17.8648C18.095 15 18.2815 15.1865 18.2815 15.4166V16.25C18.2815 16.4801 18.095 16.6666 17.8648 16.6666H3.69818C3.46806 16.6666 3.28151 16.4801 3.28151 16.25V15.4166C3.28151 15.1865 3.46806 15 3.69818 15ZM8.69818 9.16665H17.8648C18.095 9.16665 18.2815 9.35319 18.2815 9.58331V10.4166C18.2815 10.6468 18.095 10.8333 17.8648 10.8333H8.69818C8.46806 10.8333 8.28151 10.6468 8.28151 10.4166V9.58331C8.28151 9.35319 8.46806 9.16665 8.69818 9.16665Z" fill="#1F2329"/>
|
||||
<path d="M1.73792 10.256C1.77957 10.3251 1.83746 10.383 1.90655 10.4246L5.44325 12.5566C5.67773 12.6979 5.98239 12.6224 6.12374 12.3879C6.1703 12.3107 6.19491 12.2222 6.19491 12.132L6.19501 7.86796C6.19502 7.59417 5.97307 7.37222 5.69929 7.37221C5.60908 7.37221 5.52059 7.39682 5.44333 7.44339L1.90654 9.5755C1.67207 9.71685 1.59657 10.0215 1.73792 10.256Z" fill="#1F2329"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1127_1532)">
|
||||
<path d="M14 8.00009C14 4.68642 11.3137 2.00009 8.00001 2.00009C6.48001 2.00009 5.09234 2.56509 4.03501 3.49676L3.35734 2.32342C4.66614 1.24984 6.30722 0.664246 8.00001 0.666756C12.05 0.666756 15.3333 3.95009 15.3333 8.00009C15.3333 9.35876 14.964 10.6311 14.32 11.7221L13.5247 10.3448L13.527 10.3391L12.179 8.00009H14ZM3.09667 4.54076L4.14534 6.33509H2.23401C2.07829 6.87637 1.99952 7.43686 2.00001 8.00009C2.00001 11.3138 4.68634 14.0001 8.00001 14.0001C9.93234 14.0001 11.6513 13.0868 12.749 11.6678L13.4587 12.8971C12.116 14.3928 10.168 15.3334 8.00001 15.3334C3.95001 15.3334 0.666672 12.0501 0.666672 8.00009C0.664084 6.27817 1.26996 4.6107 2.37734 3.29209L3.09767 4.53976L3.09667 4.54076Z" fill="#646A73"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.13642 4.66663C7.86709 4.66663 7.62309 4.82329 7.51442 5.06663L5.13775 10.4C5.10232 10.4804 5.08327 10.5672 5.08172 10.6551C5.08017 10.743 5.09616 10.8304 5.12874 10.912C5.16132 10.9937 5.20985 11.0681 5.27149 11.1308C5.33314 11.1935 5.40665 11.2433 5.48775 11.2773C5.65213 11.3487 5.83795 11.3527 6.00526 11.2885C6.17257 11.2243 6.30799 11.097 6.38242 10.934L6.94709 9.66663H9.32642L9.89109 10.9333C9.96539 11.0963 10.1007 11.2236 10.2678 11.2879C10.435 11.3522 10.6207 11.3484 10.7851 11.2773C10.8663 11.2433 10.9399 11.1935 11.0016 11.1307C11.0633 11.068 11.1118 10.9935 11.1444 10.9118C11.177 10.83 11.193 10.7426 11.1914 10.6546C11.1898 10.5666 11.1706 10.4798 11.1351 10.3993L8.75842 5.06596C8.65042 4.82329 8.40642 4.66663 8.13642 4.66663ZM7.54109 8.33329L8.13642 6.99663L8.73242 8.33329H7.54109Z" fill="#646A73"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1127_1532">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,157 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import Menu from './Menu.vue'
|
||||
import Workspace from './Workspace.vue'
|
||||
import Person from './Person.vue'
|
||||
import LOGO from '@/assets/LOGO.svg'
|
||||
import LOGO_fold from '@/assets/LOGO-fold.svg'
|
||||
import icon_moments_categories_outlined from '@/assets/svg/icon_moments-categories_outlined.svg'
|
||||
import icon_side_fold_outlined from '@/assets/svg/icon_side-fold_outlined.svg'
|
||||
import icon_side_expand_outlined from '@/assets/svg/icon_side-expand_outlined.svg'
|
||||
const collapse = ref(false)
|
||||
|
||||
const handleFoldExpand = () => {
|
||||
collapse.value = !collapse.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="system-layout">
|
||||
<div class="left-side" :class="collapse && 'left-side-collapse'">
|
||||
<LOGO_fold v-if="collapse" style="margin: 0 0 6px 5px"></LOGO_fold>
|
||||
<LOGO v-else style="margin-bottom: 6px"></LOGO>
|
||||
<Workspace :collapse="collapse"></Workspace>
|
||||
<Menu :collapse="collapse"></Menu>
|
||||
<div class="bottom">
|
||||
<div class="back-to_workspace">
|
||||
<el-icon size="16">
|
||||
<icon_moments_categories_outlined></icon_moments_categories_outlined>
|
||||
</el-icon>
|
||||
{{ collapse ? '' : '返回工作空间' }}
|
||||
</div>
|
||||
<div class="personal-info">
|
||||
<Person :collapse="collapse"></Person>
|
||||
<el-icon size="20" class="fold" @click="handleFoldExpand">
|
||||
<icon_side_expand_outlined v-if="collapse"></icon_side_expand_outlined>
|
||||
<icon_side_fold_outlined v-else></icon_side_fold_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-main">
|
||||
<div class="content">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.system-layout {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #f1f4f3;
|
||||
display: flex;
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
width: 240px;
|
||||
}
|
||||
100% {
|
||||
width: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.left-side {
|
||||
width: 240px;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
|
||||
.bottom {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 16px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
width: calc(100% - 32px);
|
||||
.back-to_workspace {
|
||||
background-color: #1f23290a;
|
||||
border: 1px solid #d0d3d6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
|
||||
.ed-icon {
|
||||
margin-right: 4.95px;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16px;
|
||||
|
||||
.fold {
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.left-side-collapse {
|
||||
width: 64px;
|
||||
padding: 16px 12px;
|
||||
animation: rotate 1s ease-in-out;
|
||||
|
||||
.ed-menu--collapse {
|
||||
--ed-menu-icon-width: 32px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
left: 12px;
|
||||
width: calc(100% - 24px);
|
||||
.ed-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.personal-info {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.default-avatar {
|
||||
margin: 0 0 26px 4px;
|
||||
}
|
||||
|
||||
.fold {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-main {
|
||||
flex: 1;
|
||||
padding: 8px 8px 8px 0;
|
||||
max-height: 100vh;
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px 24px;
|
||||
box-shadow: 0px 2px 4px 0px #1f23291f;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
|
||||
&:has(.no-padding) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { ElMenu } from 'element-plus-secondary'
|
||||
import { useRoute } from 'vue-router'
|
||||
import MenuItem from './MenuItem.vue'
|
||||
|
||||
defineProps({
|
||||
collapse: Boolean,
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const menuList = computed(() => route.matched[0]?.children || [])
|
||||
|
||||
const activeIndex = computed(() => {
|
||||
const arr = route.path.split('/')
|
||||
return arr[arr.length - 1]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu :default-active="activeIndex" class="ed-menu-vertical" :collapse="collapse">
|
||||
<MenuItem v-for="menu in menuList" :key="menu.path" :menu="menu"></MenuItem>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ed-menu-vertical {
|
||||
--ed-menu-item-height: 40px;
|
||||
--ed-menu-bg-color: transparent;
|
||||
--ed-menu-base-level-padding: 4px;
|
||||
border-right: none;
|
||||
.ed-menu-item {
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
&.is-active.is-active.is-active {
|
||||
background-color: #1cba901a;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { ElMenuItem, ElSubMenu, ElIcon } from 'element-plus-secondary'
|
||||
|
||||
import model from '@/assets/svg/icon_dataset_filled.svg'
|
||||
import ds from '@/assets/svg/ds.svg'
|
||||
|
||||
const iconMap = {
|
||||
icon_ai: model,
|
||||
ds,
|
||||
} as { [key: string]: any }
|
||||
|
||||
const titleWithIcon = (props: any) => {
|
||||
const { title, icon } = props.menu?.meta || {}
|
||||
return [
|
||||
h(ElIcon, null, { default: () => h(iconMap[icon]) }),
|
||||
h('span', null, { default: () => title }),
|
||||
]
|
||||
}
|
||||
|
||||
const MenuItem = (props: any) => {
|
||||
const { children, hidden, path } = props.menu
|
||||
if (hidden) {
|
||||
return null
|
||||
}
|
||||
if (children?.length) {
|
||||
return h(
|
||||
ElSubMenu,
|
||||
{ index: path },
|
||||
{
|
||||
title: () => titleWithIcon(props),
|
||||
default: () => children.map((ele: any) => h(MenuItem, { menu: ele })),
|
||||
}
|
||||
)
|
||||
}
|
||||
const { title, icon } = props.menu?.meta || {}
|
||||
const iconCom: any = iconMap[icon] ? ElIcon : null
|
||||
return h(
|
||||
ElMenuItem,
|
||||
{ index: path },
|
||||
{
|
||||
title: h('span', null, { default: () => title }),
|
||||
default: h(iconCom, null, {
|
||||
default: () => h(iconMap[icon]),
|
||||
}),
|
||||
}
|
||||
)
|
||||
}
|
||||
export default MenuItem
|
||||
</script>
|
||||
@@ -0,0 +1,286 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import Default_avatar from '@/assets/svg/icon-member-default.svg'
|
||||
import icon_admin_outlined from '@/assets/svg/icon_admin_outlined.svg'
|
||||
import icon_maybe_outlined from '@/assets/svg/icon-maybe_outlined.svg'
|
||||
import icon_key_outlined from '@/assets/svg/icon-key_outlined.svg'
|
||||
import icon_translate_outlined from '@/assets/svg/icon_translate_outlined.svg'
|
||||
import icon_logout_outlined from '@/assets/svg/icon_logout_outlined.svg'
|
||||
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
|
||||
defineProps({
|
||||
collapse: { type: [Boolean], required: true },
|
||||
})
|
||||
|
||||
const name = ref('飞小致')
|
||||
const currentLanguage = ref('zh-CN')
|
||||
const emit = defineEmits(['selectPerson'])
|
||||
const languageList = [
|
||||
{
|
||||
name: 'English',
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
name: '简体中文',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
{
|
||||
name: '繁體中文',
|
||||
value: 'zh-CN',
|
||||
},
|
||||
]
|
||||
const handlePersonChange = () => {
|
||||
emit('selectPerson')
|
||||
}
|
||||
|
||||
const handleDefaultLanguageChange = (item: any) => {
|
||||
currentLanguage.value = item.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover popper-class="system-person" :placement="collapse ? 'right' : 'top'">
|
||||
<template #reference>
|
||||
<button class="person" :class="collapse && 'collapse'">
|
||||
<el-icon size="32" class="default-avatar">
|
||||
<Default_avatar></Default_avatar>
|
||||
</el-icon>
|
||||
<span v-if="!collapse" class="name">{{ name }}</span>
|
||||
</button></template
|
||||
>
|
||||
<div class="popover">
|
||||
<div class="popover-content">
|
||||
<div class="info">
|
||||
<el-icon style="transform: scale(1.25)" size="32">
|
||||
<Default_avatar></Default_avatar>
|
||||
</el-icon>
|
||||
<div class="top">{{ name }}</div>
|
||||
<div class="bottom">feixaozhi</div>
|
||||
</div>
|
||||
<div class="popover-item" @click="handlePersonChange">
|
||||
<el-icon size="16">
|
||||
<icon_admin_outlined></icon_admin_outlined>
|
||||
</el-icon>
|
||||
<div class="datasource-name">系统管理</div>
|
||||
</div>
|
||||
<div class="popover-item">
|
||||
<el-icon size="16">
|
||||
<icon_key_outlined></icon_key_outlined>
|
||||
</el-icon>
|
||||
<div class="datasource-name">修改密码</div>
|
||||
</div>
|
||||
<el-popover :teleported="false" popper-class="system-language" placement="right">
|
||||
<template #reference>
|
||||
<div class="popover-item">
|
||||
<el-icon size="16">
|
||||
<icon_translate_outlined></icon_translate_outlined>
|
||||
</el-icon>
|
||||
<div class="datasource-name">语言</div>
|
||||
<el-icon style="transform: scale(1.33)" class="right" size="16">
|
||||
<icon_right_outlined></icon_right_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<div class="language-popover">
|
||||
<div
|
||||
v-for="ele in languageList"
|
||||
:key="ele.name"
|
||||
class="popover-item_language"
|
||||
:class="currentLanguage === ele.value && 'isActive'"
|
||||
@click="handleDefaultLanguageChange(ele)"
|
||||
>
|
||||
<div class="language-name">{{ ele.name }}</div>
|
||||
<el-icon size="16" class="done">
|
||||
<icon_done_outlined></icon_done_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<div class="popover-item">
|
||||
<el-icon size="16">
|
||||
<icon_maybe_outlined></icon_maybe_outlined>
|
||||
</el-icon>
|
||||
<div class="datasource-name">帮助</div>
|
||||
</div>
|
||||
<div class="popover-item mr4">
|
||||
<el-icon size="16">
|
||||
<icon_logout_outlined></icon_logout_outlined>
|
||||
</el-icon>
|
||||
<div class="datasource-name">退出登录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.person {
|
||||
border-radius: 6px;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 156px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
|
||||
&.collapse {
|
||||
min-width: 48px;
|
||||
margin-left: -4px;
|
||||
position: relative;
|
||||
margin-top: -6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.default-avatar {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background: #1f23291a;
|
||||
border: 1px solid #1f23291a;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #1f232926;
|
||||
border: 1px solid #1f23291a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.system-person.system-person {
|
||||
padding: 0;
|
||||
width: 200px !important;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border: 1px solid #dee0e3;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 40px;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: #dee0e3;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background: #dee0e3;
|
||||
}
|
||||
|
||||
.popover {
|
||||
.info {
|
||||
height: 62px;
|
||||
padding: 8px;
|
||||
|
||||
.ed-icon {
|
||||
margin: 6px 8px 0 7px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.top {
|
||||
float: left;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
float: left;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
}
|
||||
.popover-item {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
.datasource-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&.mr4 {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.right {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-language.system-language {
|
||||
padding: 4px 4px 2px 4px;
|
||||
width: 240px !important;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border: 1px solid #dee0e3;
|
||||
|
||||
.language-popover {
|
||||
.popover-item_language {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
margin-bottom: 2px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:not(.empty):hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
.language-name {
|
||||
margin-left: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.done {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
color: var(--ed-color-primary);
|
||||
|
||||
.done {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,195 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import icon_expand_down_filled from '@/assets/svg/icon_expand-down_filled.svg'
|
||||
import icon_moments_categories_outlined from '@/assets/svg/icon_moments-categories_outlined.svg'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
|
||||
defineProps({
|
||||
collapse: { type: [Boolean], required: true },
|
||||
})
|
||||
|
||||
const name = ref('默认工作空间')
|
||||
const currentWorkspace = ref('默认工作空间')
|
||||
const defaultDatasourceList = ref([
|
||||
{
|
||||
name: '默认工作空间',
|
||||
},
|
||||
] as any[])
|
||||
const workspaceKeywords = ref('')
|
||||
const defaultWorkspaceListWithSearch = computed(() => {
|
||||
if (!workspaceKeywords.value) return defaultDatasourceList.value
|
||||
return defaultDatasourceList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(workspaceKeywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const formatKeywords = (item: string) => {
|
||||
if (!workspaceKeywords.value) return item
|
||||
return item.replaceAll(
|
||||
workspaceKeywords.value,
|
||||
`<span class="isSearch">${workspaceKeywords.value}</span>`
|
||||
)
|
||||
}
|
||||
|
||||
const emit = defineEmits(['selectWorkspace'])
|
||||
|
||||
const handleDefaultWorkspaceChange = (item: any) => {
|
||||
currentWorkspace.value = item.name
|
||||
emit('selectWorkspace', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover popper-class="system-workspace" :placement="collapse ? 'right' : 'bottom'">
|
||||
<template #reference>
|
||||
<button class="workspace" :class="collapse && 'collapse'">
|
||||
<el-icon size="16">
|
||||
<icon_moments_categories_outlined></icon_moments_categories_outlined>
|
||||
</el-icon>
|
||||
<span v-if="!collapse" class="name">{{ name }}</span>
|
||||
<el-icon v-if="!collapse" style="transform: scale(0.5)" class="expand" size="24">
|
||||
<icon_expand_down_filled></icon_expand_down_filled>
|
||||
</el-icon></button
|
||||
></template>
|
||||
<div class="popover">
|
||||
<el-input
|
||||
v-model="workspaceKeywords"
|
||||
clearable
|
||||
style="width: 100%; margin-right: 12px"
|
||||
:placeholder="$t('datasource.search_by_name')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="popover-content">
|
||||
<div
|
||||
v-for="ele in defaultWorkspaceListWithSearch"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
:class="currentWorkspace === ele.name && 'isActive'"
|
||||
@click="handleDefaultWorkspaceChange(ele)"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<icon_moments_categories_outlined></icon_moments_categories_outlined>
|
||||
</el-icon>
|
||||
<div class="datasource-name" v-html="formatKeywords(ele.name)"></div>
|
||||
<el-icon size="16" class="done">
|
||||
<icon_done_outlined></icon_done_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-if="!defaultWorkspaceListWithSearch.length" class="popover-item empty">
|
||||
没有找到相关结果
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workspace {
|
||||
background: #1f23290a;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #1f23291a;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 208px;
|
||||
height: 40px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&.collapse {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.expand {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #1f232926;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.system-workspace.system-workspace {
|
||||
padding: 4px 0;
|
||||
width: 280px !important;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border: 1px solid #dee0e3;
|
||||
.ed-input {
|
||||
.ed-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
border-bottom: 1px solid #1f232926;
|
||||
}
|
||||
|
||||
.popover {
|
||||
.popover-content {
|
||||
padding: 4px;
|
||||
}
|
||||
.popover-item {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
margin-bottom: 2px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:not(.empty):hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.empty {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #8f959e;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.datasource-name {
|
||||
margin-left: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.done {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isSearch {
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
color: var(--ed-color-primary);
|
||||
|
||||
.done {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ export interface SelectOption {
|
||||
}
|
||||
|
||||
export const modelTypeOptions: SelectOption[] = [
|
||||
{ label: 'OpenAI', value: 0 },
|
||||
{ label: '大语言模型', value: 0 },
|
||||
/* { label: 'Anthropic', value: 1 },
|
||||
{ label: 'Baidu', value: 2 },
|
||||
{ label: 'iFLYTEK', value: 3 },
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import icon_alybl_colorful from '@/assets/model/icon_alybl_colorful.png'
|
||||
import icon_bdyzn_colorful from '@/assets/model/icon_bdyzn_colorful.png'
|
||||
import icon_deepseek_colorful from '@/assets/model/icon_deepseek_colorful.png'
|
||||
type ModelConfig = Record<number, { api_domain: string; model_options: string[] }>
|
||||
|
||||
export const supplierList: Array<{
|
||||
id: number
|
||||
name: string
|
||||
icon: any
|
||||
model_config: ModelConfig
|
||||
}> = [
|
||||
{
|
||||
id: 1,
|
||||
name: '阿里云百炼',
|
||||
icon: icon_alybl_colorful,
|
||||
model_config: {
|
||||
0: {
|
||||
api_domain: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
model_options: ['qwen-plus'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '千帆大模型I',
|
||||
icon: icon_bdyzn_colorful,
|
||||
model_config: {
|
||||
0: {
|
||||
api_domain: 'https://qianfan.baidubce.com/v2/',
|
||||
model_options: ['ernie-x1-turbo-32k'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'DeepSeek',
|
||||
icon: icon_deepseek_colorful,
|
||||
model_config: {
|
||||
0: {
|
||||
api_domain: 'https://api.deepseek.com',
|
||||
model_options: ['deepseek-chat'],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const base_model_options = (supplier_id: number, model_type?: number) => {
|
||||
const supplier = get_supplier(supplier_id)
|
||||
return supplier?.model_config[model_type || 0].model_options
|
||||
}
|
||||
|
||||
export const get_supplier = (supplier_id: number) => {
|
||||
return supplierList.find((item: any) => item.id === supplier_id)
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
},
|
||||
"common": {
|
||||
"input_content": "Please Input Content",
|
||||
"empty": " ",
|
||||
"back": "Back",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
@@ -71,6 +72,7 @@
|
||||
"type": "Data Source Type",
|
||||
"status": "Status",
|
||||
"actions": "Actions",
|
||||
"pieces_in_total": "Showing {ms} records out of {msg}",
|
||||
"test_connection": "Test Connection",
|
||||
"connection_success": "Connection Successful",
|
||||
"connection_failed": "Connection Failed, Please Check Configuration",
|
||||
@@ -147,9 +149,64 @@
|
||||
"success": "Connect success",
|
||||
"failed": "Connect failed"
|
||||
},
|
||||
"timeout":"Timeout(second)"
|
||||
"timeout": "Timeout(second)"
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"search_by_name": "Search by Name",
|
||||
"search": "Search",
|
||||
"all_types": "All types",
|
||||
"new_data_source": "New data source",
|
||||
"open_query": "Open query",
|
||||
"edit": "Edit",
|
||||
"source_connection_failed": "Data source connection failed",
|
||||
"confirm": "Confirm",
|
||||
"enabled_status": "Enabled status",
|
||||
"custom_notes": "Custom notes",
|
||||
"field_type": "Field type",
|
||||
"field_name": "Field name",
|
||||
"relevant_content_found": "No relevant content found",
|
||||
"please_enter": "Please enter",
|
||||
"table_notes": "Table notes",
|
||||
"field_notes": "Field notes",
|
||||
"select_all": "Select all",
|
||||
"mysql_data_source": "Modify {msg} data source",
|
||||
"configuration_information": "Configuration information",
|
||||
"data_source": "Do you want to delete the data source: {msg}?",
|
||||
"operate_with_caution": "After being deleted, the data source cannot be intelligently counted. Please operate with caution.",
|
||||
"data_source_de": "Unable to delete data source: {msg}",
|
||||
"cannot_be_deleted": "The chart of the dashboard uses this data source and cannot be deleted.",
|
||||
"got_it": "Got it"
|
||||
},
|
||||
"model": {
|
||||
"default_model": "Default model",
|
||||
"model_type": "Model type",
|
||||
"basic_model": "Basic model",
|
||||
"set_successfully": "Set successfully",
|
||||
"operate_with_caution": "After the system default model is replaced, the result of intelligent question will be affected, please operate with caution.",
|
||||
"system_default_model": "Do you want to set {msg} as the system default model?",
|
||||
"ai_model_configuration": "AI model configuration",
|
||||
"system_default_model_de": "System default model",
|
||||
"relevant_results_found": "No relevant results found",
|
||||
"add_model": "Add model",
|
||||
"select_supplier": "Select supplier",
|
||||
"the_basic_model": "Please set a name for the basic model",
|
||||
"the_basic_model_de": "Please select the basic model",
|
||||
"model_name": "Model name",
|
||||
"custom_model_name": "Custom model name",
|
||||
"enter_to_add": "For models not listed in the list, just enter the model name and press Enter to add",
|
||||
"api_domain_name": "API domain name",
|
||||
"advanced_settings": "Advanced settings",
|
||||
"model_parameters": "Model parameters",
|
||||
"add": "Add",
|
||||
"parameters": "Parameters",
|
||||
"display_name": "Display name",
|
||||
"parameter_value": "Parameter value",
|
||||
"text": "Text",
|
||||
"number": "Number",
|
||||
"parameter_type": "Parameter type",
|
||||
"verification_successful": "Verification successful"
|
||||
},
|
||||
"chat": {
|
||||
"chart_type": {
|
||||
"table": "Table",
|
||||
@@ -186,4 +243,4 @@
|
||||
"back_community": "Restore to Community Edition",
|
||||
"confirm_tips": "Are you sure to restore to Community Edition?"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
},
|
||||
"common": {
|
||||
"input_content": "请输入内容",
|
||||
"empty": "",
|
||||
"back": "返回",
|
||||
"confirm": "确认",
|
||||
"cancel": "取消",
|
||||
@@ -71,6 +72,7 @@
|
||||
"name": "数据源名称",
|
||||
"type": "数据源类型",
|
||||
"status": "状态",
|
||||
"pieces_in_total": "显示 {ms} 条数据,共 {msg} 条",
|
||||
"actions": "操作",
|
||||
"test_connection": "测试连接",
|
||||
"connection_success": "连接成功",
|
||||
@@ -148,9 +150,64 @@
|
||||
"success": "连接成功",
|
||||
"failed": "连接失败"
|
||||
},
|
||||
"timeout":"查询超时(秒)"
|
||||
"timeout": "查询超时(秒)"
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"search_by_name": "通过名称搜索",
|
||||
"search": "搜索",
|
||||
"all_types": "全部类型",
|
||||
"new_data_source": "新建数据源",
|
||||
"open_query": "开启问数",
|
||||
"edit": "编辑",
|
||||
"source_connection_failed": "数据源连接失败",
|
||||
"confirm": "确定",
|
||||
"enabled_status": "启用状态",
|
||||
"custom_notes": "自定义备注",
|
||||
"field_type": "字段类型",
|
||||
"field_name": "字段名称",
|
||||
"relevant_content_found": "没有找到相关内容",
|
||||
"please_enter": "请输入",
|
||||
"table_notes": "表备注",
|
||||
"field_notes": "字段备注",
|
||||
"select_all": "全选",
|
||||
"mysql_data_source": "修改 {msg} 数据源",
|
||||
"configuration_information": "配置信息",
|
||||
"data_source": "是否删除数据源:{msg}?",
|
||||
"operate_with_caution": "被删除后,将无法对该数据源进行智能问数,请谨慎操作。",
|
||||
"data_source_de": "无法删除数据源:{msg}",
|
||||
"cannot_be_deleted": "仪表板的图表使用了该数据源,不可删除。",
|
||||
"got_it": "知道了"
|
||||
},
|
||||
"model": {
|
||||
"default_model": "默认模型",
|
||||
"model_type": "模型类型",
|
||||
"basic_model": "基础模型",
|
||||
"set_successfully": "设置成功",
|
||||
"operate_with_caution": "系统默认模型被替换后,智能问数的结果将会受到影响,请谨慎操作。",
|
||||
"system_default_model": "是否设置 {msg} 为系统默认模型?",
|
||||
"ai_model_configuration": "AI 模型配置",
|
||||
"system_default_model_de": "系统默认模型",
|
||||
"relevant_results_found": "没有找到相关结果",
|
||||
"add_model": "添加模型",
|
||||
"select_supplier": "选择供应商",
|
||||
"the_basic_model": "请给基础模型设置一个名称",
|
||||
"the_basic_model_de": "请选择基础模型",
|
||||
"model_name": "模型名称",
|
||||
"custom_model_name": "自定义的模型名称",
|
||||
"enter_to_add": "列表中未列出的模型,直接输入模型名称,回车即可添加",
|
||||
"api_domain_name": "API 域名",
|
||||
"advanced_settings": "高级设置",
|
||||
"model_parameters": "模型参数",
|
||||
"add": "添加",
|
||||
"parameters": "参数",
|
||||
"display_name": "显示名称",
|
||||
"parameter_value": "参数值",
|
||||
"text": "文本",
|
||||
"number": "数值",
|
||||
"parameter_type": "参数类型",
|
||||
"verification_successful": "校验成功"
|
||||
},
|
||||
"chat": {
|
||||
"chart_type": {
|
||||
"table": "明细表",
|
||||
@@ -187,4 +244,4 @@
|
||||
"back_community": "还原至社区版",
|
||||
"confirm_tips": "确定还原至社区版?"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import Layout from '@/components/layout/index.vue'
|
||||
import LayoutDsl from '@/components/layout/LayoutDsl.vue'
|
||||
import SinglePage from '@/components/layout/SinglePage.vue'
|
||||
import login from '@/views/login/index.vue'
|
||||
import chat from '@/views/chat/index.vue'
|
||||
import ds from '@/views/ds/index.vue'
|
||||
import Datasource from '@/views/ds/Datasource.vue'
|
||||
import DashboardEditor from '@/views/dashboard/editor/index.vue'
|
||||
import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
|
||||
import Dashboard from '@/views/dashboard/index.vue'
|
||||
import Model from '@/views/system/model/index.vue'
|
||||
// import Model from '@/views/system/model/index.vue'
|
||||
import Model from '@/views/system/model/Model.vue'
|
||||
// import User from "@/views/system/user/index.vue";
|
||||
import { watchRouter } from './watch'
|
||||
const router = createRouter({
|
||||
@@ -46,13 +48,13 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: '/ds',
|
||||
component: Layout,
|
||||
component: LayoutDsl,
|
||||
redirect: '/ds/index',
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'ds',
|
||||
component: ds,
|
||||
component: Datasource,
|
||||
meta: { title: 'Data Connections', icon: 'ds' },
|
||||
},
|
||||
],
|
||||
@@ -95,17 +97,30 @@ const router = createRouter({
|
||||
},
|
||||
],
|
||||
}, */
|
||||
// {
|
||||
// path: '/system',
|
||||
// component: Layout,
|
||||
// redirect: '/system/model',
|
||||
// children: [
|
||||
// /* {
|
||||
// path: "user",
|
||||
// name: "user",
|
||||
// component: User,
|
||||
// meta: { title: "User Management", icon: "icon_user" },
|
||||
// }, */
|
||||
// {
|
||||
// path: 'model',
|
||||
// name: 'model',
|
||||
// component: Model,
|
||||
// meta: { title: 'AI Model Configuration', icon: 'icon_ai' },
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/system',
|
||||
component: Layout,
|
||||
component: LayoutDsl,
|
||||
redirect: '/system/model',
|
||||
children: [
|
||||
/* {
|
||||
path: "user",
|
||||
name: "user",
|
||||
component: User,
|
||||
meta: { title: "User Management", icon: "icon_user" },
|
||||
}, */
|
||||
{
|
||||
path: 'model',
|
||||
name: 'model',
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -26,7 +25,7 @@
|
||||
--border-radius: 8px;
|
||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
|
||||
--el-color-primary: #1cba90 !important;
|
||||
--ed-color-primary: #1cba90 !important;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -111,7 +110,6 @@ body {
|
||||
border-radius: 8px !important;
|
||||
}
|
||||
|
||||
|
||||
.hover-icon {
|
||||
cursor: pointer;
|
||||
height: 24px !important;
|
||||
@@ -145,3 +143,9 @@ body {
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<script lang="ts" setup>
|
||||
import delIcon from '@/assets/svg/icon_delete.svg'
|
||||
import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg'
|
||||
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
import icon_chat_outlined from '@/assets/svg/icon_chat_outlined.svg'
|
||||
import { ref, unref, computed } from 'vue'
|
||||
import { ClickOutside as vClickOutside } from 'element-plus-secondary'
|
||||
import { dsTypeWithImg } from './js/ds-type'
|
||||
import edit from '@/assets/svg/icon_edit_outlined.svg'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string
|
||||
type: string
|
||||
rate?: string
|
||||
description?: string
|
||||
id?: string
|
||||
}>(),
|
||||
{
|
||||
name: '-',
|
||||
type: '-',
|
||||
rate: '-',
|
||||
description: '-',
|
||||
id: '-',
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits(['edit', 'del', 'question', 'dataTableDetail'])
|
||||
const icon = computed(() => {
|
||||
return (dsTypeWithImg.find((ele) => props.type === ele.type) || {}).img
|
||||
})
|
||||
const handleEdit = () => {
|
||||
emits('edit')
|
||||
}
|
||||
|
||||
const handleDel = () => {
|
||||
emits('del')
|
||||
}
|
||||
|
||||
const handleQuestion = () => {
|
||||
emits('question', props.id)
|
||||
}
|
||||
|
||||
const dataTableDetail = () => {
|
||||
emits('dataTableDetail')
|
||||
}
|
||||
const buttonRef = ref()
|
||||
const popoverRef = ref()
|
||||
const onClickOutside = () => {
|
||||
unref(popoverRef).popperRef?.delayHide?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card" @click="dataTableDetail">
|
||||
<div class="name-icon">
|
||||
<img :src="icon" width="32px" height="32px" />
|
||||
<div class="info">
|
||||
<div class="name">{{ name }}</div>
|
||||
<div class="type">{{ type }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="type-value">
|
||||
{{ description }}
|
||||
</div>
|
||||
|
||||
<div class="bottom-info">
|
||||
<div class="form-rate">
|
||||
<el-icon class="form-icon" size="16">
|
||||
<icon_form_outlined></icon_form_outlined>
|
||||
</el-icon>
|
||||
{{ rate }}
|
||||
</div>
|
||||
<div click.stop class="methods">
|
||||
<el-button type="primary" style="margin-right: 8px" @click.stop="handleQuestion">
|
||||
<el-icon style="margin-right: 2px" size="12">
|
||||
<icon_chat_outlined></icon_chat_outlined>
|
||||
</el-icon>
|
||||
{{ $t('datasource.open_query') }}
|
||||
</el-button>
|
||||
<el-icon
|
||||
ref="buttonRef"
|
||||
v-click-outside="onClickOutside"
|
||||
class="more"
|
||||
size="16"
|
||||
@click.stop
|
||||
>
|
||||
<icon_more_outlined></icon_more_outlined>
|
||||
</el-icon>
|
||||
<el-popover
|
||||
ref="popoverRef"
|
||||
:virtual-ref="buttonRef"
|
||||
virtual-triggering
|
||||
trigger="click"
|
||||
:teleported="false"
|
||||
popper-class="popover-card"
|
||||
placement="bottom"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="item" @click.stop="handleEdit">
|
||||
<el-icon size="16">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
{{ $t('datasource.edit') }}
|
||||
</div>
|
||||
<div class="item" @click.stop="handleDel">
|
||||
<el-icon size="16">
|
||||
<delIcon></delIcon>
|
||||
</el-icon>
|
||||
{{ $t('dashboard.delete') }}
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card {
|
||||
width: 370px;
|
||||
border: 1px solid #dee0e3;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
margin: 0 16px 16px 0;
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 24px 0px #1f232914;
|
||||
}
|
||||
|
||||
.name-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.info {
|
||||
margin-left: 12px;
|
||||
.name {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.type {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #646a73;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type-value {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
height: 44px;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-info {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 28px;
|
||||
|
||||
.ed-button {
|
||||
height: 28px;
|
||||
min-width: 78px;
|
||||
}
|
||||
|
||||
.form-rate {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #646a73;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
|
||||
.form-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.methods {
|
||||
align-items: center;
|
||||
display: none;
|
||||
|
||||
.more {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.methods {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.popover-card.popover-card.popover-card {
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dee0e3;
|
||||
width: 120px !important;
|
||||
min-width: 120px !important;
|
||||
padding: 0;
|
||||
.content {
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 40px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #dee0e3;
|
||||
}
|
||||
.item {
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
.ed-icon {
|
||||
margin-right: 8px;
|
||||
color: #646a73;
|
||||
}
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 112px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #1f23291a;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,512 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import edit from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ParamsForm from './ParamsForm.vue'
|
||||
|
||||
interface Table {
|
||||
name: string
|
||||
host: string
|
||||
port: string
|
||||
username: string
|
||||
password: string
|
||||
database: string
|
||||
extraJdbc: string
|
||||
dbSchema: string
|
||||
filename: string
|
||||
sheets: string
|
||||
mode: string
|
||||
timeout: string
|
||||
configuration: string
|
||||
id: number
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
info: Table
|
||||
}>(),
|
||||
{
|
||||
info: () => ({
|
||||
name: '-',
|
||||
host: '-',
|
||||
port: '-',
|
||||
username: '-',
|
||||
password: '-',
|
||||
database: '-',
|
||||
extraJdbc: '-',
|
||||
dbSchema: '-',
|
||||
filename: '-',
|
||||
sheets: '-',
|
||||
mode: '-',
|
||||
timeout: '-',
|
||||
configuration: '-',
|
||||
id: 0,
|
||||
}),
|
||||
}
|
||||
)
|
||||
const { t } = useI18n()
|
||||
const paramsFormRef = ref()
|
||||
const tableList = ref([] as any[])
|
||||
const keywords = ref('')
|
||||
const tableListWithSearch = computed(() => {
|
||||
if (!keywords.value) return tableList.value
|
||||
return tableList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const total = ref(1000)
|
||||
const showNum = ref(100)
|
||||
const currentTable = ref<any>({})
|
||||
const ds = ref<any>({})
|
||||
const btnSelect = ref('q')
|
||||
|
||||
const init = () => {
|
||||
datasourceApi.getDs(props.info.id).then((res) => {
|
||||
ds.value = res
|
||||
fieldList.value = []
|
||||
datasourceApi.tableList(props.info.id).then((res) => {
|
||||
tableList.value = res
|
||||
})
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
const tableComment = ref('')
|
||||
const fieldDialog = ref<boolean>(false)
|
||||
const tableDialog = ref<boolean>(false)
|
||||
const fieldComment = ref('')
|
||||
const currentField = ref<any>({})
|
||||
const previewData = ref<any>({})
|
||||
const fieldList = ref<any>([])
|
||||
|
||||
const buildData = () => {
|
||||
return { table: currentTable.value, fields: fieldList.value }
|
||||
}
|
||||
|
||||
const handleSelectTableList = () => {
|
||||
paramsFormRef.value.open(props.info)
|
||||
}
|
||||
|
||||
const clickTable = (table: any) => {
|
||||
currentTable.value = table
|
||||
datasourceApi.fieldList(table.id).then((res) => {
|
||||
fieldList.value = res
|
||||
datasourceApi.previewData(props.info.id, buildData()).then((res) => {
|
||||
previewData.value = res
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const closeTable = () => {
|
||||
tableDialog.value = false
|
||||
}
|
||||
const editTable = () => {
|
||||
tableComment.value = currentTable.value.custom_comment
|
||||
tableDialog.value = true
|
||||
}
|
||||
const saveTable = () => {
|
||||
currentTable.value.custom_comment = tableComment.value
|
||||
datasourceApi.saveTable(currentTable.value).then(() => {
|
||||
closeTable()
|
||||
ElMessage({
|
||||
message: t('common.save_success'),
|
||||
type: 'success',
|
||||
showClose: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
const closeField = () => {
|
||||
fieldDialog.value = false
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
datasourceApi.tableList(props.info.id).then((res) => {
|
||||
tableList.value = res
|
||||
if (!currentTable.value.table_name) return
|
||||
const nameArr = tableList.value.map((ele: any) => ele.table_name)
|
||||
if (!nameArr.includes(currentTable.value.table_name)) {
|
||||
currentTable.value = {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const saveField = () => {
|
||||
currentField.value.custom_comment = fieldComment.value
|
||||
datasourceApi.saveField(currentField.value).then(() => {
|
||||
closeField()
|
||||
ElMessage({
|
||||
message: t('common.save_success'),
|
||||
type: 'success',
|
||||
showClose: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const editField = (row: any) => {
|
||||
currentField.value = row
|
||||
fieldComment.value = currentField.value.custom_comment
|
||||
fieldDialog.value = true
|
||||
}
|
||||
|
||||
const changeStatus = (row: any) => {
|
||||
currentField.value = row
|
||||
datasourceApi.saveField(currentField.value).then(() => {
|
||||
closeField()
|
||||
ElMessage({
|
||||
message: t('common.save_success'),
|
||||
type: 'success',
|
||||
showClose: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="data-table no-padding">
|
||||
<div class="info">
|
||||
{{ $t('ds.title') }}
|
||||
<el-icon size="12">
|
||||
<icon_right_outlined></icon_right_outlined>
|
||||
</el-icon>
|
||||
<div class="name">{{ info.name }}</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="side-list">
|
||||
<div class="select-table_top">
|
||||
{{ $t('ds.tables') }}
|
||||
|
||||
<el-tooltip effect="dark" :content="$t('ds.form.choose_tables')" placement="top">
|
||||
<el-icon size="18" @click="handleSelectTableList">
|
||||
<icon_form_outlined></icon_form_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 232px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<div class="list-content">
|
||||
<div
|
||||
v-for="ele in tableListWithSearch"
|
||||
:key="ele.table_name"
|
||||
class="model"
|
||||
:class="currentTable.table_name === ele.table_name && 'isActive'"
|
||||
@click="clickTable(ele)"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<icon_form_outlined></icon_form_outlined>
|
||||
</el-icon>
|
||||
<span class="name">{{ ele.table_name }}</span>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !tableListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
style="width: 100%; margin-top: 100px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTable.table_name" class="info-table">
|
||||
<div class="table-name">
|
||||
<div class="name">{{ currentTable.table_name }}</div>
|
||||
<div class="notes">
|
||||
{{ $t('about.remark') }}:
|
||||
{{ currentTable.custom_comment || '-' }}
|
||||
<el-icon style="margin-left: 8px; cursor: pointer" size="16" @click="editTable">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<div class="btn-select">
|
||||
<el-button :class="[btnSelect === 'd' && 'is-active']" text @click="btnSelect = 'd'">
|
||||
{{ t('ds.table_schema') }}
|
||||
</el-button>
|
||||
<el-button :class="[btnSelect === 'q' && 'is-active']" text @click="btnSelect = 'q'">
|
||||
{{ t('ds.preview') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="preview-or-schema">
|
||||
<el-table v-if="btnSelect === 'd'" :data="fieldList" style="width: 100%">
|
||||
<el-table-column prop="field_name" :label="t('datasource.field_name')" width="180" />
|
||||
<el-table-column prop="field_type" :label="t('datasource.field_type')" width="180" />
|
||||
<el-table-column prop="field_comment" :label="t('about.remark')" />
|
||||
<el-table-column :label="t('datasource.custom_notes')">
|
||||
<template #default="scope">
|
||||
<div class="field-comment">
|
||||
<span>{{ scope.row.custom_comment }}</span>
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('datasource.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="editField(scope.row)">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('datasource.enabled_status')" width="180">
|
||||
<template #default="scope">
|
||||
<div style="display: flex; align-items: center">
|
||||
<el-switch
|
||||
v-model="scope.row.checked"
|
||||
size="small"
|
||||
@change="changeStatus(scope.row)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template v-else>
|
||||
<div class="preview-num">
|
||||
{{ t('ds.pieces_in_total', { msg: total, ms: showNum }) }}
|
||||
</div>
|
||||
<el-table :data="previewData.data" style="width: 100%; height: 600px">
|
||||
<el-table-column
|
||||
v-for="(c, index) in previewData.fields"
|
||||
:key="index"
|
||||
:prop="c"
|
||||
:label="c"
|
||||
/>
|
||||
</el-table>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="tableDialog"
|
||||
:title="t('datasource.table_notes')"
|
||||
width="600"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
@closed="closeTable"
|
||||
>
|
||||
<el-input
|
||||
v-model="tableComment"
|
||||
:placeholder="$t('datasource.please_enter')"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeTable">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveTable">{{ t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="fieldDialog"
|
||||
:title="t('datasource.field_notes')"
|
||||
width="600"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
@closed="closeField"
|
||||
>
|
||||
<el-input
|
||||
v-model="fieldComment"
|
||||
:placeholder="$t('datasource.please_enter')"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeField">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveField">{{ t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<ParamsForm ref="paramsFormRef" @refresh="refresh"></ParamsForm>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.data-table {
|
||||
height: 100%;
|
||||
.info {
|
||||
height: 56px;
|
||||
width: 100%;
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
border-bottom: 1px solid #1f232926;
|
||||
|
||||
.name {
|
||||
color: #1f2329;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
height: calc(100% - 56px);
|
||||
position: relative;
|
||||
.side-list {
|
||||
width: 280px;
|
||||
padding: 8px 16px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #1f232926;
|
||||
.select-table_top {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
|
||||
.ed-icon:hover {
|
||||
cursor: pointer;
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.ed-input {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
height: calc(100% - 100px);
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.name {
|
||||
margin-left: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
&:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
background: #1cba901a;
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.info-table {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: calc(100% - 280px);
|
||||
.table-name {
|
||||
height: 80px;
|
||||
padding: 16px 0 0 24px;
|
||||
border-bottom: 1px solid #1f232926;
|
||||
|
||||
.name {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.notes {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.table-content {
|
||||
padding: 16px 24px;
|
||||
|
||||
.btn-select {
|
||||
height: 32px;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
display: inline-flex;
|
||||
background: #ffffff;
|
||||
align-items: center;
|
||||
border: 1px solid #d0d3d6;
|
||||
border-radius: 4px;
|
||||
|
||||
.is-active {
|
||||
background: #1cba901a;
|
||||
}
|
||||
|
||||
.ed-button:not(.is-active) {
|
||||
color: #1f2329;
|
||||
}
|
||||
.ed-button.is-text {
|
||||
height: 24px;
|
||||
width: auto;
|
||||
padding: 0 8px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.ed-button + .ed-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-or-schema {
|
||||
margin-top: 16px;
|
||||
|
||||
.field-comment {
|
||||
height: 24px;
|
||||
}
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-num {
|
||||
margin: 12px 0;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,399 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, shallowRef, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import arrow_down from '@/assets/svg/arrow-down.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import DataTable from './DataTable.vue'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
|
||||
import DatasourceList from './DatasourceList.vue'
|
||||
import DatasourceListSide from './DatasourceListSide.vue'
|
||||
import DatasourceForm from './DatasourceForm.vue'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import Card from './Card.vue'
|
||||
import { dsTypeWithImg } from './js/ds-type'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
interface Datasource {
|
||||
name: string
|
||||
type: string
|
||||
img: string
|
||||
rate?: string
|
||||
id?: string
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const keywords = ref('')
|
||||
const defaultDatasourceKeywords = ref('')
|
||||
const datasourceConfigvVisible = ref(false)
|
||||
const editDatasource = ref(false)
|
||||
const activeStep = ref(0)
|
||||
const activeName = ref('')
|
||||
const datasourceFormRef = ref()
|
||||
|
||||
const datasourceList = shallowRef([] as Datasource[])
|
||||
const defaultDatasourceList = shallowRef(dsTypeWithImg as (Datasource & { img: string })[])
|
||||
|
||||
const currentDefaultDatasource = ref('')
|
||||
const datasourceListWithSearch = computed(() => {
|
||||
if (!keywords.value) return datasourceList.value
|
||||
return datasourceList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const beforeClose = () => {
|
||||
datasourceConfigvVisible.value = false
|
||||
}
|
||||
const defaultDatasourceListWithSearch = computed(() => {
|
||||
if (!defaultDatasourceKeywords.value) return defaultDatasourceList.value
|
||||
return defaultDatasourceList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(defaultDatasourceKeywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const handleDefaultDatasourceChange = (item: any) => {
|
||||
currentDefaultDatasource.value = item.name
|
||||
}
|
||||
|
||||
const formatKeywords = (item: string) => {
|
||||
if (!defaultDatasourceKeywords.value) return item
|
||||
return item.replaceAll(
|
||||
defaultDatasourceKeywords.value,
|
||||
`<span class="isSearch">${defaultDatasourceKeywords.value}</span>`
|
||||
)
|
||||
}
|
||||
const currentType = ref('')
|
||||
const handleEditDatasource = (res: any) => {
|
||||
activeStep.value = 1
|
||||
datasourceConfigvVisible.value = true
|
||||
editDatasource.value = true
|
||||
currentType.value = res.type_name
|
||||
nextTick(() => {
|
||||
datasourceFormRef.value.initForm(res)
|
||||
})
|
||||
}
|
||||
|
||||
const handleAddDatasource = () => {
|
||||
editDatasource.value = false
|
||||
datasourceConfigvVisible.value = true
|
||||
}
|
||||
|
||||
const deleteHandler = (item: any) => {
|
||||
ElMessageBox.confirm(t('datasource.data_source', { msg: item.name }), {
|
||||
confirmButtonType: 'danger',
|
||||
tip: t('datasource.operate_with_caution'),
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
})
|
||||
.then(() => {
|
||||
datasourceApi.delete(item.id).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: t('dashboard.delete_success'),
|
||||
})
|
||||
search()
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessageBox.confirm(t('datasource.data_source_de', { msg: item.name }), {
|
||||
tip: t('datasource.cannot_be_deleted'),
|
||||
cancelButtonText: t('datasource.got_it'),
|
||||
showConfirmButton: false,
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const clickDatasource = (ele: any) => {
|
||||
activeStep.value = 1
|
||||
activeName.value = ele.name
|
||||
}
|
||||
|
||||
const clickDatasourceSide = (ele: any) => {
|
||||
activeName.value = ele.name
|
||||
activeStep.value = 1
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
datasourceApi.list().then((res: any) => {
|
||||
datasourceList.value = res
|
||||
})
|
||||
}
|
||||
search()
|
||||
|
||||
const submit = (item: any) => {
|
||||
if (!item.id) {
|
||||
datasourceApi.add(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Add completed',
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
datasourceApi.edit(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Edit completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const currentDataTable = ref()
|
||||
const dataTableDetail = (ele: any) => {
|
||||
currentDataTable.value = ele
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-show="!currentDataTable" class="datasource-config">
|
||||
<div class="datasource-methods">
|
||||
<span class="title">{{ $t('ds.title') }}</span>
|
||||
<div class="button-input">
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 240px; margin-right: 12px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-popover popper-class="system-default_datasource" placement="bottom">
|
||||
<template #reference>
|
||||
<el-button secondary>
|
||||
{{ $t('datasource.all_types') }}
|
||||
<el-icon style="margin-left: 8px">
|
||||
<arrow_down></arrow_down>
|
||||
</el-icon> </el-button
|
||||
></template>
|
||||
<div class="popover">
|
||||
<el-input
|
||||
v-model="defaultDatasourceKeywords"
|
||||
clearable
|
||||
style="width: 100%; margin-right: 12px"
|
||||
:placeholder="$t('datasource.search_by_name')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="popover-content">
|
||||
<div
|
||||
v-for="ele in defaultDatasourceListWithSearch"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
:class="currentDefaultDatasource === ele.name && 'isActive'"
|
||||
@click="handleDefaultDatasourceChange(ele)"
|
||||
>
|
||||
<img :src="ele.img" width="24px" height="24px" />
|
||||
<div class="datasource-name" v-html="formatKeywords(ele.name)"></div>
|
||||
<el-icon size="16" class="done">
|
||||
<icon_done_outlined></icon_done_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-if="!defaultDatasourceListWithSearch.length" class="popover-item empty">
|
||||
没有找到相关结果
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<el-button type="primary" @click="handleAddDatasource">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ $t('datasource.new_data_source') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !datasourceListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
/>
|
||||
|
||||
<div v-else class="card-content">
|
||||
<Card
|
||||
v-for="ele in datasourceListWithSearch"
|
||||
:id="ele.id"
|
||||
:key="ele.id"
|
||||
:name="ele.name"
|
||||
:type="ele.type"
|
||||
:rate="ele.rate"
|
||||
@edit="handleEditDatasource(ele)"
|
||||
@del="deleteHandler(ele)"
|
||||
@data-table-detail="dataTableDetail(ele)"
|
||||
></Card>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="datasourceConfigvVisible"
|
||||
:close-on-click-modal="false"
|
||||
size="calc(100% - 100px)"
|
||||
modal-class="datasource-drawer-fullscreen"
|
||||
direction="btt"
|
||||
:before-close="beforeClose"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<span style="white-space: nowrap">{{
|
||||
editDatasource
|
||||
? t('datasource.mysql_data_source', { msg: currentType })
|
||||
: $t('datasource.new_data_source')
|
||||
}}</span>
|
||||
<div v-if="!editDatasource" class="flex-center" style="width: 100%">
|
||||
<el-steps custom style="max-width: 500px; flex: 1" :active="activeStep" align-center>
|
||||
<el-step>
|
||||
<template #title> {{ $t('qa.select_datasource') }} </template>
|
||||
</el-step>
|
||||
<el-step>
|
||||
<template #title> {{ $t('datasource.configuration_information') }} </template>
|
||||
</el-step>
|
||||
<el-step>
|
||||
<template #title> {{ $t('ds.form.choose_tables') }} </template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</div>
|
||||
<el-icon style="cursor: pointer" @click="close">
|
||||
<icon_close_outlined></icon_close_outlined>
|
||||
</el-icon>
|
||||
</template>
|
||||
<DatasourceList v-if="activeStep === 0" @click-datasource="clickDatasource"></DatasourceList>
|
||||
<DatasourceListSide
|
||||
v-if="activeStep === 1 && !editDatasource"
|
||||
:active-name="activeName"
|
||||
@click-datasource="clickDatasourceSide"
|
||||
></DatasourceListSide>
|
||||
<DatasourceForm
|
||||
v-if="[1, 2].includes(activeStep)"
|
||||
ref="datasourceFormRef"
|
||||
:active-step="activeStep"
|
||||
:active-name="activeName"
|
||||
@submit="submit"
|
||||
@change-active-step="(val: number) => (activeStep = val)"
|
||||
></DatasourceForm>
|
||||
</el-drawer>
|
||||
</div>
|
||||
<DataTable v-if="currentDataTable" :info="currentDataTable"></DataTable>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.datasource-config {
|
||||
height: calc(100% - 16px);
|
||||
.datasource-methods {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.system-default_datasource.system-default_datasource {
|
||||
padding: 4px 0;
|
||||
width: 325px !important;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border: 1px solid #dee0e3;
|
||||
.ed-input {
|
||||
.ed-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
border-bottom: 1px solid #1f232926;
|
||||
}
|
||||
|
||||
.popover {
|
||||
.popover-content {
|
||||
padding: 4px;
|
||||
}
|
||||
.popover-item {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
margin-bottom: 2px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:not(.empty):hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.empty {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #8f959e;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.datasource-name {
|
||||
margin-left: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.done {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isSearch {
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
color: var(--ed-color-primary);
|
||||
|
||||
.done {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.datasource-drawer-fullscreen {
|
||||
.ed-drawer__body {
|
||||
padding: 0;
|
||||
}
|
||||
.is-process .ed-step__line {
|
||||
background-color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
.confirm-no_icon {
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
.tip {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,665 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { datasourceApi } from '@/api/datasource'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import { encrypted, decrypted } from './js/aes'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import type { FormInstance, FormRules } from 'element-plus-secondary'
|
||||
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
import FixedSizeList from 'element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs'
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
import { useCache } from '@/utils/useCache'
|
||||
import { dsType, haveSchema } from '@/views/ds/js/ds-type'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
activeName: string
|
||||
activeStep: number
|
||||
}>(),
|
||||
{
|
||||
activeName: '',
|
||||
activeStep: 0,
|
||||
}
|
||||
)
|
||||
|
||||
const { wsCache } = useCache()
|
||||
const dsFormRef = ref<FormInstance>()
|
||||
const emit = defineEmits(['refresh', 'submit', 'changeActiveStep'])
|
||||
const isCreate = ref(true)
|
||||
const isEditTable = ref(false)
|
||||
const checkList = ref<any>([])
|
||||
const tableList = ref<any>([])
|
||||
const excelUploadSuccess = ref(false)
|
||||
const tableListLoading = ref(false)
|
||||
const token = wsCache.get('user.token')
|
||||
const headers = ref<any>({ 'X-SQLBOT-TOKEN': `Bearer ${token}` })
|
||||
const dialogTitle = ref('')
|
||||
const getUploadURL = import.meta.env.VITE_API_BASE_URL + '/datasource/uploadExcel'
|
||||
const saveLoading = ref<boolean>(false)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
name: [
|
||||
{ required: true, message: t('ds.form.validate.name_required'), trigger: 'blur' },
|
||||
{ min: 1, max: 50, message: t('ds.form.validate.name_length'), trigger: 'blur' },
|
||||
],
|
||||
type: [{ required: true, message: t('ds.form.validate.type_required'), trigger: 'change' }],
|
||||
host: [{ required: true, message: 'Please input host', trigger: 'blur' }],
|
||||
port: [{ required: true, message: 'Please input port', trigger: 'blur' }],
|
||||
database: [{ required: true, message: 'Please input database', trigger: 'blur' }],
|
||||
mode: [{ required: true, message: 'Please choose mode', trigger: 'change' }],
|
||||
dbSchema: [{ required: true, message: 'Please input schema', trigger: 'blur' }],
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const form = ref<any>({
|
||||
name: '',
|
||||
description: '',
|
||||
type: 'mysql',
|
||||
configuration: '',
|
||||
driver: '',
|
||||
host: '',
|
||||
port: 0,
|
||||
username: '',
|
||||
password: '',
|
||||
database: '',
|
||||
extraJdbc: '',
|
||||
dbSchema: '',
|
||||
filename: '',
|
||||
sheets: [],
|
||||
mode: 'service_name',
|
||||
timeout: 30,
|
||||
})
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
isCreate.value = true
|
||||
emit('changeActiveStep', 0)
|
||||
isEditTable.value = false
|
||||
checkList.value = []
|
||||
tableList.value = []
|
||||
excelUploadSuccess.value = false
|
||||
saveLoading.value = false
|
||||
}
|
||||
|
||||
const initForm = (item: any, editTable: boolean = false) => {
|
||||
isEditTable.value = false
|
||||
dsFormRef.value!.clearValidate()
|
||||
if (item) {
|
||||
dialogTitle.value = editTable ? t('ds.form.title.choose_tables') : t('ds.form.title.edit')
|
||||
isCreate.value = false
|
||||
form.value.id = item.id
|
||||
form.value.name = item.name
|
||||
form.value.description = item.description
|
||||
form.value.type = item.type
|
||||
form.value.configuration = item.configuration
|
||||
if (item.configuration) {
|
||||
const configuration = JSON.parse(decrypted(item.configuration))
|
||||
form.value.host = configuration.host
|
||||
form.value.port = configuration.port
|
||||
form.value.username = configuration.username
|
||||
form.value.password = configuration.password
|
||||
form.value.database = configuration.database
|
||||
form.value.extraJdbc = configuration.extraJdbc
|
||||
form.value.dbSchema = configuration.dbSchema
|
||||
form.value.filename = configuration.filename
|
||||
form.value.sheets = configuration.sheets
|
||||
form.value.mode = configuration.mode
|
||||
form.value.timeout = configuration.timeout ? configuration.timeout : 30
|
||||
}
|
||||
|
||||
if (editTable) {
|
||||
dialogTitle.value = t('ds.form.choose_tables')
|
||||
emit('changeActiveStep', 2)
|
||||
isEditTable.value = true
|
||||
isCreate.value = false
|
||||
// request tables and check tables
|
||||
|
||||
datasourceApi.tableList(item.id).then((res: any) => {
|
||||
checkList.value = res.map((ele: any) => {
|
||||
return ele.table_name
|
||||
})
|
||||
if (item.type === 'excel') {
|
||||
tableList.value = form.value.sheets
|
||||
nextTick(() => {
|
||||
handleCheckedTablesChange([...checkList.value])
|
||||
})
|
||||
} else {
|
||||
tableListLoading.value = true
|
||||
const requestObj = buildConf()
|
||||
datasourceApi
|
||||
.getTablesByConf(requestObj)
|
||||
.then((table) => {
|
||||
tableList.value = table
|
||||
checkList.value = checkList.value.filter((ele: string) => {
|
||||
return table
|
||||
.map((ele: any) => {
|
||||
return ele.tableName
|
||||
})
|
||||
.includes(ele)
|
||||
})
|
||||
nextTick(() => {
|
||||
handleCheckedTablesChange([...checkList.value])
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
tableListLoading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
dialogTitle.value = t('ds.form.title.add')
|
||||
isCreate.value = true
|
||||
isEditTable.value = false
|
||||
checkList.value = []
|
||||
tableList.value = []
|
||||
form.value = {
|
||||
name: '',
|
||||
description: '',
|
||||
type: 'mysql',
|
||||
configuration: '',
|
||||
driver: '',
|
||||
host: '',
|
||||
port: 0,
|
||||
username: '',
|
||||
password: '',
|
||||
database: '',
|
||||
extraJdbc: '',
|
||||
dbSchema: '',
|
||||
filename: '',
|
||||
sheets: [],
|
||||
mode: 'service_name',
|
||||
timeout: 30,
|
||||
}
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const save = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
saveLoading.value = true
|
||||
const list = tableList.value
|
||||
.filter((ele: any) => {
|
||||
return checkList.value.includes(ele.tableName)
|
||||
})
|
||||
.map((ele: any) => {
|
||||
return { table_name: ele.tableName, table_comment: ele.tableComment }
|
||||
})
|
||||
|
||||
const requestObj = buildConf()
|
||||
if (form.value.id) {
|
||||
if (!isEditTable.value) {
|
||||
// only update datasource config info
|
||||
datasourceApi.update(requestObj).then(() => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
} else {
|
||||
// save table and field
|
||||
datasourceApi.chooseTables(form.value.id, list).then(() => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
}
|
||||
} else {
|
||||
requestObj.tables = list
|
||||
datasourceApi.add(requestObj).then(() => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const buildConf = () => {
|
||||
form.value.configuration = encrypted(
|
||||
JSON.stringify({
|
||||
host: form.value.host,
|
||||
port: form.value.port,
|
||||
username: form.value.username,
|
||||
password: form.value.password,
|
||||
database: form.value.database,
|
||||
extraJdbc: form.value.extraJdbc,
|
||||
dbSchema: form.value.dbSchema,
|
||||
filename: form.value.filename,
|
||||
sheets: form.value.sheets,
|
||||
mode: form.value.mode,
|
||||
timeout: form.value.timeout,
|
||||
})
|
||||
)
|
||||
const obj = JSON.parse(JSON.stringify(form.value))
|
||||
delete obj.driver
|
||||
delete obj.host
|
||||
delete obj.port
|
||||
delete obj.username
|
||||
delete obj.password
|
||||
delete obj.database
|
||||
delete obj.extraJdbc
|
||||
delete obj.dbSchema
|
||||
delete obj.filename
|
||||
delete obj.sheets
|
||||
delete obj.mode
|
||||
delete obj.timeout
|
||||
return obj
|
||||
}
|
||||
|
||||
const check = () => {
|
||||
const requestObj = buildConf()
|
||||
datasourceApi.check(requestObj).then((res: any) => {
|
||||
if (res) {
|
||||
ElMessage({
|
||||
message: t('ds.form.connect.success'),
|
||||
type: 'success',
|
||||
showClose: true,
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
message: t('ds.form.connect.failed'),
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const next = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (form.value.type === 'excel') {
|
||||
// next, show tables
|
||||
if (excelUploadSuccess.value) {
|
||||
emit('changeActiveStep', props.activeStep + 1)
|
||||
}
|
||||
} else {
|
||||
// check status if success do next
|
||||
const requestObj = buildConf()
|
||||
datasourceApi.check(requestObj).then((res: boolean) => {
|
||||
if (res) {
|
||||
emit('changeActiveStep', props.activeStep + 1)
|
||||
// request tables
|
||||
datasourceApi.getTablesByConf(requestObj).then((res: any) => {
|
||||
tableList.value = res
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
message: t('ds.form.connect.failed'),
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const preview = () => {
|
||||
emit('changeActiveStep', props.activeStep - 1)
|
||||
}
|
||||
|
||||
const beforeUpload = (rawFile: any) => {
|
||||
if (rawFile.size / 1024 / 1024 > 50) {
|
||||
ElMessage.error('File size can not exceed 50MB!')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const onSuccess = (response: any) => {
|
||||
form.value.filename = response.data.filename
|
||||
form.value.sheets = response.data.sheets
|
||||
tableList.value = response.data.sheets
|
||||
excelUploadSuccess.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
dsFormRef.value!.clearValidate()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const submitModle = () => {
|
||||
dsFormRef.value!.validate((res: any) => {
|
||||
if (res) {
|
||||
emit('submit')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const keywords = ref('')
|
||||
const tableListWithSearch = computed(() => {
|
||||
if (!keywords.value) return tableList.value
|
||||
return tableList.value.filter((ele: any) =>
|
||||
ele.tableName.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
watch(keywords, () => {
|
||||
const tableNameArr = tableListWithSearch.value.map((ele: any) => ele.tableName)
|
||||
checkList.value = checkTableList.filter((ele) => tableNameArr.includes(ele))
|
||||
const checkedCount = checkList.value.length
|
||||
checkAll.value = checkedCount === tableListWithSearch.value.length
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < tableListWithSearch.value.length
|
||||
})
|
||||
|
||||
const checkAll = ref(false)
|
||||
const isIndeterminate = ref(false)
|
||||
let checkTableList = [] as any[]
|
||||
|
||||
const handleCheckAllChange = (val: any) => {
|
||||
checkList.value = val
|
||||
? [
|
||||
...new Set([
|
||||
...tableListWithSearch.value.map((ele: any) => ele.tableName),
|
||||
...checkList.value,
|
||||
]),
|
||||
]
|
||||
: []
|
||||
isIndeterminate.value = false
|
||||
const tableNameArr = tableListWithSearch.value.map((ele: any) => ele.tableName)
|
||||
checkTableList = val
|
||||
? [...new Set([...tableNameArr, ...checkTableList])]
|
||||
: checkTableList.filter((ele) => !tableNameArr.includes(ele))
|
||||
}
|
||||
|
||||
const handleCheckedTablesChange = (value: any[]) => {
|
||||
const checkedCount = value.length
|
||||
checkAll.value = checkedCount === tableListWithSearch.value.length
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < tableListWithSearch.value.length
|
||||
const tableNameArr = tableListWithSearch.value.map((ele: any) => ele.tableName)
|
||||
checkTableList = [
|
||||
...new Set([...checkTableList.filter((ele) => !tableNameArr.includes(ele)), ...value]),
|
||||
]
|
||||
}
|
||||
|
||||
const tableListSave = () => {
|
||||
save(dsFormRef.value)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
initForm,
|
||||
submitModle,
|
||||
tableListSave,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-form" :class="(!isCreate || activeStep === 2) && 'edit-form'">
|
||||
<div v-if="isCreate && activeStep !== 2" class="model-name">{{ activeName }}</div>
|
||||
|
||||
<div class="form-content">
|
||||
<el-form
|
||||
v-show="activeStep === 1"
|
||||
ref="dsFormRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
label-width="auto"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item :label="t('ds.form.name')" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.description')">
|
||||
<el-input v-model="form.description" :rows="2" type="textarea" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.type')" prop="type">
|
||||
<el-select v-model="form.type" placeholder="Select Type" :disabled="!isCreate">
|
||||
<el-option
|
||||
v-for="item in dsType"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div v-if="form.type === 'excel'">
|
||||
<el-form-item label="File">
|
||||
<el-upload
|
||||
:disabled="!isCreate"
|
||||
accept=".xls, .xlsx, .csv"
|
||||
:headers="headers"
|
||||
:action="getUploadURL"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="onSuccess"
|
||||
>
|
||||
<el-button :disabled="!isCreate">{{ t('ds.form.upload.button') }}</el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">{{ t('ds.form.upload.tip') }}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-form-item :label="t('ds.form.host')" prop="host">
|
||||
<el-input v-model="form.host" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.port')" prop="port">
|
||||
<el-input v-model="form.port" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.username')">
|
||||
<el-input v-model="form.username" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.password')">
|
||||
<el-input v-model="form.password" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.database')" prop="database">
|
||||
<el-input v-model="form.database" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.type === 'oracle'"
|
||||
:label="t('ds.form.connect_mode')"
|
||||
prop="mode"
|
||||
>
|
||||
<el-radio-group v-model="form.mode">
|
||||
<el-radio value="service_name">{{ t('ds.form.mode.service_name') }}</el-radio>
|
||||
<el-radio value="sid">{{ t('ds.form.mode.sid') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.extra_jdbc')">
|
||||
<el-input v-model="form.extraJdbc" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="haveSchema.includes(form.type)"
|
||||
:label="t('ds.form.schema')"
|
||||
prop="dbSchema"
|
||||
>
|
||||
<el-input v-model="form.dbSchema" />
|
||||
<el-button v-if="false" link type="primary" :icon="Plus">Get Schema</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('ds.form.timeout')" prop="timeout">
|
||||
<el-input-number v-model="form.timeout" :min="0" :max="300" controls-position="right" />
|
||||
</el-form-item>
|
||||
<span>
|
||||
<span>{{ t('ds.form.support_version') }}: </span>
|
||||
<span v-if="form.type === 'sqlServer'">2012+</span>
|
||||
<span v-else-if="form.type === 'oracle'">12+</span>
|
||||
<span v-else-if="form.type === 'mysql'">5.6+</span>
|
||||
<span v-else-if="form.type === 'pg'">9.6+</span>
|
||||
</span>
|
||||
</div>
|
||||
</el-form>
|
||||
<div v-show="activeStep === 2" v-loading="tableListLoading" class="select-data_table">
|
||||
<div class="title">
|
||||
{{ $t('ds.form.choose_tables') }} ({{ checkList.length }}/ {{ tableList.length }})
|
||||
</div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 100%; margin-bottom: 16px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="container">
|
||||
<div class="select-all">
|
||||
<el-checkbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAllChange"
|
||||
>
|
||||
{{ t('datasource.select_all') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !tableListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<el-checkbox-group
|
||||
v-else
|
||||
v-model="checkList"
|
||||
style="position: relative"
|
||||
@change="handleCheckedTablesChange"
|
||||
>
|
||||
<FixedSizeList
|
||||
:item-size="32"
|
||||
:data="tableListWithSearch"
|
||||
:total="tableListWithSearch.length"
|
||||
:width="800"
|
||||
:height="460"
|
||||
:scrollbar-always-on="true"
|
||||
class-name="ed-select-dropdown__list"
|
||||
layout="vertical"
|
||||
>
|
||||
<template #default="{ index, style }">
|
||||
<div class="list-item_primary" :style="style">
|
||||
<el-checkbox :label="tableListWithSearch[index].tableName">
|
||||
<el-icon size="16" style="margin-right: 8px">
|
||||
<icon_form_outlined></icon_form_outlined>
|
||||
</el-icon>
|
||||
{{ tableListWithSearch[index].tableName }}</el-checkbox
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</FixedSizeList>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draw-foot">
|
||||
<el-button @click="close">{{ t('common.cancel') }}</el-button>
|
||||
<el-button v-show="!isCreate && !isEditTable && form.type !== 'excel'" @click="check">
|
||||
{{ t('ds.test_connection') }}
|
||||
</el-button>
|
||||
<el-button v-show="activeStep === 1 && isCreate" type="primary" @click="next(dsFormRef)">
|
||||
{{ t('common.next') }}
|
||||
</el-button>
|
||||
<el-button v-show="activeStep === 2 && isCreate" @click="preview">
|
||||
{{ t('ds.previous') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-show="activeStep === 2 || !isCreate"
|
||||
:loading="saveLoading"
|
||||
type="primary"
|
||||
@click="save(dsFormRef)"
|
||||
>
|
||||
{{ t('common.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-form {
|
||||
width: calc(100% - 280px);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 56px;
|
||||
height: 100%;
|
||||
padding-bottom: 120px;
|
||||
overflow-y: auto;
|
||||
.model-name {
|
||||
height: 56px;
|
||||
width: 100%;
|
||||
padding-left: 24px;
|
||||
border-bottom: 1px solid #1f232926;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
padding-top: 24px;
|
||||
|
||||
.ed-form-item--default {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.is-error {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.draw-foot) {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: calc(100% - 280px);
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
border-top: 1px solid #1f232926;
|
||||
padding-right: 24px;
|
||||
background-color: #fff;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&.edit-form {
|
||||
width: 100%;
|
||||
|
||||
:deep(.draw-foot) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.select-data_table {
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
.container {
|
||||
height: 524px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #dee0e3;
|
||||
border-radius: 4px;
|
||||
|
||||
.select-all {
|
||||
background: #f5f6f7;
|
||||
height: 40px;
|
||||
padding-left: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #dee0e3;
|
||||
}
|
||||
|
||||
:deep(.ed-checkbox__label) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ed-vl__window) {
|
||||
scrollbar-width: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, computed } from 'vue'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import { dsTypeWithImg } from './js/ds-type'
|
||||
|
||||
interface Datasource {
|
||||
name: string
|
||||
type: string
|
||||
img: string
|
||||
rate?: string
|
||||
id?: string
|
||||
}
|
||||
const keywords = ref('')
|
||||
const modelList = shallowRef(dsTypeWithImg as Datasource[])
|
||||
const modelListWithSearch = computed(() => {
|
||||
if (!keywords.value) return modelList.value
|
||||
return modelList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const emits = defineEmits(['clickDatasource'])
|
||||
const handleModelClick = (item: any) => {
|
||||
emits('clickDatasource', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-list">
|
||||
<div class="title">{{ $t('qa.select_datasource') }}</div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 100%; margin-right: 12px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="list-content">
|
||||
<div
|
||||
v-for="ele in modelListWithSearch"
|
||||
:key="ele.name"
|
||||
class="model"
|
||||
@click="handleModelClick(ele)"
|
||||
>
|
||||
<img width="32px" height="32px" :src="ele.img" />
|
||||
<span class="name">{{ ele.name }}</span>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !modelListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
style="width: 100%; margin-top: 100px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-list {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
max-height: 100%;
|
||||
padding-top: 24px;
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
height: calc(100% - 40px);
|
||||
flex-wrap: wrap;
|
||||
|
||||
.model:nth-child(odd) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.model {
|
||||
width: 392px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 16px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #dee0e3;
|
||||
border-radius: 4px;
|
||||
margin-left: 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 24px 0px #1f232914;
|
||||
}
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, shallowRef, computed } from 'vue'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import { dsTypeWithImg } from './js/ds-type'
|
||||
|
||||
interface Datasource {
|
||||
name: string
|
||||
type: string
|
||||
img: string
|
||||
rate?: string
|
||||
id?: string
|
||||
}
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
activeName: string
|
||||
}>(),
|
||||
{
|
||||
activeName: '',
|
||||
}
|
||||
)
|
||||
const keywords = ref('')
|
||||
const modelList = shallowRef(dsTypeWithImg as Datasource[])
|
||||
const modelListWithSearch = computed(() => {
|
||||
if (!keywords.value) return modelList.value
|
||||
return modelList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const emits = defineEmits(['clickDatasource'])
|
||||
const handleModelClick = (item: any) => {
|
||||
emits('clickDatasource', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-list_side">
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 232px; margin: 16px 0 8px 24px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="list-content">
|
||||
<div
|
||||
v-for="ele in modelListWithSearch"
|
||||
:key="ele.name"
|
||||
class="model"
|
||||
:class="activeName === ele.name && 'isActive'"
|
||||
@click="handleModelClick(ele)"
|
||||
>
|
||||
<img width="20px" height="20px" :src="ele.img" />
|
||||
<span class="name">{{ ele.name }}</span>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !modelListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
style="width: 100%; margin-top: 100px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-list_side {
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #1f232926;
|
||||
|
||||
.list-content {
|
||||
height: calc(100% - 56px);
|
||||
padding: 0 16px;
|
||||
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.name {
|
||||
margin-left: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
&:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
background: #1cba901a;
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref } from 'vue'
|
||||
import DatasourceForm from './DatasourceForm.vue'
|
||||
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
|
||||
|
||||
const datasourceFormRef = ref()
|
||||
const datasourceConfigvVisible = ref(false)
|
||||
const beforeClose = () => {
|
||||
datasourceConfigvVisible.value = false
|
||||
}
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
const changeActiveStep = (val: any) => {
|
||||
if (val === 0) {
|
||||
datasourceConfigvVisible.value = false
|
||||
}
|
||||
}
|
||||
const save = () => {
|
||||
datasourceFormRef.value.tableListSave()
|
||||
}
|
||||
|
||||
const open = (item: any) => {
|
||||
datasourceConfigvVisible.value = true
|
||||
nextTick(() => {
|
||||
datasourceFormRef.value.initForm(item, true)
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="datasourceConfigvVisible"
|
||||
:close-on-click-modal="false"
|
||||
size="calc(100% - 100px)"
|
||||
modal-class="datasource-drawer-fullscreen"
|
||||
direction="btt"
|
||||
:before-close="beforeClose"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<span style="white-space: nowrap">{{ $t('ds.form.choose_tables') }}</span>
|
||||
<el-icon style="cursor: pointer" @click="close">
|
||||
<icon_close_outlined></icon_close_outlined>
|
||||
</el-icon>
|
||||
</template>
|
||||
<DatasourceForm
|
||||
ref="datasourceFormRef"
|
||||
:active-step="2"
|
||||
active-name=""
|
||||
@change-active-step="changeActiveStep"
|
||||
@refresh="refresh"
|
||||
></DatasourceForm>
|
||||
<template #footer>
|
||||
<el-button secondary @click="beforeClose"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="save"> {{ $t('common.save') }} </el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -105,7 +105,7 @@
|
||||
@closed="closeTable"
|
||||
>
|
||||
<div>{{ t('ds.edit.table_comment_label') }}</div>
|
||||
<el-input v-model="tableComment" />
|
||||
<el-input v-model="tableComment" :rows="3" type="textarea" />
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeTable">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveTable">{{ t('common.confirm') }}</el-button>
|
||||
@@ -121,7 +121,7 @@
|
||||
@closed="closeField"
|
||||
>
|
||||
<div>{{ t('ds.edit.field_comment_label') }}</div>
|
||||
<el-input v-model="fieldComment" />
|
||||
<el-input v-model="fieldComment" :rows="3" type="textarea" />
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeField">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveField">{{ t('common.confirm') }}</el-button>
|
||||
|
||||
@@ -332,8 +332,7 @@ const save = async (formEl: FormInstance | undefined) => {
|
||||
if (form.value.id) {
|
||||
if (!isEditTable.value) {
|
||||
// only update datasource config info
|
||||
datasourceApi.update(requestObj).then((res) => {
|
||||
console.log(res)
|
||||
datasourceApi.update(requestObj).then(() => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
@@ -346,8 +345,7 @@ const save = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
} else {
|
||||
requestObj.tables = list
|
||||
datasourceApi.add(requestObj).then((res: any) => {
|
||||
console.log(res)
|
||||
datasourceApi.add(requestObj).then(() => {
|
||||
close()
|
||||
emit('refresh')
|
||||
})
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import mysql_ds from '@/assets/datasource/icon_mysql.png'
|
||||
import excel from '@/assets/datasource/icon_excel.png'
|
||||
import oracle from '@/assets/datasource/icon_oracle.png'
|
||||
import pg from '@/assets/datasource/icon_PostgreSQL.png'
|
||||
import sqlServer from '@/assets/datasource/icon_SQL_Server.png'
|
||||
|
||||
export const dsType = [
|
||||
{ label: 'MySQL', value: 'mysql' },
|
||||
{ label: 'PostgreSQL', value: 'pg' },
|
||||
@@ -6,4 +12,12 @@ export const dsType = [
|
||||
{ label: 'Excel/CSV', value: 'excel' },
|
||||
]
|
||||
|
||||
export const dsTypeWithImg = [
|
||||
{ name: 'MySQL', type: 'mysql', img: mysql_ds },
|
||||
{ name: 'PostgreSQL', type: 'pg', img: pg },
|
||||
{ name: 'Microsoft SQL Server', type: 'sqlServer', img: sqlServer },
|
||||
{ name: 'Oracle', type: 'oracle', img: oracle },
|
||||
{ name: '本地 Excel/CSV', type: 'excel', img: excel },
|
||||
]
|
||||
|
||||
export const haveSchema = ['sqlServer', 'pg', 'oracle']
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
<script lang="ts" setup>
|
||||
import delIcon from '@/assets/svg/icon_delete.svg'
|
||||
import edit from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import { get_supplier } from '@/entity/supplier'
|
||||
import { computed } from 'vue'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string
|
||||
modleType: string
|
||||
baseModle: string
|
||||
id?: string
|
||||
isDefault?: boolean
|
||||
supplier?: number
|
||||
}>(),
|
||||
{
|
||||
name: '-',
|
||||
modleType: '-',
|
||||
baseModle: '-',
|
||||
id: '-',
|
||||
isDefault: false,
|
||||
supplier: 0,
|
||||
}
|
||||
)
|
||||
const current_supplier = computed(() => {
|
||||
if (!props.supplier) {
|
||||
return null
|
||||
}
|
||||
return get_supplier(props.supplier)
|
||||
})
|
||||
const emits = defineEmits(['edit', 'del'])
|
||||
|
||||
const handleEdit = () => {
|
||||
emits('edit', props.id)
|
||||
}
|
||||
|
||||
const handleDel = () => {
|
||||
emits('del', { id: props.id, name: props.name, default_model: props.isDefault })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="name-icon">
|
||||
<img :src="current_supplier?.icon" width="32px" height="32px" />
|
||||
<span class="name">{{ name }}</span>
|
||||
<span v-if="isDefault" class="default">{{ $t('model.default_model') }}</span>
|
||||
</div>
|
||||
<div class="type-value">
|
||||
<span class="type">{{ $t('model.model_type') }}</span>
|
||||
<span class="value"> {{ modleType }}</span>
|
||||
</div>
|
||||
<div class="type-value">
|
||||
<span class="type">{{ $t('model.basic_model') }}</span>
|
||||
<span class="value"> {{ baseModle }}</span>
|
||||
</div>
|
||||
<div class="methods">
|
||||
<el-tooltip :offset="14" effect="dark" :content="$t('datasource.edit')" placement="top">
|
||||
<el-icon size="16" @click="handleEdit">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
<template v-if="!isDefault">
|
||||
<span class="divide"></span>
|
||||
<el-tooltip :offset="14" effect="dark" :content="$t('dashboard.delete')" placement="top">
|
||||
<el-icon size="16" @click="handleDel">
|
||||
<delIcon></delIcon>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.card {
|
||||
width: 404px;
|
||||
height: 160px;
|
||||
border: 1px solid #dee0e3;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
margin: 0 16px 16px 0;
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 24px 0px #1f232914;
|
||||
}
|
||||
|
||||
.name-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.default {
|
||||
margin-left: auto;
|
||||
background: #1cba9033;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
color: #189e7a;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.type-value {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
.type {
|
||||
color: #646a73;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.methods {
|
||||
margin-top: 16px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
display: none;
|
||||
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.divide {
|
||||
height: 14px;
|
||||
width: 1px;
|
||||
background-color: #1f232926;
|
||||
margin: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.methods {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,439 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, shallowRef, reactive, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import icon_admin_outlined from '@/assets/svg/icon_admin_outlined.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
|
||||
import ModelList from './ModelList.vue'
|
||||
import ModelListSide from './ModelListSide.vue'
|
||||
import ModelForm from './ModelForm.vue'
|
||||
import { modelApi } from '@/api/system'
|
||||
import Card from './Card.vue'
|
||||
import { getModelTypeName } from '@/entity/CommonEntity.ts'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { get_supplier } from '@/entity/supplier'
|
||||
|
||||
interface Model {
|
||||
name: string
|
||||
model_type: string
|
||||
base_model: string
|
||||
id?: string
|
||||
default_model: boolean
|
||||
supplier: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
const keywords = ref('')
|
||||
const defaultModelKeywords = ref('')
|
||||
const modelConfigvVisible = ref(false)
|
||||
const editModel = ref(false)
|
||||
const activeStep = ref(0)
|
||||
const activeName = ref('')
|
||||
const modelFormRef = ref()
|
||||
|
||||
reactive({
|
||||
form: {
|
||||
id: '',
|
||||
name: '',
|
||||
model_type: 0,
|
||||
api_key: '',
|
||||
api_domain: '',
|
||||
},
|
||||
selectedIds: [],
|
||||
})
|
||||
const modelList = shallowRef([] as Model[])
|
||||
|
||||
const modelListWithSearch = computed(() => {
|
||||
if (!keywords.value) return modelList.value
|
||||
return modelList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const beforeClose = () => {
|
||||
modelConfigvVisible.value = false
|
||||
}
|
||||
const defaultModelListWithSearch = computed(() => {
|
||||
let tempModelList = modelList.value
|
||||
if (defaultModelKeywords.value) {
|
||||
tempModelList = tempModelList.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(defaultModelKeywords.value.toLowerCase())
|
||||
)
|
||||
}
|
||||
return tempModelList.map((item: any) => {
|
||||
item['supplier_item'] = get_supplier(item.supplier)
|
||||
return item
|
||||
})
|
||||
})
|
||||
|
||||
const handleDefaultModelChange = (item: any) => {
|
||||
ElMessageBox.confirm(`是否设置 ${item.name} 为系统默认模型?`, {
|
||||
confirmButtonType: 'primary',
|
||||
tip: '系统默认模型被替换后,智能问数的结果将会受到影响,请谨慎操作。',
|
||||
confirmButtonText: t('datasource.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
callback: (val: string) => {
|
||||
if (val === 'confirm') {
|
||||
modelList.value.forEach((ele: any) => {
|
||||
ele.default_model = ele.id === item.id
|
||||
})
|
||||
ElMessage.success('设置成功')
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const formatKeywords = (item: string) => {
|
||||
if (!defaultModelKeywords.value) return item
|
||||
return item.replaceAll(
|
||||
defaultModelKeywords.value,
|
||||
`<span class="isSearch">${defaultModelKeywords.value}</span>`
|
||||
)
|
||||
}
|
||||
const handleAddModel = () => {
|
||||
activeStep.value = 0
|
||||
modelConfigvVisible.value = true
|
||||
}
|
||||
const handleEditModel = (id: any) => {
|
||||
activeStep.value = 1
|
||||
modelApi.query(id).then((res: any) => {
|
||||
modelConfigvVisible.value = true
|
||||
nextTick(() => {
|
||||
modelFormRef.value.initForm({ ...res })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const deleteHandler = (item: any) => {
|
||||
if (item.default_model) {
|
||||
ElMessageBox.confirm(`无法删除模型:${item.name}?`, {
|
||||
confirmButtonType: 'primary',
|
||||
tip: '该模型为系统默认模型,请先设置其他模型为系统默认模型,再删除此模型。',
|
||||
showConfirmButton: false,
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '知道了',
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
callback: (val: string) => {
|
||||
console.log(val)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
ElMessageBox.confirm(`是否删除模型:${item.name}?`, {
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: '删除',
|
||||
cancelButtonText: '取消',
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
callback: (value: string) => {
|
||||
if (value === 'confirm') {
|
||||
modelApi.delete(item.id).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功',
|
||||
})
|
||||
search()
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const clickModel = (ele: any) => {
|
||||
activeStep.value = 1
|
||||
supplierChang(ele)
|
||||
}
|
||||
|
||||
const supplierChang = (ele: any) => {
|
||||
activeName.value = ele.name
|
||||
nextTick(() => {
|
||||
modelFormRef.value.supplierChang({ ...ele })
|
||||
})
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
beforeClose()
|
||||
}
|
||||
|
||||
const preStep = () => {
|
||||
activeStep.value = 0
|
||||
}
|
||||
|
||||
const saveModel = () => {
|
||||
modelFormRef.value.submitModle()
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
modelApi.queryAll().then((res: any) => {
|
||||
modelList.value = res
|
||||
})
|
||||
}
|
||||
search()
|
||||
|
||||
const submit = (item: any) => {
|
||||
if (!item.id) {
|
||||
modelApi.add(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Add completed',
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
modelApi.edit(item).then(() => {
|
||||
beforeClose()
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Edit completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-config">
|
||||
<div class="model-methods">
|
||||
<span class="title">{{ t('model.ai_model_configuration') }}</span>
|
||||
<div class="button-input">
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 240px; margin-right: 12px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-popover popper-class="system-default_model" placement="bottom">
|
||||
<template #reference>
|
||||
<el-button secondary>
|
||||
<template #icon>
|
||||
<icon_admin_outlined></icon_admin_outlined>
|
||||
</template>
|
||||
{{ t('model.system_default_model_de') }}
|
||||
</el-button></template
|
||||
>
|
||||
<div class="popover">
|
||||
<el-input
|
||||
v-model="defaultModelKeywords"
|
||||
clearable
|
||||
style="width: 100%; margin-right: 12px"
|
||||
:placeholder="t('datasource.search_by_name')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="popover-content">
|
||||
<div
|
||||
v-for="ele in defaultModelListWithSearch"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
:class="ele.default_model && 'isActive'"
|
||||
@click="handleDefaultModelChange(ele)"
|
||||
>
|
||||
<img :src="ele.supplier_item.icon" width="24px" height="24px" />
|
||||
<div class="model-name" v-html="formatKeywords(ele.name)"></div>
|
||||
<el-icon size="16" class="done">
|
||||
<icon_done_outlined></icon_done_outlined>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-if="!defaultModelListWithSearch.length" class="popover-item empty">
|
||||
{{ t('model.relevant_results_found') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<el-button type="primary" @click="handleAddModel">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ t('model.add_model') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !modelListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
/>
|
||||
|
||||
<div v-else class="card-content">
|
||||
<card
|
||||
v-for="ele in modelListWithSearch"
|
||||
:id="ele.id"
|
||||
:key="ele.id"
|
||||
:name="ele.name"
|
||||
:supplier="ele.supplier"
|
||||
:modle-type="getModelTypeName(ele['model_type'])"
|
||||
:base-modle="ele['base_model']"
|
||||
:is-default="ele['default_model']"
|
||||
@edit="handleEditModel"
|
||||
@del="deleteHandler"
|
||||
></card>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="modelConfigvVisible"
|
||||
:close-on-click-modal="false"
|
||||
size="calc(100% - 100px)"
|
||||
modal-class="model-drawer-fullscreen"
|
||||
direction="btt"
|
||||
:before-close="beforeClose"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<span style="white-space: nowrap">{{ t('model.add_model') }}</span>
|
||||
<div v-if="!editModel" class="flex-center" style="width: 100%">
|
||||
<el-steps custom style="max-width: 500px; flex: 1" :active="activeStep" align-center>
|
||||
<el-step>
|
||||
<template #title> {{ t('model.select_supplier') }} </template>
|
||||
</el-step>
|
||||
<el-step>
|
||||
<template #title> {{ t('model.add_model') }} </template>
|
||||
</el-step>
|
||||
</el-steps>
|
||||
</div>
|
||||
<el-icon style="cursor: pointer" @click="close">
|
||||
<icon_close_outlined></icon_close_outlined>
|
||||
</el-icon>
|
||||
</template>
|
||||
<ModelList v-if="activeStep === 0" @click-model="clickModel"></ModelList>
|
||||
<ModelListSide
|
||||
v-if="activeStep === 1"
|
||||
:active-name="activeName"
|
||||
@click-model="supplierChang"
|
||||
></ModelListSide>
|
||||
<ModelForm
|
||||
v-if="activeStep === 1"
|
||||
ref="modelFormRef"
|
||||
:active-name="activeName"
|
||||
@submit="submit"
|
||||
></ModelForm>
|
||||
<template #footer>
|
||||
<el-button secondary @click="cancel"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button secondary @click="preStep"> {{ $t('ds.previous') }} </el-button>
|
||||
<el-button type="primary" @click="saveModel"> {{ $t('common.save') }} </el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-config {
|
||||
height: calc(100% - 16px);
|
||||
.model-methods {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.system-default_model.system-default_model {
|
||||
padding: 4px 0;
|
||||
width: 325px !important;
|
||||
box-shadow: 0px 4px 8px 0px #1f23291a;
|
||||
border: 1px solid #dee0e3;
|
||||
.ed-input {
|
||||
.ed-input__wrapper {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
border-bottom: 1px solid #1f232926;
|
||||
}
|
||||
|
||||
.popover {
|
||||
.popover-content {
|
||||
padding: 4px;
|
||||
}
|
||||
.popover-item {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
margin-bottom: 2px;
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:not(.empty):hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.empty {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #8f959e;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.model-name {
|
||||
margin-left: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.done {
|
||||
margin-left: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isSearch {
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
color: var(--ed-color-primary);
|
||||
|
||||
.done {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-drawer-fullscreen {
|
||||
.ed-drawer__body {
|
||||
padding: 0;
|
||||
}
|
||||
.is-process .ed-step__line {
|
||||
background-color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
.confirm-no_icon {
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
.tip {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,390 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, nextTick, computed } from 'vue'
|
||||
import arrow_down from '@/assets/svg/arrow-down.svg'
|
||||
import dashboard_info from '@/assets/svg/dashboard-info.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import icon_delete from '@/assets/svg/icon_delete.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import ParamsForm from './ParamsForm.vue'
|
||||
import { modelTypeOptions } from '@/entity/CommonEntity.ts'
|
||||
import { base_model_options } from '@/entity/supplier'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
activeName: string
|
||||
}>(),
|
||||
{
|
||||
activeName: '',
|
||||
}
|
||||
)
|
||||
|
||||
interface ParamsFormData {
|
||||
key?: string
|
||||
val?: string
|
||||
name?: string
|
||||
id?: string
|
||||
}
|
||||
const { t } = useI18n()
|
||||
|
||||
const modelForm = reactive({
|
||||
id: '',
|
||||
supplier: 0,
|
||||
name: '',
|
||||
model_type: 0,
|
||||
base_model: '',
|
||||
api_key: '',
|
||||
api_domain: '',
|
||||
config_list: [],
|
||||
})
|
||||
let isCreate = false
|
||||
const modelRef = ref()
|
||||
const paramsFormRef = ref()
|
||||
const advancedSetting = ref([] as ParamsFormData[])
|
||||
const paramsFormDrawer = ref(false)
|
||||
const configExpand = ref(false)
|
||||
|
||||
const modelList = computed(() => {
|
||||
if (!modelForm.supplier) {
|
||||
return []
|
||||
}
|
||||
return base_model_options(modelForm.supplier, modelForm.model_type)
|
||||
})
|
||||
const handleParamsEdite = (ele?: any) => {
|
||||
isCreate = false
|
||||
paramsFormDrawer.value = true
|
||||
nextTick(() => {
|
||||
paramsFormRef.value.initForm(ele)
|
||||
})
|
||||
}
|
||||
|
||||
const handleParamsCreate = () => {
|
||||
isCreate = true
|
||||
paramsFormDrawer.value = true
|
||||
nextTick(() => {
|
||||
paramsFormRef.value.initForm()
|
||||
})
|
||||
}
|
||||
|
||||
const handleParamsDel = (item: any) => {
|
||||
advancedSetting.value = advancedSetting.value.filter((ele) => ele.key !== item.key)
|
||||
}
|
||||
|
||||
const rules = {
|
||||
model_type: [
|
||||
{
|
||||
required: true,
|
||||
message: 'type',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
api_domain: [{ required: true, message: 'api_domain', trigger: 'blur' }],
|
||||
base_model: [{ required: true, message: t('model.the_basic_model_de'), trigger: 'blur' }],
|
||||
name: [{ required: true, message: t('model.the_basic_model'), trigger: 'blur' }],
|
||||
api_key: [{ required: true, message: 'api_key', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
modelRef.value.clearValidate()
|
||||
}, 100)
|
||||
})
|
||||
|
||||
const addParams = () => {
|
||||
paramsFormRef.value.submit()
|
||||
}
|
||||
|
||||
const submit = (item: any) => {
|
||||
if (isCreate) {
|
||||
advancedSetting.value.push({ ...item })
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
for (const key in advancedSetting.value) {
|
||||
const element = advancedSetting.value[key]
|
||||
if (element.id === item.id) {
|
||||
Object.assign(element, { ...item })
|
||||
}
|
||||
}
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
const cancel = () => {
|
||||
paramsFormDrawer.value = false
|
||||
}
|
||||
const supplierChang = (supplier: any) => {
|
||||
modelForm.supplier = supplier.id
|
||||
const config = supplier.model_config[modelForm.model_type || 0]
|
||||
modelForm.api_domain = config.api_domain
|
||||
if (!modelForm.id) {
|
||||
modelForm.name = supplier.name
|
||||
}
|
||||
}
|
||||
const initForm = (item?: any) => {
|
||||
modelForm.id = ''
|
||||
modelRef.value.clearValidate()
|
||||
if (item) {
|
||||
Object.assign(modelForm, { ...item })
|
||||
if (item?.config_list?.length) {
|
||||
advancedSetting.value = item.config_list
|
||||
}
|
||||
}
|
||||
}
|
||||
const emits = defineEmits(['submit'])
|
||||
|
||||
const submitModle = () => {
|
||||
modelRef.value.validate((res: any) => {
|
||||
if (res) {
|
||||
emits('submit', {
|
||||
...modelForm,
|
||||
config_list: [...advancedSetting.value],
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
initForm,
|
||||
submitModle,
|
||||
supplierChang,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-form">
|
||||
<div class="model-name">{{ activeName }}</div>
|
||||
<div class="form-content">
|
||||
<el-form
|
||||
ref="modelRef"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
:model="modelForm"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-form-item class="custom-require flex-inline" prop="name">
|
||||
<template #label
|
||||
><span class="custom-require_danger">{{ t('model.model_name') }}</span>
|
||||
<el-tooltip effect="dark" :content="t('model.custom_model_name')" placement="right">
|
||||
<el-icon style="margin-left: 4px" size="16">
|
||||
<dashboard_info></dashboard_info>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-input v-model="modelForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" :label="t('model.model_type')">
|
||||
<el-select v-model="modelForm.model_type" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in modelTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="custom-require" prop="modelName">
|
||||
<template #label
|
||||
><span class="custom-require_danger">{{ t('model.basic_model') }}</span>
|
||||
<span class="enter">{{ t('model.enter_to_add') }}</span>
|
||||
</template>
|
||||
<el-select v-model="modelForm['base_model']" style="width: 100%">
|
||||
<el-option v-for="item in modelList" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item prop="api_domain" :label="t('model.api_domain_name')">
|
||||
<el-input
|
||||
v-model="modelForm.api_domain"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('model.api_domain_name')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="api_key" label="API Key">
|
||||
<el-input
|
||||
v-model="modelForm.api_key"
|
||||
:placeholder="$t('datasource.please_enter') + $t('common.empty') + 'API Key'"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div
|
||||
class="advance-setting"
|
||||
:class="configExpand && 'expand'"
|
||||
@click="configExpand = !configExpand"
|
||||
>
|
||||
{{ t('model.advanced_settings') }}
|
||||
<el-icon size="16">
|
||||
<arrow_down></arrow_down>
|
||||
</el-icon>
|
||||
</div>
|
||||
<div v-if="configExpand" class="model-params">
|
||||
{{ t('model.model_parameters') }}
|
||||
<span class="add" @click="handleParamsCreate">
|
||||
<el-icon size="16">
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</el-icon>
|
||||
{{ t('model.add') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="configExpand" class="params-table">
|
||||
<el-table :data="advancedSetting" style="width: 100%">
|
||||
<el-table-column prop="key" :label="t('model.parameters')" width="280" />
|
||||
<el-table-column prop="name" :label="t('model.display_name')" width="280" />
|
||||
<el-table-column prop="val" :label="t('model.parameter_value')" />
|
||||
<el-table-column
|
||||
fixed="right"
|
||||
width="80"
|
||||
class-name="operation-column_text"
|
||||
:label="$t('ds.actions')"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button text type="primary" @click="handleParamsEdite(scope.row)">
|
||||
<el-icon size="16">
|
||||
<icon_edit_outlined></icon_edit_outlined>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<el-button text type="primary" @click="handleParamsDel(scope.row)">
|
||||
<el-icon size="16">
|
||||
<icon_delete></icon_delete>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="paramsFormDrawer"
|
||||
:size="600"
|
||||
:title="$t('model.add') + $t('common.empty') + $t('model.parameters')"
|
||||
>
|
||||
<ParamsForm ref="paramsFormRef" @submit="submit"></ParamsForm>
|
||||
<template #footer>
|
||||
<el-button secondary @click="cancel"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="addParams"> {{ t('model.add') }} </el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-form {
|
||||
width: calc(100% - 280px);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 56px;
|
||||
height: 100%;
|
||||
.model-name {
|
||||
height: 56px;
|
||||
width: 100%;
|
||||
padding-left: 24px;
|
||||
border-bottom: 1px solid #1f232926;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.form-content {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
padding-top: 24px;
|
||||
|
||||
.ed-form-item--default {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.is-error {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(
|
||||
.custom-require.ed-form-item.is-required:not(.is-no-asterisk).asterisk-right
|
||||
> .ed-form-item__label:after
|
||||
) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:deep(.flex-inline .ed-form-item__label) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.enter {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #ff8800;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.custom-require_danger::after {
|
||||
color: var(--ed-color-danger);
|
||||
content: '*';
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.advance-setting {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
cursor: pointer;
|
||||
|
||||
.ed-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
&.expand {
|
||||
.ed-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-params {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin: 16px 0 8px 0;
|
||||
|
||||
.add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ed-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.params-table {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #dee0e3;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.operation-column_text {
|
||||
.ed-button {
|
||||
color: #646a73;
|
||||
height: 24px;
|
||||
}
|
||||
.ed-button:not(.is-disabled):hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
.ed-button + .ed-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import { supplierList } from '@/entity/supplier'
|
||||
|
||||
const keywords = ref('')
|
||||
|
||||
const modelListWithSearch = computed(() => {
|
||||
if (!keywords.value) return supplierList
|
||||
return supplierList.filter((ele) => ele.name.toLowerCase().includes(keywords.value.toLowerCase()))
|
||||
})
|
||||
const emits = defineEmits(['clickModel'])
|
||||
const handleModelClick = (item: any) => {
|
||||
emits('clickModel', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-list">
|
||||
<div class="title">{{ $t('model.select_supplier') }}</div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 100%; margin-right: 12px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="list-content">
|
||||
<div
|
||||
v-for="ele in modelListWithSearch"
|
||||
:key="ele.name"
|
||||
class="model"
|
||||
@click="handleModelClick(ele)"
|
||||
>
|
||||
<img width="32px" height="32px" :src="ele.icon" />
|
||||
<span class="name">{{ ele.name }}</span>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !modelListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
style="width: 100%; margin-top: 100px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-list {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
max-height: 100%;
|
||||
padding-top: 24px;
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 40px);
|
||||
gap: 16px;
|
||||
.model:nth-child(odd) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.model {
|
||||
width: 392px;
|
||||
height: 64px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-left: 16px;
|
||||
flex-wrap: wrap;
|
||||
border: 1px solid #dee0e3;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
box-shadow: 0px 6px 24px 0px #1f232914;
|
||||
}
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import { supplierList } from '@/entity/supplier'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
activeName: string
|
||||
}>(),
|
||||
{
|
||||
activeName: '',
|
||||
}
|
||||
)
|
||||
const keywords = ref('')
|
||||
|
||||
const modelListWithSearch = computed(() => {
|
||||
if (!keywords.value) return supplierList
|
||||
return supplierList.filter((ele) => ele.name.toLowerCase().includes(keywords.value.toLowerCase()))
|
||||
})
|
||||
const emits = defineEmits(['clickModel'])
|
||||
const handleModelClick = (item: any) => {
|
||||
emits('clickModel', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-list_side">
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 232px; margin: 16px 0 8px 24px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="list-content">
|
||||
<div
|
||||
v-for="ele in modelListWithSearch"
|
||||
:key="ele.name"
|
||||
class="model"
|
||||
:class="activeName === ele.name && 'isActive'"
|
||||
@click="handleModelClick(ele)"
|
||||
>
|
||||
<img width="32px" height="32px" :src="ele.icon" />
|
||||
<span class="name">{{ ele.name }}</span>
|
||||
</div>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !modelListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
img-type="tree"
|
||||
style="width: 100%; margin-top: 100px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-list_side {
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #1f232926;
|
||||
|
||||
.list-content {
|
||||
height: calc(100% - 56px);
|
||||
padding: 0 16px;
|
||||
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.name {
|
||||
margin-left: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
&:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
background: #1cba901a;
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const paramsRef = ref()
|
||||
const paramsForm = reactive({
|
||||
name: '',
|
||||
key: '',
|
||||
val: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('model.display_name'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('model.parameters'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
val: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('model.parameter_value'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const initForm = (item: any) => {
|
||||
if (item) {
|
||||
Object.assign(paramsForm, { ...item })
|
||||
}
|
||||
paramsRef.value.clearValidate()
|
||||
}
|
||||
|
||||
const emits = defineEmits(['submit'])
|
||||
|
||||
const submit = () => {
|
||||
paramsRef.value.validate((res: any) => {
|
||||
if (res) {
|
||||
emits('submit', paramsForm)
|
||||
}
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
initForm,
|
||||
submit,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="params-form">
|
||||
<el-form
|
||||
ref="paramsRef"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
:model="paramsForm"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-form-item prop="key" :label="$t('model.parameters')">
|
||||
<el-input
|
||||
v-model="paramsForm.key"
|
||||
:placeholder="$t('datasource.please_enter') + $t('common.empty') + $t('model.parameters')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" :label="$t('model.display_name')">
|
||||
<el-input
|
||||
v-model="paramsForm.name"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('model.display_name')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="val" :label="$t('model.parameter_value')">
|
||||
<el-input
|
||||
v-model="paramsForm.val"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('model.parameter_value')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.params-form {
|
||||
.ed-form-item--default {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.is-error {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.ed-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -265,10 +265,10 @@ const onFormClose = () => {
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
modelApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
|
||||
/* modelApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
|
||||
state.tableData = res.items
|
||||
state.pageInfo.total = res.total
|
||||
})
|
||||
}) */
|
||||
}
|
||||
const addTerm = () => {
|
||||
const param: any = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
"lib": ["es2021", "dom"],
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
||||
@@ -46,6 +46,16 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
},
|
||||
// server: {
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
// target: 'https://sqlbot.fit2cloud.cn',
|
||||
// changeOrigin: true,
|
||||
// rewrite: path => path.replace(/^\/api/, 'api')
|
||||
// }
|
||||
// },
|
||||
// port: 8080
|
||||
// },
|
||||
esbuild: {
|
||||
jsxFactory: 'h',
|
||||
jsxFragment: 'Fragment',
|
||||
|
||||