feat(router): 添加工具路由,更新聊天和智能体相关功能,优化工具获取逻辑
This commit is contained in:
parent
770d098f05
commit
75c52a3855
@ -4,6 +4,7 @@ from server.routers.auth_router import auth
|
||||
from server.routers.chat_router import chat
|
||||
from server.routers.knowledge_router import knowledge
|
||||
from server.routers.graph_router import graph
|
||||
from server.routers.tool_router import tool
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -13,3 +14,4 @@ router.include_router(auth) # /api/auth/*
|
||||
router.include_router(chat) # /api/chat/*
|
||||
router.include_router(knowledge) # /api/knowledge/*
|
||||
router.include_router(graph) # /api/graph/*
|
||||
router.include_router(tool) # /api/tool/*
|
||||
|
||||
@ -15,7 +15,7 @@ from src.knowledge import HistoryManager
|
||||
from src.agents import agent_manager
|
||||
from src.models import select_model
|
||||
from src.utils.logging_config import logger
|
||||
from src.agents.tools_factory import get_all_tools
|
||||
from src.agents.tools_factory import get_runnable_tools
|
||||
from server.routers.auth_router import get_admin_user
|
||||
from server.utils.auth_middleware import get_required_user, get_db
|
||||
from server.models.user_model import User
|
||||
@ -44,9 +44,13 @@ async def get_default_agent(current_user: User = Depends(get_required_user)):
|
||||
raise HTTPException(status_code=500, detail=f"获取默认智能体出错: {str(e)}")
|
||||
|
||||
@chat.post("/set_default_agent")
|
||||
async def set_default_agent(agent_id: str = Body(..., embed=True), current_user = Depends(get_admin_user)):
|
||||
async def set_default_agent(request_data: dict = Body(...), current_user = Depends(get_admin_user)):
|
||||
"""设置默认智能体ID (仅管理员)"""
|
||||
try:
|
||||
agent_id = request_data.get("agent_id")
|
||||
if not agent_id:
|
||||
raise HTTPException(status_code=422, detail="缺少必需的 agent_id 字段")
|
||||
|
||||
# 验证智能体是否存在
|
||||
agents = await agent_manager.get_agents_info()
|
||||
agent_ids = [agent.get("id", "") for agent in agents]
|
||||
@ -70,11 +74,6 @@ async def set_default_agent(agent_id: str = Body(..., embed=True), current_user
|
||||
# > === 对话分组 ===
|
||||
# =============================================================================
|
||||
|
||||
@chat.get("/")
|
||||
async def chat_get(current_user: User = Depends(get_required_user)):
|
||||
"""聊天服务健康检查(需要登录)"""
|
||||
return "Chat Get!"
|
||||
|
||||
@chat.post("/call")
|
||||
async def call(query: str = Body(...), meta: dict = Body(None), current_user: User = Depends(get_required_user)):
|
||||
"""调用模型进行简单问答(需要登录)"""
|
||||
@ -183,7 +182,7 @@ async def update_chat_models(model_provider: str, model_names: list[str], curren
|
||||
@chat.get("/tools")
|
||||
async def get_tools(current_user: User = Depends(get_admin_user)):
|
||||
"""获取所有可用工具(需要登录)"""
|
||||
return {"tools": list(get_all_tools().keys())}
|
||||
return {"tools": list(get_runnable_tools().keys())}
|
||||
|
||||
@chat.post("/agent/{agent_id}/config")
|
||||
async def save_agent_config(
|
||||
|
||||
15
server/routers/tool_router.py
Normal file
15
server/routers/tool_router.py
Normal file
@ -0,0 +1,15 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from src.agents.tools_factory import get_all_tools_info
|
||||
from server.models.user_model import User
|
||||
from server.utils.auth_middleware import get_required_user
|
||||
|
||||
tool = chat = APIRouter(prefix="/tool", tags=["tool"])
|
||||
|
||||
@tool.get("/tools")
|
||||
async def get_tools(current_user: User = Depends(get_required_user)):
|
||||
"""获取所有可用工具的信息"""
|
||||
try:
|
||||
tools_info = get_all_tools_info()
|
||||
return {"tools": tools_info}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
@ -3,7 +3,7 @@ import uuid
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from src.agents.registry import Configuration
|
||||
from src.agents.tools_factory import get_all_tools
|
||||
from src.agents.tools_factory import get_runnable_tools
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ChatbotConfiguration(Configuration):
|
||||
@ -37,7 +37,7 @@ class ChatbotConfiguration(Configuration):
|
||||
default_factory=list,
|
||||
metadata={
|
||||
"name": "工具",
|
||||
"options": list(get_all_tools().keys()), # 这里的选择是所有的工具
|
||||
"options": list(get_runnable_tools().keys()), # 这里的选择是所有的工具
|
||||
"description": "工具列表"
|
||||
},
|
||||
)
|
||||
|
||||
@ -15,12 +15,11 @@ from src.utils import logger
|
||||
from src.agents.registry import State, BaseAgent
|
||||
from src.agents.utils import load_chat_model, get_cur_time_with_utc
|
||||
from src.agents.chatbot.configuration import ChatbotConfiguration
|
||||
from src.agents.tools_factory import get_all_tools
|
||||
from src.agents.tools_factory import get_runnable_tools
|
||||
|
||||
class ChatbotAgent(BaseAgent):
|
||||
name = "对话机器人(Chatbot)"
|
||||
description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。"
|
||||
requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"]
|
||||
config_schema = ChatbotConfiguration
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@ -34,7 +33,7 @@ class ChatbotAgent(BaseAgent):
|
||||
默认不使用任何工具。
|
||||
如果配置为列表,则使用列表中的工具。
|
||||
"""
|
||||
platform_tools = get_all_tools()
|
||||
platform_tools = get_runnable_tools()
|
||||
if tools is None or not isinstance(tools, list) or len(tools) == 0:
|
||||
# 默认不使用任何工具
|
||||
logger.info("未配置工具或配置为空,不使用任何工具")
|
||||
@ -68,7 +67,7 @@ class ChatbotAgent(BaseAgent):
|
||||
|
||||
workflow = StateGraph(State, config_schema=self.config_schema)
|
||||
workflow.add_node("chatbot", self.llm_call)
|
||||
workflow.add_node("tools", ToolNode(tools=list(get_all_tools().values())))
|
||||
workflow.add_node("tools", ToolNode(tools=list(get_runnable_tools().values())))
|
||||
workflow.add_edge(START, "chatbot")
|
||||
workflow.add_conditional_edges(
|
||||
"chatbot",
|
||||
|
||||
@ -8,6 +8,7 @@ export * from './system_api' // 系统管理API
|
||||
export * from './knowledge_api' // 知识库管理API
|
||||
export * from './auth_api' // 认证API
|
||||
export * from './graph_api' // 图谱API
|
||||
export * from './tools.js' // 工具API
|
||||
|
||||
// 导出基础工具函数
|
||||
export { apiRequest, apiGet, apiPost, apiPut, apiDelete } from './base'
|
||||
@ -29,5 +30,8 @@ export { apiRequest, apiGet, apiPost, apiPut, apiDelete } from './base'
|
||||
* 4. graph_api.js: 图谱API
|
||||
* - 知识图谱相关功能
|
||||
*
|
||||
* 5. tools.js: 工具API
|
||||
* - 工具信息获取
|
||||
*
|
||||
* 注意:API模块已处理权限验证和请求头,使用时无需再手动添加认证头
|
||||
*/
|
||||
18
web/src/apis/tools.js
Normal file
18
web/src/apis/tools.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { apiGet } from './base'
|
||||
|
||||
/**
|
||||
* 工具管理API模块
|
||||
* 包含工具信息获取等功能
|
||||
*/
|
||||
|
||||
// =============================================================================
|
||||
// === 工具信息分组 ===
|
||||
// =============================================================================
|
||||
|
||||
export const toolsApi = {
|
||||
/**
|
||||
* 获取所有可用工具的信息
|
||||
* @returns {Promise} - 工具信息列表
|
||||
*/
|
||||
getTools: () => apiGet('/api/tool/tools', {}, true)
|
||||
}
|
||||
@ -69,20 +69,10 @@
|
||||
>
|
||||
<div class="conf-content">
|
||||
<div class="agent-info">
|
||||
<h3>详细配置信<span @click="toggleDebugMode">息</span></h3>
|
||||
<p>详细配置信<span @click="toggleDebugMode">息</span></p>
|
||||
<p>{{ selectedAgent.description }}</p>
|
||||
<pre v-if="state.debug_mode">{{ selectedAgent }}</pre>
|
||||
|
||||
<!-- 添加requirements显示部分 -->
|
||||
<div v-if="agents[selectedAgentId]?.requirements && agents[selectedAgentId]?.requirements.length > 0" class="info-section">
|
||||
<h3>所需环境变量:</h3>
|
||||
<div class="requirements-list">
|
||||
<a-tag v-for="req in agents[selectedAgentId].requirements" :key="req">
|
||||
{{ req }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<div v-if="selectedAgentId && configSchema" class="config-modal-content">
|
||||
@ -111,7 +101,7 @@
|
||||
<a-textarea
|
||||
v-else-if="key === 'system_prompt'"
|
||||
v-model:value="agentConfig[key]"
|
||||
:rows="4"
|
||||
:rows="2"
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
/>
|
||||
|
||||
@ -129,6 +119,25 @@
|
||||
{{ option.label || option }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<!-- 工具选择特殊处理 -->
|
||||
<div v-else-if="key === 'tools'" class="tools-selector">
|
||||
<div class="tools-summary">
|
||||
<div class="tools-summary-left">
|
||||
<span class="tools-count">已选择 {{ getSelectedCount(key) }} 个工具</span>
|
||||
<a-button type="link" size="small" @click="clearSelection(key)" v-if="getSelectedCount(key) > 0">
|
||||
清空
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="primary" @click="openToolsModal" class="select-tools-btn">
|
||||
选择工具
|
||||
</a-button>
|
||||
</div>
|
||||
<div v-if="getSelectedCount(key) > 0" class="selected-tools-preview">
|
||||
<a-tag v-for="toolId in agentConfig[key]" :key="toolId" closable @close="removeSelectedTool(toolId)">
|
||||
{{ getToolNameById(toolId) }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 多选 -->
|
||||
<div v-else-if="value?.options && value?.type === 'list'" class="multi-select-cards">
|
||||
<div class="multi-select-label">
|
||||
@ -149,7 +158,7 @@
|
||||
@click="toggleOption(key, option)"
|
||||
>
|
||||
<div class="option-content">
|
||||
<span class="option-text">{{ option }}</span>
|
||||
<span class="option-text">{{ getToolNameById(toolId) }}</span>
|
||||
<div class="option-indicator">
|
||||
<CheckCircleOutlined v-if="isOptionSelected(key, option)" />
|
||||
<PlusCircleOutlined v-else />
|
||||
@ -198,6 +207,56 @@
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 工具选择弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="state.toolsModalOpen"
|
||||
title="选择工具"
|
||||
:width="800"
|
||||
:footer="null"
|
||||
:maskClosable="false"
|
||||
class="tools-modal"
|
||||
>
|
||||
<div class="tools-modal-content">
|
||||
<div class="tools-search">
|
||||
<a-input
|
||||
v-model:value="toolsSearchText"
|
||||
placeholder="搜索工具..."
|
||||
allow-clear
|
||||
/>
|
||||
</div>
|
||||
<div class="tools-list">
|
||||
<div
|
||||
v-for="tool in filteredTools"
|
||||
:key="tool.id"
|
||||
class="tool-item"
|
||||
:class="{ 'selected': selectedTools.includes(tool.id) }"
|
||||
@click="toggleToolSelection(tool.id)"
|
||||
>
|
||||
<div class="tool-content">
|
||||
<div class="tool-header">
|
||||
<span class="tool-name">{{ tool.name }}</span>
|
||||
<div class="tool-indicator">
|
||||
<CheckCircleOutlined v-if="selectedTools.includes(tool.id)" />
|
||||
<PlusCircleOutlined v-else />
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-description">{{ tool.description }}</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tools-modal-footer">
|
||||
<div class="selected-count">
|
||||
已选择 {{ selectedTools.length }} 个工具
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<a-button @click="cancelToolsSelection">取消</a-button>
|
||||
<a-button type="primary" @click="confirmToolsSelection">确认</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 中间内容区域 -->
|
||||
<div class="content">
|
||||
<AgentChatComponent
|
||||
@ -230,6 +289,7 @@ import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { chatApi } from '@/apis/auth_api';
|
||||
import { agentConfigApi } from '@/apis/system_api';
|
||||
import { toolsApi } from '@/apis/tools';
|
||||
|
||||
// 路由
|
||||
const router = useRouter();
|
||||
@ -242,12 +302,36 @@ const defaultAgentId = ref(null); // 存储默认智能体ID
|
||||
const state = reactive({
|
||||
agentConfOpen: false,
|
||||
debug_mode: false,
|
||||
toolsModalOpen: false,
|
||||
isEmptyConfig: computed(() =>
|
||||
!selectedAgentId.value ||
|
||||
Object.keys(configurableItems.value).length === 0
|
||||
)
|
||||
});
|
||||
|
||||
// 工具相关状态
|
||||
const availableTools = ref([]);
|
||||
const selectedTools = ref([]);
|
||||
const toolsSearchText = ref('');
|
||||
|
||||
// 过滤后的工具列表
|
||||
const filteredTools = computed(() => {
|
||||
if (!toolsSearchText.value) {
|
||||
return availableTools.value;
|
||||
}
|
||||
const searchLower = toolsSearchText.value.toLowerCase();
|
||||
return availableTools.value.filter(tool =>
|
||||
tool.name.toLowerCase().includes(searchLower) ||
|
||||
tool.description.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
// 根据工具ID获取工具名称
|
||||
const getToolNameById = (toolId) => {
|
||||
const tool = availableTools.value.find(t => t.id === toolId);
|
||||
return tool ? tool.name : toolId;
|
||||
};
|
||||
|
||||
const selectedAgent = computed(() => agents.value[selectedAgentId.value] || {});
|
||||
const configSchema = computed(() => selectedAgent.value.config_schema || {});
|
||||
const configurableItems = computed(() => {
|
||||
@ -321,6 +405,53 @@ const clearSelection = (key) => {
|
||||
agentConfig.value[key] = [];
|
||||
};
|
||||
|
||||
// 工具选择相关方法
|
||||
const openToolsModal = async () => {
|
||||
try {
|
||||
// 加载可用工具列表
|
||||
const response = await toolsApi.getTools();
|
||||
// 将工具对象转换为数组格式,保留id和name信息
|
||||
availableTools.value = Object.values(response.tools || {});
|
||||
|
||||
// 初始化已选择的工具
|
||||
selectedTools.value = [...(agentConfig.value.tools || [])];
|
||||
|
||||
// 打开弹窗
|
||||
state.toolsModalOpen = true;
|
||||
} catch (error) {
|
||||
console.error('加载工具列表失败:', error);
|
||||
message.error('加载工具列表失败');
|
||||
}
|
||||
};
|
||||
|
||||
const toggleToolSelection = (toolId) => {
|
||||
const index = selectedTools.value.indexOf(toolId);
|
||||
if (index > -1) {
|
||||
selectedTools.value.splice(index, 1);
|
||||
} else {
|
||||
selectedTools.value.push(toolId);
|
||||
}
|
||||
};
|
||||
|
||||
const removeSelectedTool = (toolId) => {
|
||||
const index = agentConfig.value.tools.indexOf(toolId);
|
||||
if (index > -1) {
|
||||
agentConfig.value.tools.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmToolsSelection = () => {
|
||||
agentConfig.value.tools = [...selectedTools.value];
|
||||
state.toolsModalOpen = false;
|
||||
toolsSearchText.value = '';
|
||||
};
|
||||
|
||||
const cancelToolsSelection = () => {
|
||||
state.toolsModalOpen = false;
|
||||
toolsSearchText.value = '';
|
||||
selectedTools.value = [];
|
||||
};
|
||||
|
||||
// 获取默认智能体ID
|
||||
const fetchDefaultAgent = async () => {
|
||||
try {
|
||||
@ -536,7 +667,7 @@ const toggleConf = () => {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
--agent-view-header-height: 60px;
|
||||
--agent-view-header-height: 45px;
|
||||
}
|
||||
|
||||
.agent-view-header {
|
||||
@ -597,27 +728,7 @@ const toggleConf = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加requirements相关样式
|
||||
.info-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.requirements-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.ant-tag {
|
||||
user-select: all;
|
||||
}
|
||||
}
|
||||
|
||||
.agent-model {
|
||||
width: 100%;
|
||||
@ -712,6 +823,211 @@ const toggleConf = () => {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
// 工具选择器样式(与项目风格一致)
|
||||
.tools-selector {
|
||||
.tools-summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
// margin-bottom: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--gray-50);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--gray-200);
|
||||
font-size: 14px;
|
||||
color: var(--gray-700);
|
||||
transition: border-color 0.2s ease;
|
||||
|
||||
.tools-summary-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.tools-count {
|
||||
color: var(--gray-900);
|
||||
}
|
||||
}
|
||||
|
||||
.select-tools-btn {
|
||||
background: var(--main-color);
|
||||
border: none;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 4px 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
height: 28px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--main-600);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-tools-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 0;
|
||||
background: none;
|
||||
border: none;
|
||||
min-height: 32px;
|
||||
:deep(.ant-tag) {
|
||||
margin: 0;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
background: var(--gray-100);
|
||||
border: 1px solid var(--gray-300);
|
||||
color: var(--gray-900);
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
.anticon-close {
|
||||
color: var(--gray-600);
|
||||
margin-left: 4px;
|
||||
&:hover {
|
||||
color: var(--gray-900);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 工具选择弹窗样式(与项目风格一致)
|
||||
.tools-modal {
|
||||
:deep(.ant-modal-content) {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.ant-modal-header) {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
padding: 16px 20px;
|
||||
.ant-modal-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
}
|
||||
}
|
||||
:deep(.ant-modal-body) {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
}
|
||||
.tools-modal-content {
|
||||
.tools-search {
|
||||
margin-bottom: 16px;
|
||||
:deep(.ant-input) {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--gray-300);
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
&:focus {
|
||||
border-color: var(--main-color);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tools-list {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--gray-200);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
.tool-item {
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, border 0.2s;
|
||||
border-left: 3px solid transparent;
|
||||
&:last-child { border-bottom: none; }
|
||||
&:hover {
|
||||
background: var(--gray-50);
|
||||
}
|
||||
&.selected {
|
||||
background: var(--main-10);
|
||||
border-left: 3px solid var(--main-color);
|
||||
}
|
||||
.tool-content {
|
||||
.tool-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
.tool-name {
|
||||
font-weight: 500;
|
||||
color: var(--gray-900);
|
||||
font-size: 14px;
|
||||
}
|
||||
.tool-indicator { display: none; }
|
||||
}
|
||||
.tool-description {
|
||||
font-size: 13px;
|
||||
color: var(--gray-700);
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.tools-modal-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0 0 0;
|
||||
border-top: 1px solid var(--gray-200);
|
||||
.selected-count {
|
||||
font-size: 13px;
|
||||
color: var(--gray-700);
|
||||
background: none;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
:deep(.ant-btn) {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
padding: 6px 18px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
&.ant-btn-default {
|
||||
border: 1px solid var(--gray-300);
|
||||
color: var(--gray-900);
|
||||
background: #fff;
|
||||
&:hover {
|
||||
border-color: var(--main-color);
|
||||
color: var(--main-color);
|
||||
background: var(--main-10);
|
||||
}
|
||||
}
|
||||
&.ant-btn-primary {
|
||||
background: var(--main-color);
|
||||
border: none;
|
||||
color: #fff;
|
||||
&:hover {
|
||||
background: var(--main-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 多选卡片样式
|
||||
.multi-select-cards {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user