【易售小程序项目】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】

文章目录

  • 说明
  • 细节一:首页滑动到底部,需要查询下一页的商品
    • 界面预览
    • 页面实现
  • 细节二:当页面滑动到下方,出现一个回到顶端的悬浮按钮
  • 细节三:商品分列
    • 说明
    • 优化前后效果对比
    • 使用回溯算法实现
      • Controller
      • Service
      • 回溯算法
    • 优化:减少图片的网络请求
      • 数据表增加字段
      • 将数据表中已有数据的宽高比计算出来,并更新到数据表中
      • 修改商品发布页面的代码
      • Service改进
    • 优化:考虑分页的分组高宽比总和平衡
      • 页面代码
      • Controller
      • Service
      • 回溯算法
    • 优化:考虑商品信息的高宽比
      • Controller
      • Service
      • 回溯算法
  • 页面整体代码
  • 同项目其他文章

说明

之前已经在【UniApp开发小程序】小程序首页(展示商品、商品搜索、商品分类搜索)【后端基于若依管理系统开发】这篇文章中介绍了首页的实现,但是当时的实现并不是完善的,因为项目开发是一个持续的过程,也因为我是个人的第一次尝试开发这种类型的项目,很多细节没有提前构思清楚,因此这篇文章作为一个补充,用来优化前面的一些问题

细节一:首页滑动到底部,需要查询下一页的商品

界面预览

当滑动底部的时候,底部出现”正在加载“字样,同时向后端发送请求获取下一页的商品数据

在这里插入图片描述
当商品被全部加载出之后,显示“没有更多了”字样

在这里插入图片描述

页面实现

下面的方法可以监听用户滑动页面到达底部,当滑动到底部的时候,调用方法查询更多商品

// 监听用户滑动到底部
onReachBottom() {this.getMoreProductVo();
},

注意,当还有商品没有被查询出来时,才会调用listProductVo方法去找服务端查询数据。如果没有了,则提示“没有更多了”

/*** 获取下一页的商品*/
getMoreProductVo() {if (this.productList[0].length + this.productList[1].length >= this.total) {// this.$refs.uToast.show({// 	type: 'warning',// 	message: "已经加载完所有商品数据",// 	duration: 1000// })} else {if (this.loadData != true) {// console.log("--------------------------获取下一页商品---------------------------")this.page.pageNum++;// 显示正在加载this.loadmoreStatus = "loading";this.listProductVo().then(() => {if (this.productList[0].length + this.productList[1].length >= this.total) {// 没有更多了this.loadmoreStatus = "nomore";} else {// 加载更多this.loadmoreStatus = "loadmore";}});}}
},

细节二:当页面滑动到下方,出现一个回到顶端的悬浮按钮

增加一个标签

<!-- 回到上方按钮 -->
<u-back-top :scroll-top="scrollTop"></u-back-top>

因为标签绑定了一个变量,需要声明出来

// 用来控制滚动到最上方
scrollTop: 0

除此之外,还需要实时记录滚动的位置

// 在滑动过程实时获取现在的滚动条位置,并保存当前的滚动条位置
onPageScroll(e) {this.scrollTop = e.scrollTop;
},

细节三:商品分列

说明

上篇文章中,使用了最简单的方式来实现分列,那就是直接遍历一遍商品数组,依次将商品分到第一列和第二列,但是这样会出现两列商品高度不平衡的情况,如下图
在这里插入图片描述
因此,我们需要更换一种分组策略,用来平衡两列商品内容的高度,这样视觉效果更好。问题可以理解为:假设有十个物品,每个物品的长度不太一样,要求将这些物品分到两组中,最后两组物品长度总和最接近,请问需要怎么来分这两个组?

优化前后效果对比

在这里插入图片描述

使用回溯算法实现

因为采用的是分页查询,而且每次查询出来的数据量并不大,因此可以直接使用回溯算法获取所有的分组情况,最后选择出高度差距最小的分组方案即可

Controller

/*** 查询商品Vo列表*/
@PreAuthorize("@ss.hasPermi('market:product:list')")
@PostMapping("/listProductVo")
@ApiOperation("获取商品列表")
public AjaxResult listProductVo(@RequestBody ProductVo productVo) {startPage();if (productVo.getProductCategoryId() != null) {// --if-- 当分类不为空的时候,只按照分类来搜索productVo.setKeyword(null);}if (productVo.getIsSearchStar() != null && productVo.getIsSearchStar() == true) {productVo.setStarPeopleId(getLoginUser().getUserId());}List<ProductVo> productVoList = productService.selectProductVoList(productVo);// 将productVoList分成两组,要求两组的高度之和相差最小List<ProductVo>[] groups = productService.splitToTwoGroups(productVoList);Map<String, Object> map = new HashMap<>();TableDataInfo pageMes = getDataTable(productVoList);map.put("pageMes", pageMes);map.put("groups", groups);return AjaxResult.success(map);
}

Service

@Override
public List<ProductVo>[] splitToTwoGroups(List<ProductVo> productVoList) {List<ProductVo>[] resultArr = new List[2];for (int i = 0; i < resultArr.length; i++) {resultArr[i] = new ArrayList<>();}/// 数据准备// 获取每个图片的高宽比Map<Long, Double> idAndRatioMap = new HashMap<>();// 存储所有商品的idList<Long> idList = new ArrayList<>();long start = System.currentTimeMillis();for (ProductVo productVo : productVoList) {idList.add(productVo.getId());if (productVo.getPicList() != null && productVo.getPicList().size() > 0) {try {BufferedImage sourceImg = ImageIO.read(new URL(productVo.getPicList().get(0)).openStream());idAndRatioMap.put(productVo.getId(), sourceImg.getHeight() * 1.0 / sourceImg.getWidth());} catch (IOException e) {throw new RuntimeException(e);}} else {idAndRatioMap.put(productVo.getId(), 0.0);}}System.out.println("分组时间:" + (System.currentTimeMillis() - start) + "ms");/// 深度优先遍历,找出所有方案,选择两组高度差距最小的分组方案GroupDivide groupDivide = new GroupDivide();groupDivide.dfsSearch(idList, 0, new ArrayList<>(), idAndRatioMap);/// 最后处理分组List<Long> group1 = groupDivide.bestGroup1;List<Long> group2 = new ArrayList<>();for (Long id : idList) {if (group1.indexOf(id) == -1) {group2.add(id);}}for (ProductVo productVo : productVoList) {if (group1.indexOf(productVo.getId()) != -1) {resultArr[0].add(productVo);} else {resultArr[1].add(productVo);}}return resultArr;
}

由于下面的方法获取每个图片的高宽比都需要进行网络请求,因此速度较慢,因此需要进行优化

BufferedImage sourceImg = ImageIO.read(new URL(productVo.getPicList().get(0)).openStream());
idAndRatioMap.put(productVo.getId(), sourceImg.getHeight() * 1.0 / sourceImg.getWidth());

在这里插入图片描述

回溯算法

为了加速算法的求解,其中使用了减枝策略,不去搜索没有必要搜索的方案

package com.shm.algorithm;import com.ruoyi.common.utils.clone.CloneUtil;import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 首页商品数据分组** @Author dam* @create 2023/8/30 14:12*/
public class GroupDivide {/*** 最小间距*/private double minOffSet = Double.MAX_VALUE;/*** 存储最好的第一组*/public List<Long> bestGroup1=null;public void dfsSearch(List<Long> idList, int begin, List<Long> curGroup1, Map<Long, Double> idAndRatioMap) {if (begin == idList.size()) {// 递归完成return;}for (int i = begin; i < idList.size(); i++) {curGroup1.add(idList.get(i));// 计算组1的长度-组2的长度double offSet = calculateGroup1DifHeifGroup2Hei(idList, curGroup1, idAndRatioMap);if (offSet > minOffSet) {// 如果当前差距已经大于最小差距,执行剪枝,因为如果再往第一组增加图片的话,那差距只会更大,没必要再往下搜索了// 删除最后一个元素curGroup1.remove(curGroup1.size() - 1);continue;} else if (Math.abs(offSet) < minOffSet) {// 找到更小的间距,保存最优解minOffSet = Math.abs(offSet);bestGroup1 = CloneUtil.arrayListClone(curGroup1);}dfsSearch(idList, i + 1, curGroup1, idAndRatioMap);// 删除最后一个元素curGroup1.remove(curGroup1.size() - 1);}}/*** 计算第一组的图片的总高度 减去 第二组图片的总高度** @param idList* @param group1* @param idAndRatioMap* @return*/private double calculateGroup1DifHeifGroup2Hei(List<Long> idList, List<Long> group1, Map<Long, Double> idAndRatioMap) {double sum1 = 0, sum2 = 0;for (Long id : idList) {if (group1.indexOf(id) == -1) {sum2 += idAndRatioMap.get(id);}else {sum1 += idAndRatioMap.get(id);}}return sum1 - sum2;}}

优化:减少图片的网络请求

因为图片的高宽比是一个不变量,可以将其作为一个属性存储到数据表中,这样只需要查询出来即可,不再需要使用网络请求来获取,但是需要在存储图片到数据表之前获取高宽比,并将该属性进行存储

数据表增加字段

在这里插入图片描述

将数据表中已有数据的宽高比计算出来,并更新到数据表中

因为我的数据表中已经存在了一些图片数据,为了小程序地正确运行,需要对这批数据进行修复,即为每张图片补充高宽比。因为数据表的数据量不大,而且是一次性任务,直接每次修改单条数据即可。如果数据量很大,可以使用多线程和分批批量修改来优化修复速度

@Overridepublic void updatePictureSheetSetAspectRatio() {Picture picture = new Picture();picture.setType(0);// 取消使用分页clearPage();List<Picture> pictureList = pictureMapper.selectPictureList(picture);for (Picture picture1 : pictureList) {String address = picture1.getAddress();try {BufferedImage sourceImg = ImageIO.read(new URL(address));picture1.setAspectRatio(sourceImg.getHeight() * 1.0 / sourceImg.getWidth());pictureMapper.updatePicture(picture1);} catch (IOException e) {throw new RuntimeException(e);}}}

修改商品发布页面的代码

现在数据表需要保存图片的高宽比,虽然可以直接由服务端在保存图片之前计算高宽比,但是这样还是要发送很多网络请求,影响接口的并发性能,因此建议由客户端来计算高宽比,然后直接上传给服务端,服务端直接将数据保存即可

/**
* 上传闲置商品
*/
uploadSellProduct() {// console.log("上传闲置商品picList:" + JSON.stringify(this.picList));if (this.product.productCategoryId) {if (this.picList.length == 0) {this.$refs.uToast.show({type: 'error',message: "商品图片没有上传成功"})} else {this.setPicAspectRatio().then(() => {// console.log("即将上传的商品:" + JSON.stringify(this.product));uploadSellProduct(this.product).then(res => {this.$refs.uToast.show({type: 'success',message: "您的商品已经发布到平台"})setTimeout(() => {uni.reLaunch({url: "/pages/index/index"})}, 1000)}).catch(error => {console.log("error:" + JSON.stringify(error));this.$refs.uToast.show({type: 'error',message: "商品发布失败"})});});}} else {this.$refs.uToast.show({type: 'error',message: "请选择分类"})}
},
/*** 设置图片的宽高比*/
setPicAspectRatio() {return new Promise((resolve, reject) => {this.product.picList = [];let promises = [];for (let i = 0; i < this.picList.length; i++) {let picUrl = this.picList[i];promises.push(this.getAspectRatio(picUrl).then((res) => {let pic = {address: picUrl,aspectRatio: res}this.product.picList.push(pic);console.log("当前图片高宽比设置完成");}))}Promise.all(promises).then(() => {console.log("所有图片高宽比设置完成,this.product.picList:" + JSON.stringify(this.product.picList));resolve();})})
},
/*** 获取单个图片的高宽比* @param {Object} url*/
getAspectRatio(url) {return new Promise((resolve, reject) => {uni.getImageInfo({src: url,success: function(res) {let aspectRatio = res.height / res.width;resolve(aspectRatio);}});})
},

注意点:

  • 因为getAspectRatio方法获取图片的高宽比发送网络请求,因此使用Promise来确保高宽比获取成功才resolve
  • 在上传商品之前,需要先设置商品所对应的所有图片的高宽比。如果图片有多张,需要等待所有图片的高宽比都设置完成,本文使用Promise.all(promises)来等待所有图片的高宽比都设置完成,再resolve

Service改进

因为已经将图片的高宽比存储到数据表中,因此不需要再发送网路请求,直接获取属性值即可

@Override
public List<ProductVo>[] splitToTwoGroups(List<ProductVo> productVoList) {List<ProductVo>[] resultArr = new List[2];for (int i = 0; i < resultArr.length; i++) {resultArr[i] = new ArrayList<>();}/// 数据准备// 获取每个图片的高宽比Map<Long, Double> idAndRatioMap = new HashMap<>();// 存储所有商品的idList<Long> idList = new ArrayList<>();long start = System.currentTimeMillis();for (ProductVo productVo : productVoList) {idList.add(productVo.getId());if (productVo.getPicList() != null && productVo.getPicList().size() > 0) {
//                try {
//                    BufferedImage sourceImg = ImageIO.read(new URL(productVo.getPicList().get(0)).openStream());
//                    idAndRatioMap.put(productVo.getId(), sourceImg.getHeight() * 1.0 / sourceImg.getWidth());
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }idAndRatioMap.put(productVo.getId(), productVo.getPicList().get(0).getAspectRatio());} else {idAndRatioMap.put(productVo.getId(), 0.0);}}System.out.println("分组时间:" + (System.currentTimeMillis() - start) + "ms");/// 深度优先遍历,找出所有方案,选择两组高度差距最小的分组方案GroupDivide groupDivide = new GroupDivide();groupDivide.dfsSearch(idList, 0, new ArrayList<>(), idAndRatioMap);/// 最后处理分组List<Long> group1 = groupDivide.bestGroup1;List<Long> group2 = new ArrayList<>();for (Long id : idList) {if (group1.indexOf(id) == -1) {group2.add(id);}}for (ProductVo productVo : productVoList) {if (group1.indexOf(productVo.getId()) != -1) {resultArr[0].add(productVo);} else {resultArr[1].add(productVo);}}return resultArr;
}

【测试】
在不需要发送网络请求之后,可以看到获取图片高宽比的时间被大大减少

在这里插入图片描述

优化:考虑分页的分组高宽比总和平衡

虽然上面已经使用算法来平衡两列的高宽比总和了,但是还存在一个问题,即商品数据是分页查询的,比如第第一页查询的结果是第一列的高宽比总和大于第二列的高宽比总和。那么为了可以更好地平衡两列的高宽比总和,第二页数据的查询结果应该是第二列的高宽比总和大于第一列的高宽比总和。为了处理这个问题,在使用回溯算法的时候,需要接收当前已渲染页面的两列宽高比,这样才能方便更好地进行决策

页面代码

从下面的代码中,可以很直观地看到,每次分页查询都更新两列对应地高宽比总和,并在发送请求的时候带上这两个参数

/**
* 查询商品vo集合
*/
listProductVo() {
return new Promise((resolve, reject) => {// 设置当前两列的高宽比总和this.searchForm.sumAspectRatioOfColumn1 = this.sumAspectRatioOfColumn1;this.searchForm.sumAspectRatioOfColumn2 = this.sumAspectRatioOfColumn2;listProductVo(this.searchForm, this.page).then(res => {// console.log("listProductVo:" + JSON.stringify(res))let productVoList = res.data.pageMes.rows;this.total = res.data.pageMes.total;// this.productList = [// 	[],// 	[]// ];// for (var i = 0; i < productVoList.length; i++) {// 	if (i % 2 == 0) {// 		// 第一列数据// 		this.productList[0].push(productVoList[i]);// 	} else {// 		// 第二列数据// 		this.productList[1].push(productVoList[i]);// 	}// }let groups = res.data.groups;for (var i = 0; i < groups[0].length; i++) {if (groups[0][i].picList != null && groups[0][i].picList.length > 0) {this.sumAspectRatioOfColumn1 += groups[0][i].picList[0].aspectRatio;}}for (var i = 0; i < groups[1].length; i++) {if (groups[1][i].picList != null && groups[1][i].picList.length > 0) {this.sumAspectRatioOfColumn2 += groups[1][i].picList[0].aspectRatio;}}this.productList[0] = this.productList[0].concat(groups[0]);this.productList[1] = this.productList[1].concat(groups[1]);resolve();})})},

Controller

/*** 查询商品Vo列表*/
@PreAuthorize("@ss.hasPermi('market:product:list')")
@PostMapping("/listProductVo")
@ApiOperation("获取商品列表")
public AjaxResult listProductVo(@RequestBody ProductVo productVo) {startPage();if (productVo.getProductCategoryId() != null) {// --if-- 当分类不为空的时候,只按照分类来搜索productVo.setKeyword(null);}if (productVo.getIsSearchStar() != null && productVo.getIsSearchStar() == true) {productVo.setStarPeopleId(getLoginUser().getUserId());}List<ProductVo> productVoList = productService.selectProductVoList(productVo);// 将productVoList分成两组,要求两组的高度之和相差最小List<ProductVo>[] groups = productService.splitToTwoGroups(productVoList, productVo.getSumAspectRatioOfColumn1(), productVo.getSumAspectRatioOfColumn2());Map<String, Object> map = new HashMap<>();TableDataInfo pageMes = getDataTable(productVoList);map.put("pageMes", pageMes);map.put("groups", groups);return AjaxResult.success(map);
}

Service

@Override
public List<ProductVo>[] splitToTwoGroups(List<ProductVo> productVoList, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2) {List<ProductVo>[] resultArr = new List[2];for (int i = 0; i < resultArr.length; i++) {resultArr[i] = new ArrayList<>();}/// 数据准备// 获取每个图片的高宽比Map<Long, Double> idAndRatioMap = new HashMap<>();// 存储所有商品的idList<Long> idList = new ArrayList<>();long start = System.currentTimeMillis();for (ProductVo productVo : productVoList) {idList.add(productVo.getId());if (productVo.getPicList() != null && productVo.getPicList().size() > 0) {
//                try {
//                    BufferedImage sourceImg = ImageIO.read(new URL(productVo.getPicList().get(0)).openStream());
//                    idAndRatioMap.put(productVo.getId(), sourceImg.getHeight() * 1.0 / sourceImg.getWidth());
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }idAndRatioMap.put(productVo.getId(), productVo.getPicList().get(0).getAspectRatio());} else {idAndRatioMap.put(productVo.getId(), 0.0);}}System.out.println("分组时间:" + (System.currentTimeMillis() - start) + "ms");/// 深度优先遍历,找出所有方案,选择两组高度差距最小的分组方案GroupDivide groupDivide = new GroupDivide();groupDivide.dfsSearch(idList, 0, new ArrayList<>(), idAndRatioMap,sumAspectRatioOfColumn1,sumAspectRatioOfColumn2);/// 最后处理分组List<Long> group1 = groupDivide.bestGroup1;List<Long> group2 = new ArrayList<>();for (Long id : idList) {if (group1.indexOf(id) == -1) {group2.add(id);}}for (ProductVo productVo : productVoList) {if (group1.indexOf(productVo.getId()) != -1) {resultArr[0].add(productVo);} else {resultArr[1].add(productVo);}}return resultArr;
}

回溯算法

package com.shm.algorithm;import com.ruoyi.common.utils.clone.CloneUtil;import java.util.List;
import java.util.Map;/*** 首页商品数据分组** @Author dam* @create 2023/8/30 14:12*/
public class GroupDivide {/*** 最小间距*/private double minOffSet = Double.MAX_VALUE;/*** 存储最好的第一组*/public List<Long> bestGroup1 = null;public void dfsSearch(List<Long> idList, int begin, List<Long> curGroup1, Map<Long, Double> idAndRatioMap, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2) {if (begin == idList.size()) {// 递归完成return;}for (int i = begin; i < idList.size(); i++) {curGroup1.add(idList.get(i));// 计算组1的长度-组2的长度double offSet = calculateGroup1DifHeifGroup2Hei(idList, curGroup1, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2);if (offSet > minOffSet) {// 如果当前差距已经大于最小差距,执行剪枝,因为如果再往第一组增加图片的话,那差距只会更大,没必要再往下搜索了// 删除最后一个元素curGroup1.remove(curGroup1.size() - 1);continue;} else if (Math.abs(offSet) < minOffSet) {// 找到更小的间距,保存最优解minOffSet = Math.abs(offSet);bestGroup1 = CloneUtil.arrayListClone(curGroup1);}dfsSearch(idList, i + 1, curGroup1, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2);// 删除最后一个元素curGroup1.remove(curGroup1.size() - 1);}}/*** 计算第一组的图片的总高度 减去 第二组图片的总高度** @param idList* @param group1* @param idAndRatioMap* @param sumAspectRatioOfColumn1* @param sumAspectRatioOfColumn2* @return*/private double calculateGroup1DifHeifGroup2Hei(List<Long> idList, List<Long> group1, Map<Long, Double> idAndRatioMap, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2) {// 设置初始值double sum1 = sumAspectRatioOfColumn1, sum2 = sumAspectRatioOfColumn2;for (Long id : idList) {if (group1.indexOf(id) == -1) {sum2 += idAndRatioMap.get(id);} else {sum1 += idAndRatioMap.get(id);}}return sum1 - sum2;}}

优化:考虑商品信息的高宽比

在这里插入图片描述
上面还有一个问题,第二页的数据应该放到第二列更好,解决方式如下,直接根据元素id获取元素的实际高度/实际宽度(即考虑到商品信息的实际高宽比)

<u-row customStyle="margin-top: 10px" gutter="20rpx" align="start"
v-if="productList[0].length>0&&loadData==false"><u-col span="6" class="col"><view id="view1"><view class="productVoItem" v-for="(productVo,index1) in productList[0]" :key="index1"@click="seeProductDetail(productVo)"><u--image v-if="productVo.picList!=null&&productVo.picList.length>0" :showLoading="true":src="productVo.picList[0].address" width="100%":height="productVo.picList[0].aspectRatio*100+'%'" radius="10" mode="widthFix":lazy-load="true" :fade="true" duration="450"@error="reloadPir(productVo.picList[0].address)"><!-- 加载失败展示 --><view slot="error" style="font-size: 24rpx;">加载失败</view><!-- 加载中提示 --><template v-slot:loading><view style="height: 100px;width: 100%;"><u-loading-icon color="#A2A2A2"></u-loading-icon></view></template></u--image><!-- <u--image v-else :showLoading="true" :src="src" @click="click"></u--image> --><view class="productMes"><text class="productName">【{{productVo.name}}】</text><text>{{productVo.description==null?'':productVo.description}}</text></view><view style="display: flex;align-items: center;"><!-- 现价 --><view class="price">¥<text class="number">{{productVo.price}}</text>/{{productVo.unit}}</view><view style="width: 10px;"></view><!-- 原价 --><view class="originPrice">¥{{productVo.originalPrice}}/{{productVo.unit}}</view></view><view style="display: flex;align-items: center;"><u--image :src="productVo.avatar" width="20" height="20" shape="circle"></u--image><view style="width: 10px;"></view><view style="font-size: 30rpx;"> {{productVo.nickname}}</view></view></view></view></u-col><u-col span="6" class="col"><view id="view2"><view class="productVoItem" v-for="(productVo,index1) in productList[1]" :key="index1"@click="seeProductDetail(productVo)"><u--image v-if="productVo.picList!=null&&productVo.picList.length>0" :showLoading="true":src="productVo.picList[0].address" width="100%":height="productVo.picList[0].aspectRatio*100+'%'" radius="10" mode="widthFix":lazy-load="true" :fade="true" duration="450"@error="reloadPir(productVo.picList[0].address)"><!-- 加载失败展示 --><view slot="error" style="font-size: 24rpx;">加载失败</view><!-- 加载中提示 --><template v-slot:loading><view style="height: 100px;width: 100%;"><u-loading-icon color="#A2A2A2"></u-loading-icon></view></template></u--image><!-- <u--image v-else :showLoading="true" :src="src" @click="click"></u--image> --><view class="productMes"><text class="productName">【{{productVo.name}}】</text><text>{{productVo.description==null?'':productVo.description}}</text></view><view style="display: flex;align-items: center;"><!-- 现价 --><view class="price">¥<text class="number">{{productVo.price}}</text>/{{productVo.unit}}</view><view style="width: 10px;"></view><!-- 原价 --><view class="originPrice">¥{{productVo.originalPrice}}/{{productVo.unit}}</view></view><view style="display: flex;align-items: center;"><u--image :src="productVo.avatar" width="20" height="20" shape="circle"></u--image><view style="font-size: 30rpx;"></view><view> {{productVo.nickname}}</view></view></view></view></u-col>
</u-row>

设置实际的高宽比

/**
* 设置高宽比参数*/
setParam() {return new Promise((resolve, reject) => {// select中的参数就如css选择器一样选择元素uni.createSelectorQuery().in(this).select("#view1").boundingClientRect((rect) => {console.log("rect:" + JSON.stringify(rect));//拿到聊天框的高度this.searchForm.sumAspectRatioOfColumn1 = rect.height * 1.0 / rect.width;uni.createSelectorQuery().in(this).select("#view2").boundingClientRect((rect) => {//拿到聊天框的高度this.searchForm.sumAspectRatioOfColumn2 = rect.height * 1.0 / rect.width;resolve();}).exec();}).exec();})
},

除此之外,后端服务在使用回溯算法的时候,也应该考虑到商品信息的高宽比,由于商品信息中的元素大小都是使用rpx为单位的,因此可以直接计算出商品信息的高宽比,后面将该参数传递给后端即可

// 标题、价格、头像的高宽比 分子、分母的单位都是rpx
messageAspectRatio: (30 + 40 + 32) / ((750 - 20 * 2 - 20) / 2)

Controller

/**
* 查询商品Vo列表
*/
@PreAuthorize("@ss.hasPermi('market:product:list')")
@PostMapping("/listProductVo")
@ApiOperation("获取商品列表")
public AjaxResult listProductVo(@RequestBody ProductVo productVo) {startPage();if (productVo.getProductCategoryId() != null) {// --if-- 当分类不为空的时候,只按照分类来搜索productVo.setKeyword(null);}if (productVo.getIsSearchStar() != null && productVo.getIsSearchStar() == true) {productVo.setStarPeopleId(getLoginUser().getUserId());}List<ProductVo> productVoList = productService.selectProductVoList(productVo);Map<String, Object> map = new HashMap<>();TableDataInfo pageMes = getDataTable(productVoList);map.put("pageMes", pageMes);if (productVo.getSumAspectRatioOfColumn1() != null && productVo.getSumAspectRatioOfColumn2() != null) {// 将productVoList分成两组,要求两组的高度之和相差最小List<ProductVo>[] groups = productService.splitToTwoGroups(productVoList, productVo.getSumAspectRatioOfColumn1(), productVo.getSumAspectRatioOfColumn2(),productVo.getMessageAspectRatio());map.put("groups", groups);}return AjaxResult.success(map);
}

Service

    @Overridepublic List<ProductVo>[] splitToTwoGroups(List<ProductVo> productVoList, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2, Double messageAspectRatio) {List<ProductVo>[] resultArr = new List[2];for (int i = 0; i < resultArr.length; i++) {resultArr[i] = new ArrayList<>();}/// 数据准备// 获取每个图片的高宽比Map<Long, Double> idAndRatioMap = new HashMap<>();// 存储所有商品的idList<Long> idList = new ArrayList<>();
//        long start = System.currentTimeMillis();for (ProductVo productVo : productVoList) {idList.add(productVo.getId());if (productVo.getPicList() != null && productVo.getPicList().size() > 0) {
//                try {
//                    BufferedImage sourceImg = ImageIO.read(new URL(productVo.getPicList().get(0)).openStream());
//                    idAndRatioMap.put(productVo.getId(), sourceImg.getHeight() * 1.0 / sourceImg.getWidth());
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }idAndRatioMap.put(productVo.getId(), productVo.getPicList().get(0).getAspectRatio());} else {idAndRatioMap.put(productVo.getId(), 0.0);}}
//        System.out.println("分组时间:" + (System.currentTimeMillis() - start) + "ms");/// 深度优先遍历,找出所有方案,选择两组高度差距最小的分组方案GroupDivide groupDivide = new GroupDivide();groupDivide.search(idList, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2, messageAspectRatio);/// 最后处理分组List<Long> group1 = groupDivide.bestGroup1;List<Long> group2 = new ArrayList<>();for (Long id : idList) {if (group1.indexOf(id) == -1) {group2.add(id);}}for (ProductVo productVo : productVoList) {if (group1.indexOf(productVo.getId()) != -1) {resultArr[0].add(productVo);} else {resultArr[1].add(productVo);}}return resultArr;}

回溯算法

package com.shm.algorithm;import com.ruoyi.common.utils.clone.CloneUtil;import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 首页商品数据分组** @Author dam* @create 2023/8/30 14:12*/
public class GroupDivide {/*** 最小间距*/private double minOffSet = Double.MAX_VALUE;/*** 存储最好的第一组*/public List<Long> bestGroup1 = null;public void search(List<Long> idList, Map<Long, Double> idAndRatioMap, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2, Double messageAspectRatio) {List<Long> curGroup1 = new ArrayList<>();// 先搜索组1为空的方案double offSet = calculateGroup1DifHeifGroup2Hei(idList, curGroup1, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2, messageAspectRatio);if (Math.abs(offSet) < minOffSet) {// 找到更小的间距,保存最优解minOffSet = Math.abs(offSet);bestGroup1 = CloneUtil.arrayListClone(curGroup1);}// 递归搜索组1不为空的其他方案this.dfsSearch(idList, 0, curGroup1, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2,messageAspectRatio);}/*** 深度优先遍历搜索* @param idList* @param begin* @param curGroup1* @param idAndRatioMap* @param sumAspectRatioOfColumn1* @param sumAspectRatioOfColumn2* @param messageAspectRatio*/public void dfsSearch(List<Long> idList, int begin, List<Long> curGroup1, Map<Long, Double> idAndRatioMap, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2,Double messageAspectRatio) {if (begin == idList.size()) {// 递归完成return;}for (int i = begin; i < idList.size(); i++) {curGroup1.add(idList.get(i));// 计算组1的长度-组2的长度double offSet = calculateGroup1DifHeifGroup2Hei(idList, curGroup1, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2, messageAspectRatio);if (offSet > minOffSet) {// 如果当前差距已经大于最小差距,执行剪枝,因为如果再往第一组增加图片的话,那差距只会更大,没必要再往下搜索了// 删除最后一个元素curGroup1.remove(curGroup1.size() - 1);continue;} else if (Math.abs(offSet) < minOffSet) {// 找到更小的间距,保存最优解minOffSet = Math.abs(offSet);bestGroup1 = CloneUtil.arrayListClone(curGroup1);}dfsSearch(idList, i + 1, curGroup1, idAndRatioMap, sumAspectRatioOfColumn1, sumAspectRatioOfColumn2,messageAspectRatio);// 删除最后一个元素curGroup1.remove(curGroup1.size() - 1);}}/*** 计算第一组的图片的总高度 减去 第二组图片的总高度** @param idList* @param group1* @param idAndRatioMap* @param sumAspectRatioOfColumn1* @param sumAspectRatioOfColumn2* @param messageAspectRatio* @return*/private double calculateGroup1DifHeifGroup2Hei(List<Long> idList, List<Long> group1, Map<Long, Double> idAndRatioMap, Double sumAspectRatioOfColumn1, Double sumAspectRatioOfColumn2, Double messageAspectRatio) {// 设置初始值double sum1 = sumAspectRatioOfColumn1, sum2 = sumAspectRatioOfColumn2;for (Long id : idList) {if (group1.indexOf(id) == -1) {sum2 += idAndRatioMap.get(id);sum2 += messageAspectRatio;} else {sum1 += idAndRatioMap.get(id);sum1 += messageAspectRatio;}}return sum1 - sum2;}}

页面整体代码

【index页面】

<template><view class="content"><u-toast ref="uToast"></u-toast><!-- 回到上方按钮 --><u-back-top :scroll-top="scrollTop"></u-back-top><view style="display: flex;align-items: center;"><u-search placeholder="请输入商品名称" v-model="searchForm.keyword" @search="listProductVo" :showAction="false":clearabled="true"></u-search><text class="iconfont" style="font-size: 35px;" @click="selectCategory()">&#xe622;</text></view><u-row customStyle="margin-top: 10px" gutter="20rpx" align="start"v-if="productList[0].length>0&&loadData==false"><u-col span="6" class="col"><view id="view1"><view class="productVoItem" v-for="(productVo,index1) in productList[0]" :key="index1"@click="seeProductDetail(productVo)"><u--image v-if="productVo.picList!=null&&productVo.picList.length>0" :showLoading="true":src="productVo.picList[0].address" width="100%":height="productVo.picList[0].aspectRatio*100+'%'" radius="10" mode="widthFix":lazy-load="true" :fade="true" duration="450"@error="reloadPir(productVo.picList[0].address)"><!-- 加载失败展示 --><view slot="error" style="font-size: 24rpx;">加载失败</view><!-- 加载中提示 --><template v-slot:loading><view style="height: 100px;width: 100%;"><u-loading-icon color="#A2A2A2"></u-loading-icon></view></template></u--image><!-- <u--image v-else :showLoading="true" :src="src" @click="click"></u--image> --><view class="productMes"><text class="productName">【{{productVo.name}}】</text><text>{{productVo.description==null?'':productVo.description}}</text></view><view style="display: flex;align-items: center;"><!-- 现价 --><view class="price">¥<text class="number">{{productVo.price}}</text>/{{productVo.unit}}</view><view style="width: 10px;"></view><!-- 原价 --><view class="originPrice">¥{{productVo.originalPrice}}/{{productVo.unit}}</view></view><view style="display: flex;align-items: center;"><u--image :src="productVo.avatar" width="20" height="20" shape="circle"></u--image><view style="width: 10px;"></view><view style="font-size: 30rpx;"> {{productVo.nickname}}</view></view></view></view></u-col><u-col span="6" class="col"><view id="view2"><view class="productVoItem" v-for="(productVo,index1) in productList[1]" :key="index1"@click="seeProductDetail(productVo)"><u--image v-if="productVo.picList!=null&&productVo.picList.length>0" :showLoading="true":src="productVo.picList[0].address" width="100%":height="productVo.picList[0].aspectRatio*100+'%'" radius="10" mode="widthFix":lazy-load="true" :fade="true" duration="450"@error="reloadPir(productVo.picList[0].address)"><!-- 加载失败展示 --><view slot="error" style="font-size: 24rpx;">加载失败</view><!-- 加载中提示 --><template v-slot:loading><view style="height: 100px;width: 100%;"><u-loading-icon color="#A2A2A2"></u-loading-icon></view></template></u--image><!-- <u--image v-else :showLoading="true" :src="src" @click="click"></u--image> --><view class="productMes"><text class="productName">【{{productVo.name}}】</text><text>{{productVo.description==null?'':productVo.description}}</text></view><view style="display: flex;align-items: center;"><!-- 现价 --><view class="price">¥<text class="number">{{productVo.price}}</text>/{{productVo.unit}}</view><view style="width: 10px;"></view><!-- 原价 --><view class="originPrice">¥{{productVo.originalPrice}}/{{productVo.unit}}</view></view><view style="display: flex;align-items: center;"><u--image :src="productVo.avatar" width="20" height="20" shape="circle"></u--image><view style="font-size: 30rpx;"></view><view> {{productVo.nickname}}</view></view></view></view></u-col></u-row><!-- 显示加载相关字样 --><u-loadmore v-if="productList[0].length>0&&loadData==false" :status="loadmoreStatus" /><u-empty v-if="productList[0].length==0&&loadData==false" mode="data" texColor="#ffffff" iconSize="180"iconColor="#D7DEEB" text="所选择的分类没有对应的商品,请重新选择" textColor="#D7DEEB" textSize="18" marginTop="30"></u-empty><view style="margin-top: 20px;" v-if="loadData==true"><u-skeleton :loading="true" :animate="true" rows="10"></u-skeleton></view><!-- 浮动按钮 --><FloatButton @click="cellMyProduct()"><u--image :src="floatButtonPic" shape="circle" width="60px" height="60px"></u--image></FloatButton></view>
</template><script>import FloatButton from "@/components/FloatButton/FloatButton.vue";import {listProductVo} from "@/api/market/product.js";import pictureApi from "@/utils/picture.js";import Vue from 'vue';import {debounce} from "@/utils/debounce.js"export default {components: {FloatButton},onShow: function() {let categoryNameList = uni.getStorageSync("categoryNameList");if (categoryNameList) {this.categoryNameList = categoryNameList;this.searchForm.productCategoryId = uni.getStorageSync("productCategoryId");this.searchForm.keyword = this.getCategoryLayerName(this.categoryNameList);uni.removeStorageSync("categoryNameList");uni.removeStorageSync("productCategoryId");this.listProductVo();}},data() {return {title: 'Hello',// 浮动按钮的图片floatButtonPic: require("@/static/cellLeaveUnused.png"),searchForm: {// 商品搜索关键词keyword: "",productCategoryId: undefined,sumAspectRatioOfColumn1: 0,sumAspectRatioOfColumn2: 0,// 标题、价格、头像的高宽比 分子、分母的单位都是rpxmessageAspectRatio: (30 + 40 + 32) / ((750 - 20 * 2 - 20) / 2)},productList: [[],[]],loadData: false,// 用来锁定,防止多次同时进行websocket连接lockReconnect: false,// 心跳一次间隔的时间,单位毫秒heartbeatTime: 5000,page: {pageNum: 1,pageSize: 10},// 总数据条数total: 0,// 数据加载状态loadmoreStatus: "loadmore",// 用来控制滚动到最上方scrollTop: 0,// 分别存储两列的高宽比总和sumAspectRatioOfColumn1: 0,sumAspectRatioOfColumn2: 0,}},onLoad() {},created() {this.initWebsocket();// this.getMoreProductVo = debounce(this.getMoreProductVo);},mounted() {this.loadData = true;this.listProductVo().then(() => {this.loadData = false;});},// 监听用户滑动到底部onReachBottom() {this.getMoreProductVo();},// 在滑动过程实时获取现在的滚动条位置,并保存当前的滚动条位置onPageScroll(e) {this.scrollTop = e.scrollTop;},methods: {/*** 查询商品vo集合*/listProductVo() {return new Promise((resolve, reject) => {// 设置当前两列的高宽比总和// this.searchForm.sumAspectRatioOfColumn1 = this.sumAspectRatioOfColumn1;// this.searchForm.sumAspectRatioOfColumn2 = this.sumAspectRatioOfColumn2;console.log("this.searchForm:" + JSON.stringify(this.searchForm));listProductVo(this.searchForm, this.page).then(res => {// console.log("listProductVo:" + JSON.stringify(res))let productVoList = res.data.pageMes.rows;this.total = res.data.pageMes.total;// this.productList = [// 	[],// 	[]// ];// for (var i = 0; i < productVoList.length; i++) {// 	if (i % 2 == 0) {// 		// 第一列数据// 		this.productList[0].push(productVoList[i]);// 	} else {// 		// 第二列数据// 		this.productList[1].push(productVoList[i]);// 	}// }let groups = res.data.groups;for (var i = 0; i < groups[0].length; i++) {if (groups[0][i].picList != null && groups[0][i].picList.length > 0) {this.sumAspectRatioOfColumn1 += groups[0][i].picList[0].aspectRatio;}}for (var i = 0; i < groups[1].length; i++) {if (groups[1][i].picList != null && groups[1][i].picList.length > 0) {this.sumAspectRatioOfColumn2 += groups[1][i].picList[0].aspectRatio;}}this.productList[0] = this.productList[0].concat(groups[0]);this.productList[1] = this.productList[1].concat(groups[1]);resolve();})})},/*** 获取下一页的商品*/getMoreProductVo() {if (this.productList[0].length + this.productList[1].length >= this.total) {// this.$refs.uToast.show({// 	type: 'warning',// 	message: "已经加载完所有商品数据",// 	duration: 1000// })} else {if (this.loadData != true) {// console.log("--------------------------获取下一页商品---------------------------")this.page.pageNum++;// 显示正在加载this.loadmoreStatus = "loading";this.setParam().then(res => {this.listProductVo().then(() => {if (this.productList[0].length + this.productList[1].length >= this.total) {// 没有更多了this.loadmoreStatus = "nomore";} else {// 加载更多this.loadmoreStatus = "loadmore";}});})}}},/*** 设置高宽比参数*/setParam() {return new Promise((resolve, reject) => {// select中的参数就如css选择器一样选择元素uni.createSelectorQuery().in(this).select("#view1").boundingClientRect((rect) => {console.log("rect:" + JSON.stringify(rect));//拿到聊天框的高度this.searchForm.sumAspectRatioOfColumn1 = rect.height * 1.0 / rect.width;uni.createSelectorQuery().in(this).select("#view2").boundingClientRect((rect) => {//拿到聊天框的高度this.searchForm.sumAspectRatioOfColumn2 = rect.height * 1.0 / rect.width;resolve();}).exec();}).exec();})},/*** 跳转到卖闲置页面*/cellMyProduct() {console.log("我要卖闲置");uni.navigateTo({url: "/pages/sellMyProduct/sellMyProduct"})},/*** 获取高宽比 乘以 100%*/getAspectRatio(url) {return pictureApi.getAspectRatio(url);},/*** 选择分类*/selectCategory() {uni.navigateTo({url: "/pages/sellMyProduct/selectCategory"})},/*** 获取商品名称*/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];},/*** 查看商品的详情*/seeProductDetail(productVo) {// console.log("productVo:"+JSON.stringify(productVo))uni.navigateTo({url: "/pages/product/detail?productVo=" + encodeURIComponent(JSON.stringify(productVo))})},/*** 重新加载图片*/reloadPir(pic) {console.log("图片加载失败,pic:" + pic)},/*** 创建websocket连接*/initWebsocket() {console.log("this.socket:" + JSON.stringify(this.$socket))// this.$socket == null,刚刚进入首页,还没有建立过websocket连接// this.$socket.readyState==0 表示正在连接当中// this.$socket.readyState==1 表示处于连接状态// this.$socket.readyState==2 表示连接正在关闭// this.$socket.readyState==3 表示连接已经关闭if (this.$socket == null || (this.$socket.readyState != 1 && this.$socket.readyState != 0)) {this.$socket = uni.connectSocket({url: "ws://10.23.17.146:8085/websocket/" + uni.getStorageSync("curUser").userName,success(res) {console.log('WebSocket连接成功', res);},})// console.log("this.socket:" + this.$socket)// 监听WebSocket连接打开事件this.$socket.onOpen((res) => {console.log("websocket连接成功")Vue.prototype.$socket = this.$socket;// 连接成功,开启心跳this.headbeat();});// 连接异常this.$socket.onError((res) => {console.log("websocket连接出现异常");// 重连this.reconnect();})// 连接断开this.$socket.onClose((res) => {console.log("websocket连接关闭");// 重连this.reconnect();})}},/*** 重新连接*/reconnect() {// console.log("重连");// 防止重复连接if (this.lockReconnect == true) {return;}// 锁定,防止重复连接this.lockReconnect = true;// 间隔一秒再重连,避免后台服务出错时,客户端连接太频繁setTimeout(() => {this.initWebsocket();}, 1000)// 连接完成,设置为falsethis.lockReconnect = false;},// 开启心跳headbeat() {// console.log("websocket心跳");var that = this;setTimeout(function() {if (that.$socket.readyState == 1) {// websocket已经连接成功that.$socket.send({data: JSON.stringify({status: "ping"})})// 调用启动下一轮的心跳that.headbeat();} else {// websocket还没有连接成功,重连that.reconnect();}}, that.heartbeatTime);},/*** 返回方法*/back() {}}}
</script><style lang="scss">.content {padding: 20rpx;.col {width: 50%;}.productVoItem {margin-bottom: 20px;.productMes {overflow: hidden;text-overflow: ellipsis;display: -webkit-box;/* 显示2行 */-webkit-line-clamp: 1;-webkit-box-orient: vertical;font-size: 32rpx;.productName {font-weight: bold;}}.price {color: #F84442;font-weight: bold;.number {font-size: 40rpx;}}.originPrice {color: #A2A2A2;font-size: 15px;// 给文字添加中划线text-decoration: line-through;}}}
</style>

【上传销售商品页面】

<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.description" placeholder="请输入商品描述" height="150"></u--textarea><!-- 图片上传 --><view><imageUpload v-model="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/product.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: undefined,number: 1,unit: '个',isContribute: 0,originalPrice: 0.00,price: 0.00,// 成色fineness: 0,// 功能状态functionalStatus: 0,brandId: 0},value: 'dasdas',categoryNameList: ["选择分类"],finenessList: ["全新", "几乎全新", "轻微使用痕迹", "明显使用痕迹", "外观破损"],functionList: ["功能完好无维修", "维修过,可正常使用", "有小问题,不影响使用", "无法正常使用"],picList: [],}},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() {// console.log("上传闲置商品picList:" + JSON.stringify(this.picList));if (this.product.productCategoryId) {if (this.picList.length == 0) {this.$refs.uToast.show({type: 'error',message: "商品图片没有上传成功"})} else {this.setPicAspectRatio().then(() => {// console.log("即将上传的商品:" + JSON.stringify(this.product));uploadSellProduct(this.product).then(res => {this.$refs.uToast.show({type: 'success',message: "您的商品已经发布到平台"})setTimeout(() => {uni.reLaunch({url: "/pages/index/index"})}, 1000)}).catch(error => {console.log("error:" + JSON.stringify(error));this.$refs.uToast.show({type: 'error',message: "商品发布失败"})});});}} else {this.$refs.uToast.show({type: 'error',message: "请选择分类"})}},/*** 设置图片的宽高比*/setPicAspectRatio() {return new Promise((resolve, reject) => {this.product.picList = [];let promises = [];for (let i = 0; i < this.picList.length; i++) {let picUrl = this.picList[i];promises.push(this.getAspectRatio(picUrl).then((res) => {let pic = {address: picUrl,aspectRatio: res}this.product.picList.push(pic);console.log("当前图片高宽比设置完成");}))}Promise.all(promises).then(() => {console.log("所有图片高宽比设置完成,this.product.picList:" + JSON.stringify(this.product.picList));resolve();})})},/*** 获取单个图片的高宽比* @param {Object} url*/getAspectRatio(url) {return new Promise((resolve, reject) => {uni.getImageInfo({src: url,success: function(res) {let aspectRatio = res.height / res.width;resolve(aspectRatio);}});})},/*** 选择分类*/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>

同项目其他文章

该项目的其他文章请查看【易售小程序项目】项目介绍、小程序页面展示与系列文章集合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/119097.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git企业级使用

1.初始Git 1.1创建Git仓库 要提前说的是&#xff0c;仓库是进⾏版本控制的⼀个⽂件⽬录。我们要想对⽂件进⾏版本控制&#xff0c;就必须先创建⼀个仓库出来。创建⼀个Git本地仓库对应的命令为 git init &#xff0c;注意命令要在⽂件⽬录下执⾏&#xff0c;例如&#xff1a;…

docker安装gitlab

安装gitlab sudo docker run --detach \--hostname gitlab \--publish 543:443 --publish 90:80 --publish 222:22 \ --name gitlab \--restart always \--volume $GITLAB_HOME/config:/etc/gitlab \--volume $GITLAB_HOME/logs:/var/log/gitlab \--volume $GITLAB_HOME/data:…

RTPEngine 通过 HTTP 获取指标的方式

文章目录 1.背景介绍2.RTPEngine 支持的 HTTP 请求3.通过 HTTP 请求获取指标的方法3.1 脚本配置3.2 请求方式 1.背景介绍 RTPEngine 是常用的媒体代理服务器&#xff0c;通常被集成到 SIP 代理服务器中以减小代理服务器媒体传输的压力&#xff0c;其架构如下图所示。这种使用方…

使用Docker安装和部署kkFileView

&#x1f388;1 参考文档 kkFileView官方文档 &#x1f680;2 安装kkFileView 拉取Redis镜像。 docker pull keking/kkfileview启动docker容器。 docker run -it -d -p 8012:8012 keking/kkfileview --restart always解释&#xff1a; docker run redis # 从kkfileview镜像运行…

计算机视觉的应用12-卷积神经网络中图像特征提取的可视化研究,让大家理解特征提取的全过程

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用12-卷积神经网络中图像特征提取的可视化研究&#xff0c;让大家理解特征提取的全过程。 要理解卷积神经网络中图像特征提取的全过程&#xff0c;我们可以将其比喻为人脑对视觉信息的处理过程。就像…

《YOLOv5:从入门到实战》专栏介绍 专栏目录

&#x1f31f;YOLOv5&#xff1a;从入门到实战 | 目录 | 使用教程&#x1f31f; 本专栏涵盖了丰富的YOLOv5算法从入门到实战系列教程&#xff0c;专为学习YOLOv5的同学而设计&#xff0c;堪称全网最详细的教程&#xff01;该专栏从YOLOv5基础知识入门到项目应用实战都提供了详细…

教育培训小程序的设计与功能解析

随着互联网的发展&#xff0c;线上教育逐渐成为一种趋势&#xff0c;越来越多的人开始选择在线学习。而搭建一个适合自己的线上教育小程序&#xff0c;可以为教育机构或个人提供更好的教学和学习体验。在本文中&#xff0c;我们将介绍如何通过一个第三方制作平台来搭建在线教育…

python实现MQTT协议(发布者,订阅者,topic)

python实现MQTT协议 一、简介 1.1 概述 本文章针对物联网MQTT协议完成python实现 1.2 环境 Apache-apollo创建brokerPython实现发布者和订阅者 1.3 内容 MQTT协议架构说明 &#xff1a; 利用仿真服务体会 MQTT协议 针对MQTT协议进行测试 任务1&#xff1a;MQTT协议应…

浅谈安防视频监控平台EasyCVR视频汇聚平台对于夏季可视化智能溺水安全告警平台的重要性

每年夏天都是溺水事故高发的时期&#xff0c;许多未成年人喜欢在有水源的地方嬉戏&#xff0c;这导致了悲剧的发生。常见的溺水事故发生地包括水库、水坑、池塘、河流、溪边和海边等场所。 为了加强溺水风险的提示和预警&#xff0c;完善各类安全防护设施&#xff0c;并及时发现…

如何制作一个百货小程序

在这个数字化时代&#xff0c;小程序已成为各行各业的必备工具。其中&#xff0c;百货小程序因其便捷性和多功能性&#xff0c;越来越受到人们的青睐。那么&#xff0c;如何制作一个百货小程序呢&#xff1f;下面&#xff0c;我们就详细介绍一下无需编写代码的步骤。 一、进入后…

【GAMES202】Real-Time Global Illumination(screen space)1—实时全局光照(屏幕空间)1

一、Real-Time Global Illumination(in 3D cont.) 上篇只介绍了RSM&#xff0c;这里我们还会简要介绍另外两种在3D空间中做全局光照的方法&#xff0c;分别是LPV和VXGI。 1.Light Propagation Volumes (LPV) 首先我们知道Radiance在传播过程中是不会被改变的&#xff0c;这点…

Redis事务为什么不支持回滚

Redis事务中过程中的错误分类两类&#xff1a; 在exec执行之前的错误&#xff0c;这种错误通常是指令错误&#xff0c;比如指令语法错误、内存不足等... --> 在开始事务后&#xff0c;传输指令时&#xff0c;遇到这种错误&#xff0c;Redis会给出Error错误提示&#xff0c;…

lv3 嵌入式开发-4 linux shell命令(进程管理、用户管理)

目录 1 进程处理相关命令 1.1 进程的概念 1.2 查看进程的命令 1.3 发送信号命令 2 用户管理相关命令 2.1 用户管理相关文件介绍 2.2 用户管理相关命令介绍 1 进程处理相关命令 1.1 进程的概念 进程的概念主要有两点&#xff1a; 进程是一个实体。每一个进程都有它自己…

基于java Swing 和 mysql实现的飞机订票系统(源码+数据库+ppt+ER图+流程图+架构说明+论文+运行视频指导)

一、项目简介 本项目是一套基于java Swing 和 mysql实现的飞机订票系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过…

PostgreSQL命令行工具psql常用命令

1. 概述 通常情况下操作数据库使用图形化客户端工具&#xff0c;在实际工作中&#xff0c;生产环境是不允许直接连接数据库主机&#xff0c;只能在跳板机上登录到Linux服务器才能连接数据库服务器&#xff0c;此时就需要使用到命令行工具。psql是PostgreSQL中的一个命令行交互…

【Linux】线程安全-信号量

文章目录 信号量原理信号量保证同步和互斥的原理探究信号量相关函数初始化信号量函数等待信号量函数释放信号量函数销毁信号量函数 信号量实现生产者消费者模型 信号量原理 信号量的原理&#xff1a;资源计数器 PCB等待队列 函数接口 资源计数器&#xff1a;对共享资源的计…

【Linux】进程的优先级

我们都知道进程等待需要cpu处理的&#xff0c;那就需要一个数据结构来记录要被cpu处理的进程&#xff0c;那这些进程是按一个什么样的方式在这个结构中进行等待呢&#xff1f;下面就要谈到进程的优先级了&#xff1a; 目录 一、进程的优先级的概念 二、查看进程的优先级 2.1…

【javaweb】学习日记Day8 - Mybatis入门 Mysql 多表查询 事务 索引

之前学习过的SQL语句笔记总结戳这里→【数据库原理与应用 - 第六章】T-SQL 在SQL Server的使用_Roye_ack的博客-CSDN博客 【数据库原理与应用 - 第八章】数据库的事务管理与并发控制_一级封锁协议_Roye_ack的博客-CSDN博客 目录 一、多表查询 1、概述 &#xff08;1&#…

Spring-Kafka生产者源码分析

文章目录 概要初始化消息发送小结 概要 本文主要概括Spring Kafka生产者发送消息的主流程 代码准备&#xff1a; SpringBoot项目中maven填加以下依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent&…

大数据技术原理与应用学习笔记第1章

黄金组合访问地址&#xff1a;http://dblab.xmu.edu.cn/post/7553/ 1.《大数据技术原理与应用》教材 官网&#xff1a;http://dblab.xmu.edu.cn/post/bigdata/ 2.大数据软件安装和编程实践指南 官网林子雨编著《大数据技术原理与应用》教材配套大数据软件安装和编程实践指…