Vue3 + TypeScript:后端数据在表格内渲染后进行增删改的完整实现步骤

一、环境准备与项目搭建

本文假设你已经有一个可用的 Vue3 + TypeScript 项目。如果还没有,可以通过 Vite 脚手架快速创建:

bash 复制代码
npm create vite@latest my-project -- --template vue-ts
cd my-project
npm install

接下来安装必要的依赖。本文以 Element Plus 作为 UI 组件库,Axios 作为 HTTP 请求库:

bash 复制代码
npm install element-plus axios

main.ts 中全局注册 Element Plus:

typescript 复制代码
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

二、定义 TypeScript 接口

src/types 目录下新建 user.ts(以用户管理为例),定义数据实体的类型:

typescript 复制代码
export interface User {
  id: number
  name: string
  email: string
  role: string
  status: number
  createTime: string
}

// 分页请求参数
export interface UserQueryParams {
  page: number
  pageSize: number
  keyword?: string
}

// 分页响应结构
export interface PageResult<T> {
  list: T[]
  total: number
  page: number
  pageSize: number
}

这一步很重要:TypeScript 接口不仅让代码有智能提示,还能在后续的增删改操作中对请求体和响应体做类型约束,减少低级错误。

三、封装 Axios 请求层

src/api 目录下创建 request.ts,封装统一的请求实例:

typescript 复制代码
import axios from 'axios'
import { ElMessage } from 'element-plus'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000,
})

// 响应拦截器:统一处理错误
request.interceptors.response.use(
  (response) => {
    const { code, data, message } = response.data
    if (code === 200) {
      return data
    }
    ElMessage.error(message || '请求失败')
    return Promise.reject(new Error(message))
  },
  (error) => {
    ElMessage.error(error.message || '网络异常')
    return Promise.reject(error)
  }
)

export default request

再创建 src/api/user.ts,封装与用户表相关的所有接口:

typescript 复制代码
import request from './request'
import type { User, UserQueryParams, PageResult } from '@/types/user'

// 分页查询用户列表
export function getUserList(params: UserQueryParams) {
  return request.get<PageResult<User>>('/users', { params })
}

// 新增用户
export function addUser(data: Omit<User, 'id' | 'createTime'>) {
  return request.post<User>('/users', data)
}

// 编辑用户
export function updateUser(data: Partial<User> & { id: number }) {
  return request.put<User>(`/users/${data.id}`, data)
}

// 删除用户
export function deleteUser(id: number) {
  return request.delete<void>(`/users/${id}`)
}

这里用到了 TypeScript 的 OmitPartial 工具类型:新增时不需要传 idcreateTime,编辑时所有字段可选但 id 必传。这种类型约束能有效防止字段遗漏或传错。

四、在组件中获取数据并渲染表格

src/views 下创建 UserList.vue,先实现基础的数据获取和表格渲染:

vue 复制代码
<template>
  <div class="user-container">
    <!-- 搜索栏 -->
    <div class="search-bar">
      <el-input
        v-model="queryParams.keyword"
        placeholder="请输入姓名或邮箱搜索"
        clearable
        style="width: 240px"
        @keyup.enter="fetchData"
      />
      <el-button type="primary" @click="fetchData">搜索</el-button>
      <el-button type="success" @click="openAddDialog">新增用户</el-button>
    </div>

    <!-- 数据表格 -->
    <el-table
      :data="tableData"
      border
      stripe
      v-loading="loading"
      style="width: 100%"
    >
      <el-table-column prop="id" label="ID" width="80" />
      <el-table-column prop="name" label="姓名" width="120" />
      <el-table-column prop="email" label="邮箱" min-width="180" />
      <el-table-column prop="role" label="角色" width="120" />
      <el-table-column prop="status" label="状态" width="100">
        <template #default="{ row }">
          <el-tag :type="row.status === 1 ? 'success' : 'danger'">
            {{ row.status === 1 ? '启用' : '禁用' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="createTime" label="创建时间" width="180" />
      <el-table-column label="操作" width="200" fixed="right">
        <template #default="{ row }">
          <el-button type="primary" size="small" @click="openEditDialog(row)">
            编辑
          </el-button>
          <el-button type="danger" size="small" @click="handleDelete(row)">
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页器 -->
    <el-pagination
      v-model:current-page="queryParams.page"
      v-model:page-size="queryParams.pageSize"
      :total="total"
      :page-sizes="[10, 20, 50, 100]"
      layout="total, sizes, prev, pager, next, jumper"
      @size-change="fetchData"
      @current-change="fetchData"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getUserList, deleteUser } from '@/api/user'
import type { User, UserQueryParams } from '@/types/user'

const loading = ref(false)
const tableData = ref<User[]>([])
const total = ref(0)

const queryParams = reactive<UserQueryParams>({
  page: 1,
  pageSize: 10,
  keyword: '',
})

async function fetchData() {
  loading.value = true
  try {
    const res = await getUserList({ ...queryParams })
    tableData.value = res.list
    total.value = res.total
  } catch (error) {
    console.error('获取数据失败', error)
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchData()
})
</script>

至此,数据已经可以从后端拉取并以表格形式渲染出来。接下来进入核心部分------增删改的具体实现。

五、新增与编辑(表单弹窗)

新增和编辑通常共用同一个表单弹窗。我们通过一个 dialogVisible 控制弹窗显隐,通过 isEdit 区分新增还是编辑模式,通过 formData 绑定表单数据。

<script setup> 中补充以下逻辑:

typescript 复制代码
import { addUser, updateUser } from '@/api/user'

const dialogVisible = ref(false)
const isEdit = ref(false)
const formRef = ref()
const formLoading = ref(false)

const formData = reactive<Partial<User>>({
  name: '',
  email: '',
  role: '',
  status: 1,
})

const formRules = {
  name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
  email: [
    { required: true, message: '请输入邮箱', trigger: 'blur' },
    { type: 'email', message: '邮箱格式不正确', trigger: 'blur' },
  ],
  role: [{ required: true, message: '请选择角色', trigger: 'change' }],
}

// 打开新增弹窗
function openAddDialog() {
  isEdit.value = false
  // 重置表单
  Object.assign(formData, {
    name: '',
    email: '',
    role: '',
    status: 1,
  })
  dialogVisible.value = true
  // 清除上一次的校验状态
  nextTick(() => formRef.value?.resetFields())
}

// 打开编辑弹窗(回填当前行数据)
function openEditDialog(row: User) {
  isEdit.value = true
  Object.assign(formData, {
    id: row.id,
    name: row.name,
    email: row.email,
    role: row.role,
    status: row.status,
  })
  dialogVisible.value = true
  nextTick(() => formRef.value?.clearValidate())
}

// 提交表单
async function handleSubmit() {
  const valid = await formRef.value.validate().catch(() => false)
  if (!valid) return

  formLoading.value = true
  try {
    if (isEdit.value) {
      await updateUser(formData as User)
      ElMessage.success('编辑成功')
    } else {
      await addUser(formData as Omit<User, 'id' | 'createTime'>)
      ElMessage.success('新增成功')
    }
    dialogVisible.value = false
    fetchData() // 刷新表格
  } catch (error) {
    console.error('提交失败', error)
  } finally {
    formLoading.value = false
  }
}
</script>

<template> 中添加弹窗组件:

vue 复制代码
<el-dialog
  v-model="dialogVisible"
  :title="isEdit ? '编辑用户' : '新增用户'"
  width="520px"
  :close-on-click-modal="false"
  @closed="formRef?.resetFields()"
>
  <el-form
    ref="formRef"
    :model="formData"
    :rules="formRules"
    label-width="80px"
  >
    <el-form-item label="姓名" prop="name">
      <el-input v-model="formData.name" placeholder="请输入姓名" />
    </el-form-item>
    <el-form-item label="邮箱" prop="email">
      <el-input v-model="formData.email" placeholder="请输入邮箱" />
    </el-form-item>
    <el-form-item label="角色" prop="role">
      <el-select v-model="formData.role" placeholder="请选择角色" style="width: 100%">
        <el-option label="管理员" value="admin" />
        <el-option label="普通用户" value="user" />
        <el-option label="访客" value="guest" />
      </el-select>
    </el-form-item>
    <el-form-item label="状态" prop="status">
      <el-radio-group v-model="formData.status">
        <el-radio :label="1">启用</el-radio>
        <el-radio :label="0">禁用</el-radio>
      </el-radio-group>
    </el-form-item>
  </el-form>
  <template #footer>
    <el-button @click="dialogVisible = false">取消</el-button>
    <el-button type="primary" :loading="formLoading" @click="handleSubmit">
      确定
    </el-button>
  </template>
</el-dialog>

关键点:新增 时清空所有字段并重置校验;编辑 时通过 Object.assign 将当前行的数据回填到表单,同时记录 id 用于调用更新接口。

六、删除操作

删除操作需要弹出确认框,避免用户误操作。确认后才调用后端接口:

typescript 复制代码
async function handleDelete(row: User) {
  try {
    await ElMessageBox.confirm(
      `确定要删除用户「${row.name}」吗?删除后不可恢复。`,
      '删除确认',
      {
        confirmButtonText: '确定删除',
        cancelButtonText: '取消',
        type: 'warning',
      }
    )
    await deleteUser(row.id)
    ElMessage.success('删除成功')
    // 如果当前页删除后只剩 1 条,回退到上一页
    if (tableData.value.length === 1 && queryParams.page > 1) {
      queryParams.page--
    }
    fetchData()
  } catch (error) {
    // 用户取消删除或接口报错都会进入这里
    if (error !== 'cancel' && error !== 'close') {
      console.error('删除失败', error)
    }
  }
}
</script>

这里做了一个贴心的分页处理:当删除当前页最后一条数据后,自动回退到上一页,避免表格显示为空。

七、乐观更新 vs 重新请求

上述所有增删改操作在成功后都调用了 fetchData() 重新拉取列表。这种方式最简单可靠,但会有短暂的网络等待。如果你的场景对响应速度要求很高,也可以采用乐观更新

  • 新增 :接口成功后直接 tableData.value.unshift(newRecord),不重新请求。
  • 编辑 :接口成功后找到对应索引,直接替换 tableData.value[index]
  • 删除 :接口成功后 tableData.value.splice(index, 1)

但乐观更新需要额外处理失败回滚的问题,建议在稳定的小型项目中尝试。绝大多数场景下,重新请求一次列表是最稳妥的做法

八、完整文件结构

最后整理一下本文涉及的所有文件,方便对照检查:

bash 复制代码
src/
├── api/
│   ├── request.ts          # Axios 实例 + 拦截器
│   └── user.ts             # 用户相关接口
├── types/
│   └── user.ts             # User 类型定义
├── views/
│   └── UserList.vue        # 表格 + 增删改完整组件
└── main.ts                 # 入口文件(注册 Element Plus)

九、总结

回顾整个流程,Vue3 + TypeScript 实现后端数据表格渲染及增删改的核心步骤可以浓缩为以下几点:

  1. 定义类型接口 :使用 TypeScript 的 interface 和工具类型(OmitPartialPick)为数据实体和 API 响应建模。
  2. 封装请求层:通过 Axios 实例统一处理 baseURL、超时和错误拦截,再按模块拆分接口文件。
  3. 组件内获取数据 :在 onMounted 或搜索触发时请求列表,将响应赋值给 tableData 响应式数组。
  4. 新增/编辑共用弹窗 :通过 isEdit 标志区分模式,编辑时回填数据,提交时调用不同接口。
  5. 删除二次确认 :使用 ElMessageBox.confirm 防止误删,并处理分页边界情况。
  6. 刷新策略 :增删改成功后统一调用 fetchData() 刷新表格,保证数据与后端一致。

按照以上步骤实现后,一个完整的「请求 → 渲染 → 增删改」流程就跑通了。你可以将本文的示例代码直接拷贝到项目中,根据实际业务字段调整表格列和表单字段即可快速落地。

相关推荐
水獭比特1 小时前
AI 视频生成不是一次 HTTP 请求:先把长任务状态机补齐
人工智能·typescript
他们叫我 悦儿遥遥雨1 小时前
.NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑
前端·javascript·.net
用户2181697049301 小时前
Flutter(九)StatelessWidget StatefullWidget
前端
paopaokaka_luck1 小时前
基于springboot3+vue3的乡村医生诊疗管理系统(AI助手、协同过滤算法、webSocket实时聊天、Echarts图形化分析)
前端·网络·人工智能·spring boot·websocket·网络协议·echarts
编程风暴1 小时前
3类人群的数据分析实习类型选择+4条选错红线
java·前端·数据分析
其美杰布-富贵-李1 小时前
Vue 3 模块导入教程:import、export 与文件组织
前端·javascript·vue.js
Bigger1 小时前
堆了 20 套主题、10+ 组件,用户还是用不出来?我给设计 Skill 加了「自动驾驶」
前端·ai编程·视觉设计
weipt1 小时前
springboot项目运行的几种方式
javascript·css·html
前端技术官2 小时前
前端真的比后端简单吗?
javascript·webview·dom·前端工程化·浏览器兼容性