Vite Vue 2025

Vite Vue 安裝

版本號
node v22.14.0 && npm 11.4.2

參考資料:
https://vite.dev/guide/

安裝

1
npm create vite@latest 資料名稱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
> npx
> "create-vite" vite_vue_demo_project


◆ Select a framework:
│ ● Vanilla
│ ○ Vue
│ ○ React
│ ○ Preact
│ ○ Lit
│ ○ Svelte
│ ○ Solid
│ ○ Qwik
│ ○ Angular
│ ○ Marko
│ ○ Others



◇ Select a framework:
│ Vue

◇ Select a variant:
│ TypeScript

◇ Use rolldown-vite (Experimental)?:
參考資料:https://cn.vite.dev/guide/rolldown
│ Yes

◆ Install with npm and start now?
│ ● Yes / ○ No

Nuxt4 安裝

清除 npx 快取

確保 Nuxt CLI 不存在快取或版本上的差異,可以清除快取來重新安裝。

1
npx clear-npx-cache

Nuxt 安裝

Node版本號 22.13.1
安裝指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// pnpm
pnpm create nuxt@latest <project-name>
// yarn
yarn create nuxt <project-name>
// npm
npm create nuxt@latest <project-name>

create-nuxt@3.34.0
Ok to proceed? (y)


> npx
> create-nuxt nuxt_demo_project


.d$b.
i$$A$$L .d$b
.$$F` `$$L.$$A$$.
j$$' `4$$:` `$$.
j$$' .4$: `$$.
j$$` .$$: `4$L
:$$:____.d$$: _____.:$$:
`4$$$$$$$$P` .i$$$$$$$$P`

┌ Welcome to Nuxt!
│載入模板
◇ Templates loaded
◇ 從快取載入模板
◇ Templates loaded

│◆ 您想使用哪一個範本?
◆ Which template would you like to use?
content – 內容驅動網站

○ minimal – Nuxt 4 的極簡配置(建議)
│ ● minimal – Minimal setup for Nuxt 4 (recommended)
○ module – Nuxt 模組
│ ○ module – Nuxt module
●. * ui – 使用 Nuxt UI 的應用
│ ○ ui – App using Nuxt UI
◇ 在 nuxt4_demo_project 中建立項目
◇ Creating project in nuxt4_demo_project
◇ 已下載最小模板
◇ Downloaded minimal template


◆ 您想使用哪個軟體套件管理器?
◆ Which package manager would you like to use?
│ ● npm (current)
│ ○ pnpm
│ ○ yarn
│ ○ bun
│ ○ deno
◆ 初始化 Git 倉庫?
Initialize git repository?
│ No

◇ 已安裝依賴項
◇ Dependencies installed

│ ◇ 是否要瀏覽並安裝模組?
◇ Would you like to browse and install modules?
│ Yes

│ ◇ 已載入模組
◇ Modules loaded

│ ◇ 搜尋並選擇模組:
│ 0 items selected

└ ✨ Nuxt 專案是使用最小範本建立的。
└ ✨ Nuxt project has been created with the minimal template.

╭── 👉 Next steps ───────────╮
│ │
│ › cd demo_nuxt_project │
│ › npm run dev │
│ │
╰────────────────────────────╯
larahuang@larahuang Nuxt % cd demo_nuxt_project
larahuang@larahuang demo_nuxt_project % npm run dev

Nuxt 4.4.2 (with Nitro 2.13.3, Vite 7.3.2 and Vue 3.5.32)
下午1:57:17
➜ Local: http://localhost:3000/
➜ Network: use --host to expose

預設監聽 Port: 3000,若因為衝突或有需要做調整可以使用這個參數

1
2
3
npm run dev -- -p 5173
# 等價
npx nuxi dev -p 5173

建立 .env 檔案:在專案根目錄建立 .env 檔案,定義您的環境變數。

1
2
API_BASE_URL=https://example.com

清除自動產生的 Nuxt 檔案和快取

1
npx nuxi cleanup

Nuxt 3 實戰筆記

nuxt4架構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
├── .nuxt/                 # Nuxt開發中,根據目錄結構生成。
├── .output/ # Nuxt build 後的輸出目錄(用於部署)
├── .env # 環境配置檔
├── plugins/ # 套件
├── public/ # 靜態公開檔案
├── server/ # 放後端邏輯
│ └── api/
│ └── order.js # 對應 /api/order
├── app.vue # Nuxt 應用入口點
├── app.vue # 入口點
├── layouts/ # 自訂佈局
├── middleware/ # 中介層邏輯
├── assets/ # 靜態資源
├── components/ # Vue 元件
├── composables/ # 邏輯函式
├── content/ # 資料檔案
├── pages/ # 頁面檔案
├── nuxt.config.ts # Nuxt 配置設定檔(支援 TypeScript)
├── package.json # 套件與指令設定
├── tsconfig.json # TypeScript 設定
└── README.md # 專案說明

安裝Tailwindcss

參考Nuxt UI 官網

安裝 Nuxt UI 套件

1
npm install @nuxt/ui tailwindcss --save

將 Nuxt UI 模組新增到您的系統中nuxt.config.ts

1
2
3
export default defineNuxtConfig({
modules: ['@nuxt/ui']
})

在您的 CSS 中匯入 Tailwind CSS 和 Nuxt UI

app/assets/css/main.css nuxt.config.ts

1
2
3
// app/assets/css/main.css 
@import "tailwindcss";
@import "@nuxt/ui";

.vscode/settings.json

1
2
3
4
5
6
7
8
9
10
11
{
"files.associations": {
"*.css": "tailwindcss"
},
"editor.quickSuggestions": {
"strings": "on"
},
"tailwindCSS.classAttributes": ["class", "ui"],
"tailwindCSS.classFunctions": ["defineAppConfig"]
}

安裝. ElementPLus

1
2
3
npm i @element-plus/nuxt -D
npm i --save element-plus
npm i --save @element-plus/icons-vue

https://github.com/element-plus/element-plus-nuxt-starter/tree/main

配置

nuxt.config.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
export default defineNuxtConfig({
// 模組新增到您的系統中nuxt
modules: [
'@nuxt/eslint',
'@nuxt/ui',
'@element-plus/nuxt'
],
// 開發者工具
devtools: {
enabled: true
},
// 樣式
css: ['~/assets/scss/main.scss'],
// 路由規則
routeRules: {
// 預渲染
'/': { prerender: true }
},
// 相容性日​​期
compatibilityDate: '2025-01-15',
// 相容性日​​期
vite: {
css: {
// 預處理器選項:
preprocessorOptions: {
scss: {
// 附加數據
additionalData: `@use "@/assets/scss/element/index.scss" as element;`
}
}
}
},
// elementPlus
elementPlus: {
icon: 'el-icon',
importStyle: 'scss',
themes: ['dark']
}
})

Nuxt4 Layouts 佈局

Layouts

參考資料
透過在你的 app.vue 中加入 來啟用 Layouts。
app/app.vue

1
2
3
4
5
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

新增一個 ~/layouts/default.vue
app/layouts/default.vue
在佈局檔案中,頁面的內容將會顯示在 元件中。

1
2
3
4
5
6
<template>
<div>
<p>所有頁面共享一些預設佈局內容</p>
<slot />
</div>
</template>

具名佈局(Named Layout)

1
2
3
-| layouts/
---| default.vue
---| custom.vue

你可以在頁面中使用 custom 佈局
pages/about.vue

1
2
3
4
5
6
7
8
9
10
11
<script setup lang="ts">
declare module 'nuxt/app' {
interface NuxtLayouts {
'custom': unknown
}
}
// ---cut---
definePageMeta({
layout: 'custom',
})
</script>

你可以使用 的 name 屬性直接覆寫所有頁面的預設佈局。
app/app.vue

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
// 您可能會根據 API 呼叫或登入狀態來選擇此項目。
const layout = 'custom'
</script>

<template>
<NuxtLayout :name="layout">
<NuxtPage />
</NuxtLayout>
</template>

Nuxt4 環境變數

1.在根目錄新增.env

1
2
API_BASE_URL=https://example.com
DB_PASSWORD=your_secret_password

2.nuxt.config.ts設定

在 nuxt.config.ts 中綁定變數

1
2
3
4
5
6
7
8
9
10
11
12
export default defineNuxtConfig({
//
runtimeConfig: {
// 伺服器端私有變數
dbPassword: process.env.DB_PASSWORD,

// 公開變數(也會暴露給客戶端)
public: {
apiBaseUrl: process.env.API_BASE_URL,
}
}
})

3. process問題

解決方法,安裝 @types/node

1
npm i --save-dev @types/node 

專案根目錄建立檔案 server/api/test.ts:

1
2
3
4
5
6
7
8
export default defineEventHandler((event) => {
const config = useRuntimeConfig()

// 這會在您的終端機(Terminal)印出,而不是瀏覽器
// console.log('伺服器端讀取密碼:', config.dbPassword)

return { success: true }
})

在頁面或組件中讀取

範例放在app.vue

1
2
3
4
5
6
7
8
9
<script setup lang="ts" >
const runtimeConfig = useRuntimeConfig()

// 讀取公開變數(客戶端與伺服器皆可見)
console.log('讀取公開變數(客戶端與伺服器皆可見)',runtimeConfig.public.apiBaseUrl)

console.log('伺服器端讀取密碼:',runtimeConfig.dbPassword)

</script>

Nuxt4 如何部署vercel

nuxt如何部署vercel

一、修改本機專案配置 Nitro 預設:在 nuxt.config.ts 中將 nitro 預設為 vercel。
1.

1
2
3
4
5
6
7
//打字稿
導出預設的defineNuxtConfig({
硝基:{
預設:'vercel'
}
})

2.提交程式碼:確保您的程式碼已經透過 Git 初始化並自動至 GitHub、GitLab 或 Bitbucket 倉庫。
二、Vercel導入與部署登入Vercel儀表板。點選新增新建,選擇項目。授權並選擇您的GitHub倉庫,點擊匯入。 Vercel環境變數中填入。點選部署,等待建置即可完成上線。
nuxt.config.ts 中加入硝基.vercel.config 參數。環境變數管理:如果您使用 dotenv,可以透過 Dotenv Nuxt 官方的 Nuxt 部署至 Vercel 指南或 iT 邦幫忙 Vercel 部署攻略。

SAP UI5 專案架構

SAP UI5專案架構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
├── node_modules              # 
webapp/ # 應用程式的核心根目錄

├── controller/ # 邏輯控制層 (Controller)
│ ├── App.controller.js
│ └── Main.controller.js

├── view/ # 介面顯示層 (View,通常為 XML 格式)
│ ├── App.view.xml
│ └── Main.view.xml

├── model/ # 資料模型層 (Model,處理本地或全域資料)
│ ├── formatter.js # 欄位格式化工具
│ └── models.js # 初始化模型的工廠方法

├── i18n/ # 多國語言語系檔
│ ├── i18n.properties # 預設語系
│ └── i18n_zh_TW.properties

├── css/ # 自訂樣式表 (選填)
│ └── style.css

├── Component.js # 應用的入口組件,負責初始化與路由啟動
├── manifest.json # 核心元數據描述文件(描述配置、資料源、路由)
└── index.html # 獨立運行的本地測試入口網頁
├── .gitignore git版控
├── package.json # 套件與指令設定
└── README.md # 專案說明

核心架構元件解析

  • manifest.json (App Descriptor):
  1. 核心配置中心:定義應用程式的識別碼(ID)、版本、所依賴的 UI5 核心庫,以及多國語言檔路徑。
  2. 資料源與模型宣告:在此處定義與後端溝通的 dataSources(例如 SAP OData 服務),並直接綁定至全域 models。
  3. 路由配置 (Routing):定義導頁邏輯(Routes)與對應的顯示目標(Targets),實現單頁應用(SPA)的切換。
  • Component.js
  1. 應用啟動點:繼承自 sap.ui.core.UIComponent。
  2. 自動載入:框架會引導此檔案自動讀取 manifest.json。它的主要職責是例項化應用的根視圖(Root View)並初始化路由服務。
  • View (視圖層):
  1. 宣告式 UI:強烈建議使用 XML 格式(如 Main.view.xml)。
  2. 核心職責:僅負責佈局與控制項定義(如表格、輸入框、按鈕),不包含任何業務邏輯,確保介面與邏輯分離。
  • Controller (控制層):
  1. 行為處理:使用 JavaScript 撰寫(如 Main.controller.js),負責處理視圖上觸發的事件(如點擊按鈕、輸入驗證)。
  2. 資料操作:透過控制項獲取模型,或將後端資料更新至模型中。
  • Model (模型層與資料綁定):
    雙向綁定:支援 JSONModel(本地雙向)、ResourceModel(多國語言 i18n)以及 ODataModel(後端伺服器資料同步)。

兩種主要的開發架構型態

  1. SAP UI5 Free-style (自由開發):
  2. 依循上述標準 MVC 架構,開發者擁有極高的 UI 客製化彈性。
  3. 適用於業務邏輯極度特殊、現有標準元件無法滿足的企業流程。
  4. SAP Fiori Elements (配置驅動開發):
  5. 無代碼/低代碼 UI:開發者不需要手動撰寫 View 和 Controller。
  6. 元數據驅動:完全依賴後端 OData 服務的註解 (Annotations)與元數據來自動渲染標準的 Fiori 頁面(例如:List Report、Object Page)。
  7. 適用於高標準化、講求快速交付的增刪查改(CRUD)應用。

manifest.json 解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
{
"_version": "1.12.0",
"sap.app": {
"id": "my.app", // 應用的唯一識別碼
"i18n": "i18n/i18n.properties", // 多國語言文字檔的路徑
"title": "{{appTitle}}", // 應用標題,對應 i18n 裡的 key
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
},
"dataSources": {
"orderApi": {
"url": "https://script.google.com/macros/s/AKfycby3VIvl_TU4O_KoFUNXlvfINS0Lv1CEZuU5AoY7YU9JaFTgZ2x1f1lvStjoIuUldREFKw/exec",
"type": "JSON"
}
}
},
"sap.ui": {
"technology": "UI5",
"icons": {},
"deviceTypes": {
"desktop": true,
"tablet": true,
"phone": true
}
},
"sap.ui5": {
"rootView": {
"viewName": "my.app.view.Main", // 應用的第一個根視圖(通常只包含 sap.m.App 控制項)
"type": "XML",
"async": true,
"id": "app"
},
"resources": {
"css": [
{
"uri": "css/style.css"
}
]
},
"dependencies": {
"minUI5Version": "1.71.66",
"libs": {
"sap.ui.core": {},
"sap.m": {}, // 必備的行動與桌面端核心 UI 庫
"sap.tnt": {},
"sap.f": {}
}
},
"handleValidation": true,
"contentDensities": {
"compact": true,
"cozy": true
},
"models": {
"i18n": {. // 宣告全域多國語言模型
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "uxc.integration.i18n.i18n",
"async": true
}
},
"": { // 空白字串代表「預設模型」,通常綁定主要 OData 服務
"dataSource": "mainService",
"preload": true,
"settings": {
"v2Mode": true,
"defaultBindingMode": "TwoWay" // 設定雙向綁定(畫面改,資料改)
}
},
"ui": {
"type": "sap.ui.model.json.JSONModel"
}
},
"routing": { ... }
}
}

參考資料

index.html
引入 SAPUI5 核心庫
全部語法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SAPUI5 1.71 Google Sheets CRUD 系統</title>
<!-- 引入 SAPUI5 核心庫 src="https://sdk.openui5.org/resources/sap-ui-core.js" -->

<script
src="https://sdk.openui5.org/resources/sap-ui-core.js"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-resourceroots='{"my.app": "./"}'
data-sap-ui-compatVersion="edge"
data-sap-ui-oninit="module:sap/ui/core/ComponentSupport"
data-sap-ui-async="true"
>
</script>
</head>
<body class="sapUiBody" id="content">
<div data-sap-ui-component data-name="my.app"></div>
</body>
</html>

Component.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sap.ui.define([
"sap/ui/core/UIComponent"
], function (UIComponent) {
"use strict";
return UIComponent.extend("my.app.Component", {
metadata: {
manifest: "json"
},
init: function () {
UIComponent.prototype.init.apply(this, arguments);

}
});
});



Main.view.xml 如同 html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<mvc:View
controllerName="my.app.controller.Main"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core">
<App>
<Page title="SAPUI5 1.71.66 Google Sheets RESTful CRUD 系統">
<content><!-- 使用 SimpleForm 構建表單 -->
<f:SimpleForm
editable="true"
layout="ResponsiveGridLayout"
labelSpanXL="3"
labelSpanL="3"
labelSpanM="3"
labelSpanS="12"
adjustLabelSpan="false"
emptySpanXL="4"
emptySpanL="4"
emptySpanM="4"
emptySpanS="0"
columnsXL="1"
columnsL="1"
columnsM="1"
singleContainerFullWidth="true">
<f:content>
<VBox>
<Label text="產品名稱" required="true" />
<Input
placeholder="請輸入產品名稱..."

/>

<Label text="活動區域" required="true" />
<Select
id="zoneSelect"
selectedKey="{/selectedZone}"
items="{path: '/zones'}"
change="onZoneChange"
>
<items>
<!-- key 是傳給後台的值,text 是顯示給使用者看的文字 -->
<core:Item key="{key}" text="{text}" />
</items>
</Select>

<!-- 職業 -->
<Label text="職業" required="true" />
<Select placeholder="請選擇職業"
selectedKey="{/selectedTags}"
items="{path: '/tags'}"
change="onTagsChange"
>
<core:Item key="{tagId}" text="{tagName}" />

</Select>
<Label text="活動時間" required="true" />
<!-- 運用 HBox 將日期與時間選擇器並排 -->
<HBox alignItems="Center">
<DatePicker
placeholder="Pick a date" valueFormat="yyyy-MM-dd"
displayFormat="yyyy/MM/dd"
change="onDateChange"
/>
<Text text="-" class="sapUiTinyMarginEnd" />
<TimePicker placeholder="Pick a time" />
</HBox>
<!-- 即時送達 -->
<Label text="即時送達" />
<Switch state="false" type="AcceptReject" />

<!-- 活動地點 -->
<Label text="活動地點" required="true" />
<SegmentedButton selectedKey="home">
<items>
<SegmentedButtonItem key="home" text="Home" />
<SegmentedButtonItem key="company" text="Company" />
<SegmentedButtonItem key="school" text="School" />
</items>
</SegmentedButton>
<!-- 活動類型 -->
<Label text="活動類型" required="true" />
<VBox>
<HBox>
<CheckBox text="公司" class="sapUiLargeMarginEnd" />
<CheckBox text="車站" />
</HBox>
<HBox>
<CheckBox text="辦公室" class="sapUiLargeMarginEnd" />
<CheckBox text="捷運" />
</HBox>

<!-- 資源 -->
<Label text="資源" required="true" />
<RadioButtonGroup columns="2">
<buttons>
<RadioButton text="Sponsorship" />
<RadioButton text="Venue" />
</buttons>
</RadioButtonGroup>
<!-- Activity form -->
<Label text="Activity form" required="true" />
<TextArea rows="4" growing="true" width="100%" />
</VBox>
<Button
text="送出資料"
press="onButtonClick"
type="Emphasized" />
</VBox>
</f:content>
</f:SimpleForm>
</content>
</Page>
</App>
</mvc:View>

Main.controller.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"sap/m/MessageBox",
"sap/m/MessageToast",
"sap/m/Dialog",
"sap/m/Button",
"sap/ui/layout/form/SimpleForm",
"sap/m/Label",
"sap/m/Input",
], function (Controller, JSONModel, MessageBox, MessageToast, Dialog, Button, SimpleForm, Label, Input)
{
"use strict";

return Controller.extend("my.app.controller.Main", {
// 初始化
onInit: function ()
{
var oDataSource = this.getOwnerComponent().getMetadata().getManifestEntry("sap.app").dataSources.orderApi;
this._sBaseUrl = oDataSource.url;
console.log('this._sBaseUrl', this._sBaseUrl)
this.onReadData()
// 1. 準備下拉選單的資料來源
var oData = {
// Select 的資料
zones: [
{ key: "Z1", text: "台北總部" },
{ key: "Z2", text: "台中分部" },
{ key: "Z3", text: "高雄辦事處" }
],
// 預設選中台北
selectedZone: "Z1",

// ComboBox 的資料
owners: [
{ id: "EMP001", name: "王大同" },
{ id: "EMP002", name: "李小美" },
{ id: "EMP003", name: "張三豐" }
],
selectedOwner: "",

// MultiComboBox 的資料
tags: [
{ tagId: "T1", tagName: "科技" },
{ tagId: "T2", tagName: "金融" },
{ tagId: "T3", tagName: "教育" }
],
selectedTags: ["T1"] // 預設多選勾選科技與金融
};

// 2. 建立 JSON 模型並設置到 View 上
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
},

onReadData: function ()
{
jQuery.ajax({
url: this._sBaseUrl ,
method: "GET",
dataType: "json",
success: function (aData) {
console.log('aData',aData)
// 將拿到的陣列塞入 JSONModel
this._oQuizModel.setProperty("", aData);
oTable.setBusy(false);
MessageToast.show("題庫加載完畢!");
}.bind(this),
error: function (oError) {
oTable.setBusy(false);
MessageBox.error("加載失敗,原因:" + oError.responseText);
}
});
},
// 下拉選單數值改變時的事件監聽
onZoneChange: function (oEvent)
{
console.log('onZoneChange',oEvent)
var oSelectedItem = oEvent.getParameter("selectedItem");
var sSelectedKey = oSelectedItem.getKey();
var sSelectedText = oSelectedItem.getText();

console.log("使用者選擇了 Key: " + sSelectedKey + " , Text: " + sSelectedText);
},
onTagsChange: function (oEvent)
{
console.log('onTagsChange',oEvent)
var oSelectedItem = oEvent.getParameter("selectedItem");
var sSelectedKey = oSelectedItem.getKey();
var sSelectedText = oSelectedItem.getText();
console.log("使用者選擇了 Key: " + sSelectedKey + " , Text: " + sSelectedText);
},
onDateChange: function (oEvent) {
// === 方法 A:取得格式化後的字串(例如 "2026-07-13")===
// 這是最常用的做法,可以直接拿來傳給後端 API
var sValue = oEvent.getParameter("value");
var bValid = oEvent.getParameter("valid"); // 檢查使用者輸入的日期是否合法

if (!bValid) {
MessageToast.show("請輸入正確的日期格式!");
return;
}

// === 方法 B:取得原生 JS Date 物件 ===
// 如果你需要拿這個日期去做計算(例如計算天數、判斷是否大於今天)
var oDatePicker = oEvent.getSource();
var oDateObject = oDatePicker.getDateValue(); // 得到標準的 Date 物件

// 測試輸出
console.log("字串格式 (value):", sValue);
console.log("JS 物件 (dateValue):", oDateObject);

MessageToast.show("您選擇了:" + sValue);
},
onCreatePress: function () {
MessageToast.show("表單已成功創建!");
},

onResetPress: function () {
MessageToast.show("表單已重置。");
// 在此處加入清除各控制項欄位值的邏輯
}
})
})

使用node 來啟動

package.json 套件與指令設定
scripts 使用ui5 serve –port 8080 -o index.html

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "sapui5-google-sheets-crud-app",
"version": "1.0.0",
"description": "SAPUI5 1.71 Google Sheets App with UI5 Tooling Local Proxy",
"scripts": {
"start": "ui5 serve --port 8080 -o index.html"
},
"devDependencies": {
"@ui5/cli": "^3.0.0",
"ui5-middleware-simpleproxy": "^3.1.2"
}
}

npm 安裝會產生一個node_modules資料夾

1
npm install 

啟動

1
node run start

SAP UI5 表單

主選單 BaseController.js

controller/BaseController.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function (Controller, History) {
"use strict";

return Controller.extend("my.navigation.app.controller.BaseController", {
// 封裝獲取 Router 的方法
getRouter: function () {
return this.getOwnerComponent().getRouter();
},

// 全域導頁按鈕事件
onNavToHome: function () { this.getRouter().navTo("RouteHome"); },

onNavToProduct: function () { this.getRouter().navTo("RouteProduct"); },

onNavToForms: function () { this.getRouter().navTo("RouteForms"); },

onNavToContact: function () { this.getRouter().navTo("RouteContact"); }
});
});

Label
在 SAP UI5 中,sap.m.Label 是一種專門用來顯示「文字標籤」的 UI 控制項(Control)。它最常見的用途是放在輸入框(如 sap.m.Input)、下拉選單或核取方塊的前面或上方,用來提示使用者該欄位需要填寫或選擇什麼內容。
  • 核心特性與重要屬性
  • text 屬性:用來設定標籤要顯示的文字。通常會透過資料綁定 (Data Binding) 連接到 i18n 國際化語系檔案,以支援多國語言。
  • labelFor 屬性: 這是 Label 最關鍵的關聯屬性。你需要將它設定為對應輸入控制項的 id(例如:labelFor="myInputId")。這樣做有兩大好處:
    • 無障礙螢幕閱讀器支援 (Accessibility):讓盲人或視障使用者使用的螢幕閱讀器能正確讀出該輸入框的含義。
    • 點擊導向:使用者用滑鼠點擊該 Label 文字時,游標會自動聚焦(Focus)到對應的輸入框中。
  • required 屬性:當設為 true 時,標籤文字的前面或後面會自動出現一個紅色的星號(*),用來視覺化提示使用者該欄位為「必填欄位」。
  • 樣式調整::支援透過屬性微調設計,例如設定文字加粗(design="Bold")或文字對齊方式(textAlign)。
程式碼範例 (XML View)在 SAP UI5 的檢視畫面(View)中,通常會結合佈局控制項(如 Form)一起使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:f="sap.ui.layout.form">
<f:SimpleForm editable="true">
<f:content>
<!-- 宣告 Label,並使用 labelFor 綁定到下方的 Input 控制項 -->
<Label text="使用者名稱" required="true" labelFor="usernameInput" />
<Input id="usernameInput" placeholder="請輸入您的姓名" />

<Label text="電子郵件" labelFor="emailInput" />
<Input id="emailInput" type="Email" />
</f:content>
</f:SimpleForm>
</mvc:View>

controller/Forms.controller.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/** 引入相關元件BaseController 主選單,
* MessageToast:內建提示工具:在使用 MessageToast 之前,必須先在 Controller 的最上方(AMD 載入模組區)將 sap/m/MessageToast 引入。=>如果主選單已經引入就不需要重複引入
*/
sap.ui.define([
"my/navigation/app/controller/BaseController",
"sap/m/MessageToast",
"sap/ui/model/json/JSONModel",
"sap/m/MessageBox",
"sap/m/Dialog",
"sap/m/Button",
"sap/ui/layout/form/SimpleForm",
"sap/m/Label",
"sap/m/Input",
], function (BaseController, MessageToast,JSONModel,MessageBox,Dialog, Button, SimpleForm, Label, Input) {
"use strict";

return BaseController.extend("my.navigation.app.controller.Forms", {
onInit: function ()
{
var oDataSource = this.getOwnerComponent().getMetadata().getManifestEntry("sap.app").dataSources.orderApi;
this._sBaseUrl = oDataSource.url;
console.log('this._sBaseUrl', this._sBaseUrl)
this.onReadData()

// 1. 準備下拉選單的資料來源
var oData = {
// Select 的資料
zones: [
{ key: "Z1", text: "台北總部" },
{ key: "Z2", text: "台中分部" },
{ key: "Z3", text: "高雄辦事處" }
],
// 預設選中台北
selectedZone: "Z1",

// ComboBox 的資料
owners: [
{ id: "EMP001", name: "王大同" },
{ id: "EMP002", name: "李小美" },
{ id: "EMP003", name: "張三豐" }
],
selectedOwner: "",

// MultiComboBox 的資料
tags: [
{ tagId: "T1", tagName: "科技" },
{ tagId: "T2", tagName: "金融" },
{ tagId: "T3", tagName: "教育" }
],
selectedTags: ["T1"] // 預設多選勾選科技與金融
};

// 2. 建立 JSON 模型並設置到 View 上
var oModel = new JSONModel(oData);
this.getView().setModel(oModel);
},
// 讀取
onReadData: function () {
jQuery.ajax({
url: this._sBaseUrl ,
method: "GET",
dataType: "json",
success: function (aData) {
console.log('aData',aData)
// 將拿到的陣列塞入 JSONModel
this._oQuizModel.setProperty("", aData);
oTable.setBusy(false);
MessageToast.show("題庫加載完畢!");
}.bind(this),
error: function (oError) {
oTable.setBusy(false);
MessageBox.error("加載失敗,原因:" + oError.responseText);
}
});
},
// 下拉選單數值改變時的事件監聽
onZoneChange: function (oEvent)
{
console.log('onZoneChange',oEvent)
var oSelectedItem = oEvent.getParameter("selectedItem");
var sSelectedKey = oSelectedItem.getKey();
var sSelectedText = oSelectedItem.getText();

console.log("使用者選擇了 Key: " + sSelectedKey + " , Text: " + sSelectedText);
},
onTagsChange: function (oEvent)
{
console.log('onTagsChange',oEvent)
var oSelectedItem = oEvent.getParameter("selectedItem");
var sSelectedKey = oSelectedItem.getKey();
var sSelectedText = oSelectedItem.getText();
console.log("使用者選擇了 Key: " + sSelectedKey + " , Text: " + sSelectedText);
},
onDateChange: function (oEvent) {
// === 方法 A:取得格式化後的字串(例如 "2026-07-13")===
// 這是最常用的做法,可以直接拿來傳給後端 API
var sValue = oEvent.getParameter("value");
var bValid = oEvent.getParameter("valid"); // 檢查使用者輸入的日期是否合法

if (!bValid) {
MessageToast.show("請輸入正確的日期格式!");
return;
}

// === 方法 B:取得原生 JS Date 物件 ===
// 如果你需要拿這個日期去做計算(例如計算天數、判斷是否大於今天)
var oDatePicker = oEvent.getSource();
var oDateObject = oDatePicker.getDateValue(); // 得到標準的 Date 物件

// 測試輸出
console.log("字串格式 (value):", sValue);
console.log("JS 物件 (dateValue):", oDateObject);

MessageToast.show("您選擇了:" + sValue);
},
onCreatePress: function () {
MessageToast.show("表單已成功創建!");
},

onResetPress: function () {
MessageToast.show("表單已重置。");
// 在此處加入清除各控制項欄位值的邏輯
},
onSubmit: function () {
MessageToast.show("表單已成功送出,我們將盡快與您聯絡!");
}
});
});

view/Forms.view.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<mvc:View
controllerName="my.navigation.app.controller.Forms"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m"
xmlns:f="sap.ui.layout.form"
xmlns:core="sap.ui.core">
<Page showHeader="true" title="聯絡我們">
<OverflowToolbar width="100%" class="sapUiContentPadding">
<Button text="首頁" press="onNavToHome" />
<Button text="產品介紹" press="onNavToProduct" />
<Button text="表單" press="onNavToForms" />
<Button text="聯絡我們" type="Emphasized" press="onNavToContact" />
</OverflowToolbar>
<content>
<VBox class="sapUiMediumMargin">
<Title text="表單" titleStyle="H1" class="sapUiSmallMarginBottom" />
<f:SimpleForm
editable="true"
layout="ResponsiveGridLayout"
labelSpanXL="3"
labelSpanL="3"
labelSpanM="3"
labelSpanS="12"
adjustLabelSpan="false"
emptySpanXL="4"
emptySpanL="4"
emptySpanM="4"
emptySpanS="0"
columnsXL="1"
columnsL="1"
columnsM="1"
singleContainerFullWidth="true">
<f:content>
<VBox>
<Label text="產品名稱" required="true" />
<Input
placeholder="請輸入產品名稱..."
/>

<Label text="活動區域" required="true" />
<Select
id="zoneSelect"
selectedKey="{/selectedZone}"
items="{path: '/zones'}"
change="onZoneChange"
>
<items>
<!-- key 是傳給後台的值,text 是顯示給使用者看的文字 -->
<core:Item key="{key}" text="{text}" />
</items>
</Select>

<!-- 職業 -->
<Label text="職業" required="true" />
<Select placeholder="請選擇職業"
selectedKey="{/selectedTags}"
items="{path: '/tags'}"
change="onTagsChange"
>
<core:Item key="{tagId}" text="{tagName}" />

</Select>
<Label text="活動時間" required="true" />
<!-- 運用 HBox 將日期與時間選擇器並排 -->
<HBox alignItems="Center">
<DatePicker
placeholder="Pick a date" valueFormat="yyyy-MM-dd"
displayFormat="yyyy/MM/dd"
change="onDateChange"
/>
<Text text="-" class="sapUiTinyMarginEnd" />
<TimePicker placeholder="Pick a time" />
</HBox>
<!-- 即時送達 -->
<Label text="即時送達" />
<Switch state="false" type="AcceptReject" />

<!-- 活動地點 -->
<Label text="活動地點" required="true" />
<SegmentedButton selectedKey="home">
<items>
<SegmentedButtonItem key="home" text="Home" />
<SegmentedButtonItem key="company" text="Company" />
<SegmentedButtonItem key="school" text="School" />
</items>
</SegmentedButton>
<!-- 活動類型 -->
<Label text="活動類型" required="true" />
<VBox>
<HBox>
<CheckBox text="公司" class="sapUiLargeMarginEnd" />
<CheckBox text="車站" />
</HBox>
<HBox>
<CheckBox text="辦公室" class="sapUiLargeMarginEnd" />
<CheckBox text="捷運" />
</HBox>

<!-- 資源 -->
<Label text="資源" required="true" />
<RadioButtonGroup columns="2">
<buttons>
<RadioButton text="Sponsorship" />
<RadioButton text="Venue" />
</buttons>
</RadioButtonGroup>
<!-- Activity form -->
<Label text="Activity form" required="true" />
<TextArea rows="4" growing="true" width="100%" />
</VBox>
<Button
text="送出資料"
press="onButtonClick"
type="Emphasized" />
</VBox>
</f:content>
</f:SimpleForm>
</VBox>
</content>
</Page>
</mvc:View>

SAP UI5 元件工具

相關內建工具

MessageToast
在使用 MessageToast 之前,必須先在 Controller 的最上方(AMD 載入模組區)將 sap/m/MessageToast 引入。

註解:

  1. 直接呼叫 show 方法
  2. 進階參數設定(客製化提示行為):MessageToast.show 除了可以傳入純文字(String)外,還可以傳入一個設定物件(Options Object),讓您可以微調它的行為。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    sap.ui.define([
    "sap/ui/core/mvc/Controller",
    "sap/m/MessageToast" // 1. 先在此引入模組
    ], function (Controller, MessageToast) { // 2. 傳入對應的變數
    "use strict";

    return Controller.extend("insurance.policymanagement.controller.Main", {

    onSavePress: function () {
    // 1. 直接呼叫 show 方法
    MessageToast.show("保單資料已成功儲存!");

    // 2. 進階參數設定(客製化提示行為)
    MessageToast.show("處理中,請稍候...", {
    duration: 5000, // 提示框顯示的時間(毫秒),預設是 3000 (3秒)
    width: "25em", // 提示框的寬度,預設是 15em
    my: "center center", // 決定提示框的哪裡(這裏是中心)...
    at: "center center", // ...要對齊到螢幕的哪裡(這裏是螢幕正中央)
    autoClose: true, // 預設為 true,時間到自動消失
    onClose: function() { // 當提示框消失時,要觸發的寫法(Callback 函式)
    console.log("提示框已消失,可以進行下一步。");
    }
    });

    }

    });
    });

    MessageBox
    在 SAP UI5 中,sap.m.MessageBox 是一個非常核心且強大的強制對話框(Modal Dialog)工具。與前面介紹、會自動消失的 MessageToast 不同,MessageBox 彈出時會產生一層灰色遮罩,強制中斷使用者的操作。使用者必須親自點擊對話框上的按鈕(如「確認」、「取消」或「關閉」),畫面才會解鎖。它專門用於顯示重要錯誤、警告通知,或是需要使用者確認的關鍵情境(如刪除資料)。以下為您詳細拆解它的基本語法、五大快捷類型、實戰確認確認框(Confirm)以及進階技巧:
  3. 快捷類型與基本用法
    使用前,同樣必須先在 Controller 最上方引入 sap/m/MessageBox。框架提供了 5 種最常見的快捷方法,會自動帶入對應的圖示(Icon)與語氣:
    ① 成功(Success)
    1
    MessageBox.success("保單合約已正式核准生效。");
    ② 錯誤(Error)
    用於系統出錯、API 失敗、或不可忽略的檢核失敗。
    1
    MessageBox.error("網路連線逾時,保單資料儲存失敗,請重新再試。");
    ③ 警告(Warning)
    1
    MessageBox.warning("您輸入的保額已接近上限,請確認是否正確。");
    ④ 資訊(Information)
    1
    MessageBox.information("系統將於今晚 22:00 進行例行性維護。");
    ⑤ 經典對話框(Show)
    1
    最原始、客製化彈性最高的方法(後面會詳細說明)。
    實戰最常用:做一個「確認刪除」的對話框(Confirm)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    MessageBox.confirm("您確定要刪除這筆保單資料嗎?(刪除後將無法復原)", {
    title: "系統確認", // 對話框標題
    actions: [MessageBox.Action.YES, MessageBox.Action.NO], // 顯示的按鈕
    emphasizedAction: MessageBox.Action.YES, // 強調(高亮)藍色按鈕
    onClose: function (sAction) { // 使用者點選按鈕後的動作
    // sAction 會拿到使用者點擊的字串,例如 "YES" 或 "NO"
    if (sAction === MessageBox.Action.YES) {
    // 執行真正的刪除 API
    this._deletePolicyData();
    } else {
    // 使用者按了取消/否
    MessageToast.show("已取消刪除操作。");
    }
    }.bind(this) // 注意:記得綁定 .bind(this),否則在 function 內會拿不到 Controller 的實體
    });

  4. 進階進階功能:細緻客製化(MessageBox.show)如果您需要更複雜的組合,例如改變標題、自訂按鈕文字、甚至加入特定的 CSS 樣式,可以使用最底層的 MessageBox.show:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    MessageBox.show("這是一則包含詳細說明的通知。", {
    icon: MessageBox.Icon.INFORMATION, // 自訂圖示 (ERROR, WARNING, SUCCESS等)
    title: "自訂標題",
    actions: [MessageBox.Action.OK, MessageBox.Action.CANCEL],
    id: "myCustomMessageBoxId", // 設定 ID 方便自動化測試 (OPA5)
    details: "這裡可以放超長的詳細錯誤訊息(例如後端回傳的 SQL 報錯或 JSON 堆疊),UI5 會自動把它做成可展開/收合的樣式。",
    contentWidth: "30rem" // 限制對話框寬度
    });

    總結
    • MessageToast 像「輕輕拍肩」,告訴使用者做完了(3 秒後消失,不干擾)。
    • MessageBox 像「拉住手臂」,強制使用者看完了才能走(必須點擊按鈕,有遮罩)。
Dialog
  • 新增/編輯資料:點擊「新增保單」時,彈出一個填寫欄位的表單視窗。
  • 進階搜尋/篩選:點擊「進階查詢」時,彈出一個包含多個條件組合的搜尋面板。
  • 清單選擇:點擊欄位旁的放大鏡,彈出一個客戶清單或保費費率表格供使用者勾選。
實戰最標準寫法:
搭配 Fragment 載入在 SAP UI5 的最佳實踐中,我們絕對不會用 JavaScript 程式碼去刻 Dialog 裡面的控制項。我們會把 Dialog 的外殼與內容寫在一個獨立的 XML Fragment 檔案中,並在 Controller 動態載入它。

步驟一:建立 XML Fragment (例如:view/fragment/CreatePolicyDialog.fragment.xml)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:f="sap.ui.layout.form">

<Dialog id="createPolicyDialog" title="新增保戶資料">
<!-- 內容區:您可以自由塞任何控制項 -->
<content>
<f:SimpleForm editable="true" layout="ResponsiveGridLayout">
<f:content>
<Label text="保戶姓名" required="true" />
<Input id="inputCustomerName" placeholder="請輸入姓名" />

<Label text="身分證字號" />
<Input id="inputCustomerId" placeholder="請輸入身分證" />
</f:content>
</f:SimpleForm>
</content>

<!-- 底部按鈕區 -->
<buttons>
<Button text="儲存" type="Emphasized" press="onSaveDialog" />
<Button text="取消" press="onCloseDialog" />
</buttons>
</Dialog>

</core:FragmentDefinition>

步驟二:在 Controller 中動態載入與開啟 (SAP UI5 新版非同步標準寫法)在 Controller 中,我們必須在使用者點擊按鈕時去載入這個 Fragment,並將它附加(addDependent)到當前的 View 上,這樣彈窗才能共享 View 的 Data Model(資料模型)與 i18n 語系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/Fragment"
], function (Controller, Fragment) {
"use strict";

return Controller.extend("insurance.policymanagement.controller.Main", {

// 使用者點擊畫面的「新增」按鈕
onOpenDialogPress: function () {
var oView = this.getView();

// 檢查是否已經建立過這個 Dialog 實體,避免重複載入
if (!this._pDialog) {
// 使用 Fragment.load 非同步載入 XML
const query = {
id: oView.getId(), // 確保 Fragment 內的元件 ID 會加上 View 的 ID 作為前綴
name: "my.navigation.app.view.fragment.CreatePolicyDialog", // 這裡填寫剛才建立的 Fragment 路徑 (配合 resource-roots)
controller: this // 關鍵:將此 Controller 綁定給 Fragment,這樣彈窗內的按鈕事件才能觸發這裡的方法
}
this._pDialog = Fragment.load(query).then(function (oDialog) {
// 關鍵:將 Dialog 掛載到 View 上,使其能共享 Model、i18n 與生命週期
oView.addDependent(oDialog);
return oDialog;
});
}

// 開啟 Dialog
this._pDialog.then(function (oDialog) {
oDialog.open();
});
},

// 點擊彈窗內「取消」按鈕
onCloseDialog: function () {
// 利用 id 找到 Dialog 並關閉它(它還會留在記憶體中,下次開啟很快)
this.byId("createPolicyDialog").close();
},

// 點擊彈窗內「儲存」按鈕
onSaveDialog: function () {
var sName = this.byId("inputCustomerName").getValue();
if (!sName) {
sap.m.MessageBox.error("保戶姓名為必填欄位!");
return;
}
// 執行後續儲存邏輯...
this.onCloseDialog(); // 儲存成功後關閉
}

});
});

Fragment 路徑 (配合 resource-roots) 從哪裡看?
要找出 Fragment 的正確引用路徑,您必須結合 index.html(或初始化設定)中的 data-sap-ui-resource-roots 與專案的實體資料夾結構。其核心公式為:[Resource Roots 的 Key] . [資料夾名稱] . [Fragment 檔名] (注意:不加 .fragment.xml 後綴)。 1. 第一步:去 index.html 找出地圖根路徑(Key): 打開您專案的入口網頁(通常是 index.html),尋找 sap-ui-bootstrap 段落。假設您看到以下設定:
1
2
3
4
5
6
<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-resource-roots='{
"my.navigation.app": "./"
}'>
</script>
這裡得到的線索:my.navigation.app 這個字串,就代表專案的根目錄(./,也就是 webapp 資料夾)。
2. 第二步:對照您的專案資料夾結構(目錄樹) webapp (根目錄,對應上方的 "./")
1
2
3
4
5
6
7
8
┣ controller
┃ ┗ Main.controller.js
┣ view
┃ ┣ fragment
┃ ┃ ┗ CreatePolicyDialog.fragment.xml <-- 假設這是您剛建好的彈窗檔案
┃ ┗ Main.view.xml
┣ index.html
┗ manifest.json
  • 核心技術重點複習(必記)
  • 為什麼不直接寫在 View.xml 裡面?
    如果把 Dialog 直接寫在 Main.view.xml 裡,網頁一打開時,即使使用者沒點按鈕,瀏覽器也會順便渲染 Dialog 的 DOM,會拖慢首頁載入速度。用 Fragment 拆開,可以達到「用到時才載入(Lazy Loading)」的效能優化。
  • oView.addDependent(oDialog) 的作用是什麼?
    Dialog 在網頁結構中是跳出在最外層的( 層級),不屬於任何 View。加上這行後,Dialog 就變成了 View 的「小跟班」,能自動繼承主頁面的所有資料模型(Models)。
  • this.byId("dialogInput") 的陷阱
    在 Fragment.load 時有設定 id: oView.getId(),所以在 Controller 中不論是主畫面還是彈窗內的控制項,都可以統一用 this.byId("元件ID") 輕鬆抓到!
Button
在 SAPUI5 框架中,sap.m.Button 是最基礎且頻繁使用的控制項(Control)之一,主要用於讓使用者透過點擊、輕觸或鍵盤按鍵來觸發特定的商務邏輯或操作。 它符合 SAP Fiori 設計規範,內建了多種視覺樣式與狀態設定。
核心屬性 (Properties)
  • 在定義一個 Button 時,你可以透過以下常用屬性來調整它的外觀與行為:
  • text:按鈕上顯示的文字字串。
  • icon:按鈕內嵌的圖示,通常使用 SAP 圖示庫(例如 sap-icon://save)。
  • enabled:控制按鈕是否可供點擊(預設為 true,設為 false 則變成灰色不可用狀態)。
  • type:按鈕的語意類型(例如 Default、Accept 綠色成功、Reject 紅色拒絕、Emphasized 藍色強調)。
  • width:按鈕的寬度,可使用 CSS 單位(例如 100px 或 auto)。
核心事件 (Events)
press:這是按鈕最關鍵的事件,當使用者點擊、輕觸或在聚焦時按下 Enter/Space 鍵,就會觸發綁定的 JavaScript 函式。
代碼範例
1
2
3
4
5
6
7
8
9
10
11
12
13
<mvc:View 
controllerName="myApp.controller.Main"
xmlns:mvc="sap.ui.core.mvc"
xmlns="sap.m">

<Button
text="儲存資料"
icon="sap-icon://save"
type="Emphasized"
press="onSavePress" />

</mvc:View>

控制器 (Controller)

1
2
3
4
5
6
7
8
9
10
11
12
13
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function (Controller, MessageToast) {
"use block";

return Controller.extend("myApp.controller.Main", {
onSavePress: function (oEvent) {
// 當按鈕被點擊時執行的商務邏輯
MessageToast.show("資料已成功儲存!");
}
});
});
Label
在 SAP UI5 中,sap.m.Label 是一種專門用來顯示「文字標籤」的 UI 控制項(Control)。它最常見的用途是放在輸入框(如 sap.m.Input)、下拉選單或核取方塊的前面或上方,用來提示使用者該欄位需要填寫或選擇什麼內容。
  • 核心特性與重要屬性
  • text 屬性:用來設定標籤要顯示的文字。通常會透過資料綁定 (Data Binding) 連接到 i18n 國際化語系檔案,以支援多國語言。
  • labelFor 屬性: 這是 Label 最關鍵的關聯屬性。你需要將它設定為對應輸入控制項的 id(例如:labelFor="myInputId")。這樣做有兩大好處:
    • 無障礙螢幕閱讀器支援 (Accessibility):讓盲人或視障使用者使用的螢幕閱讀器能正確讀出該輸入框的含義。
    • 點擊導向:使用者用滑鼠點擊該 Label 文字時,游標會自動聚焦(Focus)到對應的輸入框中。
  • required 屬性:當設為 true 時,標籤文字的前面或後面會自動出現一個紅色的星號(*),用來視覺化提示使用者該欄位為「必填欄位」。
  • 樣式調整::支援透過屬性微調設計,例如設定文字加粗(design="Bold")或文字對齊方式(textAlign)。
程式碼範例 (XML View)在 SAP UI5 的檢視畫面(View)中,通常會結合佈局控制項(如 Form)一起使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" xmlns:f="sap.ui.layout.form">
<f:SimpleForm editable="true">
<f:content>
<!-- 宣告 Label,並使用 labelFor 綁定到下方的 Input 控制項 -->
<Label text="使用者名稱" required="true" labelFor="usernameInput" />
<Input id="usernameInput" placeholder="請輸入您的姓名" />

<Label text="電子郵件" labelFor="emailInput" />
<Input id="emailInput" type="Email" />
</f:content>
</f:SimpleForm>
</mvc:View>

input
sap.m.Input 是 SAP UI5 框架中最核心的文字輸入元件
  • 基礎屬性:
    • value:控制或綁定輸入欄位的文本內容。
    • placeholder:當欄位為空時顯示的提示文字。
    • type::定義輸入類型,例如 Text(文字)、Number(數字)、Password(密碼)、Email 或 Tel(電話)。
  • 輸入建議 (Suggestions):
    • 透過設定 showSuggestion="true",當使用者打字時,元件會自動下拉匹配 JSON 模型或 OData 中的數據清單。
    • 值說明 (Value Help / F4 Help):
      設定 showValueHelp="true" 後,欄位右側會出現一個放大鏡或正方形圖示。
      當使用者點擊時,會觸發 valueHelpRequest 事件,通常用於彈出一個對話視窗(Select Dialog)供使用者搜尋並選擇複雜的資料(例如:客戶編號、工廠代碼)。
    • 值狀態 (Value State): 支援 valueState 屬性(如 Error、Warning、Success、Information、None),常與 valueStateText 搭配,在表單驗證失敗時向使用者顯示紅框與錯誤訊息。
    常見事件 (Events)
    • 開發者通常會監聽以下事件來控制商業邏輯:
    • liveChange:當使用者每輸入或刪除一個字元時即時觸發,常用於即時字數檢查或動態過濾。
    • change:當欄位失去焦點(Blur)且內容已被改變時觸發,最常用於欄位格式驗證。
    • submit:當使用者在欄位內按下 Enter 鍵時觸發,適合用來執行表單提交或搜尋。
    • suggestionItemSelected:當使用者從下拉的輸入建議清單中選中某個項目時觸發。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <mvc:View 
    controllerName="my.app.controller.Main"
    xmlns:mvc="sap.ui.core.mvc"
    xmlns="sap.m"
    xmlns:core="sap.ui.core">

    <VBox class="sapUiSmallMargin">
    <Label text="產品編號輸入 (請試著打 'A'、按 Enter 或點擊放大鏡)" labelFor="myInput" />

    <Input
    id="myInput"
    placeholder="請輸入產品代碼..."
    showSuggestion="true"
    showValueHelp="true"
    liveChange="onLiveChange"
    change="onChange"
    submit="onSubmit"
    suggestionItemSelected="onSuggestionItemSelected"
    valueHelpRequest="onValueHelpRequest">

    <!-- 設定輸入建議的下拉選單資料 -->
    <suggestionItems>
    <core:Item key="A001" text="Apple iPhone" />
    <core:Item key="A002" text="Asus Laptop" />
    <core:Item key="B001" text="BMW Car" />
    </suggestionItems>
    </Input>
    </VBox>

    </mvc:View>

    MultiInput :多選表單
    • 主畫面 MultiInput 控制項
    • showValueHelp="true": 顯示右側開窗圖示
    • valueHelpOnly="true": 唯讀開窗模式。禁止使用者手動鍵盤輸入,限制只能透- 過點擊圖示或整條輸入框來觸發彈窗,確保資料來源的一致性[1]。
    • tokenChange: 當輸入框裡面的『標籤(Token)』發生變動時,立刻發出通知。」
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //xml 
    <VBox class="sapUiMediumMargin">
    <Label text="主約險種" labelFor="mainInsuranceInput" class="sapUiTinyMarginBottom" />
    <MultiInput
    id="mainInsuranceInput"
    width="100%"
    showValueHelp="true"
    valueHelpOnly="true"
    valueHelpRequest="openMainInsuranceDialog"
    tokenChange="monitorMainInsuranceSelectDialog"
    />
    </VBox>

    controller js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    // 檔案:controller/DialogInput.controller.js
    sap.ui.define([
    "my/navigation/app/controller/BaseController",
    "sap/ui/core/Fragment",
    "sap/ui/model/json/JSONModel",
    "sap/m/Token",
    "sap/ui/model/Filter",
    "sap/ui/model/FilterOperator"
    ], function (BaseController,Fragment,JSONModel,Token,Filter,FilterOperator) {
    "use strict";

    return BaseController.extend("my.navigation.app.controller.DialogInput", {
    // 初始化
    onInit: function () {
    // 模擬後端獲取的險種陣列數據
    var oData = {
    mainInsurances: [
    { planCode: "CAB", planName: "南山人壽護您久久終身防癌健康保險" },
    { planCode: "4ISWL", planName: "南山人壽威利久久利率變動型增額終身壽險" },
    { planCode: "20SLTC", planName: "南山人壽全方位長期照顧終身保險" },
    { planCode: "10STCB", planName: "南山人壽優樂康護短期照顧終身健康保險" },
    ],
    };
    this.getView().setModel(new JSONModel(oData));
    },
    // 打開主要險種跳出框
    openMainInsuranceDialog: function (oEvent)
    {
    var oView = this.getView();
    console.log('this 打開主約險種',)
    // 防呆:避免重複加載 Fragment
    if (!this._oMainInsuranceDialog) {
    const payload = {
    id: oView.getId(),
    // ⚠️ 請改為您的專案實際路徑
    name: "my.navigation.app.view.fragment.MainInsuranceDialog",
    controller: this
    }

    Fragment.load(payload).then(function (oDialog) {
    this._oMainInsuranceDialog = oDialog;
    oView.addDependent(this._oMainInsuranceDialog);
    this._oMainInsuranceDialog.open();
    }.bind(this));
    } else {
    this._oMainInsuranceDialog.open();
    }
    },
    // 搜尋過濾主要選種
    filterMainInsuranceSelectDialog: function (oEvent)
    {
    var sValue = oEvent.getParameter("value");
    var oList = this.byId("idMainSelectDialog-list");
    console.log('oList',oList)
    var oBinding = oList.getBinding("items");
    if (!oBinding) {
    return;
    }
    if (sValue) {
    // 針對 code 欄位的過濾器
    var oFilterCode = new sap.ui.model.Filter("planCode", sap.ui.model.FilterOperator.Contains, sValue);
    // 針對 name 欄位的過濾器
    var oFilterName = new sap.ui.model.Filter("planName", sap.ui.model.FilterOperator.Contains, sValue);

    // 關鍵點:將 bAnd 設為 false,代表「OR (或)」的邏輯關係
    var oCombinedFilter = new sap.ui.model.Filter({
    filters: [oFilterCode, oFilterName],
    and: false
    });

    // 4. 執行過濾
    oBinding.filter([oCombinedFilter]);
    } else {
    oBinding.filter([]);
    }
    },

    // 確定主要選種選擇項目 => 點擊「確定」傳回數據並關閉視窗
    confirmMainInsuranceSelect: function (oEvent) {
    // 獲取附約險種表單id
    var oMultiInput = this.byId("mainInsuranceInput");
    var aSelectedContexts = oEvent.getParameter("selectedContexts");

    // 先清空原本輸入框裡的舊標籤
    oMultiInput.removeAllTokens();

    if (aSelectedContexts && aSelectedContexts.length > 0) {
    var aFinalSelectedArray = [];

    // 遍歷使用者在畫面上勾選的所有項目
    aSelectedContexts.forEach(function (oContext) {
    var oObject = oContext.getObject();

    // 1. 將數據收集到陣列中(以便您未來傳給後端 API)
    aFinalSelectedArray.push(oObject);

    // 2. 即時建立 Token 標籤並塞回主畫面的 MultiInput 顯示
    oMultiInput.addToken(new Token({
    key: oObject.planCode, // 標籤的內部數值 (Key)
    text: oObject.planName // 標籤顯示的文字 (Text)
    }));
    });

    // 💡 這就是您最終拿到的純資料陣列
    console.log("最終選取的數據陣列:", aFinalSelectedArray);
    }

    // 關閉時自動清除搜尋過濾條件,確保下次打開是完整的
    oEvent.getSource().getBinding("items").filter([]);
    },
    cancelMainInsuranceSelect: function (oEvent)
    {
    oEvent.getSource().getBinding("items").filter([]);
    },
    });
    });