跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
TypeScriptNode.js大前端

苍穹外卖前端开发:员工与套餐管理功能实现

综述由AI生成介绍苍穹外卖项目的前端环境搭建及核心功能实现,涵盖员工管理与套餐管理模块。内容包括技术选型(Vue、TypeScript、ElementUI)、前后端交互封装、分页查询、状态启停控制、新增修改表单校验及批量删除等功能。通过具体代码示例展示了路由跳转、API 请求封装、组件生命周期应用及表单验证规则配置,帮助开发者快速掌握基于 Vue 的企业级后台管理系统前端开发流程。

邪神洛基发布于 2026/4/5更新于 2026/5/2129 浏览
苍穹外卖前端开发:员工与套餐管理功能实现

前端环境搭建

技术选型

使用的前端技术栈:Node.js、Vue、ElementUI、Axios、Vuex、Vue Router、TypeScript

代码结构

核心目录 / 文件:

目录 / 文件说明
api封装 Ajax 请求的文件目录
components公共组件存放目录
views视图组件存放目录
App.vue项目主组件、页面入口文件
main.ts整个项目的入口文件
router.ts路由配置文件

环境准备

安装依赖包(生成 node_modules 目录):

npm install

启动前端项目(需同时启动后端 Java 服务):

npm run serve

员工管理

员工分页查询

需求分析和接口设计

文章配图

代码开发

步骤一:制作页面头部

文章配图

<div>
  <label>员工姓名:</label>
  <el-input placeholder="请输入员工姓名" />
  <el-button type="primary">查询</el-button>
  <el-button type="primary">+ 添加员工</el-button>
</div>

说明:输入框和按钮均使用 ElementUI 提供的组件,可参考其官方文档进行修改。

步骤二:实现前后端数据交互

绑定查询事件:为查询按钮添加 @click="pageQuery()" 事件。

<el-button type="primary" @click="pageQuery()">查询</el-button>

定义查询方法:在 methods 中定义 pageQuery 方法,验证方法能否正常执行。

<script lang="ts">
export default {
  methods: {
    // 分页查询
    pageQuery() {
      // 验证当前方法能否成功执行
      alert(1);
    }
  }
};
</script>

封装 API 请求:在 src/api/employee.ts 中定义 getEmployeeList 方法,用于发送 Ajax 请求获取分页数据。

// 分页查询
export const getEmployeeList = (params: any) => {
  return request({
    url: '/employee/page',
    method: 'get',
    params: params,
  });
};

导入 API 并定义模型数据:在员工管理组件中导入 getEmployeeList 方法,并在 data() 中定义分页相关的模型数据。

import { getEmployeeList } from '@/api/employee';

export default {
  // 模型数据
  data() {
    return {
      name: '', // 员工姓名,对应上面的输入框
      page: 1, // 页码
      pageSize: 10, // 每页记录数
      total: 0, // 总记录数
      records: [], // 当前页要展示的数据集合
    };
  },
};

双向绑定输入框:将 name 属性与员工姓名输入框进行双向绑定。

<el-input v-model="name" placeholder="请输入员工姓名" clearable />

完善查询方法:在 pageQuery 方法中调用 getEmployeeList 方法,处理返回数据。

// 分页查询
pageQuery() {
  // 准备参数
  const params = {
    page: this.page,
    pageSize: this.pageSize,
    name: this.name,
  };
  // 发送请求
  getEmployeeList(params)
    .then((res) => {
      // 解析结果
      if (res.data.code === 1) {
        this.records = res.data.data.records;
        this.total = res.data.data.total;
      }
    })
    .catch((err) => {
      this.$message.error('请求出错了:' + err.message);
    });
}

步骤三:自动发送 Ajax 请求

使用 Vue 的 created 生命周期钩子,可以在组件加载后自动发送 Ajax 请求,查询第一页数据。

// 声明周期方法
created() {
  this.pageQuery();
}

步骤四:使用表格展示分页数据

使用 ElementUI 的表格组件展示后端返回的员工数据。

<el-table :data="records" stripe>
  <el-table-column prop="name" label="员工姓名" />
  <el-table-column prop="username" label="账号" />
  <el-table-column prop="phone" label="手机号" />
  <el-table-column prop="status" label="账号状态">
    <template slot-scope="scope">
      <span :class="scope.row.status === 0 ? 'stopUse' : 'stopUse'">
        {{ scope.row.status === 0 ? '禁用' : '启用' }}
      </span>
    </template>
  </el-table-column>
  <el-table-column prop="updateTime" label="最后操作时间" />
  <el-table-column prop="操作" label="操作">
    <template slot-scope="scope">
      <el-button size="small" type="text">修改</el-button>
      <el-button size="small" type="text">
        {{ scope.row.status === 1 ? '禁用' : '启用' }}
      </el-button>
      <el-button size="small" type="text">删除</el-button>
    </template>
  </el-table-column>
</el-table>

步骤五:使用分页条实现翻页效果

官方示例:https://element.eleme.io/#/zh-CN/component/pagination

分页组件代码:

<el-pagination
  :page-sizes="[10, 20, 30, 40, 50]"
  :page-size="pageSize"
  layout="total, sizes, prev, pager, next, jumper"
  :total="total"
  @size-change="handleSizeChange"
  @current-change="handleCurrentChange"
/>

事件处理函数:

// 分页条的事件处理函数,pageSize 改变时会触发
handleSizeChange(pageSize) {
  this.pageSize = pageSize;
  this.pageQuery();
},
// 分页条的事件处理函数,currentPage 改变时会触发
handleCurrentChange(currentPage) {
  this.page = currentPage;
  this.pageQuery();
}

启用禁用员工账号

需求分析和接口设计

文章配图

代码开发

步骤一:绑定按钮单击事件

为表格中的 "启用 / 禁用" 按钮绑定 handleStartOrStop 事件,并根据当前状态动态显示按钮文字。

<el-button type="text" size="small" @click="handleStartOrStop(scope.row)">
  {{ scope.row.status == '1' ? '禁用' : '启用' }}
</el-button>

步骤二:编写对应的处理函数

在 methods 中定义 handleStartOrStop 方法,验证方法能否成功执行。

// 启用、禁用员工账号
handleStartOrStop(row) {
  alert(`id=${row.id} status=${row.status}`);
}

步骤三:封装 API 请求

在 src/api/employee.ts 中定义 enableOrDisableEmployee 方法,用于发送 Ajax 请求更新员工状态。

// 启用禁用员工账号
export const enableOrDisableEmployee = (params: any) => {
  return request({
    url: `/employee/status/${params.status}`,
    method: 'post',
    params: { id: params.id },
  });
};

步骤四:完善处理函数

在员工管理组件中导入 enableOrDisableEmployee 方法,并完善 handleStartOrStop 方法,添加确认弹窗和状态更新逻辑。

// 启用、禁用员工账号
handleStartOrStop(row) {
  this.$confirm('确认调整该账号的状态?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    enableOrDisableEmployee({ id: row.id, status: !row.status ? 1 : 0 })
      .then((res) => {
        if (res.status === 200) {
          this.$message.success('账号状态更改成功!');
          this.pageQuery(); // 刷新数据
        }
      })
      .catch((err) => {
        this.$message.error('请求出错了:' + err.message);
      });
  });
}

步骤五:代码优化

在 handleStartOrStop 方法中添加判断,如果是管理员账号则不允许修改状态并给出提示。

// 启用、禁用员工账号
handleStartOrStop(row) {
  if (row.username === 'admin') {
    this.$message.error('admin 为管理员账号,不能更改账号状态!');
    return;
  }
  this.$confirm('确认调整该账号的状态?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    enableOrDisableEmployee({ id: row.id, status: !row.status ? 1 : 0 })
      .then((res) => {
        if (res.status === 200) {
          this.$message.success('账号状态更改成功!');
          this.pageQuery();
        }
      })
      .catch((err) => {
        this.$message.error('请求出错了:' + err.message);
      });
  });
}

新增员工

需求分析和接口设计

产品原型:

文章配图

文章配图

代码开发

步骤一:为 "添加员工" 按钮绑定单击事件

<div>
  <label>员工姓名:</label>
  <el-input v-model="name" placeholder="请输入员工姓名" clearable />
  <el-button type="primary" @click="pageQuery()">查询</el-button>
  <el-button type="primary" @click="handleAddEmp">+ 添加员工</el-button>
</div>

步骤二:编写 handleAddEmp 方法,进行路由跳转

// 添加员工,跳转至添加员工页面(组件)
handleAddEmp() {
  this.$router.push('/employee/add');
}

路由配置(已在路由文件中定义):

{
  path: "/employee/add",
  component: () => import("@/views/employee/addEmployee.vue"),
  meta: { title: "添加/修改员工", hidden: true }
}

步骤三:开发新增页面表单元素

<template>
  <div>
    <div>
      <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="180px">
        <el-form-item label="账号" prop="username">
          <el-input v-model="ruleForm.username"></el-input>
        </el-form-item>
        <el-form-item label="员工姓名" prop="name">
          <el-input v-model="ruleForm.name"></el-input>
        </el-form-item>
        <el-form-item label="手机号" prop="phone">
          <el-input v-model="ruleForm.phone"></el-input>
        </el-form-item>
        <el-form-item label="性别" prop="sex">
          <el-radio v-model="ruleForm.sex" label="1">男</el-radio>
          <el-radio v-model="ruleForm.sex" label="2">女</el-radio>
        </el-form-item>
        <el-form-item label="身份证号" prop="idNumber">
          <el-input v-model="ruleForm.idNumber"></el-input>
        </el-form-item>
        <div>
          <el-button type="primary" @click="submitForm('ruleForm', false)">保存</el-button>
          <el-button type="primary" @click="submitForm('ruleForm', true)">保存并继续添加员工</el-button>
          <el-button @click="() => this.$router.push('/employee')">返回</el-button>
        </div>
      </el-form>
    </div>
  </div>
</template>

步骤四:定义模型数据和表单校验规则

export default {
  data() {
    return {
      ruleForm: {
        name: '',
        username: '',
        sex: '1',
        phone: '',
        idNumber: '',
      },
      rules: {
        name: [
          { required: true, message: '请输入员工姓名', trigger: 'blur' },
        ],
        username: [
          { required: true, message: '请输入账号', trigger: 'blur' },
        ],
        phone: [
          {
            required: true,
            trigger: 'blur',
            validator: (rule, value, callback) => {
              if (
                value === '' ||
                !/^1[3|4|5|6|7|8]\d{9}$/.test(value)
              ) {
                callback(new Error('请输入正确的手机号'));
              } else {
                callback();
              }
            },
          },
        ],
        idNumber: [
          {
            required: true,
            trigger: 'blur',
            validator: (rule, value, callback) => {
              if (
                value === '' ||
                !/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value)
              ) {
                callback(new Error('请输入正确的身份证号'));
              } else {
                callback();
              }
            },
          },
        ],
      },
    };
  },
};

步骤五:在 employee.ts 中封装新增员工方法

// 新增员工
export const addEmployee = (params: any) => {
  return request({
    url: '/employee',
    method: 'post',
    data: params,
  });
};

步骤六:定义提交表单的方法 submitForm:

methods: {
  // 提交表单数据
  submitForm(formName, isContinue) {
    // 表单数据校验
    this.$refs[formName].validate((valid) => {
      if (valid) {
        addEmployee(this.ruleForm)
          .then((res: any) => {
            if (res.data.code === 1) {
              this.$message.success('员工添加成功!');
              if (isContinue) {
                this.$router.push({ path: '/employee/add' });
              } else {
                this.ruleForm = {
                  username: '',
                  name: '',
                  phone: '',
                  sex: '1',
                  idNumber: '',
                };
              }
            } else {
              this.$message.error(res.data.msg);
            }
          });
      }
    });
  },
}

修改员工

需求分析和接口设计

文章配图

文章配图

代码开发

步骤一:为 "修改" 按钮绑定单击事件

<el-button type="text" size="small" @click="handleUpdateEmp(scope.row)">修改</el-button>

步骤二:编写 handleUpdateEmp 方法,实现路由跳转

在员工列表组件的 methods 中定义跳转方法,并对管理员账号进行保护:

// 修改员工,跳转至修改员工页面(组件)
handleUpdateEmp(row) {
  if (row.username === 'admin') {
    // 如果是内置管理员账号,则不允许修改
    this.$message.error('admin 为管理员账号,不能修改!');
    return;
  }
  // 跳转到修改页面,通过地址栏传递参数
  this.$router.push({ path: '/employee/add', query: { id: row.id } });
}

地址栏传递参数:this.$router.push({path: 路由路径,query:{参数名:参数值}})

步骤三:在 addEmployee.vue 中定义操作类型并区分新增 / 修改

在组件的 data() 中定义 optType 用于区分操作类型,并在 created 生命周期中根据路由参数判断:

<script lang="ts">
import { addEmployee } from '@/api/employee';

export default {
  data() {
    return {
      optType: '', // 当前操作类型:新增(add)或者修改(update)
      ruleForm: { /* ... */ },
      rules: { /* ... */ },
    };
  },
  created() {
    // 获取路由参数,如果有则为修改操作,否则为新增操作
    this.optType = this.$route.query.id ? 'update' : 'add';
  },
  methods: { /* ... */ },
};
</script>

获取路由参数:this.$router.query.参数名

步骤四:在 employee.ts 中封装根据 ID 查询员工的方法

用于修改操作时的数据回显:

// 根据 id 查询员工
export const queryEmployeeById = (id: number) => {
  return request({
    url: `/employee/${id}`,
    method: 'get',
  });
};

步骤五:在 addEmployee.vue 中实现数据回显

在 created 方法中,如果是修改操作,则调用查询方法回显数据:

created() {
  // 获取路由参数,如果有则为修改操作,否则为新增操作
  this.optType = this.$route.query.id ? 'update' : 'add';
  if (this.optType === 'update') {
    // 修改操作,需要根据 id 查询原始数据,用于回显
    queryEmployeeById(this.$route.query.id)
      .then((res) => {
        if (res.data.code === 1) {
          this.ruleForm = res.data.data;
        }
      });
  }
}

步骤六:控制 "保存并继续添加员工" 按钮的显示

在模板中使用 v-if 指令,仅在新增操作时显示该按钮:

<div>
  <el-button type="primary" @click="submitForm('ruleForm', false)">保存</el-button>
  <el-button v-if="this.optType === 'add'" type="primary" @click="submitForm('ruleForm', true)">保存并继续添加员工</el-button>
  <el-button @click="() => this.$router.push('/employee')">返回</el-button>
</div>

步骤七:在 employee.ts 中封装修改员工的方法

// 修改员工
export const updateEmployee = (params: any) => {
  return request({
    url: '/employee',
    method: 'put',
    data: params,
  });
};

步骤八:修改 submitForm 方法,区分新增和修改操作

在组件的 methods 中,根据 optType 执行不同的请求:

submitForm(formName, isContinue) {
  // 表单数据校验
  this.$refs[formName].validate((valid) => {
    if (valid) {
      // 根据操作类型执行新增或者修改操作
      if (this.optType === 'add') {
        // 新增操作
        addEmployee(this.ruleForm)
          .then((res: any) => {
            /* ... */
          });
      } else {
        // 修改操作
        updateEmployee(this.ruleForm)
          .then((res: any) => {
            if (res.data.code === 1) {
              this.$message.success('员工修改成功!');
              this.$router.push({ path: '/employee' });
            } else {
              this.$message.error(res.data.msg);
            }
          });
      }
    }
  });
}

套餐管理

套餐分页查询

需求分析和接口设计

产品原型:

文章配图

文章配图

文章配图

代码开发

步骤一:制作页面头部效果

<div>
  <label>套餐名称:</label>
  <el-input v-model="name" clearable />
  <label>套餐分类:</label>
  <el-select v-model="categoryId" placeholder="请选择">
    <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
  </el-select>
  <label>售卖状态:</label>
  <el-select v-model="status" placeholder="请选择" clearable>
    <el-option v-for="item in statusArr" :key="item.value" :label="item.label" :value="item.value" />
  </el-select>
  <el-button type="primary">查询</el-button>
  <div>
    <el-button type="danger">批量删除</el-button>
    <el-button type="info">+ 新建套餐</el-button>
  </div>
</div>

模型数据定义:

export default {
  data() {
    return {
      name: '',
      categoryId: '', // 分类 id
      status: '', // 售卖状态
      options: [], // 为套餐分类下拉框提供的选项
      statusArr: [ // 为售卖状态下拉框提供的数据
        { value: '1', label: '启售' },
        { value: '0', label: '停售' },
      ],
    };
  },
};

步骤二:动态填充套餐分类下拉框数据

封装 API 请求:在 src/api/category.ts 中已定义 getCategoryByType 方法,用于根据类型查询分类。

// 根据类型查询分类:1 为菜品分类 2 为套餐分类
export const getCategoryByType = (params: any) => {
  return request({
    url: '/category/list',
    method: 'get',
    params: params,
  });
};

导入并调用 API:在套餐管理组件中导入该方法,并在 created 生命周期中调用,动态填充下拉框。

<script lang="ts">
import { getCategoryByType } from '@/api/category';

export default {
  data() {
    /* ... */
  },
  created() {
    // 查询套餐分类,用于填充查询页面的下拉框
    getCategoryByType({ type: 2 })
      .then((res) => {
        if (res.data.code === 1) {
          this.options = res.data.data;
        }
      });
  },
};
</script>

步骤三:动态获取套餐分页数据

绑定查询事件:为查询按钮添加 @click="pageQuery" 事件。

<el-button type="primary" @click="pageQuery()">查询</el-button>

封装 API 请求:在 src/api/setMeal.ts 中定义 getSetmealPage 方法,用于发送 Ajax 请求获取套餐分页数据。

// 套餐分页查询
export const getSetmealPage = (params: any) => {
  return request({
    url: '/setmeal/page',
    method: 'get',
    params: params,
  });
};

导入 API 并定义模型数据:在套餐管理组件中导入 getSetmealPage 方法,并在 data() 中定义分页相关的模型数据。

import { getSetmealPage } from '@/api/setMeal';

export default {
  data() {
    return {
      page: 1, // 页码
      pageSize: 10, // 每页记录数
      total: 0, // 总记录数
      records: [], // 当前页要展示的数据集合
      name: '',
      categoryId: '',
      status: '',
      options: [],
      statusArr: [/* ... */],
    };
  },
};

完善查询方法:在 pageQuery 方法中调用 getSetmealPage 方法,处理返回数据。

// 套餐分页查询
pageQuery() {
  // 封装分页查询参数
  const params = {
    page: this.page,
    pageSize: this.pageSize,
    name: this.name,
    status: this.status,
    categoryId: this.categoryId,
  };
  // 调用分页查询接口
  getSetmealPage(params)
    .then((res) => {
      if (res.data.code === 1) {
        this.total = res.data.data.total;
        this.records = res.data.data.records;
      }
    });
}

步骤四:自动发送 Ajax 请求

在 created 生命周期中调用 pageQuery 方法,可以在组件加载后自动发送 Ajax 请求,查询第一页数据。

created() {
  // 查询套餐分类,用于填充查询页面的下拉框
  getCategoryByType({ type: 2 })
    .then((res) => {
      if (res.data.code === 1) {
        this.options = res.data.data;
      }
    });
  // 查询套餐分页数据
  this.pageQuery();
}

步骤五:使用表格展示分页数据

官方示例:https://element.eleme.io/#/zh-CN/component/table

<el-table :data="records" stripe>
  <el-table-column prop="image" label="图片">
    <template slot-scope="scope">
      <el-image :src="scope.row.image"></el-image>
    </template>
  </el-table-column>
  <el-table-column prop="name" label="套餐名称" />
  <el-table-column prop="price" label="套餐价" />
  <el-table-column prop="categoryName" label="套餐分类" />
  <el-table-column label="售卖状态">
    <template slot-scope="scope">
      <div :class="scope.row.status === 0 ? 'stopUse' : 'stopUse'">
        {{ scope.row.status === 0 ? '停售' : '启售' }}
      </div>
    </template>
  </el-table-column>
  <el-table-column prop="updateTime" label="最后操作时间" />
  <el-table-column label="操作">
    <template slot-scope="scope">
      <el-button type="text" size="small">修改</el-button>
      <el-button type="text" size="small">
        {{ scope.row.status === 1 ? '停售' : '启售' }}
      </el-button>
      <el-button type="text" size="small">删除</el-button>
    </template>
  </el-table-column>
</el-table>

步骤六:使用分页条实现翻页效果

官方示例:https://element.eleme.io/#/zh-CN/component/pagination

分页组件代码:

<el-pagination
  background
  @size-change="handleSizeChange"
  @current-change="handleCurrentChange"
  :current-page="page"
  :page-sizes="[10, 20, 30, 40, 50]"
  :page-size="pageSize"
  layout="total, sizes, prev, pager, next, jumper"
  :total="total"
>
</el-pagination>

事件处理函数:

// 分页条的事件处理函数,pageSize 改变时会触发
handleSizeChange(pageSize) {
  this.pageSize = pageSize;
  this.pageQuery();
},
// 分页条的事件处理函数,currentPage 改变时会触发
handleCurrentChange(page) {
  this.page = page;
  this.pageQuery();
}

启售停售套餐

需求分析和接口设计

产品原型:

文章配图

文章配图

代码开发

步骤一:绑定按钮单击事件

为表格中的 "起售 / 停售" 按钮绑定 handleStartOrStop 事件,并根据当前状态动态显示按钮文字。

<el-button type="text" size="small" @click="handleStartOrStop(scope.row)">
  {{ scope.row.status == '1' ? '停售' : '启售' }}
</el-button>

步骤二:编写对应的处理函数

在 methods 中定义 handleStartOrStop 方法,验证方法能否成功执行。

// 套餐起售、停售
handleStartOrStop(row) {
  alert(`id=${row.id} status=${row.status}`);
}

步骤三:封装 API 请求

在 src/api/setMeal.ts 中定义 enableOrDisableSetmeal 方法,用于发送 Ajax 请求更新套餐状态。

// 套餐起售禁售
export const enableOrDisableSetmeal = (params: any) => {
  return request({
    url: `/setmeal/status/${params.status}`,
    method: 'post',
    params: { id: params.id },
  });
};

步骤四:完善处理函数

在套餐管理组件中导入 enableOrDisableSetmeal 方法,并完善 handleStartOrStop 方法,添加确认弹窗和状态更新逻辑。

// 套餐起售、停售
handleStartOrStop(row) {
  this.$confirm('确认调整该套餐的售卖状态?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    enableOrDisableSetmeal({ id: row.id, status: !row.status ? 1 : 0 })
      .then((res) => {
        if (res.status === 200) {
          this.$message.success('套餐售卖状态更改成功!');
          this.pageQuery(); // 刷新数据
        }
      })
      .catch((err) => {
        this.$message.error('请求出错了:' + err.message);
      });
  });
}

删除套餐

需求分析和接口设计

产品原型:

文章配图

代码开发

步骤一:封装删除套餐的 API 方法

在 src/api/setMeal.ts 中定义 deleteSetmeal 方法,用于发送 Ajax 请求删除套餐。

// 删除套餐接口
export const deleteSetmeal = (ids: string) => {
  return request({
    url: '/setmeal',
    method: 'delete',
    params: { ids: ids },
  });
};

步骤二:为 "批量删除" 按钮绑定事件

为批量删除按钮绑定 handleDelete 事件,验证方法执行。

<el-button type="danger" @click="handleDelete">批量删除</el-button>
// 删除套餐
handleDelete() {
  alert('删除套餐');
}

步骤三:监听表格选择变化

为表格添加 selection-change 事件,动态获取当前勾选的套餐行。

<el-table :data="records" stripe @selection-change="handleSelectionChange">
  <el-table-column type="selection" />
  <!-- 其他列 -->
</el-table>

在 data() 中定义存储选中行的数组:

data() {
  return {
    // ...其他数据
    multipleSelection: [], // 当前被选中的行
  };
}

编写事件处理函数:

// 当选择项发生变化时会触发该事件
handleSelectionChange(val) {
  this.multipleSelection = val; // alert(this.multipleSelection.length);
}

步骤四:完善 handleDelete 方法,处理批量删除

在 handleDelete 方法中,获取选中的套餐 ID 并拼接成字符串。

// 删除套餐
handleDelete() {
  const arr = [];
  this.multipleSelection.forEach((element) => {
    // 将套餐 id 放入数组中
    arr.push(element.id);
  });
  const ids = arr.join(','); // 将数组中的 id 拼接到一起,中间用逗号分隔
  alert(ids);
}

步骤五:为 "删除" 按钮绑定事件

为单个删除按钮绑定 handleDelete 事件,并通过参数区分操作类型。

<el-button type="text" size="small" @click="handleDelete('S', scope.row.id)">删除</el-button>

步骤六:调整 handleDelete 方法,兼容单个和批量删除

修改 handleDelete 方法,根据传入的 type 参数(S 表示单个删除,B 表示批量删除)执行不同逻辑。

// 删除套餐
handleDelete(type: string, id: string) {
  let param;
  // 判断当前是单个删除还是批量删除
  if (type === 'S') {
    // 单个删除
    param = id;
  } else {
    // 批量删除
    const arr = [];
    this.multipleSelection.forEach((element) => {
      // 将套餐 id 放入数组中
      arr.push(element.id);
    });
    param = arr.join(','); // 将数组中的 id 拼接到一起,中间用逗号分隔
  }
  deleteSetmeal(param)
    .then((res) => {
      if (res.data.code === 1) {
        this.$message.success('删除成功!');
        this.pageQuery();
      } else {
        this.$message.error(res.data.msg);
      }
    });
}

步骤七:完善 handleDelete 方法,添加提示和确认

// 删除套餐
handleDelete(type: string, id: string) {
  if (type === 'B' && this.multipleSelection.length === 0) {
    this.$message('请选择需要删除的套餐!');
    return;
  }
  this.$confirm('确定删除该套餐?', '确定删除', {
    confirmButtonText: '删除',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    let param;
    // 判断当前是单个删除还是批量删除
    if (type === 'S') {
      // 单个删除
      param = id;
    } else {
      // 批量删除
      const arr = [];
      this.multipleSelection.forEach((element) => {
        // 将套餐 id 放入数组中
        arr.push(element.id);
      });
      param = arr.join(','); // 将数组中的 id 拼接到一起,中间用逗号分隔
    }
    deleteSetmeal(param)
      .then((res) => {
        if (res.data.code === 1) {
          this.$message.success('删除成功!');
          this.pageQuery();
        } else {
          this.$message.error(res.data.msg);
        }
      });
  });
}

新增套餐

需求分析和接口设计

产品原型:

文章配图

文章配图

文章配图

文章配图

文章配图

文章配图

代码解读

步骤一:找到新建套餐按钮及绑定事件

在套餐管理列表页面中,找到新建套餐按钮,其绑定的点击事件为 handleAdd:

<el-button type="info" @click="handleAdd">+ 新建套餐</el-button>

步骤二:查看 handleAdd 方法的路由跳转逻辑

在 methods 中找到 handleAdd 方法,它通过路由跳转到新增套餐页面:

// 新增套餐,跳转到新增页面(组件)
handleAdd() {
  this.$router.push('/setmeal/add');
}

步骤三:在路由文件中定位对应组件

在路由配置文件中,路径 /setmeal/add 对应的视图组件为 src/views/setmeal/addSetmeal.vue:

{
  path: "/setmeal/add",
  component: () => import("@/views/setmeal/addSetmeal.vue"),
  meta: { title: "添加套餐", hidden: true }
}

步骤四:核心代码解读

解读 src/views/setmeal/addSetmeal.vue 文件:

<template>
  <div>
    <div>
      <div>
        <label>套餐名称:</label>
        <el-input clearable v-model="name" />
        <label>套餐分类:</label>
        <el-select v-model="categoryId" placeholder="请选择" clearable>
          <el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id"></el-option>
        </el-select>
        <label>售卖状态:</label>
        <el-select v-model="status" placeholder="请选择" clearable>
          <el-option v-for="item in statusArr" :key="item.value" :label="item.label" :value="item.value" />
        </el-select>
        <el-button type="primary" @click="pageQuery()">查询</el-button>
        <div>
          <el-button type="danger" @click="handleDelete('B')">批量删除</el-button>
          <el-button type="info" @click="handleAdd">+ 新建套餐</el-button>
        </div>
      </div>
      <el-table :data="records" stripe @selection-change="handleSelectionChange">
        <el-table-column type="selection" />
        <el-table-column prop="name" label="套餐名称" />
        <el-table-column label="图片">
          <template slot-scope="scope">
            <el-image :src="scope.row.image"></el-image>
          </template>
        </el-table-column>
        <el-table-column prop="categoryName" label="套餐分类" />
        <el-table-column prop="price" label="套餐价" />
        <el-table-column label="售卖状态">
          <template slot-scope="scope">
            <div :class="{ 'stop-use': scope.row.status === 0 }">
              {{ scope.row.status === 0 ? '停售' : '启售' }}
            </div>
          </template>
        </el-table-column>
        <el-table-column prop="updateTime" label="最后操作时间" />
        <el-table-column label="操作">
          <template slot-scope="scope">
            <el-button type="text" size="small">修改</el-button>
            <el-button type="text" size="small" @click="handleStartOrStop(scope.row)">
              {{ scope.row.status == '1' ? '停售' : '启售' }}
            </el-button>
            <el-button type="text" size="small" @click="handleDelete('S', scope.row.id)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        :page-sizes="[10, 20, 30, 40]"
        :page-size="pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script lang="ts">
import { getCategoryByType } from '@/api/category';
import { getSetmealPage, enableOrDisableSetmeal, deleteSetmeal } from '@/api/setMeal';

export default {
  data() {
    return {
      page: 1,
      pageSize: 10,
      name: '', // 套餐名称
      status: '', // 售卖状态
      categoryId: '', // 分类 id
      total: 0,
      records: [],
      options: [],
      statusArr: [ // 为售卖状态下拉框提供的数据
        { value: '1', label: '启售' },
        { value: '0', label: '停售' },
      ],
      multipleSelection: [], // 当前被选中的行
    };
  },
  created() {
    // 查询套餐分类,用于填充查询页面的下拉框
    getCategoryByType({ type: 2 })
      .then((res) => {
        if (res.data.code === 1) {
          this.options = res.data.data;
        }
      });
    // 查询套餐分页数据
    this.pageQuery();
  },
  methods: {
    // 套餐分页查询
    pageQuery() {
      // 封装分页查询参数
      const params = {
        page: this.page,
        pageSize: this.pageSize,
        name: this.name,
        status: this.status,
        categoryId: this.categoryId,
      };
      // 调用分页查询接口
      getSetmealPage(params)
        .then((res) => {
          if (res.data.code === 1) {
            this.total = res.data.data.total;
            this.records = res.data.data.records;
          }
        });
    },
    // 分页条的事件处理函数,pageSize 改变时会触发
    handleSizeChange(pageSize) {
      this.pageSize = pageSize;
      this.pageQuery();
    },
    // 分页条的事件处理函数,currentPage 改变时会触发
    handleCurrentChange(page) {
      this.page = page;
      this.pageQuery();
    },
    // 套餐起售、停售
    handleStartOrStop(row) {
      this.$confirm('确认调整该套餐的售卖状态?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        enableOrDisableSetmeal({ id: row.id, status: !row.status ? 1 : 0 })
          .then((res) => {
            if (res.status === 200) {
              this.$message.success('套餐售卖状态更改成功!');
              this.pageQuery();
            }
          })
          .catch((err) => {
            this.$message.error('请求出错了:' + err.message);
          });
      });
    },
    // 删除套餐
    handleDelete(type: string, id: string) {
      if (type === 'B' && this.multipleSelection.length === 0) {
        this.$message('请选择需要删除的套餐!');
        return;
      }
      this.$confirm('确定删除该套餐?', '确定删除', {
        confirmButtonText: '删除',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        let param;
        // 判断当前是单个删除还是批量删除
        if (type === 'S') {
          // 单个删除
          param = id;
        } else {
          // 批量删除
          const arr = [];
          this.multipleSelection.forEach((element) => {
            // 将套餐 id 放入数组中
            arr.push(element.id);
          });
          param = arr.join(','); // 将数组中的 id 拼接到一起,中间用逗号分隔
        }
        deleteSetmeal(param)
          .then((res) => {
            if (res.data.code === 1) {
              this.$message.success('删除成功!');
              this.pageQuery();
            } else {
              this.$message.error(res.data.msg);
            }
          });
      });
    },
    // 当选择项发生变化时会触发该事件
    handleSelectionChange(val) {
      this.multipleSelection = val; // alert(this.multipleSelection.length);
    },
    // 新增套餐,跳转到新增页面(组件)
    handleAdd() {
      this.$router.push('/setmeal/add');
    },
  },
};
</script>

<style lang="scss">
.el-table-column--selection .cell {
  padding-left: 10px;
}
</style>

<style lang="scss" scoped>
.dashboard {
  &-container {
    margin: 30px;
    .container {
      background: #fff;
      position: relative;
      z-index: 1;
      padding: 30px 28px;
      border-radius: 4px;
      .tableBar {
        margin-bottom: 20px;
        .tableLab {
          float: right;
          span {
            cursor: pointer;
            display: inline-block;
            font-size: 14px;
            padding: 0 20px;
            color: $gray-2;
          }
        }
      }
      .tableBox {
        width: 100%;
        border: 1px solid $gray-5;
        border-bottom: 0;
      }
      .pageList {
        text-align: center;
        margin-top: 30px;
      }
      // 查询黑色按钮样式
      .normal-btn {
        background: #333333;
        color: white;
        margin-left: 20px;
      }
    }
  }
}
</style>

目录

  1. 前端环境搭建
  2. 技术选型
  3. 代码结构
  4. 环境准备
  5. 员工管理
  6. 员工分页查询
  7. 需求分析和接口设计
  8. 代码开发
  9. 启用禁用员工账号
  10. 需求分析和接口设计
  11. 代码开发
  12. 新增员工
  13. 需求分析和接口设计
  14. 代码开发
  15. 修改员工
  16. 需求分析和接口设计
  17. 代码开发
  18. 套餐管理
  19. 套餐分页查询
  20. 需求分析和接口设计
  21. 代码开发
  22. 启售停售套餐
  23. 需求分析和接口设计
  24. 代码开发
  25. 删除套餐
  26. 需求分析和接口设计
  27. 代码开发
  28. 新增套餐
  29. 需求分析和接口设计
  30. 代码解读
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • OpenClaw 多智能体架构配置指南
  • 面试实战:如何决定使用 HashMap 还是 TreeMap?
  • 基于 Isaac Lab 从零训练机器人行走
  • 从零开始使用 Isaac Lab 训练机器人行走
  • 免费开源AI工具:CoPaw与OpenFang整理
  • MySQL 内置函数实战指南:日期、字符串与数学运算
  • 图的寻路算法详解:基于深度优先搜索 (DFS) 的实现
  • 模拟算法基础:核心概念与典型案例分析
  • 模拟算法实战:核心概念与经典案例解析
  • 2025 年 3 月 CCF-GESP C++ 三级真题解析
  • AI Agent 架构:基础组成模块深度解析
  • 2026 年协作机器人十大品牌盘点
  • 基于 Python Reflex 搭建 ZeroClaw 本地 AI 管理面板
  • 无人机飞行空域申请全流程指南
  • Microi 吾码低代码平台技术特性与跨数据库集成方案
  • 大语言模型(LLM)微调方法总结
  • 基于魔塔社区环境从零微调 Yi 开源大模型实战
  • AI 核心概念解析:AIGC、RAG、Agent 与 MCP
  • Git 在 Windows 环境下的安装与使用教程
  • Ubuntu 22.04 安装 Docker 及 Docker Compose v2 教程

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online