mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 09:17:06 +08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -28,6 +28,7 @@ from apps.chat.curd.chat import save_question, save_full_sql_message, save_full_
|
||||
get_old_questions, save_analysis_predict_record, list_base_records, rename_chat
|
||||
from apps.chat.models.chat_model import ChatQuestion, ChatRecord, Chat, RenameChat
|
||||
from apps.datasource.crud.datasource import get_table_schema
|
||||
from apps.datasource.crud.datasource import is_normal_user
|
||||
from apps.datasource.crud.row_permission import transFilterTree
|
||||
from apps.datasource.models.datasource import CoreDatasource, CoreTable
|
||||
from apps.db.db import exec_sql
|
||||
@@ -791,26 +792,28 @@ class LLMService:
|
||||
SQLBotLogUtil.info(full_sql_text)
|
||||
|
||||
# todo row permission
|
||||
sql_json_str = extract_nested_json(full_sql_text)
|
||||
data = orjson.loads(sql_json_str)
|
||||
if is_normal_user(self.current_user):
|
||||
sql_json_str = extract_nested_json(full_sql_text)
|
||||
data = orjson.loads(sql_json_str)
|
||||
|
||||
sql = ''
|
||||
message = ''
|
||||
error = False
|
||||
if data['success']:
|
||||
sql = data['sql']
|
||||
sql = ''
|
||||
message = ''
|
||||
error = False
|
||||
if data['success']:
|
||||
sql = data['sql']
|
||||
else:
|
||||
message = data['message']
|
||||
error = True
|
||||
if error:
|
||||
raise Exception(message)
|
||||
if sql.strip() == '':
|
||||
raise Exception("SQL query is empty")
|
||||
|
||||
sql_result = self.generate_filter(data.get('sql'), data.get('tables')) # maybe no sql and tables
|
||||
SQLBotLogUtil.info(sql_result)
|
||||
sql = self.check_save_sql(res=sql_result)
|
||||
else:
|
||||
message = data['message']
|
||||
error = True
|
||||
if error:
|
||||
raise Exception(message)
|
||||
if sql.strip() == '':
|
||||
raise Exception("SQL query is empty")
|
||||
|
||||
sql_result = self.generate_filter(data.get('sql'), data.get('tables')) # maybe no sql and tables
|
||||
SQLBotLogUtil.info(sql_result)
|
||||
sql = self.check_save_sql(res=sql_result)
|
||||
# sql = llm_service.check_save_sql(res=full_sql_text)
|
||||
sql = self.check_save_sql(res=full_sql_text)
|
||||
|
||||
SQLBotLogUtil.info(sql)
|
||||
format_sql = sqlparse.format(sql, reindent=True)
|
||||
|
||||
@@ -5,10 +5,12 @@ from typing import List
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import and_, text, cast, or_, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlbot_xpack.permissions.models.ds_permission import DsPermission
|
||||
from sqlbot_xpack.permissions.api.permission import transRecord2DTO
|
||||
from sqlbot_xpack.permissions.models.ds_permission import DsPermission, PermissionDTO
|
||||
from sqlbot_xpack.permissions.models.ds_rules import DsRules
|
||||
from sqlmodel import select
|
||||
|
||||
from apps.datasource.crud.row_permission import transFilterTree
|
||||
from apps.datasource.utils.utils import aes_decrypt
|
||||
from apps.db.constant import DB
|
||||
from apps.db.db import get_engine, get_tables, get_fields, exec_sql
|
||||
@@ -238,43 +240,69 @@ def updateField(session: SessionDep, field: CoreField):
|
||||
|
||||
|
||||
def preview(session: SessionDep, current_user: CurrentUser, id: int, data: TableObj):
|
||||
ds = session.query(CoreDatasource).filter(CoreDatasource.id == id).first()
|
||||
check_status(session, ds, True)
|
||||
|
||||
if data.fields is None or len(data.fields) == 0:
|
||||
return {"fields": [], "data": [], "sql": ''}
|
||||
|
||||
# column is checked, and, column permission for data.fields
|
||||
where = None
|
||||
f_list = [f for f in data.fields if f.checked]
|
||||
column_permissions = session.query(DsPermission).filter(
|
||||
and_(DsPermission.table_id == data.table.id, DsPermission.type == 'column')).all()
|
||||
if column_permissions is not None:
|
||||
for permission in column_permissions:
|
||||
# check permission and user in same rules
|
||||
obj = session.query(DsRules).filter(
|
||||
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
|
||||
or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)),
|
||||
DsRules.user_list.op('@>')(cast([current_user.id], JSONB))))
|
||||
).first()
|
||||
if obj is not None:
|
||||
permission_list = json.loads(permission.permissions)
|
||||
f_list = filter_list(f_list, permission_list)
|
||||
if is_normal_user(current_user):
|
||||
# column is checked, and, column permission for data.fields
|
||||
column_permissions = session.query(DsPermission).filter(
|
||||
and_(DsPermission.table_id == data.table.id, DsPermission.type == 'column')).all()
|
||||
if column_permissions is not None:
|
||||
for permission in column_permissions:
|
||||
# check permission and user in same rules
|
||||
obj = session.query(DsRules).filter(
|
||||
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
|
||||
or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)),
|
||||
DsRules.user_list.op('@>')(cast([current_user.id], JSONB))))
|
||||
).first()
|
||||
if obj is not None:
|
||||
permission_list = json.loads(permission.permissions)
|
||||
f_list = filter_list(f_list, permission_list)
|
||||
|
||||
# row permission tree
|
||||
row_permissions = session.query(DsPermission).filter(
|
||||
and_(DsPermission.table_id == data.table.id, DsPermission.type == 'row')).all()
|
||||
res: List[PermissionDTO] = []
|
||||
if row_permissions is not None:
|
||||
for permission in row_permissions:
|
||||
# check permission and user in same rules
|
||||
obj = session.query(DsRules).filter(
|
||||
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
|
||||
or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)),
|
||||
DsRules.user_list.op('@>')(cast([current_user.id], JSONB))))
|
||||
).first()
|
||||
if obj is not None:
|
||||
res.append(transRecord2DTO(session, permission))
|
||||
wheres = transFilterTree(session, res, ds)
|
||||
where = (' where ' + wheres) if wheres is not None and wheres != '' else ''
|
||||
|
||||
fields = [f.field_name for f in f_list]
|
||||
if fields is None or len(fields) == 0:
|
||||
return {"fields": [], "data": [], "sql": ''}
|
||||
|
||||
ds = session.query(CoreDatasource).filter(CoreDatasource.id == id).first()
|
||||
check_status(session, ds, True)
|
||||
conf = DatasourceConf(**json.loads(aes_decrypt(ds.configuration))) if ds.type != "excel" else get_engine_config()
|
||||
sql: str = ""
|
||||
if ds.type == "mysql":
|
||||
sql = f"""SELECT `{"`, `".join(fields)}` FROM `{data.table.table_name}` LIMIT 100"""
|
||||
sql = f"""SELECT `{"`, `".join(fields)}` FROM `{data.table.table_name}`
|
||||
{where}
|
||||
LIMIT 100"""
|
||||
elif ds.type == "sqlServer":
|
||||
sql = f"""SELECT [{"], [".join(fields)}] FROM [{conf.dbSchema}].[{data.table.table_name}]
|
||||
{where}
|
||||
ORDER BY [{data.fields[0].field_name}]
|
||||
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY"""
|
||||
elif ds.type == "pg" or ds.type == "excel":
|
||||
sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}" LIMIT 100"""
|
||||
sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}"
|
||||
{where}
|
||||
LIMIT 100"""
|
||||
elif ds.type == "oracle":
|
||||
sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}"
|
||||
{where}
|
||||
ORDER BY "{data.fields[0].field_name}"
|
||||
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY"""
|
||||
return exec_sql(ds, sql)
|
||||
@@ -320,19 +348,20 @@ def get_table_obj_by_ds(session: SessionDep, current_user: CurrentUser, ds: Core
|
||||
fields = session.query(CoreField).filter(and_(CoreField.table_id == table.id, CoreField.checked == True)).all()
|
||||
|
||||
# do column permissions, filter fields
|
||||
column_permissions = session.query(DsPermission).filter(
|
||||
and_(DsPermission.table_id == table.id, DsPermission.type == 'column')).all()
|
||||
if column_permissions is not None:
|
||||
for permission in column_permissions:
|
||||
# check permission and user in same rules
|
||||
obj = session.query(DsRules).filter(
|
||||
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
|
||||
or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)),
|
||||
DsRules.user_list.op('@>')(cast([current_user.id], JSONB))))
|
||||
).first()
|
||||
if obj is not None:
|
||||
permission_list = json.loads(permission.permissions)
|
||||
fields = filter_list(fields, permission_list)
|
||||
if is_normal_user(current_user):
|
||||
column_permissions = session.query(DsPermission).filter(
|
||||
and_(DsPermission.table_id == table.id, DsPermission.type == 'column')).all()
|
||||
if column_permissions is not None:
|
||||
for permission in column_permissions:
|
||||
# check permission and user in same rules
|
||||
obj = session.query(DsRules).filter(
|
||||
and_(DsRules.permission_list.op('@>')(cast([permission.id], JSONB)),
|
||||
or_(DsRules.user_list.op('@>')(cast([f'{current_user.id}'], JSONB)),
|
||||
DsRules.user_list.op('@>')(cast([current_user.id], JSONB))))
|
||||
).first()
|
||||
if obj is not None:
|
||||
permission_list = json.loads(permission.permissions)
|
||||
fields = filter_list(fields, permission_list)
|
||||
|
||||
_list.append(TableAndFields(schema=schema, table=table, fields=fields))
|
||||
return _list
|
||||
@@ -376,3 +405,7 @@ def filter_list(list_a, list_b):
|
||||
id_to_invalid[b['field_id']] = True
|
||||
|
||||
return [a for a in list_a if not id_to_invalid.get(a.id, False)]
|
||||
|
||||
|
||||
def is_normal_user(current_user: CurrentUser):
|
||||
return current_user.id != 1 and (current_user.weight is not None and current_user.weight != 1)
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
from typing import List, Dict
|
||||
|
||||
from apps.datasource.models.datasource import CoreField, CoreDatasource
|
||||
from sqlbot_xpack.permissions.models.ds_permission import PermissionTree, PermissionDTO
|
||||
|
||||
from apps.datasource.models.datasource import CoreField, CoreDatasource
|
||||
from apps.db.constant import DB
|
||||
from common.core.deps import SessionDep
|
||||
|
||||
|
||||
@@ -48,7 +50,8 @@ def transTreeItem(session: SessionDep, item: Dict, ds: CoreDatasource) -> str |
|
||||
if field is None:
|
||||
return None
|
||||
|
||||
whereName = field.field_name
|
||||
db = DB.get_db(ds.type)
|
||||
whereName = db.prefix + field.field_name + db.suffix
|
||||
if item['filter_type'] == 'enum':
|
||||
if len(item['enum_value']) > 0:
|
||||
if ds['type'] == 'sqlServer' and (
|
||||
|
||||
@@ -95,7 +95,7 @@ async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Tran
|
||||
return await user_ws_options(session, current_user.id, trans)
|
||||
|
||||
@router.put("/ws/{oid}")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
|
||||
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
|
||||
if not any(x.id == oid for x in ws_list):
|
||||
@@ -137,7 +137,7 @@ async def create(session: SessionDep, creator: UserCreator):
|
||||
session.commit()
|
||||
|
||||
@router.put("")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="editor.id")
|
||||
async def update(session: SessionDep, editor: UserEditor):
|
||||
user_model: UserModel = get_db_user(session = session, user_id = editor.id)
|
||||
origin_oid: int = user_model.oid
|
||||
@@ -173,7 +173,7 @@ async def batch_del(session: SessionDep, id_list: list[int]):
|
||||
await single_delete(session, id)
|
||||
|
||||
@router.put("/language")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
|
||||
lang = language.language
|
||||
if lang not in ["zh-CN", "en"]:
|
||||
@@ -184,7 +184,7 @@ async def langChange(session: SessionDep, current_user: CurrentUser, language: U
|
||||
session.commit()
|
||||
|
||||
@router.patch("/pwd/{id}")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
|
||||
if not current_user.isAdmin:
|
||||
raise HTTPException('only for admin')
|
||||
@@ -194,7 +194,7 @@ async def pwdReset(session: SessionDep, current_user: CurrentUser, id: int):
|
||||
session.commit()
|
||||
|
||||
@router.put("/pwd")
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def pwdUpdate(session: SessionDep, current_user: CurrentUser, editor: PwdEditor):
|
||||
db_user: UserModel = get_db_user(session=session, user_id=current_user.id)
|
||||
if not verify_md5pwd(editor.pwd, db_user.password):
|
||||
|
||||
@@ -22,7 +22,7 @@ def get_user_by_account(*, session: Session, account: str) -> BaseUserDTO | None
|
||||
return None
|
||||
return BaseUserDTO.model_validate(db_user.model_dump())
|
||||
|
||||
#@cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="user_id")
|
||||
@cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="user_id")
|
||||
async def get_user_info(*, session: Session, user_id: int) -> UserInfoDTO | None:
|
||||
db_user: UserModel = get_db_user(session = session, user_id = user_id)
|
||||
userInfo = UserInfoDTO.model_validate(db_user.model_dump())
|
||||
@@ -58,7 +58,7 @@ async def user_ws_options(session: Session, uid: int, trans: Optional[I18n] = No
|
||||
for id, name in result.all()
|
||||
]
|
||||
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
async def single_delete(session: SessionDep, id: int):
|
||||
user_model: UserModel = get_db_user(session = session, user_id = id)
|
||||
del_stmt = sqlmodel_delete(UserWsModel).where(UserWsModel.uid == id)
|
||||
@@ -66,6 +66,6 @@ async def single_delete(session: SessionDep, id: int):
|
||||
session.delete(user_model)
|
||||
session.commit()
|
||||
|
||||
#@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="id")
|
||||
async def clean_user_cache(id: int):
|
||||
SQLBotLogUtil.info(f"User cache for [{id}] has been cleaned")
|
||||
|
||||
@@ -124,6 +124,8 @@ def cache(
|
||||
result = await func(*args, **kwargs)
|
||||
|
||||
actual_expire = expire + random.randint(-jitter, jitter)
|
||||
if await backend.get(cache_key):
|
||||
await backend.clear(cache_key)
|
||||
await backend.set(cache_key, result, actual_expire)
|
||||
|
||||
SQLBotLogUtil.debug(f"Cache set: {cache_key} (expire: {actual_expire}s)")
|
||||
|
||||
@@ -6,13 +6,11 @@ import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import { userApi } from '@/api/auth'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
defineProps({
|
||||
collapse: { type: [Boolean], required: true },
|
||||
})
|
||||
@@ -46,7 +44,6 @@ const handleDefaultWorkspaceChange = (item: any) => {
|
||||
currentWorkspace.value = { id: item.id, name: item.name }
|
||||
userApi.ws_change(item.id).then(() => {
|
||||
ElMessage.success(t('common.switch_success'))
|
||||
router.push('/chat/index')
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 300)
|
||||
|
||||
@@ -114,38 +114,6 @@ const router = createRouter({
|
||||
component: DashboardPreview,
|
||||
meta: { title: 'DashboardPreview', icon: 'dashboard' },
|
||||
},
|
||||
/* {
|
||||
path: "/setting",
|
||||
component: Layout,
|
||||
redirect: "/setting/index",
|
||||
children: [
|
||||
{
|
||||
path: "index",
|
||||
name: "setting",
|
||||
component: setting,
|
||||
meta: { title: "Settings", icon: "setting" },
|
||||
},
|
||||
],
|
||||
}, */
|
||||
// {
|
||||
// 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: LayoutDsl,
|
||||
|
||||
@@ -26,10 +26,7 @@ export const watchRouter = (router: any) => {
|
||||
if (!userStore.getUid) {
|
||||
await userStore.info()
|
||||
}
|
||||
/* if (!wsCache.get('user.uid')) {
|
||||
await userStore.info()
|
||||
} */
|
||||
if (to.path === '/') {
|
||||
if (to.path === '/' || accessCrossPermission(to)) {
|
||||
next('/chat')
|
||||
return
|
||||
}
|
||||
@@ -41,6 +38,14 @@ export const watchRouter = (router: any) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const accessCrossPermission = (to: any) => {
|
||||
if (!to?.path) return false
|
||||
return (
|
||||
(to.path.startsWith('/system') && !userStore.isAdmin) ||
|
||||
(to.path.startsWith('/set') && !userStore.isSpaceAdmin)
|
||||
)
|
||||
}
|
||||
const loadXpackStatic = () => {
|
||||
if (document.getElementById('sqlbot_xpack_static')) {
|
||||
return Promise.resolve()
|
||||
|
||||
@@ -164,6 +164,10 @@ body {
|
||||
.tip {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.ed-message-box__message {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-content_error {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, shallowRef } from 'vue'
|
||||
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import { useRouter } from 'vue-router'
|
||||
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import { chatApi, ChatInfo } from '@/api/chat.ts'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { datasourceApi } from '@/api/datasource.ts'
|
||||
import DatasourceItemCard from '../ds/DatasourceItemCard.vue'
|
||||
import Card from '@/views/ds/ChatCard.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -13,32 +18,48 @@ const props = withDefaults(
|
||||
}
|
||||
)
|
||||
|
||||
const dsList = ref<Array<any>>([])
|
||||
const router = useRouter()
|
||||
const searchLoading = ref(false)
|
||||
const datasourceConfigvVisible = ref(false)
|
||||
const keywords = ref('')
|
||||
const datasourceList = shallowRef([] as any[])
|
||||
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
|
||||
keywords.value = ''
|
||||
}
|
||||
|
||||
const emits = defineEmits(['onChatCreated'])
|
||||
|
||||
function listDs() {
|
||||
datasourceApi.list().then((res) => {
|
||||
dsList.value = res
|
||||
})
|
||||
searchLoading.value = true
|
||||
datasourceApi
|
||||
.list()
|
||||
.then((res) => {
|
||||
datasourceList.value = res
|
||||
})
|
||||
.finally(() => {
|
||||
searchLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const DatasourceListRef = ref()
|
||||
|
||||
const innerDs = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
function showDs() {
|
||||
listDs()
|
||||
dialogVisible.value = true
|
||||
datasourceConfigvVisible.value = true
|
||||
}
|
||||
|
||||
function hideDs() {
|
||||
innerDs.value = undefined
|
||||
dialogVisible.value = false
|
||||
datasourceConfigvVisible.value = false
|
||||
}
|
||||
|
||||
function selectDsInDialog(ds: any) {
|
||||
@@ -77,9 +98,12 @@ onMounted(() => {
|
||||
if (props.hidden) {
|
||||
return
|
||||
}
|
||||
listDs()
|
||||
})
|
||||
|
||||
const handleAddDatasource = () => {
|
||||
router.push('/ds/index')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDs,
|
||||
hideDs,
|
||||
@@ -88,79 +112,107 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!hidden">
|
||||
<el-drawer
|
||||
ref="DatasourceListRef"
|
||||
v-model="dialogVisible"
|
||||
direction="btt"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
size="100%"
|
||||
>
|
||||
<template #header>
|
||||
<div>
|
||||
<div>Choose Datasource</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar v-loading="loading">
|
||||
<div class="ds-row-container">
|
||||
<template v-for="(item, _index) in dsList" :key="_index">
|
||||
<DatasourceItemCard
|
||||
:ds="item"
|
||||
class="ds-card"
|
||||
:class="[item?.id === innerDs ? 'ds-card-selected' : '']"
|
||||
@click="selectDsInDialog(item)"
|
||||
/>
|
||||
<el-drawer
|
||||
v-model="datasourceConfigvVisible"
|
||||
:close-on-click-modal="false"
|
||||
size="calc(100% - 100px)"
|
||||
modal-class="datasource-drawer-chat"
|
||||
direction="btt"
|
||||
:before-close="beforeClose"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<span style="white-space: nowrap">{{ $t('qa.select_datasource') }}</span>
|
||||
<div class="flex-center" style="width: 100%">
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 320px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :disabled="loading" @click="hideDs">Cancel</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="loading || innerDs === undefined"
|
||||
@click="confirmSelectDs"
|
||||
>
|
||||
Confirm
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-icon style="cursor: pointer" @click="close">
|
||||
<icon_close_outlined></icon_close_outlined>
|
||||
</el-icon>
|
||||
</template>
|
||||
<div v-if="datasourceListWithSearch.length" class="card-content">
|
||||
<Card
|
||||
v-for="ele in datasourceListWithSearch"
|
||||
:id="ele.id"
|
||||
:key="ele.id"
|
||||
:name="ele.name"
|
||||
:type="ele.type"
|
||||
:type-name="ele.type_name"
|
||||
:num="ele.num"
|
||||
:is-selected="ele.id === innerDs"
|
||||
:description="ele.description"
|
||||
@select-ds="selectDsInDialog(ele)"
|
||||
></Card>
|
||||
</div>
|
||||
<template v-if="!keywords && !datasourceListWithSearch.length && !searchLoading">
|
||||
<EmptyBackground
|
||||
class="datasource-yet_btn"
|
||||
:description="$t('datasource.data_source_yet')"
|
||||
img-type="noneWhite"
|
||||
/>
|
||||
|
||||
<div style="text-align: center; margin-top: -10px">
|
||||
<el-button type="primary" @click="handleAddDatasource">
|
||||
<template #icon>
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</template>
|
||||
{{ $t('datasource.new_data_source') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<EmptyBackground
|
||||
v-if="!!keywords && !datasourceListWithSearch.length"
|
||||
:description="$t('datasource.relevant_content_found')"
|
||||
class="datasource-yet"
|
||||
img-type="tree"
|
||||
/>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button secondary :disabled="loading" @click="hideDs">{{
|
||||
$t('common.cancel')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
:type="loading || innerDs === undefined ? 'info' : 'primary'"
|
||||
:disabled="loading || innerDs === undefined"
|
||||
@click="confirmSelectDs"
|
||||
>
|
||||
{{ $t('datasource.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.welcome-content {
|
||||
padding: 12px;
|
||||
}
|
||||
<style lang="less">
|
||||
.datasource-drawer-chat {
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: grey;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.datasource-yet {
|
||||
padding-bottom: 0;
|
||||
height: auto;
|
||||
padding-top: 200px;
|
||||
}
|
||||
|
||||
.ds-select-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ds-row-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.ds-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ds-card-selected {
|
||||
box-shadow: 0 1px 3px var(--ed-color-primary-light-5);
|
||||
border: 1px solid var(--ed-color-primary-light-5);
|
||||
.datasource-yet_btn {
|
||||
height: auto !important;
|
||||
padding-top: 200px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, shallowRef } from 'vue'
|
||||
import icon_close_outlined from '@/assets/svg/operate/ope-close.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import { chatApi, ChatInfo } from '@/api/chat.ts'
|
||||
import { datasourceApi } from '@/api/datasource.ts'
|
||||
import Card from '@/views/ds/ChatCard.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
hidden?: boolean
|
||||
}>(),
|
||||
{
|
||||
hidden: false,
|
||||
}
|
||||
)
|
||||
|
||||
const datasourceConfigvVisible = ref(false)
|
||||
const keywords = ref('')
|
||||
const datasourceList = shallowRef([] as any[])
|
||||
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
|
||||
keywords.value = ''
|
||||
}
|
||||
|
||||
const emits = defineEmits(['onChatCreated'])
|
||||
|
||||
function listDs() {
|
||||
datasourceApi.list().then((res) => {
|
||||
datasourceList.value = res
|
||||
})
|
||||
}
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const innerDs = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
function showDs() {
|
||||
listDs()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function hideDs() {
|
||||
innerDs.value = undefined
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
function selectDsInDialog(ds: any) {
|
||||
innerDs.value = ds.id
|
||||
}
|
||||
|
||||
function confirmSelectDs() {
|
||||
if (innerDs.value) {
|
||||
createChat(innerDs.value)
|
||||
}
|
||||
}
|
||||
|
||||
function createChat(datasource: number) {
|
||||
loading.value = true
|
||||
chatApi
|
||||
.startChat({
|
||||
datasource: datasource,
|
||||
})
|
||||
.then((res) => {
|
||||
const chat: ChatInfo | undefined = chatApi.toChatInfo(res)
|
||||
if (chat == undefined) {
|
||||
throw Error('chat is undefined')
|
||||
}
|
||||
emits('onChatCreated', chat)
|
||||
hideDs()
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.hidden) {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
showDs,
|
||||
hideDs,
|
||||
createChat,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="datasourceConfigvVisible"
|
||||
:close-on-click-modal="false"
|
||||
size="calc(100% - 100px)"
|
||||
modal-class="datasource-drawer-chat"
|
||||
direction="btt"
|
||||
:before-close="beforeClose"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<span style="white-space: nowrap">{{ $t('qa.select_datasource') }}</span>
|
||||
<div class="flex-center" style="width: 100%">
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 320px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-icon style="cursor: pointer" @click="close">
|
||||
<icon_close_outlined></icon_close_outlined>
|
||||
</el-icon>
|
||||
</template>
|
||||
<div class="card-content">
|
||||
<Card
|
||||
v-for="ele in datasourceListWithSearch"
|
||||
:id="ele.id"
|
||||
:key="ele.id"
|
||||
:name="ele.name"
|
||||
:type="ele.type"
|
||||
:type-name="ele.type_name"
|
||||
:num="ele.num"
|
||||
:description="ele.description"
|
||||
@select-ds="selectDsInDialog(ele)"
|
||||
></Card>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button secondary :disabled="loading" @click="hideDs">{{
|
||||
$t('common.cancel')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="loading || innerDs === undefined"
|
||||
@click="confirmSelectDs"
|
||||
>
|
||||
{{ $t('datasource.confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.datasource-drawer-chat {
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
max-height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -412,7 +412,7 @@ const createNewChat = async () => {
|
||||
chatCreatorRef.value?.showDs()
|
||||
}
|
||||
|
||||
function getChatList() {
|
||||
function getChatList(callback?: () => void) {
|
||||
loading.value = true
|
||||
chatApi
|
||||
.list()
|
||||
@@ -421,6 +421,9 @@ function getChatList() {
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -754,10 +757,6 @@ function stop(func?: (...p: any[]) => void, ...param: any[]) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChatList()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
getHistoryList,
|
||||
toAssistantHistory,
|
||||
@@ -767,7 +766,7 @@ defineExpose({
|
||||
|
||||
const hiddenChatCreatorRef = ref()
|
||||
|
||||
onMounted(() => {
|
||||
function jumpCreatChat() {
|
||||
if (props.startChatDsId) {
|
||||
const _id = props.startChatDsId
|
||||
nextTick(() => {
|
||||
@@ -776,6 +775,10 @@ onMounted(() => {
|
||||
const newUrl = window.location.hash.replace(/\?.*$/, '')
|
||||
history.replaceState({}, '', newUrl)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChatList(jumpCreatChat)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
<script lang="ts" setup>
|
||||
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
import { computed } from 'vue'
|
||||
import { dsTypeWithImg } from './js/ds-type'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string
|
||||
type: string
|
||||
typeName: string
|
||||
num: string
|
||||
isSelected: boolean
|
||||
description?: string
|
||||
id?: string
|
||||
}>(),
|
||||
{
|
||||
name: '-',
|
||||
type: '-',
|
||||
description: '-',
|
||||
id: '-',
|
||||
typeName: '-',
|
||||
isSelected: false,
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits(['selectDs'])
|
||||
const icon = computed(() => {
|
||||
return (dsTypeWithImg.find((ele) => props.type === ele.type) || {}).img
|
||||
})
|
||||
|
||||
const SelectDs = () => {
|
||||
emits('selectDs')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card" @click="SelectDs" :class="isSelected && 'is-selected'">
|
||||
<div class="name-icon">
|
||||
<img :src="icon" width="32px" height="32px" />
|
||||
<div class="info">
|
||||
<div :title="name" class="name ellipsis">{{ name }}</div>
|
||||
<div class="type">{{ typeName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :title="description" 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>
|
||||
{{ num }}
|
||||
</div>
|
||||
<div click.stop class="methods"></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;
|
||||
max-width: 250px;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border: 1px solid #1cba90;
|
||||
background: #1cba901a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { ref, onMounted, reactive, nextTick } from 'vue'
|
||||
import {
|
||||
uwsOption,
|
||||
workspaceUserList,
|
||||
@@ -24,7 +24,7 @@ const workspaceForm = reactive({
|
||||
name: '',
|
||||
id: '',
|
||||
})
|
||||
const selectable = (row: any) => ![userStore.getUid].includes(row.id)
|
||||
const selectable = (row: any) => ![userStore.getUid].includes(row.id) && row.weight !== 1
|
||||
|
||||
onMounted(() => {
|
||||
search()
|
||||
@@ -90,17 +90,19 @@ const deleteHandler = (row: any) => {
|
||||
})
|
||||
}
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
const ids = fieldList.value.map((ele: any) => ele.id)
|
||||
if (toggleRowLoading.value) return
|
||||
const arr = fieldList.value.filter(selectable)
|
||||
const ids = arr.map((ele: any) => ele.id)
|
||||
multipleSelectionAll.value = [
|
||||
...multipleSelectionAll.value.filter((ele) => !ids.includes(ele.id)),
|
||||
...val,
|
||||
]
|
||||
isIndeterminate.value = !(val.length === 0 || val.length === fieldList.value.length)
|
||||
checkAll.value = val.length === fieldList.value.length
|
||||
isIndeterminate.value = !(val.length === 0 || val.length === arr.length)
|
||||
checkAll.value = val.length === arr.length
|
||||
}
|
||||
const handleCheckAllChange = (val: any) => {
|
||||
isIndeterminate.value = false
|
||||
handleSelectionChange(val ? fieldList.value : [])
|
||||
handleSelectionChange(val ? fieldList.value.filter(selectable) : [])
|
||||
if (val) {
|
||||
handleToggleRowSelection()
|
||||
} else {
|
||||
@@ -108,17 +110,22 @@ const handleCheckAllChange = (val: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const toggleRowLoading = ref(false)
|
||||
|
||||
const handleToggleRowSelection = (check: boolean = true) => {
|
||||
toggleRowLoading.value = true
|
||||
const arr = fieldList.value.filter(selectable)
|
||||
let i = 0
|
||||
const ids = multipleSelectionAll.value.map((ele: any) => ele.id)
|
||||
for (const key in fieldList.value) {
|
||||
if (ids.includes((fieldList.value[key] as any).id)) {
|
||||
for (const key in arr) {
|
||||
if (ids.includes((arr[key] as any).id)) {
|
||||
i += 1
|
||||
multipleTableRef.value.toggleRowSelection(fieldList.value[key], check)
|
||||
multipleTableRef.value.toggleRowSelection(arr[key], check)
|
||||
}
|
||||
}
|
||||
checkAll.value = i === fieldList.value.length
|
||||
isIndeterminate.value = !(i === 0 || i === fieldList.value.length)
|
||||
toggleRowLoading.value = false
|
||||
checkAll.value = i === arr.length
|
||||
isIndeterminate.value = !(i === 0 || i === arr.length)
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
@@ -127,8 +134,12 @@ const search = () => {
|
||||
pageInfo.currentPage,
|
||||
pageInfo.pageSize
|
||||
).then((res) => {
|
||||
toggleRowLoading.value = true
|
||||
fieldList.value = res.items
|
||||
pageInfo.total = res.total
|
||||
nextTick(() => {
|
||||
handleToggleRowSelection()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -220,11 +231,10 @@ const handleCurrentChange = (val: number) => {
|
||||
<el-table
|
||||
ref="multipleTableRef"
|
||||
:data="fieldList"
|
||||
:selectable="selectable"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column :selectable="selectable" type="selection" width="55" />
|
||||
<el-table-column prop="name" :label="$t('user.name')" width="160" />
|
||||
<el-table-column prop="account" :label="$t('user.account')" width="160" />
|
||||
<el-table-column prop="status" :label="$t('user.user_status')">
|
||||
@@ -288,7 +298,7 @@ const handleCurrentChange = (val: number) => {
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-container">
|
||||
<div v-if="fieldList.length" class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pageInfo.currentPage"
|
||||
v-model:page-size="pageInfo.pageSize"
|
||||
|
||||
@@ -216,9 +216,13 @@ defineExpose({
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.ed-checkbox {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.border-r {
|
||||
border-right: 1px solid #dee0e3;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,7 +38,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const { t } = useI18n()
|
||||
const showDel = ref(false)
|
||||
const keywords = ref('')
|
||||
const activeName = ref({})
|
||||
const activeName = ref()
|
||||
const checklist = ref<string[]>([])
|
||||
const filterList = ref<any[]>([])
|
||||
|
||||
@@ -152,7 +152,22 @@ const emits = defineEmits(['update:item', 'del'])
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-select
|
||||
v-if="['null', 'empty'].includes(item.term)"
|
||||
v-model="item.term"
|
||||
style="max-width: 280px; margin-left: 8px"
|
||||
:placeholder="t('datasource.Please_select')"
|
||||
>
|
||||
<el-option
|
||||
v-for="ele in operators"
|
||||
:key="ele.value"
|
||||
:label="t(ele.label)"
|
||||
:value="ele.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-else
|
||||
v-model="item.value"
|
||||
style="max-width: 280px; margin-left: 8px"
|
||||
:placeholder="$t('datasource.please_enter')"
|
||||
|
||||
@@ -25,8 +25,8 @@ const termFormRef = ref()
|
||||
const columnFormRef = ref()
|
||||
const drawerTitle = ref('')
|
||||
const dialogTitle = ref('')
|
||||
const activeDs = ref({})
|
||||
const activeTable = ref({})
|
||||
const activeDs = ref(null)
|
||||
const activeTable = ref(null)
|
||||
const ruleList = ref<any[]>([])
|
||||
|
||||
const defaultPermission = {
|
||||
@@ -146,8 +146,8 @@ const saveAuthTree = (val: any) => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
const getDsList = (row: any) => {
|
||||
activeDs.value = {}
|
||||
activeTable.value = {}
|
||||
activeDs.value = null
|
||||
activeTable.value = null
|
||||
datasourceApi
|
||||
.list()
|
||||
.then((res: any) => {
|
||||
@@ -211,6 +211,17 @@ const handleColumnPermission = (row: any) => {
|
||||
: t('permission.add_column_permission')
|
||||
}
|
||||
|
||||
const handleInitDsIdChange = (val: any) => {
|
||||
columnForm.ds_id = val.id
|
||||
columnForm.ds_name = val.name
|
||||
datasourceApi.tableList(val.id).then((res: any) => {
|
||||
tableListOptions.value = res || []
|
||||
activeTable.value = null
|
||||
fieldListOptions.value = []
|
||||
columnForm.permissions = []
|
||||
})
|
||||
}
|
||||
|
||||
const handleDsIdChange = (val: any) => {
|
||||
columnForm.ds_id = val.id
|
||||
columnForm.ds_name = val.name
|
||||
@@ -259,6 +270,9 @@ const handleEditeTable = (val: any) => {
|
||||
})
|
||||
}
|
||||
const beforeClose = () => {
|
||||
if (termFormRef.value) {
|
||||
termFormRef.value.clearValidate()
|
||||
}
|
||||
ruleConfigvVisible.value = false
|
||||
activeStep.value = 0
|
||||
isCreate.value = false
|
||||
@@ -398,8 +412,8 @@ const saveHandler = () => {
|
||||
})
|
||||
)
|
||||
}
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -417,7 +431,8 @@ const next = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
const savePermission = () => {
|
||||
|
||||
const save = () => {
|
||||
const { id, name, permissions, users } = cloneDeep(currentPermission)
|
||||
|
||||
const permissionsObj = permissions.map((ele: any) => {
|
||||
@@ -459,6 +474,18 @@ const savePermission = () => {
|
||||
handleSearch()
|
||||
})
|
||||
}
|
||||
const savePermission = () => {
|
||||
if (!isCreate.value && activeStep.value === 0) {
|
||||
termFormRef.value.validate((res: any) => {
|
||||
if (res) {
|
||||
save()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
save()
|
||||
}
|
||||
|
||||
const columnRules = {
|
||||
name: [
|
||||
@@ -471,8 +498,8 @@ const columnRules = {
|
||||
table_id: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('permission.rule_group_name'),
|
||||
trigger: 'blur',
|
||||
message: t('datasource.Please_select') + t('common.empty') + t('permission.set_rule'),
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -485,10 +512,9 @@ const columnRules = {
|
||||
<div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 240px; margin-right: 12px"
|
||||
:placeholder="$t('permission.search_rule_group')"
|
||||
@keyup.enter="handleSearch"
|
||||
@blur="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
@@ -526,7 +552,11 @@ const columnRules = {
|
||||
></Card>
|
||||
</div>
|
||||
<template v-if="!keywords && !ruleListWithSearch.length && !searchLoading">
|
||||
<EmptyBackground :description="$t('permission.no_permission_rule')" img-type="noneWhite" />
|
||||
<EmptyBackground
|
||||
class="ed-empty_200"
|
||||
:description="$t('permission.no_permission_rule')"
|
||||
img-type="noneWhite"
|
||||
/>
|
||||
|
||||
<div style="text-align: center; margin-top: -10px">
|
||||
<el-button type="primary" @click="addHandler()">
|
||||
@@ -579,6 +609,8 @@ const columnRules = {
|
||||
>
|
||||
<el-form-item prop="name" :label="t('permission.rule_group_name')">
|
||||
<el-input
|
||||
maxlength="50"
|
||||
clearable
|
||||
v-model="currentPermission.name"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') +
|
||||
@@ -722,6 +754,8 @@ const columnRules = {
|
||||
<el-form-item prop="name" :label="t('permission.rule_name')">
|
||||
<el-input
|
||||
v-model="columnForm.name"
|
||||
maxlength="50"
|
||||
clearable
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('permission.rule_name')
|
||||
"
|
||||
@@ -731,12 +765,13 @@ const columnRules = {
|
||||
<el-form-item prop="table_id" :label="t('permission.data_table')">
|
||||
<el-select
|
||||
v-model="activeDs"
|
||||
filterable
|
||||
style="width: 416px"
|
||||
value-key="id"
|
||||
:placeholder="
|
||||
$t('datasource.Please_select') + $t('common.empty') + $t('permission.data_source')
|
||||
"
|
||||
@change="handleDsIdChange"
|
||||
@change="handleInitDsIdChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dsListOptions"
|
||||
@@ -747,6 +782,7 @@ const columnRules = {
|
||||
</el-select>
|
||||
<el-select
|
||||
v-model="activeTable"
|
||||
filterable
|
||||
style="width: 416px; margin-left: auto"
|
||||
:disabled="!columnForm.ds_id"
|
||||
value-key="id"
|
||||
@@ -809,7 +845,9 @@ const columnRules = {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.permission {
|
||||
.ed-empty {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.ed-empty_200 {
|
||||
padding-top: 200px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
@@ -864,6 +902,10 @@ const columnRules = {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.ed-form-item__content {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
@@ -871,7 +913,7 @@ const columnRules = {
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 400px);
|
||||
|
||||
&.border-bottom {
|
||||
@@ -929,7 +971,7 @@ const columnRules = {
|
||||
border: 1px solid #1f232926;
|
||||
border-top: none;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 400px);
|
||||
.ed-table__empty-text {
|
||||
padding-top: 0;
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="pagination-container">
|
||||
<div v-if="state.tableData.length" class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="state.pageInfo.currentPage"
|
||||
v-model:page-size="state.pageInfo.pageSize"
|
||||
|
||||
@@ -480,7 +480,7 @@ const handleCurrentChange = (val: number) => {
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-container">
|
||||
<div v-if="fieldListWithSearch.length" class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="pageInfo.currentPage"
|
||||
v-model:page-size="pageInfo.pageSize"
|
||||
@@ -602,6 +602,7 @@ const handleCurrentChange = (val: number) => {
|
||||
|
||||
.list-content {
|
||||
height: calc(100% - 100px);
|
||||
overflow-y: auto;
|
||||
|
||||
.more {
|
||||
display: none;
|
||||
|
||||
Reference in New Issue
Block a user