Просмотр исходного кода

feat: 优化组织管理资产分配

xieyonghong 3 лет назад
Родитель
Сommit
fb29187f12

+ 50 - 6
src/components/ProTableCard/CardItems/device.tsx

@@ -1,10 +1,12 @@
-import React, { useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import type { DeviceInstance } from '@/pages/device/Instance/typings';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import { TableCard, Ellipsis } from '@/components';
 import '@/style/common.less';
 import '../index.less';
 import { CheckOutlined } from '@ant-design/icons';
+import { Store } from 'jetlinks-store';
+import { Checkbox } from 'antd';
 
 export interface DeviceCardProps extends Partial<DeviceInstance> {
   detail?: React.ReactNode;
@@ -18,6 +20,11 @@ export interface DeviceCardProps extends Partial<DeviceInstance> {
   showBindBtn?: boolean;
   cardType?: 'bind' | 'unbind';
   showTool?: boolean;
+  assetsOptions?: any[];
+  permissionInfoList?: any[];
+  allAssets?: string[];
+  onAssetsChange?: (value?: any) => void;
+  isChecked?: boolean;
 }
 
 const defaultImage = require('/public/images/device-type-3-big.png');
@@ -36,6 +43,32 @@ export const handlePermissionsMap = (permissions?: string[]) => {
 
 export const ExtraDeviceCard = (props: DeviceCardProps) => {
   const [imgUrl, setImgUrl] = useState<string>(props.photoUrl || defaultImage);
+  const [assetKeys, setAssetKeys] = useState<string[]>(['read']);
+  const [disabled, setDisabled] = useState(true);
+  const disabledRef = useRef<boolean>(true);
+
+  const assetsOptions =
+    props.assetsOptions && props.permissionInfoList
+      ? props.assetsOptions.filter((item: any) =>
+          props.permissionInfoList.some((pItem) => pItem.id === item.value),
+        )
+      : [];
+
+  useEffect(() => {
+    Store.subscribe('assets-device', (data: any) => {
+      if (data.isAll && data.bindKeys.includes(props.id)) {
+        setAssetKeys(data.assets);
+        setDisabled(true);
+        disabledRef.current = true;
+      } else if (!data.isAll && data.bindKeys.includes(props.id)) {
+        setDisabled(false);
+        disabledRef.current = false;
+      } else {
+        setDisabled(true);
+        disabledRef.current = true;
+      }
+    });
+  }, []);
 
   return (
     <TableCard
@@ -78,11 +111,22 @@ export const ExtraDeviceCard = (props: DeviceCardProps) => {
             </div>
             {props.cardType === 'bind' ? (
               <div className={'flex-auto'}>
-                <label>说明</label>
-                <Ellipsis title={props.describe || ''} />
-                {/*<Tooltip title={props.describe}>*/}
-                {/*  <div className={'ellipsis'}>{props.describe}</div>*/}
-                {/*</Tooltip>*/}
+                <Checkbox.Group
+                  options={assetsOptions?.map((item) => {
+                    return {
+                      ...item,
+                      disabled: disabled,
+                    };
+                  })}
+                  value={assetKeys}
+                  onChange={(e) => {
+                    console.log('assetKeys', disabledRef.current, assetKeys, e);
+                    if (!disabledRef.current) {
+                      setAssetKeys(e as string[]);
+                      props.onAssetsChange?.(e);
+                    }
+                  }}
+                />
               </div>
             ) : (
               <div className={'flex-auto'}>

+ 54 - 4
src/components/ProTableCard/CardItems/product.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { StatusColorEnum } from '@/components/BadgeStatus';
 import { useIntl } from 'umi';
@@ -6,6 +6,8 @@ import { Ellipsis, TableCard } from '@/components';
 import '@/style/common.less';
 import '../index.less';
 import { CheckOutlined } from '@ant-design/icons';
+import { Checkbox } from 'antd';
+import { Store } from 'jetlinks-store';
 
 export interface ProductCardProps extends Partial<ProductItem> {
   detail?: React.ReactNode;
@@ -19,6 +21,11 @@ export interface ProductCardProps extends Partial<ProductItem> {
   showBindBtn?: boolean;
   cardType?: 'bind' | 'unbind';
   showTool?: boolean;
+  assetsOptions?: any[];
+  permissionInfoList?: any[];
+  allAssets?: string[];
+  onAssetsChange?: (value?: any) => void;
+  isChecked?: boolean;
 }
 
 const defaultImage = require('/public/images/device-product.png');
@@ -99,6 +106,32 @@ export const ExtraSceneProductCard = (props: ProductCardProps) => {
 export const ExtraProductCard = (props: ProductCardProps) => {
   const intl = useIntl();
   const [imgUrl, setImgUrl] = useState<string>(props.photoUrl || defaultImage);
+  const [assetKeys, setAssetKeys] = useState<string[]>(['read']);
+  const [disabled, setDisabled] = useState(true);
+  const disabledRef = useRef<boolean>(true);
+  console.log(props.assetsOptions, props.permissionInfoList);
+  const assetsOptions =
+    props.assetsOptions && props.permissionInfoList
+      ? props.assetsOptions.filter((item: any) =>
+          props.permissionInfoList.some((pItem) => pItem.id === item.value),
+        )
+      : [];
+
+  useEffect(() => {
+    Store.subscribe('assets-product', (data: any) => {
+      if (data.isAll && data.bindKeys.includes(props.id)) {
+        setAssetKeys(data.assets);
+        setDisabled(true);
+        disabledRef.current = true;
+      } else if (!data.isAll && data.bindKeys.includes(props.id)) {
+        setDisabled(false);
+        disabledRef.current = false;
+      } else {
+        setDisabled(true);
+        disabledRef.current = true;
+      }
+    });
+  }, []);
 
   return (
     <TableCard
@@ -115,7 +148,10 @@ export const ExtraProductCard = (props: ProductCardProps) => {
         1: StatusColorEnum.success,
       }}
       className={props.className}
-      onClick={props.onClick}
+      onClick={(e) => {
+        e.stopPropagation();
+        props.onClick?.(e);
+      }}
     >
       <div className={'pro-table-card-item'}>
         <div className={'card-item-avatar'}>
@@ -143,8 +179,22 @@ export const ExtraProductCard = (props: ProductCardProps) => {
             </div>
             {props.cardType === 'bind' ? (
               <div className={'flex-auto'}>
-                <label>说明</label>
-                <Ellipsis title={props.describe} />
+                <Checkbox.Group
+                  options={assetsOptions?.map((item) => {
+                    return {
+                      ...item,
+                      disabled: item.disabled !== true ? disabled : item.disabled,
+                    };
+                  })}
+                  value={assetKeys}
+                  onChange={(e) => {
+                    console.log('assetKeys', disabledRef.current, assetKeys, e);
+                    if (!disabledRef.current) {
+                      setAssetKeys(e as string[]);
+                      props.onAssetsChange?.(e);
+                    }
+                  }}
+                />
               </div>
             ) : (
               <div className={'flex-auto'}>

+ 5 - 2
src/components/ProTableCard/TableCard.tsx

@@ -17,7 +17,7 @@ export interface TableCardProps {
   children?: React.ReactNode;
   actions?: React.ReactNode[];
   contentClassName?: string;
-  onClick?: () => void;
+  onClick?: React.MouseEventHandler<HTMLDivElement>;
 }
 
 function getAction(actions: React.ReactNode[]) {
@@ -76,7 +76,10 @@ export default (props: TableCardProps) => {
   return (
     <div
       className={classNames('iot-card', { hover: maskShow }, props.className)}
-      onClick={props.onClick}
+      onClick={(e) => {
+        e.stopPropagation();
+        props.onClick?.(e);
+      }}
     >
       <div className={'card-warp'}>
         <div

+ 7 - 2
src/components/ProTableCard/index.tsx

@@ -37,6 +37,7 @@ interface ProTableCardProps<T> {
   noPadding?: boolean;
   cardScrollY?: number;
   modelChange?: (type: ModelType) => void;
+  cardProps?: any;
 }
 
 const ProTableCard = <
@@ -71,10 +72,12 @@ const ProTableCard = <
    * @param dataSource
    */
   const handleCard = useCallback(
-    (dataSource: readonly T[] | undefined, rowSelection?: any): JSX.Element => {
+    (dataSource: readonly T[] | undefined, rowSelection?: any, _cardProps?: any): JSX.Element => {
       setDataLength(dataSource ? dataSource.length : 0);
 
       const Item = (dom: React.ReactNode) => {
+        // @ts-ignore
+        const card_props = isFunction(_cardProps) ? _cardProps(dom.props) : _cardProps;
         if (!rowSelection || (rowSelection && !rowSelection.selectedRowKeys)) {
           return dom;
         }
@@ -85,12 +88,14 @@ const ProTableCard = <
 
         // @ts-ignore
         return React.cloneElement(dom, {
+          ...card_props,
           // @ts-ignore
           className: classNames(dom.props.className, {
             'item-active': selectedRowKeys && selectedRowKeys.includes(id),
           }),
           key: id,
           onClick: (e: any) => {
+            if (e.target.nodeName !== 'DIV') return;
             e.stopPropagation();
             if (onChange || onSelect) {
               const isSelect = selectedRowKeys.includes(id);
@@ -299,7 +304,7 @@ const ProTableCard = <
         tableViewRender={
           model === ModelEnum.CARD
             ? (tableProps) => {
-                return handleCard(tableProps.dataSource, extraProps?.rowSelection);
+                return handleCard(tableProps.dataSource, extraProps?.rowSelection, props.cardProps);
               }
             : undefined
         }

+ 1 - 1
src/pages/device/components/Metadata/index.less

@@ -11,6 +11,6 @@
   position: absolute;
   top: 12px;
   z-index: 1;
-  margin-left: 330px;
+  margin-left: 380px;
   font-weight: 100;
 }

+ 2 - 1
src/pages/device/components/Metadata/index.tsx

@@ -46,7 +46,7 @@ const Metadata = observer((props: Props) => {
 
   return (
     <div className={'device-detail-metadata'} style={{ position: 'relative' }}>
-      <div className={styles.tips} style={{ width: '40%' }}>
+      <div className={styles.tips} style={{ width: 'calc(100% - 670px)' }}>
         <Tooltip
           title={
             InstanceModel.detail?.independentMetadata && props.type === 'device'
@@ -64,6 +64,7 @@ const Metadata = observer((props: Props) => {
       </div>
       <Tabs
         className={styles.metadataNav}
+        type="card"
         tabBarExtraContent={
           <Space>
             {InstanceModel.detail?.independentMetadata && props.type === 'device' && (

+ 135 - 20
src/pages/system/Department/Assets/deivce/bind.tsx

@@ -1,31 +1,37 @@
 // 资产-产品分类-绑定
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { DeviceBadge, service } from './index';
-import { message, Modal } from 'antd';
+import { Checkbox, message, Modal, Switch } from 'antd';
 import Models from './model';
 import { useEffect, useRef, useState } from 'react';
 import { observer } from '@formily/react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import type { DeviceItem } from '@/pages/system/Department/typings';
-import PermissionModal from '@/pages/system/Department/Assets/permissionModal';
 import SearchComponent from '@/components/SearchComponent';
 import { ExtraDeviceCard } from '@/components/ProTableCard/CardItems/device';
 import { ProTableCard } from '@/components';
 import { AssetsModel } from '@/pages/system/Department/Assets';
 import encodeQuery from '@/utils/encodeQuery';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { Store } from 'jetlinks-store';
+import { onlyMessage } from '@/utils/util';
 
 interface Props {
   reload: () => void;
   visible: boolean;
   onCancel: () => void;
   parentId: string;
+  assetsType?: string[];
 }
 
 const Bind = observer((props: Props) => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
-  const saveRef = useRef<{ saveData: Function }>();
+  const [loading, setLoading] = useState(false);
+  const [checkAssets, setCheckAssets] = useState<string[]>(['read']);
+  const [isAll, setIsAll] = useState<boolean>(false);
+  const bindChecks = useRef(new Map());
 
   const columns: ProColumns<DeviceItem>[] = [
     {
@@ -108,7 +114,26 @@ const Bind = observer((props: Props) => {
   const handleBind = () => {
     AssetsModel.params = {};
     if (Models.bindKeys.length) {
-      saveRef.current?.saveData();
+      setLoading(true);
+      const _data: any[] = [];
+      bindChecks.current.forEach((value, key) => {
+        _data.push({
+          targetType: 'org',
+          targetId: props.parentId,
+          assetType: 'device',
+          assetIdList: [key],
+          permission: value,
+        });
+      });
+      service.bind('device', _data).subscribe({
+        next: () => onlyMessage('操作成功'),
+        error: () => onlyMessage('操作失败', 'error'),
+        complete: () => {
+          setLoading(false);
+          props.reload();
+          props.onCancel();
+        },
+      });
     } else {
       message.warn('请先勾选数据');
       // props.onCancel();
@@ -161,21 +186,58 @@ const Bind = observer((props: Props) => {
       visible={props.visible}
       onOk={handleBind}
       onCancel={props.onCancel}
+      confirmLoading={loading}
       width={'75vw'}
       title="绑定"
     >
-      <PermissionModal
-        type="device"
-        bindKeys={Models.bindKeys}
-        parentId={props.parentId}
-        ref={saveRef}
-        onCancel={(type) => {
-          if (type) {
-            props.reload();
-            props.onCancel();
-          }
-        }}
-      />
+      {/*<PermissionModal*/}
+      {/*  type="device"*/}
+      {/*  bindKeys={Models.bindKeys}*/}
+      {/*  parentId={props.parentId}*/}
+      {/*  ref={saveRef}*/}
+      {/*  onCancel={(type) => {*/}
+      {/*    if (type) {*/}
+      {/*      props.reload();*/}
+      {/*      props.onCancel();*/}
+      {/*    }*/}
+      {/*  }}*/}
+      {/*/>*/}
+      <div className={'assets-bind-tip'}>
+        <ExclamationCircleOutlined style={{ paddingRight: 8 }} />
+        只能分配有“共享”权限的资产数据
+      </div>
+      <div className={'assets-bind-switch'}>
+        <span>批量配置</span>
+        <Switch
+          checkedChildren="开"
+          unCheckedChildren="关"
+          onChange={(e) => {
+            setIsAll(e);
+            Store.set('assets-device', {
+              isAll: e,
+              assets: checkAssets,
+              bindKeys: Models.bindKeys,
+            });
+          }}
+        />
+      </div>
+      <div>
+        <Checkbox.Group
+          options={props.assetsType}
+          value={checkAssets}
+          onChange={(e: any) => {
+            Store.set('assets-device', {
+              isAll: isAll,
+              assets: e,
+              bindKeys: Models.bindKeys,
+            });
+            bindChecks.current.forEach((_, key) => {
+              bindChecks.current.set(key, e);
+            });
+            setCheckAssets(e);
+          }}
+        />
+      </div>
       <SearchComponent<DeviceItem>
         field={columns}
         enableSave={false}
@@ -225,7 +287,19 @@ const Bind = observer((props: Props) => {
           gridColumn={2}
           columnEmptyText={''}
           cardRender={(record) => (
-            <ExtraDeviceCard showBindBtn={false} showTool={false} {...record} cardType={'bind'} />
+            <ExtraDeviceCard
+              showBindBtn={false}
+              showTool={false}
+              {...record}
+              assetsOptions={props.assetsType}
+              allAssets={checkAssets}
+              cardType={'bind'}
+              onAssetsChange={(e) => {
+                if (bindChecks.current.has(record.id)) {
+                  bindChecks.current.set(record.id, e);
+                }
+              }}
+            />
           )}
           tableAlertRender={({ selectedRowKeys }) => <div>已选择 {selectedRowKeys.length} 项</div>}
           tableAlertOptionRender={() => {
@@ -249,9 +323,16 @@ const Bind = observer((props: Props) => {
                 Models.bindKeys = [
                   ...new Set([...Models.bindKeys, ...getSelectedRowsKey(selectedRows)]),
                 ];
+                bindChecks.current.set(record.id, checkAssets);
               } else {
                 Models.bindKeys = Models.bindKeys.filter((item) => item !== record.id);
+                bindChecks.current.delete(record.id);
               }
+              Store.set('assets-device', {
+                isAll: isAll,
+                assets: checkAssets,
+                bindKeys: Models.bindKeys,
+              });
             },
             onSelectAll: (selected, selectedRows, changeRows) => {
               if (selected) {
@@ -266,9 +347,43 @@ const Bind = observer((props: Props) => {
               }
             },
           }}
-          request={(params) =>
-            service.queryDeviceList({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
-          }
+          request={async (params) => {
+            const resp: any = await service.queryDeviceList({
+              ...params,
+              sorts: [{ name: 'createTime', order: 'desc' }],
+            });
+            let newData = [];
+            if (resp.status === 200) {
+              newData = [...resp.result.data];
+              const assetsResp = await service.getBindingAssets(
+                'product',
+                resp.result.data.map((item: any) => item.id),
+              );
+              if (assetsResp.status === 200) {
+                newData = newData.map((item: any) => {
+                  const assetsItem = assetsResp.result.find(
+                    (aItem: any) => (aItem.assetId = item.id),
+                  );
+                  console.log(assetsItem);
+                  return {
+                    ...item,
+                    ...assetsItem,
+                  };
+                });
+              }
+            }
+
+            return {
+              code: resp.message,
+              result: {
+                data: newData as DeviceItem[],
+                pageIndex: 0,
+                pageSize: 0,
+                total: 0,
+              },
+              status: resp.status,
+            };
+          }}
           params={searchParam}
           height={'none'}
         />

+ 55 - 24
src/pages/system/Department/Assets/deivce/index.tsx

@@ -1,7 +1,7 @@
 // 资产分配-设备管理
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { Badge } from 'antd';
+import { Badge, Button, Dropdown, Menu } from 'antd';
 import { useEffect, useRef, useState } from 'react';
 import { observer } from '@formily/react';
 import type { DeviceItem } from '@/pages/system/Department/typings';
@@ -42,9 +42,28 @@ export default observer((props: { parentId: string }) => {
   const [updateVisible, setUpdateVisible] = useState(false);
   const [updateId, setUpdateId] = useState('');
   const [permissions, setPermissions] = useState<string[]>([]);
+  const [assetsType, setAssetsType] = useState([]);
+
+  // 资产类型的权限定义
+  const getAssetsType = () => {
+    service.getAssetsType('device').then((res) => {
+      if (res.status === 200) {
+        setAssetsType(
+          res.result.map((item: any) => ({
+            label: item.name,
+            value: item.id,
+            disabled: item.id === 'read',
+          })),
+        );
+      } else {
+        setAssetsType([]);
+      }
+    });
+  };
 
   useEffect(() => {
     if (AssetsModel.tabsIndex === ASSETS_TABS_ENUM.Device && actionRef.current) {
+      getAssetsType();
       actionRef.current.reload();
     }
 
@@ -275,12 +294,44 @@ export default observer((props: { parentId: string }) => {
     Models.unBindKeys = [];
   }, [props.parentId]);
 
+  const menu = (
+    <Menu>
+      <Menu.Item key={'1'}>
+        <PermissionButton
+          icon={<DisconnectOutlined />}
+          key="unBind"
+          onClick={handleUnBind}
+          isPermission={permission.bind}
+        >
+          {intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '批量解绑',
+          })}
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key={'2'}>
+        <PermissionButton
+          icon={<EditOutlined />}
+          key="update"
+          isPermission={permission.assert}
+          onClick={() => {
+            setUpdateId('');
+            setUpdateVisible(true);
+          }}
+        >
+          批量编辑
+        </PermissionButton>
+      </Menu.Item>
+    </Menu>
+  );
+
   return (
     <div className="content">
       {Models.bind && (
         <Bind
           visible={Models.bind}
           onCancel={closeModal}
+          assetsType={assetsType}
           reload={() => actionRef.current?.reload()}
           parentId={props.parentId}
         />
@@ -458,29 +509,9 @@ export default observer((props: { parentId: string }) => {
               defaultMessage: '资产分配',
             })}
           </PermissionButton>,
-          <PermissionButton
-            icon={<DisconnectOutlined />}
-            key="unBind"
-            popConfirm={{
-              title: intl.formatMessage({
-                id: 'pages.system.role.option.unBindUser',
-                defaultMessage: '是否批量解除绑定',
-              }),
-              onConfirm: handleUnBind,
-            }}
-            tooltip={{
-              title: intl.formatMessage({
-                id: 'pages.system.role.option.unBindUser',
-                defaultMessage: '批量解绑',
-              }),
-            }}
-            isPermission={permission.bind}
-          >
-            {intl.formatMessage({
-              id: 'pages.system.role.option.unBindUser',
-              defaultMessage: '批量解绑',
-            })}
-          </PermissionButton>,
+          <Dropdown overlay={menu} placement="bottom">
+            <Button>批量操作</Button>
+          </Dropdown>,
         ]}
       />
     </div>

+ 11 - 0
src/pages/system/Department/Assets/index.less

@@ -3,3 +3,14 @@
     padding-left: 0 !important;
   }
 }
+.assets-bind-tip {
+  padding: 12px;
+  background-color: #f6f6f6;
+}
+
+.assets-bind-switch {
+  margin: 16px 0;
+  span {
+    padding-right: 12px;
+  }
+}

+ 135 - 29
src/pages/system/Department/Assets/product/bind.tsx

@@ -1,23 +1,26 @@
 // 资产-产品分类-绑定
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { service } from './index';
-import { Badge, message, Modal, Space } from 'antd';
+import { Badge, Checkbox, message, Modal, Space, Switch } from 'antd';
 import Models from './model';
 import { useEffect, useRef, useState } from 'react';
 import { observer } from '@formily/react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import PermissionModal from '@/pages/system/Department/Assets/permissionModal';
 import type { ProductItem } from '@/pages/system/Department/typings';
 import SearchComponent from '@/components/SearchComponent';
 import { ExtraProductCard } from '@/components/ProTableCard/CardItems/product';
 import { ProTableCard } from '@/components';
 import { AssetsModel } from '@/pages/system/Department/Assets';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import { Store } from 'jetlinks-store';
+import { onlyMessage } from '@/utils/util';
 
 interface Props {
   reload: () => void;
   visible: boolean;
   onCancel: () => void;
   parentId: string;
+  assetsType: string[];
 }
 
 const status = {
@@ -29,8 +32,10 @@ const Bind = observer((props: Props) => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
   const [searchParam, setSearchParam] = useState({});
-  const saveRef = useRef<{ saveData: Function }>();
   const [loading, setLoading] = useState(false);
+  const [checkAssets, setCheckAssets] = useState<string[]>(['read']);
+  const [isAll, setIsAll] = useState<boolean>(false);
+  const bindChecks = useRef(new Map());
 
   const columns: ProColumns<ProductItem>[] = [
     {
@@ -89,9 +94,27 @@ const Bind = observer((props: Props) => {
   ];
 
   const handleBind = () => {
-    if (Models.bindKeys.length && saveRef.current) {
+    if (Models.bindKeys.length) {
       setLoading(true);
-      saveRef.current?.saveData();
+      const _data: any[] = [];
+      bindChecks.current.forEach((value, key) => {
+        _data.push({
+          targetType: 'org',
+          targetId: props.parentId,
+          assetType: 'product',
+          assetIdList: [key],
+          permission: value,
+        });
+      });
+      service.bind('product', _data).subscribe({
+        next: () => onlyMessage('操作成功'),
+        error: () => onlyMessage('操作失败', 'error'),
+        complete: () => {
+          setLoading(false);
+          props.reload();
+          props.onCancel();
+        },
+      });
     } else {
       message.warn('请先勾选数据');
       // props.onCancel();
@@ -123,18 +146,54 @@ const Bind = observer((props: Props) => {
         title="绑定"
         confirmLoading={loading}
       >
-        <PermissionModal
-          type="product"
-          parentId={props.parentId}
-          bindKeys={Models.bindKeys}
-          ref={saveRef}
-          onCancel={(type) => {
-            if (type) {
-              props.reload();
-              props.onCancel();
-            }
-          }}
-        />
+        <div className={'assets-bind-tip'}>
+          <ExclamationCircleOutlined style={{ paddingRight: 8 }} />
+          只能分配有“共享”权限的资产数据
+        </div>
+        <div className={'assets-bind-switch'}>
+          <span>批量配置</span>
+          <Switch
+            checkedChildren="开"
+            unCheckedChildren="关"
+            onChange={(e) => {
+              setIsAll(e);
+              Store.set('assets-product', {
+                isAll: e,
+                assets: checkAssets,
+                bindKeys: Models.bindKeys,
+              });
+            }}
+          />
+        </div>
+        <div>
+          <Checkbox.Group
+            options={props.assetsType}
+            value={checkAssets}
+            onChange={(e: any) => {
+              Store.set('assets-product', {
+                isAll: isAll,
+                assets: e,
+                bindKeys: Models.bindKeys,
+              });
+              bindChecks.current.forEach((_, key) => {
+                bindChecks.current.set(key, e);
+              });
+              setCheckAssets(e);
+            }}
+          />
+        </div>
+        {/*<PermissionModal*/}
+        {/*  type="product"*/}
+        {/*  parentId={props.parentId}*/}
+        {/*  bindKeys={Models.bindKeys}*/}
+        {/*  ref={saveRef}*/}
+        {/*  onCancel={(type) => {*/}
+        {/*    if (type) {*/}
+        {/*      props.reload();*/}
+        {/*      props.onCancel();*/}
+        {/*    }*/}
+        {/*  }}*/}
+        {/*/>*/}
         <SearchComponent
           field={columns}
           model={'simple'}
@@ -206,9 +265,16 @@ const Bind = observer((props: Props) => {
                   Models.bindKeys = [
                     ...new Set([...Models.bindKeys, ...getSelectedRowsKey(selectedRows)]),
                   ];
+                  bindChecks.current.set(record.id, checkAssets);
                 } else {
                   Models.bindKeys = Models.bindKeys.filter((item) => item !== record.id);
+                  bindChecks.current.delete(record.id);
                 }
+                Store.set('assets-product', {
+                  isAll: isAll,
+                  assets: checkAssets,
+                  bindKeys: Models.bindKeys,
+                });
                 AssetsModel.params = {
                   productId: Models.bindKeys,
                 };
@@ -229,21 +295,61 @@ const Bind = observer((props: Props) => {
                 };
               },
             }}
-            request={(params) =>
-              service.queryProductList({
+            request={async (params) => {
+              const resp: any = await service.queryProductList({
                 ...params,
                 sorts: [{ name: 'createTime', order: 'desc' }],
-              })
-            }
+              });
+              let newData = [];
+              if (resp.status === 200) {
+                newData = [...resp.result.data];
+                const assetsResp = await service.getBindingAssets(
+                  'product',
+                  resp.result.data.map((item: any) => item.id),
+                );
+                if (assetsResp.status === 200) {
+                  newData = newData.map((item: any) => {
+                    const assetsItem = assetsResp.result.find(
+                      (aItem: any) => (aItem.assetId = item.id),
+                    );
+                    console.log(assetsItem);
+                    return {
+                      ...item,
+                      ...assetsItem,
+                    };
+                  });
+                }
+              }
+
+              return {
+                code: resp.message,
+                result: {
+                  data: newData as ProductItem[],
+                  pageIndex: 0,
+                  pageSize: 0,
+                  total: 0,
+                },
+                status: resp.status,
+              };
+            }}
             params={searchParam}
-            cardRender={(record) => (
-              <ExtraProductCard
-                showBindBtn={false}
-                showTool={false}
-                {...record}
-                cardType={'bind'}
-              />
-            )}
+            cardRender={(record) => {
+              return (
+                <ExtraProductCard
+                  showBindBtn={false}
+                  showTool={false}
+                  {...record}
+                  assetsOptions={props.assetsType}
+                  allAssets={checkAssets}
+                  cardType={'bind'}
+                  onAssetsChange={(e) => {
+                    if (bindChecks.current.has(record.id)) {
+                      bindChecks.current.set(record.id, e);
+                    }
+                  }}
+                />
+              );
+            }}
             height={'none'}
           />
         </div>

+ 70 - 24
src/pages/system/Department/Assets/product/index.tsx

@@ -1,7 +1,7 @@
 // 资产分配-产品分类
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { Badge, Modal, Space } from 'antd';
+import { Badge, Button, Dropdown, Menu, Modal, Space } from 'antd';
 import { useEffect, useRef, useState } from 'react';
 import { observer } from '@formily/react';
 import type { ProductItem } from '@/pages/system/Department/typings';
@@ -36,9 +36,28 @@ export default observer((props: { parentId: string }) => {
   const [updateVisible, setUpdateVisible] = useState(false);
   const [updateId, setUpdateId] = useState('');
   const [permissions, setPermissions] = useState<string[]>([]);
+  const [assetsType, setAssetsType] = useState([]);
+
+  // 资产类型的权限定义
+  const getAssetsType = () => {
+    service.getAssetsType('product').then((res) => {
+      if (res.status === 200) {
+        setAssetsType(
+          res.result.map((item: any) => ({
+            label: item.name,
+            value: item.id,
+            disabled: item.id === 'read',
+          })),
+        );
+      } else {
+        setAssetsType([]);
+      }
+    });
+  };
 
   useEffect(() => {
     if (AssetsModel.tabsIndex === ASSETS_TABS_ENUM.Product && actionRef.current) {
+      getAssetsType();
       actionRef.current.reload();
     }
   }, [AssetsModel.tabsIndex]);
@@ -258,12 +277,59 @@ export default observer((props: { parentId: string }) => {
     });
   };
 
+  const menu = (
+    <Menu>
+      <Menu.Item key={'1'}>
+        <PermissionButton
+          icon={<DisconnectOutlined />}
+          key="unBind"
+          popConfirm={{
+            title: intl.formatMessage({
+              id: 'pages.system.role.option.unBindUser',
+              defaultMessage: '是否批量解除绑定',
+            }),
+            onConfirm: handleUnBind,
+          }}
+          isPermission={permission.bind}
+        >
+          {intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '批量解绑',
+          })}
+        </PermissionButton>
+      </Menu.Item>
+      <Menu.Item key={'2'}>
+        <PermissionButton
+          icon={<EditOutlined />}
+          key="update"
+          isPermission={permission.assert}
+          popConfirm={{
+            title: intl.formatMessage({
+              id: 'pages.system.role.option.unBindUser',
+              defaultMessage: '是否批量解除绑定',
+            }),
+            onConfirm: () => {
+              if (Models.unBindKeys.length) {
+                setUpdateVisible(true);
+              } else {
+                onlyMessage('请勾选需要解绑的数据', 'warning');
+              }
+            },
+          }}
+        >
+          批量编辑
+        </PermissionButton>
+      </Menu.Item>
+    </Menu>
+  );
+
   return (
     <div className="content">
       {Models.bind && (
         <Bind
           visible={Models.bind}
           onCancel={closeModal}
+          assetsType={assetsType}
           reload={() => {
             setDeviceVisible(true);
             actionRef.current?.reload();
@@ -461,29 +527,9 @@ export default observer((props: { parentId: string }) => {
               defaultMessage: '资产分配',
             })}
           </PermissionButton>,
-          <PermissionButton
-            icon={<DisconnectOutlined />}
-            key="unBind"
-            popConfirm={{
-              title: intl.formatMessage({
-                id: 'pages.system.role.option.unBindUser',
-                defaultMessage: '是否批量解除绑定',
-              }),
-              onConfirm: handleUnBind,
-            }}
-            tooltip={{
-              title: intl.formatMessage({
-                id: 'pages.system.role.option.unBindUser',
-                defaultMessage: '批量解绑',
-              }),
-            }}
-            isPermission={permission.bind}
-          >
-            {intl.formatMessage({
-              id: 'pages.system.role.option.unBindUser',
-              defaultMessage: '批量解绑',
-            })}
-          </PermissionButton>,
+          <Dropdown overlay={menu} placement="bottom">
+            <Button>批量操作</Button>
+          </Dropdown>,
           // <Popconfirm
           //   title={intl.formatMessage({
           //     id: 'pages.system.role.option.unBindUser',

+ 6 - 0
src/pages/system/Department/Assets/service.ts

@@ -112,6 +112,12 @@ class Service<T> extends BaseService<T> {
       method: 'PUT',
       data: permission,
     });
+
+  getAssetsType = (type: string) =>
+    request(`${this.uri}/bindings/${type}/permissions`, { method: 'GET' });
+
+  getBindingAssets = (type: string, data: any[]) =>
+    request(`${this.uri}/bindings/${type}`, { method: 'POST', data });
 }
 
 export default Service;