原子是CabloyJS最基本的要素,如文章、公告、请假单,等等

通过原子的组合,就可以实现任何想要的功能,如CMS、OA、CRM、ERP等等

正由于从各种业务模型中抽象出来一个通用的原子概念,因而,CabloyJS为原子实现了许多通用的特性和功能,从而可以便利的为各类实际业务赋能

更详细的原子概念请参见:Cabloy:原子基本概念

在这里,主要通过原子类型party介绍最基本的概念与用法

业务模块与原子类型

当我们创建一个业务模块时,就会自动创建一个原子类型。比如,模块test-party中的party原子类型。由于模块test-party已经存在,在这里仅把创建模块的命令列出来,并不需要再次执行

  1. 1$ cd /path/to/project
  2. 2$ npm run cli :create:module test-party -- [--template=module-business] [--suite=test-party]
名称 说明
moduleName 模块名称,比如test-party
template 模版名称,比如module-business
suite 套件名称,比如test-partytest-party套件包含许多模块,test-party模块仅仅是其中一个

声明原子类型

原子类型原子对应的元数据信息,也是在模块的meta.js文件中设置

src/suite-vendor/test-party/modules/test-party/backend/src/meta.js

  1. 1const meta = {
  2. 2 base: {
  3. 3 atoms: {
  4. 4 party: {
  5. 5 info: {
  6. 6 bean: 'party',
  7. 7 title: 'Party',
  8. 8 tableName: 'testParty',
  9. 9 tableNameModes: {
  10. 10 default: 'testPartyView',
  11. 11 },
  12. 12 language: false,
  13. 13 category: true,
  14. 14 tag: true,
  15. 15 },
  16. 16 actions: {
  17. 17 },
  18. 18 validator: 'party',
  19. 19 search: {
  20. 20 validator: 'partySearch',
  21. 21 },
  22. 22 },
  23. 23 },
  24. 24 },
  25. 25};
名称 说明
info.bean 原子类型对应的Bean组件名称
info.title 原子类型的名称
info.tableName 原子类型对应的业务数据表名称
info.tableNameModes.default 可以根据需要指定数据视图
info.language 是否启用本地化
info.category 是否启用目录
info.tag 是否启用标签
validator 原子类型对应的验证器,用于渲染业务表单,并验证表单数据
search.validator 与搜索相关的验证器,用于渲染自定义的搜索字段

原子业务数据表

与模块相关的数据架构变更管理,都在Bean组件version.manager

src/suite-vendor/test-party/modules/test-party/backend/src/bean/version.manager.js

  1. 1async update(options) {
  2. 2 if (options.version === 1) {
  3. 3 // create table: testParty
  4. 4 let sql = `
  5. 5 CREATE TABLE testParty (
  6. 6 id int(11) NOT NULL AUTO_INCREMENT,
  7. 7 createdAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  8. 8 updatedAt timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  9. 9 deleted int(11) DEFAULT '0',
  10. 10 iid int(11) DEFAULT '0',
  11. 11 atomId int(11) DEFAULT '0',
  12. 12 personCount int(11) DEFAULT '0',
  13. 13 partyTypeId int(11) DEFAULT '0',
  14. 14 PRIMARY KEY (id)
  15. 15 )
  16. 16 `;
  17. 17 await this.ctx.model.query(sql);
  18. 18 }
  19. 19}

src/suite-vendor/test-party/modules/test-party/backend/src/beans.js

  1. 1const versionManager = require('./bean/version.manager.js');
  2. 2
  3. 3module.exports = app => {
  4. 4 const beans = {
  5. 5 // version
  6. 6 'version.manager': {
  7. 7 mode: 'app',
  8. 8 bean: versionManager,
  9. 9 },
  10. 10 };
  11. 11 return beans;
  12. 12};

model对象

定义与业务数据表对应的model对象,可以更便利的操作数据

src/suite-vendor/test-party/modules/test-party/backend/src/model/party.js

  1. 1module.exports = app => {
  2. 2 class Party extends app.meta.Model {
  3. 3 constructor(ctx) {
  4. 4 super(ctx, { table: 'testParty', options: { disableDeleted: false } });
  5. 5 }
  6. 6 }
  7. 7 return Party;
  8. 8};
名称 默认值 说明
table model对象对应的数据表名
disableDeleted false 是否禁用软删除特性

验证器

CabloyJS的验证机制底层采用ajv,建议您对ajv有初步的了解

使用验证器,我们只需定义好与业务相关的JSON Schema,就可以自动渲染表单,同时还可以自动验证表单数据,如果表单数据不符合预期,会自动把错误信息显示出来

关于表单验证的更详细信息请参见:CabloyJS:表单验证

src/suite-vendor/test-party/modules/test-party/backend/src/config/validation/schemas.js

  1. 1module.exports = app => {
  2. 2 const schemas = {};
  3. 3 // party
  4. 4 schemas.party = {
  5. 5 type: 'object',
  6. 6 properties: {
  7. 7 atomName: {
  8. 8 type: 'string',
  9. 9 ebType: 'text',
  10. 10 ebTitle: 'Party Name',
  11. 11 notEmpty: true,
  12. 12 },
  13. 13 personCount: {
  14. 14 type: 'number',
  15. 15 ebType: 'text',
  16. 16 ebTitle: 'Person Count',
  17. 17 minimum: 1,
  18. 18 notEmpty: true,
  19. 19 },
  20. 20 partyTypeId: {
  21. 21 type: 'number',
  22. 22 ebType: 'select',
  23. 23 ebTitle: 'Party Type',
  24. 24 ebOptionsUrl: '/test/party/party/types',
  25. 25 ebOptionTitleKey: 'name',
  26. 26 ebOptionValueKey: 'id',
  27. 27 ebOptionsBlankAuto: true,
  28. 28 notEmpty: true,
  29. 29 },
  30. 30 atomCategoryId: {
  31. 31 type: 'number',
  32. 32 ebType: 'category',
  33. 33 ebTitle: 'Category',
  34. 34 },
  35. 35 atomTags: {
  36. 36 type: [ 'string', 'null' ],
  37. 37 ebType: 'tags',
  38. 38 ebTitle: 'Tags',
  39. 39 },
  40. 40 },
  41. 41 };
  42. 42 // party search
  43. 43 schemas.partySearch = {
  44. 44 type: 'object',
  45. 45 properties: {
  46. 46 partyTypeId: {
  47. 47 type: 'number',
  48. 48 ebType: 'select',
  49. 49 ebTitle: 'Party Type',
  50. 50 ebOptionsUrl: '/test/party/party/types',
  51. 51 ebOptionTitleKey: 'name',
  52. 52 ebOptionValueKey: 'id',
  53. 53 ebOptionsBlankAuto: true,
  54. 54 },
  55. 55 },
  56. 56 };
  57. 57 return schemas;
  58. 58};

原子Bean与原子指令

CabloyJS将所有业务数据的操作称为原子指令,主要分两类:

  1. 基本指令:create、read、write、delete
  2. 扩展指令:与具体业务相关的操作

只需为原子类型提供一个Bean组件,即可封装所有原子指令的业务逻辑

src/suite-vendor/test-party/modules/test-party/backend/src/bean/atom.party.js

  1. 1module.exports = app => {
  2. 2
  3. 3 const gPartyTypeEmojis = {
  4. 4 Birthday: '🎂',
  5. 5 Dance: '💃',
  6. 6 Garden: '🏡',
  7. 7 };
  8. 8
  9. 9 class Atom extends app.meta.AtomBase {
  10. 10
  11. 11 async create({ atomClass, item, user }) {
  12. 12 // super
  13. 13 const key = await super.create({ atomClass, item, user });
  14. 14 // add party
  15. 15 const res = await this.ctx.model.party.insert({
  16. 16 atomId: key.atomId,
  17. 17 });
  18. 18 return { atomId: key.atomId, itemId: res.insertId };
  19. 19 }
  20. 20
  21. 21 async read({ atomClass, options, key, user }) {
  22. 22 // super
  23. 23 const item = await super.read({ atomClass, options, key, user });
  24. 24 if (!item) return null;
  25. 25 // read
  26. 26 await this._getMeta(item, options);
  27. 27 // ok
  28. 28 return item;
  29. 29 }
  30. 30
  31. 31 async select({ atomClass, options, items, user }) {
  32. 32 // super
  33. 33 await super.select({ atomClass, options, items, user });
  34. 34 // select
  35. 35 for (const item of items) {
  36. 36 await this._getMeta(item, options);
  37. 37 }
  38. 38 }
  39. 39
  40. 40 async write({ atomClass, target, key, item, options, user }) {
  41. 41 // super
  42. 42 await super.write({ atomClass, target, key, item, options, user });
  43. 43 // update party
  44. 44 const data = await this.ctx.model.party.prepareData(item);
  45. 45 data.id = key.itemId;
  46. 46 await this.ctx.model.party.update(data);
  47. 47 }
  48. 48
  49. 49 async delete({ atomClass, key, user }) {
  50. 50 // delete party
  51. 51 await this.ctx.model.party.delete({
  52. 52 id: key.itemId,
  53. 53 });
  54. 54 // super
  55. 55 await super.delete({ atomClass, key, user });
  56. 56 }
  57. 57
  58. 58 async _getMeta(item, options) {
  59. 59 // layout: list/table/mobile/pc
  60. 60 const layout = options && options.layout;
  61. 61 // meta
  62. 62 const meta = this._ensureItemMeta(item);
  63. 63 // meta.flags
  64. 64 if (item.partyOver) {
  65. 65 meta.flags.push(this.ctx.text('PartyOverFlag'));
  66. 66 }
  67. 67 if (layout !== 'table' && item.personCount) {
  68. 68 meta.flags.push(item.personCount + 'P');
  69. 69 }
  70. 70 // meta.summary
  71. 71 if (item.partyTypeCode) {
  72. 72 const dictItem = await this.ctx.bean.dict.findItem({
  73. 73 dictKey: 'test-party:dictPartyType',
  74. 74 code: item.partyTypeCode,
  75. 75 });
  76. 76 meta.summary = `${dictItem.options.emoji}${dictItem.titleLocaleFull}`;
  77. 77 }
  78. 78 }
  79. 79
  80. 80 }
  81. 81
  82. 82 return Atom;
  83. 83};
名称 说明
create 新建party时调用
read 查询单条party时调用
select 查询多条party时调用
write 修改party时调用
delete 删除party时调用

src/suite-vendor/test-party/modules/test-party/backend/src/beans.js

  1. 1const atomParty = require('./bean/atom.party.js');
  2. 2
  3. 3module.exports = app => {
  4. 4 const beans = {
  5. 5 // atom
  6. 6 'atom.party': {
  7. 7 mode: 'app',
  8. 8 bean: atomParty,
  9. 9 },
  10. 10 };
  11. 11 return beans;
  12. 12};