Introduction #
CRUD customization enables highly personalized UI through template overrides, including custom column display (conditional styles, progress bars), multi-level headers, custom detail pages (drawer/dialog), custom validation rules, custom functions, and custom default values.
| Custom Area | Block Template |
|---|---|
| Custom Column | body_table_column |
| Multi-level Header | body_table_columns |
| Custom Detail Page | dialog_card |
| Custom Add Page | dialog_modify |
| Custom Indicator Card | body_extHead |
Overriding Component Areas #
Custom Column Display #

- Show “sales” column in red when value > 100:
<!-- Custom table column -->
{% block body_table_column %}
<span v-else-if="item==='sales'" :style="scope.row[item]>100?'color:red':''">{[ scope.row[item]]}</span>
{% endblock %}
- Display “rate” field as a progress bar:
{% block body_table_column %}
<el-progress v-else-if="item==='rate'" :percentage="scope.row[item]"></el-progress>
{% endblock %}
- Show up/down arrows based on value:
<!-- Style area -->
.red{color:red}
.green{color:green}
<!-- Template area -->
<span v-else-if="item==='sales'"><i class="scope.row[item]>0?'el-icon-top red':'el-icon-bottom green'"></i>{[ Math.abs(scope.row[item]) ]}</span>
Note: Use actual field names, not display names
Multi-level Headers #

<!-- Style area: define header borders -->
.el-table--border th.el-table__cell,.el-table__fixed-right-patch{border:0.5px solid;)
<!-- Template area: override headers -->
{% block body_table_columns %}
<el-table-column prop="date" label="Date" width="150"></el-table-column>
<el-table-column label="Delivery Info">
<el-table-column prop="name" label="Name"></el-table-column>
<el-table-column label="Address">
<el-table-column prop="province" label="Province"></el-table-column>
<el-table-column prop="city" label="City"></el-table-column>
</el-table-column>
</el-table-column>
{% endblock %}
Custom Detail Page #
detailis bound to data when clicking view:
<!-- Custom detail page -->
{% block dialog_card %}
<el-dialog :visible.sync="dialogTableVisible" :width="dialogWidth">
<el-table :data="detail" style="width: 100%" size="mini">
<el-table-column prop="date" label="Date"></el-table-column>
<el-table-column prop="name" label="Name" width="180"></el-table-column>
</el-table>
</el-dialog>
{% endblock %}
- Define detail page as drawer:
{% block dialog_card %}
<el-drawer :visible.sync="dialogTableVisible" id="partCard" size="98%"></el-drawer>
{% endblock %}
Other Overrides #
<!-- Override search area -->
{% block body_search %}
<div class="search_base_div" v-if="searchHead"><div class="search_div"><div style="display:flex;" v-if="startRender" id="partSearch1"></div></div></div>
{% endblock %}
<!-- Filter area component -->
{% block body_filter %}
<template v-if="!searchHead" id="partSearch2"></template>
{% endblock %}
<!-- Custom add/modify dialog -->
{% block dialog_modify %}
<el-dialog :title="title" :visible.sync="dialogFormVisible">
<el-form ref="form" :model="form" :rules="rules">
<el-form-item prop="displayName" label="Display Name" :label-width="formLabelWidth">
<el-input v-model.trim="form.fieldName" autocomplete="off"></el-input>
</el-form-item>
</el-form>
</el-dialog>
{% endblock %}
Custom Area — Indicator Cards #

- Template override:
{% block body_extHead %}
<div class="search_base_div"><div class="search_div"><div style="display:flex;" v-if="startRender">
<el-statistic group-separator="," :precision="2" :value=diyVars.d1.value :title=diyVars.d1.title></el-statistic>
<el-statistic title="Ratio"><template slot="formatter">456/2</template></el-statistic>
</div></div></div>
{% endblock %}
- JS assignment:
vapp.diyVars.d1={value:12345, title:'Metric A'}
- From dataset (e.g., #4):
select 'Metric A' as title, 12345 as value from xxx;
select 'Metric B' as title, 1245 as value from xxx
ds_refresh(4);
vapp.diyVars.d1=ds_createMap_all(data4.df0);
vapp.diyVars.d2=ds_createMap_all(data4.df1);
Custom Validation Rules #
vapp.rules = {
username: [
{ required: true, message: 'Please enter username', trigger: 'blur' },
{ min: 3, max: 15, message: 'Username must be 3-15 characters', trigger: 'blur' }
],
qty: [{
required: true,
validator: (rule, value, callback) => {
if (value === '') {
callback(new Error('Please enter quantity'));
} else if (isNaN(value)) {
callback(new Error('Must be a number'));
} else {
callback();
}
},
trigger: 'blur'
}],
};
Custom Functions #
// Table row class for highlighting
vapp.tableRowClassName=({row, rowIndex})=>{
if (row.fieldName > 100) {
return 'warning-row';
}
return '';
}
// Auto-calc on input
vapp.calcChange=(k)=>{
if(k=='food_price'){vapp.form.totalPrice=vapp.form.food_price*100}
};
// Row click action
vapp.handleRowClickChange=(row)=>{}
// Data export action
vapp.daochu=()=>{}
// Custom function
vapp.diyFun=(param)=>{}
// Custom variables
vapp.diyVars={}
// Refresh table
vapp.refreshTable();
// Refresh filter
vapp.data_filter();
Custom Config Keys #
Specify which configs use custom definitions instead of page-saved configs:
vapp.diyKeys='nameDict,optionDict';
Custom Default Values #
vapp.defaultDict={
"calcDate":new Date().format('yyyy-MM-dd'),
"orderDate":[new Date().setDate(new Date().getDate()-30),new Date()]
}
- To apply params on page open:
ds_setParam('calcDate', vapp.defaultDict.calcDate);
ds_setParam('orderDate_s', vapp.defaultDict.orderDate[0]);
ds_setParam('orderDate_e', vapp.defaultDict.orderDate[1]);