@cqsjjb/jjb-react-admin-component 3.2.0-rc.0 → 3.3.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,8 +12,6 @@ import {
12
12
  interface ExtraProps<T> {
13
13
  /** 标签文本 */
14
14
  label?: string;
15
- /** 标签宽度 (Col span) */
16
- labelSpan?: number;
17
15
  /** 自定义样式 */
18
16
  style?: React.CSSProperties;
19
17
  /** 受控值 */
@@ -1,12 +1,6 @@
1
1
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
2
  import React from 'react';
3
- import PropTypes from 'prop-types';
4
- import { Row, Col, Input as OriginInput, Select as OriginSelect, Cascader as OriginCascader, DatePicker as OriginDatePicker, TreeSelect as OriginTreeSelect } from 'antd';
5
- import { tools } from '@cqsjjb/jjb-common-lib';
6
- import { getAlgorithm, getPrefixCls } from '../tools/index.js';
7
- import './index.less';
8
- const algorithm = getAlgorithm();
9
- const prefixCls = getPrefixCls();
3
+ import { Input as OriginInput, Select as OriginSelect, Cascader as OriginCascader, DatePicker as OriginDatePicker, TreeSelect as OriginTreeSelect } from 'antd';
10
4
 
11
5
  /**
12
6
  * 通用包裹组件
@@ -17,8 +11,6 @@ function withLabel(WrappedComponent, extraProps = {}) {
17
11
  return function Wrapper(props) {
18
12
  const {
19
13
  label,
20
- labelSpan,
21
- style,
22
14
  onChange,
23
15
  ...restProps
24
16
  } = props;
@@ -28,23 +20,14 @@ function withLabel(WrappedComponent, extraProps = {}) {
28
20
  onChange(val === '' ? undefined : val, ...args);
29
21
  }
30
22
  };
31
- return /*#__PURE__*/React.createElement("div", {
32
- style: style,
33
- className: tools.classNames(`${prefixCls}-form-item-control-label-wrapper`, `${prefixCls}-form-item-control-label-wrapper-${algorithm}`, label && `${prefixCls}-form-item-control-label-has`)
34
- }, /*#__PURE__*/React.createElement(Row, {
35
- align: "middle"
36
- }, label && /*#__PURE__*/React.createElement(Col, {
37
- span: labelSpan,
38
- className: `${prefixCls}-form-item-control-label-span`
39
- }, label), /*#__PURE__*/React.createElement(Col, {
40
- flex: 1
41
- }, /*#__PURE__*/React.createElement(WrappedComponent, _extends({
23
+ return /*#__PURE__*/React.createElement(WrappedComponent, _extends({
42
24
  style: {
43
25
  width: '100%'
44
- }
26
+ },
27
+ prefix: label
45
28
  }, extraProps, restProps, {
46
29
  onChange: handleChange
47
- })))));
30
+ }));
48
31
  };
49
32
  }
50
33
 
@@ -8,8 +8,6 @@ import { AxiosResponse } from 'axios'
8
8
  export interface FileUploaderProps<T> {
9
9
  // 上传文件大小 单位KB 默认-5120
10
10
  size?: number;
11
- // 版本 v1-才俊java v2-杨飞java 默认-v2
12
- version?: 'v1' | 'v2';
13
11
  // 组件内部按钮属性-Ant.Button
14
12
  buttonProps?: ButtonProps;
15
13
  // Http上传携带其他数据
@@ -3,7 +3,7 @@ import MD5 from 'spark-md5';
3
3
  import React from 'react';
4
4
  import { Tooltip, Button, message } from 'antd';
5
5
  import { DeleteOutlined, PaperClipOutlined } from '@ant-design/icons';
6
- import { getAppKey, getToken, isHttpUrl, isVersion1, isVersion2 } from '../tools/index.js';
6
+ import { getAppKey, getToken, isHttpUrl } from '../tools/index.js';
7
7
  import { tools } from '@cqsjjb/jjb-common-lib';
8
8
  import './index.less';
9
9
  const {
@@ -137,14 +137,6 @@ class FileUploader extends React.Component {
137
137
  }
138
138
  }
139
139
 
140
- /**
141
- * @description 获取版本
142
- * @return {string}
143
- */
144
- get version() {
145
- return this.props.version || 'v2';
146
- }
147
-
148
140
  /**
149
141
  * @description 发起上传请求
150
142
  * @param file {File}
@@ -194,17 +186,13 @@ class FileUploader extends React.Component {
194
186
  Object.keys(this.httpConfig.data).forEach(key => form.append(key, this.httpConfig.data[key]));
195
187
  xhr.send(form);
196
188
  });
197
- if (isVersion2(this.version)) {
198
- const read = new FileReader();
199
- read.onload = e => {
200
- md5.appendBinary(e.target.result);
201
- form.append('md5', md5.end());
202
- fn();
203
- };
204
- read.readAsDataURL(file);
205
- } else {
189
+ const read = new FileReader();
190
+ read.onload = e => {
191
+ md5.appendBinary(e.target.result);
192
+ form.append('md5', md5.end());
206
193
  fn();
207
- }
194
+ };
195
+ read.readAsDataURL(file);
208
196
  });
209
197
  }
210
198
 
@@ -245,7 +233,7 @@ class FileUploader extends React.Component {
245
233
  size: maxSize = 5120
246
234
  } = this.props;
247
235
  this.verifyFileAccept(fileType, accept).then(() => this.verifyFileSize(fileSize, maxSize).then(() => this.onFormRequest(file).then(res => {
248
- const data = isVersion1(this.version) ? res.url : {
236
+ const data = {
249
237
  fileId: res.fileId || res.url,
250
238
  fileUrl: res.fileUrl || res.fileName
251
239
  };
@@ -282,7 +270,7 @@ class FileUploader extends React.Component {
282
270
  disabled: this.disabled,
283
271
  onChange: e => this.onUpload(e)
284
272
  }), /*#__PURE__*/React.createElement("span", null, children)), this.value.map((value, index) => {
285
- const $value = this.version === 'v1' ? value : value.fileUrl;
273
+ const $value = value?.fileUrl;
286
274
  return /*#__PURE__*/React.createElement("div", {
287
275
  key: index,
288
276
  className: `${prefixCls}-upload-list-text-container`
@@ -1,8 +1,6 @@
1
1
  import React from 'react';
2
2
 
3
3
  export interface ImageUploaderProps {
4
- // 版本 v1-才俊java v2-杨飞java 默认-v2
5
- version?: 'v1' | 'v2';
6
4
  // 宽高 默认-50*50
7
5
  wh?: string;
8
6
  // 是否是svg 默认-false
@@ -1,12 +1,11 @@
1
1
  import MD5 from 'spark-md5';
2
2
  import React, { useState, useRef, useCallback } from 'react';
3
- import PropTypes from 'prop-types';
4
3
  import { tools } from '@cqsjjb/jjb-common-lib';
5
4
  import { Button, Col, Image as ImageFunc, Modal, Row, message } from 'antd';
6
5
  import { PlusOutlined, EyeOutlined, DeleteOutlined, SyncOutlined, ScissorOutlined } from '@ant-design/icons';
7
6
  import ImageCropper from '../ImageCropper';
8
7
  import './index.less';
9
- import { getTenantCode, getToken, getViewAsp, isVersion1, isVersion2, getAppKey, isHttpUrl } from '../tools/index.js';
8
+ import { getToken, getViewAsp, getAppKey, isHttpUrl } from '../tools/index.js';
10
9
  const {
11
10
  toObject,
12
11
  isPromise,
@@ -23,7 +22,6 @@ const ImageUploader = props => {
23
22
  const [resource, setResource] = useState(undefined);
24
23
  const cropperRef = useRef(null);
25
24
  const fileInputRef = useRef(null);
26
- const version = props.version || 'v2';
27
25
  const algorithm = (() => {
28
26
  const value = document.documentElement.style.getPropertyValue('--saas-algorithm');
29
27
  return value ? value === '#FFF' ? 'default' : 'dark' : 'default';
@@ -36,7 +34,7 @@ const ImageUploader = props => {
36
34
  return new Promise(resolve => {
37
35
  const reader = new FileReader();
38
36
  reader.onload = e => {
39
- if (version === 'v2') md5.appendBinary(e.target.result);
37
+ md5.appendBinary(e.target.result);
40
38
  const isImage = (svg ? FILE_TYPE_POWER : FILE_TYPE).includes(file.type);
41
39
  if (isImage) {
42
40
  const image = new Image();
@@ -46,7 +44,7 @@ const ImageUploader = props => {
46
44
  resolve({
47
45
  file,
48
46
  base64,
49
- md5: isVersion2(version) ? md5.end() : null,
47
+ md5: md5.end(),
50
48
  size: file.size,
51
49
  type: file.type,
52
50
  name: file.name,
@@ -57,7 +55,7 @@ const ImageUploader = props => {
57
55
  } else {
58
56
  resolve({
59
57
  file,
60
- md5: isVersion2(version) ? md5.end() : null,
58
+ md5: md5.end(),
61
59
  size: file.size,
62
60
  type: file.type,
63
61
  name: file.name,
@@ -69,7 +67,7 @@ const ImageUploader = props => {
69
67
  };
70
68
  reader.readAsDataURL(file);
71
69
  });
72
- }, [props, version]);
70
+ }, [props]);
73
71
  const onCompression = useCallback(data => {
74
72
  const {
75
73
  compressionConfig
@@ -123,9 +121,10 @@ const ImageUploader = props => {
123
121
  const form = new FormData();
124
122
  const headers = toObject($headers);
125
123
  headers.appKey = getAppKey();
126
- if (needToken) headers.token = getToken();
127
- if (isVersion1(version)) headers.tenantCode = getTenantCode();
128
- if (isVersion2(version) && transfer.md5) form.append('md5', transfer.md5);
124
+ if (needToken) {
125
+ headers.token = getToken();
126
+ }
127
+ transfer.md5 && form.append('md5', transfer.md5);
129
128
  setLoading(true);
130
129
  xhr.open('POST', requestURL);
131
130
  Object.keys(headers).forEach(key => xhr.setRequestHeader(key, headers[key]));
@@ -135,19 +134,22 @@ const ImageUploader = props => {
135
134
  const res = parseObject(xhr.responseText);
136
135
  if (isFunction(customRequest)) {
137
136
  const fn = customRequest(res);
138
- if (isPromise(fn)) fn.then(value => onChange && onChange(value));
137
+ if (isPromise(fn)) {
138
+ fn.then(value => onChange && onChange(value));
139
+ }
139
140
  } else {
140
141
  const {
141
142
  success,
142
- respCode,
143
143
  errMessage
144
144
  } = res;
145
- if (respCode === '0000' || success) {
145
+ if (success) {
146
146
  const result = toObject(res.data);
147
- if (typeof onChange === 'undefined') console.warn('ImageUploader警告:缺少必要的onChange回调,可能无法正确显示图片!');
148
- onChange && onChange(isVersion1(version) ? result.url : {
149
- fileId: result.fileId || result.url,
150
- fileUrl: result.fileUrl || result.fileName
147
+ if (typeof onChange === 'undefined') {
148
+ console.warn('ImageUploader警告:缺少必要的onChange回调,可能无法正确显示图片!');
149
+ }
150
+ onChange && onChange({
151
+ fileId: result.fileId,
152
+ fileUrl: result.fileUrl
151
153
  });
152
154
  message.success('上传成功!');
153
155
  } else {
@@ -159,7 +161,7 @@ const ImageUploader = props => {
159
161
  form.append(fieldName, transfer.file);
160
162
  Object.keys(data).forEach(key => form.append(key, data[key]));
161
163
  xhr.send(form);
162
- }, [props, version]);
164
+ }, [props]);
163
165
  const onUpload = useCallback(e => {
164
166
  const target = e.target;
165
167
  const files = target.files;
@@ -236,7 +238,7 @@ const ImageUploader = props => {
236
238
  }, value ? /*#__PURE__*/React.createElement("div", {
237
239
  className: `${prefixCls}_image-file-uploader-preview_`
238
240
  }, value && /*#__PURE__*/React.createElement(ImageFunc, {
239
- src: version === 'v1' ? value : toObject(value).fileUrl,
241
+ src: toObject(value).fileUrl,
240
242
  preview: !disabled ? {
241
243
  visible: preview,
242
244
  onVisibleChange: () => setPreview(false)
@@ -0,0 +1,190 @@
1
+
2
+ import React, { ReactNode, ReactElement, RefAttributes } from 'react';
3
+ import { TableProps, FormItemProps } from 'antd';
4
+ import { OverTableProps } from '../Table';
5
+
6
+ /**
7
+ * 基础表单项组件的通用类型约束
8
+ * 描述:定义搜索表单中所有表单项组件(如 Select、Input、DatePicker 等)的公共属性,
9
+ * 确保不同类型的表单项都能兼容「清除、占位符、子元素」等基础能力
10
+ * @type {BaseFieldComponent}
11
+ */
12
+ type BaseFieldComponent = React.ComponentType<{
13
+ /** 是否支持清空选中值/输入值(通用表单项能力) */
14
+ allowClear?: boolean;
15
+ /** 输入/选择提示文本(提升用户体验) */
16
+ placeholder?: string;
17
+ /** 组件的子元素(如 Select.Option、DatePicker 的下拉内容等) */
18
+ children?: ReactNode;
19
+ /** 兼容组件的自定义属性(如 Select 的 options、Input 的 maxLength 等,避免类型约束过死) */
20
+ [key: string]: any;
21
+ }>;
22
+
23
+ /**
24
+ * 搜索表单单个配置项的接口
25
+ * 描述:用于定义搜索表单中每一个表单项的完整配置,包括组件类型、字段名、校验规则等,
26
+ * 组件内部会根据该配置自动生成对应的 Form.Item 结构
27
+ *
28
+ * @interface SearchFormItemConfig
29
+ */
30
+ export interface SearchFormItemConfig {
31
+ /** 表单项的核心组件(如 Select、Input、DatePicker 等,需符合 BaseFieldComponent 类型) */
32
+ field: BaseFieldComponent;
33
+ /** 表单项对应的表单字段名(与 form.validateFields() 返回的键名一致,用于数据提交) */
34
+ name: string;
35
+ /** 表单项的标签文本(显示在输入组件左侧,用于用户识别) */
36
+ label: string;
37
+ /** 传递给表单项组件的自定义属性(如 Select 的 options、Input 的 disabled 等) */
38
+ fieldProps?: Record<string, any>;
39
+ /** 传递给 antd Form.Item 的自定义属性(如 rules、hasFeedback、className 等) */
40
+ itemProps?: FormItemProps & Record<string, any>;
41
+ /** 表单项组件的子元素(如 Select.Option 列表、Radio.Group 的 Radio 按钮等) */
42
+ content?: ReactNode;
43
+ /** 是否为必填项(为 true 时,组件内部会自动添加「必填校验规则」和「*」标记) */
44
+ required?: boolean;
45
+ /** 兼容其他未明确列举的自定义属性(如 Form.Item 的 extra 提示文本等,预留扩展能力) */
46
+ [key: string]: any;
47
+ }
48
+
49
+ /**
50
+ * ListDataContainer 组件的 Props 接口
51
+ * 描述:定义通用列表容器组件的所有可配置属性,支持表格类型切换、搜索表单配置、分页初始值等,
52
+ * 通过泛型 T 关联「表格类型」和「表格属性」,确保类型一致性
53
+ *
54
+ * @interface ListDataContainerProps
55
+ * @template T - 表格类型开关(true = 使用 ProTable;false = 使用 antd 原生 Table,默认 false)
56
+ */
57
+ export interface ListDataContainerProps<T extends boolean = false> {
58
+ /**
59
+ * 表格列配置
60
+ * 描述:遵循 antd Table 组件的 columns 规范(即使使用 ProTable,列配置结构也与 antd 一致),
61
+ * 用于定义表格的列名、数据映射、渲染逻辑等
62
+ * @type {TableProps<any>['columns']}
63
+ */
64
+ columns: TableProps<any>['columns'];
65
+
66
+ /**
67
+ * 数据请求接口函数
68
+ * 描述:组件内部会调用该函数获取表格数据,需返回包含「列表数据」和「总数」的 Promise,
69
+ * 参数自动携带「分页信息」和「搜索表单值」,支持外部传入参数覆盖
70
+ *
71
+ * @param {Object} params - 请求参数(分页参数 + 搜索表单参数 + 外部覆盖参数)
72
+ * @param {number} params.page - 当前页码(由组件内部分页状态维护)
73
+ * @param {number} params.pageSize - 每页条数(由组件内部分页状态维护)
74
+ * @param {any} params[key] - 搜索表单字段(与 searchFormConfig 中的 name 对应)
75
+ * @returns {Promise<Object>} 数据响应(需包含 list 和 total 字段)
76
+ * @returns {Array} response.data.list - 表格数据源数组
77
+ * @returns {number} response.data.total - 数据总数(用于分页计算)
78
+ */
79
+ fetchDataApi: (params: {
80
+ page: number;
81
+ pageSize: number;
82
+ [key: string]: any;
83
+ }) => Promise<{
84
+ data: {
85
+ list: any[];
86
+ total: number;
87
+ [key: string]: any;
88
+ };
89
+ [key: string]: any;
90
+ }>;
91
+
92
+ /**
93
+ * 搜索表单配置数组
94
+ * 描述:由 SearchFormItemConfig 类型的配置项组成,组件内部会根据该数组自动生成搜索表单,
95
+ * 不传递则不显示搜索表单
96
+ * @type {SearchFormItemConfig[]}
97
+ * @optional
98
+ */
99
+ searchFormConfig?: SearchFormItemConfig[];
100
+
101
+ /**
102
+ * 表格行的唯一标识字段名
103
+ * 描述:用于 antd Table/ProTable 的 rowKey 属性,确保表格行的唯一性(避免渲染警告),
104
+ * 默认值为 'id'(若数据的唯一标识字段不是 id,需手动传递)
105
+ * @type {string}
106
+ * @default 'id'
107
+ * @optional
108
+ */
109
+ rowKey?: string;
110
+
111
+ /**
112
+ * 初始分页配置
113
+ * 描述:定义表格初始的分页状态(如默认页码、每页条数、是否显示分页控件等),
114
+ * 基于 antd Table 的 pagination 类型扩展,确保分页能力符合通用需求
115
+ *
116
+ * @type {TableProps<any>['pagination'] & Object}
117
+ * @property {number} [current=1] - 默认当前页码
118
+ * @property {number} [pageSize=10] - 默认每页条数
119
+ * @property {boolean} [showSizeChanger=true] - 是否显示「条数切换」控件(如 10/20/50 条)
120
+ * @property {boolean} [showQuickJumper=true] - 是否显示「快速跳转」输入框
121
+ * @property {Function} [showTotal] - 总数显示文案(如 (total) => `共 ${total} 条`)
122
+ * @optional
123
+ */
124
+ initialPagination?: TableProps<any>['pagination'] & {
125
+ current?: number;
126
+ pageSize?: number;
127
+ showSizeChanger?: boolean;
128
+ showQuickJumper?: boolean;
129
+ showTotal?: (total: number) => ReactNode;
130
+ };
131
+
132
+ /**
133
+ * 表格组件的自定义属性
134
+ * 描述:根据 proTable 开关动态匹配对应的表格属性类型,支持覆盖组件内部的默认配置(如 loading、pagination 等)
135
+ * - 当 proTable={true} 时:支持 OverTableProps(项目自定义 ProTable 的所有属性)
136
+ * - 当 proTable={false} 时:支持 TableProps(antd 原生 Table 的所有属性)
137
+ *
138
+ * @type {T extends true ? OverTableProps<any> : TableProps<any>}
139
+ * @optional
140
+ * @example
141
+ */
142
+ tableProps?: T extends true
143
+ ? OverTableProps<any> // ProTable 专属属性(如 search、toolBar 等)
144
+ : TableProps<any>; // antd 原生 Table 属性(如 bordered、scroll 等)
145
+
146
+ /**
147
+ * 表格类型切换开关
148
+ * 描述:控制组件内部使用「项目自定义 ProTable」还是「antd 原生 Table」,
149
+ * 泛型 T 会自动根据该值推断,确保 tableProps 类型匹配
150
+ *
151
+ * @type {T}
152
+ * @default false - 默认使用 antd 原生 Table
153
+ * @optional
154
+ */
155
+ proTable?: T;
156
+ }
157
+
158
+ /**
159
+ * ListDataContainer 组件通过 ref 暴露的方法接口
160
+ * 描述:组件内部通过 useImperativeHandle 暴露的方法,允许父组件调用以实现「数据刷新」等能力
161
+ *
162
+ * @interface ListDataContainerExposedMethods
163
+ */
164
+ export interface ListDataContainerExposedMethods {
165
+ /**
166
+ * 数据刷新方法
167
+ * 描述:触发表格数据重新请求,支持传入参数覆盖默认的「分页+搜索」参数,
168
+ * 适用于父组件主动刷新数据(如弹窗关闭后刷新列表)
169
+ *
170
+ * @param {Record<string, any>} [overrideParams] - 覆盖请求的参数(如 { status: 1, type: 2 })
171
+ * @returns {Promise<void>} - 数据请求完成的 Promise(可用于等待刷新结束)
172
+ */
173
+ loadDataSource: (overrideParams?: Record<string, any>) => Promise<void>;
174
+ }
175
+
176
+ /**
177
+ * 通用列表数据容器组件声明
178
+ * 描述:泛型组件,整合「搜索表单 + 表格 + 分页 + 数据请求」的完整列表解决方案,
179
+ * 支持切换表格类型,暴露刷新方法,适用于项目中各类列表页面(如数据管理、列表查询等)
180
+ *
181
+ * @component ListDataContainer
182
+ * @template T - 表格类型开关(true = ProTable;false = antd Table,默认 false)
183
+ * @param {ListDataContainerProps<T> & RefAttributes<ListDataContainerExposedMethods>} props - 组件 props
184
+ * @returns {ReactElement} - 列表容器组件的 React 元素
185
+ */
186
+ declare const ListDataContainer: <T extends boolean = false>(
187
+ props: ListDataContainerProps<T> & RefAttributes<ListDataContainerExposedMethods>
188
+ ) => ReactElement;
189
+
190
+ export default ListDataContainer;
@@ -0,0 +1,205 @@
1
+ function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
+ import React, { useState, useEffect, useCallback, useImperativeHandle, forwardRef } from 'react';
3
+ import { Table, Row, Col, Form } from 'antd';
4
+ import { isEqual } from 'lodash';
5
+ import SearchForm from '../SearchForm';
6
+ import ProTable from '../Table';
7
+
8
+ /**
9
+ * @component ListDataContainer
10
+ * @description 通用列表数据容器组件,提供搜索表单、数据表格、分页等功能的完整列表页面解决方案
11
+ *
12
+ * @props {Array} columns - 表格列配置,遵循Ant Design Table组件的columns规范
13
+ * @props {Function} fetchDataApi - 数据获取API函数,接收分页和搜索参数,返回包含list和total的对象
14
+ * @props {Array} searchFormConfig - 搜索表单配置数组
15
+ * @param {React.Component} searchFormConfig[].field - 表单项组件(如ControlWrapper.Select)
16
+ * @param {string} searchFormConfig[].name - 表单项字段名
17
+ * @param {string} searchFormConfig[].label - 表单项标签
18
+ * @param {Object} searchFormConfig[].fieldProps - 传递给字段组件的属性
19
+ * @param {Object} searchFormConfig[].itemProps - 传递给Form.Item的属性
20
+ * @param {React.ReactNode} searchFormConfig[].content - 字段组件的子元素(如Select.Option列表)
21
+ * @param {boolean} searchFormConfig[].required - 是否必填
22
+ * @props {string} rowKey - 表格行键值,默认为'id'
23
+ * @props {Object} initialPagination - 初始分页配置,包含current、pageSize等分页属性
24
+ * @props {Object} tableProps - 传递给Table组件的属性
25
+ * @props {boolean} proTable - 是否使用ProTable组件,默认为false
26
+ * @example
27
+ * // 基础用法
28
+ * <ListDataContainer
29
+ * columns={tableColumns}
30
+ * fetchDataApi={getCourseList}
31
+ * searchFormConfig={[
32
+ * {
33
+ * field: Select,
34
+ * name: 'status',
35
+ * label: '状态',
36
+ * }
37
+ * {
38
+ * field: Input,
39
+ * name: 'name',
40
+ * label: '名称',
41
+ * required: true,
42
+ * }
43
+ * ]}
44
+ * />
45
+ *
46
+ */
47
+
48
+ const SELECT_COMPONENTS = ['Select', 'TreeSelect', 'Cascader', 'DatePicker', 'TimePicker', 'Checkbox', 'Radio', 'Switch', 'Slider', 'Upload'];
49
+ const INPUT_COMPONENTS = ['Input', 'InputNumber'];
50
+ const ListDataContainer = /*#__PURE__*/forwardRef(({
51
+ columns,
52
+ fetchDataApi,
53
+ searchFormConfig = [],
54
+ rowKey = 'id',
55
+ initialPagination = {
56
+ current: 1,
57
+ pageSize: 10,
58
+ showSizeChanger: true,
59
+ showQuickJumper: true,
60
+ showTotal: total => `共 ${total} 条记录`
61
+ },
62
+ tableProps = {},
63
+ proTable = false
64
+ }, ref) => {
65
+ const [form] = Form.useForm();
66
+ const [tableData, setTableData] = useState([]);
67
+ const [loading, setLoading] = useState(false);
68
+ const [pagination, setPagination] = useState(initialPagination);
69
+ const [totalCount, setTotalCount] = useState(0);
70
+ const getComponentName = Component => {
71
+ return Component.displayName || Component.constructor.name;
72
+ };
73
+
74
+ /**
75
+ * 数据加载与刷新方法
76
+ * @param {Object} [overrideParams={}] - 可选,用于覆盖内部参数的键值对
77
+ * 若传入,会覆盖同名的表单参数和分页参数(如page、pageSize或搜索字段)
78
+ */
79
+ const loadDataSource = useCallback(async (overrideParams = {}) => {
80
+ if (!fetchDataApi) return;
81
+ setLoading(true);
82
+ try {
83
+ // 1. 获取组件内部的表单参数
84
+ const formValues = await form.validateFields();
85
+
86
+ // 2. 构造基础参数:分页参数 + 表单参数
87
+ const baseParams = {
88
+ page: pagination.current,
89
+ pageSize: pagination.pageSize,
90
+ ...formValues
91
+ };
92
+
93
+ // 3. 用外部传入的参数覆盖基础参数(同名参数以外部为准)
94
+ const requestParams = {
95
+ ...baseParams,
96
+ ...overrideParams
97
+ };
98
+
99
+ // 4. 发起请求
100
+ const response = await fetchDataApi(requestParams);
101
+ setTableData(response.data.list || []);
102
+ setTotalCount(response.data.total || 0);
103
+ } catch (error) {
104
+ console.error('列表数据请求失败:', error);
105
+ setTableData([]);
106
+ setTotalCount(0);
107
+ } finally {
108
+ setLoading(false);
109
+ }
110
+ }, [fetchDataApi, pagination, form]);
111
+
112
+ // 暴露刷新方法给父组件
113
+ useImperativeHandle(ref, () => ({
114
+ // 外部调用时可传入覆盖参数
115
+ loadDataSource: (overrideParams = {}) => loadDataSource(overrideParams)
116
+ }));
117
+ const handleFinish = values => {
118
+ // 搜索时重置到第一页,并用表单值覆盖(values会合并到baseParams中)
119
+ setPagination(prev => ({
120
+ ...prev,
121
+ current: 1
122
+ }));
123
+ loadDataSource(values);
124
+ };
125
+ const handleReset = () => {
126
+ // 清空表单
127
+ form.setFieldsValue(searchFormConfig.reduce((obj, item) => {
128
+ obj[item.name] = undefined;
129
+ return obj;
130
+ }, {}));
131
+ // 重置分页并使用内部默认参数查询
132
+ setPagination(initialPagination);
133
+ loadDataSource();
134
+ };
135
+ const handlePaginationChange = newPagination => {
136
+ // 更新分页后,用新分页参数查询
137
+ setPagination(newPagination);
138
+ };
139
+
140
+ // 分页变化时自动重新加载
141
+ useEffect(() => {
142
+ // 避免初始加载时重复调用,判断两个对象是否相等
143
+ if (!isEqual(pagination, initialPagination)) {
144
+ loadDataSource();
145
+ }
146
+ }, [pagination, loadDataSource]);
147
+ const generateFormItems = () => {
148
+ return searchFormConfig.map((item, index) => {
149
+ const {
150
+ field: FieldComponent,
151
+ name,
152
+ label,
153
+ required,
154
+ fieldProps = {},
155
+ itemProps = {},
156
+ content,
157
+ ...restProps
158
+ } = item;
159
+ return /*#__PURE__*/React.createElement(Form.Item, _extends({
160
+ key: `search-item-${name}-${index}`,
161
+ name: name,
162
+ label: label
163
+ }, required ? {
164
+ required: true,
165
+ rules: [{
166
+ required: true,
167
+ message: `${SELECT_COMPONENTS.includes(getComponentName(FieldComponent)) ? '请选择' : '请输入'}${label}`
168
+ }]
169
+ } : {}, itemProps, restProps), /*#__PURE__*/React.createElement(FieldComponent, _extends({
170
+ allowClear: true,
171
+ placeholder: `${SELECT_COMPONENTS.includes(getComponentName(FieldComponent)) ? '请选择' : '请输入'}${label}`
172
+ }, fieldProps), content || null));
173
+ }).filter(Boolean);
174
+ };
175
+ const TableComponent = proTable ? ProTable : Table;
176
+ return /*#__PURE__*/React.createElement(Row, {
177
+ gutter: [16, 24]
178
+ }, /*#__PURE__*/React.createElement(Col, {
179
+ span: 24
180
+ }, /*#__PURE__*/React.createElement(SearchForm, {
181
+ expand: true,
182
+ form: form,
183
+ loading: loading,
184
+ formLine: generateFormItems(),
185
+ onReset: handleReset,
186
+ onFinish: handleFinish
187
+ })), /*#__PURE__*/React.createElement(Col, {
188
+ span: 24
189
+ }, /*#__PURE__*/React.createElement(TableComponent, _extends({
190
+ columns: columns,
191
+ dataSource: tableData,
192
+ rowKey: rowKey,
193
+ loading: loading,
194
+ pagination: {
195
+ ...pagination,
196
+ total: totalCount
197
+ },
198
+ onChange: handlePaginationChange,
199
+ scroll: {
200
+ x: 'max-content'
201
+ },
202
+ bordered: true
203
+ }, tableProps))));
204
+ });
205
+ export default ListDataContainer;
package/Table/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  // @ts-ignore
4
4
  import type { TableProps } from 'antd';
5
5
 
6
- interface OverTableProps extends TableProps {
6
+ export interface OverTableProps extends TableProps {
7
7
  // 是否禁用内容区滚动,默认-false
8
8
  disabledResizer?: boolean;
9
9
  // 当一个路由下存在多个表格的情况下 需要给每一个表格设置一个唯一存储索引 若没有设置则使用默认索引,请注意缓存数据会被覆盖
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cqsjjb/jjb-react-admin-component",
3
- "version": "3.2.0-rc.0",
3
+ "version": "3.3.0-beta.0",
4
4
  "description": "jjb-react-admin-组件库@new",
5
5
  "main": "index.js",
6
6
  "author": "jjb-front-team",
@@ -10,6 +10,7 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "axios": "^1.6.5",
13
+ "lodash": "^4.17.21",
13
14
  "spark-md5": "^3.0.2",
14
15
  "cropperjs": "^1.6.2",
15
16
  "@wangeditor-next/editor": "latest",
@@ -1,38 +0,0 @@
1
- @com-prefix-cls: if(isdefined(@ant-prefix), @ant-prefix, ant);
2
-
3
- .@{com-prefix-cls}-form-item-control-label-wrapper {
4
- .@{com-prefix-cls}-form-item-control-label-span {
5
- border: 1px solid #d9d9d9;
6
- height: 32px;
7
- line-height: 30px;
8
- box-sizing: border-box;
9
- margin-right: -1px;
10
- padding: 0 2px 0 8px;
11
- border-top-left-radius: 4px;
12
- border-bottom-left-radius: 4px;
13
- white-space: nowrap;
14
- }
15
-
16
- &.@{com-prefix-cls}-form-item-control-label-has {
17
- .@{com-prefix-cls}-select-selector, .@{com-prefix-cls}-picker, .@{com-prefix-cls}-input, .@{com-prefix-cls}-input-affix-wrapper {
18
- border-top-left-radius: 0;
19
- border-bottom-left-radius: 0;
20
- border-left-color: transparent !important;
21
- &:hover, &:focus, &:focus-within{
22
- border-left-color: @colorPrimary !important;
23
- }
24
- }
25
- }
26
-
27
- &-default {
28
- .@{com-prefix-cls}-form-item-control-label-span {
29
- border-color: #d9d9d9;
30
- }
31
- }
32
-
33
- &-dark {
34
- .@{com-prefix-cls}-form-item-control-label-span {
35
- border-color: #424242;
36
- }
37
- }
38
- }