UNPKG

amis

Version:

一种MIS页面生成工具

455 lines (384 loc) 14.9 kB
import {types, getEnv, flow, getRoot, detach} from 'mobx-state-tree'; import debounce = require('lodash/debounce'); import {ServiceStore} from './service'; import {FormItemStore, IFormItemStore, SFormItemStore} from './formItem'; import {Api, fetchOptions, Payload} from '../types'; import { getVariable, setVariable, deleteVariable, cloneObject, createObject, difference, guid, isObject, isEmpty, mapObject } from '../utils/helper'; import {IComboStore} from './combo'; import isEqual = require('lodash/isEqual'); import {IRendererStore} from '.'; class ServerError extends Error { type = 'ServerError'; } export const FormStore = ServiceStore.named('FormStore') .props({ inited: false, validated: false, submited: false, submiting: false, validating: false, items: types.optional(types.array(types.late(() => FormItemStore)), []), canAccessSuperData: true, persistData: false }) .views(self => ({ get loading() { return self.saving || self.fetching; }, get errors() { let errors: { [propName: string]: Array<string>; } = {}; self.items.forEach(item => { if (!item.valid) { errors[item.name] = Array.isArray(errors[item.name]) ? errors[item.name].concat(item.errors) : item.errors.concat(); } }); return errors; }, getValueByName(name: string) { return getVariable(self.data, name, self.canAccessSuperData); }, getPristineValueByName(name: string) { return getVariable(self.pristine, name); }, getItemById(id: string) { return self.items.find(item => item.id === id); }, getItemByName(name: string) { return self.items.find(item => item.name === name); }, getItemsByName(name: string) { return self.items.filter(item => item.name === name); }, get valid() { return self.items.every(item => item.valid); }, get isPristine() { return isEqual(self.pristine, self.data); } })) .actions(self => { function setValues(values: object, tag?: object) { self.updateData(values, tag); // 同步 options syncOptions(); } function setValueByName(name: string, value: any, isPristine: boolean = false, force: boolean = false) { // 没有变化就不跑了。 const origin = getVariable(self.data, name, false); const prev = self.data; const data = cloneObject(self.data); if (value !== origin) { if (prev.__prev) { // 基于之前的 __prev 改 const prevData = cloneObject(prev.__prev); setVariable(prevData, name, origin); Object.defineProperty(data, '__prev', { value: prevData, enumerable: false, configurable: false, writable: false }); } else { Object.defineProperty(data, '__prev', { value: {...prev}, enumerable: false, configurable: false, writable: false }); } } else if (!force) { return; } setVariable(data, name, value); if (isPristine) { const pristine = cloneObject(self.pristine); setVariable(pristine, name, value); self.pristine = pristine; } if (!data.__pristine) { Object.defineProperty(data, '__pristine', { value: self.pristine, enumerable: false, configurable: false, writable: false }); } self.data = data; if (self.persistData) { setPersistData(); } // 同步 options syncOptions(); } function deleteValueByName(name: string) { const prev = self.data; const data = cloneObject(self.data); if (prev.__prev) { // 基于之前的 __prev 改 const prevData = cloneObject(prev.__prev); setVariable(prevData, name, getVariable(prev, name)); Object.defineProperty(data, '__prev', { value: prevData, enumerable: false, configurable: false, writable: false }); } else { Object.defineProperty(data, '__prev', { value: {...prev}, enumerable: false, configurable: false, writable: false }); } deleteVariable(data, name); self.data = data; } function trimValues() { let data = mapObject(self.data, (item: any) => (typeof item === 'string' ? item.trim() : item)); self.updateData(data); } function syncOptions() { self.items.forEach(item => item.syncOptions()); } const saveRemote: (api: Api, data?: object, options?: fetchOptions) => Promise<any> = flow(function* saveRemote( api: string, data: object, options: fetchOptions = {} ) { try { options = { method: 'post', // 默认走 post ...options }; if (options && options.beforeSend) { let ret = options.beforeSend(data); if (ret && ret.then) { ret = yield ret; } if (ret === false) { return; } } self.markSaving(true); const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(api, data, options); // 失败也同样 merge,如果有数据的话。 if (!isEmpty(json.data) || json.ok) { setValues(json.data, { __saved: Date.now() }); self.updatedAt = Date.now(); } if (!json.ok) { // 验证错误 if (json.status === 422 && json.errors) { const errors = json.errors; Object.keys(errors).forEach((key: string) => { const item = self.getItemById(key); if (item) { item.setError(errors[key]); } else { self.getItemsByName(key).forEach(item => item.setError(errors[key])); } }); self.updateMessage(json.msg || (options && options.errorMessage) || '验证错误', true); } else { self.updateMessage(json.msg || (options && options.errorMessage), true); } throw new ServerError(self.msg); } else { if (options && options.onSuccess) { const ret = options.onSuccess(json); if (ret && ret.then) { yield ret; } } self.markSaving(false); self.updateMessage(json.msg || (options && options.successMessage)); self.msg && (getRoot(self) as IRendererStore).notify('success', self.msg); return json.data; } } catch (e) { if ((getRoot(self) as IRendererStore).storeType !== 'RendererStore') { // 已经销毁了,不管这些数据了。 return; } self.markSaving(false); // console.error(e.stack);` (getRoot(self) as IRendererStore).notify('error', e.message); throw e; } }); const submit: ( fn?: (values: object) => Promise<any>, hooks?: Array<() => Promise<any>>, failedMessage?: string ) => Promise<any> = flow(function* submit(fn: any, hooks?: Array<() => Promise<any>>, failedMessage?: string) { self.submited = true; self.submiting = true; try { let valid = yield validate(hooks); if (!valid) { (getRoot(self) as IRendererStore).notify('error', failedMessage || '表单验证失败,请仔细检查'); throw new Error('验证失败'); } if (fn) { const diff = difference(self.data, self.pristine); yield fn( createObject( createObject(self.data.__super, { diff: diff, __diff: diff, pristine: self.pristine }), self.data ) ); } } finally { self.submiting = false; } return self.data; }); const validate: (hooks?: Array<() => Promise<any>>, forceValidate?: boolean) => Promise<boolean> = flow( function* validate(hooks?: Array<() => Promise<any>>, forceValidate?: boolean) { self.validating = true; self.validated = true; const items = self.items.concat(); for (let i = 0, len = items.length; i < len; i++) { let item = items[i] as IFormItemStore; if (!item.validated || forceValidate) { yield item.validate(); } } if (hooks && hooks.length) { for (let i = 0, len = hooks.length; i < len; i++) { yield hooks[i](); } } self.validating = false; return self.valid; } ); const validateFields: (fields: Array<string>) => Promise<boolean> = flow(function* validateFields( fields: Array<string> ) { self.validating = true; const items = self.items.concat(); let result: Array<boolean> = []; for (let i = 0, len = items.length; i < len; i++) { let item = items[i] as IFormItemStore; if (~fields.indexOf(item.name)) { result.push(yield item.validate()); } } self.validating = false; return result.every(item => item); }); function clearErrors() { const items = self.items.concat(); items.forEach(item => item.reset()); } function reset(cb?: (data: any) => void, resetData: boolean = true) { if (resetData) { self.data = self.pristine; } // 值可能变了,重新验证一次。 self.validated = false; self.submited = false; self.items.forEach(item => item.reset()); cb && cb(self.data); } function registryItem( name: string, options?: Partial<SFormItemStore> & { value?: any; } ): IFormItemStore { let item: IFormItemStore; self.items.push({ identifier: guid(), name } as any); item = self.items[self.items.length - 1] as IFormItemStore; // 默认值可能在原型上,把他挪到当前对象上。 setValueByName(item.name, item.value, false, false); options && item.config(options); return item; } function unRegistryItem(item: IFormItemStore) { detach(item); } function beforeDetach() { // 本来是想在组件销毁的时候处理, // 但是 componentWillUnmout 是父级先执行,form 都销毁了 formItem 就取不到 父级就不是 combo 了。 if (self.parentStore && self.parentStore.storeType === 'ComboStore') { const combo = self.parentStore as IComboStore; self.items.forEach(item => { if (item.unique) { combo.unBindUniuqueItem(item); } }); combo.removeForm(self as IFormStore); combo.forms.forEach(item => item.items.forEach(item => item.unique && item.syncOptions())); } self.items.forEach(item => detach(item)); } function setCanAccessSuperData(value: boolean = true) { self.canAccessSuperData = value; } function setInited(value: boolean) { self.inited = value; } const setPersistData = debounce(() => { localStorage.setItem(location.pathname + self.path, JSON.stringify(self.data)); }, 250); function getPersistData() { self.persistData = true; let data = localStorage.getItem(location.pathname + self.path); if (data) { self.updateData(JSON.parse(data)); } } function clearPersistData() { localStorage.removeItem(location.pathname + self.path); } return { setInited, setValues, setValueByName, trimValues, submit, validate, validateFields, clearErrors, saveRemote, reset, registryItem, unRegistryItem, beforeDetach, syncOptions, setCanAccessSuperData, deleteValueByName, getPersistData, setPersistData, clearPersistData }; }); export type IFormStore = typeof FormStore.Type; export {IFormItemStore};