mirror of
https://github.com/Funkoala14/KnowledgeBase_OOIN.git
synced 2025-06-08 05:09:44 +08:00
[dev]update search
This commit is contained in:
parent
167b06315d
commit
fbfff98123
@ -47,32 +47,23 @@ const SearchBar = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 当搜索框获得焦点且有关键词时显示下拉框
|
||||
const handleFocus = () => {
|
||||
if (searchKeyword.trim().length > 0) {
|
||||
// 只有在用户执行搜索后且有结果时显示下拉框
|
||||
useEffect(() => {
|
||||
if (isSearching && searchResults.length > 0) {
|
||||
setShowDropdown(true);
|
||||
}
|
||||
};
|
||||
}, [isSearching, searchResults]);
|
||||
|
||||
// 处理输入变化
|
||||
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);
|
||||
}
|
||||
// 搜索提交后,如果有关键词,将显示下拉框(由searchResults决定)
|
||||
};
|
||||
|
||||
return (
|
||||
@ -86,7 +77,6 @@ const SearchBar = ({
|
||||
placeholder={placeholder}
|
||||
value={searchKeyword}
|
||||
onChange={handleInputChange}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
{searchKeyword.trim() && (
|
||||
<button
|
||||
@ -107,9 +97,9 @@ const SearchBar = ({
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* 搜索结果下拉框 */}
|
||||
{showDropdown && (
|
||||
<div className='position-absolute bg-white shadow-sm rounded-3 mt-1 w-100 search-results-dropdown'>
|
||||
{/* 搜索结果下拉框 - 仅在用户搜索且有结果时显示 */}
|
||||
{showDropdown && (isSearchLoading || searchResults?.length > 0) && (
|
||||
<div className='position-absolute bg-white shadow-sm rounded-3 mt-1 w-100 search-results-dropdown z-1'>
|
||||
<div className='p-2 overflow-auto' style={{ maxHeight: '350px', zIndex: '1050' }}>
|
||||
{isSearchLoading ? (
|
||||
<div className='text-center p-3'>
|
||||
|
@ -11,7 +11,7 @@ const Snackbar = ({ type = 'primary', message, duration = 3000, onClose }) => {
|
||||
}, duration);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [duration, onClose]);
|
||||
}, [message, duration, onClose]);
|
||||
|
||||
const icons = {
|
||||
success: 'check-circle-fill',
|
||||
@ -20,22 +20,21 @@ const Snackbar = ({ type = 'primary', message, duration = 3000, onClose }) => {
|
||||
danger: 'exclamation-triangle-fill',
|
||||
};
|
||||
|
||||
// 处理关闭按钮点击
|
||||
const handleClose = (e) => {
|
||||
e.preventDefault();
|
||||
if (onClose) onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`snackbar alert alert-${type} d-flex align-items-center justify-content-between position-fixed top-10 start-50 translate-middle w-50 alert-dismissible z-2 gap-2`}
|
||||
className={`snackbar alert alert-${type} d-flex align-items-center justify-content-between position-fixed top-10 start-50 translate-middle w-50 z-2 gap-2`}
|
||||
role='alert'
|
||||
>
|
||||
<SvgIcon className={icons[type]} />
|
||||
<div className='flex-fill'>{message}</div>
|
||||
<button
|
||||
type='button'
|
||||
className='btn-close flex-end'
|
||||
data-bs-dismiss='alert'
|
||||
aria-label='Close'
|
||||
></button>
|
||||
<button type='button' className='btn-close flex-end' onClick={handleClose} aria-label='Close'></button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -7,7 +7,6 @@ export default function Mainlayout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<HeaderWithNav />
|
||||
<NotificationSnackbar />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
@ -18,10 +18,11 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
|
||||
error: messagesError,
|
||||
} = useSelector((state) => state.chat.messages);
|
||||
const { status: sendStatus, error: sendError } = useSelector((state) => state.chat.sendMessage);
|
||||
const knowledgeBase = useSelector((state) =>
|
||||
state.knowledgeBase.list.data?.items?.find((kb) => kb.id === knowledgeBaseId)
|
||||
);
|
||||
const isLoadingKnowledgeBases = useSelector((state) => state.knowledgeBase.list.isLoading);
|
||||
|
||||
// 使用新的Redux状态结构
|
||||
const knowledgeBases = useSelector((state) => state.knowledgeBase.knowledgeBases || []);
|
||||
const knowledgeBase = knowledgeBases.find((kb) => kb.id === knowledgeBaseId);
|
||||
const isLoadingKnowledgeBases = useSelector((state) => state.knowledgeBase.loading);
|
||||
|
||||
// 获取聊天消息
|
||||
useEffect(() => {
|
||||
@ -56,7 +57,7 @@ export default function ChatWindow({ chatId, knowledgeBaseId }) {
|
||||
// 从 Redux store 获取知识库信息
|
||||
useEffect(() => {
|
||||
if (!knowledgeBase && !isLoadingKnowledgeBases) {
|
||||
dispatch(fetchKnowledgeBases());
|
||||
dispatch(fetchKnowledgeBases({ page: 1, page_size: 50 }));
|
||||
}
|
||||
}, [dispatch, knowledgeBase, isLoadingKnowledgeBases]);
|
||||
|
||||
|
@ -8,23 +8,20 @@ import SvgIcon from '../../components/SvgIcon';
|
||||
export default function NewChat() {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// 从 Redux store 获取知识库数据
|
||||
const { data, status, error } = useSelector((state) => state.knowledgeBase.list);
|
||||
const knowledgeBases = data?.items || [];
|
||||
const isLoading = status === 'loading';
|
||||
// 从 Redux store 获取知识库数据 - 使用新的状态结构
|
||||
const knowledgeBases = useSelector((state) => state.knowledgeBase.knowledgeBases || []);
|
||||
const isLoading = useSelector((state) => state.knowledgeBase.loading);
|
||||
const error = useSelector((state) => state.knowledgeBase.error);
|
||||
|
||||
// 获取知识库列表
|
||||
useEffect(() => {
|
||||
if (!data?.items?.length && status !== 'loading') {
|
||||
dispatch(fetchKnowledgeBases());
|
||||
}
|
||||
}, [dispatch, data, status]);
|
||||
dispatch(fetchKnowledgeBases({ page: 1, page_size: 50 }));
|
||||
}, [dispatch]);
|
||||
|
||||
// 监听错误状态
|
||||
useEffect(() => {
|
||||
if (status === 'failed' && error) {
|
||||
if (error) {
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: `获取知识库列表失败: ${error.message || error}`,
|
||||
@ -32,7 +29,7 @@ export default function NewChat() {
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [status, error, dispatch]);
|
||||
}, [error, dispatch]);
|
||||
|
||||
// 过滤出有 can_read 权限的知识库
|
||||
const readableKnowledgeBases = knowledgeBases.filter((kb) => kb.permissions && kb.permissions.can_read === true);
|
||||
|
@ -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 { getKnowledgeBaseById } from '../../../store/knowledgeBase/knowledgeBase.thunks';
|
||||
import { fetchKnowledgeBaseDetail } from '../../../store/knowledgeBase/knowledgeBase.thunks';
|
||||
import SvgIcon from '../../../components/SvgIcon';
|
||||
import DatasetTab from './DatasetTab';
|
||||
import SettingsTab from './SettingsTab';
|
||||
@ -13,14 +13,15 @@ export default function KnowledgeBaseDetail() {
|
||||
const dispatch = useDispatch();
|
||||
const [activeTab, setActiveTab] = useState(tab === 'settings' ? 'settings' : 'datasets');
|
||||
|
||||
// Get knowledge base details from Redux store
|
||||
const { data: knowledgeBase, status, error } = useSelector((state) => state.knowledgeBase.detail);
|
||||
const isLoading = status === 'loading';
|
||||
// Get knowledge base details from Redux store - 使用新的状态结构
|
||||
const knowledgeBase = useSelector((state) => state.knowledgeBase.currentKnowledgeBase);
|
||||
const loading = useSelector((state) => state.knowledgeBase.loading);
|
||||
const error = useSelector((state) => state.knowledgeBase.error);
|
||||
|
||||
// Fetch knowledge base details when component mounts or ID changes
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
dispatch(getKnowledgeBaseById(id));
|
||||
dispatch(fetchKnowledgeBaseDetail(id));
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
|
||||
@ -33,7 +34,7 @@ export default function KnowledgeBaseDetail() {
|
||||
|
||||
// If knowledge base not found, show notification and redirect
|
||||
useEffect(() => {
|
||||
if (status === 'failed' && error) {
|
||||
if (!loading && error) {
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: `获取知识库失败: ${error.message || '未找到知识库'}`,
|
||||
@ -42,7 +43,7 @@ export default function KnowledgeBaseDetail() {
|
||||
);
|
||||
navigate('/knowledge-base');
|
||||
}
|
||||
}, [status, error, dispatch, navigate]);
|
||||
}, [loading, error, dispatch, navigate]);
|
||||
|
||||
// Handle tab change
|
||||
const handleTabChange = (tab) => {
|
||||
@ -51,7 +52,7 @@ export default function KnowledgeBaseDetail() {
|
||||
};
|
||||
|
||||
// Show loading state if knowledge base not loaded yet
|
||||
if (isLoading || !knowledgeBase) {
|
||||
if (loading || !knowledgeBase) {
|
||||
return (
|
||||
<div className='container-fluid px-4 py-5 text-center'>
|
||||
<div className='spinner-border' role='status'>
|
||||
|
@ -16,7 +16,6 @@ import CreateKnowledgeBaseModal from '../../components/CreateKnowledgeBaseModal'
|
||||
import Pagination from '../../components/Pagination';
|
||||
import SearchBar from '../../components/SearchBar';
|
||||
import ApiModeSwitch from '../../components/ApiModeSwitch';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
// 导入拆分的组件
|
||||
import KnowledgeBaseList from './components/KnowledgeBaseList';
|
||||
@ -47,7 +46,7 @@ export default function KnowledgeBase() {
|
||||
|
||||
// Search state
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
|
||||
|
||||
// Pagination state
|
||||
const [pagination, setPagination] = useState({
|
||||
@ -68,100 +67,26 @@ export default function KnowledgeBase() {
|
||||
const searchResults = useSelector((state) => state.knowledgeBase.searchResults);
|
||||
const searchLoading = useSelector((state) => state.knowledgeBase.searchLoading);
|
||||
|
||||
// Determine which data to display based on search state
|
||||
const displayData = isSearching ? searchResults : knowledgeBases;
|
||||
const displayTotal = paginationData.total;
|
||||
const displayStatus = loading ? 'loading' : 'succeeded';
|
||||
const displayError = error;
|
||||
|
||||
// Fetch knowledge bases when component mounts or pagination changes
|
||||
useEffect(() => {
|
||||
if (!isSearching) {
|
||||
// 无论是否在搜索,都正常获取知识库列表
|
||||
dispatch(fetchKnowledgeBases(pagination));
|
||||
} else if (searchKeyword.trim()) {
|
||||
dispatch(
|
||||
searchKnowledgeBases({
|
||||
keyword: searchKeyword,
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [dispatch, pagination.page, pagination.page_size, isSearching, searchKeyword]);
|
||||
|
||||
// 实时搜索处理函数
|
||||
const debouncedSearch = useCallback(
|
||||
debounce((keyword) => {
|
||||
if (keyword.trim()) {
|
||||
dispatch(
|
||||
searchKnowledgeBases({
|
||||
keyword,
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(clearSearchResults());
|
||||
}
|
||||
}, 300),
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchInputChange = (e) => {
|
||||
const value = e.target.value;
|
||||
setSearchKeyword(value);
|
||||
|
||||
// 实时搜索
|
||||
if (value.trim()) {
|
||||
debouncedSearch(value);
|
||||
} else {
|
||||
dispatch(clearSearchResults());
|
||||
}
|
||||
};
|
||||
|
||||
// Handle search submit
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (searchKeyword.trim()) {
|
||||
setIsSearching(true);
|
||||
setPagination((prev) => ({ ...prev, page: 1 })); // Reset to first page
|
||||
dispatch(
|
||||
searchKnowledgeBases({
|
||||
keyword: searchKeyword,
|
||||
page: 1,
|
||||
page_size: pagination.page_size,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// If search is empty, reset to normal list view
|
||||
handleClearSearch();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle clear search
|
||||
const handleClearSearch = () => {
|
||||
setSearchKeyword('');
|
||||
setIsSearching(false);
|
||||
setPagination((prev) => ({ ...prev, page: 1 })); // Reset to first page
|
||||
dispatch(clearSearchResults());
|
||||
};
|
||||
}, [dispatch, pagination.page, pagination.page_size]);
|
||||
|
||||
// Show loading state while fetching data
|
||||
const isLoading = displayStatus === 'loading';
|
||||
const isLoading = loading;
|
||||
|
||||
// Show error notification if fetch fails
|
||||
useEffect(() => {
|
||||
if (displayStatus === 'failed' && displayError) {
|
||||
if (!isLoading && error) {
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: `获取知识库列表失败: ${displayError.message || displayError}`,
|
||||
message: `获取知识库列表失败: ${error.message || error}`,
|
||||
type: 'danger',
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [displayStatus, displayError, dispatch]);
|
||||
}, [isLoading, error, dispatch]);
|
||||
|
||||
// Show notification for operation status
|
||||
useEffect(() => {
|
||||
@ -169,17 +94,7 @@ export default function KnowledgeBase() {
|
||||
// 操作成功通知由具体函数处理,这里只刷新列表
|
||||
|
||||
// Refresh the list after successful operation
|
||||
if (isSearching && searchKeyword.trim()) {
|
||||
dispatch(
|
||||
searchKnowledgeBases({
|
||||
keyword: searchKeyword,
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
dispatch(fetchKnowledgeBases(pagination));
|
||||
}
|
||||
} else if (operationStatus === 'failed' && operationError) {
|
||||
dispatch(
|
||||
showNotification({
|
||||
@ -188,7 +103,47 @@ export default function KnowledgeBase() {
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [operationStatus, operationError, dispatch, pagination, isSearching, searchKeyword]);
|
||||
}, [operationStatus, operationError, dispatch, pagination]);
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchInputChange = (e) => {
|
||||
const value = e.target.value;
|
||||
setSearchKeyword(value);
|
||||
|
||||
// 如果搜索框清空,关闭下拉框
|
||||
if (!value.trim()) {
|
||||
dispatch(clearSearchResults());
|
||||
setIsSearchDropdownOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle search submit - 只影响下拉框,不影响主列表
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (searchKeyword.trim()) {
|
||||
// 只设置下拉框搜索状态,不设置全局isSearching状态
|
||||
dispatch(
|
||||
searchKnowledgeBases({
|
||||
keyword: searchKeyword,
|
||||
page: 1,
|
||||
page_size: 5, // 下拉框只显示少量结果
|
||||
})
|
||||
);
|
||||
setIsSearchDropdownOpen(true);
|
||||
} else {
|
||||
// 清空搜索及关闭下拉框
|
||||
handleClearSearch();
|
||||
}
|
||||
};
|
||||
|
||||
// Handle clear search
|
||||
const handleClearSearch = () => {
|
||||
setSearchKeyword('');
|
||||
// 不影响主列表显示,只关闭下拉框
|
||||
setIsSearchDropdownOpen(false);
|
||||
dispatch(clearSearchResults());
|
||||
};
|
||||
|
||||
// Handle pagination change
|
||||
const handlePageChange = (newPage) => {
|
||||
@ -426,7 +381,7 @@ export default function KnowledgeBase() {
|
||||
};
|
||||
|
||||
// Calculate total pages
|
||||
const totalPages = Math.ceil(displayTotal / pagination.page_size);
|
||||
const totalPages = Math.ceil(paginationData.total / pagination.page_size);
|
||||
|
||||
// 打开创建知识库弹窗
|
||||
const handleOpenCreateModal = () => {
|
||||
@ -458,7 +413,7 @@ export default function KnowledgeBase() {
|
||||
<div className='d-flex justify-content-between align-items-center mb-3'>
|
||||
<SearchBar
|
||||
searchKeyword={searchKeyword}
|
||||
isSearching={isSearching}
|
||||
isSearching={isSearchDropdownOpen} // 用于控制下拉框显示
|
||||
onSearchChange={handleSearchInputChange}
|
||||
onSearch={handleSearch}
|
||||
onClearSearch={handleClearSearch}
|
||||
@ -474,12 +429,6 @@ export default function KnowledgeBase() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isSearching && (
|
||||
<div className='alert alert-info'>
|
||||
搜索结果: "{searchKeyword}" - 找到 {displayTotal} 个知识库
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className='d-flex justify-content-center my-5'>
|
||||
<div className='spinner-border' role='status'>
|
||||
@ -489,14 +438,14 @@ export default function KnowledgeBase() {
|
||||
) : (
|
||||
<>
|
||||
<KnowledgeBaseList
|
||||
knowledgeBases={displayData}
|
||||
isSearching={isSearching}
|
||||
knowledgeBases={knowledgeBases}
|
||||
isSearching={false} // 始终为false,因为搜索不影响主列表
|
||||
onCardClick={handleCardClick}
|
||||
onRequestAccess={handleRequestAccess}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
|
||||
{/* Pagination */}
|
||||
{/* Pagination - 始终显示 */}
|
||||
{totalPages > 1 && (
|
||||
<Pagination
|
||||
currentPage={pagination.page}
|
||||
|
@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { checkAuthThunk, loginThunk } from '../../store/auth/auth.thunk';
|
||||
import { showNotification } from '../../store/notification.slice';
|
||||
|
||||
export default function Login() {
|
||||
const dispatch = useDispatch();
|
||||
@ -10,6 +11,7 @@ export default function Login() {
|
||||
const [password, setPassword] = useState('leader123');
|
||||
const [errors, setErrors] = useState({});
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
@ -22,18 +24,20 @@ export default function Login() {
|
||||
try {
|
||||
await dispatch(checkAuthThunk()).unwrap();
|
||||
if (user) navigate('/');
|
||||
} catch (error) {}
|
||||
} catch (error) {
|
||||
// 检查登录状态失败,不需要显示通知
|
||||
}
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
if (!username) {
|
||||
newErrors.username = 'Username is required';
|
||||
newErrors.username = '请输入用户名';
|
||||
}
|
||||
if (!password) {
|
||||
newErrors.password = 'Password is required';
|
||||
newErrors.password = '请输入密码';
|
||||
} else if (password.length < 6) {
|
||||
newErrors.password = 'Password must be at least 6 characters';
|
||||
newErrors.password = '密码长度不能少于6个字符';
|
||||
}
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
@ -44,17 +48,19 @@ export default function Login() {
|
||||
setSubmitted(true);
|
||||
|
||||
if (validateForm()) {
|
||||
console.log('Form submitted successfully!');
|
||||
console.log('Username:', username);
|
||||
console.log('Password:', password);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await dispatch(loginThunk({ username, password })).unwrap();
|
||||
navigate('/');
|
||||
} catch (error) {
|
||||
// 登录失败的错误通知已在thunk中处理
|
||||
console.error('Login failed:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='position-absolute top-50 start-50 translate-middle d-flex flex-column gap-4 align-items-center'>
|
||||
<div className='title text-center h1'>OOIN 智能知识库</div>
|
||||
@ -69,7 +75,7 @@ export default function Login() {
|
||||
type='text'
|
||||
className={`form-control form-control-lg${submitted && errors.username ? ' is-invalid' : ''}`}
|
||||
id='username'
|
||||
placeholder='Username'
|
||||
placeholder='用户名'
|
||||
required
|
||||
onChange={(e) => setUsername(e.target.value.trim())}
|
||||
></input>
|
||||
@ -80,7 +86,7 @@ export default function Login() {
|
||||
value={password}
|
||||
type='password'
|
||||
id='password'
|
||||
placeholder='Password'
|
||||
placeholder='密码'
|
||||
required
|
||||
className={`form-control form-control-lg${submitted && errors.password ? ' is-invalid' : ''}`}
|
||||
aria-describedby='passwordHelpBlock'
|
||||
@ -89,14 +95,25 @@ export default function Login() {
|
||||
{submitted && errors.password && <div className='invalid-feedback'>{errors.password}</div>}
|
||||
</div>
|
||||
<Link to='#' className='find-password text-body-secondary'>
|
||||
Forgot password?
|
||||
忘记密码?
|
||||
</Link>
|
||||
<button type='submit' className='btn btn-dark btn-lg w-100'>
|
||||
Login
|
||||
<button type='submit' className='btn btn-dark btn-lg w-100' disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<span
|
||||
className='spinner-border spinner-border-sm me-2'
|
||||
role='status'
|
||||
aria-hidden='true'
|
||||
></span>
|
||||
登录中...
|
||||
</>
|
||||
) : (
|
||||
'登录'
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
<Link to='/signup' className='go-to-signup w-100 link-underline-light h5 text-center'>
|
||||
Need Account?
|
||||
没有账号?去注册
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
@ -10,6 +10,7 @@ import Login from '../pages/Auth/Login';
|
||||
import Signup from '../pages/Auth/Signup';
|
||||
import ProtectedRoute from './protectedRoute';
|
||||
import { useSelector } from 'react-redux';
|
||||
import NotificationSnackbar from '../components/NotificationSnackbar';
|
||||
|
||||
function AppRouter() {
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
@ -19,6 +20,8 @@ function AppRouter() {
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<NotificationSnackbar />
|
||||
|
||||
<Routes>
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route
|
||||
|
@ -101,7 +101,7 @@ const get = async (url, params = {}) => {
|
||||
return await mockGet(url, params);
|
||||
}
|
||||
|
||||
const res = await api.get(url, { params });
|
||||
const res = await api.get(url, { ...params });
|
||||
return res.data;
|
||||
} catch (error) {
|
||||
if (!hasCheckedServer || (error.request && !error.response)) {
|
||||
|
@ -10,9 +10,12 @@ export const loginThunk = createAsyncThunk(
|
||||
'auth/login',
|
||||
async ({ username, password }, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const { message, data } = await post('/auth/login/', { username, password });
|
||||
console.log('data', data);
|
||||
const { message, data, code } = await post('/auth/login/', { username, password });
|
||||
console.log('code', code);
|
||||
|
||||
if (code !== 200) {
|
||||
throw new Error(message || 'Something went wrong');
|
||||
}
|
||||
if (!data) {
|
||||
throw new Error(message || 'Something went wrong');
|
||||
}
|
||||
@ -24,6 +27,7 @@ export const loginThunk = createAsyncThunk(
|
||||
return data;
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.message || 'Something went wrong';
|
||||
console.log(errorMessage);
|
||||
dispatch(
|
||||
showNotification({
|
||||
message: errorMessage,
|
||||
|
@ -34,7 +34,7 @@ export const fetchKnowledgeBases = createAsyncThunk(
|
||||
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/', {
|
||||
const response = await get('/knowledge-bases/search', {
|
||||
params: {
|
||||
keyword,
|
||||
page,
|
||||
|
Loading…
Reference in New Issue
Block a user