UI 元件
@synopackageland/ui 是遵循 DSM 7 樣式的 Vue 3 元件庫。預設的 native-go 專案就是使用它的 Vue App:側邊欄外殼、狀態頁,以及你啟用的各項能力的頁面。外殼與能力頁面來自 SDK 的 @synopackageland/sdk/vue。你也可以用純 JavaScript 撰寫視窗。
synopkgland init -t native-go --ui plain my-plain-app預設的 --ui vue 專案不需要額外旗標。它的 frontend/ 資料夾固定 @synopackageland/ui 0.9.10 與 @synopackageland/sdk 0.10.12 的精確版本。先執行一次 npm install,並提交它寫出的 package-lock.json;之後 npm ci 會安裝與專案建置時完全相同的內容。NAS 不會下載 npm 套件:建置時已把所有內容打包。
| Vue(預設) | Plain(--ui plain) | |
|---|---|---|
| 原始碼 | frontend/src/(Vue 3、Vite、TypeScript) | ui/app.js、ui/app.css |
| 建置 | 在 frontend/ 執行 npm run build,由 synopkgland build 啟動 | 無 |
| 檔案、jobs、delegate 畫面 | 包含 | 不包含 |
獨立登入(--with login) | 支援 | 被 check 與 init 拒絕 |
| 元件 | @synopackageland/ui、@synopackageland/sdk/vue | 自行撰寫 |
第一次建置 Vue 專案之前,請在 frontend/ 執行 npm install。build 不會安裝套件。
要在其他 Vue 專案安裝元件庫:
npm install --save-exact @synopackageland/ui@0.9.10 --registry=https://registry.npmjs.org若 UI 是用其他工具建置,請在 dsm-package.yaml 的 ui 指向建置輸出,並以清單形式提供建置命令。建置必須產生 app.js 與 app.css。
ui: dir: web/dist build: cwd: web command: [npm, run, build]app.js 以 window.DsmPackageApps[<package>].mount(root, ctx) 註冊視窗。產生的 plain UI 示範了如何從 document.currentScript 讀取套件名稱。
在 DSM 視窗內掛載
Section titled “在 DSM 視窗內掛載”每個 DSM 視窗都與桌面共用同一個頁面。請匯入內嵌佈景主題而不是 theme.css,以 AppRoot 包住 App,並為每個掛載的 App 設定唯一的 idPrefix。
import { createApp } from 'vue'import App from './App.vue'import '@synopackageland/ui/themes/dsm7-embedded.css'
export function mount(root: HTMLElement, ctx: unknown) { const app = createApp(App, { ctx }) app.config.idPrefix = `my-package-${crypto.getRandomValues(new Uint32Array(4)).join('-')}` app.mount(root) return { unmount: () => app.unmount() }}產生的 frontend/src/main.ts 也會為 DSM 視窗載入器註冊 mount。編輯時請保留該部分。
<script setup lang="ts">import { AppRoot, ConfigProvider } from '@synopackageland/ui'</script>
<template> <AppRoot modal-scope="app" style="height: 100%"> <ConfigProvider locale="en"> <!-- your pages --> </ConfigProvider> </AppRoot></template>AppRoot 會把佈景主題限定在你的 App 範圍內,並把對話框、選單與通知導向它自己內部,使它們留在視窗內。使用 modal-scope="app"(預設)時,對話框只會擋住你的 App。請為根元素設定有界的大小,例如 height: 100%。整頁的 Web 或 Portal App 可以使用 modal-scope="document",但不要在 DSM 視窗內使用。也不要在視窗內匯入 theme.css。
Application Portal 全頁啟動時,請把 ctx 傳給 AppShell。DSM 可見的 banner 顯示 App 名稱時,shell 會隱藏重複的品牌區;DSM 帳號選單可見時,會隱藏預設帳號區。自訂 sidebar-footer 會保留。banner 隱藏、缺少或無法辨識時,仍顯示 shell 自己的控制項。偵測只讀取所屬 DSM standalone 視窗的 DOM,不需要管理員權限。
<script setup lang="ts">import { AppLayout, AppSidebar, PageContent, PageHeader } from '@synopackageland/ui'</script>
<template> <AppLayout min-height="0" wide-layout="window"> <template #sidebar> <AppSidebar v-model="page" :groups="groups" /> </template> <PageContent> <PageHeader title="Status" description="MyApp" /> </PageContent> </AppLayout></template>AppLayout 有 sidebar、toolbar、預設與 footer 插槽。在 DSM 視窗中請保持 wide-layout="window",讓最大化的視窗維持其版面。只有整頁的 Portal App 才使用 wide-layout="web"。版面寬度低於 600 px 時,導覽會變成滑出式。產生的外殼會讓每個頁面保持掛載,並以 v-show 切換,所以使用者查看其他頁面時,某一頁的輪詢仍會繼續。
頁面附註與標題控制項
Section titled “頁面附註與標題控制項”在 AppShell 顯示的頁面中,從 @synopackageland/sdk/vue 匯入 PageInfo。它會在頁面標題旁、自訂 #meta 內容之前放置一個資訊入口。notes 的非空項目以清單顯示;空項目會略過,沒有自訂 slot 且整份清單為空時不顯示按鈕。按鈕名稱使用 shell 的語言與附註數量。點擊或鍵盤 focus 可開啟,Escape 關閉。
<script setup lang="ts">import { PageInfo } from '@synopackageland/sdk/vue'</script>
<template> <PageInfo :notes="['Sizes include snapshots.', 'Refresh to read current usage.']" /> <PageInfo inline label="Search details" :notes="['Search covers indexed files.']" /> <PageInfo label="About this page" :notes="['Details']"> <p>Custom content replaces the notes list.</p> </PageInfo></template>inline 讓按鈕留在宣告位置;沒有 AppShell 時也會留在原位。label 覆寫無障礙名稱。預設 slot 取代清單,也能在沒有 notes 時顯示按鈕。side 與 align 傳給 InfoTip,預設為 bottom 與 start。獨立頁面 fixture 可從同一個 SDK 入口匯入 PageInfoTarget injection key,呼叫 provide(PageInfoTarget, shallowRef<HTMLElement | null>(null)),再把 ref 設為標題元素以提供目的地,用法如同 PageActionsTarget。
PageHeader 的標題與 meta 保持同一行。actions 放不下時,整組移到標題下方並靠右。標題仍放不下時以省略號截斷,title 屬性保留完整文字。
@synopackageland/ui 的 InfoTip 接受 align="start|center|end"(預設 center)與 icon="help|info"(預設 help)。氣泡以目前 App 視窗作為碰撞邊界,保留 8 px 間距,限制在有界的 AppRoot 內。
只從 @synopackageland/ui 匯入。不要以路徑匯入元件檔案。
| 元件 | 用途 |
|---|---|
AppRoot、ConfigProvider | App 根元素、語言 |
AppLayout、AppSidebar、PageContent、PageHeader | 視窗版面 |
Button、Input、Textarea、Select、Checkbox、Switch | 控制項 |
Form、FormItem、FormActions | 表單。表單寬度小於 520 px 時,標籤會改為堆疊 |
SettingsSection、Callout、EmptyState、LoadingArea、Progress | 設定頁、訊息、狀態 |
Dialog、ToastProvider、useToast | 對話框與通知。useToast 必須在 ToastProvider 的子元件中呼叫 |
TreeView | 階層結構,有 nodes、v-model、v-model:expanded,以及在第一次展開時取得子節點的 loadChildren |
DataGrid | 表格,有 columns、rows、row-key、排序、可選取的列(selectable)與以 page-size 分頁 |
FilePicker | 從使用者的電腦選擇檔案 |
請使用元件庫的控制項,不要用原生的 <select>、<input>、<label> 或 <a> 元素。圖示來自 @lucide/vue,專案已將它列為自己的相依套件。
要查詢屬性、插槽與事件,請在 frontend/ 執行:
npx --no syno-pkg-land-ui catalognpx --no syno-pkg-land-ui describe Button選取 NAS 檔案
Section titled “選取 NAS 檔案”NasPicker 與 FileBrowser 是 @synopackageland/sdk/vue 的匯出,不屬於 @synopackageland/ui。FileBrowser 以已登入使用者的 File Station 檢視,結合 TreeView 與 DataGrid。NasPicker 則把它放進對話框。
<NasPicker v-model:open="open" :provider="provider" :locale="ctx.locale" mode="file" @selected="path = $event" />NasPicker 屬性或事件 | 意義 |
|---|---|
open(v-model:open) | 對話框是否顯示 |
provider | 來自 @synopackageland/sdk 的 createNasProvider(ctx) |
locale | ctx.locale |
mode | 'file' 或 'folder' |
title、description | 可選的對話框文字 |
selected 事件 | 送出選到的路徑。選取路徑不會授予任何權限:後端在使用時會再次檢查工作階段與權限 |
後端部分請見檔案。
從使用者的電腦上傳
Section titled “從使用者的電腦上傳”FilePicker 回傳瀏覽器的 File 物件。它不認識 NAS。產生的檔案瀏覽器把它當作上傳輸入,並限制為單一檔案、最大 16 GiB:
<FilePicker v-model="uploads" variant="compact" :max-files="1" :max-size="16 * 1024 ** 3" @rejected="onRejected" />FilePicker 屬性 | 意義 |
|---|---|
v-model | 選到的 File[] |
accept、multiple、directory | 可以選擇什麼 |
maxFiles、maxSize | 數量與大小限制(位元組)。檔案被拒絕時會觸發 rejected,原因為 type、size、count 或 duplicate |
variant | 'dropzone' 或 'compact' |
locale、text | 語言與文字覆寫 |
佈景主題與密度
Section titled “佈景主題與密度”請在自己的 App 根元素上,於元件庫樣式表之後覆寫 token,並使用不會比對到其他 App 的選擇器。請在 AppRoot 上設定 data-package。
[data-ui-app][data-package='my-package'] { --ui-primary: #0086e5;}在元素上加上 data-ui-density="compact",可讓其內的控制項與間距更緊湊。請讓你自己的 CSS 不要放進 cascade layer。若需要為裸元素設定基礎規則,請依序載入 dsm7-embedded-reset.css、你的 CSS,再載入 dsm7-embedded-theme.css。這兩個檔案合起來等同 dsm7-embedded.css。
元件庫會以 App 的九種語言翻譯自己的文字(檔案選擇器、對話框按鈕、連結註解):en、zh-TW、zh-CN、ja、ko、de、fr、es 與 it。請依 ctx.locale 設定 ConfigProvider locale;@synopackageland/sdk 的 uiLocale 會把 cht 等 DSM 語言代碼對應到這些名稱。你自己的文字放在 frontend/src/i18n/<locale>.json,請見原生 App。
| 項目 | 限制 |
|---|---|
| 透過檔案瀏覽器的瀏覽器上傳 | 1 個檔案,16 GiB |
| 最小的 DSM 視窗 | 寬 700 px,由產生的視窗設定 |
| DSM 視窗內的浮層 | 僅限 App 區域。不要混用 App 區域與整份文件的浮層 |
| 文字大小單位 | rem,會跟隨 DSM 桌面的字體大小 |
| 同一頁面上掛載的 App | 每個都需要唯一的 idPrefix |
| 症狀 | 原因 | 解法 |
|---|---|---|
| 對話框或選單出現在視窗外 | App 不在 AppRoot 內 | 以 AppRoot 包住 App |
| 邊界與字型看起來不對 | 載入的是 theme.css 而不是內嵌佈景主題 | 匯入 themes/dsm7-embedded.css |
| 對話框標題指向另一個 App | 兩個 App 共用 ID 前綴 | 在 mount 之前設定唯一的 app.config.idPrefix |
建置說缺少 node_modules | build 不會安裝套件 | 在 frontend/ 執行 npm install |
init 拒絕 --ui plain --with login | 登入需要 Vue UI | 使用 --ui vue |
| 小檔案上傳卻顯示上傳大小訊息 | 選擇器因其他原因拒絕了檔案,例如選了多個檔案 | 只選擇單一檔案 |