opti: 优化样式

This commit is contained in:
Wenjie Zhang 2025-06-16 23:08:40 +08:00
parent fbf1c892d2
commit 7f34094e15
6 changed files with 257 additions and 99 deletions

View File

@ -184,8 +184,7 @@ onMounted(() => {
.input-options { .input-options {
display: flex; display: flex;
padding: 8px 0 0; // border-top: 1px solid var(--gray-100);
border-top: 1px solid var(--gray-100);
.options__left, .options__left,
.options__right { .options__right {

View File

@ -57,12 +57,8 @@
<a-collapse-panel <a-collapse-panel
v-for="(results, filename) in groupedKnowledgeResults" v-for="(results, filename) in groupedKnowledgeResults"
:key="filename" :key="filename"
:header="filename" :header="filename + ' (' + results.length + ' chunks) '"
> >
<div class="fileinfo" v-if="results.length > 0">
<p><FileOutlined /> {{ results[0].file.type }}</p>
<p><ClockCircleOutlined /> {{ formatDate(results[0].file.created_at) }}</p>
</div>
<div class="results-list"> <div class="results-list">
<div v-for="res in results" :key="res.id" class="result-item"> <div v-for="res in results" :key="res.id" class="result-item">
<div class="result-meta"> <div class="result-meta">
@ -483,12 +479,12 @@ defineExpose({
.score-info { .score-info {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 2rem;
margin-bottom: 8px; margin-bottom: 8px;
span { span {
display: flex; display: flex;
align-items: center; align-items: center;
margin-right: 2rem;
strong { strong {
margin-right: 8px; margin-right: 8px;

View File

@ -1,48 +1,78 @@
<template> <template>
<div class="user-management"> <div class="user-management">
<h3>用户管理</h3> <!-- 头部区域 -->
<p>用户管理请谨慎操作一旦删除用户该用户将无法登录</p> <div class="header-section">
<a-button type="primary" @click="showAddUserModal"> <div class="header-content">
<template #icon><PlusOutlined /></template> <h3 class="title">用户管理</h3>
添加用户 <p class="description">管理系统用户请谨慎操作删除用户后该用户将无法登录系统</p>
</a-button>
<a-spin :spinning="userManagement.loading">
<div v-if="userManagement.error" class="error-message">
{{ userManagement.error }}
</div> </div>
<a-button type="primary" size="large" @click="showAddUserModal" class="add-btn">
<template #icon><PlusOutlined /></template>
添加用户
</a-button>
</div>
<a-table <!-- 主内容区域 -->
:dataSource="userManagement.users" <div class="content-section">
:columns="userColumns" <a-spin :spinning="userManagement.loading">
rowKey="id" <div v-if="userManagement.error" class="error-message">
:pagination="{ pageSize: 10 }" <a-alert type="error" :message="userManagement.error" show-icon />
> </div>
<!-- 角色列自定义渲染 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'role'">
<a-tag :color="getRoleColor(record.role)">{{ getRoleLabel(record.role) }}</a-tag>
</template>
<!-- 操作列 --> <div class="table-container">
<template v-if="column.key === 'action'"> <a-table
<div class="table-actions"> :dataSource="userManagement.users"
<a-button type="link" @click="showEditUserModal(record)"> :columns="userColumns"
<EditOutlined /> rowKey="id"
</a-button> :pagination="{
<a-button pageSize: 10,
type="link" showSizeChanger: true,
danger showQuickJumper: true,
@click="confirmDeleteUser(record)" showTotal: (total) => `${total} 条记录`
:disabled="record.id === userStore.userId || (record.role === 'superadmin' && userStore.userRole !== 'superadmin')" }"
> class="user-table"
<DeleteOutlined /> bordered
</a-button> >
</div> <!-- 角色列自定义渲染 -->
</template> <template #bodyCell="{ column, record }">
</template> <template v-if="column.key === 'role'">
</a-table> <a-tag :color="getRoleColor(record.role)" class="role-tag">
</a-spin> {{ getRoleLabel(record.role) }}
</a-tag>
</template>
<!-- 时间列格式化 -->
<template v-if="column.key === 'created_at' || column.key === 'last_login'">
<span class="time-text">{{ formatTime(record[column.key]) }}</span>
</template>
<!-- 操作列 -->
<template v-if="column.key === 'action'">
<div class="table-actions">
<a-tooltip title="编辑用户">
<a-button type="text" size="small" @click="showEditUserModal(record)" class="action-btn">
<EditOutlined />
</a-button>
</a-tooltip>
<a-tooltip title="删除用户">
<a-button
type="text"
size="small"
danger
@click="confirmDeleteUser(record)"
:disabled="record.id === userStore.userId || (record.role === 'superadmin' && userStore.userRole !== 'superadmin')"
class="action-btn"
>
<DeleteOutlined />
</a-button>
</a-tooltip>
</div>
</template>
</template>
</a-table>
</div>
</a-spin>
</div>
<!-- 用户表单模态框 --> <!-- 用户表单模态框 -->
<a-modal <a-modal
@ -52,11 +82,18 @@
:confirmLoading="userManagement.loading" :confirmLoading="userManagement.loading"
@cancel="userManagement.modalVisible = false" @cancel="userManagement.modalVisible = false"
:maskClosable="false" :maskClosable="false"
width="480px"
class="user-modal"
> >
<a-form layout="vertical"> <a-form layout="vertical" class="user-form">
<a-form-item label="用户名" required> <a-form-item label="用户名" required class="form-item">
<a-input v-model:value="userManagement.form.username" placeholder="请输入用户名" /> <a-input
v-model:value="userManagement.form.username"
placeholder="请输入用户名"
size="large"
/>
</a-form-item> </a-form-item>
<template v-if="userManagement.editMode"> <template v-if="userManagement.editMode">
<div class="password-toggle"> <div class="password-toggle">
<a-checkbox v-model:checked="userManagement.displayPasswordFields"> <a-checkbox v-model:checked="userManagement.displayPasswordFields">
@ -66,17 +103,25 @@
</template> </template>
<template v-if="!userManagement.editMode || userManagement.displayPasswordFields"> <template v-if="!userManagement.editMode || userManagement.displayPasswordFields">
<a-form-item label="密码" required> <a-form-item label="密码" required class="form-item">
<a-input-password v-model:value="userManagement.form.password" placeholder="请输入密码" /> <a-input-password
v-model:value="userManagement.form.password"
placeholder="请输入密码"
size="large"
/>
</a-form-item> </a-form-item>
<a-form-item label="确认密码" required> <a-form-item label="确认密码" required class="form-item">
<a-input-password v-model:value="userManagement.form.confirmPassword" placeholder="请再次输入密码" /> <a-input-password
v-model:value="userManagement.form.confirmPassword"
placeholder="请再次输入密码"
size="large"
/>
</a-form-item> </a-form-item>
</template> </template>
<a-form-item label="角色"> <a-form-item label="角色" class="form-item">
<a-select v-model:value="userManagement.form.role"> <a-select v-model:value="userManagement.form.role" size="large">
<a-select-option value="user">普通用户</a-select-option> <a-select-option value="user">普通用户</a-select-option>
<a-select-option value="admin" v-if="userStore.isSuperAdmin">管理员</a-select-option> <a-select-option value="admin" v-if="userStore.isSuperAdmin">管理员</a-select-option>
<a-select-option value="superadmin" v-if="userStore.isSuperAdmin">超级管理员</a-select-option> <a-select-option value="superadmin" v-if="userStore.isSuperAdmin">超级管理员</a-select-option>
@ -126,6 +171,19 @@ watch(() => userManagement.displayPasswordFields, (newVal) => {
} }
}); });
//
const formatTime = (timeStr) => {
if (!timeStr) return '-';
const date = new Date(timeStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// //
const fetchUsers = async () => { const fetchUsers = async () => {
try { try {
@ -167,7 +225,7 @@ const showEditUserModal = (user) => {
confirmPassword: '', confirmPassword: '',
role: user.role role: user.role
}; };
userManagement.displayPasswordFields = true; // userManagement.displayPasswordFields = false; //
userManagement.modalVisible = true; userManagement.modalVisible = true;
}; };
@ -175,7 +233,7 @@ const showEditUserModal = (user) => {
const handleUserFormSubmit = async () => { const handleUserFormSubmit = async () => {
try { try {
// //
if (!userManagement.form.username) { if (!userManagement.form.username.trim()) {
notification.error({ message: '用户名不能为空' }); notification.error({ message: '用户名不能为空' });
return; return;
} }
@ -198,7 +256,7 @@ const handleUserFormSubmit = async () => {
if (userManagement.editMode) { if (userManagement.editMode) {
// //
const updateData = { const updateData = {
username: userManagement.form.username, username: userManagement.form.username.trim(),
role: userManagement.form.role role: userManagement.form.role
}; };
@ -211,7 +269,7 @@ const handleUserFormSubmit = async () => {
notification.success({ message: '用户更新成功' }); notification.success({ message: '用户更新成功' });
} else { } else {
await userStore.createUser({ await userStore.createUser({
username: userManagement.form.username, username: userManagement.form.username.trim(),
password: userManagement.form.password, password: userManagement.form.password,
role: userManagement.form.role role: userManagement.form.role
}); });
@ -232,15 +290,6 @@ const handleUserFormSubmit = async () => {
} }
}; };
// 使
const togglePasswordFields = () => {
userManagement.displayPasswordFields = !userManagement.displayPasswordFields;
if (!userManagement.displayPasswordFields) {
userManagement.form.password = '';
userManagement.form.confirmPassword = '';
}
};
// //
const confirmDeleteUser = (user) => { const confirmDeleteUser = (user) => {
// //
@ -284,35 +333,41 @@ const userColumns = [
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
key: 'id', key: 'id',
width: 80 width: 80,
align: 'center'
}, },
{ {
title: '用户名', title: '用户名',
dataIndex: 'username', dataIndex: 'username',
key: 'username' key: 'username',
width: 150
}, },
{ {
title: '角色', title: '角色',
dataIndex: 'role', dataIndex: 'role',
key: 'role', key: 'role',
width: 120 width: 120,
align: 'center'
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'created_at', dataIndex: 'created_at',
key: 'created_at', key: 'created_at',
width: 180 width: 160,
align: 'center'
}, },
{ {
title: '最后登录', title: '最后登录',
dataIndex: 'last_login', dataIndex: 'last_login',
key: 'last_login', key: 'last_login',
width: 180 width: 160,
align: 'center'
}, },
{ {
title: '操作', title: '操作',
key: 'action', key: 'action',
width: 120 width: 100,
align: 'center'
} }
]; ];
@ -346,27 +401,135 @@ onMounted(() => {
.user-management { .user-management {
margin-top: 20px; margin-top: 20px;
h3 { .header-section {
margin-bottom: 10px; display: flex;
justify-content: space-between;
align-items: flex-start;
.header-content {
flex: 1;
.description {
font-size: 14px;
color: #8c8c8c;
margin: 0;
line-height: 1.4;
margin-bottom: 16px;
}
}
} }
p { .content-section {
margin-bottom: 20px; overflow: hidden;
}
.error-message { .error-message {
color: red; padding: 16px 24px;
margin: 10px 0; }
}
.table-actions { .table-container {
button { .user-table {
padding: 0 4px; :deep(.ant-table-thead > tr > th) {
background: #fafafa;
font-weight: 600;
color: #262626;
border-bottom: 2px solid #f0f0f0;
}
:deep(.ant-table-tbody > tr > td) {
padding: 12px 16px;
border-bottom: 1px solid #f5f5f5;
}
:deep(.ant-table-tbody > tr:hover > td) {
background: #f8f9ff;
}
:deep(.ant-pagination) {
margin: 16px 24px;
}
}
} }
} }
.password-toggle { .role-tag {
margin-bottom: 16px; font-weight: 500;
border-radius: 4px;
padding: 2px 8px;
}
.time-text {
font-size: 13px;
color: #595959;
}
.table-actions {
display: flex;
gap: 4px;
justify-content: center;
.action-btn {
width: 32px;
height: 32px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
&:hover {
transform: scale(1.1);
}
&.ant-btn-dangerous:hover {
background: #fff2f0;
border-color: #ffccc7;
color: #ff4d4f;
}
}
}
}
.user-modal {
:deep(.ant-modal-header) {
padding: 20px 24px;
border-bottom: 1px solid #f0f0f0;
.ant-modal-title {
font-size: 16px;
font-weight: 600;
color: #262626;
}
}
:deep(.ant-modal-body) {
padding: 24px;
}
.user-form {
.form-item {
margin-bottom: 20px;
:deep(.ant-form-item-label) {
padding-bottom: 4px;
label {
font-weight: 500;
color: #262626;
}
}
}
.password-toggle {
margin-bottom: 16px;
:deep(.ant-checkbox-wrapper) {
font-weight: 500;
color: #495057;
}
}
} }
} }
</style> </style>

View File

@ -76,6 +76,7 @@
> >
<p v-if="value.description" class="description">{{ value.description }}</p> <p v-if="value.description" class="description">{{ value.description }}</p>
<!-- key匹配 -->
<div v-if="key === 'model'" class="agent-model"> <div v-if="key === 'model'" class="agent-model">
<p><small>注意部分模型对于 Tool Calling 的支持不稳定建议采用{{ value.options }} </small></p> <p><small>注意部分模型对于 Tool Calling 的支持不稳定建议采用{{ value.options }} </small></p>
<ModelSelectorComponent <ModelSelectorComponent
@ -84,16 +85,18 @@
:model_provider="agentConfig[key] ? agentConfig[key].split('/')[0] : ''" :model_provider="agentConfig[key] ? agentConfig[key].split('/')[0] : ''"
/> />
</div> </div>
<a-switch
v-else-if="typeof agentConfig[key] === 'boolean'"
v-model:checked="agentConfig[key]"
/>
<a-textarea <a-textarea
v-else-if="key === 'system_prompt'" v-else-if="key === 'system_prompt'"
v-model:value="agentConfig[key]" v-model:value="agentConfig[key]"
:rows="4" :rows="4"
:placeholder="getPlaceholder(key, value)" :placeholder="getPlaceholder(key, value)"
/> />
<!-- 数据类型匹配 -->
<a-switch
v-else-if="typeof agentConfig[key] === 'boolean'"
v-model:checked="agentConfig[key]"
/>
<a-select <a-select
v-else-if="value?.options && value?.type === 'str'" v-else-if="value?.options && value?.type === 'str'"
v-model:value="agentConfig[key]" v-model:value="agentConfig[key]"

View File

@ -3,9 +3,6 @@
<HeaderComponent title="文档知识库" :loading="state.loading"> <HeaderComponent title="文档知识库" :loading="state.loading">
<template #actions> <template #actions>
<a-button type="primary" @click="newDatabase.open=true"> <a-button type="primary" @click="newDatabase.open=true">
<template #icon>
<BookPlus size="16" />
</template>
新建知识库 新建知识库
</a-button> </a-button>
</template> </template>

View File

@ -250,7 +250,7 @@ onMounted(() => {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 600; font-weight: 600;
color: white; color: white;
background: linear-gradient(135deg, var(--main-color), var(--main-color-dark)); background: linear-gradient(135deg, var(--main-500), var(--main-600));
border: none; border: none;
border-radius: 3rem; border-radius: 3rem;
cursor: pointer; cursor: pointer;
@ -260,7 +260,7 @@ onMounted(() => {
&:hover { &:hover {
transform: translateY(-3px); transform: translateY(-3px);
box-shadow: 0 7px 20px rgba(0, 0, 0, 0.15); box-shadow: 0 7px 20px rgba(0, 0, 0, 0.15);
background: linear-gradient(135deg, #5a7dc5, #203156); background: linear-gradient(135deg, var(--main-600), var(--main-700));
} }
&:active { &:active {