智能表单映射填充系统 - 前端实现完全解析
本文档将带你深入理解本系统前端的实现细节,包括如何使用 Vue 3 框架、如何通过组合式 API 组织代码逻辑、以及如何利用响应式数据来驱动动态界面。
1. 前端技术选型与整体结构
1.1 技术方案
本系统前端采用 Vue 3 框架,但为了极致的简洁和零依赖,我们并未使用 Vue CLI 或 Vite 构建工具,而是直接在 index.html 中通过 CDN 引入 Vue 3 的核心库。
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>同时,使用 Axios 库来发送 HTTP 请求与后端 API 通信。
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>所有界面、样式和逻辑都写在一个 HTML 文件中,方便维护和演示。
1.2 页面布局
页面从上到下分为四个步骤卡片(.card),对应系统的四大功能流程:
- 上传源数据:选择数据来源类型(Excel / SQL / URL),上传文件或输入地址,加载预览。
- 目标网页表单:输入目标网页的 URL,解析出表单字段。
- 字段映射:获取智能推荐映射,并以表格形式展示,用户可手动调整。
- 结果导出:填充成功后,提供 Excel / CSV 下载按钮。
每个卡片内部根据当前状态(是否加载、是否出错等)通过 v-if 条件渲染不同的内容。
2. Vue 3 应用初始化与组合式 API
2.1 创建应用实例
页面的所有动态逻辑都挂载在 #app 这个根元素上。
const { createApp, ref, reactive } = Vue;
const app = createApp({
setup() { /* 组件逻辑 */ }
});
app.mount('#app');这里使用了 Vue 3 的全局 API createApp 来创建一个应用,并指定 setup 函数作为组合式 API 的入口。
2.2 组合式 API (setup) 的核心作用
setup 函数是 Vue 3 组合式 API 的起点,它替代了 Vue 2 中的 data、methods、computed 等选项。在 setup 内部,我们可以自由地组织逻辑,使用 ref 和 reactive 来声明响应式数据,并定义各种函数来修改它们。最终,setup 需要返回一个对象,该对象中的所有属性和方法都会暴露给模板使用。
3. 响应式数据管理
Vue 3 提供了两种主要的响应式 API:ref 和 reactive,它们在本系统中承担了不同的角色。
3.1 ref 的使用
ref 用于包装一个基本类型(如字符串、数字、布尔值)或需要整体替换的对象/数组。在模板中访问时,ref 会自动解包 .value,但在 JavaScript 代码中需要显式使用 .value 来读写。
以下是一些在本系统中使用的 ref 示例及其作用:
| 变量名 | 类型 | 作用 |
|---|---|---|
sourceType | ref('excel') | 当前选中的数据来源类型,驱动步骤1的表单切换。 |
sourceUrl | ref('') | 用户输入的源数据网页 URL。 |
selectedFile | ref(null) | 存储用户选择的文件对象(Excel/SQL)。 |
sourceLoading | ref(false) | 标记“加载源数据”按钮是否处于加载中,控制按钮禁用和文字提示。 |
sourceError | ref('') | 存储源数据加载失败时的错误信息,不为空时显示错误框。 |
targetUrl | ref('') | 目标网页 URL。 |
targetLoading | ref(false) | 标记“解析目标表单”按钮是否加载中。 |
targetError | ref('') | 目标表单解析失败时的错误信息。 |
targetFieldsInfo | ref([]) | 存储从后端返回的目标表单字段详情列表(包含 label、name、type、options 等)。 |
recommendLoading | ref(false) | “获取智能推荐映射”按钮的加载状态。 |
mappingPairs | ref([]) | 映射表格的数据源,是一个数组,每个元素是一个对象 {targetName, targetLabel, source, confidence}。 |
fillLoading | ref(false) | “确认填充”按钮的加载状态。 |
fillResult | ref(null) | 存储填充执行后的结果对象(含 success、message、filled_count)。 |
这些 ref 变量就像一个个独立的“开关”或“容器”,它们的任何变化都会立刻反映到界面上。例如,当 sourceLoading 变为 true 时,按钮的文字会自动变为“加载中...”,并且按钮被禁用。
3.2 reactive 的使用
reactive 用于包装一个对象,使其内部的所有属性都变成响应式的。它适合处理一组相互关联的状态。本系统中,sourcePreview 就是一个典型的 reactive 对象:
const sourcePreview = reactive({
columns: [], // 列名数组
sample: [], // 前几行示例数据
shape: [0, 0], // 数据规模 [行数, 列数]
dtypes: {} // 列名到数据类型的映射
});修改 sourcePreview.columns 或 sourcePreview.sample 等属性都会触发界面更新。在模板中可以直接使用 sourcePreview.columns 等属性。
3.3 响应式数据的更新方式
- 对于
ref,通过.value赋值:sourceUrl.value = 'https://...'。 - 对于
reactive,直接修改其属性:sourcePreview.columns = ['A', 'B']。 - 当后端返回新数据时,我们通常使用
Object.assign(sourcePreview, response.data)来一次性更新sourcePreview的多个属性,而不改变对象引用。
4. 核心功能函数与状态驱动
每个步骤的按钮都绑定了一个异步函数,这些函数负责与后端通信、更新状态,界面则根据状态自动渲染。
4.1 loadSourceData() - 加载源数据
async function loadSourceData() {
// 1. 清除旧错误
clearSourceError();
// 2. 前端校验:确保已选择文件或输入URL
if ((sourceType.value === 'excel' || sourceType.value === 'sql') && !selectedFile.value) {
sourceError.value = '请选择文件';
return;
}
// 3. 设置加载状态为 true
sourceLoading.value = true;
try {
// 4. 构建 FormData
const formData = new FormData();
formData.append('type', sourceType.value);
if (sourceType.value === 'excel' || sourceType.value === 'sql') {
formData.append('file', selectedFile.value);
} else {
formData.append('url', sourceUrl.value);
}
// 5. 发送 POST 请求
const response = await axios.post(`${API_BASE}/source/preview`, formData, { headers... });
// 6. 更新 sourcePreview
Object.assign(sourcePreview, response.data);
// 7. 清空旧的映射数据
mappingPairs.value = [];
fillResult.value = null;
} catch (error) {
// 8. 出错时更新错误信息
sourceError.value = error.response?.data?.detail || error.message;
} finally {
// 9. 恢复加载状态
sourceLoading.value = false;
}
}状态流转:校验 → 显示加载 → 请求 → 成功则更新预览 → 失败则显示错误 → 无论成败都恢复加载状态。
4.2 getRecommendation() - 获取映射推荐
async function getRecommendation() {
// 1. 构建请求载荷(payload)
const targetLabels = {};
targetFieldsInfo.value.forEach(f => { targetLabels[f.name] = f.label; });
const payload = {
source_columns: sourcePreview.columns,
source_sample: sourcePreview.sample,
source_types: sourcePreview.dtypes,
target_columns: targetFieldsInfo.value.map(f => f.name),
target_labels: targetLabels,
target_sample: targetSample.value,
target_types: targetTypes.value
};
// 2. 发送请求
const response = await axios.post(`${API_BASE}/mapping/recommend`, payload);
const data = response.data;
// 3. 构建 mappingPairs 数组
const pairs = targetFieldsInfo.value.map(f => ({
targetName: f.name,
targetLabel: f.label,
source: data.recommended_mapping[f.name] || '',
confidence: data.confidence_scores[f.name]
}));
mappingPairs.value = pairs;
}这里的关键是将后端返回的 recommended_mapping 和 confidence_scores 结合目标的字段信息,生成一个包含标签、置信度和下拉框选项的数组,供模板渲染。
4.3 confirmFill() - 执行填充
async function confirmFill() {
// 构建最终映射对象
const finalMapping = {};
mappingPairs.value.forEach(p => {
if (p.source && p.targetName) finalMapping[p.source] = p.targetName;
});
const payload = {
mapping: finalMapping,
target_labels: Object.fromEntries(targetFieldsInfo.value.map(f => [f.name, f.label])),
target_fields_info: targetFieldsInfo.value,
target_url: targetUrl.value,
fill_options: {}
};
const response = await axios.post(`${API_BASE}/fill/execute`, payload);
fillResult.value = response.data;
}填充结果的 fillResult 对象包含 success 和 message 等字段,界面会根据 success 的真假应用不同的样式类。
5. 模板中的响应式绑定与动态渲染
5.1 数据展示与条件渲染
- 显示源数据预览:当
sourcePreview.columns.length为真时,使用v-if显示预览信息。 - 显示目标字段列表:
v-if="targetFieldsInfo.length",用v-for="f in targetFieldsInfo"循环渲染每个字段的标签、名称和类型。 - 显示错误信息:
<div v-if="sourceError" class="error-text"></div>,当sourceError非空时显示。 - 按钮禁用状态:
:disabled="sourceLoading",当sourceLoading为true时禁用按钮,并显示 “加载中...”。
5.2 映射表格的动态行与置信度颜色
映射表格是一个核心交互区域:
<tr v-for="(pair, index) in mappingPairs" :key="index"
:class="getConfidenceClass(pair.confidence)">
<td>{{ pair.targetLabel }} ({{ pair.targetName }})</td>
<td>
<select v-model="pair.source">
<option value="">-- 不映射 --</option>
<option v-for="src in sourcePreview.columns" :key="src" :value="src">{{ src }}</option>
</select>
</td>
<td>{{ pair.confidence ? (pair.confidence * 100).toFixed(0) + '%' : '无' }}</td>
</tr>v-for遍历mappingPairs数组,每一行通过v-model="pair.source"将下拉框的选中值双向绑定到pair.source。当用户修改下拉框时,mappingPairs数组中的对应项会直接更新,这正是响应式系统的魔力。:class="getConfidenceClass(pair.confidence)"根据置信度动态添加背景色类名(高/中/低),getConfidenceClass函数返回对应的CSS类名。
5.3 文件上传元素的清除
当用户切换数据源类型时(如从 Excel 切换到 SQL),需要清空之前选择的文件:
function onSourceTypeChange() {
selectedFile.value = null;
sourceUrl.value = '';
if (fileInput.value) fileInput.value.value = ''; // 清空 input[type=file] 的显示
}通过模板引用 ref="fileInput" 获取真实的 DOM 元素,直接将其 value 置空,从而重置文件选择框。
6. 与后端的通信 (Axios)
所有 API 请求都使用 axios 库,基地址统一为:
const API_BASE = 'http://localhost:8000/api';根据接口需求,请求分为几种形式:
- GET 请求:用于获取目标表单结构或导出文件,参数通过
params传递。 - POST 请求:上传文件时使用
FormData,并设置Content-Type: multipart/form-data;其他数据提交则直接发送 JSON 对象。 - blob 响应:导出功能需要下载文件,设置
responseType: 'blob',再创建临时链接触发下载。
7. 总结
本系统前端虽然只有一个 HTML 文件,但它充分利用了 Vue 3 的组合式 API 和响应式系统,实现了复杂的状态管理和交互。核心思想可以概括为:
- 状态驱动界面:将所有界面变化归结为对
ref和reactive变量的修改。 - 单向数据流:用户操作 → 调用函数 → 修改状态 → 界面自动更新。
- 组合式 API 组织逻辑:一个
setup函数包含所有逻辑,相关状态和函数集中在一起,易于理解和维护。 - 清晰的生命周期:每次操作都遵循“开始加载 → 请求 → 成功/失败处理 → 结束加载”的通用模式。
通过这份文档,你可以完全掌握本系统前端的实现方式,并能够自主修改界面样式、调整交互逻辑或新增功能。