feat: 优化共享设置界面,使用卡片式布局提升用户体验
This commit is contained in:
parent
bba9bc974f
commit
204e627839
@ -1,22 +1,40 @@
|
||||
<template>
|
||||
<div class="share-config-form">
|
||||
<div class="share-config-content">
|
||||
<div class="share-mode">
|
||||
<a-radio-group v-model:value="config.is_shared" class="share-mode-radio">
|
||||
<a-radio :value="true">全员共享</a-radio>
|
||||
<a-radio :value="false">指定部门</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="share-mode-cards" role="radiogroup" aria-label="共享设置">
|
||||
<button
|
||||
v-for="option in shareModeOptions"
|
||||
:key="option.value"
|
||||
type="button"
|
||||
role="radio"
|
||||
class="share-mode-card"
|
||||
:class="{ active: config.is_shared === option.value }"
|
||||
:aria-checked="config.is_shared === option.value"
|
||||
:tabindex="config.is_shared === option.value ? 0 : -1"
|
||||
@click="setShareMode(option.value)"
|
||||
@keydown.enter.prevent="setShareMode(option.value)"
|
||||
@keydown.space.prevent="setShareMode(option.value)"
|
||||
>
|
||||
<div class="card-icon-wrapper" aria-hidden="true">
|
||||
<component :is="option.icon" class="card-icon" :size="20" />
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="card-title">{{ option.title }}</div>
|
||||
<div class="card-description">{{ option.description }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<p class="share-hint">
|
||||
{{ config.is_shared ? '所有用户都可以访问' : '只有指定部门的用户可以访问' }}
|
||||
</p>
|
||||
<!-- 部门选择 -->
|
||||
<div v-if="!config.is_shared" class="dept-selection">
|
||||
<div v-if="!config.is_shared" class="compact-dept-selection">
|
||||
<div class="dept-selection-header">
|
||||
<div class="dept-selection-label">可访问部门</div>
|
||||
<div class="dept-selection-hint">请选择可访问该知识库的部门</div>
|
||||
</div>
|
||||
<a-select
|
||||
v-model:value="config.accessible_department_ids"
|
||||
mode="multiple"
|
||||
placeholder="请选择可访问的部门"
|
||||
style="width: 100%"
|
||||
placeholder="请选择部门"
|
||||
size="small"
|
||||
class="full-width dept-selection-input"
|
||||
:options="departmentOptions"
|
||||
/>
|
||||
</div>
|
||||
@ -26,12 +44,28 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { Globe, Building2 } from 'lucide-vue-next'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { departmentApi } from '@/apis/department_api'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const departments = ref([])
|
||||
|
||||
const shareModeOptions = [
|
||||
{
|
||||
value: true,
|
||||
title: '公开给团队',
|
||||
description: '团队内所有成员都可以访问这个知识库',
|
||||
icon: Globe
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
title: '仅指定部门',
|
||||
description: '只有选中的部门成员可以访问这个知识库',
|
||||
icon: Building2
|
||||
}
|
||||
]
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
@ -84,6 +118,13 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const setShareMode = (isShared) => {
|
||||
if (config.is_shared === isShared) {
|
||||
return
|
||||
}
|
||||
config.is_shared = isShared
|
||||
}
|
||||
|
||||
// 监听共享模式变化
|
||||
watch(
|
||||
() => config.is_shared,
|
||||
@ -190,32 +231,146 @@ defineExpose({
|
||||
|
||||
<style lang="less" scoped>
|
||||
.share-config-form {
|
||||
h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.share-config-content {
|
||||
background: var(--gray-25);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--gray-150);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
border-radius: 10px;
|
||||
|
||||
.share-mode {
|
||||
.share-mode-radio {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
.share-mode-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.share-hint {
|
||||
font-size: 13px;
|
||||
color: var(--gray-600);
|
||||
margin: 8px 0 0 0;
|
||||
.share-mode-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 76px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--gray-200);
|
||||
border-radius: 10px;
|
||||
background: var(--gray-0);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
border-color: var(--main-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--main-20);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--main-color);
|
||||
background: var(--main-10);
|
||||
box-shadow: 0 0 0 1px var(--main-20);
|
||||
}
|
||||
|
||||
.card-icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 10px;
|
||||
background: var(--gray-50);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
color: var(--gray-500);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 3px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--gray-800);
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
&.active .card-icon-wrapper {
|
||||
background: var(--main-0);
|
||||
}
|
||||
|
||||
&.active .card-icon {
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.card-description {
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
}
|
||||
|
||||
.dept-selection {
|
||||
margin-top: 12px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.compact-dept-selection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(180deg, var(--gray-0) 0%, var(--gray-50) 100%);
|
||||
}
|
||||
|
||||
.dept-selection-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.dept-selection-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--gray-800);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.dept-selection-hint {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.dept-selection-input {
|
||||
:deep(.ant-select-selector) {
|
||||
min-height: 36px;
|
||||
padding: 3px 8px !important;
|
||||
border-radius: 8px !important;
|
||||
border-color: var(--gray-200) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&:deep(.ant-select-focused .ant-select-selector),
|
||||
&:deep(.ant-select-selector:hover) {
|
||||
border-color: var(--main-color) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,117 +40,121 @@
|
||||
width="800px"
|
||||
destroyOnClose
|
||||
>
|
||||
<!-- 知识库类型选择 -->
|
||||
<h3>知识库类型<span style="color: var(--color-error-500)">*</span></h3>
|
||||
<div class="kb-type-cards">
|
||||
<div
|
||||
v-for="(typeInfo, typeKey) in orderedKbTypes"
|
||||
:key="typeKey"
|
||||
class="kb-type-card"
|
||||
:class="{ active: newDatabase.kb_type === typeKey }"
|
||||
:data-type="typeKey"
|
||||
@click="handleKbTypeChange(typeKey)"
|
||||
>
|
||||
<div class="card-header">
|
||||
<component :is="getKbTypeIcon(typeKey)" class="type-icon" />
|
||||
<span class="type-title">{{ getKbTypeLabel(typeKey) }}</span>
|
||||
<div class="new-database-form">
|
||||
<!-- 知识库类型选择 -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">
|
||||
知识库类型<span class="required-mark">*</span>
|
||||
</h3>
|
||||
<div class="kb-type-cards">
|
||||
<div
|
||||
v-for="(typeInfo, typeKey) in orderedKbTypes"
|
||||
:key="typeKey"
|
||||
class="kb-type-card"
|
||||
:class="{ active: newDatabase.kb_type === typeKey }"
|
||||
:data-type="typeKey"
|
||||
@click="handleKbTypeChange(typeKey)"
|
||||
>
|
||||
<div class="card-header">
|
||||
<component :is="getKbTypeIcon(typeKey)" class="type-icon" />
|
||||
<span class="type-title">{{ getKbTypeLabel(typeKey) }}</span>
|
||||
</div>
|
||||
<div class="card-description">{{ typeInfo.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-description">{{ typeInfo.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 类型说明 -->
|
||||
<!-- <div class="kb-type-guide" v-if="newDatabase.kb_type">
|
||||
<a-alert
|
||||
:message="getKbTypeDescription(newDatabase.kb_type)"
|
||||
:type="getKbTypeAlertType(newDatabase.kb_type)"
|
||||
show-icon
|
||||
style="margin: 12px 0;"
|
||||
/>
|
||||
</div> -->
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">知识库名称<span class="required-mark">*</span></h3>
|
||||
<a-input v-model:value="newDatabase.name" placeholder="新建知识库名称" />
|
||||
</div>
|
||||
|
||||
<h3>知识库名称<span style="color: var(--color-error-500)">*</span></h3>
|
||||
<a-input v-model:value="newDatabase.name" placeholder="新建知识库名称" size="large" />
|
||||
<div v-if="newDatabase.kb_type !== 'dify'" class="form-grid two-columns">
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">嵌入模型</h3>
|
||||
<EmbeddingModelSelector
|
||||
v-model:value="newDatabase.embed_model_name"
|
||||
class="full-width"
|
||||
placeholder="请选择嵌入模型"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template v-if="newDatabase.kb_type !== 'dify'">
|
||||
<h3>嵌入模型</h3>
|
||||
<EmbeddingModelSelector
|
||||
v-model:value="newDatabase.embed_model_name"
|
||||
style="width: 100%"
|
||||
size="large"
|
||||
placeholder="请选择嵌入模型"
|
||||
/>
|
||||
</template>
|
||||
<div class="form-section compact-section">
|
||||
<div class="chunk-preset-title-row">
|
||||
<h3 class="section-title">分块策略</h3>
|
||||
<a-tooltip :title="selectedPresetDescription">
|
||||
<QuestionCircleOutlined class="chunk-preset-help-icon" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-select
|
||||
v-model:value="newDatabase.chunk_preset_id"
|
||||
:options="chunkPresetOptions"
|
||||
class="full-width"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="newDatabase.kb_type !== 'dify'" class="chunk-preset-title-row">
|
||||
<h3 style="margin: 0">分块策略</h3>
|
||||
<a-tooltip :title="selectedPresetDescription">
|
||||
<QuestionCircleOutlined class="chunk-preset-help-icon" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-select
|
||||
v-if="newDatabase.kb_type !== 'dify'"
|
||||
v-model:value="newDatabase.chunk_preset_id"
|
||||
:options="chunkPresetOptions"
|
||||
style="width: 100%"
|
||||
size="large"
|
||||
/>
|
||||
<!-- 仅对 LightRAG 提供语言选择和LLM选择 -->
|
||||
<div v-if="newDatabase.kb_type === 'lightrag'" class="form-grid two-columns">
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">语言</h3>
|
||||
<a-select
|
||||
v-model:value="newDatabase.language"
|
||||
:options="languageOptions"
|
||||
class="full-width"
|
||||
:dropdown-match-select-width="false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 仅对 LightRAG 提供语言选择和LLM选择 -->
|
||||
<div v-if="newDatabase.kb_type === 'lightrag'">
|
||||
<h3 style="margin-top: 20px">语言</h3>
|
||||
<a-select
|
||||
v-model:value="newDatabase.language"
|
||||
:options="languageOptions"
|
||||
style="width: 100%"
|
||||
size="large"
|
||||
:dropdown-match-select-width="false"
|
||||
/>
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">语言模型 (LLM)</h3>
|
||||
<ModelSelectorComponent
|
||||
:model_spec="llmModelSpec"
|
||||
placeholder="请选择模型"
|
||||
@select-model="handleLLMSelect"
|
||||
class="full-width compact-model-selector"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 20px">语言模型 (LLM)</h3>
|
||||
<p style="color: var(--gray-700); font-size: 14px">可以在设置中配置语言模型</p>
|
||||
<ModelSelectorComponent
|
||||
:model_spec="llmModelSpec"
|
||||
placeholder="请选择模型"
|
||||
@select-model="handleLLMSelect"
|
||||
size="large"
|
||||
style="width: 100%; height: 60px"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="newDatabase.kb_type === 'dify'" class="form-grid three-columns">
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">Dify API URL</h3>
|
||||
<a-input
|
||||
v-model:value="newDatabase.dify_api_url"
|
||||
placeholder="例如: https://api.dify.ai/v1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="newDatabase.kb_type === 'dify'">
|
||||
<h3 style="margin-top: 20px">Dify API URL</h3>
|
||||
<a-input
|
||||
v-model:value="newDatabase.dify_api_url"
|
||||
placeholder="例如: https://api.dify.ai/v1"
|
||||
size="large"
|
||||
/>
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">Dify Token</h3>
|
||||
<a-input-password
|
||||
v-model:value="newDatabase.dify_token"
|
||||
placeholder="请输入 Dify API Token"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 20px">Dify Token</h3>
|
||||
<a-input-password
|
||||
v-model:value="newDatabase.dify_token"
|
||||
placeholder="请输入 Dify API Token"
|
||||
size="large"
|
||||
/>
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">Dataset ID</h3>
|
||||
<a-input
|
||||
v-model:value="newDatabase.dify_dataset_id"
|
||||
placeholder="请输入 Dify dataset_id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 20px">Dataset ID</h3>
|
||||
<a-input
|
||||
v-model:value="newDatabase.dify_dataset_id"
|
||||
placeholder="请输入 Dify dataset_id"
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 20px">知识库描述</h3>
|
||||
<p style="color: var(--gray-700); font-size: 14px">
|
||||
在智能体流程中,这里的描述会作为工具的描述。智能体会根据知识库的标题和描述来选择合适的工具。所以这里描述的越详细,智能体越容易选择到合适的工具。
|
||||
</p>
|
||||
<AiTextarea
|
||||
v-model="newDatabase.description"
|
||||
:name="newDatabase.name"
|
||||
placeholder="新建知识库描述"
|
||||
:auto-size="{ minRows: 3, maxRows: 10 }"
|
||||
/>
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">知识库描述</h3>
|
||||
<p class="field-hint description-hint">
|
||||
在智能体流程中,这里的描述会作为工具的描述。智能体会根据知识库的标题和描述来选择合适的工具。所以这里描述的越详细,智能体越容易选择到合适的工具。
|
||||
</p>
|
||||
<AiTextarea
|
||||
v-model="newDatabase.description"
|
||||
:name="newDatabase.name"
|
||||
placeholder="新建知识库描述"
|
||||
:auto-size="{ minRows: 3, maxRows: 10 }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 隐私设置(暂时隐藏)
|
||||
<h3 style="margin-top: 20px">隐私设置</h3>
|
||||
@ -170,9 +174,12 @@
|
||||
</div>
|
||||
-->
|
||||
|
||||
<!-- 共享配置 -->
|
||||
<h3>共享设置</h3>
|
||||
<ShareConfigForm v-model="shareConfig" :auto-select-user-dept="true" />
|
||||
<!-- 共享配置 -->
|
||||
<div class="form-section compact-section">
|
||||
<h3 class="section-title">共享设置</h3>
|
||||
<ShareConfigForm v-model="shareConfig" :auto-select-user-dept="true" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button key="back" @click="cancelCreateDatabase">取消</a-button>
|
||||
<a-button
|
||||
@ -552,9 +559,74 @@ onMounted(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.new-database-modal {
|
||||
.new-database-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.form-section.compact-section {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
|
||||
&.two-columns {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
&.three-columns {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.two-columns,
|
||||
&.three-columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.compact-model-selector {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
margin-left: 2px;
|
||||
color: var(--color-error-500);
|
||||
}
|
||||
|
||||
.field-hint {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.description-hint {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.chunk-preset-title-row {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@ -579,20 +651,20 @@ onMounted(() => {
|
||||
.kb-type-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
margin: 16px 0;
|
||||
gap: 12px;
|
||||
margin: 4px 0 0;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.kb-type-card {
|
||||
border: 2px solid var(--gray-150);
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
padding: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease;
|
||||
background: var(--gray-0);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@ -604,6 +676,8 @@ onMounted(() => {
|
||||
&.active {
|
||||
border-color: var(--main-color);
|
||||
background: var(--main-10);
|
||||
box-shadow: 0 0 0 1px var(--main-20);
|
||||
|
||||
.type-icon {
|
||||
color: var(--main-color);
|
||||
}
|
||||
@ -612,18 +686,18 @@ onMounted(() => {
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.type-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: var(--main-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.type-title {
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
@ -634,7 +708,6 @@ onMounted(() => {
|
||||
color: var(--gray-600);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0;
|
||||
// min-height: 40px;
|
||||
}
|
||||
|
||||
.deprecated-badge {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user