소스 검색

使用nice-hooks

tianyunperfect 5 년 전
부모
커밋
6a68fb5824

+ 17 - 7
config/config.ts

@@ -3,17 +3,18 @@ import { defineConfig, utils } from 'umi';
 import defaultSettings from './defaultSettings';
 import proxy from './proxy';
 import webpackPlugin from './plugin.config';
-
-const { winPath } = utils;
-
-// preview.pro.ant.design only do not use in your production ;
+const { winPath } = utils; // preview.pro.ant.design only do not use in your production ;
 // preview.pro.ant.design 专用环境变量,请不要在你的项目中使用它。
-const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION, REACT_APP_ENV, GA_KEY } = process.env;
 
+const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION, REACT_APP_ENV, GA_KEY } = process.env;
 export default defineConfig({
   hash: true,
   antd: {},
-  analytics: GA_KEY ? { ga: GA_KEY } : false,
+  analytics: GA_KEY
+    ? {
+        ga: GA_KEY,
+      }
+    : false,
   dva: {
     hmr: true,
   },
@@ -84,6 +85,12 @@ export default defineConfig({
               path: '/list',
               component: './ListTableList',
             },
+            {
+              name: 'list.model-list',
+              icon: 'smile',
+              path: '/modellist',
+              component: './ModelList',
+            },
             {
               component: './404',
             },
@@ -119,7 +126,7 @@ export default defineConfig({
           resourcePath: string;
         },
         _: string,
-        localName: string,
+        localName: string
       ) => {
         if (
           context.resourcePath.includes('node_modules') ||
@@ -128,7 +135,9 @@ export default defineConfig({
         ) {
           return localName;
         }
+
         const match = context.resourcePath.match(/src(.*)/);
+
         if (match && match[1]) {
           const antdProPath = match[1].replace('.less', '');
           const arr = winPath(antdProPath)
@@ -137,6 +146,7 @@ export default defineConfig({
             .map((a: string) => a.toLowerCase());
           return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
         }
+
         return localName;
       },
     },

+ 1 - 1
config/defaultSettings.ts

@@ -55,7 +55,7 @@ export default {
   menu: {
     locale: true,
   },
-  title: 'Ant Design Pro',
+  title: '意图分类服务',
   pwa: false,
   iconfontUrl: '',
 } as DefaultSettings;

+ 19 - 5
package.json

@@ -32,21 +32,33 @@
     "test:component": "umi test ./src/components",
     "tsc": "tsc"
   },
-  "husky": { "hooks": { "pre-commit": "npm run lint-staged" } },
+  "husky": {
+    "hooks": {
+      "pre-commit": "npm run lint-staged"
+    }
+  },
   "lint-staged": {
     "**/*.less": "stylelint --syntax less",
     "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
-    "**/*.{js,jsx,tsx,ts,less,md,json}": ["prettier --write"]
+    "**/*.{js,jsx,tsx,ts,less,md,json}": [
+      "prettier --write"
+    ]
   },
-  "browserslist": ["> 1%", "last 2 versions", "not ie <= 10"],
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 10"
+  ],
   "dependencies": {
     "@ant-design/icons": "^4.0.0",
     "@ant-design/pro-layout": "^5.0.8",
     "@ant-design/pro-table": "^2.1.11",
     "antd": "^4.0.0",
     "classnames": "^2.2.6",
-    "lodash": "^4.17.11",
+    "immer": "^6.0.3",
+    "lodash": "^4.17.15",
     "moment": "^2.24.0",
+    "nice-hooks": "^1.2.0",
     "omit.js": "^1.0.2",
     "path-to-regexp": "2.4.0",
     "qs": "^6.9.0",
@@ -91,7 +103,9 @@
     "puppeteer-core": "^2.1.1",
     "stylelint": "^13.0.0"
   },
-  "engines": { "node": ">=10.0.0" },
+  "engines": {
+    "node": ">=10.0.0"
+  },
   "checkFiles": [
     "src/**/*.js*",
     "src/**/*.ts*",

+ 2 - 0
src/locales/zh-CN/menu.ts

@@ -23,6 +23,8 @@ export default {
   'menu.form.advanced-form': '高级表单',
   'menu.list': '列表页',
   'menu.list.table-list': '查询表格',
+  'menu.list.model-list': '模型列表',
+  'menu.list.service-list': '服务列表',
   'menu.list.basic-list': '标准列表',
   'menu.list.card-list': '卡片列表',
   'menu.list.search-list': '搜索列表',

+ 1 - 1
src/pages/ListTableList/components/UpdateForm.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState} from 'react';
 import { Form, Button, DatePicker, Input, Modal, Radio, Select, Steps } from 'antd';
 
 import { TableListItem } from '../data.d';

+ 1 - 0
src/pages/ListTableList/index.tsx

@@ -210,6 +210,7 @@ const TableList: React.FC<{}> = () => {
           columns={columns}
           rowSelection={{}}
         />
+
       </CreateForm>
       {stepFormValues && Object.keys(stepFormValues).length ? (
         <UpdateForm

+ 30 - 0
src/pages/ModelList/_mock.ts

@@ -0,0 +1,30 @@
+import {Request, Response} from 'express';
+
+
+export default {
+  'GET /api/uploadModel': (req: Request, res: Response) => {
+    res.json(
+      {
+        flag: true,
+        data: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png'
+      }
+    );
+  },
+  'GET /api/uploadVocab': (req: Request, res: Response) => {
+    res.json(
+      {
+        flag: true,
+        data: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png'
+      }
+    );
+  },
+  'GET /api/uploadLabel': (req: Request, res: Response) => {
+    res.json(
+      {
+        flag: true,
+        data: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png'
+      }
+    );
+  },
+
+};

+ 25 - 0
src/pages/ModelList/components/CreateForm.tsx

@@ -0,0 +1,25 @@
+import React from 'react';
+import { Modal } from 'antd';
+
+interface CreateFormProps {
+  modalVisible: boolean;
+  onCancel: () => void;
+}
+
+const CreateForm: React.FC<CreateFormProps> = (props) => {
+  const { modalVisible, onCancel } = props;
+
+  return (
+    <Modal
+      destroyOnClose
+      title="新建规则"
+      visible={modalVisible}
+      onCancel={() => onCancel()}
+      footer={null}
+    >
+      {props.children}
+    </Modal>
+  );
+};
+
+export default CreateForm;

+ 162 - 0
src/pages/ModelList/components/UpdateForm.tsx

@@ -0,0 +1,162 @@
+import React from 'react';
+import {Button, Form, Input, message, Modal, Radio, Upload} from 'antd';
+// @ts-ignore
+import {useStateCB} from 'nice-hooks';
+import {TableListItem} from '../data.d';
+import {UploadOutlined} from '@ant-design/icons';
+
+// 表单特殊字段
+export interface FormValueType extends Partial<TableListItem> {
+
+}
+
+export interface UpdateFormProps {
+  onCancel: (flag?: boolean, formVals?: FormValueType) => void;
+  onSubmit: (values: FormValueType) => void;
+  updateModalVisible: boolean;
+  values: Partial<TableListItem>;
+}
+
+const FormItem = Form.Item;
+
+const formLayout = {
+  labelCol: {span: 7},
+  wrapperCol: {span: 13},
+};
+
+const UpdateForm: React.FC<UpdateFormProps> = (props) => {
+  const [formVals, setFormVals] = useStateCB<FormValueType>(props.values);
+
+  const [form] = Form.useForm();
+
+  const {
+    onSubmit: handleUpdate,
+    onCancel: handleUpdateModalVisible,
+    updateModalVisible,
+    values,
+  } = props;
+
+  const submit = async () => {
+    const fieldsValue = await form.validateFields();
+    setFormVals({...formVals, ...fieldsValue},(newValue: FormValueType)=>{
+      handleUpdate(newValue);
+    });
+  };
+
+
+  const modelFileProps = {
+    action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
+    // @ts-ignore
+    onChange(info) {
+      if (info.file.status === 'done') {
+        setFormVals({...formVals, ...{"modelFile": info.file.response.url}});
+        message.success(`file uploaded successfully`);
+      } else if (info.file.status === 'error') {
+        message.error(`file upload failed.`);
+      }
+    }
+  };
+  const vocabFileProps = {
+    action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
+    // @ts-ignore
+    onChange(info) {
+      if (info.file.status === 'done') {
+        setFormVals({...formVals, ...{"vocabFile": info.file.response.url}});
+        message.success(`file uploaded successfully`);
+      } else if (info.file.status === 'error') {
+        message.error(`file upload failed.`);
+      }
+    }
+  };
+  const labelFileProps = {
+    action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
+    // @ts-ignore
+    onChange(info) {
+      if (info.file.status === 'done') {
+        setFormVals({...formVals, ...{"labelFile": info.file.response.url}});
+        message.success(`file uploaded successfully`);
+      } else if (info.file.status === 'error') {
+        message.error(`file upload failed.`);
+      }
+    }
+  };
+
+  const renderFooter = () => {
+    return (
+      <>
+        <Button onClick={() => handleUpdateModalVisible(false, values)}>取消</Button>
+        <Button type="primary" onClick={() => submit()}>
+          提交
+        </Button>
+      </>
+    );
+  };
+
+  return (
+    <Modal
+      width={640}
+      bodyStyle={{padding: '32px 40px 48px'}}
+      destroyOnClose
+      title="配置模型"
+      visible={updateModalVisible}
+      footer={renderFooter()}
+      onCancel={() => handleUpdateModalVisible()}
+    >
+      <Form
+        {...formLayout}
+        form={form}
+        initialValues={formVals}
+      >
+        <FormItem
+          name="modelName"
+          label="模型名称"
+          rules={[{required: true, message: '请输入规则名称!'}]}
+        >
+          <Input placeholder="请输入"/>
+        </FormItem>
+
+
+        <FormItem
+          name="modelType"
+          label="模型类型"
+          rules={[{required: true, message: '请选择模型类型!'}]}
+        >
+          <Radio.Group>
+            <Radio.Button value="0">aubert</Radio.Button>
+            <Radio.Button value="1">blstm</Radio.Button>
+          </Radio.Group>
+        </FormItem>
+        <FormItem
+          label="模型文件"
+        >
+          <Upload {...modelFileProps}>
+            <Button>
+              <UploadOutlined/> Upload
+            </Button>
+          </Upload>
+        </FormItem>
+        <FormItem
+          label="词向量文件"
+        >
+          <Upload {...vocabFileProps}>
+            <Button>
+              <UploadOutlined/> Upload
+            </Button>
+          </Upload>
+        </FormItem>
+        <FormItem
+          label="标签文件"
+        >
+          <Upload {...labelFileProps}>
+            <Button>
+              <UploadOutlined/> Upload
+            </Button>
+          </Upload>
+        </FormItem>
+      </Form>
+    </Modal>
+  );
+};
+
+
+export default UpdateForm;

+ 33 - 0
src/pages/ModelList/data.d.ts

@@ -0,0 +1,33 @@
+// 表单
+export interface TableListItem {
+  id: number;
+  modelName: string;
+  modelFile: string;
+  vocabFile: string;
+  labelFile: string;
+  mustTag: string;
+  mustNotTag: string;
+  onlyWord: string;
+  modelType: string;
+}
+// 分页参数
+export interface TableListPagination {
+  total: number;
+  pageSize: number;
+  current: number;
+}
+// 返回的分页数据
+export interface TableListData {
+  list: TableListItem[];
+  pagination: Partial<TableListPagination>;
+}
+// 查询参数
+export interface TableListParams {
+  sorter?: string;
+  status?: string;
+  name?: string;
+  desc?: string;
+  id?: number;
+  pageSize?: number;
+  currentPage?: number;
+}

+ 3 - 0
src/pages/ModelList/index.less

@@ -0,0 +1,3 @@
+.upload{
+  display: inline;
+}

+ 235 - 0
src/pages/ModelList/index.tsx

@@ -0,0 +1,235 @@
+import { DownOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Divider, Dropdown, Menu, message } from 'antd';
+import React, { useState, useRef } from 'react';
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table';
+import { SorterResult } from 'antd/es/table/interface';
+
+import CreateForm from './components/CreateForm';
+import UpdateForm, { FormValueType } from './components/UpdateForm';
+import { TableListItem } from './data.d';
+import { queryRule, updateRule, addRule, removeRule } from './service';
+
+/**
+ * 添加节点
+ * @param fields
+ */
+const handleAdd = async (fields: TableListItem) => {
+  const hide = message.loading('正在添加');
+  try {
+    await addRule({ ...fields });
+    hide();
+    message.success('添加成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('添加失败请重试!');
+    return false;
+  }
+};
+
+/**
+ * 更新节点
+ * @param fields
+ */
+const handleUpdate = async (fields: FormValueType) => {
+  const hide = message.loading('正在配置');
+  try {
+    await updateRule(fields);
+    hide();
+
+    message.success('配置成功');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('配置失败请重试!');
+    return false;
+  }
+};
+
+/**
+ *  删除节点
+ * @param selectedRows
+ */
+const handleRemove = async (selectedRows: TableListItem[]) => {
+  const hide = message.loading('正在删除');
+  if (!selectedRows) return true;
+  try {
+    await removeRule({
+      key: selectedRows.map((row) => row.id),
+    });
+    hide();
+    message.success('删除成功,即将刷新');
+    return true;
+  } catch (error) {
+    hide();
+    message.error('删除失败,请重试');
+    return false;
+  }
+};
+
+const TableList: React.FC<{}> = () => {
+  const [sorter, setSorter] = useState<string>('');
+  const [createModalVisible, handleModalVisible] = useState<boolean>(false);
+  const [updateModalVisible, handleUpdateModalVisible] = useState<boolean>(false);
+  const [formValues, setFormValues] = useState({});
+  const actionRef = useRef<ActionType>();
+  const columns: ProColumns<TableListItem>[] = [
+    {
+      title: '规则名称',
+      dataIndex: 'name',
+      rules: [
+        {
+          required: true,
+          message: '规则名称为必填项',
+        },
+      ],
+    },
+    {
+      title: '描述',
+      dataIndex: 'desc',
+      valueType: 'textarea',
+    },
+    {
+      title: '服务调用次数',
+      dataIndex: 'callNo',
+      sorter: true,
+      hideInForm: true,
+      renderText: (val: string) => `${val} 万`,
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+      hideInForm: true,
+      valueEnum: {
+        0: { text: '关闭', status: 'Default' },
+        1: { text: '运行中', status: 'Processing' },
+        2: { text: '已上线', status: 'Success' },
+        3: { text: '异常', status: 'Error' },
+      },
+    },
+    {
+      title: '上次调度时间',
+      dataIndex: 'updatedAt',
+      sorter: true,
+      valueType: 'dateTime',
+      hideInForm: true,
+    },
+    {
+      title: '操作',
+      dataIndex: 'option',
+      valueType: 'option',
+      render: (_, record) => (
+        <>
+          <a
+            onClick={() => {
+              handleUpdateModalVisible(true);
+              setFormValues(record);
+              console.log(record)
+            }}
+          >
+            配置
+          </a>
+          <Divider type="vertical" />
+          <a href="">订阅警报</a>
+        </>
+      ),
+    },
+  ];
+
+  return (
+    <PageHeaderWrapper>
+      <ProTable<TableListItem>
+        headerTitle="查询表格"
+        actionRef={actionRef}
+        rowKey="key"
+        onChange={(_, _filter, _sorter) => {
+          const sorterResult = _sorter as SorterResult<TableListItem>;
+          if (sorterResult.field) {
+            setSorter(`${sorterResult.field}_${sorterResult.order}`);
+          }
+        }}
+        params={{
+          sorter,
+        }}
+        toolBarRender={(action, { selectedRows }) => [
+          <Button type="primary" onClick={() => handleModalVisible(true)}>
+            <PlusOutlined /> 新建
+          </Button>,
+          selectedRows && selectedRows.length > 0 && (
+            <Dropdown
+              overlay={
+                <Menu
+                  onClick={async (e) => {
+                    if (e.key === 'remove') {
+                      await handleRemove(selectedRows);
+                      action.reload();
+                    }
+                  }}
+                  selectedKeys={[]}
+                >
+                  <Menu.Item key="remove">批量删除</Menu.Item>
+                  <Menu.Item key="approval">批量审批</Menu.Item>
+                </Menu>
+              }
+            >
+              <Button>
+                批量操作 <DownOutlined />
+              </Button>
+            </Dropdown>
+          ),
+        ]}
+        tableAlertRender={(selectedRowKeys, selectedRows) => (
+          <div>
+            已选择 <a style={{ fontWeight: 600 }}>{selectedRowKeys.length}</a> 项&nbsp;&nbsp;
+            <span>
+              {/*服务调用次数总计 {selectedRows.reduce((pre, item) => pre + item.callNo, 0)} 万*/}
+            </span>
+          </div>
+        )}
+        request={(params) => queryRule(params)}
+        columns={columns}
+        rowSelection={{}}
+      />
+      <CreateForm onCancel={() => handleModalVisible(false)} modalVisible={createModalVisible}>
+        <ProTable<TableListItem, TableListItem>
+          onSubmit={async (value) => {
+            const success = await handleAdd(value);
+            if (success) {
+              handleModalVisible(false);
+              if (actionRef.current) {
+                actionRef.current.reload();
+              }
+            }
+          }}
+          rowKey="key"
+          type="form"
+          columns={columns}
+          rowSelection={{}}
+        />
+      </CreateForm>
+      {formValues && Object.keys(formValues).length ? (
+        <UpdateForm
+          onSubmit={async (value) => {
+            const success = await handleUpdate(value);
+            if (success) {
+              handleUpdateModalVisible(false);
+              setFormValues({});
+              if (actionRef.current) {
+                actionRef.current.reload();
+              }
+            }
+          }}
+          onCancel={() => {
+            handleUpdateModalVisible(false);
+            setFormValues({});
+          }}
+          updateModalVisible={updateModalVisible}
+          values={formValues}
+        />
+      ) : null}
+    </PageHeaderWrapper>
+  );
+};
+
+export default TableList;

+ 38 - 0
src/pages/ModelList/service.ts

@@ -0,0 +1,38 @@
+import request from '@/utils/request';
+import { TableListParams, TableListItem } from './data.d';
+
+export async function queryRule(params?: TableListParams) {
+  return request('/api/rule', {
+    params,
+  });
+}
+
+export async function removeRule(params: { key: number[] }) {
+  return request('/api/rule', {
+    method: 'POST',
+    data: {
+      ...params,
+      method: 'delete',
+    },
+  });
+}
+
+export async function addRule(params: TableListItem) {
+  return request('/api/rule', {
+    method: 'POST',
+    data: {
+      ...params,
+      method: 'post',
+    },
+  });
+}
+
+export async function updateRule(params: TableListParams) {
+  return request('/api/rule', {
+    method: 'POST',
+    data: {
+      ...params,
+      method: 'update',
+    },
+  });
+}

+ 1 - 1
src/services/user.ts

@@ -5,7 +5,7 @@ export async function query(): Promise<any> {
 }
 
 export async function queryCurrent(): Promise<any> {
-  return request('/api/currentUser');
+  return {"name":"YZS","avatar":"https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png","userid":"00000001","email":"antdesign@alipay.com","signature":"海纳百川,有容乃大","title":"交互专家","group":"蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED","tags":[{"key":"0","label":"很有想法的"},{"key":"1","label":"专注设计"},{"key":"2","label":"辣~"},{"key":"3","label":"大长腿"},{"key":"4","label":"川妹子"},{"key":"5","label":"海纳百川"}],"notifyCount":12,"unreadCount":11,"country":"China","geographic":{"province":{"label":"浙江省","key":"330000"},"city":{"label":"杭州市","key":"330100"}},"address":"西湖区工专路 77 号","phone":"0752-268888888"};
 }
 
 export async function queryNotices(): Promise<any> {

+ 14 - 0
src/utils/api.ts

@@ -0,0 +1,14 @@
+import request from '@/utils/request';
+
+export default {
+
+  get: url => params => request(url, {params,}),
+
+  post: url => params => request(url, {method: 'POST', data: {...params}}),
+
+  postForm: url => params => request(url, {method: 'POST', data: {...params}, requestType: 'form'}),
+
+  delete: url => params => request(url, {method: 'POST', data: {...params, method: 'delete'}}),
+
+  put: url => params => request(url, {method: 'POST', data: {...params, method: 'put'}})
+}

+ 66 - 0
src/utils/form-state.ts

@@ -0,0 +1,66 @@
+import React from "react";
+import produce from "immer"
+import set from "lodash/set";
+import has from "lodash/has";
+
+// eslint-disable-next-line consistent-return
+const enhancedReducer = (state, updateArg) => {
+  // check if the type of update argument is a callback function
+  if (updateArg.constructor === Function) {
+    return {...state, ...updateArg(state)};
+  }
+
+  // if the type of update argument is an object
+  if (updateArg.constructor === Object) {
+    // does the update object have _path and _value as it's keys
+    // if yes then use them to update deep object values
+    if (has(updateArg, "_path") && has(updateArg, "_value")) {
+      const {_path, _value} = updateArg;
+
+      return produce(state, draft => {
+        set(draft, _path, _value);
+      });
+    }
+    return {...state, ...updateArg};
+  }
+};
+
+const useFormState = initialState => {
+  const [state, updateState] = React.useReducer(enhancedReducer, initialState);
+
+  const updateForm = React.useCallback(({target: {value, name, type}}) => {
+    const updatePath = name.split(".");
+
+    // if the input is a checkbox then use callback function to update
+    // the toggle state based on previous state
+    if (type === "checkbox") {
+      updateState(prevState => ({
+        [name]: !prevState[name]
+      }));
+
+      return;
+    }
+
+    // if we have to update the root level nodes in the form
+    if (updatePath.length === 1) {
+      const [key] = updatePath;
+
+      updateState({
+        [key]: value
+      });
+    }
+
+    // if we have to update nested nodes in the form object
+    // use _path and _value to update them.
+    if (updatePath.length === 2) {
+      updateState({
+        _path: updatePath,
+        _value: value
+      });
+    }
+  }, []);
+
+  return [state, updateState,updateForm];
+};
+
+export default useFormState;