From 167b06315d3527799c982d8076892207654bae67 Mon Sep 17 00:00:00 2001 From: susie-laptop Date: Fri, 21 Mar 2025 22:13:42 -0400 Subject: [PATCH] [dev]register && create knowledgebase api test --- package-lock.json | 6 + package.json | 1 + src/components/CreateKnowledgeBaseModal.jsx | 91 ++++-- src/components/SearchBar.jsx | 189 ++++++++++++- src/icons/icons.js | 24 +- .../Detail/KnowledgeBaseDetail.jsx | 25 +- .../KnowledgeBase/Detail/SettingsTab.jsx | 175 +++++++++++- .../Detail/components/FileUploadModal.jsx | 49 +++- .../Detail/components/KnowledgeBaseForm.jsx | 226 +++++++++++---- src/pages/KnowledgeBase/KnowledgeBase.css | 53 ++++ src/pages/KnowledgeBase/KnowledgeBase.jsx | 222 ++++++++++++--- src/pages/Permissions/PermissionsPage.jsx | 7 +- src/pages/auth/Signup.jsx | 198 +++++++++++-- src/services/api.js | 2 +- src/store/auth/auth.thunk.js | 32 ++- .../knowledgeBase/knowledgeBase.slice.js | 263 ++++++++---------- .../knowledgeBase/knowledgeBase.thunks.js | 94 ++++++- vite.config.js | 2 +- 18 files changed, 1297 insertions(+), 362 deletions(-) create mode 100644 src/pages/KnowledgeBase/KnowledgeBase.css diff --git a/package-lock.json b/package-lock.json index 8e04914..74fc24a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "axios": "^1.8.1", "bootstrap": "^5.3.3", "crypto-js": "^4.2.0", + "lodash": "^4.17.21", "react": "^19.0.0", "react-dom": "^19.0.0", "react-redux": "^9.2.0", @@ -3370,6 +3371,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", diff --git a/package.json b/package.json index ca40cf6..ce9ab41 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "axios": "^1.8.1", "bootstrap": "^5.3.3", "crypto-js": "^4.2.0", + "lodash": "^4.17.21", "react": "^19.0.0", "react-dom": "^19.0.0", "react-redux": "^9.2.0", diff --git a/src/components/CreateKnowledgeBaseModal.jsx b/src/components/CreateKnowledgeBaseModal.jsx index 4402387..0ace1d9 100644 --- a/src/components/CreateKnowledgeBaseModal.jsx +++ b/src/components/CreateKnowledgeBaseModal.jsx @@ -11,10 +11,48 @@ import SvgIcon from './SvgIcon'; * @param {Function} props.onClose - 关闭弹窗的回调函数 * @param {Function} props.onChange - 表单输入变化的回调函数 * @param {Function} props.onSubmit - 提交表单的回调函数 + * @param {Object} props.currentUser - 当前用户信息 */ -const CreateKnowledgeBaseModal = ({ show, formData, formErrors, isSubmitting, onClose, onChange, onSubmit }) => { +const CreateKnowledgeBaseModal = ({ + show, + formData, + formErrors, + isSubmitting, + onClose, + onChange, + onSubmit, + currentUser +}) => { if (!show) return null; + // 根据用户角色确定可以创建的知识库类型 + const isAdmin = currentUser?.role === 'admin'; + const isLeader = currentUser?.role === 'leader'; + + // 获取当前用户可以创建的知识库类型 + const getAvailableTypes = () => { + if (isAdmin) { + return [ + { value: 'admin', label: 'Admin 级知识库' }, + { value: 'leader', label: 'Leader 级知识库' }, + { value: 'member', label: 'Member 级知识库' }, + { value: 'private', label: '私有知识库' }, + { value: 'secret', label: '保密知识库' } + ]; + } else if (isLeader) { + return [ + { value: 'member', label: 'Member 级知识库' }, + { value: 'private', label: '私有知识库' } + ]; + } else { + return [ + { value: 'private', label: '私有知识库' } + ]; + } + }; + + const availableTypes = getAvailableTypes(); + return (
知识库类型 * -
-
- - -
-
- - -
+
+ {availableTypes.map((type, index) => ( +
+ + +
+ ))}
+ {!isAdmin && !isLeader && ( + + 注意:您当前只能创建私有知识库。其他类型需要更高权限。 + + )} {formErrors.type &&
{formErrors.type}
}
diff --git a/src/components/SearchBar.jsx b/src/components/SearchBar.jsx index d8a1daa..d0416ea 100644 --- a/src/components/SearchBar.jsx +++ b/src/components/SearchBar.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import SvgIcon from './SvgIcon'; /** @@ -11,6 +11,10 @@ import SvgIcon from './SvgIcon'; * @param {Function} props.onClearSearch - 清除搜索的回调函数 * @param {string} props.placeholder - 搜索框占位文本 * @param {string} props.className - 额外的 CSS 类名 + * @param {Array} props.searchResults - 搜索结果 + * @param {boolean} props.isSearchLoading - 搜索是否正在加载 + * @param {Function} props.onResultClick - 点击搜索结果的回调 + * @param {Function} props.onRequestAccess - 申请权限的回调 */ const SearchBar = ({ searchKeyword, @@ -20,22 +24,179 @@ const SearchBar = ({ onClearSearch, placeholder = '搜索...', className = 'w-50', + searchResults = [], + isSearchLoading = false, + onResultClick, + onRequestAccess, }) => { + const [showDropdown, setShowDropdown] = useState(false); + const searchRef = useRef(null); + const inputRef = useRef(null); + + // 处理点击外部关闭下拉框 + useEffect(() => { + const handleClickOutside = (event) => { + if (searchRef.current && !searchRef.current.contains(event.target)) { + setShowDropdown(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + // 当搜索框获得焦点且有关键词时显示下拉框 + const handleFocus = () => { + if (searchKeyword.trim().length > 0) { + setShowDropdown(true); + } + }; + + // 处理输入变化 + const handleInputChange = (e) => { + const value = e.target.value; + onSearchChange(e); + + if (value.trim().length > 0) { + setShowDropdown(true); + } else { + setShowDropdown(false); + } + }; + + // 处理搜索提交 + const handleSubmit = (e) => { + e.preventDefault(); + onSearch(e); + if (searchKeyword.trim().length > 0) { + setShowDropdown(true); + } + }; + return ( -
- - {isSearching && ( - +
+ +
+ + {searchKeyword.trim() && ( + + )} + +
+ + + {/* 搜索结果下拉框 */} + {showDropdown && ( +
+
+ {isSearchLoading ? ( +
+
+ 加载中... +
+ 搜索中... +
+ ) : searchResults?.length > 0 ? ( + <> +
+ 搜索结果 ({searchResults.length}) +
+ {searchResults.map((item) => ( +
+
+
{ + if (item.permissions?.can_read) { + onResultClick(item.id, item.permissions); + setShowDropdown(false); + } + }} + > +
+ + + {item.highlighted_name ? ( + + ) : ( + item.name + )} + +
+
+ + {item.type === 'private' ? '私有' : item.type} + + {item.department && {item.department}} + {!item.permissions?.can_read && ( + + + 无权限 + + )} +
+
+ {!item.permissions?.can_read && ( + + )} +
+
+ ))} + + ) : ( +
未找到匹配的知识库
+ )} +
+
)} - +
); }; diff --git a/src/icons/icons.js b/src/icons/icons.js index d559ece..364ba22 100644 --- a/src/icons/icons.js +++ b/src/icons/icons.js @@ -58,9 +58,9 @@ export const icons = { key: ` `, - lock: ` - - `, + lock: ` + + `, 'stack-fill': ``, search: ``, bell: ``, - 'magnifying-glass': `` + 'magnifying-glass': ``, + close: ` + + `, + 'knowledge-base': ` + + + + `, + 'knowledge-base-large': ` + + + + `, + plus: ` + + `, }; diff --git a/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.jsx b/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.jsx index 8e42a14..77d99b7 100644 --- a/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.jsx +++ b/src/pages/KnowledgeBase/Detail/KnowledgeBaseDetail.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { showNotification } from '../../../store/notification.slice'; -import { fetchKnowledgeBases } from '../../../store/knowledgeBase/knowledgeBase.thunks'; +import { getKnowledgeBaseById } from '../../../store/knowledgeBase/knowledgeBase.thunks'; import SvgIcon from '../../../components/SvgIcon'; import DatasetTab from './DatasetTab'; import SettingsTab from './SettingsTab'; @@ -14,16 +14,15 @@ export default function KnowledgeBaseDetail() { const [activeTab, setActiveTab] = useState(tab === 'settings' ? 'settings' : 'datasets'); // Get knowledge base details from Redux store - const { data, status } = useSelector((state) => state.knowledgeBase.list); - const knowledgeBase = data?.items?.find((kb) => kb.id === id); + const { data: knowledgeBase, status, error } = useSelector((state) => state.knowledgeBase.detail); const isLoading = status === 'loading'; - // Fetch knowledge bases if not available + // Fetch knowledge base details when component mounts or ID changes useEffect(() => { - if (!data?.items?.length && status !== 'loading') { - dispatch(fetchKnowledgeBases()); + if (id) { + dispatch(getKnowledgeBaseById(id)); } - }, [dispatch, data, status]); + }, [dispatch, id]); // Update active tab when URL changes useEffect(() => { @@ -32,18 +31,18 @@ export default function KnowledgeBaseDetail() { } }, [tab]); - // If knowledge base not found in Redux store, show notification and redirect + // If knowledge base not found, show notification and redirect useEffect(() => { - if (!knowledgeBase && data?.items?.length > 0 && !isLoading) { + if (status === 'failed' && error) { dispatch( showNotification({ - message: '未找到知识库,请返回知识库列表', + message: `获取知识库失败: ${error.message || '未找到知识库'}`, type: 'warning', }) ); navigate('/knowledge-base'); } - }, [knowledgeBase, data, isLoading, dispatch, navigate]); + }, [status, error, dispatch, navigate]); // Handle tab change const handleTabChange = (tab) => { @@ -69,9 +68,7 @@ export default function KnowledgeBaseDetail() {
{knowledgeBase.name}
-

- {knowledgeBase.desc || ''} -

+

{knowledgeBase.desc || ''}


diff --git a/src/pages/KnowledgeBase/Detail/SettingsTab.jsx b/src/pages/KnowledgeBase/Detail/SettingsTab.jsx index a38eab8..975c68c 100644 --- a/src/pages/KnowledgeBase/Detail/SettingsTab.jsx +++ b/src/pages/KnowledgeBase/Detail/SettingsTab.jsx @@ -1,8 +1,12 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { showNotification } from '../../../store/notification.slice'; -import { updateKnowledgeBase, deleteKnowledgeBase } from '../../../store/knowledgeBase/knowledgeBase.thunks'; +import { + updateKnowledgeBase, + deleteKnowledgeBase, + changeKnowledgeBaseType, +} from '../../../store/knowledgeBase/knowledgeBase.thunks'; // 导入拆分的组件 import Breadcrumb from './components/Breadcrumb'; @@ -10,25 +14,98 @@ import KnowledgeBaseForm from './components/KnowledgeBaseForm'; import DeleteConfirmModal from './components/DeleteConfirmModal'; import UserPermissionsManager from './components/UserPermissionsManager'; +// 部门和组别的映射关系 +const departmentGroups = { + 技术部: ['开发组', '测试组', '运维组', '架构组', '安全组'], + 产品部: ['产品规划组', '用户研究组', '交互设计组', '项目管理组'], + 市场部: ['品牌推广组', '市场调研组', '客户关系组', '社交媒体组'], + 行政部: ['人事组', '财务组', '行政管理组', '后勤组'], +}; + +// 部门列表 +const departments = Object.keys(departmentGroups); + export default function SettingsTab({ knowledgeBase }) { const dispatch = useDispatch(); const navigate = useNavigate(); + const currentUser = useSelector((state) => state.auth.user); + const isAdmin = currentUser?.role === 'admin'; // State for knowledge base form const [knowledgeBaseForm, setKnowledgeBaseForm] = useState({ name: knowledgeBase.name, desc: knowledgeBase.desc || knowledgeBase.description || '', type: knowledgeBase.type || 'private', // 默认为私有知识库 + original_type: knowledgeBase.type || 'private', // 存储原始类型用于比较 department: knowledgeBase.department || '', group: knowledgeBase.group || '', + original_department: knowledgeBase.department || '', + original_group: knowledgeBase.group || '', }); const [formErrors, setFormErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [availableGroups, setAvailableGroups] = useState([]); + + // 当部门变化时,更新可选的组别 + useEffect(() => { + if (knowledgeBaseForm.department && departmentGroups[knowledgeBaseForm.department]) { + setAvailableGroups(departmentGroups[knowledgeBaseForm.department]); + // 如果已选择的组别不在新部门的选项中,则重置组别 + if (!departmentGroups[knowledgeBaseForm.department].includes(knowledgeBaseForm.group)) { + setKnowledgeBaseForm((prev) => ({ + ...prev, + group: '', + })); + } + } else { + setAvailableGroups([]); + setKnowledgeBaseForm((prev) => ({ + ...prev, + group: '', + })); + } + }, [knowledgeBaseForm.department]); + + // 初始化可用组别 + useEffect(() => { + if (knowledgeBase.department && departmentGroups[knowledgeBase.department]) { + setAvailableGroups(departmentGroups[knowledgeBase.department]); + } + }, [knowledgeBase]); // Handle knowledge base form input change const handleInputChange = (e) => { const { name, value } = e.target; + + // 检查如果是类型更改,确保用户有权限 + if (name === 'type') { + const role = currentUser?.role; + let allowed = false; + + // 根据角色判断可以选择的知识库类型 + if (role === 'admin') { + // 管理员可以选择任何类型 + allowed = ['admin', 'leader', 'member', 'private', 'secret'].includes(value); + } else if (role === 'leader') { + // 组长只能选择 member 和 private + allowed = ['member', 'private'].includes(value); + } else { + // 普通成员只能选择 private + allowed = value === 'private'; + } + + if (!allowed) { + dispatch( + showNotification({ + message: '您没有权限设置此类型的知识库', + type: 'warning', + }) + ); + return; + } + } + setKnowledgeBaseForm((prev) => ({ ...prev, [name]: value, @@ -59,10 +136,87 @@ export default function SettingsTab({ knowledgeBase }) { errors.type = '请选择知识库类型'; } + if (isAdmin && !knowledgeBaseForm.department) { + errors.department = '请选择部门'; + } + + if (isAdmin && !knowledgeBaseForm.group) { + errors.group = '请选择组别'; + } + setFormErrors(errors); return Object.keys(errors).length === 0; }; + // 检查是否有部门或组别变更 + const hasDepartmentOrGroupChanged = () => { + return ( + knowledgeBaseForm.department !== knowledgeBaseForm.original_department || + knowledgeBaseForm.group !== knowledgeBaseForm.original_group + ); + }; + + // 单独处理类型更改 + const handleTypeChange = (newType) => { + if (!currentUser) { + dispatch( + showNotification({ + message: '用户信息不完整,无法更改类型', + type: 'warning', + }) + ); + return; + } + + if (isAdmin && !validateForm()) { + return; + } + + setIsSubmitting(true); + + const department = isAdmin ? knowledgeBaseForm.department : currentUser.department || ''; + const group = isAdmin ? knowledgeBaseForm.group : currentUser.group || ''; + + dispatch( + changeKnowledgeBaseType({ + id: knowledgeBase.id, + type: newType, + department, + group, + }) + ) + .unwrap() + .then((updatedKB) => { + // 更新表单显示 + setKnowledgeBaseForm((prev) => ({ + ...prev, + type: updatedKB.type, + original_type: updatedKB.type, + department: updatedKB.department, + original_department: updatedKB.department, + group: updatedKB.group, + original_group: updatedKB.group, + })); + + dispatch( + showNotification({ + message: `知识库类型已更新为 ${updatedKB.type}`, + type: 'success', + }) + ); + setIsSubmitting(false); + }) + .catch((error) => { + dispatch( + showNotification({ + message: `类型更新失败: ${error.message || '未知错误'}`, + type: 'danger', + }) + ); + setIsSubmitting(false); + }); + }; + // Handle form submission const handleSubmit = (e) => { e.preventDefault(); @@ -74,7 +228,13 @@ export default function SettingsTab({ knowledgeBase }) { setIsSubmitting(true); - // Dispatch update knowledge base action + // 检查类型是否有更改,如果有,则使用单独的API更新类型 + if (knowledgeBaseForm.type !== knowledgeBaseForm.original_type || (isAdmin && hasDepartmentOrGroupChanged())) { + handleTypeChange(knowledgeBaseForm.type); + return; + } + + // Dispatch update knowledge base action (不包含类型更改) dispatch( updateKnowledgeBase({ id: knowledgeBase.id, @@ -82,9 +242,6 @@ export default function SettingsTab({ knowledgeBase }) { name: knowledgeBaseForm.name, desc: knowledgeBaseForm.desc, description: knowledgeBaseForm.desc, // Add description field for compatibility - type: knowledgeBaseForm.type, - department: knowledgeBaseForm.department, - group: knowledgeBaseForm.group, }, }) ) @@ -151,6 +308,10 @@ export default function SettingsTab({ knowledgeBase }) { onInputChange={handleInputChange} onSubmit={handleSubmit} onDelete={() => setShowDeleteConfirm(true)} + onTypeChange={handleTypeChange} + isAdmin={isAdmin} + departments={departments} + availableGroups={availableGroups} /> {/* User Permissions Manager */} diff --git a/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx b/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx index 2256a7a..915f85e 100644 --- a/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx +++ b/src/pages/KnowledgeBase/Detail/components/FileUploadModal.jsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useEffect } from 'react'; /** * 文件上传模态框组件 @@ -17,11 +17,44 @@ const FileUploadModal = ({ onUpload, }) => { const fileInputRef = useRef(null); + const modalRef = useRef(null); + + // 处理上传区域点击事件 + const handleUploadAreaClick = () => { + fileInputRef.current?.click(); + }; + + // 处理拖拽事件 + const handleDragOver = (e) => { + e.preventDefault(); + e.stopPropagation(); + onDragOver?.(e); + }; + + const handleDrop = (e) => { + e.preventDefault(); + e.stopPropagation(); + onFileDrop?.(e); + }; + + // 清理函数 + useEffect(() => { + return () => { + // 确保在组件卸载时清理所有引用 + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + if (modalRef.current) { + modalRef.current = null; + } + }; + }, []); if (!show) return null; return (
- + {newFile.file ? (

已选择文件:

diff --git a/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx b/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx index 02f303f..8f221fc 100644 --- a/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx +++ b/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useSelector } from 'react-redux'; /** * 知识库表单组件 @@ -10,7 +11,50 @@ const KnowledgeBaseForm = ({ onInputChange, onSubmit, onDelete, + onTypeChange, + isAdmin, + departments, + availableGroups, }) => { + // 获取当前用户信息 + const currentUser = useSelector((state) => state.auth.user); + + // 根据用户角色确定可以设置的知识库类型 + const isLeader = currentUser?.role === 'leader'; + + // 获取当前用户可以设置的知识库类型 + const getAvailableTypes = () => { + if (isAdmin) { + return [ + { value: 'admin', label: 'Admin 级知识库' }, + { value: 'leader', label: 'Leader 级知识库' }, + { value: 'member', label: 'Member 级知识库' }, + { value: 'private', label: '私有知识库' }, + { value: 'secret', label: '保密知识库' }, + ]; + } else if (isLeader) { + return [ + { value: 'member', label: 'Member 级知识库' }, + { value: 'private', label: '私有知识库' }, + ]; + } else { + return [{ value: 'private', label: '私有知识库' }]; + } + }; + + const availableTypes = getAvailableTypes(); + + // 检查类型是否被更改 + const hasTypeChanged = formData.original_type && formData.original_type !== formData.type; + + // 检查部门或组别是否被更改 + const hasDepartmentOrGroupChanged = + (formData.original_department && formData.department !== formData.original_department) || + (formData.original_group && formData.group !== formData.original_group); + + // 是否显示类型更改按钮 + const showTypeChangeButton = hasTypeChanged || (isAdmin && hasDepartmentOrGroupChanged); + return (
@@ -47,69 +91,151 @@ const KnowledgeBaseForm = ({ {formErrors.desc &&
{formErrors.desc}
}
-
- -
- - -
-
- - +
+ +
+ {availableTypes.map((type) => ( +
+ + +
+ ))}
+ {currentUser?.role === 'member' && ( + 您当前无权修改知识库类型。 + )} + {formErrors.type &&
{formErrors.type}
}
- - 部门信息根据知识库创建者自动填写 + {isAdmin ? ( + <> + + {formErrors.department && ( +
{formErrors.department}
+ )} + + ) : ( + <> + + 部门信息根据知识库创建者自动填写 + + )}
- - 组别信息根据知识库创建者自动填写 + {isAdmin ? ( + <> + + {formErrors.group &&
{formErrors.group}
} + {!formData.department && ( + 请先选择部门 + )} + + ) : ( + <> + + 组别信息根据知识库创建者自动填写 + + )}
-
+ {/* 类型更改按钮 */} + {showTypeChangeButton && ( +
+
+ {hasTypeChanged && ( +

+ 知识库类型已更改为 {formData.type} +

+ )} + {isAdmin && hasDepartmentOrGroupChanged &&

部门/组别已更改

} + 点击更新按钮单独保存这些更改 +
+ +
+ )} + +
- - Already have account? + 已有账号?立即登录
); diff --git a/src/services/api.js b/src/services/api.js index 6105ca1..0fa892e 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -77,7 +77,7 @@ api.interceptors.response.use( // 检查服务器状态 export const checkServerStatus = async () => { try { - await api.get('/health-check', { timeout: 3000 }); + // await api.get('/health-check', { timeout: 3000 }); isServerDown = false; hasCheckedServer = true; console.log('Server connection established'); diff --git a/src/store/auth/auth.thunk.js b/src/store/auth/auth.thunk.js index 71fbd8d..34137f0 100644 --- a/src/store/auth/auth.thunk.js +++ b/src/store/auth/auth.thunk.js @@ -35,15 +35,35 @@ export const loginThunk = createAsyncThunk( } ); -export const signupThunk = createAsyncThunk('auth/signup', async (config, { rejectWithValue, dispatch }) => { +export const signupThunk = createAsyncThunk('auth/signup', async (userData, { rejectWithValue, dispatch }) => { try { - const { message, user } = await post('/signup', config); - if (!user) { - throw new Error(message || 'Something went wrong'); + // 使用新的注册 API + const { data, code } = await post('/auth/register/', userData); + console.log('注册返回数据:', response); + + // 处理新的返回格式 + if (code === 200) { + // // 将 token 加密存储到 sessionStorage + // const { token } = data; + // if (token) { + // const encryptedToken = CryptoJS.AES.encrypt(token, secretKey).toString(); + // sessionStorage.setItem('token', encryptedToken); + // } + + // 显示注册成功通知 + dispatch( + showNotification({ + message: '注册成功', + type: 'success', + }) + ); + return null; + // return userData; } - return user; + + return response.data; } catch (error) { - const errorMessage = error.response?.data?.message || 'Signup failed. Please try again.'; + const errorMessage = error.response?.data?.message || '注册失败,请稍后重试'; dispatch( showNotification({ message: errorMessage, diff --git a/src/store/knowledgeBase/knowledgeBase.slice.js b/src/store/knowledgeBase/knowledgeBase.slice.js index 5ea1f99..2c2fbcf 100644 --- a/src/store/knowledgeBase/knowledgeBase.slice.js +++ b/src/store/knowledgeBase/knowledgeBase.slice.js @@ -1,198 +1,169 @@ import { createSlice } from '@reduxjs/toolkit'; import { fetchKnowledgeBases, - searchKnowledgeBases, createKnowledgeBase, - getKnowledgeBaseById, updateKnowledgeBase, deleteKnowledgeBase, + changeKnowledgeBaseType, + searchKnowledgeBases, + requestKnowledgeBaseAccess, } from './knowledgeBase.thunks'; const initialState = { - // List state - list: { - data: { - items: [], - total: 0, - page: 1, - page_size: 10, - }, - status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' - error: null, - }, - // Search state - search: { - data: { - items: [], - total: 0, - page: 1, - page_size: 10, - keyword: '', - }, - status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' - error: null, - }, - // Current knowledge base details - current: { - data: null, - status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' - error: null, - }, - // Create/update/delete operations status - operations: { - status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' - error: null, - operationType: null, // 'create' | 'update' | 'delete' + knowledgeBases: [], + currentKnowledgeBase: null, + searchResults: [], + searchLoading: false, + loading: false, + error: null, + pagination: { + total: 0, + page: 1, + page_size: 10, + total_pages: 1, }, + batchPermissions: {}, + batchLoading: false, + editStatus: 'idle', + requestAccessStatus: 'idle', }; const knowledgeBaseSlice = createSlice({ name: 'knowledgeBase', initialState, reducers: { - resetOperationStatus: (state) => { - state.operations = { - status: 'idle', - error: null, - operationType: null, - }; + clearCurrentKnowledgeBase: (state) => { + state.currentKnowledgeBase = null; }, - resetCurrentKnowledgeBase: (state) => { - state.current = { - data: null, - status: 'idle', - error: null, - }; + clearSearchResults: (state) => { + state.searchResults = []; }, - resetSearchState: (state) => { - state.search = { - data: { - items: [], - total: 0, - page: 1, - page_size: 10, - keyword: '', - }, - status: 'idle', - error: null, - }; + clearEditStatus: (state) => { + state.editStatus = 'idle'; }, }, extraReducers: (builder) => { builder - // Fetch knowledge bases + // 获取知识库列表 .addCase(fetchKnowledgeBases.pending, (state) => { - state.list.status = 'loading'; + state.loading = true; + state.error = null; }) .addCase(fetchKnowledgeBases.fulfilled, (state, action) => { - state.list.status = 'succeeded'; - state.list.data = action.payload; - state.list.error = null; + state.loading = false; + state.knowledgeBases = action.payload.items || []; + state.pagination = { + total: action.payload.total || 0, + page: action.payload.page || 1, + page_size: action.payload.page_size || 10, + total_pages: action.payload.total_pages || 1, + }; }) .addCase(fetchKnowledgeBases.rejected, (state, action) => { - state.list.status = 'failed'; - state.list.error = action.payload; + state.loading = false; + state.error = action.payload || 'Failed to fetch knowledge bases'; }) - // Search knowledge bases - .addCase(searchKnowledgeBases.pending, (state, action) => { - state.search.status = 'loading'; - // Store the keyword for reference - if (action.meta.arg.keyword) { - state.search.data.keyword = action.meta.arg.keyword; - } - }) - .addCase(searchKnowledgeBases.fulfilled, (state, action) => { - state.search.status = 'succeeded'; - state.search.data = action.payload; - state.search.error = null; - }) - .addCase(searchKnowledgeBases.rejected, (state, action) => { - state.search.status = 'failed'; - state.search.error = action.payload; - }) - - // Get knowledge base by ID - .addCase(getKnowledgeBaseById.pending, (state) => { - state.current.status = 'loading'; - }) - .addCase(getKnowledgeBaseById.fulfilled, (state, action) => { - state.current.status = 'succeeded'; - state.current.data = action.payload; - state.current.error = null; - }) - .addCase(getKnowledgeBaseById.rejected, (state, action) => { - state.current.status = 'failed'; - state.current.error = action.payload; - }) - - // Create knowledge base + // 创建知识库 .addCase(createKnowledgeBase.pending, (state) => { - state.operations.status = 'loading'; - state.operations.operationType = 'create'; + state.loading = true; + state.error = null; }) .addCase(createKnowledgeBase.fulfilled, (state, action) => { - state.operations.status = 'succeeded'; - // Don't add to list here - better to refetch the list to ensure consistency - state.operations.error = null; + state.loading = false; + state.editStatus = 'successful'; + // 不需要更新 knowledgeBases,因为创建后会跳转到详情页 }) .addCase(createKnowledgeBase.rejected, (state, action) => { - state.operations.status = 'failed'; - state.operations.error = action.payload; + state.loading = false; + state.error = action.payload || 'Failed to create knowledge base'; + state.editStatus = 'failed'; }) - // Update knowledge base + // 更新知识库 .addCase(updateKnowledgeBase.pending, (state) => { - state.operations.status = 'loading'; - state.operations.operationType = 'update'; + state.loading = true; + state.error = null; }) .addCase(updateKnowledgeBase.fulfilled, (state, action) => { - state.operations.status = 'succeeded'; - // Update in list if present - const index = state.list.data.items.findIndex((item) => item.id === action.payload.id); - if (index !== -1) { - state.list.data.items[index] = action.payload; - } - // Update in search results if present - const searchIndex = state.search.data.items.findIndex((item) => item.id === action.payload.id); - if (searchIndex !== -1) { - state.search.data.items[searchIndex] = action.payload; - } - // Update current if it's the same knowledge base - if (state.current.data && state.current.data.id === action.payload.id) { - state.current.data = action.payload; - } - state.operations.error = null; + state.loading = false; + state.currentKnowledgeBase = action.payload; + state.editStatus = 'successful'; }) .addCase(updateKnowledgeBase.rejected, (state, action) => { - state.operations.status = 'failed'; - state.operations.error = action.payload; + state.loading = false; + state.error = action.payload || 'Failed to update knowledge base'; + state.editStatus = 'failed'; }) - // Delete knowledge base + // 删除知识库 .addCase(deleteKnowledgeBase.pending, (state) => { - state.operations.status = 'loading'; - state.operations.operationType = 'delete'; + state.loading = true; + state.error = null; }) .addCase(deleteKnowledgeBase.fulfilled, (state, action) => { - state.operations.status = 'succeeded'; - // Remove from list if present - state.list.data.items = state.list.data.items.filter((item) => item.id !== action.payload); - // Remove from search results if present - state.search.data.items = state.search.data.items.filter((item) => item.id !== action.payload); - // Reset current if it's the same knowledge base - if (state.current.data && state.current.data.id === action.payload) { - state.current.data = null; - } - state.operations.error = null; + state.loading = false; + state.knowledgeBases = state.knowledgeBases.filter((kb) => kb.id !== action.meta.arg.knowledgeBaseId); }) .addCase(deleteKnowledgeBase.rejected, (state, action) => { - state.operations.status = 'failed'; - state.operations.error = action.payload; + state.loading = false; + state.error = action.payload || 'Failed to delete knowledge base'; + }) + + // 修改知识库类型 + .addCase(changeKnowledgeBaseType.pending, (state) => { + state.loading = true; + state.error = null; + }) + .addCase(changeKnowledgeBaseType.fulfilled, (state, action) => { + state.loading = false; + if (state.currentKnowledgeBase) { + state.currentKnowledgeBase = { + ...state.currentKnowledgeBase, + type: action.payload.type, + department: action.payload.department, + group: action.payload.group, + }; + } + state.editStatus = 'successful'; + }) + .addCase(changeKnowledgeBaseType.rejected, (state, action) => { + state.loading = false; + state.error = action.payload || 'Failed to change knowledge base type'; + state.editStatus = 'failed'; + }) + + // 搜索知识库 + .addCase(searchKnowledgeBases.pending, (state) => { + state.searchLoading = true; + state.error = null; + }) + .addCase(searchKnowledgeBases.fulfilled, (state, action) => { + state.searchLoading = false; + if (action.payload && action.payload.code === 200) { + state.searchResults = action.payload.data.items || []; + } else { + state.searchResults = action.payload.items || []; + } + }) + .addCase(searchKnowledgeBases.rejected, (state, action) => { + state.searchLoading = false; + state.error = action.payload || 'Failed to search knowledge bases'; + }) + + // 申请知识库访问权限 + .addCase(requestKnowledgeBaseAccess.pending, (state) => { + state.requestAccessStatus = 'loading'; + }) + .addCase(requestKnowledgeBaseAccess.fulfilled, (state) => { + state.requestAccessStatus = 'successful'; + }) + .addCase(requestKnowledgeBaseAccess.rejected, (state) => { + state.requestAccessStatus = 'failed'; }); }, }); -export const { resetOperationStatus, resetCurrentKnowledgeBase, resetSearchState } = knowledgeBaseSlice.actions; -const knowledgeBaseReducer = knowledgeBaseSlice.reducer; -export default knowledgeBaseReducer; +export const { clearCurrentKnowledgeBase, clearSearchResults, clearEditStatus } = knowledgeBaseSlice.actions; + +export default knowledgeBaseSlice.reducer; diff --git a/src/store/knowledgeBase/knowledgeBase.thunks.js b/src/store/knowledgeBase/knowledgeBase.thunks.js index 06baa1a..18ca463 100644 --- a/src/store/knowledgeBase/knowledgeBase.thunks.js +++ b/src/store/knowledgeBase/knowledgeBase.thunks.js @@ -1,5 +1,6 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; import { get, post, put, del } from '../../services/api'; +import { showNotification } from '../notification.slice'; /** * Fetch knowledge bases with pagination @@ -30,24 +31,26 @@ export const fetchKnowledgeBases = createAsyncThunk( * @param {number} params.page - Page number (default: 1) * @param {number} params.page_size - Page size (default: 10) */ -export const searchKnowledgeBases = createAsyncThunk( - 'knowledgeBase/searchKnowledgeBases', - async ({ keyword, page = 1, page_size = 10 }, { rejectWithValue }) => { - try { - const response = await get('/knowledge-bases/search/', { - params: { keyword, page, page_size }, - }); +export const searchKnowledgeBases = createAsyncThunk('knowledgeBase/search', async (params, { rejectWithValue }) => { + try { + const { keyword, page = 1, page_size = 10 } = params; + const response = await get('/knowledge-bases/search/', { + params: { + keyword, + page, + page_size, + }, + }); - // 处理新的返回格式 - if (response.data && response.data.code === 200) { - return response.data.data; - } + // 处理新的返回格式 + if (response.data && response.data.code === 200) { return response.data; - } catch (error) { - return rejectWithValue(error.response?.data || 'Failed to search knowledge bases'); } + return response.data; + } catch (error) { + return rejectWithValue(error.response?.data || error.message); } -); +}); /** * Create a new knowledge base @@ -80,7 +83,7 @@ export const getKnowledgeBaseById = createAsyncThunk( const response = await get(`/knowledge-bases/${id}/`); // 处理新的返回格式 if (response.data && response.data.code === 200) { - return response.data.data.knowledge_base; + return response.data.data; } return response.data; } catch (error) { @@ -128,3 +131,64 @@ export const deleteKnowledgeBase = createAsyncThunk( } } ); + +/** + * Change knowledge base type + * @param {Object} params - Parameters + * @param {string} params.id - Knowledge base ID + * @param {string} params.type - New knowledge base type + * @param {string} params.department - User department + * @param {string} params.group - User group + */ +export const changeKnowledgeBaseType = createAsyncThunk( + 'knowledgeBase/changeType', + async ({ id, type, department, group }, { rejectWithValue }) => { + try { + const response = await post(`/knowledge-bases/${id}/change_type/`, { + type, + department, + group, + }); + + // 处理新的返回格式 + if (response.data && response.data.code === 200) { + return response.data.data; + } + + return response.data; + } catch (error) { + return rejectWithValue(error.response?.data || '修改知识库类型失败'); + } + } +); + +/** + * 申请知识库访问权限 + * @param {Object} params - 参数 + * @param {string} params.knowledgeBaseId - 知识库ID + * @returns {Promise} - Promise对象 + */ +export const requestKnowledgeBaseAccess = createAsyncThunk( + 'knowledgeBase/requestAccess', + async (params, { rejectWithValue, dispatch }) => { + try { + const { knowledgeBaseId } = params; + const response = await post(`/knowledge-bases/${knowledgeBaseId}/request_access/`); + dispatch( + showNotification({ + type: 'success', + message: '权限申请已发送,请等待管理员审核', + }) + ); + return response.data; + } catch (error) { + dispatch( + showNotification({ + type: 'danger', + message: error.response?.data?.detail || '权限申请失败,请稍后重试', + }) + ); + return rejectWithValue(error.response?.data || error.message); + } + } +); diff --git a/vite.config.js b/vite.config.js index 2e6c3c9..f2ecfab 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,7 +14,7 @@ export default defineConfig(({ mode }) => { port: env.VITE_PORT, proxy: { '/api': { - target: env.VITE_API_URL || 'http://124.222.236.141:58000', + target: env.VITE_API_URL || 'http://81.69.223.133:3000', changeOrigin: true, }, },