瀏覽代碼

feat(Tenant): Tenant Manager

Lind 4 年之前
父節點
當前提交
2de1610992

+ 7 - 0
config/routes.ts

@@ -70,6 +70,13 @@
         component: './system/Tenant',
       },
       {
+        hideInMenu: true,
+        path: '/system/tenant/detail/:id',
+        name: 'tenant-detail',
+        icon: 'smile',
+        component: './system/Tenant/Detail',
+      },
+      {
         path: '/system/datasource',
         name: 'datasource',
         icon: 'smile',

+ 7 - 7
package.json

@@ -61,13 +61,13 @@
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-layout": "^6.15.3",
     "@dabeng/react-orgchart": "^1.0.0",
-    "@formily/antd": "2.0.0-beta.84",
-    "@formily/core": "2.0.0-beta.84",
-    "@formily/json-schema": "2.0.0-beta.84",
-    "@formily/react": "2.0.0-beta.84",
-    "@formily/reactive": "2.0.0-beta.84",
-    "@formily/reactive-react": "2.0.0-beta.84",
-    "@formily/shared": "2.0.0-beta.84",
+    "@formily/antd": "2.0.0-rc.14",
+    "@formily/core": "2.0.0-rc.14",
+    "@formily/json-schema": "2.0.0-rc.14",
+    "@formily/react": "2.0.0-rc.14",
+    "@formily/reactive": "2.0.0-rc.14",
+    "@formily/reactive-react": "2.0.0-rc.14",
+    "@formily/shared": "2.0.0-rc.14",
     "@jetlinks/pro-list": "^1.10.8",
     "@jetlinks/pro-table": "^2.43.7",
     "@umijs/route-utils": "^1.0.36",

+ 2 - 0
src/components/BaseCrud/save/index.tsx

@@ -26,6 +26,7 @@ import SystemConst from '@/utils/const';
 import { CurdModel } from '@/components/BaseCrud/model';
 import type { ISchemaFieldProps } from '@formily/react/lib/types';
 import type { ModalProps } from 'antd/lib/modal/Modal';
+import FUpload from '@/components/Upload';
 
 interface Props<T> {
   schema: ISchema;
@@ -72,6 +73,7 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
       FormGrid,
       Editable,
       NumberPicker,
+      FUpload,
     },
     scope: {
       icon(name: any) {

+ 202 - 0
src/components/Upload/Upload.tsx

@@ -0,0 +1,202 @@
+import React, { useEffect } from 'react';
+import type { Field } from '@formily/core';
+import { connect, mapProps, useField } from '@formily/react';
+import { Upload as AntdUpload, Button } from 'antd';
+import type {
+  UploadChangeParam,
+  UploadProps as AntdUploadProps,
+  DraggerProps as AntdDraggerProps,
+} from 'antd/lib/upload';
+import { InboxOutlined, UploadOutlined } from '@ant-design/icons';
+import { reaction } from '@formily/reactive';
+import type { UploadFile } from 'antd/lib/upload/interface';
+import { isArr, toArr } from '@formily/shared';
+import { UPLOAD_PLACEHOLDER } from './placeholder';
+import { usePrefixCls } from '@formily/antd/lib/__builtins__';
+
+type UploadProps = Omit<AntdUploadProps, 'onChange'> & {
+  textContent?: React.ReactNode;
+  onChange?: (fileList: UploadFile[]) => void;
+  serviceErrorMessage?: string;
+};
+
+type DraggerProps = Omit<AntdDraggerProps, 'onChange'> & {
+  textContent?: React.ReactNode;
+  onChange?: (fileList: UploadFile[]) => void;
+  serviceErrorMessage?: string;
+};
+
+type ComposedUpload = React.FC<UploadProps> & {
+  Dragger?: React.FC<DraggerProps>;
+};
+
+type IUploadProps = {
+  serviceErrorMessage?: string;
+  onChange?: (...args: any) => void;
+};
+
+const testOpts = (ext: RegExp, options: { exclude?: string[]; include?: string[] }) => {
+  if (options && isArr(options.include)) {
+    return options.include.some((url) => ext.test(url));
+  }
+
+  if (options && isArr(options.exclude)) {
+    return !options.exclude.some((url) => ext.test(url));
+  }
+
+  return true;
+};
+
+const getImageByUrl = (url: string, options: any) => {
+  for (let i = 0; i < UPLOAD_PLACEHOLDER.length; i += 1) {
+    if (UPLOAD_PLACEHOLDER[i].ext.test(url) && testOpts(UPLOAD_PLACEHOLDER[i].ext, options)) {
+      return UPLOAD_PLACEHOLDER[i].icon || url;
+    }
+  }
+
+  return url;
+};
+
+const getURL = (target: any) => {
+  return target?.['result'] || target?.['url'] || target?.['downloadURL'] || target?.['imgURL'];
+};
+const getThumbURL = (target: any) => {
+  return (
+    target?.['result'] ||
+    target?.['thumbUrl'] ||
+    target?.['url'] ||
+    target?.['downloadURL'] ||
+    target?.['imgURL']
+  );
+};
+
+const getErrorMessage = (target: any) => {
+  return target?.errorMessage ||
+    target?.errMsg ||
+    target?.errorMsg ||
+    target?.message ||
+    typeof target?.error === 'string'
+    ? target.error
+    : '';
+};
+
+const getState = (target: any) => {
+  if (target?.success === false) return 'error';
+  if (target?.failed === true) return 'error';
+  if (target?.error) return 'error';
+  return target?.state || target?.status;
+};
+
+const normalizeFileList = (fileList: UploadFile[]) => {
+  if (fileList && fileList.length) {
+    return fileList.map((file, index) => {
+      return {
+        ...file,
+        uid: file.uid || `${index}`,
+        status: getState(file.response) || getState(file),
+        url: getURL(file) || getURL(file?.response),
+        thumbUrl: getImageByUrl(getThumbURL(file) || getThumbURL(file?.response), {
+          exclude: ['.png', '.jpg', '.jpeg', '.gif'],
+        }),
+      };
+    });
+  }
+  return [];
+};
+
+const useValidator = (validator: (value: any) => string) => {
+  const field = useField<Field>();
+  useEffect(() => {
+    const dispose = reaction(
+      () => field.value,
+      (value) => {
+        const message = validator(value);
+        field.setFeedback({
+          type: 'error',
+          code: 'UploadError',
+          messages: message ? [message] : [],
+        });
+      },
+    );
+    return () => {
+      dispose();
+    };
+  }, []);
+};
+
+const useUploadValidator = (serviceErrorMessage = 'Upload Service Error') => {
+  // eslint-disable-next-line consistent-return
+  useValidator((value) => {
+    const list = toArr(value);
+    for (let i = 0; i < list.length; i += 1) {
+      if (list[i]?.status === 'error') {
+        return (
+          getErrorMessage(list[i]?.response) || getErrorMessage(list[i]) || serviceErrorMessage
+        );
+      }
+    }
+  });
+};
+
+function useUploadProps<T extends IUploadProps = UploadProps>({
+  serviceErrorMessage,
+  ...props
+}: T) {
+  useUploadValidator(serviceErrorMessage);
+  const onChange = (param: UploadChangeParam<UploadFile>) => {
+    props.onChange?.(normalizeFileList([...param.fileList]));
+  };
+  return {
+    ...props,
+    onChange,
+  };
+}
+
+const getPlaceholder = (props: UploadProps) => {
+  if (props.listType !== 'picture-card') {
+    return (
+      <Button>
+        <UploadOutlined />
+        {props.textContent}
+      </Button>
+    );
+  }
+  return <UploadOutlined style={{ fontSize: 20 }} />;
+};
+
+export const Upload: ComposedUpload = connect(
+  (props: React.PropsWithChildren<UploadProps>) => {
+    return (
+      <AntdUpload {...useUploadProps(props)}>{props.children || getPlaceholder(props)}</AntdUpload>
+    );
+  },
+  mapProps({
+    value: 'fileList',
+  }),
+);
+
+const Dragger = connect(
+  (props: React.PropsWithChildren<DraggerProps>) => {
+    return (
+      <div className={usePrefixCls('upload-dragger')}>
+        <AntdUpload.Dragger {...useUploadProps(props)}>
+          {props.children || (
+            <React.Fragment>
+              <p className="ant-upload-drag-icon">
+                <InboxOutlined />
+              </p>
+              {props.textContent && <p className="ant-upload-text">{props.textContent}</p>}
+            </React.Fragment>
+          )}
+        </AntdUpload.Dragger>
+      </div>
+    );
+  },
+  mapProps({
+    value: 'fileList',
+  }),
+);
+
+Upload.Dragger = Dragger;
+
+export default Upload;

+ 20 - 0
src/components/Upload/index.tsx

@@ -0,0 +1,20 @@
+import { Button } from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
+import SystemConst from '@/utils/const';
+import Token from '@/utils/token';
+import Upload from '@/components/Upload/Upload';
+
+const FUpload = (props: any) => {
+  return (
+    <Upload
+      {...props}
+      action={`/${SystemConst.API_BASE}/file/static`}
+      headers={{
+        'X-Access-Token': Token.get(),
+      }}
+    >
+      <Button icon={<UploadOutlined />}>{props.title}</Button>
+    </Upload>
+  );
+};
+export default FUpload;

+ 62 - 0
src/components/Upload/placeholder.ts

@@ -0,0 +1,62 @@
+export const UPLOAD_PLACEHOLDER = [
+  {
+    ext: /\.docx?$/i,
+    icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png',
+  },
+  {
+    ext: /\.pptx?$/i,
+    icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png',
+  },
+  {
+    ext: /\.jpe?g$/i,
+    icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
+  },
+  {
+    ext: /\.pdf$/i,
+    icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png',
+  },
+  {
+    ext: /\.png$/i,
+    icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
+  },
+  {
+    ext: /\.eps$/i,
+    icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png',
+  },
+  {
+    ext: /\.ai$/i,
+    icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png',
+  },
+  {
+    ext: /\.gif$/i,
+    icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png',
+  },
+  {
+    ext: /\.svg$/i,
+    icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png',
+  },
+  {
+    ext: /\.xlsx?$/i,
+    icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png',
+  },
+  {
+    ext: /\.psd?$/i,
+    icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png',
+  },
+  {
+    ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)$/i,
+    icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png',
+  },
+  {
+    ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)$/i,
+    icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
+  },
+  {
+    ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)$/i,
+    icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png',
+  },
+  {
+    ext: /\.[^.]+$/i,
+    icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png',
+  },
+];

+ 1 - 0
src/components/Upload/style.ts

@@ -0,0 +1 @@
+import 'antd/lib/upload/style/index';

+ 48 - 0
src/pages/system/Tenant/Detail/Assets/index.tsx

@@ -0,0 +1,48 @@
+import ProCard from '@ant-design/pro-card';
+import { EditOutlined, EyeOutlined } from '@ant-design/icons';
+import { Card, Form, Row, Select, Statistic } from 'antd';
+import { Col } from 'antd';
+
+const Assets = () => {
+  return (
+    <Card>
+      <Form.Item label="成员" style={{ width: 200 }}>
+        <Select />
+      </Form.Item>
+      <ProCard gutter={[16, 16]} style={{ marginTop: 16 }}>
+        <ProCard
+          title="产品"
+          colSpan="25%"
+          bordered
+          actions={[<EyeOutlined key="setting" />, <EditOutlined key="edit" />]}
+        >
+          <Row>
+            <Col span={12}>
+              <Statistic title="已发布" value={20} />
+            </Col>
+            <Col span={12}>
+              <Statistic title="未发布" value={19} />
+            </Col>
+          </Row>
+        </ProCard>
+
+        <ProCard
+          title="设备"
+          colSpan="25%"
+          bordered
+          actions={[<EyeOutlined key="setting" />, <EditOutlined key="edit" />]}
+        >
+          <Row>
+            <Col span={12}>
+              <Statistic title="已发布" value={20} />
+            </Col>
+            <Col span={12}>
+              <Statistic title="未发布" value={19} />
+            </Col>
+          </Row>
+        </ProCard>
+      </ProCard>
+    </Card>
+  );
+};
+export default Assets;

+ 15 - 0
src/pages/system/Tenant/Detail/Info/index.tsx

@@ -0,0 +1,15 @@
+import { Descriptions } from 'antd';
+import TenantModel from '@/pages/system/Tenant/model';
+
+const Info = () => {
+  return (
+    <div>
+      <Descriptions size="small" column={3}>
+        <Descriptions.Item label="ID">{TenantModel.detail?.id}</Descriptions.Item>
+        <Descriptions.Item label="名称">{TenantModel.detail?.name}</Descriptions.Item>
+        <Descriptions.Item label="状态">{TenantModel.detail?.state?.text}</Descriptions.Item>
+      </Descriptions>
+    </div>
+  );
+};
+export default Info;

+ 71 - 0
src/pages/system/Tenant/Detail/Member/index.tsx

@@ -0,0 +1,71 @@
+import type { ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Tooltip } from 'antd';
+import { EyeOutlined, UnlockFilled } from '@ant-design/icons';
+import type { TenantMember } from '@/pages/system/Tenant/typings';
+import { service } from '@/pages/system/Tenant';
+import { useParams } from 'umi';
+
+const Member = () => {
+  const param = useParams<{ id: string }>();
+  const columns: ProColumns<TenantMember>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      dataIndex: 'name',
+      title: '姓名',
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      title: '管理员',
+      dataIndex: 'adminMember',
+      renderText: (text) => (text ? '是' : '否'),
+      search: false,
+    },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      renderText: (text) => text.text,
+      search: false,
+    },
+    {
+      title: '操作',
+      valueType: 'option',
+      render: (text, record) => [
+        <a
+          key="edit"
+          onClick={() => {
+            console.log(JSON.stringify(record));
+          }}
+        >
+          <Tooltip title="查看资产">
+            <EyeOutlined />
+          </Tooltip>
+        </a>,
+        <a
+          key="bind"
+          onClick={() => {
+            console.log(JSON.stringify(record));
+          }}
+        >
+          <Tooltip title="解绑">
+            <UnlockFilled />
+          </Tooltip>
+        </a>,
+      ],
+    },
+  ];
+  return (
+    <ProTable
+      columns={columns}
+      rowKey="id"
+      request={(params) => service.queryMembers(param.id, params)}
+    />
+  );
+};
+export default Member;

+ 4 - 0
src/pages/system/Tenant/Detail/Permission/index.tsx

@@ -0,0 +1,4 @@
+const Permission = () => {
+  return <div>权限管理</div>;
+};
+export default Permission;

+ 59 - 0
src/pages/system/Tenant/Detail/index.tsx

@@ -0,0 +1,59 @@
+import { observer } from '@formily/react';
+import { PageContainer } from '@ant-design/pro-layout';
+import { useEffect, useState } from 'react';
+import { history, useParams } from 'umi';
+import TenantModel from '@/pages/system/Tenant/model';
+import { service } from '@/pages/system/Tenant';
+import Assets from '@/pages/system/Tenant/Detail/Assets';
+import Member from '@/pages/system/Tenant/Detail/Member';
+import Info from '@/pages/system/Tenant/Detail/Info';
+import Permission from './Permission';
+
+const TenantDetail = observer(() => {
+  const [tab, setTab] = useState<string>('assets');
+  const params = useParams<{ id: string }>();
+  const getDetail = (id: string) => {
+    service.queryDetail(id).subscribe((data) => {
+      TenantModel.detail = data;
+    });
+  };
+
+  useEffect(() => {
+    const { id } = params;
+    if (id) {
+      getDetail(id);
+    } else {
+      history.goBack();
+    }
+  }, [params.id]);
+
+  const list = [
+    // {
+    //   key: 'detail',
+    //   tab: '基本信息',
+    //   component: <Info/>
+    // },
+    {
+      key: 'assets',
+      tab: '资产信息',
+      component: <Assets />,
+    },
+    {
+      key: 'member',
+      tab: '成员管理',
+      component: <Member />,
+    },
+    {
+      key: 'permission',
+      tab: '权限管理',
+      component: <Permission />,
+    },
+  ];
+
+  return (
+    <PageContainer onBack={history.goBack} tabList={list} onTabChange={setTab} content={<Info />}>
+      {list.find((k) => k.key === tab)?.component}
+    </PageContainer>
+  );
+});
+export default TenantDetail;

+ 107 - 19
src/pages/system/Tenant/index.tsx

@@ -4,25 +4,22 @@ import type { TenantDetail } from '@/pages/system/Tenant/typings';
 import type { TenantItem } from '@/pages/system/Tenant/typings';
 import BaseCrud from '@/components/BaseCrud';
 import { useRef } from 'react';
-import { Avatar, Menu, message, Popconfirm, Tooltip } from 'antd';
+import { Avatar, message, Popconfirm, Tooltip } from 'antd';
 import Service from '@/pages/system/Tenant/service';
-import { CurdModel } from '@/components/BaseCrud/model';
 import {
   CloseCircleOutlined,
-  EditOutlined,
+  EyeOutlined,
   KeyOutlined,
   PlayCircleOutlined,
 } from '@ant-design/icons';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import moment from 'moment';
+import { Link } from 'umi';
+import TenantModel from '@/pages/system/Tenant/model';
+import type { ISchema } from '@formily/json-schema';
+
+export const service = new Service('tenant');
 
-const menu = (
-  <Menu>
-    <Menu.Item key="1">1st item</Menu.Item>
-    <Menu.Item key="2">2nd item</Menu.Item>
-    <Menu.Item key="3">3rd item</Menu.Item>
-  </Menu>
-);
-const service = new Service('tenant');
 const Tenant = () => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
@@ -50,6 +47,9 @@ const Tenant = () => {
       }),
       align: 'center',
       renderText: (text: TenantDetail) => text.name,
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
     },
     {
       dataIndex: 'members',
@@ -58,6 +58,7 @@ const Tenant = () => {
         defaultMessage: '成员数',
       }),
       align: 'center',
+      search: false,
     },
     {
       dataIndex: 'tenant',
@@ -70,6 +71,7 @@ const Tenant = () => {
       valueType: 'select',
       hideInForm: true,
       onFilter: true,
+      search: false,
       valueEnum: [
         {
           text: intl.formatMessage({
@@ -95,6 +97,16 @@ const Tenant = () => {
       ],
     },
     {
+      title: '创建时间',
+      dataIndex: 'tenant',
+      width: '200px',
+      align: 'center',
+      renderText: (record: TenantDetail) => moment(record.createTime).format('YYYY-MM-DD HH:mm:ss'),
+      sorter: true,
+      search: false,
+      defaultSortOrder: 'descend',
+    },
+    {
       title: intl.formatMessage({
         id: 'pages.data.option',
         defaultMessage: '操作',
@@ -103,16 +115,24 @@ const Tenant = () => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a key="editable" onClick={() => CurdModel.update(record)}>
+        <Link
+          onClick={() => {
+            TenantModel.current = record;
+          }}
+          to={`/system/tenant/detail/${record.tenant.id}`}
+          key="link"
+        >
           <Tooltip
             title={intl.formatMessage({
-              id: 'pages.data.option.edit',
-              defaultMessage: '编辑',
+              id: 'pages.data.option.detail',
+              defaultMessage: '查看',
             })}
+            key={'detail'}
           >
-            <EditOutlined />
+            <EyeOutlined />
           </Tooltip>
-        </a>,
+        </Link>,
+
         <a onClick={() => console.log('授权')}>
           <Tooltip
             title={intl.formatMessage({
@@ -159,18 +179,86 @@ const Tenant = () => {
     },
   ];
 
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      name: {
+        type: 'string',
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      username: {
+        type: 'string',
+        title: '用户名',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      password: {
+        type: 'string',
+        title: '密码',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+        },
+        'x-reactions': [
+          {
+            dependencies: ['.confirmPassword'],
+            fulfill: {
+              state: {
+                selfErrors:
+                  '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
+              },
+            },
+          },
+        ],
+      },
+      confirmPassword: {
+        type: 'string',
+        title: '确认密码',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+        },
+        'x-reactions': [
+          {
+            dependencies: ['.password'],
+            fulfill: {
+              state: {
+                selfErrors:
+                  '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
+              },
+            },
+          },
+        ],
+      },
+      description: {
+        type: 'string',
+        title: '备注',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input.TextArea',
+      },
+    },
+  };
+
   return (
     <PageContainer>
       <BaseCrud<TenantItem>
-        request={(params = {}) => service.queryDetail(params)}
+        request={(params = {}) => service.queryList(params)}
         columns={columns}
         service={service}
         title={intl.formatMessage({
           id: 'pages.system.tenant.list',
           defaultMessage: '租户列表',
         })}
-        schema={{}}
-        menu={menu}
+        schema={schema}
         actionRef={actionRef}
       />
     </PageContainer>

+ 13 - 0
src/pages/system/Tenant/model.ts

@@ -0,0 +1,13 @@
+import { model } from '@formily/reactive';
+import type { TenantDetail } from '@/pages/system/Tenant/typings';
+
+type TenantModelType = {
+  current: Partial<TenantDetail>;
+  detail: Partial<TenantDetail>;
+};
+const TenantModel = model<TenantModelType>({
+  current: {},
+  detail: {},
+});
+
+export default TenantModel;

+ 21 - 3
src/pages/system/Tenant/service.ts

@@ -1,14 +1,32 @@
 import type { TenantItem } from '@/pages/system/Tenant/typings';
 import BaseService from '@/utils/BaseService';
 import { request } from 'umi';
+import { defer, from } from 'rxjs';
+import { filter, map } from 'rxjs/operators';
 
 class Service extends BaseService<TenantItem> {
-  queryDetail = (params: any) => {
-    return request(`/jetlinks/tenant/detail/_query`, {
+  queryList = (params: any) =>
+    request(`${this.uri}/detail/_query`, {
       method: 'GET',
       params,
     });
-  };
+
+  update = (data: any) => request(`${this.uri}/_create`, { data, method: 'POST' });
+
+  queryDetail = (id: string) =>
+    defer(() =>
+      from(
+        request(`${this.uri}/${id}`, {
+          method: 'GET',
+        }),
+      ),
+    ).pipe(
+      filter((item) => item.status === 200),
+      map((item) => item.result),
+    );
+
+  queryMembers = (id: string, params: Record<string, unknown>) =>
+    request(`${this.uri}/${id}/members/_query`, { method: 'GET', params });
 }
 
 export default Service;

+ 15 - 1
src/pages/system/Tenant/typings.d.ts

@@ -1,8 +1,10 @@
+import type { State } from '@/utils/typings';
+
 export type TenantDetail = {
   id: string;
   name: string;
   type: string;
-  state: any;
+  state: State;
   members: number;
   photo: string;
   createTime: number;
@@ -13,3 +15,15 @@ export type TenantItem = {
   members: number;
   tenant: Partial<TenantDetail>;
 };
+
+export type TenantMember = {
+  id: string;
+  adminMember: boolean;
+  createTime: number;
+  mainTenant: true;
+  name: string;
+  state: State;
+  tenantId: string;
+  type: string;
+  userId: string;
+};

+ 1 - 1
src/utils/token.ts

@@ -1,5 +1,5 @@
 const Token = {
   set: (token: string) => localStorage.setItem('X-Access-Token', token),
-  get: () => localStorage.getItem('X-Access-Token'),
+  get: () => localStorage.getItem('X-Access-Token') || Date.now().toString(),
 };
 export default Token;