苍穹外卖(前端)

苍穹外卖(前端)

前端环境搭建:

技术选型:

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

代码结构:

核心目录 / 文件:

目录 / 文件说明
apki封装 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 = new Array 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 //判断当前是单个删除还是批量删除 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 方法,添加提示和确认

//删除套餐 handleDelete(type: string, id: string) { if (type === 'B' && this.multipleSelection.length === 0) { this.$message('请选择需要删除的套餐!') return } this.$confirm('确定删除该套餐?', '确定删除', { confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning', }).then(() => { let //判断当前是单个删除还是批量删除 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:

<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 //判断当前是单个删除还是批量删除 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> 

Read more

AstrBot+NapCat 一键部署 5 分钟搞定智能 QQ 机器人!cpolar解决公网访问 :cpolar 内网穿透实验室第 777 个成功挑战

AstrBot+NapCat 一键部署 5 分钟搞定智能 QQ 机器人!cpolar解决公网访问 :cpolar 内网穿透实验室第 777 个成功挑战

这篇教程会带你用最简单的方式:**只用一份 docker-compose,一次命令,5 分钟以内完成 AstrBot + NapCat 部署,把 DeepSeekAI 接入你的 QQ。**AstrBot 本身就是为 AI 而生的现代化机器人框架,插件丰富、支持 DeepSeek/OpenAI 等大模型、带 WebUI、可扩展性强,真正做到"搭好就能用"。照着做,你马上就能拥有属于自己的 QQ AI 机器人。 1 项目介绍 1.1 AstrBot是什么? GitHub 仓库:https://github.com/AstrBotDevs/AstrBot AstrBot 是一个专为 AI 大模型设计的开源聊天机器人框架,

RTMP高清推流直播/视频转码EasyDSS在无人机RTMP直播场景中的应用技术解析

RTMP高清推流直播/视频转码EasyDSS在无人机RTMP直播场景中的应用技术解析

在无人机直播赛道竞争日趋激烈的当下,推流稳定性、画面清晰度、延迟控制与操作便捷性,成为衡量平台实力的核心指标。EasyDSS流媒体平台之所以能在众多解决方案中脱颖而出,关键在于其深度整合机巢、RTMP推流、高清直播、低延迟等几大核心能力,形成“全链路优化、全场景适配”的竞争优势,彻底解决传统无人机直播的痛点,为用户提供更优质的直播体验。 核心优势1:RTMP推流优化,筑牢低延迟传输防线。 RTMP协议作为直播推流的主流协议,其低延迟特性的发挥直接决定直播体验。EasyDSS对RTMP协议进行深度优化,优化数据包传输逻辑,减少网络波动对推流的影响,将端到端延迟控制在2-5秒,远低于行业平均的10秒以上延迟,完美满足应急调度、实时监控等对延迟敏感的场景需求。即便在野外复杂网络环境下,也能实现流畅推流。 核心优势2:高清编码加持,呈现极致视觉体验。 高清直播是无人机直播的核心需求,尤其是在巡检、安防等场景中,画面清晰度直接影响问题识别的准确性。 EasyDSS采用H.265高效编码技术,相较于传统H.264编码,在保持相同视觉质量的前提下,压缩效率提升一倍,可在低带宽环

MK米客方德SD NAND:无人机存储的高效解决方案

MK米客方德SD NAND:无人机存储的高效解决方案

在无人机技术迅猛发展的当下,飞控系统的数据记录对于飞行性能剖析、故障排查以及飞行安全保障极为关键。以往,SD 卡是飞控 LOG 记录常见的存储介质,但随着技术的革新,新的存储方案不断涌现。本文聚焦于以 ESP32 芯片为主控制器的无人机,创新性采用 SD NAND 芯片 MKDV32GCL-STPA 芯片进行 SD NAND 存储,测试其在飞控 LOG 记录功能中的表现。 米客方德 SD NAND 芯片特性 免驱动优势:与普通存储设备不同,在该应用场景下,SD NAND 无需编写复杂的驱动程序。这极大地简化了开发流程,缩短了开发周期,减少了潜在的驱动兼容性问题,让开发者能够更专注于实现核心功能。 自带坏块管理功能:存储设备出现坏块难以避免,而 MKDV32GCL - STPA 芯片自带的坏块管理机制可自动检测并处理坏块。这确保了数据存储的可靠性,避免因坏块导致的数据丢失或错误写入,提升了整个存储系统的稳定性。 尺寸小巧与强兼容性:

NoneBot+Lagrange搭建qq机器人保姆级别教程

NoneBot+Lagrange搭建qq机器人保姆级别教程

前言 因为一些原因,go-cqhttp不一定能使用,gocq的作者也是呼吁大家尽快转移到无头NTQQ项目当中去,其中就有很多优秀的平替作品,如:NapNeko/NapCatQQ: 基于NTQQ的无头Bot框架 (github.com)还有今天要介绍的LagrangeDev/Lagrange.Core: An Implementation of NTQQ Protocol, with Pure C#, Derived from Konata.Core (github.com) 准备工作 1. 一台电脑或服务器(服务器搭建bot的教程后面会出) 2. Lagrange程序 3. python3.9及以上版本 4. nonebot插件 1.关于操作系统 可供选择的操作系统: 1. Windows 2. Linux 3. MacOS 2.Lagrange程序下载