Spring Boot + Vue 实现 XML 动态表单:固定字段 + 自由扩展方案
一、背景与痛点
在实际的企业级项目开发中,经常会遇到这样的需求:
- 系统核心表有固定的字段(如任务名称、创建人、状态等),这些字段是确定的,不会频繁变化。
- 但不同业务场景、不同地区、不同客户,往往需要采集额外的扩展信息 (如办理意见、涉及金额、是否紧急等),这些字段不确定、经常变化。
常见的应对方式有:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 每次加字段都 ALTER TABLE 加列 | 简单直接 | 改库风险大,频繁发版,无法在线配置 |
| 用 JSON 存储扩展字段 | 灵活 | 不够结构化,查询不便,与某些老旧系统对接困难 |
| 建多张 EAV 扩展表 | 查询灵活 | 表关联多、SQL 复杂、性能较差 |
本文介绍一种基于 XML + BLOB 独立扩展表 + 前端动态渲染的方案,核心思路是:
固定字段存在主表中,扩展字段以 XML 格式存储在独立的 BLOB 字段 中,通过一张字段配置表 管理扩展字段的元信息,前端根据配置动态生成表单。
二、整体架构设计
┌──────────────────────────────────────────────────────────┐
│ 前端 (Vue3 + Element Plus) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │扩展字段配置 │ │ 任务管理 │ │
│ │(增删改查) │ │ (固定+扩展) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ │ ┌──────────────┴──────────────┐ │
│ │ │ DynamicForm 动态表单组件 │ │
│ │ │根据字段配置自动渲染表单控件 │ │
│ │ └─────────────────────────────┘ │
└─────────┼────────────────────────────────────────────────┘
│ REST API
┌─────────┼────────────────────────────────────────────────┐
│ ▼ 后端 (Spring Boot + MyBatis) │
│ ┌───────────────┐ ┌──────────────┐ │
│ │FieldConfigCtrl│ │TaskController│ │
│ └──────┬────────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴───────┐ ┌──────┴───────┐ │
│ │FieldConfigSvc│ │ TaskService │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ │ ┌────────┴────────┐ │
│ │ │XmlFormConverter │ ← XML ↔ Map 互转 │
│ │ └────────┬────────┘ │
│ ┌──────┴───────┐ ┌──────┴───────┐ │
│ │FieldConfigMap│ │TaskMapper + │ │
│ └──────┬───────┘ │TaskExtMapper │ │
│ │ └──────┬───────┘ │
└─────────┼───────────────────┼────────────────────────────┘
▼ ▼
┌──────────────┐ ┌─────────────┐ ┌─────────────┐
│t_field_config│ │ t_task │ │ t_task_ext │
│ (字段配置表) │ │ (主数据表) │ │(BLOB扩展表) │
└──────────────┘ └─────────────┘ └─────────────┘
核心设计思想:
- t_task:存储固定字段(任务名称、创建人、状态等),结构稳定不变
- t_task_ext :独立扩展表,用
MEDIUMBLOB字段存储 XML 格式的扩展数据,主从分离 - t_field_config:字段配置表,定义每个扩展字段的中文名、类型、是否必填、下拉选项等
- XmlFormConverter:核心引擎,负责 XML ↔ 表单数据的双向转换
- DynamicForm:Vue 动态表单组件,根据配置自动渲染不同类型的控件
三、数据库设计
3.1 主数据表(固定字段)
sql
CREATE TABLE t_task (
task_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '任务序号',
task_name VARCHAR(200) NOT NULL COMMENT '任务名称',
creator VARCHAR(50) COMMENT '创建人',
create_time DATETIME COMMENT '创建时间',
task_status TINYINT DEFAULT 0 COMMENT '任务状态 0-待处理 1-处理中 2-已完成',
district_code VARCHAR(20) COMMENT '所属区域编码',
update_time DATETIME COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务主数据表(固定字段)';
3.2 扩展数据表(BLOB独立成表)
sql
CREATE TABLE t_task_ext (
ext_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '扩展记录ID',
task_id BIGINT NOT NULL UNIQUE COMMENT '关联任务ID',
ext_data MEDIUMBLOB COMMENT 'XML格式的扩展字段数据',
FOREIGN KEY (task_id) REFERENCES t_task(task_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务扩展数据表(自由扩展字段)';
设计要点:
- 扩展数据独立成表,主表查询列表时不需要加载 BLOB 大字段,性能友好
- 使用
MEDIUMBLOB最大支持 16MB,足够存储大量扩展字段 ON DELETE CASCADE确保主表删除时扩展数据同步清理
3.3 扩展字段配置表
sql
CREATE TABLE t_field_config (
config_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '配置ID',
field_label VARCHAR(100) NOT NULL COMMENT '字段中文名称(同时作为XML标签和前端Label)',
field_type VARCHAR(20) DEFAULT 'text' COMMENT '字段类型: text/number/date/select/textarea',
required TINYINT DEFAULT 0 COMMENT '是否必填 0-否 1-是',
options TEXT COMMENT '下拉选项JSON数组,如["选项1","选项2"]',
sort_order INT DEFAULT 0 COMMENT '显示排序',
status TINYINT DEFAULT 1 COMMENT '状态 0-禁用 1-启用',
create_time DATETIME COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='扩展字段配置表';
设计要点:
field_label一举三得:既作为字段中文名称显示在页面上,又作为 XML 标签名,同时作为前端表单的 keyfield_type支持 text / textarea / number / date / select 五种类型status字段支持软启用/禁用,禁用后的字段不在表单中显示
3.4 初始化示例数据
sql
-- 扩展字段配置
INSERT INTO t_field_config (field_label, field_type, required, options, sort_order, status, create_time) VALUES
('办理意见', 'textarea', 0, NULL, 1, 1, NOW()),
('涉及金额', 'number', 0, NULL, 2, 1, NOW()),
('是否紧急', 'select', 0, '["是","否"]', 3, 1, NOW()),
('预计完成日期', 'date', 0, NULL, 4, 1, NOW()),
('备注信息', 'text', 0, NULL, 5, 1, NOW());
-- 扩展数据(XML存储在BLOB中)
INSERT INTO t_task_ext (task_id, ext_data) VALUES
(1, '<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<办理意见>同意受理,请尽快安排排查</办理意见>
<涉及金额>50000</涉及金额>
<是否紧急>是</是否紧急>
<预计完成日期>2026-06-30</预计完成日期>
<备注信息>需协调消防部门联合检查</备注信息>
</extensions>');
四、后端实现
4.1 技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| Spring Boot | 2.3.12 | Web 框架 |
| MyBatis | 2.1.4 (starter) | ORM 框架 |
| dom4j | 1.6.1 | XML 解析与生成 |
| Lombok | - | 简化实体类 |
| MySQL | 5.7+ / 8.0 | 数据库 |
4.2 项目结构
backend/src/main/java/com/xmlform/demo/
├── XmlFormDemoApplication.java # 启动类
├── common/
│ └── Result.java # 统一响应封装
├── controller/
│ ├── TaskController.java # 任务接口
│ └── FieldConfigController.java # 字段配置接口
├── engine/
│ └── XmlFormConverter.java # 核心:XML ↔ 表单转换引擎
├── entity/
│ ├── Task.java # 任务实体
│ ├── TaskExt.java # 扩展数据实体
│ ├── TaskVO.java # 任务视图对象(含扩展字段)
│ └── FieldConfig.java # 字段配置实体
├── mapper/
│ ├── TaskMapper.java # 任务Mapper
│ ├── TaskExtMapper.java # 扩展数据Mapper
│ └── FieldConfigMapper.java # 字段配置Mapper
└── service/
├── TaskService.java # 任务业务逻辑
└── FieldConfigService.java # 字段配置业务逻辑
4.3 核心引擎:XmlFormConverter
这是整个方案的核心类,负责 XML 与表单数据的双向转换:
java
public class XmlFormConverter {
private static final String ROOT_ELEMENT = "extensions";
/**
* XML字符串 → 表单数据Map
* 将XML中的扩展字段解析为 {字段名: 值} 的Map
*/
public static Map<String, Object> toFormData(String xml, List<FieldConfig> configs) {
Map<String, Object> result = new LinkedHashMap<>();
if (xml == null || xml.trim().isEmpty()) {
return result;
}
try {
SAXReader reader = new SAXReader();
Document doc = reader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
Element root = doc.getRootElement();
for (FieldConfig config : configs) {
Element el = root.element(config.getFieldLabel());
if (el != null) {
String text = el.getTextTrim();
result.put(config.getFieldLabel(), convertType(text, config.getFieldType()));
} else {
result.put(config.getFieldLabel(), null);
}
}
} catch (Exception e) {
log.error("XML解析失败: {}", e.getMessage());
}
return result;
}
/**
* 表单数据Map → XML字符串
* 将表单提交的数据组装为XML格式
*/
public static String toXmlString(Map<String, Object> formData, List<FieldConfig> configs) {
Document doc = DocumentHelper.createDocument();
Element root = doc.addElement(ROOT_ELEMENT);
for (FieldConfig config : configs) {
String label = config.getFieldLabel();
Object value = formData.get(label);
Element el = root.addElement(label);
el.setText(value != null ? String.valueOf(value) : "");
}
return doc.asXML();
}
/**
* 类型转换:将XML中的字符串值转为对应的Java类型
*/
private static Object convertType(String value, String type) {
if (value == null || value.isEmpty()) return null;
switch (type) {
case "number":
try { return Double.parseDouble(value); }
catch (NumberFormatException e) { return value; }
case "date":
default:
return value;
}
}
}
转换流程图:
保存时:前端表单数据(Map) → toXmlString() → XML字符串 → 存入BLOB
查询时:BLOB → XML字符串 → toFormData() → Map → 返回前端渲染
生成的 XML 示例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<办理意见>同意受理,请尽快安排排查</办理意见>
<涉及金额>50000</涉及金额>
<是否紧急>是</是否紧急>
<预计完成日期>2026-06-30</预计完成日期>
<备注信息>需协调消防部门联合检查</备注信息>
</extensions>
4.4 任务业务逻辑:TaskService
java
@Service
@Slf4j
public class TaskService {
private final TaskMapper taskMapper;
private final TaskExtMapper taskExtMapper;
private final FieldConfigMapper fieldConfigMapper;
public TaskService(TaskMapper taskMapper, TaskExtMapper taskExtMapper,
FieldConfigMapper fieldConfigMapper) {
this.taskMapper = taskMapper;
this.taskExtMapper = taskExtMapper;
this.fieldConfigMapper = fieldConfigMapper;
}
/**
* 查询任务详情(核心:固定字段 + 扩展字段合并)
*/
@Transactional
public TaskVO getTaskDetail(Long taskId) {
Task task = taskMapper.selectByPrimaryKey(taskId);
if (task == null) return null;
TaskVO vo = new TaskVO();
// 拷贝固定字段
vo.setTaskId(task.getTaskId());
vo.setTaskName(task.getTaskName());
vo.setCreator(task.getCreator());
vo.setCreateTime(task.getCreateTime());
vo.setTaskStatus(task.getTaskStatus());
vo.setDistrictCode(task.getDistrictCode());
vo.setUpdateTime(task.getUpdateTime());
// 加载扩展字段配置
List<FieldConfig> configs = fieldConfigMapper.selectActive();
TaskExt ext = taskExtMapper.selectByTaskId(taskId);
if (ext != null && ext.getExtData() != null && !configs.isEmpty()) {
// 将BLOB中的XML解析为表单数据
Map<String, Object> formData = XmlFormConverter.toFormData(ext.getExtData(), configs);
List<Map<String, Object>> extFields = new ArrayList<>();
for (FieldConfig config : configs) {
Map<String, Object> fieldMap = new LinkedHashMap<>();
fieldMap.put("fieldLabel", config.getFieldLabel());
fieldMap.put("fieldType", config.getFieldType());
fieldMap.put("required", config.getRequired());
fieldMap.put("options", XmlFormConverter.parseOptions(config.getOptions()));
fieldMap.put("value", formData.getOrDefault(config.getFieldLabel(), ""));
extFields.add(fieldMap);
}
vo.setExtFields(extFields);
} else {
// 无扩展数据时,返回空值模板
List<Map<String, Object>> extFields = new ArrayList<>();
for (FieldConfig config : configs) {
Map<String, Object> fieldMap = new LinkedHashMap<>();
fieldMap.put("fieldLabel", config.getFieldLabel());
fieldMap.put("fieldType", config.getFieldType());
fieldMap.put("required", config.getRequired());
fieldMap.put("options", XmlFormConverter.parseOptions(config.getOptions()));
fieldMap.put("value", "");
extFields.add(fieldMap);
}
vo.setExtFields(extFields);
}
return vo;
}
/**
* 保存任务(固定字段 + 扩展字段)
*/
@Transactional
public void saveTask(Task task, Map<String, Object> extFormData) {
// 1. 保存主表
if (task.getTaskId() == null) {
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
taskMapper.insert(task);
} else {
task.setUpdateTime(new Date());
taskMapper.updateByPrimaryKey(task);
}
// 2. 将扩展字段转为XML存入BLOB
List<FieldConfig> configs = fieldConfigMapper.selectActive();
String xml = XmlFormConverter.toXmlString(extFormData, configs);
// 3. 保存扩展表
TaskExt existing = taskExtMapper.selectByTaskId(task.getTaskId());
if (existing != null) {
existing.setExtData(xml);
taskExtMapper.updateByTaskId(existing);
} else {
TaskExt ext = new TaskExt();
ext.setTaskId(task.getTaskId());
ext.setExtData(xml);
taskExtMapper.insert(ext);
}
}
@Transactional
public void deleteTask(Long taskId) {
taskExtMapper.deleteByTaskId(taskId);
taskMapper.deleteByPrimaryKey(taskId);
}
}
关键设计点:
- 列表查询
listTasks()只查主表 t_task,不加载 BLOB 大字段,保证列表查询性能 - 详情查询
getTaskDetail()才加载扩展数据,将 XML 解析后与字段配置合并返回 - 保存时在事务内同时操作主表和扩展表,保证数据一致性
4.5 统一响应封装
java
@Data
public class Result<T> {
private int code;
private String msg;
private T data;
public static <T> Result<T> ok(T data) {
Result<T> r = new Result<>();
r.setCode(200);
r.setMsg("success");
r.setData(data);
return r;
}
public static <T> Result<T> ok() {
return ok(null);
}
public static <T> Result<T> fail(String msg) {
Result<T> r = new Result<>();
r.setCode(500);
r.setMsg(msg);
return r;
}
}
4.6 RESTful API 设计
| 接口 | 方法 | 说明 |
|---|---|---|
/api/task/list |
GET | 任务列表(不加载扩展数据) |
/api/task/detail/{taskId} |
GET | 任务详情(含解析后的扩展字段) |
/api/task/save |
POST | 保存任务(含扩展字段) |
/api/task/delete/{taskId} |
DELETE | 删除任务 |
/api/field-config/list |
GET | 所有字段配置 |
/api/field-config/active |
GET | 启用中的字段配置 |
/api/field-config/save |
POST | 保存字段配置 |
/api/field-config/delete/{configId} |
DELETE | 删除字段配置 |
五、前端实现
5.1 技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| Vue | 3.x | 前端框架 |
| Element Plus | - | UI 组件库 |
| Axios | - | HTTP 请求 |
5.2 前端项目结构
frontend/src/
├── App.vue # 主布局(Tab切换)
├── api/
│ ├── request.js # Axios封装
│ ├── task.js # 任务API
│ └── fieldConfig.js # 字段配置API
├── components/
│ └── DynamicForm.vue # 核心:动态表单组件
└── views/
├── TaskList.vue # 任务管理页面
└── FieldConfig.vue # 扩展字段配置页面
5.3 核心组件:DynamicForm
这是前端的核心组件,根据字段配置动态生成表单控件:
vue
<template>
<div class="dynamic-form">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="140px">
<template v-for="field in fields" :key="field.fieldLabel">
<el-form-item :label="field.fieldLabel" :prop="field.fieldLabel">
<!-- 文本输入 -->
<el-input
v-if="field.fieldType === 'text' || field.fieldType === 'textarea'"
:type="field.fieldType === 'textarea' ? 'textarea' : 'text'"
v-model="formData[field.fieldLabel]"
:placeholder="'请输入' + field.fieldLabel"
/>
<!-- 数值输入 -->
<el-input-number
v-else-if="field.fieldType === 'number'"
v-model="formData[field.fieldLabel]"
style="width: 100%"
/>
<!-- 日期选择 -->
<el-date-picker
v-else-if="field.fieldType === 'date'"
v-model="formData[field.fieldLabel]"
type="date"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
<!-- 下拉选择 -->
<el-select
v-else-if="field.fieldType === 'select'"
v-model="formData[field.fieldLabel]"
style="width: 100%"
>
<el-option v-for="opt in field.options" :key="opt" :label="opt" :value="opt" />
</el-select>
</el-form-item>
</template>
</el-form>
</div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const props = defineProps({
fields: { type: Array, default: () => [] },
modelValue: { type: Object, default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
const formRef = ref(null)
const formData = ref({})
// 根据字段配置动态生成校验规则
const formRules = computed(() => {
const rules = {}
props.fields.forEach(field => {
if (field.required) {
rules[field.fieldLabel] = [
{ required: true, message: `请输入${field.fieldLabel}`, trigger: 'blur' }
]
}
})
return rules
})
// 双向绑定
watch(() => props.modelValue, (val) => {
if (val) formData.value = { ...val }
}, { immediate: true, deep: true })
watch(() => formData.value, (val) => {
emit('update:modelValue', val)
}, { deep: true })
// 暴露校验方法给父组件
function validate() {
return new Promise((resolve) => {
if (formRef.value) {
formRef.value.validate((valid) => resolve(valid))
} else {
resolve(true)
}
})
}
defineExpose({ validate })
</script>
设计要点:
- 通过
fieldsprop 接收字段配置数组,动态渲染对应类型的控件 - 使用
computed动态生成表单校验规则,必填字段自动添加 required 规则 - 支持
v-model双向绑定,父组件可以直接读写表单数据 - 通过
defineExpose暴露validate()方法,父组件可以触发表单校验
5.4 任务管理页面
任务管理页面将固定字段表单 和动态扩展字段表单组合在一起:
vue
<!-- 新增/编辑对话框 -->
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px">
<!-- 固定字段 -->
<el-divider content-position="left">固定字段</el-divider>
<el-form :model="taskForm" label-width="100px" :rules="fixedRules" ref="fixedFormRef">
<el-form-item label="任务名称" prop="taskName">
<el-input v-model="taskForm.taskName" />
</el-form-item>
<!-- ... 其他固定字段 ... -->
</el-form>
<!-- 扩展字段(动态渲染) -->
<el-divider content-position="left">
扩展字段(XML动态存储)
<el-tag type="info" size="small">存储于BLOB,列表查询不加载</el-tag>
</el-divider>
<DynamicForm
ref="dynamicFormRef"
:fields="fieldConfigs"
v-model="extFormData"
/>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSave">保存</el-button>
</template>
</el-dialog>
保存逻辑:
javascript
async function handleSave() {
// 分别校验固定字段和扩展字段
const fixedValid = await fixedFormRef.value.validate().then(() => true).catch(() => false)
const dynamicValid = await dynamicFormRef.value.validate()
if (!fixedValid || !dynamicValid) return
// 将固定字段和扩展字段合并提交
const payload = {
...taskForm.value,
extFields: { ...extFormData.value }
}
await saveTask(payload)
ElMessage.success('保存成功')
dialogVisible.value = false
loadTasks()
}
5.5 字段配置管理页面
字段配置页面允许管理员在线管理扩展字段:
- 新增字段:输入中文名称、选择类型(text/textarea/number/date/select)、设置是否必填等
- 编辑字段:修改已有字段的配置
- 启用/禁用:通过开关控制字段是否在表单中显示
- 排序:通过排序号控制字段在表单中的显示顺序
六、数据流转全流程
6.1 保存流程
用户填写表单
│
▼
前端收集数据:
{
taskName: "社区安全隐患排查",
creator: "张三",
extFields: {
"办理意见": "同意受理",
"涉及金额": 50000,
"是否紧急": "是",
"预计完成日期": "2026-06-30"
}
}
│
▼
后端 TaskController.save() 接收
│
├──→ taskMapper.insert/update → t_task 表(固定字段)
│
└──→ XmlFormConverter.toXmlString() → 生成 XML
│
▼
<?xml version="1.0" encoding="UTF-8"?>
<extensions>
<办理意见>同意受理</办理意见>
<涉及金额>50000.0</涉及金额>
<是否紧急>是</是否紧急>
<预计完成日期>2026-06-30</预计完成日期>
<备注信息></备注信息>
</extensions>
│
▼
taskExtMapper.insert/update → t_task_ext 表(BLOB)
6.2 查询流程
前端请求 GET /api/task/detail/1
│
▼
后端 TaskService.getTaskDetail(1)
│
├──→ taskMapper.selectByPrimaryKey(1) → 主表固定字段
├──→ fieldConfigMapper.selectActive() → 字段配置列表
└──→ taskExtMapper.selectByTaskId(1) → BLOB 中的 XML
│
▼
XmlFormConverter.toFormData(xml, configs)
│
▼
返回合并后的 TaskVO:
{
taskId: 1,
taskName: "社区安全隐患排查",
extFields: [
{ fieldLabel: "办理意见", fieldType: "textarea", value: "同意受理" },
{ fieldLabel: "涉及金额", fieldType: "number", value: 50000 },
{ fieldLabel: "是否紧急", fieldType: "select", value: "是" },
...
]
}
七、方案优势总结
7.1 核心优势
| 优势 | 说明 |
|---|---|
| 零改库扩展 | 新增业务字段只需在配置表中添加一行记录,无需 ALTER TABLE |
| 列表性能友好 | 扩展数据独立成表,列表查询不加载 BLOB,避免大字段拖慢查询 |
| 在线配置 | 管理员可在页面上直接增删改扩展字段,无需发版 |
| XML可读性好 | 扩展数据以 XML 存储,可读性强,便于与外部系统对接 |
| 类型安全 | 支持多种字段类型(文本、数值、日期、下拉),后端自动类型转换 |
| 表单校验 | 前端根据配置动态生成校验规则,必填字段自动校验 |
7.2 适用场景
- 政务系统中不同地区/部门有差异化采集需求
- 工单/任务系统中不同类型任务需要不同的扩展信息
- 业务字段频繁变化、需要在线配置的场景
- 需要与外部系统通过 XML 格式交换数据的场景
7.3 局限性
- 扩展字段不支持 SQL 条件查询(如按扩展字段筛选排序),因为数据在 BLOB 中
- 字段名(XML标签)使用中文,虽然 dom4j 支持,但在某些场景下可能需要转义处理
- 扩展数据没有独立的字段级权限控制
八、扩展优化方向
- 支持扩展字段条件查询:将高频查询的扩展字段同步到冗余列中
- 字段分组:给 t_field_config 增加分组字段,支持多组扩展字段
- 字段权限:增加角色-字段的权限控制,不同角色看到不同的扩展字段
- 历史版本:记录字段配置的变更历史,保证历史数据的正确解析
- 更多字段类型:支持文件上传、富文本、级联选择等复杂控件
- XML Schema 校验:使用 XSD 对 XML 数据进行严格的格式校验
九、源码地址
完整项目源码包含:
- 后端:Spring Boot + MyBatis + dom4j
- 前端:Vue 3 + Element Plus
- 数据库建表脚本及示例数据
项目结构清晰,共 14 个 Java 文件、4 个 Vue 文件,代码精简易读,适合作为学习参考或项目脚手架。
如果觉得有帮助,欢迎点赞收藏!有问题可以在评论区交流~