silence / blog Goto Github PK
View Code? Open in Web Editor NEW博客嘛内容为王,不整 github.io 那些花里胡哨的东西了
License: MIT License
博客嘛内容为王,不整 github.io 那些花里胡哨的东西了
License: MIT License
在平常的基本需求迭代中有许许多多像下面这样的纯粹的CRUD页面的开发。在B端业务中我们不免需要写一遍又一遍相似的样板代码,虽然不同的项目业务逻辑不同,但是我们确需要写相似的 表格展示页、表单、搜索,这样的应用我们统称为CURD类应用。据不保守估计这样的类似应用可能会占到业务的**30%**以上。
A low-code development platform (LCDP) is software that provides a development environment used to create application software through graphical user interfaces and configuration instead of traditional hand-coded computer programming. A low-code model enables developers of varied experience levels to create applications using a visual user interface in combination with model-driven logic.
lowcode 是指通过 GUI、配置化的方式代替传统的手写代码编程,让经验背景不同的开发者都能在低代码开发平台上,基于可视化的 UI 和模型驱动的逻辑来创建应用程序。
例如阿里的icework飞冰是一个可以通过物料市场进行可视化拖拽的低代码平台。配合它们的物料市场,能极大的加速前端应用的构建。
但是我们这里不采用可视化拖拽的方式来做腾讯云控制台业务:
tea-component
还尚不具备拖拽生成low-code的能力回到数据的model上来,其实我们的表单表格的核心都是基于数据来展现的,那么数据是哪来的呢?数据库里存取的,这些数据库里存的数据都可以称之为元数据,这里我们接小刀分享的文章前端元编程——使用注解加速你的前端开发介绍的前端元编程的概念,我们使用typescript里的Decorator
,Reflect Metadata
来定义和提取元数据。
Decorator
和Reflect
将CRUD页面所需的样板类方法属性元编程在Model上。export class Person {
@TableColumn({ header: "ID" })
id: string;
@FormField({
label: "名字",
validationSchema: Yup.string().max(20, "姓名最多20个字符").required("名字不能为空"),
})
@TableColumn({ header: "名字" })
name: string;
@FormField({
label: "年龄",
validationSchema: Yup.number().required("年龄不能为空"),
})
@TableColumn({ header: "年龄" })
age: number;
@FormField({
label: "性别",
validationSchema: Yup.string().required("性别不能为空"),
enum: Gender,
})
@TableColumn({ header: "性别" })
gender: Gender;
@FormField({
label: "爱好",
validationSchema: Yup.array().required("爱好不能为空"),
enum: Hobbies,
})
@TableColumn({
header: "爱好",
render: (record: Person) => record.hobbies.join(","),
})
hobbies: Hobbies[];
@FormField({
label: "是否需要996",
validationSchema: Yup.boolean().required("不能为空"),
})
@TableColumn({
header: "是否需要996",
render: (record: Person) => (record.is996 ? "是" : "否"),
})
is996: boolean;
}
相应的Decorator
的定义如下:
// 表格Column元数据和表格 propertyKey list
export const COLUMN_METADATA = "column";
export const COLUMN_LIST = [] as string[];
// 表单field元数据和表单 propertyKey list
export const FORM_FIELD_METADATA = "form";
export const FORM_FIELD_LIST = [] as string[];
export interface IColumnOptions {
/** 表格头 */
header: string;
/** 表格渲染方法 */
render?: (record) => React.ReactNode;
}
export function Column(options: IColumnOptions): PropertyDecorator {
return (target, propertyKey: string) => {
Reflect.defineMetadata(COLUMN_METADATA, options, target, propertyKey);
COLUMN_LIST.push(propertyKey);
};
}
export interface IFormFieldOptions {
/** 表单label */
label: string;
/** 表单验证属性 */
validationSchema?: any;
/** 枚举属性 */
enum?: Object | (string | number)[];
}
export function FormField(options: IFormFieldOptions): PropertyDecorator {
return (target, propertyKey: string) => {
Reflect.defineMetadata(FORM_FIELD_METADATA, options, target, propertyKey);
FORM_FIELD_LIST.push(propertyKey);
};
}
String
就对应 Input
组件 , Number
就对应 Input type=number
的组件,Array
就对应Checkbox
组件,Boolean
就对应Switch
组件。如果String
再定义enum
属性就会变成Radio
组件。/** 定义一个从元数据里获取FormField的函数,Type<any>代表类的类型 */
export function getFormField<T = any>(target: Type<T>) {
/** FORM_FIELD_LIST存放的是表格FormField元数据的列表,通过Reflect.getMetadata来获取类里给元数据定义的类型和选项 */
const fields = FORM_FIELD_LIST.map((propertyKey) => {
const type: Function = Reflect.getMetadata("design:type", target.prototype, propertyKey);
const options: IFormFieldOptions = Reflect.getMetadata(FORM_FIELD_METADATA, target.prototype, propertyKey);
const fieldOptions = {
name: propertyKey,
label: options.label,
};
switch (type.name) {
case "String":
return options.enum ? (
/** 若Form选项里有enum选项则定义为Radio组件,否则定义为Input组件 */
<RadioField
{...fieldOptions}
options={Object.values(options.enum).map((el) => ({
value: el,
text: el,
}))}
key={propertyKey}
/>
) : (
<InputField {...fieldOptions} size="full" key={propertyKey} />
);
case "Number":
/** 给Input组件定义type="number"属性 */
return <InputField {...fieldOptions} type="number" size="full" key={propertyKey} />;
case "Array":
/** 若为Array类型,则定义为Checkbox组件 */
return (
<CheckboxField
{...fieldOptions}
options={Object.values(options.enum).map((el) => ({
value: el,
text: el,
}))}
key={propertyKey}
/>
);
/** 若为Boolean类型,则定义为Switch组件 */
case "Boolean":
return <SwitchField {...fieldOptions} key={propertyKey} />;
}
});
return fields;
}
Person Class
直接生成所需要的列表和表单展示页了。但是我们还缺少最重要的api请求接口。到目前为止都只是前端的Model生成Form和Table的内容,但是其实数据库也是可以根据元数据生成的。
ORM全称是:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。举例来说就是,我定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
type Gender = 'male' | 'female';
type Hobbies =
| 'basketball'
| 'badminton'
| 'billiards'
| 'football'
| 'movie'
| 'hiking';
@Entity('persons')
export class PersonEntity {
@PrimaryGeneratedColumn()
id: string;
@ApiProperty()
@Column()
name: string;
@ApiProperty()
@Column()
age: number;
@ApiProperty()
@Column({
type: 'enum',
enum: ['male', 'female'],
default: 'male',
})
gender: Gender;
@ApiProperty()
@Column({
type: 'set',
enum: [
'basketball',
'badminton',
'billiards',
'football',
'movie',
'hiking',
],
default: [],
})
hobbies: Hobbies[];
@ApiProperty()
@Column()
is996: boolean;
@CreateDateColumn()
create_at: Date;
@UpdateDateColumn()
update_at: Date;
}
import { Controller } from '@nestjs/common';
import { Crud, CrudController } from '@nestjsx/crud';
import { PersonEntity } from './person.entity';
import { PersonService } from './person.service';
@Crud({
model: {
type: PersonEntity,
},
})
@Controller('person')
export class PersonController implements CrudController<PersonEntity> {
constructor(public service: PersonService) {}
}
这样我们就实现了一个由元数据驱动的CRUD前端应用。
这种前后端独立应用开发的开发模式正好对应微前端+后台微服务化的开发模式。
最近BFF的概念很火,可以让后端将ui对应的接口我们自己来维护,即将后端的Controller层接过来,这样就不会有前后端沟通某个数据排序谁来做的问题了(ps:不过这样的话前端就得干更多的活了😓,让后端更专注于底层)
这样做的缺点也是显而易见的,像这样将部分业务逻辑耦合到Model层后,若后续需要添加更多的功能,Model层会越来越庞大,也越来越难以维护,所以我建议只针对这种纯粹的CRUD应用采用这种前端元编程方式,不需要过多的配置,由数据Model直出页面布局。但是更复杂的前端应用就不合适了。当然这里也只是提供一种思路。相信未来大家一定能有更好的办法来解决现在的问题的~
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.