苍穹外卖(前端)

前端环境搭建:

技术选型:

使用的前端技术栈:node.js、vue、ElementUI、axios、vuex、vue-router、typescript

代码结构:

核心目录 / 文件:

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

环境准备:

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

bash 复制代码
npm install

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

bash 复制代码
npm run serve

员工管理:

员工分页查询:

需求分析和接口设计:

代码开发:

步骤一:制作页面头部

html 复制代码
<div class="tableBar">
    <label style="margin-right: 5px">
        员工姓名: 
    </label>
    <el-input placeholder="请输入员工姓名" style="width: 15%" />
    <el-button type="primary" style="margin-left: 20px">查询</el-button>
    <el-button type="primary" style="float: right">+添加员工</el-button>
</div>

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

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

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

html 复制代码
<el-button type="primary" style="margin-left: 20px" @click="pageQuery()">查询</el-button>

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

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

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

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

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

TypeScript 复制代码
import { getEmployeeList } from '@/api/employee'

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

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

html 复制代码
<el-input v-model="name" placeholder="请输入员工姓名" style="width: 15%" clearable />

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

TypeScript 复制代码
//分页查询
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 请求,查询第一页数据

TypeScript 复制代码
//声明周期方法
created() {
  this.pageQuery()
}

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

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

html 复制代码
<el-table :data="records" stripe class="tableBox">
  <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="操作" align="center">
    <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

分页组件代码:

html 复制代码
<el-pagination
  class="pageList"
  :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"
/>

事件处理函数:

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

启用禁用员工账号:

需求分析和接口设计:

代码开发:

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

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

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

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

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

TypeScript 复制代码
//启用、禁用员工账号
handleStartOrStop(row) {
  alert(`id=${row.id} status=${row.status}`)
}

步骤三:封装 API 请求

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

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

步骤四:完善处理函数

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

TypeScript 复制代码
//启用、禁用员工账号
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 方法中添加判断,如果是管理员账号则不允许修改状态并给出提示

TypeScript 复制代码
//启用、禁用员工账号
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)
      })
  })
}

新增员工:

需求分析和接口设计:

产品原型:

代码开发:

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

html 复制代码
<div class="tableBar">
  <label style="margin-right: 5px">员工姓名: </label>
  <el-input
    v-model="name"
    placeholder="请输入员工姓名"
    style="width: 15%"
    clearable
  />
  <el-button type="primary" style="margin-left: 20px" @click="pageQuery()">查询</el-button>
  <el-button type="primary" style="float: right" @click="handleAddEmp">+ 添加员工</el-button>
</div>

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

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

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

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

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

html 复制代码
<template>
  <div class="addBrand-container">
    <div class="container">
      <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 class="subBox">
          <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>

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

TypeScript 复制代码
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 中封装新增员工方法

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

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

TypeScript 复制代码
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)
            }
          })
      }
    });
  }
}

修改员工:

需求分析和接口设计:

代码开发:

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

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

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

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

TypeScript 复制代码
//修改员工,跳转至修改员工页面(组件)
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 生命周期中根据路由参数判断:

TypeScript 复制代码
<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 查询员工的方法

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

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

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

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

TypeScript 复制代码
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 指令,仅在新增操作时显示该按钮:

html 复制代码
<div class="subBox">
  <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 中封装修改员工的方法

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

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

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

TypeScript 复制代码
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)
            }
          })
      }
    }
  });
}

套餐管理:

套餐分页查询:

需求分析和接口设计:

产品原型:

代码开发:

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

html 复制代码
<div class="tableBar">
  <label style="margin-right: 10px">套餐名称: </label>
  <el-input v-model="name" style="width: 14%" clearable />
  <label style="margin-right: 10px; margin-left: 20px">套餐分类: </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 style="margin-right: 10px; margin-left: 20px">售卖状态: </label>
  <el-select v-model="status" style="width: 14%" placeholder="请选择" clearable>
    <el-option
      v-for="item in statusArr"
      :key="item.value"
      :label="item.label"
      :value="item.value"
    />
  </el-select>
  <el-button type="primary" style="margin-left: 20px">查询</el-button>
  <div style="float: right">
    <el-button type="danger">批量删除</el-button>
    <el-button type="info">+ 新建套餐</el-button>
  </div>
</div>

模型数据定义:

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

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

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

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

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

TypeScript 复制代码
<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" 事件

html 复制代码
<el-button type="primary" style="margin-left: 20px" @click="pageQuery()">查询</el-button>

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

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

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

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

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

TypeScript 复制代码
//套餐分页查询
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 请求,查询第一页数据

TypeScript 复制代码
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

html 复制代码
<el-table :data="records" stripe class="tableBox">
  <el-table-column prop="image" label="图片" width="80px">
    <template slot-scope="scope">
      <el-image style="width: 80px; height: 40px; border: none" :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="操作" align="center">
    <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

分页组件代码:

html 复制代码
<el-pagination
  background
  class="pageList"
  @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>

事件处理函数:

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

启售停售套餐:

需求分析和接口设计:

产品原型:

代码开发:

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

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

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

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

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

TypeScript 复制代码
//套餐起售、停售
handleStartOrStop(row) {
  alert(`id=${row.id} status=${row.status}`)
}

步骤三:封装 API 请求

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

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

步骤四:完善处理函数

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

TypeScript 复制代码
//套餐起售、停售
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 请求删除套餐

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

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

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

html 复制代码
<el-button type="danger" @click="handleDelete">批量删除</el-button>
TypeScript 复制代码
//删除套餐
handleDelete() {
  alert('删除套餐')
}

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

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

html 复制代码
<el-table :data="records" stripe class="tableBox" @selection-change="handleSelectionChange">
  <el-table-column type="selection" width="25" />
  <!-- 其他列 -->
</el-table>

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

TypeScript 复制代码
data() {
  return {
    // ...其他数据
    multipleSelection: [] //当前被选中的行
  }
}

编写事件处理函数:

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

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

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

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

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

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

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

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

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

TypeScript 复制代码
//删除套餐
handleDelete(type: string, id: string) {
  let param = ''
  //判断当前是单个删除还是批量删除
  if (type === 'S') {
    //单个删除
    param = id
  } else {
    //批量删除
    const arr = new Array
    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 方法,添加提示和确认

TypeScript 复制代码
//删除套餐
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 = new Array
      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:

html 复制代码
<el-button type="info" @click="handleAdd"> + 新建套餐 </el-button>

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

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

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

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

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

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

步骤四:核心代码解读

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

html 复制代码
<template>
  <div class="dashboard-container">
    <div class="container">
      <div class="tableBar">
        <label style="margin-right: 10px">套餐名称:</label>
        <el-input style="width: 14%" clearable v-model="name" />
        <label style="margin-right: 10px; margin-left: 20px">套餐分类:</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 style="margin-right: 10px; margin-left: 20px">售卖状态:</label>
        <el-select v-model="status" style="width: 14%" placeholder="请选择" clearable>
          <el-option v-for="item in statusArr"
                     :key="item.value"
                     :label="item.label"
                     :value="item.value" />
        </el-select>
        <el-button type="primary" style="margin-left: 20px" @click="pageQuery()"> 查询 </el-button>
        <div style="float: right">
          <el-button type="danger" @click="handleDelete('B')"> 批量删除 </el-button>
          <el-button type="info" @click="handleAdd"> + 新建套餐 </el-button>
        </div>
      </div>
      <el-table :data="records" stripe class="tableBox" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="25" />
        <el-table-column prop="name" label="套餐名称" />
        <el-table-column label="图片">
          <template slot-scope="scope">
            <el-image style="width: 80px; height: 40px; border: none" :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="tableColumn-status" :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="操作" align="center" width="250px">
          <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 class="pageList"
                     :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 = new Array
            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>
相关推荐
zheshiyangyang2 小时前
前端面试基础知识整理【Day-6】
前端·面试·职场和发展
星火开发设计2 小时前
关联式容器:set 与 multiset 的有序存储
java·开发语言·前端·c++·算法
未来龙皇小蓝2 小时前
RBAC前端架构-06:使用localstorage及Vuex用户信息存储逻辑
前端·vue.js
BUG集结者2 小时前
【Navigation3】结合ViewModel(二)
前端
用户80806181436932 小时前
JavaScript 异步编程完全指南:从入门到精通
前端
凯里欧文4272 小时前
CSS Grid 案例
前端·css
天若有情6732 小时前
Vuex 的核心作用深度解析:构建高效可维护的 Vue 应用状态管理体系
前端·javascript·vue.js·vuex
哆啦A梦15882 小时前
Vue3魔法手册 作者 张天禹 015_插槽
前端·vue.js·typescript·vue3
lisypro12 小时前
gin-vue-admin项目使用命令行进行启动
前端·vue.js·golang·gin