本文目录导读:

我将为您提供一个完整的Java低代码平台案例,包含核心功能实现,这个案例将演示如何构建一个简单的低代码应用,让用户通过配置而非编码来创建业务应用。
项目结构
low-code-platform/
├── pom.xml
├── src/main/java/com/example/lowcode/
│ ├── LowCodeApplication.java
│ ├── controller/
│ │ ├── FormController.java
│ │ └── PageController.java
│ ├── service/
│ │ ├── FormService.java
│ │ └── DataService.java
│ ├── entity/
│ │ ├── FormConfig.java
│ │ └── DataRecord.java
│ ├── repository/
│ │ ├── FormConfigRepository.java
│ │ └── DataRecordRepository.java
│ └── config/
│ └── DynamicBeanConfig.java
└── src/main/resources/
├── application.yml
└── static/
└── index.html
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>low-code-platform</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
</parent>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
主启动类
package com.example.lowcode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class LowCodeApplication {
public static void main(String[] args) {
SpringApplication.run(LowCodeApplication.class, args);
}
}
实体类
FormConfig.java
package com.example.lowcode.entity;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@Entity
@Table(name = "form_configs")
public class FormConfig {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String formName;
private String description;
@Lob
@Column(columnDefinition = "TEXT")
private String formSchema; // JSON格式的表单配置
private String status; // DRAFT, PUBLISHED, ARCHIVED
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = createdAt;
if (status == null) {
status = "DRAFT";
}
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
DataRecord.java
package com.example.lowcode.entity;
import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@Entity
@Table(name = "data_records")
public class DataRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long formConfigId;
@Lob
@Column(columnDefinition = "TEXT")
private String dataJson; // 实际数据
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = createdAt;
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
}
Repository
FormConfigRepository.java
package com.example.lowcode.repository;
import com.example.lowcode.entity.FormConfig;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface FormConfigRepository extends JpaRepository<FormConfig, Long> {
List<FormConfig> findByStatus(String status);
FormConfig findByFormName(String formName);
}
DataRecordRepository.java
package com.example.lowcode.repository;
import com.example.lowcode.entity.DataRecord;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface DataRecordRepository extends JpaRepository<DataRecord, Long> {
List<DataRecord> findByFormConfigId(Long formConfigId);
void deleteByFormConfigId(Long formConfigId);
}
Service层
FormService.java
package com.example.lowcode.service;
import com.example.lowcode.entity.FormConfig;
import com.example.lowcode.repository.FormConfigRepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
public class FormService {
@Autowired
private FormConfigRepository formConfigRepository;
@Autowired
private ObjectMapper objectMapper;
public FormConfig createForm(String formName, String description, String schemaJson) {
FormConfig formConfig = new FormConfig();
formConfig.setFormName(formName);
formConfig.setDescription(description);
formConfig.setFormSchema(schemaJson);
formConfig.setStatus("PUBLISHED");
// 验证schema格式
try {
objectMapper.readTree(schemaJson);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid schema JSON: " + e.getMessage());
}
return formConfigRepository.save(formConfig);
}
public FormConfig updateForm(Long id, String formName, String description, String schemaJson) {
Optional<FormConfig> optional = formConfigRepository.findById(id);
if (optional.isPresent()) {
FormConfig formConfig = optional.get();
if (formName != null) {
formConfig.setFormName(formName);
}
if (description != null) {
formConfig.setDescription(description);
}
if (schemaJson != null) {
// 验证JSON格式
try {
objectMapper.readTree(schemaJson);
} catch (Exception e) {
throw new IllegalArgumentException("Invalid schema JSON: " + e.getMessage());
}
formConfig.setFormSchema(schemaJson);
}
return formConfigRepository.save(formConfig);
}
throw new RuntimeException("Form not found with id: " + id);
}
public List<FormConfig> getAllForms() {
return formConfigRepository.findAll();
}
public FormConfig getForm(Long id) {
return formConfigRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Form not found with id: " + id));
}
@Transactional
public void deleteForm(Long id) {
formConfigRepository.deleteById(id);
}
public ObjectNode getFormSchemaWithSampleData(Long formId) {
FormConfig formConfig = getForm(formId);
ObjectNode result = objectMapper.createObjectNode();
try {
JsonNode schema = objectMapper.readTree(formConfig.getFormSchema());
result.set("schema", schema);
// 生成示例数据
ObjectNode sampleData = generateSampleData((ObjectNode) schema);
result.set("sampleData", sampleData);
} catch (Exception e) {
throw new RuntimeException("Error processing form schema: " + e.getMessage());
}
return result;
}
private ObjectNode generateSampleData(ObjectNode schema) {
ObjectNode sample = objectMapper.createObjectNode();
JsonNode fields = schema.get("fields");
if (fields != null && fields.isArray()) {
for (JsonNode field : fields) {
String name = field.get("name").asText();
String type = field.get("type").asText();
switch (type) {
case "text":
sample.put(name, "Sample text");
break;
case "number":
sample.put(name, 123);
break;
case "date":
sample.put(name, "2024-01-15");
break;
case "select":
sample.put(name, "option1");
break;
case "checkbox":
sample.put(name, true);
break;
default:
sample.put(name, "");
}
}
}
return sample;
}
}
DataService.java
package com.example.lowcode.service;
import com.example.lowcode.entity.DataRecord;
import com.example.lowcode.entity.FormConfig;
import com.example.lowcode.repository.DataRecordRepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class DataService {
@Autowired
private DataRecordRepository dataRecordRepository;
@Autowired
private FormConfigRepository formConfigRepository;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private FormService formService;
public DataRecord saveData(Long formConfigId, String dataJson) {
// 验证表单存在
FormConfig formConfig = formService.getForm(formConfigId);
// 验证数据符合schema
validateData(formConfig, dataJson);
DataRecord dataRecord = new DataRecord();
dataRecord.setFormConfigId(formConfigId);
dataRecord.setDataJson(dataJson);
return dataRecordRepository.save(dataRecord);
}
private void validateData(FormConfig formConfig, String dataJson) {
try {
JsonNode data = objectMapper.readTree(dataJson);
JsonNode schema = objectMapper.readTree(formConfig.getFormSchema());
JsonNode fields = schema.get("fields");
if (fields != null && fields.isArray()) {
for (JsonNode field : fields) {
String name = field.get("name").asText();
String type = field.get("type").asText();
Boolean required = field.has("required") ? field.get("required").asBoolean() : false;
JsonNode value = data.get(name);
// 必填检查
if (required && (value == null || value.isNull())) {
throw new IllegalArgumentException("Field '" + name + "' is required");
}
// 类型检查
if (value != null && !value.isNull()) {
switch (type) {
case "number":
if (!value.isNumber()) {
throw new IllegalArgumentException("Field '" + name + "' must be a number");
}
break;
case "date":
// 简单验证格式
break;
case "select":
// 验证选项
break;
}
}
}
}
} catch (Exception e) {
if (e instanceof IllegalStateException) {
throw new IllegalArgumentException(e.getMessage());
}
throw new IllegalArgumentException("Invalid data: " + e.getMessage());
}
}
public List<DataRecord> getDataByFormId(Long formConfigId) {
return dataRecordRepository.findByFormConfigId(formConfigId);
}
public void deleteData(Long dataId) {
dataRecordRepository.deleteById(dataId);
}
public ObjectNode getDataForDisplay(Long formConfigId) {
FormConfig formConfig = formService.getForm(formConfigId);
List<DataRecord> records = getDataByFormId(formConfigId);
ObjectNode result = objectMapper.createObjectNode();
// 获取字段定义
try {
JsonNode schema = objectMapper.readTree(formConfig.getFormSchema());
result.set("fields", schema.get("fields"));
// 转换数据为List of Maps
List<ObjectNode> displayData = new ArrayList<>();
for (DataRecord record : records) {
JsonNode data = objectMapper.readTree(record.getDataJson());
ObjectNode displayRecord = objectMapper.createObjectNode();
displayRecord.put("id", record.getId());
displayRecord.set("data", data);
displayData.add(displayRecord);
}
result.set("data", objectMapper.valueToTree(displayData));
} catch (Exception e) {
throw new RuntimeException("Error preparing display data: " + e.getMessage());
}
return result;
}
}
Controller
FormController.java
package com.example.lowcode.controller;
import com.example.lowcode.entity.FormConfig;
import com.example.lowcode.service.FormService;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/forms")
@CrossOrigin(origins = "*")
public class FormController {
@Autowired
private FormService formService;
@PostMapping
public ResponseEntity<FormConfig> createForm(@RequestBody ObjectNode request) {
String formName = request.get("formName").asText();
String description = request.has("description") ? request.get("description").asText() : "";
String schemaJson = request.get("schema").toString();
FormConfig form = formService.createForm(formName, description, schemaJson);
return ResponseEntity.ok(form);
}
@GetMapping
public ResponseEntity<List<FormConfig>> getAllForms() {
return ResponseEntity.ok(formService.getAllForms());
}
@GetMapping("/{id}")
public ResponseEntity<FormConfig> getForm(@PathVariable Long id) {
return ResponseEntity.ok(formService.getForm(id));
}
@PutMapping("/{id}")
public ResponseEntity<FormConfig> updateForm(@PathVariable Long id, @RequestBody ObjectNode request) {
String formName = request.has("formName") ? request.get("formName").asText() : null;
String description = request.has("description") ? request.get("description").asText() : null;
String schemaJson = request.has("schema") ? request.get("schema").toString() : null;
FormConfig form = formService.updateForm(id, formName, description, schemaJson);
return ResponseEntity.ok(form);
}
@DeleteMapping("/{id}")
public ResponseEntity<?> deleteForm(@PathVariable Long id) {
formService.deleteForm(id);
return ResponseEntity.ok().build();
}
@GetMapping("/{id}/schema-with-sample")
public ResponseEntity<ObjectNode> getFormWithSampleData(@PathVariable Long id) {
return ResponseEntity.ok(formService.getFormSchemaWithSampleData(id));
}
}
PageController.java
package com.example.lowcode.controller;
import com.example.lowcode.entity.DataRecord;
import com.example.lowcode.service.DataService;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/data")
@CrossOrigin(origins = "*")
public class PageController {
@Autowired
private DataService dataService;
@PostMapping("/{formConfigId}")
public ResponseEntity<DataRecord> saveData(@PathVariable Long formConfigId,
@RequestBody ObjectNode request) {
String dataJson = request.get("data").toString();
DataRecord savedRecord = dataService.saveData(formConfigId, dataJson);
return ResponseEntity.ok(savedRecord);
}
@GetMapping("/{formConfigId}")
public ResponseEntity<ObjectNode> getDataForDisplay(@PathVariable Long formConfigId) {
return ResponseEntity.ok(dataService.getDataForDisplay(formConfigId));
}
@DeleteMapping("/record/{dataId}")
public ResponseEntity<?> deleteData(@PathVariable Long dataId) {
dataService.deleteData(dataId);
return ResponseEntity.ok().build();
}
}
前端页面 (static/index.html)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">低代码平台</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5em;
}
.tabs {
display: flex;
margin-bottom: 30px;
border-bottom: 2px solid #eee;
}
.tab {
padding: 10px 30px;
cursor: pointer;
border: none;
background: none;
font-size: 16px;
color: #666;
transition: all 0.3s;
}
.tab.active {
color: #667eea;
border-bottom: 2px solid #667eea;
font-weight: bold;
}
.tab-form {
display: none;
}
.tab-form.active {
display: block;
}
.form-section {
margin-bottom: 20px;
}
.form-section h2 {
color: #333;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input, textarea, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
textarea {
min-height: 150px;
resize: vertical;
}
button {
padding: 10px 30px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
margin-right: 10px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
.form-card {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
}
.form-card h3 {
margin-bottom: 10px;
color: #333;
}
.form-card p {
color: #666;
margin-bottom: 10px;
}
.form-card .date {
font-size: 12px;
color: #999;
}
.actions {
margin-top: 15px;
}
.dynamic-form {
margin: 20px 0;
}
.dynamic-form .form-group {
background: #fff;
padding: 15px;
border-radius: 8px;
border: 1px solid #eee;
}
.data-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.data-table th, .data-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.data-table th {
background: #f8f9fa;
font-weight: bold;
}
.data-table tr:hover {
background: #f5f5f5;
}
.json-view {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
overflow-x: auto;
font-family: monospace;
}
.blueprint {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.blueprint th, .blueprint td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.blueprint th {
background: #667eea;
color: white;
}
.blueprint tr:nth-child(even) {
background: #f2f2f2;
}
.blueprint tr:hover {
background: #ddd;
}
.status-badge {
padding: 5px 10px;
border-radius: 5px;
font-size: 12px;
font-weight: bold;
}
.status-published {
background: #d4edda;
color: #155724;
}
.status-draft {
background: #fff3cd;
color: #856404;
}
.preview-modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
overflow: auto;
}
.preview-content {
background: white;
margin: 10% auto;
padding: 30px;
width: 80%;
max-width: 700px;
border-radius: 10px;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close:hover {
color: black;
}
.sample-data {
background: #f0f0f0;
padding: 15px;
border-radius: 5px;
margin-top: 15px;
}
.sample-data code {
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 低代码平台</h1>
<!-- Tabs -->
<div class="tabs">
<button class="tab active" onclick="switchTab('form-builder')">📝 表单构建器</button>
<button class="tab" onclick="switchTab('data-management')">🗃️ 数据管理</button>
<button class="tab" onclick="switchTab('blueprint')">📋 数据蓝图</button>
</div>
<!-- 表单构建器 -->
<div class="tab-form active" id="form-builder">
<div class="form-section">
<h2>创建新表单</h2>
<div class="form-group">
<label>表单名称</label>
<input type="text" id="form-name" placeholder="请输入表单名称">
</div>
<div class="form-group">
<label>表单描述</label>
<input type="text" id="form-description" placeholder="请输入表单描述">
</div>
<div class="form-group">
<label>表单Schema (JSON格式)</label>
<textarea id="schema-input" placeholder='{
"fields": [
{
"name": "username",
"label": "用户名",
"type": "text",
"required": true
},
{
"name": "age",
"label": "年龄",
"type": "number"
}
]
}'></textarea>
</div>
<button class="btn-primary" onclick="createForm()">创建表单</button>
<button class="btn-secondary" onclick="previewSample()">预览示例</button>
</div>
<div class="form-section">
<h2>已创建的表单</h2>
<div id="form-list"></div>
</div>
</div>
<!-- 数据管理 -->
<div class="tab-form" id="data-management">
<div class="form-section">
<div class="form-group">
<label>选择表单</label>
<select id="data-form-select" onchange="loadDataForm()">
<option value="">请选择要管理数据的表单</option>
</select>
</div>
<div id="data-entry" style="display: none;">
<h2>添加新数据</h2>
<form id="data-form"></form>
<button class="btn-primary" onclick="submitData()">提交数据</button>
<h2>已有数据</h2>
<div id="data-records"></div>
</div>
</div>
</div>
<!-- 数据蓝图 -->
<div class="tab-form" id="blueprint">
<h2>数据蓝图</h2>
<div id="blueprint-content">
<table class="blueprint">
<thead>
<tr>
<th>ID</th>
<th>表单名称</th>
<th>描述</th>
<th>状态</th>
<th>更新时间</th>
</tr>
</thead>
<tbody id="blueprint-body">
</tbody>
</table>
</div>
</div>
</div>
<!-- 预览弹窗 -->
<div id="preview-modal" class="preview-modal">
<div class="preview-content">
<span class="close" onclick="closePreview()">×</span>
<h2>表单预览</h2>
<div id="preview-content"></div>
</div>
</div>
<script>
const API_BASE = '/api';
// 切换标签页
function switchTab(tabId) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-form').forEach(t => t.classList.remove('active'));
const clickedTab = document.querySelector(`[onclick="switchTab('${tabId}')"]`);
clickedTab.classList.add('active');
document.getElementById(tabId).classList.add('active');
// 根据标签页加载数据
if (tabId === 'form-builder') {
loadForms();
} else if (tabId === 'data-management') {
loadFormSelector();
} else if (tabId === 'blueprint') {
loadBlueprint();
}
}
// 加载表单列表
async function loadForms() {
try {
const response = await fetch(`${API_BASE}/forms`);
const forms = await response.json();
const formList = document.getElementById('form-list');
formList.innerHTML = '';
forms.forEach(form => {
const card = document.createElement('div');
card.className = 'form-card';
card.innerHTML = `
<h3>${form.formName || '未命名表单'}</h3>
<p>${form.description || '无描述'}</p>
<p class="date">更新于: ${new Date(form.updatedAt).toLocaleString()}</p>
<div class="actions">
<span class="status-badge status-${form.status.toLowerCase()}">${form.status}</span>
<button class="btn-secondary" onclick="viewSchema(${form.id})">查看Schema</button>
<button class="btn-danger" onclick="deleteForm(${form.id})">删除</button>
</div>
`;
formList.appendChild(card);
});
} catch (error) {
console.error('加载表单失败:', error);
}
}
// 创建表单
async function createForm() {
const formName = document.getElementById('form-name').value;
const description = document.getElementById('form-description').value;
const schemaText = document.getElementById('schema-input').value;
if (!formName || !schemaText) {
alert('请填写表单名称和Schema');
return;
}
try {
const schema = JSON.parse(schemaText);
const response = await fetch(`${API_BASE}/forms`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
formName: formName,
description: description,
schema: schema
})
});
if (response.ok) {
alert('表单创建成功!');
document.getElementById('form-name').value = '';
document.getElementById('form-description').value = '';
document.getElementById('schema-input').value = '';
loadForms();
} else {
const error = await response.json();
alert('创建失败: ' + (error.message || '未知错误'));
}
} catch (e) {
alert('Schema JSON格式错误: ' + e.message);
}
}
// 查看Schema
async function viewSchema(id) {
try {
const response = await fetch(`${API_BASE}/forms/${id}/schema-with-sample`);
const data = await response.json();
const modal = document.getElementById('preview-modal');
const content = document.getElementById('preview-content');
content.innerHTML = `
<h3>Schema 预览</h3>
<div class="json-view">${JSON.stringify(data, null, 2)}</div>
<h3>示例数据</h3>
<div class="sample-data">
<code>${JSON.stringify(data.sampleData, null, 2)}</code>
</div>
`;
modal.style.display = 'block';
} catch (error) {
console.error('加载Schema失败:', error);
}
}
// 关闭预览
function closePreview() {
document.getElementById('preview-modal').style.display = 'none';
}
// 删除表单
async function deleteForm(id) {
if (confirm('确定要删除这个表单吗?')) {
try {
await fetch(`${API_BASE}/forms/${id}`, {method: 'DELETE'});
loadForms();
} catch (error) {
console.error('删除失败:', error);
}
}
}
// 加载表单选择器
async function loadFormSelector() {
try {
const response = await fetch(`${API_BASE}/forms`);
const forms = await response.json();
const select = document.getElementById('data-form-select');
select.innerHTML = '<option value="">请选择要管理数据的表单</option>';
forms.forEach(form => {
const option = document.createElement('option');
option.value = form.id;
option.textContent = form.formName;
select.appendChild(option);
});
document.getElementById('data-entry').style.display = 'none';
} catch (error) {
console.error('加载表单选择器失败:', error);
}
}
// 加载数据管理表单
async function loadDataForm() {
const select = document.getElementById('data-form-select');
const formId = select.value;
if (!formId) {
document.getElementById('data-entry').style.display = 'none';
return;
}
document.getElementById('data-entry').style.display = 'block';
try {
// 获取表单Schema
const schemaResponse = await fetch(`${API_BASE}/forms/${formId}/schema-with-sample`);
const schemaData = await schemaResponse.json();
// 构建动态表单
const form = document.getElementById('data-form');
form.innerHTML = '';
schemaData.schema.fields.forEach(field => {
const formGroup = document.createElement('div');
formGroup.className = 'form-group';
const label = document.createElement('label');
label.textContent = field.label || field.name;
label.htmlFor = field.name;
formGroup.appendChild(label);
let input;
if (field.type === 'select') {
input = document.createElement('select');
input.id = field.name;
input.name = field.name;
if (field.options) {
field.options.forEach(option => {
const optionElement = document.createElement('option');
optionElement.value = option.value || option;
optionElement.textContent = option.label || option;
input.appendChild(optionElement);
});
}
} else if (field.type === 'checkbox') {
input = document.createElement('input');
input.type = 'checkbox';
input.id = field.name;
input.name = field.name;
} else if (field.type === 'number') {
input = document.createElement('input');
input.type = 'number';
input.id = field.name;
input.name = field.name;
} else if (field.type === 'date') {
input = document.createElement('input');
input.type = 'date';
input.id = field.name;
input.name = field.name;
} else {
input = document.createElement('input');
input.type = 'text';
input.id = field.name;
input.name = field.name;
}
input.className = 'dynamic-input';
if (field.required) input.required = true;
formGroup.appendChild(input);
form.appendChild(formGroup);
});
// 加载已有数据
loadDataRecords(formId);
} catch (error) {
console.error('加载表单数据失败:', error);
}
}
// 提交数据
async function submitData() {
const select = document.getElementById('data-form-select');
const formId = select.value;
const form = document.getElementById('data-form');
const data = {};
form.querySelectorAll('.dynamic-input').forEach(input => {
if (input.type === 'checkbox') {
data[input.name] = input.checked;
} else {
data[input.name] = input.value;
}
});
try {
const response = await fetch(`${API_BASE}/data/${formId}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({data: data})
});
if (response.ok) {
alert('数据保存成功!');
form.reset();
loadDataRecords(formId);
} else {
const error = await response.json();
alert('保存失败: ' + error.message);
}
} catch (error) {
console.error('保存数据失败:', error);
}
}
// 加载数据记录
async function loadDataRecords(formId) {
try {
const response = await fetch(`${API_BASE}/data/${formId}`);
const data =