123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869 |
- <template>
- <view class="goods-sku-popup popup" catchtouchmove="true" :class="(value && complete) ? 'show' : 'none'"
- @touchmove.stop.prevent="moveHandle">
- <!-- 页面内容开始 -->
- <view class="mask" @click="close('mask')"></view>
- <!-- 页面开始 -->
- <view class="layer attr-content" :style="'border-radius: '+borderRadius+'rpx '+borderRadius+'rpx 0 0;'">
- <view class="specification-wrapper">
- <scroll-view class="specification-wrapper-content" scroll-y="true">
- <view class="specification-header">
- <view class="specification-left">
- <image class="product-img" :src="selectShop.image ? selectShop.image : goodsInfo[goodsThumbName]"
- mode="aspectFill"></image>
- </view>
- <view class="specification-right">
- <view class="price-content" :style="'color: '+priceColor+' ;'">
- <text class="sign">¥</text>
- <text class="price">{{ (selectShop.price || defaultPrice) | priceFilter }}</text>
- </view>
- <view class="inventory">{{ stockText }}:{{ selectShop[stockName] || defaultStock }}</view>
- <view class="choose" v-show="goodsInfo[specListName] && goodsInfo[specListName][0].name !== defaultSingleSkuName">
- <text v-if="!selectArr.every(val => val == '')">已选:{{ selectArr.join(' ') }}</text>
- </view>
- </view>
- </view>
- <view class="specification-content">
- <view v-show="goodsInfo[specListName][0].name !== defaultSingleSkuName" class="specification-item" v-for="(item, index1) in goodsInfo[specListName]" :key="index1">
- <view class="item-title">{{ item.name }}</view>
- <view class="item-wrapper">
- <view class="item-content" @tap="skuClick(item_value, index1, $event, index2)" v-for="(item_value, index2) in item.list"
- :key="index2" :class="[item_value.ishow ? '' : 'noactived', subIndex[index1] == index2 ? 'actived' : '']"
- :style="[item_value.ishow ? '' : disableStyle,
- item_value.ishow ? btnStyle :'',
- subIndex[index1] == index2 ? activedStyle : ''
- ]">
- {{ item_value.name }}
- </view>
- </view>
- </view>
- <view style="display: flex;">
- <view style="flex: 1;">
- <text style="font-size: 26rpx; color: #333; line-height: 50rpx;">数量</text>
- </view>
- <view style="flex: 4;text-align: right;">
- <number-box :min="minBuyNum" :max="maxBuyNum" :step="stepBuyNum" v-model="selectNum"
- :positive-integer="true">
- </number-box>
- </view>
- </view>
- </view>
- </scroll-view>
- <view class="close" @click="close('close')" v-if="showClose">
- <image class="close-item" :src="closeImage"></image>
- </view>
- </view>
- <view class="btn-option">
- <view class="btn-wrapper" v-if="outFoStock || mode == 4">
- <view class="sure" style="color:#ffffff;background-color:#cccccc">{{ noStockText }}</view>
- </view>
- <view class="btn-wrapper" v-else-if="mode == 1">
- <view class="sure add-cart" style="border-radius:38rpx 0rpx 0rpx 38rpx;" @click="addCart" :style="'color:'+addCartColor+';background:'+addCartBackgroundColor">{{ addCartText }}</view>
- <view class="sure" style="border-radius:0rpx 38rpx 38rpx 0rpx;" @click="buyNow" :style="'color:'+buyNowColor+';background-color:'+buyNowBackgroundColor">{{ buyNowText }}</view>
- </view>
- <view class="btn-wrapper" v-else-if="mode == 2">
- <view class="sure add-cart" @click="addCart" :style="'color:'+addCartColor+';background:'+addCartBackgroundColor">{{ addCartText }}</view>
- </view>
- <view class="btn-wrapper" v-else-if="mode == 3">
- <view class="sure" @click="buyNow" :style="'color:'+buyNowColor+';background:'+buyNowBackgroundColor">{{ buyNowText }}</view>
- </view>
- </view>
- <!-- 页面结束 -->
- </view>
- <!-- 页面内容结束 -->
- </view>
- </template>
- <script>
- import NumberBox from './number-box'
- var that; // 当前页面对象
- var vk; // 自定义函数集
- export default {
- name: 'GoodsSkuPopup',
- components: {
- NumberBox
- },
- props: {
- // true 组件显示 false 组件隐藏
- value: {
- Type: Boolean,
- default: false
- },
- // vk云函数路由模式参数开始-----------------------------------------------------------
- // 商品id
- goodsId: {
- Type: String,
- default: ""
- },
- // vk路由模式框架下的云函数地址
- action: {
- Type: String,
- default: ""
- },
- // vk云函数路由模式参数结束-----------------------------------------------------------
- // 该商品已抢完时的按钮文字
- noStockText: {
- Type: String,
- default: "该商品已抢完"
- },
- // 库存文字
- stockText: {
- Type: String,
- default: "库存"
- },
- // 商品表id的字段名
- goodsIdName: {
- Type: String,
- default: "_id"
- },
- // sku表id的字段名
- skuIdName: {
- Type: String,
- default: "_id"
- },
- // sku_list的字段名
- skuListName: {
- Type: String,
- default: "sku_list"
- },
- // spec_list的字段名
- specListName: {
- Type: String,
- default: "spec_list"
- },
- // stock的字段名
- stockName: {
- Type: String,
- default: "stock"
- },
- // sku_name的字段名
- skuName: {
- Type: String,
- default: "sku_name"
- },
- // sku组合路径的字段名
- skuArrName: {
- Type: String,
- default: "sku_name_arr"
- },
- // 默认单规格时的规格组名称
- defaultSingleSkuName: {
- Type: String,
- default: "默认"
- },
- // 模式 1:都显示 2:只显示购物车 3:只显示立即购买 4:显示缺货按钮 默认 1
- mode: {
- Type: Number,
- default: 1
- },
- // 点击遮罩是否关闭组件 true 关闭 false 不关闭 默认true
- maskCloseAble: {
- Type: Boolean,
- default: true
- },
- // 顶部圆角值
- borderRadius: {
- Type: [String, Number],
- default: 0
- },
- // 商品缩略图字段名(未选择sku时)
- goodsThumbName: {
- Type: [String],
- default: "goods_thumb"
- },
- // 最小购买数量
- minBuyNum: {
- Type: Number,
- default: 1
- },
- // 最大购买数量
- maxBuyNum: {
- Type: Number,
- default: 100000
- },
- // 每次点击后的数量
- stepBuyNum: {
- Type: Number,
- default: 1
- },
- // 自定义获取商品信息的函数
- customAction: {
- Type: [Function],
- default: null
- },
- // 价格的字体颜色
- priceColor: {
- Type: String,
- default: "#fe560a"
- },
- // 立即购买按钮的文字
- buyNowText: {
- Type: String,
- default: "立即购买"
- },
- // 立即购买按钮的字体颜色
- buyNowColor: {
- Type: String,
- default: "#ffffff"
- },
- // 立即购买按钮的背景颜色
- buyNowBackgroundColor: {
- Type: String,
- default: "linear-gradient(to right, $fuint-theme, $fuint-theme)"
- },
- // 加入购物车按钮的文字
- addCartText: {
- Type: String,
- default: "加入购物车"
- },
- // 加入购物车按钮的字体颜色
- addCartColor: {
- Type: String,
- default: "#ffffff"
- },
- // 加入购物车按钮的背景颜色
- addCartBackgroundColor: {
- Type: String,
- default: "linear-gradient(to right, $fuint-theme, $fuint-theme)"
- },
- // 不可点击时,按钮的样式
- disableStyle: {
- Type: Object,
- default: null
- },
- // 按钮点击时的样式
- activedStyle: {
- Type: Object,
- default: null
- },
- // 按钮常态的样式
- btnStyle: {
- Type: Object,
- default: null
- },
- // 是否显示右上角关闭按钮
- showClose: {
- Type: Boolean,
- default: true
- },
- // 关闭按钮的图片地址
- closeImage: {
- Type: String,
- default: "https://img.alicdn.com/imgextra/i1/121022687/O1CN01ImN0O11VigqwzpLiK_!!121022687.png"
- },
- // 默认库存数量 (未选择sku时)
- defaultStock: {
- Type: Number,
- default: 0
- },
- // 默认显示的价格 (未选择sku时)
- defaultPrice: {
- Type: Number,
- default: 0
- },
- },
- data() {
- return {
- complete: false, // 组件是否加载完成
- goodsInfo: {}, // 商品信息
- isShow: false, // true 显示 false 隐藏
- initKey: true, // 是否已初始化
- shopItemInfo: {}, // 存放要和选中的值进行匹配的数据
- selectArr: [], // 存放被选中的值
- subIndex: [], // 是否选中 因为不确定是多规格还是单规格,所以这里定义数组来判断
- selectShop: {}, // 存放最后选中的商品
- selectNum: this.minBuyNum, // 选中数量
- outFoStock: false, // 是否全部sku都缺货
- };
- },
- mounted() {
- that = this;
- vk = that.vk;
- if (that.value) {
- that.open();
- }
- },
- methods: {
- // 初始化
- init() {
- // 清空之前的数据
- that.selectArr = [];
- that.subIndex = [];
- that.selectShop = {};
- that.selectNum = that.minBuyNum;
- that.outFoStock = false;
- that.shopItemInfo = {};
- let specListName = that.specListName;
- that.goodsInfo[specListName].map(item => {
- that.selectArr.push('');
- that.subIndex.push(-1);
- });
- that.checkItem(); // 计算sku里面规格形成路径
- that.checkInpath(-1); // 传-1是为了不跳过循环
- that.autoClickSku(); // 自动选择sku策略
- },
- // 使用vk路由模式框架获取商品信息
- findGoodsInfo() {
- if (typeof vk == "undefined") {
- that.toast("custom-action必须是function");
- return false;
- }
- vk.callFunction({
- url: that.action,
- title: '请求中...',
- data: {
- goods_id: that.goodsId
- },
- success(data) {
- that.updateGoodsInfo(data.goodsInfo);
- }
- });
- },
- // 更新商品信息(库存、名称、图片)
- updateGoodsInfo(goodsInfo) {
- let skuListName = that.skuListName;
- if (JSON.stringify(that.goodsInfo) === "{}" || that.goodsInfo[that.goodsIdName] !== goodsInfo[that.goodsIdName]) {
- that.goodsInfo = goodsInfo;
- that.initKey = true;
- } else {
- that.goodsInfo[skuListName] = goodsInfo[skuListName];
- }
- if (that.initKey) {
- that.initKey = false;
- that.init();
- }
- // 更新选中sku的库存信息
- let select_sku_info = that.getListItem(that.goodsInfo[skuListName], that.skuIdName, that.selectShop[that.skuIdName]);
- Object.assign(that.selectShop, select_sku_info);
- that.complete = true;
- that.$emit("open", true);
- that.$emit("input", true);
- },
- async open() {
- let findGoodsInfoRun = true;
- let skuListName = that.skuListName;
- if (that.customAction && typeof(that.customAction) === 'function') {
- let goodsInfo = await that.customAction();
- if (goodsInfo && typeof goodsInfo == "object" && JSON.stringify(goodsInfo) != "{}") {
- findGoodsInfoRun = false;
- that.updateGoodsInfo(goodsInfo);
- } else {
- that.toast("无法获取到商品信息");
- that.$emit("input", false);
- return false;
- }
- } else {
- if (findGoodsInfoRun) {
- that.findGoodsInfo();
- }
- }
- },
- // 监听 - 弹出层收起
- close(s) {
- if (s == "close") {
- that.$emit("input", false);
- that.$emit("close", "close");
- } else if (s == "mask") {
- if (that.maskCloseAble) {
- that.$emit("input", false);
- that.$emit("close", "mask");
- }
- }
- },
- moveHandle() {
- //禁止父元素滑动
- },
- // sku按钮的点击事件
- skuClick(value, index1, event, index2) {
- if (value.ishow) {
- if (that.selectArr[index1] != value.name) {
- that.$set(that.selectArr, index1, value.name);
- that.$set(that.subIndex, index1, index2);
- } else {
- that.$set(that.selectArr, index1, '');
- that.$set(that.subIndex, index1, -1);
- }
- that.checkInpath(index1);
- // 如果全部选完
- that.checkSelectShop();
- }
- },
- // 检测是否已经选完sku
- checkSelectShop() {
- // 如果全部选完
- if (that.selectArr.every(item => item != '')) {
- that.selectShop = that.shopItemInfo[that.selectArr];
- that.selectNum = that.minBuyNum;
- } else {
- that.selectShop = {};
- }
- },
- // 检查路径
- checkInpath(clickIndex) {
- let specListName = that.specListName;
- //循环所有属性判断哪些属性可选
- //当前选中的兄弟节点和已选中属性不需要循环
- let specList = that.goodsInfo[specListName];
- for (let i = 0, len = specList.length; i < len; i++) {
- if (i == clickIndex) {
- continue;
- }
- let len2 = specList[i].list.length;
- for (let j = 0; j < len2; j++) {
- if (that.subIndex[i] != -1 && j == that.subIndex[i]) {
- continue;
- }
- let choosed_copy = [...that.selectArr];
- that.$set(choosed_copy, i, specList[i].list[j].name);
- let choosed_copy2 = choosed_copy.filter(item => item !== '' && typeof item !== 'undefined');
- if (that.shopItemInfo.hasOwnProperty(choosed_copy2)) {
- specList[i].list[j].ishow = true;
- } else {
- specList[i].list[j].ishow = false;
- }
- }
- }
- that.$set(that.goodsInfo, specListName, specList);
- },
- // 计算sku里面规格形成路径
- checkItem() {
- let skuListName = that.skuListName;
- // console.time('计算有多小种可选路径需要的时间是');
- // 去除库存小于等于0的商品sku
- let skuList = that.goodsInfo[skuListName];
- let stockNum = 0;
- for (let i = 0; i < skuList.length; i++) {
- if (skuList[i][that.stockName] <= 0) {
- skuList.splice(i, 1);
- i--;
- } else {
- stockNum += skuList[i][that.stockName];
- }
- }
- if (stockNum <= 0) {
- that.outFoStock = true;
- }
- // 计算有多小种可选路径
- let result = skuList.reduce(
- (arrs, items) => {
- return arrs.concat(
- items[that.skuArrName].reduce(
- (arr, item) => {
- return arr.concat(
- arr.map(item2 => {
- // 利用对象属性的唯一性实现二维数组去重
- if (!that.shopItemInfo.hasOwnProperty([...item2, item])) {
- that.shopItemInfo[[...item2, item]] = items;
- }
- return [...item2, item];
- })
- );
- },
- [
- []
- ]
- )
- );
- },
- [
- []
- ]
- );
- },
- // 检测sku选项是否已全部选完,且有库存
- checkSelectComplete(obj = {}) {
- let selectShop = that.selectShop;
- if (selectShop && selectShop[that.skuIdName] !== undefined) {
- // 判断库存
- if (that.selectNum <= selectShop[that.stockName]) {
- if (typeof obj.success == "function") obj.success(selectShop);
- } else {
- that.toast(that.stockText + "不足", "none")
- }
- } else {
- that.toast("请先选择对应规格", "none");
- }
- },
- // 加入购物车
- addCart() {
- that.checkSelectComplete({
- success: function(selectShop) {
- selectShop.buy_num = that.selectNum;
- that.$emit("add-cart", selectShop);
- }
- });
- },
- // 立即购买
- buyNow() {
- that.checkSelectComplete({
- success: function(selectShop) {
- selectShop.buy_num = that.selectNum;
- that.$emit("buy-now", selectShop);
- }
- });
- },
- // 弹窗
- toast(title, icon) {
- uni.showToast({
- title: title,
- icon: icon
- });
- },
- // 获取对象数组中的某一个item,根据指定的键值
- getListItem(list, key, value) {
- let item;
- for (let i in list) {
- if (typeof value == "object") {
- if (JSON.stringify(list[i][key]) === JSON.stringify(value)) {
- item = list[i];
- break;
- }
- } else {
- if (list[i][key] === value) {
- item = list[i];
- break;
- }
- }
- }
- return item;
- },
- // 自动选择sku前提是只有一组sku,默认自动选择最前面的有库存的sku
- autoClickSku() {
- let skuList = that.goodsInfo[that.skuListName];
- let specListArr = that.goodsInfo[that.specListName];
-
- if (specListArr.length == 1) {
- let specList = specListArr[0].list;
-
- for (let i = 0; i < specList.length; i++) {
- let sku = that.getListItem(skuList, that.skuArrName, [specList[i].name]);
- if (sku) {
- that.skuClick(specList[i], 0, {}, i);
- break;
- }
- }
- }
- }
- },
- // 过滤器
- filters: {
- // 金额显示过滤器
- priceFilter(n = 0) {
- if (typeof n == "string") {
- n = parseFloat(n)
- }
- return n ? n.toFixed(2) : n
- }
- },
- // 计算属性
- computed: {
- },
- watch: {
- value: function(val) {
- if (val) {
- that.open();
- }
- },
- }
- };
- </script>
- <style lang="scss" scoped>
- /* sku弹出层 */
- .goods-sku-popup {
- position: fixed;
- left: 0;
- top: 0;
- right: 0;
- bottom: 0;
- z-index: 999999999999;
- overflow: hidden;
- &.show {
- display: block;
- .mask {
- animation: showPopup 0.2s linear both;
- }
- .layer {
- animation: showLayer 0.2s linear both;
- }
- }
- &.hide {
- .mask {
- animation: hidePopup 0.2s linear both;
- }
- .layer {
- animation: hideLayer 0.2s linear both;
- }
- }
- &.none {
- display: none;
- }
- .mask {
- position: fixed;
- top: 0;
- width: 100%;
- height: 100%;
- z-index: 1;
- background-color: rgba(0, 0, 0, 0.65);
- }
- .layer {
- display: flex;
- width: 100%;
- max-height: 1200rpx;
- flex-direction: column;
- position: fixed;
- z-index: 999999;
- bottom: 0;
- border-radius: 10rpx 10rpx 0 0;
- background-color: #ffffff;
- margin-top: 10rpx;
- overflow-y: scroll;
- .btn-option {
- padding: 1rpx;
- display: block;
- clear: both;
- margin-bottom: 60rpx;
- }
- .specification-wrapper {
- width: 100%;
- margin-top: 20rpx;
- padding: 30rpx 25rpx;
- box-sizing: border-box;
- .specification-wrapper-content {
- width: 100%;
- min-height: 300rpx;
- &::-webkit-scrollbar {
- /*隐藏滚轮*/
- display: none;
- }
- .specification-header {
- width: 100%;
- display: flex;
- flex-direction: row;
- position: relative;
- margin-bottom: 40rpx;
- .specification-left {
- width: 180rpx;
- height: 180rpx;
- flex: 0 0 180rpx;
- .product-img {
- width: 180rpx;
- height: 180rpx;
- background-color: #999999;
- }
- }
- .specification-right {
- flex: 1;
- padding: 0 35rpx 10rpx 28rpx;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- justify-content: flex-end;
- font-weight: 500;
- .price-content {
- color: #fe560a;
- margin-bottom: 10rpx;
- .sign {
- font-size: 28rpx;
- margin-right: 4rpx;
- }
- .price {
- font-size: 44rpx;
- }
- }
- .inventory {
- font-size: 24rpx;
- color: #525252;
- margin-bottom: 14rpx;
- }
- .choose {
- font-size: 24rpx;
- color: #525252;
- min-height: 32rpx;
- }
- }
- }
- .specification-content {
- font-weight: 500;
- .specification-item {
- margin-bottom: 40rpx;
- &:last-child {
- margin-bottom: 0;
- }
- .item-title {
- margin-bottom: 15rpx;
- font-size: 32rpx;
- font-weight: bold;
- color: #000000;
- }
- .item-wrapper {
- display: flex;
- flex-direction: row;
- flex-flow: wrap;
- margin-bottom: -20rpx;
- .item-content {
- display: block;
- padding: 10rpx 20rpx;
- min-width: 110rpx;
- text-align: center;
- font-size: 24rpx;
- border-radius: 30rpx;
- background-color: #ffffff;
- color: #333333;
- margin-right: 20rpx;
- margin-bottom: 20rpx;
- border: 2rpx solid #cccccc;
- box-sizing: border-box;
- &.actived {
- border-color: #fe560a;
- color: #fe560a;
- }
- &.noactived {
- // background-color: #e4e4e4;
- // border-color: #e4e4e4;
- // color: #9e9e9e;
- // text-decoration: line-through;
- color: #c8c9cc;
- background: #f2f3f5;
- border-color: #f2f3f5;
- }
- }
- }
- }
- }
- }
- .close {
- position: absolute;
- top: 30rpx;
- right: 25rpx;
- width: 50rpx;
- height: 50rpx;
- text-align: center;
- line-height: 50rpx;
- .close-item {
- width: 40rpx;
- height: 40rpx;
- }
- }
- }
- .btn-wrapper {
- display: flex;
- width: 100%;
- height: 120rpx;
- flex: 0 0 120rpx;
- align-items: center;
- justify-content: space-between;
- padding: 0 26rpx;
- box-sizing: border-box;
- .layer-btn {
- width: 335rpx;
- height: 80rpx;
- border-radius: 40rpx;
- color: #fff;
- line-height: 80rpx;
- text-align: center;
- font-weight: 500;
- font-size: 28rpx;
- &.add-cart {
- background: #ffbe46;
- }
- &.buy {
- background: #fe560a;
- }
- }
- .sure {
- width: 698rpx;
- height: 80rpx;
- border-radius: 38rpx;
- color: #fff;
- line-height: 80rpx;
- text-align: center;
- font-weight: 500;
- font-size: 28rpx;
- background: #fe560a;
- }
- .sure.add-cart {
- background: #ff9402;
- }
- }
- }
- @keyframes showPopup {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 1;
- }
- }
- @keyframes hidePopup {
- 0% {
- opacity: 1;
- }
- 100% {
- opacity: 0;
- }
- }
- @keyframes showLayer {
- 0% {
- transform: translateY(120%);
- }
- 100% {
- transform: translateY(0%);
- }
- }
- @keyframes hideLayer {
- 0% {
- transform: translateY(0);
- }
- 100% {
- transform: translateY(120%);
- }
- }
- }
- </style>
|