This commit is contained in:
eleanor.mao
2025-06-03 00:03:08 +08:00
commit 48f2c49c2b
100 changed files with 34596 additions and 0 deletions

View File

@ -0,0 +1,13 @@
import { DefaultFooter } from '@ant-design/pro-components';
import React from 'react';
const Footer: React.FC = () => {
return (
<DefaultFooter
style={{ background: 'none' }}
copyright="杭州播络科技有限公司"
/>
);
};
export default Footer;

View File

@ -0,0 +1,27 @@
import { Dropdown } from 'antd';
import type { DropDownProps } from 'antd/es/dropdown';
import { createStyles } from 'antd-style';
import classNames from 'classnames';
import React from 'react';
const useStyles = createStyles(({ token }) => {
return {
dropdown: {
[`@media screen and (max-width: ${token.screenXS}px)`]: {
width: '100%',
},
},
};
});
export type HeaderDropdownProps = {
overlayClassName?: string;
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
} & Omit<DropDownProps, 'overlay'>;
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => {
const { styles } = useStyles();
return <Dropdown overlayClassName={classNames(styles.dropdown, cls)} {...restProps} />;
};
export default HeaderDropdown;

View File

@ -0,0 +1,49 @@
import { Image } from 'antd';
import { createStyles } from 'antd-style';
import { ReactElement } from 'react';
interface IProps {
videos: API.MaterialVideoInfo[];
}
const useStyles = createStyles(() => {
return {
container: {
width: 330,
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
marginLeft: '-10px',
marginBottom: '-10px',
},
imageContainer: {
marginLeft: '10px',
marginBottom: '10px',
},
};
});
const Previewer = (props: IProps) => {
const { videos = [] } = props;
const { styles } = useStyles();
const imageRender = (originalNode: ReactElement, info: { current: number }) => {
const video = videos[info.current];
if (!video || video.type === 'image') {
return originalNode;
}
// console.log('============>>>>>>', info);
return <video controls autoPlay width="400px" src={video.url} />;
};
return (
<div className={styles.container}>
<Image.PreviewGroup preview={{ imageRender, toolbarRender: () => null, destroyOnClose: true }}>
{videos.map(video => (
<Image key={video.coverUrl} width={100} src={video.coverUrl} wrapperClassName={styles.imageContainer} />
))}
</Image.PreviewGroup>
</div>
);
};
export default Previewer;

View File

@ -0,0 +1,125 @@
import { stringify } from 'querystring';
import { LogoutOutlined } from '@ant-design/icons';
import { history, useModel } from '@umijs/max';
import { Spin } from 'antd';
import { createStyles } from 'antd-style';
import type { MenuInfo } from 'rc-menu/lib/interface';
import React, { useCallback } from 'react';
import { flushSync } from 'react-dom';
import { outLogin } from '@/services/user';
import { clearToken } from '@/utils/login';
import HeaderDropdown from '../header-dropdown';
export type GlobalHeaderRightProps = {
menu?: boolean;
children?: React.ReactNode;
};
export const AvatarName = () => {
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {};
return <span className="anticon">{currentUser?.userName}</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> = ({ children }) => {
/**
* 退出登录,并且将当前的 url 保存
*/
const loginOut = async () => {
await outLogin();
clearToken();
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;
}
},
[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.userName) {
return loading;
}
const menuItems = [
{
key: 'logout',
icon: <LogoutOutlined />,
label: '退出登录',
},
];
return (
<HeaderDropdown
menu={{
selectedKeys: [],
onClick: onMenuClick,
items: menuItems,
}}
>
{children}
</HeaderDropdown>
);
};