mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: User Management
This commit is contained in:
@@ -1,9 +1,27 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from common.core.deps import CurrentUser
|
||||
from apps.system.models.user import user_grid
|
||||
from common.core.deps import CurrentUser, SessionDep
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
|
||||
router = APIRouter(tags=["user"], prefix="/user")
|
||||
|
||||
|
||||
@router.get("/info")
|
||||
async def user_info(current_user: CurrentUser):
|
||||
return current_user.to_dict()
|
||||
return current_user.to_dict()
|
||||
|
||||
|
||||
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[user_grid])
|
||||
async def pager(
|
||||
session: SessionDep,
|
||||
pageNum: int,
|
||||
pageSize: int
|
||||
):
|
||||
pagination = PaginationParams(page=pageNum, size=pageSize)
|
||||
paginator = Paginator(session)
|
||||
filters = {}
|
||||
return await paginator.get_paginated_response(
|
||||
model=user_grid,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
@@ -1,12 +1,14 @@
|
||||
|
||||
from sqlmodel import Session, select
|
||||
from ..models.user import sys_user
|
||||
from ..models.user import sys_user, user_grid
|
||||
from common.core.security import verify_md5pwd
|
||||
|
||||
def get_user_by_account(*, session: Session, account: str) -> sys_user | None:
|
||||
statement = select(sys_user).where(sys_user.account == account)
|
||||
#statement = select(sys_user).where(sys_user.account == account)
|
||||
statement = select(user_grid.id, user_grid.account, user_grid.oid, user_grid.password).where(user_grid.account == account)
|
||||
session_user = session.exec(statement).first()
|
||||
return session_user
|
||||
result_user = sys_user.model_validate(session_user)
|
||||
return result_user
|
||||
|
||||
def authenticate(*, session: Session, account: str, password: str) -> sys_user | None:
|
||||
db_user = get_user_by_account(session=session, account=account)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
from typing import Optional
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
class sys_user(SQLModel, table=True):
|
||||
class sys_user(SQLModel):
|
||||
id: int = Field(primary_key=True, index=True)
|
||||
account: str = Field(max_length=255, unique=True)
|
||||
password: str = Field(max_length=255)
|
||||
@@ -12,4 +13,12 @@ class sys_user(SQLModel, table=True):
|
||||
"id": self.id,
|
||||
"account": self.account,
|
||||
"oid": self.oid
|
||||
}
|
||||
}
|
||||
|
||||
class user_grid(sys_user, table=True):
|
||||
__tablename__ = "sys_user"
|
||||
name: str
|
||||
email: str
|
||||
status: int
|
||||
create_time: int
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ from fastapi import Depends, HTTPException, status
|
||||
# from fastapi.security import OAuth2PasswordBearer
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlmodel import Session
|
||||
from sqlmodel import Session, select
|
||||
from common.core.schemas import TokenPayload, XOAuth2PasswordBearer
|
||||
from common.core import security
|
||||
from common.core.config import settings
|
||||
from common.core.db import get_session
|
||||
from apps.system.models.user import sys_user
|
||||
from apps.system.models.user import sys_user, user_grid
|
||||
reusable_oauth2 = XOAuth2PasswordBearer(
|
||||
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
|
||||
)
|
||||
@@ -32,7 +32,11 @@ async def get_current_user(session: SessionDep, token: TokenDep) -> sys_user:
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Could not validate credentials",
|
||||
)
|
||||
user = session.get(sys_user, token_data.id)
|
||||
statement = select(user_grid.id, user_grid.account, user_grid.oid, user_grid.password).where(user_grid.id == token_data.id)
|
||||
session_user = session.exec(statement).first()
|
||||
if not session_user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
user = sys_user.model_validate(session_user)
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
""" if not user.is_active:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const userApi = {
|
||||
pager: (pageNumber: number, pageSize: number) => request.get(`/user/pager/${pageNumber}/${pageSize}`),
|
||||
add: (data: any) => request.post('/settings/terminology', data),
|
||||
edit: (data: any) => request.put('/settings/terminology', data),
|
||||
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
|
||||
query: (id: number) => request.get(`/settings/terminology/${id}`)
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
<h1>{{ currentPageTitle }}</h1>
|
||||
<div class="header-actions">
|
||||
<el-tooltip content="System manage" placement="bottom">
|
||||
<div class="header-icon-btn">
|
||||
<div class="header-icon-btn" @click="toSystem">
|
||||
<el-icon><iconsystem /></el-icon>
|
||||
<span>System manage</span>
|
||||
</div>
|
||||
@@ -77,8 +77,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="sysRouterList.length && showHead" class="sys-setting-container">
|
||||
<el-menu
|
||||
:default-active="activeMenu"
|
||||
class="el-menu-demo"
|
||||
mode="horizontal"
|
||||
>
|
||||
<el-menu-item v-for="item in sysRouterList" :key="item.path" :index="item.path" @click="menuSelect">
|
||||
<el-icon v-if="item.meta.icon">
|
||||
<component :is="resolveIcon(item.meta.icon)" />
|
||||
</el-icon>
|
||||
<span>{{ item.meta.title }}</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
<div class="page-content">
|
||||
<div v-if="sysRouterList.length && showHead" class="sys-page-content">
|
||||
<div class="sys-inner-container">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="page-content">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,19 +124,31 @@ const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
const name = ref('admin')
|
||||
const activeMenu = computed(() => route.path)
|
||||
|
||||
const routerList = computed(() => {
|
||||
return router.getRoutes().filter(route => {
|
||||
return route.path !== '/login' && !route.redirect && route.path !== '/:pathMatch(.*)*'
|
||||
return route.path !== '/login' && !route.path.includes('/system') && !route.redirect && route.path !== '/:pathMatch(.*)*'
|
||||
})
|
||||
})
|
||||
|
||||
const sysRouterList = computed(() => {
|
||||
return router.getRoutes().filter(route => route.path.includes('/system'))
|
||||
})
|
||||
|
||||
const showHead = computed(() => {
|
||||
return route.path.includes('/system')
|
||||
})
|
||||
const workspace = ref('1')
|
||||
const options = [
|
||||
{ value: '1', label: 'Default workspace' },
|
||||
{ value: '2', label: 'Workspace 2' },
|
||||
{ value: '3', label: 'Workspace 3' }
|
||||
]
|
||||
const currentPageTitle = computed(() => route.meta.title || 'Dashboard')
|
||||
const currentPageTitle = computed(() => {
|
||||
if (route.path.includes('/system')) {
|
||||
return 'System Settings'
|
||||
}
|
||||
return route.meta.title || 'Dashboard'
|
||||
})
|
||||
const resolveIcon = (iconName: any) => {
|
||||
const icons: Record<string, any> = {
|
||||
'ds': ds,
|
||||
@@ -129,13 +160,15 @@ const resolveIcon = (iconName: any) => {
|
||||
}
|
||||
|
||||
const menuSelect = (e: any) => {
|
||||
console.log(routerList.value)
|
||||
router.push(e.index)
|
||||
}
|
||||
const logout = () => {
|
||||
userStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
const toSystem = () => {
|
||||
router.push('/system')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -260,6 +293,23 @@ const logout = () => {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.sys-page-content {
|
||||
background-color: var(--white);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 24px;
|
||||
box-shadow: var(--shadow);
|
||||
margin-top: 24px;
|
||||
.sys-inner-container {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
.sys-setting-container {
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -66,6 +66,25 @@ const router = createRouter({
|
||||
meta: { title: 'Setting', icon: 'setting' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/system',
|
||||
component: Layout,
|
||||
redirect: '/system/user',
|
||||
children: [
|
||||
{
|
||||
path: 'user',
|
||||
name: 'user',
|
||||
component: () => import('@/views/system/user/index.vue'),
|
||||
meta: { title: 'User Management', icon: 'setting' }
|
||||
},
|
||||
{
|
||||
path: 'model',
|
||||
name: 'model',
|
||||
component: () => import('@/views/system/model/index.vue'),
|
||||
meta: { title: 'AI Model Configuration', icon: 'setting' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
export function formatTimestamp(timestamp: number, format: string = 'YYYY-MM-DD'): string {
|
||||
if (!timestamp) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const date = new Date(timestamp)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
|
||||
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, (match) => {
|
||||
switch (match) {
|
||||
case 'YYYY':
|
||||
return String(year)
|
||||
case 'MM':
|
||||
return month
|
||||
case 'DD':
|
||||
return day
|
||||
case 'HH':
|
||||
return hours
|
||||
case 'mm':
|
||||
return minutes
|
||||
case 'ss':
|
||||
return seconds
|
||||
default:
|
||||
return match
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class HttpService {
|
||||
}
|
||||
|
||||
// Request logging
|
||||
console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`)
|
||||
// console.log(`[Request] ${config.method?.toUpperCase()} ${config.url}`)
|
||||
|
||||
return config
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="login-form">
|
||||
<el-card class="login-card">
|
||||
<h2 class="login-title">Login</h2>
|
||||
<el-form :model="loginForm" :rules="rules" ref="loginFormRef">
|
||||
<el-form :model="loginForm" :rules="rules" ref="loginFormRef" @keyup.enter="submitForm">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="loginForm.username" placeholder="username" prefix-icon="user"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<h2>this is ai model page</h2>
|
||||
</template>
|
||||
@@ -0,0 +1,366 @@
|
||||
<template>
|
||||
<div class="sqlbot-table-container professional-container">
|
||||
<div class="sqlbot-tool">
|
||||
<div class="tool-left">
|
||||
<el-input
|
||||
v-model="keyword"
|
||||
class="sqlbot-search"
|
||||
placeholder="Search"
|
||||
prefix-icon="el-icon-search"
|
||||
@keyup.enter.native="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><search /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<!-- <div class="tool-right">
|
||||
<div class="tool-btn secondary-btn">
|
||||
<el-icon><IconOpeUpload /></el-icon>
|
||||
<span>Import Terminology</span>
|
||||
</div>
|
||||
<div class="tool-btn secondary-btn">
|
||||
<el-icon><IconOpeDownload /></el-icon>
|
||||
<span>Export Terminology</span>
|
||||
</div>
|
||||
<div class="tool-btn primary-btn" @click="openDialog">
|
||||
<el-icon><IconOpeAdd /></el-icon>
|
||||
<span>Add Terminology</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="sqlbot-table">
|
||||
<el-table :data="state.tableData" style="width: 100%">
|
||||
<el-table-column prop="account" label="Account" width="280" />
|
||||
<el-table-column prop="name" label="Name" width="280" />
|
||||
<el-table-column prop="email" label="Email" width="280" />
|
||||
<el-table-column prop="status" label="Status" width="180">
|
||||
<template #default="scope">
|
||||
<div class="user-status-container" :class="[ scope.row.status ? 'active' : 'disabled' ]">
|
||||
<el-icon>
|
||||
<SuccessFilled v-if="scope.row.status"/>
|
||||
<CircleCloseFilled v-else />
|
||||
</el-icon>
|
||||
<span>{{ scope.row.status ? 'Activated' : 'Disabled' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="Create time">
|
||||
<template #default="scope">
|
||||
<span>{{ formatTimestamp(scope.row.create_time) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="id" label="Actions" width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<div class="table-operate">
|
||||
<div class="opt-btn" @click="editHandler(scope.row.id)">
|
||||
<el-icon><IconOpeEdit /></el-icon>
|
||||
</div>
|
||||
<div class="opt-btn" @click="deleteHandler(scope.row.id)">
|
||||
<el-icon><IconOpeDelete /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" title="Add Terminology" destroy-on-close width="500" @close="onFormClose" class="sqlbot-dialog">
|
||||
<el-form :model="state.form"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
class="sqlbot-form"
|
||||
size="large"
|
||||
ref="termFormRef"
|
||||
>
|
||||
<el-form-item label="Account" >
|
||||
<el-input v-model="state.form.term" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Definition" >
|
||||
<el-input type="textarea" v-model="state.form.definition" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Domain">
|
||||
<el-select v-model="state.form.domain" placeholder="Please select a domain">
|
||||
<el-option label="domain1" value="domain1" />
|
||||
<el-option label="domain2" value="domain2" />
|
||||
<el-option label="domain3" value="domain3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeForm">Cancel</el-button>
|
||||
<el-button type="primary" @click="saveHandler">
|
||||
save
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
/* import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg';
|
||||
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg';
|
||||
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'; */
|
||||
|
||||
import { SuccessFilled, CircleCloseFilled } from '@element-plus/icons-vue'
|
||||
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg';
|
||||
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg';
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { userApi } from '@/api/auth'
|
||||
import { formatTimestamp } from '@/utils/date'
|
||||
|
||||
const keyword = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const termFormRef = ref()
|
||||
const state = reactive({
|
||||
tableData: [
|
||||
],
|
||||
form: {
|
||||
id: '',
|
||||
term: '',
|
||||
definition: '',
|
||||
domain: ''
|
||||
},
|
||||
})
|
||||
const handleSearch = (e: any) => {
|
||||
console.log('search', e);
|
||||
}
|
||||
const editHandler = (id: any) => {
|
||||
/* userApi.query(id).then((res: any) => {
|
||||
console.log('term detail', res)
|
||||
state.form = res
|
||||
dialogFormVisible.value = true
|
||||
}) */
|
||||
}
|
||||
const deleteHandler = (id: any) => {
|
||||
ElMessageBox.confirm(
|
||||
'Are you sure to delete?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
/* userApi.delete(id).then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Delete completed',
|
||||
})
|
||||
search()
|
||||
}) */
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: 'Delete canceled',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDialog = () => {
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const resetForm = () => {
|
||||
if (!termFormRef) return
|
||||
Object.keys(state.form).forEach((key) => {
|
||||
state.form[key as keyof typeof state.form] = ''
|
||||
})
|
||||
}
|
||||
|
||||
const closeForm = () => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
const onFormClose = () => {
|
||||
resetForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
userApi.pager(1, 20).then((res: any) => {
|
||||
state.tableData = res.items
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
userApi.add(state.form).then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Add completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
userApi.edit(state.form).then((res: any) => {
|
||||
console.log('edit term', res)
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Edit completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
const saveHandler = () => {
|
||||
if (state.form.id) {
|
||||
editTerm()
|
||||
} else {
|
||||
addTerm()
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sqlbot-table-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.sqlbot-tool {
|
||||
height: 42px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
.tool-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.sqlbot-search {
|
||||
width: 300px;
|
||||
--el-input-inner-height: 40px !important;
|
||||
--el-input-border-radius: 24px;
|
||||
--el-input-bg-color: #f5f7fa;
|
||||
--el-input-border-color: #e5e8ed;
|
||||
}
|
||||
}
|
||||
.tool-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 12px;
|
||||
height: 35px;
|
||||
.secondary-btn {
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.primary-btn {
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
}
|
||||
.tool-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
gap: 6px;
|
||||
&:hover {
|
||||
background-color: #f1f3f4;
|
||||
}
|
||||
&.default {
|
||||
color: #2d2e31;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
i {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-table {
|
||||
border-radius: 6px;
|
||||
:deep(.el-table) {
|
||||
--el-table-header-bg-color: #f5f7fa;
|
||||
--el-table-border-color: #ebeef5;
|
||||
--el-table-header-text-color: #606266;
|
||||
|
||||
th {
|
||||
font-weight: 600;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
td {
|
||||
height: 52px;
|
||||
}
|
||||
}
|
||||
.table-operate {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
.opt-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #5f6368;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: #f1f3f4;
|
||||
border: none;
|
||||
font-size: 18px;
|
||||
&:hover {
|
||||
background-color: #e8f0fe;
|
||||
color: #4285f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-form {
|
||||
width: 100%;
|
||||
.el-form-item {
|
||||
margin-bottom: 16px;
|
||||
text-align: left;
|
||||
label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-dialog {
|
||||
width: 500px;
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
text-align: left !important;
|
||||
font-size: 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-status-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
padding: 4px 8px;
|
||||
width: fit-content;
|
||||
border-radius: 4px;
|
||||
&.active {
|
||||
background-color: #e6f7ee;
|
||||
color: #389e0d;
|
||||
}
|
||||
&.disabled {
|
||||
background-color: #fff1f0;
|
||||
color: #cf1322;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user