From 8688db5cb4fa82991918cdd5dff6650eda5240ec Mon Sep 17 00:00:00 2001 From: susie-laptop Date: Tue, 15 Apr 2025 21:45:59 -0400 Subject: [PATCH] [dev]add update profiles --- src/components/Snackbar.jsx | 2 +- src/components/UserSettingsModal.jsx | 195 ++++++++++++++++-- src/layouts/HeaderWithNav.jsx | 4 +- .../Detail/components/KnowledgeBaseForm.jsx | 83 ++++---- src/services/websocket.js | 2 +- src/store/auth/auth.slice.js | 13 +- src/store/auth/auth.thunk.js | 70 +++++-- src/styles/style.scss | 1 + 8 files changed, 288 insertions(+), 82 deletions(-) diff --git a/src/components/Snackbar.jsx b/src/components/Snackbar.jsx index 7c26224..eae061b 100644 --- a/src/components/Snackbar.jsx +++ b/src/components/Snackbar.jsx @@ -28,7 +28,7 @@ const Snackbar = ({ type = 'primary', message, duration = 3000, onClose }) => { return (
diff --git a/src/components/UserSettingsModal.jsx b/src/components/UserSettingsModal.jsx index 73a0828..a2059e3 100644 --- a/src/components/UserSettingsModal.jsx +++ b/src/components/UserSettingsModal.jsx @@ -1,40 +1,205 @@ -import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import '../styles/style.scss'; +import { updateProfileThunk } from '../store/auth/auth.thunk'; -export default function UserSettingsModal({ show, onClose }) { - const { user } = useSelector((state) => state.auth); +// 部门和组别的映射关系 +const departmentGroups = { + 达人部门: ['达人'], + 商务部门: ['商务'], + 样本中心: ['样本'], + 产品部门: ['产品'], + AI自媒体: ['AI自媒体'], + HR: ['HR'], + 技术部门: ['技术'], +}; + +function UserSettingsModal({ show, onClose }) { + const { user, loading } = useSelector((state) => state.auth); const [lastPasswordChange] = useState('30天前'); // This would come from backend in real app + const [formData, setFormData] = useState({}); + // 可选的组别列表 + const [availableGroups, setAvailableGroups] = useState([]); + + const [submitted, setSubmitted] = useState(false); + const [errors, setErrors] = useState({}); + + const dispatch = useDispatch(); + + useEffect(() => { + if (user) { + setFormData({ + name: user.name, + email: user.email, + department: user.department, + group: user.group, + }); + } + }, [user]); + + // 当部门变化时更新可用的组别 + useEffect(() => { + if (formData.department && departmentGroups[formData.department]) { + setAvailableGroups(departmentGroups[formData.department]); + } else { + setAvailableGroups([]); + } + }, [formData.department]); if (!show) return null; + const handleInputChange = (e) => { + const { name, value } = e.target; + + if (name === 'department') { + setFormData({ + ...formData, + [name]: value, + ['group']: '', + }); + } else { + setFormData({ + ...formData, + [name]: value, + }); + } + + // 清除对应的错误信息 + if (errors[name]) { + setErrors({ + ...errors, + [name]: '', + }); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setSubmitted(true); + + if (validateForm()) { + console.log('Form submitted successfully!'); + console.log('Update data:', formData); + try { + await dispatch(updateProfileThunk(formData)).unwrap(); + } catch (error) { + console.error('Signup failed:', error); + } + } + }; + + const validateForm = () => { + const newErrors = {}; + if (!formData.email) { + newErrors.email = 'Email is required'; + } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(formData.email)) { + newErrors.email = 'Invalid email address'; + } + + if (!formData.name) { + newErrors.name = 'Name is required'; + } + + if (!formData.department) { + newErrors.department = '请选择部门'; + } + + if (!formData.group) { + newErrors.group = '请选择组别'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + return (
-
+
-
管理员个人设置
+
个人设置
个人信息
- +
+
+ + + {submitted && errors.name &&
{errors.name}
} +
+ {submitted && errors.email &&
{errors.email}
} +
+
+ + {submitted && errors.department && ( +
{errors.department}
+ )} +
+
+ + {submitted && errors.group &&
{errors.group}
}
-
+
安全设置
@@ -58,7 +223,7 @@ export default function UserSettingsModal({ show, onClose }) {
-
+
通知设置
- -
-
+
); } + +export default UserSettingsModal; diff --git a/src/layouts/HeaderWithNav.jsx b/src/layouts/HeaderWithNav.jsx index a23d0e0..1a923d8 100644 --- a/src/layouts/HeaderWithNav.jsx +++ b/src/layouts/HeaderWithNav.jsx @@ -121,11 +121,11 @@ export default function HeaderWithNav() { transform: 'translate(0px, 34px)', }} > -
  • +
  • setShowSettings(true)} + onClick={() => setShowSettings(true)} > 个人设置 diff --git a/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx b/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx index 1ed0640..05edc08 100644 --- a/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx +++ b/src/pages/KnowledgeBase/Detail/components/KnowledgeBaseForm.jsx @@ -58,7 +58,7 @@ const KnowledgeBaseForm = ({ // 是否显示类型更改按钮 const showTypeChangeButton = hasTypeChanged || (isAdmin && hasDepartmentOrGroupChanged); - + return (
    @@ -127,47 +127,46 @@ const KnowledgeBaseForm = ({
    {/* 仅当不是私有知识库时才显示部门选项 */} - {formData.type === 'member' || - (formData.type === 'leader' && ( -
    - - {isAdmin ? ( - <> - - {formErrors.department && ( -
    {formErrors.department}
    - )} - - ) : ( - <> - - - )} -
    - ))} + {(formData.type === 'member' || formData.type === 'leader') && ( +
    + + {isAdmin ? ( + <> + + {formErrors.department && ( +
    {formErrors.department}
    + )} + + ) : ( + <> + + + )} +
    + )} {/* 仅当不是私有知识库时才显示组别选项 */} {formData.type === 'member' && ( diff --git a/src/services/websocket.js b/src/services/websocket.js index 9c8e6e4..9902e19 100644 --- a/src/services/websocket.js +++ b/src/services/websocket.js @@ -2,7 +2,7 @@ import { addNotification, markNotificationAsRead } from '../store/notificationCe import store from '../store/store'; // 修改为默认导出 // 从环境变量获取 API URL -const API_URL = import.meta.env.VITE_API_URL || ''; +const API_URL = import.meta.env.VITE_API_URL || 'http://81.69.223.133:8008'; // 将 HTTP URL 转换为 WebSocket URL const WS_BASE_URL = API_URL.replace(/^http/, 'ws').replace(/\/api\/?$/, ''); diff --git a/src/store/auth/auth.slice.js b/src/store/auth/auth.slice.js index e5f2bb4..2dc029b 100644 --- a/src/store/auth/auth.slice.js +++ b/src/store/auth/auth.slice.js @@ -1,10 +1,9 @@ import { createSlice } from '@reduxjs/toolkit'; -import { checkAuthThunk, loginThunk, logoutThunk, signupThunk } from './auth.thunk'; +import { checkAuthThunk, loginThunk, logoutThunk, signupThunk, updateProfileThunk } from './auth.thunk'; const setPending = (state) => { state.loading = true; state.error = null; - state.user = null; }; const setFulfilled = (state, action) => { @@ -49,6 +48,16 @@ const authSlice = createSlice({ .addCase(signupThunk.fulfilled, setFulfilled) .addCase(signupThunk.rejected, setRejected) + .addCase(updateProfileThunk.pending, setPending) + .addCase(updateProfileThunk.fulfilled, (state, action) => { + state.user = { + ...state.user, + ...action.payload, + }; + state.loading = false; + }) + .addCase(updateProfileThunk.rejected, setRejected) + .addCase(logoutThunk.pending, (state) => { state.loading = true; state.error = null; diff --git a/src/store/auth/auth.thunk.js b/src/store/auth/auth.thunk.js index e816d44..7f8091f 100644 --- a/src/store/auth/auth.thunk.js +++ b/src/store/auth/auth.thunk.js @@ -1,5 +1,5 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; -import { get, post } from '../../services/api'; +import { get, post, put } from '../../services/api'; import { showNotification } from '../notification.slice'; import { logout } from './auth.slice'; import CryptoJS from 'crypto-js'; @@ -40,29 +40,29 @@ export const loginThunk = createAsyncThunk( export const signupThunk = createAsyncThunk('auth/signup', async (userData, { rejectWithValue, dispatch }) => { try { // 使用新的注册 API - const response = await post('/auth/register/', userData); - console.log('注册返回数据:', response); + const { data, message, code } = await post('/auth/register/', userData); + console.log('注册返回数据:', data); - // 处理新的返回格式 - if (response && response.code === 200) { - // // 将 token 加密存储到 sessionStorage - // const { token } = response.data; - // if (token) { - // const encryptedToken = CryptoJS.AES.encrypt(token, secretKey).toString(); - // sessionStorage.setItem('token', encryptedToken); - // } + if (code !== 200) { + throw new Error(message); + } + // 将 token 加密存储到 sessionStorage + const { token } = data; - // 显示注册成功通知 - dispatch( - showNotification({ - message: '注册成功', - type: 'success', - }) - ); - return response.data; + if (token) { + const encryptedToken = CryptoJS.AES.encrypt(token, secretKey).toString(); + sessionStorage.setItem('token', encryptedToken); } - return rejectWithValue(response.message || '注册失败'); + // 显示注册成功通知 + dispatch( + showNotification({ + message: '注册成功', + type: 'success', + }) + ); + + return data; } catch (error) { const errorMessage = error.response?.data?.message || '注册失败,请稍后重试'; dispatch( @@ -107,3 +107,33 @@ export const logoutThunk = createAsyncThunk('auth/logout', async (_, { rejectWit return rejectWithValue(errorMessage); } }); + +// 更新个人资料 +export const updateProfileThunk = createAsyncThunk('auth/updateProfile', async (userData, { rejectWithValue, dispatch }) => { + try { + const { data, message, code } = await put('/users/profile/', userData); + + if (code !== 200) { + throw new Error(message); + } + + // 显示更新成功通知 + dispatch( + showNotification({ + message: '个人信息更新成功', + type: 'success', + }) + ); + + return data; + } catch (error) { + const errorMessage = error.response?.data?.message || '更新失败,请稍后重试'; + dispatch( + showNotification({ + message: errorMessage, + type: 'danger', + }) + ); + return rejectWithValue(errorMessage); + } +}); \ No newline at end of file diff --git a/src/styles/style.scss b/src/styles/style.scss index b815aa5..7cd7833 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -9,6 +9,7 @@ .snackbar { top: 6.5rem; + z-index: 9999; } /* Markdown styling in chat messages */