Skip to content

Commit

Permalink
feat: 新增全局变量功能
Browse files Browse the repository at this point in the history
  • Loading branch information
2betop committed Dec 10, 2024
1 parent 395468b commit a2e14ec
Show file tree
Hide file tree
Showing 37 changed files with 2,179 additions and 255 deletions.
116 changes: 48 additions & 68 deletions examples/components/EventAction/update-data/SetVariable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,24 @@ import update from 'lodash/update';
import isEqual from 'lodash/isEqual';
import {cloneObject, setVariable} from 'amis-core';

const namespace = 'appVariables';
const initData = JSON.parse(sessionStorage.getItem(namespace)) || {
ProductName: 'BCC',
Banlance: 1234.888,
ProductNum: 10,
isOnline: false,
ProductList: ['BCC', 'BOS', 'VPC'],
PROFILE: {
FirstName: 'Amis',
Age: 18,
Address: {
street: 'ShangDi',
postcode: 100001
}
const variables = [
{
key: 'xxx',
defaultValue: 'yyy'
},

{
key: 'ProductName',
defaultValue: ''
},

{
key: 'count',
defaultValue: 0,
scope: 'page',
storageOn: 'client'
}
};
];

export default {
/** schema配置 */
Expand All @@ -30,7 +32,7 @@ export default {
body: [
{
type: 'tpl',
tpl: '变量的命名空间通过环境变量设置为了<code>appVariables</code>, 可以通过\\${appVariables.xxx}来取值'
tpl: '变量的命名空间通过环境变量设置为了<code>global</code>, 可以通过\\${global.xxx}来取值'
},
{
type: 'container',
Expand All @@ -43,12 +45,12 @@ export default {
body: [
{
type: 'tpl',
tpl: '<h2>数据域appVariables</h2>'
tpl: '<h2>数据域global</h2>'
},
{
type: 'json',
id: 'u:44521540e64c',
source: '${appVariables}',
source: '${global}',
levelExpand: 10
},
{
Expand All @@ -59,7 +61,7 @@ export default {
},
{
type: 'tpl',
tpl: '<h3>变量中的<code>ProductName (\\${appVariables.ProductName})</code>: <strong>${appVariables.ProductName|default:-}</strong></h3>',
tpl: '<h3>变量中的<code>ProductName (\\${global.ProductName})</code>: <strong>${global.ProductName|default:-}</strong></h3>',
inline: false,
id: 'u:98ed5c5534ef'
}
Expand All @@ -82,7 +84,7 @@ export default {
actions: [
{
args: {
path: 'appVariables.ProductName',
path: 'global.ProductName',
value: '${event.data.value}'
},
actionType: 'setValue'
Expand All @@ -95,8 +97,31 @@ export default {
type: 'static',
label: '产品名称描述',
id: 'u:7bd4e2a4f95e',
value: '${appVariables.ProductName}',
value: '${global.ProductName}',
name: 'staticName'
},

{
type: 'input-number',
label: 'Count (client)',
description: '存储自动存入客户端,刷新页面后数据还在',
id: 'u:7bd4e2a4f95e',
value: '${global.count}',
name: 'count',
onEvent: {
change: {
weight: 0,
actions: [
{
args: {
path: 'global.count',
value: '${event.data.value}'
},
actionType: 'setValue'
}
]
}
}
}
],
id: 'u:dc2580fa447a'
Expand All @@ -109,7 +134,7 @@ export default {
actions: [
{
args: {
path: 'appVariables.ProductName',
path: 'global.ProductName',
value: '${event.data.ProductName}'
},
actionType: 'setValue'
Expand All @@ -119,51 +144,6 @@ export default {
}
},
props: {
data: {[namespace]: JSON.parse(sessionStorage.getItem(namespace))}
},
/** 环境变量 */
env: {
beforeSetData: (renderer, action, event) => {
const value = event?.data?.value ?? action?.args?.value;
const path = action?.args?.path;
const {session = 'global'} = renderer.props?.env ?? {};
const comptList = event?.context?.scoped?.getComponentsByRefPath(
session,
path
);

for (let component of comptList) {
const {$path: targetPath, $schema: targetSchema} = component?.props;
const {$path: triggerPath, $schema: triggerSchema} = renderer?.props;

if (
!component.setData &&
(targetPath === triggerPath || isEqual(targetSchema, triggerSchema))
) {
continue;
}

if (component?.props?.onChange) {
const submitOnChange = !!component.props?.$schema?.submitOnChange;

component.props.onChange(value, submitOnChange, true);
} else if (component?.setData) {
const currentData = JSON.parse(
sessionStorage.getItem(namespace) || JSON.stringify(initData)
);
const varPath = path.replace(/^appVariables\./, '');

update(currentData, varPath, origin => {
return typeof value === typeof origin ? value : origin;
});

sessionStorage.setItem(namespace, JSON.stringify(currentData));
const newCtx = cloneObject(component?.props?.data ?? {});
setVariable(newCtx, path, value, true);

component.setData(newCtx, false);
}
}
}
globalVars: variables
}
};
7 changes: 1 addition & 6 deletions examples/components/Example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -668,12 +668,7 @@ export const examples = [
{
label: '更新全局变量数据',
path: '/examples/action/setdata/variable',
component: makeSchemaRenderer(
SetVariable.schema,
SetVariable.props ?? {},
true,
SetVariable.env
)
component: makeSchemaRenderer(SetVariable)
}
]
},
Expand Down
9 changes: 9 additions & 0 deletions examples/components/SchemaRender.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ export default function (schema, schemaProps, showCode, envOverrides) {
};
}

if (!schema.type && schema.schema) {
schemaProps = schema.props;
envOverrides = schema.env;
showCode = schema.showCode ?? true;
schema = {
...schema.schema
};
}

return withRouter(
class extends React.Component {
static displayName = 'SchemaRenderer';
Expand Down
3 changes: 3 additions & 0 deletions packages/amis-core/src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ import {
StatusScopedWrapper,
StatusScopedProps
} from './StatusScoped';
import {GlobalVariableItem} from './globalVar';

export interface RootRenderProps {
globalVars?: Array<GlobalVariableItem>;
location?: Location;
theme?: string;
data?: Record<string, any>;
context?: Record<string, any>;
locale?: string;
[propName: string]: any;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/amis-core/src/RootRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
this.store.updateContext(props.context);

Check failure on line 38 in packages/amis-core/src/RootRenderer.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

Argument of type 'Record<string, any> | undefined' is not assignable to parameter of type 'GlobalVarContext'.
this.store.initData(props.data);
this.store.updateLocation(props.location, this.props.env?.parseLocation);
this.store.setGlobalVars(props.globalVars);

// 将数据里面的函数批量的绑定到 this 上
bulkBindFunctions<RootRenderer /*为毛 this 的类型自动识别不出来?*/>(this, [
Expand Down Expand Up @@ -97,6 +98,11 @@ export class RootRenderer extends React.Component<RootRendererProps> {
componentDidUpdate(prevProps: RootRendererProps) {
const props = this.props;

// 更新全局变量
if (props.globalVars !== prevProps.globalVars) {
this.store.setGlobalVars(props.globalVars);
}

if (props.location !== prevProps.location) {
this.store.updateLocation(props.location, this.props.env?.parseLocation);
}
Expand Down Expand Up @@ -521,7 +527,7 @@ export class RootRenderer extends React.Component<RootRendererProps> {
}

render() {
const {pathPrefix, schema, render, ...rest} = this.props;
const {pathPrefix, schema, render, globalVars, ...rest} = this.props;
const store = this.store;

if (store.runtimeError) {
Expand Down
38 changes: 35 additions & 3 deletions packages/amis-core/src/SchemaRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {isExpression} from './utils/formula';
import {StatusScopedProps} from './StatusScoped';
import {evalExpression, filter} from './utils/tpl';
import Animations from './components/Animations';
import {cloneObject} from './utils/object';
import {observeGlobalVars} from './globalVar';

interface SchemaRendererProps
extends Partial<Omit<RendererProps, 'statusStore'>>,
Expand Down Expand Up @@ -101,6 +103,8 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
schema: any;
path: string;

tmpData: any;

toDispose: Array<() => any> = [];
unbindEvent: (() => void) | undefined = undefined;
unbindGlobalEvent: (() => void) | undefined = undefined;
Expand All @@ -114,13 +118,16 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
this.reRender = this.reRender.bind(this);
this.resolveRenderer(this.props);
this.dispatchEvent = this.dispatchEvent.bind(this);
this.handleGlobalVarChange = this.handleGlobalVarChange.bind(this);

const schema = props.schema;

// 监听statusStore更新
this.toDispose.push(
reaction(
() => {
const id = filter(props.schema.id, props.data);
const name = filter(props.schema.name, props.data);
const id = filter(schema.id, props.data);
const name = filter(schema.name, props.data);
return `${
props.statusStore.visibleState[id] ??
props.statusStore.visibleState[name]
Expand All @@ -135,6 +142,10 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
() => this.forceUpdate()
)
);

this.toDispose.push(
observeGlobalVars(schema, props.topStore, this.handleGlobalVarChange)
);
}

componentWillUnmount() {
Expand Down Expand Up @@ -172,6 +183,21 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
return false;
}

handleGlobalVarChange() {
const handler = this.renderer?.onGlobalVarChanged;
const newData = cloneObject(this.props.data);

// 如果渲染器自己做了实现,且返回 false,则不再继续往下执行
if (handler?.(this.cRef, this.props.schema, newData) === false) {
return;
}

this.tmpData = newData;
this.forceUpdate(() => {
delete this.tmpData;
});
}

resolveRenderer(props: SchemaRendererProps, force = false): any {
let schema = props.schema;
let path = props.$path;
Expand Down Expand Up @@ -308,7 +334,10 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
? evalExpression(_.staticOn, rest.data)
: _.static ?? rest.defaultStatic),
...subProps,
data: subProps.data || rest.data,
data:
this.tmpData && subProps.data === this.props.data
? this.tmpData
: subProps.data || rest.data,
env: env
});
}
Expand Down Expand Up @@ -517,6 +546,9 @@ export class SchemaRenderer extends React.Component<SchemaRendererProps, any> {
mobileUI: schema.useMobileUI === false ? false : rest.mobileUI
};

// 用于全局变量刷新
props.data = this.tmpData || props.data;

// style 支持公式
if (schema.style) {
(props as any).style = buildStyle(schema.style, detectData);
Expand Down
5 changes: 5 additions & 0 deletions packages/amis-core/src/actions/CmptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export class CmptAction implements RendererAction {

/** 如果args中携带path参数, 则认为是全局变量赋值, 否则认为是组件变量赋值 */
if (action.actionType === 'setValue' && path && typeof path === 'string') {
if (path.startsWith('global.')) {
const topStore = renderer.props.topStore;
topStore?.updateGlobalVarValue(path.substring(7), action.args.value);
}

const beforeSetData = event?.context?.env?.beforeSetData;
if (beforeSetData && typeof beforeSetData === 'function') {
const res = await beforeSetData(renderer, action, event);
Expand Down
8 changes: 8 additions & 0 deletions packages/amis-core/src/factory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ export interface RendererBasicConfig {
prevProps: any
) => boolean | undefined;
storeExtendsData?: boolean | ((props: any) => boolean); // 是否需要继承上层数据。
// 当全局渲染器关联的全局变量发生变化时执行
// 因为全局变量永远都是最新的,有些组件是 didUpdate 的时候比对有变化才更新
// 这里给组件一个自定义更新的机会
onGlobalVarChanged?: (
instance: React.Component,
schema: any,
data: any
) => void | boolean;
weight?: number; // 权重,值越低越优先命中。
isolateScope?: boolean;
isFormItem?: boolean;
Expand Down
Loading

0 comments on commit a2e14ec

Please sign in to comment.