Merge remote-tracking branch 'origin/main'

This commit is contained in:
wangjiahao
2026-03-16 16:41:22 +08:00
30 changed files with 311 additions and 70 deletions
+3 -1
View File
@@ -400,7 +400,9 @@ def updateNum(session: SessionDep, ds: CoreDatasource):
def get_table_obj_by_ds(session: SessionDep, current_user: CurrentUser, ds: CoreDatasource) -> List[TableAndFields]:
_list: List = []
tables = session.query(CoreTable).filter(CoreTable.ds_id == ds.id).all()
tables = session.query(CoreTable).filter(
and_(CoreTable.ds_id == ds.id, CoreTable.checked == True)
).all()
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) if ds.type != "excel" else get_engine_config()
schema = conf.dbSchema if conf.dbSchema is not None and conf.dbSchema != "" else conf.database
+5 -4
View File
@@ -670,7 +670,7 @@ def check_sql_read(sql: str, ds: CoreDatasource | AssistantOutDsSchema):
write_types = (
exp.Insert, exp.Update, exp.Delete,
exp.Create, exp.Drop, exp.Alter,
exp.Merge, exp.Command
exp.Merge, exp.Command, exp.Copy
)
for stmt in statements:
@@ -688,6 +688,7 @@ def check_sql_read(sql: str, ds: CoreDatasource | AssistantOutDsSchema):
def checkParams(extraParams: str, illegalParams: List[str]):
kvs = extraParams.split('&')
for kv in kvs:
k, v = kv.split('=')
if k in illegalParams:
raise HTTPException(status_code=500, detail=f'Illegal Parameter: {k}')
if kv and '=' in kv:
k, v = kv.split('=')
if k in illegalParams:
raise HTTPException(status_code=500, detail=f'Illegal Parameter: {k}')
+2 -1
View File
@@ -180,7 +180,8 @@ async def create(session: SessionDep, creator: UserCreator, trans: Trans):
raise Exception(trans('i18n_exist', msg = f"{trans('i18n_user.email')} [{creator.email}]"))
if not check_email_format(creator.email):
raise Exception(trans('i18n_format_invalid', key = f"{trans('i18n_user.email')} [{creator.email}]"))
data = creator.model_dump(exclude_unset=True)
#data = creator.model_dump(exclude_unset=True)
data = creator.model_dump()
user_model = UserModel.model_validate(data)
#user_model.create_time = get_timestamp()
user_model.language = "zh-CN"
+10 -2
View File
@@ -21,6 +21,7 @@ from common.core.sqlbot_cache import cache
from common.utils.aes_crypto import simple_aes_decrypt
from common.utils.utils import SQLBotLogUtil, equals_ignore_case, get_domain_list, string_to_numeric_hash
from common.core.deps import Trans
from common.core.response_middleware import ResponseMiddleware
@cache(namespace=CacheNamespace.EMBEDDED_INFO, cacheName=CacheName.ASSISTANT_INFO, keyExpression="assistant_id")
@@ -87,13 +88,20 @@ def init_dynamic_cors(app: FastAPI):
seen.add(domain)
unique_domains.append(domain)
cors_middleware = None
response_middleware = None
for middleware in app.user_middleware:
if middleware.cls == CORSMiddleware:
if not cors_middleware and middleware.cls == CORSMiddleware:
cors_middleware = middleware
if not response_middleware and middleware.cls == ResponseMiddleware:
response_middleware = middleware
if cors_middleware and response_middleware:
break
updated_origins = list(set(settings.all_cors_origins + unique_domains))
if cors_middleware:
updated_origins = list(set(settings.all_cors_origins + unique_domains))
cors_middleware.kwargs['allow_origins'] = updated_origins
if response_middleware:
response_middleware.kwargs['allow_origins'] = updated_origins
except Exception as e:
return False, e
+10 -9
View File
@@ -1,8 +1,8 @@
# Author: Junjun
# Date: 2026/1/26
import datetime
from typing import List
from fastapi import HTTPException
from sqlalchemy import and_
from sqlmodel import select
@@ -37,11 +37,12 @@ def delete(session: SessionDep, ids: List[int]):
def list_all(session: SessionDep, trans: Trans, variable: SystemVariable):
if variable.name is None:
records = session.query(SystemVariable).order_by(SystemVariable.type.desc()).all()
records = session.query(SystemVariable).order_by(SystemVariable.type.desc(),
SystemVariable.name.asc()).all()
else:
records = session.query(SystemVariable).filter(
and_(SystemVariable.name.like(f'%{variable.name}%'), SystemVariable.type != 'system')).order_by(
SystemVariable.type.desc()).all()
and_(SystemVariable.name.ilike(f'%{variable.name}%'), SystemVariable.type != 'system')).order_by(
SystemVariable.type.desc(), SystemVariable.name.asc()).all()
res = []
for r in records:
@@ -58,11 +59,11 @@ async def list_page(session: SessionDep, trans: Trans, pageNum: int, pageSize: i
filters = {}
if variable.name is None:
stmt = select(SystemVariable).order_by(SystemVariable.type.desc())
stmt = select(SystemVariable).order_by(SystemVariable.type.desc(), SystemVariable.name.asc())
else:
stmt = select(SystemVariable).where(
and_(SystemVariable.name.like(f'%{variable.name}%'), SystemVariable.type != 'system')).order_by(
SystemVariable.type.desc())
and_(SystemVariable.name.ilike(f'%{variable.name}%'), SystemVariable.type != 'system')).order_by(
SystemVariable.type.desc(), SystemVariable.name.asc())
variable_page = await paginator.get_paginated_response(
stmt=stmt,
@@ -92,7 +93,7 @@ def checkName(session: SessionDep, trans: Trans, variable: SystemVariable):
raise HTTPException(status_code=500, detail=trans('i18n_variable.name_exist'))
def checkValue(session: SessionDep, trans: Trans, values:List):
def checkValue(session: SessionDep, trans: Trans, values: List):
# values: [{"variableId":1,"variableValues":["a","b"]}]
pass
pass
+1 -1
View File
@@ -58,7 +58,7 @@ class UserCreator(BaseUser):
status: int = Field(default=1, description=f"{PLACEHOLDER_PREFIX}status")
origin: Optional[int] = Field(default=0, description=f"{PLACEHOLDER_PREFIX}origin")
oid_list: Optional[list[int]] = Field(default=None, description=f"{PLACEHOLDER_PREFIX}oid")
system_variables: Optional[List] = Field(default=None)
system_variables: Optional[List] = Field(default=[])
""" @field_validator("email")
def validate_email(cls, lang: str) -> str:
@@ -165,6 +165,7 @@ session_maker = scoped_session(sessionmaker(bind=engine, class_=Session))
@router.post("/uploadExcel", summary=f"{PLACEHOLDER_PREFIX}upload_term")
@system_log(LogConfig(operation_type=OperationType.IMPORT, module=OperationModules.TERMINOLOGY))
@require_permissions(permission=SqlbotPermission(role=['ws_admin']))
async def upload_excel(trans: Trans, current_user: CurrentUser, file: UploadFile = File(...)):
ALLOWED_EXTENSIONS = {"xlsx", "xls"}
if not file.filename.lower().endswith(tuple(ALLOWED_EXTENSIONS)):
+9 -1
View File
@@ -1,5 +1,6 @@
import json
from redis import typing
from starlette.exceptions import HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
@@ -11,6 +12,7 @@ from common.utils.utils import SQLBotLogUtil
class ResponseMiddleware(BaseHTTPMiddleware):
def __init__(self, app):
self.allow_origins = ["'self'"]
super().__init__(app)
async def dispatch(self, request, call_next):
@@ -76,7 +78,13 @@ class ResponseMiddleware(BaseHTTPMiddleware):
if k.lower() not in ("content-length", "content-type")
}
)
content_type = response.headers.get("content-type", "")
static_content_types = ["text/html", "javascript", "typescript", "css"]
if any(ct in content_type for ct in static_content_types):
if self.allow_origins:
frame_ancestors_value = " ".join(self.allow_origins)
response.headers["Content-Security-Policy"] = f"frame-ancestors {frame_ancestors_value};"
return response
+2 -2
View File
@@ -64,7 +64,7 @@
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-vue": "^10.2.0",
"globals": "^16.2.0",
"less": "^4.3.0",
"less": "4.4.2",
"pinia": "^3.0.2",
"prettier": "^3.5.3",
"typescript": "~5.7.2",
@@ -76,4 +76,4 @@
"vite-svg-loader": "^5.1.0",
"vue-tsc": "^2.2.8"
}
}
}
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618217035616" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6354" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M554.666667 256v640h-85.333334V256H213.333333V170.666667h597.333334v85.333333z" p-id="6355"></path></svg>

After

Width:  |  Height:  |  Size: 483 B

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1664337058571" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1356" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M309.312 85.312a32 32 0 0 1 32 32V128h341.312v-10.688a32 32 0 0 1 32-32h21.376a32 32 0 0 1 32 32V128h128c23.552 0 42.624 19.072 42.624 42.624V896a42.688 42.688 0 0 1-42.624 42.624H128A42.688 42.688 0 0 1 85.312 896V170.624C85.312 147.072 104.384 128 128 128h128v-10.688a32 32 0 0 1 32-32h21.312z m373.312 128H341.312V224a32 32 0 0 1-32 32H288a32 32 0 0 1-32-32v-10.688H170.624v640h682.688v-640H768V224a32 32 0 0 1-32 32h-21.376a32 32 0 0 1-32-32v-10.688zM384 437.312a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32v64a32 32 0 0 0 32 32h64a32 32 0 0 0 32-32v-64z m64 0a32 32 0 0 1 32-32h64a32 32 0 0 1 32 32v64a32 32 0 0 1-32 32h-64a32 32 0 0 1-32-32v-64z m-64 192a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32v64a32 32 0 0 0 32 32h64a32 32 0 0 0 32-32v-64z m64 0a32 32 0 0 1 32-32h64a32 32 0 0 1 32 32v64a32 32 0 0 1-32 32h-64a32 32 0 0 1-32-32v-64z m320-192a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32v64a32 32 0 0 0 32 32h64a32 32 0 0 0 32-32v-64z" p-id="1357"></path><path d="M768 629.312a32 32 0 0 0-32-32h-64a32 32 0 0 0-32 32v64a32 32 0 0 0 32 32h64a32 32 0 0 0 32-32v-64z" p-id="1358"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1618218878362" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9183" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M159.926857 689.426286h130.724572l-39.003429 193.718857c-0.859429 4.278857-1.28 9.435429-1.28 13.714286 0 20.992 14.555429 32.566857 34.706286 32.566857 20.571429 0 35.565714-11.154286 39.862857-32.146286l41.984-207.853714h202.715428L530.651429 883.145143c-1.28 4.278857-1.700571 9.435429-1.700572 13.714286 0 20.992 14.573714 32.566857 35.145143 32.566857s35.565714-11.154286 39.862857-32.146286L645.485714 689.426286h152.996572c23.570286 0 39.862857-17.133714 39.862857-40.283429 0-18.852571-12.854857-34.285714-32.146286-34.285714h-145.298286L706.377143 388.571429h149.997714c23.588571 0 39.862857-17.152 39.862857-40.283429 0-18.852571-12.854857-34.285714-32.146285-34.285714h-142.72l35.145142-172.726857c0.420571-2.56 1.28-8.137143 1.28-13.714286 0-20.992-14.994286-32.987429-35.565714-32.987429-23.990857 0-34.706286 13.275429-39.003428 33.426286l-37.705143 186.002286H442.788571l35.145143-172.726857c0.420571-2.56 1.28-8.137143 1.28-13.714286 0-20.992-15.433143-32.987429-35.565714-32.987429-24.429714 0-35.584 13.275429-39.442286 33.426286l-37.705143 186.002286h-140.580571c-23.570286 0-39.862857 17.993143-39.862857 41.563428 0 19.291429 12.873143 33.005714 32.164571 33.005715h133.284572l-45.44 226.285714h-138.422857c-23.588571 0-39.862857 17.993143-39.862858 41.563428 0 19.291429 12.854857 33.005714 32.146286 33.005715z m221.988572-74.569143l45.878857-226.285714H630.491429l-45.842286 226.285714z" p-id="9184"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+2
View File
@@ -8,6 +8,8 @@
"Details": "Details"
},
"variables": {
"please_select_date": "Please select a date",
"number_variable_error": "Please enter the correct numerical range.",
"built_in": "Built-in",
"normal_value": "Normal value",
"​​cannot_be_empty": "Variable values ​​cannot be empty.",
+3 -1
View File
@@ -24,6 +24,8 @@
"return_to_view": "화면으로 돌아가기"
},
"variables": {
"please_select_date": "날짜를 선택하십시오",
"number_variable_error": "올바른 숫자 범위를 입력하십시오.",
"built_in": "내장형",
"normal_value": "정상값",
"​​cannot_be_empty": "변수 값은 비어 있을 수 없습니다.",
@@ -943,4 +945,4 @@
"to_doc": "API 보기",
"trigger_limit": "최대 {0}개의 API 키 생성 지원"
}
}
}
+2
View File
@@ -24,6 +24,8 @@
"return_to_view": "返回查看"
},
"variables": {
"please_select_date": "请选择日期",
"number_variable_error": "请输入正确的数值范围",
"built_in": "已内置",
"normal_value": "常规值",
"​​cannot_be_empty": "变量值不能为空",
+10
View File
@@ -419,3 +419,13 @@ strong {
margin-top: -7px;
}
}
.text-variables.text-variables {
color: #3370ff;
}
.number-variables.number-variables {
color: #04b49c;
}
.datetime-variables.datetime-variables {
color: #3370ff;
}
@@ -173,6 +173,7 @@ defineExpose({
border: 1px solid #dee0e3;
padding: 16px;
margin-bottom: 8px;
cursor: pointer;
.header {
display: flex;
@@ -7,6 +7,7 @@ import {
type S2Options,
type SortMethod,
TableSheet,
type SortFuncParam,
} from '@antv/s2'
import { debounce, filter } from 'lodash-es'
import { i18n } from '@/i18n'
@@ -14,6 +15,60 @@ import '@antv/s2/dist/s2.min.css'
const { t } = i18n.global
const createSmartSortFunc = (sortMethod: string) => {
const compareNumericString = (a: string, b: string): number => {
const isNegA = a.startsWith('-')
const isNegB = b.startsWith('-')
// 负数 < 正数
if (isNegA && !isNegB) return -1
if (!isNegA && isNegB) return 1
const [intA, decA = ''] = isNegA ? a.slice(1).split('.') : a.split('.')
const [intB, decB = ''] = isNegB ? b.slice(1).split('.') : b.split('.')
// 都是正数
if (!isNegA && !isNegB) {
if (intA.length !== intB.length) return intA.length - intB.length
const intCmp = intA.localeCompare(intB)
if (intCmp !== 0) return intCmp
if (decA && decB) return decA.localeCompare(decB)
return decA ? 1 : decB ? -1 : 0
}
// 都是负数:绝对值大的实际值小,比较结果取反
if (intA.length !== intB.length) return -(intA.length - intB.length)
const intCmp = intA.localeCompare(intB)
if (intCmp !== 0) return -intCmp
if (decA && decB) return -decA.localeCompare(decB)
return decA ? 1 : decB ? -1 : 0
}
return (params: SortFuncParam) => {
const { data, sortFieldId } = params
if (!data || data.length === 0) return data
const isAsc = sortMethod.toLowerCase() === 'asc'
return [...data].sort((a: any, b: any) => {
const valA = a[sortFieldId],
valB = b[sortFieldId]
if (valA == null) return isAsc ? -1 : 1
if (valB == null) return isAsc ? 1 : -1
const strA = String(valA),
strB = String(valB)
const isNumA = !isNaN(Number(strA)) && strA.trim() !== ''
const isNumB = !isNaN(Number(strB)) && strB.trim() !== ''
if (isNumA && !isNumB) return isAsc ? -1 : 1
if (!isNumA && isNumB) return isAsc ? 1 : -1
if (isNumA && isNumB) {
const cmp = compareNumericString(strA, strB)
return isAsc ? cmp : -cmp
}
const cmp = strA.localeCompare(strB)
return isAsc ? cmp : -cmp
})
}
}
export class Table extends BaseChart {
table?: TableSheet = undefined
@@ -81,7 +136,17 @@ export class Table extends BaseChart {
const sortOrder = ['none', 'desc', 'asc']
const nextMethod = sortOrder[(sortOrder.indexOf(currentMethod) + 1) % sortOrder.length]
sortState[fieldId] = nextMethod
s2.groupSortByMethod(nextMethod === 'none' ? 'none' : (nextMethod as SortMethod), meta)
if (nextMethod === 'none') {
s2.emit(S2Event.RANGE_SORT, [{ sortFieldId: fieldId, sortMethod: 'none' as SortMethod }])
} else {
s2.emit(S2Event.RANGE_SORT, [
{
sortFieldId: fieldId,
sortMethod: nextMethod as SortMethod,
sortFunc: createSmartSortFunc(nextMethod),
},
])
}
s2.render()
}
}
+30 -1
View File
@@ -190,6 +190,15 @@ const editTable = () => {
tableComment.value = currentTable.value.custom_comment
tableDialog.value = true
}
const changeChecked = () => {
datasourceApi.saveTable(currentTable.value).then(() => {
ElMessage({
message: t('common.save_success'),
type: 'success',
showClose: true,
})
})
}
const saveTable = () => {
currentTable.value.custom_comment = tableComment.value
datasourceApi.saveTable(currentTable.value).then(() => {
@@ -473,7 +482,27 @@ const btnSelectClick = (val: any) => {
class="info-table"
>
<div class="table-name">
<div class="name">{{ currentTable.table_name }}</div>
<div class="name">
{{ currentTable.table_name }}
<div
style="
display: inline-flex;
align-items: center;
margin-left: 30px;
font-size: 14px;
font-weight: 400;
"
>
<el-switch
v-model="currentTable.checked"
@change="changeChecked"
size="small"
style="margin-right: 8px"
/>
{{ currentTable.checked ? t('user.disable') : t('user.enable') }}
</div>
</div>
<div class="notes">
{{ $t('about.remark') }}:
<span :title="currentTable.custom_comment" class="field-notes">{{
+1 -1
View File
@@ -275,7 +275,7 @@ const initOptions = () => {
<div
v-if="!searchLoading"
class="table-content"
:class="multipleSelectionAll?.length && 'show-pagination_height'"
:class="multipleSelectionAll.length ? 'show-pagination_height' : ''"
>
<filter-text
:total="pageInfo.total"
+1 -1
View File
@@ -356,7 +356,7 @@ const copyCode = (row: any, key: any = 'app_secret') => {
<div
v-if="!searchLoading"
class="table-content"
:class="multipleSelectionAll.length && 'show-pagination_height'"
:class="multipleSelectionAll.length ? 'show-pagination_height' : ''"
>
<template v-if="!oldKeywords && !fieldList.length">
<EmptyBackground
+1 -1
View File
@@ -240,7 +240,7 @@ const handleCurrentChange = (val: number) => {
</el-button>
</div>
</div>
<div class="table-content" :class="multipleSelectionAll.length && 'show-pagination_height'">
<div class="table-content" :class="multipleSelectionAll.length ? 'show-pagination_height' : ''">
<div class="preview-or-schema">
<el-table
ref="multipleTableRef"
@@ -4,6 +4,10 @@ import { ref, inject, computed, onBeforeMount, toRefs, type Ref, shallowRef } fr
import { variablesApi } from '@/api/variables'
import { useI18n } from 'vue-i18n'
import { allOptions } from '../options'
import field_text from '@/assets/svg/field_text.svg'
import field_time from '@/assets/svg/field_time.svg'
import field_value from '@/assets/svg/field_value.svg'
export interface Item {
term: string
field_id: string
@@ -118,6 +122,12 @@ const initEnumOptions = () => {
console.info('initEnumOptions')
}
const iconMap = {
text: field_text,
number: field_value,
datetime: field_time,
}
const selectItem = ({ field_name, id }: any) => {
Object.assign(item.value, {
field_id: id,
@@ -248,6 +258,12 @@ const emits = defineEmits(['update:item', 'del'])
:placeholder="t('datasource.Please_select')"
>
<el-option v-for="ele in variables" :key="ele.id" :label="ele.name" :value="ele.id">
<div style="width: 100%; display: flex; align-items: center">
<el-icon :class="`${ele.var_type}-variables`" size="16" style="margin-right: 4px">
<component :is="iconMap[ele.var_type as keyof typeof iconMap]"></component>
</el-icon>
{{ ele.name }}
</div>
</el-option>
</el-select>
</template>
@@ -2,6 +2,7 @@
import { ref, computed } from 'vue'
import AuthTree from './AuthTree.vue'
import { useI18n } from 'vue-i18n'
import { variablesApi } from '@/api/variables'
const { t } = useI18n()
const errorMessage = ref('')
@@ -26,9 +27,10 @@ const svgDashinePath = computed(() => {
return path
})
const init = (expressionTree: any) => {
const init = async (expressionTree: any) => {
const { logic: lg = 'or', items = [] } = expressionTree
logic.value = lg
await getVariables()
relationList.value = dfsInit(items)
}
const submit = () => {
@@ -65,6 +67,17 @@ const errorDetected = ({ filter_type, field_id, term, value, value_type, variabl
}
}
}
const variables = ref<any[]>([])
const getVariables = () => {
return variablesApi.listAll().then((res: any) => {
variables.value = res || []
})
}
const canPush = (value_type: any, variable_id: any) => {
if (value_type === 'normal') return true
return variables.value.some((ele) => ele.id === variable_id)
}
const dfsInit = (arr: any[]) => {
const elementList: any[] = []
arr.forEach((ele: any) => {
@@ -85,16 +98,18 @@ const dfsInit = (arr: any[]) => {
variable_id = '',
} = ele
const { name } = field || {}
elementList.push({
enum_value: enum_value.join(','),
field_id,
filter_type,
term,
value,
name,
value_type,
variable_id,
})
if (canPush(value_type, variable_id)) {
elementList.push({
enum_value: enum_value.join(','),
field_id,
filter_type,
term,
value,
name,
value_type,
variable_id,
})
}
}
})
return elementList
@@ -470,7 +470,7 @@ const changeStatus = (id: any, val: any) => {
<div
v-if="!searchLoading"
class="table-content"
:class="multipleSelectionAll?.length && 'show-pagination_height'"
:class="multipleSelectionAll.length ? 'show-pagination_height' : ''"
>
<filter-text
:total="pageInfo.total"
+1 -1
View File
@@ -506,7 +506,7 @@ const drawerMainClose = () => {
<div
v-if="!searchLoading"
class="table-content"
:class="multipleSelectionAll?.length && 'show-pagination_height'"
:class="multipleSelectionAll.length ? 'show-pagination_height' : ''"
>
<filter-text
:total="pageInfo.total"
+1 -1
View File
@@ -393,7 +393,7 @@ const onRowFormClose = () => {
<div
v-if="!searchLoading"
class="table-content"
:class="multipleSelectionAll?.length && 'show-pagination_height'"
:class="multipleSelectionAll?.length ? 'show-pagination_height' : ''"
>
<div class="preview-or-schema">
<el-table
+39 -20
View File
@@ -391,8 +391,23 @@
v-for="item in variables"
:key="item.id"
:label="item.name"
:disabled="
state.form.system_variables.map((ele: any) => ele.variableId).includes(item.id)
"
:value="item.id"
>
<div style="width: 100%; display: flex; align-items: center">
<el-icon
:class="`${variableValueMap[item.id].var_type}-variables`"
size="16"
style="margin-right: 4px"
>
<component
:is="iconMap[variableValueMap[item.id].var_type as keyof typeof iconMap]"
></component>
</el-icon>
{{ item.name }}
</div>
</el-option>
</el-select>
<el-select
@@ -422,7 +437,7 @@
>
</el-option>
</el-select>
<el-input
<el-input-number
v-else-if="
variableValueMap[state.form.system_variables[index].variableId].var_type ===
'number'
@@ -430,19 +445,17 @@
v-model.number="state.form.system_variables[index].variableValue"
style="width: 236px"
:placeholder="$t('variables.please_enter_value')"
autocomplete="off"
maxlength="50"
clearable
max="10000000000000000"
controls-position="right"
/>
<el-date-picker
v-else
v-model="state.form.system_variables[index].variableValues"
type="daterange"
style="max-width: 236px"
v-model="state.form.system_variables[index].variableValue"
type="date"
style="width: 236px"
value-format="YYYY-MM-DD"
range-separator=""
:start-placeholder="$t('variables.start_date')"
:end-placeholder="$t('variables.end_date')"
:placeholder="$t('variables.please_select_date')"
/>
<el-tooltip
:offset="14"
@@ -530,6 +543,7 @@ import UserImport from './UserImport.vue'
import SuccessFilled from '@/assets/svg/gou_icon.svg'
import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg'
import CircleCloseFilled from '@/assets/svg/icon_ban_filled.svg'
import { ElButton } from 'element-plus-secondary'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import { useI18n } from 'vue-i18n'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
@@ -544,6 +558,9 @@ import logo_lark from '@/assets/img/lark.png'
import logo_wechat_work from '@/assets/img/wechat.png'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import { userApi } from '@/api/user'
import field_text from '@/assets/svg/field_text.svg'
import field_time from '@/assets/svg/field_time.svg'
import field_value from '@/assets/svg/field_value.svg'
import { request } from '@/utils/request'
import { workspaceList } from '@/api/workspace'
import { variablesApi } from '@/api/variables'
@@ -566,6 +583,12 @@ const drawerMainRef = ref()
const userImportRef = ref()
const syncUserRef = ref()
const selectionLoading = ref(false)
const iconMap = {
text: field_text,
number: field_value,
datetime: field_time,
}
const filterOption = ref<any[]>([
{
type: 'enum',
@@ -995,10 +1018,9 @@ const formatVariableValues = () => {
if (!state.form.system_variables?.length) return []
return state.form.system_variables.map((ele: any) => ({
variableId: ele.variableId,
variableValues:
variableValueMap.value[ele.variableId].var_type === 'number'
? [ele.variableValue]
: ele.variableValues,
variableValues: ['number', 'datetime'].includes(variableValueMap.value[ele.variableId].var_type)
? [ele.variableValue]
: ele.variableValues,
}))
}
@@ -1055,12 +1077,12 @@ const validateSystemVariables = () => {
if (system_variables?.length) {
return system_variables.some((ele: any) => {
const obj = variableValueMap.value[ele.variableId]
if (obj.var_type !== 'number' && !ele.variableValues.length) {
if (obj.var_type === 'text' && !ele.variableValues.length) {
ElMessage.error(t('variables.​​cannot_be_empty'))
return true
}
if (obj.var_type === 'number' && !ele.variableValue) {
if (obj.var_type === 'number' && [null, undefined, ''].includes(ele.variableValue)) {
ElMessage.error(t('variables.​​cannot_be_empty'))
return true
}
@@ -1075,12 +1097,9 @@ const validateSystemVariables = () => {
if (obj.var_type === 'datetime') {
const [min, max] = obj.value
const [minVal, maxVal] = ele.variableValues
if (
+new Date(minVal) > +new Date(max) ||
+new Date(maxVal) < +new Date(min) ||
+new Date(maxVal) > +new Date(max) ||
+new Date(minVal) < +new Date(min)
+new Date(ele.variableValue) > +new Date(max) ||
+new Date(ele.variableValue) < +new Date(min)
) {
ElMessage.error(
t('variables.1_to_100_de', {
+60 -8
View File
@@ -7,6 +7,9 @@ import IconOpeDelete from '@/assets/svg/icon_delete.svg'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import { useI18n } from 'vue-i18n'
import field_text from '@/assets/svg/field_text.svg'
import field_time from '@/assets/svg/field_time.svg'
import field_value from '@/assets/svg/field_value.svg'
import { cloneDeep } from 'lodash-es'
interface Form {
@@ -21,6 +24,11 @@ const multipleSelectionAll = ref<any[]>([])
const keywords = ref('')
const oldKeywords = ref('')
const searchLoading = ref(false)
const iconMap = {
text: field_text,
number: field_value,
datetime: field_time,
}
const selectable = (row: any) => {
return ![1, 2, 3].includes(row.id)
@@ -74,7 +82,7 @@ const deleteBatchUser = () => {
message: t('dashboard.delete_success'),
})
multipleSelectionAll.value = []
search()
handleCurrentChange(1)
})
})
}
@@ -94,7 +102,7 @@ const deleteHandler = (row: any) => {
type: 'success',
message: t('dashboard.delete_success'),
})
search()
handleCurrentChange(1)
})
})
}
@@ -181,7 +189,12 @@ const validateValue = (_: any, value: any, callback: any) => {
}
return
}
if (value.some((ele: any) => ele === '')) {
if (var_type === 'datetime') {
if (value === null) {
callback(new Error(t('datasource.please_enter') + t('common.empty') + t('variables.date')))
}
}
if (value.some((ele: any) => ele === '' || ele === null)) {
callback(
new Error(
t('datasource.please_enter') +
@@ -229,12 +242,23 @@ const saveHandler = () => {
obj.value = [...new Set(obj.value)]
}
if (obj.var_type === 'number') {
const [min = 0, max = 0] = obj.value
if (min > max) {
ElMessage({
type: 'error',
message: t('variables.number_variable_error'),
})
return
}
}
variablesApi.save(obj).then(() => {
ElMessage({
type: 'success',
message: t('common.save_success'),
})
search()
handleCurrentChange(1)
onFormClose()
})
}
@@ -302,7 +326,7 @@ const handleCurrentChange = (val: number) => {
<div
v-if="!searchLoading"
class="table-content"
:class="multipleSelectionAll.length && 'show-pagination_height'"
:class="multipleSelectionAll.length ? 'show-pagination_height' : ''"
>
<template v-if="!oldKeywords && !fieldList.length">
<EmptyBackground
@@ -322,7 +346,30 @@ const handleCurrentChange = (val: number) => {
<el-table-column prop="name" :label="$t('variables.variable_name')">
<template #default="scope">
<div style="display: flex; align-items: center" :title="scope.row.name">
<div class="ellipsis" style="max-width: calc(100% - 60px); width: fit-content">
<div
class="ellipsis"
style="
max-width: calc(100% - 60px);
width: fit-content;
position: relative;
padding-left: 20px;
"
>
<el-icon
:class="`${scope.row.var_type}-variables`"
size="16"
style="
margin-right: 4px;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
"
>
<component
:is="iconMap[scope.row.var_type as keyof typeof iconMap]"
></component>
</el-icon>
{{ scope.row.name }}
</div>
<div v-if="scope.row.type === 'system'" class="system-flag">
@@ -522,12 +569,14 @@ const handleCurrentChange = (val: number) => {
v-model.number="pageForm.value[0]"
:placeholder="$t('variables.please_enter_value')"
clearable
max="10000000000000000"
controls-position="right"
/>
<span class="ed-range-separator separator"></span>
<el-input-number
v-model.number="pageForm.value[1]"
:placeholder="$t('variables.please_enter_value')"
max="10000000000000000"
clearable
controls-position="right"
/>
@@ -629,8 +678,11 @@ const handleCurrentChange = (val: number) => {
margin-left: 4px;
color: var(--ed-color-primary-15-d, #189e7a);
}
.ed-icon {
color: #646a73;
&:not(:has(.ellipsis)) {
.ed-icon {
color: #646a73;
}
}
.field-comment {
@@ -428,7 +428,10 @@ const handleCurrentChange = (val: number) => {
</el-input>
</div>
</div>
<div class="table-content" :class="multipleSelectionAll.length && 'show-pagination_height'">
<div
class="table-content"
:class="{ 'show-pagination_height': multipleSelectionAll.length > 0 }"
>
<div class="preview-or-schema">
<el-table
ref="multipleTableRef"