import React from 'react'; import {FormItem, FormControlProps, FormBaseControl} from './Item'; import {ClassNamesFn, themeable, ThemeProps} from '../../theme'; import {Select, Spinner} from '../../components'; import {autobind} from '../../utils/helper'; import {Option} from './Options'; import {localeable, LocaleProps} from '../../locale'; /** * City 城市选择框。 * 文档:https://baidu.gitee.io/amis/docs/components/form/city */ export interface InputCityControlSchema extends FormBaseControl { /** * 指定为城市选择框。 */ type: 'input-city'; /** * 开启后只会存城市的 code 信息 */ extractValue?: boolean; /** * 是否将各个信息拼接成字符串。 */ joinValues?: boolean; /** * 拼接的符号是啥? */ delimiter?: string; /** * 允许选择城市? */ allowCity?: boolean; /** * 允许选择地区? */ allowDistrict?: boolean; /** * 允许选择街道? */ allowStreet?: boolean; /** * 是否显示搜索框 */ searchable?: boolean; } export interface CityPickerProps extends Omit, LocaleProps, ThemeProps { value: any; onChange: (value: any) => void; extractValue: boolean; delimiter: string; allowCity: boolean; allowDistrict: boolean; allowStreet: boolean; } export interface CityPickerState { code: number; province: string; provinceCode: number; city: string; cityCode: number; district: string; districtCode: number; street: string; db?: { province: Array; city: { [propName: number]: Array; }; district: { [propName: number]: | { [propName: number]: Array; } | Array; }; [propName: string]: any; }; } export class CityPicker extends React.Component< CityPickerProps, CityPickerState > { static defaultProps = { joinValues: true, extractValue: true, delimiter: ',', allowCity: true, allowDistrict: true, allowStreet: false }; state: CityPickerState = { code: 0, province: '', provinceCode: 0, city: '', cityCode: 0, district: '', districtCode: 0, street: '' }; componentDidMount() { this.loadDb(() => this.syncIn()); } componentDidUpdate(prevProps: CityPickerProps) { const props = this.props; if (props.value !== prevProps.value) { this.loadDb(() => this.syncIn(props)); } } loadDb(callback?: () => void) { if (this.state.db) { callback?.(); return; } import('./CityDB').then(db => { this.setState( { db: { ...db.default, province: db.province as any, city: db.city, district: db.district } }, callback ); }); // require.ensure(['./CityDB'], (db: any) => // this.setState( // { // db: { // ...db.default, // province: db.province, // city: db.city, // district: db.district // } // }, // callback // ) // ); } @autobind handleProvinceChange(option: Option) { this.setState( { province: option.label as string, provinceCode: option.value as number, city: '', cityCode: 0, district: '', districtCode: 0, street: '', code: option.value }, this.syncOut ); } @autobind handleCityChange(option: Option) { if (option.value % 100) { return this.handleDistrictChange(option, { cityCode: option.value as number }); } this.setState( { city: option.label as string, cityCode: option.value as number, district: '', districtCode: 0, street: '', code: option.value }, this.syncOut ); } @autobind handleDistrictChange( option: Option, otherStates: Partial = {} ) { this.setState( { ...(otherStates as any), district: option.label as string, districtCode: option.value as number, street: '', code: option.value as number }, this.syncOut ); } @autobind handleStreetChange(e: React.ChangeEvent) { this.setState({ street: e.currentTarget.value }); } @autobind handleStreetEnd() { this.syncOut(); } @autobind syncIn(props = this.props) { const db = this.state.db!; const {value, delimiter} = props; if (!db) { return; } const state = { code: 0, province: '', provinceCode: 0, city: '', cityCode: 0, district: '', districtCode: 0, street: '' }; let code = (value && value.code) || (typeof value === 'number' && value) || (typeof value === 'string' && /(\d{6})/.test(value) && RegExp.$1); if (code && db[code]) { code = parseInt(code, 10); state.code = code; const provinceCode = code - (code % 10000); if (db[provinceCode]) { state.provinceCode = provinceCode; state.province = db[provinceCode]; } const cityCode = code - (code % 100); if (db[cityCode]) { state.cityCode = cityCode; state.city = db[cityCode]; } else if (~db.city[provinceCode]?.indexOf(code)) { state.cityCode = code; state.city = db[code]; } if (code % 100) { state.district = db[code]; state.districtCode = code; } } else if (value) { // todo 模糊查找 } if (value && value.street) { state.street = value.street; } else if (typeof value === 'string' && ~value.indexOf(delimiter)) { state.street = value.slice(value.indexOf(delimiter) + delimiter.length); } this.setState(state); } @autobind syncOut() { const { onChange, allowStreet, joinValues, extractValue, delimiter } = this.props; const {code, province, city, district, street} = this.state; if (typeof extractValue === 'undefined' ? joinValues : extractValue) { code ? onChange( allowStreet && street ? [code, street].join(delimiter) : String(code) ) : onChange(''); } else { onChange({ code, province, city, district, street }); } } render() { const { classnames: cx, className, disabled, allowCity, allowDistrict, allowStreet, searchable, translate: __ } = this.props; const {provinceCode, cityCode, districtCode, street, db} = this.state; return db ? (
).map(item => ({ label: db[item], value: item }))} value={districtCode} onChange={this.handleDistrictChange} /> ) : allowCity && db.city[provinceCode] && db.city[provinceCode].length ? ( ).map( item => ({ label: db[item], value: item }) )} value={districtCode} onChange={this.handleDistrictChange} /> ) : null} {allowStreet && provinceCode ? ( ) : null}
) : ( ); } } const ThemedCity = themeable(localeable(CityPicker)); export default ThemedCity; export interface LocationControlProps extends FormControlProps { allowCity?: boolean; allowDistrict?: boolean; extractValue?: boolean; joinValues?: boolean; allowStreet?: boolean; } export class LocationControl extends React.Component { render() { const { value, onChange, allowCity, allowDistrict, extractValue, joinValues, allowStreet, disabled, searchable } = this.props; return ( ); } } @FormItem({ type: 'input-city', sizeMutable: false }) export class CheckboxControlRenderer extends LocationControl {}