feat: add change user-config
This commit is contained in:
@@ -75,6 +75,7 @@ export const App = () => {
|
||||
<Route path='/container/*' element={<ContainerApp />} />
|
||||
<Route path='/map/*' element={<MapApp />} />
|
||||
<Route path='/user-center/*' element={<UserApp />} />
|
||||
<Route path='/user/*' element={<UserApp />} />
|
||||
<Route path='/org/*' element={<OrgApp />} />
|
||||
<Route path='/config/*' element={<ConfigApp />} />
|
||||
<Route path='/app/*' element={<UserAppApp />} />
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import Backend from 'i18next-http-backend'; // 引入 Backend 插件
|
||||
|
||||
type I18NextProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const initI18n = (basename: string) => {
|
||||
// 初始化 i18n
|
||||
i18n
|
||||
.use(Backend) // 使用 Backend 插件
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: `${basename}/locales/{{lng}}/{{ns}}.json`, // 指定 JSON 文件的路径
|
||||
},
|
||||
lng: 'zh', // 默认语言
|
||||
fallbackLng: 'en', // 备用语言
|
||||
interpolation: {
|
||||
escapeValue: false, // react 已经安全地处理了转义
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 国际化组件,初始化
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const I18NextProvider = (props: I18NextProviderProps) => {
|
||||
const { children } = props;
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,14 +1,12 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { App } from './App.tsx';
|
||||
import './globals.css';
|
||||
import { initI18n, I18NextProvider } from './I18Next.tsx';
|
||||
import { basename } from './modules/basename.ts';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
initI18n(basename);
|
||||
import { I18NextProvider } from '@kevisual/components/translate/I18Next.tsx';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<I18NextProvider>
|
||||
<I18NextProvider basename={!DEV_SERVER ? '/root/locales' : '/'}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<App />
|
||||
</Suspense>
|
||||
|
||||
@@ -14,21 +14,15 @@ import { useTranslation } from 'react-i18next';
|
||||
import React from 'react';
|
||||
|
||||
export const LayoutUser = () => {
|
||||
const { open, setOpen, ...store } = useLayoutStore(
|
||||
const { open, setOpen, isAdmin, ...store } = useLayoutStore(
|
||||
useShallow((state) => ({
|
||||
open: state.openUser, //
|
||||
setOpen: state.setOpenUser,
|
||||
me: state.me,
|
||||
switchOrg: state.switchOrg,
|
||||
isAdmin: state.isAdmin,
|
||||
})),
|
||||
);
|
||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
queryLogin.cacheStore.getCurrentUser().then((res) => {
|
||||
const org = res?.orgs || [];
|
||||
setIsAdmin(org.includes('admin'));
|
||||
});
|
||||
}, []);
|
||||
const navigate = useNewNavigate();
|
||||
const { t } = useTranslation();
|
||||
const items = useMemo(() => {
|
||||
@@ -89,7 +83,7 @@ export const LayoutUser = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('w-full h-full absolute z-20 no-drag ', !open && 'hidden')}>
|
||||
<div className={clsx('w-full h-full absolute z-20 no-drag text-primary', !open && 'hidden')}>
|
||||
<div
|
||||
className='w-full absolute h-full opacity-60 z-0'
|
||||
onClick={() => {
|
||||
|
||||
@@ -62,6 +62,8 @@ export type LayoutStore = {
|
||||
openUser: boolean;
|
||||
setOpenUser: (openUser: boolean) => void;
|
||||
switchOrg: (username?: string, type?: 'user' | 'org') => Promise<void>;
|
||||
isAdmin: boolean;
|
||||
setIsAdmin: (isAdmin: boolean) => void;
|
||||
};
|
||||
export const useLayoutStore = create<LayoutStore>((set) => ({
|
||||
open: false,
|
||||
@@ -72,6 +74,7 @@ export const useLayoutStore = create<LayoutStore>((set) => ({
|
||||
const res = await queryLogin.getMe();
|
||||
if (res.code === 200) {
|
||||
set({ me: res.data });
|
||||
set({ isAdmin: res.data.orgs?.includes('admin') });
|
||||
}
|
||||
},
|
||||
openUser: false,
|
||||
@@ -87,4 +90,6 @@ export const useLayoutStore = create<LayoutStore>((set) => ({
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
isAdmin: false,
|
||||
setIsAdmin: (isAdmin) => set({ isAdmin }),
|
||||
}));
|
||||
|
||||
@@ -72,15 +72,15 @@ const FormModal = () => {
|
||||
}}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<form className='flex flex-col gap-6' onSubmit={handleSubmit(onFinish)}>
|
||||
<form className='flex flex-col gap-6 py-4' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='key' control={control} defaultValue='' render={({ field }) => <TextField label='key' {...field} disabled />} />
|
||||
<Controller name='version' control={control} defaultValue='' render={({ field }) => <TextField label='version' {...field} />} />
|
||||
<div>
|
||||
<Button type='submit' variant='contained' color='primary'>
|
||||
{t('submit')}
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('cancel')}
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -117,10 +117,10 @@ export const AppVersionList = () => {
|
||||
versionStore.setKey(appKey);
|
||||
versionStore.getList();
|
||||
}
|
||||
}, []);
|
||||
}, [appKey]);
|
||||
const appVersion = useMemo(() => {
|
||||
return versionStore.app?.version || '';
|
||||
}, [versionStore.app]);
|
||||
}, [versionStore.app?.version]);
|
||||
return (
|
||||
<div className='w-full h-full flex bg-slate-100'>
|
||||
<div className='p-2 bg-white'>
|
||||
@@ -153,6 +153,7 @@ export const AppVersionList = () => {
|
||||
const isPublish = item.version === appVersion;
|
||||
const color = isPublish ? 'bg-green-500' : '';
|
||||
const isRunning = item.status === 'running';
|
||||
console.log('appVersion', item, appVersion, versionStore.app);
|
||||
return (
|
||||
<div className='card w-[300px]' key={index}>
|
||||
<div className={'flex items-center justify-between'}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useUserAppStore } from '../store';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
@@ -12,7 +12,7 @@ import CodeOutlined from '@ant-design/icons/CodeOutlined';
|
||||
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
|
||||
import { FormControlLabel, Switch, useTheme } from '@mui/material';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { queryLogin, useNewNavigate } from '@/modules';
|
||||
import { DialogActions, Tooltip } from '@mui/material';
|
||||
import { marked } from 'marked';
|
||||
import clsx from 'clsx';
|
||||
@@ -28,6 +28,7 @@ import { TextField, InputAdornment } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { pick } from 'lodash-es';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useLayoutStore } from '@/modules/layout/store';
|
||||
|
||||
const FormModal = () => {
|
||||
const defaultValues = {
|
||||
@@ -75,6 +76,8 @@ const FormModal = () => {
|
||||
const isEdit = containerStore?.userApp?.id;
|
||||
const theme = useTheme();
|
||||
const defaultProps = theme.components?.MuiTextField?.defaultProps as any;
|
||||
const isAdmin = useLayoutStore(useShallow((state) => state.isAdmin));
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={containerStore.showEdit}
|
||||
@@ -99,7 +102,7 @@ const FormModal = () => {
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...defaultProps} {...field} label='description' multiline rows={4} fullWidth />}
|
||||
/>
|
||||
<Controller name='proxy' control={control} render={({ field }) => <Switch {...defaultProps} {...field} checked={field.value} />} />
|
||||
{isAdmin && <Controller name='proxy' control={control} render={({ field }) => <Switch {...defaultProps} {...field} checked={field.value} />} />}
|
||||
<Controller
|
||||
name='status'
|
||||
control={control}
|
||||
|
||||
@@ -52,7 +52,7 @@ export const useAppVersionStore = create<AppVersionStore>((set, get) => {
|
||||
key,
|
||||
},
|
||||
});
|
||||
get().getApp(key);
|
||||
get().getApp(key, true);
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
set({ list: res.data });
|
||||
|
||||
@@ -19,6 +19,7 @@ import { isEmpty, pick } from 'lodash-es';
|
||||
import { usePermissionModal } from '@kevisual/resources/index.ts';
|
||||
import React from 'react';
|
||||
import { queryLogin } from '@/modules';
|
||||
import { useLayoutStore } from '@/modules/layout/store';
|
||||
type DataYamlEditProps = {
|
||||
onSave: (data: any) => Promise<void>;
|
||||
type?: 'yaml' | 'json';
|
||||
@@ -190,14 +191,11 @@ export const DrawerEdit = () => {
|
||||
export const List = () => {
|
||||
const { list, getConfigList, setShowEdit, setFormData, deleteConfig, updateData, formData, detectConfig, onOpenKey } = useConfigStore();
|
||||
const [modal, contextHolder] = useModal();
|
||||
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||
const isAdmin = useLayoutStore(useShallow((state) => state.isAdmin));
|
||||
useEffect(() => {
|
||||
getConfigList();
|
||||
queryLogin.cacheStore.getCurrentUser().then((res) => {
|
||||
const org = res?.orgs || [];
|
||||
setIsAdmin(org.includes('admin'));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const { setOpen, contextHolder: contextHolderPermission } = usePermissionModal({
|
||||
onSave: async (values) => {
|
||||
const permission = values;
|
||||
@@ -253,17 +251,30 @@ export const List = () => {
|
||||
{t('Upload Config')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('Workspace Config Tips')}>
|
||||
<Tooltip title={t('User Config Tips')}>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
onOpenKey('workspace.json');
|
||||
onOpenKey('user.json');
|
||||
}}>
|
||||
{t('Workspace Config')}
|
||||
{t('User Config')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{isAdmin && (
|
||||
<Tooltip title={t('Workspace Config Tips')}>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
onOpenKey('workspace.json');
|
||||
}}>
|
||||
{t('Workspace Config')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<Tooltip title={t('VIP Config Tips')}>
|
||||
<Button
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
// import copy from 'copy-to-clipboard';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog, DialogTitle, DialogContent, Tooltip, Button, ButtonGroup } from '@mui/material';
|
||||
import { Dialog, DialogTitle, DialogContent, Tooltip, Button, ButtonGroup, Typography } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { getDirectoryAndName, toFile, uploadFileChunked } from '@kevisual/resources/index.ts';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
@@ -223,7 +223,6 @@ const PublishFormModal = () => {
|
||||
);
|
||||
};
|
||||
export const ContainerList = () => {
|
||||
const navicate = useNewNavigate();
|
||||
const [modal, contextHolder] = useModal();
|
||||
const containerStore = useContainerStore(
|
||||
useShallow((state) => {
|
||||
@@ -245,7 +244,6 @@ export const ContainerList = () => {
|
||||
}),
|
||||
);
|
||||
|
||||
// const [codeEdit, setCodeEdit] = useState(false);
|
||||
useEffect(() => {
|
||||
containerStore.getList();
|
||||
}, []);
|
||||
@@ -264,8 +262,15 @@ export const ContainerList = () => {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
<div className='grow overflow-auto scrollbar bg-gray-100'>
|
||||
<div className='flex grow flex-col overflow-hidden h-full'>
|
||||
<div className='p-2 flex flex-col gap-2 text-secondary px-4 mx-4'>
|
||||
<div className='text-sm'>轻代码或者配置文件替换。</div>
|
||||
</div>
|
||||
<div
|
||||
className='grow overflow-auto scrollbar bg-gray-100'
|
||||
style={{
|
||||
height: 'calc(100% - 40px)',
|
||||
}}>
|
||||
<div className='flex flex-wrap gap-x-10 gap-y-4 rounded-sm pt-10 justify-center'>
|
||||
{containerStore.list.length > 0 &&
|
||||
containerStore.list.map((item) => {
|
||||
|
||||
125
src/pages/user/admin/store/admin-store.ts
Normal file
125
src/pages/user/admin/store/admin-store.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { create } from 'zustand';
|
||||
import { query } from '@/modules';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Result } from '@kevisual/query/query';
|
||||
type AdminStore = {
|
||||
/**
|
||||
* 创建新用户
|
||||
* @returns
|
||||
*/
|
||||
createNewUser: (data: any) => Promise<void>;
|
||||
/**
|
||||
* 删除用户
|
||||
* @param id
|
||||
*/
|
||||
deleteUser: (id: string) => Promise<void>;
|
||||
/**
|
||||
* 更新用户
|
||||
* @param id
|
||||
* @param data
|
||||
*/
|
||||
updateUser: (id: string, data: any) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
* @param id
|
||||
*/
|
||||
resetPassword: (id: string, password?: string) => Promise<void>;
|
||||
|
||||
/**
|
||||
* 修改用户名
|
||||
* @param id
|
||||
* @param name
|
||||
*/
|
||||
changeName: (id: string, name: string) => Promise<Result<any>>;
|
||||
|
||||
/**
|
||||
* 检查用户是否存在
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
checkUserExist: (name: string) => Promise<boolean | null>;
|
||||
};
|
||||
|
||||
export const useAdminStore = create<AdminStore>((set) => ({
|
||||
createNewUser: async (data: any) => {
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'createNewUser',
|
||||
data,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
toast.success('创建用户成功');
|
||||
} else {
|
||||
toast.error(res.message || '创建用户失败');
|
||||
}
|
||||
},
|
||||
deleteUser: async (id: string) => {
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'deleteUser',
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
toast.success('删除用户成功');
|
||||
} else {
|
||||
toast.error(res.message || '删除用户失败');
|
||||
}
|
||||
},
|
||||
updateUser: async (id: string, data: any) => {
|
||||
console.log('updateUser', id, data);
|
||||
},
|
||||
resetPassword: async (id: string, password?: string) => {
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'resetPassword',
|
||||
data: {
|
||||
id,
|
||||
password,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
if (res.data.password) {
|
||||
toast.success('new password is ' + res.data.password);
|
||||
} else {
|
||||
toast.success('重置密码成功');
|
||||
}
|
||||
} else {
|
||||
toast.error(res.message || '重置密码失败');
|
||||
}
|
||||
},
|
||||
changeName: async (id: string, name: string) => {
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'changeName',
|
||||
data: {
|
||||
id,
|
||||
newName: name,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
toast.success('修改用户名成功');
|
||||
} else {
|
||||
toast.error(res.message || '修改用户名失败');
|
||||
}
|
||||
return res;
|
||||
},
|
||||
checkUserExist: async (name: string) => {
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'checkUserExist',
|
||||
data: {
|
||||
username: name,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
const user = res.data || {};
|
||||
return !!user.id;
|
||||
} else {
|
||||
toast.error(res.message || '检查用户是否存在,请求失败');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}));
|
||||
@@ -1,22 +1,18 @@
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useUserStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@kevisual/components/card/CardBlank.tsx';
|
||||
import { Dialog, ButtonGroup, Button, DialogContent, DialogTitle } from '@mui/material';
|
||||
import { Dialog, ButtonGroup, Button, DialogContent, DialogTitle, Tooltip } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/components/button/index.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModal } from '@kevisual/components/modal/Confirm.tsx';
|
||||
import { TextField } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
import { useAdminStore } from '../admin/store/admin-store';
|
||||
import { SquareAsterisk, Edit as EditOutlined, Trash as DeleteOutlined, Plus as PlusOutlined, UserPen } from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
const FormModal = () => {
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const userStore = useUserStore(
|
||||
@@ -30,7 +26,7 @@ const FormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const adminStore = useAdminStore(useShallow((state) => state));
|
||||
useEffect(() => {
|
||||
const open = userStore.showEdit;
|
||||
if (open) {
|
||||
@@ -46,7 +42,12 @@ const FormModal = () => {
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
const pickValues = pick(values, ['id', 'username', 'description']);
|
||||
userStore.updateData(pickValues);
|
||||
if (pickValues.id) {
|
||||
userStore.updateData(pickValues);
|
||||
} else {
|
||||
const newPickValues = pick(values, ['username', 'description', 'password']);
|
||||
adminStore.createNewUser(newPickValues);
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
@@ -69,7 +70,7 @@ const FormModal = () => {
|
||||
}}>
|
||||
<DialogTitle>{isEdit ? t('Edit') : t('Add')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<form onSubmit={handleSubmit(onFinish)}>
|
||||
<form className='flex flex-col gap-6 pt-4' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='username' control={control} defaultValue='' render={({ field }) => <TextField {...field} label='username' fullWidth />} />
|
||||
<Controller
|
||||
name='description'
|
||||
@@ -77,6 +78,60 @@ const FormModal = () => {
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='description' multiline rows={4} fullWidth />}
|
||||
/>
|
||||
{!isEdit && (
|
||||
<Controller
|
||||
name='password'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='password' type='password' fullWidth />}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' type='button' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const NameModal = () => {
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const userStore = useUserStore(useShallow((state) => state));
|
||||
const adminStore = useAdminStore(useShallow((state) => state));
|
||||
const onFinish = async (values: any) => {
|
||||
const check = await adminStore.checkUserExist(values.username);
|
||||
if (check === false) {
|
||||
const uid = userStore.formData.id;
|
||||
if (!uid) {
|
||||
toast.error('获取用户id失败');
|
||||
return;
|
||||
}
|
||||
const res = await adminStore.changeName(uid, values.username);
|
||||
if (res.code === 200) {
|
||||
userStore.setShowNameEdit(false);
|
||||
userStore.getList();
|
||||
}
|
||||
} else {
|
||||
toast.error('用户名已存在,请重新输入');
|
||||
}
|
||||
};
|
||||
const onClose = () => {
|
||||
userStore.setShowNameEdit(false);
|
||||
reset({});
|
||||
userStore.setFormData({});
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog open={userStore.showNameEdit} onClose={() => userStore.setShowNameEdit(false)}>
|
||||
<DialogTitle>修改用户名</DialogTitle>
|
||||
<DialogContent>
|
||||
<form className='flex flex-col gap-6 pt-4' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='username' control={control} defaultValue='' render={({ field }) => <TextField {...field} label='username' fullWidth />} />
|
||||
<div>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Submit')}
|
||||
@@ -90,7 +145,6 @@ const FormModal = () => {
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const List = () => {
|
||||
const [modal, contextHolder] = useModal();
|
||||
const userStore = useUserStore(
|
||||
@@ -98,15 +152,16 @@ export const List = () => {
|
||||
return {
|
||||
setFormData: state.setFormData,
|
||||
setShowEdit: state.setShowEdit,
|
||||
setShowNameEdit: state.setShowNameEdit,
|
||||
list: state.list,
|
||||
deleteData: state.deleteData,
|
||||
getList: state.getList,
|
||||
loading: state.loading,
|
||||
updateData: state.updateData,
|
||||
formData: state.formData,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const adminStore = useAdminStore(useShallow((state) => state));
|
||||
useEffect(() => {
|
||||
userStore.getList();
|
||||
}, []);
|
||||
@@ -119,7 +174,7 @@ export const List = () => {
|
||||
<div className='w-full h-full flex'>
|
||||
<div className='p-2'>
|
||||
<IconButton onClick={onAdd} variant='contained'>
|
||||
<PlusOutlined />
|
||||
<PlusOutlined className='w-4 h-4' />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
@@ -151,27 +206,57 @@ export const List = () => {
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
modal.confirm({
|
||||
title: 'Delete',
|
||||
content: 'Are you sure delete this data?',
|
||||
onOk: () => {
|
||||
userStore.deleteData(item.id);
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
<Tooltip title='编辑'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='修改用户名'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowNameEdit(true);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<UserPen />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='重置密码'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
modal.confirm({
|
||||
title: '重置密码',
|
||||
content: 'Are you sure reset password?',
|
||||
onOk: () => {
|
||||
adminStore.resetPassword(item.id);
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<SquareAsterisk />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='禁用'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
modal.confirm({
|
||||
title: 'Delete',
|
||||
content: 'Are you sure delete this data?',
|
||||
onOk: async () => {
|
||||
await adminStore.deleteUser(item.id);
|
||||
userStore.getList();
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,6 +273,7 @@ export const List = () => {
|
||||
</div>
|
||||
</div>
|
||||
<FormModal />
|
||||
<NameModal />
|
||||
{contextHolder}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TextField } from '@mui/material';
|
||||
import { InputAdornment, TextField, Tooltip } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Button } from '@mui/material';
|
||||
import { useUserStore } from '../store';
|
||||
@@ -10,6 +10,8 @@ import UploadOutlined from '@ant-design/icons/UploadOutlined';
|
||||
import PandaPNG from '@/assets/panda.png';
|
||||
import { FileUpload } from '../module/FileUpload';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Edit } from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export const Profile = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -73,7 +75,7 @@ export const Profile = () => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='w-full h-full bg-amber-50 p-4 '>
|
||||
<div className='w-full h-full bg-amber-50 p-4 text-primary'>
|
||||
<div className=' shadow-lg p-4 bg-white rounded-lg'>
|
||||
<div className='text-2xl'>{t('Profile')}</div>
|
||||
<div className='text-sm text-secondary'>{t('Edit your profile')}</div>
|
||||
@@ -87,7 +89,38 @@ export const Profile = () => {
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label={t('Name')} className='w-full border rounded-lg p-2' disabled />}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
label={t('Name')}
|
||||
className='w-full border rounded-lg p-2'
|
||||
disabled
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<Tooltip title={t('Apply Change Name')}>
|
||||
<InputAdornment position='end'>
|
||||
<Edit
|
||||
className='w-4 h-4 cursor-pointer text-primary'
|
||||
onClick={() => {
|
||||
console.log('onClick edit');
|
||||
toast.info('联系客服修改,因为名称和上传文件绑定了。', {
|
||||
autoClose: 20000,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</InputAdornment>
|
||||
</Tooltip>
|
||||
),
|
||||
onKeyDown: (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
// setSearch(field.value);
|
||||
}
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name='avatar'
|
||||
|
||||
@@ -4,6 +4,8 @@ import { message } from '@/modules/message';
|
||||
type UserStore = {
|
||||
showEdit: boolean;
|
||||
setShowEdit: (showEdit: boolean) => void;
|
||||
showNameEdit: boolean;
|
||||
setShowNameEdit: (showNameEdit: boolean) => void;
|
||||
formData: any;
|
||||
setFormData: (formData: any) => void;
|
||||
loading: boolean;
|
||||
@@ -18,6 +20,8 @@ export const useUserStore = create<UserStore>((set, get) => {
|
||||
return {
|
||||
showEdit: false,
|
||||
setShowEdit: (showEdit) => set({ showEdit }),
|
||||
showNameEdit: false,
|
||||
setShowNameEdit: (showNameEdit) => set({ showNameEdit }),
|
||||
formData: {},
|
||||
setFormData: (formData) => set({ formData }),
|
||||
loading: false,
|
||||
|
||||
Reference in New Issue
Block a user