175 lines
8.1 KiB
JavaScript
175 lines
8.1 KiB
JavaScript
import React, { useState } from 'react';
|
||
import { Form, Input, TextArea, DatePicker, Grid, Popup, Radio, Stepper, Switch, Button, } from 'antd-mobile';
|
||
import OakIcon from '../icon';
|
||
import RefAttr from '../refAttr';
|
||
import Locate from '../map/locate';
|
||
import dayjs from 'dayjs';
|
||
function makeAttrInput(attrRender, onValueChange, t, label, Map) {
|
||
const [sl, setSl] = useState(false);
|
||
const [dt, setDt] = useState(false);
|
||
const [poi, setPoi] = useState(undefined);
|
||
const { value, type, params, defaultValue, enumeration, required, placeholder, min, max, maxLength } = attrRender;
|
||
switch (type) {
|
||
case 'string':
|
||
case 'varchar':
|
||
case 'char':
|
||
case 'poiName': {
|
||
return (<Input clearable={!required} placeholder={placeholder || `请输入${label}`} value={value} defaultValue={defaultValue} maxLength={maxLength} onChange={(value) => {
|
||
onValueChange(value);
|
||
}}/>);
|
||
}
|
||
case 'text': {
|
||
return (<TextArea autoSize={true} placeholder={`请输入${label}`} defaultValue={defaultValue} value={value || ''} rows={6} showCount={true} maxLength={maxLength || 1000} onChange={(value) => {
|
||
onValueChange(value);
|
||
}}/>);
|
||
}
|
||
case 'int': {
|
||
return (<Stepper min={min} max={max} defaultValue={defaultValue} value={value} digits={0} onChange={(value) => onValueChange(value)}/>);
|
||
}
|
||
case 'decimal': {
|
||
const precision = params?.precision || 10;
|
||
const scale = params?.scale || 0;
|
||
const scaleValue = Math.pow(10, 0 - scale);
|
||
/*
|
||
const threshold = Math.pow(10, precision - scale);
|
||
const max = threshold - scaleValue; // 小数在这里可能会有bug
|
||
const min = 0 - max; */
|
||
return (<Stepper min={min} max={max} defaultValue={defaultValue} value={value} digits={scale} step={scaleValue} onChange={(value) => onValueChange(value)}/>);
|
||
}
|
||
case 'money': {
|
||
// money在数据上统一用分来存储
|
||
const valueShowed = parseFloat((value / 100).toFixed(2));
|
||
const defaultValueShowed = parseFloat((defaultValue / 100).toFixed(2));
|
||
return (<Stepper min={0} defaultValue={defaultValueShowed} value={valueShowed} digits={2} step={0.01} formatter={value => `¥ ${value}`} parser={text => parseFloat(text.replace('¥', ''))} onChange={(value) => {
|
||
if (value !== null) {
|
||
const v2 = Math.round(value * 100);
|
||
onValueChange(v2);
|
||
}
|
||
else {
|
||
onValueChange(value);
|
||
}
|
||
}}/>);
|
||
}
|
||
case 'datetime':
|
||
case 'date': {
|
||
const precision = type === 'date' ? 'day' : 'second';
|
||
const format = type === 'date' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss';
|
||
return (<div style={{ display: 'flex', alignItems: 'center' }}>
|
||
<DatePicker visible={dt} onClose={() => setDt(false)} precision={precision} value={new Date(value)} defaultValue={new Date()} min={min ? new Date(min) : undefined} max={max ? new Date(max) : undefined}>
|
||
{value => dayjs(value).format(format)}
|
||
</DatePicker>
|
||
<Button onClick={() => setDt(true)}>
|
||
{type === 'date' ? t('chooseDate') : t('chooseDatetime')}
|
||
</Button>
|
||
</div>);
|
||
}
|
||
case 'time': {
|
||
return (<div>待实现</div>);
|
||
}
|
||
case 'boolean': {
|
||
return (<Switch checkedText={<OakIcon name="right"/>} uncheckedText={<OakIcon name="close"/>} checked={value} onChange={(checked) => {
|
||
onValueChange(checked);
|
||
}}/>);
|
||
}
|
||
case 'enum': {
|
||
return (<Radio.Group value={value} onChange={(value) => onValueChange(value)}>
|
||
{enumeration.map(({ label, value }) => (<Radio value={value}>{t(label)}</Radio>))}
|
||
</Radio.Group>);
|
||
}
|
||
case 'ref': {
|
||
return (<RefAttr multiple={false} entityId={value} pickerRender={attrRender} onChange={(value) => { onValueChange(value[0]); }}/>);
|
||
}
|
||
case 'coordinate': {
|
||
const { coordinate } = value || {};
|
||
const { extra } = attrRender;
|
||
const poiNameAttr = extra?.poiName || 'poiName';
|
||
const areaIdAttr = extra?.areaId || 'areaId';
|
||
return (<>
|
||
<Popup closeOnMaskClick={true} destroyOnClose={true} position='bottom' visible={sl} onClose={() => setSl(false)} bodyStyle={{
|
||
height: '100%',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
alignItems: 'stretch',
|
||
}}>
|
||
<div style={{ flex: 1 }}>
|
||
<Locate coordinate={coordinate} onLocated={(poi) => setPoi(poi)}/>
|
||
</div>
|
||
<Grid columns={2} gap={8}>
|
||
<Button block color="primary" disabled={!poi} onClick={() => {
|
||
if (poi) {
|
||
const { poiName, coordinate, areaId } = poi;
|
||
onValueChange({
|
||
type: 'point',
|
||
coordinate,
|
||
}, {
|
||
[poiNameAttr]: poiName,
|
||
[areaIdAttr]: areaId,
|
||
});
|
||
}
|
||
setSl(false);
|
||
}}>
|
||
{t('common::action.confirm')}
|
||
</Button>
|
||
<Button block fill="outline" onClick={() => {
|
||
setSl(false);
|
||
}}>
|
||
{t('common::action.cancel')}
|
||
</Button>
|
||
</Grid>
|
||
</Popup>
|
||
<div style={{
|
||
display: 'flex',
|
||
width: '100%',
|
||
alignItems: 'center',
|
||
}} onClick={() => setSl(true)}>
|
||
<Map undragable={true} unzoomable={true} zoom={13} disableWheelZoom={true} style={{ height: 200, flex: 1 }} autoLocate={true} center={coordinate} markers={coordinate ? [coordinate] : undefined}/>
|
||
</div>
|
||
</>);
|
||
}
|
||
default: {
|
||
throw new Error(`【Abstract Update】无法支持的数据类别${type}的渲染`);
|
||
}
|
||
}
|
||
}
|
||
export default function render(props) {
|
||
const { renderData = [], helps, layout = 'horizontal', mode = 'default', entity, Map } = props.data;
|
||
const { update, t } = props.methods;
|
||
return (<Form layout={layout} mode={mode}>
|
||
{renderData.map((ele) => {
|
||
// 因为i18n渲染机制的缘故,t必须放到这里来计算
|
||
const { label, attr, type, required } = ele;
|
||
let label2 = label;
|
||
if (!label2) {
|
||
if (type === 'ref') {
|
||
const { entity: refEntity } = ele;
|
||
if (attr === 'entityId') {
|
||
// 反指
|
||
label2 = t(`${refEntity}:name`);
|
||
}
|
||
else {
|
||
label2 = t(`${entity}:attr.${attr}`);
|
||
}
|
||
}
|
||
else {
|
||
label2 = t(`${entity}:attr.${attr}`);
|
||
}
|
||
}
|
||
return (<Form.Item label={label2} rules={[
|
||
{
|
||
required: !!required,
|
||
},
|
||
]} help={helps && helps[attr]}>
|
||
<>
|
||
{makeAttrInput(ele, (value, extra) => {
|
||
const { attr } = ele;
|
||
update({
|
||
[attr]: value,
|
||
...extra,
|
||
});
|
||
}, t, label2, Map)}
|
||
</>
|
||
</Form.Item>);
|
||
})}
|
||
</Form>);
|
||
}
|