一、表單地獄與 hardcoded 的痛

在我們做專案開發、SaaS 平台設計或內部工具建置時,表單往往是前端最常見、但也是最容易變成災難的地方。特別是當表單具有以下特性時,開發者就像掉進了一場「表單地獄」:

  1. 欄位多達數十個以上
  1. 各欄位型別不一(文字、數字、日期、附件、select、多選)
  1. 有條件邏輯(A 填了才出現 B)
  1. 欄位間存在依賴關係
  1. 權限限制(某些角色只可見、不能編輯)
  1. 每個表單還有各自不同的驗證規則

如此如此…還有太多需要重複開發的眉眉角角

這些需求乍看合理,但在傳統開發模式下,每一項都意味著寫死在 code 裡,例如:

<!-- 傳統 Vue 寫法 -->
<div v-if="user.role === 'admin'">
  <label>管理者備註</label>
  <textarea v-model="form.adminNote" required></textarea>
</div>

這類 hardcoded 的方式一開始可能很快,但隨著表單越來越複雜,會逐步出現以下痛點:

痛點說明
重複勞動

每新增一種表單或流程,就要重新撰寫一份 template 和邏輯。
高維護成本

欄位異動、邏輯改變需全面檢查程式碼,風險高。
難以重用

同樣的欄位規則在不同表單中無法共用。
角色權限拆解困難

欄位的可見/可編輯權限難以模組化,常寫成大堆 if else。
配合後端困難

一但後端新增欄位,前端必須手動補上元件、驗證與邏輯。
版本不一致問題

表單欄位定義分散在不同端點與檔案,容易產生版本落差。

這就像是一場「用複製貼上的方式堆砌流程邏輯的長征」,當需求改變、流程改版、欄位重新配置時,開發者往往會陷入 debug 噩夢中。

為了跳出這種寫死邏輯的泥淖,我們開始思考:

能不能讓表單自己「讀懂」結構與邏輯,而不是我們手動寫死?

這就是本文要探討的 —— metadata-driven UI framework 的核心價值。

二、Metadata-driven UI 是什麼?

如果 hardcoded 的表單開發是「逐一手刻欄位與邏輯」,那 metadata-driven UI 就是「宣告好結構,讓系統自己渲染欄位與行為」。

Metadata-driven UI 是一種 UI 架構設計模式,核心精神是:

UI 的結構、欄位型別、驗證規則、條件邏輯、權限控制等都由一份「中繼資料(metadata)」來描述,前端只需根據這份資料動態渲染元件,而非硬寫 template。

這份 metadata 本質上就是一份 描述 UI 的資料,例如每個欄位要顯示什麼?可不可以編輯?需要哪些選項?甚至哪些角色可見?都可以在這份 metadata 中定義。

範例:從 hardcoded 到 metadata setting

假設你原本手寫如下欄位:

<label>標題</label>
<input type="text" v-model="form.title" required :disabled="user.role !== 'editor'" />

用 metadata 表達的方式可能長這樣:

{
  key: 'title',
  type: 'text',
  label: '標題',
  required: true,
  disabledRoles: ['viewer']
}

而前端框架只要知道怎麼「讀這份 metadata」,就能正確地渲染出:

這個模式帶來的思維轉變:

傳統寫法Metadata-driven UI
一個表單就寫一套邏輯

多個表單共用渲染邏輯,改資料就能改 UI
前端控制 UI 呈現後端或配置決定 UI 呈現

複雜邏輯寫在 template 裡條件邏輯寫在 metadata 裡,易測試
欄位改了,要改 code欄位改了,只需改 metadata

有了這樣的初步了解,可以知道他適合:

  1. 有大量可配置的表單需求(例如 CMS、流程表單)
  1. 欄位會隨專案、單位、角色變動
  1. 欲實作可視化表單編輯器(low-code/no-code)

三、Metadata-driven 表單 Demo

Metadata 設計

const formMetadata = [
  {
    key: 'title',
    label: '標題',
    type: 'text',
    required: true,
    disabledRoles: ['viewer']
  },
  {
    key: 'category',
    label: '分類',
    type: 'select',
    options: ['建築', '機電', '土木']
  },
  {
    key: 'remark',
    label: '備註',
    type: 'textarea'
  }
]

formData自動初始化

<template>
  <form @submit.prevent="onSubmit">
    <div v-for="field in formMetadata" :key="field.key" class="mb-3">
      <label>{{ field.label }}</label>

      <input
        v-if="field.type === 'text'"
        v-model="formData[field.key]"
        type="text"
        :required="field.required"
        :disabled="isDisabled(field)"
        class="form-control"
      />

      <select
        v-else-if="field.type === 'select'"
        v-model="formData[field.key]"
        class="form-select"
      >
        <option disabled value="">請選擇</option>
        <option v-for="opt in field.options" :key="opt" :value="opt">{{ opt }}</option>
      </select>

      <textarea
        v-else-if="field.type === 'textarea'"
        v-model="formData[field.key]"
        class="form-control"
        rows="3"
      />
    </div>

    <button type="submit" class="btn btn-primary">送出</button>
  </form>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const userRole = 'editor'

const formMetadata = [
  { key: 'title', label: '標題', type: 'text', required: true, disabledRoles: ['viewer'] },
  { key: 'category', label: '分類', type: 'select', options: ['建築', '機電', '土木'] },
  { key: 'remark', label: '備註', type: 'textarea' }
]

const formData = ref(
  Object.fromEntries(formMetadata.map((field) => [field.key, '']))
)

const isDisabled = (field: any) => {
  return field.disabledRoles?.includes(userRole)
}

const onSubmit = () => {
  console.log('送出資料:', formData.value)
}
</script>

這樣就是一個最小可行、架構正確的 metadata-driven form rendering 示範。如你之後要再加入驗證(例如 required rule)、條件欄位顯示(例如 showIf)、甚至動態權限控制,都能從這個基礎擴展。(metadata 一般都是定義在後端,本篇主要介紹概念所以定義在前端)

從表單地獄,到配置驅動的優雅

在開發過程中,我們都曾為大量表單欄位的變動、客製化需求、條件邏輯與權限限制感到疲憊不堪。傳統以硬編碼方式撰寫表單,雖然直觀,但隨著需求複雜化,往往成為維護負擔與錯誤溫床。

透過 metadata-driven UI 的設計模式,我們將欄位的結構、行為、互動、驗證乃至權限抽象為一份「資料描述」,讓 UI 呈現與邏輯運作能由 metadata 所驅動。這樣的轉變不只是減少重複程式碼,更打下了未來走向 low-code 平台的基礎。

😍
萊茲特提供各項專業服務,BIM模型建置及整合、系統及程式開發、Revit教育訓練、BIM專業顧問,幫助您減輕工作負擔、提高工作效率、改善工作流程,選擇萊茲特,輕鬆又快樂。