form-driver 0.4.19 → 0.4.20

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.
@@ -2,7 +2,8 @@ import React, { Component, RefObject } from "react";
2
2
  import { DatePicker } from "antd";
3
3
  import zhCN from 'antd/lib/date-picker/locale/zh_CN';
4
4
  import moment from "moment";
5
- import { Button, Calendar } from "antd-mobile";
5
+ import { DatePicker as DatePickerM } from "antd-mobile";
6
+ import { Button as AntButton } from "antd";
6
7
  import ReactDOM from "react-dom";
7
8
  import { RangePickerProps } from "antd/lib/date-picker";
8
9
  import _ from "lodash";
@@ -25,15 +26,18 @@ type AntData = [moment.Moment | null, moment.Moment | null];
25
26
 
26
27
  interface State extends ViewerState {
27
28
  mobileDlg: boolean;
29
+ mobileStep: 'start' | 'end';
30
+ mobileStartDate?: Date;
28
31
  }
29
32
 
30
33
  export class ARangePicker extends Viewer<State> {
31
- _pickerRef: RefObject<Component<RangePickerProps, any, any>> = React.createRef();
34
+ _pickerRef: RefObject<any> = React.createRef();
32
35
  _onCalendarChangeValue?: AntData | null;
36
+ _startConfirmed = false; // 标记开始日期是否已确认,用于区分 onClose 是取消还是确认后的自动触发
33
37
 
34
38
  constructor(p: MProp) {
35
39
  super(p);
36
- this.state = { ctrlVersion: 1, noValidate: false, mobileDlg: false };
40
+ this.state = { ctrlVersion: 1, noValidate: false, mobileDlg: false, mobileStep: 'start' };
37
41
  }
38
42
 
39
43
  componentDidUpdate() {
@@ -83,39 +87,97 @@ export class ARangePicker extends Viewer<State> {
83
87
  if (MUtil.phoneLike()) {
84
88
  let show = MDateRangeType.toReadableN(assembly, this.props.schema, super.getValue());
85
89
 
90
+ // 根据 precision 配置确定移动端 DatePicker 精度(使用扁平化属性 dateRangePrecision)
91
+ const mobilePrecision = (this.props.schema.dateRangePrecision || 'day') as 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
92
+
86
93
  return <>
87
- <div className="backfill" onClick={() => this.setState({ mobileDlg: true })}> {show ?? '请点击选择'} </div>
88
- <Calendar
89
- visible={this.state.mobileDlg}
90
- pickTime={this.props.schema.dateRange?.showTime}
91
- minDate={this.props.schema.min ? new Date(this.props.schema.min) : undefined}
92
- maxDate={this.props.schema.min ? new Date(this.props.schema.max) : undefined}
93
- onCancel={() => this.setState({ mobileDlg: false })}
94
- onConfirm={(start, end) => {
95
- super.changeValueEx(this._rangePicker2Data([moment(start), moment(end)], false), true, true)
96
- this.setState({ mobileDlg: false })
94
+ <div className="backfill" onClick={() => this.setState({ mobileDlg: true, mobileStep: 'start' })}> {show ?? '请点击选择'} </div>
95
+ {/* 移动端:使用两步 DatePickerM 选择开始和结束日期 */}
96
+ <DatePickerM
97
+ key={`start_${mobilePrecision}`}
98
+ visible={this.state.mobileDlg && this.state.mobileStep === 'start'}
99
+ precision={mobilePrecision}
100
+ title="选择开始日期"
101
+ min={this.props.schema.min ? new Date(this.props.schema.min) : undefined}
102
+ max={this.props.schema.max ? new Date(this.props.schema.max) : undefined}
103
+ onConfirm={(val) => {
104
+ this._startConfirmed = true;
105
+ this.setState({ mobileStartDate: val, mobileStep: 'end' });
106
+ }}
107
+ onClose={() => {
108
+ // antd-mobile v5 确认时会同时触发 onConfirm 和 onClose
109
+ // 用实例变量同步判断:确认后的 onClose 应忽略,仅用户主动取消时才关闭
110
+ if (this._startConfirmed) {
111
+ this._startConfirmed = false;
112
+ return;
113
+ }
114
+ this.setState({ mobileDlg: false });
115
+ }}
116
+ />
117
+ <DatePickerM
118
+ key={`end_${mobilePrecision}`}
119
+ visible={this.state.mobileDlg && this.state.mobileStep === 'end'}
120
+ precision={mobilePrecision}
121
+ title="选择结束日期"
122
+ tillNow={!this.props.schema.dateRange?.hideTillNow && !this.props.schema.dateRange?.showTime}
123
+ min={this.state.mobileStartDate || (this.props.schema.min ? new Date(this.props.schema.min) : undefined)}
124
+ max={this.props.schema.max ? new Date(this.props.schema.max) : undefined}
125
+ onConfirm={(val: any) => {
126
+ const startDate = this.state.mobileStartDate;
127
+ if (startDate) {
128
+ const isTillNow = !!(val as any).tillNow;
129
+ if (isTillNow) {
130
+ // 用户选择了"至今"
131
+ super.changeValueEx(
132
+ this._rangePicker2Data([moment(startDate), moment()], true),
133
+ true, true
134
+ );
135
+ } else {
136
+ // 防御:结束日期不能早于开始日期
137
+ const finalEnd = val < startDate ? startDate : val;
138
+ super.changeValueEx(this._rangePicker2Data([moment(startDate), moment(finalEnd)], false), true, true);
139
+ }
140
+ }
141
+ this.setState({ mobileDlg: false });
142
+ }}
143
+ onClose={() => {
144
+ // 回退到第一步,让用户可以重新选择开始日期
145
+ this.setState({ mobileStep: 'start' });
97
146
  }}
98
- {...p}
99
147
  />
100
148
  </>
101
149
  } else {
150
+ // 根据 precision 配置确定 PC 端 picker 模式和 showTime(使用扁平化属性 dateRangePrecision)
151
+ const precision = this.props.schema.dateRangePrecision;
152
+ const pcShowTime = precision === 'minute' || this.props.schema.dateRange?.showTime;
153
+ const hideFooter = this.props.schema.dateRange?.hideTillNow || pcShowTime;
154
+
155
+ // 动态构建额外属性,避免 showTime 和 picker 同时传入导致类型冲突
156
+ const extraProps: any = {};
157
+ if (precision === 'year') {
158
+ extraProps.picker = 'year';
159
+ } else if (precision === 'month') {
160
+ extraProps.picker = 'month';
161
+ } else if (pcShowTime) {
162
+ extraProps.showTime = true;
163
+ }
164
+
102
165
  // 构造元素
103
166
  return <DatePicker.RangePicker
104
167
  ref={this._pickerRef}
105
- showTime={this.props.schema.dateRange?.showTime}
106
- key={this.state.ctrlVersion}
107
- renderExtraFooter={this.props.schema.dateRange?.hideTillNow || this.props.schema.dateRange?.showTime // TODO 显示时间时,“至今”无法支持
168
+ key={`${this.state.ctrlVersion}_${this.props.schema.dateRangePrecision ?? 'day'}`}
169
+ renderExtraFooter={hideFooter
108
170
  ? undefined
109
- : (mode) => <div style={{ textAlign: "right" }} {...p}>
110
- <Button
171
+ : (mode) => <div style={{ textAlign: "right" }}>
172
+ <AntButton
111
173
  size="small" style={{ width: "100px", display: "inline-block", marginTop: "5px" }}
112
174
  onClick={() => {
113
175
  super.changeValueEx(this._rangePicker2Data(this._onCalendarChangeValue, true), true, true);
114
- }}>至今</Button>
176
+ }}>至今</AntButton>
115
177
  </div>
116
178
  }
117
179
  bordered={this.props.hideBorder ? false : true}
118
- style={{ minWidth: "240px" }}
180
+ style={{ width: "300px" }}
119
181
  locale={zhCN}
120
182
  defaultValue={rangePickerData}
121
183
  onCalendarChange={(d) => {
@@ -123,7 +185,9 @@ export class ARangePicker extends Viewer<State> {
123
185
  }}
124
186
  onChange={(vv) => {
125
187
  super.changeValueEx(this._rangePicker2Data(vv, false), true, true)
126
- }} />;
188
+ }}
189
+ {...extraProps}
190
+ />;
127
191
  }
128
192
  }
129
193
  }
@@ -2,15 +2,18 @@
2
2
 
3
3
  import { Select } from "antd";
4
4
  import { Picker } from "antd-mobile";
5
- import { PickerData } from "antd-mobile/lib/picker/PropsType";
6
5
  import _ from "lodash";
7
6
  import React from "react";
8
- //import { InputHTMLAttributes } from "react";
9
7
  import { MUtil } from '../../framework/MUtil';
10
8
 
11
- interface Prop { // extends InputHTMLAttributes<HTMLInputElement> {
9
+ interface PickerOption {
10
+ label: string | React.ReactNode;
11
+ value: string;
12
+ }
13
+
14
+ interface Prop {
12
15
  data: string;
13
- options: PickerData[],
16
+ options: PickerOption[],
14
17
  onChange: (newValue?:string)=>void;
15
18
  onBlur?:()=>void;
16
19
 
@@ -18,22 +21,39 @@ interface Prop { // extends InputHTMLAttributes<HTMLInputElement> {
18
21
  openLabel?: string;
19
22
  }
20
23
 
21
- export class SelectBox extends React.Component<Prop, any> {
24
+ interface SelectBoxState {
25
+ pickerVisible: boolean;
26
+ }
27
+
28
+ export class SelectBox extends React.Component<Prop, SelectBoxState> {
29
+ constructor(props: Prop) {
30
+ super(props);
31
+ this.state = { pickerVisible: false };
32
+ }
33
+
22
34
  render(){
23
35
  if(MUtil.phoneLike()) {
24
36
  const looked = this.props.options.find(o=>o.value === this.props.data);
25
37
  const backfillClass = looked ? "backfill" : "backfill_empty";
26
38
 
27
- return <Picker extra="请选择(可选)"
28
- cols={1}
29
- data={this.props.options}
30
- value={[this.props.data]}
31
- onOk={e => this.props.onChange(_.last(e))}
32
- onDismiss={() => {
33
- if(this.props.onBlur) { this.props.onBlur() }
34
- }}>
35
- <div className={backfillClass}>{looked?.label ?? "点击选择"}</div>
36
- </Picker>
39
+ return <>
40
+ <div className={backfillClass} onClick={() => this.setState({ pickerVisible: true })}>
41
+ {looked?.label ?? "点击选择"}
42
+ </div>
43
+ <Picker
44
+ columns={[this.props.options]}
45
+ value={[this.props.data]}
46
+ visible={this.state.pickerVisible}
47
+ onConfirm={e => {
48
+ this.props.onChange(_.last(e) as string);
49
+ this.setState({ pickerVisible: false });
50
+ }}
51
+ onClose={() => {
52
+ this.setState({ pickerVisible: false });
53
+ if(this.props.onBlur) { this.props.onBlur() }
54
+ }}
55
+ />
56
+ </>
37
57
  } else {
38
58
  return <Select defaultValue={this.props.data}>
39
59
  {this.props.options.map(o=><Select.Option value={o.value}>{o.label ?? o.value}</Select.Option>)}
@@ -1,3 +1,4 @@
1
+ import "moment/locale/zh-cn";
1
2
  /**
2
3
  * 确保m3已经初始化ensureM3
3
4
  */
@@ -132,13 +132,24 @@ export interface MFieldSchema {
132
132
  /** 允许时间段重叠,默认是不能重叠 */
133
133
  overlap?: boolean;
134
134
  };
135
+ /** date 类型配置 */
136
+ date?: {
137
+ /** 日期精度:month=年月, day=年月日(默认), minute=年月日时分 */
138
+ precision?: "month" | "day" | "minute";
139
+ };
140
+ /** 日期精度(扁平化属性):year=年, month=年月, day=年月日(默认), minute=年月日时分 */
141
+ datePrecision?: string;
135
142
  /** dateRange 类型配置 */
136
143
  dateRange?: {
137
144
  /** 是否隐藏至今按钮 */
138
145
  hideTillNow?: boolean;
139
146
  /** 是否能选择时间 */
140
147
  showTime?: boolean;
148
+ /** 日期精度:month=年月, day=年月日(默认), minute=年月日时分 */
149
+ precision?: "month" | "day" | "minute";
141
150
  };
151
+ /** 日期范围精度(扁平化属性) */
152
+ dateRangePrecision?: string;
142
153
  /** 数据格式 */
143
154
  dataFormat?: "x" | "YYYYMMDD" /** 用于时间日期类型字段的数据格式,参考moment,例如x表示数据是时间戳,YYYYMMDD表示数据是形如19990130的字符串 */ | string;
144
155
  /**
package/types/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import 'antd/dist/antd.css';
2
- import 'antd-mobile/dist/antd-mobile.css';
3
2
  import { Ajax } from './framework/Ajax';
4
3
  import { M3UISpecSegmentItem, MFieldSchema, M3UISpec, MFieldSchemaAnonymity } from './framework/Schema';
5
4
  import { MViewerDebug } from './framework/MViewerDebug';
@@ -1,9 +1,9 @@
1
- import { MFieldSchemaAnonymity } from '../framework/Schema';
1
+ import { MFieldSchemaAnonymity } from "../framework/Schema";
2
2
  import { MType } from "./MType";
3
3
  export interface MDateTimeAntConf {
4
4
  dataFormat: string;
5
5
  readableFormat: string;
6
- mode: undefined | 'date' | 'year' | 'month' | 'time';
6
+ mode: undefined | "date" | "year" | "month" | "time";
7
7
  showTime: boolean;
8
8
  }
9
9
  export declare const MDateTimeType: MType & {
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { BaseViewer } from '../../BaseViewer';
2
+ import { BaseViewer } from "../../BaseViewer";
3
3
  /**
4
4
  * 日期选择框
5
5
  * 配置示例:
@@ -11,5 +11,6 @@ import { BaseViewer } from '../../BaseViewer';
11
11
  * 类型是dateTime时,dataFormat默认是x,例如1608897466955
12
12
  */
13
13
  export declare class ADatetimePicker extends BaseViewer {
14
+ constructor(props: any);
14
15
  element(): JSX.Element;
15
16
  }
@@ -6,5 +6,6 @@ import { BaseViewer } from '../../BaseViewer';
6
6
  * 数据是gb2260的地区代码
7
7
  */
8
8
  export declare class AGB2260 extends BaseViewer {
9
+ constructor(props: any);
9
10
  element(): JSX.Element;
10
11
  }
@@ -1,6 +1,5 @@
1
- import { Component, RefObject } from "react";
1
+ import { RefObject } from "react";
2
2
  import moment from "moment";
3
- import { RangePickerProps } from "antd/lib/date-picker";
4
3
  import { Viewer, ViewerState } from '../../BaseViewer';
5
4
  import { MProp } from "../../../framework/Schema";
6
5
  export type ARangePickerData = [
@@ -11,10 +10,13 @@ export type ARangePickerData = [
11
10
  type AntData = [moment.Moment | null, moment.Moment | null];
12
11
  interface State extends ViewerState {
13
12
  mobileDlg: boolean;
13
+ mobileStep: 'start' | 'end';
14
+ mobileStartDate?: Date;
14
15
  }
15
16
  export declare class ARangePicker extends Viewer<State> {
16
- _pickerRef: RefObject<Component<RangePickerProps, any, any>>;
17
+ _pickerRef: RefObject<any>;
17
18
  _onCalendarChangeValue?: AntData | null;
19
+ _startConfirmed: boolean;
18
20
  constructor(p: MProp);
19
21
  componentDidUpdate(): void;
20
22
  componentDidMount(): void;
@@ -1,13 +1,20 @@
1
- import { PickerData } from "antd-mobile/lib/picker/PropsType";
2
1
  import React from "react";
2
+ interface PickerOption {
3
+ label: string | React.ReactNode;
4
+ value: string;
5
+ }
3
6
  interface Prop {
4
7
  data: string;
5
- options: PickerData[];
8
+ options: PickerOption[];
6
9
  onChange: (newValue?: string) => void;
7
10
  onBlur?: () => void;
8
11
  openLabel?: string;
9
12
  }
10
- export declare class SelectBox extends React.Component<Prop, any> {
13
+ interface SelectBoxState {
14
+ pickerVisible: boolean;
15
+ }
16
+ export declare class SelectBox extends React.Component<Prop, SelectBoxState> {
17
+ constructor(props: Prop);
11
18
  render(): JSX.Element;
12
19
  }
13
20
  export {};