【易售小程序项目】悬浮按钮+出售闲置商品+商品分类选择【后端基于若依管理系统开发】

简介: 【易售小程序项目】悬浮按钮+出售闲置商品+商品分类选择【后端基于若依管理系统开发】

界面效果

【悬浮按钮】

【闲置商品描述信息填写界面】

【商品分类选择界面】

【分类选择完成】

界面实现

悬浮按钮实现

悬浮按钮漂浮于页面之上,等页面滑动时,悬浮按钮的位置相对于屏幕不会改变

【悬浮按钮组件】

<template>
  <div class="floating-button" @click="onClick">
    <slot>
      <!-- 这里可以放置默认的按钮样式等 -->
    </slot>
  </div>
</template>
<script>
  export default {
    name: 'FloatButton',
    props: {
    },
    data() {
      return {
      };
    },
    mounted() {
    },
    methods: {
      onClick() {
        this.$emit('click');
      }
    },
    computed: {
    }
  };
</script>
<style>
  .floating-button {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 58px;
    height: 58px;
    border-radius: 50%;
    background-color: #007aff;
    color: #fff;
    position: fixed;
    right: 20rpx;
    bottom: 20rpx;
  }
  /* 按钮点击之后会产生偏移 */
  .floating-button:active {
    transform: translate(0, 2px);
  }
</style>

【在其他界面中使用】

因为组件中使用了插槽,可以在该组件中插入其他组件

<FloatButton @click="cellMyProduct()">
  <u--image :src="floatButtonPic" shape="circle" width="60px" height="60px"></u--image>
</FloatButton>

示例中,我给浮动按钮的插槽中添加了图片组件,图片使用项目静态资源中的图片

data() {
    return {
      title: 'Hello',
      floatButtonPic: require("@/static/cellLeaveUnused.png"),
    }
  },

商品分类选择界面

分类数据的格式如下

{
  "msg": "productCategoryItemVoList",
  "code": 200,
  "data": [
    {
      "id": 5,
      "name": "数码产品",
      "children": [
        {
          "id": 10,
          "name": "电脑",
          "children": [
            {
              "id": 12,
              "name": "台式机",
              "children": [
              ],
              "icon": "a",
              "sort": 1,
              "description": "a"
            },
            {
              "id": 13,
              "name": "笔记本",
              "children": [
              ],
              "icon": "a",
              "sort": 1,
              "description": "a"
            }
          ],
          "icon": "a",
          "sort": 1,
          "description": "a"
        },
        {
          "id": 11,
          "name": "手机",
          "children": [
            {
              "id": 14,
              "name": "老人机",
              "children": [
              ],
              "icon": "a",
              "sort": 1,
              "description": "a"
            },
            {
              "id": 15,
              "name": "智能手机",
              "children": [
              ],
              "icon": "a",
              "sort": 1,
              "description": "a"
            }
          ],
          "icon": "a",
          "sort": 1,
          "description": "a"
        }
      ],
      "icon": "a",
      "sort": 1,
      "description": "a"
    },
    {
      "id": 6,
      "name": "服装",
      "children": [
      ],
      "icon": "a",
      "sort": 1,
      "description": "a"
    },
    {
      "id": 7,
      "name": "教育用品",
      "children": [
      ],
      "icon": "a",
      "sort": 1,
      "description": "a"
    },
    {
      "id": 8,
      "name": "食品",
      "children": [
      ],
      "icon": "a",
      "sort": 1,
      "description": "a"
    }
  ]
}
<template>
  <view class="container">
    <u-toast ref="uToast"></u-toast>
    <view class="titleView">
      <view class="controlButton" @click="back">
        <u-icon name="arrow-left" color="#ffffff"></u-icon>
        上一级
      </view>
      <text>{{getCategoryLayerName()}}</text>
      <view class="controlButton" @click="commit">
        完成
        <u-icon name="checkmark" color="#ffffff"></u-icon>
      </view>
    </view>
    <view style="height: 20px;"></view>
    <u-empty v-if="curLayerCategoryData.length==0" mode="search" texColor="#ffffff" iconSize="180" iconColor="#2b92ff" text="分类选择完成,请点击右上角的完成" textColor="#2b92ff" textSize="18" marginTop="30">
    </u-empty>
    <u-list @scrolltolower="scrolltolower" v-else>
      <u-list-item v-for="(category, index) in curLayerCategoryData" :key="index">
        <u-cell :title="category.name" @click="selectCurCategory(category)">
          <u-avatar slot="icon" shape="square" size="35" :src="category.icon"
            customStyle="margin: -3px 5px -3px 0"></u-avatar>
        </u-cell>
      </u-list-item>
    </u-list>
  </view>
</template>
<script>
  import {
    getProductCategoryTree
  } from "@/api/market/category.js";
  export default {
    data() {
      return {
        categoryNameList: ["分类未选择"],
        categoryTreeData: [],
        // 当前层级分类数据
        curLayerCategoryData: [],
        // 已经选择的层级分类数据
        haveSelectLayerCategoryData: [],
        // 层级
        layer: 0,
        // 商品所属分类
        productCategoryId: 0,
      }
    },
    created() {
      this.getProductCategoryTree();
    },
    methods: {
      getCategoryLayerName() {
        let str = '';
        for (let i = 0; i < this.categoryNameList.length - 1; i++) {
          str += this.categoryNameList[i] + '/';
        }
        return str + this.categoryNameList[this.categoryNameList.length - 1];
      },
      /**
       * 查询商品分类的树形结构数据
       */
      getProductCategoryTree() {
        getProductCategoryTree().then(res => {
          // console.log("getProductCategoryTree:" + JSON.stringify(res));
          this.categoryTreeData = res.data;
          this.curLayerCategoryData = this.categoryTreeData;
        })
      },
      /**
       * 选择分类
       * @param {Object} category 当前选择的分类
       */
      selectCurCategory(category) {
        if (this.layer == 0) {
          this.categoryNameList = [];
        }
        this.categoryNameList.push(category.name);
        this.productCategoryId = category.id;
        this.layer++;
        // 将当前层的数据设置进haveSelectLayerCategoryData
        this.haveSelectLayerCategoryData.push(this.curLayerCategoryData);
        this.curLayerCategoryData = category.children;
        if (this.curLayerCategoryData.length == 0) {
          this.$refs.uToast.show({
            type: 'success',
            message: "分类选择完成,请提交数据"
          })
        }
      },
      /**
       * 返回上一级
       */
      back() {
        if (this.layer == 0) {
          this.$refs.uToast.show({
            type: 'warning',
            message: "已经是第一层级,无法返回上一级"
          })
        } else {
          this.layer--;
          this.curLayerCategoryData = this.haveSelectLayerCategoryData[this.haveSelectLayerCategoryData.length -
            1];
          // 删掉最后一条数据
          this.haveSelectLayerCategoryData.splice(this.haveSelectLayerCategoryData.length - 1, 1);
        }
      },
      /**
       * 提交分类数据
       */
      commit() {
        if (this.curLayerCategoryData.length != 0) {
          this.$refs.uToast.show({
            type: 'error',
            message: "分类还没有选择完成,请继续选择"
          })
        } else {
          uni.setStorageSync("productCategoryId", this.productCategoryId);
          uni.setStorageSync("categoryNameList", this.categoryNameList);
          uni.navigateBack();
        }
      }
    }
  }
</script>
<style lang="scss">
  .container {
    background: #F6F6F6;
    min-height: 100vh;
    padding: 20rpx;
    .titleView {
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: #2b92ff;
      color: #ffffff;
      border-radius: 4px;
      .controlButton {
        // width: 100px;
        display: flex;
        // border: #2b92ff 1px solid;
        padding: 10px;
      }
    }
  }
</style>

使元素均匀分布

使用下面的代码,可以让元素在组件中的子组件在组件中横向均匀分布,效果如下图

<view class="titleView">
  <view class="controlButton" @click="back">
    <u-icon name="arrow-left" color="#ffffff"></u-icon>
    上一级
  </view>
  <text>{{getCategoryLayerName()}}</text>
  <view class="controlButton" @click="commit">
    完成
    <u-icon name="checkmark" color="#ffffff"></u-icon>
  </view>
</view>
display: flex;
justify-content: space-between;

闲置商品描述信息填写界面

<template>
  <view class="container">
    <u-toast ref="uToast"></u-toast>
    <view class="content">
      <view class="item">
        <view class="labelName">商品名称</view>
        <u--input placeholder="请输入商品名称" border="surround" v-model="product.name"></u--input>
      </view>
      <u-divider text="商品描述和外观"></u-divider>
      <!-- 商品描述 -->
      <u--textarea v-model="product.descripption" placeholder="请输入商品描述" height="150"></u--textarea>
      <!-- 图片上传 -->
      <view>
        <imageUpload v-model="product.picList" maxCount="9"></imageUpload>
      </view>
      <u-divider text="分类选择/自定义标签"></u-divider>
      <!-- 分类选择/自定义标签 -->
      <view class="item">
        <view class="labelName">分类</view>
        <view class="selectTextClass" @click="selectCategory">{{getCategoryLayerName()}}</view>
      </view>
      <!-- 商品的属性 新度 功能完整性 -->
      <view class="item">
        <view class="labelName">成色</view>
        <view class="columnClass">
          <view :class="product.fineness==index?'selectTextClass':'textClass'"
            v-for="(finessName,index) in finenessList" :key="index" @click="changeFineness(index)">
            {{finessName}}
          </view>
        </view>
      </view>
      <view class="item">
        <view class="labelName">功能状态</view>
        <view class="columnClass">
          <view :class="product.functionalStatus==index?'selectTextClass':'textClass'"
            v-for="(functionName,index) in functionList" :key="index"
            @click="changeFunctionalStatus(index)">{{functionName}}
          </view>
        </view>
      </view>
      <u-row customStyle="margin-bottom: 10px">
        <u-col span="5">
          <view class="item">
            <view class="labelName">数量</view>
            <u--input placeholder="请输入商品数量" border="surround" v-model="product.number"></u--input>
          </view>
        </u-col>
        <u-col span="7">
          <view class="item">
            <view class="labelName">计量单位</view>
            <u--input placeholder="请输入计量单位" border="surround" v-model="product.unit"></u--input>
          </view>
        </u-col>
      </u-row>
      <!-- 价格 原价 现价 -->
      <u-divider text="价格"></u-divider>
      <u-row customStyle="margin-bottom: 10px">
        <u-col span="6">
          <view class="item">
            <view class="labelName">原价</view>
            <u-input placeholder="请输入原价" border="surround" v-model="product.originalPrice" color="#ff0000"
              @blur="originalPriceChange">
              <u--text text="¥" slot="prefix" margin="0 3px 0 0" type="error"></u--text>
            </u-input>
          </view>
        </u-col>
        <u-col span="6">
          <view class="item">
            <view class="labelName">出售价格</view>
            <u-input placeholder="请输入出售价格" border="surround" v-model="product.price" color="#ff0000"
              @blur="priceChange">
              <u--text text="¥" slot="prefix" margin="0 3px 0 0" type="error"></u--text>
            </u-input>
          </view>
        </u-col>
      </u-row>
      <u-button text="出售" size="large" type="primary" @click="uploadSellProduct"></u-button>
    </view>
  </view>
</template>
<script>
  import imageUpload from "@/components/ImageUpload/ImageUpload.vue";
  import {
    uploadSellProduct
  } from "@/api/market/prodct.js"
  export default {
    components: {
      imageUpload
    },
    onShow: function() {
      let categoryNameList = uni.getStorageSync("categoryNameList");
      if (categoryNameList) {
        this.categoryNameList = categoryNameList;
        this.product.productCategoryId = uni.getStorageSync("productCategoryId");
        uni.removeStorageSync("categoryNameList");
        uni.removeStorageSync("productCategoryId");
      }
    },
    data() {
      return {
        product: {
          name: '',
          descripption: '',
          picList: [],
          productCategoryId: 0,
          number: 1,
          unit: '',
          isContribute: 0,
          originalPrice: 0.00,
          price: 0.00,
          // 成色
          fineness: 0,
          // 功能状态
          functionalStatus: 0,
          brandId: 0
        },
        value: 'dasdas',
        categoryNameList: ["选择分类"],
        finenessList: ["全新", "几乎全新", "轻微使用痕迹", "明显使用痕迹", "外观破损"],
        functionList: ["功能完好无维修", "维修过,可正常使用", "有小问题,不影响使用", "无法正常使用"]
      }
    },
    methods: {
      getCategoryLayerName() {
        let str = '';
        for (let i = 0; i < this.categoryNameList.length - 1; i++) {
          str += this.categoryNameList[i] + '/';
        }
        return str + this.categoryNameList[this.categoryNameList.length - 1];
      },
      /**
       * 价格校验
       * @param {Object} price 价格
       */
      priceVerify(price) {
        if (isNaN(price)) {
          this.$refs.uToast.show({
            type: 'error',
            message: "输入的价格不是数字,请重新输入"
          })
          return false;
        }
        if (price < 0) {
          this.$refs.uToast.show({
            type: 'error',
            message: "输入的价格不能为负数,请重新输入"
          })
          return false;
        }
        if (price.toString().indexOf('.') !== -1 && price.toString().split('.')[1].length > 2) {
          this.$refs.uToast.show({
            type: 'error',
            message: "输入的价格小数点后最多只有两位数字,请重新输入"
          })
          return false;
        }
        return true;
      },
      originalPriceChange() {
        let haha = this.priceVerify(this.product.originalPrice);
        if (haha === false) {
          console.log("haha:" + haha);
          this.product.originalPrice = 0.00;
          console.log("this.product" + JSON.stringify(this.product));
        }
      },
      priceChange() {
        if (this.priceVerify(this.product.price) === false) {
          this.product.price = 0.00;
        }
      },
      /**
       * 修改成色
       * @param {Object} index
       */
      changeFineness(index) {
        this.product.fineness = index;
      },
      /**
       * 修改功能状态
       * @param {Object} index
       */
      changeFunctionalStatus(index) {
        this.product.functionalStatus = index;
      },
      /**
       * 上传闲置商品
       */
      uploadSellProduct() {
        uploadSellProduct(this.product).then(res => {
          this.$refs.uToast.show({
            type: 'success',
            message: "您的商品已经发布到平台"
          })
          setTimeout(() => {
            uni.reLaunch({
              url: "/pages/index/index"
            })
          }, 1500)
        })
      },
      /**
       * 选择分类
       */
      selectCategory() {
        uni.navigateTo({
          url: "/pages/sellMyProduct/selectCategory"
        })
      }
    }
  }
</script>
<style lang="scss">
  .container {
    background: #F6F6F6;
    min-height: 100vh;
    padding: 20rpx;
    .content {
      background: #ffffff;
      padding: 20rpx;
      .item {
        display: flex;
        align-items: center;
        height: 50px;
        margin-bottom: 5px;
        .labelName {
          width: 70px;
          margin-right: 10px;
        }
        .textClass {
          display: inline;
          background: #F7F7F7;
          padding: 10px;
          margin-right: 15px;
          border-radius: 5px;
        }
        .selectTextClass {
          display: inline;
          background: #2B92FF;
          padding: 10px;
          margin-right: 15px;
          border-radius: 5px;
          color: #ffffff;
          font-weight: bold;
        }
        .columnClass {
          // height: 50px;
          display: flex;
          align-items: center;
          width: calc(100% - 70px);
          overflow-x: auto;
          // // 让内容只有一行
          white-space: nowrap;
        }
        .columnClass::-webkit-scrollbar {
          background-color: transparent;
          /* 设置滚动条背景颜色 */
          // width: 0px;
          height: 0px;
        }
      }
    }
  }
</style>

价格校验

价格是商品比较关键的属性,一定要确保其数据没有问题,所以在用户提交之前一定要对商品的价格进行校验,防止用户乱输或者输错数据,这里对价格有如下规定:

  • 输入的价格必须是数字,不可以是字符串
  • 输入的价格必须是正数,不可以是负数
  • 输入的价格的小数点有限制,不可以输入太多小数点

那么校验应该在什么时候触发呢?本示例在用户输入结束之后,手指离开输入组件时触发,即当元素失去焦点时触发,使用的是@blur事件

/**
 * 价格校验
* @param {Object} price 价格
 */
priceVerify(price) {
  if (isNaN(price)) {
    this.$refs.uToast.show({
      type: 'error',
      message: "输入的价格不是数字,请重新输入"
    })
    return false;
  }
  if (price < 0) {
    this.$refs.uToast.show({
      type: 'error',
      message: "输入的价格不能为负数,请重新输入"
    })
    return false;
  }
  if (price.toString().indexOf('.') !== -1 && price.toString().split('.')[1].length > 2) {
    this.$refs.uToast.show({
      type: 'error',
      message: "输入的价格小数点后最多只有两位数字,请重新输入"
    })
    return false;
  }
  return true;
},


目录
相关文章
预约按摩小程序开发,为什么很多上门按摩平台根本招聘不到优秀技师?
上门按摩平台面临招不到优秀技师的问题,主要原因是平台众多,技师选择多样。为解决此问题,平台可引入技师等级制度,根据订单数量和好评率划分高、低等级技师。高等级技师可享受70%-90%的高提成及首页推荐,这不仅能激励技师的积极性,还能帮助平台筛选出优质技师,提升服务质量和口碑,形成良性循环。
|
4天前
|
小程序 云计算 Android开发
发者社区 云计算 文章 正文 小程序开发与公众号用户关联推送消息(九)
发者社区 云计算 文章 正文 小程序开发与公众号用户关联推送消息(九)
21 3
|
10天前
|
小程序
|
14天前
|
API 持续交付 开发者
后端开发中的微服务架构实践与挑战
在数字化时代,后端服务的构建和管理变得日益复杂。本文将深入探讨微服务架构在后端开发中的应用,分析其在提高系统可扩展性、灵活性和可维护性方面的优势,同时讨论实施微服务时面临的挑战,如服务拆分、数据一致性和部署复杂性等。通过实际案例分析,本文旨在为开发者提供微服务架构的实用见解和解决策略。
|
7天前
|
存储 SQL API
探索后端开发:构建高效API与数据库交互
【10月更文挑战第36天】在数字化时代,后端开发是连接用户界面和数据存储的桥梁。本文深入探讨如何设计高效的API以及如何实现API与数据库之间的无缝交互,确保数据的一致性和高性能。我们将从基础概念出发,逐步深入到实战技巧,为读者提供一个清晰的后端开发路线图。
|
6天前
|
JSON 前端开发 API
后端开发中的API设计与文档编写指南####
本文探讨了后端开发中API设计的重要性,并详细阐述了如何编写高效、可维护的API接口。通过实际案例分析,文章强调了清晰的API设计对于前后端分离项目的关键作用,以及良好的文档习惯如何促进团队协作和提升开发效率。 ####
|
8天前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
27 4
|
7天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
【10月更文挑战第36天】本文将引导您探索Node.js的世界,通过实际案例揭示其背后的原理和实践方法。从基础的安装到高级的异步处理,我们将一起构建一个简单的后端服务,并讨论如何优化性能。无论您是新手还是有经验的开发者,这篇文章都将为您提供新的视角和深入的理解。
|
12天前
|
Web App开发 存储 JavaScript
深入浅出Node.js后端开发
【10月更文挑战第31天】本文将引导你进入Node.js的奇妙世界,探索其如何革新后端开发。通过浅显易懂的语言和实际代码示例,我们将一起学习Node.js的核心概念、搭建开发环境,以及实现一个简单但完整的Web应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇通往高效后端开发的大门。