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

文章目录

  • 说明
  • 细节一:首页滑动到底部,需要查询下一页的商品
    • 界面预览
    • 页面实现
  • 细节二:当页面滑动到下方,出现一个回到顶端的悬浮按钮
  • 细节三:商品分列
    • 说明
    • 优化前后效果对比
    • 使用回溯算法实现
      • 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.mzph.cn/news/64861.shtml

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

相关文章

stm32 iap sd卡升级

参考&#xff1a;STM32F4 IAP 跳转 APP问题_stm32程序跳转_古城码农的博客-CSDN博客 app程序改两个位置 1.程序首地址&#xff1a; 2.改中断向量表位移&#xff0c;偏移量和上面一样就可以 然后编译成bin文件就可以了

docker 笔记1

目录 1.为什么有docker ? 2.Docker 的核心概念 3.容器与虚拟机比较 3.1传统的虚拟化技术 3.2容器技术 3.3Docker容器的有什么作用&#xff1f; 3.4应用案例 4. docker 安装下载 4.1CentOS Docker 安装 4.2 Docker的基本组成 &#xff1f;&#xff08;面试&#xff09…

jmeter 性能测试工具的使用(Web性能测试)

1、下载 该软件不用安装&#xff0c;直接解压打开即可使用。 2、使用 这里就在win下进行&#xff0c;图形界面较为方便   在目录apache-jmeter-2.13\bin 下可以见到一个jmeter.bat文件&#xff0c;双击此文件&#xff0c;即看到JMeter控制面板。主界面如下&#xff1a; 3、创…

DEtection TRansformer (DETR) 与 You Only Look Once (YOLO)

曾经想过计算机如何分析图像&#xff0c;识别并定位其中的物体吗&#xff1f;这正是计算机视觉领域的目标检测所完成的任务。DEtection TRansformer&#xff08;DETR&#xff09;和You Only Look Once&#xff08;YOLO&#xff09;是目标检测的两种重要方法。YOLO已经赢得了作为…

【RISC-V】RISC-V寄存器

一、通用寄存器 32位RISC-V体系结构提供32个32位的整型通用寄存器寄存器别名全称说明X0zero零寄存器可做源寄存器(rs)或目标寄存器(rd)X1ra链接寄存器保存函数返回地址X2sp栈指针寄存器指向栈的地址X3gp全局寄存器用于链接器松弛优化X4tp线程寄存器常用于在OS中保存指向进程控…

回归预测 | MATLAB实现IBES-ELM改进的秃鹰搜索优化算法优化极限学习机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现IBES-ELM改进的秃鹰搜索优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现IBES-ELM改进的秃鹰搜索优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图…

手撕二叉平衡树

今天给大家带来的是平衡树的代码实现&#xff0c;如下&#xff1a; #pragma once #include <iostream> #include <map> #include <set> #include <assert.h> #include <math.h> using namespace std; namespace cc {template<class K, clas…

CXL寄存器介绍(2)- CXL DVSEC

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

TiDB 一栈式综合交易查询解决方案获“金鼎奖”优秀金融科技解决方案奖

日前&#xff0c;2023“金鼎奖”评选结果揭晓&#xff0c; 平凯星辰&#xff08;北京&#xff09;科技有限公司研发的 TiDB 一栈式综合交易查询解决方案获“金鼎奖”优秀金融科技解决方案奖 &#xff0c; 该方案已成功运用于 多家国有大行、城商行和头部保险企业 。 此次获奖再…

【AI】《动手学-深度学习-PyTorch版》笔记(二十一):目标检测

AI学习目录汇总 1、简述 通过前面的学习,已经了解了图像分类模型的原理及实现。图像分类是假定图像中只有一个目标,算法上是对整个图像做的分类。 下面我们来学习“目标检测”,即从一张图像中找出需要的目标,并标记出位置。 2、边界框 边界框:bounding box,就是一个方…

我想开通期权?如何开通期权账户?

场内期权的合约由交易所统一标准化定制&#xff0c;大家面对的同一个合约对应的价格都是一致的&#xff0c;比较公开透明&#xff0c;期权开户当天不能交易的&#xff0c;期权开户需要满足20日日均50万及半年交易经验即可操作&#xff0c;下文科普我想开通期权&#xff1f;如何…

Java设计模式:四、行为型模式-10:访问者模式

一、定义&#xff1a;访问者模式 访问者模式&#xff1a;核心在于同一个事物不同视角下的访问信息不同。 在一个稳定的数据结构下&#xff0c;例如用户信息、雇员信息等&#xff0c;增加易变的业务访问逻辑。为了增强扩展性&#xff0c;将两部分的业务解耦的一种设计模式。 二…

详解 SpringMVC 中获取请求参数

文章目录 1、通过ServletAPI获取2、通过控制器方法的形参获取请求参数3、[RequestParam ](/RequestParam )4、[RequestHeader ](/RequestHeader )5、[CookieValue ](/CookieValue )6、通过POJO获取请求参数7、解决获取请求参数的乱码问题总结 在Spring MVC中&#xff0c;获取请…

自建音乐播放器之一

这里写自定义目录标题 1.1 官方网站 2. Navidrome 简介2.1 简介2.2 特性 3. 准备工作4. 视频教程5. 界面演示5.1 初始化页5.2 专辑页 前言 之前给大家介绍过 Koel 音频流服务&#xff0c;就是为了解决大家的这个问题&#xff1a;下载下来的音乐&#xff0c;只能在本机欣赏&…

【pyqt5界面化工具开发-12】QtDesigner图形化界面设计

目录 0x00 前言 一、启动程序 二、基础的使用 三、保存布局文件 四、加载UI文件 0x00 前言 关于QtDesigner工具的配置等步骤&#xff08;网上链接也比较多&#xff09; 下列链接非本人的&#xff08;如果使用pip 在命令行安装过pyqt5以及tools&#xff0c;那么就可以跳过…

springboot整合SpringSecurity

先写了一个配置类 给这个访问路径&#xff0c;加上角色权限 package com.qf.config;import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; impo…

【网络编程上】

目录 一.什么是互联网 1.计算机网络的定义与分类&#xff08;了解&#xff09; &#xff08;1&#xff09;计算机网络的定义 &#xff08;2&#xff09;计算机网络的分类 ① 按照网络的作用范围进行分类 ②按照网络的使用者进行分类 2.网络的网络 &#xff08;理解&#xf…

苹果Mac系统如何优化流畅的运行?提高运行速度

Mac系统的稳定性和流畅性一直备受大家称赞&#xff0c;这也是大多数人选择Mac的原因&#xff0c;尽管如此&#xff0c;我们仍不时地对Mac进行优化、调整&#xff0c;以使其比以前更快、更流畅地运行。以下是小编分享给各位的Mac优化方法&#xff0c;记得保存哦~ 一、释放被过度…

【笔记】常用 js 函数

数组去重 Array.from(new Set()) 对象合并 Object.assign . 这里有个细节&#xff1a;当两个对象中含有key相同value不同时&#xff0c;会以 后面对象的key&#xff1a;value为准 保留小数点后几位 toFixed 注意&#xff1a; Number型&#xff0c;用该方法处理完&#xff0c;会…

Markdown Preview Plus Chrome插件使用

Markdown Preview Plus Chrome插件使用 1.插件说明2.插件下载3.插件配置4.文档样式4.1 网页显示4.2 导出PDF 系统&#xff1a;Win10 Chrome&#xff1a;113.0.5672.127 Markdown Preview Plus&#xff1a;0.7.3 1.插件说明 一般 markdown 工具自带的预览功能比较简单&#xff…