本文目录导读:

我将为您提供一个完整的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 =
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播