界面效果
【悬浮按钮】
【闲置商品描述信息填写界面】
【商品分类选择界面】
【分类选择完成】
界面实现
悬浮按钮实现
悬浮按钮漂浮于页面之上,等页面滑动时,悬浮按钮的位置相对于屏幕不会改变
【悬浮按钮组件】
<template> <div class="floating-button" @click="onClick"> <slot> <!-- 这里可以放置默认的按钮样式等 --> </slot> </div> </template> <script> export default { name: 'FloatButton', props: { }, data() { return { }; }, mounted() { }, methods: { onClick() { this.$emit('click'); } }, computed: { } }; </script> <style> .floating-button { display: flex; justify-content: center; align-items: center; width: 58px; height: 58px; border-radius: 50%; background-color: #007aff; color: #fff; position: fixed; right: 20rpx; bottom: 20rpx; } /* 按钮点击之后会产生偏移 */ .floating-button:active { transform: translate(0, 2px); } </style>
【在其他界面中使用】
因为组件中使用了插槽,可以在该组件中插入其他组件
<FloatButton @click="cellMyProduct()"> <u--image :src="floatButtonPic" shape="circle" width="60px" height="60px"></u--image> </FloatButton>
示例中,我给浮动按钮的插槽中添加了图片组件,图片使用项目静态资源中的图片
data() { return { title: 'Hello', floatButtonPic: require("@/static/cellLeaveUnused.png"), } },
商品分类选择界面
分类数据的格式如下
{ "msg": "productCategoryItemVoList", "code": 200, "data": [ { "id": 5, "name": "数码产品", "children": [ { "id": 10, "name": "电脑", "children": [ { "id": 12, "name": "台式机", "children": [ ], "icon": "a", "sort": 1, "description": "a" }, { "id": 13, "name": "笔记本", "children": [ ], "icon": "a", "sort": 1, "description": "a" } ], "icon": "a", "sort": 1, "description": "a" }, { "id": 11, "name": "手机", "children": [ { "id": 14, "name": "老人机", "children": [ ], "icon": "a", "sort": 1, "description": "a" }, { "id": 15, "name": "智能手机", "children": [ ], "icon": "a", "sort": 1, "description": "a" } ], "icon": "a", "sort": 1, "description": "a" } ], "icon": "a", "sort": 1, "description": "a" }, { "id": 6, "name": "服装", "children": [ ], "icon": "a", "sort": 1, "description": "a" }, { "id": 7, "name": "教育用品", "children": [ ], "icon": "a", "sort": 1, "description": "a" }, { "id": 8, "name": "食品", "children": [ ], "icon": "a", "sort": 1, "description": "a" } ] }
<template> <view class="container"> <u-toast ref="uToast"></u-toast> <view class="titleView"> <view class="controlButton" @click="back"> <u-icon name="arrow-left" color="#ffffff"></u-icon> 上一级 </view> <text>{{getCategoryLayerName()}}</text> <view class="controlButton" @click="commit"> 完成 <u-icon name="checkmark" color="#ffffff"></u-icon> </view> </view> <view style="height: 20px;"></view> <u-empty v-if="curLayerCategoryData.length==0" mode="search" texColor="#ffffff" iconSize="180" iconColor="#2b92ff" text="分类选择完成,请点击右上角的完成" textColor="#2b92ff" textSize="18" marginTop="30"> </u-empty> <u-list @scrolltolower="scrolltolower" v-else> <u-list-item v-for="(category, index) in curLayerCategoryData" :key="index"> <u-cell :title="category.name" @click="selectCurCategory(category)"> <u-avatar slot="icon" shape="square" size="35" :src="category.icon" customStyle="margin: -3px 5px -3px 0"></u-avatar> </u-cell> </u-list-item> </u-list> </view> </template> <script> import { getProductCategoryTree } from "@/api/market/category.js"; export default { data() { return { categoryNameList: ["分类未选择"], categoryTreeData: [], // 当前层级分类数据 curLayerCategoryData: [], // 已经选择的层级分类数据 haveSelectLayerCategoryData: [], // 层级 layer: 0, // 商品所属分类 productCategoryId: 0, } }, created() { this.getProductCategoryTree(); }, methods: { getCategoryLayerName() { let str = ''; for (let i = 0; i < this.categoryNameList.length - 1; i++) { str += this.categoryNameList[i] + '/'; } return str + this.categoryNameList[this.categoryNameList.length - 1]; }, /** * 查询商品分类的树形结构数据 */ getProductCategoryTree() { getProductCategoryTree().then(res => { // console.log("getProductCategoryTree:" + JSON.stringify(res)); this.categoryTreeData = res.data; this.curLayerCategoryData = this.categoryTreeData; }) }, /** * 选择分类 * @param {Object} category 当前选择的分类 */ selectCurCategory(category) { if (this.layer == 0) { this.categoryNameList = []; } this.categoryNameList.push(category.name); this.productCategoryId = category.id; this.layer++; // 将当前层的数据设置进haveSelectLayerCategoryData this.haveSelectLayerCategoryData.push(this.curLayerCategoryData); this.curLayerCategoryData = category.children; if (this.curLayerCategoryData.length == 0) { this.$refs.uToast.show({ type: 'success', message: "分类选择完成,请提交数据" }) } }, /** * 返回上一级 */ back() { if (this.layer == 0) { this.$refs.uToast.show({ type: 'warning', message: "已经是第一层级,无法返回上一级" }) } else { this.layer--; this.curLayerCategoryData = this.haveSelectLayerCategoryData[this.haveSelectLayerCategoryData.length - 1]; // 删掉最后一条数据 this.haveSelectLayerCategoryData.splice(this.haveSelectLayerCategoryData.length - 1, 1); } }, /** * 提交分类数据 */ commit() { if (this.curLayerCategoryData.length != 0) { this.$refs.uToast.show({ type: 'error', message: "分类还没有选择完成,请继续选择" }) } else { uni.setStorageSync("productCategoryId", this.productCategoryId); uni.setStorageSync("categoryNameList", this.categoryNameList); uni.navigateBack(); } } } } </script> <style lang="scss"> .container { background: #F6F6F6; min-height: 100vh; padding: 20rpx; .titleView { display: flex; justify-content: space-between; align-items: center; background: #2b92ff; color: #ffffff; border-radius: 4px; .controlButton { // width: 100px; display: flex; // border: #2b92ff 1px solid; padding: 10px; } } } </style>
使元素均匀分布
使用下面的代码,可以让元素在组件中的子组件在组件中横向均匀分布,效果如下图
<view class="titleView"> <view class="controlButton" @click="back"> <u-icon name="arrow-left" color="#ffffff"></u-icon> 上一级 </view> <text>{{getCategoryLayerName()}}</text> <view class="controlButton" @click="commit"> 完成 <u-icon name="checkmark" color="#ffffff"></u-icon> </view> </view>
display: flex; justify-content: space-between;
闲置商品描述信息填写界面
<template> <view class="container"> <u-toast ref="uToast"></u-toast> <view class="content"> <view class="item"> <view class="labelName">商品名称</view> <u--input placeholder="请输入商品名称" border="surround" v-model="product.name"></u--input> </view> <u-divider text="商品描述和外观"></u-divider> <!-- 商品描述 --> <u--textarea v-model="product.descripption" placeholder="请输入商品描述" height="150"></u--textarea> <!-- 图片上传 --> <view> <imageUpload v-model="product.picList" maxCount="9"></imageUpload> </view> <u-divider text="分类选择/自定义标签"></u-divider> <!-- 分类选择/自定义标签 --> <view class="item"> <view class="labelName">分类</view> <view class="selectTextClass" @click="selectCategory">{{getCategoryLayerName()}}</view> </view> <!-- 商品的属性 新度 功能完整性 --> <view class="item"> <view class="labelName">成色</view> <view class="columnClass"> <view :class="product.fineness==index?'selectTextClass':'textClass'" v-for="(finessName,index) in finenessList" :key="index" @click="changeFineness(index)"> {{finessName}} </view> </view> </view> <view class="item"> <view class="labelName">功能状态</view> <view class="columnClass"> <view :class="product.functionalStatus==index?'selectTextClass':'textClass'" v-for="(functionName,index) in functionList" :key="index" @click="changeFunctionalStatus(index)">{{functionName}} </view> </view> </view> <u-row customStyle="margin-bottom: 10px"> <u-col span="5"> <view class="item"> <view class="labelName">数量</view> <u--input placeholder="请输入商品数量" border="surround" v-model="product.number"></u--input> </view> </u-col> <u-col span="7"> <view class="item"> <view class="labelName">计量单位</view> <u--input placeholder="请输入计量单位" border="surround" v-model="product.unit"></u--input> </view> </u-col> </u-row> <!-- 价格 原价 现价 --> <u-divider text="价格"></u-divider> <u-row customStyle="margin-bottom: 10px"> <u-col span="6"> <view class="item"> <view class="labelName">原价</view> <u-input placeholder="请输入原价" border="surround" v-model="product.originalPrice" color="#ff0000" @blur="originalPriceChange"> <u--text text="¥" slot="prefix" margin="0 3px 0 0" type="error"></u--text> </u-input> </view> </u-col> <u-col span="6"> <view class="item"> <view class="labelName">出售价格</view> <u-input placeholder="请输入出售价格" border="surround" v-model="product.price" color="#ff0000" @blur="priceChange"> <u--text text="¥" slot="prefix" margin="0 3px 0 0" type="error"></u--text> </u-input> </view> </u-col> </u-row> <u-button text="出售" size="large" type="primary" @click="uploadSellProduct"></u-button> </view> </view> </template> <script> import imageUpload from "@/components/ImageUpload/ImageUpload.vue"; import { uploadSellProduct } from "@/api/market/prodct.js" export default { components: { imageUpload }, onShow: function() { let categoryNameList = uni.getStorageSync("categoryNameList"); if (categoryNameList) { this.categoryNameList = categoryNameList; this.product.productCategoryId = uni.getStorageSync("productCategoryId"); uni.removeStorageSync("categoryNameList"); uni.removeStorageSync("productCategoryId"); } }, data() { return { product: { name: '', descripption: '', picList: [], productCategoryId: 0, number: 1, unit: '', isContribute: 0, originalPrice: 0.00, price: 0.00, // 成色 fineness: 0, // 功能状态 functionalStatus: 0, brandId: 0 }, value: 'dasdas', categoryNameList: ["选择分类"], finenessList: ["全新", "几乎全新", "轻微使用痕迹", "明显使用痕迹", "外观破损"], functionList: ["功能完好无维修", "维修过,可正常使用", "有小问题,不影响使用", "无法正常使用"] } }, methods: { getCategoryLayerName() { let str = ''; for (let i = 0; i < this.categoryNameList.length - 1; i++) { str += this.categoryNameList[i] + '/'; } return str + this.categoryNameList[this.categoryNameList.length - 1]; }, /** * 价格校验 * @param {Object} price 价格 */ priceVerify(price) { if (isNaN(price)) { this.$refs.uToast.show({ type: 'error', message: "输入的价格不是数字,请重新输入" }) return false; } if (price < 0) { this.$refs.uToast.show({ type: 'error', message: "输入的价格不能为负数,请重新输入" }) return false; } if (price.toString().indexOf('.') !== -1 && price.toString().split('.')[1].length > 2) { this.$refs.uToast.show({ type: 'error', message: "输入的价格小数点后最多只有两位数字,请重新输入" }) return false; } return true; }, originalPriceChange() { let haha = this.priceVerify(this.product.originalPrice); if (haha === false) { console.log("haha:" + haha); this.product.originalPrice = 0.00; console.log("this.product" + JSON.stringify(this.product)); } }, priceChange() { if (this.priceVerify(this.product.price) === false) { this.product.price = 0.00; } }, /** * 修改成色 * @param {Object} index */ changeFineness(index) { this.product.fineness = index; }, /** * 修改功能状态 * @param {Object} index */ changeFunctionalStatus(index) { this.product.functionalStatus = index; }, /** * 上传闲置商品 */ uploadSellProduct() { uploadSellProduct(this.product).then(res => { this.$refs.uToast.show({ type: 'success', message: "您的商品已经发布到平台" }) setTimeout(() => { uni.reLaunch({ url: "/pages/index/index" }) }, 1500) }) }, /** * 选择分类 */ selectCategory() { uni.navigateTo({ url: "/pages/sellMyProduct/selectCategory" }) } } } </script> <style lang="scss"> .container { background: #F6F6F6; min-height: 100vh; padding: 20rpx; .content { background: #ffffff; padding: 20rpx; .item { display: flex; align-items: center; height: 50px; margin-bottom: 5px; .labelName { width: 70px; margin-right: 10px; } .textClass { display: inline; background: #F7F7F7; padding: 10px; margin-right: 15px; border-radius: 5px; } .selectTextClass { display: inline; background: #2B92FF; padding: 10px; margin-right: 15px; border-radius: 5px; color: #ffffff; font-weight: bold; } .columnClass { // height: 50px; display: flex; align-items: center; width: calc(100% - 70px); overflow-x: auto; // // 让内容只有一行 white-space: nowrap; } .columnClass::-webkit-scrollbar { background-color: transparent; /* 设置滚动条背景颜色 */ // width: 0px; height: 0px; } } } } </style>
价格校验
价格是商品比较关键的属性,一定要确保其数据没有问题,所以在用户提交之前一定要对商品的价格进行校验,防止用户乱输或者输错数据,这里对价格有如下规定:
- 输入的价格必须是数字,不可以是字符串
- 输入的价格必须是正数,不可以是负数
- 输入的价格的小数点有限制,不可以输入太多小数点
那么校验应该在什么时候触发呢?本示例在用户输入结束之后,手指离开输入组件时触发,即当元素失去焦点时触发,使用的是@blur
事件
/** * 价格校验 * @param {Object} price 价格 */ priceVerify(price) { if (isNaN(price)) { this.$refs.uToast.show({ type: 'error', message: "输入的价格不是数字,请重新输入" }) return false; } if (price < 0) { this.$refs.uToast.show({ type: 'error', message: "输入的价格不能为负数,请重新输入" }) return false; } if (price.toString().indexOf('.') !== -1 && price.toString().split('.')[1].length > 2) { this.$refs.uToast.show({ type: 'error', message: "输入的价格小数点后最多只有两位数字,请重新输入" }) return false; } return true; },