umi#Link TypeScript Examples

The following examples show how to use umi#Link. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: list.tsx    From yforms with MIT License 6 votes vote down vote up
function QueryParamsDemo() {
  const query = useQuery();

  const match = { params: { type: query.get('type'), id: query.get('id') } };

  const hash = useLocation().hash.split('?')[0];
  return (
    <div>
      {match.params.type ? (
        <Child match={match} />
      ) : (
        <div>
          <h2>列表</h2>
          <ul>
            <li>
              <Link to={`${hash}?type=create`}>新建</Link>
            </li>
            <li>
              <Link to={`${hash}?type=view&id=1`}>查看</Link>
            </li>
            <li>
              <Link to={`${hash}?type=edit&id=1`}>编辑</Link>
            </li>
          </ul>
        </div>
      )}
    </div>
  );
}
Example #2
Source File: index.tsx    From fower with MIT License 6 votes vote down vote up
export default function IndexPage() {
  const [colorMode, setColorMode] = useState('default');
  return (
    <Box p10>
      <Box toCenter gray500--dark spaceX2 flexWrap gap-10>
        {pages.map((i) => (
          <Link key={i} to={'/' + i}>
            {i}
          </Link>
        ))}
      </Box>
      <Box
        as="button"
        onClick={() => {
          setColorMode(colorMode === 'default' ? 'dark' : 'default');
          if (colorMode === 'default') {
            document.documentElement.classList.add('dark');
          } else {
            document.documentElement.classList.remove('dark');
          }
        }}
      >
        切换 {colorMode}
      </Box>
    </Box>
  );
}
Example #3
Source File: tag-list.tsx    From bext with MIT License 6 votes vote down vote up
TagList: FC = () => {
  const { tagList } = useMeta();
  const theme = useTheme();
  return (
    <>
      <Title>全部分类</Title>
      <div className="grid grid-cols-3 gap-3">
        {tagList.map((tag) => (
          <Link
            to={`/meta?tag=${encodeURIComponent(tag.name)}`}
            key={tag.name}
            onClick={() => trackEvent(Events.tagClick, tag.name)}
          >
            <div
              className="h-12 flex items-center justify-center cursor-pointer"
              style={{
                boxShadow: theme.effects.elevation4,
              }}
            >
              {tag.name}
            </div>
          </Link>
        ))}
      </div>
    </>
  );
}
Example #4
Source File: BasicLayout.tsx    From ant-design-pro-V4 with MIT License 6 votes vote down vote up
noMatch = (
  <Result
    status={403}
    title="403"
    subTitle="Sorry, you are not authorized to access this page."
    extra={
      <Button type="primary">
        <Link to="/user/login">Go Login</Link>
      </Button>
    }
  />
)
Example #5
Source File: BasicLayout.tsx    From ui-visualization with MIT License 6 votes vote down vote up
noMatch = (
  <Result
    status={403}
    title="403"
    subTitle="Sorry, you are not authorized to access this page."
    extra={
      <Button type="primary">
        <Link to="/user/login">Go Login</Link>
      </Button>
    }
  />
)
Example #6
Source File: BasicLayout.tsx    From jetlinks-ui-antd with MIT License 6 votes vote down vote up
noMatch = (
  <Result
    status="403"
    title="403"
    subTitle="Sorry, you are not authorized to access this page."
    extra={
      <Button type="primary">
        <Link to="/user/login">Go Login</Link>
      </Button>
    }
  />
)
Example #7
Source File: index.tsx    From umi-micro-apps with MIT License 6 votes vote down vote up
UserList = (props: any) => {
  const { shopId } = props;
  const { data = [] } = useRequest(() => request(`/api/user/list?shopId=${shopId}`));

  const columns = [
    {
      dataIndex: 'id',
      title: 'ID',
    },
    {
      dataIndex: 'name',
      title: '姓名',
    },
    {
      dataIndex: 'address',
      title: '住址',
    },
    {
      dataIndex: 'id',
      title: '操作',
      render: (id: string) => (
        <Link to={`/${id}`}>详情</Link>
      )
    },
  ];

  return (
    <div>
      <h1 style={{ marginBottom: 24 }}>用户列表</h1>

      <Table rowKey="id" columns={columns} dataSource={data} />

    </div>
  );
}
Example #8
Source File: UserLayout.tsx    From jetlinks-ui-antd with MIT License 5 votes vote down vote up
UserLayout: React.FC<UserLayoutProps> = props => {
  const {
    route = {
      routes: [],
    },
    settings,
  } = props;
  const { routes = [] } = route;
  const {
    children,
    location = {
      pathname: '',
    },
  } = props;
  const { breadcrumb } = getMenuData(routes);
  const title = getPageTitle({
    pathname: location.pathname,
    breadcrumb,
    ...props,
  });

  return (
    <>
      <Helmet>
        <title>{title}</title>
        <meta name="description" content={title} />
      </Helmet>

      <div className={styles.container}>
        <div className={styles.lang}>
          {/* <SelectLang /> */}
        </div>
        <div className={styles.content}>
          <div className={styles.top}>
            <div className={styles.header}>
              <Link to="/">
                <img alt="logo" className={styles.logo} src={logo} />
                <span className={styles.title}>JetLinks </span>
              </Link>
            </div>
            <div className={styles.desc}>捷联物联网平台</div>
          </div>
          {children}
        </div>
        {/* <DefaultFooter /> */}
      </div>
    </>
  );
}
Example #9
Source File: index.tsx    From plugin-layout with MIT License 5 votes vote down vote up
BasicLayout = (props: any) => {
  const { children, userConfig, location } = props;
  const { initialState, loading } = (useModel && useModel('@@initialState')) || {
    initialState: undefined,
    loading: false,
  }; // plugin-initial-state 未开启
  const _routes = require('@@/router').routes;
  const rightContentRender = useRightContent(userConfig, loading, initialState);
  const layoutConfig = getLayoutConfigFromRoute(_routes);
  const patchMenus: (ms: MenuItem[]) => MenuItem[] =
    userConfig.patchMenus || ((ms: MenuItem[]): MenuItem[] => ms);
  const menus = patchMenus(getMenuDataFromRoutes(_routes[0].routes));

  // layout 是否渲染相关
  const pathName = location.pathname;
  const layoutRender: any = {};

  // 动态路由匹配
  const currentMatchPaths = Object.keys(layoutConfig).filter(item =>
    pathToRegexp(`${item}(.*)`).test(pathName),
  );

  const currentPathConfig = currentMatchPaths.length
    ? layoutConfig[currentMatchPaths[currentMatchPaths.length - 1]]
    : undefined;

  if (currentPathConfig && currentPathConfig.hideMenu) {
    layoutRender.menuRender = false;
  }

  if (currentPathConfig && currentPathConfig.hideNav) {
    layoutRender.headerRender = false;
  }

  return (
    <ProLayout
      title={userConfig.name || userConfig.title}
      className="umi-plugin-layout-main"
      navTheme="dark"
      siderWidth={256}
      onMenuHeaderClick={e => {
        e.stopPropagation();
        e.preventDefault();
        history.push('/');
      }}
      menu={{ locale: userConfig.locale }}
      menuDataRender={() => menus}
      formatMessage={formatMessage}
      logo={initialState?.avatar || logo}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (menuItemProps.isUrl || menuItemProps.children) {
          return defaultDom;
        }
        return <Link to={menuItemProps.path || ''}>{defaultDom}</Link>;
      }}
      disableContentMargin
      rightContentRender={rightContentRender}
      fixSiderbar
      fixedHeader
      {...userConfig}
      {...props}
      {...layoutRender}
    >
      <ErrorBoundary>
        <WithExceptionOpChildren currentPathConfig={currentPathConfig}>
          {userConfig.childrenRender
            ? userConfig.childrenRender(children)
            : children}
        </WithExceptionOpChildren>
      </ErrorBoundary>
    </ProLayout>
  );
}
Example #10
Source File: LoginLayout.tsx    From dashboard with Apache License 2.0 5 votes vote down vote up
LoginLayout: React.FC<UserLayoutProps> = (props) => {
  const {
    route = {
      routes: [],
    },
  } = props;
  const { routes = [] } = route;
  const {
    children,
    location = {
      pathname: '',
    },
  } = props;
  const { breadcrumb } = getMenuData(routes);
  const title = getPageTitle({
    pathname: location.pathname,
    breadcrumb,
    ...props,
  });
  return (
    <HelmetProvider>
      <Helmet>
        <title>{title}</title>
        <meta name="description" content={title} />
      </Helmet>
      <div className={styles.container}>
        <div className={styles.content}>
          <div className={styles.top}>
            <div className={styles.header}>
              <Link to="/">
                <img alt="logo" className={styles.logo} src={logo} />
              </Link>
            </div>
          </div>
          {children}
        </div>
      </div>
    </HelmetProvider>
  );
}
Example #11
Source File: UserLayout.tsx    From ui-visualization with MIT License 5 votes vote down vote up
UserLayout: React.FC<UserLayoutProps> = (props) => {
  const {
    route = {
      routes: [],
    },
  } = props;
  const { routes = [] } = route;
  const {
    children,
    location = {
      pathname: '',
    },
  } = props;
  const { formatMessage } = useIntl();
  const { breadcrumb } = getMenuData(routes);
  const title = getPageTitle({
    pathname: location.pathname,
    formatMessage,
    breadcrumb,
    ...props,
  });
  return (
    <HelmetProvider>
      <Helmet>
        <title>{title}</title>
        <meta name="description" content={title} />
      </Helmet>

      <div className={styles.container}>
        <div className={styles.lang}>
          <SelectLang />
        </div>
        <div className={styles.content}>
          <div className={styles.top}>
            <div className={styles.header}>
              <Link to="/">
                <img alt="logo" className={styles.logo} src={logo} />
                <span className={styles.title}>Ant Design</span>
              </Link>
            </div>
            <div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
          </div>
          {children}
        </div>
        <DefaultFooter />
      </div>
    </HelmetProvider>
  );
}
Example #12
Source File: UserLayout.tsx    From ant-design-pro-V4 with MIT License 5 votes vote down vote up
UserLayout: React.FC<UserLayoutProps> = (props) => {
  const {
    route = {
      routes: [],
    },
  } = props;
  const { routes = [] } = route;
  const {
    children,
    location = {
      pathname: '',
    },
  } = props;
  const { formatMessage } = useIntl();
  const { breadcrumb } = getMenuData(routes);
  const title = getPageTitle({
    pathname: location.pathname,
    formatMessage,
    breadcrumb,
    ...props,
  });
  return (
    <HelmetProvider>
      <Helmet>
        <title>{title}</title>
        <meta name="description" content={title} />
      </Helmet>

      <div className={styles.container}>
        <div className={styles.lang}>
          <SelectLang />
        </div>
        <div className={styles.content}>
          <div className={styles.top}>
            <div className={styles.header}>
              <Link to="/">
                <img alt="logo" className={styles.logo} src={logo} />
                <span className={styles.title}>Ant Design</span>
              </Link>
            </div>
            <div className={styles.desc}>
              <FormattedMessage
                id="pages.layouts.userLayout.title"
                defaultMessage="Ant Design. The most influential Web design specification in Xihu District."
              />
            </div>
          </div>
          {children}
        </div>
        <DefaultFooter />
      </div>
    </HelmetProvider>
  );
}
Example #13
Source File: BasicLayout.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
BasicLayout: React.FC<BasicLayoutProps> = props => {
  const {
    dispatch,
    children,
    settings,
    location = {
      pathname: '/',
    },
  } = props;

  const [mark, setMark] = useState<string | boolean>(localStorage.getItem('hide_menu') || "false");
  const hide_menu = props.location?.query?.hide_menu;
  const token = getAccessToken();
  useEffect(() => {
    if (dispatch) {
        if(token!=='null'){
            dispatch({
                type: 'user/fetchCurrent',
              });
        }else{
            router.push('/user/login');
        }

    }
    if (hide_menu) {
      setMark(hide_menu);
      localStorage.setItem('hide_menu', hide_menu);
    }
  }, [hide_menu]);

  const handleMenuCollapse = (payload: boolean): void => {
    if (dispatch) {
      dispatch({
        type: 'global/changeLayoutCollapsed',
        payload,
      });
    }
  }; // get children authority

  const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || {
    authority: undefined,
  };

  return mark === 'true' ? (
    <Authorized authority={authorized!.authority} noMatch={noMatch}>
      {children}
    </Authorized>
  ) : (
    <ProLayout
      // logo={logo}
      logo={settings.titleIcon || logo}

      menuHeaderRender={(logoDom, titleDom) => (
        <Link to="/">
          {logoDom}
          {titleDom}
        </Link>
      )}
      onCollapse={handleMenuCollapse}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (menuItemProps.isUrl || menuItemProps.children || !menuItemProps.path) {
          return defaultDom;
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: '/',
          breadcrumbName: '首页',
        },
        ...routers,
      ]}
      itemRender={(route, params, routes, paths) => {
        const first = routes.indexOf(route) === 0;
        return first ? (
          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
        ) : (
          <span>{route.breadcrumbName}</span>
        );
      }}
      footerRender={footerRender}
      menuDataRender={menuDataRender}
      // menuDataRender={()=>menuData}
      rightContentRender={() => <RightContent />}
      {...props}
      {...settings}
      fixSiderbar
    >
      <Authorized authority={authorized!.authority} noMatch={noMatch}>
        {children}
      </Authorized>
    </ProLayout>
  );
}
Example #14
Source File: index.tsx    From ant-design-pro-V4 with MIT License 4 votes vote down vote up
DashboardWorkplace: FC = () => {
  const { loading: projectLoading, data: projectNotice = [] } = useRequest(queryProjectNotice);
  const { loading: activitiesLoading, data: activities = [] } = useRequest(queryActivities);
  const { data } = useRequest(fakeChartData);

  const renderActivities = (item: ActivitiesType) => {
    const events = item.template.split(/@\{([^{}]*)\}/gi).map((key) => {
      if (item[key]) {
        return (
          <a href={item[key].link} key={item[key].name}>
            {item[key].name}
          </a>
        );
      }
      return key;
    });
    return (
      <List.Item key={item.id}>
        <List.Item.Meta
          avatar={<Avatar src={item.user.avatar} />}
          title={
            <span>
              <a className={styles.username}>{item.user.name}</a>
              &nbsp;
              <span className={styles.event}>{events}</span>
            </span>
          }
          description={
            <span className={styles.datetime} title={item.updatedAt}>
              {moment(item.updatedAt).fromNow()}
            </span>
          }
        />
      </List.Item>
    );
  };

  return (
    <PageContainer
      content={
        <PageHeaderContent
          currentUser={{
            avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
            name: '吴彦祖',
            userid: '00000001',
            email: '[email protected]',
            signature: '海纳百川,有容乃大',
            title: '交互专家',
            group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
          }}
        />
      }
      extraContent={<ExtraContent />}
    >
      <Row gutter={24}>
        <Col xl={16} lg={24} md={24} sm={24} xs={24}>
          <Card
            className={styles.projectList}
            style={{ marginBottom: 24 }}
            title="进行中的项目"
            bordered={false}
            extra={<Link to="/">全部项目</Link>}
            loading={projectLoading}
            bodyStyle={{ padding: 0 }}
          >
            {projectNotice.map((item) => (
              <Card.Grid className={styles.projectGrid} key={item.id}>
                <Card bodyStyle={{ padding: 0 }} bordered={false}>
                  <Card.Meta
                    title={
                      <div className={styles.cardTitle}>
                        <Avatar size="small" src={item.logo} />
                        <Link to={item.href}>{item.title}</Link>
                      </div>
                    }
                    description={item.description}
                  />
                  <div className={styles.projectItemContent}>
                    <Link to={item.memberLink}>{item.member || ''}</Link>
                    {item.updatedAt && (
                      <span className={styles.datetime} title={item.updatedAt}>
                        {moment(item.updatedAt).fromNow()}
                      </span>
                    )}
                  </div>
                </Card>
              </Card.Grid>
            ))}
          </Card>
          <Card
            bodyStyle={{ padding: 0 }}
            bordered={false}
            className={styles.activeCard}
            title="动态"
            loading={activitiesLoading}
          >
            <List<ActivitiesType>
              loading={activitiesLoading}
              renderItem={(item) => renderActivities(item)}
              dataSource={activities}
              className={styles.activitiesList}
              size="large"
            />
          </Card>
        </Col>
        <Col xl={8} lg={24} md={24} sm={24} xs={24}>
          <Card
            style={{ marginBottom: 24 }}
            title="快速开始 / 便捷导航"
            bordered={false}
            bodyStyle={{ padding: 0 }}
          >
            <EditableLinkGroup onAdd={() => {}} links={links} linkElement={Link} />
          </Card>
          <Card
            style={{ marginBottom: 24 }}
            bordered={false}
            title="XX 指数"
            loading={data?.radarData?.length === 0}
          >
            <div className={styles.chart}>
              <Radar
                height={343}
                data={data?.radarData || []}
                angleField="label"
                seriesField="name"
                radiusField="value"
                area={{
                  visible: false,
                }}
                point={{
                  visible: true,
                }}
                legend={{
                  position: 'bottom-center',
                }}
              />
            </div>
          </Card>
          <Card
            bodyStyle={{ paddingTop: 12, paddingBottom: 12 }}
            bordered={false}
            title="团队"
            loading={projectLoading}
          >
            <div className={styles.members}>
              <Row gutter={48}>
                {projectNotice.map((item) => (
                  <Col span={12} key={`members-item-${item.id}`}>
                    <Link to={item.href}>
                      <Avatar src={item.logo} size="small" />
                      <span className={styles.member}>{item.member}</span>
                    </Link>
                  </Col>
                ))}
              </Row>
            </div>
          </Card>
        </Col>
      </Row>
    </PageContainer>
  );
}
Example #15
Source File: BasicLayout.tsx    From ui-visualization with MIT License 4 votes vote down vote up
BasicLayout: React.FC<BasicLayoutProps> = (props) => {
  const {
    dispatch,
    children,
    settings,
    location = {
      pathname: '/',
    },
  } = props;
  /**
   * constructor
   */

  useEffect(() => {
    if (dispatch) {
      dispatch({
        type: 'user/fetchCurrent',
      });
    }
  }, []);
  /**
   * init variables
   */

  const handleMenuCollapse = (payload: boolean): void => {
    if (dispatch) {
      dispatch({
        type: 'global/changeLayoutCollapsed',
        payload,
      });
    }
  }; // get children authority

  const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || {
    authority: undefined,
  };
  const { formatMessage } = useIntl();

  return (
    <ProLayout
      logo={logo}
      formatMessage={formatMessage}
      menuHeaderRender={(logoDom, titleDom) => (
        <Link to="/">
          {logoDom}
          {titleDom}
        </Link>
      )}
      onCollapse={handleMenuCollapse}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (menuItemProps.isUrl || menuItemProps.children || !menuItemProps.path) {
          return defaultDom;
        }

        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: '/',
          breadcrumbName: formatMessage({ id: 'menu.home' }),
        },
        ...routers,
      ]}
      itemRender={(route, params, routes, paths) => {
        const first = routes.indexOf(route) === 0;
        return first ? (
          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
        ) : (
          <span>{route.breadcrumbName}</span>
        );
      }}
      footerRender={() => defaultFooterDom}
      menuDataRender={menuDataRender}
      rightContentRender={() => <RightContent />}
      {...props}
      {...settings}
    >
      <Authorized authority={authorized!.authority} noMatch={noMatch}>
        {children}
      </Authorized>
    </ProLayout>
  );
}
Example #16
Source File: index.tsx    From ui-visualization with MIT License 4 votes vote down vote up
Login: React.FC<LoginProps> = (props) => {
  const { userLogin = {}, submitting } = props;
  const { status, type: loginType } = userLogin;
  const [autoLogin, setAutoLogin] = useState(true);
  const [type, setType] = useState<string>('account');

  const handleSubmit = (values: LoginParamsType) => {
    const { dispatch } = props;
    dispatch({
      type: 'login/login',
      payload: { ...values, type },
    });
  };
  return (
    <div className={styles.main}>
      <LoginForm activeKey={type} onTabChange={setType} onSubmit={handleSubmit}>
        <Tab key="account" tab="账户密码登录">
          {status === 'error' && loginType === 'account' && !submitting && (
            <LoginMessage content="账户或密码错误(admin/ant.design)" />
          )}

          <UserName
            name="userName"
            placeholder="用户名: admin or user"
            rules={[
              {
                required: true,
                message: '请输入用户名!',
              },
            ]}
          />
          <Password
            name="password"
            placeholder="密码: ant.design"
            rules={[
              {
                required: true,
                message: '请输入密码!',
              },
            ]}
          />
        </Tab>
        <Tab key="mobile" tab="手机号登录">
          {status === 'error' && loginType === 'mobile' && !submitting && (
            <LoginMessage content="验证码错误" />
          )}
          <Mobile
            name="mobile"
            placeholder="手机号"
            rules={[
              {
                required: true,
                message: '请输入手机号!',
              },
              {
                pattern: /^1\d{10}$/,
                message: '手机号格式错误!',
              },
            ]}
          />
          <Captcha
            name="captcha"
            placeholder="验证码"
            countDown={120}
            getCaptchaButtonText=""
            getCaptchaSecondText="秒"
            rules={[
              {
                required: true,
                message: '请输入验证码!',
              },
            ]}
          />
        </Tab>
        <div>
          <Checkbox checked={autoLogin} onChange={(e) => setAutoLogin(e.target.checked)}>
            自动登录
          </Checkbox>
          <a
            style={{
              float: 'right',
            }}
          >
            忘记密码
          </a>
        </div>
        <Submit loading={submitting}>登录</Submit>
        <div className={styles.other}>
          其他登录方式
          <AlipayCircleOutlined className={styles.icon} />
          <TaobaoCircleOutlined className={styles.icon} />
          <WeiboCircleOutlined className={styles.icon} />
          <Link className={styles.register} to="/user/register">
            注册账户
          </Link>
        </div>
      </LoginForm>
    </div>
  );
}
Example #17
Source File: BasicLayout.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
BasicLayout: React.FC<BasicLayoutProps> = (props) => {
  const {
    dispatch,
    children,
    settings,
    location = {
      pathname: '/',
    },
  } = props;
  const menuDataRef = useRef<MenuDataItem[]>([]);

  /** Init variables */

  const handleMenuCollapse = (payload: boolean): void => {
    if (dispatch) {
      dispatch({
        type: 'global/changeLayoutCollapsed',
        payload,
      });
    }
  }; // get children authority

  const noMatch = (
    <Result
      status={403}
      title="403"
      subTitle="抱歉,您无权访问此页面"
      extra={
        <Button type="primary">
          <Link to={'staffAdmin/login'}>
            去登陆
          </Link>
        </Button>
      }
    />
  );

  const authorized = useMemo(
    () =>
      getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
        authority: undefined,
      },
    [location.pathname],
  );

  return (
    <ProLayout
      logo={logo}
      {...props}
      {...settings}
      onCollapse={handleMenuCollapse}
      layout={'mix'}
      navTheme={'light'}
      headerTheme={'dark'}
      siderWidth={260}
      className={'main'}
      menu={{
        defaultOpenAll: true,
        type: 'sub',
        ignoreFlatMenu: true,
      }}
      onMenuHeaderClick={() => history.push('/')}
      headerTitleRender={() => {
        return (
          <a onClick={() => history.push('/')}>
            <img src={headerLogo} height={32} alt={'OpenSCRM'} />
          </a>
        );
      }}
      menuRender={(menuProps: HeaderViewProps, defaultDom: React.ReactNode) => {
        return <div className={'sidebar-menu'}>{defaultDom}</div>;
      }}
      menuItemRender={(menuItemProps, defaultDom) => {
        const isTopLevel = menuItemProps && menuItemProps?.pro_layout_parentKeys?.length === 0;
        if (
          menuItemProps.isUrl ||
          !menuItemProps.path ||
          location.pathname === menuItemProps.path
        ) {
          return (
            <div>
              {!isTopLevel && getIcon(menuItemProps.icon)}
              {defaultDom}
            </div>
          );
        }

        return (
          <Link to={menuItemProps.path}>
            {!isTopLevel && getIcon(menuItemProps.icon)}
            {defaultDom}
          </Link>
        );
      }}
      // breadcrumbRender={(routers = []) => [
      //   {
      //     path: '/',
      //     breadcrumbName: '首页',
      //   },
      //   ...routers,
      // ]}
      breadcrumbRender={false}
      footerRender={() => {
        // if (settings.footerRender || settings.footerRender === undefined) {
        //   return defaultFooterDom;
        // }

        return null;
      }}
      menuDataRender={menuDataRender}
      rightContentRender={() => <RightContent />}
      postMenuData={(menuData) => {
        menuDataRef.current = menuData || [];
        return menuData || [];
      }}
    >
      <Authorized authority={authorized!.authority} noMatch={noMatch}>
        {children}
      </Authorized>
    </ProLayout>
  );
}
Example #18
Source File: BasicLayout.tsx    From ant-design-pro-V4 with MIT License 4 votes vote down vote up
BasicLayout: React.FC<BasicLayoutProps> = (props) => {
  const {
    dispatch,
    children,
    settings,
    location = {
      pathname: '/',
    },
  } = props;

  const menuDataRef = useRef<MenuDataItem[]>([]);

  useEffect(() => {
    if (dispatch) {
      dispatch({
        type: 'user/fetchCurrent',
      });
    }
  }, []);
  /** Init variables */

  const handleMenuCollapse = (payload: boolean): void => {
    if (dispatch) {
      dispatch({
        type: 'global/changeLayoutCollapsed',
        payload,
      });
    }
  };
  // get children authority
  const authorized = useMemo(
    () =>
      getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
        authority: undefined,
      },
    [location.pathname],
  );

  const { formatMessage } = useIntl();

  return (
    <ProLayout
      logo={logo}
      formatMessage={formatMessage}
      {...props}
      {...settings}
      onCollapse={handleMenuCollapse}
      onMenuHeaderClick={() => history.push('/')}
      menuItemRender={(menuItemProps, defaultDom) => {
        if (
          menuItemProps.isUrl ||
          !menuItemProps.path ||
          location.pathname === menuItemProps.path
        ) {
          return defaultDom;
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      breadcrumbRender={(routers = []) => [
        {
          path: '/',
          breadcrumbName: formatMessage({ id: 'menu.home' }),
        },
        ...routers,
      ]}
      itemRender={(route, params, routes, paths) => {
        const first = routes.indexOf(route) === 0;
        return first ? (
          <Link to={paths.join('/')}>{route.breadcrumbName}</Link>
        ) : (
          <span>{route.breadcrumbName}</span>
        );
      }}
      footerRender={() => {
        if (settings.footerRender || settings.footerRender === undefined) {
          return defaultFooterDom;
        }
        return null;
      }}
      menuDataRender={menuDataRender}
      rightContentRender={() => <RightContent />}
      postMenuData={(menuData) => {
        menuDataRef.current = menuData || [];
        return menuData || [];
      }}
      waterMarkProps={{
        content: 'Ant Design Pro',
        fontColor: 'rgba(24,144,255,0.15)',
      }}
    >
      <Authorized authority={authorized!.authority} noMatch={noMatch}>
        {children}
      </Authorized>
    </ProLayout>
  );
}
Example #19
Source File: form.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
ContactWayForm: React.FC<ContactWayFormProps> = (props) => {
  const {staffs, tagGroups, initialValues, mode, ...rest} = props;
  const [staffSelectionVisible, setStaffSelectionVisible] = useState(false);
  const [selectedStaffs, setSelectedStaffs] = useState<StaffOption[]>([]);
  const [backupStaffSelectionVisible, setBackupStaffSelectionVisible] = useState(false);
  const [backupSelectedStaffs, setBackupSelectedStaffs] = useState<StaffOption[]>([]);
  const [blockNicknames, setBlockNicknames] = useState<string[]>([]);
  const [isFetchDone, setIsFetchDone] = useState(false);
  const [autoReply, setAutoReply] = useState<WelcomeMsg>({text: ''});
  const formRef = useRef<FormInstance>();
  const backupSelectRef = useRef<HTMLInputElement>(null);
  const staffSelectRef = useRef<HTMLInputElement>(null);
  const addScheduleRef = useRef<HTMLInputElement>(null);
  const [currentItem, setCurrentItem] = useState<ContactWayItem>();

  const itemDataToFormValues = (item: ContactWayItem | any): CreateContactWayParam => {
    const values: CreateContactWayParam = {
      daily_add_customer_limit: 0,
      ...item,
      id: props.itemID,
    };
    if (item.staffs) {
      setSelectedStaffs(
        item.staffs.map((staff: any) => {
          return {
            key: staff.ext_staff_id,
            label: staff.id,
            value: staff.ext_staff_id,
            ...staff,
            ext_id: staff.ext_staff_id,
          };
        }),
      );
    }

    if (item.auto_reply) {
      setAutoReply(item.auto_reply);
    }

    if (item.nickname_block_list) {
      setBlockNicknames(item.nickname_block_list);
    }

    if (item.backup_staffs) {
      setBackupSelectedStaffs(
        item.backup_staffs.map((staff: any) => {
          return {
            key: staff.ext_staff_id,
            label: staff.id,
            value: staff.ext_staff_id,
            ...staff,
            ext_id: staff.ext_staff_id,
          };
        }),
      );
    }

    const invertWeekdaysEnum = _.invert(WeekdaysEnum);

    if (item.schedules) {
      values.schedules = item.schedules.map((schedule: any) => {
        return {
          ...schedule,
          ext_staff_ids: schedule?.staffs?.map((staff: any) => staff.ext_staff_id),
          weekdays: schedule?.weekdays?.map((day: string) => invertWeekdaysEnum[day]),
          start_time: moment(schedule?.start_time, TimeLayout),
          end_time: moment(schedule?.end_time, TimeLayout),
        };
      });
    }

    // 将内置的boolean转为后端约定的数字
    Object.keys(values).forEach((key) => {
      const t = typeof values[key];
      if (
        !['schedule_enable', 'auto_skip_verify_enable'].includes(key) &&
        t === 'number' &&
        key.includes('_enable') &&
        [1, 2].includes(values[key])
      ) {
        values[key] = values[key] === True;
      }
    });

    if (!values.customer_tag_ext_ids) {
      values.customer_tag_ext_ids = [];
    }

    values.skip_verify_start_time = moment(values.skip_verify_start_time, TimeLayout);
    values.skip_verify_end_time = moment(values.skip_verify_end_time, TimeLayout);

    return values;
  };

  useEffect(() => {
    if (['edit', 'copy'].includes(mode) && props?.itemID) {
      const hide = message.loading('加载数据中');
      GetDetail(props?.itemID).then((res) => {
        hide();
        if (res.code === 0) {
          setCurrentItem(res.data);
          formRef.current?.setFieldsValue(itemDataToFormValues(res.data));
          setIsFetchDone(true);
          setAutoReply(res.data.auto_reply);
        } else {
          message.error(res.message);
        }
      });
    }
  }, [mode, props?.itemID]);

  // 校验参数
  const checkForm = (params: any): boolean => {
    if (backupSelectedStaffs.length === 0) {
      message.warning('请添加备份员工');
      backupSelectRef?.current?.focus();
      return false;
    }

    if (params.schedule_enable === Disable) {
      if (selectedStaffs.length === 0) {
        message.warning('请绑定员工');
        staffSelectRef?.current?.focus();
        return false;
      }
    }

    if (params.schedule_enable === Enable) {
      if (params.schedules.length === 0) {
        message.warning('请添加工作周期');
        addScheduleRef?.current?.focus();
        return false;
      }
    }

    return true;
  };

  // 格式化参数
  const formatParams = (origin: any): CreateContactWayParam => {
    const out = {...origin, id: props.itemID};
    // 将内置的boolean转为后端约定的数字
    Object.keys(out).forEach((key) => {
      const t = typeof out[key];
      if (t === 'boolean') {
        out[key] = out[key] ? True : False;
      }
    });

    const refStaffMap = _.keyBy(currentItem?.staffs, 'ext_staff_id');
    out.staffs = selectedStaffs.map((item): StaffParam => {
      return {
        id: refStaffMap[item.ext_id]?.id || '',
        ext_staff_id: item.ext_id,
        daily_add_customer_limit: out.daily_add_customer_limit ? out.daily_add_customer_limit : 0,
      };
    });

    const refBackupStaffMap = _.keyBy(currentItem?.backup_staffs, 'ext_staff_id');
    out.backup_staffs = backupSelectedStaffs.map((item): StaffParam => {
      return {
        id: refBackupStaffMap[item.ext_id]?.id || '',
        ext_staff_id: item.ext_id,
        daily_add_customer_limit: out.daily_add_customer_limit ? out.daily_add_customer_limit : 0,
      };
    });


    if (out.nickname_block_enable === True) {
      out.nickname_block_list = blockNicknames;
    }

    if (out.skip_verify_start_time) {
      out.skip_verify_start_time = moment(out.skip_verify_start_time, 'HH:mm')
        .format('HH:mm:ss')
        .toString();
    }

    if (out.skip_verify_end_time) {
      out.skip_verify_end_time = moment(out.skip_verify_end_time, 'HH:mm')
        .format('HH:mm:ss')
        .toString();
    }


    if (out.schedules) {
      out.schedules = out.schedules.map((schedule: any, index: number) => {
        // @ts-ignore
        const refScheduleStaffMap = _.keyBy(currentItem?.schedules[index]?.staffs, 'ext_staff_id');
        return {
          ...schedule,
          end_time: moment(schedule.end_time, DateTimeLayout).format('HH:mm:ss').toString(),
          start_time: moment(schedule.start_time, DateTimeLayout).format('HH:mm:ss').toString(),
          staffs: schedule.ext_staff_ids.map((ext_id: string) => {
            return {
              id: refScheduleStaffMap[ext_id]?.id ? refScheduleStaffMap[ext_id]?.id : '',
              daily_add_customer_limit: 0,
              ext_staff_id: ext_id,
            };
          }),
          weekdays: schedule.weekdays.map((day: string) => {
            return WeekdaysEnum[day];
          }),
        };
      });
    }

    if (autoReply) {
      out.auto_reply = autoReply;
    }

    return out;
  };

  return (
    <>
      <ProForm<ContactWayItem>
        {...rest}
        layout={'horizontal'}
        labelCol={{
          md: 4,
        }}
        className={styles.content}
        formRef={formRef}
        onFinish={async (values: any) => {
          const params = formatParams(values);
          if (!checkForm(params)) {
            return false;
          }
          if (props.onFinish) {
            return props.onFinish(params);
          }
          return false;
        }}
        initialValues={{...defaultValues, ...initialValues}}
      >
        <h3>基础信息</h3>
        <Divider/>

        <div className={styles.section}>
          <ProFormText
            labelAlign={'right'}
            width={'md'}
            name='name'
            label='渠道码名称'
            placeholder='请输入名称'
            rules={[
              {
                required: true,
                message: '请输入名称',
              },
            ]}
          />

          <ProFormSelect
            width={'md'}
            name='group_id'
            label='渠道码分组'
            placeholder='请选择分组'
            request={async () => {
              const res = await QueryGroup();
              if (res.data) {
                const items = res.data.items as ContactWayGroupItem[];
                return items.map((item) => {
                  return {label: item.name, value: item.id};
                });
              }
              return [];
            }}
            rules={[
              {
                required: true,
                message: '请选择分组',
              },
            ]}
          />

          <Form.Item
            label={<span className={'form-item-required'}>备份员工</span>}
            tooltip={'此渠道码没有活跃员工时,使用此处配置的员工兜底'}
          >
            <Button
              ref={backupSelectRef}
              icon={<PlusOutlined/>}
              onClick={() => setBackupStaffSelectionVisible(true)}
            >
              添加备用员工
            </Button>
          </Form.Item>

          <Space style={{marginTop: -12, marginBottom: 24, marginLeft: 20}}>
            {backupSelectedStaffs.map((item) => (
              <div key={item.id} className={'staff-item'}>
                <Badge
                  count={
                    <CloseCircleOutlined
                      onClick={() => {
                        setBackupSelectedStaffs(
                          backupSelectedStaffs.filter((staff) => staff.id !== item.id),
                        );
                      }}
                      style={{color: 'rgb(199,199,199)'}}
                    />
                  }
                >
                  <div className={'container'}>
                    <img alt={item.name} className={'avatar'} src={item.avatar_url}/>
                    <span className={'text'}>{item.name}</span>
                  </div>
                </Badge>
              </div>
            ))}
          </Space>

          <ProFormRadio.Group
            name='schedule_enable'
            label='绑定员工'
            tooltip={'用户扫描此二维码会添加这里配置的员工'}
            options={[
              {
                label: '全天在线',
                value: Disable,
              },
              {
                label: '自动上下线',
                value: Enable,
              },
            ]}
            rules={[
              {
                required: true,
                message: '请绑定员工',
              },
            ]}
          />
          <ProFormDependency name={['schedule_enable']}>
            {({schedule_enable}) => {
              // 全天在线
              if (schedule_enable === Disable) {
                return (
                  <div style={{marginLeft: 20}}>
                    <Space className={styles.formItem} style={{marginTop: -16, marginLeft: 12}}>
                      <Button
                        ref={staffSelectRef}
                        icon={<PlusOutlined/>}
                        onClick={() => setStaffSelectionVisible(true)}
                      >
                        添加员工
                      </Button>
                      <Text type={'secondary'}>
                        同一个二维码可绑定多个员工,客户扫码后随机分配一名员工进行接待
                      </Text>
                    </Space>

                    <Space className={styles.formItem} style={{marginTop: -16, marginLeft: 12}}>
                      {selectedStaffs.map((item) => (
                        <div key={item.id} className={'staff-item'}>
                          <Badge
                            count={
                              <CloseCircleOutlined
                                onClick={() => {
                                  setSelectedStaffs(
                                    selectedStaffs.filter((staff) => staff.id !== item.id),
                                  );
                                }}
                                style={{color: 'rgb(199,199,199)'}}
                              />
                            }
                          >
                            <div className={'container'}>
                              <img alt={item.name} className={'avatar'} src={item.avatar_url}/>
                              <span className={'text'}>{item.name}</span>
                            </div>
                          </Badge>
                        </div>
                      ))}
                    </Space>
                  </div>
                );
              }

              // 自动上下线
              return (
                <div className={styles.scheduleList} style={{marginLeft: 36}}>
                  <Alert
                    className={styles.tips}
                    type='info'
                    message={
                      '自动上下线:1、可用于员工早晚班等不同上班时间段使用;2、员工在非线上时间将不再接待新客户。'
                    }
                  />
                  <ProFormList
                    name='schedules'
                    creatorButtonProps={{
                      type: 'default',
                      style: {width: '160px'},
                      position: 'bottom',
                      creatorButtonText: '添加其他工作周期',
                    }}
                    creatorRecord={{
                      ext_staff_ids: [],
                    }}
                  >
                    <ProCard className={styles.scheduleItem} ref={addScheduleRef}>
                      <ProForm.Item name={'id'} noStyle={true}>
                        <input type={'hidden'}/>
                      </ProForm.Item>
                      <Row>
                        <label className={styles.label}>选择员工:</label>
                        <ProForm.Item name={'ext_staff_ids'} style={{width: 468}}>
                          <StaffTreeSelect options={staffs} maxTagCount={4}/>
                        </ProForm.Item>
                      </Row>
                      <Row>
                        <label className={styles.label}>工作日:</label>
                        <ProFormSelect
                          mode='multiple'
                          name='weekdays'
                          width={468}
                          valueEnum={WeekdaysEnum}
                          placeholder='请选择工作日'
                          rules={[{required: true, message: '请选择工作日'}]}
                        />
                      </Row>
                      <Row>
                        <label className={styles.label}>工作时间:</label>
                        <ProFormTimePicker
                          fieldProps={{
                            format: TimeLayout,
                          }}
                          name='start_time'
                          placeholder='开始时间'
                          rules={[{required: true, message: '请选择开始时间'}]}
                        />
                        <Space style={{marginLeft: 6}}> </Space>
                        <ProFormTimePicker
                          fieldProps={{
                            format: TimeLayout,
                          }}
                          name='end_time'
                          placeholder='结束时间'
                          rules={[{required: true, message: '请选择结束时间'}]}
                        />
                      </Row>
                    </ProCard>
                  </ProFormList>
                </div>
              );
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'员工添加上限'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='daily_add_customer_limit_enable'
            tooltip={'开启后员工可在侧边栏修改个人上下线状态'}
          />

          <ProFormDependency name={['daily_add_customer_limit_enable']}>
            {({daily_add_customer_limit_enable}) => {
              if (daily_add_customer_limit_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormDigit
                      label={
                        <Tooltip title='员工添加上限:员工从该渠道添加的客户达到每日上限后,将自动下线当日不再接待该渠道新客户'>
                          每日最多添加
                        </Tooltip>
                      }
                      width={'md'}
                      name='daily_add_customer_limit'
                      min={0}
                      max={5000}
                    />
                  </div>
                );
              }
              return <></>;
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'员工自行上下线'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='staff_control_enable'
            tooltip={'开启后员工可在侧边栏修改个人上下线状态'}
          />

          <ProFormSwitch
            label={'客户标签:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='auto_tag_enable'
            tooltip={
              <>
                通过此渠道码添加的客户,将自动打上此处配置的标签。您还可以转到
                <Link to={'/staff-admin/customer-management/customer-tag'}> 管理客户标签</Link>
              </>
            }
          />

          <ProFormDependency name={['auto_tag_enable']}>
            {({auto_tag_enable}) => {
              if (auto_tag_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProForm.Item
                      name={'customer_tag_ext_ids'}
                      label={"客户标签"}
                      rules={[{required: true, message: '请选择客户标签'}]}
                    >
                      <CustomerTagSelect isEditable={true} allTagGroups={tagGroups} maxTagCount={6}
                                         style={{maxWidth: 468}}/>
                    </ProForm.Item>
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'客户备注:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_remark_enable'
            tooltip={<>客户备注将自动附加在客户昵称之后,方便员工查看</>}
          />

          <ProFormDependency name={['customer_remark_enable']}>
            {({customer_remark_enable}) => {
              if (customer_remark_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormText
                      width={'md'}
                      name='customer_remark'
                      label='客户备注'
                      placeholder='客户备注'
                      fieldProps={{
                        prefix: <span style={{color: '#666666'}}>客户昵称-</span>,
                      }}
                      rules={[{required: true, message: '请填写客户备注'}]}
                    />
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'客户描述:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_desc_enable'
            tooltip={'开启后可为客户添加描述,将在客户画像里展示'}
          />
          <ProFormDependency name={['customer_desc_enable']}>
            {({customer_desc_enable}) => {
              if (customer_desc_enable) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormText
                      width={'md'}
                      name='customer_desc'
                      label='客户描述'
                      placeholder='客户描述'
                      rules={[{required: true, message: '请填写客户描述'}]}
                    />
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <h3 style={{marginTop: 30}}>设置欢迎语</h3>
          <Divider/>

          <Alert
            showIcon={true}
            style={{maxWidth: '680px', marginBottom: 20}}
            type='info'
            message={
              '因企业微信限制,若使用成员已在「企业微信后台」配置了欢迎语,这里的欢迎语将不会生效'
            }
          />

          <ProFormRadio.Group
            // 欢迎语类型:1,渠道欢迎语;2, 渠道默认欢迎语;3,不送欢迎语;
            name='auto_reply_type'
            label='设置欢迎语'
            tooltip={'客户添加企业之后,将自动发送此消息给客户'}
            options={[
              {
                label: '渠道欢迎语',
                value: 1,
              },
              {
                label: '渠道默认欢迎语',
                value: 2,
              },
              {
                label: '不送欢迎语',
                value: 3,
              },
            ]}
            rules={[
              {
                required: true,
                message: '请设置欢迎语',
              },
            ]}
          />

          <ProFormDependency name={['auto_reply_type']}>
            {({auto_reply_type}) => {
              if (auto_reply_type === 1) {
                // 渠道欢迎语
                return (
                  <div className={'sub-from-item'}>
                    <Form.Item
                      label={<span className={'form-item-required'}>欢迎语</span>}
                    >
                      <AutoReply welcomeMsg={autoReply} isFetchDone={isFetchDone} setWelcomeMsg={setAutoReply}/>
                    </Form.Item>
                  </div>
                );
              }
              if (auto_reply_type === 2) {
                // 渠道默认欢迎语
                return (
                  <div className={'sub-from-item'} style={{marginBottom: 32, marginLeft: 26}}>
                    <Text type={'secondary'}>
                      将发送成员已设置的欢迎语,若所选成员未设置欢迎语,则不会发送欢迎语
                    </Text>
                  </div>
                );
              }
              if (auto_reply_type === 3) {
                // 不送欢迎语
                return <div className={'sub-from-item'}></div>;
              }
              return '';
            }}
          </ProFormDependency>

          <ProFormSwitch
            label={'欢迎语屏蔽:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='nickname_block_enable'
            tooltip={<>开启后,客户昵称中包含设定的关键词的客户不会收到欢迎语</>}
          />

          <ProFormDependency name={['nickname_block_enable']}>
            {({nickname_block_enable}) => {
              if (nickname_block_enable) {
                return (
                  <div className={'sub-from-item'} style={{marginLeft: 26, marginTop:-20}}>
                    <EditableTag tags={blockNicknames} setTags={setBlockNicknames}/>
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>

          <h3 style={{marginTop: 30}}>功能设置</h3>
          <Divider/>

          <ProFormSwitch
            label={'自动通过好友:'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='skip_verify'
            tooltip={<>开启后,客户添加该企业微信时,无需好友验证,将会自动添加成功</>}
          />

          <ProFormDependency name={['skip_verify']}>
            {({skip_verify}) => {
              if (skip_verify) {
                return (
                  <div className={'sub-from-item'}>
                    <ProFormRadio.Group
                      // 欢迎语类型:1,渠道欢迎语;2, 渠道默认欢迎语;3,不送欢迎语;
                      name='auto_skip_verify_enable'
                      label='自动通过时段'
                      options={[
                        {
                          label: '全天开启',
                          value: 2,
                        },
                        {
                          label: '选择时间段',
                          value: 1,
                        },
                      ]}
                      rules={[
                        {
                          required: true,
                          message: '请选择时段控制',
                        },
                      ]}
                    />

                    <ProFormDependency name={['auto_skip_verify_enable']}>
                      {({auto_skip_verify_enable}) => {
                        if (auto_skip_verify_enable === 1) {
                          return (
                            <Row className={'sub-from-item'}>
                              <span style={{marginTop: 6, marginLeft: 36}}>工作时间:</span>
                              <ProFormTimePicker
                                width={'xs'}
                                name='skip_verify_start_time'
                                fieldProps={{
                                  format: TimeLayout,
                                }}
                                placeholder='开始时间'
                                rules={[{required: true, message: '请选择开始时间'}]}
                              />
                              <Space style={{marginLeft: 6}}> </Space>
                              <ProFormTimePicker
                                width={'xs'}
                                name='skip_verify_end_time'
                                fieldProps={{
                                  format: TimeLayout,
                                }}
                                placeholder='结束时间'
                                rules={[{required: true, message: '请选择结束时间'}]}
                              />
                            </Row>
                          );
                        }
                        return '';
                      }}
                    </ProFormDependency>
                  </div>
                );
              }
              return '';
            }}
          </ProFormDependency>
        </div>
      </ProForm>

      <StaffTreeSelectionModal
        visible={staffSelectionVisible}
        setVisible={setStaffSelectionVisible}
        defaultCheckedStaffs={selectedStaffs}
        onFinish={(values) => {
          setSelectedStaffs(values);
        }}
        allStaffs={staffs}
      />

      <StaffTreeSelectionModal
        visible={backupStaffSelectionVisible}
        setVisible={setBackupStaffSelectionVisible}
        defaultCheckedStaffs={backupSelectedStaffs}
        onFinish={(values: React.SetStateAction<any[]>) => {
          setBackupSelectedStaffs(values);
        }}
        allStaffs={staffs}
      />
    </>
  );
}
Example #20
Source File: index.tsx    From ql with MIT License 4 votes vote down vote up
export default function (props: any) {
  const logout = () => {
    request.post(`${config.apiPrefix}logout`).then(() => {
      localStorage.removeItem(config.authKey);
      history.push('/login');
    });
  };

  useEffect(() => {
    const isAuth = localStorage.getItem(config.authKey);
    if (!isAuth) {
      history.push('/login');
    }
    vhCheck();
  }, []);

  useEffect(() => {
    if (props.location.pathname === '/') {
      history.push('/crontab');
    }
  }, [props.location.pathname]);

  useEffect(() => {
    const theme = localStorage.getItem('qinglong_dark_theme') || 'auto';
    setFetchMethod(window.fetch);
    if (theme === 'dark') {
      enableDarkMode({
        brightness: 100,
        contrast: 90,
        sepia: 10,
      });
    } else if (theme === 'light') {
      disableDarkMode();
    } else {
      followSystemColorScheme({
        brightness: 100,
        contrast: 90,
        sepia: 10,
      });
    }
  }, []);

  if (props.location.pathname === '/login') {
    return props.children;
  }

  const isFirefox = navigator.userAgent.includes('Firefox');
  const isSafari =
    navigator.userAgent.includes('Safari') &&
    !navigator.userAgent.includes('Chrome');
  return (
    <ProLayout
      selectedKeys={[props.location.pathname]}
      title={
        <>
          控制面板
          <a href={changeLog} target="_blank" rel="noopener noreferrer">
            <span
              style={{
                fontSize: isFirefox ? 9 : 12,
                color: '#666',
                marginLeft: 5,
                zoom: isSafari ? 0.66 : 0.8,
              }}
            >
              {version}
            </span>
          </a>
        </>
      }
      menuItemRender={(menuItemProps: any, defaultDom: any) => {
        if (
          menuItemProps.isUrl ||
          !menuItemProps.path ||
          location.pathname === menuItemProps.path
        ) {
          return defaultDom;
        }
        return <Link to={menuItemProps.path}>{defaultDom}</Link>;
      }}
      postMenuData={(menuData) => {
        return [
          ...(menuData || []),
          {
            icon: <LogoutOutlined />,
            name: '退出登录',
            path: 'logout',
            onTitleClick: () => logout(),
          },
        ];
      }}
      pageTitleRender={() => '控制面板'}
      {...defaultProps}
    >
      {props.children}
    </ProLayout>
  );
}
Example #21
Source File: index.tsx    From ant-design-pro-V5-multitab with MIT License 4 votes vote down vote up
Login: React.FC<{}> = () => {
    const [submitting, setSubmitting] = useState(false);
    const [userLoginState, setUserLoginState] = useState<API.LoginStateType>({});
    const [type, setType] = useState<string>('account');
    const { initialState, setInitialState } = useModel('@@initialState');

    const intl = useIntl();

    const fetchUserInfo = async () => {
        const userInfo = await initialState?.fetchUserInfo?.();
        if (userInfo) {
            setInitialState({
                ...initialState,
                currentUser: userInfo,
            });
        }
    };

    const handleSubmit = async (values: LoginParamsType) => {
        setSubmitting(true);
        try {
            // 登录
            const msg = await fakeAccountLogin({ ...values, type });
            if (msg.status === 'ok') {
                message.success('登录成功!');
                await fetchUserInfo();
                goto();
                return;
            }
            // 如果失败去设置用户错误信息
            setUserLoginState(msg);
        } catch (error) {
            message.error('登录失败,请重试!');
        }
        setSubmitting(false);
    };
    const { status, type: loginType } = userLoginState;

    return (
        <div className={styles.container}>
            <div className={styles.lang}>{SelectLang && <SelectLang />}</div>
            <div className={styles.content}>
                <div className={styles.top}>
                    <div className={styles.header}>
                        <Link to="/">
                            <img alt="logo" className={styles.logo} src="/logo.svg" />
                            <span className={styles.title}>Ant Design</span>
                        </Link>
                    </div>
                    <div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
                </div>

                <div className={styles.main}>
                    <ProForm
                        initialValues={{
                            autoLogin: true,
                        }}
                        submitter={{
                            searchConfig: {
                                submitText: intl.formatMessage({
                                    id: 'pages.login.submit',
                                    defaultMessage: '登录',
                                }),
                            },
                            render: (_, dom) => dom.pop(),
                            submitButtonProps: {
                                loading: submitting,
                                size: 'large',
                                style: {
                                    width: '100%',
                                },
                            },
                        }}
                        onFinish={async (values) => {
                            handleSubmit(values);
                        }}
                    >
                        <Tabs activeKey={type} onChange={setType}>
                            <Tabs.TabPane
                                key="account"
                                tab={intl.formatMessage({
                                    id: 'pages.login.accountLogin.tab',
                                    defaultMessage: '账户密码登录',
                                })}
                            />
                            <Tabs.TabPane
                                key="mobile"
                                tab={intl.formatMessage({
                                    id: 'pages.login.phoneLogin.tab',
                                    defaultMessage: '手机号登录',
                                })}
                            />
                        </Tabs>

                        {status === 'error' && loginType === 'account' && (
                            <LoginMessage
                                content={intl.formatMessage({
                                    id: 'pages.login.accountLogin.errorMessage',
                                    defaultMessage: '账户或密码错误(admin/ant.design)',
                                })}
                            />
                        )}
                        {type === 'account' && (
                            <>
                                <ProFormText
                                    name="username"
                                    fieldProps={{
                                        size: 'large',
                                        prefix: <UserOutlined className={styles.prefixIcon} />,
                                    }}
                                    placeholder={intl.formatMessage({
                                        id: 'pages.login.username.placeholder',
                                        defaultMessage: '用户名: admin or user',
                                    })}
                                    rules={[
                                        {
                                            required: true,
                                            message: (
                                                <FormattedMessage
                                                    id="pages.login.username.required"
                                                    defaultMessage="请输入用户名!"
                                                />
                                            ),
                                        },
                                    ]}
                                />
                                <ProFormText.Password
                                    name="password"
                                    fieldProps={{
                                        size: 'large',
                                        prefix: <LockTwoTone className={styles.prefixIcon} />,
                                    }}
                                    placeholder={intl.formatMessage({
                                        id: 'pages.login.password.placeholder',
                                        defaultMessage: '密码: ant.design',
                                    })}
                                    rules={[
                                        {
                                            required: true,
                                            message: (
                                                <FormattedMessage
                                                    id="pages.login.password.required"
                                                    defaultMessage="请输入密码!"
                                                />
                                            ),
                                        },
                                    ]}
                                />
                            </>
                        )}

                        {status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
                        {type === 'mobile' && (
                            <>
                                <ProFormText
                                    fieldProps={{
                                        size: 'large',
                                        prefix: <MobileTwoTone className={styles.prefixIcon} />,
                                    }}
                                    name="mobile"
                                    placeholder={intl.formatMessage({
                                        id: 'pages.login.phoneNumber.placeholder',
                                        defaultMessage: '手机号',
                                    })}
                                    rules={[
                                        {
                                            required: true,
                                            message: (
                                                <FormattedMessage
                                                    id="pages.login.phoneNumber.required"
                                                    defaultMessage="请输入手机号!"
                                                />
                                            ),
                                        },
                                        {
                                            pattern: /^1\d{10}$/,
                                            message: (
                                                <FormattedMessage
                                                    id="pages.login.phoneNumber.invalid"
                                                    defaultMessage="手机号格式错误!"
                                                />
                                            ),
                                        },
                                    ]}
                                />
                                <ProFormCaptcha
                                    fieldProps={{
                                        size: 'large',
                                        prefix: <MailTwoTone className={styles.prefixIcon} />,
                                    }}
                                    captchaProps={{
                                        size: 'large',
                                    }}
                                    placeholder={intl.formatMessage({
                                        id: 'pages.login.captcha.placeholder',
                                        defaultMessage: '请输入验证码',
                                    })}
                                    captchaTextRender={(timing, count) =>
                                        timing
                                            ? `${count} ${intl.formatMessage({
                                                id: 'pages.getCaptchaSecondText',
                                                defaultMessage: '获取验证码',
                                            })}`
                                            : intl.formatMessage({
                                                id: 'pages.login.phoneLogin.getVerificationCode',
                                                defaultMessage: '获取验证码',
                                            })
                                    }
                                    name="captcha"
                                    rules={[
                                        {
                                            required: true,
                                            message: (
                                                <FormattedMessage
                                                    id="pages.login.captcha.required"
                                                    defaultMessage="请输入验证码!"
                                                />
                                            ),
                                        },
                                    ]}
                                    onGetCaptcha={async (mobile) => {
                                        const result = await getFakeCaptcha(mobile);
                                        if (result === false) {
                                            return;
                                        }
                                        message.success('获取验证码成功!验证码为:1234');
                                    }}
                                />
                            </>
                        )}
                        <div
                            style={{
                                marginBottom: 24,
                            }}
                        >
                            <ProFormCheckbox noStyle name="autoLogin">
                                <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
                            </ProFormCheckbox>
                            <a
                                style={{
                                    float: 'right',
                                }}
                            >
                                <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
                            </a>
                        </div>
                    </ProForm>
                    <Space className={styles.other}>
                        <FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
                        <AlipayCircleOutlined className={styles.icon} />
                        <TaobaoCircleOutlined className={styles.icon} />
                        <WeiboCircleOutlined className={styles.icon} />
                    </Space>
                </div>
            </div>
            <Footer />
        </div>
    );
}
Example #22
Source File: index.tsx    From anew-server with MIT License 4 votes vote down vote up
Login: React.FC = () => {
  const [submitting, setSubmitting] = useState(false);
  const { initialState, setInitialState } = useModel('@@initialState');

  const fetchUserInfo = async () => {
    const userInfo = await initialState?.fetchUserInfo?.();

    if (userInfo) {
      setInitialState({ ...initialState, currentUser: userInfo });
    }
  };

  const handleSubmit = async (values: API.LoginParams) => {
    setSubmitting(true);

    try {
      // 登录
      const login = await AuthLogin(values);

      if (login.status) {
        const defaultloginSuccessMessage = '登录成功';
        message.success(defaultloginSuccessMessage);
        localStorage.setItem('token', login.data.token);
        localStorage.setItem('expires', login.data.expires);
        await fetchUserInfo();
        goto();
        return;
      } // 如果失败去设置用户错误信息
    } catch (error) {
      // 全局状态已处理,这里返回
      return
    }

    setSubmitting(false);
  };

  return (
    <div className={styles.container}>
      <div className={styles.content}>
        <div className={styles.top}>
          <div className={styles.header}>
            <Link to="/">
              <img alt="logo" className={styles.logo} src="/logo.svg" />
              <span className={styles.title}>Anew-Server</span>
            </Link>
          </div>
          <div className={styles.desc}>{'运维平台'}</div>
        </div>

        <div className={styles.main}>
          <ProForm
            initialValues={{
              autoLogin: true,
            }}
            submitter={{
              searchConfig: {
                submitText: '登录',
              },
              render: (_, dom) => dom.pop(),
              submitButtonProps: {
                loading: submitting,
                size: 'large',
                style: {
                  width: '100%',
                },
              },
            }}
            onFinish={async (values) => {
              handleSubmit(values as API.LoginParams);
            }}
          >
            <>
              <ProFormText
                name="username"
                fieldProps={{
                  size: 'large',
                  prefix: <UserOutlined className={styles.prefixIcon} />,
                }}
                placeholder={'用户名: admin or user'}
                rules={[
                  {
                    required: true,
                    message: '用户名是必填项!',
                  },
                ]}
              />
              <ProFormText.Password
                name="password"
                fieldProps={{
                  size: 'large',
                  prefix: <LockOutlined className={styles.prefixIcon} />,
                }}
                placeholder={'密码: ant.design'}
                rules={[
                  {
                    required: true,
                    message: '密码是必填项!',
                  },
                ]}
              />
            </>
          </ProForm>
        </div>
      </div>
      <Footer />
    </div>
  );
}