How Formily Revolutionized Our Store‑Headquarters Form Workflow
This article details how Guming's frontend team evolved from simple page‑per‑work‑order implementations to a high‑performance, low‑code Formily ecosystem, covering the technical stack, form configuration platform, custom expression parser, and future visual builder for dynamic business workflows.
Background
Guming is a tea‑chain brand with many franchisee‑related processes such as supply chain, ordering, finance, and equipment. "Upload and dispatch" refers to the communication method between headquarters and stores. This article explains the technical evolution and implementation of that workflow.
Upload and Dispatch
Upload and dispatch consist of two parts:
Store upload: stores send messages, apply for after‑sale service, etc.
Headquarters dispatch: headquarters send messages, tasks, announcements to stores.
Store uploads are mainly work orders, though they can also originate from manual customer service. Headquarters dispatches are similar to work orders on the front‑end.
Work‑order System Evolution
Initially each work order had a dedicated page, which became inefficient as the number of orders grew.
We then tried a form‑model‑description plus rendering approach using the third‑party platform Jiandaoyun. The model was maintained there and rendered on our pages, but it showed several drawbacks:
Redundant fields – the model contained dozens of fields while only a few were needed.
Complex validation.
Inability to express field linkage (e.g., show/hide based on selections).
Front‑end rendering required many if/else statements.
We needed a solution that supported linkage, high performance, strong validation, and elegant implementation. Formily met these requirements, so we adopted it as the core of Guming’s work‑order ecosystem.
Formily Work‑order Ecosystem
Formily
Formily is a data‑and‑protocol‑driven form solution that provides high‑performance, low‑code capabilities. JSON Schema exists independently and is consumed by UI bridge layers, ensuring consistent behavior across frameworks without duplicated parsing logic. Its cross‑framework component ecosystem improves developer efficiency.
Core Advantages
High performance
Out‑of‑the‑box
Efficient linkage logic
Cross‑platform reuse
Dynamic rendering
Core Disadvantage
Steep learning curve, although 2.x has reduced complexity.
Technology Stack Selection
We chose React for the UI bridge and Ant Design for the component library in the admin console. For B‑end mini‑programs we initially used Taro UI, but later built our own component library Gudesion and migrated Formily’s extensions to it.
Integrating Extension Library (Gudesign)
Formily documentation: https://react.formilyjs.org/zh-CN/api/shared/connect
import { connect, mapReadPretty, mapProps } from '@formily/react';
import { getPrefixCls } from '@guming/formily-shared';
import { Input as GudInput, InputProps, TextareaProps } from '@guming/gudesign';
import cls from 'classnames';
import React from 'react';
import { PreviewText } from '../preview-text';
import { getFinallyLayout } from '../shared';
import './index.less';
export type FGInputProps = Omit<InputProps, 'required' | 'label' | 'defaultValue' | 'value'>;
export type FGTextAreaProps = Omit<TextareaProps, 'required' | 'defaultValue' | 'value'>;
export const Input: React.FC<FGInputProps> & { TextArea?: React.FC<FGTextAreaProps> } = connect(
GudInput,
mapProps((props, field) => {
return {
...(getFinallyLayout(field.componentType, field.decoratorProps.layout) === 'column'
? { align: 'left' }
: { align: 'right' }),
placeholder: '请输入',
...props,
className: cls(`${getPrefixCls('comp-input')}`, props.className),
// not needed
required: undefined,
label: undefined,
defaultValue: undefined,
} as InputProps;
}),
mapReadPretty(PreviewText.Input)
);
export default Input;Form Configuration Platform
We built a platform to configure JSON Schema for forms. The configuration method follows Formily’s official documentation.
Business Scenario Implementation
Example: headquarters dispatch a task to collect store façade dimensions. The process includes JSON Schema configuration, rendering the form, submitting data, and workflow nodes such as store, supervisor, finance. Nodes can hide or show fields based on the current step.
Task data structure includes:
Form JSON Schema for rendering.
Form data for initialization and submission.
Node definitions (e.g., SHOP_TASK, SUPERVISOR_TASK, FINANCE_TASK).
Node attributes (processing logic, custom hooks, etc.).
Form Data Structure
{
jsonSchema // form description
fromData // form data
effects: {
nodeA: { // node, store/supervisor
hook // custom logic
action // workflow handling
scope // fields needed by js block
beforeSubmit // hook before submit
afterSubmit // hook after submit
}
}
}effects Configuration
hook
Execution Flow
Parser
Effects contain custom logic expressed as strings. Mini‑programs forbid eval and new Function, so we explored safe execution methods.
Old solution used Acorn to parse simple expressions.
Old Scheme
Parsing a single‑line conditional expression. $deps[0] === '3' ? 'visible' : 'none' Parser implementation snippet:
import { Parser } from 'acorn';
export function CustomCompiler(input, scope) {
const parseNode = Parser.parse(input, { ecmaVersion: 'latest', locations: false });
const targetNode = parseNode?.body?.[0];
if (!targetNode) return false;
const customParser = new CustomParse({ node: targetNode, scope, input });
const result = customParser.execute();
return result;
}Configuration mapping node types to parser methods:
export const PARSE_FN_MAP = {
Identifier: 'parseIdentifier',
MemberExpression: 'parseMember',
LogicalExpression: 'parseLogical',
BinaryExpression: 'parseBinary',
Literal: 'parseLiteral',
ConditionalExpression: 'parseConditional',
UnaryExpression: 'parseUnary',
CallExpression: 'parseCall',
ChainExpression: 'parseChain',
};Current Scheme
For complex expressions we adopted an open‑source sandbox compiler.
export function customCompiler(input, scope) {
const sandbox = new Sandbox();
const exec = sandbox.compile(`return ${input}`);
const result = exec({ ...scope, dayjs }).run();
return result;
}Register the compiler with Formily:
Schema.registerCompiler(customCompiler);Use it for effects hooks:
customCompiler(effects?.[nodeId]?.hook, {
form: f,
...basicEffectsParams,
...(effects?.[nodeId]?.scope || {}),
});We are still seeking a more stable parser solution.
Recommendations
Articles on writing AST parsers:
https://github.com/peakchen90/how-to-write-an-ast-parser/blob/master/README.md
https://github.com/jamiebuilds/the-super-tiny-compiler/blob/master/README.md
Preview
Form rendering screenshot.
Future
The current form configuration lacks a visual builder; developers manually write JSON Schema, which is time‑consuming and error‑prone. We are building a visual configuration platform that generates schema from component metadata, to be released soon.
Conclusion
Formily provides strong performance, low‑code, cross‑platform, and linkage capabilities that greatly help our business, though it introduces learning cost and higher maintenance for large schemas. Our current solution fits our needs and will evolve as the business grows.
Final
Follow the Goodme frontend team public account for more practical sharing.
Goodme Frontend Team
Regularly sharing the team's insights and expertise in the frontend field
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
