init.json Guide
12003Quick Start
jianghu-init JSON manages page structure and API interfaces efficiently in JSON format, optimizing your development workflow and boosting productivity. This guide walks through jianghu-init JSON in detail.
Installation
Before you begin, make sure the jianghu-init tool is installed. Use the following commands:
npm uninstall -g @jianghujs/jianghu-init# Latest version: 3.2.5npm install -g @jianghujs/jianghu-init@latest
👉 In-depth guide to installing the jianghu-init tool 👈
Generate Reference Examples in a Jianghu Project
- First, go to an existing JianghuJS project directory.
👉 Create a jianghujs project 👈
- Generate reference examples with:
cd my-jh-projectjianghu-init json --generateType=example
Running the command will add the example_class and example_student tables to the current project database and create the following files in your project directory:
- app\view\init-json\component\exampleStudentOfClass.js
- app\view\init-json\page\exampleClass.js
- app\view\component\example\exampleStudentOfClass.html
- app\view\page\exampleClass.html
These are provided for easy reference.
Step 1: Generate a Config File from a Data Table
When you have a new business table, you can generate its configuration file. For example, suppose there is a table named class:
CREATE TABLE `class` (`id` int(11) NOT NULL AUTO_INCREMENT,`classId` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Class ID',`className` varchar(255) DEFAULT NULL,`operation` varchar(255) COLLATE utf8mb4_bin DEFAULT 'insert' COMMENT 'Operation; insert, update, jhInsert, jhUpdate, jhDelete jhRestore',`operationByUserId` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Operator userId',`operationByUser` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Operator username',`operationAt` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 'Operation time; E.g: 2021-05-28T10:24:54+08:00 ',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
To generate a base configuration file for the class table:
jianghu-init json --generateType=json --pageType=jh-page --table=class --pageId=classManagement
After running the command, a configuration file named classManagement.js will be generated under app/view/init-json/page/.
Step 2: Generate a Page from the Config File
Use the following command to generate the page from the config file created above:
jianghu-init json --generateType=page --pageType=page --file=classManagement
This will generate a page file named classManagement.html under app/view/page/ and automatically configure the corresponding _page and _resouce (resource) records in the database.
👉 Enable dev mode to auto-update the page when the config changes 👈
👉 Configure syntax highlighting for code inside strings 👈
Configuration Protocol
Structure Overview
{pageType: "jh-page", // Page type: jh-page, jh-mobile-page, or jh-componentpageId: "pageId", // Unique page identifierpageName: "Class Page", // Page titletemplate: "jhTemplateV4", // Page template; default jhTemplateV4, use jhMobileTemplateV4 for mobileversion: 'v3', // Version: empty or v2resourceList: [],// Page resources { actionId, resourceType, resourceData }headContent: [], // Header area: title, breadcrumbs, server-side search, scene search, etc.pageContent: [], // Main content areaactionContent: [], // Trigger content (e.g., drawers, dialogs)includeList: [], // External resources { type, path }common: { // Native Vue variables + uiAction {data, props, watch, methods, computed}data: {},computed: {},watch: {},methods: {},doUiAction: {}, // Custom uiAction { [key]: [method1, method2, doUiAction1]}},style: '', // Custom styles}
| Property | Type | Required | Description |
|---|---|---|---|
| pageType | string | ✅ | Page type: jh-page, jh-mobile-page, jh-component |
| pageId | string | ✅ | Unique page ID |
| template | string | No | Template |
| version | string | No | Version (currently supports v2, v3) |
| resourceList | array | No | |
| headContent | array | ✅ | Header (e.g., breadcrumbs) |
| pageContent | array | ✅ | Main content |
| actionContent | array | ✅ | Triggers (drawers, dialogs, overlays, etc.) |
| includeList | array | ✅ | External resources { type, path } |
| common | object | ✅ | Variables & methods |
| style | string | No |
HTML Tags
Within headContent, pageContent, and actionContent, you can configure a tag using a structured object, and raw HTML strings are also supported.
Object structure
pageContent: [{tag: 'v-row',attrs: {align: "center","@click": "doUiAction('updateItem')"},quickAttrs: ['no-gutters'],value: [{ tag: 'v-col', attrs: { cols: 12 }, value: '' },/*html*/`<v-col cols="12"></v-col>`]}]
| Property | Type | Required | Description |
|---|---|---|---|
| tag | string | No | Tag name |
| attr | object | ✅ | Tag attributes such as class, @click, etc. |
| quickAttrs | array | No | Shorthand attributes like v-else, small, disabled |
| value | object | No | Inner HTML content |
HTML string
pageContent: [`<v-row align="center"><v-col cols="12"></v-col></v-row>`]
API/Resource Configuration
Configure Jianghu resources. Defined resources are auto-synced to the database for frontend use.
resourceList: [{resourceHook: { "before": [], "after": [] }, // OptionalactionId: 'balance-updateItem',desc: 'Update class fund balance',resourceType: 'sql',resourceData: { "table": "class", "operation": "update" }}]
👉 Detailed resource configuration docs 👈
Including External Resources
When a page needs to include components, styles, or scripts, use includeList. Object structure and HTML strings are both supported.
includeList: [{ type: 'css', path: "/<$ ctx.app.config.appId $>/public/lib/echarts.min.css" },{ type: 'script', path: "/<$ ctx.app.config.appId $>/public/lib/echarts.min.js" },{ type: 'html', path: "component/exampleChart/lineChart.html" },{ type: 'html', path: 'component/courseBatchDrawer.html', includeType: 'auto', attrs: { id: 'courseBatchDrawer' } },{ type: 'vueComponent', name: 'v-chart', component: 'VueCharts' },`<script src="/<$ ctx.app.config.appId $>/public/lib/echarts.min.js"></script>`]
| Property | Type | Required | Description |
|---|---|---|---|
| type | string | ✅ | Include type: css, js, html, vueComponent |
| path | string | No | Include path (for css/js/html types) |
| includeType | string | No | Include mode. Use auto to append tag contents to the end of the page (for css/js/html types) |
| attrs | object | No | Custom tag attributes (for html type with includeType=auto) |
| name | string | No | Component name (for vueComponent type) |
| component | string | No | Component reference (for vueComponent type) |
Configure doUiAction
Entry point to augment doUiAction method chains.
pageContent: [{tag: 'v-btn',value: 'Test doUiAction 1',attrs: { '@click': 'doUiAction("doSomething")' },quickAttrs: ['small']},`<v-btn @click="doUiAction('doSomething')">Test doUiAction 2</v-btn>`],common: {doUiAction: {startUpdataBalance: ['async.method1', 'method2'],doSomething: ['method3(123)', 'doUiAction.startUpdataBalance']},methods: {async method1 (id) {console.log('method1', id)},async method2 () {console.log('method2')},async method3(actionData) {console.log('method3')}}}
- By default, method chains run in sequence with
await. You can prefix withasync.to mark steps as async, e.g.:
doUiAction: {startUpdataBalance: ['async.method1', 'method2'],},// Actual executioncase 'startUpdataBalance':this.method1(uiActionData);await this.method2(uiActionData);break;
- By default, items in the chain call methods. You can prefix with
doUiAction.to include another flow, e.g.:
doUiAction: {doSomething: ['method3(123)', 'doUiAction.startUpdataBalance'],doSomething2: ['method3(123)', 'async.doUiAction.startUpdataBalance']},// Actual executioncase 'doSomething':await this.method3(123, uiActionData);await this.doUiAction('startUpdataBalance', uiActionData);break;case 'doSomething2':await this.method3(123, uiActionData);this.doUiAction('startUpdataBalance', uiActionData);break;
You can reference method names, method names with args, or doUiAction names in the chain.
Configuration Examples — Desktop (PC)
Basic Page
const content = {pageType: "jh-page", pageId: "classManagement", pageName: "classManagement Page", template: 'jhTemplateV4', version: 'v3',resourceList: [{actionId: "selectItemList",resourceType: "sql",desc: "✅ Query list — classManagement",resourceData: { table: "class", operation: "select" }},// ... insertItem/updateItem/deleteItem], // { actionId: '', resourceType: '', resourceData: {}, resourceHook: {}, desc: '' }headContent: [{ tag: 'jh-page-title', value: "classManagement", attrs: { cols: 12, sm: 6, md:4 }, helpBtn: true, slot: [] },{ tag: 'v-spacer' },{tag: 'jh-search',attrs: { cols: 12, sm: 6, md:8 },searchBtn: true,value: [{ tag: "v-text-field", model: "serverSearchWhereLike.className", attrs: {prefix: 'Prefix'} },],// New in v3data: {serverSearchWhereLike: { name: '' },serverSearchWhere: { semester: '', segment: 'Primary' },}}],pageContent: [{tag: 'jh-table',attrs: { },colAttrs: { clos: 12 },cardAttrs: { class: 'rounded-lg elevation-0' },headActionList: [{ tag: 'v-btn', value: 'Create', attrs: { color: 'success', class: 'mr-2', '@click': 'doUiAction("startCreateItem")', small: true } },{ tag: 'v-spacer' },// Default filter{tag: 'v-col',attrs: { cols: '12', sm: '6', md: '3', xs: 8, class: 'pa-0' },value: [{ tag: 'v-text-field', attrs: {prefix: 'Filter', 'v-model': 'searchInput', class: 'jh-v-input', ':dense': true, ':filled': true, ':single-line': true} },],}],headers: [{ text: "id", value: "id", width: 80, sortable: true, class: "fixed", cellClass: "fixed" },{ text: "Class ID", value: "classId", width: 80, sortable: true },{ text: "className", value: "className", width: 80, sortable: true },{ text: "Operation", value: "operation", width: 80, sortable: true },{ text: "Operator userId", value: "operationByUserId", width: 80, sortable: true },{ text: "Operator username", value: "operationByUser", width: 80, sortable: true },{ text: "Operation time", value: "operationAt", width: 80, sortable: true },{ text: "Actions", value: "action", type: "action", width: 'window.innerWidth < 500 ? 70 : 120', align: "center", class: "fixed", cellClass: "fixed" },// width expressions must be wrapped in strings],value: [// vuetify table custom slot],rowActionList: [{ text: 'Edit', icon: 'mdi-note-edit-outline', color: 'success', click: 'doUiAction("startUpdateItem", item)' }, // Works on both desktop and mobile collapse{ text: 'Delete', icon: 'mdi-trash-can-outline', color: 'error', click: 'doUiAction("deleteItem", item)' } // Works on both desktop and mobile collapse],}],actionContent: [{tag: 'jh-create-drawer',key: "create",attrs: {},title: 'Create',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Create",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Create",attrs: {color: "success",':small': true,'@click': "doUiAction('createItem')"}}],},]},{tag: 'jh-update-drawer',key: "update",attrs: {},title: 'Edit',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Edit",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Edit",attrs: {color: "success",':small': true,'@click': "doUiAction('updateItem')"}}],},{ label: "Operation history", type: "component", componentPath: "recordHistory", attrs: { table: 'class', pageId: 'classManagement', ':id': 'updateItem.id' } },]},],includeList: [], // { type: < js | css | html | vueComponent >, path: '' }common: {data: {constantObj: {},validationRules: {requireRules: [v => !!v || 'Required',],},serverSearchWhereLike: { className: '' }, // Server-side LIKE searchserverSearchWhere: { }, // Server-side searchserverSearchWhereIn: { }, // Server-side IN searchfilterMap: {}, // Client-side filter criteria},dataExpression: {isMobile: 'window.innerWidth < 500'}, // data expressionswatch: {},computed: {tableDataComputed() {if(this.filterMap) {return this.tableData.filter(row => {for (const key in this.filterMap) {if (this.filterMap[key] && row[key] !== this.filterMap[key]) {return false;}}return true;});} else {return this.tableData;}},},doUiAction: {}, // Extra uiAction { [key]: [action1, action2] }methods: {}},};module.exports = content;
Title — Title Only
headContent: [{ tag: 'jh-page-title', value: "classManagement", attrs: { cols: 12, sm: 6, md:4 }, helpBtn: true, slot: [] }]
Title — Server-side Search
headContent: [{ tag: 'jh-page-title', value: "classManagement", attrs: { cols: 12, sm: 6, md:4 }, helpBtn: true, slot: [] },{ tag: 'v-spacer' },{tag: 'jh-search',attrs: { cols: 12, sm: 6, md:8 },value: [{ tag: "v-text-field", model: "serverSearchWhereLike.className", attrs: {prefix: 'Prefix'} },],searchBtn: true,data: {serverSearchWhereLike: { name: '' },}}]
Title — Scene Search
headContent: [{ tag: 'jh-page-title', value: "classManagement", attrs: { cols: 12, sm: 6, md:4 }, helpBtn: true, slot: [] },{ tag: 'v-spacer' },{tag: 'jh-scene',attrs: { ':showActionBtn': false, ':mobile': false },// New in v3data: {sceneCreateForm: {},serverSceneSearchWhere: {},serverSceneSearchWhereIn: {},serverSceneSearchWhereLike: {},serverSceneSearchWhereOptions: [],serverSceneSearchWhereOrOptions: [],currentSceneId: 'Public',defaultSceneList: [{ name: "All", where: {}, whereIn: { "articlePublishStatus": ["public", "draft"] } },{ name: "Public", where: { "articlePublishStatus": "public"}, whereIn: {} },{ name: "Draft", where: { "articlePublishStatus": "draft"}, whereIn: {} },{ name: "Recycle Bin", where: { "articlePublishStatus": "deleted"}, whereIn: {} },],maxSceneDisplay: 5,}},]
Content — Data Table
pageContent: [{tag: 'jh-table',props: {serverPagination: true // Enable server-side pagination; default limit 50},attrs: {},colAttrs: { clos: 12 },cardAttrs: { class: 'rounded-lg elevation-0' },headActionList: [{ tag: 'v-btn', value: 'Create', attrs: { color: 'success', class: 'mr-2', '@click': 'doUiAction("startCreateItem")', small: true } },{ tag: 'v-spacer' }],headers: [{ text: "id", value: "id", width: 80, sortable: true, class: "fixed", cellClass: "fixed" },{ text: "Class ID", value: "classId", width: 80, sortable: true },{ text: "className", value: "className", width: 80, sortable: true },{ text: "Operation", value: "operation", width: 80, sortable: true },{ text: "Actions", value: "action", type: "action", width: 'window.innerWidth < 500 ? 70 : 120', align: "center", class: "fixed", cellClass: "fixed" },],value: [// v-table slots// { tag: 'template', attrs: {'slot': 'body', 'slot-scope': "item"}, value: "<div>Test slot</div>" },// { tag: 'template', attrs: {'slot': 'item.className', 'slot-scope': "{item, index}"}, value: "<div>{{item.className}}</div>" },],rowActionList: [{ text: 'Edit', icon: 'mdi-note-edit-outline', color: 'success', click: 'doUiAction("startUpdateItem", item)' },{ text: 'Delete', icon: 'mdi-trash-can-outline', color: 'error', click: 'doUiAction("deleteItem", item)' }],}]
Content — Data Table (Enable Column Settings)
Common
pageContent: [{tag: 'jh-table',attrs: {}, // v-data-table attrscolAttrs: {}, // Parent v-col attrscardAttrs: {}, // Parent v-card attrsheaders: [], // v-data-table headersvalue: [],showTableColumnSettingBtn: true,headActionList: [], // v-col slot above tablerowActionList: [], // Action buttons in the action column}],
Modes
pageContent: [{tag: 'jh-table',attrs: {},value: [],showTableColumnSettingBtn: true,headActionList: [],rowActionList: [],}],common: {data: {columnSettingGroup: {'Mode 01': ["netName","stage","followUpDate","district","memberId","followUpBy","currentResidence","openVisitationStatus","totalVisitationCount","visitorAssignInfo","operationAt","action"],'Mode 02': ["netName","stage","duoxingRoomName","followUpDate","tag","projectList","followUpBy","district","memberId","currentResidence","operationAt","action"],'Mode 03': ["netName","orgId","orgName","roleId","isDefaultOrg"],},}}
| Property | Type | Required | Description |
|---|---|---|---|
| showTableColumnSettingBtn | boolean | No | Enable column settings; default false |
Drawers — Create/Edit Form
actionContent: [{tag: 'jh-create-drawer',key: "create",attrs: {},title: 'Create',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Create",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Create",attrs: {color: "success",'@click': "doUiAction('createItem')"},quickAttrs: ['small']}],},]},{tag: 'jh-update-drawer',key: "update",props: {mergeForm: true // prepareFormData will merge object forms},attrs: {},title: 'Edit',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Edit",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Edit",attrs: {color: "success",'@click': "doUiAction('updateItem')"},quickAttrs: ['small']}],},{ label: "Operation history", type: "component", componentPath: "recordHistory", attrs: { table: 'class', pageId: 'classManagement', ':id': 'updateItem.id' } },]},]
Drawer — Detail
pageContent: [`<v-btn @click="openTestDetailDrawer">Open test detail drawer</v-btn>`,],actionContent: [{tag: 'jh-drawer',key: "testDetail",attrs: {},title: 'Test Details',contentList: [{type: "form",formItemList: [{ tag: 'span', value: 'Course Info', md: 12, attrs: {class: 'title pl-2'}},{ tag: 'div', md: 12, value: [/*html*/ `<div class="grey lighten-5"><v-row class="ma-0 pa-2"><v-col cols="12" md="3" v-for="(item, index) in courseInfoItems" :key="index"><span>{{item.title}}: {{item.value}}</span></v-col></v-row></div>`]}]}]}],"common": {data: {courseInfoItems: [{ title: 'Course Name', value: 'IT Beginner Course' }]}}
Drawer — Custom Drawer
pageContent: [`<v-btn @click="openTestCustomDrawer">Open test custom drawer</v-btn>`,],actionContent: [{tag: 'jh-drawer',key: "testCustom",attrs: {},title: 'Test Custom Drawer',contentList: [`Custom content`]}]
Drawer — Multi-tab Drawer
pageContent: [`<v-btn @click="openTestMultiTabDrawer">Open test multi-tab drawer</v-btn>`,],actionContent: [{tag: 'jh-drawer',key: "testMultiTab",attrs: {},title: 'Test Multi-tab',contentList: [{ label: "Operation History", type: "component", componentPath: "recordHistory" },{ label: "Student List", type: "component", componentPath: "example/studentOfClass", bind: ['classId'] },]}]
Drawer — Custom Header Button
pageContent: [`<v-btn @click="viewTestCustomBtn">Open test custom-button drawer</v-btn>`],actionContent: [{tag: 'jh-create-drawer',key: "testCustomBtn",attrs: {},title: 'Test Custom Button',headSlot: [{ tag: 'v-spacer' },{ tag: 'v-btn', value: 'Custom Button', attrs: { color: 'success', class: 'mr-2', '@click': 'doUiAction("123")', small: true } },],contentList: [ /* Content list */ ]}]
Drawer — Close-Check on Dismiss
actionContent: [{tag: 'jh-create-drawer',key: "create",attrs: {},title: 'Create',isCheckFormBeforeClose: true,onCheckFormConfirm: 'doUiAction("createItem", { notConfirmed: true })',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Create",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Create",attrs: {color: "success",':small': true,'@click': "doUiAction('createItem')"}}],},]}]
| Property | Type | Required | Description |
|---|---|---|---|
| key | string | ✅ | Form key, e.g., create, update |
| title | string | ✅ | Drawer title |
| props.mergeForm | boolean | No | Merge form on submit |
| attrs | object | No | v-navigation-drawer attributes |
| isCheckFormBeforeClose | boolean | No | Enable close-check; default false. The checked form object key must match the drawer key (e.g., form object createItem, drawer key create). |
| onCheckFormConfirm | string | No | Method to save content |
| contentList | array | ✅ | Drawer content |
Configuration Examples — Mobile
Basic Page
const content = {pageType: "jh-mobile-page", pageId: "mobile/classManagement", pageName: "classManagement Page", template: "jhMobileTemplateV4", version: 'v2',resourceList: [{actionId: "selectItemList",resourceType: "sql",desc: "✅ Query list — classManagement",resourceData: { table: "class", operation: "select" }},// ... insertItem/updateItem/deleteItem], // { actionId: '', resourceType: '', resourceData: {}, resourceHook: {}, desc: '' }headContent: [{ tag: 'jh-page-title', value: "Class Management", helpBtn: true, slot: [] },{tag: 'jh-order',// New in v3data: {tableDataOrder: [ { column: "createAt", order: "desc" } ],tableDataOrderList: [{ text: "Registration Time ↓", value: [ { column: "createAt", order: "desc" } ] },{ text: "Exam Score ↓", value: [ { column: "middleSchoolExamScore", order: "desc" } ] },{ text: "Updated At ↓", value: [ { column: "operationAt", order: "desc" } ] },],}},{tag: 'jh-search',searchList: [{ tag: 'v-select', model: "serverSearchWhere.semester", colAttrs: { class: 'pb-0' }, attrs: { prefix: 'Semester:', color: 'success', ':items': 'constantObj.semester' } },{ tag: 'v-select', model: "serverSearchWhere.segment", colAttrs: { class: 'pb-0' }, attrs: { prefix: 'Division:', color: 'success', ':items': 'constantObj.segment' } },{ tag: 'v-text-field', model: "serverSearchWhereLike.name", colAttrs: { class: 'pb-0' }, attrs: { label: 'Student Name:', color: 'success' }, quickAttrs: ['clearable'] },],data: {serverSearchWhere: { semester: '', segment: 'Primary' },serverSearchWhereLike: { name: '' },}},{ tag: 'v-spacer'},{ tag: 'jh-mode', data: { viewMode: 'simple' } },],pageContent: [{tag: 'jh-list',props: {limit: 10,rightArrowText: '',},attrs: { cols: 12, class: 'p-0 pb-7', ':style': '`height: calc(100vh - 140px); overflow-y: auto;overscroll-behavior: contain`' },headers: [{text: "Student ID", value: "studentId", width: 80, isSimpleMode: true},{text: "Name", value: "name", width: 90, isTitle: true, slot: [`<div v-if="item.isMonitor" class="ml-1"><v-icon color="warning" small>mdi-shield-star</v-icon><span>Prefect</span></div>`]},{text: "Gender", value: "gender", width: 60, isSimpleMode: true},{text: "Status", value: "studentStatus", width: 80},{text: "Grade", value: "level", width: 70},{text: "Admission — Referrer", value: "recommendBy", width: 100, isDetailMode: true},{text: "Admission — Owner", value: "followUpByUserName", width: 100, isDetailMode: true},{text: "Follow-up Stage", value: "followUpStage", width: 80},{text: "Registered At", value: "createAt", width: 150},{text: "Operator", value: "operationByUser", width: 90},{text: "Operation Time", value: "operationAt", width: 150},{text: 'Actions', value: 'action', align: 'center', sortable: false, width: 'window.innerWidth < 500 ? 90: 180', class: 'fixed', cellClass: 'fixed'},],rowActionList: [{ text: "Edit", icon: 'mdi-note-edit-outline', color: 'success', click: 'doUiAction("startUpdateItem", item)' }],},{tag: 'jh-action',attrs: { class: 'h-16 w-16 p-2 fixed right-4 bottom-32' },actionList: [{ tag: 'v-btn', value: 'Create', icon: 'mdi-plus', color: 'success', click: "doUiAction('startCreateItem')" },{ tag: 'v-btn', value: 'Randomly Assign Owner', icon: 'mdi-plus', color: 'success', click: "doUiAction('randomFollowUp')", ':disabled': '!tableSelected.length' },]}],actionContent: [{tag: 'jh-create-drawer',key: "create",attrs: {},title: 'Create',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Create",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Create",attrs: {color: "success",':small': true,'@click': "doUiAction('createItem')"}}],},]},{tag: 'jh-update-drawer',key: "update",attrs: {},title: 'Edit',headSlot: [{ tag: 'v-spacer'}],contentList: [{label: "Edit",type: "form",formItemList: [{ label: "id", model: "id", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Class ID", model: "classId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "className", model: "className", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation", model: "operation", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator userId", model: "operationByUserId", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operator username", model: "operationByUser", tag: "v-text-field", rules: "validationRules.requireRules" },{ label: "Operation time", model: "operationAt", tag: "v-text-field", rules: "validationRules.requireRules" },],action: [{tag: "v-btn",value: "Edit",attrs: {color: "success",':small': true,'@click': "doUiAction('updateItem')"}}],},{ label: "Operation history", type: "component", componentPath: "recordHistory", attrs: { table: 'class', pageId: 'classManagement', ':id': 'updateItem.id' } },]},{tag: 'jh-detail-drawer',key: "detail",attrs: {},title: "Details",headSlot: [],card: {tag: 'div',colAttrs: { class: 'pa-0' },value: `<div class="p-3 border-b" ><div class="flex justify-between mb-2 items-center"><div><span class="font-weight-bold text-base">{{ detailItem.name }}</span></div><span :class="'text-sm '">{{ detailItem.followUpStatus }}</span></div><div class="text-gray-500 flex justify-between">Intended Division: {{ detailItem.segment }}</div><div class="text-gray-500 flex justify-between">Registration Date: {{ detailItem.createAt }}</div></div><div class="pa-0 h-2 bg-gray-100" ></div>`},contentList: [{type: 'preview',md: 12,formItemList: [{ label: "Gender", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.gender }}" },{ label: "ID Number", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.icNumber }}" },{ label: "Home Address", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.residentialAddress }}" },{ label: "Intended Division", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.segment }}" },{ label: "Intended Grade", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.level }}" },{ label: "Intended Mode", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.afterSchoolCareType }}" },{ label: "Previous School", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.schoolRoll }}" },{ label: "Exam Score", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.middleSchoolExamScore }}" },{ label: "Exam Registration No.", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.middleSchoolExamSequence }}" }],action: [{tag: 'v-btn',value: "Edit",attrs: {color: 'primary','@click': "doUiAction('startUpdateItem', detailItem); closeDetailDrawer()"},quickAttrs: ['small']}]}]},],includeList: [], // { type: < js | css | html | vueComponent >, path: '' }common: {data: {constantObj: {},validationRules: {requireRules: [v => !!v || 'Required',],},filterMap: {},},dataExpression: {isMobile: 'window.innerWidth < 500'},watch: {},computed: {tableDataComputed() {if(this.filterMap) {return this.tableData.filter(row => {for (const key in this.filterMap) {if (this.filterMap[key] && row[key] !== this.filterMap[key]) {return false;}}return true;});} else {return this.tableData;}},},doUiAction: {},async created() {await this.doUiAction('getTableData');},methods: {}},};module.exports = content;
Title — Title Only
headContent: [{ tag: 'jh-page-title', value: "Class Management" },],
Title — Sort & Filter & Mode Switch
headContent: [{ tag: 'jh-page-title', value: "Class Management" },{tag: 'jh-order',data: {tableDataOrder: [ { column: "createAt", order: "desc" } ],tableDataOrderList: [{ text: "Registration Time ↓", value: [ { column: "createAt", order: "desc" } ] },{ text: "Exam Score ↓", value: [ { column: "middleSchoolExamScore", order: "desc" } ] },{ text: "Updated At ↓", value: [ { column: "operationAt", order: "desc" } ] },],}}, // Fixed variables: model 'tableDataOrder', items 'tableDataOrderList'{tag: 'jh-search',searchList: [{ tag: 'v-select', model: "serverSearchWhere.semester", colAttrs: { class: 'pb-0' }, attrs: { prefix: 'Semester:', color: 'success', ':items': 'constantObj.semester' } },{ tag: 'v-select', model: "serverSearchWhere.segment", colAttrs: { class: 'pb-0' }, attrs: { prefix: 'Division:', color: 'success', ':items': 'constantObj.segment' } },{ tag: 'v-text-field', model: "serverSearchWhereLike.name", colAttrs: { class: 'pb-0' }, attrs: { label: 'Student Name:', color: 'success' }, quickAttrs: ['clearable'] },],data: {serverSearchWhereLike: { name: '' },serverSearchWhere: { semester: '', segment: 'Primary' },}},{ tag: 'v-spacer'},{ tag: 'jh-mode', data: { viewMode: 'simple' } },],common: {data: {constantObj: {},}}
Content — Data List
pageContent: [{tag: 'jh-list',props: {limit: 10, // Server-side pagination size},attrs: { cols: 12, class: 'p-0 pb-7', ':style': '`height: calc(100vh - 140px); overflow-y: auto;overscroll-behavior: contain`' },headers: [{text: "Student ID", value: "studentId", width: 80, isSimpleMode: true},{text: "Name", value: "name", width: 90, isTitle: true, slot: [`<div v-if="item.isMonitor" class="ml-1"><v-icon color="warning" small>mdi-shield-star</v-icon><span>Prefect</span></div>`]},{text: "Gender", value: "gender", width: 60, isSimpleMode: true},{text: "Status", value: "studentStatus", width: 80},{text: "Grade", value: "level", width: 70},{text: "Admission — Referrer", value: "recommendBy", width: 100, isDetailMode: true},{text: "Admission — Owner", value: "followUpByUserName", width: 100, isDetailMode: true},{text: "Follow-up Stage", value: "followUpStage", width: 80},{text: "Registered At", value: "createAt", width: 150},{text: "Operator", value: "operationByUser", width: 90},{text: "Operation Time", value: "operationAt", width: 150},{text: 'Actions', value: 'action'},],rowActionList: []}],common: {async created() {await this.doUiAction('getTableData');},doUiAction: {startDetailItem: ['startDetailItem']},methods: {}}
jh-list
| Property | Type | Required | Description |
|---|---|---|---|
| props.limit | string | No | Page size |
| props.rightArrowText | string | No | Text on right arrow |
| headers | array | ✅ | Field definitions |
| headers.isSimpleMode | boolean | No | Show in simple mode only; default false |
| headers.isTitle | boolean | No | Treat as row title |
| rowActionList | array | No | Row actions in detail mode |
Content — Data List (Detail Drawer)
pageContent: [{tag: 'jh-list',props: {limit: 10,rightArrowText: '',},attrs: { cols: 12, class: 'p-0 pb-7', ':style': '`height: calc(100vh - 140px); overflow-y: auto;overscroll-behavior: contain`' },headers: [{text: "Student ID", value: "studentId", width: 80, isSimpleMode: true},{text: "Name", value: "name", width: 90, isTitle: true, slot: [`<div v-if="item.isMonitor" class="ml-1"><v-icon color="warning" small>mdi-shield-star</v-icon><span>Prefect</span></div>`]},{text: "Gender", value: "gender", width: 60, isSimpleMode: true},{text: "Status", value: "studentStatus", width: 80},{text: "Follow-up Stage", value: "followUpStage", width: 80},{text: "Registered At", value: "createAt", width: 150},{text: 'Actions', value: 'action', align: 'center', sortable: false, width: 'window.innerWidth < 500 ? 90: 180', class: 'fixed', cellClass: 'fixed'},],rowActionList: [{ text: "Edit", icon: 'mdi-note-edit-outline', color: 'success', click: 'doUiAction("startUpdateItem", item)' }],}],actionContent: [{tag: 'jh-detail-drawer',key: "detail",attrs: {},title: "Details",headSlot: [],card: {tag: 'div',colAttrs: { class: 'pa-0' },value: `<div class="p-3 border-b" ><div class="flex justify-between mb-2 items-center"><div><span class="font-weight-bold text-base">{{ detailItem.name }}</span></div><span :class="'text-sm '">{{ detailItem.followUpStatus }}</span></div><div class="text-gray-500 flex justify-between">Intended Division: {{ detailItem.segment }}</div><div class="text-gray-500 flex justify-between">Registration Date: {{ detailItem.createAt }}</div></div><div class="pa-0 h-2 bg-gray-100" ></div>`},contentList: [{type: 'preview',md: 12,formItemList: [{ label: "Gender", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.gender }}" },{ label: "ID Number", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.icNumber }}" },{ label: "Home Address", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.residentialAddress }}" },{ label: "Intended Division", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.segment }}" },{ label: "Intended Grade", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.level }}" },{ label: "Intended Mode", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.afterSchoolCareType }}" },{ label: "Previous School", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.schoolRoll }}" },{ label: "Exam Score", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.middleSchoolExamScore }}" },{ label: "Exam Registration No.", tag: "span", colAttrs: { class: 'border-b pb-2 flex justify-between' }, value: "{{ detailItem.middleSchoolExamSequence }}" }],action: [{tag: 'v-btn',value: "Edit",attrs: {color: 'primary','@click': "doUiAction('startUpdateItem', detailItem); closeDetailDrawer()"},}]}]}],common: {async created() {await this.doUiAction('getTableData');},}
Content — Floating Action Menu
pageContent: [{tag: 'jh-action',attrs: { class: 'h-16 w-16 p-2 fixed right-4 bottom-32' },actionList: [{ tag: 'v-btn', value: 'Create', icon: 'mdi-plus', color: 'success', click: "doUiAction('startCreateItem')" },{ tag: 'v-btn', value: 'Randomly Assign Owner', icon: 'mdi-plus', color: 'success', click: "doUiAction('randomFollowUp')", ':disabled': '!tableSelected.length' },]}]
Drawer-related
👉 Refer to the desktop (PC) drawer configuration 👈
Command Reference
Generate Example Files & Pages
By default, generates the example_class and example_student tables, configuration files, and pages for easy reference:
jianghu-init json --generateType=example
Generate Page Config File
Generate a page config file from a database table:
jianghu-init json --generateType=json --pageType=page --table=class --pageId=classManagement
Render HTML from Config
Generate a page from the config file:
jianghu-init json --generateType=page --pageType=page --file=classManagement
Enable Dev Mode
Enable dev mode so changes to the config file automatically update the page:
jianghu-init json dev
Parameters
| Parameter | Description |
|---|---|
| generateType | Generation type: example (generate example files), json (generate config from table), page (generate page from config) |
| pageType | Page type: page, component |
| table | Table name (used when generateType is json) |
| dev | Enable development mode; watches JSON files in the current project and auto-updates the HTML files |
Miscellaneous
Syntax Highlighting for Code Inside Strings
- In VSCode, install the following extension to enable syntax highlighting for code inside strings and improve readability:
After installing, specify the language before the template string to enable highlighting.
pageContent: [/*html*/`<v-btn @click="openTestMultiTabDrawer">Open test multi-tab drawer</v-btn>`,],style: /*css*/`.jh-v-input {width: 100%;}`
- You can tweak VSCode settings to better support HTML suggestions in template strings. In Settings, search for “editor.quickSuggestions” and make sure suggestions are enabled for strings:
"editor.quickSuggestions": {"strings": true}
Upgrade Guide for version v2
Add server-side search
jh-page
{tag: 'jh-table',props: {serverPagination: true, // requires version: v2},attrs: {},}
For
doUiAction.getTableData, avoid replacing the entire method chain. If customization is needed, override selectively:common.doUiAction: {getTableData: ['prepareTableParamsDefault', // Combine search conditions into this.tableParams'prepareTableParams', // Custom condition method'getTableData', // Request'formatTableData' // Format tableData]}
formatTableDatamethod// OriginalformatTableData(rows) {rows.forEach(row => {row.operationAt = row.operationAt ? dayjs(row.operationAt).format('YYYY-MM-DD HH:mm:ss') : '';});return rows;}// v2formatTableData() {let tableData = this.tableDataFromBackend.map(row => {row.operationAt = row.operationAt ? dayjs(row.operationAt).format('YYYY-MM-DD HH:mm:ss') : '';return row;});this.tableData = tableData;}
Upgrade Guide for version v3
- Refactor init-json jh components: move variables previously placed in
common.datainto the tag’s owndatawhere appropriate to simplify component state management.
jh-order(mobile sorting){tag: 'jh-order',data: {tableDataOrder: [ { column: "createAt", order: "desc" } ],tableDataOrderList: [{ text: "Registration Time ↓", value: [ { column: "createAt", order: "desc" } ] },{ text: "Exam Score ↓", value: [ { column: "middleSchoolExamScore", order: "desc" } ] },{ text: "Updated At ↓", value: [ { column: "operationAt", order: "desc" } ] },],}},
jh-search(server-side search){tag: 'jh-search',searchList: [{ tag: 'v-select', model: "serverSearchWhere.semester", colAttrs: { class: 'pb-0' }, attrs: { prefix: 'Semester:', color: 'success', ':items': 'constantObj.semester' } },{ tag: 'v-select', model: "serverSearchWhere.segment", colAttrs: { class: 'pb-0' }, attrs: { prefix: 'Division:', color: 'success', ':items': 'constantObj.segment' } },{ tag: 'v-text-field', model: "serverSearchWhereLike.name", colAttrs: { class: 'pb-0' }, attrs: { label: 'Student Name:', color: 'success' }, quickAttrs: ['clearable'] },],data: {serverSearchWhereLike: { name: '' },serverSearchWhere: { semester: '', segment: 'Primary' },}},
jh-mode(mode switching){ tag: 'jh-mode', data: { viewMode: 'simple' } },
jh-scene(scene search){tag: 'jh-scene',attrs: { ':showActionBtn': false, ':mobile': false },// New in v3data: {sceneCreateForm: {},serverSceneSearchWhere: {},serverSceneSearchWhereIn: {},serverSceneSearchWhereLike: {},serverSceneSearchWhereOptions: [],serverSceneSearchWhereOrOptions: [],currentSceneId: 'Public',defaultSceneList: [{ name: "All", where: {}, whereIn: { "articlePublishStatus": ["public", "draft"] } },{ name: "Public", where: { "articlePublishStatus": "public"}, whereIn: {} },{ name: "Draft", where: { "articlePublishStatus": "draft"}, whereIn: {} },{ name: "Recycle Bin", where: { "articlePublishStatus": "deleted"}, whereIn: {} },],maxSceneDisplay: 5,}},