mirror of
https://gitee.com/baidu/amis.git
synced 2024-11-30 02:48:55 +08:00
commit
b321bb1366
@ -11,7 +11,8 @@ node ./build/generate-search-data.js
|
||||
|
||||
fis3 release gh-pages -c
|
||||
|
||||
node ./build/upload2cdn.js $1 $2
|
||||
# 不走 cdn 了
|
||||
# node ./build/upload2cdn.js $1 $2
|
||||
|
||||
echo "pushing"
|
||||
|
||||
|
@ -471,6 +471,35 @@ order: 67
|
||||
}
|
||||
```
|
||||
|
||||
可以结合 truncate 用来优化表格中的长内容展示,比如默认只展示 20 个字符,剩下的点击查看更多出现。
|
||||
|
||||
```schema:height="600" scope="body"
|
||||
{
|
||||
"type": "crud",
|
||||
"api": "https://houtai.baidu.com/api/sample?waitSeconds=1",
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"label": "ID"
|
||||
},
|
||||
{
|
||||
"type": "tpl",
|
||||
"name": "engine",
|
||||
"label": "Rendering engine",
|
||||
"tpl": "${engine|truncate:2}",
|
||||
"popOver": {
|
||||
"body": {
|
||||
"type": "tpl",
|
||||
"tpl": "${engine}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> 示例内容没那么长,直接配置成 2 个字符了。
|
||||
|
||||
### 表头样式
|
||||
|
||||
可以配置`"isHead": true`,来让当前列以表头的样式展示。应用场景是:
|
||||
|
@ -683,7 +683,7 @@ if (fis.project.currentMedia() === 'publish') {
|
||||
]
|
||||
});
|
||||
ghPages.match('*', {
|
||||
domain: 'https://bce.bdstatic.com/fex/amis-gh-pages',
|
||||
domain: '/amis',
|
||||
deploy: [
|
||||
fis.plugin('skip-packed'),
|
||||
fis.plugin('local-deliver', {
|
||||
|
@ -46,7 +46,7 @@
|
||||
"keycode": "^2.1.9",
|
||||
"lodash": "^4.17.15",
|
||||
"match-sorter": "2.2.1",
|
||||
"mobx": "^4.5.0 && <= 4.15.4",
|
||||
"mobx": "^4.5.0",
|
||||
"mobx-react": "^6.1.4",
|
||||
"mobx-state-tree": "^3.7.0",
|
||||
"moment": "^2.19.3",
|
||||
|
@ -105,10 +105,6 @@ function createScopedTools(
|
||||
reload(target: string, ctx: any) {
|
||||
const scoped = this;
|
||||
|
||||
if (target === 'window') {
|
||||
return location.reload();
|
||||
}
|
||||
|
||||
let targets =
|
||||
typeof target === 'string' ? target.split(/\s*,\s*/) : target;
|
||||
targets.forEach(name => {
|
||||
@ -128,8 +124,19 @@ function createScopedTools(
|
||||
name = name.substring(0, idx);
|
||||
}
|
||||
|
||||
const component = scoped.getComponentByName(name);
|
||||
component && component.reload && component.reload(subPath, query, ctx);
|
||||
if (name === 'window') {
|
||||
if (query) {
|
||||
const link = location.pathname + '?' + qsstringify(query);
|
||||
env ? env.updateLocation(link, true) : location.replace(link);
|
||||
} else {
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
const component = scoped.getComponentByName(name);
|
||||
component &&
|
||||
component.reload &&
|
||||
component.reload(subPath, query, ctx);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -68,6 +68,7 @@ export interface RendererBasicConfig {
|
||||
test: RegExp | TestFunc;
|
||||
name?: string;
|
||||
storeType?: string;
|
||||
shouldSyncSuperStore?: (store: any, props: any, prevProps: any) => boolean;
|
||||
storeExtendsData?: boolean; // 是否需要继承上层数据。
|
||||
weight?: number; // 权重,值越低越优先命中。
|
||||
isolateScope?: boolean;
|
||||
@ -232,7 +233,8 @@ export function registerRenderer(config: RendererConfig): RendererConfig {
|
||||
if (config.storeType && config.component) {
|
||||
config.component = HocStoreFactory({
|
||||
storeType: config.storeType,
|
||||
extendsData: config.storeExtendsData
|
||||
extendsData: config.storeExtendsData,
|
||||
shouldSyncSuperStore: config.shouldSyncSuperStore
|
||||
})(observer(config.component));
|
||||
}
|
||||
|
||||
@ -335,7 +337,9 @@ export interface RootRendererProps {
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
const RootStoreContext = React.createContext<IRendererStore>(undefined as any);
|
||||
export const RootStoreContext = React.createContext<IRendererStore>(
|
||||
undefined as any
|
||||
);
|
||||
|
||||
export class RootRenderer extends React.Component<RootRendererProps> {
|
||||
state = {
|
||||
@ -653,6 +657,7 @@ class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
|
||||
export function HocStoreFactory(renderer: {
|
||||
storeType: string;
|
||||
extendsData?: boolean;
|
||||
shouldSyncSuperStore?: (store: any, props: any, prevProps: any) => boolean;
|
||||
}): any {
|
||||
return function <T extends React.ComponentType<RendererProps>>(Component: T) {
|
||||
type Props = Omit<
|
||||
@ -698,12 +703,13 @@ export function HocStoreFactory(renderer: {
|
||||
this.renderChild = this.renderChild.bind(this);
|
||||
this.refFn = this.refFn.bind(this);
|
||||
|
||||
const store = (this.store = rootStore.addStore({
|
||||
const store = rootStore.addStore({
|
||||
id: guid(),
|
||||
path: this.props.$path,
|
||||
storeType: renderer.storeType,
|
||||
parentId: this.props.store ? this.props.store.id : ''
|
||||
} as any));
|
||||
}) as IIRendererStore;
|
||||
this.store = store;
|
||||
|
||||
if (renderer.extendsData === false) {
|
||||
store.initData(
|
||||
@ -750,6 +756,12 @@ export function HocStoreFactory(renderer: {
|
||||
const props = this.props;
|
||||
const store = this.store;
|
||||
|
||||
if (
|
||||
renderer.shouldSyncSuperStore?.(store, nextProps, props) === false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderer.extendsData === false) {
|
||||
if (
|
||||
props.defaultData !== nextProps.defaultData ||
|
||||
@ -1099,3 +1111,50 @@ export function getRenderers() {
|
||||
export function getRendererByName(name: string) {
|
||||
return find(renderers, item => item.name === name);
|
||||
}
|
||||
|
||||
export function withRootStore<
|
||||
T extends React.ComponentType<
|
||||
React.ComponentProps<T> & {
|
||||
rootStore: IRendererStore;
|
||||
}
|
||||
>
|
||||
>(ComposedComponent: T) {
|
||||
type OuterProps = JSX.LibraryManagedAttributes<
|
||||
T,
|
||||
Omit<React.ComponentProps<T>, 'rootStore'>
|
||||
>;
|
||||
|
||||
const result = hoistNonReactStatic(
|
||||
class extends React.Component<OuterProps> {
|
||||
static displayName = `WithRootStore(${
|
||||
ComposedComponent.displayName || ComposedComponent.name
|
||||
})`;
|
||||
static contextType = RootStoreContext;
|
||||
static ComposedComponent = ComposedComponent;
|
||||
|
||||
render() {
|
||||
const rootStore = this.context;
|
||||
const injectedProps: {
|
||||
rootStore: IRendererStore;
|
||||
} = {
|
||||
rootStore
|
||||
};
|
||||
|
||||
return (
|
||||
<ComposedComponent
|
||||
{...(this.props as JSX.LibraryManagedAttributes<
|
||||
T,
|
||||
React.ComponentProps<T>
|
||||
>)}
|
||||
{...injectedProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
ComposedComponent
|
||||
);
|
||||
|
||||
return result as typeof result & {
|
||||
ComposedComponent: T;
|
||||
};
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ import './renderers/Page';
|
||||
import './renderers/Panel';
|
||||
import './renderers/Plain';
|
||||
import './renderers/Spinner';
|
||||
import './renderers/Table';
|
||||
import './renderers/Table/index';
|
||||
import './renderers/Tabs';
|
||||
import './renderers/Tpl';
|
||||
import './renderers/Mapping';
|
||||
|
@ -11,6 +11,7 @@ import {Icon} from '../components/icons';
|
||||
import {ModalStore, IModalStore} from '../store/modal';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import {Spinner} from '../components';
|
||||
import {IServiceStore} from '../store/service';
|
||||
|
||||
export interface DialogProps extends RendererProps {
|
||||
title?: string; // 标题
|
||||
@ -518,7 +519,9 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
|
||||
storeType: ModalStore.name,
|
||||
storeExtendsData: false,
|
||||
name: 'dialog',
|
||||
isolateScope: true
|
||||
isolateScope: true,
|
||||
shouldSyncSuperStore: (store: IServiceStore, props: any) =>
|
||||
store.dialogOpen || props.show
|
||||
})
|
||||
export class DialogRenderer extends Dialog {
|
||||
static contextType = ScopedContext;
|
||||
|
@ -10,6 +10,7 @@ import {findDOMNode} from 'react-dom';
|
||||
import {IModalStore, ModalStore} from '../store/modal';
|
||||
import {filter} from '../utils/tpl';
|
||||
import {Spinner} from '../components';
|
||||
import {IServiceStore} from '../store/service';
|
||||
|
||||
export interface DrawerProps extends RendererProps {
|
||||
title?: string; // 标题
|
||||
@ -536,7 +537,9 @@ export default class Drawer extends React.Component<DrawerProps, object> {
|
||||
storeType: ModalStore.name,
|
||||
storeExtendsData: false,
|
||||
name: 'drawer',
|
||||
isolateScope: true
|
||||
isolateScope: true,
|
||||
shouldSyncSuperStore: (store: IServiceStore, props: any) =>
|
||||
store.drawerOpen || props.show
|
||||
})
|
||||
export class DrawerRenderer extends Drawer {
|
||||
static contextType = ScopedContext;
|
||||
|
@ -227,7 +227,7 @@ export default class ComboControl extends React.Component<ComboProps> {
|
||||
componentWillUnmount() {
|
||||
const {formItem} = this.props;
|
||||
|
||||
formItem && formItem.setSubStore(null);
|
||||
formItem && isAlive(formItem) && formItem.setSubStore(null);
|
||||
|
||||
this.toDispose.forEach(fn => fn());
|
||||
this.toDispose = [];
|
||||
|
@ -2,13 +2,25 @@ import React from 'react';
|
||||
import {IFormStore, IFormItemStore} from '../../store/form';
|
||||
import debouce from 'lodash/debounce';
|
||||
|
||||
import {RendererProps, Renderer} from '../../factory';
|
||||
import {
|
||||
RendererProps,
|
||||
Renderer,
|
||||
RootStoreContext,
|
||||
withRootStore
|
||||
} from '../../factory';
|
||||
import {ComboStore, IComboStore, IUniqueGroup} from '../../store/combo';
|
||||
import {anyChanged, promisify, isObject, getVariable} from '../../utils/helper';
|
||||
import {
|
||||
anyChanged,
|
||||
promisify,
|
||||
isObject,
|
||||
getVariable,
|
||||
guid
|
||||
} from '../../utils/helper';
|
||||
import {Schema} from '../../types';
|
||||
import {IIRendererStore} from '../../store';
|
||||
import {IIRendererStore, IRendererStore} from '../../store';
|
||||
import {ScopedContext, IScopedContext} from '../../Scoped';
|
||||
import {reaction} from 'mobx';
|
||||
import {FormItemStore} from '../../store/formItem';
|
||||
|
||||
export interface ControlProps extends RendererProps {
|
||||
control: {
|
||||
@ -30,6 +42,7 @@ export interface ControlProps extends RendererProps {
|
||||
pipeOut?: (value: any, originValue: any, data: any) => any;
|
||||
validate?: (value: any, values: any, name: string) => any;
|
||||
} & Schema;
|
||||
rootStore: IRendererStore;
|
||||
formStore: IFormStore;
|
||||
store: IIRendererStore;
|
||||
addHook: (fn: () => any, type?: 'validate' | 'init' | 'flush') => void;
|
||||
@ -45,7 +58,6 @@ export default class FormControl extends React.PureComponent<
|
||||
ControlState
|
||||
> {
|
||||
static propsList: any = ['control'];
|
||||
|
||||
public model: IFormItemStore | undefined;
|
||||
control: any;
|
||||
value?: any;
|
||||
@ -68,6 +80,7 @@ export default class FormControl extends React.PureComponent<
|
||||
componentWillMount() {
|
||||
const {
|
||||
formStore: form,
|
||||
rootStore,
|
||||
control: {
|
||||
name,
|
||||
id,
|
||||
@ -98,7 +111,16 @@ export default class FormControl extends React.PureComponent<
|
||||
return;
|
||||
}
|
||||
|
||||
const model = (this.model = form.registryItem(name, {
|
||||
const model = rootStore.addStore({
|
||||
id: guid(),
|
||||
path: this.props.$path,
|
||||
storeType: FormItemStore.name,
|
||||
parentId: form.id,
|
||||
name
|
||||
}) as IFormItemStore;
|
||||
this.model = model;
|
||||
form.addFormItem(model);
|
||||
model.config({
|
||||
id,
|
||||
type,
|
||||
required,
|
||||
@ -112,15 +134,11 @@ export default class FormControl extends React.PureComponent<
|
||||
labelField,
|
||||
joinValues,
|
||||
extractValue
|
||||
}));
|
||||
});
|
||||
|
||||
if (
|
||||
this.model.unique &&
|
||||
form.parentStore &&
|
||||
form.parentStore.storeType === ComboStore.name
|
||||
) {
|
||||
if (this.model.unique && form.parentStore?.storeType === ComboStore.name) {
|
||||
const combo = form.parentStore as IComboStore;
|
||||
combo.bindUniuqueItem(this.model);
|
||||
combo.bindUniuqueItem(model);
|
||||
}
|
||||
|
||||
// 同步 value
|
||||
@ -269,7 +287,7 @@ export default class FormControl extends React.PureComponent<
|
||||
combo.unBindUniuqueItem(this.model);
|
||||
}
|
||||
|
||||
this.model && form.unRegistryItem(this.model);
|
||||
this.model && form.removeFormItem(this.model);
|
||||
}
|
||||
|
||||
controlRef(control: any) {
|
||||
@ -536,6 +554,8 @@ export default class FormControl extends React.PureComponent<
|
||||
!/\/control\/control$/i.test(path),
|
||||
name: 'control'
|
||||
})
|
||||
// @ts-ignore
|
||||
@withRootStore
|
||||
export class FormControlRenderer extends FormControl {
|
||||
static displayName = 'Control';
|
||||
static contextType = ScopedContext;
|
||||
|
@ -265,6 +265,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-role="form-item"
|
||||
className={cx(`Form-item Form-item--horizontal`, className, {
|
||||
[`is-error`]: model && !model.valid,
|
||||
[`is-required`]: required
|
||||
@ -384,6 +385,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-role="form-item"
|
||||
className={cx(`Form-item Form-item--${formMode}`, className, {
|
||||
'is-error': model && !model.valid,
|
||||
[`is-required`]: required
|
||||
@ -480,6 +482,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-role="form-item"
|
||||
className={cx(`Form-item Form-item--inline`, className, {
|
||||
'is-error': model && !model.valid,
|
||||
[`is-required`]: required
|
||||
@ -581,6 +584,7 @@ export class FormItemWrap extends React.Component<FormItemProps> {
|
||||
|
||||
return (
|
||||
<div
|
||||
data-role="form-item"
|
||||
className={cx(`Form-item Form-item--${formMode}`, className, {
|
||||
'is-error': model && !model.valid,
|
||||
[`is-required`]: required
|
||||
|
@ -34,6 +34,8 @@ import {LazyComponent} from '../../components';
|
||||
import {isAlive} from 'mobx-state-tree';
|
||||
import {asFormItem} from './Item';
|
||||
import {SimpleMap} from '../../utils/SimpleMap';
|
||||
import {trace} from 'mobx';
|
||||
|
||||
export type FormGroup = FormSchema & {
|
||||
title?: string;
|
||||
className?: string;
|
||||
@ -92,6 +94,7 @@ export interface FormProps extends RendererProps, FormSchema {
|
||||
clearPersistDataAfterSubmit: boolean; // 提交成功后清空本地缓存
|
||||
trimValues?: boolean;
|
||||
lazyLoad?: boolean;
|
||||
simpleMode?: boolean;
|
||||
onInit?: (values: object, props: any) => any;
|
||||
onReset?: (values: object) => void;
|
||||
onSubmit?: (values: object, action: any) => any;
|
||||
@ -163,7 +166,8 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
'lazyChange',
|
||||
'formLazyChange',
|
||||
'lazyLoad',
|
||||
'formInited'
|
||||
'formInited',
|
||||
'simpleMode'
|
||||
];
|
||||
|
||||
hooks: {
|
||||
@ -201,11 +205,15 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
const {store, canAccessSuperData, persistData} = this.props;
|
||||
const {store, canAccessSuperData, persistData, simpleMode} = this.props;
|
||||
|
||||
store.setCanAccessSuperData(canAccessSuperData !== false);
|
||||
persistData && store.getPersistData();
|
||||
|
||||
if (simpleMode) {
|
||||
store.setInited(true);
|
||||
}
|
||||
|
||||
if (
|
||||
store &&
|
||||
store.parentStore &&
|
||||
@ -319,16 +327,6 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
this.asyncCancel && this.asyncCancel();
|
||||
this.disposeOnValidate && this.disposeOnValidate();
|
||||
this.componentCache.dispose();
|
||||
const store = this.props.store;
|
||||
|
||||
if (
|
||||
store &&
|
||||
store.parentStore &&
|
||||
store.parentStore.storeType === 'ComboStore'
|
||||
) {
|
||||
const combo = store.parentStore as IComboStore;
|
||||
isAlive(combo) && combo.removeForm(store);
|
||||
}
|
||||
}
|
||||
|
||||
async onInit() {
|
||||
@ -1165,6 +1163,9 @@ export default class Form extends React.Component<FormProps, object> {
|
||||
translate: __
|
||||
} = this.props;
|
||||
|
||||
// trace(true);
|
||||
// console.log('Form');
|
||||
|
||||
let body: JSX.Element = this.renderBody();
|
||||
|
||||
if (wrapWithPanel) {
|
||||
|
@ -467,6 +467,7 @@ export const HocQuickEdit = (config: Partial<QuickEditConfig> = {}) => (
|
||||
wrapperComponent: 'div',
|
||||
className: cx('Form--quickEdit'),
|
||||
ref: this.formRef,
|
||||
simpleMode: true,
|
||||
onInit: this.handleInit,
|
||||
onChange: this.handleChange
|
||||
})}
|
||||
|
250
src/renderers/Table/HeadCellFilterDropdown.tsx
Normal file
250
src/renderers/Table/HeadCellFilterDropdown.tsx
Normal file
@ -0,0 +1,250 @@
|
||||
import React from 'react';
|
||||
import {Api} from '../../types';
|
||||
import {RendererProps} from '../../factory';
|
||||
import {isApiOutdated, isEffectiveApi, normalizeApi} from '../../utils/api';
|
||||
import {Icon} from '../../components/icons';
|
||||
import Overlay from '../../components/Overlay';
|
||||
import PopOver from '../../components/PopOver';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import Checkbox from '../../components/Checkbox';
|
||||
import xor from 'lodash/xor';
|
||||
|
||||
export interface QuickFilterConfig {
|
||||
options: Array<any>;
|
||||
source: Api;
|
||||
multiple: boolean;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
export interface HeadCellFilterProps extends RendererProps {
|
||||
data: any;
|
||||
name: string;
|
||||
filterable: QuickFilterConfig;
|
||||
onQuery: (values: object) => void;
|
||||
}
|
||||
|
||||
export class HeadCellFilterDropDown extends React.Component<
|
||||
HeadCellFilterProps,
|
||||
any
|
||||
> {
|
||||
state = {
|
||||
isOpened: false,
|
||||
filterOptions: []
|
||||
};
|
||||
|
||||
sourceInvalid: boolean = false;
|
||||
constructor(props: HeadCellFilterProps) {
|
||||
super(props);
|
||||
|
||||
this.open = this.open.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleCheck = this.handleCheck.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {filterable} = this.props;
|
||||
|
||||
if (filterable.source) {
|
||||
this.fetchOptions();
|
||||
} else if (filterable.options.length > 0) {
|
||||
this.setState({
|
||||
filterOptions: this.alterOptions(filterable.options)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: HeadCellFilterProps) {
|
||||
const props = this.props;
|
||||
|
||||
if (
|
||||
props.name !== nextProps.name ||
|
||||
props.filterable !== nextProps.filterable ||
|
||||
props.data !== nextProps.data
|
||||
) {
|
||||
if (nextProps.filterable.source) {
|
||||
this.sourceInvalid = isApiOutdated(
|
||||
props.filterable.source,
|
||||
nextProps.filterable.source,
|
||||
props.data,
|
||||
nextProps.data
|
||||
);
|
||||
} else if (nextProps.filterable.options) {
|
||||
this.setState({
|
||||
filterOptions: this.alterOptions(nextProps.filterable.options || [])
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.sourceInvalid && this.fetchOptions();
|
||||
}
|
||||
|
||||
fetchOptions() {
|
||||
const {env, filterable, data} = this.props;
|
||||
|
||||
if (!isEffectiveApi(filterable.source, data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const api = normalizeApi(filterable.source);
|
||||
api.cache = 3000; // 开启 3s 缓存,因为固顶位置渲染1次会额外多次请求。
|
||||
|
||||
env.fetcher(api, data).then(ret => {
|
||||
let options = (ret.data && ret.data.options) || [];
|
||||
this.setState({
|
||||
filterOptions: ret && ret.data && this.alterOptions(options)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
alterOptions(options: Array<any>) {
|
||||
const {data, filterable, name} = this.props;
|
||||
const filterValue =
|
||||
data && typeof data[name] !== 'undefined' ? data[name] : '';
|
||||
|
||||
if (filterable.multiple) {
|
||||
options = options.map(option => ({
|
||||
...option,
|
||||
selected: filterValue.split(',').indexOf(option.value) > -1
|
||||
}));
|
||||
} else {
|
||||
options = options.map(option => ({
|
||||
...option,
|
||||
selected: option.value === filterValue
|
||||
}));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
open() {
|
||||
this.setState({
|
||||
isOpened: true
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({
|
||||
isOpened: false
|
||||
});
|
||||
}
|
||||
|
||||
handleClick(value: string) {
|
||||
const {onQuery, name} = this.props;
|
||||
|
||||
onQuery({
|
||||
[name]: value
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
|
||||
handleCheck(value: string) {
|
||||
const {data, name, onQuery} = this.props;
|
||||
let query: string;
|
||||
|
||||
if (data[name] && data[name] === value) {
|
||||
query = '';
|
||||
} else {
|
||||
query =
|
||||
(data[name] && xor(data[name].split(','), [value]).join(',')) || value;
|
||||
}
|
||||
|
||||
onQuery({
|
||||
[name]: query
|
||||
});
|
||||
}
|
||||
|
||||
handleReset() {
|
||||
const {name, onQuery} = this.props;
|
||||
onQuery({
|
||||
[name]: undefined
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isOpened, filterOptions} = this.state;
|
||||
const {
|
||||
data,
|
||||
name,
|
||||
filterable,
|
||||
popOverContainer,
|
||||
classPrefix: ns,
|
||||
classnames: cx,
|
||||
translate: __
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cx(
|
||||
`${ns}TableCell-filterBtn`,
|
||||
typeof data[name] !== 'undefined' ? 'is-active' : ''
|
||||
)}
|
||||
>
|
||||
<span onClick={this.open}>
|
||||
<Icon icon="column-filter" className="icon" />
|
||||
</span>
|
||||
{isOpened ? (
|
||||
<Overlay
|
||||
container={popOverContainer || (() => findDOMNode(this))}
|
||||
placement="left-bottom-left-top right-bottom-right-top"
|
||||
target={
|
||||
popOverContainer ? () => findDOMNode(this)!.parentNode : null
|
||||
}
|
||||
show
|
||||
>
|
||||
<PopOver
|
||||
classPrefix={ns}
|
||||
onHide={this.close}
|
||||
className={cx(
|
||||
`${ns}TableCell-filterPopOver`,
|
||||
(filterable as any).className
|
||||
)}
|
||||
overlay
|
||||
>
|
||||
{filterOptions && filterOptions.length > 0 ? (
|
||||
<ul className={cx('DropDown-menu')}>
|
||||
{!filterable.multiple
|
||||
? filterOptions.map((option: any, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={cx('DropDown-divider', {
|
||||
'is-selected': option.selected
|
||||
})}
|
||||
onClick={this.handleClick.bind(this, option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</li>
|
||||
))
|
||||
: filterOptions.map((option: any, index) => (
|
||||
<li key={index} className={cx('DropDown-divider')}>
|
||||
<Checkbox
|
||||
classPrefix={ns}
|
||||
onChange={this.handleCheck.bind(this, option.value)}
|
||||
checked={option.selected}
|
||||
>
|
||||
{option.label}
|
||||
</Checkbox>
|
||||
</li>
|
||||
))}
|
||||
<li
|
||||
key="DropDown-menu-reset"
|
||||
className={cx('DropDown-divider')}
|
||||
onClick={this.handleReset.bind(this)}
|
||||
>
|
||||
{__('重置')}
|
||||
</li>
|
||||
</ul>
|
||||
) : null}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
272
src/renderers/Table/HeadCellSearchDropdown.tsx
Normal file
272
src/renderers/Table/HeadCellSearchDropdown.tsx
Normal file
@ -0,0 +1,272 @@
|
||||
import React from 'react';
|
||||
import {RendererProps} from '../../factory';
|
||||
import {Action} from '../../types';
|
||||
import {Icon} from '../../components/icons';
|
||||
import Overlay from '../../components/Overlay';
|
||||
import {findDOMNode} from 'react-dom';
|
||||
import PopOver from '../../components/PopOver';
|
||||
import {ITableStore} from '../../store/table';
|
||||
|
||||
export interface QuickSearchConfig {
|
||||
type?: string;
|
||||
controls?: any;
|
||||
tabs?: any;
|
||||
fieldSet?: any;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
export interface HeadCellSearchProps extends RendererProps {
|
||||
name: string;
|
||||
searchable: boolean | QuickSearchConfig;
|
||||
classPrefix: string;
|
||||
onQuery: (values: object) => void;
|
||||
}
|
||||
|
||||
export class HeadCellSearchDropDown extends React.Component<
|
||||
HeadCellSearchProps,
|
||||
any
|
||||
> {
|
||||
state = {
|
||||
isOpened: false
|
||||
};
|
||||
|
||||
formItems: Array<string> = [];
|
||||
constructor(props: HeadCellSearchProps) {
|
||||
super(props);
|
||||
|
||||
this.open = this.open.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
this.close = this.close.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleAction = this.handleAction.bind(this);
|
||||
}
|
||||
|
||||
buildSchema() {
|
||||
const {searchable, sortable, name, label, translate: __} = this.props;
|
||||
|
||||
let schema;
|
||||
|
||||
if (searchable === true) {
|
||||
schema = {
|
||||
title: '',
|
||||
controls: [
|
||||
{
|
||||
type: 'text',
|
||||
name,
|
||||
placeholder: label,
|
||||
clearable: true
|
||||
}
|
||||
]
|
||||
};
|
||||
} else if (searchable) {
|
||||
if (searchable.controls || searchable.tabs || searchable.fieldSet) {
|
||||
schema = {
|
||||
title: '',
|
||||
...searchable
|
||||
};
|
||||
} else {
|
||||
schema = {
|
||||
title: '',
|
||||
className: searchable.formClassName,
|
||||
controls: [
|
||||
{
|
||||
type: searchable.type || 'text',
|
||||
name: searchable.name || name,
|
||||
placeholder: label,
|
||||
...searchable
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (schema && schema.controls && sortable) {
|
||||
schema.controls.unshift(
|
||||
{
|
||||
type: 'hidden',
|
||||
name: 'orderBy',
|
||||
value: name
|
||||
},
|
||||
{
|
||||
type: 'button-group',
|
||||
name: 'orderDir',
|
||||
label: __('排序'),
|
||||
options: [
|
||||
{
|
||||
label: __('正序'),
|
||||
value: 'asc'
|
||||
},
|
||||
{
|
||||
label: __('降序'),
|
||||
value: 'desc'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (schema) {
|
||||
const formItems: Array<string> = [];
|
||||
schema.controls?.forEach(
|
||||
(item: any) =>
|
||||
item.name &&
|
||||
item.name !== 'orderBy' &&
|
||||
item.name !== 'orderDir' &&
|
||||
formItems.push(item.name)
|
||||
);
|
||||
this.formItems = formItems;
|
||||
schema = {
|
||||
...schema,
|
||||
type: 'form',
|
||||
wrapperComponent: 'div',
|
||||
actions: [
|
||||
{
|
||||
type: 'button',
|
||||
label: __('重置'),
|
||||
actionType: 'reset'
|
||||
},
|
||||
|
||||
{
|
||||
type: 'button',
|
||||
label: __('取消'),
|
||||
actionType: 'cancel'
|
||||
},
|
||||
|
||||
{
|
||||
label: __('搜索'),
|
||||
type: 'submit',
|
||||
primary: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
return schema || 'error';
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
open() {
|
||||
this.setState({
|
||||
isOpened: true
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.setState({
|
||||
isOpened: false
|
||||
});
|
||||
}
|
||||
|
||||
handleAction(e: any, action: Action, ctx: object) {
|
||||
const {onAction} = this.props;
|
||||
|
||||
if (action.actionType === 'cancel' || action.actionType === 'close') {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.actionType === 'reset') {
|
||||
this.close();
|
||||
this.handleReset();
|
||||
return;
|
||||
}
|
||||
|
||||
onAction && onAction(e, action, ctx);
|
||||
}
|
||||
|
||||
handleReset() {
|
||||
const {onQuery, data, name} = this.props;
|
||||
const values = {...data};
|
||||
this.formItems.forEach(key => (values[key] = undefined));
|
||||
|
||||
if (values.orderBy === name) {
|
||||
values.orderBy = '';
|
||||
values.orderDir = 'asc';
|
||||
}
|
||||
onQuery(values);
|
||||
}
|
||||
|
||||
handleSubmit(values: any) {
|
||||
const {onQuery, name} = this.props;
|
||||
|
||||
this.close();
|
||||
|
||||
if (values.orderDir) {
|
||||
values = {
|
||||
...values,
|
||||
orderBy: name
|
||||
};
|
||||
}
|
||||
|
||||
onQuery(values);
|
||||
}
|
||||
|
||||
isActive() {
|
||||
const {data, name, orderBy} = this.props;
|
||||
|
||||
return orderBy === name || this.formItems.some(key => data?.[key]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
render,
|
||||
name,
|
||||
data,
|
||||
searchable,
|
||||
store,
|
||||
orderBy,
|
||||
popOverContainer,
|
||||
classPrefix: ns,
|
||||
classnames: cx
|
||||
} = this.props;
|
||||
|
||||
const formSchema = this.buildSchema();
|
||||
const isActive = this.isActive();
|
||||
|
||||
return (
|
||||
<span
|
||||
className={cx(`${ns}TableCell-searchBtn`, isActive ? 'is-active' : '')}
|
||||
>
|
||||
<span onClick={this.open}>
|
||||
<Icon icon="search" className="icon" />
|
||||
</span>
|
||||
{this.state.isOpened ? (
|
||||
<Overlay
|
||||
container={popOverContainer || (() => findDOMNode(this))}
|
||||
placement="left-bottom-left-top right-bottom-right-top"
|
||||
target={
|
||||
popOverContainer ? () => findDOMNode(this)!.parentNode : null
|
||||
}
|
||||
show
|
||||
>
|
||||
<PopOver
|
||||
classPrefix={ns}
|
||||
onHide={this.close}
|
||||
className={cx(
|
||||
`${ns}TableCell-searchPopOver`,
|
||||
(searchable as any).className
|
||||
)}
|
||||
overlay
|
||||
>
|
||||
{
|
||||
render('quick-search-form', formSchema, {
|
||||
data: {
|
||||
...data,
|
||||
orderBy: orderBy,
|
||||
orderDir:
|
||||
orderBy === name ? (store as ITableStore).orderDir : ''
|
||||
},
|
||||
onSubmit: this.handleSubmit,
|
||||
onAction: this.handleAction
|
||||
}) as JSX.Element
|
||||
}
|
||||
</PopOver>
|
||||
</Overlay>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
140
src/renderers/Table/TableCell.tsx
Normal file
140
src/renderers/Table/TableCell.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import React from 'react';
|
||||
import {RendererProps, Renderer} from '../../factory';
|
||||
import QuickEdit from '../QuickEdit';
|
||||
import Copyable from '../Copyable';
|
||||
import PopOverable from '../PopOver';
|
||||
import {observer} from 'mobx-react';
|
||||
|
||||
export interface TableCellProps extends RendererProps {
|
||||
wrapperComponent?: React.ReactType;
|
||||
column: object;
|
||||
}
|
||||
export class TableCell extends React.Component<RendererProps> {
|
||||
static defaultProps = {
|
||||
wrapperComponent: 'td'
|
||||
};
|
||||
|
||||
static propsList: Array<string> = [
|
||||
'type',
|
||||
'label',
|
||||
'column',
|
||||
'body',
|
||||
'tpl',
|
||||
'rowSpan',
|
||||
'remark'
|
||||
];
|
||||
|
||||
render() {
|
||||
let {
|
||||
className,
|
||||
render,
|
||||
style,
|
||||
wrapperComponent: Component,
|
||||
column,
|
||||
value,
|
||||
data,
|
||||
children,
|
||||
width,
|
||||
innerClassName,
|
||||
label,
|
||||
tabIndex,
|
||||
onKeyUp,
|
||||
rowSpan,
|
||||
body: _body,
|
||||
tpl,
|
||||
remark,
|
||||
prefix,
|
||||
affix,
|
||||
isHead,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const schema = {
|
||||
...column,
|
||||
className: innerClassName,
|
||||
type: (column && column.type) || 'plain'
|
||||
};
|
||||
|
||||
let body = children
|
||||
? children
|
||||
: render('field', schema, {
|
||||
...rest,
|
||||
value,
|
||||
data
|
||||
});
|
||||
|
||||
if (width) {
|
||||
style = {
|
||||
...style,
|
||||
width: (style && style.width) || width
|
||||
};
|
||||
|
||||
if (!/%$/.test(String(style.width))) {
|
||||
body = (
|
||||
<div style={{width: style.width}}>
|
||||
{prefix}
|
||||
{body}
|
||||
{affix}
|
||||
</div>
|
||||
);
|
||||
prefix = null;
|
||||
affix = null;
|
||||
// delete style.width;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Component) {
|
||||
return body as JSX.Element;
|
||||
}
|
||||
|
||||
if (isHead) {
|
||||
Component = 'th';
|
||||
}
|
||||
|
||||
return (
|
||||
<Component
|
||||
rowSpan={rowSpan > 1 ? rowSpan : undefined}
|
||||
style={style}
|
||||
className={className}
|
||||
tabIndex={tabIndex}
|
||||
onKeyUp={onKeyUp}
|
||||
>
|
||||
{prefix}
|
||||
{body}
|
||||
{affix}
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)table\/(?:.*\/)?cell$/,
|
||||
name: 'table-cell'
|
||||
})
|
||||
@QuickEdit()
|
||||
@PopOverable()
|
||||
@Copyable()
|
||||
@observer
|
||||
export class TableCellRenderer extends TableCell {
|
||||
static propsList = [
|
||||
'quickEdit',
|
||||
'quickEditEnabledOn',
|
||||
'popOver',
|
||||
'copyable',
|
||||
'inline',
|
||||
...TableCell.propsList
|
||||
];
|
||||
}
|
||||
|
||||
@Renderer({
|
||||
test: /(^|\/)field$/,
|
||||
name: 'field'
|
||||
})
|
||||
@PopOverable()
|
||||
@Copyable()
|
||||
export class FieldRenderer extends TableCell {
|
||||
static defaultProps = {
|
||||
...TableCell.defaultProps,
|
||||
wrapperComponent: 'div'
|
||||
};
|
||||
}
|
233
src/renderers/Table/TableContent.tsx
Normal file
233
src/renderers/Table/TableContent.tsx
Normal file
@ -0,0 +1,233 @@
|
||||
import React from 'react';
|
||||
import {ClassNamesFn} from '../../theme';
|
||||
import {IColumn, IRow} from '../../store/table';
|
||||
import {SchemaNode, Action} from '../../types';
|
||||
import {TableRow} from './TableRow';
|
||||
import {filter} from '../../utils/tpl';
|
||||
import {observer} from 'mobx-react';
|
||||
import {trace, reaction} from 'mobx';
|
||||
|
||||
export interface TableContentProps {
|
||||
className?: string;
|
||||
tableClassName?: string;
|
||||
classnames: ClassNamesFn;
|
||||
columns: Array<IColumn>;
|
||||
columnsGroup: Array<{
|
||||
label: string;
|
||||
index: number;
|
||||
colSpan: number;
|
||||
has: Array<any>;
|
||||
}>;
|
||||
rows: Array<IRow>;
|
||||
placeholder?: string;
|
||||
render: (region: string, node: SchemaNode, props?: any) => JSX.Element;
|
||||
onMouseMove: (event: React.MouseEvent) => void;
|
||||
onScroll: (event: React.UIEvent) => void;
|
||||
tableRef: (table?: HTMLTableElement | null) => void;
|
||||
renderHeadCell: (column: IColumn, props?: any) => JSX.Element;
|
||||
renderCell: (
|
||||
region: string,
|
||||
column: IColumn,
|
||||
item: IRow,
|
||||
props: any
|
||||
) => React.ReactNode;
|
||||
onCheck: (item: IRow) => void;
|
||||
onQuickChange?: (
|
||||
item: IRow,
|
||||
values: object,
|
||||
saveImmediately?: boolean | any,
|
||||
savePristine?: boolean
|
||||
) => void;
|
||||
footable?: boolean;
|
||||
footableColumns: Array<IColumn>;
|
||||
checkOnItemClick?: boolean;
|
||||
buildItemProps?: (item: IRow, index: number) => any;
|
||||
onAction?: (e: React.UIEvent<any>, action: Action, ctx: object) => void;
|
||||
rowClassNameExpr?: string;
|
||||
rowClassName?: string;
|
||||
}
|
||||
|
||||
export class TableContent extends React.Component<TableContentProps> {
|
||||
reaction?: () => void;
|
||||
constructor(props: TableContentProps) {
|
||||
super(props);
|
||||
|
||||
const rows = props.rows;
|
||||
|
||||
this.reaction = reaction(
|
||||
() => `${rows.map(item => item.id).join(',')}`,
|
||||
() => this.forceUpdate(),
|
||||
{
|
||||
onError: () => this.reaction!()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: TableContentProps) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.columns !== nextProps.columns) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
componentwillUnmount() {
|
||||
this.reaction?.();
|
||||
}
|
||||
|
||||
renderRows(
|
||||
rows: Array<any>,
|
||||
columns = this.props.columns,
|
||||
rowProps: any = {}
|
||||
): any {
|
||||
const {
|
||||
rowClassName,
|
||||
rowClassNameExpr,
|
||||
onAction,
|
||||
buildItemProps,
|
||||
checkOnItemClick,
|
||||
classnames: cx,
|
||||
render,
|
||||
renderCell,
|
||||
onCheck,
|
||||
onQuickChange,
|
||||
footable,
|
||||
footableColumns
|
||||
} = this.props;
|
||||
|
||||
return rows.map((item: IRow, rowIndex: number) => {
|
||||
const itemProps = buildItemProps ? buildItemProps(item, rowIndex) : null;
|
||||
|
||||
const doms = [
|
||||
<TableRow
|
||||
{...itemProps}
|
||||
classnames={cx}
|
||||
checkOnItemClick={checkOnItemClick}
|
||||
key={item.index}
|
||||
itemIndex={rowIndex}
|
||||
item={item}
|
||||
itemClassName={cx(
|
||||
rowClassNameExpr
|
||||
? filter(rowClassNameExpr, item.data)
|
||||
: rowClassName,
|
||||
{
|
||||
'is-last': item.depth > 1 && rowIndex === rows.length - 1
|
||||
}
|
||||
)}
|
||||
columns={columns}
|
||||
renderCell={renderCell}
|
||||
render={render}
|
||||
onAction={onAction}
|
||||
onCheck={onCheck}
|
||||
// todo 先注释 quickEditEnabled={item.depth === 1}
|
||||
onQuickChange={onQuickChange}
|
||||
{...rowProps}
|
||||
/>
|
||||
];
|
||||
|
||||
if (footable && footableColumns.length) {
|
||||
if (item.depth === 1) {
|
||||
doms.push(
|
||||
<TableRow
|
||||
{...itemProps}
|
||||
classnames={cx}
|
||||
checkOnItemClick={checkOnItemClick}
|
||||
key={`foot-${item.index}`}
|
||||
itemIndex={rowIndex}
|
||||
item={item}
|
||||
itemClassName={cx(
|
||||
rowClassNameExpr
|
||||
? filter(rowClassNameExpr, item.data)
|
||||
: rowClassName
|
||||
)}
|
||||
columns={footableColumns}
|
||||
renderCell={renderCell}
|
||||
render={render}
|
||||
onAction={onAction}
|
||||
onCheck={onCheck}
|
||||
footableMode
|
||||
footableColSpan={columns.length}
|
||||
onQuickChange={onQuickChange}
|
||||
{...rowProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (Array.isArray(item.data.children)) {
|
||||
// 嵌套表格
|
||||
doms.push(
|
||||
...this.renderRows(item.children, columns, {
|
||||
...rowProps,
|
||||
parent: item
|
||||
})
|
||||
);
|
||||
}
|
||||
return doms;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
placeholder,
|
||||
classnames: cx,
|
||||
render,
|
||||
className,
|
||||
columns,
|
||||
columnsGroup,
|
||||
onMouseMove,
|
||||
onScroll,
|
||||
tableRef,
|
||||
rows,
|
||||
renderHeadCell
|
||||
} = this.props;
|
||||
|
||||
const tableClassName = cx('Table-table', this.props.tableClassName);
|
||||
const hideHeader = columns.every(column => !column.label);
|
||||
|
||||
return (
|
||||
<div
|
||||
onMouseMove={onMouseMove}
|
||||
className={cx('Table-content', className)}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
<table ref={tableRef} className={tableClassName}>
|
||||
<thead>
|
||||
{columnsGroup.length ? (
|
||||
<tr>
|
||||
{columnsGroup.map((item, index) => (
|
||||
<th
|
||||
key={index}
|
||||
data-index={item.index}
|
||||
colSpan={item.colSpan}
|
||||
>
|
||||
{item.label ? render('tpl', item.label) : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
) : null}
|
||||
<tr className={hideHeader ? 'fake-hide' : ''}>
|
||||
{columns.map(column =>
|
||||
renderHeadCell(column, {
|
||||
'data-index': column.index,
|
||||
'key': column.index
|
||||
})
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.length ? (
|
||||
this.renderRows(rows, columns)
|
||||
) : (
|
||||
<tr className={cx('Table-placeholder')}>
|
||||
<td colSpan={columns.length}>
|
||||
{render('placeholder', placeholder || '暂无数据')}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
209
src/renderers/Table/TableRow.tsx
Normal file
209
src/renderers/Table/TableRow.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import {observer} from 'mobx-react';
|
||||
import React from 'react';
|
||||
import {IRow, IColumn} from '../../store/table';
|
||||
import {RendererProps} from '../../factory';
|
||||
import {Action} from '../Action';
|
||||
import {reaction} from 'mobx';
|
||||
|
||||
interface TableRowProps extends Pick<RendererProps, 'render'> {
|
||||
onCheck: (item: IRow) => void;
|
||||
classPrefix: string;
|
||||
renderCell: (
|
||||
region: string,
|
||||
column: IColumn,
|
||||
item: IRow,
|
||||
props: any
|
||||
) => React.ReactNode;
|
||||
columns: Array<IColumn>;
|
||||
item: IRow;
|
||||
parent?: IRow;
|
||||
itemClassName?: string;
|
||||
itemIndex: number;
|
||||
regionPrefix?: string;
|
||||
checkOnItemClick?: boolean;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
export class TableRow extends React.Component<TableRowProps> {
|
||||
reaction?: () => void;
|
||||
constructor(props: TableRowProps) {
|
||||
super(props);
|
||||
this.handleAction = this.handleAction.bind(this);
|
||||
this.handleQuickChange = this.handleQuickChange.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
|
||||
const item = props.item;
|
||||
const parent = props.parent;
|
||||
const columns = props.columns;
|
||||
this.reaction = reaction(
|
||||
() =>
|
||||
`${item.isHover}${item.checked}${JSON.stringify(item.data)}${
|
||||
item.moved
|
||||
}${item.modified}${item.expanded}${parent?.expanded}${columns.length}`,
|
||||
() => this.forceUpdate(),
|
||||
{
|
||||
onError: () => this.reaction!()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: TableRowProps) {
|
||||
const props = this.props;
|
||||
if (props.columns !== nextProps.columns) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 不需要更新,因为孩子节点已经 observer 了
|
||||
return false;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.reaction?.();
|
||||
}
|
||||
|
||||
handleClick(e: React.MouseEvent<HTMLTableRowElement>) {
|
||||
const target: HTMLElement = e.target as HTMLElement;
|
||||
const ns = this.props.classPrefix;
|
||||
let formItem;
|
||||
|
||||
if (
|
||||
!e.currentTarget.contains(target) ||
|
||||
~['INPUT', 'TEXTAREA'].indexOf(target.tagName) ||
|
||||
((formItem = target.closest(`button, a, [data-role="form-item"]`)) &&
|
||||
e.currentTarget.contains(formItem))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onCheck(this.props.item);
|
||||
}
|
||||
|
||||
handleAction(e: React.UIEvent<any>, action: Action, ctx: any) {
|
||||
const {onAction, item} = this.props;
|
||||
onAction && onAction(e, action, ctx || item.data);
|
||||
}
|
||||
|
||||
handleQuickChange(
|
||||
values: object,
|
||||
saveImmediately?: boolean,
|
||||
savePristine?: boolean
|
||||
) {
|
||||
const {onQuickChange, item} = this.props;
|
||||
onQuickChange && onQuickChange(item, values, saveImmediately, savePristine);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
itemClassName,
|
||||
itemIndex,
|
||||
item,
|
||||
columns,
|
||||
renderCell,
|
||||
children,
|
||||
footableMode,
|
||||
footableColSpan,
|
||||
regionPrefix,
|
||||
checkOnItemClick,
|
||||
classPrefix: ns,
|
||||
render,
|
||||
classnames: cx,
|
||||
parent,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
// console.log('TableRow');
|
||||
|
||||
if (footableMode) {
|
||||
if (!item.expanded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
data-id={item.id}
|
||||
data-index={item.newIndex}
|
||||
onClick={checkOnItemClick ? this.handleClick : undefined}
|
||||
className={cx(itemClassName, {
|
||||
'is-hovered': item.isHover,
|
||||
'is-checked': item.checked,
|
||||
'is-modified': item.modified,
|
||||
'is-moved': item.moved,
|
||||
[`Table-tr--odd`]: itemIndex % 2 === 0,
|
||||
[`Table-tr--even`]: itemIndex % 2 === 1
|
||||
})}
|
||||
>
|
||||
<td className={cx(`Table-foot`)} colSpan={footableColSpan}>
|
||||
<table className={cx(`Table-footTable`)}>
|
||||
<tbody>
|
||||
{columns.map(column => (
|
||||
<tr key={column.index}>
|
||||
{column.label !== false ? (
|
||||
<th>
|
||||
{render(
|
||||
`${regionPrefix}${itemIndex}/${column.index}/tpl`,
|
||||
column.label
|
||||
)}
|
||||
</th>
|
||||
) : null}
|
||||
|
||||
{renderCell(
|
||||
`${regionPrefix}${itemIndex}/${column.index}`,
|
||||
column,
|
||||
item,
|
||||
{
|
||||
...rest,
|
||||
width: null,
|
||||
rowIndex: itemIndex,
|
||||
colIndex: column.rawIndex,
|
||||
key: column.index,
|
||||
onAction: this.handleAction,
|
||||
onQuickChange: this.handleQuickChange
|
||||
}
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
if (parent && !parent.expanded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
onClick={checkOnItemClick ? this.handleClick : undefined}
|
||||
data-index={item.depth === 1 ? item.newIndex : undefined}
|
||||
data-id={item.id}
|
||||
className={cx(
|
||||
itemClassName,
|
||||
{
|
||||
'is-hovered': item.isHover,
|
||||
'is-checked': item.checked,
|
||||
'is-modified': item.modified,
|
||||
'is-moved': item.moved,
|
||||
'is-expanded': item.expanded,
|
||||
'is-expandable': item.expandable,
|
||||
[`Table-tr--odd`]: itemIndex % 2 === 0,
|
||||
[`Table-tr--even`]: itemIndex % 2 === 1
|
||||
},
|
||||
`Table-tr--${item.depth}th`
|
||||
)}
|
||||
>
|
||||
{columns.map(column =>
|
||||
renderCell(`${itemIndex}/${column.index}`, column, item, {
|
||||
...rest,
|
||||
rowIndex: itemIndex,
|
||||
colIndex: column.rawIndex,
|
||||
key: column.index,
|
||||
onAction: this.handleAction,
|
||||
onQuickChange: this.handleQuickChange
|
||||
})
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,30 @@
|
||||
import {types, SnapshotIn, isAlive} from 'mobx-state-tree';
|
||||
import {types, SnapshotIn, isAlive, onAction} from 'mobx-state-tree';
|
||||
import {iRendererStore} from './iRenderer';
|
||||
import {FormItemStore, IFormItemStore} from './formItem';
|
||||
import {FormStore, IFormStore} from './form';
|
||||
import {FormItemStore} from './formItem';
|
||||
import {FormStore, IFormStore, IFormItemStore} from './form';
|
||||
import {getStoreById} from './index';
|
||||
|
||||
export const UniqueGroup = types.model('UniqueGroup', {
|
||||
name: types.identifier,
|
||||
items: types.array(types.reference(types.late(() => FormItemStore)))
|
||||
});
|
||||
export const UniqueGroup = types
|
||||
.model('UniqueGroup', {
|
||||
name: types.identifier,
|
||||
itemsRef: types.array(types.string)
|
||||
})
|
||||
.views(self => ({
|
||||
get items() {
|
||||
return self.itemsRef.map(
|
||||
id => (getStoreById(id) as any) as IFormItemStore
|
||||
);
|
||||
}
|
||||
}))
|
||||
.actions(self => ({
|
||||
removeItem(item: IFormItemStore) {
|
||||
self.itemsRef.replace(self.itemsRef.filter(id => id !== item.id));
|
||||
},
|
||||
|
||||
addItem(item: IFormItemStore) {
|
||||
self.itemsRef.push(item.id);
|
||||
}
|
||||
}));
|
||||
|
||||
export type IUniqueGroup = typeof UniqueGroup.Type;
|
||||
|
||||
@ -14,13 +32,16 @@ export const ComboStore = iRendererStore
|
||||
.named('ComboStore')
|
||||
.props({
|
||||
uniques: types.map(UniqueGroup),
|
||||
forms: types.array(types.reference(types.late(() => FormStore))),
|
||||
formsRef: types.optional(types.array(types.string), []),
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
length: 0,
|
||||
activeKey: 0
|
||||
})
|
||||
.views(self => ({
|
||||
get forms() {
|
||||
return self.formsRef.map(item => getStoreById(item) as IFormStore);
|
||||
},
|
||||
get addable() {
|
||||
if (self.maxLength && self.length >= self.maxLength) {
|
||||
return false;
|
||||
@ -77,25 +98,38 @@ export const ComboStore = iRendererStore
|
||||
});
|
||||
}
|
||||
let group: IUniqueGroup = self.uniques.get(item.name) as IUniqueGroup;
|
||||
group.items.push(item);
|
||||
group.addItem(item);
|
||||
}
|
||||
|
||||
function unBindUniuqueItem(item: IFormItemStore) {
|
||||
let group: IUniqueGroup = self.uniques.get(item.name) as IUniqueGroup;
|
||||
group.items.remove(item);
|
||||
group.removeItem(item);
|
||||
if (!group.items.length) {
|
||||
self.uniques.delete(item.name);
|
||||
}
|
||||
}
|
||||
|
||||
function addForm(form: IFormStore) {
|
||||
self.forms.push(form);
|
||||
self.formsRef.push(form.id);
|
||||
}
|
||||
|
||||
function removeForm(form: IFormStore) {
|
||||
// form 可能再它自己销毁的是已经被移除了。因为调用的是 destroy,所以 self.forms 里面也被一起移除。
|
||||
// 再来尝试移除,会报错。
|
||||
self.forms.includes(form) && self.forms.remove(form);
|
||||
function onChildStoreDispose(child: IFormStore) {
|
||||
if (child.storeType === FormStore.name) {
|
||||
const idx = self.formsRef.indexOf(child.id);
|
||||
if (~idx) {
|
||||
self.formsRef.splice(idx, 1);
|
||||
child.items.forEach(item => {
|
||||
if (item.unique) {
|
||||
unBindUniuqueItem(item);
|
||||
}
|
||||
});
|
||||
|
||||
self.forms.forEach(item =>
|
||||
item.items.forEach(item => item.unique && item.syncOptions())
|
||||
);
|
||||
}
|
||||
}
|
||||
self.removeChildId(child.id);
|
||||
}
|
||||
|
||||
function setActiveKey(key: number) {
|
||||
@ -108,7 +142,7 @@ export const ComboStore = iRendererStore
|
||||
bindUniuqueItem,
|
||||
unBindUniuqueItem,
|
||||
addForm,
|
||||
removeForm
|
||||
onChildStoreDispose
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -184,14 +184,10 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
delete ctx[options.perPageField || 'perPage'];
|
||||
}
|
||||
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
ctx,
|
||||
{
|
||||
...options,
|
||||
cancelExecutor: (executor: Function) => (fetchCancel = executor)
|
||||
}
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, ctx, {
|
||||
...options,
|
||||
cancelExecutor: (executor: Function) => (fetchCancel = executor)
|
||||
});
|
||||
fetchCancel = null;
|
||||
|
||||
if (!json.ok) {
|
||||
@ -199,7 +195,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
json.msg || options.errorMessage || self.__('获取失败'),
|
||||
true
|
||||
);
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
json.msg,
|
||||
json.msgTimeout !== undefined
|
||||
@ -314,27 +310,22 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
// 配置了获取成功提示后提示,默认是空不会提示。
|
||||
options &&
|
||||
options.successMessage &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
getEnv(self).notify('success', self.msg);
|
||||
}
|
||||
|
||||
self.markFetching(false);
|
||||
return json;
|
||||
} catch (e) {
|
||||
const root = getRoot(self) as IRendererStore;
|
||||
|
||||
if (!isAlive(root) || root.storeType !== 'RendererStore') {
|
||||
// 已经销毁了,不管这些数据了。
|
||||
return;
|
||||
}
|
||||
const env = getEnv(self) as IRendererStore;
|
||||
|
||||
self.markFetching(false);
|
||||
|
||||
if (root.isCancel(e)) {
|
||||
if (env.isCancel(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(e.stack);
|
||||
root.notify('error', e.message);
|
||||
env.notify('error', e.message);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@ -364,11 +355,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
};
|
||||
|
||||
self.markSaving(true);
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
options
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, options);
|
||||
self.markSaving(false);
|
||||
|
||||
if (!isEmpty(json.data) || json.ok) {
|
||||
@ -387,7 +374,7 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
json.msg || options.errorMessage || self.__('保存失败'),
|
||||
true
|
||||
);
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
self.msg,
|
||||
json.msgTimeout !== undefined
|
||||
@ -400,15 +387,12 @@ export const CRUDStore = ServiceStore.named('CRUDStore')
|
||||
throw new ServerError(self.msg);
|
||||
} else {
|
||||
self.updateMessage(json.msg || options.successMessage);
|
||||
self.msg &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
self.msg && getEnv(self).notify('success', self.msg);
|
||||
}
|
||||
return json.data;
|
||||
} catch (e) {
|
||||
self.markSaving(false);
|
||||
e.type !== 'ServerError' &&
|
||||
(getRoot(self) as IRendererStore) &&
|
||||
(getRoot(self) as IRendererStore).notify('error', e.message);
|
||||
e.type !== 'ServerError' && getEnv(self).notify('error', e.message);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {types, getEnv, flow, getRoot, detach} from 'mobx-state-tree';
|
||||
import {types, getEnv, flow, getRoot, detach, destroy} from 'mobx-state-tree';
|
||||
import debounce from 'lodash/debounce';
|
||||
import {ServiceStore} from './service';
|
||||
import {FormItemStore, IFormItemStore, SFormItemStore} from './formItem';
|
||||
@ -17,7 +17,7 @@ import {
|
||||
} from '../utils/helper';
|
||||
import {IComboStore} from './combo';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import {IRendererStore} from '.';
|
||||
import {IRendererStore, getStoreById, removeStore} from '.';
|
||||
|
||||
export const FormStore = ServiceStore.named('FormStore')
|
||||
.props({
|
||||
@ -26,59 +26,70 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
submited: false,
|
||||
submiting: false,
|
||||
validating: false,
|
||||
items: types.optional(types.array(types.late(() => FormItemStore)), []),
|
||||
// items: types.optional(types.array(types.late(() => FormItemStore)), []),
|
||||
itemsRef: types.optional(types.array(types.string), []),
|
||||
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);
|
||||
.views(self => {
|
||||
function getItems() {
|
||||
return self.itemsRef.map(item => getStoreById(item) as IFormItemStore);
|
||||
}
|
||||
}))
|
||||
|
||||
return {
|
||||
get loading() {
|
||||
return self.saving || self.fetching;
|
||||
},
|
||||
|
||||
get items() {
|
||||
return getItems();
|
||||
},
|
||||
|
||||
get errors() {
|
||||
let errors: {
|
||||
[propName: string]: Array<string>;
|
||||
} = {};
|
||||
|
||||
getItems().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 getItems().find(item => item.itemId === id);
|
||||
},
|
||||
|
||||
getItemByName(name: string) {
|
||||
return getItems().find(item => item.name === name);
|
||||
},
|
||||
|
||||
getItemsByName(name: string) {
|
||||
return getItems().filter(item => item.name === name);
|
||||
},
|
||||
|
||||
get valid() {
|
||||
return getItems().every(item => item.valid);
|
||||
},
|
||||
|
||||
get isPristine() {
|
||||
return isEqual(self.pristine, self.data);
|
||||
}
|
||||
};
|
||||
})
|
||||
.actions(self => {
|
||||
function setValues(values: object, tag?: object, replace?: boolean) {
|
||||
self.updateData(values, tag, replace);
|
||||
@ -223,11 +234,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
}
|
||||
|
||||
self.markSaving(true);
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
options
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, options);
|
||||
|
||||
// 失败也同样修改数据,如果有数据的话。
|
||||
if (!isEmpty(json.data) || json.ok) {
|
||||
@ -282,22 +289,16 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
}
|
||||
self.markSaving(false);
|
||||
self.updateMessage(json.msg || (options && options.successMessage));
|
||||
self.msg &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
self.msg && getEnv(self).notify('success', self.msg);
|
||||
return json.data;
|
||||
}
|
||||
} catch (e) {
|
||||
if ((getRoot(self) as IRendererStore).storeType !== 'RendererStore') {
|
||||
// 已经销毁了,不管这些数据了。
|
||||
return;
|
||||
}
|
||||
|
||||
self.markSaving(false);
|
||||
// console.error(e.stack);`
|
||||
|
||||
if (e.type === 'ServerError') {
|
||||
const result = (e as ServerError).response;
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
e.message,
|
||||
result.msgTimeout !== undefined
|
||||
@ -308,7 +309,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
: undefined
|
||||
);
|
||||
} else {
|
||||
(getRoot(self) as IRendererStore).notify('error', e.message);
|
||||
getEnv(self).notify('error', e.message);
|
||||
}
|
||||
|
||||
throw e;
|
||||
@ -332,7 +333,7 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
|
||||
if (!valid) {
|
||||
const msg = failedMessage ?? self.__('表单验证失败,请仔细检查');
|
||||
msg && (getRoot(self) as IRendererStore).notify('error', msg);
|
||||
msg && getEnv(self).notify('error', msg);
|
||||
throw new Error(self.__('验证失败'));
|
||||
}
|
||||
|
||||
@ -420,51 +421,14 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
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;
|
||||
|
||||
function addFormItem(item: IFormItemStore) {
|
||||
self.itemsRef.push(item.id);
|
||||
// 默认值可能在原型上,把他挪到当前对象上。
|
||||
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 removeFormItem(item: IFormItemStore) {
|
||||
removeStore(item);
|
||||
}
|
||||
|
||||
function setCanAccessSuperData(value: boolean = true) {
|
||||
@ -500,6 +464,14 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
localStorage.removeItem(location.pathname + self.path);
|
||||
}
|
||||
|
||||
function onChildStoreDispose(child: IFormItemStore) {
|
||||
if (child.storeType === FormItemStore.name) {
|
||||
const itemsRef = self.itemsRef.filter(id => id !== child.id);
|
||||
self.itemsRef.replace(itemsRef);
|
||||
}
|
||||
self.removeChildId(child.id);
|
||||
}
|
||||
|
||||
return {
|
||||
setInited,
|
||||
setValues,
|
||||
@ -511,15 +483,15 @@ export const FormStore = ServiceStore.named('FormStore')
|
||||
clearErrors,
|
||||
saveRemote,
|
||||
reset,
|
||||
registryItem,
|
||||
unRegistryItem,
|
||||
beforeDetach,
|
||||
addFormItem,
|
||||
removeFormItem,
|
||||
syncOptions,
|
||||
setCanAccessSuperData,
|
||||
deleteValueByName,
|
||||
getPersistData,
|
||||
setPersistData,
|
||||
clearPersistData,
|
||||
onChildStoreDispose,
|
||||
beforeDestroy() {
|
||||
syncOptions.cancel();
|
||||
setPersistData.cancel();
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
flow,
|
||||
getRoot,
|
||||
hasParent,
|
||||
isAlive
|
||||
isAlive,
|
||||
getEnv
|
||||
} from 'mobx-state-tree';
|
||||
import {IFormStore} from './form';
|
||||
import {str2rules, validate as doValidate} from '../utils/validations';
|
||||
@ -29,6 +30,7 @@ import find from 'lodash/find';
|
||||
import {SimpleMap} from '../utils/SimpleMap';
|
||||
import memoize from 'lodash/memoize';
|
||||
import {TranslateFn} from '../locale';
|
||||
import {storeNode} from './node';
|
||||
|
||||
interface IOption {
|
||||
value?: string | number | null;
|
||||
@ -44,9 +46,9 @@ const ErrorDetail = types.model('ErrorDetail', {
|
||||
tag: ''
|
||||
});
|
||||
|
||||
export const FormItemStore = types
|
||||
.model('FormItemStore', {
|
||||
identifier: types.identifier,
|
||||
export const FormItemStore = storeNode
|
||||
.named('FormItemStore')
|
||||
.props({
|
||||
isFocused: false,
|
||||
type: '',
|
||||
unique: false,
|
||||
@ -56,7 +58,7 @@ export const FormItemStore = types
|
||||
messages: types.optional(types.frozen(), {}),
|
||||
errorData: types.optional(types.array(ErrorDetail), []),
|
||||
name: types.string,
|
||||
id: '', // 因为 name 可能会重名,所以加个 id 进来,如果有需要用来定位具体某一个
|
||||
itemId: '', // 因为 name 可能会重名,所以加个 id 进来,如果有需要用来定位具体某一个
|
||||
unsetValueOnInvisible: false,
|
||||
validated: false,
|
||||
validating: false,
|
||||
@ -76,11 +78,11 @@ export const FormItemStore = types
|
||||
})
|
||||
.views(self => {
|
||||
function getForm(): any {
|
||||
return hasParent(self, 2) ? getParent(self, 2) : null;
|
||||
return self.parentStore;
|
||||
}
|
||||
|
||||
function getValue(): any {
|
||||
return getForm() ? getForm().getValueByName(self.name) : undefined;
|
||||
return getForm()?.getValueByName(self.name);
|
||||
}
|
||||
|
||||
function getLastOptionValue(): any {
|
||||
@ -105,9 +107,7 @@ export const FormItemStore = types
|
||||
},
|
||||
|
||||
get prinstine(): any {
|
||||
return (getParent(self, 2) as IFormStore).getPristineValueByName(
|
||||
self.name
|
||||
);
|
||||
return (getForm() as IFormStore).getPristineValueByName(self.name);
|
||||
},
|
||||
|
||||
get errors() {
|
||||
@ -204,11 +204,7 @@ export const FormItemStore = types
|
||||
},
|
||||
|
||||
get __(): TranslateFn {
|
||||
return isAlive(self) &&
|
||||
getRoot(self) &&
|
||||
(getRoot(self) as IRendererStore).storeType === 'RendererStore'
|
||||
? (getRoot(self) as IRendererStore).__
|
||||
: (str: string) => str;
|
||||
return getEnv(self).__;
|
||||
}
|
||||
};
|
||||
})
|
||||
@ -251,7 +247,7 @@ export const FormItemStore = types
|
||||
}
|
||||
|
||||
typeof type !== 'undefined' && (self.type = type);
|
||||
typeof id !== 'undefined' && (self.id = id);
|
||||
typeof id !== 'undefined' && (self.itemId = id);
|
||||
typeof messages !== 'undefined' && (self.messages = messages);
|
||||
typeof required !== 'undefined' && (self.required = !!required);
|
||||
typeof unique !== 'undefined' && (self.unique = !!unique);
|
||||
@ -399,15 +395,11 @@ export const FormItemStore = types
|
||||
|
||||
self.loading = true;
|
||||
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
{
|
||||
autoAppend: false,
|
||||
cancelExecutor: (executor: Function) => (loadCancel = executor),
|
||||
...config
|
||||
}
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, {
|
||||
autoAppend: false,
|
||||
cancelExecutor: (executor: Function) => (loadCancel = executor),
|
||||
...config
|
||||
});
|
||||
loadCancel = null;
|
||||
let result: any = null;
|
||||
|
||||
@ -418,7 +410,7 @@ export const FormItemStore = types
|
||||
reason: json.msg || (config && config.errorMessage)
|
||||
})
|
||||
);
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
self.errors.join(''),
|
||||
json.msgTimeout !== undefined
|
||||
@ -435,21 +427,16 @@ export const FormItemStore = types
|
||||
self.loading = false;
|
||||
return result;
|
||||
} catch (e) {
|
||||
const root = getRoot(self) as IRendererStore;
|
||||
if (root.storeType !== 'RendererStore') {
|
||||
// 已经销毁了,不管这些数据了。
|
||||
return;
|
||||
}
|
||||
const env = getEnv(self);
|
||||
|
||||
self.loading = false;
|
||||
|
||||
if (root.isCancel(e)) {
|
||||
if (env.isCancel(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(e.stack);
|
||||
getRoot(self) &&
|
||||
(getRoot(self) as IRendererStore).notify('error', e.message);
|
||||
env.notify('error', e.message);
|
||||
return;
|
||||
}
|
||||
} as any);
|
||||
|
@ -1,64 +1,26 @@
|
||||
import {types, getRoot, Instance, destroy, isAlive} from 'mobx-state-tree';
|
||||
import {types} from 'mobx-state-tree';
|
||||
import {extendObject, createObject} from '../utils/helper';
|
||||
import {IRendererStore} from './index';
|
||||
import {dataMapping} from '../utils/tpl-builtin';
|
||||
import {SimpleMap} from '../utils/SimpleMap';
|
||||
import {TranslateFn} from '../locale';
|
||||
import {storeNode} from './node';
|
||||
|
||||
export const iRendererStore = types
|
||||
.model('iRendererStore', {
|
||||
id: types.identifier,
|
||||
path: '',
|
||||
storeType: types.string,
|
||||
export const iRendererStore = storeNode
|
||||
.named('iRendererStore')
|
||||
.props({
|
||||
hasRemoteData: types.optional(types.boolean, false),
|
||||
data: types.optional(types.frozen(), {}),
|
||||
initedAt: 0, // 初始 init 的时刻
|
||||
updatedAt: 0, // 从服务端更新时刻
|
||||
pristine: types.optional(types.frozen(), {}),
|
||||
disposed: false,
|
||||
parentId: '',
|
||||
childrenIds: types.optional(types.array(types.string), []),
|
||||
action: types.optional(types.frozen(), undefined),
|
||||
dialogOpen: false,
|
||||
dialogData: types.optional(types.frozen(), undefined),
|
||||
drawerOpen: false,
|
||||
drawerData: types.optional(types.frozen(), undefined)
|
||||
})
|
||||
.views(self => {
|
||||
return {
|
||||
get parentStore(): any {
|
||||
return isAlive(self) &&
|
||||
self.parentId &&
|
||||
getRoot(self) &&
|
||||
(getRoot(self) as IRendererStore).storeType === 'RendererStore'
|
||||
? (getRoot(self) as IRendererStore).stores.get(self.parentId)
|
||||
: null;
|
||||
},
|
||||
|
||||
get __(): TranslateFn {
|
||||
return isAlive(self) &&
|
||||
getRoot(self) &&
|
||||
(getRoot(self) as IRendererStore).storeType === 'RendererStore'
|
||||
? (getRoot(self) as IRendererStore).__
|
||||
: (str: string) => str;
|
||||
}
|
||||
};
|
||||
})
|
||||
.actions(self => {
|
||||
const dialogCallbacks = new SimpleMap<(result?: any) => void>();
|
||||
|
||||
function dispose() {
|
||||
// 先标记自己是要销毁的。
|
||||
self.disposed = true;
|
||||
const parent = self.parentStore;
|
||||
|
||||
if (!self.childrenIds.length) {
|
||||
const id = self.id;
|
||||
destroy(self);
|
||||
parent && parent.onChildDispose(id);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initData(data: object = {}) {
|
||||
self.initedAt = Date.now();
|
||||
@ -177,16 +139,7 @@ export const iRendererStore = types
|
||||
dialogCallbacks.delete(self.drawerData);
|
||||
setTimeout(() => callback(result), 200);
|
||||
}
|
||||
},
|
||||
|
||||
onChildDispose(childId: string) {
|
||||
const childrenIds = self.childrenIds.filter(item => item !== childId);
|
||||
self.childrenIds.replace(childrenIds);
|
||||
|
||||
self.disposed && dispose();
|
||||
},
|
||||
|
||||
dispose
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -9,6 +9,9 @@ import {TableStore} from './table';
|
||||
import {ListStore} from './list';
|
||||
import {ModalStore} from './modal';
|
||||
import {TranslateFn} from '../locale';
|
||||
import find from 'lodash/find';
|
||||
import {IStoreNode} from './node';
|
||||
import {FormItemStore} from './formItem';
|
||||
|
||||
setLivelynessChecking(
|
||||
process.env.NODE_ENV === 'production' ? 'ignore' : 'error'
|
||||
@ -21,30 +24,13 @@ const allowedStoreList = [
|
||||
CRUDStore,
|
||||
TableStore,
|
||||
ListStore,
|
||||
ModalStore
|
||||
ModalStore,
|
||||
FormItemStore
|
||||
];
|
||||
|
||||
export const RendererStore = types
|
||||
.model('RendererStore', {
|
||||
storeType: 'RendererStore',
|
||||
stores: types.map(
|
||||
types.union(
|
||||
{
|
||||
eager: false,
|
||||
dispatcher: (snapshort: SIRendererStore) => {
|
||||
for (let storeFactory of allowedStoreList) {
|
||||
if (storeFactory.name === snapshort.storeType) {
|
||||
return storeFactory;
|
||||
}
|
||||
}
|
||||
|
||||
return iRendererStore;
|
||||
}
|
||||
},
|
||||
iRendererStore,
|
||||
...allowedStoreList
|
||||
)
|
||||
)
|
||||
storeType: 'RendererStore'
|
||||
})
|
||||
.views(self => ({
|
||||
get fetcher() {
|
||||
@ -61,30 +47,42 @@ export const RendererStore = types
|
||||
|
||||
get __(): TranslateFn {
|
||||
return getEnv(self).translate;
|
||||
}
|
||||
}))
|
||||
.views(self => ({
|
||||
},
|
||||
getStoreById(id: string) {
|
||||
return self.stores.get(id);
|
||||
return getStoreById(id);
|
||||
}
|
||||
}))
|
||||
.actions(self => ({
|
||||
addStore(store: SIRendererStore): IIRendererStore {
|
||||
if (self.stores.has(store.id as string)) {
|
||||
return self.stores.get(store.id) as IIRendererStore;
|
||||
}
|
||||
addStore(store: {
|
||||
storeType: string;
|
||||
id: string;
|
||||
path: string;
|
||||
parentId?: string;
|
||||
[propName: string]: any;
|
||||
}): IStoreNode {
|
||||
const factory = find(
|
||||
allowedStoreList,
|
||||
item => item.name === store.storeType
|
||||
)!;
|
||||
|
||||
if (store.parentId) {
|
||||
const parent = self.stores.get(store.parentId) as IIRendererStore;
|
||||
parent.childrenIds.push(store.id);
|
||||
}
|
||||
return addStore(factory.create(store, getEnv(self)));
|
||||
|
||||
self.stores.put(store);
|
||||
return self.stores.get(store.id) as IIRendererStore;
|
||||
// if (self.stores.has(store.id as string)) {
|
||||
// return self.stores.get(store.id) as IIRendererStore;
|
||||
// }
|
||||
|
||||
// if (store.parentId) {
|
||||
// const parent = self.stores.get(store.parentId) as IIRendererStore;
|
||||
// parent.childrenIds.push(store.id);
|
||||
// }
|
||||
|
||||
// self.stores.put(store);
|
||||
// return self.stores.get(store.id) as IIRendererStore;
|
||||
},
|
||||
|
||||
removeStore(store: IIRendererStore) {
|
||||
store.dispose();
|
||||
removeStore(store: IStoreNode) {
|
||||
// store.dispose();
|
||||
removeStore(store);
|
||||
}
|
||||
}));
|
||||
|
||||
@ -93,3 +91,32 @@ export {iRendererStore, IIRendererStore};
|
||||
export const RegisterStore = function (store: any) {
|
||||
allowedStoreList.push(store as any);
|
||||
};
|
||||
|
||||
const stores: {
|
||||
[propName: string]: IStoreNode;
|
||||
} = {};
|
||||
|
||||
export function addStore(store: IStoreNode) {
|
||||
if (stores[store.id]) {
|
||||
return stores[store.id];
|
||||
}
|
||||
|
||||
stores[store.id] = store;
|
||||
|
||||
// drawer dialog 不加进去,否则有些容器就不会自我销毁 store 了。
|
||||
if (store.parentId && !/(?:dialog|drawer)$/.test(store.path)) {
|
||||
const parent = stores[store.parentId] as IIRendererStore;
|
||||
parent.addChildId(store.id);
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
export function removeStore(store: IStoreNode) {
|
||||
const id = store.id;
|
||||
store.dispose(() => delete stores[id]);
|
||||
}
|
||||
|
||||
export function getStoreById(id: string) {
|
||||
return stores[id];
|
||||
}
|
||||
|
66
src/store/node.ts
Normal file
66
src/store/node.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {types, destroy, isAlive, detach, getEnv} from 'mobx-state-tree';
|
||||
import {getStoreById} from './index';
|
||||
|
||||
export const storeNode = types
|
||||
.model('storeNode', {
|
||||
id: types.identifier,
|
||||
path: '',
|
||||
storeType: types.string,
|
||||
disposed: false,
|
||||
parentId: '',
|
||||
childrenIds: types.optional(types.array(types.string), [])
|
||||
})
|
||||
.views(self => {
|
||||
return {
|
||||
get parentStore(): any {
|
||||
return isAlive(self) && self.parentId
|
||||
? getStoreById(self.parentId)
|
||||
: null;
|
||||
},
|
||||
|
||||
get __() {
|
||||
return getEnv(self).__;
|
||||
}
|
||||
};
|
||||
})
|
||||
.actions(self => {
|
||||
function addChildId(id: string) {
|
||||
self.childrenIds.push(id);
|
||||
}
|
||||
|
||||
function removeChildId(id: string) {
|
||||
const childrenIds = self.childrenIds.filter(item => item !== id);
|
||||
self.childrenIds.replace(childrenIds);
|
||||
|
||||
self.disposed && dispose();
|
||||
}
|
||||
|
||||
function dispose(callback?: () => void) {
|
||||
// 先标记自己是要销毁的。
|
||||
self.disposed = true;
|
||||
|
||||
if (/(?:dialog|drawer)$/.test(self.path)) {
|
||||
destroy(self);
|
||||
callback?.();
|
||||
} else if (!self.childrenIds.length) {
|
||||
const parent = self.parentStore;
|
||||
parent?.onChildStoreDispose?.(self);
|
||||
destroy(self);
|
||||
callback?.();
|
||||
// destroy(self);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
onChildStoreDispose(child: any) {
|
||||
removeChildId(child.id);
|
||||
},
|
||||
|
||||
dispose,
|
||||
addChildId,
|
||||
removeChildId
|
||||
};
|
||||
});
|
||||
|
||||
export type IStoreNode = typeof storeNode.Type;
|
||||
export type SIStoreNode = typeof storeNode.SnapshotType;
|
@ -81,19 +81,15 @@ export const ServiceStore = iRendererStore
|
||||
}
|
||||
|
||||
(options && options.silent) || markFetching(true);
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
{
|
||||
...options,
|
||||
cancelExecutor: (executor: Function) => (fetchCancel = executor)
|
||||
}
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, {
|
||||
...options,
|
||||
cancelExecutor: (executor: Function) => (fetchCancel = executor)
|
||||
});
|
||||
fetchCancel = null;
|
||||
|
||||
if (!json.ok) {
|
||||
updateMessage(json.msg || (options && options.errorMessage), true);
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
json.msg,
|
||||
json.msgTimeout !== undefined
|
||||
@ -125,25 +121,21 @@ export const ServiceStore = iRendererStore
|
||||
// 配置了获取成功提示后提示,默认是空不会提示。
|
||||
options &&
|
||||
options.successMessage &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
getEnv(self).notify('success', self.msg);
|
||||
}
|
||||
|
||||
markFetching(false);
|
||||
return json;
|
||||
} catch (e) {
|
||||
const root = getRoot(self) as IRendererStore;
|
||||
if (!isAlive(root) || root.storeType !== 'RendererStore') {
|
||||
// 已经销毁了,不管这些数据了。
|
||||
return;
|
||||
}
|
||||
const env = getEnv(self);
|
||||
|
||||
if (root.isCancel(e)) {
|
||||
if (env.isCancel(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
markFetching(false);
|
||||
e.stack && console.error(e.stack);
|
||||
root.notify('error', e.message || e);
|
||||
env.notify('error', e.message || e);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@ -169,7 +161,7 @@ export const ServiceStore = iRendererStore
|
||||
}
|
||||
|
||||
(options && options.silent) || markFetching(true);
|
||||
const json: Payload = yield ((getRoot(
|
||||
const json: Payload = yield ((getEnv(
|
||||
self
|
||||
) as IRendererStore) as IRendererStore).fetcher(api, data, {
|
||||
...options,
|
||||
@ -192,7 +184,7 @@ export const ServiceStore = iRendererStore
|
||||
|
||||
if (!json.ok) {
|
||||
updateMessage(json.msg || (options && options.errorMessage), true);
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
self.msg,
|
||||
json.msgTimeout !== undefined
|
||||
@ -216,25 +208,21 @@ export const ServiceStore = iRendererStore
|
||||
// 配置了获取成功提示后提示,默认是空不会提示。
|
||||
options &&
|
||||
options.successMessage &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
getEnv(self).notify('success', self.msg);
|
||||
}
|
||||
|
||||
markFetching(false);
|
||||
return json;
|
||||
} catch (e) {
|
||||
const root = getRoot(self) as IRendererStore;
|
||||
if (!isAlive(root) || root.storeType !== 'RendererStore') {
|
||||
// 已经销毁了,不管这些数据了。
|
||||
return;
|
||||
}
|
||||
const env = getEnv(self);
|
||||
|
||||
if (root.isCancel(e)) {
|
||||
if (env.isCancel(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
markFetching(false);
|
||||
e.stack && console.error(e.stack);
|
||||
root.notify('error', e.message || e);
|
||||
env.notify('error', e.message || e);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@ -259,11 +247,7 @@ export const ServiceStore = iRendererStore
|
||||
}
|
||||
markSaving(true);
|
||||
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
options
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, options);
|
||||
|
||||
if (!isEmpty(json.data) || json.ok) {
|
||||
self.updatedAt = Date.now();
|
||||
@ -294,8 +278,7 @@ export const ServiceStore = iRendererStore
|
||||
}
|
||||
|
||||
updateMessage(json.msg || (options && options.successMessage));
|
||||
self.msg &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
self.msg && getEnv(self).notify('success', self.msg);
|
||||
}
|
||||
|
||||
markSaving(false);
|
||||
@ -305,7 +288,7 @@ export const ServiceStore = iRendererStore
|
||||
// console.log(e.stack);
|
||||
if (e.type === 'ServerError') {
|
||||
const result = (e as ServerError).response;
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
e.message,
|
||||
result.msgTimeout !== undefined
|
||||
@ -316,7 +299,7 @@ export const ServiceStore = iRendererStore
|
||||
: undefined
|
||||
);
|
||||
} else {
|
||||
(getRoot(self) as IRendererStore).notify('error', e.message);
|
||||
getEnv(self).notify('error', e.message);
|
||||
}
|
||||
|
||||
throw e;
|
||||
@ -363,11 +346,7 @@ export const ServiceStore = iRendererStore
|
||||
};
|
||||
}
|
||||
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
options
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, options);
|
||||
fetchSchemaCancel = null;
|
||||
|
||||
if (!json.ok) {
|
||||
@ -377,7 +356,7 @@ export const ServiceStore = iRendererStore
|
||||
self.__('获取失败,请重试'),
|
||||
true
|
||||
);
|
||||
(getRoot(self) as IRendererStore).notify(
|
||||
getEnv(self).notify(
|
||||
'error',
|
||||
self.msg,
|
||||
json.msgTimeout !== undefined
|
||||
@ -403,26 +382,22 @@ export const ServiceStore = iRendererStore
|
||||
// 配置了获取成功提示后提示,默认是空不会提示。
|
||||
options &&
|
||||
options.successMessage &&
|
||||
(getRoot(self) as IRendererStore).notify('success', self.msg);
|
||||
getEnv(self).notify('success', self.msg);
|
||||
}
|
||||
|
||||
self.initializing = false;
|
||||
return json.data;
|
||||
} catch (e) {
|
||||
const root = getRoot(self) as IRendererStore;
|
||||
if (!isAlive(root) || root.storeType !== 'RendererStore') {
|
||||
// 已经销毁了,不管这些数据了。
|
||||
return;
|
||||
}
|
||||
const env = getEnv(self);
|
||||
|
||||
self.initializing = false;
|
||||
|
||||
if (root.isCancel(e)) {
|
||||
if (env.isCancel(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.stack && console.error(e.stack);
|
||||
root.notify('error', e.message || e);
|
||||
env.notify('error', e.message || e);
|
||||
}
|
||||
});
|
||||
|
||||
@ -441,11 +416,7 @@ export const ServiceStore = iRendererStore
|
||||
|
||||
try {
|
||||
self.checking = true;
|
||||
const json: Payload = yield (getRoot(self) as IRendererStore).fetcher(
|
||||
api,
|
||||
data,
|
||||
options
|
||||
);
|
||||
const json: Payload = yield getEnv(self).fetcher(api, data, options);
|
||||
json.ok &&
|
||||
self.updateData(
|
||||
json.data,
|
||||
|
@ -118,9 +118,9 @@ export const Row = types
|
||||
},
|
||||
|
||||
get expanded(): boolean {
|
||||
return (getParent(self, self.depth * 2) as ITableStore).isExpanded(
|
||||
self as IRow
|
||||
);
|
||||
const table = getParent(self, self.depth * 2) as ITableStore;
|
||||
|
||||
return !table.dragging && table.isExpanded(self as IRow);
|
||||
},
|
||||
|
||||
get moved() {
|
||||
@ -173,6 +173,11 @@ export const Row = types
|
||||
|
||||
setIsHover(value: boolean) {
|
||||
self.isHover = value;
|
||||
},
|
||||
|
||||
replaceWith(data: any) {
|
||||
delete data.id;
|
||||
Object.keys(data).forEach(key => ((self as any)[key] = data[key]));
|
||||
}
|
||||
}));
|
||||
|
||||
@ -680,7 +685,6 @@ export const TableStore = iRendererStore
|
||||
pristine: item,
|
||||
data: item,
|
||||
rowSpans: {},
|
||||
modified: false,
|
||||
children:
|
||||
item && Array.isArray(item.children)
|
||||
? initChildren(item.children, 1, key, id)
|
||||
@ -696,7 +700,7 @@ export const TableStore = iRendererStore
|
||||
arr = autoCombineCell(arr, self.columns, self.combineNum);
|
||||
}
|
||||
|
||||
self.rows.replace(arr as Array<IRow>);
|
||||
replaceRow(arr);
|
||||
self.isNested = self.rows.some(item => item.children.length);
|
||||
|
||||
const expand = self.footable && self.footable.expand;
|
||||
@ -717,6 +721,30 @@ export const TableStore = iRendererStore
|
||||
self.dragging = false;
|
||||
}
|
||||
|
||||
// 尽可能的复用 row
|
||||
function replaceRow(arr: Array<SRow>) {
|
||||
const pool = arr.concat();
|
||||
|
||||
// 把多的删了先
|
||||
if (self.rows.length > arr.length) {
|
||||
self.rows.splice(arr.length, self.rows.length - arr.length);
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
const len = self.rows.length;
|
||||
while (pool.length) {
|
||||
const item = pool.shift()!;
|
||||
|
||||
if (index < len) {
|
||||
self.rows[index].replaceWith(item);
|
||||
} else {
|
||||
const row = Row.create(item);
|
||||
self.rows.push(row);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelected(selected: Array<any>, valueField?: string) {
|
||||
self.selectedRows.clear();
|
||||
self.rows.forEach(item => {
|
||||
|
Loading…
Reference in New Issue
Block a user