# How Form Submission and Data Collection Work End-to-End in Luban H5

> Explore end-to-end form submission and data collection in Luban H5. Discover how lbp-form-input and lbp-form-button plugins power your data collection process.

- Repository: [小小鲁班/luban-h5](https://github.com/ly525/luban-h5)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Luban H5 implements form submission through two core plugins—`lbp-form-input` for rendering fields and `lbp-form-button` for collecting data and posting to `/works/form/submit/:workId`.**

Understanding how form submission and data collection work in the [ly525/luban-h5](https://github.com/ly525/luban-h5) editor requires examining both the front-end plugin architecture and the back-end Express API. The system uses a lightweight, attribute-based discovery mechanism where input fields expose metadata via HTML5 data attributes, enabling the submit button to gather values dynamically without tight coupling. This article traces the complete data flow from the designer's canvas to persistent storage in the database.

## Front-End Form Collection

The front-end implementation relies on two specialized plugins that communicate through DOM attributes rather than direct component references.

### Rendering Input Fields with lbp-form-input

When a designer drags a form input onto the canvas, the editor instantiates the `lbp-form-input` plugin located at [`src/components/core/plugins/lbp-form-input.js`](https://github.com/ly525/luban-h5/blob/main/src/components/core/plugins/lbp-form-input.js). This component renders a standard HTML input element while attaching two critical attributes: `data-type="lbp-form-input"` for discovery and `data-uuid` containing a unique identifier generated by the editor.

```javascript
// src/components/core/plugins/lbp-form-input.js
// The render function attaches discovery attributes
<input 
  data-type="lbp-form-input" 
  data-uuid="auto-generated-uuid-123" 
  placeholder="Enter your name"
/>

```

The `data-type` prefix serves as a selector hook, allowing the submit button to query all form elements regardless of their specific position in the component tree or nested layout structures.

### Submitting Data with lbp-form-button

The `lbp-form-button` plugin handles the actual data collection and transmission. Located at [`src/components/core/plugins/lbp-form-button.js`](https://github.com/ly525/luban-h5/blob/main/src/components/core/plugins/lbp-form-button.js), its `handleClick` method executes a four-step process:

```javascript
// src/components/core/plugins/lbp-form-button.js
handleClick() {
  // 1. Discover all form inputs using the data-type prefix
  let inputs = document.querySelectorAll("[data-type^='lbp-form-input']")
  
  // 2. Build FormData payload mapping UUID to value
  let formData = new FormData()
  inputs.forEach(input => {
    formData.append(input.dataset.uuid, input.value)
  })
  
  // 3. Acquire work ID from global state
  const workId = window.__work.id
  
  // 4. POST to backend endpoint
  const req = new XMLHttpRequest()
  req.open('post', `/works/form/submit/${workId}`, true)
  req.send(formData)
}

```

The button reads the current work identifier from `window.__work.id`, a global shortcut maintained by the Vuex store. Upon receiving the server response, it displays success or failure toasts using Vant's `Toast` component based on the HTTP status code.

## Back-End Processing and Storage

The back-end service resides in `back-end/h5-api` and processes multipart form-data submissions through an Express route.

### Express Route Handling

The submission endpoint is defined in [`back-end/h5-api/server.js`](https://github.com/ly525/luban-h5/blob/main/back-end/h5-api/server.js) and handles validation and parsing:

```javascript
// back-end/h5-api/server.js
app.post('/works/form/submit/:workId', async (req, res) => {
  const { workId } = req.params
  
  // Parsed by multer middleware into req.body
  const formData = req.body
  
  // Validation and permission checks
  if (!validateWorkId(workId)) {
    return res.status(400).json({ error: 'Invalid work ID' })
  }
  
  // Persist records and respond
  try {
    await saveFormRecords(workId, formData)
    res.status(200).json({ success: true })
  } catch (err) {
    res.status(500).json({ error: 'Submission failed' })
  }
})

```

Multer middleware extracts the `FormData` payload into a standard JavaScript object before it reaches the route handler. The server validates the `workId` against the `works` table, ensuring the submission targets an existing project.

### Database Persistence

Each form field is stored as a separate record in the `form_record` table, defined in [`back-end/h5-api/models/form_record.js`](https://github.com/ly525/luban-h5/blob/main/back-end/h5-api/models/form_record.js). The schema links individual field values to their parent work through foreign key relationships:

- **uuid**: Maps to the `data-uuid` from the front-end input
- **value**: The submitted text content
- **workId**: References the parent work entity

This structure allows the system to reconstruct the complete form submission later by querying all records associated with a specific `workId`.

## Vuex State Management

The editor's Vuex module maintains the current work context and makes it available to plugins through both reactive state and global window objects.

In [`src/store/modules/editor.js`](https://github.com/ly525/luban-h5/blob/main/src/store/modules/editor.js), the `SET_WORK` mutation initializes the environment:

```javascript
// src/store/modules/editor.js
const state = {
  work: {},  // Contains id, name, pages, etc.
  formDetailOfWork: {}
}

const mutations = {
  SET_WORK(state, work) {
    state.work = work
    // Global shortcut for non-Vue contexts (like plugins)
    window.__work = work
  }
}

```

The `formDetailOfWork` state property stores previously submitted records retrieved via `GET /works/form/query/:workId`, enabling analytics views to display submission statistics without redundant API calls.

## Complete End-to-End Workflow

The form submission and data collection process follows this chronological sequence:

1. **Designer configures form**: Drag `lbp-form-input` components onto the canvas, configuring placeholders and types (text, email, phone)
2. **UUID assignment**: The editor automatically generates unique identifiers and injects them as `data-uuid` attributes during render
3. **Submit button placement**: Add `lbp-form-button` to the same page, which remains dormant until user interaction
4. **User interaction**: End-user fills inputs and clicks the submit button
5. **Data gathering**: `handleClick` queries `[data-type^='lbp-form-input']`, constructs `FormData` with UUID-value pairs
6. **Transmission**: XMLHttpRequest sends `POST /works/form/submit/:workId` with multipart payload
7. **Server processing**: Express validates work existence, persists each field to `form_record` table
8. **Feedback loop**: HTTP 200 triggers "提交成功" toast; error codes display "提交失败"
9. **Retrieval**: Admin users fetch submissions through `formDetailOfWork` state or direct API queries for analysis

## Summary

- **Attribute-based discovery**: Form inputs expose `data-type="lbp-form-input"` and `data-uuid` attributes for DOM selection
- **Decoupled architecture**: The submit button queries the DOM rather than maintaining references to input components
- **Global work context**: `window.__work.id` provides the target identifier for submissions, set by the Vuex `SET_WORK` mutation
- **RESTful endpoint**: `POST /works/form/submit/:workId` accepts multipart form-data and returns HTTP 200 on success
- **Relational storage**: Each field persists as a `form_record` row linked to the parent work via foreign key

## Frequently Asked Questions

### How does the submit button find form inputs without direct component references?

The `lbp-form-button` plugin uses `document.querySelectorAll("[data-type^='lbp-form-input']")` to discover all input elements carrying the specific data attribute. This DOM-based approach decouples the button from the input components, allowing flexible layouts where inputs might be nested inside groups or repeated lists.

### Where does the work ID come from during form submission?

The work ID originates from the Vuex editor module's `state.work.id`, which gets mirrored to `window.__work.id` via the `SET_WORK` mutation. When a designer opens a project, the editor commits this mutation, making the identifier globally accessible to plugins that run outside the Vue instance context.

### What format does the back-end expect for form data?

The server expects standard `multipart/form-data` encoding. The front-end constructs a `FormData` object where each entry uses the input's `data-uuid` as the key and the input value as the value. Multer middleware in the Express pipeline parses this into a JavaScript object available on `req.body`.

### Can I customize the submission endpoint for different environments?

Yes. While the default [`lbp-form-button.js`](https://github.com/ly525/luban-h5/blob/main/lbp-form-button.js) posts to `/works/form/submit/${workId}`, you can modify the `handleClick` method in [`src/components/core/plugins/lbp-form-button.js`](https://github.com/ly525/luban-h5/blob/main/src/components/core/plugins/lbp-form-button.js) to target custom endpoints. For example, change `req.open('post', `/works/form/submit/${workId}`, true)` to `req.open('post', `/api/custom/form/submit/${workId}`, true)` to integrate with external CRM systems or specialized microservices.