Skip to content

完成更新业务与完成删除业务(Day3 2/2) ​

本页对应 day3,共 6 段视频,约 1 小时 49 分。

学习目标 ​

  1. 能使用 Element Plus 表单的 validator 自定义校验规则,完成品牌名称至少两位汉字、品牌 Logo 地址非空等校验,并返回 Error 提示信息。
  2. 能通过 formRef.validate() 在提交前触发整体表单校验,并根据返回 Promise 的成功或失败结果控制后续请求是否执行。
  3. 能在打开添加/更新对话框前或图片上传成功后,使用 resetFields、clearValidate 清除指定字段的校验结果,并能用 nextTick 或可选链处理组件未渲染问题。
  4. 能根据表格行数据回填更新表单,依据是否存在 ID 区分添加与更新,动态设置对话框标题、成功/失败提示及更新后的分页页码。
  5. 能调用删除品牌接口完成删除业务,使用 ElMessageBox.confirm 进行二次确认,删除成功后给出提示并刷新当前页品牌数据。
  6. 能引入 xlsx 插件,将品牌列表 JSON 数据转换为工作表数据并导出为 Excel 文件。

知识点 ​

更新业务的目标与前置认知 ​

更新业务要解决的问题是:表格里已经有一条品牌数据,点击“修改”后,把这条数据回填到弹窗表单里,用户改完再提交,后端根据品牌 ID 完成更新。转写稿里反复强调“不要只看页面,要盯数据”,因为页面展示只是结果,真正决定更新是否成功的是:row 里的已有数据有没有被正确复制到表单对象 tradeMark 中,以及提交时有没有带上 id。接口层面,更新通常是 PUT 请求,需要携带品牌 id、品牌名称和品牌 logo 地址。

先回填,再提交:为什么需要 row ​

为什么需要:点击表格某一行的“修改”按钮时,必须知道“改的是哪一条”。这个信息就在表格当前行的 row 里。
怎么想:row 是当前品牌对象,里面包含 id、tmName、logoURL 等字段。弹窗表单绑定的 tradeMark 初始是空对象,所以点击修改后要先把 row 的值复制给 tradeMark。
怎么做:在修改按钮回调里接收 row,先打印确认,再赋值。转写稿中调试时就是先 console.log(row),确认拿到已有品牌数据,再复制给 tradeMark。关键片段按转写稿整理如下:

js
// 点击“修改”时回填已有品牌数据
const updateTradeMark = (row) => {
  tradeMark.id = row.id
  tradeMark.tmName = row.tmName
  tradeMark.logoURL = row.logoURL
  dialogVisible.value = true
}

易错点:只复制了名称和 logo,忘记复制 id。名称和 logo 能让页面看起来“有数据”,但提交更新时没有 id,后端就不知道更新谁。转写稿里特别提醒:id 虽然不展示,但将来更新必须用。

复用添加弹窗:标题要动态 ​

为什么需要:添加和更新往往共用一个弹窗。如果标题写死“添加品牌”,用户点修改时还显示“添加品牌”,体验和语义都不对。
怎么想:已有品牌有 id,新增品牌没有 id。所以可以用 tradeMark.id 是否存在来判断当前是更新还是添加。
怎么做:弹窗标题写成动态表达式:

html
:title="tradeMark.id ? '更新品牌' : '添加品牌'"

提交成功后的提示也要分情况:有 id 提示“更新成功/更新失败”,没有 id 提示“添加成功/添加失败”。转写稿中一开始提示写死为“添加成功”,调试后发现更新时也提示添加,属于状态没区分。

易错点:只改标题不改提示;或者判断条件用错,把 id 为 0 当成有值。转写稿中清空时把 id 设为 0,所以判断“是否有 ID”要结合业务约定,不能把字符串空值随便塞进去,因为接口需要 Number 类型。

清空数据时要把 id 一起清掉 ​

为什么需要:弹窗关闭后如果只清了名称和 logo,没有清 id,下一次点“添加品牌”时,tradeMark.id 还保留上一次修改的 ID,提交时就会被当成更新。
怎么想:转写稿中的调试现象是:点修改后再关闭,再点添加,弹窗标题仍可能变成“更新品牌”,因为 id 没清。
怎么做:清空函数里同时清 id、tmName、logoURL,id 按接口要求设为 0 或符合类型的空值:

js
tradeMark.id = 0
tradeMark.tmName = ''
tradeMark.logoURL = ''

易错点:给 id 赋空字符串导致类型飘红;或者只清展示字段,不清隐藏的 id。

更新后刷新列表:添加回第一页,更新留当前页 ​

为什么需要:添加成功后通常回第一页看最新数据;更新成功后用户希望留在当前页,而不是被强行拉回第一页。
怎么想:刷新列表函数 getTradeMark 接收页码参数。提交时根据 tradeMark.id 是否存在分流:更新传当前页,添加传 1。
怎么做:关键逻辑可整理为:

js
// 伪代码:按转写稿逻辑整理
getTradeMark(tradeMark.id ? 当前页 : 1)

易错点:刷新列表不传页码,默认永远第一页;更新后页码丢失,用户还要重新翻页。

提交与校验的衔接 ​

更新弹窗复用了添加表单,所以表单校验规则、图片上传等非表单组件的校验也要保持一致。转写稿中提到,普通表单元素可靠 change、blur 触发校验;图片上传这类非表单组件无法自然触发时,要通过表单实例的 validate() 在点击确定、发请求前统一校验。只有校验通过才发更新请求,失败则中断,不关闭弹窗、不提示成功。清空校验结果时可用 resetFields 或 clearValidate,但要注意调用时机和表单实例是否已渲染。

易错点清单 ​

  1. 点击修改只打印 row 不回填,弹窗仍是空数据。
  2. 回填时漏掉 id,导致更新接口缺少主键。
  3. 标题、成功提示写死为“添加”,更新时语义错误。
  4. 关闭弹窗只清名称和 logo,不清 id,下次添加被误判为更新。
  5. 刷新列表永远回第一页,更新后没有留在当前页。
  6. 提交前没有统一校验,图片等非表单组件校验未通过也发请求。

本节笔记未附图片,故未插入图片;以上代码为转写稿中明确出现的关键逻辑整理。

先看接口:删除必须带品牌 ID ​

为什么需要:删除不是“把页面这一行删掉”这么简单,真正要删的是数据库里的那条品牌数据,因此请求必须明确告诉后端删谁。
怎么想:转写稿里先手动验证接口,找到“删除品牌”的请求,发现它需要传 id,并且返回数据为空,所以前端调用时重点是把当前行 row.id 传过去。
怎么做:在 API 文件中补删除方法,函数参数用 number,不要写成字符串。示意代码如下,路径按项目实际接口填写:

ts
export function reqDeleteById(id: number) {
  return request.delete(`/product/trademark/${id}`)
}

易错点:不要凭感觉写请求方法,删除一般是 DELETE;不要把 id 拼错或传成 row 整个对象;返回值为空时不要硬接一个不存在的数据结构。

用 Popconfirm 做删除确认 ​

为什么需要:删除是不可逆操作,用户点“删除”时不能立刻发请求,必须先问一句“确定要删除吗”。
怎么想:转写稿中使用的是 Element Plus 的 Popconfirm 气泡确认框,它适合放在表格操作列里。标题可以动态显示品牌名,让用户确认自己删的是哪一条。
怎么做:在表格最后一列的操作列中包一层 el-popconfirm,把真正的删除按钮放到 #reference 插槽里,在 @confirm 时调用删除方法:

vue
<el-table-column label="操作" width="200">
  <template #default="{ row }">
    <el-popconfirm
      :title="`确定要删除${row.tmName}吗?`"
      @confirm="deleteTradeMark(row.id)"
    >
      <template #reference>
        <el-button type="danger" :icon="Delete">删除</el-button>
      </template>
    </el-popconfirm>
  </template>
</el-table-column>

易错点:@confirm 才是用户点“确定”的事件,不要误写成普通按钮的 @click;按钮文字、图标颜色可以调,但核心是确认后才发请求;row.id 的类型要与接口要求一致。

删除方法:请求成功后再刷新列表 ​

为什么需要:只发删除请求,页面上的表格不会自动少一行;只前端删掉,数据库又没变。正确顺序是“请求成功 → 提示成功 → 重新获取列表”。
怎么想:转写稿中调试时先打印 id,确认参数有值,再发请求;请求成功后调用获取品牌列表的方法。删除后应停留在当前页,所以刷新时要传当前页码,而不是永远回第一页。
怎么做:

ts
async function deleteTradeMark(id: number) {
  try {
    await reqDeleteById(id)
    ElMessage.success('删除成功')
    await getTradeMark(page.value)
  } catch (e) {
    ElMessage.error('删除失败')
  }
}

易错点:不要先刷新再删除;失败时不要提示“删除成功”;刷新列表别忘了传当前页;如果接口报错,先看 Network 里请求地址和 id 是否正确。

调试顺序:先手动请求,再点按钮打印 ​

为什么需要:删除业务看起来简单,但错误常出在“接口没通”“参数没传”“刷新没执行”三处,盲目改页面效率低。
怎么想:转写稿的调试过程是先找接口、手动删一条数据,确认后端接口可用;再回到页面,在删除回调里打印 row.id;最后观察请求和列表变化。
怎么做:第一步在接口工具里验证删除;第二步在 @confirm 中打印 row.id;第三步看浏览器 Network 的请求方法、URL、参数;第四步看删除成功后 getTradeMark 是否带着当前页码执行。
易错点:不要只盯着页面提示,要盯请求和响应;如果列表没变,先确认刷新函数有没有执行,再确认刷新参数是不是当前页。

提示与当前页:删除完成后的收尾 ​

为什么需要:用户需要知道操作结果;同时删除后如果直接回第一页,会打断用户操作。
怎么想:转写稿中删除成功后用 ElMessage.success,失败用 ElMessage.error;重新获取列表时保留当前页。
怎么做:成功和失败分开提示;刷新列表时传 page.value;如果项目里删除后总数变化导致当前页为空,再按业务决定是否回退页码。
易错点:不要所有情况都提示成功;不要把“删除失败”的异常吞掉;刷新列表时不要漏掉当前页参数。这样删除业务才算闭环:确认、请求、提示、刷新、停留当前页。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>品牌表单自定义校验</title>
  <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
</head>
<body>
<div id="app">
  <el-button type="primary" @click="openDialog">打开品牌表单</el-button>

  <el-dialog v-model="dialogVisible" :title="form.id ? '更新品牌' : '添加品牌'" width="500px">
    <el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
      <el-form-item label="品牌名称" prop="tmName">
        <el-input v-model="form.tmName" placeholder="请输入品牌名称" />
      </el-form-item>

      <el-form-item label="品牌LOGO" prop="logoURL">
        <el-upload
          action="#"
          :auto-upload="false"
          :show-file-list="false"
          :on-change="handleLogoChange"
        >
          <el-button type="primary">选择图片</el-button>
        </el-upload>
        <div v-if="form.logoURL" style="margin-top:8px;color:#67c23a">
          已选择:{{ form.logoURL }}
        </div>
      </el-form-item>
    </el-form>

    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handleConfirm">确定</el-button>
    </template>
  </el-dialog>
</div>

<script>
const { createApp, ref, reactive, nextTick } = Vue;
const { ElMessage } = ElementPlus;

createApp({
  setup() {
    const dialogVisible = ref(false);
    const formRef = ref(null);
    const form = reactive({
      id: 0,
      tmName: '',
      logoURL: ''
    });

    const rules = {
      tmName: [
        { required: true, message: '品牌名称不能为空', trigger: 'blur' },
        {
          validator: (rule, value, callback) => {
            // 自定义规则:至少两位汉字
            if (/^[\u4e00-\u9fa5]{2,}$/.test(value)) {
              callback();
            } else {
              callback(new Error('品牌名称至少两位汉字'));
            }
          },
          trigger: 'blur'
        }
      ],
      logoURL: [
        {
          validator: (rule, value, callback) => {
            if (value) {
              callback();
            } else {
              callback(new Error('请上传品牌LOGO'));
            }
          },
          trigger: 'change'
        }
      ]
    };

    const clearFormValidate = () => {
      nextTick(() => {
        formRef.value?.clearValidate();
      });
    };

    const openDialog = () => {
      form.id = 0;
      form.tmName = '';
      form.logoURL = '';
      dialogVisible.value = true;
      clearFormValidate();
    };

    const handleLogoChange = (file) => {
      // 模拟上传成功后的地址
      form.logoURL = file.name || 'mock-logo-url';
      nextTick(() => {
        formRef.value?.clearValidate('logoURL');
      });
    };

    const handleConfirm = async () => {
      try {
        // validate 返回 Promise,校验失败会 reject,必须 catch/await 处理
        await formRef.value.validate();
        ElMessage.success('校验通过');
        dialogVisible.value = false;
      } catch (e) {
        // 校验失败,不提交
      }
    };

    return {
      dialogVisible,
      formRef,
      form,
      rules,
      openDialog,
      handleLogoChange,
      handleConfirm
    };
  }
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>

要点解释关键行和易错点:

  • validator: (rule, value, callback) 是自定义校验核心,必须调用 callback() 放行,或 callback(new Error('提示')) 返回错误。
  • 品牌名称用 /^[\u4e00-\u9fa5]{2,}$/ 限制至少两位汉字。
  • el-upload 不是普通表单元素,不能依赖 blur/change 自动触发校验,所以确定时统一 formRef.value.validate()。
  • validate() 返回 Promise,校验失败会 reject,必须 try/catch 或 .catch,否则控制台会出现未处理异常。
  • 打开弹窗时用 nextTick 后 clearValidate() 清除上一次校验结果。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>品牌更新业务</title>
  <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
</head>
<body>
<div id="app">
  <el-button type="primary" @click="handleAdd">添加品牌</el-button>

  <el-table :data="tableData" border style="margin-top:12px">
    <el-table-column prop="id" label="ID" width="80" />
    <el-table-column prop="tmName" label="品牌名称" />
    <el-table-column prop="logoURL" label="LOGO地址" />
    <el-table-column label="操作" width="160">
      <template #default="{ row }">
        <el-button type="primary" link @click="handleUpdate(row)">更新</el-button>
      </template>
    </el-table-column>
  </el-table>

  <el-pagination
    v-model:current-page="page"
    :page-size="5"
    :total="total"
    layout="prev, pager, next"
    style="margin-top:12px"
    @current-change="loadData"
  />

  <el-dialog v-model="dialogVisible" :title="form.id ? '更新品牌' : '添加品牌'" width="500px">
    <el-form ref="formRef" :model="form" :rules="rules" label-width="90px">
      <el-form-item label="品牌名称" prop="tmName">
        <el-input v-model="form.tmName" />
      </el-form-item>
      <el-form-item label="LOGO地址" prop="logoURL">
        <el-input v-model="form.logoURL" />
      </el-form-item>
    </el-form>

    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handleConfirm">确定</el-button>
    </template>
  </el-dialog>
</div>

<script>
const { createApp, ref, reactive, nextTick } = Vue;
const { ElMessage } = ElementPlus;

createApp({
  setup() {
    const page = ref(1);
    const total = ref(0);
    const tableData = ref([]);
    const dialogVisible = ref(false);
    const formRef = ref(null);

    const form = reactive({
      id: 0,
      tmName: '',
      logoURL: ''
    });

    const rules = {
      tmName: [{ required: true, message: '品牌名称不能为空', trigger: 'blur' }],
      logoURL: [{ required: true, message: 'LOGO地址不能为空', trigger: 'blur' }]
    };

    // 模拟数据库数据
    const db = ref(
      Array.from({ length: 12 }, (_, i) => ({
        id: i + 1,
        tmName: `品牌${i + 1}`,
        logoURL: `logo-${i + 1}.png`
      }))
    );

    const loadData = () => {
      const start = (page.value - 1) * 5;
      tableData.value = db.value.slice(start, start + 5);
      total.value = db.value.length;
    };

    const resetForm = () => {
      form.id = 0; // 易错:必须重置为 number 0,否则下次会被误判为更新
      form.tmName = '';
      form.logoURL = '';
      nextTick(() => {
        formRef.value?.clearValidate();
      });
    };

    const handleAdd = () => {
      resetForm();
      dialogVisible.value = true;
    };

    const handleUpdate = (row) => {
      // 复制行数据,不要直接引用表格行对象
      form.id = row.id;
      form.tmName = row.tmName;
      form.logoURL = row.logoURL;
      dialogVisible.value = true;
      nextTick(() => {
        formRef.value?.clearValidate();
      });
    };

    const handleConfirm = async () => {
      try {
        await formRef.value.validate();

        // 根据 id 判断添加还是更新
        const isUpdate = !!form.id;

        // 模拟接口请求
        await new Promise((resolve) => setTimeout(resolve, 300));

        if (isUpdate) {
          const index = db.value.findIndex((item) => item.id === form.id);
          if (index > -1) {
            db.value[index] = {
              id: form.id,
              tmName: form.tmName,
              logoURL: form.logoURL
            };
          }
          ElMessage.success('更新成功');
          // 更新后留在当前页,不要强制回第一页
        } else {
          db.value.push({
            id: Date.now(),
            tmName: form.tmName,
            logoURL: form.logoURL
          });
          ElMessage.success('添加成功');
          page.value = 1; // 添加后回第一页
        }

        dialogVisible.value = false;
        loadData();
      } catch (e) {
        // 校验失败或请求失败
      }
    };

    loadData();

    return {
      page,
      total,
      tableData,
      dialogVisible,
      formRef,
      form,
      rules,
      handleAdd,
      handleUpdate,
      handleConfirm,
      loadData
    };
  }
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>

要点解释关键行和易错点:

  • 点击“更新”时,把当前行 row 的 id/tmName/logoURL 复制到表单,用于回显和后续提交。
  • 添加/更新的判断依据是 form.id 是否有值,弹窗标题也根据 form.id 动态显示“更新品牌/添加品牌”。
  • 清空表单时 form.id = 0 必须重置,否则下一次添加会被误判成更新。
  • 更新成功后提示“更新成功”,并保持当前页;添加成功后提示“添加成功”,并回到第一页。
  • 不要直接把 row 对象赋给 form,否则修改表单可能影响表格行数据。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>品牌删除业务</title>
  <link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css" />
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://unpkg.com/element-plus/dist/index.full.js"></script>
</head>
<body>
<div id="app">
  <el-table :data="tableData" border>
    <el-table-column prop="id" label="ID" width="80" />
    <el-table-column prop="tmName" label="品牌名称" />
    <el-table-column label="操作" width="180">
      <template #default="{ row }">
        <el-popconfirm
          :title="`确定要删除${row.tmName}吗?`"
          confirm-button-text="确定"
          cancel-button-text="取消"
          @confirm="handleDelete(row.id)"
        >
          <template #reference>
            <el-button type="danger" link>删除</el-button>
          </template>
        </el-popconfirm>
      </template>
    </el-table-column>
  </el-table>

  <el-pagination
    v-model:current-page="page"
    :page-size="5"
    :total="total"
    layout="prev, pager, next"
    style="margin-top:12px"
    @current-change="loadData"
  />
</div>

<script>
const { createApp, ref } = Vue;
const { ElMessage } = ElementPlus;

createApp({
  setup() {
    const page = ref(1);
    const total = ref(0);
    const tableData = ref([]);

    const db = ref(
      Array.from({ length: 12 }, (_, i) => ({
        id: i + 1,
        tmName: `品牌${i + 1}`
      }))
    );

    const loadData = () => {
      const start = (page.value - 1) * 5;
      tableData.value = db.value.slice(start, start + 5);
      total.value = db.value.length;

      // 删除最后一页最后一条后,当前页可能超出最大页数,需要修正
      const maxPage = Math.max(1, Math.ceil(total.value / 5));
      if (page.value > maxPage) {
        page.value = maxPage;
        loadData();
      }
    };

    // 模拟删除接口:删除品牌,参数为 id
    const reqDeleteById = async (id) => {
      await new Promise((resolve) => setTimeout(resolve, 300));
      const index = db.value.findIndex((item) => item.id === id);
      if (index === -1) {
        throw new Error('删除失败');
      }
      db.value.splice(index, 1);
    };

    const handleDelete = async (id) => {
      try {
        await reqDeleteById(id);
        ElMessage.success('删除成功');
        // 删除后重新获取当前页,不要固定回第一页
        loadData();
      } catch (e) {
        ElMessage.error('删除失败');
      }
    };

    loadData();

    return {
      page,
      total,
      tableData,
      loadData,
      handleDelete
    };
  }
}).use(ElementPlus).mount('#app');
</script>
</body>
</html>

要点解释关键行和易错点:

  • 删除操作使用 el-popconfirm,只有点击“确定”触发 @confirm 后才发请求。
  • 删除接口需要带品牌 id,即 handleDelete(row.id)。
  • 删除成功后调用 ElMessage.success('删除成功'),失败调用 ElMessage.error('删除失败')。
  • 删除后要重新获取数据,且应保留当前页,不要写死回第一页。
  • 删除最后一页最后一条时,当前页可能超出最大页数,需要修正页码,否则会出现空页。

常见错误 ​

  1. 现象:点“修改”后弹窗能打开,但表单为空或仍显示上一次数据。→ 原因:只打开了弹窗,没有把当前行数据回填到表单模型。→ 修法:在打开弹窗前将 row 的 id/name/logoUrl 同步到表单对象,再展示弹窗。

  2. 现象:修改完成后关闭弹窗,再点“添加品牌”,标题和提交行为仍像“更新”。→ 原因:关闭时只清了名称、图片,没有清 id;后续逻辑仍按有 id 走更新分支。→ 修法:关闭/初始化弹窗时同时重置 id(数值型给 0 或 undefined),并清空表单字段。

  3. 现象:更新成功提示写成“添加成功”,失败提示也混用。→ 原因:成功/失败回调里没有按 id 区分添加与更新。→ 修法:提交前根据 id 是否存在分支,成功提示分别用“添加成功/更新成功”,失败提示分别用“添加失败/更新失败”。

  4. 现象:在第 5、6 页更新一条数据后,列表跳回第 1 页。→ 原因:更新后重新拉取列表时没有传当前页码,调用了默认第一页。→ 修法:添加后回第 1 页;更新后传当前页 page,刷新后留在当前页。

  5. 现象:删除确认框点“确定”没反应,或删除后列表数量没变。→ 原因:el-popconfirm 的确认事件未绑定,或删除成功后未重新获取列表。→ 修法:在确认事件中传当前行 id 调删除接口,成功后按当前页重新拉取列表并给出成功/失败提示。

  6. 现象:删除接口报错或删错数据。→ 原因:请求地址未正确拼接 id,或参数类型/传参位置不对。→ 修法:按接口文档拼接 id,确认参数类型为 number,删除方法接收 id 后直接传入请求。

  7. 现象:图片上传等非表单组件校验不触发,或弹窗关闭后旧校验红字仍保留。→ 原因:非表单组件没有 change/blur 触发条件;校验结果未在合适时机清除。→ 修法:用 formRef.validate() 在提交前统一校验;在打开弹窗前或上传成功后按字段清理校验结果。

练习 ​

  1. 完成“更新品牌”业务:点击表格行“修改”后回填名称、图片、id,弹窗标题根据 id 动态显示“添加品牌/更新品牌”。

  2. 完成更新后的提示与分页:添加成功回第 1 页;更新成功留在当前页;成功/失败提示按添加或更新分别显示。

  3. 完成“删除品牌”业务:使用 el-popconfirm 做二次确认,确认后按行 id 调删除接口,成功后按当前页刷新列表并提示。

  4. 修复弹窗状态残留:关闭弹窗后清空表单字段和 id,再次打开“添加品牌”时不能带入上一次更新数据。

  5. 拓展练习:将当前品牌列表导出为 Excel,要求先转换数据格式,再创建容器写入数据并导出文件。

原视频对照 ​

#视频时长
1表单验证之自定义校验规则-上14:32
2表单校验之自定义校验规则-下19:30
3完成表单校验结果清除30:25
4完成品牌管理模块更新业务19:26
5完成删除已有品牌的业务11:43
6导出excel数据拓展13:54

小结 ​

  1. 更新业务的核心不是“再写一个表单”,而是回填当前行数据并用 id 区分添加与更新。

  2. 弹窗关闭时必须复位表单字段和 id,否则标题、提交分支、提示文案都会串。

  3. 添加后回第 1 页,更新后留当前页;分页参数要在刷新列表时显式传递。

  4. 删除业务要抓住三件事:二次确认、传对 id、成功后刷新当前页。

  5. 成功/失败提示要按业务分支区分,不能所有提交都写“添加成功”。

  6. 非表单组件的校验不要依赖 change/blur,提交前用表单实例统一校验更稳。

  7. 导出 Excel 属于拓展能力,按“数据转换→创建容器→写入数据→导出文件”落地,并注意依赖安装与文档查阅。