mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: AI Model
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
"""004_add_aimodel_auto
|
||||
|
||||
Revision ID: 8fe654655905
|
||||
Revises: d116056121c3
|
||||
Create Date: 2025-05-07 15:51:34.768842
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8fe654655905'
|
||||
down_revision = 'd116056121c3'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('ai_model',
|
||||
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('type', sa.Integer(), nullable=False),
|
||||
sa.Column('api_key', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
|
||||
sa.Column('endpoint', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
|
||||
sa.Column('max_context_window', sa.Integer(), nullable=False),
|
||||
sa.Column('temperature', sa.Float(), nullable=False),
|
||||
sa.Column('status', sa.Boolean(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True),
|
||||
sa.Column('create_time', sa.BigInteger(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index(op.f('ix_ai_model_id'), 'ai_model', ['id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_ai_model_id'), table_name='ai_model')
|
||||
op.drop_table('ai_model')
|
||||
# ### end Alembic commands ###
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from apps.system.api import login, user
|
||||
from apps.system.api import login, user, aimodel
|
||||
from apps.settings.api import terminology
|
||||
from apps.datasource.api import datasource
|
||||
|
||||
@@ -8,6 +8,7 @@ from apps.datasource.api import datasource
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(login.router)
|
||||
api_router.include_router(user.router)
|
||||
api_router.include_router(aimodel.router)
|
||||
api_router.include_router(terminology.router)
|
||||
api_router.include_router(datasource.router)
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter
|
||||
from sqlmodel import select
|
||||
from apps.system.models.system_modle import AiModelDetail
|
||||
from apps.system.schemas.system_schema import model_status
|
||||
from common.core.deps import SessionDep
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
from common.utils.time import get_timestamp
|
||||
|
||||
router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel")
|
||||
|
||||
|
||||
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[AiModelDetail])
|
||||
async def pager(
|
||||
session: SessionDep,
|
||||
pageNum: int,
|
||||
pageSize: int
|
||||
):
|
||||
pagination = PaginationParams(page=pageNum, size=pageSize)
|
||||
paginator = Paginator(session)
|
||||
filters = {}
|
||||
return await paginator.get_paginated_response(
|
||||
model=AiModelDetail,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
|
||||
@router.get("/{id}", response_model=AiModelDetail)
|
||||
async def get_model_by_id(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.get(AiModelDetail, id)
|
||||
return term
|
||||
|
||||
@router.post("", response_model=AiModelDetail)
|
||||
async def add_model(
|
||||
session: SessionDep,
|
||||
creator: AiModelDetail
|
||||
):
|
||||
data = AiModelDetail.model_validate(creator)
|
||||
data.create_time = get_timestamp()
|
||||
session.add(data)
|
||||
session.commit()
|
||||
return creator
|
||||
|
||||
@router.put("", response_model=AiModelDetail)
|
||||
async def update_terminology(
|
||||
session: SessionDep,
|
||||
model: AiModelDetail
|
||||
):
|
||||
model.id = int(model.id)
|
||||
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == model.id)).first()
|
||||
update_data = model.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(term, field, value)
|
||||
session.add(term)
|
||||
session.commit()
|
||||
return model
|
||||
|
||||
@router.delete("/{id}", response_model=AiModelDetail)
|
||||
async def delete_terminology(
|
||||
session: SessionDep,
|
||||
id: int
|
||||
):
|
||||
term = session.exec(select(AiModelDetail).where(AiModelDetail.id == id)).first()
|
||||
session.delete(term)
|
||||
session.commit()
|
||||
return {
|
||||
"message": f"AiModel with ID {id} deleted successfully."
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/status", response_model=dict)
|
||||
async def status(session: SessionDep, dto: model_status):
|
||||
ids = dto.ids
|
||||
status = dto.status
|
||||
if not ids:
|
||||
return {"message": "ids is empty"}
|
||||
statement = select(AiModelDetail).where(AiModelDetail.id.in_(ids))
|
||||
terms = session.exec(statement).all()
|
||||
for term in terms:
|
||||
term.status = status
|
||||
session.add(term)
|
||||
session.commit()
|
||||
return {"message": f"AiModel with IDs {ids} updated successfully."}
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
from sqlmodel import BigInteger, Field, SQLModel
|
||||
from common.core.models import SnowflakeBase
|
||||
|
||||
|
||||
class AiModelBase:
|
||||
name: str = Field(max_length=255, nullable=False)
|
||||
type: int = Field(nullable=False)
|
||||
|
||||
class AiModelDetail(AiModelBase, SnowflakeBase, table=True):
|
||||
__tablename__ = "ai_model"
|
||||
api_key: str | None = Field(max_length=255, nullable=True)
|
||||
endpoint: str = Field(max_length=255, nullable=False)
|
||||
max_context_window: int = Field(default=0)
|
||||
temperature: float = Field(default=0.0)
|
||||
status: bool = Field(default=True)
|
||||
description: str | None = Field(max_length=255, nullable=True)
|
||||
create_time: int = Field(default=0, sa_type=BigInteger())
|
||||
@@ -0,0 +1,5 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class model_status(BaseModel):
|
||||
status: bool
|
||||
ids: list[int]
|
||||
@@ -13,21 +13,7 @@ class SnowflakeBase(SQLModel):
|
||||
nullable=False
|
||||
)
|
||||
|
||||
@field_validator("id", mode="before")
|
||||
def validate_id(cls, v):
|
||||
if isinstance(v, str):
|
||||
if not v:
|
||||
return None
|
||||
try:
|
||||
return int(v)
|
||||
except ValueError:
|
||||
raise ValueError("Invalid bigint string")
|
||||
elif isinstance(v, int):
|
||||
return v
|
||||
raise TypeError("BigInt must be int or string")
|
||||
|
||||
|
||||
class Config:
|
||||
json_encoders = {
|
||||
int: lambda v: str(v) if v > 2**53-1 else v
|
||||
int: lambda v: str(v) if isinstance(v, int) and v > (2**53 - 1) else v
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_timestamp():
|
||||
dt_millis = int(datetime.now().timestamp() * 1000)
|
||||
return dt_millis
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
usage() {
|
||||
echo -e "${GREEN}Usage:${NC} $0 [\"migration message\"]"
|
||||
echo -e "Example: $0 \"Added user table\""
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo -e "${RED}Error:${NC} No migration message provided"
|
||||
usage
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Generating migration with message:${NC} \"$1\""
|
||||
alembic revision --autogenerate -m "$1"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}Migration created successfully!${NC}"
|
||||
else
|
||||
echo -e "${RED}Error:${NC} Failed to create migration"
|
||||
exit 1
|
||||
fi
|
||||
Vendored
+3
@@ -11,6 +11,7 @@ declare module 'vue' {
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
@@ -22,7 +23,9 @@ declare module 'vue' {
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const modelApi = {
|
||||
pager: (pageNumber: number, pageSize: number) => request.get(`/system/aimodel/pager/${pageNumber}/${pageSize}`),
|
||||
add: (data: any) => request.post('/system/aimodel', data),
|
||||
edit: (data: any) => request.put('/system/aimodel', data),
|
||||
delete: (id: number) => request.delete(`/system/aimodel/${id}`),
|
||||
query: (id: number) => request.get(`/system/aimodel/${id}`),
|
||||
status: (data: any) => request.patch('/system/aimodel/status', data),
|
||||
}
|
||||
@@ -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 class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 0a512 512 0 1 0 0 1024A512 512 0 0 0 512 0zM113.777778 512a398.222222 398.222222 0 0 1 398.222222-398.222222c89.429333 0 171.690667 29.809778 238.193778 79.587555L193.422222 750.136889A395.377778 395.377778 0 0 1 113.777778 512z m398.222222 398.222222a395.719111 395.719111 0 0 1-238.136889-79.587555L830.577778 273.806222A396.060444 396.060444 0 0 1 910.222222 512a398.222222 398.222222 0 0 1-398.222222 398.222222z" fill="#1E2330" /></svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -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 class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 0a512 512 0 1 0 0 1024A512 512 0 0 0 512 0z m0 910.222222A398.222222 398.222222 0 1 1 512 113.777778a398.222222 398.222222 0 0 1 0 796.444444z" fill="#1E2330" /><path d="M448.056889 584.305778L322.389333 458.638222 243.541333 537.543111l205.596445 205.539556h0.056889l0.512 0.512 325.859555-325.859556-80.440889-80.440889z" fill="#1E2330" /></svg>
|
||||
|
After Width: | Height: | Size: 619 B |
@@ -83,7 +83,7 @@ class HttpService {
|
||||
// Response interceptor
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
console.log(`[Response] ${response.config.url}`, response.data)
|
||||
// console.log(`[Response] ${response.config.url}`, response.data)
|
||||
|
||||
// Return raw response if configured
|
||||
if ((response.config as FullRequestConfig).requestOptions?.rawResponse) {
|
||||
@@ -164,8 +164,7 @@ class HttpService {
|
||||
// ElMessage.error(errorMessage)
|
||||
ElMessage({
|
||||
message: errorMessage,
|
||||
type: 'error',
|
||||
duration: 0
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,517 @@
|
||||
<template>
|
||||
<h2>this is ai model page</h2>
|
||||
</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"
|
||||
@keyup.enter.native="handleSearch"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon"><IconSearch /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="tool-right">
|
||||
<div class="tool-btn secondary-btn" @click="batchEnableHandler">
|
||||
<el-icon><IconEnable /></el-icon>
|
||||
<span>Batch Enable</span>
|
||||
</div>
|
||||
<div class="tool-btn secondary-btn" @click="batchDisableHandler">
|
||||
<el-icon><IconDisable /></el-icon>
|
||||
<span>Batch Disable</span>
|
||||
</div>
|
||||
<div class="tool-btn primary-btn" @click="openDialog">
|
||||
<el-icon><IconOpeAdd /></el-icon>
|
||||
<span>Add Model</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sqlbot-table">
|
||||
<el-table :data="state.tableData" style="width: 100%" row-key="id" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="name" label="Model Name" width="280" />
|
||||
<el-table-column prop="type" label="Model Type" width="280" />
|
||||
<el-table-column prop="api_key" label="API KEY" width="280">
|
||||
<template #default="scope">
|
||||
<div class="user-status-container" :class="[ scope.row.api_key ? 'active' : 'disabled' ]">
|
||||
<span>{{ scope.row.api_key ? 'Configured' : 'Not Configured' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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 ? 'Enabled' : '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, 'YYYY-MM-DD HH:mm:ss') }}</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 class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="state.pageInfo.currentPage"
|
||||
v-model:page-size="state.pageInfo.pageSize"
|
||||
:page-sizes="[10, 20, 30]"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="state.pageInfo.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" title="Add Ai Model" 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="Model Name" >
|
||||
<el-input v-model="state.form.name" autocomplete="off" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Model Type">
|
||||
<el-select v-model="state.form.type" placeholder="Please select a type">
|
||||
<el-option label="domain1" value="0" />
|
||||
<el-option label="domain2" value="1" />
|
||||
<el-option label="domain3" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="API KEY" >
|
||||
<el-input type="password" v-model="state.form.api_key" show-password placeholder="Please input password" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="API Endpoint URL" >
|
||||
<el-input v-model="state.form.endpoint" placeholder="Please input endpoint" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Maximum Context Window" >
|
||||
<el-input v-model="state.form.max_context_window" type="number" placeholder="Please input max_context_window" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Temperature" >
|
||||
<el-slider v-model="state.form.temperature" :format-tooltip="formatTooltip" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Model Status" >
|
||||
<el-checkbox v-model="state.form.status" label="" size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="Description" >
|
||||
<el-input type="textarea" v-model="state.form.description" />
|
||||
</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 IconEnable from '@/assets/svg/operate/ope-enable.svg';
|
||||
import IconDisable from '@/assets/svg/operate/ope-disable.svg';
|
||||
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg';
|
||||
|
||||
import { SuccessFilled, CircleCloseFilled, Search as IconSearch } from '@element-plus/icons-vue'
|
||||
import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg';
|
||||
import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg';
|
||||
import { modelApi } from '@/api/system'
|
||||
import { formatTimestamp } from '@/utils/date'
|
||||
|
||||
const keyword = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const termFormRef = ref()
|
||||
const state = reactive({
|
||||
tableData: [
|
||||
],
|
||||
form: {
|
||||
id: '',
|
||||
name: '',
|
||||
type: 0,
|
||||
api_key: '',
|
||||
endpoint: '',
|
||||
max_context_window: 0,
|
||||
temperature: 0,
|
||||
status: false,
|
||||
description: ''
|
||||
},
|
||||
pageInfo: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
},
|
||||
selectedIds: []
|
||||
})
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
const handleSearch = (e: any) => {
|
||||
console.log('search', e);
|
||||
}
|
||||
const editHandler = (id: any) => {
|
||||
modelApi.query(id).then((res: any) => {
|
||||
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(() => {
|
||||
modelApi.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] = null
|
||||
})
|
||||
}
|
||||
|
||||
const closeForm = () => {
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
const onFormClose = () => {
|
||||
resetForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
modelApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
|
||||
state.tableData = res.items
|
||||
state.pageInfo.total = res.total
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
const param: any = {
|
||||
...state.form,
|
||||
}
|
||||
delete param.id
|
||||
modelApi.add(param).then(() => {
|
||||
dialogFormVisible.value = false
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Add completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
const editTerm = () => {
|
||||
modelApi.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()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSizeChange = (val: number) => {
|
||||
state.pageInfo.pageSize = val
|
||||
search()
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
state.pageInfo.currentPage = val
|
||||
search()
|
||||
}
|
||||
const formatTooltip = (val: number) => {
|
||||
return val / 100
|
||||
}
|
||||
const handleSelectionChange = (rows: any) => {
|
||||
state.selectedIds = rows.map((item: any) => item.id)
|
||||
}
|
||||
|
||||
const batchDisableHandler = () => {
|
||||
if (state.selectedIds.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: 'Please select at least one item',
|
||||
})
|
||||
return
|
||||
}
|
||||
const param = {
|
||||
ids: state.selectedIds,
|
||||
status: false
|
||||
}
|
||||
|
||||
ElMessageBox.confirm(
|
||||
'Are you sure to execute this operation?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
modelApi.status(param).then(() => {
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Batch disable completed',
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: 'Execute canceled',
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
const batchEnableHandler = () => {
|
||||
if (state.selectedIds.length === 0) {
|
||||
ElMessage({
|
||||
type: 'warning',
|
||||
message: 'Please select at least one item',
|
||||
})
|
||||
return
|
||||
}
|
||||
const param = {
|
||||
ids: state.selectedIds,
|
||||
status: true
|
||||
}
|
||||
modelApi.status(param).then(() => {
|
||||
search()
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: 'Batch enable completed',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
</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;
|
||||
background-color: var(--white);
|
||||
padding: 16px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
.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;
|
||||
}
|
||||
/* border-left: 1px solid var(--el-table-border-color);
|
||||
border-right: 1px solid var(--el-table-border-color); */
|
||||
}
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
background-color: var(--white);
|
||||
padding: 16px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.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>
|
||||
|
||||
@@ -63,6 +63,19 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:current-page="state.pageInfo.currentPage"
|
||||
v-model:page-size="state.pageInfo.pageSize"
|
||||
:page-sizes="[10, 20, 30]"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="state.pageInfo.total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -124,6 +137,11 @@ const state = reactive({
|
||||
definition: '',
|
||||
domain: ''
|
||||
},
|
||||
pageInfo: {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
}
|
||||
})
|
||||
const handleSearch = (e: any) => {
|
||||
console.log('search', e);
|
||||
@@ -182,8 +200,9 @@ const onFormClose = () => {
|
||||
}
|
||||
|
||||
const search = () => {
|
||||
userApi.pager(1, 20).then((res: any) => {
|
||||
userApi.pager(state.pageInfo.currentPage, state.pageInfo.pageSize).then((res: any) => {
|
||||
state.tableData = res.items
|
||||
state.pageInfo.total = res.total
|
||||
})
|
||||
}
|
||||
const addTerm = () => {
|
||||
@@ -214,6 +233,14 @@ const saveHandler = () => {
|
||||
addTerm()
|
||||
}
|
||||
}
|
||||
const handleSizeChange = (val: number) => {
|
||||
state.pageInfo.pageSize = val
|
||||
search()
|
||||
}
|
||||
const handleCurrentChange = (val: number) => {
|
||||
state.pageInfo.currentPage = val
|
||||
search()
|
||||
}
|
||||
onMounted(() => {
|
||||
search()
|
||||
})
|
||||
@@ -228,7 +255,11 @@ onMounted(() => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
background-color: var(--white);
|
||||
padding: 16px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 20px;
|
||||
.tool-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -295,11 +326,14 @@ onMounted(() => {
|
||||
td {
|
||||
height: 52px;
|
||||
}
|
||||
border-left: 1px solid var(--el-table-border-color);
|
||||
border-right: 1px solid var(--el-table-border-color);
|
||||
}
|
||||
.table-operate {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
|
||||
.opt-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -319,6 +353,16 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
background-color: var(--white);
|
||||
padding: 16px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-form {
|
||||
|
||||
Reference in New Issue
Block a user