feat: 初始化项目并完成基础功能开发
- 完成项目初始化
- 实现用户注册、登录功能
- 完成用户管理与权限管理模块
- 开发后端 Tracker 服务器项目管理接口
- 实现日志管理接口
Change-Id: Ia4bde1c9ff600352a7ff0caca0cc50b02cad1af7
diff --git a/react-ui/src/components/RightContent/AvatarDropdown.tsx b/react-ui/src/components/RightContent/AvatarDropdown.tsx
new file mode 100644
index 0000000..34ceb15
--- /dev/null
+++ b/react-ui/src/components/RightContent/AvatarDropdown.tsx
@@ -0,0 +1,142 @@
+import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
+import { history, useModel } from '@umijs/max';
+import { Spin } from 'antd';
+import { createStyles } from 'antd-style';
+import { stringify } from 'querystring';
+import type { MenuInfo } from 'rc-menu/lib/interface';
+import React, { useCallback } from 'react';
+import { flushSync } from 'react-dom';
+import HeaderDropdown from '../HeaderDropdown';
+import { setRemoteMenu } from '@/services/session';
+import { clearSessionToken } from '@/access';
+import { logout } from '@/services/system/auth';
+
+export type GlobalHeaderRightProps = {
+ menu?: boolean;
+ children?: React.ReactNode;
+};
+
+export const AvatarName = () => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser } = initialState || {};
+ return <span className="anticon">{currentUser?.nickName}</span>;
+};
+
+const useStyles = createStyles(({ token }) => {
+ return {
+ action: {
+ display: 'flex',
+ height: '48px',
+ marginLeft: 'auto',
+ overflow: 'hidden',
+ alignItems: 'center',
+ padding: '0 8px',
+ cursor: 'pointer',
+ borderRadius: token.borderRadius,
+ '&:hover': {
+ backgroundColor: token.colorBgTextHover,
+ },
+ },
+ };
+});
+
+export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => {
+ /**
+ * 退出登录,并且将当前的 url 保存
+ */
+ const loginOut = async () => {
+ await logout();
+ clearSessionToken();
+ setRemoteMenu(null);
+ const { search, pathname } = window.location;
+ const urlParams = new URL(window.location.href).searchParams;
+ /** 此方法会跳转到 redirect 参数所在的位置 */
+ const redirect = urlParams.get('redirect');
+ // Note: There may be security issues, please note
+ if (window.location.pathname !== '/user/login' && !redirect) {
+ history.replace({
+ pathname: '/user/login',
+ search: stringify({
+ redirect: pathname + search,
+ }),
+ });
+ }
+ };
+ const { styles } = useStyles();
+
+ const { initialState, setInitialState } = useModel('@@initialState');
+
+ const onMenuClick = useCallback(
+ (event: MenuInfo) => {
+ const { key } = event;
+ if (key === 'logout') {
+ flushSync(() => {
+ setInitialState((s) => ({ ...s, currentUser: undefined }));
+ });
+ loginOut();
+ return;
+ }
+ history.push(`/account/${key}`);
+ },
+ [setInitialState],
+ );
+
+ const loading = (
+ <span className={styles.action}>
+ <Spin
+ size="small"
+ style={{
+ marginLeft: 8,
+ marginRight: 8,
+ }}
+ />
+ </span>
+ );
+
+ if (!initialState) {
+ return loading;
+ }
+
+ const { currentUser } = initialState;
+
+ if (!currentUser || !currentUser.nickName) {
+ return loading;
+ }
+
+ const menuItems = [
+ ...(menu
+ ? [
+ {
+ key: 'center',
+ icon: <UserOutlined />,
+ label: '个人中心',
+ },
+ {
+ key: 'settings',
+ icon: <SettingOutlined />,
+ label: '个人设置',
+ },
+ {
+ type: 'divider' as const,
+ },
+ ]
+ : []),
+ {
+ key: 'logout',
+ icon: <LogoutOutlined />,
+ label: '退出登录',
+ },
+ ];
+
+ return (
+ <HeaderDropdown
+ menu={{
+ selectedKeys: [],
+ onClick: onMenuClick,
+ items: menuItems,
+ }}
+ >
+ {children}
+ </HeaderDropdown>
+ );
+};
diff --git a/react-ui/src/components/RightContent/index.tsx b/react-ui/src/components/RightContent/index.tsx
new file mode 100644
index 0000000..20a7831
--- /dev/null
+++ b/react-ui/src/components/RightContent/index.tsx
@@ -0,0 +1,31 @@
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import { SelectLang as UmiSelectLang } from '@umijs/max';
+import React from 'react';
+
+export type SiderTheme = 'light' | 'dark';
+
+export const SelectLang = () => {
+ return (
+ <UmiSelectLang
+ style={{
+ padding: 4,
+ }}
+ />
+ );
+};
+
+export const Question = () => {
+ return (
+ <div
+ style={{
+ display: 'flex',
+ height: 26,
+ }}
+ onClick={() => {
+ window.open('https://pro.ant.design/docs/getting-started');
+ }}
+ >
+ <QuestionCircleOutlined />
+ </div>
+ );
+};