xieyonghong il y a 4 ans
Parent
commit
72886be0f5

+ 4 - 3
.github/workflows/ci.yml

@@ -1,14 +1,15 @@
 name: Node CI
 
-on: [push, pull_request]
+on: [ push, pull_request ]
 
 jobs:
   build:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        node_version: [14.x]
-        os: [ubuntu-latest, windows-latest, macOS-latest]
+        node_version: [ 14.x ]
+        #        os: [ubuntu-latest, windows-latest, macOS-latest]
+        os: [ ubuntu-latest,macOS-latest ]
     steps:
       - uses: actions/checkout@v1
       - name: Use Node.js ${{ matrix.node_version }}

+ 2 - 2
src/components/PermissionButton/index.tsx

@@ -1,5 +1,5 @@
-import { Button, Tooltip, Popconfirm } from 'antd';
-import type { TooltipProps, PopconfirmProps, ButtonProps } from 'antd';
+import type { ButtonProps, PopconfirmProps, TooltipProps } from 'antd';
+import { Button, Popconfirm, Tooltip } from 'antd';
 import usePermissions from '@/hooks/permission';
 import { useCallback } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';

+ 86 - 61
src/components/SearchComponent/index.tsx

@@ -14,7 +14,7 @@ import {
   Space,
 } from '@formily/antd';
 import type { Field, FieldDataSource } from '@formily/core';
-import { createForm, onFieldReact, onFormInit } from '@formily/core';
+import { createForm, onFieldReact } from '@formily/core';
 import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
 import {
   DeleteOutlined,
@@ -23,18 +23,8 @@ import {
   SaveOutlined,
   SearchOutlined,
 } from '@ant-design/icons';
-import {
-  Button,
-  Card,
-  Dropdown,
-  Empty,
-  Menu,
-  message,
-  Popconfirm,
-  Popover,
-  Typography,
-} from 'antd';
-import { useEffect, useMemo, useState } from 'react';
+import { Button, Card, Dropdown, Empty, Menu, message, Popover, Typography } from 'antd';
+import { useEffect, useMemo, useRef, useState } from 'react';
 import type { ProColumns } from '@jetlinks/pro-table';
 import type { EnumData } from '@/utils/typings';
 import styles from './index.less';
@@ -75,6 +65,7 @@ interface Props<T> {
   defaultParam?: SearchTermsServer | Term[];
   // pattern?: 'simple' | 'advance';
   enableSave?: boolean;
+  initParam?: SearchTermsServer;
 }
 
 const termType = [
@@ -91,7 +82,6 @@ const termType = [
 ];
 
 const service = new Service();
-const initTerm = { termType: 'like' };
 const SchemaField = createSchemaField({
   components: {
     FormItem,
@@ -133,15 +123,7 @@ const sortField = (field: ProColumns[]) => {
 };
 
 const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
-  const { field, target, onSearch, defaultParam, enableSave = true } = props;
-
-  const intl = useIntl();
-  const [expand, setExpand] = useState<boolean>(true);
-  const initForm = server2Ui([{ terms: [initTerm] }]);
-  const [logVisible, setLogVisible] = useState<boolean>(false);
-  const [aliasVisible, setAliasVisible] = useState<boolean>(false);
-  const [initParams, setInitParams] = useState<SearchTermsUI>(initForm);
-  const [history, setHistory] = useState([]);
+  const { field, target, onSearch, defaultParam, enableSave = true, initParam } = props;
 
   /**
    * 过滤不参与搜索的数据
@@ -151,22 +133,36 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
       .filter((item) => item.dataIndex)
       .filter((item) => !item.hideInSearch)
       .filter((item) => !['index', 'option'].includes(item.dataIndex as string));
+
   // 处理后的搜索条件
   const processedField = sortField(filterSearchTerm());
+  const defaultTerms = (index: number) =>
+    ({
+      termType: 'like',
+      column: (processedField[index]?.dataIndex as string) || null,
+      type: 'or',
+    } as Partial<Term>);
+  const intl = useIntl();
+  const [expand, setExpand] = useState<boolean>(true);
+  const initForm = server2Ui(initParam || [{ terms: [defaultTerms(0)] }]);
+  const [aliasVisible, setAliasVisible] = useState<boolean>(false);
 
+  const [initParams, setInitParams] = useState<SearchTermsUI>(initForm);
+  const [history, setHistory] = useState([]);
+  const [logVisible, setLogVisible] = useState<boolean>(false);
   const form = useMemo(
     () =>
       createForm<SearchTermsUI>({
         validateFirst: true,
         initialValues: initParams,
         effects() {
-          onFormInit((form1) => {
-            if (expand) {
-              form1.setValues({
-                terms1: [{ column: processedField[0]?.dataIndex, termType: 'like' }],
-              });
-            }
-          });
+          // onFormInit((form1) => {
+          //   if (expand && !initParam) {
+          //     form1.setValues({
+          //       terms1: [{column: processedField[0]?.dataIndex, termType: 'like'}],
+          //     });
+          //   }
+          // });
           onFieldReact('*.*.column', async (typeFiled, f) => {
             const _column = (typeFiled as Field).value;
             const _field = field.find((item) => item.dataIndex === _column);
@@ -212,7 +208,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
           });
         },
       }),
-    [expand],
+    [target],
   );
 
   const historyForm = createForm();
@@ -323,30 +319,49 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
     },
   };
 
-  const handleExpand = () => {
-    const value = form.values;
+  const uiParamRef = useRef(initParam);
 
-    if (expand) {
-      value.terms1 = [0, 1, 2].map((i) => ({
-        termType: 'like',
-        column: (processedField[i]?.dataIndex as string) || null,
-        type: 'or',
-      }));
-      value.terms2 = [3, 4, 5].map((i) => ({
-        termType: 'like',
-        column: (processedField[i]?.dataIndex as string) || null,
-        type: 'or',
-      }));
-    } else {
+  const handleForm = (_expand?: boolean) => {
+    const value = form.values;
+    const __expand = _expand || expand;
+    // 第一组条件值
+    const _terms1 = _.cloneDeep(value.terms1?.[0]);
+    const uiParam = uiParamRef.current;
+    // 判断一下条件。。是否展开。
+    if (__expand) {
       value.terms1 = [
-        { termType: 'like', column: (processedField[0].dataIndex as string) || null },
+        uiParam?.[0]?.terms?.[0] || _terms1 || defaultTerms(0),
+        uiParam?.[0]?.terms?.[1] || defaultTerms(1),
+        uiParam?.[0]?.terms?.[2] || defaultTerms(2),
+      ];
+      value.terms2 = [
+        uiParam?.[1]?.terms?.[0] || defaultTerms(3),
+        uiParam?.[1]?.terms?.[1] || defaultTerms(4),
+        uiParam?.[1]?.terms?.[2] || defaultTerms(5),
       ];
+    } else {
+      value.terms1 = _terms1 ? [_terms1] : [defaultTerms(0)];
       value.terms2 = [];
     }
     setInitParams(value);
+  };
+  const handleExpand = () => {
+    handleForm();
     setExpand(!expand);
   };
 
+  useEffect(() => {
+    //  1、一组条件时的表单值
+    //  2、六组条件时的表单值
+    //  3、拥有默认条件时的表单值
+    // 合并初始化的值
+
+    //expand false 6组条件 true 1组条件
+
+    if (initParam && initParam[0].terms && initParam[0].terms.length > 1) {
+      handleExpand();
+    }
+  }, []);
   const simpleSchema: ISchema = {
     type: 'object',
     properties: {
@@ -355,8 +370,14 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
   };
   const handleHistory = (item: SearchHistory) => {
     const log = JSON.parse(item.content) as SearchTermsUI;
-    form.setValues(log);
-    setExpand(!((log.terms1 && log.terms1?.length > 1) || (log.terms2 && log.terms2?.length > 1)));
+    setLogVisible(false);
+    uiParamRef.current = ui2Server(log);
+    const _expand =
+      (log.terms1 && log.terms1?.length > 1) || (log.terms2 && log.terms2?.length > 1);
+    if (_expand) {
+      setExpand(false);
+    }
+    handleForm(_expand);
   };
 
   const historyDom = (
@@ -366,8 +387,8 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
           <Menu.Item onClick={() => handleHistory(item)} key={item.id || randomString(9)}>
             <div className={styles.list}>
               <Typography.Text ellipsis={{ tooltip: item.name }}>{item.name}</Typography.Text>
-              <Popconfirm
-                onConfirm={async (e) => {
+              <DeleteOutlined
+                onClick={async (e) => {
                   e?.stopPropagation();
                   const response = await service.history.remove(`${target}-search`, item.key);
                   if (response.status === 200) {
@@ -376,10 +397,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
                     setHistory(temp);
                   }
                 }}
-                title={'确认删除吗?'}
-              >
-                <DeleteOutlined onClick={(e) => e.stopPropagation()} />
-              </Popconfirm>
+              />
             </div>
           </Menu.Item>
         ))
@@ -402,7 +420,6 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
 
   const formatValue = (value: SearchTermsUI): SearchTermsServer => {
     let _value = ui2Server(value);
-
     // 处理默认查询参数
     if (defaultParam && defaultParam?.length > 0) {
       if ('terms' in defaultParam[0]) {
@@ -459,11 +476,15 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
   };
 
   const resetForm = async () => {
-    const temp = initForm;
-    const expandData = Array(expand ? 1 : 3).fill(initTerm);
-    temp.terms1 = expandData;
-    temp.terms2 = expandData;
-    await form.reset();
+    const value = form.values;
+    if (!expand) {
+      value.terms1 = [defaultTerms(0), defaultTerms(1), defaultTerms(2)];
+      value.terms2 = [defaultTerms(3), defaultTerms(4), defaultTerms(5)];
+    } else {
+      value.terms1 = [defaultTerms(0)];
+      value.terms2 = [];
+    }
+    setInitParams(value);
     await handleSearch();
   };
 
@@ -477,7 +498,7 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
       <Dropdown.Button
         icon={<SearchOutlined />}
         placement={'bottomLeft'}
-        trigger={['click']}
+        destroyPopupOnHide
         onClick={handleSearch}
         visible={logVisible}
         onVisibleChange={async (visible) => {
@@ -510,6 +531,10 @@ const SearchComponent = <T extends Record<string, any>>(props: Props<T>) => {
                       max: 64,
                       message: '最多可输入64个字符',
                     },
+                    {
+                      required: true,
+                      message: '请输入名称',
+                    },
                   ],
                 },
               },

+ 90 - 14
src/components/SearchComponent/readme.md

@@ -1,6 +1,6 @@
 ### 字段数据由后端提供
 
-```typescript jsx
+```
 {
   title: '测试字段',
     dataIndex
@@ -14,11 +14,17 @@
   'center',
     request
 :
-  () => request('/jetlinks/dictionary/device-log-type/items')
-    .then(response =>
-      response.result
-        .map((item: { text: string, value: string }) =>
-          ({ label: item.text, value: item.value }))),
+  () => request(
+    '/jetlinks/dictionary/device-log-type/items'
+  )
+    .then(response => response.result.map((item: {
+                                             text: string,
+                                             value: string
+                                           }
+    ) => ({
+      label: item.text,
+      value: item.value
+    }))),
 }
 ,
 ```
@@ -28,21 +34,91 @@
 - `digit` 数字类型
 - `dateTime` 日期时间
 
-## defaultParams
+## defaultParams 默认查询参数不展示到 UI 上
 
 支持两种类型的默认参数
 
-```typescript jsx
-const a = { [{ column: 'test', value: 'admin' }] };
+```
+const a = {[{column: 'test', value: 'admin'}]};
 
 const b = {
   [
     {
-      terms: [{ column: 'parentId$isnull', value: '' }, { column: 'parentId$not', value: 'test', type: 'or' }],
-    },
-    {
-      terms: [{ column: 'id$not', value: 'test', type: 'and' }],
+      terms: [{column: 'parentId$isnull', value: ''}, {column: 'parentId$not', value: 'test', type: 'or'}],
     },
-  ]
+{
+  terms: [{column: 'id$not', value: 'test', type: 'and'}],
 }
+,
+]
+}
+```
+
+## initParam 默认查询参数,展示到 UI 上
+
+```
+{[
+  {
+    terms: [
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '123'
+      },
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '444'
+      },
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '555'
+      },
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '666'
+      },
+      {
+        column: 'username',
+        termType: 'eq',
+        value: 'username',
+        type: 'and'
+      }
+    ],
+    type: 'or'
+  },
+  {
+    terms: [
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '132323'
+      },
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '22'
+      },
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '333'
+      },
+      {
+        column: 'name',
+        termType: 'eq',
+        value: '444'
+      },
+      {
+        column: 'username',
+        termType: 'eq',
+        value: '123123',
+        type: 'and'
+      }
+    ],
+    type: 'or'
+  },
+]}
 ```

+ 12 - 12
src/e2e/baseLayout.e2e.js

@@ -47,16 +47,16 @@ describe('Ant Design Pro E2E test', () => {
     it(`test pages ${route}`, testPage(route));
   });
 
-  it('topmenu should have footer', async () => {
-    const params = '?navTheme=light&layout=topmenu';
-    await page.goto(`${BASE_URL}${params}`);
-    await page.waitForSelector('footer', {
-      timeout: 2000,
-    });
-
-    const haveFooter = await page.evaluate(
-      () => document.getElementsByTagName('footer').length > 0,
-    );
-    expect(haveFooter).toBeTruthy();
-  });
+  // it('topmenu should have footer', async () => {
+  //   const params = '?navTheme=light&layout=topmenu';
+  //   await page.goto(`${BASE_URL}${params}`);
+  //   await page.waitForSelector('footer', {
+  //     timeout: 2000,
+  //   });
+  //
+  //   const haveFooter = await page.evaluate(
+  //     () => document.getElementsByTagName('footer').length > 0,
+  //   );
+  //   expect(haveFooter).toBeTruthy();
+  // });
 });

+ 1 - 1
src/hooks/permission/index.ts

@@ -1,6 +1,6 @@
 import { useEffect, useState } from 'react';
+import type { BUTTON_PERMISSION, MENUS_CODE_TYPE } from '@/utils/menu/router';
 import { BUTTON_PERMISSION_ENUM } from '@/utils/menu/router';
-import type { MENUS_CODE_TYPE, BUTTON_PERMISSION } from '@/utils/menu/router';
 import { MENUS_BUTTONS_CACHE } from '@/utils/menu';
 
 export type permissionKeyType = keyof typeof BUTTON_PERMISSION_ENUM;

+ 1 - 2
src/pages/device/Instance/Detail/Diagnose/index.tsx

@@ -1,7 +1,6 @@
 import { Badge, Card, Col, Row } from 'antd';
 import type { ReactNode } from 'react';
-import { useEffect } from 'react';
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
 import Message from './Message';
 import Status from './Status';
 import './index.less';

+ 1 - 1
src/pages/device/Instance/Detail/index.tsx

@@ -19,7 +19,7 @@ import type { DeviceMetadata } from '@/pages/device/Product/typings';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
 import { Store } from 'jetlinks-store';
 import SystemConst from '@/utils/const';
-import { getMenuPathByParams, getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { PermissionButton } from '@/components';
 

+ 1 - 1
src/pages/device/Instance/Save/index.tsx

@@ -1,4 +1,4 @@
-import { Col, Form, Input, message, Row, Select, Modal } from 'antd';
+import { Col, Form, Input, message, Modal, Row, Select } from 'antd';
 import { service } from '@/pages/device/Instance';
 import type { DeviceInstance } from '../typings';
 import { useEffect, useState } from 'react';

+ 2 - 3
src/pages/device/Instance/index.tsx

@@ -4,7 +4,7 @@ import type { DeviceInstance } from '@/pages/device/Instance/typings';
 import moment from 'moment';
 import { Badge, Button, Dropdown, Menu, message, Tooltip } from 'antd';
 import { useEffect, useRef, useState } from 'react';
-import { useHistory } from 'umi';
+import { useHistory, useIntl } from 'umi';
 import {
   CheckCircleOutlined,
   DeleteOutlined,
@@ -19,13 +19,12 @@ import {
 import { model } from '@formily/reactive';
 import Service from '@/pages/device/Instance/service';
 import type { MetadataItem } from '@/pages/device/Product/typings';
-import { useIntl } from 'umi';
 import Save from './Save';
 import Export from './Export';
 import Import from './Import';
 import Process from './Process';
 import SearchComponent from '@/components/SearchComponent';
-import { ProTableCard, PermissionButton } from '@/components';
+import { PermissionButton, ProTableCard } from '@/components';
 import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
 import DeviceCard from '@/components/ProTableCard/CardItems/device';

+ 2 - 1
src/pages/device/Product/index.tsx

@@ -19,7 +19,7 @@ import { useEffect, useRef, useState } from 'react';
 import Save from '@/pages/device/Product/Save';
 import SearchComponent from '@/components/SearchComponent';
 import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
-import { ProTableCard, AIcon, PermissionButton } from '@/components';
+import { AIcon, PermissionButton, ProTableCard } from '@/components';
 import ProductCard from '@/components/ProTableCard/CardItems/product';
 import { downloadObject } from '@/utils/util';
 
@@ -390,6 +390,7 @@ const Product = observer(() => {
                 } catch {
                   message.error('请上传json格式文件');
                 }
+                return true;
               };
               return false;
             }}

+ 1 - 1
src/pages/link/Gateway/index.tsx

@@ -17,8 +17,8 @@ import type { ISchema } from '@formily/json-schema';
 import Service from '@/pages/link/Gateway/service';
 import { CurdModel } from '@/components/BaseCrud/model';
 import type { Field, FormPathPattern } from '@formily/core';
-import { action } from '@formily/reactive';
 import { onFieldReact, onFieldValueChange } from '@formily/core';
+import { action } from '@formily/reactive';
 import { useAsyncDataSource } from '@/utils/util';
 
 export const service = new Service('gateway/device');

+ 2 - 2
src/pages/link/Protocol/index.tsx

@@ -13,7 +13,7 @@ import {
 import Service from '@/pages/link/Protocol/service';
 import { useIntl } from 'umi';
 import SearchComponent from '@/components/SearchComponent';
-import { ProTableCard, PermissionButton } from '@/components';
+import { PermissionButton, ProTableCard } from '@/components';
 import ProcotolCard from '@/components/ProTableCard/CardItems/protocol';
 import Save from './save';
 
@@ -182,7 +182,7 @@ const Protocol = () => {
           <PermissionButton
             onClick={() => {
               setVisible(true);
-              setCurrent({});
+              setCurrent(undefined);
             }}
             style={{ marginRight: 12 }}
             isPermission={permission.add}

+ 1 - 1
src/pages/link/Protocol/save/index.tsx

@@ -3,7 +3,7 @@ import { createForm, registerValidateRules } from '@formily/core';
 import { createSchemaField } from '@formily/react';
 import React, { useEffect, useState } from 'react';
 import * as ICONS from '@ant-design/icons';
-import { Select, Form, FormItem, Input, FormGrid } from '@formily/antd';
+import { Form, FormGrid, FormItem, Input, Select } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import { service } from '@/pages/link/Protocol';
 import { Modal } from '@/components';

+ 2 - 7
src/pages/media/Device/index.tsx

@@ -13,16 +13,11 @@ import {
 } from '@ant-design/icons';
 import type { DeviceItem } from '@/pages/media/Device/typings';
 import { useHistory, useIntl } from 'umi';
-import { BadgeStatus, ProTableCard, PermissionButton } from '@/components';
+import { BadgeStatus, PermissionButton, ProTableCard } from '@/components';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import SearchComponent from '@/components/SearchComponent';
 import MediaDevice from '@/components/ProTableCard/CardItems/mediaDevice';
-import {
-  // getButtonPermission,
-  getMenuPathByCode,
-  getMenuPathByParams,
-  MENUS_CODE,
-} from '@/utils/menu';
+import { getMenuPathByCode, getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 import Service from './service';
 import Save from './Save';
 

+ 2 - 2
src/pages/notice/Template/Detail/doc/AliyunSms.tsx

@@ -22,8 +22,8 @@ const AliyunSms = () => {
       <div>
         <div> 1、绑定配置</div>
         <div> 绑定通知配置</div>
-        <div> 2、模板ID</div>
-        <div> 阿里云短信对每一条短信模板分配的唯一ID标识</div>
+        <div> 2、模板</div>
+        <div> 阿里云短信平台自定义的模板名称</div>
         <div> 3、收信人</div>
         <div> 当前仅支持国内手机号,此处若不填,则在模板调试和配置告警通知时手动填写</div>
         <div> 4、签名</div>

+ 3 - 5
src/pages/system/User/Save/index.tsx

@@ -171,7 +171,7 @@ const Save = (props: Props) => {
                           if (resp.result.passed) {
                             resolve('');
                           } else {
-                            resolve(resp.result.reason);
+                            resolve(model === 'edit' ? '' : resp.result.reason);
                           }
                         }
                         resolve('');
@@ -200,8 +200,6 @@ const Save = (props: Props) => {
           checkStrength: true,
           placeholder: '请输入密码',
         },
-        maxLength: 128,
-        minLength: 6,
         'x-reactions': [
           {
             dependencies: ['.confirmPassword'],
@@ -220,7 +218,7 @@ const Save = (props: Props) => {
             message: '密码最多可输入128位',
           },
           {
-            min: 6,
+            min: model === 'edit' ? 0 : 6,
             message: '密码不能少于6位',
           },
           {
@@ -249,7 +247,7 @@ const Save = (props: Props) => {
             message: '密码最多可输入128位',
           },
           {
-            min: 6,
+            min: model === 'edit' ? 0 : 6,
             message: '密码不能少于6位',
           },
           {

+ 30 - 34
src/pages/system/User/index.tsx

@@ -3,7 +3,7 @@ import { PageContainer } from '@ant-design/pro-layout';
 import SearchComponent from '@/components/SearchComponent';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { Badge, Button, message, Popconfirm, Tooltip } from 'antd';
+import { Badge, message, Popconfirm } from 'antd';
 import {
   CloseCircleOutlined,
   DeleteOutlined,
@@ -15,7 +15,6 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import { useRef, useState } from 'react';
 import Save from './Save';
 import { observer } from '@formily/react';
-import { getButtonPermission } from '@/utils/menu';
 import { PermissionButton } from '@/components';
 import usePermissions from '@/hooks/permission';
 
@@ -116,34 +115,32 @@ const User = observer(() => {
       valueType: 'option',
       width: 200,
       render: (text, record) => [
-        <Button
+        <PermissionButton
           style={{ padding: 0 }}
           type="link"
-          disabled={getButtonPermission('system/User', ['update', 'view'])}
+          isPermission={userPermission.update}
           key="editable"
           onClick={() => edit(record)}
-        >
-          <Tooltip
-            title={intl.formatMessage({
+          tooltip={{
+            title: intl.formatMessage({
               id: 'pages.data.option.edit',
               defaultMessage: '编辑',
-            })}
-          >
-            <EditOutlined />
-          </Tooltip>
-        </Button>,
-        <Button
+            }),
+          }}
+        >
+          <EditOutlined />
+        </PermissionButton>,
+        <PermissionButton
           style={{ padding: 0 }}
-          disabled={getButtonPermission('system/User', ['action'])}
+          isPermission={userPermission.action}
           type="link"
           key="changeState"
-        >
-          <Popconfirm
-            title={intl.formatMessage({
+          popConfirm={{
+            title: intl.formatMessage({
               id: `pages.data.option.${record.status ? 'disabled' : 'enabled'}.tips`,
               defaultMessage: `确认${record.status ? '禁用' : '启用'}?`,
-            })}
-            onConfirm={async () => {
+            }),
+            onConfirm: async () => {
               await service.update({
                 id: record.id,
                 status: record.status ? 0 : 1,
@@ -155,18 +152,17 @@ const User = observer(() => {
                 }),
               );
               actionRef.current?.reload();
-            }}
-          >
-            <Tooltip
-              title={intl.formatMessage({
-                id: `pages.data.option.${record.status ? 'disabled' : 'enabled'}`,
-                defaultMessage: record.status ? '禁用' : '启用',
-              })}
-            >
-              {record.status ? <CloseCircleOutlined /> : <PlayCircleOutlined />}
-            </Tooltip>
-          </Popconfirm>
-        </Button>,
+            },
+          }}
+          tooltip={{
+            title: intl.formatMessage({
+              id: `pages.data.option.${record.status ? 'disabled' : 'enabled'}`,
+              defaultMessage: record.status ? '禁用' : '启用',
+            }),
+          }}
+        >
+          {record.status ? <CloseCircleOutlined /> : <PlayCircleOutlined />}
+        </PermissionButton>,
         <PermissionButton
           type="link"
           key="delete"
@@ -208,11 +204,11 @@ const User = observer(() => {
         columns={columns}
         search={false}
         headerTitle={
-          <Button
+          <PermissionButton
             onClick={() => {
               setMode('add');
             }}
-            disabled={getButtonPermission('system/User', ['add'])}
+            isPermission={userPermission.add}
             key="button"
             icon={<PlusOutlined />}
             type="primary"
@@ -221,7 +217,7 @@ const User = observer(() => {
               id: 'pages.data.option.add',
               defaultMessage: '新增',
             })}
-          </Button>
+          </PermissionButton>
         }
         request={async (params) =>
           service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })