feat:
This commit is contained in:
9
src/access.ts
Normal file
9
src/access.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @see https://umijs.org/docs/max/access#access
|
||||
* */
|
||||
export default function access(initialState: { currentUser?: API.CurrentUser } | undefined) {
|
||||
const { currentUser } = initialState ?? {};
|
||||
return {
|
||||
canAdmin: !!currentUser,
|
||||
};
|
||||
}
|
122
src/app.tsx
Normal file
122
src/app.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
|
||||
import { SettingDrawer } from '@ant-design/pro-components';
|
||||
import type { RunTimeLayoutConfig } from '@umijs/max';
|
||||
import { Link, history } from '@umijs/max';
|
||||
|
||||
import Footer from '@/components/footer';
|
||||
import { AvatarDropdown, AvatarName } from '@/components/right-content/avatar-dropdown';
|
||||
import { LOGIN_PATH } from '@/constants/global';
|
||||
import { requestConfig } from '@/requestConfig';
|
||||
import { currentUser as queryCurrentUser } from '@/services/user';
|
||||
import { gotoLogin } from '@/utils/login';
|
||||
|
||||
import defaultSettings from '../config/defaultSettings';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
/**
|
||||
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
||||
* */
|
||||
export async function getInitialState(): Promise<{
|
||||
settings?: Partial<LayoutSettings>;
|
||||
currentUser?: API.CurrentUser;
|
||||
loading?: boolean;
|
||||
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
|
||||
}> {
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const userInfo = await queryCurrentUser({
|
||||
skipErrorHandler: true,
|
||||
});
|
||||
return userInfo;
|
||||
} catch (error) {
|
||||
gotoLogin();
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
// 如果不是登录页面,执行
|
||||
const { location } = history;
|
||||
if (location.pathname !== LOGIN_PATH) {
|
||||
const currentUser = await fetchUserInfo();
|
||||
return {
|
||||
fetchUserInfo,
|
||||
currentUser,
|
||||
settings: defaultSettings as Partial<LayoutSettings>,
|
||||
};
|
||||
}
|
||||
return {
|
||||
fetchUserInfo,
|
||||
settings: defaultSettings as Partial<LayoutSettings>,
|
||||
};
|
||||
}
|
||||
|
||||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
||||
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
|
||||
return {
|
||||
actionsRender: () => [],
|
||||
avatarProps: {
|
||||
src:
|
||||
initialState?.currentUser?.avatar ||
|
||||
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
|
||||
title: <AvatarName />,
|
||||
render: (_, avatarChildren) => {
|
||||
return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
|
||||
},
|
||||
},
|
||||
waterMarkProps: {
|
||||
content: initialState?.currentUser?.userName,
|
||||
},
|
||||
footerRender: () => <Footer />,
|
||||
onPageChange: () => {
|
||||
const { location } = history;
|
||||
// 如果没有登录,重定向到 login
|
||||
if (!initialState?.currentUser && location.pathname !== LOGIN_PATH) {
|
||||
gotoLogin();
|
||||
}
|
||||
},
|
||||
links: isDev
|
||||
? [
|
||||
<Link key="openapi" to="/umi/plugin/openapi" target="_blank">
|
||||
<LinkOutlined />
|
||||
<span>OpenAPI 文档</span>
|
||||
</Link>,
|
||||
]
|
||||
: [],
|
||||
menuHeaderRender: undefined,
|
||||
// 自定义 403 页面
|
||||
// unAccessible: <div>unAccessible</div>,
|
||||
// 增加一个 loading 的状态
|
||||
childrenRender: children => {
|
||||
// if (initialState?.loading) return <PageLoading />;
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{isDev && (
|
||||
<SettingDrawer
|
||||
disableUrlParams
|
||||
enableDarkTheme
|
||||
settings={initialState?.settings}
|
||||
onSettingChange={settings => {
|
||||
setInitialState(preInitialState => ({
|
||||
...preInitialState,
|
||||
settings,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
...initialState?.settings,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @name request 配置,可以配置错误处理
|
||||
* 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
|
||||
* @doc https://umijs.org/docs/max/request#配置
|
||||
*/
|
||||
export const request = {
|
||||
...requestConfig,
|
||||
};
|
13
src/components/footer/index.tsx
Normal file
13
src/components/footer/index.tsx
Normal 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;
|
27
src/components/header-dropdown/index.tsx
Normal file
27
src/components/header-dropdown/index.tsx
Normal 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;
|
49
src/components/previewer/index.tsx
Normal file
49
src/components/previewer/index.tsx
Normal 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;
|
125
src/components/right-content/avatar-dropdown.tsx
Normal file
125
src/components/right-content/avatar-dropdown.tsx
Normal 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>
|
||||
);
|
||||
};
|
4
src/constants/anchor.ts
Normal file
4
src/constants/anchor.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum AnchorStatusType {
|
||||
Normal = 0,
|
||||
Forbid = 1,
|
||||
}
|
28
src/constants/api.ts
Normal file
28
src/constants/api.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export enum AdminAPI {
|
||||
// 用户级别
|
||||
LOGIN = '/api/bo/adminUser/login', // 登录
|
||||
OUT_LOGIN = '/api/bo/adminUser/outLogin',
|
||||
USER = '/api/bo/adminUser/get',
|
||||
// 群
|
||||
GROUP_LIST = '/api/bo/imGroup/list',
|
||||
GROUP_UPDATE = '/api/bo/imGroup/update',
|
||||
// 通告
|
||||
JOB_LIST = '/api/bo/job/list',
|
||||
JOB_UPDATE = '/api/bo/job/update',
|
||||
// 定制群
|
||||
ANCHOR_GROUP_LIST = '/api/bo/myGroup/list',
|
||||
ADD_ANCHOR_GROUP = '/api/bo/product/addGroup',
|
||||
// 职位发布人
|
||||
PUBLISHER_LIST = '/api/bo/publisher/list',
|
||||
PUBLISHER_DECLARATION_LIST = '/api/bo/publisher/listWithDeclaration',
|
||||
PUBLISHER_UPDATE = '/api/bo/publisher/update',
|
||||
// 主播
|
||||
ANCHOR_LIST = '/api/bo/user/list',
|
||||
ANCHOR_UPDATE = '/api/bo/user/update',
|
||||
// 主播报单(解锁)
|
||||
DECLARATION_LIST = '/api/bo/declaration/list',
|
||||
DECLARATION_UPDATE = '/api/bo/declaration/update',
|
||||
// 模卡
|
||||
MATERIAL_LIST = '/api/bo/resume/list',
|
||||
MATERIAL_UPDATE = '/api/bo/resume/update',
|
||||
}
|
3908
src/constants/city.ts
Normal file
3908
src/constants/city.ts
Normal file
File diff suppressed because it is too large
Load Diff
2
src/constants/global.ts
Normal file
2
src/constants/global.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const LOGIN_PATH = '/user/login';
|
||||
export const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
19
src/constants/http.ts
Normal file
19
src/constants/http.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export enum HTTP_STATUS {
|
||||
SUCCESS = 200,
|
||||
FAIL = 417,
|
||||
}
|
||||
|
||||
export enum RESPONSE_ERROR_CODE {
|
||||
INVALID_PARAMETER = 'INVALID_PARAMETER',
|
||||
NOT_FUND = 'NOT_FUND',
|
||||
INNER_EXCEPTION = 'INNER_EXCEPTION',
|
||||
SYSTEM_ERROR = 'SYSTEM_ERROR',
|
||||
NEED_LOGIN = 'NEED_LOGIN',
|
||||
}
|
||||
|
||||
export const RESPONSE_ERROR_MESSAGE = {
|
||||
[RESPONSE_ERROR_CODE.INVALID_PARAMETER]: '参数无效',
|
||||
[RESPONSE_ERROR_CODE.NOT_FUND]: '找到对应资源',
|
||||
[RESPONSE_ERROR_CODE.INNER_EXCEPTION]: '服务内部错误',
|
||||
[RESPONSE_ERROR_CODE.SYSTEM_ERROR]: '系统错误',
|
||||
};
|
24
src/constants/job.ts
Normal file
24
src/constants/job.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export enum JobType {
|
||||
All = 'ALL',
|
||||
Finery = 'FINERY', // 服饰
|
||||
Makeups = 'MAKEUPS', // 美妆
|
||||
Digital = 'DIGITAL', //数码
|
||||
Foods = 'FOODS', // 食品酒饮
|
||||
Jewelry = 'JEWELRY', // 珠宝
|
||||
Appliance = 'APPLIANCE', // 家电
|
||||
Furniture = 'FURNITURE', // 日用家具
|
||||
PetFamily = 'PET_FAMILY', // 母婴宠物
|
||||
Luxury = 'LUXURY', // 奢品
|
||||
}
|
||||
|
||||
export enum EmployType {
|
||||
All = 'ALL',
|
||||
Full = 'FULL_TIME',
|
||||
Part = 'PARTY_TIME',
|
||||
}
|
||||
|
||||
export const EMPLOY_OPTIONS = [
|
||||
{ label: '全职', value: EmployType.Full },
|
||||
{ label: '兼职', value: EmployType.Part },
|
||||
{ label: '不限', value: EmployType.All },
|
||||
];
|
37
src/constants/material.ts
Normal file
37
src/constants/material.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export enum WorkedYears {
|
||||
LessOneYear = 0.5,
|
||||
OneYear = 1,
|
||||
TwoYear = 2,
|
||||
MoreThreeYear = 3,
|
||||
}
|
||||
|
||||
export enum GenderType {
|
||||
MEN = 0,
|
||||
WOMEN = 1,
|
||||
}
|
||||
|
||||
// 1主播主动创建 2主播填写表单创建 3 运营人工创建 4 机器人创建
|
||||
export enum ProfileCreateSource {
|
||||
User = 1,
|
||||
UserInput = 2,
|
||||
Bl = 3,
|
||||
Robot = 4,
|
||||
}
|
||||
|
||||
export enum StyleType {
|
||||
Broadcasting = 1,
|
||||
HoldOrder = 2,
|
||||
Passion = 3,
|
||||
}
|
||||
|
||||
export enum MaterialStatus {
|
||||
Open = 0,
|
||||
Close = 1,
|
||||
}
|
||||
|
||||
export const WORK_YEAR_LABELS = {
|
||||
[WorkedYears.LessOneYear]: '1 年以下',
|
||||
[WorkedYears.OneYear]: '1 年',
|
||||
[WorkedYears.TwoYear]: '2 年',
|
||||
[WorkedYears.MoreThreeYear]: '3 年以上',
|
||||
};
|
13
src/constants/product.ts
Normal file
13
src/constants/product.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export enum PayType {
|
||||
Free = 'free',
|
||||
VX = 'vx',
|
||||
AliPay = 'alipay',
|
||||
Other = 'other',
|
||||
}
|
||||
|
||||
export enum DeclarationType {
|
||||
// 直接连接通告主
|
||||
Direct = 0,
|
||||
// 客服联系 customer service
|
||||
CS = 1,
|
||||
}
|
52
src/global.less
Normal file
52
src/global.less
Normal file
@ -0,0 +1,52 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
|
||||
.colorWeak {
|
||||
filter: invert(80%);
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
|
||||
left: unset;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
&-thead > tr,
|
||||
&-tbody > tr {
|
||||
> th,
|
||||
> td {
|
||||
white-space: pre;
|
||||
> span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
93
src/global.tsx
Normal file
93
src/global.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import '@umijs/max';
|
||||
import { Button, message, notification } from 'antd';
|
||||
|
||||
import defaultSettings from '../config/defaultSettings';
|
||||
const { pwa } = defaultSettings;
|
||||
const isHttps = document.location.protocol === 'https:';
|
||||
const clearCache = () => {
|
||||
// remove all caches
|
||||
if (window.caches) {
|
||||
caches
|
||||
.keys()
|
||||
.then(keys => {
|
||||
keys.forEach(key => {
|
||||
caches.delete(key);
|
||||
});
|
||||
})
|
||||
.catch(e => console.log(e));
|
||||
}
|
||||
};
|
||||
|
||||
// if pwa is true
|
||||
if (pwa) {
|
||||
// Notify user if offline now
|
||||
window.addEventListener('sw.offline', () => {
|
||||
message.warning('当前处于离线状态');
|
||||
});
|
||||
|
||||
// Pop up a prompt on the page asking the user if they want to use the latest version
|
||||
window.addEventListener('sw.updated', (event: Event) => {
|
||||
const e = event as CustomEvent;
|
||||
const reloadSW = async () => {
|
||||
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
|
||||
const worker = e.detail && e.detail.waiting;
|
||||
if (!worker) {
|
||||
return true;
|
||||
}
|
||||
// Send skip-waiting event to waiting SW with MessageChannel
|
||||
await new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = msgEvent => {
|
||||
if (msgEvent.data.error) {
|
||||
reject(msgEvent.data.error);
|
||||
} else {
|
||||
resolve(msgEvent.data);
|
||||
}
|
||||
};
|
||||
worker.postMessage(
|
||||
{
|
||||
type: 'skip-waiting',
|
||||
},
|
||||
[channel.port2],
|
||||
);
|
||||
});
|
||||
clearCache();
|
||||
window.location.reload();
|
||||
return true;
|
||||
};
|
||||
const key = `open${Date.now()}`;
|
||||
const btn = (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
notification.destroy(key);
|
||||
reloadSW();
|
||||
}}
|
||||
>
|
||||
{'刷新'}
|
||||
</Button>
|
||||
);
|
||||
notification.open({
|
||||
message: '有新内容',
|
||||
description: '请点击“刷新”按钮或者手动刷新页面',
|
||||
btn,
|
||||
key,
|
||||
onClose: async () => null,
|
||||
});
|
||||
});
|
||||
} else if ('serviceWorker' in navigator && isHttps) {
|
||||
// unregister service worker
|
||||
const { serviceWorker } = navigator;
|
||||
if (serviceWorker.getRegistrations) {
|
||||
serviceWorker.getRegistrations().then(sws => {
|
||||
sws.forEach(sw => {
|
||||
sw.unregister();
|
||||
});
|
||||
});
|
||||
}
|
||||
serviceWorker.getRegistration().then(sw => {
|
||||
if (sw) sw.unregister();
|
||||
});
|
||||
clearCache();
|
||||
}
|
22
src/manifest.json
Normal file
22
src/manifest.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "boluo",
|
||||
"short_name": "boluo",
|
||||
"display": "standalone",
|
||||
"start_url": "./?utm_source=homescreen",
|
||||
"theme_color": "#002140",
|
||||
"background_color": "#001529",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-192x192.png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-128x128.png",
|
||||
"sizes": "128x128"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512x512.png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
16
src/pages/404.tsx
Normal file
16
src/pages/404.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { history } from '@umijs/max';
|
||||
import { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
const NoFoundPage: React.FC = () => (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle={'抱歉,您访问的页面不存在。'}
|
||||
extra={
|
||||
<Button type="primary" onClick={() => history.push('/')}>
|
||||
{'返回首页'}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
export default NoFoundPage;
|
95
src/pages/table-list/anchor-group/index.tsx
Normal file
95
src/pages/table-list/anchor-group/index.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { PageContainer, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { TIME_FORMAT } from '@/constants/global';
|
||||
import { getAnchorGroupList } from '@/services/list';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '已申请', value: 0 },
|
||||
{ label: '已申请未进群 ', value: 1 },
|
||||
{ label: '已进群 ', value: 2 },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
const columns: ProColumns<API.AnchorGroupListItem>[] = [
|
||||
{
|
||||
title: '主播昵称',
|
||||
dataIndex: 'nickName',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '主播ID',
|
||||
dataIndex: 'userId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '主播手机号',
|
||||
dataIndex: 'userPhone',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '群ID',
|
||||
dataIndex: 'blGroupId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '群名称',
|
||||
dataIndex: 'imGroupNick',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '加群状态',
|
||||
dataIndex: 'status',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
return STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '绑定时间',
|
||||
dataIndex: 'created',
|
||||
valueType: 'dateTime',
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
// search: {
|
||||
// transform: (created: string) => dayjs(created).valueOf().toString(),
|
||||
// },
|
||||
},
|
||||
{
|
||||
title: '绑定人',
|
||||
dataIndex: 'creator',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.AnchorGroupListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getAnchorGroupList}
|
||||
columns={columns}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
147
src/pages/table-list/anchor/index.tsx
Normal file
147
src/pages/table-list/anchor/index.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, PageContainer, ProFormSelect, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { CITY_CODE_TO_NAME_MAP, CITY_OPTIONS } from '@/constants/city';
|
||||
import { TIME_FORMAT } from '@/constants/global';
|
||||
import { getAnchorList, updateAnchorInfo } from '@/services/list';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '正常', value: 0 },
|
||||
{ label: '封禁', value: 1 },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.AnchorListItem>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
|
||||
const columns: ProColumns<API.AnchorListItem>[] = [
|
||||
{
|
||||
title: '主播昵称',
|
||||
dataIndex: 'nickName',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '主播ID',
|
||||
dataIndex: 'userId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '主播手机号',
|
||||
dataIndex: 'userPhone',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '注册时间',
|
||||
dataIndex: 'created',
|
||||
valueType: 'dateTime',
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
// search: {
|
||||
// transform: (created: string) => dayjs(created).valueOf().toString(),
|
||||
// },
|
||||
},
|
||||
{
|
||||
title: '最后登录时间',
|
||||
dataIndex: 'lastLoginDate',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '账号状态',
|
||||
dataIndex: 'status',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
return STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '城市',
|
||||
dataIndex: 'city',
|
||||
valueType: 'textarea',
|
||||
renderText(cityCode: string) {
|
||||
return CITY_CODE_TO_NAME_MAP.get(cityCode);
|
||||
},
|
||||
renderFormItem() {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
options={CITY_OPTIONS}
|
||||
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
key="config"
|
||||
onClick={() => {
|
||||
handleUpdateModalOpen(true);
|
||||
setCurrentRow(record);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.AnchorListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getAnchorList}
|
||||
columns={columns}
|
||||
/>
|
||||
<ModalForm
|
||||
title="更新主播信息"
|
||||
width="400px"
|
||||
formRef={formRef}
|
||||
open={updateModalOpen}
|
||||
onOpenChange={handleUpdateModalOpen}
|
||||
onFinish={async data => {
|
||||
const params: API.UpdateAnchorParams = {
|
||||
userId: currentRow!.userId,
|
||||
status: data.status,
|
||||
};
|
||||
console.log('update confirm', data, params);
|
||||
try {
|
||||
await updateAnchorInfo(params);
|
||||
actionRef.current?.reload();
|
||||
formRef.current?.resetFields();
|
||||
} catch (e) {}
|
||||
handleUpdateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<ProFormSelect
|
||||
name="status"
|
||||
label="账号状态"
|
||||
options={STATUS_OPTIONS}
|
||||
rules={[{ required: true, message: '必填项' }]}
|
||||
/>
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
228
src/pages/table-list/declaration/index.tsx
Normal file
228
src/pages/table-list/declaration/index.tsx
Normal file
@ -0,0 +1,228 @@
|
||||
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, PageContainer, ProFormSelect, ProFormTextArea, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { CITY_CODE_TO_NAME_MAP, CITY_OPTIONS } from '@/constants/city';
|
||||
import { TIME_FORMAT } from '@/constants/global';
|
||||
import { DeclarationType } from '@/constants/product';
|
||||
import { getDeclarationList, updateDeclarationInfo } from '@/services/list';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '待处理', value: 0 },
|
||||
{ label: '已处理', value: 1 },
|
||||
];
|
||||
|
||||
const TYPE_OPTIONS = [
|
||||
{ label: '直接联系', value: DeclarationType.Direct },
|
||||
{ label: '客服联系', value: DeclarationType.CS },
|
||||
];
|
||||
|
||||
const WE_COM_OPTIONS = [
|
||||
{ label: '未加', value: 0 },
|
||||
{ label: '已加', value: 1 },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.DeclarationListItem>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
|
||||
const columns: ProColumns<API.DeclarationListItem>[] = [
|
||||
{
|
||||
title: '主播昵称',
|
||||
dataIndex: 'nickName',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '主播ID',
|
||||
dataIndex: 'userId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '主播手机号',
|
||||
dataIndex: 'userPhone',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '通告标题',
|
||||
dataIndex: 'title',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '通告ID',
|
||||
dataIndex: 'jobId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '解锁时间',
|
||||
dataIndex: 'useDate',
|
||||
valueType: 'dateTime',
|
||||
renderText(time: string) {
|
||||
return dayjs(Number(time)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '职位发布人昵称',
|
||||
dataIndex: 'publisher',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '职位发布人ID',
|
||||
dataIndex: 'blPublisherId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '职位发布人微信',
|
||||
dataIndex: 'publisherAcctNo',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '报单类型',
|
||||
dataIndex: 'type',
|
||||
valueType: 'textarea',
|
||||
renderText(type: number) {
|
||||
return TYPE_OPTIONS.find(option => option.value === type)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select allowClear options={TYPE_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '报单时间',
|
||||
dataIndex: 'declarationDate',
|
||||
valueType: 'dateTime',
|
||||
renderText(time: string) {
|
||||
return dayjs(Number(time)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '报单处理状态',
|
||||
dataIndex: 'declaredStatus',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
const declaredStatus = Number(status);
|
||||
return STATUS_OPTIONS.find(option => option.value === declaredStatus)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '报单处理时间',
|
||||
dataIndex: 'declaredDate',
|
||||
valueType: 'dateTime',
|
||||
renderText(time: string) {
|
||||
return dayjs(Number(time)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '是否加企微',
|
||||
dataIndex: 'weComStatus',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
return WE_COM_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={WE_COM_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '通告所属城市',
|
||||
dataIndex: 'jobCityCode',
|
||||
valueType: 'textarea',
|
||||
renderText(cityCode: string) {
|
||||
return CITY_CODE_TO_NAME_MAP.get(cityCode);
|
||||
},
|
||||
renderFormItem() {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
options={CITY_OPTIONS}
|
||||
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '报单备注信息',
|
||||
dataIndex: 'declaredMark',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
key="config"
|
||||
onClick={() => {
|
||||
handleUpdateModalOpen(true);
|
||||
setCurrentRow(record);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.DeclarationListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getDeclarationList}
|
||||
columns={columns}
|
||||
scroll={{ x: 'max-content' }}
|
||||
/>
|
||||
<ModalForm
|
||||
title="更新报单信息"
|
||||
width="400px"
|
||||
formRef={formRef}
|
||||
open={updateModalOpen}
|
||||
onOpenChange={handleUpdateModalOpen}
|
||||
onFinish={async data => {
|
||||
const params: API.UpdateDeclarationParams = {
|
||||
id: currentRow!.id,
|
||||
weComStatus: data.weComStatus,
|
||||
};
|
||||
console.log('update confirm', data, params);
|
||||
try {
|
||||
await updateDeclarationInfo(params);
|
||||
actionRef.current?.reload();
|
||||
formRef.current?.resetFields();
|
||||
} catch (e) {}
|
||||
handleUpdateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<ProFormSelect
|
||||
name="weComStatus"
|
||||
label="是否加企微"
|
||||
options={WE_COM_OPTIONS}
|
||||
rules={[{ required: true, message: '必填项' }]}
|
||||
/>
|
||||
<ProFormTextArea name="declaredMark" label="报单备注" placeholder="输入备注信息" />
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
131
src/pages/table-list/group/index.tsx
Normal file
131
src/pages/table-list/group/index.tsx
Normal file
@ -0,0 +1,131 @@
|
||||
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, PageContainer, ProFormSelect, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { CITY_CODE_TO_NAME_MAP, CITY_OPTIONS } from '@/constants/city';
|
||||
import { getGroupList, updateGroupInfo } from '@/services/list';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '正常', value: 0 },
|
||||
{ label: '暂停', value: 1 },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.GroupListItem>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
|
||||
const columns: ProColumns<API.GroupListItem>[] = [
|
||||
{
|
||||
title: '群名称',
|
||||
dataIndex: 'imGroupNick',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '群ID',
|
||||
dataIndex: 'id',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '所属城市',
|
||||
dataIndex: 'city',
|
||||
valueType: 'textarea',
|
||||
renderText(cityCode: string) {
|
||||
return CITY_CODE_TO_NAME_MAP.get(cityCode);
|
||||
},
|
||||
renderFormItem() {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
options={CITY_OPTIONS}
|
||||
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '群状态',
|
||||
dataIndex: 'disable',
|
||||
valueType: 'textarea',
|
||||
renderText(disable: boolean) {
|
||||
return disable ? '暂停' : '正常';
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '机器人ID',
|
||||
dataIndex: 'robotId',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '机器人微信昵称',
|
||||
dataIndex: 'robotImNick',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '机器人微信账号',
|
||||
dataIndex: 'robotImNo',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
key="config"
|
||||
onClick={() => {
|
||||
handleUpdateModalOpen(true);
|
||||
setCurrentRow(record);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.GroupListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getGroupList}
|
||||
columns={columns}
|
||||
/>
|
||||
<ModalForm
|
||||
title="更新群信息"
|
||||
width="400px"
|
||||
formRef={formRef}
|
||||
open={updateModalOpen}
|
||||
onOpenChange={handleUpdateModalOpen}
|
||||
onFinish={async formData => {
|
||||
const params: API.UpdateGroupParams = {
|
||||
id: currentRow!.id,
|
||||
city: formData.city?.value,
|
||||
disable: formData.disable,
|
||||
};
|
||||
console.log('update confirm', formData, params);
|
||||
try {
|
||||
await updateGroupInfo(params);
|
||||
actionRef.current?.reload();
|
||||
formRef.current?.resetFields();
|
||||
} catch (e) {}
|
||||
handleUpdateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<ProFormSelect.SearchSelect name="city" mode="single" label="所属城市" options={CITY_OPTIONS} />
|
||||
<ProFormSelect name="disable" label="群状态" options={STATUS_OPTIONS} />
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
209
src/pages/table-list/job/index.tsx
Normal file
209
src/pages/table-list/job/index.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, PageContainer, ProFormSelect, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { CITY_CODE_TO_NAME_MAP, CITY_OPTIONS } from '@/constants/city';
|
||||
import { TIME_FORMAT } from '@/constants/global';
|
||||
import { getJobList, updateJobInfo } from '@/services/list';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '正常', value: 0 },
|
||||
{ label: '暂停', value: 1 },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.JobListItem>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
|
||||
const columns: ProColumns<API.JobListItem>[] = [
|
||||
{
|
||||
title: '职位名称',
|
||||
dataIndex: 'title',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '职位描述',
|
||||
dataIndex: 'sourceText',
|
||||
valueType: 'textarea',
|
||||
colSize: 2,
|
||||
search: false,
|
||||
copyable: true,
|
||||
renderText(sourceText: string) {
|
||||
return sourceText?.substring(0, 30);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '职位ID',
|
||||
dataIndex: 'jobId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '城市',
|
||||
dataIndex: 'cityCode',
|
||||
valueType: 'textarea',
|
||||
renderText(cityCode: string) {
|
||||
return CITY_CODE_TO_NAME_MAP.get(cityCode);
|
||||
},
|
||||
renderFormItem() {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
options={CITY_OPTIONS}
|
||||
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '所在群名称',
|
||||
dataIndex: 'imGroupNick',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '群ID',
|
||||
dataIndex: 'blGroupId',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '发布人昵称',
|
||||
dataIndex: 'publisher',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '发布人ID',
|
||||
dataIndex: 'blPublisherId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '发布人微信账号',
|
||||
dataIndex: 'publisherAcctNo',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '发布群数量',
|
||||
dataIndex: 'relateGroupCount',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '机器人ID',
|
||||
dataIndex: 'robotId',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '机器人微信昵称',
|
||||
dataIndex: 'robotImNick',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '机器人微信账号',
|
||||
dataIndex: 'robotImNo',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '通告状态',
|
||||
dataIndex: 'disable',
|
||||
valueType: 'textarea',
|
||||
renderText(disable: boolean) {
|
||||
return disable ? '暂停' : '正常';
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '小程序用户 id',
|
||||
dataIndex: 'appUid',
|
||||
valueType: 'textarea',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
valueType: 'dateTime',
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updated',
|
||||
valueType: 'dateTime',
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
key="config"
|
||||
onClick={() => {
|
||||
handleUpdateModalOpen(true);
|
||||
setCurrentRow(record);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.JobListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getJobList}
|
||||
columns={columns}
|
||||
scroll={{ x: 'max-content' }}
|
||||
/>
|
||||
<ModalForm
|
||||
title="更新通告信息"
|
||||
width="400px"
|
||||
formRef={formRef}
|
||||
open={updateModalOpen}
|
||||
onOpenChange={handleUpdateModalOpen}
|
||||
onFinish={async formData => {
|
||||
const params: API.UpdateJobParams = {
|
||||
id: currentRow!.id,
|
||||
jobId: currentRow!.jobId,
|
||||
disable: Number(formData.disable) !== 0,
|
||||
};
|
||||
console.log('update confirm', formData, params);
|
||||
try {
|
||||
await updateJobInfo(params);
|
||||
actionRef.current?.reload();
|
||||
formRef.current?.resetFields();
|
||||
} catch (e) {}
|
||||
handleUpdateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<ProFormSelect name="disable" label="通告状态" options={STATUS_OPTIONS} />
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
198
src/pages/table-list/material/index.tsx
Normal file
198
src/pages/table-list/material/index.tsx
Normal file
@ -0,0 +1,198 @@
|
||||
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, PageContainer, ProFormSelect, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import Previewer from '@/components/previewer';
|
||||
import { CITY_CODE_TO_NAME_MAP, CITY_OPTIONS } from '@/constants/city';
|
||||
import { TIME_FORMAT } from '@/constants/global';
|
||||
import { getMaterialList, updateMaterialInfo } from '@/services/list';
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '开放', value: true },
|
||||
{ label: '封禁', value: false },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.MaterialListItem>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
|
||||
const columns: ProColumns<API.MaterialListItem>[] = [
|
||||
{
|
||||
title: '视频',
|
||||
dataIndex: 'userId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
search: false,
|
||||
render(_dom, entity) {
|
||||
return <Previewer videos={entity.materialVideoInfoList} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '主播ID',
|
||||
dataIndex: 'userId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '模卡昵称',
|
||||
dataIndex: 'name',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '主播昵称',
|
||||
dataIndex: 'nickname',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '自身优势',
|
||||
dataIndex: 'advantages',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '模卡状态',
|
||||
dataIndex: 'isOpen',
|
||||
valueType: 'textarea',
|
||||
renderText(status: boolean) {
|
||||
return STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '主播模卡状态',
|
||||
dataIndex: 'userOpen',
|
||||
valueType: 'textarea',
|
||||
renderText(status: boolean) {
|
||||
return STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '后台模卡状态',
|
||||
dataIndex: 'adminOpen',
|
||||
valueType: 'textarea',
|
||||
renderText(status: boolean) {
|
||||
return STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '意向城市',
|
||||
dataIndex: 'cityCode',
|
||||
valueType: 'textarea',
|
||||
renderText(cityCode: string) {
|
||||
return CITY_CODE_TO_NAME_MAP.get(cityCode);
|
||||
},
|
||||
renderFormItem() {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
options={CITY_OPTIONS}
|
||||
filterOption={(input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase())}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '播过的品类',
|
||||
dataIndex: 'workedSecCategoryStr',
|
||||
valueType: 'textarea',
|
||||
search: false,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
valueType: 'dateTime',
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updated',
|
||||
valueType: 'dateTime',
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
key="config"
|
||||
onClick={() => {
|
||||
handleUpdateModalOpen(true);
|
||||
setCurrentRow(record);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.MaterialListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getMaterialList}
|
||||
columns={columns}
|
||||
scroll={{ x: 'max-content' }}
|
||||
/>
|
||||
<ModalForm
|
||||
title="更新模卡信息"
|
||||
width="400px"
|
||||
formRef={formRef}
|
||||
open={updateModalOpen}
|
||||
onOpenChange={handleUpdateModalOpen}
|
||||
onFinish={async data => {
|
||||
const params: API.UpdateMaterialParams = {
|
||||
id: currentRow!.id,
|
||||
adminOpen: Number(data.adminOpen),
|
||||
};
|
||||
console.log('update confirm', data, params);
|
||||
try {
|
||||
await updateMaterialInfo(params);
|
||||
actionRef.current?.reload();
|
||||
formRef.current?.resetFields();
|
||||
} catch (e) {}
|
||||
handleUpdateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<ProFormSelect
|
||||
name="adminOpen"
|
||||
label="后台模卡状态"
|
||||
options={STATUS_OPTIONS as any}
|
||||
rules={[{ required: true, message: '必填项' }]}
|
||||
/>
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
195
src/pages/table-list/publisher/index.tsx
Normal file
195
src/pages/table-list/publisher/index.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import type { ActionType, ProColumns, ProFormInstance } from '@ant-design/pro-components';
|
||||
import { ModalForm, PageContainer, ProFormSelect, ProFormText, ProTable } from '@ant-design/pro-components';
|
||||
import '@umijs/max';
|
||||
import { Select } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
import { TIME_FORMAT } from '@/constants/global';
|
||||
import { getPublisherList, updatePublisherInfo } from '@/services/list';
|
||||
|
||||
const WX_STATUS_OPTIONS = [
|
||||
{ label: '为空', value: 0 },
|
||||
{ label: '不为空', value: 1 },
|
||||
];
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ label: '正常', value: 0 },
|
||||
{ label: '暂停', value: 1 },
|
||||
];
|
||||
|
||||
const ADD_WX_STATUS_OPTIONS = [
|
||||
{ label: '待申请', value: 0 },
|
||||
{ label: '已申请', value: 1 },
|
||||
{ label: '不可添加', value: 2 },
|
||||
{ label: '被封号', value: 3 },
|
||||
];
|
||||
|
||||
const HAS_DECLARE_OPTIONS = [
|
||||
{ label: '有报单', value: true },
|
||||
{ label: '无报单', value: false },
|
||||
];
|
||||
|
||||
const TableList: React.FC = () => {
|
||||
const [updateModalOpen, handleUpdateModalOpen] = useState<boolean>(false);
|
||||
const [currentRow, setCurrentRow] = useState<API.PublisherListItem>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<ProFormInstance>();
|
||||
|
||||
const columns: ProColumns<API.PublisherListItem>[] = [
|
||||
{
|
||||
title: '发布人昵称',
|
||||
dataIndex: 'publisher',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '发布人ID',
|
||||
dataIndex: 'blPublisherId',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '账号状态',
|
||||
dataIndex: 'status',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
return STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '发布人微信账号',
|
||||
dataIndex: 'publisherAcctNo',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '发布人微信状态',
|
||||
dataIndex: 'publisherAcctStatus',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
const publisherAcctStatus = Number(status);
|
||||
return WX_STATUS_OPTIONS.find(option => option.value === publisherAcctStatus)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={WX_STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '是否申请好友',
|
||||
dataIndex: 'addAcctStatus',
|
||||
valueType: 'textarea',
|
||||
renderText(status: number) {
|
||||
return ADD_WX_STATUS_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={ADD_WX_STATUS_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '机器人微信昵称',
|
||||
dataIndex: 'robotImNick',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '来源群',
|
||||
dataIndex: 'imGroupNick',
|
||||
valueType: 'textarea',
|
||||
copyable: true,
|
||||
},
|
||||
{
|
||||
title: '是否有报单',
|
||||
dataIndex: 'hasDeclareOrder',
|
||||
hideInTable: true,
|
||||
renderText(status: boolean) {
|
||||
return HAS_DECLARE_OPTIONS.find(option => option.value === status)?.label;
|
||||
},
|
||||
renderFormItem() {
|
||||
return <Select showSearch allowClear options={HAS_DECLARE_OPTIONS} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最新报单时间',
|
||||
dataIndex: 'declareTime',
|
||||
valueType: 'dateTime',
|
||||
sorter: true,
|
||||
renderText(created: string) {
|
||||
return created ? dayjs(Number(created)).format(TIME_FORMAT) : '';
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '小程序用户 id',
|
||||
dataIndex: 'appUid',
|
||||
valueType: 'textarea',
|
||||
search: true,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
valueType: 'dateTime',
|
||||
sorter: true,
|
||||
renderText(created: string) {
|
||||
return dayjs(Number(created)).format(TIME_FORMAT);
|
||||
},
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
render: (_, record) => (
|
||||
<a
|
||||
key="config"
|
||||
onClick={() => {
|
||||
handleUpdateModalOpen(true);
|
||||
setCurrentRow(record);
|
||||
}}
|
||||
>
|
||||
修改
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<API.PublisherListItem, API.PageParams>
|
||||
headerTitle="查询表格"
|
||||
actionRef={actionRef}
|
||||
rowKey="key"
|
||||
search={{ labelWidth: 120, collapsed: false, collapseRender: false }}
|
||||
request={getPublisherList}
|
||||
columns={columns}
|
||||
/>
|
||||
<ModalForm
|
||||
title="更新发布人信息"
|
||||
width="400px"
|
||||
formRef={formRef}
|
||||
open={updateModalOpen}
|
||||
onOpenChange={handleUpdateModalOpen}
|
||||
onFinish={async data => {
|
||||
const params: API.UpdatePublisherParams = {
|
||||
blPublisherId: currentRow!.blPublisherId,
|
||||
publisherAcctNo: data.publisherAcctNo,
|
||||
status: data.status,
|
||||
addAcctStatus: data.addAcctStatus,
|
||||
};
|
||||
console.log('update confirm', data, params);
|
||||
try {
|
||||
await updatePublisherInfo(params);
|
||||
actionRef.current?.reload();
|
||||
formRef.current?.resetFields();
|
||||
} catch (e) {}
|
||||
handleUpdateModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<ProFormText width="md" name="publisherAcctNo" label="发布人微信账号" />
|
||||
<ProFormSelect name="status" label="账号状态" options={STATUS_OPTIONS} />
|
||||
<ProFormSelect name="addAcctStatus" label="是否申请好友" options={ADD_WX_STATUS_OPTIONS} />
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
export default TableList;
|
104
src/pages/user/login/index.tsx
Normal file
104
src/pages/user/login/index.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { LoginForm, ProFormText } from '@ant-design/pro-components';
|
||||
import { Helmet, history, useModel } from '@umijs/max';
|
||||
import { Alert, message } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import React, { useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
|
||||
import Footer from '@/components/footer';
|
||||
import { login } from '@/services/user';
|
||||
import { setToken } from '@/utils/login';
|
||||
|
||||
import Settings from '../../../../config/defaultSettings';
|
||||
|
||||
const useStyles = createStyles(() => {
|
||||
return {
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const [loginError, setLoginError] = useState<boolean>(false);
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
const { styles } = useStyles();
|
||||
const fetchUserInfo = async () => {
|
||||
const userInfo = await initialState?.fetchUserInfo?.();
|
||||
if (userInfo) {
|
||||
flushSync(() => {
|
||||
setInitialState(s => ({
|
||||
...s,
|
||||
currentUser: userInfo,
|
||||
}));
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleSubmit = async (values: API.LoginParams) => {
|
||||
try {
|
||||
// 登录
|
||||
console.log('login params', values);
|
||||
const result = await login({ ...values });
|
||||
if (result.token) {
|
||||
setToken(result.token);
|
||||
message.success('登录成功!');
|
||||
await fetchUserInfo();
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
history.push(urlParams.get('redirect') || '/');
|
||||
return;
|
||||
}
|
||||
console.log(result);
|
||||
// 如果失败去设置用户错误信息
|
||||
setLoginError(true);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
message.error('登录失败,请重试!');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Helmet>
|
||||
<title>
|
||||
{'登录'}- {Settings.title}
|
||||
</title>
|
||||
</Helmet>
|
||||
<div style={{ flex: '1', padding: '32px 0' }}>
|
||||
<LoginForm
|
||||
contentStyle={{
|
||||
minWidth: 280,
|
||||
maxWidth: '75vw',
|
||||
}}
|
||||
logo={<img alt="logo" src={Settings.logo} />}
|
||||
subTitle=" "
|
||||
title="播络管理后台"
|
||||
initialValues={{
|
||||
autoLogin: true,
|
||||
}}
|
||||
onFinish={async values => {
|
||||
await handleSubmit(values as API.LoginParams);
|
||||
}}
|
||||
>
|
||||
<ProFormText
|
||||
name="userName"
|
||||
fieldProps={{ size: 'large', prefix: <UserOutlined /> }}
|
||||
placeholder="请输入账号"
|
||||
rules={[{ required: true, message: '账号不能为空!' }]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="pwd"
|
||||
fieldProps={{ size: 'large', prefix: <LockOutlined /> }}
|
||||
placeholder="请输入密码"
|
||||
rules={[{ required: true, message: '密码不能为空!' }]}
|
||||
/>
|
||||
{loginError && <Alert style={{ marginBottom: 24 }} message="用户名或密码错误" type="error" showIcon />}
|
||||
</LoginForm>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Login;
|
84
src/requestConfig.ts
Normal file
84
src/requestConfig.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import type { AxiosResponse, RequestOptions } from '@@/plugin-request/request';
|
||||
import type { RequestConfig } from '@umijs/max';
|
||||
import { message } from 'antd';
|
||||
|
||||
import { RESPONSE_ERROR_CODE, RESPONSE_ERROR_MESSAGE } from '@/constants/http';
|
||||
import { clearToken, getToken, gotoLogin } from '@/utils/login';
|
||||
|
||||
import { IRequestResponse } from './types/http';
|
||||
|
||||
/**
|
||||
* @name 全局请求配置
|
||||
* @doc https://umijs.org/docs/max/request#配置
|
||||
*/
|
||||
export const requestConfig: RequestConfig = {
|
||||
// baseURL: 'https://neighbourhood.cn',
|
||||
baseURL: 'http://192.168.60.120:8082',
|
||||
|
||||
// 错误处理: umi@3 的错误处理方案。
|
||||
errorConfig: {
|
||||
// 错误抛出
|
||||
errorThrower: res => {
|
||||
const { code, msg, traceId } = res as IRequestResponse;
|
||||
if (code) {
|
||||
const error: any = new Error(msg);
|
||||
error.name = 'BizError';
|
||||
error.info = { code, msg, traceId };
|
||||
throw error; // 抛出自制的错误
|
||||
}
|
||||
},
|
||||
// 错误接收及处理
|
||||
errorHandler: (error: any, opts: any) => {
|
||||
if (opts?.skipErrorHandler) throw error;
|
||||
// 我们的 errorThrower 抛出的错误。
|
||||
if (error.name === 'BizError') {
|
||||
const errorInfo: IRequestResponse | undefined = error.info;
|
||||
if (!errorInfo) {
|
||||
return;
|
||||
}
|
||||
const { code, msg, traceId } = errorInfo;
|
||||
switch (code) {
|
||||
case RESPONSE_ERROR_CODE.INVALID_PARAMETER:
|
||||
default:
|
||||
message.error(`Request error, msg: ${msg}, traceId: ${traceId}`);
|
||||
}
|
||||
} else if (error.response) {
|
||||
// Axios 的错误
|
||||
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
|
||||
const { data, status } = error.response as AxiosResponse<IRequestResponse>;
|
||||
const code = data?.code as RESPONSE_ERROR_CODE;
|
||||
switch (code) {
|
||||
case RESPONSE_ERROR_CODE.NEED_LOGIN:
|
||||
clearToken();
|
||||
gotoLogin();
|
||||
return;
|
||||
default:
|
||||
message.error(`${RESPONSE_ERROR_MESSAGE[code] || '请求错误'}, 错误码:${status}`);
|
||||
return;
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 请求已经成功发起,但没有收到响应
|
||||
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
|
||||
// 而在node.js中是 http.ClientRequest 的实例
|
||||
message.error('None response! Please retry.');
|
||||
} else {
|
||||
// 发送请求时出了点问题
|
||||
message.error('Request error, please retry.');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// 请求拦截器
|
||||
requestInterceptors: [
|
||||
(config: RequestOptions) => {
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
config.headers = {
|
||||
...(config.headers || {}),
|
||||
Authorization: 'Bearer ' + token,
|
||||
};
|
||||
}
|
||||
return config;
|
||||
},
|
||||
],
|
||||
};
|
59
src/service-worker.js
Normal file
59
src/service-worker.js
Normal file
@ -0,0 +1,59 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* globals workbox */
|
||||
workbox.core.setCacheNameDetails({
|
||||
prefix: 'antd-pro',
|
||||
suffix: 'v5',
|
||||
});
|
||||
// Control all opened tabs ASAP
|
||||
workbox.clientsClaim();
|
||||
|
||||
/**
|
||||
* Use precaching list generated by workbox in build process.
|
||||
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
|
||||
*/
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
|
||||
|
||||
/**
|
||||
* Register a navigation route.
|
||||
* https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
|
||||
*/
|
||||
workbox.routing.registerNavigationRoute('/index.html');
|
||||
|
||||
/**
|
||||
* Use runtime cache:
|
||||
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
|
||||
*
|
||||
* Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
|
||||
* https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
|
||||
*/
|
||||
|
||||
/** Handle API requests */
|
||||
workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
|
||||
|
||||
/** Handle third party requests */
|
||||
workbox.routing.registerRoute(/^https:\/\/gw\.alipayobjects\.com\//, workbox.strategies.networkFirst());
|
||||
workbox.routing.registerRoute(/^https:\/\/cdnjs\.cloudflare\.com\//, workbox.strategies.networkFirst());
|
||||
workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
|
||||
|
||||
/** Response to client after skipping waiting with MessageChannel */
|
||||
addEventListener('message', event => {
|
||||
const replyPort = event.ports[0];
|
||||
const message = event.data;
|
||||
if (replyPort && message && message.type === 'skip-waiting') {
|
||||
event.waitUntil(
|
||||
self.skipWaiting().then(
|
||||
() => {
|
||||
replyPort.postMessage({
|
||||
error: null,
|
||||
});
|
||||
},
|
||||
error => {
|
||||
replyPort.postMessage({
|
||||
error,
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
224
src/services/list.ts
Normal file
224
src/services/list.ts
Normal file
@ -0,0 +1,224 @@
|
||||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { AdminAPI } from '@/constants/api';
|
||||
import { EmployType, JobType } from '@/constants/job';
|
||||
import { request } from '@umijs/max';
|
||||
import { SortOrder } from 'antd/es/table/interface';
|
||||
|
||||
function transformPageParams(params: API.PageParams & Record<string, any>) {
|
||||
params.page = params.current;
|
||||
delete params.current;
|
||||
Object.keys(params).forEach((key: string) => {
|
||||
if (typeof params[key] === 'string' && !params[key]) {
|
||||
delete params[key];
|
||||
}
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
function transformSort(sort: Record<string, SortOrder>) {
|
||||
if (!sort) {
|
||||
return {};
|
||||
}
|
||||
const sortField = Object.keys(sort)[0];
|
||||
if (!sort[sortField]) {
|
||||
return {};
|
||||
}
|
||||
const asc = sort[sortField] === 'ascend';
|
||||
return { sortField, asc };
|
||||
}
|
||||
|
||||
function sortTableList<T extends { created: number; updated: number }>(
|
||||
response: API.TableList<T>,
|
||||
{ sortField, asc }: ReturnType<typeof transformSort>,
|
||||
): API.TableList<T> {
|
||||
if (sortField === 'created' || sortField === 'updated') {
|
||||
response.data.sort((itemA, itemB) => {
|
||||
const valueA = Number(itemA[sortField]);
|
||||
const valueB = Number(itemB[sortField]);
|
||||
return asc ? valueA - valueB : valueB - valueA;
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function getJobList(params: API.PageParams & Partial<API.JobListItem>, options?: {
|
||||
[key: string]: any
|
||||
}) {
|
||||
if (!params.category) {
|
||||
params.category = JobType.All;
|
||||
}
|
||||
if (!params.cityCode) {
|
||||
params.cityCode = 'ALL';
|
||||
}
|
||||
if (!params.employType) {
|
||||
params.employType = EmployType.All;
|
||||
}
|
||||
const result = await request<API.TableList<API.JobListItem>>(AdminAPI.JOB_LIST, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新通告接口,必须传 数据库 id 和 jobId
|
||||
*/
|
||||
export async function updateJobInfo(options: API.UpdateJobParams) {
|
||||
return request<API.JobListItem>(AdminAPI.JOB_UPDATE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getGroupList(
|
||||
params: API.PageParams & Partial<API.GroupListItem>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const result = await request<API.TableList<API.GroupListItem>>(AdminAPI.GROUP_LIST, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新群信息,必须传 数据库 id
|
||||
*/
|
||||
export async function updateGroupInfo(options: API.UpdateGroupParams) {
|
||||
return request<API.GroupListItem>(AdminAPI.GROUP_UPDATE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAnchorGroupList(
|
||||
params: API.PageParams & Partial<API.AnchorGroupListItem>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const result = await request<API.TableList<API.AnchorGroupListItem>>(AdminAPI.ANCHOR_GROUP_LIST, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function addAnchorGroup(options?: { [key: string]: any }) {
|
||||
return request<API.GroupListItem>(AdminAPI.ADD_ANCHOR_GROUP, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getPublisherList(
|
||||
params: API.PageParams & Partial<API.PublisherListItem>,
|
||||
sort: Record<string, SortOrder>,
|
||||
) {
|
||||
const url = params.hasDeclareOrder ? AdminAPI.PUBLISHER_DECLARATION_LIST : AdminAPI.PUBLISHER_LIST;
|
||||
const result = await request<API.TableList<API.PublisherListItem>>(url, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...transformSort(sort),
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updatePublisherInfo(options: API.UpdatePublisherParams) {
|
||||
return request<API.PublisherListItem>(AdminAPI.PUBLISHER_UPDATE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getAnchorList(
|
||||
params: API.PageParams & Partial<API.AnchorListItem>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const result = await request<API.TableList<API.AnchorListItem>>(AdminAPI.ANCHOR_LIST, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateAnchorInfo(options: API.UpdateAnchorParams) {
|
||||
return request<API.DeclarationListItem>(AdminAPI.ANCHOR_UPDATE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getMaterialList(
|
||||
params: API.PageParams & Partial<API.MaterialListItem>,
|
||||
sort: Record<string, SortOrder>,
|
||||
) {
|
||||
const formatedSort = transformSort(sort);
|
||||
const result = await request<API.TableList<API.MaterialListItem>>(AdminAPI.MATERIAL_LIST, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...formatedSort,
|
||||
},
|
||||
});
|
||||
|
||||
return sortTableList<API.MaterialListItem>(result, formatedSort);
|
||||
}
|
||||
|
||||
export async function updateMaterialInfo(options: API.UpdateMaterialParams) {
|
||||
return request<API.DeclarationListItem>(AdminAPI.MATERIAL_UPDATE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDeclarationList(
|
||||
params: API.PageParams & Partial<API.DeclarationListItem>,
|
||||
options?: { [key: string]: any },
|
||||
) {
|
||||
const result = await request<API.TableList<API.DeclarationListItem>>(AdminAPI.DECLARATION_LIST, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...transformPageParams(params),
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateDeclarationInfo(options: API.UpdateDeclarationParams) {
|
||||
return request<API.DeclarationListItem>(AdminAPI.DECLARATION_UPDATE, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
...(options || {}),
|
||||
},
|
||||
});
|
||||
}
|
325
src/services/typings.d.ts
vendored
Normal file
325
src/services/typings.d.ts
vendored
Normal file
@ -0,0 +1,325 @@
|
||||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
|
||||
declare namespace API {
|
||||
type CurrentUser = {
|
||||
id: string; // 数据库 id
|
||||
userId: string;
|
||||
userName: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
interface LoginParams {
|
||||
userName?: string;
|
||||
pwd?: string;
|
||||
}
|
||||
|
||||
interface LoginResult {
|
||||
token?: string;
|
||||
expires?: number;
|
||||
}
|
||||
|
||||
interface PageParams {
|
||||
// proTable 给的分页页数
|
||||
current?: number;
|
||||
pageSize?: number;
|
||||
// 实际接口需要的分页页数
|
||||
page?: number;
|
||||
}
|
||||
|
||||
interface JobListItem {
|
||||
// 数据库 id
|
||||
id: string;
|
||||
// 小程序用户 id
|
||||
appUid: string;
|
||||
// 通告 id
|
||||
jobId: string;
|
||||
// 通告标题
|
||||
title: string;
|
||||
// 简介
|
||||
jobDescription: string;
|
||||
// 详细描述(原始信息)
|
||||
sourceText: string;
|
||||
// 城市 code,查询时必传:默认为 ALL
|
||||
cityCode: string;
|
||||
// 品类,查询时必传:默认为 ALL
|
||||
category: string;
|
||||
// 工作类型,查询时必传:默认为 ALL
|
||||
employType: string;
|
||||
// 发布人微信昵称
|
||||
publisher: string;
|
||||
// 发布人 id
|
||||
blPublisherId: string;
|
||||
// 发布人微信号
|
||||
publisherAcctNo: string;
|
||||
// 群昵称
|
||||
imGroupNick: string;
|
||||
// 微信群id
|
||||
imGroupId: string;
|
||||
// 播络群 id
|
||||
blGroupId: string;
|
||||
// 机器人 id
|
||||
robotId: string;
|
||||
// 机器人微信昵称
|
||||
robotImNick: string;
|
||||
// 机器人微信号
|
||||
robotImNo: string;
|
||||
// 通告发布群数量
|
||||
relateGroupCount: number;
|
||||
// 创建时间,时间戳
|
||||
created: string;
|
||||
// 更新时间,时间戳
|
||||
updated: string;
|
||||
// 是否禁用,默认为 false
|
||||
disable: boolean;
|
||||
}
|
||||
|
||||
interface GroupListItem {
|
||||
// 播络群 id
|
||||
id: string;
|
||||
// 微信群id
|
||||
imGroupId: string;
|
||||
// 群昵称
|
||||
imGroupNick: string;
|
||||
groupType: string;
|
||||
// 群主微信昵称
|
||||
groupOwnerNick: string;
|
||||
// 群主微信号
|
||||
groupOwnerImAcctNo: string;
|
||||
// 群主boluo账号
|
||||
groupOwnerAcctNo: string;
|
||||
// 机器人 id
|
||||
robotId: string;
|
||||
// 机器人微信昵称
|
||||
robotImNick: string;
|
||||
// 机器人微信号
|
||||
robotImNo: string;
|
||||
// 城市 code
|
||||
city: string;
|
||||
// 通告数量
|
||||
jobCount: number;
|
||||
// 创建时间,时间戳
|
||||
created: string;
|
||||
// 更新时间,时间戳
|
||||
updated: string;
|
||||
// 是否禁用,默认为 false
|
||||
disable: boolean;
|
||||
// 是否删除
|
||||
isDeleted: boolean;
|
||||
}
|
||||
|
||||
interface AnchorGroupListItem {
|
||||
// 主播用户 id
|
||||
userId: string;
|
||||
// 主播昵称
|
||||
nickName: string;
|
||||
// 主播手机号
|
||||
userPhone: string;
|
||||
// 群名称
|
||||
imGroupNick: string;
|
||||
// 微信群id
|
||||
imGroupId: string;
|
||||
// 播络群 id
|
||||
blGroupId: string;
|
||||
// 是否已经支付
|
||||
payed: boolean;
|
||||
// 支付方式, vx、alipay、other
|
||||
payType: string;
|
||||
// 群定制时间,时间戳
|
||||
created: string;
|
||||
// 绑定群的客服操作人员
|
||||
creator: string;
|
||||
// 备注
|
||||
mark?: string;
|
||||
// 加群状态:0 已申请加群 1已申请加群未进群 2 已申请加群已进群
|
||||
status: number;
|
||||
}
|
||||
|
||||
interface PublisherListItem {
|
||||
// 发布人微信昵称
|
||||
publisher: string;
|
||||
// 发布人 id
|
||||
blPublisherId: string;
|
||||
// 小程序用户 id
|
||||
appUid: string;
|
||||
// 发布人微信号
|
||||
publisherAcctNo: string;
|
||||
// 发布人微信状态, 0 空,1 非空
|
||||
publisherAcctStatus: number;
|
||||
// 账号状态: 0 正常,1 暂停
|
||||
status: number;
|
||||
// 申请好友状态: 0 待申请,1 已申请,2 不能添加,3 被封号
|
||||
addAcctStatus: number;
|
||||
phone: string;
|
||||
email: string;
|
||||
// 机器人微信昵称
|
||||
robotImNick: string;
|
||||
// 客服操作人员
|
||||
operator: string;
|
||||
// 群名称
|
||||
imGroupNick: string;
|
||||
// 微信群id
|
||||
imGroupId: string;
|
||||
// 播络群 id
|
||||
blGroupId: string;
|
||||
// 创建时间
|
||||
created: string;
|
||||
// 更新时间
|
||||
updated: string;
|
||||
// 是否有报单
|
||||
hasDeclareOrder?: boolean;
|
||||
// 最新报单时间
|
||||
declareTime?: string;
|
||||
jobId?: string;
|
||||
}
|
||||
|
||||
interface AnchorListItem {
|
||||
// 主播用户 id
|
||||
userId: string;
|
||||
// 主播昵称
|
||||
nickName: string;
|
||||
// 主播手机号
|
||||
userPhone: string;
|
||||
// 是否绑定了手机号
|
||||
isBindPhone: boolean;
|
||||
// 注册时间
|
||||
created: string;
|
||||
// 最后一次登录时间
|
||||
lastLoginDate: string;
|
||||
// 状态: 0 正常,1 封禁
|
||||
status: number;
|
||||
// 所属城市 code
|
||||
city: string;
|
||||
}
|
||||
|
||||
interface DeclarationListItem {
|
||||
// 报单 id
|
||||
id: string;
|
||||
// 报单类型
|
||||
type: number;
|
||||
// 主播用户 id
|
||||
userId: string;
|
||||
// 主播昵称
|
||||
nickName: string;
|
||||
// 主播手机号
|
||||
userPhone: string;
|
||||
// 通告 id
|
||||
jobId: string;
|
||||
// 通告标题
|
||||
title: string;
|
||||
// 解锁时间
|
||||
useDate: string;
|
||||
// 发布人微信昵称
|
||||
publisher: string;
|
||||
// 发布人 id
|
||||
blPublisherId: string;
|
||||
// 发布人微信号
|
||||
publisherAcctNo: string;
|
||||
// 报单时间
|
||||
declarationDate: string;
|
||||
// 报单处理状态
|
||||
declaredStatus: number;
|
||||
// 报单处理时间
|
||||
declaredDate: string;
|
||||
// 备注
|
||||
declaredMark?: string;
|
||||
// 加企微状态
|
||||
weComStatus: number;
|
||||
// 通告城市信息,如 400100
|
||||
jobCityCode: string;
|
||||
}
|
||||
|
||||
interface MaterialVideoInfo {
|
||||
url: string;
|
||||
coverUrl: string;
|
||||
type: 'image' | 'video';
|
||||
title: string;
|
||||
isDefault: boolean;
|
||||
}
|
||||
|
||||
interface MaterialProfile {
|
||||
materialVideoInfoList: MaterialVideoInfo[];
|
||||
// 基础信息
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
age: number;
|
||||
height: number; // cm
|
||||
weight: number; // kg
|
||||
gender: GenderType;
|
||||
shoeSize: number; // 鞋码
|
||||
// 求职意向
|
||||
employType: EmployType; // 工作类型
|
||||
fullTimeMinPrice: number; // 全职期望薪资下限
|
||||
fullTimeMaxPrice: number; // 全职期望薪资上限
|
||||
partyTimeMinPrice: number; // 兼职期望薪资下限
|
||||
partyTimeMaxPrice: number; // 兼职期望薪资上限
|
||||
cityCode: string; // 城市
|
||||
cityCodes: string; // 城市。多个城市用 、分割,如 '110100、141100'
|
||||
acceptWorkForSit: boolean; // 是否接受坐班
|
||||
// 直播经验
|
||||
workedYear: WorkedYears; // 工作年限,单位年,无为0、半年 0.5、1、2、3、4、5年及以上用100表示,默认为1年
|
||||
workedAccounts: string; // 直播过的账号
|
||||
newAccountExperience: number; // 是否有起号经验,0无 1有
|
||||
workedCategory: string; // 直播过的品类
|
||||
workedSecCategoryStr: string; // 直播过的二级品类
|
||||
style: string; // 风格。多个分割用 、分割
|
||||
maxGmv: number; // 最高 GMV,单位 w
|
||||
maxOnline: number; // 最高在线人数
|
||||
// 自身优势
|
||||
advantages: string;
|
||||
// 其他
|
||||
approveStatus: boolean; // 审核状态:0 待审 ,1 成功 ,2 不通过
|
||||
isOpen: boolean; // 整体状态是否开放 1开放 0不开放
|
||||
createType: ProfileCreateSource;
|
||||
creator: string; // 创建人id
|
||||
progressBar: number; // 进度百分比
|
||||
filledItems: number; // 已填资料项数
|
||||
created: number; // 时间戳
|
||||
updated: number; // 时间戳
|
||||
}
|
||||
|
||||
interface MaterialListItem extends MaterialProfile {
|
||||
// 主播用户 id
|
||||
userId: string;
|
||||
// 主播昵称
|
||||
nickname: string;
|
||||
// 主播手机号
|
||||
userPhone: string;
|
||||
userOpen: boolean;
|
||||
adminOpen: boolean; // 后台控制是否开放 1开放 0不开放
|
||||
}
|
||||
|
||||
interface ListResult<T> {
|
||||
data: T[];
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
// 排序字段
|
||||
sortField: string;
|
||||
// 是否升序
|
||||
asc: string;
|
||||
}
|
||||
|
||||
interface TableList<T> extends ListResult<T> {
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
type UpdateJobParams = Pick<JobListItem, 'id' | 'jobId' | 'disable'>;
|
||||
|
||||
type UpdateGroupParams = Pick<GroupListItem, 'id' | 'city' | 'disable'>;
|
||||
|
||||
type UpdatePublisherParams = Pick<
|
||||
PublisherListItem,
|
||||
'blPublisherId' | 'publisherAcctNo' | 'status' | 'addAcctStatus'
|
||||
>;
|
||||
|
||||
type UpdateAnchorParams = Pick<AnchorListItem, 'userId' | 'status'>;
|
||||
|
||||
type UpdateDeclarationParams = Pick<DeclarationListItem, 'id' | 'weComStatus'>;
|
||||
|
||||
type UpdateMaterialParams = Pick<MaterialListItem, 'id'> & { adminOpen: number };
|
||||
}
|
31
src/services/user.ts
Normal file
31
src/services/user.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// @ts-ignore
|
||||
/* eslint-disable */
|
||||
import { AdminAPI } from '@/constants/api';
|
||||
import { request } from '@umijs/max';
|
||||
import { md5 } from 'js-md5';
|
||||
|
||||
export async function currentUser(options?: { [key: string]: any }) {
|
||||
return request<API.CurrentUser>(AdminAPI.USER, {
|
||||
method: 'POST',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
|
||||
body.pwd = md5(body.pwd || '');
|
||||
return request<API.LoginResult>(AdminAPI.LOGIN, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
export async function outLogin(options?: { [key: string]: any }) {
|
||||
// return request<Record<string, any>>(AdminAPI.OUT_LOGIN, {
|
||||
// method: 'POST',
|
||||
// ...(options || {}),
|
||||
// });
|
||||
}
|
9
src/types/http.ts
Normal file
9
src/types/http.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { RESPONSE_ERROR_CODE } from '@/constants/http';
|
||||
|
||||
export interface IRequestResponse<T = any> {
|
||||
data: T;
|
||||
// 请求出错时才会返回下面几个字段
|
||||
code: RESPONSE_ERROR_CODE;
|
||||
msg: string;
|
||||
traceId: string;
|
||||
}
|
20
src/typings.d.ts
vendored
Normal file
20
src/typings.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
declare module 'slash2';
|
||||
declare module '*.css';
|
||||
declare module '*.less';
|
||||
declare module '*.scss';
|
||||
declare module '*.sass';
|
||||
declare module '*.svg';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.jpeg';
|
||||
declare module '*.gif';
|
||||
declare module '*.bmp';
|
||||
declare module '*.tiff';
|
||||
declare module 'omit.js';
|
||||
declare module 'numeral';
|
||||
declare module '@antv/data-set';
|
||||
declare module 'mockjs';
|
||||
declare module 'react-fittext';
|
||||
declare module 'bizcharts-plugin-slider';
|
||||
|
||||
declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
|
6
src/utils/common.ts
Normal file
6
src/utils/common.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const isValidPhone = (phone: string, callback: (msg?: string) => void) => {
|
||||
if (!/^1\d{10}$/.test(phone)) {
|
||||
callback('手机号格式不正确');
|
||||
}
|
||||
callback();
|
||||
};
|
22
src/utils/login.ts
Normal file
22
src/utils/login.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { history } from '@umijs/max';
|
||||
|
||||
import { LOGIN_PATH } from '@/constants/global';
|
||||
|
||||
let _token = '';
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
_token = token;
|
||||
};
|
||||
|
||||
export const getToken = () => {
|
||||
return _token;
|
||||
};
|
||||
|
||||
export const clearToken = () => {
|
||||
return (_token = '');
|
||||
};
|
||||
|
||||
export const gotoLogin = () => {
|
||||
console.trace('gotoLogin');
|
||||
history.push(LOGIN_PATH);
|
||||
};
|
Reference in New Issue
Block a user