猪齿鱼 Choerodon UI使用

这个UI使用比较少,只提供一个参照。记录使用中遇到的部分常用场景,以及ds的使用。确实不咋好用.......

文档链接地址:

https://open.hand-china.com/choerodon-ui/zh/procmp/data-display/table#%E4%BD%95%E6%97%B6%E4%BD%BF%E7%94%A8

一、DS使用

1、ds基础使用

2、ds中刷新下拉接口

javascript 复制代码
formDataDs.getField('demo')?.options?.query();
// 修改下拉接口的值?
ds.getField('demo')?.options?.setQueryParameter('custCode', custCode);
ds.getField('demo')?.options?.setQueryParameter('sybCode', belongIndustry);

3、ds中添加正则校验 validator

javascript 复制代码
 {
      name: 'cashAndBill',
      type: FieldType.string,
      label: languageConfig('register.label.cashAndBill', '现金余额(含票据)'),
      placeholder: languageConfig(
        'register.placeholder.cashAndBill',
        '请输入现金余额(含票据)',
      ),
      validator: value => {
        if (!isvalidRegexNumber.test(value)) {
          return languageConfig(
            'tips.regexHintNumber',
            '小数点前最多8位,小数点后最多两位',
          );
        }
        return true;
      },
      computedProps: {
        required() {
          return pageType ? false : true;
        },
      },
    },

4、ds中必填校验 required

javascript 复制代码
{
      name: 'overseasChannel',
      type: FieldType.string,
      label: languageConfig('register.label.overseasChannel', '是否海外渠道'),
      lookupCode: 'LTC_YES_OR_NO',
    },
    {
      name: 'currency',
      type: FieldType.string,
      label: languageConfig('register.label.currency', '主要交易币种'),
      placeholder: languageConfig('register.placeholder.select', '请选择'),
      lookupCode: 'PRM-CR-CURRENCY',
      computedProps: {
        required({ record }) {
          return record.get('overseasChannel') === 'Y';
        },
      },
    },

5、页面直接获取ds中 options下拉接口

javascript 复制代码
const getFetch = async () => {
    await formDataDs.getField('demo')?.options?.query();
    let options = formDataDs.getField('demo')?.options?.toData();
 };
javascript 复制代码
  const options = formDs.current?.getField('otherBrandCode')?.options?.toData();

6、ds中根据接口获取下拉 options

javascript 复制代码
{
      name: 'authRegion',
      type: FieldType.string,
      label: languageConfig('register.label.authRegion', '授权区域'),
      placeholder: languageConfig('register.placeholder.select', '请选择'),
      textField: 'name',
      valueField: 'code',
      options: cityOptionsDs,
      required: true,
    },
/**
- 获取城市列表
*/
export const cityOptionsDs = new DataSet({
  autoQuery: true,
  idField: 'code',
  fields: [
    { name: 'code', type: FieldType.string },
    { name: 'name', type: FieldType.string },
  ],
  paging: false,
  transport: {
    read: () => {
      return {
        ...getCountryListApi(),
      };
    },
  },
});

7、ds中options的获取下拉出现勾选时一闪一闪

(1)一闪一闪效果图

(2)代码实现

javascript 复制代码
/** 授权产品 */
const authProductCache = new Map();

const getAuthProductOptions = (divisionCode: string, provinceName: string) => {
  const cacheKey = `${divisionCode}-${provinceName}`;

  if (authProductCache.has(cacheKey)) {
    return authProductCache.get(cacheKey);
  }

  const ds = categoryOptionDs(divisionCode, provinceName);
  authProductCache.set(cacheKey, ds);
  return ds;
};

// ds    
{
      name: 'applyAuthProductId',
      type: FieldType.string,
      label: languageConfig(
        'register.general.label.applyAuthProductId',
        '拟申请授权产品',
      ),
      placeholder: PLACEHOLDER_PLEASE_SELECT(),
      textField: 'authCategoryName',
      valueField: 'id',
      options: new DataSet({}),
      // computedProps: {
      //   options: ({ record }) => {
      //     const { divisionCode, province, city } = record.toData();

      //     const provinceName = province ? province : city;

      //     if (divisionCode) {
      //       return categoryOptionDs(divisionCode, provinceName);
      //     }
      //     return new DataSet({});
      //   },
      //   required() {
      //     return pageType ? false : true;
      //   },
      // },
      dynamicProps: {
        options: ({ record }) => {
          const { divisionCode, province, city } = record.toData();
          const provinceName = province || city;

          if (divisionCode) {
            return getAuthProductOptions(divisionCode, provinceName);
          }
          return new DataSet({});
        },
        required: () => !pageType,
      },
    },

(3)dynamicProps 和 computedProps 的区别

dynamicProps

  • 触发时机

    在字段的 每次渲染时 动态计算属性(类似 React 的每次 render 都会执行)。

  • 特点

    • 适合需要 实时响应 变化的场景(如表单交互、依赖字段变化)。

    • 每次组件渲染时都会重新计算,保证数据最新。

    • 性能开销稍高(因频繁执行)。

  • 典型场景

    • 动态加载选项(如下拉框的 options 依赖其他字段值)。

    • 表单字段的联动(如显示/隐藏、禁用状态)。

javascript 复制代码
dynamicProps: {
  options: ({ record }) => {
    const value = record.get('dependencyField');
    return value ? fetchOptions(value) : new DataSet({});
  },
  disabled: ({ record }) => record.get('status') === 'disabled',
}

computedProps

  • 触发时机

    仅在 依赖的字段值发生变化时 计算(类似 Vue 的计算属性)。

  • 特点

    • 适合 依赖项明确 且计算较重的场景。

    • 只有依赖的字段变化时才会触发,性能更优。

    • 不支持响应式更新(除非依赖的字段变化)。

  • 典型场景

    • 计算派生数据(如汇总、格式化)。

    • 条件性校验规则(如 required 依赖其他字段)。

javascript 复制代码
computedProps: {
  options: ({ record }) => {
    // 仅在 divisionCode 或 province 变化时重新计算
    const divisionCode = record.get('divisionCode');
    const province = record.get('province');
    return divisionCode ? fetchOptions(divisionCode, province) : new DataSet({});
  },
  required: ({ record }) => record.get('type') === 'VIP',
}
场景 推荐使用
需要实时响应变化(如联动下拉框) dynamicProps
依赖字段变化时才需更新(如计算汇总值) computedProps
高频更新的属性(如禁用状态) dynamicProps
性能敏感且依赖项明确 computedProps

8、ds中动态配置'值集'

javascript 复制代码
 {
      name: 'authProduct',
      type: FieldType.string,
      label: languageConfig('register.label.authProduct', '授权产品'),
      placeholder: languageConfig('register.placeholder.select', '请选择'),
      // PRM_MD_OVERSEAS_AUTHPRODUCT_DEVELOPED: 海外事业部(发达)值集:通用、机器人、工程机械
      // PRM_MD_OVERSEAS_AUTHPRODUCT:海外事业部(新兴)值集:通用、机器人、电梯
      dynamicProps: {
        lookupCode: ({ record }) => {
          const divisionCode = record.get('divisionCode');
       // 根据'事业部'显示对应:值集下拉
          return divisionCode === 'D000070'
            ? 'PRM_MD_OVERSEAS_AUTHPRODUCT_DEVELOPED'
            : 'PRM_MD_OVERSEAS_AUTHPRODUCT';
        },
      },
      required: true,
    },

9、ds中详情的时候select中数据用tag标签显示

(1)效果图

(2)html代码

javascript 复制代码
import React, { useEffect, useState } from 'react';
import formatterCollections from 'hzero-front/lib/utils/intl/formatterCollections';
import {
  commonModelPrompt,
  prmMdTemCode,
} from '@/language/language';
import '@/assets/styles/c7n.less';
import { Form, Output } from 'choerodon-ui/pro';
import { toJS } from 'mobx';
import { labelWidth } from '@/utils/utils';
import { LabelLayout } from 'choerodon-ui/pro/lib/form/enum';
import ItemGroup from 'choerodon-ui/pro/lib/form/ItemGroup';

const BaseInfo = (props: any) => {
  const { formDataDs } = props;

  const [show, setShow] = useState<boolean>(true); // 管理收缩状态
  const [productTags, setProductTags] = useState<React.ReactNode>(null);

  useEffect(() => {
    const fetchProducts = async () => {
      const record = formDataDs.current;
      if (!record) return;

      const applyAuthProductId = toJS(record.get('applyAuthProductId'));
      const divisionCode = record.get('divisionCode');

      if (applyAuthProductId && divisionCode) {
        // 1、获取当前计算后的 options DataSet
        const optionsDs = record.getField('applyAuthProductId')?.options;

        if (optionsDs) {
          // 2、执行查询并等待完成
          await optionsDs.query({ divisionCode });

          // 3、获取数据
          const options = optionsDs.toData() || [];
          console.log('最新选项:', options);

          // 4、解析数据,匹配出数据,显示标签,未匹配出就显示输入框
          const result = applyAuthProductId.reduce((acc, id) => {
            const item = options.find(o => o.id === id);
            return item ? [...acc, item.authCategoryName] : acc;
          }, []);

          // 5、有数据显示内容,无数据显示输入框
          setProductTags(
            result.length > 0 ? (
              <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                {result.map(item => (
                  <span
                    key={item}
                    style={{
                      background: '#E0F7FE',
                      borderRadius: 4,
                      padding: '0 8px',
                      lineHeight: '20px',
                    }}
                  >
                    {item}
                  </span>
                ))}
              </div>
            ) : (
              <Output name="applyAuthProductId" />
            ),
          );
          return;
        }
      }
      setProductTags(<Output name="applyAuthProductId" />);
    };

    fetchProducts();
  }, [formDataDs]);

  return (
    <>
      <Form
        dataSet={formDataDs}
        columns={4}
        labelWidth={labelWidth}
        labelLayout={LabelLayout.vertical}
      >
        <Output name="divisionName" disabled />
        <ItemGroup name="applyAuthProductId">
          <div>{productTags}</div>
        </ItemGroup>
      </Form>
    </>
  );
};

export default formatterCollections({
  code: [prmMdTemCode, commonModelPrompt],
})(BaseInfo);

(3)store代码

javascript 复制代码
export const generalFormList = (pageType?: string) => {
  return [
    {
      name: 'applyAuthProductId',
      type: FieldType.string,
      label: languageConfig(
        'register.label.applyAuthProductId',
        '拟申请授权产品',
      ),
      placeholder: languageConfig('register.placeholder.select', '请选择'),
      textField: 'authCategoryName',
      valueField: 'id',
      options: new DataSet({}),
      computedProps: {
        options: ({ record }) => {
          if (record.get('divisionCode')) {
            return categoryOptionDs(record.get('divisionCode'));
          }
          return new DataSet({});
        },
        required() {
          return pageType ? false : true;
        },
      },
    },
  ];
};

/** 产品授权类别(生效) */
const categoryOptionDs = (divisionCode: string) => {
  return new DataSet({
    autoQuery: true,
    paging: false,
    idField: 'code',
    childrenField: 'directories',
    fields: [
      { name: 'id', type: FieldType.string },
      { name: 'authCategoryName', type: FieldType.string },
    ],
    transport: {
      read: (config: AxiosRequestConfig): AxiosRequestConfig => {
        return {
          ...getCategorySelectListApi({ divisionCode }),
          ...config,
        };
      },
    },
  });
};

10、动态监听ds

不用ds自带的监听,可用下面方法

javascript 复制代码
const [processCategory, setProcessCategory] = useState('');

  useEffect(() => {
    const handler = () => {
      const newValue = formDataDs.current?.get('processCategory');
      console.log('newValue', newValue);
      if (newValue !== processCategory) {
        setProcessCategory(newValue);
      }
    };
    formDataDs.addEventListener('update', handler);
    return () => formDataDs.removeEventListener('update', handler);
  }, [formDataDs, processCategory]);

二、table ds

1、table 添加查询 loading

(1)代码

javascript 复制代码
 /** 查询列表 */
  const querySearchList = async (data: {
    channelName: string;
    status: string;
  }) => {
    if (tableDs.status === 'loading') return;

    try {
      const queryParameters = {
        channelName: data.channelName,
        status: data.status,
      };

      Object.entries(queryParameters).forEach(([key, value]): void => {
        tableDs.setQueryParameter(key, value);
      });

      await tableDs.query();
    } catch (error) {
      message.error(API_RESPONSE_FAILED, 1.5, 'top');
      return false;
    } finally {
      console.log(COMMON_PROCESS_COMPLETE);
    }
  };

  const onChange = async ({ record }) => {
    querySearchList(record.toData());
  };

  /** 查询 ds */
  const queryListDs = useMemo(() => new DataSet(queryList(onChange)), []);
  const queryConfig = [
    { name: 'channelName', dom: TextField, clearButton: true },
    { name: 'status', dom: Select, clearButton: true },
  ];
        <QueryBar
          onShowMore={onShowMore}
          onQuery={querySearchList}
          queryBarDs={queryListDs}
          queryConfig={queryConfig}
        />

2、select 查询条件,模糊查询调用接口

(1)效果图

(2)代码

javascript 复制代码
// 查询input
        {
          name: 'channelName',
          dom: Select,
          clearButton: true,
          searchable: true,
          searchMatcher: 'channelName',
        },

ds代码
export const queryList = (onChange): DataSetProps => {
  return {
    autoCreate: true,
    fields: [
      {
        name: 'channelName',
        type: FieldType.string,
        placeholder: languageConfig('auth.channelName', '渠道名称'),
        textField: 'channelName',
        valueField: 'channelCode',
        options: new DataSet({
          autoQuery: true,
          // paging: false,
          queryFields: [
            {
              name: 'channelName',
              type: FieldType.string,
              label: '渠道名称',
            },
          ],
          transport: {
            read: (config: AxiosRequestConfig): AxiosRequestConfig => {
              const { params } = config;
              return {
                ...config,
                url: ${prefixPath}/common/plat/interface/getByChannelName,
                method: 'GET',
                params: {
                  page: params.page,
                  size: params.size,
                  channelName: params.channelName || '', // 获取用户输入的值
                },
              };
            },
          },
        }),
      },

    ],
    events: {
      update: onChange,
    },
  };
};

3、table 中行编辑时,设置编辑的输入框为inputNumber

(1)效果图

(2)代码

javascript 复制代码
 const columns = useMemo(() => {
    return [
      {
        name: 'currentEmployeeCount',
        // editor: type === 'view' ? false : true,
        editor:
          type === 'view' ? (
            false
          ) : (
            <NumberField precision={0} min={0} step={1} defaultValue={0} />
          ),
      },
      {
        name: 'employeeOverThreeYears',
        editor:
          type === 'view' ? (
            false
          ) : (
            <NumberField precision={0} min={0} step={1} defaultValue={0} />
          ),
      },
      {
        name: 'salesPersonCount',
        editor:
          type === 'view' ? (
            false
          ) : (
            <NumberField precision={0} min={0} step={1} defaultValue={0} />
          ),
      },
      {
        name: 'technicalPersonCount',
        editor:
          type === 'view' ? (
            false
          ) : (
            <NumberField precision={0} min={0} step={1} defaultValue={0} />
          ),
      },
      {
        name: 'certifiedEngineerCount',
        editor:
          type === 'view' ? (
            false
          ) : (
            <NumberField precision={0} min={0} step={1} defaultValue={0} />
          ),
      },
    ];
  }, [type]);

4、table的头部,文字过多换行显示

注:关键修改点: headerRowHeight={'auto'}

(1)原先效果图

(2)效果图

(3)代码

javascript 复制代码
  const tableDs = useMemo(
    () =>
      new DataSet({
        ...tableList(),
        events: {
          load: () => {
            // console.log('tableDs.toData()', tableDs.toData());
            handleUploadTable();
          },
        },
      }),
    [],
  );
  const columns = useMemo(() => {
    return [
      { name: 'brandName', width: '10%', align: 'center' },
      { name: 'productCategory', width: '10%', align: 'center' },
      {
        name: 'salesRevenue',
        width: '17%',
        align: 'center',
        header: () => (
          <div style={{ whiteSpace: 'normal', lineHeight: '16px' }}>
            {languageConfig(
              'register.general.business.label.salesRevenue',
              '品牌产品(贸易)销售营业额(万元)',
            )}
          </div>
        ),
      },
      {
        name: 'integratedRevenue',
        width: '17%',
        align: 'center',
        header: () => (
          <div style={{ whiteSpace: 'normal', lineHeight: '16px' }}>
            {languageConfig(
              'register.general.business.label.integratedRevenue',
              '品牌产品系统集成营业额(万元)',
            )}
          </div>
        ),
      },
      {
        name: 'totalRevenue',
        width: '17%',
        align: 'center',
        header: () => (
          <div style={{ whiteSpace: 'normal', lineHeight: '16px' }}>
            {languageConfig(
              'register.general.business.label.totalRevenue',
              '品牌总营业额(万元)',
            )}
          </div>
        ),
      },
      {
        name: 'brandRevenueRatio',
        width: '17%',
        align: 'center',
        header: () => (
          <div style={{ whiteSpace: 'normal', lineHeight: '16px' }}>
            {languageConfig(
              'register.general.business.label.brandRevenueRatio',
              '该品牌占公司总营业额比例',
            )}
          </div>
        ),
        renderer: ({ value }) => {
          return <div>{value}%</div>;
        },
      },
      {
        header: TABLE_COLUMN_OPERATION,
        lock: ColumnLock.right,
        command: ({ record }) => {
          return [
            <Button
              key="edit"
              funcType={FuncType.link}
              onClick={() => {
                setVisible(true);
                setRow(record.toData() || {});
              }}
            >
              {COMMON_EDIT}
            </Button>,
            <Popconfirm
              key="delete"
              title={languageConfig(
                'tips.confirmDeleteNowDate',
                '确认是否删除当前数据',
              )}
              okText={STATUS_YES}
              cancelText={STATUS_NO}
              onConfirm={async () => {
                await handleDelete(record);
              }}
            >
              <Button funcType={FuncType.link} color={ButtonColor.red}>
                {COMMON_DELETE}
              </Button>
            </Popconfirm>,
          ];
        },
      },
    ];
  }, []);

  <Table
    dataSet={tableDs}
    columns={columns}
    selectionMode={SelectionMode.click}
    pagination={false}
    headerRowHeight={'auto'}
    renderEmpty={() => {
      return <div>{NO_DATA}</div>;
    }}
    footer={() => {
      return tableDs.toData().length > 0 ? (
        <>
          {  <Table
    dataSet={tableDs}
    columns={columns}
    selectionMode={SelectionMode.click}
    buttons={tableButtons}
    pagination={false}
    headerRowHeight={'auto'}
    renderEmpty={() => {
      return <div>{NO_DATA}</div>;
    }}
    footer={() => {
      return tableDs.toData().length > 0 ? (
        <>
          {renderFooterTotal(
            totalSalesRevenue,
            totalIntegratedRevenue,
            totalRevenue,
          )}
        </>
      ) : (
        <></>
      );
    }}
    style={{ marginBottom: '16px' }}
  />(
            totalSalesRevenue,
            totalIntegratedRevenue,
            totalRevenue,
          )}
        </>
      ) : (
        <></>
      );
    }}
    style={{ marginBottom: '16px' }}
  />
    

/** store.ts */
export const tableList = () => {
  return {
    autoQuery: true,
    fields: [
      {
        name: 'brandName',
        type: FieldType.string,
        label: languageConfig(
          'register.general.business.label.brandName',
          '经营品牌',
        ),
      },
      {
        name: 'brandCode',
        type: FieldType.string,
      },
      {
        name: 'productCategory',
        type: FieldType.string,
        label: languageConfig(
          'register.general.business.label.productCategory',
          '主要产品类别',
        ),
      },
      {
        name: 'salesRevenue',
        type: FieldType.string,
        label: languageConfig(
          'register.general.business.label.salesRevenue',
          '品牌产品(贸易)销售营业额(万元)',
        ),
      },
      {
        name: 'integratedRevenue',
        type: FieldType.string,
        label: languageConfig(
          'register.general.business.label.integratedRevenue',
          '品牌产品系统集成营业额(万元)',
        ),
      },
      {
        name: 'totalRevenue',
        type: FieldType.string,
        label: languageConfig(
          'register.general.business.label.totalRevenue',
          '品牌总营业额(万元)',
        ),
      },
      {
        name: 'brandRevenueRatio',
        type: FieldType.string,
        label: languageConfig(
          'register.general.business.label.brandRevenueRatio',
          '该品牌占公司总营业额比例',
        ),
      },
    ],
  };
};

/** hook */
import React from 'react';
import styles from '@/pages/register/detail/main.less';
import { languageConfig } from '@/language/language';

/** 表格:footer 合计 */
export const renderFooterTotal = (
  totalSalesRevenue,
  totalIntegratedRevenue,
  totalRevenue,
) => {
  return (
    <>
      <div className={styles.registerDetail_content_business}>
        <div className={styles.registerDetail_content_business_title}>
          {languageConfig('register.general.business.label.total', '合计')}:
        </div>
        {/* 品牌产品(贸易)销售营业额(万元) */}
        <div className={styles.registerDetail_content_business_item}>
          {totalSalesRevenue}
        </div>
        {/* 品牌产品系统集成营业额(万元) */}
        <div className={styles.registerDetail_content_business_item}>
          {totalIntegratedRevenue}
        </div>
        {/* 品牌总营业额(万元) */}
        <div className={styles.registerDetail_content_business_item}>
          {totalRevenue}
        </div>
        <div className={styles.registerDetail_content_business_item}></div>
      </div>
    </>
  );
};

/** styles */
    &_business {
      width: 100%;
      background: #f5faff;
      display: flex;
      line-height: 36px;
      &_title {
        // flex: 1 1 auto;
        width: 20%;
        text-align: right;
        color: #788295;
        padding-right: 40px;
      }
      &_item {
        width: 17%;
        color: #222222;
        font-weight: 700;
        text-align: center;
      }
    }

5、table勾选,添加禁用条件

(1)table添加属性

javascript 复制代码
<Table
  dataSet={tableDs}
  columns={columns}
  selectedHighLightRow
  autoHeight={{ type: TableAutoHeightType.maxHeight, diff: 30 }}
  rowHeight={36}
  buttons={tableButtons}
  // selectionMode={SelectionMode.click} // 这个是隐藏勾选功能
/>

import { FieldType } from 'choerodon-ui/dataset/data-set/enum';
import { languageConfig } from '@/language/language';
import { DataSetProps } from 'choerodon-ui/dataset/data-set/DataSet';

/** ds table */
export const tableList = () => {
  return {
    autoQuery: true,
    fields: [
      {
        name: 'registerFlowNo',
        type: FieldType.string,
        label: languageConfig('register.label.registerFlowNo', '申请编号'),
      },
      {
        name: 'status',
        type: FieldType.string,
        label: languageConfig('register.label.status', '状态'),
        lookupCode: 'PRM_MD_STATUS',
      },
    ],
    transport: {
      read: config => {
        const params = {
          page: config.params.page,
          size: config.params.size,
          ...config?.data,
        };
        return {
          ...getRegisterList(),
        };
      },
    },
    // 禁用
    record: {
      dynamicProps: {
        selectable: record => {
          return !record.get('remark')
        },
      },
    },
  };
};
相关推荐
梁萌8 小时前
前端UI组件库
前端·ui
步、步、为营2 天前
.NET8 正式发布, C#12 新变化
ui·c#·.net
伽蓝_游戏2 天前
Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(7)
游戏·ui·unity·架构·c#·游戏引擎·.net
安卓开发者3 天前
Jetpack Compose for XR:构建下一代空间UI的完整指南
ui·xr
伽蓝_游戏3 天前
Unity UI的未来之路:从UGUI到UI Toolkit的架构演进与特性剖析(6)
游戏·ui·unity·架构·c#·游戏引擎·.net
笑尘pyrotechnic3 天前
自动布局视图来实现聊天室的界面
ui·ios·objective-c
小梦白3 天前
RPG增容3:尝试使用MVC结构搭建玩家升级UI(一)
游戏·ui·ue5·mvc
小妖6665 天前
Next.js 怎么使用 Chakra UI
前端·javascript·ui
唐 城5 天前
【可用有效】Axure RP 9 授权码
ui·photoshop