【易售小程序项目】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】
文章目录
- 说明
- 细节一:首页滑动到底部,需要查询下一页的商品
- 界面预览
- 页面实现
- 细节二:当页面滑动到下方,出现一个回到顶端的悬浮按钮
- 细节三:商品分列
- 说明
- 优化前后效果对比
- 使用回溯算法实现
- 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()"></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>
同项目其他文章
该项目的其他文章请查看【易售小程序项目】项目介绍、小程序页面展示与系列文章集合
相关文章:

【易售小程序项目】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】
文章目录 说明细节一:首页滑动到底部,需要查询下一页的商品界面预览页面实现 细节二:当页面滑动到下方,出现一个回到顶端的悬浮按钮细节三:商品分列说明优化前后效果对比使用回溯算法实现ControllerService回溯算法 优…...

素数求原根
1 模m原根的定义 1.1符号说明: Z m ∗ Z_m^* Zm∗:代表满足 1 < i < m − 1 , ( i , m ) 1 1<i<m-1,(i,m)1 1<i<m−1,(i,m)1的数字 i i i组成的集合 o r d m ( a ) ord_m(a) ordm(a):代表 a ( m o d m ) a(mod m) a(modm)在 Z m ∗ Z_m^* Zm∗中的…...

【Apollo学习笔记】——规划模块TASK之PATH_ASSESSMENT_DECIDER
文章目录 前言PATH_ASSESSMENT_DECIDER功能简介PATH_ASSESSMENT_DECIDER相关信息PATH_ASSESSMENT_DECIDER总体流程1. 去除无效路径2. 分析并加入重要信息给speed决策SetPathInfoSetPathPointType 3. 排序选择最优的路径4. 更新必要的信息 前言 在Apollo星火计划学习笔记——Ap…...

09 mysql fetchSize 所影响的服务器和客户端的交互
前言 这是一个 之前使用 spark 的时候 记一次 spark 读取大数据表 OOM OutOfMemoryError: GC overhead limit exceeded 因为一个 OOM 的问题, 当时使用了 fetchSize 的参数 应用服务 hang 住, 导致服务 503 Service Unavailable 在这个问题的地方, 出现了一个查询 32w 的数据…...

DevEco Studio 配置
首先,打开deveco studio 进入首页 …我知道你们想说什么,我也想说 汉化配置 没办法,老样子,先汉化吧,毕竟母语看起来舒服 首先,点击软件左下角的configure,在配置菜单里选择plugins 进入到插件页面, 输入chinese,找到汉化插件,(有一说一写到这我心里真是很不舒服) 然后点击o…...

Nginx自动探活后端服务状态自动转发,nginx_upstream_check_module的使用
一、三种方案 nginx对后端节点健康检查的方式主要有3种 1. gx_http_proxy_module 模块和ngx_http_upstream_module模块(自带) 官网地址:http://nginx.org/cn/docs/http/ng … proxy_next_upstream 严格来说,nginx自带是没有针对负载均衡后端节点的健康检查的,但是可以通…...

CSS 一个好玩的卡片“开卡效果”
文章目录 一、用到的一些CSS技术二、实现效果三、代码 一、用到的一些CSS技术 渐变 conic-gradientbox-shadowclip-path变换、过渡 transform、transition动画 animation keyframes伪类、伪元素 :hover、::before、::after …绝对布局。。。 clip-path 生成网站 https://techb…...

lintcode 667 · 最长的回文序列【中等 递归到动态规划】
题目 https://www.lintcode.com/problem/667/ 给一字符串 s, 找出在 s 中的最长回文子序列的长度. 你可以假设 s 的最大长度为 1000.样例 样例1输入: "bbbab" 输出: 4 解释: 一个可能的最长回文序列为 "bbbb" 样例2输入…...

oracle sql语言模糊查询
在Where子句中,可以对datetime、char、varchar字段类型的列用Like子句配合通配符选取那些“很像...”的数据记录,以下是可使用的通配符: % 零或者多个字符 _ 单一任何字符(下划线) / 特殊字符 [] 在某一范…...

【Ubuntu】解决ubuntu虚拟机和物理机之间复制粘贴问题(无需桌面工具)
解决Ubuntu虚拟机和物理机之间复制粘贴问题 第一步 先删除原来的vmware tools(如果有的话) sudo apt-get autoremove open-vm-tools第二步 安装软件包,一般都是用的desktop版本(如果是server换一下) sudo apt-get …...

解决ubuntu文件系统变成只读的方法
所欲文件变成只读,这种情况一般是程序执行发生错误,磁盘的一种保护措施 使用fsck修复 方法一: # 切换root sudo su # 修复磁盘错误 fsck -t ext4 -v /dev/sdb6 方法二: fsck.ext4 -y /dev/sdb6 重新用读写挂载 上面两种方法&…...

高数刷题笔记
常见等价无穷小 注意讨论 第二个等价无穷小 夹逼定理!!! 递归数列可以尝试用关系式法 通常用到夹逼定理的时候都会用到放缩构造出一大一小两个函数,将原函数夹在中间,然后使得两端函数极限相同则可推出原函数的极限&am…...

c++入门一
参考:https://www.learncpp.com/cpp-tutorial/ When you finish, you will not only know how to program in C, you will know how NOT to program in C, which is arguably as important. Tired or unhappy programmers make mistakes, and debugging code tends…...

2023年项目进度管理平台排行榜
项目进度管理是项目管理学科中的一门重要课程,通过合理的项目计划,有效控制项目进度,保障项目能够按时交付。 不过,项目进度管理并不是一件简单的工作,不仅需要面对项目过程中各种突发情况,还需要做好团队协…...

【设计模式】面向对象设计八大原则
(1)依赖倒置原则(DIP) 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定)。抽象(稳定)不应该依赖于…...

python数分实战探索霍尔特法之销售预测python代码实现以及预测图绘制
探索霍尔特法:时间序列预测中的线性趋势捕捉 时间序列分析是统计学和数据分析中的一个核心领域。无论是预测股票市场的走势,还是预测未来的销售量,一个精确和可靠的预测模型都是至关重要的。在众多的时间序列预测方法中,霍尔特法(Holts method)脱颖而出,特别是当我们面…...

java线程状态
图形说明: Thread.State源码注释: public enum State {/*** 新生状态:线程对象创建,但是还未start()*/NEW,/*** 线程处于可运行状态,但是这个可运行状态并不代表线程一定在虚拟机中执行。* 需要等待从操作系统获取到资源(比如处理器时间片…...

编译问题:error: ‘printf’ was not declared in this scope
这个错误提示意味着编译器在当前作用域内无法找到 printf 函数的声明。这通常是因为没有包含 <stdio.h> 头文件导致的。 解决方法是在程序中添加 #include <stdio.h> 这一行代码。这个头文件中包含了 printf 函数的声明,告诉编译器如何处理该函数。...

改变C++中私有变量成员的值
1、没有引用的情况: #include <iostream> #include <queue> using namespace std; class Person { public:queue<int>que; public:queue<int> getQueue(){return que;}void push(int a){que.push(a);}void pop(){que.pop();} };int main()…...

线程唯一的单例
经典设计模式的单例模式是指进程唯一的对象实例,实现code如下: package cun.zheng.weng.design.sinstnce;import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExec…...

明厨亮灶监控实施方案 opencv
明厨亮灶监控实施方案通过pythonopencv网络模型图像识别算法,一旦发现现场人员没有正确佩戴厨师帽或厨师服,及时发现明火离岗、不戴口罩、厨房抽烟、老鼠出没以及陌生人进入后厨等问题生成告警信息并进行提示。OpenCV是一个基于Apache2.0许可(…...

14 mysql bit/json/enum/set 的数据存储
前言 这里主要是 由于之前的一个 datetime 存储的时间 导致的问题的衍生出来的探究 探究的主要内容为 int 类类型的存储, 浮点类类型的存储, char 类类型的存储, blob 类类型的存储, enum/json/set/bit 类类型的存储 本文主要 的相关内容是 bit/json/enum/set 类类型的相关…...

04_19linux自己撸内存池实战,仿造slab分配器
前言 自己撸一个内存池 其实就相当于linux里面带的 slab分配器 可以翻翻之前的章 看看slab 和伙伴分配器的不同 在学习c语言时,我们常常会使用到malloc()去申请一块内存空间,用于存放我们的数据。刚开始我们只要知道申请内存时使用用malloc去申请一块就…...

【HDFS】XXXRpcServer和ClientNamenodeProtocolServerSideTranslatorPB小记
初始化RouterRpcServer时候会new ClientNamenodeProtocolServerSideTranslatorPB,并把当前RouterRpcServer对象(this)传入构造函数: ClientNamenodeProtocolServerSideTranslatorPBclientProtocolServerTranslator =new ClientNamenodeProtocolServerSideTranslatorPB(this…...

二分,Dijkstra,340. 通信线路
在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站 Ai 和 Bi。 特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。 现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 L…...

Stable Diffusion---Ai绘画-下载-入门-进阶(笔记整理)
前言 注:本文偏向于整理,都是跟着大佬们学的。 推荐两个b站up主,学完他们俩的东西基本就玩转SD为底的ai绘画: 秋葉aaaki,Nenly同学 1.首先SD主流的就是秋叶佬的Webui了,直接压缩包下载即可,下…...

Java 乘等赋值运算
下面这个题目是在一公司发过来的,如果你对 Java 的赋值运算比较了解的话,会很快知道答案的。 这个运算符在 Java 里面叫做乘等或者乘和赋值操作符,它把左操作数和右操作数相乘赋值给左操作数。 例如下面的:density * invertedRat…...

【性能优化】聊聊性能优化那些事
针对于互联网应用来说,性能优化其实就是一直需要做的事情,因为系统响应慢,是非常影响用户的体验,可能回造成用户流失。所以对于性能非常重要。最近正好接到一个性能优化的需求,需要对所负责的系统进行性能提升。目前接…...

k8s 查看加入主节点命令 k8s重新查看加入节点命令 k8s输入删除,重新查看加入命令 kuberadm查看加入节点命令
1. 使用kuberadm 安装成功后,clear清除了屏幕数据,加入命令无法查看,使用如下,重新查看node如何加入主节点命令: kubeadm token create --print-join-command --ttl 0 2.画圈的全部是,都复制,在…...

Scalene:Python CPU+GPU+内存分析器,具有人工智能驱动的优化建议
一、前言 Python 是一种广泛使用的编程语言,通常与其他语言编写的库一起使用。在这种情况下,如何提高性能和内存使用率可能会变得很复杂。但是,现在有一个解决方案,可以轻松地解决这些问题 - 分析器。 分析器旨在找出哪些代码段…...