Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:55:36 +08:00
commit cc90ad1792
20 changed files with 8932 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
/**
* SAPUI5 Component Template
*
* Usage: Replace placeholders with actual values:
* - {{namespace}}: Your app namespace (e.g., com.mycompany.myapp)
* - {{appId}}: Application ID
*
* File: Component.js
*/
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel",
"sap/ui/Device"
], function(UIComponent, JSONModel, Device) {
"use strict";
return UIComponent.extend("{{namespace}}.Component", {
metadata: {
manifest: "json"
},
/**
* Component initialization
* Called once when component is instantiated
*/
init: function() {
// Call parent init
UIComponent.prototype.init.apply(this, arguments);
// Create device model
var oDeviceModel = new JSONModel(Device);
oDeviceModel.setDefaultBindingMode("OneWay");
this.setModel(oDeviceModel, "device");
// Create router
this.getRouter().initialize();
},
/**
* Get content density class based on device
* @returns {string} CSS class
*/
getContentDensityClass: function() {
if (!this._sContentDensityClass) {
if (!Device.support.touch) {
this._sContentDensityClass = "sapUiSizeCompact";
} else {
this._sContentDensityClass = "sapUiSizeCozy";
}
}
return this._sContentDensityClass;
}
});
});

235
templates/controller.js Normal file
View File

@@ -0,0 +1,235 @@
/**
* SAPUI5 Controller Template
*
* Usage: Replace placeholders with actual values:
* - {{namespace}}: Your app namespace
* - {{ControllerName}}: Controller name
*
* File: controller/{{ControllerName}}.controller.js
*/
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator",
"sap/m/MessageToast",
"sap/m/MessageBox",
"{{namespace}}/model/formatter"
], function(Controller, JSONModel, Filter, FilterOperator, MessageToast, MessageBox, formatter) {
"use strict";
return Controller.extend("{{namespace}}.controller.{{ControllerName}}", {
formatter: formatter,
/* =========================================================== */
/* lifecycle methods */
/* =========================================================== */
/**
* Called when controller is instantiated
*/
onInit: function() {
// Create view model
var oViewModel = new JSONModel({
busy: false,
selectedItemsCount: 0,
title: ""
});
this.getView().setModel(oViewModel, "view");
// Get router
var oRouter = this.getOwnerComponent().getRouter();
oRouter.getRoute("{{routeName}}").attachPatternMatched(this._onObjectMatched, this);
},
/**
* Called before view is rendered
*/
onBeforeRendering: function() {
// Preparation before rendering
},
/**
* Called after view is rendered
*/
onAfterRendering: function() {
// DOM manipulation if needed
},
/**
* Called when controller is destroyed
*/
onExit: function() {
// Cleanup
},
/* =========================================================== */
/* event handlers */
/* =========================================================== */
/**
* Refresh data
*/
onRefresh: function() {
var oBinding = this.byId("table").getBinding("items");
if (oBinding) {
oBinding.refresh();
MessageToast.show(this.getResourceBundle().getText("refreshSuccess"));
}
},
/**
* Search handler
* @param {sap.ui.base.Event} oEvent Search event
*/
onSearch: function(oEvent) {
var sQuery = oEvent.getParameter("query") || oEvent.getParameter("newValue");
var aFilters = [];
if (sQuery && sQuery.length > 0) {
aFilters.push(new Filter({
filters: [
new Filter("{{Field1}}", FilterOperator.Contains, sQuery),
new Filter("{{Field2}}", FilterOperator.Contains, sQuery)
],
and: false
}));
}
this.byId("table").getBinding("items").filter(aFilters);
},
/**
* Item press handler
* @param {sap.ui.base.Event} oEvent Press event
*/
onPress: function(oEvent) {
var oItem = oEvent.getSource();
var oContext = oItem.getBindingContext();
var sObjectId = oContext.getProperty("{{IdField}}");
this.getOwnerComponent().getRouter().navTo("detail", {
objectId: sObjectId
});
},
/**
* Selection change handler
* @param {sap.ui.base.Event} oEvent Selection change event
*/
onSelectionChange: function(oEvent) {
var iSelectedItems = this.byId("table").getSelectedItems().length;
this.getView().getModel("view").setProperty("/selectedItemsCount", iSelectedItems);
},
/**
* Add button press handler
*/
onAdd: function() {
var oModel = this.getView().getModel();
var oContext = oModel.createEntry("/{{EntitySet}}", {
properties: {
{{Field1}}: "",
{{Field2}}: "",
{{Field3}}: 0
}
});
// Navigate to detail page or open dialog
MessageToast.show(this.getResourceBundle().getText("addSuccess"));
},
/**
* Delete button press handler
*/
onDelete: function() {
var that = this;
var aSelectedItems = this.byId("table").getSelectedItems();
if (aSelectedItems.length === 0) {
MessageBox.warning(this.getResourceBundle().getText("noItemsSelected"));
return;
}
MessageBox.confirm(
this.getResourceBundle().getText("deleteConfirm", [aSelectedItems.length]),
{
onClose: function(sAction) {
if (sAction === MessageBox.Action.OK) {
that._deleteSelectedItems(aSelectedItems);
}
}
}
);
},
/* =========================================================== */
/* internal methods */
/* =========================================================== */
/**
* Route pattern matched handler
* @param {sap.ui.base.Event} oEvent Pattern matched event
* @private
*/
_onObjectMatched: function(oEvent) {
var sObjectId = oEvent.getParameter("arguments").objectId;
this.getView().bindElement({
path: "/{{EntitySet}}('" + sObjectId + "')",
events: {
dataRequested: function() {
this.getView().getModel("view").setProperty("/busy", true);
}.bind(this),
dataReceived: function() {
this.getView().getModel("view").setProperty("/busy", false);
}.bind(this)
}
});
},
/**
* Delete selected items
* @param {Array} aSelectedItems Selected items
* @private
*/
_deleteSelectedItems: function(aSelectedItems) {
var oModel = this.getView().getModel();
var that = this;
this.getView().getModel("view").setProperty("/busy", true);
var aPromises = aSelectedItems.map(function(oItem) {
var sPath = oItem.getBindingContext().getPath();
return new Promise(function(resolve, reject) {
oModel.remove(sPath, {
success: resolve,
error: reject
});
});
});
Promise.all(aPromises)
.then(function() {
that.getView().getModel("view").setProperty("/busy", false);
MessageToast.show(that.getResourceBundle().getText("deleteSuccess"));
that.byId("table").removeSelections();
})
.catch(function(oError) {
that.getView().getModel("view").setProperty("/busy", false);
MessageBox.error(that.getResourceBundle().getText("deleteError"));
});
},
/**
* Get resource bundle for i18n
* @returns {sap.ui.model.resource.ResourceModel} Resource bundle
* @private
*/
getResourceBundle: function() {
return this.getOwnerComponent().getModel("i18n").getResourceBundle();
}
});
});

200
templates/formatter.js Normal file
View File

@@ -0,0 +1,200 @@
/**
* SAPUI5 Formatter Template
*
* Common formatter functions for data display
*
* File: model/formatter.js
*/
sap.ui.define([
"sap/ui/core/format/DateFormat",
"sap/ui/core/format/NumberFormat"
], function(DateFormat, NumberFormat) {
"use strict";
return {
/**
* Format date to localized string
* @param {Date} oDate Date object
* @returns {string} Formatted date
*/
formatDate: function(oDate) {
if (!oDate) {
return "";
}
var oDateFormat = DateFormat.getDateInstance({
pattern: "dd.MM.yyyy"
});
return oDateFormat.format(oDate);
},
/**
* Format date and time to localized string
* @param {Date} oDateTime DateTime object
* @returns {string} Formatted date and time
*/
formatDateTime: function(oDateTime) {
if (!oDateTime) {
return "";
}
var oDateFormat = DateFormat.getDateTimeInstance({
pattern: "dd.MM.yyyy HH:mm:ss"
});
return oDateFormat.format(oDateTime);
},
/**
* Format number with 2 decimal places
* @param {number} fNumber Number to format
* @returns {string} Formatted number
*/
formatNumber: function(fNumber) {
if (fNumber === null || fNumber === undefined) {
return "";
}
var oNumberFormat = NumberFormat.getFloatInstance({
minFractionDigits: 2,
maxFractionDigits: 2
});
return oNumberFormat.format(fNumber);
},
/**
* Format currency with symbol
* @param {number} fAmount Amount
* @param {string} sCurrency Currency code
* @returns {string} Formatted currency
*/
formatCurrency: function(fAmount, sCurrency) {
if (fAmount === null || fAmount === undefined) {
return "";
}
var oCurrencyFormat = NumberFormat.getCurrencyInstance();
return oCurrencyFormat.format(fAmount, sCurrency);
},
/**
* Format status to display text
* @param {string} sStatus Status code
* @returns {string} Status text
*/
statusText: function(sStatus) {
var oResourceBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
var mStatusText = {
"A": oResourceBundle.getText("statusApproved"),
"R": oResourceBundle.getText("statusRejected"),
"P": oResourceBundle.getText("statusPending"),
"D": oResourceBundle.getText("statusDraft")
};
return mStatusText[sStatus] || sStatus;
},
/**
* Format status to sap.ui.core.ValueState
* @param {string} sStatus Status code
* @returns {string} Value state
*/
statusState: function(sStatus) {
var mStatusState = {
"A": "Success",
"R": "Error",
"P": "Warning",
"D": "None"
};
return mStatusState[sStatus] || "None";
},
/**
* Format boolean to Yes/No text
* @param {boolean} bValue Boolean value
* @returns {string} Yes or No
*/
formatBoolean: function(bValue) {
var oResourceBundle = this.getOwnerComponent().getModel("i18n").getResourceBundle();
return bValue ? oResourceBundle.getText("yes") : oResourceBundle.getText("no");
},
/**
* Truncate long text with ellipsis
* @param {string} sText Text to truncate
* @param {number} iMaxLength Maximum length
* @returns {string} Truncated text
*/
truncateText: function(sText, iMaxLength) {
if (!sText) {
return "";
}
iMaxLength = iMaxLength || 50;
if (sText.length <= iMaxLength) {
return sText;
}
return sText.substring(0, iMaxLength) + "...";
},
/**
* Calculate percentage
* @param {number} fValue Current value
* @param {number} fTotal Total value
* @returns {string} Percentage with % sign
*/
formatPercentage: function(fValue, fTotal) {
if (!fTotal || fTotal === 0) {
return "0%";
}
var fPercentage = (fValue / fTotal) * 100;
return fPercentage.toFixed(1) + "%";
},
/**
* Format full name from first and last name
* @param {string} sFirstName First name
* @param {string} sLastName Last name
* @returns {string} Full name
*/
formatFullName: function(sFirstName, sLastName) {
if (!sFirstName && !sLastName) {
return "";
}
return (sFirstName || "") + " " + (sLastName || "");
},
/**
* Format file size in human-readable format
* @param {number} iBytes File size in bytes
* @returns {string} Formatted file size
*/
formatFileSize: function(iBytes) {
if (!iBytes || iBytes === 0) {
return "0 B";
}
var aUnits = ["B", "KB", "MB", "GB", "TB"];
var iUnit = 0;
while (iBytes >= 1024 && iUnit < aUnits.length - 1) {
iBytes /= 1024;
iUnit++;
}
return iBytes.toFixed(1) + " " + aUnits[iUnit];
},
/**
* Highlight search term in text
* @param {string} sText Full text
* @param {string} sSearchTerm Search term to highlight
* @returns {string} Text with highlighted term
*/
highlightText: function(sText, sSearchTerm) {
if (!sText || !sSearchTerm) {
return sText;
}
var sRegex = new RegExp("(" + sSearchTerm + ")", "gi");
return sText.replace(sRegex, "<strong>$1</strong>");
}
};
});

133
templates/manifest.json Normal file
View File

@@ -0,0 +1,133 @@
{
"_version": "1.42.0",
"sap.app": {
"id": "{{namespace}}",
"type": "application",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"mainService": {
"uri": "/sap/opu/odata/sap/SERVICE_SRV/",
"type": "OData",
"settings": {
"localUri": "localService/metadata.xml",
"odataVersion": "2.0"
}
}
}
},
"sap.ui": {
"technology": "UI5",
"icons": {
"icon": "",
"favIcon": "",
"phone": "",
"phone@2": "",
"tablet": "",
"tablet@2": ""
},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"flexEnabled": true,
"dependencies": {
"minUI5Version": "1.120.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.ui.layout": {},
"sap.f": {}
}
},
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "{{namespace}}.i18n.i18n",
"supportedLocales": [""],
"fallbackLocale": ""
}
},
"": {
"dataSource": "mainService",
"preload": true,
"settings": {
"defaultBindingMode": "TwoWay",
"defaultCountMode": "Inline",
"refreshAfterChange": false,
"metadataUrlParams": {
"sap-value-list": "none"
}
}
}
},
"resources": {
"css": [
{
"uri": "css/style.css"
}
]
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"type": "View",
"viewType": "XML",
"path": "{{namespace}}.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
},
"async": true
},
"routes": [
{
"pattern": "",
"name": "main",
"target": "main"
},
{
"pattern": "detail/{objectId}",
"name": "detail",
"target": "detail"
}
],
"targets": {
"main": {
"viewName": "Main",
"viewId": "main",
"viewLevel": 1
},
"detail": {
"viewName": "Detail",
"viewId": "detail",
"viewLevel": 2
},
"notFound": {
"viewName": "NotFound",
"viewId": "notFound"
}
}
},
"rootView": {
"viewName": "{{namespace}}.view.App",
"type": "XML",
"async": true,
"id": "app"
}
}
}

114
templates/xml-view.xml Normal file
View File

@@ -0,0 +1,114 @@
<mvc:View
controllerName="{{namespace}}.controller.{{ControllerName}}"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:core="sap.ui.core"
displayBlock="true">
<Page
id="page"
title="{i18n>{{pageTitle}}}">
<headerContent>
<Button
icon="sap-icon://refresh"
press=".onRefresh"
tooltip="{i18n>refreshTooltip}"/>
</headerContent>
<content>
<Table
id="table"
inset="false"
items="{
path: '/{{EntitySet}}',
sorter: {
path: '{{SortField}}'
}
}"
growing="true"
growingThreshold="20"
mode="SingleSelectMaster"
selectionChange=".onSelectionChange">
<headerToolbar>
<OverflowToolbar>
<Title
text="{i18n>{{tableTitle}}}"
level="H2"/>
<ToolbarSpacer/>
<SearchField
width="50%"
search=".onSearch"
placeholder="{i18n>searchPlaceholder}"/>
<Button
icon="sap-icon://add"
press=".onAdd"
tooltip="{i18n>addTooltip}"/>
</OverflowToolbar>
</headerToolbar>
<columns>
<Column
width="12em">
<Text text="{i18n>{{column1}}}"/>
</Column>
<Column
minScreenWidth="Tablet"
demandPopin="true"
hAlign="End">
<Text text="{i18n>{{column2}}}"/>
</Column>
<Column
minScreenWidth="Tablet"
demandPopin="true">
<Text text="{i18n>{{column3}}}"/>
</Column>
</columns>
<items>
<ColumnListItem
type="Navigation"
press=".onPress">
<cells>
<ObjectIdentifier
title="{{{Field1}}}"
text="{{{Field2}}}"/>
<ObjectNumber
number="{
path: '{{Field3}}',
type: 'sap.ui.model.type.Float',
formatOptions: {
minFractionDigits: 2,
maxFractionDigits: 2
}
}"
unit="{{{CurrencyField}}}"/>
<ObjectStatus
text="{
path: '{{StatusField}}',
formatter: '.formatter.statusText'
}"
state="{
path: '{{StatusField}}',
formatter: '.formatter.statusState'
}"/>
</cells>
</ColumnListItem>
</items>
</Table>
</content>
<footer>
<OverflowToolbar>
<ToolbarSpacer/>
<Button
text="{i18n>deleteButton}"
icon="sap-icon://delete"
type="Reject"
press=".onDelete"
enabled="{= ${view>/selectedItemsCount} > 0}"/>
</OverflowToolbar>
</footer>
</Page>
</mvc:View>