Skip to content

智能表单映射填充系统 - 前端实现完全解析

字数
2715 字
阅读时间
12 分钟

本文档将带你深入理解本系统前端的实现细节,包括如何使用 Vue 3 框架、如何通过组合式 API 组织代码逻辑、以及如何利用响应式数据来驱动动态界面。

1. 前端技术选型与整体结构

1.1 技术方案

本系统前端采用 Vue 3 框架,但为了极致的简洁和零依赖,我们并未使用 Vue CLI 或 Vite 构建工具,而是直接在 index.html 中通过 CDN 引入 Vue 3 的核心库。

html
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

同时,使用 Axios 库来发送 HTTP 请求与后端 API 通信。

html
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

所有界面、样式和逻辑都写在一个 HTML 文件中,方便维护和演示。

1.2 页面布局

页面从上到下分为四个步骤卡片(.card),对应系统的四大功能流程:

  1. 上传源数据:选择数据来源类型(Excel / SQL / URL),上传文件或输入地址,加载预览。
  2. 目标网页表单:输入目标网页的 URL,解析出表单字段。
  3. 字段映射:获取智能推荐映射,并以表格形式展示,用户可手动调整。
  4. 结果导出:填充成功后,提供 Excel / CSV 下载按钮。

每个卡片内部根据当前状态(是否加载、是否出错等)通过 v-if 条件渲染不同的内容。

2. Vue 3 应用初始化与组合式 API

2.1 创建应用实例

页面的所有动态逻辑都挂载在 #app 这个根元素上。

javascript
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 中的 datamethodscomputed 等选项。在 setup 内部,我们可以自由地组织逻辑,使用 refreactive 来声明响应式数据,并定义各种函数来修改它们。最终,setup 需要返回一个对象,该对象中的所有属性和方法都会暴露给模板使用。

3. 响应式数据管理

Vue 3 提供了两种主要的响应式 API:refreactive,它们在本系统中承担了不同的角色。

3.1 ref 的使用

ref 用于包装一个基本类型(如字符串、数字、布尔值)或需要整体替换的对象/数组。在模板中访问时,ref 会自动解包 .value,但在 JavaScript 代码中需要显式使用 .value 来读写。

以下是一些在本系统中使用的 ref 示例及其作用:

变量名类型作用
sourceTyperef('excel')当前选中的数据来源类型,驱动步骤1的表单切换。
sourceUrlref('')用户输入的源数据网页 URL。
selectedFileref(null)存储用户选择的文件对象(Excel/SQL)。
sourceLoadingref(false)标记“加载源数据”按钮是否处于加载中,控制按钮禁用和文字提示。
sourceErrorref('')存储源数据加载失败时的错误信息,不为空时显示错误框。
targetUrlref('')目标网页 URL。
targetLoadingref(false)标记“解析目标表单”按钮是否加载中。
targetErrorref('')目标表单解析失败时的错误信息。
targetFieldsInforef([])存储从后端返回的目标表单字段详情列表(包含 label、name、type、options 等)。
recommendLoadingref(false)“获取智能推荐映射”按钮的加载状态。
mappingPairsref([])映射表格的数据源,是一个数组,每个元素是一个对象 {targetName, targetLabel, source, confidence}
fillLoadingref(false)“确认填充”按钮的加载状态。
fillResultref(null)存储填充执行后的结果对象(含 success、message、filled_count)。

这些 ref 变量就像一个个独立的“开关”或“容器”,它们的任何变化都会立刻反映到界面上。例如,当 sourceLoading 变为 true 时,按钮的文字会自动变为“加载中...”,并且按钮被禁用。

3.2 reactive 的使用

reactive 用于包装一个对象,使其内部的所有属性都变成响应式的。它适合处理一组相互关联的状态。本系统中,sourcePreview 就是一个典型的 reactive 对象:

javascript
const sourcePreview = reactive({
  columns: [],    // 列名数组
  sample: [],     // 前几行示例数据
  shape: [0, 0],  // 数据规模 [行数, 列数]
  dtypes: {}      // 列名到数据类型的映射
});

修改 sourcePreview.columnssourcePreview.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() - 加载源数据

javascript
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() - 获取映射推荐

javascript
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_mappingconfidence_scores 结合目标的字段信息,生成一个包含标签、置信度和下拉框选项的数组,供模板渲染。

4.3 confirmFill() - 执行填充

javascript
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 对象包含 successmessage 等字段,界面会根据 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",当 sourceLoadingtrue 时禁用按钮,并显示 “加载中...”。

5.2 映射表格的动态行与置信度颜色

映射表格是一个核心交互区域:

html
<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),需要清空之前选择的文件:

javascript
function onSourceTypeChange() {
  selectedFile.value = null;
  sourceUrl.value = '';
  if (fileInput.value) fileInput.value.value = ''; // 清空 input[type=file] 的显示
}

通过模板引用 ref="fileInput" 获取真实的 DOM 元素,直接将其 value 置空,从而重置文件选择框。

6. 与后端的通信 (Axios)

所有 API 请求都使用 axios 库,基地址统一为:

javascript
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 和响应式系统,实现了复杂的状态管理和交互。核心思想可以概括为:

  1. 状态驱动界面:将所有界面变化归结为对 refreactive 变量的修改。
  2. 单向数据流:用户操作 → 调用函数 → 修改状态 → 界面自动更新。
  3. 组合式 API 组织逻辑:一个 setup 函数包含所有逻辑,相关状态和函数集中在一起,易于理解和维护。
  4. 清晰的生命周期:每次操作都遵循“开始加载 → 请求 → 成功/失败处理 → 结束加载”的通用模式。

通过这份文档,你可以完全掌握本系统前端的实现方式,并能够自主修改界面样式、调整交互逻辑或新增功能。

贡献者

The avatar of contributor named as freeway348 freeway348

文件历史

撰写