UNPKG

y-taro-ui

Version:

基于taro的表单解决方案 & 基础组件

1,133 lines (1,100 loc) 143 kB
import React, { useRef, useState, memo, createContext, useContext, useMemo, useEffect, useCallback, Fragment, useImperativeHandle } from 'react'; import { jsx, jsxs } from 'react/jsx-runtime'; import { Text, View, Button as Button$1, Picker as Picker$1, Input as Input$1, Textarea as Textarea$1, Image as Image$1, Label, Block, Form as Form$1, WebView as WebView$1, ScrollView } from '@tarojs/components'; import Taro, { chooseImage as chooseImage$1, showToast as showToast$1, previewImage, hideToast, createSelectorQuery, vibrateShort, createOffscreenCanvas } from '@tarojs/taro'; import defaultsDeep from 'lodash/defaultsDeep'; import { interpolate } from 'going-merry'; import { CSSTransition } from 'react-transition-group'; import throttle from 'lodash/throttle'; function createCommonjsModule(fn, basedir, module) { return module = { path: basedir, exports: {}, require: function (path, base) { return commonjsRequire(path, (base === undefined || base === null) ? module.path : base); } }, fn(module, module.exports), module.exports; } function commonjsRequire () { throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs'); } var classnames = createCommonjsModule(function (module) { /*! Copyright (c) 2018 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames */ /* global define */ (function () { var hasOwn = {}.hasOwnProperty; function classNames() { var classes = []; for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (!arg) continue; var argType = typeof arg; if (argType === 'string' || argType === 'number') { classes.push(arg); } else if (Array.isArray(arg)) { if (arg.length) { var inner = classNames.apply(null, arg); if (inner) { classes.push(inner); } } } else if (argType === 'object') { if (arg.toString === Object.prototype.toString) { for (var key in arg) { if (hasOwn.call(arg, key) && arg[key]) { classes.push(key); } } } else { classes.push(arg.toString()); } } } return classes.join(' '); } if (module.exports) { classNames.default = classNames; module.exports = classNames; } else { window.classNames = classNames; } }()); }); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } const isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]'; const flatted = promise => { return promise .then(data => { return { data, error: null }; }) .catch(error => { return { error, data: null }; }); }; const chooseImage = params => { return flatted(chooseImage$1(params)); }; const showToast = (message, duration = 2000) => { showToast$1({ icon: 'none', title: message, duration }); }; const showSuccess = (message) => { showToast$1({ icon: 'success', title: message }); }; const showLoading = (message, duration = 60000) => { showToast$1({ icon: 'loading', mask: true, title: message, duration }); }; const stopPropagation = (e) => { e.stopPropagation(); }; const preventDefault = (e, isStopPropagation = true) => { e === null || e === void 0 ? void 0 : e.preventDefault(); if (isStopPropagation) { stopPropagation(e); } }; function delay(delayTime = 25) { return new Promise(resolve => { setTimeout(() => { resolve(null); }, delayTime); }); } const PREFIX_CLASSNAME = 'y-taro-ui-'; const PREFIX_VARIABLENAME = '--y-taro-ui-'; const createPrefixClassName = (className) => `${PREFIX_CLASSNAME}${className}`; const createPrefixClassNames = (classNames) => { if (Array.isArray(classNames)) { return classnames(classNames.map(createPrefixClassName)); } return ''; }; const createPrefixVariable = (variableName, value) => ({ [`${PREFIX_VARIABLENAME}${variableName}`]: value }); const createPrefixVariables = (variableMap) => { if (isObject(variableMap)) { return Object.keys(variableMap).reduce((acc, key) => { return Object.assign(Object.assign({}, acc), { [`${PREFIX_VARIABLENAME}${key}`]: variableMap[key] }); }, {}); } return {}; }; const Icon = props => { const { type, style, className, onClick, animated } = props; const iconClassNames = classnames(className, createPrefixClassName('icon'), createPrefixClassName(`icon-${type}`), { [createPrefixClassName('icon-animate')]: animated }); return jsx(Text, { className: iconClassNames, onClick: onClick, style: style }); }; const Button = props => { const { border = true, formType, onClick, style, className, async = false, native = true, type = 'primary', block = false, loading = false, disabled = false, shape = 'default', size = 'middle', loadingText, loadingIcon, showLoadingIcon = true, children, icon, flex = 1, wait = 1 } = props, rest = __rest(props, ["border", "formType", "onClick", "style", "className", "async", "native", "type", "block", "loading", "disabled", "shape", "size", "loadingText", "loadingIcon", "showLoadingIcon", "children", "icon", "flex", "wait"]); const [_loading, setLoading] = React.useState(false); const status = React.useRef(false); const isDisabled = disabled || loading || _loading; const handleClick = async (event) => { if (isDisabled || status.current) { return; } if (async) { setLoading(true); try { onClick && (await onClick(event)); } catch (e) { } finally { setLoading(false); } return; } status.current = true; onClick && onClick(event); setTimeout(() => (status.current = false), wait * 1000); }; const _props = { style: Object.assign(Object.assign({}, style), { flex }), className: classnames(className, createPrefixClassName('button'), createPrefixClassName('button-text-overflow-hide'), createPrefixClassName(`button-${type}`), createPrefixClassName(`button-size-${size}`), createPrefixClassName(`button-shape-${shape}`), { [createPrefixClassName('button-block')]: block, [createPrefixClassName('button-disabled')]: isDisabled, [createPrefixClassName('button-loading')]: isDisabled, [createPrefixClassName('button-border')]: !border }), onClick: handleClick, disabled: isDisabled }; const childrens = React.useMemo(() => { return loading || _loading ? (jsxs(View, Object.assign({ className: createPrefixClassName('button-loading-wrapper') }, { children: [!!showLoadingIcon && (jsx(View, Object.assign({ className: createPrefixClassName('button-loading-icon') }, { children: loadingIcon || jsx(Icon, { type: 'loading-outlined' }) }))), jsx(View, Object.assign({ className: createPrefixClassName('button-text-overflow-hide') }, { children: loadingText || children }))] }))) : icon ? (jsxs(View, Object.assign({ className: createPrefixClassName('button-loading-wrapper') }, { children: [jsx(View, Object.assign({ className: createPrefixClassName('button-icon') }, { children: jsx(Icon, { type: icon }) })), jsx(View, Object.assign({ className: createPrefixClassName('button-text-overflow-hide') }, { children: children }))] }))) : (children); }, [loading, _loading, showLoadingIcon, loadingIcon, loadingText, children, icon]); return native ? (jsx(Button$1, Object.assign({ formType: formType }, _props, rest, { children: childrens }))) : (jsx(View, Object.assign({}, _props, { children: childrens }))); }; const Buttons = (props) => { return (jsx(View, Object.assign({ style: props.style, className: classnames(createPrefixClassName('buttons'), props.className) }, { children: props.children }))); }; Buttons.Button = Button; const zhCN = { components: { form: { reset: '重置', cancel: '取消', submit: '确定', requiredError: ' 不可为空', validatorError: ' 校验未通过' }, imagePicker: { add: '添加', previewError: '图片预览失败,请稍后重试!', chooseImageError: '获取图片失败,请重新选择或拍摄图片!', loading: '上传中...', uploadError: '上传失败' } } }; const enUS = { components: { form: { reset: 'Reset', cancel: 'Cancel', submit: 'Submit', requiredError: ' is required', validatorError: ' check failed' }, imagePicker: { add: 'Add Image', previewError: 'Image preview failed, please try again later!', chooseImageError: 'Failed to get the picture, please select again or take a picture!', loading: 'Uploading...', uploadError: 'upload failed' } } }; const HOOK_MARK = 'Y-TARO-UI-KEY-YY0380'; class FormStore { constructor(forceRootUpdate) { this.fieldEntities = []; this.values = {}; this.initialValues = {}; this.callbacks = {}; this.validateMessages = []; this.subscribable = true; this.init = true; this.config = { layout: 'horizontal', locale: 'zh-cn', readonly: false, textAlign: 'right', language: zhCN }; this.setConfigs = (config) => { this.config = defaultsDeep(config, this.config); }; this.getFieldValue = (name) => { return this.values[name]; }; this.getFieldsValue = () => this.values; this.reRenderFieldComponent = (values, prevValue, nextValue, reset) => { this.getFieldEntities().forEach(({ name, onStoreChange, shouldUpdate }) => { if ((typeof shouldUpdate === 'function' && shouldUpdate(prevValue, nextValue)) || shouldUpdate) { onStoreChange({}); return; } Object.keys(values).forEach(key => { if (name === key || reset) { onStoreChange({}); } }); }); }; this.setFieldsValue = (values, reset) => { const prevValue = Object.assign({}, this.values); const nextValue = Object.assign(Object.assign({}, this.values), values); this.values = nextValue; if (this.subscribable) { this.forceRootUpdate({}); this.reRenderFieldComponent(values, prevValue, nextValue, reset); } else { this.reRenderFieldComponent(values, prevValue, nextValue, reset); } const { onValuesChange } = this.callbacks; if (onValuesChange) { onValuesChange(values, nextValue); } }; this.getFieldEntities = (pure = false) => { if (!pure) { return this.fieldEntities; } }; this.setInitialValues = (initialValues, init) => { if (this.init || init) { this.initialValues = initialValues || {}; this.setFieldsValue(initialValues); this.init = false; } }; this.resetStatus = () => { // TODO 解决外部使用useForm 实例不能及时卸载,导致无法多次初始化数据的问题 this.init = true; }; this.registerField = (entity) => { this.fieldEntities.push(entity); const { initialValue, name } = entity; if (initialValue !== undefined && name) { const value = { [name]: initialValue }; this.initialValues = Object.assign(Object.assign({}, this.initialValues), value); this.setFieldsValue(value); } return () => { this.fieldEntities = this.fieldEntities.filter(item => item !== entity); const nextValue = Object.assign({}, this.values); delete nextValue[entity.name]; this.values = nextValue; }; }; this.setCallbacks = (callbacks) => { this.callbacks = callbacks; }; this.getFields = () => { return this.fieldEntities.map(entity => { const value = this.getFieldValue(entity.name); return Object.assign({ value }, entity); }); }; this.getField = (name) => { const [entity] = this.fieldEntities.filter(_entity => _entity.name === name); const value = this.getFieldValue(entity.name); return Object.assign({ value }, entity); }; this.getInternalHooks = (key) => { if (key === HOOK_MARK) { return { registerField: this.registerField, setInitialValues: this.setInitialValues, setCallbacks: this.setCallbacks, getFields: this.getFields, setConfigs: this.setConfigs, resetStatus: this.resetStatus }; } return null; }; this.validateFields = () => { this.validateMessages = []; return this.getFields().every(entity => { const { name, rules, label } = entity; if (!rules) { return true; } return rules.every(rule => { // @ts-ignore const beforeLabel = label || ''; if ((typeof rule.required === 'function' ? rule.required() : !!rule.required) && !this.values[name]) { this.validateMessages.push({ name, label, message: rule.message || `${beforeLabel}${this.config.language.components.form.requiredError}` }); return false; } if (rule.validator && !rule.validator(this.values[name])) { this.validateMessages.push({ name, label, message: rule.message || `${beforeLabel}${this.config.language.components.form.validatorError}` }); return false; } return true; }); }); }; this.getFieldsError = () => { this.validateFields(); return this.validateMessages; }; this.useSubscribe = (subscribable) => { this.subscribable = subscribable; }; this.reset = () => { const values = {}; this.getFieldEntities().forEach(({ name }) => { if (name) { values[name] = this.initialValues[name]; } }); this.setFieldsValue(values, true); }; this.submit = () => { const passed = this.validateFields(); if (passed) { this.callbacks.onFinish && this.callbacks.onFinish(this.config.filterInitialValues ? this.fieldEntities.reduce((prev, next) => { return Object.assign(Object.assign({}, prev), { [next.name]: this.values[next.name] }); }, {}) : this.values); } else { this.callbacks.onFinishFailed && this.callbacks.onFinishFailed(this.validateMessages); } }; this.getForm = () => { return { useSubscribe: this.useSubscribe, getFields: this.getFields, getField: this.getField, getFieldValue: this.getFieldValue, getFieldsValue: this.getFieldsValue, setFieldsValue: this.setFieldsValue, setInitialValues: this.setInitialValues, submit: this.submit, reset: this.reset, getInternalHooks: this.getInternalHooks, getFieldsError: this.getFieldsError }; }; this.forceRootUpdate = forceRootUpdate; } } const useForm = (form) => { const formRef = useRef(); const [, forceUpdate] = useState({}); if (!formRef.current) { if (!form) { formRef.current = new FormStore(forceUpdate).getForm(); } else { formRef.current = form; } } return [formRef.current]; }; const Radio = memo((props) => { const { options = [], value, onChange, vlidate, fieldId, disabled, direction = 'column', justify = 'flex-start', icon = 'check-circle-filled' } = props; const onSelfChange = id => { if ((vlidate && vlidate(id)) || (!vlidate && !disabled)) { if (id !== value) { onChange && onChange(id, fieldId); } } }; return (jsx(View, Object.assign({ className: classnames(createPrefixClassName('radio'), createPrefixClassName(`radio-direction-${direction}`), { [createPrefixClassName(`radio-justify-${justify}`)]: direction === 'row' }) }, { children: options.map(item => { return (jsxs(View, Object.assign({ className: classnames(createPrefixClassName('radio-item'), { [createPrefixClassName('radio-item__disabled')]: disabled || item.disabled }), onClick: () => { if (disabled || item.disabled) { return; } onSelfChange(item.id); } }, { children: [value === item.id ? (jsx(Icon, { type: icon, className: classnames(createPrefixClassName('radio-item-checkbox'), { [createPrefixClassName('radio-item-checkbox__disabled')]: disabled || item.disabled, [createPrefixClassName('radio-item-checkbox__checked')]: true }) })) : (jsx(Icon, { type: 'uncheck-circle-outlined', className: classnames(createPrefixClassName('radio-item-checkbox'), { [createPrefixClassName('radio-item-checkbox__disabled')]: disabled || item.disabled }) })), jsx(Text, Object.assign({ className: createPrefixClassName('radio-item-text') }, { children: item.name }))] }), item.id)); }) }))); }); const FormContext = createContext({ readonly: false, disabled: false, validateFirst: false, layout: 'horizontal', textAlign: 'right', locale: 'zh-cn', language: zhCN, formInstance: {} }); const valueKeys = new Map([ [ 'multiSelector', (data, subscripts = [], key) => { if (!subscripts) { return null; } return subscripts.map((subscript, index) => { const item = data[index][subscript]; if (isObject(item)) { return item[key]; } return item; }); } ], [ 'selector', (data, subscript, key) => { const item = data[subscript]; if (isObject(item)) { return item[key]; } return item; } ] ]); const renderKeys = new Map([ [ 'multiSelector', (data, values = [], renderKey, valueKey) => { if (!values) { return null; } return values.map((id, index) => { const item = data[index].find(i => { return i[valueKey] === id; }); if (isObject(item)) { return item[renderKey]; } return item; }); } ], [ 'selector', (data, value, renderKey, valueKey) => { const item = data.find(d => { return d[valueKey] === value; }); if (isObject(item)) { return item[renderKey]; } return item; } ] ]); const selectKeys = new Map([ [ 'region', (_, value) => { if (!value) { return []; } return value; } ], [ 'date', (_, value) => { if (!value) { return ''; } return value; } ], [ 'time', (_, value) => { if (!value) { return '00:00'; } return value; } ], [ 'multiSelector', (data, values, key) => { if (!values) { return []; } return values.map((value, index) => { let _index = 0; data[index].forEach((item, i) => { if ((isObject(item) && item[key] === value) || item === value) { _index = i; } }); return _index; }); } ], [ 'selector', (data, value, key) => { let _index = 0; data.forEach((item, i) => { if ((isObject(item) && item[key] === value) || item === value) { _index = i; } }); return String(_index); } ] ]); const Picker = memo((props) => { const { icon = 'right-outlined', placeholder, disabled = false, value, vlidate, mode = 'date', fieldId, start, end, fields = 'day', range = [], formatValue, rangeKey = 'name', valueKey = 'id', layout, onColumnChange } = props; const { textAlign } = useContext(FormContext); const onChange = e => { if ((vlidate && vlidate(e.detail.value)) || !vlidate) { const getValueByMode = () => { return (valueKeys.has(mode) && valueKeys.get(mode)(range, e.detail.value, valueKey)) || e.detail.value; }; props.onChange && props.onChange(getValueByMode(), fieldId); } }; const isExist = ((Array.isArray(value) && value.length) || ['string', 'number'].includes(typeof value)) && value; const rangeValueByMode = () => { return (renderKeys.has(mode) && renderKeys.get(mode)(range, value, rangeKey, valueKey)) || value; }; const selectValueByMode = () => { return selectKeys.has(mode) && selectKeys.get(mode)(range, value, valueKey); }; return (jsx(View, Object.assign({ className: createPrefixClassName('picker') }, { children: jsx(Picker$1, Object.assign({ disabled: disabled, range: range, mode: mode, onChange: onChange, value: selectValueByMode(), start: start, end: end, fields: fields, onColumnChange: onColumnChange, rangeKey: typeof range[0] === 'object' ? rangeKey : '' }, { children: jsxs(View, Object.assign({ style: layout === 'vertical' ? { justifyContent: 'space-between' } : textAlign == 'right' ? { justifyContent: 'flex-end' } : {}, className: createPrefixClassName('picker-item') }, { children: [isExist ? (jsxs(View, Object.assign({ className: classnames(createPrefixClassName('picker-item-value'), { [createPrefixClassName('picker-item__disabled')]: disabled }) }, { children: [props.addonBefore, (formatValue && formatValue(value)) || rangeValueByMode(), props.addonAfter] }))) : (jsxs(View, Object.assign({ className: createPrefixClassName('picker-placeholder') }, { children: [props.addonPlaceholderBefore, placeholder, props.addonPlaceholderAfter] }))), !disabled && icon !== 'none' && (jsx(Icon, { className: createPrefixClassName('picker-icon'), type: icon }))] })) })) }))); }); const Input = memo((props) => { const { value, onChange, vlidate, onBlur, addonBefore, addonAfter, type = 'text', fieldId, disabled, placeholderClass } = props, resetProps = __rest(props, ["value", "onChange", "vlidate", "onBlur", "addonBefore", "addonAfter", "type", "fieldId", "disabled", "placeholderClass"]); const onSelfChange = e => { const { detail: { value: input } } = e; if ((vlidate && vlidate(input)) || !vlidate) { if (input !== value) { onChange && onChange(input, fieldId); } } }; const onSelfBlur = e => { const { detail: { value: input } } = e; if ((vlidate && vlidate(input)) || !vlidate) { // @ts-ignore onBlur && onBlur(input, fieldId); } }; const inputclassName = classnames(createPrefixClassName('input'), { [createPrefixClassName('input-main__disabled')]: disabled }); return (jsxs(View, Object.assign({ className: inputclassName }, { children: [addonBefore ? jsx(View, Object.assign({ className: createPrefixClassName('input-addonBefore') }, { children: addonBefore })) : null, jsx(Input$1, Object.assign({ disabled: disabled, type: type, onInput: onSelfChange, onBlur: onSelfBlur, value: value, className: createPrefixClassName('input-main'), placeholderClass: classnames(createPrefixClassName('input-placeholder'), placeholderClass) }, resetProps)), addonAfter ? jsx(View, Object.assign({ className: createPrefixClassName('input-addonAfter') }, { children: addonAfter })) : null] }))); }); const Textarea = React.memo((props) => { const { value, onChange, vlidate, fieldId, onBlur, line = 4, disabled, placeholderClass, style, className } = props, resetProps = __rest(props, ["value", "onChange", "vlidate", "fieldId", "onBlur", "line", "disabled", "placeholderClass", "style", "className"]); const onSelfChange = e => { const { detail: { value: input } } = e; if ((vlidate && vlidate(input)) || !vlidate) { if (input !== value) { onChange && onChange(input, fieldId); } } }; const onSelfBlur = e => { const { detail: { value: input } } = e; if ((vlidate && vlidate(input)) || !vlidate) { // @ts-ignore onBlur && onBlur(input, fieldId); } }; const _className = classnames(createPrefixClassName('textarea'), { [createPrefixClassName('textarea-main__disabled')]: disabled }); return (jsx(View, Object.assign({ className: _className }, { children: jsx(Textarea$1, Object.assign({ disabled: disabled, onInput: onSelfChange, onBlur: onSelfBlur, value: value, placeholderClass: classnames(createPrefixClassName('textarea-placeholder'), placeholderClass), className: classnames(createPrefixClassName('textarea-main'), className) }, resetProps, { style: Object.assign(Object.assign({}, style), { height: `${line * 1.5 * 14}px` }) })) }))); }); const DEFAULT_FILE_ITEM = { url: 'url', id: 'file_id', type: 'file_type' }; const ImagePicker = memo((props) => { const { disabled, value = '', borderType = 'dashed', showAdd = true, showDelete = true, maxCount = 9, mode = 'aspectFit', className, addonAfter, onUpload, onGetfile, fileConfig: fileConfigProps, dataFormat, showErrorMessage = true, valueType = 'string' } = props; const { language = zhCN } = useContext(FormContext); const [files, setFiles] = useState([]); const loaded = useRef(false); const prevValue = useRef(value); const fileConfig = useMemo(() => defaultsDeep(fileConfigProps, DEFAULT_FILE_ITEM), [fileConfigProps]); useEffect(() => { if (!loaded.current && prevValue.current && onGetfile) { loaded.current = true; const fatchFiles = async () => { const items = (valueType === 'string' && typeof value === 'string' ? value.split(',') : value); const nextFiles = await items.reduce(async (prev, next) => { const prevFile = await prev.then(); const file = await onGetfile(next).catch(() => { return { [fileConfig.id]: '', [fileConfig.url]: '' }; }); if (dataFormat) { prevFile.push(dataFormat(file)); } else { prevFile.push({ id: file[fileConfig.id], type: file[fileConfig.type], url: file[fileConfig.url] }); } return Promise.resolve(prevFile); }, Promise.resolve([])); setFiles(nextFiles); }; fatchFiles(); } }, [value, onGetfile, fileConfig, dataFormat, loaded, valueType]); const handlePreview = url => { const { onPreview } = props; onPreview && onPreview(); previewImage({ current: url, urls: [url] }).catch(() => { showToast(language.components.imagePicker.previewError); }); }; const formatFiles = (nextFiles) => { const result = nextFiles.map(file => file.id); if (valueType === typeof value) { return result.toString(); } return result; }; const hanldeAddClick = async () => { const { onChange } = props; const { data, error } = await chooseImage({ count: 1, sourceType: ['camera', 'album'], sizeType: ['original', 'compressed'] }); if (error) { const { errMsg = '' } = error; if (errMsg === 'chooseImage:fail cancel') { return; } showToast(errMsg); } const { tempFilePaths: [blobUrl] } = data; if (!blobUrl) { console.log('chooseImage api success but tempFilePaths is empty!'); return showToast(language.components.imagePicker.chooseImageError); } if (onUpload) { showLoading(language.components.imagePicker.loading); const fileData = await onUpload(blobUrl) .finally(() => { hideToast(); }) .catch(({ message = language.components.imagePicker.uploadError }) => { showErrorMessage && showToast(message); }); if (fileData) { const nextFiles = [ ...files, { id: fileData[fileConfig.id], type: fileData[fileConfig.type], url: fileData[fileConfig.url] || blobUrl } ]; setFiles(nextFiles); onChange && onChange(formatFiles(nextFiles)); } } }; const handleDeleteClick = fileIndex => { const { onChange } = props; const nextFiles = [...files]; nextFiles.splice(fileIndex, 1); setFiles(nextFiles); onChange && onChange(formatFiles(nextFiles)); }; const itemClass = classnames(createPrefixClassName('image-picker-item')); return (jsxs(View, Object.assign({ className: classnames(createPrefixClassName('image-picker'), className) }, { children: [files.map((file, index) => (jsx(View, Object.assign({ className: itemClass }, { children: jsxs(View, Object.assign({ className: classnames(createPrefixClassName('image-picker-content'), createPrefixClassName(`image-picker-content__${borderType}`)) }, { children: [!disabled && showDelete && (jsx(Icon, { type: 'close-filled', className: createPrefixClassName('image-picker-content-delete'), onClick: () => handleDeleteClick(index) })), file.url ? (jsx(Image$1, { className: createPrefixClassName('image-picker-content-img'), src: file.url, mode: mode, onClick: e => { e.stopPropagation(); handlePreview(file.url); } })) : null] })) }), `${file.id}-${index}`))), !disabled && showAdd && files.length < maxCount ? (jsx(View, Object.assign({ className: itemClass }, { children: jsx(View, Object.assign({ className: classnames(createPrefixClassName('image-picker-content'), createPrefixClassName(`image-picker-content__${borderType}`)) }, { children: jsxs(View, Object.assign({ className: createPrefixClassName('image-picker-content-wrap'), onClick: hanldeAddClick }, { children: [jsx(Icon, { type: 'camera-add-filled', className: createPrefixClassName('image-picker-content-add') }), jsx(Text, Object.assign({ className: createPrefixClassName('image-picker-content-add-text') }, { children: language.components.imagePicker.add }))] })) })) }))) : null, addonAfter] }))); }); const Upload = memo((props) => { const { fieldId, onChange } = props, resetProps = __rest(props, ["fieldId", "onChange"]); const HandleChange = images => { onChange && onChange(images, fieldId); }; return (jsx(View, Object.assign({ className: createPrefixClassName('upload') }, { children: jsx(ImagePicker, Object.assign({}, resetProps, { onChange: HandleChange })) }))); }); const CheckboxGroupContext = React.createContext(null); const Checkbox = React.memo(props => { const { children, label, disabled, checked = false, onChange, icon = 'check-circle-filled' } = props; const context = React.useContext(CheckboxGroupContext); const onSelfChange = () => { if (disabled) { return; } (context === null || context === void 0 ? void 0 : context.onToggle) && context.onToggle(Object.assign({}, props)); onChange && onChange(!checked); }; return (jsxs(View, Object.assign({ className: classnames(createPrefixClassName('checkbox'), { [createPrefixClassName('checkbox__disabled')]: disabled }), onClick: onSelfChange }, { children: [checked ? (jsx(Icon, { type: icon, className: classnames(createPrefixClassName('checkbox-checkbox'), { [createPrefixClassName('checkbox-checkbox__disabled')]: disabled, [createPrefixClassName('checkbox-checkbox__checked')]: true }) })) : (jsx(Icon, { type: 'uncheck-circle-outlined', className: classnames(createPrefixClassName('checkbox-checkbox'), { [createPrefixClassName('checkbox-checkbox__disabled')]: disabled }) })), label && jsx(Text, Object.assign({ className: createPrefixClassName('checkbox-text') }, { children: label })), children] }))); }); const CheckboxGroup = React.memo((props) => { const { options = [], value = [], onChange, vlidate, fieldId, disabled, max = 0, direction = 'column', justify = 'flex-start', icon = 'check-circle-filled', children } = props; const onToggle = (option) => { const index = value.indexOf(option.value); const _value = [...value]; if (index === -1) { max !== 0 ? _value.length + 1 <= max && _value.push(option.value) : _value.push(option.value); } else { _value.splice(index, 1); } onSelfChange(_value); }; const onSelfChange = v => { if ((vlidate && vlidate(v)) || (!vlidate && !disabled)) { onChange && onChange(v, fieldId); } }; const context = { value, disabled, name: fieldId, onToggle, max }; return (jsx(View, Object.assign({ className: classnames(createPrefixClassName('checkbox-group'), createPrefixClassName(`checkbox-group-direction-${direction}`), { [createPrefixClassName(`checkbox-group-justify-${justify}`)]: direction === 'row' }) }, { children: jsxs(CheckboxGroupContext.Provider, Object.assign({ value: context }, { children: [options && options.map(item => { return (jsx(Checkbox, { checked: value === null || value === void 0 ? void 0 : value.includes(item.id), className: item.className, style: item.style, disabled: disabled || item.disabled, value: item.id, label: item.name, icon: icon }, `${item.name}-${item.id}`)); }), children] })) }))); }); const types = new Map([ ['TEXT', (props) => jsx(View, { children: props.value })], // ['CUSTOM', (props: any) => <View>{props.children}</View>], ['INPUT', (props) => jsx(Input, Object.assign({}, props))], ['PICKER', (props) => jsx(Picker, Object.assign({}, props))], ['RADIO', (props) => jsx(Radio, Object.assign({}, props))], ['TEXTAREA', (props) => jsx(Textarea, Object.assign({}, props))], ['UPLOAD', (props) => jsx(Upload, Object.assign({}, props))], ['CHECKBOX', (props) => jsx(CheckboxGroup, Object.assign({}, props))] ]); const Field = memo(props => { const { initialValue, children, type, label, name, rules, render, componentProps, shouldUpdate, isShow, layout: propsLayout } = props; const [, onStoreChange] = useState({}); const { textAlign, formInstance, layout: contextLayout = 'horizontal', readonly, disabled } = useContext(FormContext); const fieldEntity = useRef({ touched: false, shouldUpdate: isShow !== undefined ? true : shouldUpdate, rules, name, label, initialValue, onStoreChange }); const { setFieldsValue, getInternalHooks, getFieldValue } = formInstance; const { registerField } = getInternalHooks(HOOK_MARK); const layout = propsLayout || contextLayout; useEffect(() => { let unRegisterField = () => { }; unRegisterField = registerField(fieldEntity.current); return () => { unRegisterField(); }; }, [registerField]); const controllProps = useCallback((onChange) => { if (name) { return defaultsDeep({ disabled: disabled || readonly, value: getFieldValue(name) || initialValue, layout, onChange: value => { setFieldsValue({ [name]: value }); onChange && onChange(value); } }, Object.assign({}, componentProps)); } return Object.assign(Object.assign({ disabled: disabled || readonly }, componentProps), { layout }); }, [name, componentProps, getFieldValue, setFieldsValue, disabled, readonly, layout]); let Element = jsx(Text, { children: "not support this type" }); if (React.isValidElement(children)) { Element = React.cloneElement(children, defaultsDeep(controllProps(children.props.onChange), children.props)); } else if (types.has(type)) { Element = types.get(type)(controllProps(componentProps === null || componentProps === void 0 ? void 0 : componentProps.onChange)); } const isRequired = rules && rules.find(rule => (typeof rule.required === 'function' ? rule.required() : !!rule.required)); if (layout) { return (jsxs(Label, Object.assign({ className: classnames(createPrefixClassName('forms-layout'), createPrefixClassName(`forms-layout-${layout}`)), for: name }, { children: [label ? (jsx(View, Object.assign({ className: classnames(createPrefixClassName(`forms-layout-${layout}-name`), { [createPrefixClassName('forms-layout-name__required')]: isRequired, [createPrefixClassName('forms-layout-name')]: true }) }, { children: jsx(View, Object.assign({ className: createPrefixClassName('forms-layout-name-text') }, { children: label })) }))) : null, jsx(View, Object.assign({ style: layout === 'horizontal' ? { textAlign } : {}, className: createPrefixClassName('forms-layout-value'), id: name }, { children: typeof children === 'function' ? children(formInstance) : render ? render(formInstance) : Element }))] }))); } return Element; }); const WrapperField = props => { const { isShow, dependences } = props; const { formInstance } = useContext(FormContext); const { getFieldsValue, getFieldValue } = formInstance; const values = getFieldsValue(); // TODO 处理渲染依赖 if (isShow === false || (typeof isShow === 'function' && !isShow({ getFieldsValue, getFieldValue }))) { return null; } if (typeof dependences === 'string' && !values[dependences]) { return null; } if (typeof dependences === 'function' && !dependences({ getFieldsValue, getFieldValue })) { return null; } if (isObject(dependences) && !dependences.value.test(values[dependences.name])) { return null; } if (Array.isArray(dependences)) { const notPass = !dependences.every(dependence => { if (typeof dependence === 'string') { return !!values[dependence]; } if (isObject(dependence)) { return !!dependence.value.test(values[dependence.name]); } console.warn('依赖的数组项不正确, 请修改为 (string | { name: string; value: RegExp })[]'); return false; }); if (notPass) { return null; } } if (!props.name && !props.label) { // @ts-ignore return typeof props.children === 'function' ? props.children(formInstance) : props.children; } return jsx(Field, Object.assign({}, props)); }; const Fields = props => { const { children } = props; const { formInstance } = useContext(FormContext); // @ts-ignore const { submit, reset } = formInstance; const renderChildren = childs => { if (React.isValidElement(childs)) { return React.cloneElement(childs); } if (Array.isArray(childs)) { return childs.map(child => { if (React.isValidElement(child)) { // 优先校验button 元素 const { props: _props } = child; if (_props['formType'] === 'submit') { return React.cloneElement(child, Object.assign(Object.assign({}, _props), { onClick: () => { // @ts-ignore _props.onClick && _props.onClick(); if (submit) { submit(); } } })); } if (_props['formType'] === 'reset') { return React.cloneElement(child, Object.assign(Object.assign({}, _props), { // @ts-ignore onClick: () => { // @ts-ignore _props.onClick && _props.onClick(); if (reset) { reset(); } } })); } return React.cloneElement(child); } if (Array.isArray(child)) { return renderChildren(child); } if (isObject(child)) { return jsx(WrapperField, Object.assign({}, child)); } }); } }; return jsx(Fragment, { children: renderChildren(children) }); }; const DEFAULT_LOCALE = new Map([ ['zh-cn', zhCN], ['en', enUS] ]); const checkButtonElementOfChildren = childs => { if (React.isValidElement(childs)) { const { props } = childs; if (['submit', 'reset'].includes(props['formType'])) { return true; } return false; } if (Array.isArray(childs)) { return childs.some(child => { if (React.isValidElement(child)) { const { props } = child; if (['submit', 'reset'].includes(props['formType'])) { return true; } return false; } if (Array.isArray(child)) { return checkButtonElementOfChildren(child); } if (isObject(child)) { return false; } }); } }; const InternalForm = (props, ref) => { const { filterInitialValues = true, readonly, layout = 'horizontal', textAlign = 'right', locale = 'zh-cn', fields = [], submitter, onValuesChange, initialValues = {}, disabled, onFinish, onFinishFailed, children, form, loading = false, loadingIcon = jsx(Text, {}), className, style } = props; const [formInstance] = useForm(form); const { getInternalHooks, submit, reset } = formInstance; const { setCallbacks, setInitialValues, setConfigs } = getInternalHooks(HOOK_MARK); useImperativeHandle(ref, () => formInstance); const language = DEFAULT_LOCALE.get(locale); useEffect(() => { setCallbacks({ onFinish, onFinishFailed, onValuesChange }); setConfigs({ disabled, readonly, layout, textAlign, locale, language, filterInitialValues }); }, [ disabled, setConfigs, onFinish, onFinishFailed, onValuesChange, readonly, layout, textAlign, locale, language, setCallbacks, filterInitialValues ]); if (!loading) { setInitialValues(initialValues); } const renderFormItems = useMemo(() => { if (children) { return jsx(Fields, { children: children }); } if (fields) { return jsx(Fields, { children: fields }); } }, [fields, children]); const renderFooterButtons = useMemo(() => { if (readonly) { return null; } if (submitter === null) { return submitter; } if (typeof submitter === 'function') { return submitter({ onSubmit: submit, onReset: reset }); } if (children && checkButtonElementOfChildren(children)) { return null; } if (!submitter) { return (jsx(Block, { children: jsx(Button, Object.assign({ block: true, onClick: submit }, { children: language === null || language === void 0 ? void 0 : language.components.form.submit })) })); } const { resetButton, cancelButton, submitButton = {}, render } = submitter; if (render) { return render({ onSubmit: submit, onReset: reset }); } return (jsxs(Block, { children: [resetButton && (jsx(Button, Object.assign({ block: true, formType: 'reset' }, resetButton, { onClick: reset }, { children: (isObject(resetButton) && resetButton.text) || (language === null || language === void 0 ? void 0 : language.components.form.reset) }))), cancelButton && (jsx(Button, Object.assign({ block: true }, cancelButton, { children: (isObject(cancelButton) && cancelButton.text) || (language === null || language === void 0 ? void 0 : language.components.form.cancel) }))), jsx(Button, Object.assign({ block: true, formType: 'submit' }, submitButton, { onClick: submit }, { children: (isObject(submitButton) && submitButton.text) || (language === null || language === void 0 ? void 0 : language.components.form.submit) }))] })); }, [submitter, language, submit, reset, readonly, children]); const providerValues = useMemo(()