Chapter 2:Build a Dashboard
The first part of this tutorial introduced you to most of the concepts of the Widget framework. Now it's time to gain a deeper understanding of the Oinone framework, which is the framework used by the Web client.
To start learning, you need a running Oinone service and a set-up development environment. Before starting the exercises, ensure you have followed all the steps described in the tutorial introduction. In this chapter, we will continue to use the simple Card component built in the previous chapter and gradually add features to it using the Oinone framework.
I. A New Mask
Most interfaces in the Oinone client use a common layout: a control component with some functions at the top, and a lower area divided into two parts— a menu for switching pages on the left and a main content area on the right. This is achieved using a Mask
rendering framework.
Remember the registerLayout
method used in the previous chapter to register layouts? For masks, there is a corresponding registerMask
method to register masks.
Below is the default mask provided by the platform. Generally, you only need to modify it as needed:
<mask>
<multi-tabs />
<header>
<widget widget="app-switcher" />
<block>
<widget widget="notification" />
<widget widget="divider" />
<widget widget="language" />
<widget widget="divider" />
<widget widget="user" />
</block>
</header>
<container>
<sidebar>
<widget widget="nav-menu" height="100%" />
</sidebar>
<content>
<breadcrumb />
<block width="100%">
<widget width="100%" widget="main-view" />
</block>
</content>
</container>
</mask>
In the examples of this chapter, you need to make the following changes to this mask:
- Retain all top-bar functions and remove the left navigation menu component.
- Remove the breadcrumb component to make the main content distribution area appear larger.
- Apply this mask to the page corresponding to the "Country Groups" menu item.
After making these modifications, we can proceed with the following learning content.
Tip
For more information on masks, refer to: Mask
II. Theory: Initiate a Back-end Request
In practice, each component may initiate requests to the back-end to fetch necessary data for display on the page. We usually manage these requests in the service
directory for easy use in any component.
The Oinone framework provides a unified method to send requests. We can define a request to the back-end like this to obtain statistical results of "country groups":
import { GenericFunctionService } from '@kunlun/dependencies';
const MODEL_MODEL = 'resource.ResourceCountryGroup';
export class ResourceCountryGroupService {
public static async countByWrapper(rsql?: string): Promise<number> {
const count = await GenericFunctionService.INSTANCE.simpleExecuteByName<number>(MODEL_MODEL, 'countByWrapper', {
rsql
});
return Number(count || 0);
}
}
Tip
For more information on custom requests, refer to: Customize GraphQL Request
Then, we can use it when the component is mounted and display the fetched data on the page:
@Widget.Reactive()
protected count: number = -1;
protected async mounted() {
super.mounted();
this.count = await ResourceCountryGroupService.countByWrapper();
}
If query conditions need to be carried, we can filter the dataset through the function's input parameter rsql
to obtain the required statistical results:
protected async mounted() {
super.mounted();
this.count = await ResourceCountryGroupService.countByWrapper("code =like= 'a'");
}
Tip
For more information on RSQL
, refer to: RSQL Service
III. Add a Statistics Count Card
Next, we will complete a simple dashboard by implementing a statistics count card. This is the final page effect:

Please try to complete the exercise according to the following prompts:
- Use
registerMask
to remove the left menu and breadcrumbs. (Completely the same as the first section) - Create a
StatisticsCard
component that initiates statistical requests for specified models and displays them on the page. This component has three configurable parameters: title, model code (modelModel
), and filter expression (rsql
). - Use
registerLayout
to render the component in the page'smain content distribution area
. Display information such as "Total Number of Countries", "Total Number of Country Groups", "Number of Languages", "Number of System Users", and "Number of System Roles".
Tip
Data such as countries, country groups, and languages can be maintained in "Resources".
Data such as users and roles can be maintained in "Management Center".
Since we are practicing on the "Country Groups" page, readers can first comment out the code related to the custom-registered Mask
and Layout
, maintain the data, and then continue with the exercise.
A feasible layout template can be defined as follows:
<view type="TABLE">
<div class="statistics-card-demo-groups">
<element widget="StatisticsCard" title="国家的总数" modelModel="resource.ResourceCountry" />
<element widget="StatisticsCard" title="国家分组的总数" modelModel="resource.ResourceCountryGroup" />
<element widget="StatisticsCard" title="语言种类" modelModel="resource.ResourceLang" />
</div>
<div class="statistics-card-demo-groups">
<element widget="StatisticsCard" title="系统用户数" modelModel="user.PamirsUser" rsql="source == 'BUILD_IN'" />
<element widget="StatisticsCard" title="非系统用户数" modelModel="user.PamirsUser" rsql="source != 'BUILD_IN'" />
<element widget="StatisticsCard" title="系统角色数" modelModel="auth.AuthRole" rsql="source == 'BUILD_IN'" />
<element widget="StatisticsCard" title="非系统角色数" modelModel="auth.AuthRole" rsql="source != 'BUILD_IN'" />
</div>
<div />
</view>
Tip
In the table view, the last div
tag is processed to expand according to the screen height, which is a side effect of the built-in css
styles.
Using different view types may cause side effects where built-in css
styles affect the current page display, and readers need to properly handle these issues according to the actual situation.
The layout provided above solves this problem with an empty div
tag.
IV. Display a Pie Chart
Everyone loves charts(!), so let's add a pie chart to the dashboard. It will show the proportion of system users (source == 'BUILD_IN'
) and non-system users (source != 'BUILD_IN'
) in the entire system.
To make our pie chart component more versatile, readers can explore implementing a more generic pie chart that shows the proportion of system roles and non-system roles in the entire system. This is the final page effect:

Please try to complete the exercise according to the following prompts:
- Create a
PieChart
component that displays a pie chart based on the configured statistical dimensions (series). - Use the
ECharts
third-party chart plugin to implement the pie chart display. - Due to the order of requests and rendering, initiate the request in the
mounted
method, and use thewatch
method provided byVue
to monitor data changes, finally re-rendering the pie chart component after data changes. - In the
onUnmounted
hook, call thedispose
method provided by theECharts
component instance to destroy it. - (Bonus) Use
Loading
animation effects to handle the loading process when network requests are slow. - (Bonus) Design the registered
Layout
structure to make theLayout
readable.
To help readers better understand the readability of Layout
, this section provides a reasonable definition method.
For structured data, the performance in XML
is usually the definition of multiple identical sub-tags. A feasible layout template can be defined as follows:
<element widget="PieChart" title="用户统计" modelModel="user.PamirsUser">
<series name="系统用户数" rsql="source == 'BUILD_IN'" />
<series name="非系统用户数" rsql="source != 'BUILD_IN'" />
</element>
Tip
The sub-tags defined in the layout can be obtained using this.template.widgets
in the initialize
method, which is used to parse the available dimension (series) definition part of the component.
V. Take It Further
If you have time, here are some small improvements you can try:
- Ensure your page can be translated (use
$translate
for static text in Vue templates andtranslateValueByKey
for static text in TypeScript). - Implement clicking a part of the pie chart to navigate to the list page of the corresponding model data and display all data.
- Create a dashboard component that can configure the dashboard content and save it in user preferences.