最近需要编写一个后台管理系统,涉及到大量表格表单,本身这些内容如果直接用 Django 来做,可能就没我们前端什么事情了,但是后端是新来的 JAVA 团队,前后端分离开发模式,所以前端工作量非常大,于是抽象了一个接口类似于 django 的前端框架。
基于 react class component 编写和使用了 ant design UI 库,通过配置的形式直接实现一个管理页面的增删改查以及分页功能,能够减少大量的逻辑代码,不由得感慨面向对象编程的伟大,以及 django 这个框架设计的精妙。
一个页面的实现大致如下所示:
import { RegisterRoute } from '@src/components/router'
import { ModelForm } from '@src/core/model/form'
import { RemoteDataChoiceWidget } from '@src/core/model/widget'
import { Display, ModelAdmin } from '@src/core/template/model-admin'
import { TemplateTablePagination } from '@src/core/template/template-table'
import { UploadWidget } from '@src/core/widgets/upload-widget'
import { $fetch } from '@src/utils/network'
import { Switch } from 'antd'
class SouceChoiceWidget extends RemoteDataChoiceWidget<any, any> {
type = 1
async getRemoteData(): Promise<{ label: string; value: string | number }[]> {
const result = await $fetch.post<any, Business.SourceInfo[]>('investment/lead/way/list', {
type: this.type
})
return result.map(val => {
return {
label: val.name,
value: val.id
}
})
}
}
class ClueSourceChoiceWidget extends SouceChoiceWidget {
type = 0
}
class Form extends ModelForm {
name = ModelForm.InputField({
verboseName: '线索名称',
})
contactPerson = ModelForm.InputField({
verboseName: '联系人',
})
contactTel = ModelForm.PhoneNumberField({
verboseName: '联系电话',
})
fromType = ModelForm.InputField({
verboseName: '线索来源',
widget: ClueSourceChoiceWidget
})
wayType = ModelForm.InputField({
verboseName: '渠道类型',
widget: SouceChoiceWidget
})
file = ModelForm.InputField({
verboseName: '附件',
widget: UploadWidget
})
}
interface AppInfo {
id: number;
parkId: number;
appName: string;
appCate: number;
appUrl: string;
isDisabled: number;
createAt: string;
createBy: string;
}
@RegisterRoute('clue/list')
export default class App extends ModelAdmin<any, any> {
filters: string[] = ['name']
listDisplay: string[] = [
'name',
'type',
'disabled',
'action',
]
getModel(): new () => ModelForm {
return Form
}
async getTableDataSource(pagination: TemplateTablePagination, filters?: any) {
const data = Object.assign({}, filters || {}, {
pageSize: pagination.pageSize,
pageNo: pagination.current,
})
const result = await $fetch.post<any, App.Pagination<AppInfo>>(
'/smart-park-invest/investmentLead/page',
data
)
return result
}
async onFormAddSubmit(
composeValues: any,
formValues: any
) {
await $fetch.post('/smart-park-invest/investment/lead/way/add', {
...formValues
})
}
async onFormUpdateSubmit(
composeValues: any,
formValues: any,
initialValues: any
) {
await $fetch.post('/smart-park-invest/investment/lead/way/update', {
...formValues,
id: initialValues.id,
})
}
getaddFormInitialValues() {
return null
}
async getUpdateFormInitialValues(record: any) {
return record
}
async onConfirmDeleteRow(record: any) {
await $fetch.post(`/smart-park-invest/investment/lead/way/delete/${record.id}`)
}
@Display({
name: 'disabled',
title: '启用',
})
displayDisabled(_: any, record: any) {
return (
<Switch
size="small"
checked={record.enable}
onChange={(val) => this.setAppEnabel(record, val)}
/>
)
}
@Display({
name: 'action',
title: '操作',
})
displayAction(_: any, record: any) {
return (
<div>
<span
className="ml-2 text-blue-400 cursor-pointer"
onClick={() => this.modifyRowData(record)}>
编辑
</span>
<span
className="ml-2 text-red-400 cursor-pointer"
onClick={() => this.deleteRowData(record)}>
删除
</span>
</div>
)
}
async setAppEnabel(record: any, bool: boolean) {
await $fetch.post('/smart-park-invest/investment/lead/way/update', {
id: record.id,
enable: bool
})
this.notifyDataChange()
}
}
虽然 react 现在都在推崇 hooks 编程,甚至官方文档都不再教学 class 组件,但 class component 在有些场景还是很好用的。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.