springboot vue 开源 会员收银系统 (6) 收银台的搭建

前言

完整版演示

前面我们对会员系统 分类和商品的开发 完成了收银所需的基础信息 下面我们开始完成收银台的开发

简单画了一个收银的流程图大家参考下

从这张图我们可以分析一下几点

  • 可以选择会员或散客收银
  • 选择会员使用相应的会员价结算
  • 使用会员卡则在价格基础根据卡折扣结算

根据上述分析我搭建了一个简单的收银台

  • 左边显示会员和已选择的商品信息
  • 右边显示商品的价格信息
  • 商品选择后可以修改数字
  • 总金额自动计算
  • 后续完成挂单功能便于保证收银连续性

开发中运用的组件

  1. Tabs 标签页 和 Card 完成右侧的商品选择
  2. el-autocomplete 完成会员的检索
  3. InputNumber Table 完成购物车开发

收银台运用过多组件会导致页面很乱 我们后续需要进行组件的封装

javascript 复制代码
<template>
  <div class="app-container"  style="border-box:box-sizing;">

<!--    left-->
    <el-row :gutter="30" style="">
      <el-col :span="10">

        <el-autocomplete
          style="width: 100%"
          popper-class="my-autocomplete"
          v-model="querySearchStr"
          :fetch-suggestions="querySearch"
          placeholder="会员检索"
          @select="handleSelect">
          <i
            class="el-icon-search el-input__icon"
            slot="suffix"
           >
          </i>
          <template slot-scope="{ item }">
            <div v-if="item.errorMsg">
              {{item.errorMsg}}
            </div>

            <template v-else>
              <div class="name">
                <i class="el-icon-user"></i>
                {{ item.memberName }}
              </div>
              <span class="addr">
                <i class="el-icon-phone"></i>
                {{ item.memberPhone }}
              </span>
            </template>
          </template>
        </el-autocomplete>

        <el-descriptions  border title="用户信息" v-if="member.memberId" style="margin-top: 20px;margin-bottom: 20px;">
          <el-descriptions-item label="用户名">{{member.memberName}}</el-descriptions-item>
          <el-descriptions-item label="手机号">{{member.memberPhone}}</el-descriptions-item>
          <el-descriptions-item label="备注">
             {{member.remark}}
          </el-descriptions-item>
        </el-descriptions>
        <el-descriptions  border title="用户信息" v-else style="margin-top: 20px;margin-bottom: 20px;">
          <el-descriptions-item label="用户名">散客</el-descriptions-item>
        </el-descriptions>
        <div class="grid-content bg-purple">
          <el-table

            border
            height="400"
            max-height="400"
            :data="shopCart"
            style="width: 100%;margin-bottom: 20px;">

        <el-table-column
          align="center"
          prop="categoryName"
          label="分类"
          width="100">
        </el-table-column>

        <el-table-column
          align="center"
          prop="productName"
          label="商品名"
          >
        </el-table-column>

        <el-table-column
          align="center"
          prop="productAmount"
          label="价格"
          width="100"
        >
          <template slot-scope="scope">
            <span v-if="app.isEmpty(member.memberId)" class="price">{{ scope.row.productAmount }}</span>
            <span v-else="member.memberId" class="price">{{ scope.row.productMemberAmount }}</span>
          </template>
        </el-table-column>

        <el-table-column
          align="center"
          prop="count"
          label="数量"
          width="200"
        >
          <template slot-scope="scope">
            <el-input-number size="mini" :min="0" :max="100" v-model="scope.row.count" @change="handleChange($event,scope)" ></el-input-number>
          </template>
        </el-table-column>

         <el-table-column
           align="center"
          prop="totalPrice"
          label="总金额"
          width="100"
         >
           <template slot-scope="scope">
             <span class="price">{{ scope.row.totalPrice }}</span>
           </template>
        </el-table-column>
      </el-table>
          <el-divider></el-divider>
          总金额: <span class="price">{{app.getFloatStr(getTotalPrice)}}</span>
          <el-divider></el-divider>

          <el-row>
            <el-button type="success">结算</el-button>
            <el-button type="warning">挂单</el-button>
            <el-button type="primary">取单</el-button>
          </el-row>
      </div>
      </el-col>
<!--right-->
      <el-col :span="14">
        <div class="grid-content bg-purple">
        <el-tabs  v-model="activeName" @tab-click="handleClick" type="border-card" style="height: calc(100vh - 180px);width: 100%;overflow: scroll;overflow:hidden;">
          <el-tab-pane
            v-for="(item, index) in categoryList"
            :key="item.categoryId"
            :label="item.categoryName"
            :name="item.categoryId"
          >
            <div>
                <el-row :gutter="20" style="" v-if="productList.length>0">
                <el-col @click.native="chooseProduct(item)" :span="6" v-for="item in productList" :key="item.productId" style="margin-top: 20px;cursor: pointer;">
                  <el-card :body-style="{ padding: '0px' }" style="height: 230px;">

                    <div style="height: 150px;display: flex;align-items: center;justify-content: center; 	box-sizing: border-box;padding-top: 10px;">
                      <img style="width:85%;height: 140px;border-radius: 5px;border: 1px solid #eee;" :src="baseUrl  + item.productImageUrl" class="image"/>
                    </div>
                    <div style="padding:10px 0 0 10px;font-size: 16px;">
                      {{item.productName}}
                    </div>
                    <div style="padding:5px 0 0 10px;">
                      <span style="font-size: 14px;"> 散客价 <span class="price">¥ {{item.productAmount}}</span></span><br/>
                      <span style="font-size: 14px;"> 会员价 <span class="price">¥ {{item.productMemberAmount}}</span></span>
                    </div>

                  </el-card>
                </el-col>
              </el-row>
              <div v-else>
                <el-empty description="该分类商品为空">
                </el-empty>
                </div>
            </div>
          </el-tab-pane>
        </el-tabs>
        </div>
      </el-col>
    </el-row>
  </div>
</template>

<script>
    import {accAdd, accMul,isEmpty} from "@/utils";
    import confirm from "@/utils/confirm";
    import {getCategoryList} from "@/api/business/category/category";
    import {getProductList} from "@/api/business/product/product";
    import {getMemberList} from "@/api/business/member/member";

    export default {
        name: "cashierDesk",
        data(){
          return{
            shopCart: [],
            categoryList: [],
            productList: [],
            activeName: "",
            member: {},
            querySearchStr: '',
          }
        },
      mounted() {
          this.getCategoryList()
      },
      computed:{
        getTotalPrice(){
          let totalPrice = 0;
          for(let product of this.shopCart){
            const noMember = isEmpty(this.member.memberId)
            let productTotalPrice
            if(noMember){
               productTotalPrice = accMul(product.productAmount,product.count)
            }else{
              productTotalPrice = accMul(product.productMemberAmount,product.count)
            }

            this.$set(product, 'totalPrice', productTotalPrice)
            totalPrice = accAdd(totalPrice,productTotalPrice);
          }
          return totalPrice;
        }
      },
      watch:{
        shopCart(newVal,oldVal){
          let showCardList = newVal
          console.log('这是监听属性新的')
        }
      },
      methods:{
        querySearch(queryString, cb) {
            if(queryString){
              getMemberList({querySearch:queryString}).then(res=>{
                // 调用 callback 返回建议列表的数据
                cb(res.data);
              })
            }else{
              cb([
                {errorMsg:'暂无数据'}
              ])
            }

          },
          getCategoryList(){
            getCategoryList().then(res=>{
              this.categoryList = res.data
              if(this.categoryList.length>0){
                this.activeName = this.categoryList[0].categoryId
                getProductList({categoryId:this.activeName }).then(res=>{
                  this.productList = res.data
                })
              }
            })
          },
         handleClick(e){
            //拿到分类id
           const categoryId = e.name
           getProductList({categoryId:categoryId}).then(res=>{
             this.productList = res.data
           })
         },
        handleChange(e,scope){
          console.log(e)
          console.log(scope.$index)
          if(e==0){
            confirm('要删除该条目吗?').then(res=>{
              if(res){
                this.shopCart.splice(scope.$index,1)
              }else{
                this.shopCart[scope.$index].count = 1
              }
            })
          }
        },
        chooseProduct(e){
            let product = e;
            let findProductIndex = this.shopCart.findIndex(p => p.productId ==  product.productId);
            console.log(findProductIndex)
            if(findProductIndex == -1){
              this.$set(product, 'count', 1)
              this.shopCart.push(product)
            }else{
              this.shopCart[findProductIndex].count += 1
            }
            console.log(this.shopCart)
        },
        handleSelect(e){
          console.log(e)
          this.member = e
        }
      }
    }
</script>

<style scoped>
  ::v-deep .el-tabs--border-card>.el-tabs__header .el-tabs__item{
    height: 50px;
    line-height: 50px;
    font-size: 14px;
  }

  .price{
    font-weight: bold;
    color: #ff5b57;
  }
</style>

代码地址
https://gitee.com/ddeatrr/memberShop

然后为了大家预览我把开发板也部署了 大家可以参考
http://store.gbadd.space/

相关推荐
维天说27 分钟前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane19941 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
sugar__salt2 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
杨充2 小时前
11.DDD与战术建模
设计模式·开源·代码规范
杨充2 小时前
12.综合实战图片框架
设计模式·开源·代码规范
噢,我明白了2 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
郝亚军2 小时前
使用Vue 3和Nginx打包和部署Vue.js项目的一般步骤
前端·vue.js·nginx
吠品2 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘2 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记3 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端