[dev]add update profiles

This commit is contained in:
susie-laptop 2025-04-15 21:45:59 -04:00
parent 1ee764d4e8
commit 8688db5cb4
8 changed files with 288 additions and 82 deletions

View File

@ -28,7 +28,7 @@ const Snackbar = ({ type = 'primary', message, duration = 3000, 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 z-2 gap-2 z-3`}
className={`snackbar alert alert-${type} d-flex align-items-center justify-content-between position-fixed top-10 start-50 translate-middle w-50 gap-2`}
role='alert'
>
<SvgIcon className={icons[type]} />

View File

@ -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 (
<div className='modal show d-block' style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
<div className='modal-dialog modal-dialog-centered'>
<div className='modal-content'>
<form className='modal-content' onSubmit={handleSubmit}>
<div className='modal-header border-0'>
<h5 className='modal-title'>管理员个人设置</h5>
<h5 className='modal-title'>个人设置</h5>
<button type='button' className='btn-close' onClick={onClose}></button>
</div>
<div className='modal-body'>
<div className='mb-4'>
<h6 className='text-secondary mb-3'>个人信息</h6>
<div className='mb-3'>
<label className='form-label text-secondary'>姓名</label>
<label className='form-label text-secondary'>用户</label>
<input type='text' className='form-control' value={user?.username || ''} readOnly />
</div>
<div className='mb-3'>
<label className='form-label text-secondary'>姓名</label>
<input
type='text'
className='form-control'
value={formData?.name || ''}
onChange={handleInputChange}
disabled={loading}
/>
{submitted && errors.name && <div className='invalid-feedback'>{errors.name}</div>}
</div>
<div className='mb-3'>
<label className='form-label text-secondary'>邮箱</label>
<input
type='email'
className='form-control'
value={user?.email || 'admin@ooin.com'}
readOnly
value={formData?.email || 'admin@ooin.com'}
onChange={handleInputChange}
disabled={loading}
/>
{submitted && errors.email && <div className='invalid-feedback'>{errors.email}</div>}
</div>
<div className='mb-3'>
<select
className={`form-select form-select-lg${
submitted && errors.department ? ' is-invalid' : ''
}`}
id='department'
name='department'
value={formData.department}
onChange={handleInputChange}
disabled={loading}
required
>
<option value='' disabled>
选择部门
</option>
{Object.keys(departmentGroups).map((dept, index) => (
<option key={index} value={dept}>
{dept}
</option>
))}
</select>
{submitted && errors.department && (
<div className='invalid-feedback'>{errors.department}</div>
)}
</div>
<div className='mb-3'>
<select
className={`form-select form-select-lg${
submitted && errors.group ? ' is-invalid' : ''
}`}
id='group'
name='group'
value={formData.group}
onChange={handleInputChange}
disabled={loading || !formData.department}
required
>
<option value='' disabled>
{formData.department ? '选择组别' : '请先选择部门'}
</option>
{availableGroups.map((group, index) => (
<option key={index} value={group}>
{group}
</option>
))}
</select>
{submitted && errors.group && <div className='invalid-feedback'>{errors.group}</div>}
</div>
</div>
<div className='mb-4'>
<div className='d-none mb-4'>
<h6 className='text-secondary mb-3'>安全设置</h6>
<div className='d-flex justify-content-between align-items-center p-3 bg-light rounded'>
<div>
@ -58,7 +223,7 @@ export default function UserSettingsModal({ show, onClose }) {
</div>
</div>
<div>
<div className='d-none'>
<h6 className='text-secondary mb-3'>通知设置</h6>
<div className='form-check form-switch mb-3 dark-switch'>
<input
@ -87,15 +252,17 @@ export default function UserSettingsModal({ show, onClose }) {
</div>
</div>
<div className='modal-footer border-0'>
<button type='button' className='btn btn-outline-dark' onClick={onClose}>
<button type='button' disabled={loading} className='btn btn-outline-dark' onClick={onClose}>
取消
</button>
<button type='button' className='btn btn-dark'>
<button type='submit' className='btn btn-dark' disabled={loading}>
保存更改
</button>
</div>
</div>
</form>
</div>
</div>
);
}
export default UserSettingsModal;

View File

@ -121,11 +121,11 @@ export default function HeaderWithNav() {
transform: 'translate(0px, 34px)',
}}
>
<li className='d-none'>
<li>
<Link
className='dropdown-item'
to='#'
// onClick={() => setShowSettings(true)}
onClick={() => setShowSettings(true)}
>
个人设置
</Link>

View File

@ -127,8 +127,7 @@ const KnowledgeBaseForm = ({
</div>
{/* 仅当不是私有知识库时才显示部门选项 */}
{formData.type === 'member' ||
(formData.type === 'leader' && (
{(formData.type === 'member' || formData.type === 'leader') && (
<div className='mb-3'>
<label htmlFor='department' className='form-label'>
部门 {isAdmin && <span className='text-danger'>*</span>}
@ -167,7 +166,7 @@ const KnowledgeBaseForm = ({
</>
)}
</div>
))}
)}
{/* 仅当不是私有知识库时才显示组别选项 */}
{formData.type === 'member' && (

View File

@ -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\/?$/, '');

View File

@ -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;

View File

@ -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,17 +40,19 @@ 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;
if (token) {
const encryptedToken = CryptoJS.AES.encrypt(token, secretKey).toString();
sessionStorage.setItem('token', encryptedToken);
}
// 显示注册成功通知
dispatch(
@ -59,10 +61,8 @@ export const signupThunk = createAsyncThunk('auth/signup', async (userData, { re
type: 'success',
})
);
return response.data;
}
return rejectWithValue(response.message || '注册失败');
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);
}
});

View File

@ -9,6 +9,7 @@
.snackbar {
top: 6.5rem;
z-index: 9999;
}
/* Markdown styling in chat messages */