xiaogang há 3 anos atrás
commit
f4e0940064
100 ficheiros alterados com 20057 adições e 0 exclusões
  1. 143 0
      App.vue
  2. 256 0
      components/basic-component/app-button/app-button.vue
  3. 73 0
      components/basic-component/app-cart-image/app-cart-image.vue
  4. 128 0
      components/basic-component/app-check-box/app-check-box.vue
  5. 178 0
      components/basic-component/app-city-swiper/app-city-swiper.vue
  6. 201 0
      components/basic-component/app-close/app-close.vue
  7. 210 0
      components/basic-component/app-composition/app-composition.vue
  8. 115 0
      components/basic-component/app-css-icon/app-css-icon.vue
  9. 167 0
      components/basic-component/app-datetime-picker/app-datetime-picker.vue
  10. 42 0
      components/basic-component/app-empty-bottom/app-empty-bottom.vue
  11. 30 0
      components/basic-component/app-empty/app-empty.vue
  12. 67 0
      components/basic-component/app-form-id/app-form-id.vue
  13. 43 0
      components/basic-component/app-hotspot/app-hotspot.vue
  14. 69 0
      components/basic-component/app-image/app-image.vue
  15. 230 0
      components/basic-component/app-input/app-input.vue
  16. 40 0
      components/basic-component/app-iphone-x/app-iphone-x.vue
  17. 446 0
      components/basic-component/app-jump-button/app-jump-button.vue
  18. 259 0
      components/basic-component/app-layout/app-coupon-modal/app-coupon-modal.vue
  19. 383 0
      components/basic-component/app-layout/app-layout.vue
  20. 847 0
      components/basic-component/app-layout/app-payment/app-payment.vue
  21. 151 0
      components/basic-component/app-layout/app-permissions-auth/app-permissions-auth.vue
  22. 288 0
      components/basic-component/app-layout/app-user-login/app-user-login.vue
  23. 140 0
      components/basic-component/app-layout/bd-mandatory-attention/bd-mandatory-attention.vue
  24. 174 0
      components/basic-component/app-layout/u-authorized-iphone/u-authorized-iphone.vue
  25. 24 0
      components/basic-component/app-load-text/app-load-text.vue
  26. BIN
      components/basic-component/app-load-text/image/load.gif
  27. 109 0
      components/basic-component/app-loading/app-loading.vue
  28. 199 0
      components/basic-component/app-model/app-model.vue
  29. BIN
      components/basic-component/app-model/image/close.png
  30. 186 0
      components/basic-component/app-order/app-form-data.vue
  31. 90 0
      components/basic-component/app-prompt-box/app-prompt-box.vue
  32. 150 0
      components/basic-component/app-radio/app-radio-group.vue
  33. 111 0
      components/basic-component/app-radio/app-radio.vue
  34. BIN
      components/basic-component/app-radio/image/yes.png
  35. 112 0
      components/basic-component/app-report-error/app-report-error.vue
  36. 28 0
      components/basic-component/app-rich/components/wxParseAudio.vue
  37. 114 0
      components/basic-component/app-rich/components/wxParseImg.vue
  38. 52 0
      components/basic-component/app-rich/components/wxParseTable.vue
  39. 143 0
      components/basic-component/app-rich/components/wxParseTemplate0.vue
  40. 139 0
      components/basic-component/app-rich/components/wxParseTemplate1.vue
  41. 15 0
      components/basic-component/app-rich/components/wxParseVideo.vue
  42. 234 0
      components/basic-component/app-rich/libs/html2json.js
  43. 126 0
      components/basic-component/app-rich/libs/htmlparser.js
  44. 210 0
      components/basic-component/app-rich/libs/wxDiscode.js
  45. 228 0
      components/basic-component/app-rich/parse.scss
  46. 139 0
      components/basic-component/app-rich/parse.vue
  47. 82 0
      components/basic-component/app-switch-tab/app-switch-tab.vue
  48. 104 0
      components/basic-component/app-tab-bar/app-tab-bar.vue
  49. 110 0
      components/basic-component/app-tab-nav/app-tab-nav.vue
  50. 153 0
      components/basic-component/app-tabs/app-tabs.vue
  51. 25 0
      components/basic-component/app-text/app-text.vue
  52. 162 0
      components/basic-component/app-textarea/app-textarea.vue
  53. 55 0
      components/basic-component/app-timer/app-timer.vue
  54. 302 0
      components/basic-component/app-upload-image/app-upload-image.vue
  55. 219 0
      components/basic-component/u-count-to/u-count-to.vue
  56. 85 0
      components/basic-component/u-mask/u-mask.vue
  57. 322 0
      components/basic-component/u-popup/u-popup.vue
  58. 424 0
      components/basic-component/u-tabs-swiper/u-tabs-swiper.vue
  59. 199 0
      components/basic-component/uni-swiper-dot/uni-swiper-dot.vue
  60. 151 0
      components/page-component/app-account-balance/app-account-balance.vue
  61. 67 0
      components/page-component/app-account-balance/app-account-style.vue
  62. 146 0
      components/page-component/app-ad/app-ad.vue
  63. 347 0
      components/page-component/app-area-picker/app-area-picker.vue
  64. 240 0
      components/page-component/app-associated-link/app-associated-link.vue
  65. 1195 0
      components/page-component/app-attr/app-attr.vue
  66. 111 0
      components/page-component/app-buy-prompt/app-buy-prompt.vue
  67. 195 0
      components/page-component/app-cash-model/app-cash-model.vue
  68. 94 0
      components/page-component/app-category-list/app-category-list.vue
  69. 87 0
      components/page-component/app-check-in/app-check-in.vue
  70. 55 0
      components/page-component/app-check-in/check-in-award.js
  71. 197 0
      components/page-component/app-clerk-historys/app-clerk-historys.vue
  72. 103 0
      components/page-component/app-common/app-wechat-share.vue
  73. 56 0
      components/page-component/app-copyright/app-copyright.vue
  74. 177 0
      components/page-component/app-coupon-center/app-coupon-center.vue
  75. 122 0
      components/page-component/app-diy-form/app-diy-form-checkbox-group.vue
  76. 861 0
      components/page-component/app-diy-form/app-diy-form.vue
  77. 209 0
      components/page-component/app-diy-goods-list/app-diy-composition-image.vue
  78. 1055 0
      components/page-component/app-diy-goods-list/app-diy-goods-list.vue
  79. 567 0
      components/page-component/app-diy-goods-list/app-diy-m-goods-list.vue
  80. 180 0
      components/page-component/app-diy-goods-list/app-goods-timer.vue
  81. 107 0
      components/page-component/app-diy-timer/app-diy-timer.vue
  82. 380 0
      components/page-component/app-exclusive-coupon/app-exclusive-coupon-two.vue
  83. 353 0
      components/page-component/app-exclusive-coupon/app-exclusive-coupon.vue
  84. 437 0
      components/page-component/app-good-shop-recommendation/app-good-shop-recommendation.vue
  85. 45 0
      components/page-component/app-goods-detail/app-name.vue
  86. 713 0
      components/page-component/app-goods-list/app-goods-list.vue
  87. 156 0
      components/page-component/app-goods-poster/app-goods-poster-four.vue
  88. 158 0
      components/page-component/app-goods-poster/app-goods-poster-one.vue
  89. 166 0
      components/page-component/app-goods-poster/app-goods-poster-three.vue
  90. 176 0
      components/page-component/app-goods-poster/app-goods-poster-two.vue
  91. 228 0
      components/page-component/app-goods-poster/app-poster-image.vue
  92. 119 0
      components/page-component/app-goods-poster/app-poster-price.vue
  93. 79 0
      components/page-component/app-goods-recommend/app-goods-recommend.vue
  94. 145 0
      components/page-component/app-head-navigation/app-head-navigation.vue
  95. 190 0
      components/page-component/app-image-ad/app-image-ad.vue
  96. 130 0
      components/page-component/app-index-cat/app-index-cat.vue
  97. 245 0
      components/page-component/app-index-wholesale/app-index-wholesale.vue
  98. 40 0
      components/page-component/app-iphonex-bottom/app-iphonex-bottom.vue
  99. 64 0
      components/page-component/app-join-member/app-join-member.vue
  100. 580 0
      components/page-component/app-live/app-live.vue

+ 143 - 0
App.vue

xqd
@@ -0,0 +1,143 @@
+<script>
+    export default {
+        globalData() {
+            return {
+                stystem: {},
+                text: ''
+            }
+        },
+        onLaunch: function (options) {
+			console.log(options)
+            console.log('app onLaunch--->'); // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log(options);            // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log('<---app onLaunch'); // 公众号文章进小程序无底部导航调试,请勿删除
+            // #ifdef H5
+            if (!this.$jwx.isWechat()) {
+               uni.getLocation({
+                   success() {
+                   },
+                   fail() {
+                   }
+               });
+            }
+            // #endif
+            if (options && options.scene) {
+                this.$appScene = options.scene;
+            }
+            this.$store.dispatch('mallConfig/actionGetConfig');
+            let _this = this;
+            wx.getSystemInfo({
+                success: function (response) {
+                    _this.$store.dispatch('gConfig/setSystemInfo', response);
+                    _this.$store.dispatch('iPhoneX/setIphone', response);
+                }
+            });
+            // #ifdef MP-WEIXIN
+            if (options.scene == '1011' || options.scene == '1012' || options.scene == '1013'
+                || options.scene == '1047' || options.scene == '1048' || options.scene == '1049') {
+                this.$store.dispatch('page/actionSetIsScanQrCode', true);
+            }
+            // #endif
+            // #ifdef MP-ALIPAY
+            if (typeof options.query != 'undefined') {
+                this.$store.dispatch('page/actionSetQeury', options.query)
+            }
+            // #endif
+            if (options.query && typeof options.query.user_id !== 'undefined') {
+                this.$store.dispatch('user/setTempParentId', options.query.user_id)
+            }
+            // #ifdef H5
+            this.$storage.setStorageSync('platform', 'wechat');
+            if (this.$jwx.isWechat()) {
+                if (!this.$storage.getStorageSync('_USER_SIGN')) {
+                    this.$storage.setStorageSync('isSign', false);
+                    this.$storage.removeStorageSync('_USER_ACCESS_TOKEN');
+                    this.$user.silentLogin();
+                } else {
+                    this.$storage.setStorageSync('_USER_SIGN', false);
+                    let params = this.$utils.getUrlParam('code');
+                    if (params) {
+                        this.$request({
+                            url: this.$api.registered.login,
+                            data: {
+                                code: params,
+                                type: 'wechat'
+                            },
+                            method: 'post'
+                        }).then(response => {
+                            if (response.code === 0) {
+                                this.$storage.setStorageSync('_USER_ACCESS_TOKEN', response.data.access_token);
+                            }
+                            let url = window.location.href;
+                            let res = url.replace(url.slice(url.indexOf('?'), url.indexOf('#')), '');
+                            let index = res.indexOf('#');
+                            let newRes = res.slice(0, index) + '?' + res.slice(index);
+                            this.$storage.setStorageSync('_USER_SIGN', true);
+                            window.location.replace(newRes);
+                            this.$storage.setStorageSync('isSign', true);
+                        });
+                    }
+                    return true;
+                }
+            } else {
+                this.$storage.setStorageSync('isSign', true);
+                if (window.location.href.indexOf('?#') === -1) {
+                    let { hash, origin, pathname } = window.location;
+                    this.$storage.setStorageSync('_USER_SIGN', true);
+                    window.location.replace(`${origin}${pathname}?${hash}`);
+                    return true;
+                }
+            }
+            if (this.$jwx.isWechat()) {
+                let hash= window.location.hash;
+                if (hash.indexOf('isWechat=true') > -1 && hash.indexOf('isPay=ture') > -1 && hash.indexOf('payType=alipay_h5') > -1) {
+                    uni.reLaunch({
+                        url: '/pages/registered/placard'
+                    });
+                }
+                this.$storage.setStorageSync('isSign', true);
+            }
+            // #endif
+
+            // #ifdef MP-WEIXIN
+            this.$user.silentLogin();
+            // #endif
+        },
+        onShow(options) {
+            console.log('app onShow--->'); // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log(options);          // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log('<---app onShow'); // 公众号文章进小程序无底部导航调试,请勿删除
+            if (options && options.scene) {
+                this.$appScene = options.scene;
+            }
+        }
+    };
+</script>
+
+<style lang="scss">
+    /*每个页面公共css */
+    @import "./static/css/flex.scss";
+    @import "./static/css/themeColor.scss";
+    @import "./static/css/text.scss";
+    @import "./static/css/image.scss";
+    @import "./static/css/parse.scss";
+    @import "./static/css/gift.scss";
+    @import "./static/css/u-index.scss";
+    @import "./static/css/border-box.scss";
+    @import './static/css/iconfont.css';
+    .u-goods-detail {
+        background-image: url("./static/image/goods.png");
+        background-repeat: no-repeat;
+        background-size: 100% 100%;
+        min-height: 100vh;
+    }
+    .u-border-box {
+        box-sizing: border-box;
+    }
+    /* #ifdef H5 */
+    body.pages-index-index uni-page-body { background: transparent!important;}
+    /* #endif */
+    /* #ifdef H5 */
+    //uni-page-head { display: none}
+    /* #endif */
+</style>

+ 256 - 0
components/basic-component/app-button/app-button.vue

xqd
@@ -0,0 +1,256 @@
+<template>
+    <view class="app-view" :style="{
+        'width': `${width ? width : '100%'}${width ? 'rpx': ''}`,
+         'height': `${setHeight}rpx`,
+    }">
+        <button
+            v-if="type === 'important'"
+            :style="[
+                {
+                    'line-height': `${setHeight - 2}rpx`,
+                    'font-size': `${fontSize ? fontSize : 32}rpx`,
+                    'width': `${width ? width : '100%'}${width ? 'rpx': ''}`,
+                    'height': `${setHeight}rpx`,
+                    'background-color': `${background ? background : theme.background}`,
+                    'border-radius': `${roundSize ? roundSize : ''}`,
+                    color: `${color ? color : theme.main_text}`,
+                    padding: padding
+                }
+            ]"
+            @click="handleClick"
+            :class="[
+                'app-button',
+                `app-important`,
+              `${round ? 'is-round' : ''}`,
+               `${disabled ? 'app-important-disabled is-disabled' : ''}`,
+              `${arrangement === 'column' ? 'app-button-column' : arrangement === 'row' ? 'app-button-row' : ''}`
+            ]"
+        >
+            <slot></slot>
+        </button>
+        <button
+                @click="handleClick"
+                v-else-if="type === 'general'"
+                :class="[
+                `app-button`,
+                `app-general`,
+                `${disabled ? 'app-general-disabled  is-disabled' : 'app-general-disabled-not'}`,
+                `${round ? 'is-round': ''}`,
+                `${arrangement === 'column' ? 'app-button-column' : arrangement === 'row' ? 'app-button-row' : ''}`
+                ]"
+                :style="{
+                    'height': `${setHeight}rpx`,
+                    'line-height': `${setHeight - 2}rpx`,
+                    'font-size': `${fontSize ? fontSize : 32}rpx`,
+                    'width': `${width ? width : '100%'}${width ? 'rpx': ''}`,
+                    'background-color': `${background ? background : ''}`,
+                    'border-radius': `${roundSize ? roundSize : ''}`,
+                    color: color,
+                    padding: padding
+                }"
+        >
+            <slot></slot>
+        </button>
+        <button
+                @click="handleClick"
+                v-else
+              :style="{
+                'height': `${setHeight}rpx`,
+                'line-height': `${setHeight - 2}rpx`,
+                'font-size': `${fontSize ? fontSize : 32}rpx`,
+                'width': `${width ? width : '100%'}${width ? 'rpx': ''}`,
+                'background-color': `${background ? background : theme.background}`,
+                'border-radius': `${roundSize ? roundSize : ''}`,
+                'border-color': `${borderColor ? borderColor : theme.border}`,
+                'color': `${color ? color : theme.main_text}`,
+                'padding': padding
+            }"
+              :class="[
+                'app-button',
+                `app-secondary`,
+              `${round ? 'is-round' : ''}`,
+               `${disabled ? 'app-secondary-disabled is-disabled' : ''}`,
+               `${arrangement === 'column' ? 'app-button-column' : arrangement === 'row' ? 'app-button-row' : ''}`
+            ]"
+        >
+            <slot></slot>
+        </button>
+    </view>
+</template>
+
+<script>
+
+    export default {
+    props: {
+        disabled: Boolean,
+        type: String,
+        round: Boolean,
+        theme: Object,
+        height: String,
+        fontSize: String,
+        width: String,
+        color: String,
+        size: String,
+        background: String,
+        form: Boolean,
+        arrangement: String,
+        roundSize: String,
+        padding: String,
+        borderColor: String,
+    },
+    data() {
+        return {
+            touch: false
+        }
+    },
+    methods: {
+        handleClick(e) {
+            this.$emit('click', e);
+        }
+    },
+    computed: {
+        setHeight: function() {
+            if (this.height) {
+                return this.height;
+            } else {
+                switch (this.size) {
+                    case 'large':
+                        return 100;
+                    case 'small':
+                        return 60;
+                    case 'medium':
+                        return 80;
+                    default:
+                        return 80;
+                }
+            }
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+    
+    $border-radius: 40rpx;
+    $height: 80rpx;
+    $border-size: 1px;
+
+    .app-view {
+        position: relative;
+        /*width:100%;*/
+        .app-button {
+            height: $height;
+            text-align: center;
+            line-height: $height;
+        }
+        .is-round {
+            border-radius: $border-radius;
+        }
+        .is-disabled {
+            /*background-color: #cdcdcd;*/
+            pointer-events: none;
+        }
+        .app-general {
+            background-color: #ffffff;
+            border: $border-size solid #cdcdcd;
+        }
+        .app-general-disabled {
+            color: #cdcdcd;
+        }
+        .app-general-disabled-not {
+            color: #353535;
+        }
+        .app-mask {
+            background-color: rgba(0, 0, 0, .2);
+            width: 100%;
+            height: $height;
+            position: absolute;
+            top:0;
+            left: 0;
+        }
+        .app-important {
+            color: #ffffff;
+        }
+        .app-important-disabled {
+            background-color: #cdcdcd;
+        }
+        .app-secondary {
+            border-width: $border-size;
+            border-style: solid;
+        }
+        .app-secondary-disabled {
+            background-color: #ffffff;
+            border-color: #cdcdcd;
+            color: #cdcdcd;
+        }
+    }
+    .app-button:active {
+        box-shadow: inset 0 0 #{1000rpx} rgba(0, 0, 0, .15);
+    }
+</style>
+
+<style scoped lang="scss">
+    /* #ifdef MP-WEIXIN */
+    button {
+        border: none;
+        padding: 0 #{24rpx};
+    }
+    button:after {
+        border: none;
+        border-radius: 0;
+    }
+    
+    /* #endif */
+    
+    /* #ifdef MP-TOUTIAO */
+    div {
+        display: block !important;
+    }
+    button {
+        border-radius: 0;
+        padding: 0;
+        height: 100%;
+        //border-color:inherit;
+    }
+    button:after {
+        border: none;
+        border-radius: 0;
+    }
+    /* #endif */
+    
+    /* #ifdef MP-ALIPAY */
+    button {
+        border: none;
+    //background-color: transparent;
+        border-radius: 0;
+    }
+    /* #endif */
+    
+    /* #ifdef MP-BAIDU*/
+    button {
+        padding: 0;
+    }
+    button:after {
+        border-radius: 0;
+        border: none;
+    }
+    /* #endif */
+
+    .app-button-column {
+	    height: 100%;
+	    width: 100%;
+	    display: flex;
+	    flex-direction: column;
+	    justify-content: center;
+	    align-items: center;
+    }
+
+    .app-button-row {
+	    height: 100%;
+	    width: 100%;
+	    display: flex;
+	    flex-direction: row;
+	    justify-content: center;
+	    align-items: center;
+    }
+</style>

+ 73 - 0
components/basic-component/app-cart-image/app-cart-image.vue

xqd
@@ -0,0 +1,73 @@
+<template>
+    <view class="bd-image" :style="cartStyle" :class="cartClass"></view>
+</template>
+
+<script>
+    import {mapGetters} from "vuex";
+
+    export default {
+        name: "app-cart-image",
+
+        data() {
+            return {
+                is_loading: false,
+				disable: 'disable'
+            }
+        },
+
+        props: {
+            imageWidth: {
+                type: String,
+                default: '36rpx'
+            },
+            imageHeight: {
+                type: String,
+                default: '36rpx'
+            },
+            src: {
+                type: String,
+                default: '/static/image/icon/goods-cart.png'
+            },
+            sign: String,
+            theme: String,
+			goods: {
+				type: Object|null,
+				default: null,
+			}
+        },
+
+        computed: {
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+            cartStyle() {
+                let style = `width: ${this.imageWidth};height: ${this.imageHeight};`;
+                if((!this.goods || this.goods.buy_goods_auth) && this.sign !== 'gift') {
+                    style += `background-color : ${this.getTheme.background}`
+                }
+                return style;
+            },
+			cartClass() {
+				if (!this.goods || this.goods.buy_goods_auth) {
+					return this.sign !== 'gift' ? '' : this.theme + '-background';
+				} else {
+					return this.disable + '-m-back ' + this.disable;
+				}
+			}
+        },
+        methods: {
+            imgLoad() {
+                this.is_loading = true;
+            }
+        }
+
+    }
+</script>
+
+<style scoped>
+.bd-image {
+    background-repeat: no-repeat;
+    background-size: 101% 101%;
+    background-image:url("../../../static/image/icon/goods-cart.png");
+}
+</style>

+ 128 - 0
components/basic-component/app-check-box/app-check-box.vue

xqd
@@ -0,0 +1,128 @@
+<template>
+    <view>
+        <view class="app-view" v-if="type === 'all'" @click="selectAll">
+            <view class="app-item"
+                  :style="{'width': `${size ? size : 32}rpx`}"
+                  :class="[
+                {'app-radius': shape === 'round'}
+            ]"
+            ></view>
+            <view class="app-mask"
+                  v-show="value.boolean"
+                  :style="{'width': `${size ? size : 32}rpx`}"
+                  :class="[
+                {'app-radius': shape === 'round'},
+                `${theme}-background`
+                ]"
+            ></view>
+        </view>
+        <view class="app-view" @click="handleCheckBox" v-if="type !== 'all'">
+            <view class="app-item"
+                  :style="{'width': `${size ? size : 32}rpx`}"
+                  :class="[
+                {'app-radius': shape === 'round'}
+            ]"
+            ></view>
+            <view class="app-mask"
+                  v-show="value.boolean"
+                  :style="{'width': `${size ? size : 32}rpx`}"
+                  :class="[
+                {'app-radius': shape === 'round'},
+                `${theme}-background`
+                ]"
+            ></view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-check-box',
+        props: {
+            selectData: Array,
+            shape: String,
+            theme: String,
+            size: String,
+            type: String,
+            item: Object,
+            value: {
+                default: {
+                    type: 'all',
+                    boolean: false
+                },
+            },
+            isShow: Boolean,
+        },
+        data() {
+            return {
+                showHidden: this.value,
+                allBoolean: this.value,
+            }
+        },
+       methods: {
+           handleCheckBox() {
+               this.showHidden = !this.showHidden;
+               this.$emit('input', this.showHidden);
+           },
+           selectAll() {
+               this.allBoolean = !this.allBoolean;
+               this.$emit('input', this.allBoolean);
+               
+           }
+       },
+        watch: {
+            selectData: {
+                handler: function() {
+                    // let num = 0;
+                    // let add = this.selectData.length;
+                    // for (let i = 0; i < this.selectData.length; i++) {
+                    //     if (this.selectData[i].boolean === true) {
+                    //         num++;
+                    //     } else {
+                    //         add--;
+                    //     }
+                    // }
+                    // if (add < this.selectData.length) {
+                    //     this.allBoolean = false;
+                    //     this.$emit('input', this.allBoolean);
+                    // }
+                    // if (num === this.selectData.length) {
+                    //     this.allBoolean = true;
+                    //     this.$emit('input', this.allBoolean);
+                    // }
+                },
+                deep: true,
+                immediate: true,
+            },
+            isShow: {
+                handler: function(response) {
+                    // for (let i = 0;i < this.selectData.length; i++) {
+                    //     this.selectData[i].boolean = response;
+                    // }
+                }
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+
+    .app-view {
+        position: relative;
+    }
+    .app-item {
+        width: #{32rpx};
+        height: #{32rpx};
+        border: #{1rpx} solid $uni-general-color-three;
+    }
+    .app-radius {
+        border-radius: 50%;
+    }
+    .app-mask {
+        width: #{32rpx};
+        height: #{32rpx};
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+</style>

+ 178 - 0
components/basic-component/app-city-swiper/app-city-swiper.vue

xqd
@@ -0,0 +1,178 @@
+<template>
+	<view class="app-picker">
+		<view class="app-mask" :class="{'show': showPicker}" v-if="showPicker" @tap="maskTap" @touchmove.stop.prevent catchtouchmove="true"></view>
+		<view class="app-picker-cnt" :class="{'show':showPicker}">
+			<view class="app-picker-hd" @touchmove.stop.prevent catchtouchmove="true">
+				<view class="app-picker-btn" @tap="pickerCancel">取消</view>
+				<view class="app-picker-btn" :style="{'color':themeColor}" @tap="pickerConfirm">确定</view>
+			</view>
+			<view class="app-picker-view" v-if="mode=='region'">
+				<picker-view :indicator-style="itemHeight" :value="pickVal" @change="bindChange">
+					<picker-view-column>
+						<view class="app-picker-item"  v-for="(item,index) in selectData" :key="index">{{item.name}}</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="app-picker-item" v-if="provinceId === item.parent_id_id" v-for="(item,index) in selectData[provinceId].list" :key="index">{{item.name}}</view>
+					</picker-view-column>
+					<picker-view-column>
+						<view class="app-picker-item" v-if="cityId === item.parent_id_id" v-for="(item,index) in selectData[provinceId].list[cityId].list" :key="index">{{item.name}}</view>
+					</picker-view-column>
+				</picker-view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+    export default {
+        name: "app-city",
+	    data() {
+            return {
+                showPicker: false,
+                pickVal: [0,0,0],
+                itemHeight:`height: ${uni.upx2px(88)}px;`,
+                checkArr:[],
+                provinceId: 0,
+	            cityId: 0,
+            }
+	    },
+	    props: {
+            themeColor:{
+                type:String,
+                default(){
+                    return "#f00"
+                }
+            },
+            mode: {
+                type:String,
+                default(){
+                    return "region"
+                }
+            },
+            cityData: {
+                type: Object,
+			    default() {
+                    return {}
+			    }
+		    }
+	    },
+	    methods: {
+            maskTap(){
+                this.showPicker = false;
+            },
+            bindChange(val) {
+                let arr = val.detail.value;
+                this.provinceId = arr[0];
+                this.cityId = arr[1];
+                this.$nextTick().then(() => {
+                    this.pickVal = arr;
+                });
+            },
+            pickerCancel() {
+                this.showPicker = false;
+            },
+            pickerConfirm() {
+                // 返回结果
+                this.showPicker = false;
+            },
+		    initData(list) {
+                for (let i = 0; i < list.length; i++) {
+                    list[i].id_id = i;
+                    for (let j = 0; j < list[i].list.length; j++) {
+                        list[i].list[j].parent_id_id = i;
+                        list[i].list[j].id_id = j;
+                        for (let p = 0; p < list[i].list[j].list.length; p++) {
+                            list[i].list[j].list[p].parent_id_id = j;
+                            list[i].list[j].list[p].id_id = p;
+                        }
+                    }
+                }
+                return list;
+		    }
+	    },
+	    computed: {
+            selectData: function() {
+                return this.initData(this.cityData);
+            }
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-picker {
+		position: relative;
+		z-index: 1888;
+		height:100%;
+		.app-mask {
+			position: fixed;
+			z-index: 1000;
+			top: 0;
+			right: 0;
+			left: 0;
+			bottom: 0;
+			background: rgba(0, 0, 0, 0.5);
+			transition: all 0.3s ease;
+			.app-mask.show{
+				visibility: visible;
+				opacity: 1;
+			}
+		}
+		.app-picker-cnt {
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 100%;
+			transition: all 0.3s ease;
+			transform: translateY(100%);
+			z-index: 1600;
+		}
+		.app-picker-cnt.show {
+			transform: translateY(0);
+		}
+		.app-picker-hd {
+			display: flex;
+			align-items: center;
+			padding: 0 #{30rpx};
+			height: #{88rpx};
+			background-color: #fff;
+			position: relative;
+			text-align: center;
+			font-size: #{32rpx};
+			justify-content: space-between;
+			.app-picker-btn{
+				font-size: #{30rpx};
+			}
+		}
+		.app-picker-hd:after {
+			content: ' ';
+			position: absolute;
+			left: 0;
+			bottom: 0;
+			right: 0;
+			height: 1px;
+			border-bottom: 1px solid #e5e5e5;
+			color: #e5e5e5;
+			transform-origin: 0 100%;
+			transform: scaleY(0.5);
+		}
+		.app-picker-view {
+			width: 100%;
+			height: #{476rpx};
+			overflow: hidden;
+			background-color: rgba(255, 255, 255, 1);
+			z-index: 666;
+		}
+		.app-picker-item {
+			text-align: center;
+			width: 100%;
+			height: #{88rpx};
+			line-height: #{88rpx};
+			text-overflow: ellipsis;
+			white-space: nowrap;
+			font-size: #{30rpx};
+		}
+		picker-view{
+			height: 100%;
+		}
+	}
+</style>

+ 201 - 0
components/basic-component/app-close/app-close.vue

xqd
@@ -0,0 +1,201 @@
+<template>
+    <view>
+        <u-mask v-if="modal" :show="mallStatus.is_open == 2" zIndex="3000" :maskClickAble="false" :zoom="false">
+            <view style="height: 100%;width: 100%" class="main-center cross-center">
+                <view class="open-dialog">
+                    <view class="open-dialog-title">商家已打烊</view>
+                    <view class="open-dialog-content">
+                        <view v-if="list.length > 0 && list != '商城'">{{list}}已经打烊</view>
+                        <view v-if="!list">您访问的商家已经打烊</view>
+                        <view v-if="mallStatus.auto_open_text.length > 0">{{mallStatus.auto_open_text}}</view>
+                    </view>
+                    <view @click="toIndex" class="dialog-button" :style="{'color':getTheme.color}">继续浏览</view>
+                </view>
+            </view>
+        </u-mask>
+        <view v-else>
+            <view class="dir-left-nowrap cross-center close-tip" v-if="mallStatus.is_open == 2">
+                <image src="/static/image/icon/be-close.png"></image>
+                <view class="dir-top-nowrap main-center">
+                    <view>商家已打烊</view>
+                    <view class="close-content" v-if="mallStatus.auto_open_text.length > 0">{{mallStatus.auto_open_text}}</view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+
+	import {mapState, mapGetters} from 'vuex';
+    import uMask from "../u-mask/u-mask.vue";
+	
+    export default {
+        name: "app-close",
+        components:{
+            uMask
+        },
+	    props: {
+            modal: {
+                type: Boolean,
+                default() {
+                    return true;
+                }
+            },
+            toBack: {
+                type: Boolean,
+                default() {
+                    return false;
+                }
+            },
+            mch_id: {
+                type: [Number, String],
+                default() {
+                    return 0;
+                }
+            },
+            mch_list: {
+                type: String,
+                default() {
+                    return ''
+                }
+            }
+	    },
+        data() {
+            return {
+                mallStatus: {
+                    is_open: 0,
+                    auto_open_text: ''
+                },
+                isMall: true,
+                list: ''
+            }
+        },
+        created() {
+            let para = {};
+            let mch_list = ''
+            if(this.mch_list) {
+                mch_list = this.mch_list;
+                if(mch_list.length > 0) {
+                    para.mch_id_list = mch_list;
+                    if(mch_list.indexOf(0) == -1) {
+                        this.isMall = false;
+                    }
+                }
+            }
+            if(this.mch_id > 0) {
+                para.mch_id_list = JSON.stringify([this.mch_id]);
+                this.isMall = false;
+            }
+            this.$request({
+                url: this.$api.index.status,
+                data: para
+            }).then(response => {
+                console.log(para)
+                let mallStatus = response.data;
+                if(!this.isMall) {
+                    response.data.shift();
+                }
+                this.mallStatus = response.data[0]
+                if(this.mch_id > 0) {
+                    for(let item of response.data) {
+                        if(item.mch_id == this.mch_id) {
+                            this.mallStatus = item
+                        }
+                    }
+                }
+                if(mch_list.length > 0) {
+                    this.list = '';
+                    for(let item of response.data) {
+                        if(item.is_open == 2) {
+                            if(!this.mallStatus.auto_open_text) {
+                                this.mallStatus.auto_open_text = item.auto_open_text;
+                            }
+                            this.mallStatus.is_open = item.is_open;
+                            if(this.list.length > 0) {
+                                this.list += '、'
+                            }
+                            this.list += item.name
+                        }
+                    }
+                    this.$emit('update', this.mallStatus)
+                }else {
+                    this.$emit('update', this.mallStatus)
+                }
+            })
+        },
+	    computed: {
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+            ...mapState({
+                mall: state => state.mallConfig.mall,
+                userInfo: state => state.user.info,
+            })
+	    },
+        methods: {
+            toIndex() {
+                if(this.toBack) {
+                    uni.navigateBack();
+                }else {
+                    uni.redirectTo({
+                        url: '/pages/index/index'
+                    })
+                }
+            },
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .close-tip {
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        height: 130rpx;
+        width: 100%;
+        z-index: 20;
+        font-size: 30rpx;
+        color: #353535;
+        background-color: #fff7d7;
+        image {
+            width: 70rpx;
+            height: 70rpx;
+            margin: 0 24rpx;
+        }
+        .close-content {
+            padding-top: 10rpx;
+            font-size: 24rpx;
+            color: #999999;
+        }
+    }
+    .open-dialog {
+        width: 620rpx;
+        border-radius: 16rpx;
+        background-color: #fff;
+        position: relative;
+        padding-top: 40rpx;
+        padding-bottom: 105rpx;
+        font-size: 32rpx;
+        color: #353535;
+        text-align: center;
+        .open-dialog-content {
+            padding: 25rpx 0;
+            font-size: 26rpx;
+            color: #666666;
+            view {
+                margin: 5rpx 0;
+            }
+        }
+        .dialog-button {
+            font-size: 30rpx;
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            border-top: 2rpx solid #e2e2e2;
+            height: 90rpx;
+            line-height: 90rpx;
+            width: 100%;
+        }
+    }
+</style>

+ 210 - 0
components/basic-component/app-composition/app-composition.vue

xqd
@@ -0,0 +1,210 @@
+<template>
+    <view class="app-composition dir-left-nowrap">
+        <view @click.stop="open">
+            <view class="app-composition-goods" :class="item.goods_list.length == 2 ? 'two' : item.goods_list.length == 3 ? 'three' : item.goods_list.length == 4 ? 'four': 'five'" v-if="item.type == 1">
+                <image mode="aspectFill" :src="goods.cover_pic" v-for="goods in item.goods_list" :key="goods.id"></image>
+            </view>
+            <view class="app-composition-goods" :class="item.goods_list.length == 1 ? 'two' : item.goods_list.length == 2 ? 'three' : item.goods_list.length == 3 ? 'four': 'five'" v-if="item.type == 2">
+                <image mode="aspectFill" :src="goods.cover_pic" v-for="goods in item.host_list" :key="goods.id"></image>
+                <image mode="aspectFill" :src="goods.cover_pic" v-for="goods in item.goods_list" :key="goods.id"></image>
+            </view>
+            <slot></slot>
+        </view>
+        <view @click.stop="toDetail" class="app-composition-right">
+            <view class="dir-left-nowrap cross-center app-composition-info">
+                <view class="app-composition-type" v-if="item.type == 1 && !large" :style="{'color':theme.color}">固定</view>
+                <view class="app-composition-type" v-if="item.type == 2 && !large" :style="{'color':theme.color}">搭配</view>
+                <view class="app-composition-name t-omit" :class="large ? 'long-name' :  ''">{{item.name}}</view>
+            </view>
+            <view class="app-composition-type" v-if="item.type == 1 && large" :style="{'color':theme.color}">固定套餐</view>
+            <view class="app-composition-type" v-if="item.type == 2 && large" :style="{'color':theme.color}">搭配套餐</view>
+            <view class="app-composition-price">
+                套餐价<text :style="{'color':theme.color}">¥{{item.min_composition_price}}</text>
+            </view>
+            <view class="app-composition-discount">
+                最多可省<text>¥{{item.max_discount}}</text>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+
+	import {mapState} from 'vuex';
+	
+    export default {
+        name: "app-composition",
+	    props: {
+            item: {
+                type: Object
+            },
+            large: Boolean,
+            theme: Object
+	    },
+        data() {
+            return {
+            }
+        },
+        created() {
+            let that = this;
+        },
+        methods: {
+            open(e) {
+                this.$emit('click', e);
+            },
+            toDetail(e) {
+                this.$emit('look', e);
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-composition {
+        .app-composition-goods {
+            width: #{288rpx};
+            height: #{140rpx};
+            flex-shrink: 0;
+            margin-right: #{24rpx};
+            position: relative;
+            &.two {
+                image {
+                    position: absolute;
+                    width: #{140rpx};
+                    height: #{140rpx};
+                    border-radius: #{8rpx};
+                    top: 0;
+                }
+                image:first-of-type {
+                    left: 0;
+                }
+                image:last-of-type {
+                    right: 0;
+                }
+            }
+            &.three {
+                image {
+                    position: absolute;
+                    width: #{140rpx};
+                    height: #{66rpx};
+                    border-radius: #{8rpx};
+                }
+                image:first-of-type {
+                    left: 0;
+                    top: 0;
+                    height: #{140rpx};
+                    width: #{140rpx};
+                }
+                image:nth-child(2) {
+                    top: 0;
+                    right: 0;
+                }
+                image:last-of-type {
+                    bottom: 0;
+                    right: 0;
+                }
+            }
+            &.four {
+                image {
+                    position: absolute;
+                    width: #{66rpx};
+                    height: #{66rpx};
+                    border-radius: #{8rpx};
+                }
+                image:first-of-type {
+                    left: 0;
+                    top: 0;
+                    height: #{140rpx};
+                    width: #{140rpx};
+                }
+                image:nth-child(2) {
+                    top: 0;
+                    right: 0;
+                    width: #{140rpx};
+                }
+                image:nth-child(3) {
+                    right: #{74rpx};
+                    bottom: 0;
+                }
+                image:last-of-type {
+                    right: 0;
+                    bottom: 0;
+                }
+            }
+            &.five {
+                image {
+                    position: absolute;
+                    width: #{66rpx};
+                    height: #{66rpx};
+                    border-radius: #{8rpx};
+                }
+                image:first-of-type {
+                    left: 0;
+                    top: 0;
+                    height: #{140rpx};
+                    width: #{140rpx};
+                }
+                image:nth-child(2) {
+                    top: 0;
+                    right: #{74rpx};
+                }
+                image:nth-child(3) {
+                    right: 0;
+                    top: 0;
+                }
+                image:nth-child(4) {
+                    right: #{74rpx};
+                    bottom: 0;
+                }
+                image:last-of-type {
+                    right: 0;
+                    bottom: 0;
+                }
+            }
+        }
+        .app-composition-right {
+            width: #{342rpx};
+        }
+        .app-composition-info {
+            height: #{44rpx};
+            width: #{342rpx};
+            .app-composition-type {
+                font-size: #{22rpx};
+                display: block;
+                margin: 0 #{16rpx} 0 0;
+            }
+            .app-composition-name {
+                height: #{44rpx};
+                line-height: #{44rpx};
+                width: #{260rpx};
+                font-size: #{28rpx};
+                color: #353535;
+                &.long-name {
+                    width: #{342rpx};
+                }
+            }
+        }
+        .app-composition-type {
+            padding: #{2rpx 12rpx};
+            flex-shrink: 0;
+            border: #{2rpx} solid;
+            border-radius: #{20rpx};
+            margin: #{14rpx} 0 0;
+            font-size: #{22rpx};
+            display: inline-block;
+        }
+        .app-composition-price {
+            color: #999999;
+            font-size: #{24rpx};
+            margin: #{14rpx} 0 #{10rpx};
+            text {
+                font-size: #{28rpx};
+            }
+        }
+        .app-composition-discount {
+            color: #999999;
+            font-size: #{24rpx};
+
+        }
+    }
+</style>

+ 115 - 0
components/basic-component/app-css-icon/app-css-icon.vue

xqd
@@ -0,0 +1,115 @@
+<template>
+    <view class="app-css-icon"
+          :class="[icon,round?'round':'',padding?'padding':'']"
+          :style="{
+          borderColor: color,
+          fontSize: iSize,
+          transform: transform,
+          backgroundColor: background,
+          }"></view>
+</template>
+
+<script>
+    export default {
+        name: "app-css-icon",
+        props: {
+            icon: {
+                default: 'point',
+                type: String,
+            },
+            size: {
+                default: 50,
+            },
+            color: {
+                default: '#333',
+                type: String,
+            },
+            transform: {
+                default: '',
+            },
+            background: {
+                default: 'transparent',
+                type: String,
+            },
+            round: {
+                default: false,
+                type: Boolean,
+            },
+            padding: {
+                default: false,
+                type: Boolean,
+            },
+        },
+        computed: {
+            iSize() {
+                if (isNaN(this.size)) {
+                    return `${this.size}`;
+                } else {
+                    return `${this.size}rpx`;
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-css-icon {
+        display: inline-block;
+        height: 1em;
+        width: 1em;
+        box-sizing: border-box;
+        text-indent: -#{100em};
+        vertical-align: middle;
+        position: relative;
+        overflow: hidden;
+        transform: translate(0, -10%);
+    }
+
+    .app-css-icon.round {
+        border-radius: 10em;
+    }
+
+    .app-css-icon::before,
+    .app-css-icon::after {
+        content: '';
+        box-sizing: inherit;
+        position: absolute;
+        left: 50%;
+        top: 50%;
+        -ms-transform: translate(-50%, -50%);
+        transform: translate(-50%, -50%);
+        border-color: inherit;
+    }
+
+    .check::before {
+        border-style: solid;
+        border-width: 0 0 .15em .15em;
+        height: .5em;
+        width: .9em;
+        transform: translate(-50%, -.375em) rotate(-45deg);
+    }
+
+    .padding.check::before {
+        border-width: 0 0 .1em .1em;
+        height: .333em;
+        width: .6em;
+        transform: translate(-50%, -.25em) rotate(-45deg);
+    }
+
+
+    .arrow-right::before {
+        height: .65em;
+        width: .65em;
+        border-style: solid;
+        border-width: .15em 0 0 .15em;
+        transform: translate(-75%, -50%) rotate(135deg);
+    }
+
+    .padding.arrow-right::before {
+        height: .4333em;
+        width: .4333em;
+        border-style: solid;
+        border-width: .1em 0 0 .1em;
+        transform: translate(-75%, -50%) rotate(135deg);
+    }
+</style>

+ 167 - 0
components/basic-component/app-datetime-picker/app-datetime-picker.vue

xqd
@@ -0,0 +1,167 @@
+<template>
+    <view class="app-datetime-picker" @click="handleClick">
+        <!-- #ifndef MP-ALIPAY -->
+        <picker :mode="mode"
+                :value="timeValue"
+                :start="start"
+                :end="end"
+                :fields="fields"
+                @change="handleChange"
+                @cancel="handleCancel">
+            <view class="dir-left-nowrap cross-center" :style="{
+                'background': `${background}`,
+                'border': showBorder? `1rpx solid ${borderColor}` : 'none',
+                'height': `${height}rpx`,
+                'border-radius': `${radius}rpx`,
+                'color': `${textColor}`,
+            }">
+                <view class="box-grow-1" :class="[`text-align-${textPosition}`]">
+                    <!-- #ifdef MP-TOUTIAO -->
+                    <slot></slot>
+                    <!-- #endif -->
+                    <!-- #ifndef MP-TOUTIAO -->
+                    {{text}}
+                    <!-- #endif -->
+                </view>
+                <view class="box-grow-0" v-if="showArrow" :style="{'padding': padding}">
+                    <image class="arrow" src="/static/image/icon/arrow-right.png"
+                           style="width: 12rpx;height: 22rpx;"></image>
+                </view>
+            </view>
+        </picker>
+        <!-- #endif -->
+        <!-- #ifdef MP-ALIPAY -->
+        <view class="dir-left-nowrap cross-center" :style="{
+            'background': `${background}`,
+            'border': showBorder? `1rpx solid ${borderColor}` : 'none',
+            'height': `${height}rpx`,
+            'border-radius': `${radius}rpx`,
+            'color': `${textColor}`,
+        }">
+            <view class="box-grow-1" :class="[`text-align-${textPosition}`]">
+                <!-- #ifdef MP-TOUTIAO -->
+                <slot></slot>
+                <!-- #endif -->
+                <!-- #ifndef MP-TOUTIAO -->
+                {{text}}
+                <!-- #endif -->
+            </view>
+            <view class="box-grow-0" v-if="showArrow" :style="{'padding': padding}">
+                <image class="arrow" src="/static/image/icon/arrow-right.png"
+                       style="width: 12rpx;height: 22rpx;"></image>
+            </view>
+        </view>
+        <!-- #endif -->
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-datetime-picker',
+        props: {
+            padding: {
+                type: String,
+                default: '0 24rpx'
+            },
+            value: {
+                type: String,
+                default: '0',
+            },
+            text: null,
+            mode: {
+                type: String,
+                default: 'date',
+            },
+            start: {
+                type: String,
+                default: '',
+            },
+            end: {
+                type: String,
+                default: '',
+            },
+            fields: {
+                type: String,
+                default: 'day',
+            },
+            disabled: false,
+            showArrow: {
+                type: Boolean,
+                default: true,
+            },
+            sign: {
+                default: null,
+            },
+            background: {
+                default: 'transparent',
+            },
+            showBorder: {
+                default: false,
+            },
+            borderColor: {
+                default: 'transparent',
+            },
+            height: {
+                default: 80,
+            },
+            radius: {
+                default: 0,
+            },
+            textColor: {
+                default: '#666666',
+            },
+            textPosition: {
+                default: 'right',
+            },
+            defaultValue: {
+                type: String,
+                default: '',
+            }
+        },
+        data() {
+            return {
+                timeValue: 0
+            }
+        },
+        created() {
+            this.timeValue = this.value != 0 ? this.value : this.defaultValue
+        },
+        methods: {
+            handleChange(e) {
+                this.timeValue = e.detail.value
+                this.$emit('input', e.detail.value, this.sign);
+                this.$emit('change', e, this.sign);
+            },
+            handleCancel(e) {
+                this.$emit('cancel', e.detail.value, this.sign);
+            },
+            handleClick(e) {
+                // #ifdef MP-ALIPAY
+                my.datePicker({
+                    format: this.mode === 'date' ? 'yyyy-MM-dd' : 'HH:mm',
+                    currentDate: this.timeValue,
+                    startDate: this.start,
+                    endDate: this.end,
+                    success: (e) => {
+                        this.handleChange({
+                            detail: {
+                                value: e.date,
+                            },
+                        });
+                    },
+                    fail: this.handleCancel,
+                });
+                // #endif
+            },
+        },
+    }
+</script>
+<style lang="scss" scoped>
+    .text-align-left {
+        text-align: left;
+    }
+
+    .text-align-right {
+        text-align: right;
+    }
+</style>

+ 42 - 0
components/basic-component/app-empty-bottom/app-empty-bottom.vue

xqd
@@ -0,0 +1,42 @@
+<template>
+	<view class="empty-bottom" :style="{backgroundColor: backgroundColor,height: `${botBool ?  BotHeight + height : height + emptyHeight}rpx`,}"></view>
+</template>
+
+<script>
+	import {mapGetters} from 'vuex';
+    export default {
+        name: "app-empty-bottom",
+	    props: {
+            backgroundColor: {
+                type: String,
+	            default() {
+                    return 'white';
+	            }
+            },
+            height: {
+                type: Number,
+	            default() {
+                    return 0;
+                }
+            },
+		    botBool: {
+                type: Boolean,
+			    default() {
+                    return false;
+			    }
+		    }
+	    },
+	    computed: {
+            ...mapGetters('iPhoneX', {
+                BotHeight: 'getBotHeight',
+	            emptyHeight: 'getEmpty',
+            })
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.empty-bottom {
+		width: 100%;
+	}
+</style>

+ 30 - 0
components/basic-component/app-empty/app-empty.vue

xqd
@@ -0,0 +1,30 @@
+<template>
+    <view class="app-empty" :style="{height: height + 'rpx',backgroundColor: backgroundColor}"></view>
+</template>
+
+<script>
+    export default {
+        name: "app-empty",
+        props: {
+            height: {
+                type: Number,
+                default() {
+                    return 10;
+                }
+            },
+            backgroundColor: {
+                type: String,
+                default() {
+                    return `#ffffff`;
+                }
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-empty {
+        width: 100%;
+        display: block;
+    }
+</style>

+ 67 - 0
components/basic-component/app-form-id/app-form-id.vue

xqd
@@ -0,0 +1,67 @@
+<template>
+    <view class="app-form-id">
+        <form report-submit @submit="formSubmit">
+            <button formType="submit" :style="{'color': color ? color : ''}" hover-class="none">
+                <slot></slot>
+            </button>
+        </form>
+    </view>
+</template>
+
+<script>
+    import { push } from '../../../core/formId.js';
+
+    export default {
+        name: 'app-form-id',
+        props: {
+            color: String,
+            item: Object,
+        },
+        methods: {
+            formSubmit(e) {
+                push(e.detail.formId);
+                this.$emit('click', e, this.item);
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    form {
+        display: block;
+        height: 100%;
+        width: 100%;
+    }
+    button {
+        height: 100%;
+        width: 100%;
+    }
+    button {
+        display: block;
+        line-height: inherit;
+        text-align: inherit;
+        padding: 0 0;
+        background: transparent;
+        border: none;
+        border-radius: 0;
+        overflow: inherit;
+        font-size: inherit;
+        color: inherit;
+    }
+    button:after {
+        display: none;
+    }
+    button.button-hover {
+        color: inherit;
+        background-color: transparent;
+    }
+
+    .app-form-id {
+        /* #ifndef MP-ALIPAY */
+        height: 100%;
+        /* #endif */
+        /* #ifdef MP-ALIPAY */
+        max-height: 100%;
+        /* #endif */
+    }
+</style>

+ 43 - 0
components/basic-component/app-hotspot/app-hotspot.vue

xqd
@@ -0,0 +1,43 @@
+<template>
+    <view class="app-hotspot" :style="aa">
+        <app-jump-button :open_type="hotspot.link.openType" :url="hotspot.link.url" :params="hotspot.link.params">
+            <slot></slot>
+        </app-jump-button>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: "app-hotspot",
+        props: {
+            hotspot: {
+                type: Object,
+                default() {
+                    return {};
+                }
+            }
+        },
+        data() {
+            return {}
+        },
+        onLoad() {
+        },
+        computed: {
+            aa() {
+                if (this.hotspot) {
+                    return `left:${this.hotspot.left}rpx;top:${this.hotspot.top}rpx;width:${this.hotspot.width}rpx;height:${this.hotspot.height}rpx;`;
+                } else {
+                    return ``;
+                }
+            }
+        }
+    };
+</script>
+
+<style lang="scss" scoped>
+    .app-hotspot {
+        position: absolute;
+        z-index: 1000;
+    }
+</style>

+ 69 - 0
components/basic-component/app-image/app-image.vue

xqd
@@ -0,0 +1,69 @@
+<template>
+    <view class="app-image" :style="appBackground" v-if="imgSrc">
+        <image src="/static/image/icon/loading-img.png" class="img" :class="is_error ? '' : 'default'"
+               mode="aspectFill" lazy-load v-if="is_loading" :style="imgStyle"></image>
+        <image :src="imgSrc" class="img" :mode="mode" @error="imgError" @load="imgLoad" lazy-load
+               v-if="!is_error" :style="imgStyle"></image>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-image",
+        props: {
+            imgSrc: String,
+            width: String,
+            height: String,
+            mode: {
+                type: String,
+                default() {
+                    return 'aspectFill';
+                }
+            },
+            borderRadius: String,
+        },
+        data() {
+            return {
+                is_loading: true,
+                is_error: false
+            };
+        },
+        computed: {
+            appBackground() {
+                return `width: ${this.width};height: ${this.height};`;
+            },
+            imgStyle() {
+                return `border-radius: ${this.borderRadius}`;
+            },
+        },
+
+        methods: {
+            imgError() {
+                this.is_error = true;
+            },
+            imgLoad() {
+                this.is_loading = false;
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-image {
+        display: block;
+        position: relative;
+        .img {
+            width: 100%;
+            height: 100%;
+            display: block;
+            will-change: transform;
+
+            &.default {
+                position: absolute;
+                left: 0;
+                top: 0;
+                z-index: 0;
+            }
+        }
+    }
+</style>

+ 230 - 0
components/basic-component/app-input/app-input.vue

xqd
@@ -0,0 +1,230 @@
+<template>
+    <view class="app-view"
+          :style="{'height': `${setHeight}rpx`, 'width': `${width}rpx`}"
+    >
+        <template v-if="type !== 'textarea'">
+            <!--  #ifndef  MP-TOUTIAO -->
+            <input :disabled="disabled"
+                   :value="value ? value : defaultValue"
+                   :class="{'is-disabled': disabled, 'is-round': round, 'is-border': border}"
+                   class="app-input"
+                   :type="type"
+                   :password="password"
+                   :placeholder-style="placeholderStyle"
+                   :placeholder="placeholder"
+                   :focus="focus"
+                   :style="{
+                   'padding-left': !center && icon ? '30rpx' : `${paddingLeft}rpx`,
+                   'text-align': center ? 'center' : 'none',
+                   'height': `${setHeight}rpx`,
+                   'width': `${width}rpx`,
+                   'color': `${color}`,
+                   backgroundColor: backgroundColor,
+                   borderRadius: `${radius}rpx`,
+                   borderColor: borderColor,
+                   }"
+                   @input="changeValue"
+                   @blur="blur"
+                   @confirm="confirm"
+            />
+            <!--  #endif -->
+            <!-- MP-TOUTIAO focus属性有问题,去除。2020-02-24 -->
+            <!--  #ifdef  MP-TOUTIAO -->
+            <input :disabled="disabled"
+                   :value="value || value === 0 ? value : defaultValue"
+                   :class="{'is-disabled': disabled, 'is-round': round, 'is-border': border}"
+                   class="app-input"
+                   :type="type"
+                   :password="password"
+                   :placeholder-style="placeholderStyle"
+                   :placeholder="placeholder"
+                   :style="{
+                   'padding-left': !center && icon ? '30rpx' : `${paddingLeft}rpx`,
+                   'text-align': center ? 'center' : 'none',
+                   'height': `${setHeight}rpx`,
+                   'width': `${width}rpx`,
+                   'color': `${color}`,
+                   backgroundColor: backgroundColor,
+                   borderRadius: `${radius}rpx`,
+                   borderColor: borderColor,
+                   }"
+                   @input="changeValue"
+                   @blur="blur"
+                   @confirm="confirm"
+            />
+            <!--  #endif -->
+            <icon v-if="icon" class="app-icon"
+                  :style="{'background-image': `url(${icon})`, 'left': center ? '42%': '1%'}" type></icon>
+        </template>
+        <template v-else>
+            <textarea :value="value ? value : defaultValue"
+                      :maxlength="maxLength ? maxLength : -1"
+                      @blur="blur"
+                      @confirm="confirm"
+                      :placeholder-style="placeholderStyle"
+                      :show-confirm-bar="showConfirmBar"
+                      :placeholder="placeholder"
+                      :auto-height="autoHeight"
+                      :disabled="disabled"
+                      @input="changeValue"
+                      class="app-input-textarea"
+                      :class="{'is-disabled': disabled, 'is-round': round, 'is-border': border}"
+                      :style="{
+                            'color': `${color}`,
+                            backgroundColor: backgroundColor,
+                            borderRadius: `${radius}rpx`,
+                            padding: `24rpx ${paddingLeft}rpx`,
+                            borderColor: borderColor,
+                      }"
+            >
+            </textarea>
+        </template>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: 'app-input',
+        props: {
+            type: String,
+            password: Boolean,
+            disabled: Boolean,
+            placeholder: {
+                default: '',
+                type: String,
+            },
+            autoHeight: Boolean,
+            showConfirmBar: Boolean,
+            placeholderStyle: {
+                type: String,
+                default: function () {
+                    return 'color: #999999';
+                }
+            },
+            maxLength: String,
+            value: {
+                default: '',
+            },
+            round: Boolean,
+            border: {
+                type: [Number, Boolean]
+            },
+            borderColor: {
+                default: '#c0c4cc',
+            },
+            icon: String,
+            center: Boolean,
+            size: String,
+            width: String,
+            color: {
+                default: '#353535',
+                type: String,
+            },
+            height: String,
+            backgroundColor: String,
+            radius: Number,
+            focus: {
+                type: Boolean,
+                default: false,
+            },
+            paddingLeft: {
+                default: 12,
+            },
+            defaultValue: {
+                default: '',
+            },
+        },
+        data() {
+            return {
+                newValue: this.value
+            }
+        },
+        methods: {
+            blur(event) {
+                this.$emit('blur', event.detail);
+            },
+            confirm(event) {
+                this.$emit('confirm', event.detail);
+            },
+            changeValue(value) {
+                this.$emit('input', value.detail.value);
+            }
+        },
+        computed: {
+            setHeight: function () {
+                if (this.height) return this.height;
+                switch (this.size) {
+                    case 'large':
+                        return 100;
+                    case 'small':
+                        return 60;
+                    case 'medium':
+                        return 80;
+                    default:
+                        return 80;
+                }
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+
+    .app-input {
+        border-radius: #{10rpx};
+        box-sizing: border-box;
+        outline: none;
+        height: #{80rpx};
+        width: 100%;
+        line-height: #{80rpx};
+        color: $uni-general-color-three;
+    }
+
+    .is-border {
+        border: #{2rpx} solid #dcdfe6;
+    }
+
+    .app-input:active {
+        border-color: #c0c4cc
+    }
+
+    .app-input.is-disabled {
+        background-color: #f5f7fa;
+        border-color: #e4e7ed;
+        color: #c0c4cc;
+        cursor: not-allowed;
+    }
+
+    .is-round {
+        border-radius: #{50rpx};
+    }
+
+    .app-view {
+        position: relative;
+    }
+
+    .app-icon {
+        width: #{25rpx};
+        height: #{25rpx};
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        background-repeat: no-repeat;
+        background-size: 100% 100%;
+    }
+
+    .app-input-textarea {
+        max-height: 100%;
+        width: 100%;
+        word-wrap: break-word;
+    }
+
+    /* #ifdef MP-ALIPAY */
+    .app-input,
+    .app-input-textarea {
+        overflow: hidden;
+    }
+
+    /* #endif */
+</style>

+ 40 - 0
components/basic-component/app-iphone-x/app-iphone-x.vue

xqd
@@ -0,0 +1,40 @@
+<template>
+	<view class="iphone-x">
+		<slot name="empty-area"></slot>
+		<view class="x-line" v-if="getBoolEmpty.XBoolean" :style="{height: getBoolEmpty.emptyHeight + 'rpx', backgroundColor: backgroundColor,}"></view>
+	</view>
+</template>
+
+<script>
+    import { mapGetters } from 'vuex';
+
+    export default {
+        props: {
+            backgroundColor: {
+                type: String,
+                default: function() {
+                    return 'white';
+                }
+            }
+        },
+        computed: {
+            ...mapGetters('iPhoneX', {
+                getBoolEmpty: 'getBoolEmpty'
+            }),
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+	.iphone-x {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		z-index: 1500;
+	}
+	.x-line {
+		width: 100%;
+		background-color: pink;
+	}
+</style>

+ 446 - 0
components/basic-component/app-jump-button/app-jump-button.vue

xqd
@@ -0,0 +1,446 @@
+<template>
+    <form v-if="form" report-submit @submit="jumpLogic">
+        <!-- #ifndef MP-ALIPAY  -->
+        <button
+                :open-type="open_type === 'contact' ? 'contact': open_type === 'share' ? 'share' : ''"
+                hover-class="none"
+                :style="{'background-color': `${backgroundColor ? backgroundColor : '' }`, 'height': `${height}%`, 'width': `${width}%`}"
+                :class="[`${arrangement === 'column' ? 'app-button-column' : arrangement === 'row' ? 'app-button-row' : arrangement === 'left' ? 'app-button-left': arrangement === 'topCenter' ? 'app-button-top-cross-center' : arrangement === 'a' ? 'app-left' : arrangement === 'b' ? 'app-left-between' : ''}`]"
+                formType="submit"
+        >
+            <slot></slot>
+        </button>
+        <!-- #endif -->
+        <!-- #ifdef MP-ALIPAY -->
+        <template >
+            <button
+                    :open-type="open_type === 'share' ? 'share' : ''" hover-class="none"
+                    :style="{'background-color': `${backgroundColor ? backgroundColor : '' }`, 'height': `${height}%`, 'width': `${width}%`}"
+                    :class="[`${arrangement === 'column' ? 'app-button-column' : arrangement === 'row' ? 'app-button-row' : arrangement === 'left' ? 'app-button-left': arrangement === 'topCenter' ? 'app-button-top-cross-center' : arrangement === 'a' ? 'app-left' : arrangement === 'b' ? 'app-left-between' : ''}`]"
+                    formType="submit"
+            >
+                <slot></slot>
+            </button>
+        </template>
+        <!-- #endif -->
+    </form>
+    <button
+            v-else
+            :open-type="open_type === 'contact' ? 'contact': ''" hover-class="none"
+            :style="{'background-color': `${backgroundColor ? backgroundColor : '' }`, 'height': `${height}%`, 'width': `${width}%`}"
+            :class="[`${arrangement === 'column' ? 'app-button-column' : arrangement === 'row' ? 'app-button-row' :  arrangement === 'left' ? 'app-button-left':  arrangement === 'topCenter' ? 'app-button-top-cross-center' : arrangement === 'a' ? 'app-left' : arrangement === 'b' ? 'app-left-between' : ''}`]"
+            @click="jumpLogic"
+    >
+        <slot></slot>
+    </button>
+</template>
+
+<script>
+    import {push} from '../../../core/formId.js';
+
+    export default {
+        name: 'app-jump-button',
+        props: {
+            item: Object,
+            arrangement: {
+                type: String,
+                default: function () {
+                    return 'row';
+                },
+                required: false
+            },
+            backgroundColor: {
+                type: String,
+                required: false
+            },
+            form: {
+                type: Boolean,
+                default: function () {
+                    return true;
+                },
+                required: false
+            },
+            height: {
+                type: String,
+                default: function () {
+                    return '100';
+                },
+                required: false
+            },
+            width: {
+                type: String,
+                default: function () {
+                    return '100';
+                },
+                required: false
+            },
+            open_type: {
+                type: String,
+                default: function () {
+                    return 'navigate';
+                },
+                required: false
+            },
+            url: {
+                type: String,
+                default: function () {
+                    return '';
+                },
+                required: false
+            },
+            params: {
+                type: [Array, String],
+                default: function () {
+                    return [];
+                },
+                required: false
+            },
+            number: {
+                type: String,
+                default: function () {
+                    return '';
+                },
+                required: false
+            },
+            appId: {
+                type: String,
+                default: function () {
+                    return '';
+                },
+                required: false
+            },
+            path: {
+                type: String,
+                default: function () {
+                    return '';
+                },
+                required: false
+            },
+            latitude: {
+                type: String,
+                default: function () {
+                    return '0';
+                },
+                required: false
+            },
+            longitude: {
+                type: String,
+                default: function () {
+                    return '0';
+                },
+                required: false
+            },
+            address: {
+                type: String,
+                default: function () {
+                    return '';
+                },
+                required: false
+            }
+        },
+        methods: {
+            jumpLogic(e) {
+                if (this.form) push(e.detail.formId);
+                switch (this.open_type) {
+	                case 'reLaunch':
+	                    uni.reLaunch({
+		                    url: this.url,
+	                    });
+	                    break;
+                    case 'redirect':
+                        uni.redirectTo({
+                            url: this.url
+                        });
+                        break;
+                    case 'navigate':
+                        if (this.url) {
+                            let url = this.url;
+                            if (this.params != '""' && this.params && this.params.length > 0) {
+                                let p = `?`;
+                                for (let i = 0; i < this.params.length; i++) {
+                                    p += `${this.params[i].key}=${this.params[i].value}&`
+                                }
+                                url = url.split('?')[0];
+                                url += p.slice(0, p.length - 1);
+                            }
+                            /* #ifdef MP-BAIDU */
+                            if (this.url !== '/plugins/step/index/index') {
+                                uni.navigateTo({
+                                    url: url
+                                });
+                            }
+                            /* #endif */
+                            /* #ifdef MP-TOUTIAO */
+                            if (this.url !== '/plugins/step/index/index' && this.url.indexOf('/plugins/community') === -1) {
+                                uni.navigateTo({
+                                    url: url
+                                });
+                            }
+                            /* #endif */
+                            /* #ifdef MP-WEIXIN || MP-ALIPAY || H5 */
+                            uni.navigateTo({
+                                url: url
+                            });
+                            /* #endif */
+                        }
+
+                        break;
+                    case 'app_admin':
+                        if (this.$store.state.user.info.identity.is_admin == 1) {
+                            uni.navigateTo({
+                                url: this.url,
+                            });
+                        }
+                        break;
+                    case 'back':
+                        uni.navigateBack({});
+                        break;
+                    case 'tel':
+                        if (this.params.length === 1) {
+                            uni.makePhoneCall({
+                                phoneNumber: this.params[0].value
+                            });
+                        } else if (this.number) {
+                            uni.makePhoneCall({
+                                phoneNumber: this.number
+                            });
+                        } else {
+                            uni.makePhoneCall({
+                                phoneNumber: this.url.split('?')[1].split('=')[1],
+                            });
+                        }
+                        break;
+                    case 'web':
+                        if (this.params.length > 0) {
+                            uni.navigateTo({
+                                url: `${this.url}?url=${encodeURIComponent(this.params[0].value)}`
+                            });
+                        } else {
+                            uni.navigateTo({
+                                url: this.url
+                            });
+                        }
+                        break;
+                    // #ifdef MP
+                    case 'app':
+                        if (this.url) {
+                            let originalPath = '';
+                            if (this.url !== 'app') {
+                                originalPath = this.url.split('app?')[1];
+                            } else {
+                                for (let i = 0; i < this.params.length; i++) {
+                                    originalPath += `${this.params[i].key}=${this.params[i].value}&`;
+                                }
+                            }
+                            if (typeof originalPath !== 'string') return;
+                            let groups = originalPath.split('&');
+                            let obj = {};
+                            for (let i in groups) {
+                                if (typeof groups[i] !== 'string') continue;
+                                if (!groups[i].length) continue;
+                                let kvs = groups[i].split('=');
+                                if (kvs.length !== 2) {
+                                    let s = '';
+                                    for (let i = 1; i < kvs.length; i++) {
+                                        if (i+1 === kvs.length) {
+                                            continue;
+                                        } else {
+                                            s += `${kvs[i]}=${kvs[i+1]}`
+                                        }
+                                    }
+                                    obj[kvs[0]] = s;
+                                } else {
+                                    obj[kvs[0]] = kvs[1];
+                                }
+                            }
+                            let params = obj;
+                            let appId = '';
+                            let path = '';
+                            // #ifdef MP-WEIXIN
+                            appId = params.app_id || '';
+                            path = params.path || '';
+                            // #endif
+                            // #ifdef MP-ALIPAY
+                            appId = params.ali_app_id || '';
+                            path = params.ali_path || '';
+                            // #endif
+                            // #ifdef MP-TOUTIAO
+                            appId = params.tt_app_id || '';
+                            path = params.tt_path || '';
+                            // #endif
+                            // #ifdef MP-BAIDU
+                            appId = params.bd_app_key || '';
+                            path = params.bd_path || '';
+                            // #endif
+                            uni.navigateToMiniProgram({
+                                appId: appId,
+                                path: path
+                            });
+                        } else if (this.appId) {
+                            uni.navigateToMiniProgram({
+                                appId: this.appId,
+                                path: this.path ? this.path : ''
+                            });
+                        }
+                        break;
+                    // #endif
+                    case 'clear_cache':
+                        uni.showModal({
+                            content: '确定要清理缓存?',
+                            cancelText: '取消',
+                            confirmText: '确认',
+                            success: (e) => {
+                                if (e.confirm) {
+                                    this.$storage.clearStorage();
+                                    // #ifdef H5
+                                    this.$storage.setStorageSync('platform', 'wechat');
+                                    this.$storage.setStorageSync('isSign', true);
+                                    // #endif
+                                    if (this.$user && this.$store && this.$store.state.user.accessToken) {
+                                        this.$user.loginByToken(this.$store.state.user.accessToken);
+                                    }
+                                    this.$store.dispatch('mallConfig/actionResetConfig');
+                                    // uni.hideLoading();
+                                    uni.showToast({
+                                        title: '清理完成',
+                                        duration: 1000,
+	                                    icon: 'none',
+                                    });
+                                }
+                            },
+                        });
+                        break;
+                    case 'map':
+                        uni.openLocation({
+                            latitude: Number(this.latitude),
+                            longitude: Number(this.longitude),
+                            name: this.address,
+                            address: this.address
+                        });
+                        break;
+                    case 'share':
+                    // uni.share({
+                    //     provider: 'weixin',
+                    //     type: 5,
+                    //     imageUrl: 'https://img-cdn-qiniu.dcloud.net.cn/uniapp/app/share-logo@3.png',
+                    //     title: '欢迎体验uniapp',
+                    //     miniProgram: {
+                    //         id: 'gh_abcdefg',
+                    //         path: 'pages/index/index',
+                    //         type: 0,
+                    //         webUrl: 'http://uniapp.dcloud.io'
+                    //     },
+                    //     success: ret => {
+                    //     }
+                    // });
+                }
+            },
+            getUrlParam(url,name) {
+                let search = url.split('?')[1];
+                if (search) {
+                    let r = search.substr(0).match(new RegExp('(^|&)' + name + '=([^&]*)(&|$)'))
+                    if (r !== null) {
+                        return unescape(r[2])
+                    }
+                    return null
+                } else {
+                    return null
+                }
+            }
+        },
+        // #ifdef H5
+        computed: {
+            isWechat: function() {
+                return this.$jwx.isWechat();
+            }
+        }
+        // #endif
+    }
+</script>
+
+<style scoped lang="scss">
+    button {
+        display: block;
+        line-height: inherit;
+        text-align: inherit;
+        padding: 0 0;
+        background: transparent;
+        border: none;
+        border-radius: 0;
+        overflow: inherit;
+        font-size: inherit;
+        color: inherit;
+    }
+
+    button:after {
+        display: none;
+    }
+
+    button.button-hover {
+        color: inherit;
+        background-color: transparent;
+    }
+
+    form {
+        display: block;
+        height: 100%;
+        width: 100%;
+    }
+
+    .app-button-column {
+        height: 100%;
+        width: 100%;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .app-button-row {
+        height: 100%;
+        width: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .app-button-left {
+        height: 100%;
+        width: 100%;
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+    }
+
+    .app-button-top-cross-center {
+        display: -webkit-box;
+        display: -webkit-flex;
+        display: flex;
+        -webkit-box-orient: vertical;
+        -webkit-flex-direction: column;
+        flex-direction: column;
+        flex-wrap: nowrap;
+        -webkit-align-items: center;
+        align-items: center;
+    }
+
+    .app-left {
+        height: 100%;
+        width: 100%;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+    }
+
+    .app-left-between {
+        display: -webkit-box;
+        display: -webkit-flex;
+        display: flex;
+        -webkit-flex-direction: row;
+        flex-direction: row;
+        flex-wrap: nowrap;
+        justify-content: space-between;
+        align-items: center;
+    }
+</style>

+ 259 - 0
components/basic-component/app-layout/app-coupon-modal/app-coupon-modal.vue

xqd
@@ -0,0 +1,259 @@
+<!-- 初版 代和yu分支融合 -->
+<template>
+    <view class="app-coupon-modal main-center cross-center" v-if="coupon && coupon.list && coupon.list.length > 0">
+        <view class="coupon-modal">
+            <image class="title-img" :src="img"></image>
+            <view class="get-coupon-content">
+                <view v-if="coupon.type === `receive` && coupon.list[0].rest >= 0"
+                      class="rest dir-left-nowrap main-center cross-center">
+                    <view>还剩</view>
+                    <view class="rest-count">{{coupon.list[0].rest}}</view>
+                    <view>次领取次数</view>
+                </view>
+                <view class="invite coupon-head-label" v-if="coupon.type === `invite`">
+                    <view>成功邀请{{coupon.list[0].id}}位好友,获得奖励</view>
+                </view>
+                <view v-else class="coupon-head-label">*{{labelText}}</view>
+                <scroll-view scroll-y="true" class='coupon-list'>
+                    <view class="dir-left-nowrap cross-center coupon-item" v-for="(item, index) in coupon.list"
+                          :key="index">
+                        <view class="price box-grow-0" >
+                            <image v-if="item.share_type === 1" src='/static/image/hongbao.png'/>
+                            <image v-if="item.share_type === 2" src='/static/image/integral.png'/>
+                            <image v-if="item.share_type === 3" :src="item.pic_url" class="card"/>
+                            <block v-if="item.share_type === 4">
+                                <template v-if="item.type == 2">
+                                    <app-price :price="item.sub_price"></app-price>
+                                </template>
+                                <template v-else>
+                                    <view class="discount">{{item.discount}}</view>
+                                </template>
+                            </block>
+                        </view>
+                        <view class="right dir-top-nowrap main-center box-grow-1">
+                            <view v-if="[1,2,3].includes(item.share_type)"
+                                  :class="[`t-omit${item.share_type === 3 ? '-two': ''}`]">{{item.name}}
+                            </view>
+                            <view v-else class="t-omit">{{item.name}}</view>
+
+                            <view class="t-omit content">{{item.content}}</view>
+                            <view class="content" v-if="item.discount_limit">优惠上限:¥{{item.discount_limit}}</view>
+                        </view>
+                        <view class="box-grow-0 btn"  @click="toGoods(item.page_url)">
+                            去使用
+                        </view>
+                    </view>
+                </scroll-view>
+            </view>
+            <view class='main-center' @click='closeCouponBox'>
+                <image src='/static/image/icon/icon-popup-close.png' class="bottom-close"></image>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapState} from "vuex";
+    import appPrice from "../../../page-component/goods/app-price.vue";
+
+    export default {
+        name: "app-coupon-modal",
+        components: {
+            'app-price': appPrice,
+        },
+        data() {
+            return {};
+        },
+        computed: {
+            ...mapState({
+                mallConfig: state => state.mallConfig,
+                coupon: state => state.page.coupon
+            }),
+            labelText() {
+                if (this.coupon && this.coupon.list && this.coupon.list.length) {
+                    const first = this.coupon.list[0];
+                    switch (first.share_type) {
+                        case 4:
+                            return '优惠券已发放到账户,请到我的优惠券查看';
+                        case 2:
+                            return '积分已发放到账户,请到我的积分查看';
+                        case 1:
+                            return '余额红包已发放到账户,请到我的余额查看';
+                        case 3:
+                            return '卡劵已发放到账户,请到我的卡劵查看';
+                        default:
+                            return '';
+                    }
+                }
+            },
+            img() {
+                let img = '';
+                if (this.coupon.type == 'register') {
+                    img = this.mallConfig.__wxapp_img.coupon.get_coupon_title;
+                } else if (this.coupon.type == 'share') {
+                    img = this.mallConfig.__wxapp_img.coupon.get_coupon_share;
+                } else if (this.coupon.type == 'receive') {
+                    img = this.mallConfig.__wxapp_img.coupon.get_coupon_receive;
+                } else if (this.coupon.type === 'award') {
+                    img = this.mallConfig.__wxapp_img.coupon.get_coupon_award;
+                }
+                return img
+            }
+        },
+        methods: {
+            closeCouponBox() {
+                let coupon = {
+                    list: [],
+                    type: ''
+                };
+                this.$store.dispatch('page/actionSetCoupon', coupon)
+            },
+            toGoods(page_url) {
+                uni.navigateTo({
+                    url: page_url
+                });
+                this.closeCouponBox();
+            },
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .invite {
+        text-align: center;
+        margin-top: #{20px - 24px};
+
+        view {
+            padding: #{16rpx} #{24rpx};
+            color: #ffffff;
+            background: rgba(0, 0, 0, 0.2);
+            font-size: #{26rpx};
+            line-height: 1;
+            display: inline-block;
+            border-radius: #{34rpx};
+        }
+    }
+
+    .invite-text {
+        font-size: #{22rpx};
+        line-height: 1;
+        padding-top: #{16rpx};
+
+        text:first-child {
+            color: #ffee01;
+            font-weight: bold;
+        }
+    }
+
+    .app-coupon-modal {
+        position: fixed;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        background: rgba(0, 0, 0, 0.5);
+        z-index: 1700;
+
+        .coupon-modal {
+            width: 100%;
+            overflow: visible;
+            margin-top: #{-50rpx};
+
+            .title-img {
+                width: #{750rpx};
+                height: #{360rpx};
+                display: block;
+            }
+
+            .get-coupon-content {
+                width: #{580rpx};
+                background: #ef3030;
+                border-radius: 0 0 #{16rpx 16rpx};
+                padding: #{24rpx 30rpx 30rpx 30rpx};
+                margin: 0 auto;
+                color: #ffffff;
+                font-size: $uni-font-size-weak-two;
+
+                .rest {
+                    color: #ffffff;
+                    font-size: $uni-font-size-general-one;
+                    margin-bottom: #{24rpx};
+
+                    .rest-count {
+                        color: #EE3030;
+                        font-size: $uni-font-size-import-one;
+                        background-color: #ffffff;
+                        padding: #{0 10rpx};
+                        margin: #{0 10rpx};
+                        border-radius: #{4rpx};
+                    }
+                }
+
+                .coupon-head-label {
+                    margin-bottom: #{16rpx};
+                }
+
+                .coupon-list {
+                    max-height: #{312rpx};
+
+                    view:first-child {
+                        margin-top: 0;
+                    }
+
+                    .coupon-item {
+                        width: 100%;
+                        padding: #{0 32rpx};
+                        margin-top: #{16rpx};
+                        border-radius: #{16rpx};
+                        background-color: #ffffff;
+                        height: #{144rpx};
+
+                        .price {
+                            font-size: #{56rpx};
+                            color: #ff4544;
+                            .discount:after {
+                                content: '折';
+                                font-size: 50%;
+                            }
+
+                            image {
+                                height: #{80rpx};
+                                width: #{80rpx};
+                                display: block;
+                            }
+
+                            .card {
+                                border-radius: 50%;
+                            }
+                        }
+
+                        .right {
+                            margin-left: #{26rpx};
+                            font-size: $uni-font-size-general-one;
+                            color: $uni-important-color-black;
+
+                            .content {
+                                font-size: #{$uni-font-size-weak-two};
+                                color: $uni-general-color-two;
+                            }
+                        }
+
+                        .btn {
+                            padding: #{12rpx 16rpx};
+                            border-radius: #{50rpx};
+                            font-size: $uni-font-size-weak-one;
+                            margin-left: #{19rpx};
+                            background-color: #ff4544;
+                        }
+                    }
+                }
+            }
+
+            .bottom-close {
+                width: #{30rpx};
+                height: #{30rpx};
+                margin-top: #{64rpx};
+            }
+        }
+    }
+</style>

+ 383 - 0
components/basic-component/app-layout/app-layout.vue

xqd
@@ -0,0 +1,383 @@
+<template>
+    <!--   #ifdef MP -->
+    <view class="app-layout" :style="[layoutStyle]" :class="haveBackground ? 'app-layout-background' : ''" >
+        <app-prompt-box v-if="promptBox.show" :text="promptBox.text"></app-prompt-box>
+        <app-user-login v-if="isGuest"></app-user-login>
+        <!--#ifndef MP_BAIDU-->
+        <u-iphone-frame v-else-if="!isGuest && is_mobile_auth == 1"></u-iphone-frame>
+        <!--#endif-->
+        <app-payment></app-payment>
+        <app-report-error :content="reportAndError.content" v-if="reportAndError.boolean"></app-report-error>
+        <app-coupon-modal></app-coupon-modal>
+        <view>
+            <slot></slot>
+        </view>
+        <app-loading :type="loadingType" :text="loadingText" :color="loadingColor" v-if="loadingIsShow"
+                     :backgroundImage="loadingBackgroundImage"></app-loading>
+        <template v-if="tabbarbool">
+            <view class="safe-area-inset-bottom">
+                <view :style="{height: '110rpx'}" class="nav-margin "
+                      :class="haveBackground ? 'app-layout-background' : ''"></view>
+            </view>
+            <app-tab-bar :page-count="page_count"></app-tab-bar>
+        </template>
+    </view>
+    <!--    #endif-->
+    <!--   #ifdef H5 -->
+    <view class="app-layout" :style="[layoutStyle]" :class="haveBackground ? 'app-layout-background' : ''" v-if="isSign">
+        <app-prompt-box v-if="promptBox.show" :text="promptBox.text"></app-prompt-box>
+        <app-user-login v-if="isGuest"></app-user-login>
+        <bd-mandatory-attention v-if="attention"></bd-mandatory-attention>
+        <app-payment></app-payment>
+        <app-wechat-share></app-wechat-share>
+        <app-report-error :content="reportAndError.content" v-if="reportAndError.boolean"></app-report-error>
+        <app-coupon-modal></app-coupon-modal>
+        <view>
+            <slot></slot>
+        </view>
+        <app-loading :type="loadingType" :text="loadingText" :color="loadingColor" v-if="loadingIsShow"
+                     :backgroundImage="loadingBackgroundImage"></app-loading>
+        <template v-if="tabbarbool">
+            <view class="safe-area-inset-bottom">
+                <view :style="{height: '110rpx'}" class="nav-margin "
+                      :class="haveBackground ? 'app-layout-background' : ''"></view>
+            </view>
+            <app-tab-bar :page-count="page_count"></app-tab-bar>
+        </template>
+    </view>
+    <!--    #endif-->
+</template>
+
+<script>
+   import {mapState, mapGetters} from 'vuex';
+   import appTabBar from '../../../components/basic-component/app-tab-bar/app-tab-bar.vue';
+   import AppPayment from './app-payment/app-payment';
+   import tabBar from '../../../core/tabbar.js';
+   import AppUserLogin from './app-user-login/app-user-login';
+   import appLoading from '../app-loading/app-loading.vue';
+   import appRepeatError from '../app-report-error/app-report-error.vue';
+   import appPromptBox from '../app-prompt-box/app-prompt-box.vue';
+   import appCouponModal from "./app-coupon-modal/app-coupon-modal.vue";
+   // #ifndef MP_BAIDU
+   import uIphoneFrame from './u-authorized-iphone/u-authorized-iphone.vue';
+   // #endif
+   // #ifdef H5
+   import bdMandatoryAttention from './bd-mandatory-attention/bd-mandatory-attention.vue';
+   import appWechatShare from "../../page-component/app-common/app-wechat-share.vue";
+   // #endif
+
+   export default {
+       name: "app-layout",
+       data() {
+           return {
+               currentRoute: '',
+               tabbarbool: true,
+               navigationBarTitle: '',
+               page_count: getCurrentPages().length,
+               isAttention:false
+           };
+       },
+       components: {
+           'app-tab-bar': appTabBar,
+           'app-payment': AppPayment,
+           'app-user-login': AppUserLogin,
+           'app-loading': appLoading,
+           'app-report-error': appRepeatError,
+           'app-prompt-box': appPromptBox,
+           'app-coupon-modal': appCouponModal,
+           // #ifndef MP_BAIDU
+           'u-iphone-frame': uIphoneFrame,
+            // #endif
+            // #ifdef H5
+            'bd-mandatory-attention': bdMandatoryAttention,
+            appWechatShare,
+            // #endif
+       },
+       props: {
+           haveBackground: {
+               type: Boolean,
+               default() {
+                   return true;
+               }
+           },
+            overflow: {
+                type: Boolean,
+                default() {
+                    return true;
+                }
+            }
+       },
+       computed: {
+           ...mapState('mallConfig', {
+               tabBarNavs: state => state.navbar.navs,
+               bar_title: state => state.bar_title,
+               top_background_color: state => state.navbar.top_background_color,
+               top_text_color: state => state.navbar.top_text_color,
+               bottom_background_color: (state) => {
+                   return state.navbar.bottom_background_color;
+               },
+               is_must_login: state => state.mall.setting.is_must_login,
+               // #ifndef MP_BAIDU
+               is_mobile_auth: state => state.mall.setting.is_mobile_auth
+               // #endif
+           }),
+           ...mapState('user', {
+               accessToken: state => state.accessToken,
+           }),
+           ...mapState('gConfig', {
+               reportAndError: state => state.reportAndError,
+               promptBox: state => state.promptBox,
+               iphone: (data) => {
+                   return data.iphone;
+               },
+           }),
+           isGuest() {
+               if ((this.$store.state.user.accessToken === '' || this.$store.state.user.accessToken === null) && 1) {
+                   return true;
+               } else {
+                   return false;
+               }
+           },
+           ...mapState('loading', {
+               loadingType: state => state.type,
+               loadingText: state => state.text,
+               loadingColor: state => state.color,
+               loadingBackgroundImage: state => state.backgroundImage,
+               loadingIsShow: state => state.isShow,
+           }),
+           ...mapGetters('iPhoneX', {
+               BotHeight: 'getBotHeight',
+               getNavHei: 'getNavHei',
+           }),
+           // #ifdef H5
+           ...mapGetters({
+               userInfo: 'user/info',
+               showAttention: 'user/showAttention'
+           }),
+           isSign: function() {
+               return this.$storage.getStorageSync('isSign');
+           },
+           // #endif
+           layoutStyle() {
+               if (this.overflow) {
+                   return {
+                       overflow: 'hidden'
+                   }
+               } else {
+                   return ''
+               }
+           },
+            // #ifdef H5
+            attention: function() {
+                return this.showAttention && this.$jwx.isWechat();
+            }
+            // #endif
+       },
+       watch: {
+           tabBarNavs: {
+               handler: function () {
+                   this.setTabbar();
+                   // #ifndef MP-TOUTIAO
+                   if (this.top_background_color !== undefined) {
+                       uni.setNavigationBarColor({
+                           backgroundColor: this.top_background_color,
+                           // #ifndef MP-ALIPAY
+                           frontColor: this.top_text_color,
+                           // #endif
+                       });
+                   }
+                   // #endif
+               },
+               immediate: true,
+           },
+           is_must_login: {
+               handler: function () {
+                    if ((this.$user.isLogin() || this.is_must_login === 1) && this.$platDiff.route() != '/pages/disabled/disabled') {
+                        this.$store.dispatch('user/info');
+                    }
+               },
+               immediate: true,
+           },
+           accessToken: {
+               handler: function () {
+                   if (!this.accessToken) {
+                    this.$store.commit('user/info', null);
+                   }
+               },
+               immediate: true,
+           },
+           // #ifdef H5
+           '$route': {
+               handler: function(data) {
+                   let { query, meta } = data;
+                   let str = '?';
+                   for (let key in query) {
+                       str += `${key}=${query[key]}&`
+                   }
+                   str.slice(0, str.length - 1);
+               },
+               deep: true
+           },
+           isSign: {
+               handler(newVal) {
+                   if (newVal) {
+                       // #ifndef H5
+                       if (this.top_background_color !== undefined) {
+                           uni.setNavigationBarColor({
+                               backgroundColor: this.top_background_color,
+                               frontColor: this.top_text_color,
+                           });
+                       }
+                       // #endif
+                   }
+               },
+               deep: true,
+               immediate: true
+           }
+           // #endif
+       },
+       created() {
+           this.$store.dispatch('mallConfig/actionGetConfig');
+           this.$nextTick(() => {
+               // #ifdef MP
+               let currentRoute = this.$platDiff.route();
+               // #endif
+               // #ifdef H5
+               let hash = window.location.hash;
+               if (hash.split('#')[1] === '/' || hash.split('#')[1].indexOf('/?id') > -1) {
+                   hash = '/pages/index/index';
+               }
+               let currentRoute = hash;
+               // #endif
+               tabBar.setNavigationBarTitle(this.bar_title, currentRoute).then(res => {
+                   if(currentRoute != '/pages/article/article-detail/article-detail') {
+                       this.navigationBarTitle = res;
+                   }
+               });
+           });
+           if ((this.$user.isLogin() || this.is_must_login === 1) && this.$platDiff.route() != '/pages/disabled/disabled') {
+               this.$store.dispatch('user/info');
+           }
+           this.$hideLoading();
+       },
+       mounted() {
+           // #ifdef MP-WEIXIN
+           // 直播转发绑定分销关系
+           try {
+               let pages = getCurrentPages();
+               if (pages.length) {
+                   let page = pages[pages.length - 1];
+                   let options = page.options;
+                   let customParams = {};
+                   if (typeof options.custom_params !== 'undefined') {
+                       customParams = JSON.parse(decodeURIComponent(options.custom_params));
+                   }
+                   if (typeof options.user_id !== 'undefined') {
+                       this.$store.dispatch('user/setTempParentId', options.user_id)
+                   } else if (typeof customParams.user_id !== 'undefined') {
+                       this.$store.dispatch('user/setTempParentId', customParams.user_id)
+                   }
+               }
+           } catch (e) {
+           }
+           // #endif
+
+           this.currentRoute = this.$platDiff.tabBarUrl(null, this.page_count);
+           this.setTabbar();
+
+           // #ifdef MP
+           // #ifndef MP-TOUTIAO
+           if (this.top_background_color !== undefined) {
+               uni.setNavigationBarColor({
+                   backgroundColor: this.top_background_color,
+                   // #ifndef MP-ALIPAY
+                   frontColor: this.top_text_color,
+                   // #endif
+               });
+           }
+           // #endif
+           // #endif
+       },
+       beforeDestroy() {
+           this.$hideLoading();
+       },
+       methods: {
+           touchMove() {
+               return true;
+           },
+           setTabbar() {
+               let currentRoute = this.currentRoute;
+               if (
+                   this.$appScene
+                   && [1001, 1045, 1046, 1058, 1067, 1084, 1091].indexOf(this.$appScene) > -1
+                   && (currentRoute.indexOf('appid') > -1 || currentRoute.indexOf('appmsg_compact_url') > -1 || currentRoute .indexOf('wxwork_userid') > -1 || currentRoute .indexOf('weixinadinfo') > -1 || currentRoute .indexOf('gdt_vid') > -1)
+               ) {
+                   currentRoute = this.$utils.deleteUrlParam(currentRoute, ['appid', 'appmsg_compact_url', 'wxwork_userid', 'weixinadinfo', 'gdt_vid'], true);
+               }
+			   console.log(currentRoute,"--------=========")
+			   console.log(this.tabBarNavs,"---------=========")
+               for (let i = 0; i < this.tabBarNavs.length; i++) {
+                   if (currentRoute == this.tabBarNavs[i].url) {
+                       return this.tabbarbool = true;
+                   }
+               }
+			   console.log(this.tabbarbool)
+               return this.tabbarbool = false;
+           },
+           // #ifdef H5
+           getUrlParam(name) {
+               let url = window.location.href.split('#')[0];
+               let search = url.split('?')[1]
+               if (search) {
+                   let r = search.substr(0).match(new RegExp('(^|&)' + name + '=([^&]*)(&|$)'))
+                   if (r !== null) return unescape(r[2])
+                   return null
+               } else {
+                   return null
+               }
+           }
+           // #endif
+       }
+   }
+</script>
+
+<style scoped lang="scss">
+   .app-layout {
+       max-width: 100%;
+       //#ifdef MP-ALIPAY
+       position: relative;
+       min-height: 100vh;
+       z-index: 1;
+       //#endif
+   }
+
+   .app-layout-background {
+       background-color: #f7f7f7;
+   }
+
+   .app-scroll-y {
+       width: 100%;
+       height: 100%;
+   }
+
+   .app-bottom {
+       height: #{160rpx};
+   }
+
+   .nav-margin {
+       width: #{750rpx};
+   }
+
+   .app-tabbar {
+       height: #{110rpx};
+   }
+
+   .model {
+       position: fixed;
+       bottom: 0;
+       left: 0;
+       width: #{750rpx};
+       height: #{50rpx};
+       z-index: 1600;
+   }
+</style>

+ 847 - 0
components/basic-component/app-layout/app-payment/app-payment.vue

xqd
@@ -0,0 +1,847 @@
+<!-- 全局支付组件 -->
+<template>
+    <view class="app-payment main-center cross-center" :class="showPayment?'show':''">
+        <view class="modal" v-if="payData">
+            <view class="title">
+                <view>支付方式</view>
+                <view class="cancel" @click="cancel">
+                    <image src="/static/image/icon/close.png"></image>
+                </view>
+            </view>
+            <view class="pay-amount">支付金额 {{payData.amount}} 元</view>
+            <view class="pay-type-list">
+                <view v-for="(item, index) in payData.list"
+                      @click="checkPayType(index)"
+                      :key="index"
+                      class="pay-type-item cross-center">
+                    <view class="box-grow-0">
+                        <image class="pay-type-icon" :src="item.icon"></image>
+                    </view>
+                    <view class="box-grow-1">
+                        <view class="pay-type-name">{{item.name}}</view>
+                        <view class="pay-type-desc" v-if="item.desc">{{item.desc}}</view>
+                    </view>
+                    <view class="box-grow-0">
+                        <view v-if="item.key === 'balance' && item.desc === '账户余额不足'"
+                              style="height: 26rpx;width:113rpx"
+                              @click.prevent.stop="navPay">
+                            <image style="height: 100%;width:100%" load-lazy="true"
+                                   src="/static/image/pay-td.png"></image>
+                        </view>
+                        <app-radio v-else-if="item.checked" :theme="getTheme" v-model="item.checked"
+                                   type="round"></app-radio>
+                    </view>
+                    <view class="into-modal main-center cross-center" v-if="printPassword && item.key === 'balance'">
+                        <view class="password-tip" v-if="item.is_pay_password == 0 && !setPassword">
+                            <view class="password-content dir-top-nowrap main-center cross-center">
+                                <view>您的账户尚未设置余额支付密码</view>
+                                <view>是否立即设置?</view>
+                            </view>
+                            <view class="password-btn main-center cross-center">
+                                <view @click="payByBalance">暂不设置</view>
+                                <view class="line"></view>
+                                <view @click="setPassword = !setPassword;password=''" :style="{'color':getTheme.color}">确认</view>
+                            </view>
+                        </view>
+                        <view class="password-view" v-else-if="printPassword">
+                            <image class="password-close" @click="printPassword = false;setPassword = false;verifyPassword = false;" src="/static/image/icon/icon-close.png"></image>
+                            <view class="password-title">请{{verifyPassword ? '确认':'输入'}}余额支付密码</view>
+                            <!-- #ifdef MP-ALIPAY -->
+                            <input type="number" :class="!haveFocus ? 'input' :''" @focus="haveFocus=true" @input="passwordInput" @blur="haveFocus=false;getFocus=false" v-model="password">
+                            <!-- #endif -->
+                            <!-- #ifdef MP-WEIXIN -->
+                            <input type="number" style="top:0;right: 400%" @input="passwordInput" :focus="getFocus" @blur="getFocus=false" v-model="password">
+                            <!-- #endif -->
+                            <!-- #ifndef MP-ALIPAY || MP-WEIXIN -->
+                            <input type="number" v-show="getFocus" style="top:0;right: 400%" @input="passwordInput" :focus="getFocus" @blur="getFocus=false" v-model="password">
+                            <!-- #endif -->
+                            <view @click="getInputFocus" class="passoword-input main-center cross-center">
+                                <view class="password-item main-center cross-center">
+                                    <view v-if="password.length > 0" class="password-placeholder"></view>
+                                </view>
+                                <view class="password-item main-center cross-center">
+                                    <view v-if="password.length > 1" class="password-placeholder"></view>
+                                </view>
+                                <view class="password-item main-center cross-center">
+                                    <view v-if="password.length > 2" class="password-placeholder"></view>
+                                </view>
+                                <view class="password-item main-center cross-center">
+                                    <view v-if="password.length > 3" class="password-placeholder"></view>
+                                </view>
+                                <view class="password-item main-center cross-center">
+                                    <view v-if="password.length > 4" class="password-placeholder"></view>
+                                </view>
+                                <view class="password-item main-center cross-center">
+                                    <view v-if="password.length > 5" class="password-placeholder"></view>
+                                </view>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+            </view>
+            <view class="footer">
+                <!--  #ifdef MP -->
+                <app-button type="important" :theme="getTheme" @click="confirm" round>提交订单</app-button>
+                <!--  #endif-->
+                <!--  #ifdef H5 -->
+                <app-button type="important" :theme="getTheme" @click.native.stop="confirm" round>提交订单</app-button>
+                <!--  #endif-->
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import Vue from 'vue';
+    import {mapGetters, mapState} from 'vuex';
+    import AppRadio from '@/components/basic-component/app-radio/app-radio';
+
+    export default {
+        name: 'app-payment',
+        components: {AppRadio},
+        computed: {
+            ...mapState({
+                mall: state => state.mallConfig.mall,
+                userInfo: state => state.user.info,
+                showPayment: function(state) {
+                    return state.payment.showPayment;
+                },
+                payData: state => state.payment.payData,
+            }),
+            ...mapGetters('mallConfig',{
+                getTheme: 'getTheme',
+            }),
+        },
+        data() {
+            return {
+                is_need_pay_password: 0,
+                haveFocus: false,
+                getFocus: false,
+                printPassword: false,
+                setPassword: false,
+                verifyPassword: false,
+                password: '',
+                verify_pay_password: '',
+                pay_password: '',
+                payPassword: '',
+            }
+        },
+        created() {
+            this.setPayment();
+        },
+        methods: {
+            getInputFocus() {
+                this.$nextTick(() => {
+                    this.getFocus = true;
+                })
+            },
+            passwordInput() {
+                if(this.password.length == 6) {
+                    setTimeout(()=>{
+                        if(this.setPassword) {
+                            this.setPayPassword();
+                        }else {
+                            uni.showLoading({
+                                mask: true
+                            });
+                            uni.hideKeyboard();
+                            this.verifyPayPassword();
+                        }
+                    })
+                }
+            },
+            setPayPassword() {
+                if(this.password.length < 6) {
+                    return false;
+                }
+                if(!this.verifyPassword) {
+                    this.pay_password = this.password.toString().substring(0,6);
+                    this.verifyPassword = true;
+                    this.password = '';
+                }else {
+                    this.verify_pay_password = this.password.toString().substring(0,6);;
+                    if (this.pay_password === this.verify_pay_password) {
+                        uni.showLoading({
+                            mask: true
+                        });
+                        let data = {
+                            pay_password: this.pay_password,
+                            verify_pay_password: this.verify_pay_password,
+                        }
+                        this.$request({
+                            url: this.$api.member.set_password,
+                            method: "post",
+                            data: data
+                        }).then(response => {
+                            uni.hideLoading();
+                            if (response.code === 0) {
+                                this.payPassword = this.pay_password;
+                                this.printPassword = false;
+                                this.setPassword = false;
+                                this.verifyPassword = false;
+                                this.password = '';
+                                this.pay_password = '';
+                                this.verify_pay_password = '';
+                                this.$store.commit('payment/showPayment', false);
+                                this.payByBalance();
+                            } else {
+                                this.password = '';
+                                this.pay_password = '';
+                                this.verify_pay_password = '';
+                                uni.showToast({
+                                    icon: 'none',
+                                    title: response.msg
+                                });
+                            }
+                        });
+                    } else {
+                        this.verifyPassword = false;
+                        this.password = '';
+                        this.pay_password = '';
+                        this.verify_pay_password = '';
+                        uni.showToast({
+                            icon: 'none',
+                            title: '两次输入的密码不一致'
+                        })
+                    }
+                }
+            },
+            navPay() {
+                this.$store.commit('payment/showPayment', false);
+                this.$store.state.payment.reject({
+                    errMsg: '5b03b6e009796c698d132908cb635fca',
+                });
+                uni.navigateTo({
+                    url: "/pages/balance/recharge"
+                });
+            },
+            setPayment() {
+                const vm = this;
+                Vue.use({
+                    install(Vue, options) {
+                        Vue.prototype.$payment = {
+                            pay: vm.pay,
+                        };
+                    },
+                });
+            },
+            pay(id) {
+                return new Promise((resolve, reject) => {
+                    this.$store.commit('payment/setAll', {
+                        showPayment: false,
+                        payData: null,
+                        payType: null,
+                        id: id,
+                        resolve: resolve,
+                        reject: reject,
+                    });
+                    console.log('debug payment, setAll ok, id:', this.$store.state.payment.id);
+                    console.log('debug payment, setAll ok, resolve:', this.$store.state.payment.resolve);
+                    console.log('debug payment, setAll ok, reject:', this.$store.state.payment.reject);
+                    uni.showLoading({
+                        mask: true,
+                        title: '请求支付...',
+                    });
+                    this.$request({
+                        url: this.$api.payment.get_payments,
+                        data: {
+                            id: id,
+                        }
+                    }).then(response => {
+                        uni.hideLoading();
+                        console.log('debug 1--->', response);
+                        if (response.code === 0) {
+                            console.log('debug payment, set resolve 2,', this.$store.state.payment.resolve);
+                            return this.showPaymentModal(response.data);
+                        } else {
+                            response.errMsg = response.msg || '';
+                            return this.$store.state.payment.reject(response.msg);
+                        }
+                    }).catch(e => {
+                        uni.hideLoading();
+                        e.errMsg = e.msg || '';
+                        return this.$store.state.payment.reject(e);
+                    });
+                });
+            },
+            showPaymentModal(data) {
+                console.log('debug 2--->', data);
+                for (let i in data.list) {
+                    if (typeof data.list[i].checked === 'undefined') {
+                        data.list[i].checked = false;
+                    }
+                }
+                this.$store.commit('payment/payData', data);
+                if (data.amount === 0 || data.amount === 0.00 || data.amount === '0' || data.amount === '0.00') {
+                    this.$store.commit('payment/payType', 'balance');
+                    for (let i in this.$store.state.payment.payData.list) {
+                        if (this.$store.state.payment.payData.list[i].key === 'balance') {
+                            this.$store.state.payment.payData.list[i].checked = true;
+                        } else {
+                            this.$store.state.payment.payData.list[i].checked = false;
+                        }
+                    }
+                    this.confirm();
+                    return;
+                }
+                this.$store.commit('payment/showPayment', true);
+            },
+            confirm() {
+                console.log('payment confirm 1:');
+                console.log('debug payment, confirm 1,', this.$store.state.payment.resolve);
+                for (let i in this.$store.state.payment.payData.list) {
+                    if (this.$store.state.payment.payData.list[i].checked) {
+                        this.$store.commit('payment/payType', this.$store.state.payment.payData.list[i].key);
+                    }
+                }
+                if (!this.$store.state.payment.payType) {
+                    uni.showModal({
+                        title: '提示',
+                        content: '请选择支付方式',
+                        showCancel: false,
+                    });
+                    return;
+                }
+                this.$store.commit('payment/showPayment', false);
+                console.log('payment confirm 2:', this.$store.state.payment.payType);
+                console.log('debug payment, confirm 2,', this.$store.state.payment.resolve);
+                return this.getPayData();
+            },
+            cancel() {
+                this.$store.commit('payment/showPayment', false);
+                return this.$store.state.payment.reject({
+                    errMsg: '支付取消',
+                });
+            },
+            checkPayType(index) {
+                if (this.$store.state.payment.payData.list[index].disabled || this.$store.state.payment.payData.list[index].checked) {
+                    return false;
+                }
+                const payData = this.$store.state.payment.payData;
+                for (let i in payData.list) {
+                    if (i == index) {
+                        payData.list[i].checked = true;
+                    } else {
+                        payData.list[i].checked = false;
+                    }
+                }
+                this.$store.commit('payment/payData', payData);
+            },
+            getPayData() {
+                console.log('debug payment, getPayData 1,', this.$store.state.payment.resolve);
+                uni.showLoading({
+                    mask: true,
+                    title: '请求支付...',
+                });
+                let _this = this;
+                let data = {
+                    id: this.$store.state.payment.id,
+                    pay_type: this.$store.state.payment.payType,
+                }
+                // #ifdef H5
+                this.$storage.setStorageSync('WEB_URL', window.location.href + '&pay_id_weChart=' + data.id + '&isWechat=true');
+                if (window.location.hash.indexOf('/pages/balance/recharge') > -1) {
+                    data.url = window.location.href.split('#')[0] + '#/pages/balance/recharge?isPay=ture';
+                } else {
+                    if (window.location.hash.indexOf('?') > -1) {
+                        data.url = window.location.href + '&isPay=ture'
+                    } else {
+                        data.url = window.location.href + '?isPay=ture'
+                    }
+                }
+                data.url += `&isWechat=true&payType=${this.$store.state.payment.payType}`
+                if (!this.$jwx.isWechat()) {
+                    data.url += '&pay_id_weChart=' + data.id
+                }
+                // #endif
+                this.$request({
+                    url: this.$api.payment.pay_data,
+                    data: data
+                }).then(response => {
+                    uni.hideLoading();
+                    if (response.code === 0) {
+                        switch (this.$store.state.payment.payType) {
+                            case 'balance':
+                                this.callBranch(response.data);
+                                break;
+                            case 'huodao':
+                                this.callHuodao(response.data);
+                                break;
+                            // #ifdef H5
+                            case 'wechat_h5':
+                                console.log('debug payment, wechat_h5');
+                                this.$jwx.chooseWXPay({
+                                    timestamp: response.data.timeStamp,
+                                    nonceStr: response.data.nonceStr,
+                                    packAge: response.data.package,
+                                    signType: response.data.signType,
+                                    paySign: response.data.paySign,
+                                    webUrl: response.data.mweb_url,
+                                    success() {
+                                         _this.$store.state.payment.resolve({
+                                            errMsg: '支付成功',
+                                        });
+                                    },
+                                    fail(res) {
+                                         _this.$store.state.payment.reject({
+                                            errMsg: res.msg
+                                        });
+                                    }
+                                });
+                                uni.showModal({
+                                    content: '确定已完成支付?',
+                                    confirmText: '确定',
+                                    cancelText: '返回支付',
+                                    success(res) {
+                                        if (res.confirm) {
+                                            _this.weChartPay(_this.$store.state.payment.id);
+                                        } else if (res.cancel) {
+                                            _this.$store.state.payment.reject({
+                                                errMsg: '支付取消'
+                                            });
+                                        }
+                                    }
+                                });
+                                break;
+                            case 'alipay_h5':
+                                if (this.$jwx.isWechat()) {
+                                    _AP.pay(response.data.url);
+                                } else {
+                                    window.location.href = response.data.url;
+                                    uni.showModal({
+                                        content: '确定已完成支付?',
+                                        confirmText: '确定',
+                                        cancelText: '返回支付',
+                                        success(res) {
+                                            if (res.confirm) {
+                                                _this.weChartPay(_this.$store.state.payment.id);
+                                                // _this.$store.state.payment.resolve({
+                                                //     errMsg: '支付成功',
+                                                // });
+                                            } else if (res.cancel) {
+                                                _this.$store.state.payment.reject({
+                                                    errMsg: '支付取消'
+                                                });
+                                            }
+                                        },
+                                        fail() {
+                                        }
+                                    });
+                                }
+                                console.log('debug payment, alipay_h5');
+                                break;
+                            // #endif
+                            default:
+                                // #ifdef MP
+                                console.log('debug payment, getPayData 2,', this.$store.state.payment.resolve);
+                                this.callPlatformPayment(response.data);
+                                // #endif
+                                break;
+                        }
+                    } else {
+                        return this.$store.state.payment.reject(response.msg);
+                    }
+                }).catch(e => {
+                    uni.hideLoading();
+                    e.errMsg = e.msg || '';
+                    return this.$store.state.payment.reject(e);
+                });
+            },
+            callBranch(data) {
+                let that = this;
+                if (data.order_amount === 0 || data.order_amount === 0.00 || data.order_amount === '0' || data.order_amount === '0.00') {
+                    this.payByBalance();
+                } else {
+                    uni.showModal({
+                        title: '余额支付确认',
+                        content: `账户余额:${data.balance_amount},支付金额:${data.order_amount}`,
+                        success: (e) => {
+                            if (e.confirm) {
+                                for(let item of this.payData.list) {
+                                    if(item.key == 'balance') {
+                                        if(item.is_open_pay_password == 1) {
+                                            this.payPassword = '';
+                                            this.is_need_pay_password = item.is_pay_password;
+                                            this.password = '';
+                                            this.$store.commit('payment/showPayment', true);
+                                            this.printPassword = true;
+                                            setTimeout(() => {
+                                                this.getFocus = true;
+                                            },800)
+                                        }else {
+                                            this.payByBalance();
+                                        }
+                                        break
+                                    }
+                                }
+                            } else {
+                                return this.$store.state.payment.reject({
+                                    errMsg: '支付取消.',
+                                });
+                            }
+                        }
+                    });
+                }
+            },
+            verifyPayPassword() {
+                if(this.password.length < 6) {
+                    return false;
+                }
+                this.payPassword = this.password.toString().substring(0,6);
+                this.$request({
+                    url: this.$api.member.verify_password,
+                    data: {
+                        pay_password: this.payPassword,
+                    },
+                    method: 'post'
+                }).then(response => {
+                    this.password = '';
+                    uni.hideLoading();
+                    if (response.code === 0) {
+                        this.$store.commit('payment/showPayment', false);
+                        this.payByBalance();
+                    } else {
+                        this.password = '';
+                        this.payPassword = '';
+                        uni.showModal({
+                            title: '提示',
+                            content: response.msg,
+                            showCancel: false
+                        });
+                    }
+                }).catch(e => {
+                    uni.hideLoading();
+                    e.errMsg = e.msg || '';
+                    return this.$store.state.payment.reject(e);
+                });
+            },
+            payByBalance() {
+                uni.showLoading({
+                    mask: true,
+                    title: '支付中...',
+                });
+                let para = {
+                    id: this.$store.state.payment.id,
+                    pay_password: this.payPassword ? this.payPassword : '',
+                    is_need_pay_password: this.is_need_pay_password
+                }
+                this.$request({
+                    url: this.$api.payment.pay_buy_balance,
+                    data: para
+                }).then(response => {
+                    uni.hideLoading();
+                    if (response.code === 0) {
+                        this.$store.commit('payment/showPayment', false);
+                        return this.$store.state.payment.resolve({
+                            errMsg: '支付成功',
+                        });
+                    } else {
+                        return this.$store.state.payment.reject({
+                            errMsg: response.msg,
+                        });
+                    }
+                }).catch(e => {
+                    e.errMsg = e.msg || '';
+                    return this.$store.state.payment.reject(e);
+                });
+            },
+            callHuodao() {
+                uni.showLoading({
+                    mask: true,
+                    title: '提交中...',
+                });
+                this.$request({
+                    url: this.$api.payment.pay_buy_huodao,
+                    data: {
+                        id: this.$store.state.payment.id,
+                    },
+                }).then(response => {
+                    uni.hideLoading();
+                    if (response.code === 0) {
+                        return this.$store.state.payment.resolve({
+                            errMsg: '支付成功',
+                        });
+                    } else {
+                        return this.$store.state.payment.reject({
+                            errMsg: response.msg,
+                        });
+                    }
+                }).catch(e => {
+                    uni.hideLoading();
+                    e.errMsg = e.msg || '';
+                    return this.$store.state.payment.reject(e);
+                });
+            },
+            // #ifdef MP
+            callPlatformPayment(data) {
+                console.log('debug payment, callPlatformPayment 1,', this.$store.state.payment.resolve);
+                let paymentProvider = null;
+                // #ifdef MP-WEIXIN
+                paymentProvider = ['wxpay'];
+                // #endif
+                // #ifdef MP-ALIPAY
+                paymentProvider = ['alipay'];
+                // #endif
+                // #ifdef MP-BAIDU
+                paymentProvider = ['baidu'];
+                // #endif
+                // #ifdef MP-TOUTIAO
+                paymentProvider = ['toutiao'];
+                // #endif
+                uni.requestPayment({
+                    provider: paymentProvider,
+                    success: (e) => {
+                        console.log('debug payment, callPlatformPayment 3,', this.$store.state.payment.resolve);
+                        console.log('success:', e);
+                        // #ifndef MP-ALIPAY
+                        return this.$store.state.payment.resolve(e);
+                        // #endif
+                        // #ifdef MP-ALIPAY
+                        if (e.resultCode === 9000 || e.resultCode === '9000') {
+                            return this.$store.state.payment.resolve(e);
+                        } else {
+                            return this.$store.state.payment.reject({
+                                errMsg: e.memo,
+                            });
+                        }
+                        // #endif
+                    },
+                    fail: (e) => {
+                        const cancelMsgList = [
+                            'requestPayment:fail cancel',
+                        ];
+                        if (e.errMsg && cancelMsgList.indexOf(e.errMsg) >= 0) {
+                            e.errMsg = '取消支付';
+                        }
+                        console.log('debug payment, callPlatformPayment 4,', this.$store.state.payment.resolve);
+                        console.log('fail:', e);
+                        return this.$store.state.payment.reject(e);
+                    },
+                    ...data
+                });
+            },
+            // #endif
+            // #ifdef H5
+            alipayH5Pay() {},
+            weChartPay(id) {
+                this.$request({
+                    url: this.$api.registered.pay,
+                    method: 'get',
+                    data: {
+                        payment_order_union_id: id
+                    }
+                }).then((res) => {
+                    if (res.code === 0) {
+                        if (res.data.status === 1) {
+                            this.$store.state.payment.resolve({
+                                errMsg: '支付成功',
+                            });
+                            uni.redirectTo({
+                                url: `/pages/order-submit/pay-result?payment_order_union_id=${id}`,
+                            });
+                        } else {
+                            uni.redirectTo({
+                                url: '/pages/order/index/index'
+                            });
+                        }
+                    }
+                })
+            }
+            // #endif
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    $bigPadding: #{50rpx};
+    $smallPadding: #{25rpx};
+    $middlePadding: #{30rpx};
+    $smallFont: #{24rpx};
+    $lineWidth: #{1rpx};
+    $modalWidth: #{600rpx};
+    $iconWidth: #{60rpx};
+
+    .app-payment {
+        background: rgba(0, 0, 0, .5);
+        position: fixed;
+        z-index: 2000;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+        visibility: hidden;
+        opacity: 0;
+        transition: 150ms;
+
+        .modal {
+            background: #fff;
+            width: $modalWidth;
+            border-radius: #{15rpx};
+
+            .title {
+                text-align: center;
+                padding: $middlePadding;
+                border-bottom: $lineWidth solid #e2e2e2;
+                position: relative;
+            }
+
+            .cancel {
+                position: absolute;
+                right: 0;
+                top: 0;
+                padding: $middlePadding;
+
+                image {
+                    width: #{36rpx};
+                    height: #{36rpx};
+                }
+            }
+
+            .pay-amount {
+                text-align: center;
+                padding: $bigPadding;
+                font-weight: bolder;
+            }
+
+            .pay-type-list {
+                padding: 0 $bigPadding;
+            }
+
+            .pay-type-item {
+                border-bottom: $lineWidth solid #e2e2e2;
+                padding: $smallPadding 0;
+
+                .pay-type-icon {
+                    width: $iconWidth;
+                    height: $iconWidth;
+                    margin-right: $smallPadding;
+                }
+
+                .pay-type-desc {
+                    color: #909090;
+                    font-size: $smallFont;
+                }
+            }
+
+            .pay-type-item:last-child {
+                border-bottom: none;
+            }
+
+            .footer {
+                padding: $bigPadding;
+            }
+        }
+    }
+
+    .app-payment.show {
+        visibility: visible;
+        opacity: 1;
+    }
+    .into-modal {
+        background: rgba(0, 0, 0, .5);
+        position: fixed;
+        z-index: 2100;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+        opacity: 1;
+        transition: 150ms;
+        .password-tip {
+            width: #{630rpx};
+            height: #{340rpx};
+            position: relative;
+            border-radius: #{16rpx};
+            background-color: #fff;
+            .password-content {
+                height: #{240rpx};
+                width: 100%;
+                color: #353535;
+            }
+            .password-btn {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                width: 100%;
+                color: #666666;
+                height: #{88rpx};
+                border-top: #{2rpx} solid #e2e2e2;
+                >view {
+                    width: 50%;
+                    text-align: center;
+                    height: #{88rpx};
+                    line-height: #{88rpx};
+                }
+                .line {
+                    width: #{2rpx};
+                    height: #{32rpx};
+                    background-color: #e2e2e2;
+                }
+            }
+        }
+        .password-view {
+            position: relative;
+            width: #{560rpx};
+            height: #{300rpx};
+            border-radius: #{16rpx};
+            background-color: #fff;
+            margin-top: #{-200rpx};
+            .password-close {
+                position: absolute;
+                top: #{29rpx};
+                right: #{28rpx};
+                width: #{30rpx};
+                height: #{30rpx};
+                z-index: 2101;
+            }
+            .password-title {
+                height: #{140rpx};
+                line-height: #{140rpx};
+                text-align: center;
+                margin-bottom: #{128rpx};
+            }
+            .password-button {
+                padding: 0 #{60rpx};
+                position: relative;
+                z-index: 2101;
+            }
+            input {
+                position: absolute;
+                top: -300%;
+                width: #{475rpx};
+                height: #{78rpx};
+                margin: 0 auto;
+                z-index: 9999;
+            }
+            .input {
+                top: #{144rpx};
+                height: #{70rpx};
+                left: #{42.5rpx};
+                color: #fff;
+                font-size: #{1rpx};
+                background-color: transparent;
+                opacity: 0;
+            }
+            .passoword-input {
+                position: absolute;
+                background-color: #fff;
+                top: #{140rpx};
+                left: 0;
+                width: 100%;
+                z-index: 2101;
+                .password-item {
+                    border: #{2rpx} solid #e2e2e2;
+                    margin-left: #{-2rpx};
+                    height: #{78rpx};
+                    width: #{78rpx};
+                    .password-placeholder {
+                        width: #{24rpx};
+                        height: #{24rpx};
+                        border-radius: 50%;
+                        background-color: #353535;
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 151 - 0
components/basic-component/app-layout/app-permissions-auth/app-permissions-auth.vue

xqd
@@ -0,0 +1,151 @@
+<template>
+    <view v-if='isShow' class="permissions show bt">
+        <view class="permissions-bg"></view>
+        <view class="permissions-pic main-center">
+            <view class="info-model cross-center dir-top-nowrap">
+                <view class="order-title">提示</view>
+                <view class="info-box dir-left-nowrap cross-center">
+                    {{text}}
+                </view>
+                <view class="info-end dir-left-nowrap cross-center">
+                    <view @click='cancel' class="box-grow-1 main-center cross-center">取消</view>
+                    <view class="box-grow-0 info-line"></view>
+                    <view class="box-grow-1 red main-center cross-center">
+                        <button @click='cancel' open-type="openSetting" class="btn">去设置</button>
+                    </view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-permissions-auth',
+        data() {
+            return {}
+        },
+        props: {
+            isShow: {
+                type: Boolean,
+                default: false,
+            },
+            text: {
+                type: String,
+                default: '请在设置中打开相应权限',
+            }
+        },
+        methods: {
+            cancel() {
+                this.$emit('cancel', false);
+            },
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .permissions {
+        position: fixed;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+        z-index: 1000;
+        transition: 200ms;
+    }
+
+    .permissions.show {
+        visibility: visible;
+        opacity: 1;
+    }
+
+    .permissions.bt {
+        -webkit-animation-name: fadeIn; /*动画名称*/
+        -webkit-animation-duration: 0.25s; /*动画持续时间*/
+        -webkit-animation-iteration-count: 1; /*动画次数*/
+        -webkit-animation-delay: 0s; /*延迟时间*/
+    }
+
+    .permissions .permissions-bg {
+        background: rgba(0, 0, 0, 0.8);
+        position: fixed;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+        z-index: 1;
+    }
+
+    .permissions .permissions-pic {
+        position: fixed;
+        left: 0;
+        top: #{188rpx};
+        width: 100%;
+        height: 100%;
+        z-index: 1;
+    }
+
+    .permissions .permissions-close image {
+        width: #{50rpx};
+        height: #{50rpx};
+        margin-top: #{50rpx};
+    }
+
+    .permissions .info-model {
+        height: #{360rpx};
+        width: #{620rpx};
+        background: #fff;
+        border-radius: #{16rpx};
+    }
+
+    .permissions .order-title {
+        margin: #{48rpx} 0
+    }
+
+    .permissions .place {
+        color: #cdcdcd
+    }
+
+    .permissions .info-box {
+        height: #{88rpx};
+        margin-bottom: #{48rpx};
+        padding: 0 60rpx;
+    }
+
+    .permissions .info-input {
+        font-size: #{32rpx};
+        color: #353535;
+    }
+
+    .permissions .info-line {
+        height: #{32rpx} !important;
+        width: 1px !important;
+        background: #e2e2e2;
+    }
+
+    .permissions .info-end {
+        color: #666666;
+        height: #{88rpx};
+        font-size: #{32rpx};
+        border-top: #{1rpx} solid #e2e2e2;
+        width: 100%;
+    }
+
+    .permissions .info-end view {
+        height: 100%;
+        width: 100%;
+    }
+
+    button::after {
+        border: none;
+        content: '';
+    }
+
+    .btn {
+        border: none;
+        padding: 0;
+        background-color: #fff;
+        font-size: 32#{rpx};
+        color: $uni-general-color-one;
+    }
+</style>

+ 288 - 0
components/basic-component/app-layout/app-user-login/app-user-login.vue

xqd
@@ -0,0 +1,288 @@
+<template>
+	<view class="login-1 dir-left-nowrap main-center cross-center" :class="showLoginModal ? 'show' : ''">
+		<view class="login-content">
+			<image :src="auth_page && auth_page.pic_url" class="login-img"></image>
+			<view>
+				<app-hotspot :hotspot="auth_page.hotspot_link">
+					<button class="login-btn" @click="link"></button>
+				</app-hotspot>
+			</view>
+			<view>
+				<app-hotspot :hotspot="auth_page.hotspot_cancel">
+					<button class="login-btn" @click="cancel"></button>
+				</app-hotspot>
+			</view>
+			<view>
+				<app-hotspot :hotspot="auth_page.hotspot">
+					<!-- #ifdef MP -->
+					<button class="login-btn" :open-type="openType" scope="userInfo" @getAuthorize="getUserInfo"
+						@tap="getUserInfo" @click="getUserInfoClick">
+					</button>
+					<!-- #endif -->
+					<!-- #ifdef H5 -->
+					<view class="login-btn" @click="getUserInfo"></view>
+					<!-- #endif -->
+				</app-hotspot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import Vue from 'vue';
+	import {
+		mapState
+	} from 'vuex';
+	import appHotspot from '../../../basic-component/app-hotspot/app-hotspot.vue';
+
+	export default {
+		name: 'app-user-login',
+		components: {
+			appHotspot,
+		},
+		data() {
+			return {};
+		},
+		computed: {
+			openType() {
+				// #ifdef MP-ALIPAY
+				return 'getAuthorize';
+				// #endif
+				return 'getUserInfo';
+			},
+			...mapState('mallConfig', {
+				auth_page: state => state.auth_page,
+			}),
+			...mapState({
+				showLoginModal: function(state) {
+					return state.user.showLoginModal
+				}
+			}),
+		},
+		created() {
+			const vm = this;
+			Vue.use({
+				install(Vue, options) {2
+					Vue.prototype.$layout = {
+						getUserInfo() {
+							vm.showLoginModal = true;
+							return new Promise((resolve, reject) => {
+								vm.getUserInfo = (e) => {};
+							});
+						},
+					};
+				},
+			});
+		},
+		methods: {
+			link() {
+				this.$store.commit('user/showLoginModal', false);
+			},
+			cancel() {
+				this.$store.commit('user/showLoginModal', false);
+				this.$user.getUserInfoReject('getUserInfo fail: cancel.');
+				let pages = getCurrentPages();
+				let list = ['/pages/index/index', '/pages/user-center/user-center'];
+				// #ifdef MP
+				let url = this.$platDiff.route();
+				// #endif
+				// #ifdef H5
+				let {
+					hash
+				} = window.location;
+				hash = hash.split('#')[1];
+				let url = hash;
+				if (url === '/') {
+					url = '/pages/index/index'
+				}
+				// #endif
+
+				if (list.includes(url)) {
+					// #ifdef MP
+					url = this.$platDiff.routeWithOption();
+					// #endif
+					// #ifdef H5
+					url = window.location.hash;
+					// #endif
+					uni.redirectTo({
+						url: url
+					});
+				} else if (pages.length >= 2) {
+					uni.navigateBack({
+						delta: 1
+					});
+				} else {
+					uni.redirectTo({
+						url: '/pages/index/index'
+					});
+				}
+			},
+			// #ifdef MP
+			getUserInfoClick(e) {
+				// #ifdef MP-TOUTIAO
+				this.getUserInfo(e);
+				// #endif
+			},
+			// #endif
+			getUserInfo(e) {
+				// #ifdef H5
+				if (this.$jwx.isWechat()) {
+					this.$request({
+						url: this.$api.registered.url,
+						method: 'get',
+						data: {
+							scope: 'snsapi_userinfo',
+							response_type: 'code',
+							url: `${window.location.href}`
+						}
+					}).then(res => {
+						if (res.code === 0) {
+							this.$storage.setStorageSync('_USER_SIGN', true);
+							window.location.replace(res.data.url);
+						} else {
+							uni.navigateTo({
+								url: '/pages/registered/sign'
+							});
+						}
+					});
+				} else {
+					uni.navigateTo({
+						url: '/pages/registered/sign'
+					});
+				}
+				// #endif
+				// #ifdef MP
+				this.$store.commit('user/showLoginModal', false);
+				const resolve = this.$user.getUserInfoResolve;
+				const reject = this.$user.getUserInfoReject;
+				this.$event.on(this.$const.EVENT_USER_LOGIN, true).then(() => {
+					this.$jump({
+						open_type: 'reload'
+					})
+				});
+				// #ifdef MP-WEIXIN
+				wx.getUserProfile({
+					desc: "用于完善用户资料",
+					success: (res) => {
+						if (res.errMsg !== 'getUserProfile:ok') {
+							this.$store.commit('user/showLoginModal', true);
+							return reject(res.errMsg);
+						} else {
+							return resolve({
+								detail: res
+							});
+						}
+					}
+				})
+				// #endif
+
+				// #ifdef MP-ALIPAY
+				my.getOpenUserInfo({
+					success(openUserInfo) {
+						const response = JSON.parse(openUserInfo.response);
+						e.detail = {
+							rawData: JSON.stringify(response.response),
+							encryptedData: '',
+							iv: '',
+							signature: '',
+						};
+						return resolve(e);
+					},
+					fail(failE) {
+						console.log('getOpenUserInfo:', failE);
+					},
+				});
+				// #endif
+
+				// #ifdef MP-BAIDU
+				e.detail.rawData = JSON.stringify(e.detail.userInfo);
+				e.detail.encryptedData = '';
+				e.detail.iv = '';
+				e.detail.signature = '';
+				return resolve(e);
+				// #endif
+
+				// #ifdef MP-TOUTIAO
+				uni.login({
+					success() {
+						uni.getUserInfo({
+							success(result) {
+								e.detail = {
+									rawData: result.rawData,
+									encryptedData: '',
+									iv: '',
+									signature: '',
+								};
+								return resolve(e);
+							},
+							fail(e) {
+								console.log('getUserInfo fail:', e);
+							},
+						});
+					},
+					fail(e) {
+						console.log('login fail:', e);
+					},
+				});
+				// #endif
+				// #endif
+			},
+			// #ifdef H5
+			getUrlParam(name) {
+				let url = window.location.href.split('#')[0];
+				let search = url.split('?')[1]
+				if (search) {
+					let r = search.substr(0).match(new RegExp('(^|&)' + name + '=([^&]*)(&|$)'))
+					if (r !== null) return unescape(r[2])
+					return null
+				} else {
+					return null
+				}
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	$login-padding: #{200rpx} #{50rpx};
+
+	.login-1 {
+		box-sizing: border-box;
+		position: fixed;
+		top: 0;
+		left: 0;
+		z-index: 10000;
+		width: 100%;
+		height: 100%;
+		background: rgba(0, 0, 0, .5);
+		padding: $login-padding;
+		visibility: hidden;
+		opacity: 0;
+		transition: opacity 200ms;
+
+		.login-btn {
+			display: block;
+			width: 100%;
+			height: 100%;
+			opacity: 0;
+			padding: 0;
+		}
+
+		.login-content {
+			position: relative;
+			width: #{650rpx};
+			height: #{700rpx};
+		}
+
+		.login-img {
+			width: #{650rpx};
+			height: #{700rpx};
+		}
+	}
+
+	.login-1.show {
+		visibility: visible;
+		opacity: 1;
+	}
+</style>

+ 140 - 0
components/basic-component/app-layout/bd-mandatory-attention/bd-mandatory-attention.vue

xqd
@@ -0,0 +1,140 @@
+<template>
+  <view class="app-mandatory-attention">
+      <u-popup v-model="newValue" mode="center" :maskCloseAble="false" border-radius="14" :safeAreaInsetBottom="true" >
+          <view class="bd-model" @touchmove.stop.prevent>
+<!--               <view class="bd-h5" v-if="!isWechat">-->
+<!--                   需要关注公众号才购买-->
+<!--               </view>-->
+              <view class="bd-wechat cross-center dir-top-nowrap" v-if="isWechat">
+                  <view class="bd-title">关注公众号</view>
+                  <image class="bd-logo" :src="userInfo && userInfo.wechat_logo"></image>
+                  <view class="bd-name">{{userInfo.wechat_name}}</view>
+                  <image :src="userInfo && userInfo.qrcode" class="bd-qrcode"></image>
+                  <view class="bd-info">长按识别二维码关注公众号</view>
+              </view>
+              <view class="bd-btn" @click="close">
+                  确认关注
+              </view>
+          </view>
+      </u-popup>
+  </view>
+</template>
+
+<script>
+import uPopup from '../../u-popup/u-popup.vue';
+import {mapGetters} from "vuex";
+
+export default {
+  name: "app-mandatory-attention",
+    data() {
+        return {
+            newValue: true,
+            isWechat: false
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userInfo: 'user/info',
+            showAttentionTwo: 'user/showAttentionTwo',
+        }),
+    },
+    methods: {
+        close() {
+            if (this.showAttentionTwo) {
+                this.$user.getInfo({
+                    refresh: true
+                }).then(() => {
+                    this.newValue = false;
+                });
+                this.$store.dispatch('user/showAttentionTwo', false);
+            } else {
+                this.$request({
+                    url: this.$api.registered.update,
+                    method: 'get'
+                }).then(response => {
+                    if (response.code === 0) {
+                        if (response.data.subscribe === 1) {
+                            this.$user.getInfo({
+                                refresh: true
+                            }).then(() => {
+                                this.newValue = false;
+                            });
+                        } else {
+                            uni.showToast({
+                                icon: 'none',
+                                title: '请关注'
+                            });
+                        }
+                    }
+                });
+            }
+        }
+    },
+    created() {
+        this.isWechat = this.$jwx.isWechat();
+    },
+    components: {
+        uPopup
+    },
+    watch: {
+      newValue: {
+          handler(newVal) {
+              if (newVal === false) {
+                  this.$store.dispatch('user/showAttention', false);
+              }
+          }
+      }
+    }
+}
+</script>
+
+<style scoped lang="scss">
+.bd-model {
+    background-color: #ffffff;
+    width: 630upx;
+}
+.bd-btn {
+    font-size: 30upx;
+    border-top: 1upx solid #f1f1f1;
+    color: #ff4544;
+    line-height: 88upx;
+    text-align: center;
+}
+.bd-h5 {
+    font-size: 32upx;
+    color: #353535;
+    text-align: center;
+    line-height: 204upx;
+}
+.bd-wechat {
+    .bd-title {
+        font-size: 32upx;
+        color: #353535;
+        text-align: center;
+        margin-top: 40upx;
+    }
+    .bd-logo {
+        width: 110upx;
+        height: 110upx;
+        margin-bottom: 10upx;
+        margin-top:30upx;
+    }
+    .bd-name {
+        font-size: 32upx;
+        margin-top: 15upx;
+        margin-bottom:20upx;
+        color: #353535;
+    }
+    .bd-qrcode {
+        width:330upx;
+        height:330upx;
+        margin: 10upx 0;
+    }
+    .bd-info {
+        font-size:22upx;
+        color: #353535;
+        margin-bottom: 40upx;
+        margin-top:10upx;
+    }
+}
+</style>

+ 174 - 0
components/basic-component/app-layout/u-authorized-iphone/u-authorized-iphone.vue

xqd
@@ -0,0 +1,174 @@
+<template>
+    <u-popup v-model="show" mode="center" border-radius="16" :length="630" :maskCloseAble="false">
+        <view class="u-content" >
+            <view class='u-header'>授权获取手机号</view>
+            <view class="u-body dir-top-nowrap cross-center">
+                <image class="u-img" :src="img"></image>
+                <text class="u-text">申请获取您绑定的手机号</text>
+                <!--#ifndef MP-ALIPAY-->
+                <button
+                    hover-class="u-hover-class"
+                    class="u-btn"
+                    open-type="getPhoneNumber"
+                    @getphonenumber="getPhoneNumber"
+                >确认</button>
+                <!--#endif-->
+                <!--#ifdef MP-ALIPAY-->
+                <button
+                    class="u-btn"
+                    hover-class="u-hover-class"
+                    open-type="getAuthorize"
+                    scope='phoneNumber'
+                    @getAuthorize="onGetAuthorize"
+                >点击授权</button>
+                <!--#endif-->
+            </view>
+        </view>
+    </u-popup>
+</template>
+
+<script>
+    import {mapState} from "vuex";
+    import uPopup from '../../u-popup/u-popup.vue';
+
+    export default {
+        name: "u-authorized-iphone",
+        computed: {
+            ...mapState({
+                _app_config: state => state.mallConfig
+            }),
+            showPhone() {
+                return this.$store.state.user.info;
+            },
+            img() {
+                let img = '';
+                // #ifdef MP-WEIXIN
+                img = this._app_config.__wxapp_img.mall.icon_wechat;
+                // #endif
+                // #ifdef MP-ALIPAY
+                img = this._app_config.__wxapp_img.mall.icon_alipay;
+                // #endif
+                // #ifdef MP-TOUTIAO
+                img = this._app_config.__wxapp_img.mall.icon_ttapp;
+                // #endif
+                return img;
+            }
+        },
+        data() {
+            return {
+                // #ifndef MP-ALIPAY
+                code: null,
+                // #endif
+                show: false
+            };
+        },
+        watch: {
+            showPhone: {
+                handler(newVal) {
+                    newVal && this.$validation.isEmpty(newVal.mobile) ? this.show = true : this.show = false;
+                },
+                immediate: true
+            }
+        },
+        created() {
+            // #ifndef MP-ALIPAY
+            let _this= this;
+            uni.login({
+                scopes: 'auth_base',
+                success(res) {
+                    if (res.errMsg === 'login:ok') {
+                        _this.code = res.code;
+                    }
+                }
+            })
+            // #endif
+        },
+        destroyed() {
+            this.show = false;
+        },
+        methods: {
+            // #ifndef MP-ALIPAY
+            getPhoneNumber(e) {
+                if (e.detail.errMsg === 'getPhoneNumber:fail user deny') return;
+                this.$request({
+                    method: 'post',
+                    url: this.$api.phone.binding,
+                    data: {
+                        encryptedData: e.detail.encryptedData,
+                        iv: e.detail.iv,
+                        code: this.code
+                    }
+                }).then(() => {
+                    this.show = false;
+                    this.$store.dispatch('user/refresh');
+                });
+            },
+            // #endif
+            // #ifdef MP-ALIPAY
+            onGetAuthorize() {
+                let _this = this;
+                my.getPhoneNumber({
+                    success: (res) => {
+                        this.$request({
+                            method: 'post',
+                            url: _this.$api.phone.binding,
+                            data: {
+                                data: JSON.parse(res.response).response,
+                            }
+                        }).then(() => {
+                            _this.show = false;
+                            _this.$store.dispatch('user/refresh');
+                        });
+                    },
+                    fail: () => {
+                    }
+                });
+            }
+            // #endif
+        },
+        components: {
+            uPopup
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .u-content {
+        background-color: #ffffff;
+    }
+
+    .u-header {
+        text-align: center;
+        padding: 30upx 0;
+        line-height: 60upx;
+        border-bottom: 1upx solid #eeeeee;
+        color: #353535;
+        font-size: 35upx;
+    }
+    .u-img {
+        width: 88upx;
+        height: 88upx;
+        text-align: center;
+        margin: 32upx 0 40upx;
+    }
+    .u-body {
+        padding: 0 24upx;
+
+    }
+    .u-text {
+        margin-bottom: 40upx;
+        font-size: 26upx;
+        color: #666666;
+    }
+    .u-btn {
+        background-color: #04be01;
+        width: 500upx;
+        height: 80upx;
+        line-height: 80upx;
+        border-radius: 80upx;
+        color: #ffffff;
+        font-size: 30upx;
+        margin-bottom: 40upx;
+        padding: 0;
+    }
+</style>

+ 24 - 0
components/basic-component/app-load-text/app-load-text.vue

xqd
@@ -0,0 +1,24 @@
+<template>
+	<view class="app-load-text">
+		<icon class="app-load image-no-rep image-cover" type></icon>
+	</view>
+</template>
+
+<script>
+    export default {
+        name: 'app-load-data'
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-load-text {
+		width: #{750rpx};
+		text-align: center;
+		height: #{42rpx};
+	}
+	.app-load {
+		width: #{70rpx};
+		height: #{42rpx};
+		background-image: url("./image/load.gif");
+	}
+</style>

BIN
components/basic-component/app-load-text/image/load.gif


+ 109 - 0
components/basic-component/app-loading/app-loading.vue

xqd
@@ -0,0 +1,109 @@
+<template>
+   <view>
+       <view v-if="type === 'global'" class="app-loading app-loading-global">
+           <view class="app-loading-image app-loading-global-image" :style="{'background-image': `url(${background})`}"></view>
+           <text :style="{'color': color}" class="app-loading-global-text" v-if="text">{{text}}</text>
+       </view>
+       <view v-else-if="type === 'toast'" class="app-loading app-loading-toast">
+            <view class="app-loading-image app-loading-toast-image"
+                  :style="{'background-image': `url(${background})`}"
+            ></view>
+            <text :style="{'color': color}" class="app-loading-toast-text" v-if="text">{{text}}</text>
+       </view>
+   </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-loading',
+        props: {
+            type: {
+                type: String,
+                default() {
+                    return "";
+                }
+            },
+            text: {
+                type: String,
+                default() {
+                    return "";
+                }
+            },
+            color: {
+                type: String,
+                default() {
+                    return "";
+                }
+            },
+            backgroundImage: {
+                type: String,
+	            default() {
+                    return "";
+	            }
+            },
+        },
+	    computed: {
+            background: function() {
+                return this.backgroundImage;
+            }
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+
+    .app-loading {
+        position: fixed;
+        z-index: 1501;
+    }
+    .app-loading-toast {
+        top: 50%;
+        left:50%;
+        transform: translate(-50%, -50%);
+    }
+    .app-loading-global {
+        top: 0;
+        width: 100%;
+        height: 100%;
+        background-color: rgba(31,31,31, .5);
+    }
+    .app-loading-global-text {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+    }
+    .app-loading-global-image {
+        position: absolute;
+        top: 40%;
+        left: 50%;
+        width: #{80rpx};
+        height: #{80rpx};
+        border-radius: 50%;
+        transform: translate(-50%, -50%);
+    }
+    .app-loading-toast-image {
+        width: #{130rpx};
+        height: #{130rpx};
+        border-radius: 50%;
+    }
+    .app-loading-toast {
+        width: #{80rpx};
+        height: #{80rpx};
+        background-color: rgba(0, 0, 0, 0.5);
+        border-radius: #{20rpx};
+        color: white;
+        display: flex;
+        flex-direction: column;
+        flex-wrap: nowrap;
+        justify-content:center;
+        align-items: center;
+    }
+    .app-loading-toast-text {
+        font-size:#{30rpx};
+    }
+    .app-loading-image {
+        opacity: .8;
+        background-size: 100% 100%;
+    }
+</style>

+ 199 - 0
components/basic-component/app-model/app-model.vue

xqd
@@ -0,0 +1,199 @@
+<template>
+    <view class="app-mode" :class="{'app-show': display}" @click.stop="close">
+	    <view v-if="type === '1'" class="app-content" :style="{backgroundColor: background, bottom: setHeight, height: `${height+108}rpx`}">
+		    <view class="app-top">
+			    <slot name="title">赠卡券</slot>
+			    <view class="app-icon">
+				    <app-form-id @click="close()">
+					    <icon class="app-icon-close image-no-rep image-cover" type></icon>
+                    </app-form-id>
+                </view>
+            </view>
+            <view class="app-bottom">
+                <slot name="content"></slot>
+            </view>
+        </view>
+        <view v-if="type === '2'" class="app-center" :style="{backgroundColor: background}">
+            <view class="app-top">
+                <slot name="title">限购</slot>
+                <view class="app-icon">
+                    <app-form-id @click.stop="close()">
+                        <icon class="app-icon-close image-no-rep image-cover" type></icon>
+                    </app-form-id>
+                </view>
+            </view>
+            <view class="app-bottom">
+                <slot name="content"></slot>
+            </view>
+        </view>
+
+        <view v-if="type === '3'" class="app-content" :style="{backgroundColor: background, bottom: setHeight}"
+              @click.stop="bubble">
+            <view class="app-common main-center">
+                <slot name="title">提现方式</slot>
+                <view class="app-icon">
+                    <app-form-id @click="close()">
+                        <icon class="app-icon-close image-no-rep image-cover" type></icon>
+                    </app-form-id>
+                </view>
+            </view>
+            <view class="app-bottom">
+                <slot name="content"></slot>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-model',
+	    props: {
+            type: {
+                type: String,
+                default: function() {
+                    return '1';
+                }
+            },
+		    background: {
+                type: String,
+			    default: function() {
+			        return 'white';
+			    }
+		    },
+		    height: {
+                type: Number,
+			    default: function() {
+			        return 500;
+			    }
+		    },
+		    value: {
+                type: Boolean,
+			    default: function() {
+			        return false;
+			    }
+		    }
+	    },
+	    data() {
+            return {
+                display: this.value
+            }
+        },
+        methods: {
+            bubble() {
+                return false;
+            },
+            close() {
+                this.display = false;
+                this.$emit('input', this.display);
+            },
+        },
+        computed: {
+            setHeight() {
+                if (this.display === true) {
+                    return `0`;
+                } else {
+                    return `-${this.height + 108}rpx`;
+                }
+            }
+        },
+        watch: {
+            value: function() {
+                this.display = this.value;
+            }
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-mode {
+        position: fixed;
+        z-index: 1600;
+        top: 0;
+        left: 0;
+        width: #{750rpx};
+        height: 100%;
+        background-color: rgba(127, 127, 127, 0.4);
+        transition: all 0.2s linear;
+        visibility: hidden;
+        opacity: 0;
+        overflow: hidden;
+
+        .app-common {
+            color: #353535;
+            font-size: #{36rpx};
+            width: 100%;
+            line-height: #{100rpx};
+            border-bottom: 1px solid #E2E2E2;
+
+            .app-icon-close {
+                width: #{30rpx};
+                height: #{30rpx};
+                position: absolute;
+                background-image: url("../../../static/image/icon/icon-close.png");
+                top: #{35rpx};
+                right: #{32rpx};
+            }
+        }
+
+        .app-icon {
+            position: absolute;
+            top: 0;
+            right: 0;
+            height: #{108rpx};
+            width: #{54rpx};
+        }
+
+        .app-icon-close {
+            width: #{30rpx};
+            height: #{30rpx};
+            position: absolute;
+            background-image: url("../../../static/image/icon/icon-close.png");
+            top: #{24rpx};
+            right: #{24rpx};
+        }
+
+        .app-content {
+            position: absolute;
+            width: #{750rpx};
+            transition: bottom 0.5s linear;
+            border-top-left-radius: #{16rpx};
+            border-top-right-radius: #{16rpx};
+
+            .app-top {
+                width: #{750rpx};
+                height: #{108rpx};
+            }
+        }
+
+        .app-center {
+            width: #{600rpx};
+            border-radius: #{16rpx};
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+
+            .app-top {
+                width: #{600rpx};
+                height: #{108rpx};
+            }
+
+            .app-bottom {
+                width: #{520rpx};
+                margin: 0 #{40rpx} #{48rpx} #{40rpx};
+                font-size: #{28rpx};
+                color: #353535;
+            }
+        }
+    }
+	.app-show {
+		opacity: 1;
+		visibility: visible;
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        transform: scale(1);
+	}
+</style>

BIN
components/basic-component/app-model/image/close.png


+ 186 - 0
components/basic-component/app-order/app-form-data.vue

xqd
@@ -0,0 +1,186 @@
+<template>
+    <view class="app-form-data">
+        <block v-if="formList && formList.length">
+            <slot></slot>
+            <view v-for="(row,idx) in formList" :class="['goods-form', `${idx == 0? 'more':''}`]" :key="idx">
+                <view v-for="(goods,index) in row" :key="index" v-if="formList.length > 1">
+                    <view class="goods">
+                        <image class="goods-img" :src='goods.goods_info.goods_attr.pic_url ? goods.goods_info.goods_attr.pic_url : goods.goods_info.goods_attr.cover_pic'></image>
+                        <view class='t-omit-two goods-name'>{{goods.goods_info.goods_attr.name}}</view>
+                        <view class="goods-attr t-omit" v-if="goods.goods_type === 'goods'">
+                            <text v-for="attr in goods.attr_list" :key="item.attr_id">{{attr.attr_group_name}}:{{attr.attr_name}}</text>
+                        </view>
+                        <view class="goods-num">x{{goods.num}}</view>
+                        <view class="goods-price">¥{{goods.total_original_price}}</view>
+                    </view>
+                </view>
+                <view v-for="(item,index) in row[0]['form_data']" v-if="item.value" :key="index" :class="[ `${item.key !== 'img_upload' ? 'dir-left-nowrap' : ''}`,`price-item`]">
+                    <view class="price-label">{{item.label}}:</view>
+                    <view v-if="item.key === 'img_upload'">
+                        <view v-if="Array.isArray(item.value)">
+                            <block v-for="(img,key) in item.value" :key="key">
+                                <image v-if="img" v-show="!item.loadOver" @load="imageFormLoad(idx,index)" @click='look(img)' class="form-img" :src='img'></image>
+                            </block>
+                        </view>
+                        <view v-else>
+                            <image v-if="item.value" v-show="!item.loadOver" @load="imageFormLoad(idx,index)" @click='look(item.value)' class="form-img" :src='item.value'></image>
+                        </view>
+                    </view>
+                    <view style="word-break: break-all" v-else>{{item.value}}</view>
+                </view>
+            </view>
+        </block>
+        <view v-if="detail.order_form.length > 0">
+            <slot></slot>
+            <view v-for="(item,index) in detail.order_form" v-if="item.value" :key="index" :class="[ `${item.key !== 'img_upload' ? 'dir-left-nowrap' : ''}`,`price-item`]">
+                <view class="price-label">{{item.label}}:</view>
+                <view v-if="item.key === 'img_upload'">
+                    <view v-if="Array.isArray(item.value)">
+                        <block v-for="(img,key) in item.value" :key="key">
+                            <image v-if="img" v-show="!item.loadOver" @load="imageLoad(index)" @click.stop='look(img)' class="form-img" :src='img'></image>
+                        </block>
+                    </view>
+                    <view v-else>
+                        <image v-if="item.value" v-show="!item.loadOver" @load="imageLoad(index)" @click.stop='look(item.value)' class="form-img" :src='item.value'></image>
+                    </view>
+                </view>
+                <view style="word-break: break-all" v-else>{{item.value}}</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        props: {
+            order: {
+                type: Object,
+            }
+        },
+        data() {
+            return {
+                detail: this.order
+            }
+        },
+        computed: {
+            formList() {
+                const self = this;
+                let orderDetail = self.detail.detail;
+                let newArr = {};
+                let form_ids = [];
+
+                if (orderDetail && orderDetail.length) {
+                    for (let goods of orderDetail) {
+                        if (goods.form_id == '0') {
+                            continue;
+                        }
+                        if (form_ids.indexOf(goods.form_id) === -1) {
+                            form_ids.push(goods.form_id);
+                            newArr[goods.form_id] = [goods];
+                        } else {
+                            newArr[goods.form_id].push(goods);
+                        }
+                    }
+                }
+                let list = Object.values(newArr)
+                this.$emit('show', list);
+                return list;
+            }
+        },
+        watch: {
+            order: {
+                handler (newValue) {
+                    this.detail = newValue
+                },
+                deep: true
+            }
+        },
+        methods: {
+            imageLoad(index) {
+                this.order.order_form[index].loadOver = false;
+            },
+            // 查看图片
+            look(e) {
+                uni.previewImage({
+                    current: e, // 当前显示图片的http链接
+                    urls: [e] // 需要预览的图片http链接列表
+                })
+            },
+            imageFormLoad(idx,index) {
+                this.order.detail[idx].form_data[index].loadOver = false;
+            },
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .app-form-data {
+        .goods-form {
+            border-top: #{2rpx solid #e2e2e2};
+            margin-top: #{20rpx};
+            &.more {
+                border-top: 0;
+                margin-top: #{10rpx};
+            }
+        }
+
+        .form-img {
+            height: #{120rpx};
+            width: #{120rpx};
+            margin-right: #{10rpx};
+            margin-top: #{10rpx};
+        }
+
+        .price-label {
+            color: #999;
+            margin-right: #{10rpx};
+        }
+        .price-item {
+            margin-bottom: #{10rpx};
+            font-size: #{24rpx};
+            color: #353535;
+        }
+
+        .goods {
+            height: #{160rpx};
+            margin-top: #{24rpx};
+            position: relative;
+            font-size: #{24rpx};
+            color: #353535;
+            margin-bottom: #{24rpx};
+            .goods-img {
+                height: #{160rpx};
+                width: #{160rpx};
+                float: left;
+                margin-right: #{20rpx};
+                border-radius: #{4rpx};
+            }
+            .goods-attr {
+                font-size: #{24rpx};
+                color: #999;
+                width: 70%;
+                position: absolute;
+                width: 70%;
+                top: #{78rpx};
+                left: #{180rpx};
+                text {
+                    margin-right: #{20rpx};
+                }
+            }
+            .goods-num {
+                font-size: #{24rpx};
+                color: #999;
+                position: absolute;
+                top: #{126rpx};
+                left: #{180rpx};
+            }
+            .goods-price {
+                font-size: #{24rpx};
+                color: #353535;
+                position: absolute;
+                bottom: 0;
+                right: 0;
+            }
+        }
+    }
+</style>

+ 90 - 0
components/basic-component/app-prompt-box/app-prompt-box.vue

xqd
@@ -0,0 +1,90 @@
+<template>
+	<view class="app-prompt-box">
+		<view class="app-content">
+			<text class="app-title">提示</text>
+			<view class="app-text">
+				{{text}}
+			</view>
+			<view class="app-buttons dir-left-nowrap">
+                <view class="app-button app-close" @click="close(false)">取消</view>
+				<view class="app-line"></view>
+                <view class="app-button app-sure" @click="close(true)">确认</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+    export default {
+        name: 'app-prompt-box',
+	    props: {
+            text: {
+                type: String,
+            }
+	    },
+	    methods: {
+            close(boolean) {
+                this.$emit('click', boolean);
+            }
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-prompt-box {
+		width: 100%;
+		height: 100%;
+		position: fixed;
+		top: 0;
+		left: 0;
+		background-color: rgba(153, 153, 153, 0.3);
+		z-index: 1500;
+		.app-content {
+			width: #{620rpx};
+			border-radius: #{8rpx};
+			background-color: white;
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%, -50%);
+			text-align: center;
+			
+			.app-title {
+				display: inline-block;
+				font-size: #{32rpx};
+				margin-top: #{40rpx};
+				margin-bottom: #{64rpx};
+				color: #353535;
+				text-align: center;
+			}
+			.app-text {
+				font-size: #{32rpx};
+				color: #353535;
+				text-align: center;
+				margin-bottom: #{64rpx};
+			}
+			.app-buttons {
+				border-top: #{1rpx} solid #e2e2e2;
+				.app-button {
+					font-size: #{32rpx};
+					width: #{309.5rpx};
+					height: #{88rpx};
+					line-height: #{88rpx};
+					text-align: center;
+				}
+				.app-sure {
+					color: #ff4544;
+				}
+				.app-close {
+					color: #666666;
+				}
+				.app-line {
+					width: #{1rpx};
+					height: #{32rpx};
+					margin-top: #{30rpx};
+					color: #e2e2e2;
+				}
+			}
+		}
+	}
+</style>

+ 150 - 0
components/basic-component/app-radio/app-radio-group.vue

xqd
@@ -0,0 +1,150 @@
+<template>
+    <view class="app-radio-group">
+        <view style="overflow-y:hidden;">
+        <view class="dir-left-wrap">
+            <template v-for="(item, index) in model">
+                <view :key="index" class="item cross-center dir-left-nowrap" :style="{height: `${height}rpx`,}">
+                    <template v-if="type==='round'">
+                        <view class="item-round text-ellipsis"
+                              :class="[
+                                `white-background`,
+                                item.value?`background`:``,
+                                ]"
+                              :style="{color: color ? color: '#FF4544',
+                                  borderColor: color ? color: '#FF4544',
+                                  backgroundColor: item.value ? color: '#FFFFFF'}"
+                              @click="handleClick(index)">{{item.label}}
+                        </view>
+                    </template>
+                    <template v-else>
+                        <view class="checker">
+                            <app-radio v-model="item.value" type="round" @input="handleInput" :sign="index"></app-radio>
+                        </view>
+                        <view class="label text-ellipsis">{{item.label}}</view>
+                    </template>
+                </view>
+            </template>
+        </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import AppRadio from "./app-radio";
+
+    export default {
+        name: 'app-radio-group',
+        components: {AppRadio},
+        props: {
+            type: {
+                default: 'default',
+            },
+            value: {
+                default: null,
+            },
+            list: {
+                type: Array,
+                default: [],
+            },
+            height: {
+                type: Number,
+                default: 88,
+            },
+            sign: {
+                default: null,
+            },
+            color: {
+                default: '#ff4544',
+            }
+        },
+        data() {
+            const list = this.list;
+            for (let i in list) {
+                if (list[i].label === this.value) {
+                    list[i].value = true;
+                }
+            }
+            return {
+                model: this.list,
+            };
+        },
+        methods: {
+            handleInput(e, index) {
+                if (e === false) {
+                    this.model[index].value = true;
+                    return;
+                }
+                for (let i in this.model) {
+                    if (index != i) {
+                        this.model[i].value = false;
+                    }
+                }
+                this.$emit('input', this.model[index].label, this.sign);
+                this.$emit('change', this.model, this.sign);
+            },
+            handleClick(index) {
+                for (let i in this.model) {
+                    if (i == index) {
+                        if (!this.model[i].value) {
+                            this.model[i].value = true;
+                            this.$emit('input', this.model[index].label, this.sign);
+                            this.$emit('change', this.model, this.sign);
+                        }
+                    } else {
+                        this.model[i].value = false;
+                    }
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .label {
+        color: #666666;
+    }
+
+    .item {
+        margin-bottom: #{12rpx};
+        margin-top: #{12rpx};
+        margin-right: #{10rpx};
+    }
+
+    .item:last-child {
+        margin-right: 0;
+    }
+
+    .checker {
+        margin-right: #{16rpx};
+    }
+
+    .item-round {
+        display: inline-block;
+        height: #{56rpx};
+        line-height: #{54rpx};
+        border: #{2rpx} solid;
+        padding: 0 #{24rpx};
+        border-radius: #{1000rpx};
+        font-size: $uni-font-size-general-one;
+    }
+
+    .background {
+        background-color: #ff4544;
+        color: #ffffff !important;
+    }
+
+    .border {
+        border-color: #ff4544;
+    }
+
+    .color {
+        color: #ff4544;
+    }
+
+    .text-ellipsis {
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: #{420rpx};
+    }
+</style>

+ 111 - 0
components/basic-component/app-radio/app-radio.vue

xqd
@@ -0,0 +1,111 @@
+<template>
+    <view class="app-default" :style="{'width': `${width}rpx`, 'height': `${height}rpx`}" @click.stop="radioSelection">
+        <view
+            v-if="value"
+            styke
+            class="app-default-active"
+            :style="{'background-color': sign ? '' : theme.background}"
+            :class="[
+            {'round-active' : type === 'round'},
+            sign ? theme +'-background' : '',
+            theme
+           ]"
+        ></view>
+        <view v-if="!value" class="app-default-border"
+              :class="{'round-border' : type === 'round'}"
+              :style="{
+              borderColor: borderColor,
+              }"></view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-radio',
+        props: {
+            type: String,
+            theme: [Object,String],
+            value: {
+                default: false,
+                type: Boolean,
+            },
+            width: {
+                type: String,
+                default: '40'
+            },
+            height: {
+                type: String,
+                default: '40'
+            },
+            item: {
+                type: Object,
+                default() {
+                    return {}
+                }
+            },
+            sign: {
+                default: null,
+            },
+            borderColor: {
+                default: '#cccccc',
+            },
+        },
+        data() {
+            return {
+                active: this.value,
+            }
+        },
+        methods: {
+            radioSelection() {
+                this.active = !this.active;
+                this.$emit('input', {active:this.active, item:this.sign});
+                this.$emit('click', {active:this.active, item:this.item});
+            }
+        },
+        watch: {
+            value: {
+                handler(value) {
+                    this.active = value;
+                }
+            }
+        }
+    }
+</script>
+
+<style lang="scss">
+
+    .round-active {
+        border-radius: 50%;
+    }
+
+    .round-border {
+        border-radius: 50%;
+    }
+
+    .app-default {
+        position: relative;
+    }
+
+    .app-default-active {
+        position: absolute;
+        background-image: url("../../../static/image/icon/yes-radio.png");
+        background-size: 100% 100%;
+	    top: 50%;
+	    left: 50%;
+	    transform: translate(-50%, -50%);
+        background-repeat: no-repeat;
+	    width: #{40rpx};
+	    height: #{40rpx};
+    }
+
+    .app-default-border {
+	    position: absolute;
+        border: #{2rpx} solid #cccccc;
+	    top: 50%;
+	    left: 50%;
+	    transform: translate(-50%, -50%);
+	    width: #{40rpx};
+	    height: #{40rpx};
+    }
+
+</style>

BIN
components/basic-component/app-radio/image/yes.png


+ 112 - 0
components/basic-component/app-report-error/app-report-error.vue

xqd
@@ -0,0 +1,112 @@
+<template>
+    <view class="app-view">
+        <view class="app-spring-board dir-top-nowrap main-justify-between">
+            <view class="app-text">
+                <view class="app-title">网络错误</view>
+                <view class="app-content">{{content}}</view>
+            </view>
+            <view class="app-buttons dir-left-nowrap ">
+                <app-form-button @handleClick="copyText" class="app-button">复制错误</app-form-button>
+                <app-form-button @handleClick="refreshPage" class="app-button button-border" color="#7e8dae">刷新页面</app-form-button>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import appFormButton from '../app-form-id/app-form-id.vue';
+
+    export default {
+        name: 'app-prompt-dialog',
+        components: {
+            "app-form-button": appFormButton,
+        },
+        props: {
+            content: String,
+        },
+        computed: {},
+        methods: {
+            handleClick(boolean) {
+                this.$emit('toastModelClick', boolean);
+            },
+            copyText() {
+                this.$store.dispatch('gConfig/reportAndErrorB', false);
+                // #ifndef MP-ALIPAY
+                this.$utils.uniCopy({
+                    data: this.content
+                });
+                // #endif
+                // #ifdef MP-ALIPAY
+                my.setClipboard({
+                    text: this.content // 剪贴板数据
+                });
+                // #endif
+            },
+            refreshPage() {
+                this.$store.dispatch('gConfig/reportAndErrorB', false);
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-view {
+        width: 100%;
+        height: 100%;
+        background-color: rgba(0,0,0, .3);
+        position: absolute;
+        z-index: 1501;
+        top: 0;
+    }
+    .app-spring-board {
+        background-color: #ffffff;
+        width: 80%;
+        min-height: 20%;
+        position: absolute;
+        border-radius: #{10rpx};
+        box-shadow: 0 0 #{2rpx} #000000;
+        left: 50%;
+        top: 50%;
+        transform: translate(-50%, -90%);
+    }
+    .app-buttons {
+        width:100%;
+        height: #{100rpx};
+        border: #{1rpx} solid #dddddd;
+    }
+    .app-button {
+        width: 50%;
+    }
+    .button-border {
+        border-left: #{1rpx} solid #dddddd;
+        color: blue;
+    }
+    .app-button /deep/ form {
+        display: block;
+        width: 100%;
+        height: 100%;
+    }
+    .app-button /deep/ button {
+        display: block;
+        width: 100%;
+        height: 100%;
+        text-align: center;
+        line-height: #{100rpx};
+        background-color: #ffffff;
+    }
+    .app-title {
+        font-weight: bold;
+        font-size: #{39rpx};
+        text-align: center;
+        height: #{70rpx};
+        line-height:  #{70rpx};
+        margin-top: #{30rpx};
+    }
+    .app-content {
+        padding-left: #{20rpx};
+        padding-right: #{20rpx};
+        text-align: center;
+        margin-bottom: #{30rpx};
+        font-size: #{38rpx};
+    }
+</style>

+ 28 - 0
components/basic-component/app-rich/components/wxParseAudio.vue

xqd
@@ -0,0 +1,28 @@
+<template>
+	<!-- '<audio/>' 组件不再维护,建议使用能力更强的 'uni.createInnerAudioContext' 接口 有时间再改-->
+  <!--增加audio标签支持-->
+  <audio
+    :id="node.attr.id"
+    :class="node.classStr"
+    :style="node.styleStr"
+    :src="node.attr.src"
+    :loop="node.attr.loop"
+    :poster="node.attr.poster"
+    :name="node.attr.name"
+    :author="node.attr.author"
+    controls></audio>
+</template>
+
+<script>
+export default {
+  name: 'wxParseAudio',
+  props: {
+    node: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+};
+</script>

+ 114 - 0
components/basic-component/app-rich/components/wxParseImg.vue

xqd
@@ -0,0 +1,114 @@
+<template>
+    <image
+        :lazy-load="newNode.attr.lazyLoad"
+        :class="newNode.classStr"
+        :style="newStyleStr || newNode.styleStr"
+        :data-src="newNode.attr.src"
+        :src="newNode.attr.src"
+        @tap="wxParseImgTap"
+        @load="wxParseImgLoad"
+    />
+</template>
+
+<script>
+    import {mapState} from 'vuex';
+
+    export default {
+        name: 'wxParseImg',
+
+        data() {
+            return {
+                style: '',
+                preview: true
+            };
+        },
+
+        props: {
+            node: {
+                type: Object,
+                default() {
+                    return {};
+                }
+            },
+            parentNode: {}
+        },
+
+        computed: {
+            newStyleStr: function() {
+                if (this.parentNode.styleStr && this.parentNode.styleStr.indexOf('text-align: center') > -1) {
+                    this.style += 'margin: 0 auto';
+                }
+                return this.style;
+            },
+            ...mapState('gConfig', {
+                windowWidth: (state) => {
+                    return state.imageWidth;
+                },
+            }),
+            newNode: function() {
+                return this.node;
+            }
+        },
+        methods: {
+            wxParseImgTap(e) {
+                if (!this.preview) return;
+                const { src } = e.currentTarget.dataset;
+                if (!src) return;
+                let parent = this.$parent;
+                while (!parent.preview || typeof parent.preview !== 'function') {
+                    parent = parent.$parent;
+                }
+                parent.preview(src, e);
+            },
+            // 图片视觉宽高计算函数区
+            wxParseImgLoad(e) {
+                const { src } = e.currentTarget.dataset;
+                if (!src) return;
+                let { width, height } = e.mp.detail;
+                const recal = this.wxAutoImageCal(width, height);
+                const { imageheight, imageWidth } = recal;
+                const { padding, mode } = this.newNode.attr;//删除padding
+                const { styleStr } = this.newNode;
+                const imageHeightStyle = mode === 'widthFix' ? '' : `height: ${imageheight}px;`;
+                this.$nextTick().then(() => {
+                    this.style = `${styleStr ? styleStr: null}; ${imageHeightStyle}; width: ${imageWidth}; padding: 0 ${+padding}px;display:block;`;//删除padding
+                });
+            },
+            wxAutoImageCal(originalWidth, originalHeight) {
+                // 获取图片的原始长宽
+                const windowWidth = this.windowWidth;
+
+                const results = {};
+
+                if (originalWidth < 60 || originalHeight < 60) {
+                    const { src } = this.newNode.attr;
+                    let parent = this.$parent;
+                    while (!parent.preview || typeof parent.preview !== 'function') {
+                        parent = parent.$parent;
+                    }
+                    parent.removeImageUrl(src);
+                    this.preview = false;
+                }
+                results.imageWidth = originalWidth;
+                results.imageheight = originalHeight;
+                // 判断按照那种方式进行缩放
+                if (originalWidth  > windowWidth) {
+                    // 在图片width大于手机屏幕width时候
+                    results.imageWidth = `100%`;
+                    results.imageheight = windowWidth * (uni.upx2px(originalHeight) / uni.upx2px(originalWidth));
+                } else {
+                    // 否则展示原来的数据
+                    results.imageWidth = `${uni.upx2px(originalWidth)}px`;
+                    results.imageheight = uni.upx2px(originalHeight);
+                }
+                return results;
+            }
+        }
+    };
+</script>
+
+<style lang="scss">
+    image:host{
+        width: 702upx;
+    }
+</style>

+ 52 - 0
components/basic-component/app-rich/components/wxParseTable.vue

xqd
@@ -0,0 +1,52 @@
+<template>
+	<rich-text style="width: 100%" :nodes="nodes" :class="node.classStr"></rich-text>
+</template>
+<script>
+export default {
+	name: 'wxParseTable',
+	props: {
+		node: {
+			type: Object,
+			default() {
+				return {};
+			},
+		},
+	},
+	data() {
+		return {
+			nodes:[]
+		};
+	},
+	mounted() {
+		this.nodes=this.loadNode([this.node]);
+	},
+	methods: {
+		loadNode(node) {
+			let obj = [];
+			for (let children of node) {
+				if (children.node=='element') {
+					let t = {
+						name:children.tag,
+						attrs: {
+							class: children.classStr,
+							style: children.styleStr,
+						},
+						children: children.nodes?this.loadNode(children.nodes):[]
+					};
+					
+					obj.push(t)
+				} else if(children.node=='text'){
+					obj.push({
+						type: 'text',
+						text: children.text
+					})
+				}
+			}
+			return obj
+		}
+	}
+};
+</script>
+<style>
+	@import url("../parse.scss");
+</style>

+ 143 - 0
components/basic-component/app-rich/components/wxParseTemplate0.vue

xqd
@@ -0,0 +1,143 @@
+<template>
+	<!--判断是否是标签节点-->
+	<block v-if="node.node === 'element'">
+
+		<!--button类型-->
+		<button v-if="node.tag === 'button'" type="default" size="mini" :class="node.classStr" :style="node.styleStr">
+			<we-parse-template-1 :node="node" />
+		</button>
+		
+		<!--a类型-->
+		<view v-else-if="node.tag === 'a'" @click="wxParseATap(node.attr,$event)" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--div类型-->
+		<view v-else-if="node.tag === 'div'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--span类型-->
+		<view v-else-if="node.tag === 'span'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--p类型-->
+		<view v-else-if="node.tag === 'p'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--ul类型-->
+		<view v-else-if="node.tag === 'ul'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--li类型-->
+		<view v-else-if="node.tag === 'li'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--table类型-->
+		<wx-parse-table v-else-if="node.tag === 'table'" :style="node.styleStr" :node="node" />
+		
+		<!--br类型-->
+		<text v-else-if="node.tag === 'br'">\n</text>
+
+		<!--video类型-->
+		<wx-parse-video :node="node" v-else-if="node.tag === 'video'"/>
+	
+		<!--audio类型-->
+		<wx-parse-audio :node="node" v-else-if="node.tag === 'audio'"/>
+	
+		<!--img类型-->
+		<wx-parse-img :parent-node="parentNode" :node="node" v-else-if="node.tag === 'img'" :style="node.styleStr"/>
+		
+		<!-- strong标签 -->
+		<view v-else-if="node.tag === 'strong'" style="font-weight: bold;display: inline;">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!-- em标签 -->
+		<view v-else-if="node.tag === 'em'" style="font-style: italic">
+			<block v-for="(node, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="node" />
+			</block>
+		</view>
+		
+		<!-- section标签 -->
+		<view v-else-if="node.tag === 'section'" >
+			<block v-for="(node, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="node" />
+			</block>
+		</view>
+		
+		<!--其他标签-->
+		<view v-else :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<we-parse-template-1 :node="item" :parent-node="node"/>
+			</block>
+		</view>
+	</block>
+	
+	<!--判断是否是文本节点-->
+	<block v-else-if="node.node === 'text'">
+		<text>{{node.text}}</text>
+	</block>
+</template>
+
+<script>
+	
+	import wxParseTemplate from './wxParseTemplate1';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+	import wxParseTable from './wxParseTable';
+
+	export default {
+		name: 'wxParseTemplate0',
+
+		props: {
+			node: {
+			    type: Object,
+				default() {
+			        return {}
+				}
+			},
+            parentNode: {}
+		},
+		components: {
+			'we-parse-template-1': wxParseTemplate,
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+			wxParseTable
+		},
+		methods: {
+			wxParseATap(attr,e) {
+				const {
+					href
+				} = e.currentTarget.dataset;// TODO currentTarget才有dataset
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {// TODO 遍历获取父节点执行方法
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e, attr);
+			},
+		}
+	};
+</script>

+ 139 - 0
components/basic-component/app-rich/components/wxParseTemplate1.vue

xqd
@@ -0,0 +1,139 @@
+<template>
+	<!--判断是否是标签节点-->
+	<block v-if="node.node === 'element'">
+		<!--button类型-->
+		<button v-if="node.tag === 'button'" type="default" size="mini" :class="node.classStr" :style="node.styleStr">
+			<wx-parse-template :node="node" />
+		</button>
+		
+		<!--a类型-->
+		<view v-else-if="node.tag === 'a'" @click="wxParseATap(node.attr,$event)" :class="node.classStr" :data-href="node.attr.href" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<wx-parse-template :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--div类型-->
+		<view v-else-if="node.tag === 'div'" :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<wx-parse-template :node="item" :parent-node="node"/>
+			</block>
+		</view>
+		
+		<!--span类型-->
+		<view v-else-if="node.tag === 'span'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--p类型-->
+		<view v-else-if="node.tag === 'p'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--ul类型-->
+		<view v-else-if="node.tag === 'ul'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--li类型-->
+		<view v-else-if="node.tag === 'li'" :class="node.classStr" :style="node.styleStr">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!--table类型-->
+		<wx-parse-table v-else-if="node.tag === 'table'" :class="node.classStr" :style="node.styleStr" :node="node" />
+		
+		<!--br类型-->
+		<text v-else-if="node.tag === 'br'">\n</text>
+		
+		<!--video类型-->
+		<wx-parse-video :node="node" v-else-if="node.tag === 'video'"/>
+	
+		<!--audio类型-->
+		<wx-parse-audio :node="node" v-else-if="node.tag === 'audio'"/>
+	
+		<!--img类型-->
+		<wx-parse-img :parent-node="parentNode" :node="node" v-else-if="node.tag === 'img'" :style="node.styleStr"/>
+		
+		<!-- strong标签 -->
+		<view v-else-if="node.tag === 'strong'" style="display: inline;font-weight: bold;">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!-- em标签 -->
+		<view v-else-if="node.tag === 'em'" style="font-style: italic">
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+		<!-- section标签 -->
+		<view v-else-if="node.tag === 'section'" >
+            <block v-for="(item, index) of node.nodes" :key="index">
+                <wx-parse-template :node="item" :parent-node="node"/>
+            </block>
+		</view>
+		
+<!--		&lt;!&ndash;其他标签&ndash;&gt;-->
+		<view v-else :class="node.classStr" :style="node.styleStr">
+			<block v-for="(item, index) of node.nodes" :key="index">
+				<wx-parse-template :node="item" :parent-node="node"/>
+			</block>
+		</view>
+	</block>
+	
+	<!--判断是否是文本节点-->
+	<block v-else-if="node.node === 'text'">
+		<text>{{node.text}}</text>
+	</block>
+</template>
+
+<script>
+    // #ifdef MP
+	import wxParseTemplate from './wxParseTemplate0';
+	// #endif
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+	import wxParseTable from './wxParseTable';
+	
+	export default {
+		name: 'wxParseTemplate1',
+		props: {
+			node: {},
+            parentNode: {}
+		},
+		components: {
+		    // #ifdef MP
+			wxParseTemplate,
+            // #endif
+			wxParseImg,
+			wxParseVideo,
+			wxParseAudio,
+			wxParseTable
+		},
+		methods: {
+			wxParseATap(attr,e) {
+				const {
+					href
+				} = e.currentTarget.dataset;
+				if (!href) return;
+				let parent = this.$parent;
+				while(!parent.preview || typeof parent.preview !== 'function') {
+					parent = parent.$parent;
+				}
+				parent.navigate(href, e, attr);
+			},
+		},
+	};
+</script>

+ 15 - 0
components/basic-component/app-rich/components/wxParseVideo.vue

xqd
@@ -0,0 +1,15 @@
+<template>
+    <!--增加video标签支持,并循环添加-->
+    <view :class="node.classStr" :style="node.styleStr">
+        <video :class="node.classStr" :style="node.styleStr" class="video-video" :src="node.attr.src"></video>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'wxParseVideo',
+        props: {
+            node: {},
+        },
+    };
+</script>

+ 234 - 0
components/basic-component/app-rich/libs/html2json.js

xqd
@@ -0,0 +1,234 @@
+import wxDiscode from './wxDiscode';
+import HTMLParser from './htmlparser';
+
+function makeMap(str) {
+    const obj = {};
+    const items = str.split(',');
+    for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+    return obj;
+}
+
+const block = makeMap('br,code,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+function removeDOCTYPE(html) {
+    return /<body.*>([^]*)<\/body>/.test(html) ? RegExp.$1 : html;
+}
+
+function trimHtml(html) {
+    return html.replace(/<!--.*?-->/gi, '').replace(/\/\*.*?\*\//gi, '').replace(/[ ]+</gi, '<').replace(/<script[^]*<\/script>/gi, '').replace(/<style[^]*<\/style>/gi, '');
+}
+
+function getScreenInfo() {
+    const screen = {};
+    wx.getSystemInfo({
+        success: (res) => {
+            screen.width = res.windowWidth;
+            screen.height = res.windowHeight;
+        },
+    });
+    return screen;
+}
+
+function html2json(html, customHandler, imageProp, host) {
+    // 处理字符串
+    html = removeDOCTYPE(html);
+    // 去除注释 样式 js
+    html = trimHtml(html);
+    html = wxDiscode.strDiscode(html);
+    const bufArray = [];
+
+    const results = {
+        nodes: [],
+        imageUrls: [],
+    };
+
+    const screen = getScreenInfo();
+    function Node(tag) {
+        this.node = 'element';
+        this.tag = tag;
+        this.$screen = screen;
+    }
+
+    HTMLParser(html, {
+        start(tag, attrs, unary) {
+            const node = new Node(tag);
+
+            if (bufArray.length !== 0) {
+                const parent = bufArray[0];
+                if (parent.nodes === undefined) {
+                    parent.nodes = [];
+                }
+            }
+
+            if (block[tag]) {
+                node.tagType = 'block';
+            } else if (inline[tag]) {
+                node.tagType = 'inline';
+            } else if (closeSelf[tag]) {
+                node.tagType = 'closeSelf';
+            }
+
+            node.attr = attrs.reduce((pre, attr) => {
+                const { name } = attr;
+                let { value } = attr;
+                if (name === 'class') {
+                    node.classStr = value;
+                }
+
+                if (name === 'style') {
+                    node.styleStr = value;
+                }
+
+                if (value.match(/ /)) {
+                    value = value.split(' ');
+                }
+
+                // merge it
+                if (pre[name]) {
+                    if (Array.isArray(pre[name])) {
+                        // already array, push to last
+                        pre[name].push(value);
+                    } else {
+                        // single value, make it array
+                        pre[name] = [pre[name], value];
+                    }
+                } else {
+                    // not exist, put it
+                    pre[name] = value;
+                }
+
+                return pre;
+            }, {});
+
+            // 优化样式相关属性
+            if (node.classStr) {
+                node.classStr += ` ${node.tag}`;
+            } else {
+                node.classStr = node.tag;
+            }
+            if (node.tagType === 'inline') {
+                node.classStr += ' inline';
+            }
+
+            // 对img添加额外数据
+            if (node.tag === 'img') {
+                let imgUrl = node.attr.src;
+                imgUrl = wxDiscode.urlToHttpUrl(imgUrl, imageProp.domain);
+                Object.assign(node.attr, imageProp, {
+                    src: imgUrl || '',
+                });
+                if (imgUrl) {
+                    results.imageUrls.push(imgUrl);
+                }
+            }
+            // 处理a标签属性
+            if (node.tag === 'a') {
+                node.attr.href = node.attr.href || '';
+            }
+
+            // 处理font标签样式属性
+            if (node.tag === 'font') {
+                const fontSize = [
+                    'x-small',
+                    'small',
+                    'medium',
+                    'large',
+                    'x-large',
+                    'xx-large',
+                    '-webkit-xxx-large',
+                ];
+                const styleAttrs = {
+                    color: 'color',
+                    face: 'font-family',
+                    size: 'font-size',
+                };
+                if (!node.styleStr) node.styleStr = '';
+                Object.keys(styleAttrs).forEach((key) => {
+                    if (node.attr[key]) {
+                        const value = key === 'size' ? fontSize[node.attr[key] - 1] : node.attr[key];
+                        node.styleStr += `${styleAttrs[key]}: ${value};`;
+                    }
+                });
+            }
+
+            // 临时记录source资源
+            if (node.tag === 'source') {
+                results.source = node.attr.src;
+            }
+
+            // #ifndef MP-BAIDU
+            if (customHandler.start) {
+                customHandler.start(node, results);
+            }
+            // #endif
+
+            if (unary) {
+                // if this tag doesn't have end tag
+                // like <img src="hoge.png"/>
+                // add to parents
+                const parent = bufArray[0] || results;
+                if (parent.nodes === undefined) {
+                    parent.nodes = [];
+                }
+                parent.nodes.push(node);
+            } else {
+                bufArray.unshift(node);
+            }
+        },
+        end(tag) {
+            const node = bufArray.shift();
+            if (node.tag !== tag) {
+                console.error('invalid state: mismatch end tag');
+            }
+
+            if (node.tag === 'video' && results.source) {
+                node.attr.src = results.source;
+                delete results.source;
+            }
+
+            if (customHandler.end) {
+                customHandler.end(node, results);
+            }
+
+            if (bufArray.length === 0) {
+                results.nodes.push(node);
+            } else {
+                const parent = bufArray[0];
+                if (!parent.nodes) {
+                    parent.nodes = [];
+                }
+                parent.nodes.push(node);
+            }
+        },
+        chars(text) {
+            if (!text.trim()) return;
+
+            const node = {
+                node: 'text',
+                text,
+            };
+
+            if (customHandler.chars) {
+                customHandler.chars(node, results);
+            }
+
+            if (bufArray.length === 0) {
+                results.nodes.push(node);
+            } else {
+                const parent = bufArray[0];
+                if (parent.nodes === undefined) {
+                    parent.nodes = [];
+                }
+                parent.nodes.push(node);
+            }
+        },
+    });
+
+    return results;
+}
+
+export default html2json;

+ 126 - 0
components/basic-component/app-rich/libs/htmlparser.js

xqd
@@ -0,0 +1,126 @@
+
+const startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z0-9_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+const endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+const attr = /([a-zA-Z0-9_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
+
+function makeMap(str) {
+    const obj = {};
+    const items = str.split(',');
+    for (let i = 0; i < items.length; i += 1) obj[items[i]] = true;
+    return obj;
+}
+
+const empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr');
+
+const block = makeMap('address,code,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video');
+
+const inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var');
+
+const closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
+
+const fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected');
+
+function HTMLParser(html, handler) {
+    let index;
+    let chars;
+    let match;
+    let last = html;
+    const stack = [];
+
+    stack.last = () => stack[stack.length - 1];
+
+    function parseEndTag(tag, tagName) {
+        let pos;
+        if (!tagName) {
+            pos = 0;
+        } else {
+            tagName = tagName.toLowerCase();
+            for (pos = stack.length - 1; pos >= 0; pos -= 1) {
+                if (stack[pos] === tagName) break;
+            }
+        }
+        if (pos >= 0) {
+            for (let i = stack.length - 1; i >= pos; i -= 1) {
+                if (handler.end) handler.end(stack[i]);
+            }
+            stack.length = pos;
+        }
+    }
+
+    function parseStartTag(tag, tagName, rest, unary) {
+        tagName = tagName.toLowerCase();
+
+        if (block[tagName]) {
+            while (stack.last() && inline[stack.last()]) {
+                parseEndTag('', stack.last());
+            }
+        }
+
+        if (closeSelf[tagName] && stack.last() === tagName) {
+            parseEndTag('', tagName);
+        }
+
+        unary = empty[tagName] || !!unary;
+
+        if (!unary) stack.push(tagName);
+
+        if (handler.start) {
+            const attrs = [];
+
+            rest.replace(attr, function genAttr(matches, name) {
+                const value = arguments[2] || arguments[3] || arguments[4] || (fillAttrs[name] ? name : '');
+
+                attrs.push({
+                    name,
+                    value,
+                    escaped: value.replace(/(^|[^\\])"/g, '$1\\"'), // "
+                });
+            });
+
+            if (handler.start) {
+                handler.start(tagName, attrs, unary);
+            }
+        }
+    }
+
+    while (html) {
+        chars = true;
+
+        if (html.indexOf('</') === 0) {
+            match = html.match(endTag);
+            if (match) {
+                html = html.substring(match[0].length);
+                match[0].replace(endTag, parseEndTag);
+                chars = false;
+            }
+        } else if (html.indexOf('<') === 0) {
+            match = html.match(startTag);
+            if (match) {
+                html = html.substring(match[0].length);
+                match[0].replace(startTag, parseStartTag);
+                chars = false;
+            }
+        }
+
+        if (chars) {
+            index = html.indexOf('<');
+            let text = '';
+            while (index === 0) {
+                text += '<';
+                html = html.substring(1);
+                index = html.indexOf('<');
+            }
+            text += index < 0 ? html : html.substring(0, index);
+            html = index < 0 ? '' : html.substring(index);
+
+            if (handler.chars) handler.chars(text);
+        }
+
+        if (html === last) throw new Error(`Parse Error: ${html}`);
+        last = html;
+    }
+
+    parseEndTag();
+}
+
+export default HTMLParser;

+ 210 - 0
components/basic-component/app-rich/libs/wxDiscode.js

xqd
@@ -0,0 +1,210 @@
+// HTML 支持的数学符号
+function strNumDiscode(str) {
+    str = str.replace(/&forall;/g, '∀');
+    str = str.replace(/&part;/g, '∂');
+    str = str.replace(/&exist;/g, '∃');
+    str = str.replace(/&empty;/g, '∅');
+    str = str.replace(/&nabla;/g, '∇');
+    str = str.replace(/&isin;/g, '∈');
+    str = str.replace(/&notin;/g, '∉');
+    str = str.replace(/&ni;/g, '∋');
+    str = str.replace(/&prod;/g, '∏');
+    str = str.replace(/&sum;/g, '∑');
+    str = str.replace(/&minus;/g, '−');
+    str = str.replace(/&lowast;/g, '∗');
+    str = str.replace(/&radic;/g, '√');
+    str = str.replace(/&prop;/g, '∝');
+    str = str.replace(/&infin;/g, '∞');
+    str = str.replace(/&ang;/g, '∠');
+    str = str.replace(/&and;/g, '∧');
+    str = str.replace(/&or;/g, '∨');
+    str = str.replace(/&cap;/g, '∩');
+    str = str.replace(/&cup;/g, '∪');
+    str = str.replace(/&int;/g, '∫');
+    str = str.replace(/&there4;/g, '∴');
+    str = str.replace(/&sim;/g, '∼');
+    str = str.replace(/&cong;/g, '≅');
+    str = str.replace(/&asymp;/g, '≈');
+    str = str.replace(/&ne;/g, '≠');
+    str = str.replace(/&le;/g, '≤');
+    str = str.replace(/&ge;/g, '≥');
+    str = str.replace(/&sub;/g, '⊂');
+    str = str.replace(/&sup;/g, '⊃');
+    str = str.replace(/&nsub;/g, '⊄');
+    str = str.replace(/&sube;/g, '⊆');
+    str = str.replace(/&supe;/g, '⊇');
+    str = str.replace(/&oplus;/g, '⊕');
+    str = str.replace(/&otimes;/g, '⊗');
+    str = str.replace(/&perp;/g, '⊥');
+    str = str.replace(/&sdot;/g, '⋅');
+    return str;
+}
+
+// HTML 支持的希腊字母
+function strGreeceDiscode(str) {
+    str = str.replace(/&Alpha;/g, 'Α');
+    str = str.replace(/&Beta;/g, 'Β');
+    str = str.replace(/&Gamma;/g, 'Γ');
+    str = str.replace(/&Delta;/g, 'Δ');
+    str = str.replace(/&Epsilon;/g, 'Ε');
+    str = str.replace(/&Zeta;/g, 'Ζ');
+    str = str.replace(/&Eta;/g, 'Η');
+    str = str.replace(/&Theta;/g, 'Θ');
+    str = str.replace(/&Iota;/g, 'Ι');
+    str = str.replace(/&Kappa;/g, 'Κ');
+    str = str.replace(/&Lambda;/g, 'Λ');
+    str = str.replace(/&Mu;/g, 'Μ');
+    str = str.replace(/&Nu;/g, 'Ν');
+    str = str.replace(/&Xi;/g, 'Ν');
+    str = str.replace(/&Omicron;/g, 'Ο');
+    str = str.replace(/&Pi;/g, 'Π');
+    str = str.replace(/&Rho;/g, 'Ρ');
+    str = str.replace(/&Sigma;/g, 'Σ');
+    str = str.replace(/&Tau;/g, 'Τ');
+    str = str.replace(/&Upsilon;/g, 'Υ');
+    str = str.replace(/&Phi;/g, 'Φ');
+    str = str.replace(/&Chi;/g, 'Χ');
+    str = str.replace(/&Psi;/g, 'Ψ');
+    str = str.replace(/&Omega;/g, 'Ω');
+
+    str = str.replace(/&alpha;/g, 'α');
+    str = str.replace(/&beta;/g, 'β');
+    str = str.replace(/&gamma;/g, 'γ');
+    str = str.replace(/&delta;/g, 'δ');
+    str = str.replace(/&epsilon;/g, 'ε');
+    str = str.replace(/&zeta;/g, 'ζ');
+    str = str.replace(/&eta;/g, 'η');
+    str = str.replace(/&theta;/g, 'θ');
+    str = str.replace(/&iota;/g, 'ι');
+    str = str.replace(/&kappa;/g, 'κ');
+    str = str.replace(/&lambda;/g, 'λ');
+    str = str.replace(/&mu;/g, 'μ');
+    str = str.replace(/&nu;/g, 'ν');
+    str = str.replace(/&xi;/g, 'ξ');
+    str = str.replace(/&omicron;/g, 'ο');
+    str = str.replace(/&pi;/g, 'π');
+    str = str.replace(/&rho;/g, 'ρ');
+    str = str.replace(/&sigmaf;/g, 'ς');
+    str = str.replace(/&sigma;/g, 'σ');
+    str = str.replace(/&tau;/g, 'τ');
+    str = str.replace(/&upsilon;/g, 'υ');
+    str = str.replace(/&phi;/g, 'φ');
+    str = str.replace(/&chi;/g, 'χ');
+    str = str.replace(/&psi;/g, 'ψ');
+    str = str.replace(/&omega;/g, 'ω');
+    str = str.replace(/&thetasym;/g, 'ϑ');
+    str = str.replace(/&upsih;/g, 'ϒ');
+    str = str.replace(/&piv;/g, 'ϖ');
+    str = str.replace(/&middot;/g, '·');
+    return str;
+}
+
+function strcharacterDiscode(str) {
+    // 加入常用解析
+    str = str.replace(/&nbsp;/g, ' ');
+    str = str.replace(/&ensp;/g, ' ');
+    str = str.replace(/&emsp;/g, ' ');
+    str = str.replace(/&quot;/g, "'");
+    str = str.replace(/&amp;/g, '&');
+    str = str.replace(/&lt;/g, '<');
+    str = str.replace(/&gt;/g, '>');
+    str = str.replace(/&#8226;/g, '•');
+
+    return str;
+}
+
+// HTML 支持的其他实体
+function strOtherDiscode(str) {
+    str = str.replace(/&OElig;/g, 'Œ');
+    str = str.replace(/&oelig;/g, 'œ');
+    str = str.replace(/&Scaron;/g, 'Š');
+    str = str.replace(/&scaron;/g, 'š');
+    str = str.replace(/&Yuml;/g, 'Ÿ');
+    str = str.replace(/&fnof;/g, 'ƒ');
+    str = str.replace(/&circ;/g, 'ˆ');
+    str = str.replace(/&tilde;/g, '˜');
+    str = str.replace(/&ensp;/g, '');
+    str = str.replace(/&emsp;/g, '');
+    str = str.replace(/&thinsp;/g, '');
+    str = str.replace(/&zwnj;/g, '');
+    str = str.replace(/&zwj;/g, '');
+    str = str.replace(/&lrm;/g, '');
+    str = str.replace(/&rlm;/g, '');
+    str = str.replace(/&ndash;/g, '–');
+    str = str.replace(/&mdash;/g, '—');
+    str = str.replace(/&lsquo;/g, '‘');
+    str = str.replace(/&rsquo;/g, '’');
+    str = str.replace(/&sbquo;/g, '‚');
+    str = str.replace(/&ldquo;/g, '“');
+    str = str.replace(/&rdquo;/g, '”');
+    str = str.replace(/&bdquo;/g, '„');
+    str = str.replace(/&dagger;/g, '†');
+    str = str.replace(/&Dagger;/g, '‡');
+    str = str.replace(/&bull;/g, '•');
+    str = str.replace(/&hellip;/g, '…');
+    str = str.replace(/&permil;/g, '‰');
+    str = str.replace(/&prime;/g, '′');
+    str = str.replace(/&Prime;/g, '″');
+    str = str.replace(/&lsaquo;/g, '‹');
+    str = str.replace(/&rsaquo;/g, '›');
+    str = str.replace(/&oline;/g, '‾');
+    str = str.replace(/&euro;/g, '€');
+    str = str.replace(/&trade;/g, '™');
+
+    str = str.replace(/&larr;/g, '←');
+    str = str.replace(/&uarr;/g, '↑');
+    str = str.replace(/&rarr;/g, '→');
+    str = str.replace(/&darr;/g, '↓');
+    str = str.replace(/&harr;/g, '↔');
+    str = str.replace(/&crarr;/g, '↵');
+    str = str.replace(/&lceil;/g, '⌈');
+    str = str.replace(/&rceil;/g, '⌉');
+
+    str = str.replace(/&lfloor;/g, '⌊');
+    str = str.replace(/&rfloor;/g, '⌋');
+    str = str.replace(/&loz;/g, '◊');
+    str = str.replace(/&spades;/g, '♠');
+    str = str.replace(/&clubs;/g, '♣');
+    str = str.replace(/&hearts;/g, '♥');
+
+    str = str.replace(/&diams;/g, '♦');
+    str = str.replace(/&#39;/g, "'");
+    return str;
+}
+
+function strDiscode(str) {
+    str = strNumDiscode(str);
+    str = strGreeceDiscode(str);
+    str = strcharacterDiscode(str);
+    str = strOtherDiscode(str);
+    return str;
+}
+
+function urlToHttpUrl(url, domain) {
+    if (/^\/\//.test(url)) {
+        return `https:${url}`;
+    } else if (/^\//.test(url)) {
+        return `https://${domain}${url}`;
+    } else if (Array.isArray(url)) {
+        return arrUrl(url, domain);
+    }
+    return url;
+}
+
+function arrUrl(data, domain) {
+    for (let i = 0; i < data.length; i++) {
+        if (data[i] !== '') {
+            if (/^\/\//.test(data[i])) {
+                return `https:${data[i]}`;
+            } else if (/^\//.test(data[i])) {
+                return `https://${domain}${data[i]}`;
+            }
+            return data[i];
+        }
+    }
+}
+
+export default {
+    strDiscode,
+    urlToHttpUrl,
+};

+ 228 - 0
components/basic-component/app-rich/parse.scss

xqd
@@ -0,0 +1,228 @@
+.wxParse {
+	user-select:none;
+	width: 100%;
+	color: #333;
+	line-height: 1.5;
+	font-size: 1em;
+	text-align:justify;/* //左右两端对齐 */
+	white-space: pre-wrap
+}
+.wxParse view ,.wxParse uni-view{
+	word-break: break-word;
+}
+.wxParse .p {
+	padding-bottom: 0.5em;
+	clear: both;
+	/* letter-spacing: 0;//字间距 */
+}
+.wxParse .inline {
+  display: inline;
+  margin: 0;
+  padding: 0;
+}
+
+.wxParse .div {
+  margin: 0;
+  padding: 0;
+  display: block;
+}
+
+.wxParse .h1{
+  font-size: 2em;
+  line-height: 1.2em;
+  margin: 0.67em 0;
+}
+.wxParse .h2{
+  font-size: 1.5em;
+  margin: 0.83em 0;
+}
+.wxParse .h3{
+  font-size: 1.17em;
+  margin: 1em 0;
+}
+.wxParse .h4{
+  margin: 1.33em 0;
+}
+.wxParse .h5{
+  font-size: 0.83em;
+  margin: 1.67em 0;
+}
+.wxParse .h6{
+  font-size: 0.83em;
+  margin: 1.67em 0;
+}
+
+.wxParse .h1,
+.wxParse .h2,
+.wxParse .h3,
+.wxParse .h4,
+.wxParse .h5,
+.wxParse .h6,
+.wxParse .b,
+.wxParse .strong{
+  font-weight: bolder;
+}
+
+.wxParse .i,
+.wxParse .cite,
+.wxParse .em,
+.wxParse .var,
+.wxParse .address {
+  font-style: italic;
+}
+
+.wxParse .pre,
+.wxParse .tt,
+.wxParse .code,
+.wxParse .kbd,
+.wxParse .samp {
+  font-family: monospace;
+}
+.wxParse .pre {
+  overflow: auto;
+  background: #f5f5f5;
+  padding: 16rpx;
+  white-space: pre;
+  margin: 1em 0rpx;
+}
+.wxParse .code {
+  display: inline;
+  background: #f5f5f5;
+}
+
+.wxParse .big {
+  font-size: 1.17em;
+}
+
+.wxParse .small,
+.wxParse .sub,
+.wxParse .sup {
+  font-size: 0.83em;
+}
+
+.wxParse .sub {
+  vertical-align: sub;
+}
+.wxParse .sup {
+  vertical-align: super;
+}
+
+.wxParse .s,
+.wxParse .strike,
+.wxParse .del {
+  text-decoration: line-through;
+}
+
+.wxParse .strong,
+.wxParse .s {
+  display: inline;
+}
+
+.wxParse .a {
+  color: deepskyblue;
+}
+
+.wxParse .video {
+  text-align: center;
+  margin: 22rpx 0;
+}
+
+.wxParse .video-video {
+  width: 100%;
+}
+.wxParse .uni-image{
+	max-width: 100%;
+}
+.wxParse .img {
+  display: block;
+  max-width: 100%;
+  margin-bottom: 0em;/* //与p标签底部padding同时修改 */
+  overflow: hidden;
+}
+
+.wxParse .blockquote {
+  margin: 10rpx 0;
+  padding: 22rpx 0 22rpx 22rpx;
+  //font-family: Courier, Calibri, '宋体';
+  background: #f5f5f5;
+  border-left: 6rpx solid #dbdbdb;
+}
+.wxParse .blockquote .p {
+  margin: 0;
+}
+.wxParse .ul, .wxParse .ol {
+  display: block;
+  margin: 1em 0;
+  padding-left: 2em;
+}
+.wxParse .ol {
+  list-style-type: disc;
+}
+.wxParse .ol {
+  list-style-type: decimal;
+}
+.wxParse .ol>weixin-parse-template,.wxParse .ul>weixin-parse-template {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+
+.wxParse .ol>.li,.wxParse .ul>.li {
+  display: list-item;
+  align-items: baseline;
+  text-align: match-parent;
+}
+.wxParse .ul .ul, .wxParse .ol .ul {
+  list-style-type: circle;
+}
+.wxParse .ol .ol .ul, .wxParse .ol .ul .ul, .wxParse .ul .ol .ul, .wxParse .ul .ul .ul {
+    list-style-type: square;
+}
+
+.wxParse .u {
+  text-decoration: underline;
+}
+.wxParse .hide {
+  display: none;
+}
+.wxParse .del {
+  display: inline;
+}
+.wxParse .figure {
+  overflow: hidden;
+}
+.wxParse .table .table{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	/* 内边框 */
+	border: 1px solid #dadada;
+	width: 100%;
+}
+.wxParse .tbody{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	/* 内边框 */
+	border: 1px solid #dadada;
+}
+.wxParse .thead, .wxParse .tfoot, .wxParse .th{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	background: #ececec;
+	font-weight: 40;
+}
+.wxParse .tr {
+	border-collapse:collapse;
+	box-sizing: border-box;
+	border: 2px solid #F0AD4E; 
+	overflow:auto;
+}
+.wxParse .th,
+.wxParse .td{
+	border-collapse:collapse;
+	box-sizing: border-box;
+	border: 2rpx solid #dadada;
+	overflow:auto;
+}
+.wxParse .audio, .wxParse .uni-audio-default{
+	display: block;
+}

+ 139 - 0
components/basic-component/app-rich/parse.vue

xqd
@@ -0,0 +1,139 @@
+
+<template>
+	<!--基础元素-->
+	<view class="wxParse" :class="className" :style="'user-select:' + userSelect + ';background-color: ' + background">
+		<template v-if="!loading">
+			<block v-for="(node, index) of nodes" :key="index"  >
+				<wxParseTemplate :node="node" :parent-node="nodes"/>
+			</block>
+		</template>
+	</view>
+</template>
+
+<script>
+import HtmlToJson from './libs/html2json';
+import wxParseTemplate from './components/wxParseTemplate0';
+
+export default {
+	name: 'wxParse',
+	props: {
+		userSelect:{
+			type:String,
+			default:'text'
+		},
+		imgOptions:{
+			type:[Object,Boolean],
+			default:function(){
+				return {
+					loop: false,
+					indicator:'number',
+					longPressActions:false
+				}
+			}
+		},
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		background: {
+			type: String,
+			default: '#ffffff'
+		},
+		className: {
+			type: String,
+			default: ''
+		},
+		content: {
+			type: String,
+			default: ''
+		},
+		noData: {
+			type: String,
+			default: ''
+		},
+		startHandler: {
+			type: Function,
+			default() {
+				return node => {
+					node.attr.class = null;
+					node.attr.style = null;
+				};
+			}
+		},
+		endHandler: {
+			type: Function,
+			default: null
+		},
+		charsHandler: {
+			type: Function,
+			default: null
+		},
+		imageProp: {
+			type: Object,
+			default() {
+				return {
+					mode: 'aspectFit',
+					padding: 0,
+					lazyLoad: false,
+					domain: ''
+				};
+			}
+		},
+	},
+	components: {
+		wxParseTemplate,
+	},
+	data() {
+		return {
+			nodes:{},
+			imageUrls: [],
+			wxParseWidth:{
+				value:0
+			}
+		};
+	},
+	mounted() {
+		this.setHtml();
+	},
+	methods: {
+		setHtml(){
+			/* console.log(this,'测试接受数据'); */
+			let { content, noData, imageProp, startHandler, endHandler, charsHandler } = this;
+			let parseData = content || noData;
+			let customHandler = {
+				start: startHandler,
+				end: endHandler,
+				chars: charsHandler
+			};
+			let results = HtmlToJson(parseData, customHandler, imageProp, this);
+			this.imageUrls = results.imageUrls;
+			this.nodes = results.nodes;
+		},
+		navigate(href, $event) {
+			this.$emit('navigate', href, $event);
+		},
+		preview(src, $event) {
+			if (!this.imageUrls.length || typeof this.imgOptions === 'boolean'){
+			} else {
+				uni.previewImage({
+					current: src,
+					urls: this.imageUrls,
+					loop: this.imgOptions.loop,
+					indicator: this.imgOptions.indicator,
+					longPressActions: this.imgOptions.longPressActions
+				});
+			}
+			this.$emit('preview', src, $event);
+		},
+		removeImageUrl(src) {
+			const { imageUrls } = this;
+			imageUrls.splice(imageUrls.indexOf(src), 1);
+		}
+	},
+	watch: {
+      content() {
+        this.setHtml();
+      }
+    }
+};
+</script>

+ 82 - 0
components/basic-component/app-switch-tab/app-switch-tab.vue

xqd
@@ -0,0 +1,82 @@
+<template>
+    <view class='app-switch-tab dir-left-nowrap'
+          :class="isborderBottom ? 'tab-bottom-border' : ''"
+          :style="{'background-color':bgColor,'height': tabHeight + 'rpx'}">
+        <view v-for="(item, index) in list" :key="index" @click='tabClick(index)' class='box-grow-1 main-center'>
+            <view class="item cross-center" :class="dIndex == index ? theme + '-m-text active-item ' + theme : 'no-active-item'">
+                {{item.name}}
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        props: {
+            bgColor: {
+                type: String,
+                default: '#fff'
+            },
+            list: {
+                type: Array,
+                default: []
+            },
+            tabHeight: {
+                // 高度
+                type: Number,
+                default: 80
+            },
+            currentIndex: {
+                // 当前点击的元素索引
+                type: Number,
+                default: 0,
+            },
+            isborderBottom: {
+                type: Boolean,
+                default: true
+            },
+            theme: {
+                type: String,
+                default: 'default'
+            }
+        },
+        data() {
+            return {}
+        },
+        computed: {
+            dIndex: function () {
+                return this.currentIndex;
+            }
+        },
+        methods: {
+            tabClick(index) {
+                this.$emit('tabEvent', {
+                    currentIndex: index
+                });
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .item {
+        height: 100%;
+    }
+   
+    .tab-bottom-border {
+        border-bottom: #{1rpx} solid $uni-weak-color-one;
+    }
+
+    .no-active-item {
+        border-bottom: #{4rpx} solid trasparent;
+    }
+    .active-item {
+        border-bottom: #{4rpx} solid;
+    }
+    .default-m-text {
+        color: #ff4544;
+    }
+    .blue-m-text {
+        color: #446dfd;
+    }
+</style>

+ 104 - 0
components/basic-component/app-tab-bar/app-tab-bar.vue

xqd
@@ -0,0 +1,104 @@
+<template>
+    <view class="app-navigation-bar safe-area-inset-bottom" :style="{backgroundColor: bottom_background_color}" :class="{'app-tab-bar-shadow': shadow}" >
+        <view v-for="(item, index) in tabBarNavs.navs" :key="index" class="app-tab-bar-item box-grow-1" :style="{height: botNavHei + 'rpx',backgroundColor: bottom_background_color, width: `${100/tabBarNavs.navs.length}%`}">
+            <app-jump-button :backgroundColor="bottom_background_color"
+                             form
+                             class="app-button"
+                             :url="item.url"
+                             :open_type="item.open_type"
+                             :params="item.params"
+                             arrangement="column"
+            >
+	            <image class="app-icon" :src=" router === item.url ? item.active_icon : item.icon"></image>
+                <text class="app-nav-text" v-bind:style="{'color': router === item.url ? item.active_color : item.color}">
+                    {{item.text}}
+                </text>
+            </app-jump-button>
+        </view>
+    </view>
+</template>
+
+<script>
+    import { mapGetters, mapState } from 'vuex';
+    
+    export default {
+        data() {
+              return {
+                    router: '',
+              }
+        },
+        props: {
+            pageCount: Number,
+        },
+        computed: {
+            ...mapGetters('mallConfig', {
+                tabBarNavs: 'getNavBar'
+            }),
+	        ...mapGetters('iPhoneX', {
+                botNavHei: 'getNavHei',
+	        }),
+            ...mapState('mallConfig', {
+                bottom_background_color: state => state.navbar.bottom_background_color,
+                shadow: state => state.navbar.shadow,
+            }),
+        },
+        // #ifdef MP
+	    created() {
+            this.router = this.$platDiff.tabBarUrl(null, this.pageCount);
+        },
+        // #endif
+        watch: {
+            // #ifdef H5
+            '$route': {
+                handler: function(data) {
+                    let { query, meta } = data;
+                    let str = '?';
+                    for (let key in query) {
+                        str += `${key}=${query[key]}&`
+                    }
+                    let url = '/' + meta.pagePath + str;
+                    url = url.slice(0, url.length - 1);
+                    this.router = url;
+                },
+                deep: true,
+                immediate: true
+            }
+            // #endif
+        }
+    };
+</script>
+
+<style lang="scss" scoped>
+    .app-navigation-bar {
+        display: flex;
+        flex-direction: row;
+        width: 100%;
+        bottom: 0;
+        left: 0;
+        background-color: white;
+        z-index: 1600;
+        position: fixed;
+    }
+   
+    .app-nav-text {
+        font-size: #{22rpx};
+        line-height: #{22rpx};
+        margin-top: #{75rpx};
+    }
+    .app-icon {
+        width: #{50rpx};
+        height: #{50rpx};
+        position: absolute;
+        top: 0;
+	    left: 50%;
+        margin-top: #{20rpx};
+	    transform: translateX(-50%);
+    }
+    .app-tab-bar-item {
+        flex-grow: 1;
+        position: relative;
+    }
+    .app-tab-bar-shadow {
+        border-top: 1rpx solid $uni-weak-color-one;
+    }
+</style>

+ 110 - 0
components/basic-component/app-tab-nav/app-tab-nav.vue

xqd
@@ -0,0 +1,110 @@
+<template>
+    <view>
+        <view 
+            :class="[shadow ? `main-between app-nav shadow` : 'main-between app-nav']"
+            :style="[
+                {
+                    'line-height': `${setHeight ? setHeight : 90}rpx`,
+                    'font-size': `${fontSize ? fontSize : 28}rpx`,
+                    'height': `${setHeight ? setHeight : 90}rpx`,
+                    'top': `${setTop > 0 ? setTop + 'rpx' : '0'}`,
+                    'backgroundColor': `${background ? background : '#fff'}`,
+                }
+            ]"
+        >
+            <view @click="handleClick" v-for="(item) in tabList" :key="item.id" :data-id="item.id" class="box-grow-1 nav-item"
+            :style="[
+                {
+                    'borderBottom': `${border ? 1 : 0}rpx solid #e2e2e2`,
+                }
+            ]"
+            >
+                <text
+                    :class="[item.id == activeItem ? `active-text` : '']"
+                    :style="[
+                        {
+                            'color': `${ item.id == activeItem ? theme.color : '#353535'}`,
+                            'border-color':  `${ item.id == activeItem ? theme.color : ''}`,
+                            'height': `${setHeight ? setHeight : 90}rpx`,
+                            'padding': `0 ${padding}rpx`,
+                        }
+                    ]"
+                >{{item.name}}</text>
+            </view>
+        </view>
+        <view
+            :style="[
+                {'height': `${placeHeight ? placeHeight : 90}rpx`}
+            ]"
+        >
+        </view>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: 'app-tab-nav',
+        props: {
+            background: String,
+            setTop: {
+                type: [Number, String]
+            },
+            padding: {
+                default: 45,
+                type: [Number, String],
+            },
+            setHeight: Number,
+            placeHeight: Number,
+            fontSize: Number,
+            theme: {
+                type: Object,
+            },
+            border: {
+                default: true,
+                type: Boolean,
+            },
+            shadow: {
+                default: true,
+                type: Boolean,
+            },
+            activeItem: {
+                type: [Number, String]
+            },
+            tabList: Array
+        },
+        methods: {
+            handleClick(e) {
+                this.$emit('click', e);
+            }
+        },
+       
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-nav {
+        color: #353535;
+        width: 100%;
+        position: fixed;
+        left: 0;
+        background-color: #fff;
+        z-index: 11;
+        .nav-item {
+            text-align: center;
+            text {
+                display: inline-block;
+            }
+        }
+        .active-text {
+            color: #ff4544;
+            border-bottom: #{4rpx} solid #ff4544;
+        }
+    }
+    .app-nav.shadow {
+        box-shadow: 0 #{2rpx} #{10rpx} rgba(0, 0, 0, 0.06);
+    }
+    .blue-m-text {
+        color: #446dfd;
+    }
+</style>

+ 153 - 0
components/basic-component/app-tabs/app-tabs.vue

xqd
@@ -0,0 +1,153 @@
+<template>
+	<view class="tab-box" id="tab-box" v-if="tabList.length > 0">
+		<view class="horizontal">
+			<scroll-view :scroll-x="true" style="white-space: nowrap; display: flex;" scroll-with-animation :scroll-left="slider.scrollLeft">
+				<block v-for="(item, index) in tabList" :key="index" >
+				<view class="item" :class="{ active: activeIndex === index }" :id="'tab_'+index" @click="tabClick(index)">{{ item.text || item.name}}</view>
+				</block>
+				<view class="underline" :style="'transform:translateX(' + slider.left + 'px;width:' + slider.width + 'px'"></view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'liuyuno-tabs',
+		props: {
+			tabData: {
+				type: Array,
+				default: () => []
+			},
+			defaultIndex: {
+				type: Number,
+				default: 0
+			},
+			underLinePadding: {
+				type: Number,
+				default: 10
+			},
+		},
+		data() {
+			return {
+				tabList: [],
+				tabListSlider: [],
+				box: {
+					left: 0,
+					right: 0,
+					top: 0,
+					width: 0,
+					height: 0,
+					bottom: 0,
+				},
+				slider: {
+					left: 0,
+					width: 0,
+					scrollLeft: 0
+				},
+				activeIndex: 0
+			};
+		},
+		watch: {
+			tabData(value) {
+				this.tabList = value;
+				setTimeout(() => {
+					this.updateTabWidth();
+				}, 0);
+			},
+		},
+		mounted() {
+			this.tabList = this.tabData;
+			this.activeIndex = this.defaultIndex;
+			
+			setTimeout(() => {
+				
+				const query = uni.createSelectorQuery().in(this);
+				query.select('.tab-box').boundingClientRect((res) => {
+					this.box = res;
+					this.updateTabWidth();
+				}).exec();
+				
+			}, 0);
+			
+		},
+		methods: {
+			
+			tabClick(index) {
+				this.activeIndex = index;
+				this.tabToIndex(index);
+				this.$emit('tabClick', index);
+			},
+			
+			tabToIndex(index) {
+				let _slider = this.tabListSlider[index];
+
+				this.slider = {
+					left: _slider.left + this.underLinePadding,
+					width: _slider.width - this.underLinePadding * 2,
+					scrollLeft: _slider.scrollLeft,
+				}
+			},
+			
+			updateTabWidth(index = 0) {
+				let data = this.tabList;
+				
+				if (data.length == 0) return false;
+				
+				const query = uni.createSelectorQuery().in(this);
+				
+				query.select('#tab_' + index).boundingClientRect((res) => {
+					let _prev_slider = this.tabListSlider[index - 1];
+					this.tabListSlider[index] = {
+						left: res.left - this.box.left,
+						width: res.width,
+						scrollLeft: res.left - this.box.left - (_prev_slider ? _prev_slider.width : 0),
+					}
+
+					if (this.activeIndex == index) {
+						this.tabToIndex(this.activeIndex);
+					}
+
+					index++;
+					if (data.length > index) {
+						this.updateTabWidth(index);
+					}
+				}).exec();
+			}
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	.tab-box {
+		width: 100%;
+		color: rgba(0, 0, 0, 0.8);
+		display: flex;
+		height: 90upx;
+		background: #fff;
+		font-size: 28upx;
+		box-shadow: 0 1px 5px rgba(0, 0, 0, 0.06);
+		position: relative;
+		z-index: 10;
+		overflow: hidden;
+		.active {
+			color: #e54d42;
+		}
+		.horizontal {
+			width: 100%;
+			.item {
+				display: inline-block;
+				text-align: center;
+				padding: 0 30upx;
+				height: 86upx;
+				line-height: 90upx;
+			}
+			.underline {
+				height: 4upx;
+				background-color: #e54d42;
+				border-radius: 3px;
+				transition: .5s;
+			}
+		}
+	}
+</style>

+ 25 - 0
components/basic-component/app-text/app-text.vue

xqd
@@ -0,0 +1,25 @@
+<template>
+    <text :style="textStyle">
+        <slot></slot>
+    </text>
+</template>
+
+<script>
+    export default {
+        name: 'app-text',
+        props: {
+            theme: Object,
+            fontSize: String,
+            color: String,
+        },
+        computed: {
+            textStyle() {
+                let style = `font-size: ${this.fontSize ? this.fontSize : 32}rpx;color: ${this.color ? this.color : this.theme.color};`;
+                return style;
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+</style>

+ 162 - 0
components/basic-component/app-textarea/app-textarea.vue

xqd
@@ -0,0 +1,162 @@
+<template>
+    <view class="app-textarea">
+        <view class="a"
+              @click="showInput=true"
+              :style="{
+              background: background,
+              fontSize: `${fontSize}rpx`,
+              color: color,
+              borderRadius: `${borderRadius}rpx`,
+              border: showBorder ? `2rpx solid ${borderColor}` : 'none',
+              padding: `${paddingY}rpx ${paddingX}rpx`,
+              }">
+            <text v-if="inValue" class="content" :style="{
+            color: color,
+            }">{{inValue}}</text>
+            <view v-else
+                  class="placeholder"
+                  :style="placeholderStyle"
+                  :class="placeholderClass">{{placeholder}}
+            </view>
+        </view>
+        <view class="b" v-if="showInput">
+            <textarea class="textarea"
+                      :value="inValue"
+                      :placeholder="placeholder"
+                      :focus="showInput"
+                      :maxlength="maxlength"
+                      @input="handleInput"
+                      @blur="complete"
+                      @confirm="complete"/>
+            <view class="c" @click="complete"></view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-textarea',
+        props: {
+            value: {
+                default: '',
+            },
+            placeholder: {
+                default: '',
+            },
+            placeholderStyle: {
+                type: [String, Array],
+                default: function () {
+                    return '';
+                }
+            },
+            placeholderClass: {
+                type: Array,
+                default: function () {
+                    return [];
+                },
+            },
+            disable: {
+                default: false,
+            },
+            maxlength: {
+                default: 10000,
+            },
+            focus: {
+                default: false,
+            },
+            confirmType: {
+                default: 'done'
+            },
+
+            showBorder: {
+                default: true,
+            },
+            borderColor: {
+                default: '#cccccc',
+            },
+            borderRadius: {
+                default: 8,
+            },
+            fontSize: {
+                default: 32,
+            },
+            color: {
+                default: '#555',
+            },
+            background: {
+                default: '#fff',
+            },
+            paddingX: {
+                default: 24,
+            },
+            paddingY: {
+                default: 24,
+            },
+            defaultValue: {
+                default: '',
+            },
+        },
+        data() {
+            return {
+                showInput: !!this.focus,
+                inValue: this.value ? this.value : this.defaultValue,
+            };
+        },
+        methods: {
+            handleInput(e) {
+                this.inValue = e.detail.value;
+            },
+            complete(e) {
+                this.showInput = false;
+                this.$emit('input', this.inValue);
+            },
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .a {
+
+        .content {
+            color: #444;
+            display: block;
+            width: 100%;
+            min-height: $uni-font-size;
+            word-wrap: break-word;
+        }
+
+        .placeholder {
+            color: #aaa;
+            min-height: $uni-font-size;
+        }
+    }
+
+    .b {
+        position: fixed;
+        background: rgba(0, 0, 0, 0.5);
+        padding: #{50rpx};
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        z-index: 2000;
+
+        .textarea {
+            width: 100%;
+            background: #fff;
+            border: #{1rpx} solid #ccc;
+            z-index: 1;
+            padding: #{24rpx};
+            border-radius: #{5rpx};
+        }
+
+        .c {
+            position: fixed;
+            left: 0;
+            top: 0;
+            right: 0;
+            bottom: 0;
+            z-index: 0;
+        }
+    }
+</style>

+ 55 - 0
components/basic-component/app-timer/app-timer.vue

xqd
@@ -0,0 +1,55 @@
+<template>
+	<text :style="{color: color, fontSize: `${fontSize}rpx`}">{{html}}</text>
+</template>
+
+<script>
+    export default {
+        name: 'app-timer',
+	    data() {
+            return {
+                time: null,
+	            html: ''
+            }
+	    },
+	    props: {
+            startTime: {
+                type: String,
+	            default: function() {
+	                return '2019-8-30 10:00:00';
+	            }
+            },
+            color: {
+                type: String,
+	            default: function() {
+	                return 'white';
+	            }
+            },
+            fontSize: {
+                type: String,
+	            default: function() {
+	                return '26';
+	            }
+            }
+	    },
+	    beforeDestroy() {
+            clearInterval(this.time);
+        },
+	    watch: {
+            startTime: {
+                handler: function(v) {
+                    let timelog = new Date(v.replace(/-/g, '/'));
+                    this.time = setInterval(() =>{
+                        let timenow = new Date();//获取当前时间
+                        let time = timelog.getTime() - timenow.getTime();//时间差的所有毫秒数
+                        let day = parseInt((time/1000/60/60/24)%30);
+                        let hou = parseInt((time/1000/60/60)%24);
+                        let min = parseInt((time/1000/60)%60);
+                        let sec = parseInt((time/1000)%60);
+                        this.html = day+"天"+hou+":"+(min<10?"0"+min:min) + ":"+(sec<10?"0"+sec:sec);
+                    },1000);
+                },
+	            immediate: true
+            }
+	    }
+    }
+</script>

+ 302 - 0
components/basic-component/app-upload-image/app-upload-image.vue

xqd
@@ -0,0 +1,302 @@
+<template>
+    <view class="app-upload-image">
+        <view class='upload-box' :style="{'background':backgroundColor}">
+            <view class='flex-wrap'>
+                <view v-for='(item, index) in imageList' :key='item.id' class='img-box'>
+                    <view mode="aspectFill" @click='remove(index)' class='remove cross-center main-center'>x</view>
+                    <image @click='previewImage(index)' :src='item' mode="aspectFill" class='img'></image>
+                </view>
+                <view @click='chooseImage'>
+                    <view v-if='isAddImg' :style="{'margin':margin + 'rpx'}" :class="{'other-border': diy ? true : false}" class="add-img dir-top-nowrap cross-center main-center">
+                        <image mode="aspectFill" class='add-img-icon' :src='defaultImg'></image>
+                        <text class='text'>{{text}}</text>
+                        <text class='text' v-if="showNumber">(最多{{maxNum}}张)</text>
+                    </view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+<script>
+import uploadFile from '@/core/upload.js';
+
+export default {
+    name: 'app-upload-image',
+    props: {
+        value: {
+            default: null,
+        },
+        defaultImg: {
+            // 添加图片的默认背景图片
+            type: String,
+            default: '/static/image/icon/icon-image.png'
+        },
+        maxNum: {
+            // 可添加最大图片数量
+            type: [Number, String],
+            default: 3
+        },
+        // 标记
+        // 当组件用于循环时使用
+        sign: {
+            type: String,
+            default: ''
+        },
+        backgroundColor: {
+            type: String,
+            default: '#f7f7f7',
+        },
+        margin: {
+            type: String,
+            default: '10'
+        },
+        diy: {
+            type: Boolean,
+            default: false
+        },
+        showNumber: {
+            type: Boolean,
+            default: true,
+        },
+        text: {
+            type: String,
+            default: '上传图片',
+        },
+        count: {
+            type: Number,
+            default: 9
+        }
+    },
+    data() {
+        return {
+            imageList: this.value ? this.value : [],
+            isAddImg: true
+        }
+    },
+    methods: {
+        // 上传最大图片数量
+        checkMaxNum() {
+            let status = this.imageList.length >= this.maxNum ? false : true;
+            this.isAddImg = status;
+        },
+        // 移除图片
+        remove(index) {
+            let imageList = this.imageList;
+            let data = imageList.splice(index, 1);
+            this.imageList = imageList;
+            this.checkMaxNum();
+
+            // 触发事件 tabEvent
+            this.$emit('imageEvent', {
+                imageList: imageList,
+                sign: this.sign
+            })
+        },
+        // 选择图片
+        chooseImage() {
+            let self = this;
+            let imageList = self.imageList;
+            // #ifdef MP
+            uni.chooseImage({
+                count: self.maxNum,
+                success: function(e) {
+                    for (let i in e.tempFilePaths) {
+                        if (i >= (self.maxNum - imageList.length)) {
+                            break;
+                        }
+                        let fileName = '';
+                        // #ifdef MP-BAIDU
+                        fileName = e.tempFilePaths[i].substr(e.tempFilePaths[i].lastIndexOf('/') + 1);
+                        // #endif
+                        uni.uploadFile({
+                            url: self.$api.upload.file,
+                            filePath: e.tempFilePaths[i],
+                            name: 'file',
+                            fileType: 'image',
+                            formData: {
+                                file: e.tempFilePaths[i],
+                                file_name: fileName,
+                            },
+                            success(res) {
+                                const data = res.data;
+                                let result = null;
+                                if (typeof data === 'string') {
+                                    result = JSON.parse(data);
+                                } else {
+                                    result = data;
+                                }
+                                if (result.code == 0) {
+                                    imageList.push(result.data.url)
+                                    self.imageList = imageList;
+                                    self.checkMaxNum();
+                                    self.$emit('imageEvent', {
+                                        imageList: imageList,
+                                        sign: self.sign
+                                    });
+                                } else {
+                                    uni.showModal({
+                                        title: '',
+                                        content: result.msg,
+                                        showCancel: false,
+                                    });
+                                }
+                            },
+                            fail(e) {
+                                if (e && e.errMsg) {
+                                    uni.showModal({
+                                        title: '错误',
+                                        content: e.errMsg,
+                                        showCancel: false,
+                                    });
+                                }
+                            },
+                        });
+                    }
+                },
+                complete: function(e) {
+                    // 触发事件 tabEvent
+                    self.$emit('imageEvent', {
+                        imageList: imageList,
+                        sign: self.sign
+                    })
+                }
+            })
+            // #endif
+
+
+
+            // #ifdef H5
+            uni.chooseImage({
+                count: Number(self.maxNum),
+                success: function(e) {
+                    for (let i in e.tempFilePaths) {
+                        if (i >= (self.maxNum - imageList.length)) {
+                            break;
+                        }
+                        let image = new Image();
+                        image.src = e.tempFilePaths[i];
+                        image.onload = () => {
+                            let canvas = document.createElement("canvas");
+                            canvas.width = image.width;
+                            canvas.height = image.height;
+                            let ctx = canvas.getContext("2d");
+                            ctx.drawImage(image, 0, 0, image.width, image.height);
+                            let ext = image.src.substring(image.src.lastIndexOf(".") + 1).toLowerCase();
+                            let dataURL = canvas.toDataURL("image/" + ext);
+                            uploadFile({
+                                url: self.$api.upload.file,
+                                maxNum: self.maxNum,
+                                success: function({res, header}) {
+                                    self.$request({
+                                        url: self.$api.upload.file + '&name=base64',
+                                        header: header,
+                                        method: 'post',
+                                        data: {
+                                            database: dataURL
+                                        }
+                                    }).then(res => {
+                                        if (res.code === 0) {
+                                            self.imageList.push(res.data.url);
+                                            self.checkMaxNum();
+                                            self.$emit('imageEvent', {
+                                                imageList: self.imageList,
+                                                sign: self.sign
+                                            });
+                                        } else {
+                                            uni.showModal({
+                                                title: '',
+                                                content: res.msg,
+                                                showCancel: false,
+                                            });
+                                        }
+                                    })
+                                }
+                            });
+                        };
+                    }
+                },
+                complete: function(e) {
+                    // 触发事件 tabEvent
+                    self.$emit('imageEvent', {
+                        imageList: imageList,
+                        sign: self.sign
+                    })
+                }
+            });
+            // #endif
+
+        },
+        // 图片预览
+        previewImage(index) {
+            let imageList = this.imageList;
+            uni.previewImage({
+                current: imageList[index],
+                urls: imageList
+            })
+        },
+        createObjectURL(blob) {
+            return (window.URL) ? window.URL.createObjectURL(blob) : window.webkitURL.createObjectURL(blob);
+        }
+    },
+    created() {
+        this.checkMaxNum();
+    }
+}
+</script>
+<style lang="scss" scoped>
+.upload-box {
+    background-color: #fff;
+}
+
+.upload-box .title {
+    padding: 15#{rpx} 0 15#{rpx} 20#{rpx};
+}
+
+.upload-box .img {
+    width: 188#{rpx};
+    height: 188#{rpx};
+    margin: 10#{rpx};
+    display: block;
+}
+
+.upload-box .add-img {
+    width: 188#{rpx};
+    height: 188#{rpx};
+    border: 1#{rpx} dotted $uni-weak-color-one;
+    background-color: #fff;
+}
+
+.upload-box .add-img .text {
+    color: $uni-general-color-two;
+    font-size: $uni-font-size-weak-two;
+}
+
+.upload-box .add-img-icon {
+    width: 56#{rpx};
+    height: 56#{rpx};
+    margin-bottom: 10#{rpx};
+}
+
+.upload-box .img-box {
+    position: relative;
+}
+
+
+.upload-box .remove {
+    width: 40#{rpx};
+    height: 40#{rpx};
+    position: absolute;
+    right: -5rpx;
+    top: -10rpx;
+    background: $uni-important-color-red;
+    color: #fff;
+    border-radius: 50%;
+    padding-bottom: 8#{rpx};
+    font-size: 24#{rpx};
+    z-index: 968;
+}
+
+.upload-box .add-img.other-border {
+    border: 1#{rpx} solid $uni-weak-color-one
+}
+</style>

+ 219 - 0
components/basic-component/u-count-to/u-count-to.vue

xqd
@@ -0,0 +1,219 @@
+<template>
+    <view
+        class="u-count-num"
+        :style="{
+			fontSize: fontSize + 'rpx',
+			fontWeight: bold ? 'bold' : 'normal',
+			color: color
+		}"
+    >
+        {{ displayValue }}
+    </view>
+</template>
+
+<script>
+/**
+ * countTo 数字滚动
+ * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
+ * @property {String Number} start-val 开始值
+ * @property {String Number} end-val 结束值
+ * @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000)
+ * @property {Boolean} autoplay 是否自动开始滚动(默认true)
+ * @property {String Number} decimals 要显示的小数位数,见官网说明(默认0)
+ * @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true)
+ * @property {String} separator 千位分隔符,见官网说明
+ * @property {String} color 字体颜色(默认#303133)
+ * @property {String Number} font-size 字体大小,单位rpx(默认50)
+ * @property {Boolean} bold 字体是否加粗(默认false)
+ * @event {Function} end 数值滚动到目标值时触发
+ * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
+ */
+export default {
+    name: 'u-count-to',
+    props: {
+        startVal: {
+            type: [Number, String],
+            default: 0
+        },
+        endVal: {
+            type: [Number, String],
+            default: 0,
+            required: true
+        },
+        duration: {
+            type: [Number, String],
+            default: 2000
+        },
+        autoplay: {
+            type: Boolean,
+            default: true
+        },
+        decimals: {
+            type: [Number, String],
+            default: 0
+        },
+        useEasing: {
+            type: Boolean,
+            default: true
+        },
+        decimal: {
+            type: [Number, String],
+            default: '.'
+        },
+        color: {
+            type: String,
+            default: '#303133'
+        },
+        fontSize: {
+            type: [Number, String],
+            default: 50
+        },
+        bold: {
+            type: Boolean,
+            default: false
+        },
+        separator: {
+            type: String,
+            default: ''
+        }
+    },
+    data() {
+        return {
+            localStartVal: this.startVal,
+            displayValue: this.formatNumber(this.startVal),
+            printVal: null,
+            paused: false,
+            localDuration: Number(this.duration),
+            startTime: null,
+            timestamp: null,
+            remaining: null,
+            rAF: null,
+            lastTime: 0
+        };
+    },
+    computed: {
+        countDown() {
+            return this.startVal > this.endVal;
+        }
+    },
+    watch: {
+        startVal() {
+            this.autoplay && this.start();
+        },
+        endVal() {
+            this.autoplay && this.start();
+        }
+    },
+    mounted() {
+        this.autoplay && this.start();
+    },
+    methods: {
+        easingFn(t, b, c, d) {
+            return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
+        },
+        requestAnimationFrame(callback) {
+            const currTime = new Date().getTime();
+            const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
+            const id = setTimeout(() => {
+                callback(currTime + timeToCall);
+            }, timeToCall);
+            this.lastTime = currTime + timeToCall;
+            return id;
+        },
+        cancelAnimationFrame(id) {
+            clearTimeout(id);
+        },
+        start() {
+            this.localStartVal = this.startVal;
+            this.startTime = null;
+            this.localDuration = this.duration;
+            this.paused = false;
+            this.rAF = this.requestAnimationFrame(this.count);
+        },
+        reStart() {
+            if (this.paused) {
+                this.resume();
+                this.paused = false;
+            } else {
+                this.stop();
+                this.paused = true;
+            }
+        },
+        stop() {
+            this.cancelAnimationFrame(this.rAF);
+        },
+        resume() {
+            this.startTime = null;
+            this.localDuration = this.remaining;
+            this.localStartVal = this.printVal;
+            this.requestAnimationFrame(this.count);
+        },
+        reset() {
+            this.startTime = null;
+            this.cancelAnimationFrame(this.rAF);
+            this.displayValue = this.formatNumber(this.startVal);
+        },
+        count(timestamp) {
+            if (!this.startTime) this.startTime = timestamp;
+            this.timestamp = timestamp;
+            const progress = timestamp - this.startTime;
+            this.remaining = this.localDuration - progress;
+            if (this.useEasing) {
+                if (this.countDown) {
+                    this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
+                } else {
+                    this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
+                }
+            } else {
+                if (this.countDown) {
+                    this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
+                } else {
+                    this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
+                }
+            }
+            if (this.countDown) {
+                this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
+            } else {
+                this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
+            }
+            this.displayValue = this.formatNumber(this.printVal);
+            if (progress < this.localDuration) {
+                this.rAF = this.requestAnimationFrame(this.count);
+            } else {
+                this.$emit('end');
+            }
+        },
+        isNumber(val) {
+            return !isNaN(parseFloat(val));
+        },
+        formatNumber(num) {
+            num = Number(num);
+            num = num.toFixed(Number(this.decimals));
+            num += '';
+            const x = num.split('.');
+            let x1 = x[0];
+            const x2 = x.length > 1 ? this.decimal + x[1] : '';
+            const rgx = /(\d+)(\d{3})/;
+            if (this.separator && !this.isNumber(this.separator)) {
+                while (rgx.test(x1)) {
+                    x1 = x1.replace(rgx, '$1' + this.separator + '$2');
+                }
+            }
+            return x1 + x2;
+        },
+        destroyed() {
+            this.cancelAnimationFrame(this.rAF);
+        }
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+
+.u-count-num {
+    /* #ifndef APP-NVUE */
+    display: inline-flex;
+    /* #endif */
+    text-align: center;
+}
+</style>

+ 85 - 0
components/basic-component/u-mask/u-mask.vue

xqd
@@ -0,0 +1,85 @@
+<template>
+	<view class="u-mask" :style="[maskStyle]" :class="[show ? 'u-mask-show' : '']" @tap="click" @touchmove.stop.prevent>
+		<slot />
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			// 是否显示遮罩
+			show: {
+				type: [Boolean, Number],
+				default: false
+			},
+			// 层级z-index
+			zIndex: {
+				type: [Number, String],
+				default: 1001
+			},
+			// 用户自定义样式
+			customStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放
+			zoom: {
+				type: Boolean,
+				default: true
+			},
+			// 遮罩的过渡时间,单位为ms
+			duration: {
+				type: [Number, String],
+				default: 300
+			},
+			// 是否可以通过点击遮罩进行关闭
+			maskClickAble: {
+				type: Boolean,
+				default: true
+			}
+		},
+		computed: {
+			maskStyle() {
+				let style = {};
+				style.backgroundColor = "rgba(0, 0, 0, 0.6)";
+				style.zIndex = this.zIndex;
+				style.transition = `all ${this.duration / 1000}s ease-in-out`;
+				// 缩放
+				if (this.zoom == true) style.transform = 'scale(1.2, 1.2)';
+				// 判断用户传递的对象是否为空
+				if (Object.keys(this.customStyle).length) style = { ...style,
+					...this.customStyle
+				};
+				// 合并自定义的样式
+				//Object.assign(style, customStyle);
+				return style;
+			}
+		},
+		methods: {
+			click() {
+				if(!this.maskClickAble) return ;
+				this.$emit('click');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.u-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.u-mask-show {
+		opacity: 1;
+		visibility: visible;
+		transform: scale(1);
+	}
+</style>

+ 322 - 0
components/basic-component/u-popup/u-popup.vue

xqd
@@ -0,0 +1,322 @@
+<template>
+    <view v-if="visibleSync" :style="[customStyle]" :class="{ 'u-drawer-visible': showDrawer }" class="u-drawer"  @touchmove.stop.prevent>
+        <u-mask :maskClickAble="maskCloseAble" :show="showDrawer && mask" @click="maskClick"></u-mask>
+        <view
+                class="u-drawer-content"
+                @tap="modeCenterClose(mode)"
+                :class="[
+				safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
+				'u-drawer-' + mode,
+				showDrawer ? 'u-drawer-content-visible' : '',
+				zoom && mode == 'center' ? 'u-animation-zoom' : ''
+			]"
+                :style="[style]"
+        >
+            <view class="u-mode-center-box" @tap.stop v-if="mode == 'center'" :style="[centerStyle]"><slot /></view>
+            <block v-else><slot /></block>
+        </view>
+    </view>
+</template>
+
+<script>
+    import uMask from '../u-mask/u-mask.vue';
+
+    export default {
+        name: 'uDrawer',
+        props: {
+            /**
+             * 显示状态
+             */
+            show: {
+                type: Boolean,
+                default: false
+            },
+            /**
+             * 弹出方向,left|right|top|bottom|center
+             */
+            mode: {
+                type: String,
+                default: 'left'
+            },
+            /**
+             * 是否显示遮罩
+             */
+            mask: {
+                type: Boolean,
+                default: true
+            },
+            // 抽屉的宽度(mode=left|right),或者高度(mode=top|bottom),单位rpx,或者"auto"
+            // 或者百分比"50%",表示由内容撑开高度或者宽度
+            length: {
+                type: [Number, String],
+                default: 'auto'
+            },
+            // 是否开启缩放动画,只在mode=center时有效
+            zoom: {
+                type: Boolean,
+                default: true
+            },
+            // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+            safeAreaInsetBottom: {
+                type: Boolean,
+                default: false
+            },
+            // 是否可以通过点击遮罩进行关闭
+            maskCloseAble: {
+                type: Boolean,
+                default: true
+            },
+            // 用户自定义样式
+            customStyle: {
+                type: Object,
+                default() {
+                    return {};
+                }
+            },
+            value: {
+                type: [Boolean, Number],
+                default: false
+            },
+            // 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
+            // 对v-model双向绑定多层调用造成报错不能修改props值的问题
+            popup: {
+                type: Boolean,
+                default: true
+            },
+            // 显示显示弹窗的圆角,单位rpx
+            borderRadius: {
+                type: [Number, String],
+                default: 0
+            }
+        },
+        data() {
+            return {
+                visibleSync: false,
+                showDrawer: false,
+                timer: null,
+                style1: {}
+            };
+        },
+        computed: {
+            // 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
+            style() {
+                let style = {};
+                let translate = '100%';
+                // 判断是否是否百分比或者auto值,是的话,直接使用该值,否则默认为rpx单位的数值
+                let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
+                // 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
+                if (this.mode == 'left' || this.mode == 'top') translate = length == 'auto' ? '-100%' : '-' + length;
+                if (this.mode == 'left' || this.mode == 'right') {
+                    style = {
+                        width: length,
+                        height: '100%',
+                        transform: `translate3D(${translate},0px,0px)`
+                    };
+                } else if (this.mode == 'top' || this.mode == 'bottom') {
+                    style = {
+                        width: '100%',
+                        height: length,
+                        transform: `translate3D(0px,${translate},0px)`
+                    };
+                }
+                // 如果用户设置了borderRadius值,添加弹窗的圆角
+                if (this.borderRadius) {
+                    switch (this.mode) {
+                        case 'left':
+                            style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
+                            break;
+                        case 'top':
+                            style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
+                            break;
+                        case 'right':
+                            style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
+                            break;
+                        case 'bottom':
+                            style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
+                            break;
+                        default: ;
+                    }
+                    // 不加可能圆角无效
+                    // style.overflow = 'hidden';
+                }
+                return style;
+            },
+            // 中部弹窗的特有样式
+            centerStyle() {
+                let style = {};
+                let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
+                style.width = length;
+                if(this.borderRadius) {
+                    style.borderRadius = `${this.borderRadius}rpx`;
+                    // // 不加可能圆角无效
+                    style.overflow = 'hidden';
+                }
+                return style;
+            }
+        },
+        watch: {
+            value(val) {
+                if (val) {
+                    this.open();
+                } else {
+                    this.close();
+                }
+            }
+        },
+        created() {
+            // 先让弹窗组件渲染,再改变遮罩和抽屉元素的样式,让其动画其起作用(必须要有延时,才会有效果)
+            this.visibleSync = this.value;
+            setTimeout(() => {
+                this.showDrawer = this.value;
+            }, 30);
+        },
+        methods: {
+            // 遮罩被点击
+            maskClick() {
+                this.close();
+            },
+            close() {
+                this.change('showDrawer', 'visibleSync', false);
+            },
+            // 中部弹出时,需要.u-drawer-content将居中内容,此元素会铺满屏幕,点击需要关闭弹窗
+            // 让其只在mode=center时起作用
+            modeCenterClose(mode) {
+                if (mode != 'center' || !this.maskCloseAble) return;
+                this.close();
+            },
+            open() {
+                this.change('visibleSync', 'showDrawer', true);
+            },
+            // 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
+            // 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
+            change(param1, param2, status) {
+                // 如果this.popup为false,以为着为picker,actionsheet等组件调用了popup组件
+                if (this.popup == true) this.$emit('input', status);
+                this[param1] = status;
+                if (this.timer) {
+                    clearTimeout(this.timer);
+                }
+                this.timer = setTimeout(
+                    () => {
+                        this[param2] = status;
+                        this.$emit(status ? 'open' : 'close');
+                    },
+                    status ? 30 : 300
+                );
+            }
+        },
+        components: {
+            uMask
+        }
+    };
+</script>
+
+<style scoped lang="scss">
+    .u-drawer {
+        /* #ifndef APP-NVUE */
+        display: block;
+        /* #endif */
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        overflow: hidden;
+        z-index: 1999;
+    }
+
+    .u-drawer-content {
+        /* #ifndef APP-NVUE */
+        display: block;
+        /* #endif */
+        position: absolute;
+        z-index: 1003;
+        transition: all 0.25s linear;
+    }
+
+    .u-drawer-left {
+        top: 0;
+        bottom: 0;
+        left: 0;
+        background-color: #ffffff;
+    }
+
+    .u-drawer-right {
+        right: 0;
+        top: 0;
+        bottom: 0;
+        background-color: #ffffff;
+    }
+
+    .u-drawer-top {
+        top: 0;
+        left: 0;
+        right: 0;
+        background-color: #ffffff;
+    }
+
+    .u-drawer-bottom {
+        bottom: 0;
+        left: 0;
+        right: 0;
+        background-color: #ffffff;
+    }
+
+    .u-drawer-center {
+        /* #ifndef APP-NVUE */
+        display: flex;
+        flex-direction: column;
+        /* #endif */
+        bottom: 0;
+        left: 0;
+        right: 0;
+        top: 0;
+        justify-content: center;
+        align-items: center;
+        opacity: 0;
+        z-index: 99999;
+    }
+
+    .u-mode-center-box {
+        min-width: 100rpx;
+        min-height: 100rpx;
+        /* #ifndef APP-NVUE */
+        display: block;
+        /* #endif */
+        position: relative;
+    }
+
+    .u-drawer-content-visible.u-drawer-center {
+        transform: scale(1);
+        opacity: 1;
+    }
+
+    .u-animation-zoom {
+        transform: scale(1.15);
+    }
+
+    .u-drawer-content-visible {
+        transform: translate3D(0px, 0px, 0px) !important;
+    }
+
+    .u-drawer-mask {
+        /* #ifndef APP-NVUE */
+        display: block;
+        /* #endif */
+        opacity: 0;
+        position: absolute;
+        top: 0;
+        left: 0;
+        bottom: 0;
+        right: 0;
+        background-color: rgba(0, 0, 0, 0.4);
+        transition: opacity 0.25s;
+    }
+
+    .u-drawer-mask-visible {
+        /* #ifndef APP-NVUE */
+        display: block;
+        /* #endif */
+        opacity: 1;
+    }
+</style>

+ 424 - 0
components/basic-component/u-tabs-swiper/u-tabs-swiper.vue

xqd
@@ -0,0 +1,424 @@
+<template>
+    <view
+            class="u-tabs"
+            :style="{
+			zIndex: zIndex,
+			background: bgColor
+		}"
+    >
+        <scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{ zIndex: zIndex + 1 }">
+            <view class="u-tabs-scroll-box" :class="{'u-tabs-scorll-flex': !isScroll}">
+                <view
+                        class="u-tabs-item"
+                        :style="{
+						height: height + 'rpx',
+						lineHeight: height + 'rpx',
+						padding: `0 ${gutter / 2}rpx`,
+						color: tabsInfo.length > 0 ? (tabsInfo[index] ? tabsInfo[index].color : activeColor) : inactiveColor,
+						fontSize: fontSize + 'rpx',
+						zIndex: zIndex + 2,
+						fontWeight: (index == getCurrent && bold) ? 'bold' : 'normal'
+					}"
+                        v-for="(item, index) in getTabs"
+                        :key="index"
+                        :class="[preId + index]"
+                        @tap="emit(index)"
+                >
+                    {{ item[name] || item['name']}}
+                </view>
+                <view
+                        class="u-scroll-bar"
+                        :style="{
+						width: barWidthPx + 'px',
+						height: barHeight + 'rpx',
+						borderRadius: '100px',
+						backgroundColor: activeColor,
+						left: scrollBarLeft + 'px'
+					}"
+                ></view>
+            </view>
+        </scroll-view>
+    </view>
+</template>
+
+<script>
+    import colorGradient from '../../../core/libs/function/colorGradient';
+    let color = colorGradient;
+    const { windowWidth } = uni.getSystemInfoSync();
+    const preId = 'UEl_';
+
+    export default {
+        props: {
+            // 导航菜单是否需要滚动,如只有2或者3个的时候,就不需要滚动了,此时使用flex平分tab的宽度
+            isScroll: {
+                type: Boolean,
+                default: true
+            },
+            //需循环的标签列表
+            list: {
+                type: Array,
+                default() {
+                    return [];
+                }
+            },
+            // 当前活动tab的索引
+            current: {
+                type: [Number, String],
+                default: 0
+            },
+            // 导航栏的高度和行高,单位rpx
+            height: {
+                type: [Number, String],
+                default: 80
+            },
+            // 字体大小,单位rpx
+            fontSize: {
+                type: [Number, String],
+                default: 30
+            },
+            // 过渡动画时长, 单位s
+            // duration: {
+            // 	type: [Number, String],
+            // 	default: 0.5
+            // },
+            swiperWidth: {
+                //line3生效, 外部swiper的宽度, 单位rpx
+                type: [String, Number],
+                default: 750
+            },
+            // 选中项的主题颜色
+            activeColor: {
+                type: String,
+                default: '#2979ff'
+            },
+            // 未选中项的颜色
+            inactiveColor: {
+                type: String,
+                default: '#303133'
+            },
+            // 菜单底部移动的bar的宽度,单位rpx
+            barWidth: {
+                type: [Number, String],
+                default: 40
+            },
+            // 移动bar的高度
+            barHeight: {
+                type: [Number, String],
+                default: 6
+            },
+            // 单个tab的左或右内边距(各占一半),单位rpx
+            gutter: {
+                type: [Number, String],
+                default: 40
+            },
+            // 如果是绝对定位,添加z-index值
+            zIndex: {
+                type: [Number, String],
+                default: 1
+            },
+            // 导航栏的背景颜色
+            bgColor: {
+                type: String,
+                default: '#ffffff'
+            },
+            //滚动至中心目标类型
+            autoCenterMode: {
+                type: String,
+                default: 'window'
+            },
+            // 读取传入的数组对象的属性
+            name: {
+                type: String,
+                default: 'name'
+            },
+            // 活动tab字体是否加粗
+            bold: {
+                type: Boolean,
+                default: true
+            }
+        },
+        data() {
+            return {
+                scrollLeft: 0, // 滚动scroll-view的左边滚动距离
+                tabQueryInfo: [], // 存放对tab菜单查询后的节点信息
+                windowWidth: 0, // 屏幕宽度,单位为px
+                //scrollBarLeft: 0, // 移动bar需要通过translateX()移动的距离
+                animationFinishCurrent: this.current,
+                componentsWidth: 0,
+                line3AddDx: 0,
+                line3Dx: 0,
+                preId,
+                sW: 0,
+                tabsInfo: [],
+                colorGradientArr: [],
+                colorStep: 100 // 两个颜色之间的渐变等分
+            };
+        },
+        computed: {
+            // 获取当前活跃的current值
+            getCurrent() {
+                const current = Number(this.current);
+                // 判断是否超出边界
+                if (current > this.getTabs.length - 1) {
+                    return this.getTabs.length - 1;
+                }
+                if (current < 0) return 0;
+                return current;
+            },
+            getTabs() {
+                return this.list;
+            },
+            // 滑块需要移动的距离
+            scrollBarLeft() {
+                return Number(this.line3Dx) + Number(this.line3AddDx);
+            },
+            // 滑块的宽度转为px单位
+            barWidthPx() {
+                return uni.upx2px(this.barWidth);
+            }
+        },
+        watch: {
+            current(n, o) {
+                this.change(n);
+                this.setFinishCurrent(n);
+            },
+            list() {
+                this.$nextTick(() => {
+                    this.init();
+                })
+            }
+        },
+        mounted() {
+            this.init();
+        },
+        methods: {
+
+            async init() {
+                this.countPx();
+                await this.getTabsInfo();
+                this.countLine3Dx();
+                this.getQuery(() => {
+                    this.setScrollViewToCenter();
+                });
+                // 颜色渐变过程数组
+                this.colorGradientArr = color.colorGradient(this.inactiveColor, this.activeColor, this.colorStep);
+            },
+            // 获取各个tab的节点信息
+            getTabsInfo() {
+                return new Promise((resolve, reject) => {
+                    let view = uni.createSelectorQuery().in(this);
+                    for (let i = 0; i < this.list.length; i++) {
+                        view.select('.' + preId + i).boundingClientRect();
+                    }
+                    view.exec(res => {
+                        const arr = [];
+                        for (let i = 0; i < res.length; i++) {
+                            // 给每个tab添加其文字颜色属性
+                            res[i].color = this.inactiveColor;
+                            // 当前tab直接赋予activeColor
+                            if (i == this.getCurrent) res[i].color = this.activeColor;
+                            arr.push(res[i]);
+                        }
+                        this.tabsInfo = arr;
+                        resolve();
+                    });
+                })
+            },
+            // 当swiper滑动结束,计算滑块最终要停留的位置
+            countLine3Dx() {
+                const tab = this.tabsInfo[this.animationFinishCurrent];
+                // 让滑块中心点和当前tab中心重合
+                if (tab) this.line3Dx = tab.left + tab.width / 2 - this.barWidthPx / 2;
+            },
+            countPx() {
+                // swiper宽度由rpx转为px单位,因为dx等,都是px单位
+                this.sW = uni.upx2px(Number(this.swiperWidth));
+            },
+            emit(index) {
+                this.$emit('change', index);
+            },
+            change() {
+                this.setScrollViewToCenter();
+            },
+            getQuery(cb) {
+                try {
+                    let view = uni.createSelectorQuery().in(this).select('.u-tabs');
+                    view.fields(
+                        {
+                            size: true
+                        },
+                        data => {
+                            if (data) {
+                                this.componentsWidth = data.width;
+                                if (cb && typeof cb === 'function') cb(data);
+                            } else {
+                                this.getQuery(cb);
+                            }
+                        }
+                    ).exec();
+                } catch (e) {
+                    this.componentsWidth = windowWidth;
+                }
+            },
+            // 把活动tab移动到屏幕中心点
+            setScrollViewToCenter() {
+                let tab;
+                tab = this.tabsInfo[this.animationFinishCurrent];
+                if (tab) {
+                    let tabCenter = tab.left + tab.width / 2;
+                    let fatherWidth;
+                    // 活动tab移动到中心时,以屏幕还是tab组件为宽度为基准
+                    if (this.autoCenterMode === 'window') {
+                        fatherWidth = windowWidth;
+                    } else {
+                        fatherWidth = this.componentsWidth;
+                    }
+                    this.scrollLeft = tabCenter - fatherWidth / 2;
+                }
+            },
+            setDx(dx) {
+                let nextTabIndex = dx > 0 ? this.animationFinishCurrent + 1 : this.animationFinishCurrent - 1;
+                // 判断索引是否超出边界
+                nextTabIndex = nextTabIndex <= 0 ? 0 : nextTabIndex;
+                nextTabIndex = nextTabIndex >= this.list.length ? this.list.length - 1 : nextTabIndex;
+                const tab = this.tabsInfo[nextTabIndex];
+                // 当前tab中心点x轴坐标
+                let nowTab = this.tabsInfo[this.animationFinishCurrent];
+                let nowTabX = nowTab.left + nowTab.width / 2;
+                // 下一个tab
+                let nextTab = this.tabsInfo[nextTabIndex];
+                let nextTabX = nextTab.left + nextTab.width / 2;
+                // 两个tab之间的距离,因为下一个tab可能在当前tab的左边或者右边,取绝对值即可
+                let distanceX = Math.abs(nextTabX - nowTabX);
+                this.line3AddDx = (dx / this.sW) * distanceX;
+                this.setTabColor(this.animationFinishCurrent, nextTabIndex, dx);
+            },
+            // 设置tab的颜色
+            setTabColor(nowTabIndex, nextTabIndex, dx) {
+                let colorIndex = Math.abs(Math.ceil((dx / this.sW) * 100));
+                let colorLength = this.colorGradientArr.length;
+                // 处理超出索引边界的情况
+                colorIndex = colorIndex >= colorLength ? colorLength - 1 : colorIndex <= 0 ? 0 : colorIndex;
+                // 设置下一个tab的颜色
+                this.tabsInfo[nextTabIndex].color = this.colorGradientArr[colorIndex];
+                // 设置当前tab的颜色
+                this.tabsInfo[nowTabIndex].color = this.colorGradientArr[colorLength - 1 - colorIndex];
+            },
+            // swiper结束滑动
+            setFinishCurrent(current) {
+                // 如果滑动的索引不一致,修改tab颜色变化,因为可能会有直接点击tab的情况
+                if (current != this.animationFinishCurrent) {
+                    this.tabsInfo.map((val, index) => {
+                        if (current == index) val.color = this.activeColor;
+                        else val.color = this.inactiveColor;
+                        return val;
+                    });
+                }
+                this.line3AddDx = 0;
+                this.animationFinishCurrent = current;
+                this.countLine3Dx();
+            }
+        }
+    };
+</script>
+
+<style scoped lang="scss">
+    view,
+    scroll-view {
+        box-sizing: border-box;
+    }
+
+    .u-tabs {
+        width: 100%;
+        transition-property: background-color, color;
+    }
+
+    ::-webkit-scrollbar,
+    ::-webkit-scrollbar,
+    ::-webkit-scrollbar {
+        display: none;
+        width: 0 !important;
+        height: 0 !important;
+        -webkit-appearance: none;
+        background: transparent;
+    }
+
+    /* #ifdef H5 */
+    // 通过样式穿透,隐藏H5下,scroll-view下的滚动条
+    scroll-view /deep/ ::-webkit-scrollbar {
+        display: none;
+        width: 0 !important;
+        height: 0 !important;
+        -webkit-appearance: none;
+        background: transparent;
+    }
+    /* #endif */
+
+    .u-scroll-view {
+        width: 100%;
+        white-space: nowrap;
+        position: relative;
+    }
+
+    .u-tabs-scroll-box {
+        position: relative;
+    }
+
+    .u-tabs-scorll-flex {
+        display: flex;
+        justify-content: space-between;
+    }
+
+    .u-tabs-scorll-flex .u-tabs-item {
+        flex: 1;
+    }
+
+    .u-tabs-item {
+        position: relative;
+        display: inline-block;
+        text-align: center;
+        transition-property: background-color, color, font-weight;
+    }
+
+    .content {
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+    }
+
+    .boxStyle {
+        pointer-events: none;
+        position: absolute;
+        transition-property: all;
+    }
+
+    .boxStyle2 {
+        pointer-events: none;
+        position: absolute;
+        bottom: 0;
+        transition-property: all;
+        transform: translateY(-100%);
+    }
+
+    .itemBackgroundBox {
+        pointer-events: none;
+        position: absolute;
+        top: 0;
+        transition-property: left, background-color;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .itemBackground {
+        height: 100%;
+        width: 100%;
+        transition-property: all;
+    }
+
+    .u-scroll-bar {
+        position: absolute;
+        bottom: 4rpx;
+    }
+</style>

+ 199 - 0
components/basic-component/uni-swiper-dot/uni-swiper-dot.vue

xqd
@@ -0,0 +1,199 @@
+<template>
+	<view class="uni-swiper__warp">
+		<slot />
+		<view v-if="mode === 'default'" :style="{'bottom':dots.bottom}" class="uni-swiper__dots-box" key='default'>
+			<view v-for="(item,index) in info" :style="{
+        'width': (index === current? dots.width*2:dots.width ) + 'px','height':dots.width/3 +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border-radius':'0px'}"
+			 :key="index" class="uni-swiper__dots-item uni-swiper__dots-bar" />
+		</view>
+		<view v-if="mode === 'dot'" :style="{'bottom':dots.bottom}" class="uni-swiper__dots-box" key='dot'>
+			<view v-for="(item,index) in info" :style="{
+        'width': dots.width + 'px','height':dots.height +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}"
+			 :key="index" class="uni-swiper__dots-item" />
+		</view>
+		<view v-if="mode === 'round'" :style="{'bottom':dots.bottom}" class="uni-swiper__dots-box" key='round'>
+			<view v-for="(item,index) in info" :class="[index === current&&'uni-swiper__dots-long']" :style="{
+		    'width':(index === current? dots.width*3:dots.width ) + 'px','height':dots.height +'px' ,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}"
+			 :key="index" class="uni-swiper__dots-item " />
+		</view>
+		<view v-if="mode === 'nav'" key='nav' :style="{'background-color':dotsStyles.backgroundColor,'bottom':'0'}" class="uni-swiper__dots-box uni-swiper__dots-nav">
+			<text :style="{'color':dotsStyles.color}" class="uni-swiper__dots-nav-item">{{ (current+1)+"/"+info.length +' ' +info[current][field] }}</text>
+		</view>
+		<view v-if="mode === 'indexes'" key='indexes' :style="{'bottom':dots.bottom}" class="uni-swiper__dots-box">
+			<view v-for="(item,index) in info" :style="{
+        'width':dots.width + 'px','height':dots.height +'px' ,'color':index === current?dots.selectedColor:dots.color,'background-color':index !== current?dots.backgroundColor:dots.selectedBackgroundColor,'border':index !==current ? dots.border:dots.selectedBorder}"
+			 :key="index" class="uni-swiper__dots-item uni-swiper__dots-indexes"><text class="uni-swiper__dots-indexes-text">{{ index+1 }}</text></view>
+		</view>
+		<view v-if="mode === 'customize' &&info.length > 1" key='indexes' class="uni-swiper__dots-box customize">
+			<view v-for="(item,index) in info" :style="{'width':index === current?'20rpx':'16rpx','height': '6rpx','background-color': index === current ? theme.background : '#e2e2e2'}" :key="index" class="uni-swiper__dots-item uni-swiper__dots-indexes"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'UniSwiperDot',
+		props: {
+			info: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			current: {
+				type: Number,
+				default: 0
+			},
+			dotsStyles: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// 类型 :default(默认) indexes long nav
+			mode: {
+				type: String,
+				default: 'default'
+			},
+			// 只在 nav 模式下生效,变量名称
+			field: {
+				type: String,
+				default: ''
+			},
+			theme: Object
+		},
+		data() {
+			return {
+				dots: {
+					width: 8,
+					height: 8,
+					bottom: 10,
+					color: '#fff',
+					backgroundColor: 'rgba(0, 0, 0, .3)',
+					border: '1px rgba(0, 0, 0, .3) solid',
+					selectedBackgroundColor: '#333',
+					selectedBorder: '1px rgba(0, 0, 0, .9) solid'
+				}
+			}
+		},
+		watch: {
+			dotsStyles(newVal) {
+				this.dots = Object.assign(this.dots, this.dotsStyles)
+			},
+			mode(newVal) {
+				if (newVal === 'indexes') {
+					this.dots.width = 20
+					this.dots.height = 20
+				} else {
+					this.dots.width = 8
+					this.dots.height = 8
+				}
+			}
+
+		},
+		created() {
+			if (this.mode === 'indexes') {
+				this.dots.width = 20
+				this.dots.height = 20
+			}
+			this.dots = Object.assign(this.dots, this.dotsStyles)
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.customize {
+		bottom: #{24rpx};
+		.uni-swiper__dots-item {
+			margin-left: #{4rpx};
+			border-radius: #{6rpx};
+		}
+	}
+	.uni-swiper__warp {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: column;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.uni-swiper__dots-box {
+		/* #ifndef MP-ALIPAY */
+		position: absolute;
+		/* #endif */
+		/* #ifdef MP-ALIPAY */
+		margin: #{10px 0 0 0};
+		/* #endif */
+		bottom: 10px;
+		left: 0;
+		right: 0;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.uni-swiper__dots-item {
+		width: 8px;
+		border-radius: 100px;
+		margin-left: 6px;
+		// transition: width 0.2s linear;  不要取消注释,不然会不能变色
+	}
+	
+	.uni-swiper__dots-item:first-child {
+		margin: 0;
+	}
+
+	.uni-swiper__dots-default {
+		border-radius: 100px;
+	}
+
+	.uni-swiper__dots-long {
+		border-radius: 50px;
+	}
+
+	.uni-swiper__dots-bar {
+		border-radius: 50px;
+	}
+
+	.uni-swiper__dots-nav {
+		bottom: 0px;
+		height: 40px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		justify-content: flex-start;
+		align-items: center;
+		background-color: rgba(0, 0, 0, 0.2);
+	}
+
+	.uni-swiper__dots-nav-item {
+		/* overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap; */
+		font-size: $uni-font-size-base;
+		color: #fff;
+		margin: 0 15px;
+	}
+
+	.uni-swiper__dots-indexes {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		// flex: 1;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.uni-swiper__dots-indexes-text {
+		color: #fff;
+		font-size: $uni-font-size-sm;
+	}
+</style>

+ 151 - 0
components/page-component/app-account-balance/app-account-balance.vue

xqd
@@ -0,0 +1,151 @@
+<template>
+    <view class="app-account-balance dir-left-nowrap cross-center"
+          :class="[!margin?'no-margin':'', !round?'no-round':'',]" v-if="showBar">
+        <template v-if="showIntegral">
+            <view class="box-grow-1 split" :class="[showCount > 1?'':'data-col']">
+                <app-account-style :show-count="showCount" :icon="userCenter.account_bar.integral.icon"
+                                   :text="userCenter.account_bar.integral.text"
+                                   :value="userInfo?userInfo.integral:'-'"
+                                   :page="userCenter.account_bar.integral.navigate_enabled?'/plugins/integral_mall/index/index':'/pages/user-center/integral-detail/integral-detail'"></app-account-style>
+            </view>
+        </template>
+        <template v-if="showBalance">
+            <view class="box-grow-1 split" :class="[showCount > 1?'':'data-col']">
+                <app-account-style :show-count="showCount" :icon="userCenter.account_bar.balance.icon"
+                                   :text="userCenter.account_bar.balance.text"
+                                   :value="userInfo?userInfo.balance:'-'"
+                                   page="/pages/balance/balance"></app-account-style>
+            </view>
+        </template>
+        <template v-if="showCoupon">
+            <view class="box-grow-1 split" :class="[showCount > 1?'':'data-col']">
+                <app-account-style :show-count="showCount" :icon="userCenter.account_bar.coupon.icon"
+                                   :text="userCenter.account_bar.coupon.text"
+                                   :value="userInfo?userInfo.coupon:'-'"
+                                   page="/pages/coupon/index/index"></app-account-style>
+            </view>
+        </template>
+        <template v-if="showCard">
+            <view class="box-grow-1 split" :class="[showCount > 1?'':'data-col']">
+                <app-account-style :show-count="showCount" :icon="userCenter.account_bar.card.icon"
+                                   :text="userCenter.account_bar.card.text"
+                                   :value="userInfo?userInfo.card:'-'"
+                                   page="/pages/card/index/index"></app-account-style>
+            </view>
+        </template>
+    </view>
+</template>
+
+<script>
+    import {mapState} from 'vuex';
+    import appAccountStyle from "./app-account-style.vue";
+
+    export default {
+        name: 'app-account-balance',
+        components: {
+            'app-account-style': appAccountStyle,
+        },
+        props: {
+            margin: {
+                type: Boolean,
+                default: false,
+            },
+            round: {
+                type: Boolean,
+                default: false,
+            },
+            inputUserCenter: null,
+        },
+        computed: {
+            ...mapState({
+                userInfo: state => state.user.info,
+                storeUserCenter: state => state.mallConfig.user_center,
+            }),
+            showBar() {
+                if (this.userCenter
+                    && this.userCenter.account_bar
+                    && this.userCenter.account_bar.status == 0
+                ) return false;
+                return this.showBalance || this.showIntegral;
+            },
+            userCenter() {
+                if (this.inputUserCenter) return this.inputUserCenter;
+                return this.storeUserCenter;
+            },
+            showIntegral() {
+                if (!this.userCenter) return false;
+                if (this.userCenter.account_bar && this.userCenter.account_bar.integral.status == 1) return true;
+                return false;
+            },
+            showBalance() {
+                if (!this.userCenter) return false;
+                if (this.userCenter.recharge_setting
+                    && this.userCenter.recharge_setting.status == 1
+                    && this.userCenter.account_bar
+                    && this.userCenter.account_bar.balance.status == 1
+                ) return true;
+                return false;
+            },
+            showCoupon() {
+                if (!this.userCenter) return false;
+                if (this.userCenter.account_bar && this.userCenter.account_bar.coupon.status == 1) return true;
+                return false;
+            },
+            showCard() {
+                if (!this.userCenter) return false;
+                if (this.userCenter.account_bar && this.userCenter.account_bar.card.status == 1) return true;
+                return false;
+            },
+            showCount() {
+                let count = 0;
+                if (this.showIntegral) {
+                    count++;
+                }
+                if (this.showBalance) {
+                    count++;
+                }
+                if (this.showCoupon) {
+                    count++;
+                }
+                if (this.showCard) {
+                    count++;
+                }
+                return count;
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .app-account-balance.no-margin {
+        width: 100%;
+        margin: 0 auto;
+        box-shadow: none;
+    }
+
+    .app-account-balance.no-round {
+        border-radius: 0;
+    }
+
+    .app-account-balance {
+        width: #{702rpx};
+        height: #{100rpx};
+        background: #fff;
+        border-radius: #{16rpx};
+        margin: #{24rpx} auto;
+        box-shadow: 0 0 #{8rpx} rgba(0, 0, 0, .05);
+    }
+
+    .data-col {
+        width: 100%;
+        padding: 0 #{32rpx};
+    }
+
+    .split {
+        border-left: #{1rpx #eeeeee solid};
+
+        &:first-child {
+            border: none;
+        }
+    }
+</style>

+ 67 - 0
components/page-component/app-account-balance/app-account-style.vue

xqd
@@ -0,0 +1,67 @@
+<template>
+    <view class="app-account-style" :style="{width: 702/showCount + 'rpx'}">
+        <app-jump-button form arrangement="column" :url="page">
+            <template v-if="showCount >= 3">
+                <view class="dir-top-nowrap cross-center" style="width: 100%;overflow: hidden">
+                    <view class="value box-grow-0 t-omit">{{value}}</view>
+                    <view class="box-grow-1 dir-left-nowrap cross-center">
+                        <view class="box-grow-0">
+                            <image class="icon" :src="icon"></image>
+                        </view>
+                        <view class="unit box-grow-1">
+                            <view class="t-omit">{{text}}</view>
+                        </view>
+                    </view>
+                </view>
+            </template>
+            <template v-else>
+                <view class="dir-left-nowrap cross-center" :class="[showCount > 1?'':'data-col']">
+                    <view :class="[showCount > 1?'':'box-grow-0']">
+                        <image class="icon" :src="icon"></image>
+                    </view>
+                    <view class="unit" :class="[showCount > 1?'':'box-grow-1']">
+                        <view class="t-omit">{{text}}</view>
+                    </view>
+                    <view class="value t-omit" :class="[showCount > 1?'':'box-grow-0']">{{value}}</view>
+                </view>
+            </template>
+        </app-jump-button>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-account-style",
+        props: {
+            showCount: Number,
+            icon: String,
+            text: String,
+            value: [Number, String],
+            page: String,
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-account-style {
+    }
+
+    .icon {
+        width: #{26rpx};
+        height: #{26rpx};
+        display: block;
+        margin-right: #{12rpx};
+    }
+
+    .unit {
+        color: $uni-general-color-one;
+        margin-right: #{16rpx};
+        font-size: $uni-font-size-general-two;
+    }
+
+    .value {
+        color: #ffbb43;
+        font-weight: bold;
+        padding: 0 10upx;
+    }
+</style>

+ 146 - 0
components/page-component/app-ad/app-ad.vue

xqd
@@ -0,0 +1,146 @@
+<template>
+    <view>
+        <!--#ifndef MP-ALIPAY-->
+        <ad v-if="type=== ``" :unit-id="unitId" @load="onAdLoad" @error="onAdError" @close="onAdClose" ad-intervals="0"/>
+        <ad v-else-if="type === `video`" :unit-id="unitId" :ad-type="type" :ad-theme="theme" @load="onAdLoad" @error="onAdError" @close="onAdClose"/>
+        <ad v-else-if="type === `grid`" :unit-id="unitId" :ad-theme="theme" :ad-type="type" @load="onAdLoad" @error="onAdError" @close="onAdClose" grid-opacity="0.8" grid-count="5"/>
+        <!--#endif-->
+        <!--#ifdef MP-WEIXIN-->
+        <img v-else-if="type === `rewarded-video`" class="ad-img" @click="showRewardedVideoAd" :src="picUrl"/>
+        <img v-else-if="type === `interstitial`" class="ad-img" @click="showInterstitialAd" :src="picUrl"/>
+        <!--#endif-->
+        <view v-if="type ===`before-video`" class="ad-video">
+            <video :src="videoUrl" :ad-unit-id="unitId" :poster="picUrl" @adplay="onAdPlay" @adload="onAdLoad" @adclose="onAdClose" @aderror="onAdError"/>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-ad",
+        props: {
+            type: String,
+            unitId: String,
+            picUrl: String,
+            videoUrl: String,
+            // #ifndef MP-ALIPAY
+            theme: {
+                type: String,
+                default() {
+                    return 'white';
+                }
+            },
+            //#endif
+            //#ifdef MP-WEIXIN
+            couponUrl: String,
+            couponParams: Object,
+            //#endif
+        },
+        //#ifdef MP-WEIXIN
+        data() {
+            return {
+                //单屏问题
+                rewardedVideoAd: null,
+                interstitialAd: null
+            }
+        },
+        //#endif
+        //#ifdef MP-WEIXIN
+        created: function () {
+            this.init();
+        },
+        //#endif
+        methods: {
+            onAdLoad() {},
+            onAdPlay() {},
+            onAdClose() {},
+            onAdError() {},
+            //#ifdef MP-WEIXIN
+            init: function () {
+                switch (this.type) {
+                    case 'rewarded-video':
+                        this.initRewardedVideoAd();
+                        break;
+                    case 'interstitial':
+                        this.initInterstitialAd();
+                        break;
+                    default:
+                        break;
+                }
+            },
+            //#endif
+            //#ifdef MP-WEIXIN
+            initRewardedVideoAd: function () {
+                if (wx.createRewardedVideoAd) {
+                    this.rewardedVideoAd = wx.createRewardedVideoAd({adUnitId: this.unitId});
+                    this.rewardedVideoAd.onClose((res) => {
+                        if (res && res.isEnded) {
+                            this.getUserCoupon();
+                        }
+                    });
+                }
+            },
+            //#endif
+            //#ifdef MP-WEIXIN
+            initInterstitialAd: function () {
+                if (wx.createInterstitialAd) {
+                    this.interstitialAd = wx.createInterstitialAd({adUnitId: this.unitId});
+                }
+            },
+            //#endif
+            //#ifdef MP-WEIXIN
+            showRewardedVideoAd: function () {
+                this.rewardedVideoAd.show().catch(() => {
+                    this.rewardedVideoAd.load().then(() => this.rewardedVideoAd.show()).catch(err => {
+                        uni.showToast({title: err.errMsg, icon: 'none'});
+                    })
+                });
+            },
+            //#endif
+            //#ifdef MP-WEIXIN
+            showInterstitialAd: function () {
+                this.interstitialAd.show().catch((err) => {
+                    uni.showToast({title: err.errMsg, icon: 'none'});
+                });
+            },
+            //#endif
+            //#ifdef MP-WEIXIN
+            getUserCoupon() {
+                this.$request({
+                    url: this.couponUrl,
+                    method: 'POST',
+                    data: Object.assign({}, this.couponParams)
+                }).then(info => {
+                    if (info.code === 0) {
+                        this.$store.dispatch('page/actionSetCoupon', {
+                            list: info.data.list,
+                            type: 'award'
+                        })
+                    } else {
+                        uni.showToast({title: info.msg, icon: 'none'});
+                    }
+                })
+            },
+            //#endif
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    //#ifdef MP-WEIXIN
+    .ad-img {
+        display: block;
+        height: 139px;
+        width: 100vw;
+    }
+    //#endif
+    .ad-video {
+        height: 230px;
+        width: 100vw;
+
+        video {
+            height: 100%;
+            width: 100%;
+        }
+    }
+</style>

+ 347 - 0
components/page-component/app-area-picker/app-area-picker.vue

xqd
@@ -0,0 +1,347 @@
+<template>
+    <view class="dir-left-nowrap cross-center">
+        <!-- #ifdef MP-WEIXIN || MP-BAIDU || H5 -->
+        <picker class="box-grow-1 area-picker-left"
+                mode="multiSelector"
+                @change="bindMultiPickerChange"
+                @columnchange="bindMultiPickerColumnChange"
+                :value="multiIndex"
+                range-key="name"
+                :range="multiArray">
+            <text v-if="place!==`请选择`" class="address-name-color">{{place}}</text>
+            <text v-else class="address-place-name-color">{{place}}</text>
+        </picker>
+        <!-- #endif -->
+
+        <!-- #ifdef MP-ALIPAY || MP-TOUTIAO -->
+        <view class="area-picker-left" @click="showAreaPicker">
+            <text v-if="place!==`请选择`" class="address-name-color">{{place}}</text>
+            <text v-else class="address-place-name-color">{{place}}</text>
+        </view>
+        <view class="area-picker" :class="area_picker_show">
+            <view class="area-picker-bg" bindtap="hideAreaPicker">
+                <scroll-view></scroll-view>
+            </view>
+            <view class="area-picker-body">
+                <scroll-view>
+                    <view class="area-picker-top">
+                        <text class="area-picker-cancel" @click="hideAreaPicker">取消</text>
+                        <text class="area-picker-confirm" @click="areaPickerConfirm">确认</text>
+                    </view>
+                    <view class="area-picker-row">
+                        <picker-view v-if="multiIndex && multiIndex.length" indicator-style="height: 50px" style="width: 100%; height: 250px;"
+                                     :value="multiIndex" @change="areaPickerChange">
+                            <picker-view-column>
+                                <view v-if="multiArray[0]" v-for="(item,index) in multiArray[0]" :key="index"
+                                      style="line-height: 50px;height: 50px;text-align: center">{{item.name}}
+                                </view>
+                            </picker-view-column>
+                            <picker-view-column>
+                                <view v-if="multiArray[1]" v-for="(item,index) in multiArray[1]" :key="index"
+                                      style="line-height: 50px;height: 50px;text-align: center">{{item.name}}
+                                </view>
+                            </picker-view-column>
+                            <picker-view-column>
+                                <view v-if="multiArray[2]" v-for="(item,index) in multiArray[2]" :key="index"
+                                      style="line-height: 50px;height: 50px;text-align: center">{{item.name}}
+                                </view>
+                            </picker-view-column>
+                        </picker-view>
+                    </view>
+                </scroll-view>
+            </view>
+        </view>
+        <!-- #endif -->
+        <image class="box-grow-0 arrow-image" src="/static/image/icon/arrow-right.png"></image>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-area-picker",
+        props: {
+            ids: {
+                type: Array,
+                default: function () {
+                    return []
+                }
+            }
+        },
+        data() {
+            return {
+                tempIds : this.ids,
+                area_picker_show: '',
+                list: [],
+                multiIndex: [],
+                multiArray: [],
+                place: '',
+            }
+        },
+        created: function () {
+            this.tempIds = this.tempIds.concat();
+        },
+        watch: {
+            ids: {
+                handler:function (newData,oldData){
+                    this.tempIds = this.ids;
+                },
+            },
+            tempIds: {
+                handler:function (newData,oldData){
+                    const self = this;
+                    self.before((data) => {
+                        self.init(data);
+                    })
+                    this.$emit('ids', this.tempIds);
+                },
+                deep: true,
+                immediate: true,
+            },
+        },
+
+        methods: {
+            //#ifdef MP-ALIPAY || MP-TOUTIAO
+            showAreaPicker: function () {
+                this.area_picker_show = 'active';
+            },
+
+            hideAreaPicker: function () {
+                this.area_picker_show = '';
+            },
+
+            areaPickerConfirm: function (e) {
+                this.bindMultiPickerChange({detail: {value: this.multiIndex}});
+                this.hideAreaPicker();
+            },
+
+            areaPickerChange: function (e) {
+                const self = this;
+                if (self.area_picker_show == '') return;
+                let detail = {
+                    column: '',
+                    value: '',
+                };
+                for (let i = 0; i < 3; i++) {
+                    if (e.detail.value[i] !== self.multiIndex[i]) {
+                        detail.column = i;
+                        detail.value = e.detail.value[i];
+                        break;
+                    }
+                }
+                self.multiIndex = e.detail.value;
+                self.bindMultiPickerColumnChange({detail: detail});
+            },
+            //#endif
+
+            before(cb) {
+                const self = this;
+                const district = this.$storage.getStorageSync("_DISTRICT");
+                if (district) {
+                    cb(district);
+                } else {
+                    this.$request({
+                        url: self.$api.default.district,
+                    }).then(info => {
+                        if (info.code === 0) {
+                            this.$storage.setStorageSync("_DISTRICT", info.data.list);
+                            cb(info.data.list);
+                        }
+                    })
+                }
+            },
+
+            init: function (list) {
+                const null_status = this.tempIds.length === 3 && this.tempIds[0] != 0;
+                const ids = null_status ? this.tempIds : [2, 3, 4];
+                const multiIndex = this.getIndex(list, ids);
+                const multiArray = [
+                    list,
+                    list[multiIndex[0]].list,
+                    list[multiIndex[0]].list[multiIndex[1]].list
+                ];
+                let place = multiArray[0][multiIndex[0]].name
+                    + ','
+                    + multiArray[1][multiIndex[1]].name
+                    + ','
+                    + multiArray[2][multiIndex[2]].name;
+
+                /////初始化
+                let eve = [
+                    multiArray[0][multiIndex[0]],
+                    multiArray[1][multiIndex[1]],
+                    multiArray[2][multiIndex[2]],
+                ];
+                ////
+                this.setEvent(eve, null_status);
+                [this.list, this.multiArray, this.multiIndex, this.place] = [list, multiArray, multiIndex, null_status ? place : '请选择']
+            },
+
+            getIndex: function (list, data) {
+                let arr = [];
+                list.map((item, index) => {
+                    if (data[0] == item.id) arr.push(index)
+                });
+
+                list[arr[0]].list.map((item, index) => {
+                    if (data[1] == item.id) arr.push(index)
+                });
+
+                list[arr[0]].list[arr[1]].list.map((item, index) => {
+                    if (data[2] == item.id) arr.push(index);
+                });
+                return arr;
+            },
+
+            bindMultiPickerChange: function (e) {
+                let list = [
+                    this.multiArray[0][e.detail.value[0]],
+                    this.multiArray[1][e.detail.value[1]],
+                    this.multiArray[2][e.detail.value[2]],
+                ];
+
+                let place = list[0].name
+                    + ','
+                    + list[1].name
+                    + ','
+                    + list[2].name;
+                [this.multiIndex, this.place] = [e.detail.value, place];
+                this.setEvent(list);
+            },
+
+            setEvent: function (list, status = true) {
+                let data = {
+                    province: {
+                        id: list[0].id,
+                        name: list[0].name
+                    },
+                    city: {
+                        id: list[1].id,
+                        name: list[1].name
+                    },
+                    district: {
+                        id: list[2].id,
+                        name: list[2].name
+                    },
+                };
+                this.$emit('customevent', status ? data : null);
+            },
+
+            bindMultiPickerColumnChange: function (e) {
+                let data = {
+                    multiArray: this.multiArray,
+                    multiIndex: this.multiIndex
+                };
+                data.multiIndex[e.detail.column] = e.detail.value;
+                switch (e.detail.column) {
+                    case 0:
+                        data.multiIndex.splice(1, 1, 0);
+                        data.multiIndex.splice(2, 1, 0);
+                        data.multiArray.splice(1, 1, this.list[data.multiIndex[0]].list);
+                        data.multiArray.splice(2, 1, this.list[data.multiIndex[0]].list[data.multiIndex[1]].list);
+                        break;
+                    case 1:
+                        data.multiIndex.splice(2, 1, 0);
+                        data.multiArray.splice(2, 1, this.list[data.multiIndex[0]].list[data.multiIndex[1]].list);
+                        break;
+                }
+                [this.multiArray, this.multiIndex] = [data.multiArray, data.multiIndex]
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .area-picker-left {
+        min-width: #{115rpx};
+        font-size: #{28rpx};
+        padding-right: #{24rpx};
+        line-height: 1.5;
+        margin-left: auto;
+
+        .address-name-color {
+            color: #353535;
+        }
+
+        .address-place-name-color {
+            color: #999999;
+        }
+    }
+
+    .arrow-image {
+        width: #{12rpx};
+        height: #{24rpx};
+    }
+
+    /* #ifdef MP-ALIPAY || MP-TOUTIAO  */
+    .area-picker {
+        position: fixed;
+        z-index: 1600;
+        left: 0;
+        top: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        flex-direction: column;
+        transform: translateY(100%);
+        transition: 250ms opacity;
+        opacity: 0;
+        background: rgba(0, 0, 0, .3);
+    }
+
+    .area-picker.active {
+        transform: translateY(0);
+        opacity: 1;
+    }
+
+    .area-picker .area-picker-bg {
+        flex-grow: 1;
+        position: relative;
+    }
+
+    .area-picker .area-picker-bg > scroll-view {
+        left: 0;
+        top: 0;
+        height: 100%;
+        width: 100%;
+        position: absolute;
+    }
+
+    .area-picker .area-picker-body {
+        flex-grow: 0;
+        height: #{600rpx};
+        background: #fff;
+        transform: translateY(100%);
+        transition: 250ms transform;
+        position: relative;
+    }
+
+    .area-picker.active .area-picker-body {
+        transform: translateY(0);
+    }
+
+    .area-picker .area-picker-body > scroll-view {
+        left: 0;
+        top: 0;
+        height: 100%;
+        width: 100%;
+        position: absolute;
+    }
+
+    .area-picker .area-picker-cancel,
+    .area-picker .area-picker-confirm {
+        display: inline-block;
+        padding: #{24rpx};
+        color: #888;
+    }
+
+    .area-picker .area-picker-confirm {
+        float: right;
+        color: #00aa00;
+    }
+
+    .area-picker .area-picker-row {
+        width: 100%;
+        height: #{500rpx};
+    }
+    /* #endif */
+</style>

+ 240 - 0
components/page-component/app-associated-link/app-associated-link.vue

xqd
@@ -0,0 +1,240 @@
+<template>
+	<view class="app-associated-link dir-left-nowrap main-left cross-center" :style="{backgroundColor: background}">
+		<app-jump-button style="width: 100%" form :open_type="link.open_type" :url="link.value" :params="link.params">
+			<view class="dir-left-nowrap" :class="[`${className}`]" :style="{backgroundColor: background}">
+				<view v-if="styleNum == '1'" class="app-link-title box-grow-1" :style="{textAlign: position}">
+					<image class="app-title-icon" v-if="picSwitch" :src="picUrl">
+					</image>
+					<text :style="{color: color}">{{title}}</text>
+				</view>
+				<view v-else-if="styleNum == '2'" class="app-link-title box-grow-1 main-center cross-center">
+                    <image :style="{backgroundColor: styleColor}" class="app-title-line" :src="styleImg.line"></image>
+                    <view :style="{color: color}">{{title}}</view>
+                    <image :style="{backgroundColor: styleColor}" class="app-title-line" :src="styleImg.line"></image>
+                </view>
+                <view v-else-if="styleNum == '3'" class="app-link-title box-grow-1 main-center cross-center">
+                    <view class="app-title-line star">
+                    	<view :style="{backgroundColor: styleColor}" class="top-icon"></view>
+                    	<view :style="{backgroundColor: styleColor}" class="bottom-icon"></view>
+                    </view>
+                    <view :style="{color: color}">{{title}}</view>
+                    <view class="app-title-line star">
+                    	<view :style="{backgroundColor: styleColor}" class="top-icon"></view>
+                    	<view :style="{backgroundColor: styleColor}" class="bottom-icon"></view>
+                    </view>
+                </view>
+                <view v-else-if="styleNum == '4'" class="app-link-title box-grow-1 dir-left-nowrap cross-center">
+                    <image :style="{backgroundColor: styleColor}" class="app-title-line div" :src="styleImg.div"></image>
+                    <view :style="{color: color}">{{title}}</view>
+                </view>
+                <view v-else-if="styleNum == '5'" class="app-link-title box-grow-1 dir-left-nowrap cross-center">
+                    <view :style="[{'border-color':`${styleColor}`,'background-color':`${background}`}]" class="app-title-line radius"></view>
+                    <view :style="{color: color}">{{title}}</view>
+                </view>
+                <view v-if="arrowsSwitch" class="app-more-text">更多</view>
+				<icon class="app-icon" v-if="arrowsSwitch" type></icon>
+			</view>
+		</app-jump-button>
+	</view>
+</template>
+
+<script>
+    import {mapState} from 'vuex';
+
+    export default {
+        name: "app-associated-link",
+        data() {
+            return {
+                className: ''
+            }
+        },
+        computed: {
+            ...mapState({
+                styleImg: state => state.mallConfig.__wxapp_img.diy
+            })
+        },
+	    props: {
+            arrowsSwitch: {
+                type: Boolean,
+	            default: function() {
+	                return false;
+	            }
+            },
+            background: {
+                type: String,
+	            default: function() {
+	                return '#E11B1B';
+	            }
+            },
+            color: {
+                type: String,
+	            default: function() {
+	                return '#F4EBEB';
+	            }
+            },
+            link: {
+                type: Object,
+	            default: function() {
+	                return {
+                        open_type: "tel",
+                        params: [
+	                        {
+                                value: "111",
+	                        }
+                        ],
+	                }
+	            }
+            },
+            styleColor: {
+                type: String,
+	            default: function() {
+	                return '#353535';
+	            }
+            },
+            picSwitch: {
+                type:Boolean,
+	            default: function() {
+	                return false;
+	            }
+            },
+            picUrl: {
+                type: String,
+	            default: function() {
+	                return '';
+	            }
+            },
+		    position: {
+                type: String,
+	            default: function() {
+	                return 'left';
+	            }
+		    },
+		    title: {
+                type: String,
+			    default: function() {
+			        return '';
+			    }
+		    },
+		    styleNum: {
+                type: String,
+			    default: function() {
+			        return '1';
+			    }
+		    },
+		    fontSize: {
+                type: String,
+			    default: function() {
+			        return '36';
+			    }
+		    }
+	    },
+        created() {
+            let that = this;
+            if(this.fontSize == '36') {
+            	this.className = 'big-style'
+            }else if(this.fontSize == '28') {
+            	this.className = 'medium-style'
+            }else {
+            	this.className = 'small-style'
+            }
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-associated-link {
+		width: #{750rpx};
+		height: #{72rpx};
+		position: relative;
+		.app-more-text {
+			font-size: #{24rpx};
+			color: #999;
+			white-space: nowrap;
+		}
+		.app-icon {
+			background-image: url("../../../static/image/icon/arrow-right.png");
+			width: #{12rpx};
+			height: #{22rpx};
+			background-repeat: no-repeat;
+			background-size: cover;
+			position: absolute;
+			right: #{24rpx};
+			top: 50%;
+			transform: translateY(-50%);
+		}
+		.app-link-title {
+        	flex-grow: 1;
+        	width: 100%;
+        	padding: 0 24rpx;
+        	.app-title-line {
+		        width: 82rpx;
+		        height: 5rpx;
+		        margin: 0 20rpx;
+		    }
+		    .app-title-line.star {
+		        width: 28rpx;
+		        height: 20rpx;
+		        .top-icon {
+		        	width: 28rpx;
+		        	height: 4rpx;
+		        	margin-bottom: 12rpx;
+		        }
+		        .bottom-icon {
+		        	width: 28rpx;
+		        	height: 4rpx;
+		        }
+		    }
+		    .app-title-line.div {
+		        width: 8rpx;
+		        height: 28rpx;
+		        margin: 0 10rpx 0 0;
+		    }
+		    .app-title-line.radius {
+		        width: 28rpx;
+		        margin: 0 10rpx 0 0;
+		        border-radius: 14rpx;
+		        border: 2rpx solid #353535;
+		        height: 28rpx;
+		    }
+		}
+    	.big-style {
+	        height: 50rpx;
+	        line-height: 50rpx;
+	        font-size: 36rpx;
+	        width: 100%;
+          padding-right: 48rpx;
+	        .app-title-icon {
+		        width: 50rpx;
+		        height: 50rpx;
+		        margin-right: 16rpx;
+		        vertical-align: top;
+		    }
+    	}
+    	.medium-style {
+	        height: 36rpx;
+	        line-height: 36rpx;
+	        font-size: 28rpx;
+	        width: 100%;
+	        padding-right: 48rpx;
+	        .app-title-icon {
+		        width: 36rpx;
+		        height: 36rpx;
+		        margin-right: 16rpx;
+		        vertical-align: top;
+		    }
+    	}
+    	.small-style {
+	        height: 28rpx;
+	        line-height: 28rpx;
+	        font-size: 24rpx;
+	        width: 100%;
+	        padding-right: 48rpx;
+	        .app-title-icon {
+		        width: 28rpx;
+		        height: 28rpx;
+		        margin-right: 16rpx;
+		        vertical-align: top;
+		    }
+    	}
+	}
+</style>

+ 1195 - 0
components/page-component/app-attr/app-attr.vue

xqd
@@ -0,0 +1,1195 @@
+<template>
+    <view class="app-attr">
+        <view @click="alert">
+            <slot name="button"></slot>
+        </view>
+	   <view class="modal" v-if="display === 'block'"  @click="close" >
+		   <view class="safe-area-inset-bottom u-attr-fixed" >
+			   <view class="content" @tap.native.stop="preventD">
+				   <image src="/static/image/icon/close.png" class="close" @click="close"></image>
+				   <view class="first dir-left-nowrap" :class="sign =='wholesale' ? 'no-border' : ''">
+					   <view class="box-grow-0 img" @click.stop="clickImg(attrPic)">
+						   <app-image :img-src="attrPic" width="100%" height="100%"></app-image>
+					   </view>
+					   <view class="info">
+						   <view class="dir-left-nowrap cross-center" :class="priceColor">
+							   <view class="dir-left-nowrap cross-center">
+								   <view class="dir-left-nowrap cross-center" :style="{'color':getTheme.color}" v-if="(selectAttr && selectAttr.extra) || goods.extra">
+									   <view>{{selectAttr.extra ? selectAttr.extra.value + selectAttr.extra.name :
+										   goods.extra.value + goods.extra.name}}
+									   </view>
+									   <view v-if="!(goods.sign === 'integral_mall' && attrPrice == 0)">+</view>
+								   </view>
+								   <app-price :theme="theme" v-if="!(goods.sign === 'integral_mall' && attrPrice == 0) && sign !== 'wholesale'" type="text-price-all" :price="attrPrice"
+											  :default-price="goods.price"></app-price>
+                                   <app-price :theme="theme" v-if="sign === 'wholesale'" type="text-price-all" :price="attrPrice" :max="goods.level_show == 1 ? goods.price_member_max : goods.price_max" :min="goods.level_show == 1 ? goods.price_member_min : goods.price_min"></app-price>
+							   </view>
+							   <view v-if="goods.level_show === 1 && (selectAttr || sign == 'wholesale')">
+								   <app-member-mark :theme="theme"></app-member-mark>
+							   </view>
+						   </view>
+						   <view class="stock">库存:{{attrNum}}</view>
+					   </view>
+				   </view>
+				   <view class="second" :class="sign ==='wholesale' ? 'no-padding' : ''">
+					   <slot name="extra"></slot>
+                        <view class="wholesale" v-if="sign ==='wholesale'">
+                            <view class="wholesale-attr-list dir-left-nowrap cross-center" v-if="index != goods.attr_groups.length -1" v-for="(item, index) in goods.attr_groups" :key="index">
+                                <view class="position cross-center">
+                                    <view class="wholesale-attr-group t-omit-two">{{item.attr_group_name}}</view>
+                                </view>
+                                <scroll-view @scroll="scrollGet($event,index)" :scroll-left="item.scrollLeft" class="wholesale-attr-item" scroll-x="true">
+                                    <view class="scroll-attr-group cross-center">
+                                        <view class="wholesale-attr-group t-omit">{{item.attr_group_name.length > 10 ? item.less_attr_group_name : item.attr_group_name}}</view>
+                                    </view>
+                                    <view class="attr-name attr-background" v-for="(attr, key) in item.attr_list" :key="key" :style="{'background-color': attr.active ? theme.background : '#f2f2f2'}" @click.stop="chooseAttr(index,attr)">{{attr.attr_name}}
+                                        <view class="attr-number" :style="{'right':`${attr.length + 'rpx'}`,'background-color': `${theme.background}`}" v-if="index == 0 && attr.number > 0">{{attr.number}}</view>
+                                    </view>
+                                    <view class="attr-name attr-background" style="visibility: hidden">
+                                    </view>
+                                </scroll-view>
+                                <view @click.stop="toBottom(index)" class="right-icon">
+                                    <view></view>
+                                    <image src="/static/image/icon/right.png"></image>
+                                </view>
+                            </view>
+                            <view class="wholesale-attr-group-list">
+                                <view class="wholesale-attr-group" v-for="(item, index) in goodsAttr" :key="index">
+                                   <view class="dir-left-nowrap">
+                                        <view class="wholesale-attr-item main-between">
+                                            <view class="dir-top-nowrap main-center attr-name t-omit">
+                                                <view class="t-omit">{{item.attr_list[item.attr_list.length - 1].attr_name}}</view>
+                                                <view class="attr-price">¥{{goods.level_show === 1 ? item.price_member : item.price}}</view>
+                                            </view>
+                                            <view class="dir-left-nowrap wholesale-number-box cross-center">
+                                               <image :src="item.number <= 0 ? '/static/image/plugins/un-low.png' : '/static/image/plugins/low.png'" class="block box-grow-0 cross-center main-center" @click.stop="wholesaleNumberSub(index)">
+                                               </image>
+                                               <view class="wholesale-number-input box-grow-0 cross-center main-center">
+                                                   <app-input height="60" type="number" v-model="item.number" paddingLeft="0" :center="true" placeholder=" " @blur="wholesaleNumberBlur(index)" :focus="false" width="88"></app-input>
+                                               </view>
+                                               <image :src="item.number >= item.stock? '/static/image/plugins/un-add.png' : '/static/image/plugins/add.png'" class="block box-grow-0 cross-center main-center" @click.stop="wholesaleNumberAdd(index)"></image>
+                                            </view>
+                                        </view>
+                                   </view>
+                               </view>
+                           </view>
+                        </view>
+					   <view class="attr-group" v-if="goods.type === 'goods' && sign !='wholesale'" v-for="(item, index) in newAttrGroupList" :key="index">
+						   <view class="attr-group-name">{{item.attr_group_name}}</view>
+						   <view class="dir-left-wrap">
+							   <view v-for="(attr, key) in item.attr_list" :key="key" class="attr-item"
+                                     :style="{'background-color':attr.checked && sign !== 'gift' ? theme.background : ''}"
+									 :class="attr.checked && sign !== 'gift' ?  'active' : attr.checked && sign === 'gift' ?  theme + '-background active' : 'attr-item-default' + (attr.attr_num_0 ? ' attr_num_0' : '')"
+									 @click.stop="storeAttrClick(attr.attr_id, item.attr_group_id)">{{attr.attr_name}}
+							   </view>
+						   </view>
+					   </view>
+					   <view v-if="chooseNumber" class="dir-left-nowrap number-box cross-center">
+						   <view class="box-grow-1">数量</view>
+						   <image :src="number <= 1 ? '/static/image/icon/can-be-reduced.png' : '/static/image/cart/can-be-reduced.png'" class="block box-grow-0 cross-center main-center" @click.stop="numberSub">
+						   </image>
+						   <view class="number-input box-grow-0 cross-center main-center">
+							   <app-input type="number" v-model="number" paddingLeft="0" :center="true" placeholder=" " @blur="numberBlur"
+										  :focus="false" width="88"></app-input>
+						   </view>
+						   <image src="/static/image/cart/can-be-added.png" class="block box-grow-0 cross-center main-center" @click.stop="numberAdd"></image>
+					   </view>
+				   </view>
+                    <view v-if="sign ==='wholesale'" class="total">已选<text>{{totalNumber}}</text>件 总计<text>¥{{totalPrice > 0? totalPrice : '0.00'}}</text></view>
+					<view v-if="sell_time > 0">
+						<app-sell-tip :time="sell_time" @changeTime="changeTime"></app-sell-tip>
+					</view>
+					<app-jump-button form>
+					<view class="three dir-left-nowrap">
+						<view class="bd-btn box-grow-1" v-if="cartShow"
+                             :style="{'background-color': theme.background_s,'color': theme.secondary_text}" @click="cart">{{addText}}
+						</view>
+						<view v-if="showRight" class="bd-btn box-grow-1  buy"  :style="{'background-color': theme.background,'color': theme.main_text}" @click="buy">
+						   {{rightRemindText}}
+						</view>
+					</view>
+					</app-jump-button>
+			   </view>
+		   </view>
+	   </view>
+    </view>
+</template>
+
+<script>
+    import { mapState, mapGetters } from "vuex";
+    import appPrice from "../../page-component/goods/app-price.vue";
+    import appMemberMark from '../../page-component/app-member-mark/app-member-mark.vue';
+    import appSellTip from '@/components/page-component/goods/app-sell-tip.vue';
+	import goodsMixin from '@/core/goods-mixin.js';
+    export default {
+        name: "app-attr",
+		mixins: [goodsMixin],
+        components: {
+            appPrice,
+            appMemberMark,
+			appSellTip
+        },
+        props: {
+            goods: Object,
+            attrGroupList: Array,
+            attrCart: {
+                type: Array,
+                default() {
+                    return [];
+                }
+            },
+            cartShow: {
+                type: [Boolean, Number],
+                default() {
+                    return true
+                }
+            },
+            previewUrl: String,
+            submitUrl: String,
+            goodsId: {
+                type:Number,
+                default() {
+                    return 0
+                }
+            },
+            show: Number,
+            buyText: {
+                type: String,
+                default() {
+                    return '立即购买';
+                }
+            },
+            plugin: {
+                default: '',
+            },
+	        theme: {
+                type: Object,
+	        },
+            chooseNumber: {
+                type: Boolean,
+                default: true,
+            },
+            noPay: {
+                type: Boolean,
+	            default: false,
+            },
+			buyClick: {
+                type: Boolean,
+				default: false,
+			},
+			addText: {
+				type: String,
+				default: '加入购物车',
+			},
+			is_show_buy: {
+				type: Boolean,
+				default: true,
+			},
+            sign: {
+                type: String
+            },
+            totalPrice: {
+                type: String,
+                default: '0.00',
+            },
+            totalNumber: {
+                type: Number,
+                default: 0,
+            },
+            discount: {
+                type: Number,
+                default: 0,
+            },
+            wholesaleType: {
+                type: Number,
+                default: 0,
+            },
+            attentionSign: {
+                type: String
+            }
+        },
+        data() {
+            return {
+                display: 'none',
+                number: 1,
+                selectAttr: null,
+                newAttrGroupList: null,
+				pic_url: null,
+                // 商品批发
+                activeAttr: [],
+                goodsAttr: [],
+				sell_time: 0,
+            };
+        },
+        watch: {
+            show() {
+                if (this.display === 'block') {
+                	this.selectAttr = null;
+					this.close();
+				} else if (this.display === 'none') {
+					this.alert();
+				}
+            },
+            newData: {
+                handler() {
+                    this.$emit('attr', this.newData)
+                },
+                immediate: true,
+            },
+            attrGroupList: {
+                handler() {
+					this.newAttrGroupList = this.attrGroupList;
+					if (this.display == 'block') {
+                        this.alert();
+					}
+				},
+				immediate: true
+			},
+	        goods: {
+                handler() {
+                    if (this.display == 'block') {
+                        this.alert();
+                    }
+					if (this.goods) {
+						this.sell_time = this.goods.sell_time;
+					}
+                },
+		        immediate: true
+	        }
+        },
+        mounted() {
+            if(this.sign == 'wholesale') {
+                this.pic_url = this.goods.attr_groups[0].attr_list[0].pic_url
+                if(this.goods.attr_groups.length == 1) {
+                    this.goodsAttr = this.goods.attr;
+                }else {
+                    for(let item of this.goods.attr_groups) {
+                        let para = {
+                            attr_group_name: '',
+                            attr_group_id: '',
+                            attr_id: '',
+                            attr_name: ''
+                        };
+                        para.attr_group_name = item.attr_group_name
+                        para.attr_group_id = item.attr_group_id
+                        para.attr_id = item.attr_list[0].attr_id
+                        para.attr_name = item.attr_list[0].attr_name
+                        this.activeAttr.push(para)
+                    }
+                    for(let item of this.goods.attr) {
+                        let same = true;
+                        for(let i = 0;i < item.attr_list.length -1;i++) {
+                            let first = {
+                                attr_group_name: item.attr_list[i].attr_group_name,
+                                attr_group_id: item.attr_list[i].attr_group_id,
+                                attr_id: item.attr_list[i].attr_id,
+                                attr_name: item.attr_list[i].attr_name
+                            }
+                            if(JSON.stringify(first) != JSON.stringify(this.activeAttr[i])) {
+                                same = false;
+                            }
+                        }
+                        if(same) {
+                            this.goodsAttr.push(item)
+                        }
+                    }
+                }
+            }
+        },
+        methods: {
+            alert() {
+                if (this.attrGroupList && this.attrGroupList.length === 0) {
+                    return;
+                }
+                if(this.sign !== 'wholesale') {
+                    let attr_group_list = this.attrGroupList;
+                    let attrs = this.goods.attr;
+                    let select_attr = null;
+                    this.number = 1;
+                    if (attr_group_list.length === 1) {
+                        for (let i in attrs) {
+                            for (let j in attr_group_list[0].attr_list) {
+                                if (attr_group_list[0].attr_list[j].attr_id == attrs[i].attr_list[0].attr_id) {
+                                    if (attrs[i].stock > 0) {
+                                        if (attrs.length === 1) {
+                                            attr_group_list[0].attr_list[j].checked = true;
+                                        }
+                                        attr_group_list[0].attr_list[j].attr_num_0 = false;
+                                        this.pic_url = attr_group_list[0].attr_list[j].pic_url;
+                                    } else {
+                                        this.number = 0;
+                                        attr_group_list[0].attr_list[j].checked = false;
+                                        attr_group_list[0].attr_list[j].attr_num_0 = true;
+                                    }
+                                }
+                            }
+                        }
+                        if (attrs.length === 1) {
+                            select_attr = attrs[0];
+                            this.$emit('attrtap', select_attr);
+                        }
+                    }
+                    this.newAttrGroupList = attr_group_list;
+                    if(this.goods.selectAttr) {
+                        this.selectAttr = this.goods.selectAttr
+                    }else {
+                        this.selectAttr = select_attr;
+                    }
+                }
+                this.display = 'block';
+            },
+            scrollGet(e,index) {
+                this.goods.attr_groups[index].scrollLeft = e.detail.scrollLeft;
+                this.$forceUpdate();
+            },
+            toBottom(index) {
+                this.$nextTick().then(() => {
+                    this.goods.attr_groups[index].scrollLeft = 99999;
+                    this.$forceUpdate();
+                })
+            },
+            chooseAttr(index,attrItem) {
+                let that = this;
+                for(let attr of that.goods.attr_groups[index].attr_list) {
+                    attr.active = false;
+                    if(attr.attr_id == attrItem.attr_id && attr.attr_name == attrItem.attr_name) {
+                        attr.active = true;
+                    }
+                }
+                if(index == 0) {
+                    that.pic_url = attrItem.pic_url;
+                }
+                that.activeAttr[index].attr_id = attrItem.attr_id
+                that.activeAttr[index].attr_name = attrItem.attr_name
+                that.goodsAttr = [];
+                for(let item of that.goods.attr) {
+                    let same = true;
+                    for(let i = 0;i < item.attr_list.length -1;i++) {
+                        let first = {
+                            attr_group_name: item.attr_list[i].attr_group_name,
+                            attr_group_id: item.attr_list[i].attr_group_id,
+                            attr_id: item.attr_list[i].attr_id,
+                            attr_name: item.attr_list[i].attr_name
+                        }
+                        if(JSON.stringify(first) != JSON.stringify(this.activeAttr[i])) {
+                            same = false;
+                        }
+                    }
+                    if(same) {
+                        that.goodsAttr.push(item)
+                    }
+                }
+                that.$forceUpdate();
+                that.count();
+            },
+            wholesaleNumberSub(index) {
+                if(this.goodsAttr[index].number == 0) {
+                    return  false;
+                }
+                this.goodsAttr[index].number--;
+                if(this.goods.attr_groups.length == 1) {
+                    this.pic_url = this.goodsAttr[index].pic_url;
+                }
+                this.count(index);
+            },
+            wholesaleNumberAdd(index) {
+                if(this.goodsAttr[index].number > this.goodsAttr[index].stock || this.goodsAttr[index].number == this.goodsAttr[index].stock) {
+                    return false
+                }
+                this.goodsAttr[index].number++;
+                if(this.goods.attr_groups.length == 1) {
+                    this.pic_url = this.goodsAttr[index].pic_url;
+                }
+                this.count(index);
+            },
+            wholesaleNumberBlur(index) {
+                if(+this.goodsAttr[index].number > +this.goodsAttr[index].stock) {
+                    this.goodsAttr[index].number = this.goodsAttr[index].stock
+                }
+                if(this.goods.attr_groups.length == 1) {
+                    this.pic_url = this.goodsAttr[index].pic_url;
+                }
+                this.count(index);
+            },
+            count(index) {
+                let that =this;
+                this.$emit('attrtap', {goods: that.goods, goodsAttr:that.goodsAttr});
+                setTimeout(()=>{
+                    that.selectAttr = that.goodsAttr[index];
+                })
+            },
+            close() {
+                this.display = 'none';
+                this.$emit('close', false)
+            },
+            preventD() {
+            },
+            storeAttrClick(attr_id, attr_group_id) {
+                let attr_group_list = JSON.parse(JSON.stringify(this.newAttrGroupList));
+                let attrs = this.goods.attr;
+                let checkedAttr = [];
+                let attr_cart = this.attrCart;
+                for (let i in attr_group_list) {
+                    for (let j in attr_group_list[i].attr_list) {
+                        let temp = attr_group_list[i].attr_list[j];
+                        if (parseInt(attr_group_list[i].attr_group_id) == parseInt(attr_group_id)) {
+                            if (parseInt(temp.attr_id) === parseInt(attr_id)) {
+                                if (temp.checked) {
+                                    temp.checked = false;
+                                } else {
+                                    temp.checked = true;
+                                }
+                                if (temp.attr_num_0) {
+                                    return;
+                                }
+                            } else {
+                                temp.checked = false;
+                            }
+                        }
+                        if (temp.checked) {
+                            if (i == 0) {
+                                this.pic_url = attr_group_list[0].attr_list[j].pic_url;
+							}
+                            checkedAttr.push(attr_group_list[i].attr_group_id + '-' + temp.attr_id);
+                        }
+                    }
+                }
+
+                function inArray(val, arr) {
+                    return arr.some(function (v) {
+                        return val == v;
+                    })
+                }
+
+                let attrNum_0 = [];
+                let select_attr = null;
+                let number = 1;
+                for (let i in attrs) {
+                    let arr = [];
+                    let sign = 0;
+                    for (let j in attrs[i].attr_list) {
+                        let param = attrs[i].attr_list[j].attr_group_id + '-' + attrs[i].attr_list[j].attr_id;
+                        if (!inArray(param, checkedAttr)) {
+                            sign += 1;
+                            arr.push(param);
+                        }
+                    }
+                    if (attrs[i].stock == 0 && sign <= 1) {
+                        attrNum_0 = attrNum_0.concat(arr);
+                    }
+                    if (sign == 0) {
+                        if (!select_attr) {
+                            select_attr = {};
+                        }
+                        select_attr = attrs[i];
+                        attr_cart.forEach(item => {
+
+                            if (item.attr_id == select_attr.id) {
+                                number = item.num;
+                            }
+                        });
+                        if (select_attr.stock <= 0) {
+                            uni.showToast({
+                                title: '库存不足',
+                                icon: 'none'
+                            });
+                            return;
+                        }
+                        if (select_attr.stock <= number) {
+                            number = select_attr.stock;
+                        }
+                    }
+                }
+                if (checkedAttr.length == 0) {
+                    select_attr = null;
+                }
+
+                //库存为0的规格添加标识
+                for (let i in attr_group_list) {
+                    for (let j in attr_group_list[i].attr_list) {
+                        let cAttr = attr_group_list[i].attr_list[j];
+                        let cParam = attr_group_list[i].attr_group_id + '-' + cAttr.attr_id;
+                        if (inArray(cParam, attrNum_0) && !inArray(cParam, checkedAttr)) {
+                            cAttr.attr_num_0 = true;
+                        } else {
+                            cAttr.attr_num_0 = false;
+                        }
+                    }
+                }
+                this.newAttrGroupList = attr_group_list;
+                this.selectAttr = select_attr;
+                this.number = number;
+                this.$emit('attrtap', this.selectAttr);
+            },
+            numberBlur(number) {
+                number = parseInt(number.value);
+                if (number > this.attrNum) {
+                    uni.showToast({
+                        title: '库存不足',
+                        icon: 'none'
+                    });
+                    number = this.attrNum;
+                }
+                this.$emit('attrtap', this.selectAttr);
+                return this.number = number;
+            },
+            numberSub() {
+                let number = this.number;
+                if (number <= 1) {
+                    return true;
+                }
+                number--;
+                this.number = number;
+                this.$emit('attrtap', this.selectAttr);
+            },
+            numberAdd() {
+                let number = this.number;
+                number++;
+                if (number > this.attrNum) {
+                    uni.showToast({
+                        title: '库存不足',
+                        icon: 'none'
+                    });
+                    this.number = this.attrNum;
+                    return;
+                }
+                this.number = number;
+                this.$emit('attrtap', this.selectAttr);
+            },
+            cart() {
+                if (!this.submit()) {
+                    return false;
+                }
+                let select_attr = this.selectAttr;
+                if (this.goods.sign === 'pick' || this.goods.sign === 'community') {
+                	this.$emit('add', select_attr, this.number);
+                	return;
+				}
+			// 普通商品
+			if (this.goods.type === 'goods') {
+                if (this.goods.sign === 'miaosha') {
+                    this.$request({
+                        url: this.$api.miaosha.add_cart,
+                        data: {
+                            miaosha_goods_id: select_attr.goods_id,
+                            attr_id: select_attr.id,
+                            num: this.number
+                        },
+                        method: 'post'
+                    }).then(e => {
+                        uni.showToast({
+                            title: e.msg,
+                            type: 'success'
+                        });
+                        this.display = 'none';
+                        this.selectAttr.number = this.number;
+                        this.$emit('selectNumber', this.selectAttr);
+                    }).catch(() => {
+                        this.display = 'none';
+                    });
+                } else if (this.goods.sign === 'flash_sale') {
+					this.$request({
+						url: this.$api.flash_sale.add_cart,
+						data: {
+							flash_goods_id: select_attr.goods_id,
+							attr_id: select_attr.id,
+							num: this.number
+						},
+						method: 'post'
+					}).then(e => {
+						uni.showToast({
+							title: e.msg,
+							type: 'success'
+						});
+						this.display = 'none';
+						this.selectAttr.number = this.number;
+						this.$emit('selectNumber', this.selectAttr);
+					}).catch(() => {
+						this.display = 'none';
+					});
+				}else if(this.goods.sign == 'wholesale') {
+                    if(this.totalNumber < this.goods.wholesaleGoods.rise_num) {
+                        uni.showToast({
+                            title: '至少采购' + this.goods.wholesaleGoods.rise_num + this.goods.unit,
+                            image: '/static/image/plugins/tip.png',
+                            duration: 1000
+                        });
+                        return false
+                    }
+                    let para = [];
+                    for(let item of this.goods.attr) {
+                        if(item.number > 0) {
+                            para.push(item)
+                        }
+                    }
+                    this.$request({
+                        url: this.$api.wholesale.cart,
+                        data: {
+                            attr:  JSON.stringify(para)
+                        },
+                        method:'post'
+                    }).then(response => {
+                        this.display = 'none';
+                        if (response.code === 0) {
+                            for(let item of this.goods.attr) {
+                                item.number = '0'
+                            }
+                            for(let item of this.goodsAttr) {
+                                item.number = '0'
+                            }
+                            this.count();
+                            uni.hideLoading();
+                            uni.showToast({
+                                title: '添加成功',
+                                duration: 1000
+                            });
+                        }
+                    }).catch(response => {
+                        this.display = 'none';
+                    })
+                } else {
+                    this.$request({
+                        url: this.$api.cart.add,
+                        data: {
+                            goods_id: select_attr.goods_id,
+                            attr: select_attr.id,
+                            num: this.number
+                        },
+                        method: 'post'
+                    }).then(e => {
+                        if (e.code === 0) {
+							uni.showToast({
+								title: e.msg,
+								type: 'success'
+							});
+							this.display = 'none';
+							this.selectAttr.number = this.number;
+							this.$emit('selectNumber', this.selectAttr);
+						} else {
+							uni.showToast({
+								title: e.msg,
+								icon: "none",
+								duration: 2500
+							});
+							this.display = 'none';
+						}
+						}).catch(() => {
+							this.display = 'none';
+						});
+					}
+				//	虚拟商品
+				} else if (this.goods.type === 'ecard') {
+					uni.showToast({
+						title: '虚拟商品不允许加入购物车',
+						icon: 'none'
+					});
+				}
+            },
+            buy() {
+                // 判断登入
+                if (!this.$user.isLogin()) {
+                    this.$user.getInfo();
+                    return;
+                }
+				if (this.goods.sell_time > 0) {
+					this.rightTip();
+					return ;
+				}
+                if (!this.submit()) return false;
+                if (this.noPay) {
+                    this.$emit('pay', this.number);
+                    return;
+                }
+                if (this.buyClick) {
+                    this.display = 'none';
+                    this.selectAttr.number = this.number;
+                    this.$emit('buyClick', this.selectAttr);
+                    return false;
+				}
+                if(this.goods.sign == 'wholesale') {
+                    if(this.totalNumber < this.goods.wholesaleGoods.rise_num) {
+                        uni.showToast({
+                            title: '至少采购' + this.goods.wholesaleGoods.rise_num + this.goods.unit,
+                            image: '/static/image/plugins/tip.png',
+                            duration: 1000
+                        });
+                        return false
+                    }
+                    let para = {};
+                    let mch_list = [{
+                        mch_id: 0,
+                        goods_list: []
+                    }];
+                    for(let item of this.goods.attr) {
+                        if(item.number > 0) {
+                           para = {
+                                id: item.goods_id,
+                                attr: [],
+                                num: item.number,
+                                cat_id: 0,
+                                goods_attr_id: item.id
+                            }
+                            for(let attr of item.attr_list) {
+                                let attrList = {
+                                    attr_id: attr.attr_id,
+                                    attr_group_id: attr.attr_group_id
+                                }
+                                para.attr.push(attrList)
+
+                            }
+                            mch_list[0].goods_list.push(para)
+                        }
+                    }
+                    let url = `/pages/order-submit/order-submit?mch_list=${JSON.stringify(mch_list)}`;
+                    if (this.submitUrl && this.previewUrl) {
+                        url += `&preview_url=${encodeURIComponent(this.previewUrl)}&submit_url=${encodeURIComponent(this.submitUrl)}&plugin=${this.plugin}`;
+                    }
+                    uni.navigateTo({
+                        url: url
+                    })
+                }else {
+                    let goods = this.goods;
+                    let number = this.number;
+                    let select_attr = this.selectAttr;
+                    let goods_attr_id = select_attr.id;
+                    let attr = [];
+                    for (let i in select_attr.attr_list) {
+                        attr.push({
+                            attr_id: select_attr.attr_list[i].attr_id,
+                            attr_group_id: select_attr.attr_list[i].attr_group_id
+                        });
+                    }
+                    let mch_list = [{
+                        mch_id: goods.mch_id ? goods.mch_id : 0,
+                        goods_list: [{
+                            id: this.goodsId ? this.goodsId: goods.id,
+                            attr: attr,
+                            num: number,
+                            cat_id: 0,
+                            goods_attr_id: goods_attr_id
+                        }]
+                    }];
+                    let url = `/pages/order-submit/order-submit?mch_list=${JSON.stringify(mch_list)}`;
+                    if (this.submitUrl && this.previewUrl) {
+                        url += `&preview_url=${encodeURIComponent(this.previewUrl)}&submit_url=${encodeURIComponent(this.submitUrl)}&plugin=${this.plugin}`;
+                    }
+                    uni.navigateTo({
+                        url: url
+                    })
+                }
+            },
+            submit() {
+                let select_attr = this.selectAttr;
+                if(this.goods.sign === 'wholesale') {
+                    return true;
+                }
+                if (!select_attr) {
+                    uni.showToast({
+                        title: '请先选规格',
+                        icon: 'none'
+                    });
+                    return false;
+                }
+                if (select_attr.stock <= 0) {
+                    uni.showToast({
+                        title: '库存不足',
+                        icon: 'none'
+                    });
+                    return false;
+				}
+                if (this.number <= 0) {
+                    uni.showToast({
+                        title: '数量不能为0',
+                        icon: 'none'
+                    });
+                    return false;
+                }
+                if (!this.goods) {
+                    return false;
+                }
+
+                return true;
+            },
+            clickImg(src) {
+                uni.previewImage({
+                    current: 0,
+                    urls: [src]
+                });
+            },
+			changeTime(time) {
+				this.sell_time = time;
+			}
+        },
+        computed: {
+            ...mapState({
+                gConfig: state => state.gConfig,
+				isTip: state => state.mallConfig.mall.setting.is_remind_sell_time
+            }),
+            attrPic() {
+                if (this.pic_url) {
+                    return this.pic_url;
+                } else if (this.goods) {
+					return this.goods.cover_pic;
+                } else {
+					return ``;
+				}
+            },
+            priceColor() {
+                if (this.goods && this.goods.level_show === 1) {
+                    return `member`;
+                } else {
+                    return this.theme + '-color';
+                }
+            },
+            attrNum() {
+                if (this.selectAttr) {
+                    return this.selectAttr.stock;
+                } else if (this.goods) {
+					return this.goods.goods_num;
+                }  else {
+					return 0;
+				}
+            },
+            attrPrice() {
+				if (this.selectAttr) {
+                    let price;
+					if (this.goods.level_show === 1) {
+                        price = this.selectAttr.price_member
+					} else {
+						price = this.selectAttr.price;
+					}
+                    if(this.sign == 'wholesale') {
+                        if(this.selectAttr.number == 0) {
+                            return 'undefined'
+                        }
+                        if(this.wholesaleType == 0) {
+                            price = (price*(this.discount/10)).toFixed(2);
+                            return price
+                        }else {
+                            price = (price - this.discount).toFixed(2);
+                            return price
+                        }
+                    }else {
+                        return price
+                    }
+				} else if(this.sign == 'wholesale') {
+                    return 'undefined';
+                } else if (this.goods) {
+                    if (this.goods.hasOwnProperty('price_min')) {
+    					return this.goods.price_min;
+    				} else {
+    					return this.goods.price;
+    				}
+				} else {
+					return 0;
+				}
+            },
+            newData() {
+                const { number, display, selectAttr } = this;
+                return {
+                    number,
+                    display,
+                    selectAttr
+                }
+            },
+	        ...mapGetters('iPhoneX', {
+                boolEmpty: 'getBoolEmpty'
+	        }),
+            ...mapGetters({
+                userInfo: 'user/info',
+            }),
+			showRight() {
+				return !(this.isTip == 0 && this.goods.sell_time > 0) && this.is_show_buy
+			},
+			remindParams() {
+				return {
+					sell_time: this.sell_time,
+					goods_id: this.goods.id,
+					template_message_list: this.goods.template_message_list,
+					buy_text: this.buyText
+				};
+			},
+        }
+    }
+
+</script>
+
+<style scoped lang="scss">
+    .app-attr {
+        background-color: #ffffff;
+        .modal {
+            background-color: rgba(0, 0, 0, 0.5);
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+			z-index: 1603;
+        }
+		.content {
+			width: 100%;
+			background-color: #ffffff;
+			border-radius: #{16rpx} #{16rpx} 0 0;
+			.close {
+				width: #{30rpx};
+				height: #{30rpx};
+				position: absolute;
+				right: #{24rpx};
+				top: #{24rpx};
+				background-color: #ffffff;
+			}
+			.first {
+				margin: 0 #{24rpx};
+				border-bottom: #{1rpx} solid #e2e2e2;
+                &.no-border {
+                    border-bottom: 0;
+                }
+				.img {
+					width: #{200rpx};
+					height: #{200rpx};
+					border: #{4rpx} solid #ffffff;
+					border-radius: #{8rpx};
+					margin-top: #{-64rpx};
+					display: block;
+				}
+				.info {
+					margin: #{36rpx} 0 #{26rpx} #{24rpx};
+					font-size: $uni-font-size-import-two;
+					line-height: 1;
+					.stock {
+						font-size: $uni-font-size-weak-one;
+						color: $uni-general-color-two;
+						margin-top: #{18rpx};
+					}
+					view {
+						&:first-child {
+							margin-right: #{12rpx};
+						}
+					}
+					>.member {
+						color: #f39800;
+					}
+				}
+			}
+			.second {
+				max-height: #{650rpx};
+                overflow-y: auto;
+				overflow-x: hidden;
+				padding: #{4rpx} #{24rpx};
+				font-size: $uni-font-size-general-two;
+                &.no-padding {
+                    padding: 0;
+                }
+				.attr-group {
+					padding: #{32rpx} 0;
+					border-bottom: #{1rpx} solid #e2e2e2;
+					.attr-group-name {
+						color: $uni-general-color-one;
+						margin-bottom: #{20rpx};
+					}
+					.attr-item {
+						margin-right: #{20rpx};
+						padding: #{15rpx 24rpx};
+						border-radius: #{8rpx};
+						margin-bottom: #{20rpx};
+						&.attr-item-default {
+							background-color: #f2f2f2;
+							color: $uni-important-color-black;
+						}
+						&.active {
+							color: #ffffff;
+						}
+						&.attr_num_0 {
+							color: #cdcdcd;
+							background-color: #f7f7f7;
+						}
+					}
+				}
+				.number-box {
+					color: $uni-general-color-one;
+					height: #{124rpx};
+					.block {
+						width: #{60rpx};
+						height: #{60rpx};
+						margin: 0 #{4rpx};
+						&.disabled {
+							background-color: #fbfbfb;
+							color: $uni-general-color-two;
+						}
+					}
+					.number-input {
+						width: #{88rpx};
+						height: #{60rpx};
+						color: $uni-important-color-black;
+						font-size: $uni-font-size-general-one;
+						background-color: $uni-weak-color-two;
+					}
+				}
+			}
+			.three {
+				height: #{110rpx};
+				width: 100%;
+                padding: 20upx 24upx;
+				font-size: $uni-font-size-general-one;
+			}
+		}
+    }
+	.buy {
+		color: #ffffff;
+        margin-left: 20upx;
+	}
+    .wholesale {      
+        .wholesale-attr-list {
+            height: #{88rpx};
+            margin: 0 #{24rpx};
+            border-bottom: #{1rpx} solid #e2e2e2;
+            position: relative;
+            &:first-of-type {
+                border-top: #{1rpx} solid #e2e2e2;
+            }
+            .position {
+                position: absolute;
+                left: 0;
+                top: #{2rpx};
+                z-index: 10000;
+                background-color: #fff;
+                height: #{83rpx};
+            }
+            .wholesale-attr-group {
+                max-width: #{200rpx};
+                flex-shrink: 0;
+                margin-right: #{4rpx};
+                font-size: #{20rpx};
+                color: #666666;
+                background-color: #fff;
+            }
+            .right-icon {
+                position: absolute;
+                right: #{-24rpx};
+                top: 0;
+                height: #{84rpx};
+                padding-left: #{10rpx};
+                width: #{50rpx};
+                z-index: 10000;
+                background-color: #fff;
+                view {
+                    height: #{40rpx};
+                    width: #{3rpx};
+                    background-color: #e2e2e2;
+                    position: absolute;
+                    left: 0;
+                    top: #{24rpx};
+                }
+                image {
+                    margin-top: #{33rpx};
+                    height: #{22rpx};
+                    width: #{16rpx};
+                }
+            }
+            .wholesale-attr-item {
+                height: 100%;
+                width: 100%;
+                white-space : nowrap;
+                .scroll-attr-group {
+                    display: inline-block;
+                    visibility: hidden;
+                    .wholesale-attr-group {
+                        height: #{44rpx};
+                    }
+                }
+                .attr-name {
+                    color: #fff;
+                    padding: 0 #{22rpx};
+                    height: #{56rpx};
+                    line-height: #{56rpx};
+                    border-radius: #{8rpx};
+                    font-size: #{26rpx};
+                    margin-left: #{20rpx};
+                    display: inline-block;
+                    position: relative;
+                    min-width: #{10rpx};
+                    .attr-number {
+                        color: #fff;
+                        position: absolute;
+                        top: #{-15rpx};
+                        height: #{30rpx};
+                        line-height: #{26rpx};
+                        border: #{2rpx} solid #fff;
+                        padding: 0 #{10rpx};
+                        border-radius: #{15rpx};
+                        font-size: #{20rpx};
+                        z-index: 100;
+                    }
+                    &.attr-background {
+                        background-color: #f2f2f2;
+                        color: #353535;
+                    }
+                }
+            }
+        }
+        .wholesale-attr-group-list {
+            margin-top: #{20rpx};
+            .wholesale-attr-group {
+                margin-bottom: #{20rpx};
+                .wholesale-attr-item {
+                    padding: #{20rpx};
+                    background-color: #f7f7f7;
+                    width: 100%;
+                    .attr-name {
+                        width: 60%;
+                        .attr-price {
+                            color: #999999;
+                            font-size: #{24rpx};
+                        }
+                    }
+                    .wholesale-number-box {
+                        color: $uni-general-color-one;
+                        .block {
+                            width: #{60rpx};
+                            height: #{60rpx};
+                            margin: 0 #{4rpx};
+                            &.disabled {
+                                background-color: #fbfbfb;
+                                color: $uni-general-color-two;
+                            }
+                        }
+
+                        .wholesale-number-input {
+                            width: #{88rpx};
+                            height: #{60rpx};
+                            color: $uni-important-color-black;
+                            font-size: $uni-font-size-general-one;
+                            background-color: #fff;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    .total {
+        width: 100%;
+        height: #{80rpx};
+        line-height: #{80rpx};
+        text-align: right;
+        padding: 0 24rpx;
+        color: #353535;
+        font-size: #{28rpx};
+        border-top: #{1rpx} solid #e2e2e2;
+        text {
+            color: #ff4544;
+        }
+    }
+    .u-attr-fixed {
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        width: 100%;
+        background-color: #ffffff;
+    }
+    .bd-btn {
+        line-height: 70upx;
+        text-align: center;
+        font-size: 26upx;
+        border-radius: 35upx;
+    }
+</style>

+ 111 - 0
components/page-component/app-buy-prompt/app-buy-prompt.vue

xqd
@@ -0,0 +1,111 @@
+<template>
+    <view v-if="buy_data && buy_data.length" class="buy-prompt-box" :style="[hiddenHeight()]">
+        <swiper circular vertical autoplay>
+            <block v-for="(item,index) in buy_data" :key="index">
+                <swiper-item @touchmove.stop.prevent catchtouchmove='catchTouchMove'>
+                    <view class="box">
+                        <view class="cross-center center dir-left-nowrap">
+                            <image class="box-grow-0 image" :src="item.avatar"></image>
+                            <view class="box-grow-0 text-a">{{item.time_str}}</view>
+                            <view class="box-grow-0 text-b">{{item.content}}</view>
+                        </view>
+                    </view>
+                </swiper-item>
+            </block>
+        </swiper>
+    </view>
+</template>
+
+<script>
+    import { mapState} from "vuex";
+    export default {
+        name: "app-buy-prompt",
+        props: {
+            isShowAttention: Boolean
+        },
+        data() {
+            return {
+                buy_data: null,
+            }
+        },
+        computed: {
+            ...mapState({
+                systemInfo: state => state.gConfig.systemInfo,
+                mBarHeight: state => state.gConfig.mBarHeight
+            }),
+            hiddenHeight() {
+                return () => {
+                    let n;
+                    if (this.isShowAttention) {
+                        n = uni.upx2px(280);
+                    } else {
+                        n = uni.upx2px(97);
+                    }
+                    // #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO
+                    n = n + this.systemInfo.statusBarHeight + this.mBarHeight;
+                    // #endif
+                    return Object.assign({}, {
+                        top: n + 'px',
+                    })
+                }
+            },
+        },
+        created: function () {
+            const self = this;
+            self.$request({
+                url: self.$api.index.buy_data,
+            }).then(info => {
+                if (info.code === 0) {
+                    self.buy_data = info.data;
+                }
+            });
+        },
+        methods: {
+            catchTouchMove: function () {
+                return false
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    swiper {
+        height: #{60rpx};
+        width: #{568rpx};
+    }
+
+    .buy-prompt-box {
+        position: fixed;
+        top: #{97rpx};
+        left: #{24rpx};
+        z-index: 9999;
+    }
+
+    .buy-prompt-box .box {
+        display: inline-block
+    }
+
+    .buy-prompt-box .center {
+        height: #{60rpx};
+        background-color: rgba(0, 0, 0, 0.8);
+        border-radius: #{30rpx};
+        color: #fff;
+    }
+
+    .buy-prompt-box .image {
+        height: #{60rpx};
+        width: #{60rpx};
+        border-radius: 50%;
+    }
+
+    .buy-prompt-box .text-a {
+        padding-left: #{10rpx};
+        font-size: #{24rpx};
+    }
+
+    .buy-prompt-box .text-b {
+        padding-right: #{24rpx};
+        font-size: #{24rpx};
+        white-space: nowrap;
+    }
+</style>

+ 195 - 0
components/page-component/app-cash-model/app-cash-model.vue

xqd
@@ -0,0 +1,195 @@
+<template>
+    <view>
+        <app-model v-model="display" type="3">
+            <view slot="title">{{title}}</view>
+            <view slot="content">
+                <view class="dir-top-nowrap">
+                    <view class="cash-type-item dir-left-nowrap cross-center" v-if="isAuto">
+                        <image class="icon" src="/static/image/icon/cash/icon-auto.png"></image>
+                        <view class="dir-left-nowrap box-grow-1 cash-type-box cross-center"
+                              @click="payTypeChange(`auto`)">
+                            <!--  #ifdef MP-WEIXIN -->
+                            <view class="box-grow-1 cross-center">微信零钱</view>
+                            <!--  #endif -->
+                            <!--  #ifdef MP-ALIPAY -->
+                            <view class="box-grow-1 cross-center">支付宝余额</view>
+                            <!--  #endif -->
+                            <!--  #ifndef MP-WEIXIN || MP-ALIPAY -->
+                            <view class="box-grow-1 cross-center">自动</view>
+                            <!--  #endif -->
+                            <view class="cross-center">
+                                <view v-if="payType === `auto`" class="radio-single-active" :style="{'background-color': theme.background}"></view>
+                                <view v-else class="radio-single"></view>
+                            </view>
+                        </view>
+                    </view>
+                    <view class="cash-type-item dir-left-nowrap cross-center" v-if="isWx">
+                        <image class="icon" src="/static/image/icon/cash/icon-wechat.png"></image>
+                        <view class="dir-left-nowrap cross-center box-grow-1 cash-type-box"
+                              @click="payTypeChange(`wx`)">
+                            <view class="box-grow-1">微信线下打款</view>
+                            <view class="box-grow-0">
+                                <view v-if="payType === `wx`" class="radio-single-active" :style="{'background-color': theme.background}"></view>
+                                <view v-else class="radio-single"></view>
+                            </view>
+                        </view>
+                    </view>
+                    <view class="cash-type-item dir-left-nowrap cross-center" v-if="isAlipay">
+                        <image class="icon" src="/static/image/icon/cash/icon-alipay.png"></image>
+                        <view class="dir-left-nowrap cross-center box-grow-1 cash-type-box"
+                              @click="payTypeChange(`alipay`)">
+                            <view class="box-grow-1">支付宝线下打款</view>
+                            <view class="box-grow-0">
+                                <view v-if="payType === `alipay`" class="radio-single-active" :style="{'background-color': theme.background}"></view>
+                                <view v-else class="radio-single"></view>
+                            </view>
+                        </view>
+                    </view>
+                    <view class="cash-type-item dir-left-nowrap cross-center" v-if="isBank">
+                        <image class="icon" src="/static/image/icon/cash/icon-bank.png"></image>
+                        <view class="dir-left-nowrap cross-center box-grow-1 cash-type-box"
+                              @click="payTypeChange(`bank`)">
+                            <view class="box-grow-1">银联线下打款</view>
+                            <view class="box-grow-0">
+                                <view v-if="payType === `bank`" class="radio-single-active" :style="{'background-color': theme.background}"></view>
+                                <view v-else class="radio-single"></view>
+                            </view>
+                        </view>
+                    </view>
+                    <view class="cash-type-item dir-left-nowrap cross-center" v-if="isBalance">
+                        <image class="icon" src="/static/image/icon/cash/icon-balance.png"></image>
+                        <view class="dir-left-nowrap cross-center box-grow-1 cash-type-box"
+                              @click="payTypeChange(`balance`)">
+                            <view class="box-grow-1">商城余额</view>
+                            <view class="box-grow-0">
+                                <view v-if="payType === `balance`" class="radio-single-active" :style="{'background-color': theme.background}"></view>
+                                <view v-else class="radio-single"></view>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+            </view>
+        </app-model>
+    </view>
+</template>
+
+<script>
+    import appModel from '../../basic-component/app-model/app-model.vue';
+
+    export default {
+        name: "app-cash-model",
+        components: {appModel},
+        props: {
+            title: {
+                type: String,
+                default() {
+                    return '提现方式';
+                }
+            },
+            payType: String,
+            /* balance bank alipay wx auto*/
+            isAuto: {
+                type: Boolean,
+                default() {
+                    return false
+                }
+            },
+            isWx: {
+                type: Boolean,
+                default() {
+                    return false
+                }
+            },
+            isAlipay: {
+                type: Boolean,
+                default() {
+                    return false
+                }
+            },
+            isBank: {
+                type: Boolean,
+                default() {
+                    return false
+                }
+            },
+            isBalance: {
+                type: Boolean,
+                default() {
+                    return false
+                }
+            },
+            value: {
+                type: Boolean,
+                default() {
+                    return false
+                }
+            },
+            theme: {
+                type: Object,
+            }
+        },
+        data() {
+            return {
+                display: this.value
+            }
+        },
+        watch: {
+            value: function (value) {
+                this.display = value;
+            },
+            display: function (value) {
+                this.$emit('input', value);
+            }
+        },
+        computed: {},
+        methods: {
+            payTypeChange(pay_type) {
+                // this.payType = pay_type;
+                this.$emit('change', pay_type);
+                this.display = false;
+            },
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .cash-type-item {
+        height: #{120rpx};
+        padding-left: #{32rpx};
+
+        > view {
+            height: 100%;
+        }
+
+        .cash-type-box {
+            border-bottom: 1px solid #E2E2E2;
+            padding-right: #{32rpx};
+
+
+            .radio-single {
+                width: #{40rpx};
+                height: #{40rpx};
+                border-radius: 50%;
+                background-color: white;
+                border: #{1rpx} solid #e2e2e2;
+            }
+
+            .radio-single-active {
+                width: #{40rpx};
+                height: #{40rpx};
+                border-radius: 50%;
+                background-repeat: repeat;
+                background-size: 100% 100%;
+                background-image: url("../../../static/image/icon/yes-radio.png");
+            }
+        }
+
+        .icon {
+            height: #{40rpx};
+            width: #{40rpx};
+            margin-right: #{16rpx};
+            display: flex;
+            justify-content: center;
+        }
+    }
+</style>

+ 94 - 0
components/page-component/app-category-list/app-category-list.vue

xqd
@@ -0,0 +1,94 @@
+<template>
+    <scroll-view scroll-y class="app-category-list"
+                 :style="{height: `${noSetHeight}` ? `${noSetHeight}` : `${setHeight}`}">
+		<view class="app-item dir-left-nowrap" v-for="(item, index) in list" :key="index" @click="active(item, index)">
+			<view class="app-border" :style="{'background-color': item.active === true ? theme.background : '#f7f7f7'}"></view>
+			<view class="app-text" :style="{'color': item.active === true ? theme.color : ''}">{{item.name}}</view>
+		</view>
+	</scroll-view>
+</template>
+
+<script>
+
+    export default {
+        name: 'app-category-list',
+	    props: {
+            list: {
+                type: Array,
+	            default() {
+                    return [];
+	            }
+            },
+            windowHeight: {
+                type: Number,
+	            default() {
+                    return 0
+	            }
+            },
+            windowWidth: {
+                type: Number,
+                default() {
+                    return 0
+                }
+            },
+            botHeight: {
+                type: Number,
+                default() {
+                    return 0
+                }
+            },
+            noSetHeight: {
+                type: String
+            },
+			theme: Object
+	    },
+	    methods: {
+            active(item, index) {
+                this.$emit('click', {
+                    item, index
+                });
+            }
+	    },
+	    computed: {
+		    setHeight() {
+                let bottom = 0;
+                if (this.$parent.$parent.$children[0].tabbarbool) {
+                    bottom = this.botHeight;
+                }
+                return (this.windowHeight * (750 / this.windowWidth)) - bottom - 88 + 'rpx';
+		    }
+	    }
+    }
+</script>
+
+<style lang="scss">
+	.app-border {
+		width: #{8rpx};
+		height: #{106rpx};
+		background-color: #f7f7f7;
+	}
+	.app-category-list {
+		width: #{204rpx};
+		background-color: #f7f7f7;
+	}
+	.app-item {
+		width: #{204rpx};
+		height: #{106rpx};
+		background-color: #f7f7f7;
+	}
+	.app-text {
+		background-color: #f7f7f7;
+		width: #{196rpx};
+		height: #{106rpx};
+		line-height: #{106rpx};
+		text-align: center;
+		font-size: #{28rpx};
+		color: #666666;
+		word-break: break-all;
+		text-overflow: ellipsis;
+		display: -webkit-box;
+		-webkit-box-orient: vertical;
+		-webkit-line-clamp: 1;
+		overflow: hidden;
+	}
+</style>

+ 87 - 0
components/page-component/app-check-in/app-check-in.vue

xqd
@@ -0,0 +1,87 @@
+<template>
+    <view class="app-check-in dir-left-nowrap" :class="'main-' + textPosition" :style="{backgroundImage: `url(`+backgroundPicUrl+`)`}">
+        <app-hotspot :hotspot="hotspot">
+            <view @click='checkIn' style="width:100%;height:100%;"></view>
+        </app-hotspot>
+        <view class="dir-top-nowrap main-center" v-if="userInfo.check_in && showText" :style="{color:textColor,textAlign: textPosition}">
+            <view class="box-grow-0 first">今天签到可获得{{userInfo.check_in.todayAward}}</view>
+            <view class="box-grow-0 second">已连续签到{{userInfo.check_in.continue}}天</view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapGetters} from 'vuex';
+    import appHotspot from '../../basic-component/app-hotspot/app-hotspot.vue';
+    import checkInAward from './check-in-award.js';
+
+    export default {
+        name: "app-check-in",
+        components: {
+            'app-hotspot': appHotspot
+        },
+        props: {
+            backgroundPicUrl: {
+                type: String,
+                default() {
+                    return '';
+                }
+            },
+            hotspot: {
+                type: Object,
+                default() {
+                    return {};
+                }
+            },
+            showText: {
+                type: Boolean,
+                default() {
+                    return false;
+                }
+            },
+            textColor: String,
+            textPosition: String,
+        },
+        computed: {
+            ...mapGetters({
+                userInfo: 'user/info',
+            })
+        },
+        methods: {
+            checkIn() {
+                uni.showLoading({
+                    title: '签到中'
+                });
+                checkInAward.getAward(1, 1).then(result => {
+                    uni.hideLoading();
+                    uni.showToast({
+                        title: '签到成功',
+                        icon: 'success',
+                        mask: false
+                    });
+                    this.$store.dispatch('user/info');
+                }).catch(e => {
+                    uni.hideLoading();
+                    uni.showToast({
+                        title: e,
+                        mask: false,
+                        icon: 'none'
+                    })
+                });
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-check-in {
+        width: 100%;
+        height: #{200rpx};
+        padding: 0 #{50rpx};
+        position: relative;
+
+        background-position: center;
+        background-size: cover;
+        background-repeat: no-repeat;
+    }
+</style>

+ 55 - 0
components/page-component/app-check-in/check-in-award.js

xqd
@@ -0,0 +1,55 @@
+import request from '../../../core/request.js';
+import api from '../../../core/appOnLaunch.js';
+
+export default {
+    getAward(status,day) {
+        return new Promise((resolve, reject) => {
+            request({
+                url: api.check_in.sign_in,
+                data: {
+                    status: status,
+                    day: day ? day : 1
+                }
+            }).then(response=>{
+                if(response.code == 0) {
+                    this.checkInResult(response.data.queueId, response.data.token).then(result => {
+                        return resolve(result);
+                    }).catch(e => {
+                        return reject(e);
+                    });
+                }else {
+                    return reject(response.msg);
+                }
+            }).catch(response => {
+                return reject(response);
+            });
+        });
+    },
+    checkInResult(queueId, token) {
+        return new Promise((resolve, reject) => {
+            request({
+                url: api.check_in.sign_in_result,
+                data: {
+                    queueId: queueId,
+                    token: token
+                }
+            }).then(response=>{
+                if(response.code == 0) {
+                    if (response.data.retry == 1) {
+                        this.checkInResult(queueId, token).then(result => {
+                            return resolve(result);
+                        }).catch(e => {
+                            return reject(e);
+                        });
+                    } else {
+                        return resolve(response.data);
+                    }
+                }else {
+                    return reject(response.msg);
+                }
+            }).catch(response => {
+                return reject(response);
+            });
+        });
+    },
+}

+ 197 - 0
components/page-component/app-clerk-historys/app-clerk-historys.vue

xqd
@@ -0,0 +1,197 @@
+<template>
+    <view @touchmove.stop.prevent="" class="app-clerk-historys" v-show="visible">
+        <view class="history-box">
+            <view class="top-box">
+                <span class="text">历史核销记录</span>
+                <image @click="close" class="close" src="/static/image/icon/icon-close.png"></image>
+            </view>
+            <scroll-view class="scroll-view" scroll-y="true" @scrolltolower="lower">
+                <view class="item" v-for="item in list" :key="item.id">
+                    <view class="title">已核销({{item.use_number}}次)</view>
+                    <view class="clerk-info">
+                        <view class="list-item">
+                            核销时间:{{item.clerked_at}}
+                        </view>
+                        <view class="list-item">
+                            核销门店:{{item.store_name}}
+                        </view>
+                        <view class="list-item">
+                            核销员:{{item.clerk_user}}
+                        </view>
+                    </view>
+                </view>
+                <view class="not-more" v-if="isMore">没有更多数据!</view>
+            </scroll-view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-clerk-historys',
+        components: {},
+        props: {
+            isShow: {
+                type: Boolean,
+                default: false
+            },
+            userCardId: {
+                type: Number,
+                default: 0,
+            }
+        },
+        watch: {
+            isShow(newVal) {
+                if (!newVal) {
+                    this.visible = false;
+                }
+
+                if (newVal) {
+                    this.page = 1;
+                    this.list = [];
+                    this.getList();
+                }
+            }
+        },
+        data() {
+            return {
+                visible: false,
+                list: [],
+                page: 1,
+                isMore: false,
+            }
+        },
+        methods: {
+            close() {
+                this.$emit('update:isShow', false);
+            },
+            getList() {
+                let that = this;
+                that.$showLoading({
+                    text: '加载中...'
+                });
+                that.$request({
+                    url: that.$api.card.history,
+                    data: {
+                        user_card_id: that.userCardId,
+                        page: that.page,
+                    },
+                }).then(response => {
+                    that.$hideLoading();
+                    that.visible = true;
+                    if (response.code === 0) {
+                        that.list = that.list.concat(response.data.list);
+                        that.page = response.data.list.length > 0 ? that.page + 1 : that.page;
+                        if (response.data.list.length === 0) {
+                            that.isMore = true;
+                        }
+                    } else {
+                        uni.showToast({
+                            title: response.msg,
+                            icon: 'none',
+                            duration: 2000,
+                        });
+                    }
+                }).catch(() => {
+                    that.$hideLoading();
+                });
+            },
+            lower() {
+                this.getList();
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-clerk-historys {
+        width: 100%;
+        height: 100vh;
+        background: rgba(0, 0, 0, 0.5);
+        position: fixed;
+        top: 0;
+        left: 0;
+
+        .history-box {
+            position: absolute;
+            left: 0;
+            bottom: 0;
+            width: 100%;
+            max-height: 920#{rpx};
+            overflow: hidden;
+            border-top-left-radius: 16#{rpx};
+            border-top-right-radius: 16#{rpx};
+            background: #f7f7f7;
+
+            .top-box {
+                width: 100%;
+                height: 120#{rpx};
+                display: flex;
+                justify-content: center;
+                align-items: center;
+                position: relative;
+                background: #ffffff;
+
+                .text {
+                    font-size: 38#{rpx};
+                    color: #353535;
+                }
+
+                .close {
+                    width: 35#{rpx};
+                    height: 35#{rpx};
+                    position: absolute;
+                    right: 24#{rpx};
+                    top: 24#{rpx};
+                }
+            }
+
+            .scroll-view {
+                max-height: 800#{rpx};
+
+                .item {
+                    display: flex;
+                    flex-direction: column;
+                    margin-top: 18#{rpx};
+                    background: #ffffff;
+                    max-height: 800#{rpx};
+
+                    .title {
+                        height: 80#{rpx};
+                        display: flex;
+                        align-items: center;
+                        font-size: 28#{rpx};
+                        color: #353535;
+                        border-bottom: 1#{rpx} solid #dcdcdc;
+                        padding-left: 24#{rpx};
+                    }
+
+                    .clerk-info {
+                        display: flex;
+                        flex-direction: column;
+                        padding: 25#{rpx} 24#{rpx};
+
+                        .list-item {
+                            margin-bottom: 20#{rpx};
+                            font-size: 28#{rpx};
+                            color: #353535;
+                        }
+                        .list-item:last-child {
+                            margin-bottom: 0;
+                        }
+                    }
+                }
+
+                .not-more {
+                    width: 100%;
+                    display: flex;
+                    justify-content:center;
+                    align-items: center;
+                    color: #353535;
+                    font-size:28#{rpx};
+                    padding:20#{rpx} 0;
+                }
+            }
+        }
+    }
+</style>

+ 103 - 0
components/page-component/app-common/app-wechat-share.vue

xqd
@@ -0,0 +1,103 @@
+<template>
+    <view v-if='is_add_show' class="app-view" @click="close">
+        <view class="app-wechat-share point-box"
+              :style="{'background-color':setting.add_app_bg_color,
+              'opacity':setting.add_app_bg_transparency / 100,
+              'border-radius': setting.add_app_bg_radius + `rpx`}"
+        >
+            <view class='triangle'
+                  :style="{'border-bottom':'16rpx solid ' + setting.add_app_bg_color,'opacity': setting.add_app_bg_transparency / 100}"
+            ></view>
+            <view class='dir-left-nowrap cross-center'>
+                <image @click='close'
+                       v-if='setting.add_app_icon_color_type == 2'
+                       class='icon-fork'
+                       src='/static/image/icon/fork_black.png'
+                ></image>
+                <view class='cross-center point-text'
+                      :style="{'color':setting.add_app_text_color}">
+                    {{ setting.add_app_text }}
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+<script>
+
+export default {
+    name: "app-wechat-share",
+    data() {
+        return {
+            //is_add_show: false,
+            setting: {
+                add_app_bg_color: '#FFFFFF',
+                add_app_bg_radius: 36,
+                add_app_bg_transparency: 100,
+                add_app_icon_color_type: 2,
+                add_app_text: '请点击右上角的“...”按钮,分享给好友',
+                add_app_text_color: '#353535',
+            },
+        }
+    },
+    computed: {
+        is_add_show() {
+            // #ifdef H5
+            return this.$store.state.share.status;
+            // #endif
+        },
+    },
+    methods: {
+        close: function () {
+            this.$store.commit('share/status', false);
+        }
+    },
+    destroyed(){
+        // #ifdef H5
+        this.$store.commit('share/status', false);
+        // #endif
+    },
+    created() {
+        // #ifdef H5
+        this.$store.commit('share/status', false);
+        // #endif
+    }
+}
+</script>
+
+<style scoped lang="scss">
+.app-view {
+    position: fixed;
+    height: 100vh;
+    width: 100vw;
+    background-color: rgba(0, 0, 0, 0.5);
+    z-index: 2002;
+}
+
+.point-box {
+    position: fixed;
+    z-index: 2003;
+    top: 16#{rpx};
+    right: 24#{rpx};
+    height: 72#{rpx};
+
+    .triangle {
+        width: 0;
+        height: 0;
+        border-right: 16#{rpx} solid transparent;
+        border-left: 16#{rpx} solid transparent;
+        position: absolute;
+        top: -15#{rpx};
+        right: 50#{rpx};
+    }
+
+    .icon-fork {
+        width: 72#{rpx};
+        height: 72#{rpx};
+    }
+
+    .point-text {
+        margin: 0 28#{rpx};
+        font-size: $uni-font-size-general-one;
+    }
+}
+</style>

+ 56 - 0
components/page-component/app-copyright/app-copyright.vue

xqd
@@ -0,0 +1,56 @@
+<template>
+    <view class="app-view">
+        <app-jump-button form :url="link.url" :open_type="link.openType" :params="link.params?link.params:[]">
+            <view class="app-view dir-top-nowrap cross-center" :style="{backgroundColor: backgroundColor}">
+                <icon class="app-icon" v-if="picUrl" :style="{backgroundImage: `url(${picUrl})`}" type></icon>
+                <text class="app-text" :style="{marginTop: picUrl ? '16rpx': '24rpx'}">{{text}}</text>
+            </view>
+        </app-jump-button>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: 'app-copyright',
+        props: {
+            backgroundColor: {
+                type: String,
+                default() {
+                    return '#ff4544';
+                }
+            },
+            link: {
+                type: Object,
+                default() {
+                    return null;
+                }
+            },
+            picUrl: String,
+            text: String,
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-view {
+        width: #{750rpx};
+        border: none;
+        border-radius: 0;padding-bottom: 24rpx;
+    }
+
+    .app-text {
+        font-size: #{24rpx};
+        text-align: center;
+        color: #999999;
+    }
+
+    .app-icon {
+        width: #{160rpx};
+        height: #{50rpx};
+        margin-top: #{36rpx};
+        background-size: 100% 100%;
+        background-repeat: no-repeat;
+    }
+
+</style>

+ 177 - 0
components/page-component/app-coupon-center/app-coupon-center.vue

xqd
@@ -0,0 +1,177 @@
+<template>
+	<view class="app-coupon-center">
+		<view class="app-coupon-item"
+		      v-for="(item, index) in list"
+		      :key="index"
+		      @click="jump(item)"
+		>
+			<view class="app-top dir-left-nowrap main-between"  :class="{'app-unexpired': item.is_receive === '0', 'app-received': item.is_receive > '0'}">
+				<view class="app-text dir-left-nowrap main-left">
+					<view class="app-price">
+						<text class="app-symbol">¥</text>
+						<text class="app-number">{{item.sub_price}}</text>
+					</view>
+					<view class="app-amount dir-top-nowrap main-left">
+						<text>优惠券</text>
+						<text>满{{item.min_price}}使用</text>
+					</view>
+				</view>
+				<view class="app-button">
+					<app-button type="important"
+					            height="56"
+					            background="#ffffff"
+					            round
+					            :color="`${item.is_receive === '0' ? '#ff4544' : item.is_receive > '0' ? '#999999' : ''}`"
+					            fontSize="28"
+					            @click="use(item.id)"
+					>{{item.is_receive === '0' ? '立即领取' : item.is_receive > '0' ? '已领取' : ''}}</app-button>
+				</view>
+			</view>
+			<view class="app-bottom dir-top-nowrap main-between">
+				<text class="app-time">
+					有效时间: {{item.begin_time}} - {{item.end_time}}
+				</text>
+				<text class="app-range">
+					适用范围: {{setRange(item.appoint_type)}}
+				</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import jump from '../../../core/jump.js';
+	
+    export default {
+        name: 'app-coupon-center',
+	    props: {
+            list: {
+                type: Array,
+	            default: function() {
+	                return [
+		                {
+                            appoint_type: "",
+                            begin_time: "",
+                            cat: [],
+                            couponGoods: [],
+                            created_at: "",
+                            deleted_at: "",
+                            desc: "",
+                            discount: "",
+                            end_time: "",
+                            expire_day: "",
+                            expire_type: "",
+                            goods: [],
+                            id: "",
+                            is_delete: "",
+                            is_member: "",
+                            is_receive: "",
+                            mall_id: "",
+                            min_price: "",
+                            name: "",
+                            pic_url: "",
+                            rule: "",
+                            sort: "",
+                            sub_price: "",
+                            total_count: "",
+                            type: "",
+                            updated_at: "",
+		                }
+	                ]
+	            }
+            }
+	    },
+	    methods: {
+            setRange: function(appoint_type) {
+                if (appoint_type === '1') {
+                    return '限品类';
+                } else if (appoint_type === '2') {
+                    return '限商品';
+                } else if (appoint_type === '3') {
+                    return '全场通用';
+                }
+            },
+            jump(item) {
+                jump({
+	                open_type: 'navigate',
+	                url: `/pages/coupon/details/details?id=${item.id}`
+                });
+            },
+            use() {
+            }
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-coupon-center {
+		width: #{750rpx};
+		overflow: hidden;
+		.app-coupon-item {
+			margin: #{20rpx} #{24rpx} #{0} #{24rpx};
+			width: #{702rpx};
+			height: #{160+112rpx};
+			.app-top {
+				width: #{702rpx};
+				height: #{160rpx};
+				background-size: 100% 100%;
+				background-repeat: no-repeat;
+				.app-button {
+					width: #{160+24rpx};
+					padding-right: #{24rpx};
+					padding-top: #{48rpx};
+					/deep/ button {
+						padding: 0 #{10rpx} !important;
+					}
+				}
+				.app-text {
+					width: #{702-160-24rpx};
+					height: #{160rpx};
+					margin-left: #{24rpx};
+					.app-price {
+						padding-top: #{52rpx};
+						color: #ffffff;
+						.app-symbol {
+							font-size: #{36rpx};
+						}
+						.app-number {
+							font-size: #{72rpx};
+						}
+					}
+					.app-amount {
+						padding-top: #{50rpx};
+						color: #ffffff;
+						font-size: #{28rpx};
+						margin-left: #{20rpx};
+					}
+				}
+			}
+			.app-unexpired {
+				background-image: url("../../../static/image/icon/unexpired-coupon.png");
+			}
+			.app-received {
+				background-image: url("../../../static/image/icon/received-coupon.png");
+			}
+			.app-bottom {
+				width: #{702rpx};
+				height: #{112rpx};
+				border-left: #{1rpx} solid #cfcfcf;
+				border-bottom: #{1rpx} solid #cfcfcf;
+				border-right: #{1rpx} solid #cfcfcf;
+				border-bottom-left-radius: #{16rpx};
+				border-bottom-right-radius: #{16rpx};
+				text {
+					font-size: #{24rpx};
+					color: #666666;
+					margin-left: #{24rpx};
+				}
+				.app-time {
+					margin-top: #{24rpx};
+				}
+				.app-range {
+					margin-bottom: #{32rpx};
+				}
+			}
+		}
+	}
+</style>

+ 122 - 0
components/page-component/app-diy-form/app-diy-form-checkbox-group.vue

xqd
@@ -0,0 +1,122 @@
+<template>
+    <view class="app-diy-form-checkbox-group">
+        <view class="list dir-left-wrap">
+            <view v-for="(item, index) in model"
+                  :key="index"
+                  @click="handleClick(index)" class="out-of-item box-grow-0">
+                <view class="item text-ellipsis"
+                      :style="{color: color ? color: '#FF4544',
+                              borderColor: color ? color: '#FF4544',
+                              backgroundColor: item.value ? color: '#FFFFFF',
+                              }"
+                      :class="[
+                        `white-background`,
+                        item.value?`background`:``,
+                        ]">{{item.label}}
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: 'app-diy-form-checkbox-group',
+        props: {
+            sign: {
+                default: null,
+            },
+            value: {
+                type: Array,
+                default: [],
+            },
+            list: {
+                type: Array,
+                default: [],
+            },
+            color: {
+                default: '#ff4544',
+            }
+        },
+        data() {
+            const model = this.list;
+            for (let i in model) {
+                let inArray = false;
+                for (let j in this.value) {
+                    if (model[i].label === this.value[j]) {
+                        inArray = true;
+                        break;
+                    }
+                }
+                if (inArray) {
+                    model[i].value = true;
+                }
+            }
+            return {
+                model: model,
+            };
+        },
+        methods: {
+            handleClick(index) {
+                this.model[index].value = !this.model[index].value;
+                this.outputData();
+            },
+            outputData() {
+                const value = [];
+                for (let i in this.model) {
+                    if (this.model[i].value === true) {
+                        value.push(this.model[i].label);
+                    }
+                }
+                this.$emit('change', value, this.sign);
+                this.$emit('input', value, this.sign);
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-diy-form-checkbox-group {
+        width: 100%;
+        overflow-y: hidden;
+        //overflow-x: hidden;
+    }
+
+    .list {
+        //margin-bottom: #{-24rpx};
+        //margin: 0 -#{6rpx};
+    }
+
+    .out-of-item {
+        margin-bottom: #{12rpx};
+        margin-top: #{12rpx};
+        margin-right: #{10rpx};
+        display: inline-block;
+    }
+
+    .item {
+        //display: inline-block;
+        height: #{56rpx};
+        line-height: #{54rpx};
+        border: #{2rpx} solid;
+        padding: 0 #{24rpx};
+        border-radius: #{1000rpx};
+        font-size: $uni-font-size-general-one;
+    }
+    .background {
+        color: #ffffff !important;
+    }
+    .border {
+        border-color: #ff4544;
+    }
+    .color {
+        color: #ff4544;
+    }
+    .text-ellipsis{
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        max-width: #{420rpx};
+    }
+</style>

+ 861 - 0
components/page-component/app-diy-form/app-diy-form.vue

xqd
@@ -0,0 +1,861 @@
+<template>
+    <view class="app-diy-form" :style="{
+        paddingTop: `${marginTop}rpx`,
+        paddingBottom: `${marginBottom}rpx`,
+        backgroundColor: `${marginColor}`
+    }">
+        <view class="title" v-if="title !== '' && title !== null">{{title}}</view>
+        <!--<view style="border: 1rpx solid #ff3455">
+            <view v-for="(item, index) in myList" :key="index">{{item.name}}:{{item.value}}</view>
+        </view>-->
+        <view :style="{
+            backgroundColor: backgroundColor,
+            backgroundImage: backgroundImage ? `url(${backgroundImage})` : `none`,
+            backgroundPosition: backgroundPosition,
+            backgroundSize: `${backgroundWidth}% ${backgroundHeight}%`,
+            backgroundRepeat: backgroundRepeat,
+            paddingTop: `${paddingTop}rpx`,
+            paddingBottom: `${paddingBottom}rpx`,
+        }">
+            <view class="list"
+                  :class="[showItemBorder?'item-border':'', showAllItems?'show-all':'show-first',labelFs28?'label-fs-28':'']">
+                <template v-for="(item, index) in myList">
+
+                    <view :key="index" v-if="item.key=='text'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view v-if="labelPosition !== 'inset'" class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1">
+                            <app-input v-model="item.value"
+                                       :default-value="item.value"
+                                       :background-color="inputBackground"
+                                       @input="textInput"
+                                       :color="inputTextColor"
+                                       :placeholder="labelPosition === 'inset' ? item.name:item.hint"
+                                       :height="`${itemHeight}`"
+                                       :border="showInputBorder"
+                                       :radius="inputRadius"
+                                       :placeholder-style="`color:${inputPlaceholderColor}`"
+                                       :padding-left="getInputPaddingLeft"
+                                       :border-color="inputBorderColor"></app-input>
+                        </view>
+                    </view>
+
+                    <view :key="index" v-if="item.key=='textarea'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view v-if="labelPosition !== 'inset'" class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1">
+                            <app-textarea v-model="item.value"
+                                          :default-value="item.value"
+                                          :background="inputBackground"
+                                          @input="textInput"
+                                          :color="inputTextColor"
+                                          :placeholder="labelPosition === 'inset' ? item.name:item.hint"
+                                          :show-border="showInputBorder"
+                                          :border-radius="inputRadius"
+                                          :padding-x="getInputPaddingLeft"
+                                          :placeholder-style="[`color:${inputPlaceholderColor}`]"
+                                          :border-color="inputBorderColor"></app-textarea>
+                        </view>
+                    </view>
+
+                    <view :key="index" v-if="item.key=='date'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1">
+                            <app-datetime-picker v-model="item.value"
+                                                 @change="datetimeChange"
+                                                 :text="item.value||''"
+                                                 :sign="index"
+                                                 :show-border="showInputBorder"
+                                                 :background="inputBackground"
+                                                 :height="itemHeight"
+                                                 :radius="inputRadius"
+                                                 :default-value="item.default"
+                                                 :text-color="inputTextColor"
+                                                 :text-position="getDateTimeTextPosition"
+                                                 :border-color="inputBorderColor"
+                                                 :start="item.min ? item.min : ''"
+                                                 :padding="datePadding"
+                                                 :end="item.max ? item.max : ''">
+                                <!-- #ifdef MP-TOUTIAO -->
+                                {{item.value||''}}
+                                <!-- #endif -->
+                            </app-datetime-picker>
+                        </view>
+                    </view>
+
+                    <view :key="index" v-if="item.key=='time'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1">
+                            <app-datetime-picker v-model="item.value"
+                                                 @change="datetimeChange"
+                                                 mode="time"
+                                                 :text="item.value||''"
+                                                 :sign="index"
+                                                 :default-value="item.default"
+                                                 :show-border="showInputBorder"
+                                                 :background="inputBackground"
+                                                 :height="itemHeight"
+                                                 :radius="inputRadius"
+                                                 :text-color="inputTextColor"
+                                                 :text-position="getDateTimeTextPosition"
+                                                 :border-color="inputBorderColor"
+                                                 :start="item.min ? item.min : ''"
+                                                 :end="item.max ? item.max : ''">
+                                <!-- #ifdef MP-TOUTIAO -->
+                                {{item.value||''}}
+                                <!-- #endif -->
+                            </app-datetime-picker>
+                        </view>
+                    </view>
+
+                    <view :key="index" v-if="item.key=='radio'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1" :style="{
+                        padding: labelPosition === 'top' ? `0 0` : `0 12rpx`,
+                        }">
+                            <app-radio-group :list="item.list" v-model="item.value"
+                                             :color="selectBoxColor"
+                                             type="round"
+                                             :height="74"
+                                             @change="checkChange"></app-radio-group>
+                        </view>
+                    </view>
+
+                    <view :key="index" v-if="item.key=='checkbox'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1 dir-left-wrap" :style="{
+                        padding: labelPosition === 'top' ? `0 0` : `0 12rpx`,
+                        }">
+                            <app-diy-form-checkbox-group :list="item.list"
+                                                         v-model="item.value"
+                                                         :color="selectBoxColor"
+                                                         @change="checkChange"></app-diy-form-checkbox-group>
+                        </view>
+                    </view>
+
+                    <view :key="index" v-if="item.key=='img_upload'"
+                          class="item"
+                          :class="[itemClass, index===0 ? `is-first-item` : ``,]"
+                          :style="{
+                      'padding': `0 ${itemPaddingX}rpx`,
+                      'margin-bottom': `${itemMarginY}rpx`,
+                      }">
+                        <view class="box-grow-0 cross-top label">
+                            <image v-if="showRequiredIcon && (item.is_required == 1 || item.is_required == '1')"
+                                   class="required-icon"
+                                   src="/static/image/icon/required.png"></image>
+                            <view class="name-key"
+                                  :class="[`text-align-${labelTextAlign}`]"
+                                  :style="{
+                                  'color': labelColor
+                                  }">{{item.name}}
+                            </view>
+                        </view>
+                        <view class="box-grow-1 dir-left-wrap" :style="{
+                        padding: labelPosition === 'top' ? `12rpx 0` : `12rpx 12rpx`,
+                        }">
+                            <!-- 普通图片 -->
+                            <template v-if="item.img_type == 1">
+                                <app-upload-image
+                                        :value="uploadShowImage(item)"
+                                        :max-num="item.num ? item.num:1"
+                                        @imageEvent="handleImageUpload"
+                                        :sign="`${index}`"
+                                        :show-number="false"
+                                ></app-upload-image>
+                            </template>
+
+                            <!-- 身份证,正反面 -->
+                            <template v-if="item.img_type == 2">
+                                <app-upload-image
+                                        :value="(item.value && item.value[0])?[item.value[0]]:null"
+                                        style="margin-right: 12rpx"
+                                        :max-num="1"
+                                        @imageEvent="handleUserIdFrontUpload"
+                                        :sign="`${index}`"
+                                        text="身份证正面"
+                                        :show-number="false"
+                                        default-img="/static/image/user-id-card-front.png"></app-upload-image>
+                                <app-upload-image
+                                        :value="(item.value && item.value[1])?[item.value[1]]:null"
+                                        :max-num="1"
+                                        @imageEvent="handleUserIdBackUpload"
+                                        :sign="`${index}`"
+                                        text="身份证反面"
+                                        :show-number="false"
+                                        default-img="/static/image/user-id-card-back.png"></app-upload-image>
+                            </template>
+
+                            <!-- 营业执照 -->
+                            <template v-if="item.img_type == 3">
+                                <app-upload-image
+                                        :value="item.value?[item.value]:null"
+                                        :max-num="1"
+                                        @imageEvent="handleImageUpload"
+                                        :sign="`${index}`"
+                                        text="营业执照"
+                                        :show-number="false"
+                                        default-img="/static/image/company-license.png"></app-upload-image>
+                            </template>
+                        </view>
+                    </view>
+
+                </template>
+            </view>
+
+            <view class="main-center cross-center scroll-bar" v-if="showScrollBtn"
+                  @click="showAllItems = !showAllItems">
+                <view class="cross-center">点击{{showAllItems?'收起':'展开'}}</view>
+                <view class="cross-center">
+                    <image v-if="showAllItems" src="/static/image/icon/icon-up.png"
+                           style="width: 18rpx;height: 10rpx;"></image>
+                    <image v-else src="/static/image/icon/icon-down.png"
+                           style="width: 18rpx;height: 10rpx;"></image>
+                </view>
+            </view>
+
+            <view v-if="showSubmit" class="submit" :style="{
+                'padding': `${submitBtnMargin}rpx ${submitBtnPadding}rpx 24rpx`,
+                }">
+                <app-form-id @click="submit">
+                    <view class="submit-button" :style="{
+                'background-color': submitBtnBackground,
+                'border-color': submitBtnBorderColor,
+                'color': submitBtnTextColor,
+                'border-radius': `${submitBtnRadius}rpx`,
+                'height': `${submitBtnHeight}rpx`,
+                'line-height': `${submitBtnHeight-2}rpx`,
+                }">{{submitBtnText}}
+                    </view>
+                </app-form-id>
+            </view>
+
+        </view>
+
+    </view>
+</template>
+
+<script>
+    import AppDatetimePicker from '../../basic-component/app-datetime-picker/app-datetime-picker.vue';
+    import AppRadio from "../../basic-component/app-radio/app-radio";
+    import AppRadioGroup from "../../basic-component/app-radio/app-radio-group";
+    import AppDiyFormCheckboxGroup from "./app-diy-form-checkbox-group";
+    import AppUploadImage from "../../basic-component/app-upload-image/app-upload-image";
+    import AppTextarea from "../../basic-component/app-textarea/app-textarea";
+
+    export default {
+        name: 'app-diy-form',
+        components: {AppTextarea, AppUploadImage, AppDiyFormCheckboxGroup, AppRadioGroup, AppRadio, AppDatetimePicker},
+        props: {
+            sign: {
+                default: null,
+            },
+            datePadding: {
+                type: String,
+                default: '0 24rpx',
+            },
+            title: {
+                type: String,
+                default: null,
+            },
+            backgroundColor: {
+                default: '#ffffff',
+            },
+            backgroundImage: {
+                default: null,
+            },
+            backgroundPosition: {
+                default: 'center',
+            },
+            backgroundWidth: {
+                default: 100,
+            },
+            backgroundHeight: {
+                default: 100,
+            },
+            backgroundRepeat: {
+                default: 'no-repeat',
+            },
+            marginTop: {
+                default: 0,
+            },
+            marginBottom: {
+                default: 0,
+            },
+            paddingTop: {
+                default: 0,
+            },
+            paddingBottom: {
+                default: 0,
+            },
+            list: {
+                type: Array,
+                default: [],
+            },
+            showRequiredIcon: {
+                type: Boolean,
+                default: false,
+            },
+            itemPaddingX: {
+                default: 24,
+            },
+            itemMarginY: {
+                default: 0,
+            },
+            itemHeight: {
+                type: Number,
+                default: 88,
+            },
+            showItemBorder: {
+                default: true,
+            },
+            labelPosition: {
+                default: 'left',
+            },
+            labelColor: {
+                default: '#666666',
+            },
+            labelTextAlign: {
+                default: 'right',
+            },
+            showInputBorder: {
+                type: Boolean,
+                default: false,
+            },
+            inputBackground: {
+                default: '#ffffff',
+            },
+            inputBorderColor: {
+                default: '#c0c4cc',
+            },
+            inputTextColor: {
+                default: '#666666',
+            },
+            inputPlaceholderColor: {
+                default: '#bbbbbb',
+            },
+            inputRadius: {
+                default: 0,
+            },
+
+            showSubmit: {
+                type: Boolean,
+                default: false,
+            },
+            submitUrl: {
+                type: String,
+                default: null,
+            },
+            submitBtnText: {
+                default: '提交',
+            },
+            submitBtnHeight: {
+                default: 80,
+            },
+            submitBtnPadding: {
+                default: 24,
+            },
+            submitBtnMargin: {
+                default: 40,
+            },
+            submitBtnRadius: {
+                default: 40,
+            },
+            submitBtnBackground: {
+                default: '#ff4544',
+            },
+            submitBtnBorderColor: {
+                default: '#ff4544',
+            },
+            submitBtnTextColor: {
+                default: '#ffffff',
+            },
+            showScrollBtn: {
+                default: false,
+            },
+            labelFs28: {
+                default: false,
+            },
+            marginColor: {
+                default: '#ffffff',
+            },
+            selectBoxColor: {
+                default: '#ff4544',
+            },
+        },
+        data() {
+            const newList = [];
+            for (let i in this.list) {
+                const item = this.list[i];
+                // if (typeof item.default === 'undefined') {
+                //     item.default = null;
+                // }
+                if (typeof item.default === 'undefined' || item.default === null || item.default === '') {
+                    if (item.key === 'text' || item.key === 'textarea' || item.key === 'time' || item.key === 'date') {
+                        item.default = '';
+                    }
+                    if (item.key === 'radio' || item.key === 'checkbox') {
+                        item.default = [];
+                    }
+                }
+                if (typeof item.value === 'undefined' || item.value === null || item.value === '') {
+                    item.value = item.default;
+                }
+                item.hint = item.hint || '';
+                if (item.key === 'radio' || item.key === 'checkbox') {
+                    if (!item.list) item.list = [];
+                    for (let j in item.list) {
+                        if (!item.list[j].value || item.list[j].value === false || item.list[j].value === 'false') {
+                            item.list[j].value = false;
+                        } else {
+                            item.list[j].value = true;
+                        }
+                    }
+                }
+                if (item.key === 'img_upload' && (item.img_type === 2 || item.img_type === '2') && !item.value) {
+                    item.value = ['', ''];
+                }
+                if(item.key == 'text') {
+                }
+                newList[i] = item;
+            }
+            return {
+                myList: newList,
+                randomString: '',
+                validateResult: {
+                    hasError: false,
+                    errors: [],
+                },
+                showAllItems: true, //默认展开全部
+            };
+        },
+        computed: {
+            uploadShowImage() {
+                return function (item) {
+                    if (Array.isArray(item.value)) {
+                        return item.value;
+                    }
+                    if (item.value) {
+                        return [item.value];
+                    }
+                    return null;
+                }
+            },
+            itemClass() {
+                if (this.labelPosition === 'left') {
+                    return 'label-left dir-left-nowrap cross-top';
+                }
+                if (this.labelPosition === 'inset') {
+                    return 'label-inset dir-left-nowrap cross-top';
+                }
+                if (this.labelPosition === 'top') {
+                    return 'label-top';
+                }
+            },
+            getDateTimeTextPosition() {
+                if (this.labelPosition === 'top') {
+                    return 'left';
+                }
+                return 'right';
+            },
+            getInputPaddingLeft() {
+                if (this.labelPosition === 'top') {
+                    if (this.showInputBorder) {
+                        return 24;
+                    } else {
+                        return 0;
+                    }
+                }
+                return 24;
+            },
+        },
+        created() {
+            this.validate();
+            this.outputData();
+        },
+        methods: {
+            textInput() {
+                this.outputData();
+                // #ifdef H5
+                this.$forceUpdate()
+                // #endif
+            },
+            datetimeChange() {
+                this.outputData();
+                // #ifdef H5
+                this.$forceUpdate()
+                // #endif
+            },
+            checkChange() {
+                setTimeout(() => {
+                    this.outputData();
+                }, 10);
+            },
+            handleImageUpload({sign, imageList}) {
+                const index = parseInt(sign);
+                if (imageList.length === 1) {
+                    this.myList[index].value = imageList[0];
+                } else if (imageList.length > 0) {
+                    this.myList[index].value = imageList;
+                } else {
+                    this.myList[index].value = '';
+                }
+                this.outputData();
+            },
+            handleUserIdFrontUpload({sign, imageList}) {
+                const index = parseInt(sign);
+                if (imageList.length > 0) {
+                    this.myList[index].value[0] = imageList[0];
+                } else {
+                    this.myList[index].value[0] = '';
+                }
+                this.outputData();
+            },
+            handleUserIdBackUpload({sign, imageList}) {
+                const index = parseInt(sign);
+                if (imageList.length > 0) {
+                    this.myList[index].value[1] = imageList[0];
+                } else {
+                    this.myList[index].value[1] = '';
+                }
+                this.outputData();
+            },
+            validate() {
+                this.validateResult = {
+                    hasError: false,
+                    errors: [],
+                };
+                for (let i in this.myList) {
+                    const item = this.myList[i];
+                    if (item.is_required === 1 || item.is_required === '1') {
+                        if ((typeof item.value === 'undefined'
+                            || item.value === null
+                            || item.value === ''
+                            || ['radio', 'checkbox'].indexOf(item.key) !== -1 && !item.value.length)
+                        ) {
+                            this.validateResult.hasError = true;
+                            this.validateResult.errors.push({
+                                index: i,
+                                msg: `"${item.name}"不能为空。`,
+                            });
+                            continue;
+                        }
+                        if (item.img_type && parseInt(item.img_type) === 2) {
+                            if (!item.value || !item.value.length) {
+                                this.validateResult.hasError = true;
+                                this.validateResult.errors.push({
+                                    index: i,
+                                    msg: `"${item.name}"不能为空。`,
+                                });
+                                continue;
+                            }
+                            let imgErr = false;
+                            for (let j in item.value) {
+                                if (item.value[j] === null || item.value[j] === '') {
+                                    imgErr = true;
+                                    break;
+                                }
+                            }
+                            if (imgErr) {
+                                this.validateResult.hasError = true;
+                                this.validateResult.errors.push({
+                                    index: i,
+                                    msg: `"${item.name}"不能为空。`,
+                                });
+                                continue;
+                            }
+                        }
+                    }
+                }
+                this.$emit('validate',{
+                    result: this.validateResult,
+                    sign: this.sign
+                });
+            },
+            outputData() {
+                this.validate();
+                for (let i in this.myList) {
+                    this.myList[i].label = this.myList[i].name;
+                    this.myList[i].required = this.myList[i].is_required;
+                }
+                this.$emit('input',{
+                    data: this.myList,
+                    sign: this.sign
+                });
+                // #ifdef MP-TOUTIAO
+                setTimeout(() => {
+                    this.randomString = this.$utils.randomString(32);
+                }, 1);
+                // #endif
+            },
+            submit() {
+                this.outputData();
+                if (this.validateResult.hasError && this.validateResult.errors) {
+                    uni.showModal({
+                        title: '提示',
+                        content: this.validateResult.errors[0].msg,
+                        showCancel: false,
+                    });
+                    return;
+                }
+                uni.showLoading({
+                    mask: true,
+                    title: '正在提交...',
+                });
+                this.$request({
+                    url: this.submitUrl ? this.submitUrl : this.$api.diy.page_store,
+                    method: 'post',
+                    data: {
+                        form_data: JSON.stringify(this.myList),
+                    },
+                }).then(response => {
+                    uni.hideLoading();
+                    if (response.code === 0) {
+                        setTimeout(() => {
+                            let copyList = this.myList;
+                            for (let i in copyList) {
+                                copyList[i].value = copyList[i].default;
+                            }
+                            this.myList = [];
+                            setTimeout(() => {
+                                this.myList = copyList;
+                            }, 0);
+                        }, 300);
+                        uni.showModal({
+                            title: '提示',
+                            content: response.msg || '提交成功',
+                            showCancel: false,
+                        });
+                    } else {
+                        uni.showModal({
+                            title: '提示',
+                            content: response.msg || '提交失败',
+                            showCancel: false,
+                        });
+                    }
+                }).catch(() => {
+                    uni.hideLoading();
+                });
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .name-key {
+        width: 100%;
+        text-align: right;
+        font-size: #{32rpx};
+        color: #353535
+    }
+
+    .name-key.text-align-left {
+        text-align: left;
+    }
+
+    .name-key.text-align-right {
+        text-align: right;
+    }
+
+    .title {
+        padding: #{24rpx};
+        color: #999;
+        font-size: #{26rpx};
+    }
+
+    .arrow {
+        width: #{12rpx};
+        height: #{22rpx};
+    }
+
+    .scroll-bar {
+        border-top: #{1rpx} solid $uni-weak-color-one;
+        height: #{72rpx};
+        color: $uni-general-color-two;
+
+        image {
+            margin-left: #{16rpx};
+        }
+    }
+
+    .list {
+        .item {
+            height: 0 !important;
+            overflow: hidden;
+
+            .label {
+                min-width: #{125rpx};
+                padding: #{24rpx} #{12rpx} #{12rpx} 0;
+                height: 100%;
+                position: relative;
+            }
+
+            .required-icon {
+                width: #{15rpx};
+                height: #{15rpx};
+                display: inline-block;
+                position: absolute;
+                left: #{-14rpx};
+                top: #{23rpx};
+            }
+        }
+
+        .item.is-first-item {
+            height: auto !important;
+            overflow: auto;
+        }
+
+        .item.label-left {
+            .name-key {
+                max-width: #{200rpx};
+            }
+        }
+
+        .item.label-top {
+            .name-key {
+                text-align: left;
+            }
+
+            .label {
+                padding: #{20rpx} #{12rpx} #{8rpx} 0;
+            }
+        }
+
+        .item:last-child {
+            margin-bottom: 0 !important;
+        }
+    }
+
+    .show-all {
+        .item {
+            height: auto !important;
+            overflow: auto;
+        }
+    }
+
+    .show-first {
+        .item {
+            margin-bottom: 0 !important;
+            border: none !important;
+        }
+    }
+
+    .item-border {
+        .item {
+            border-bottom: #{2rpx} solid $uni-weak-color-one;
+        }
+
+        .item:last-child {
+            border-bottom: none;
+        }
+    }
+
+    .submit-button {
+        text-align: center;
+        border: #{1rpx} solid;
+    }
+
+    .submit-button:active {
+        box-shadow: inset 0 0 #{1000rpx} rgba(0, 0, 0, .15);
+    }
+
+    .label-fs-28 {
+        .name-key {
+            font-size: #{28rpx};
+        }
+    }
+</style>

+ 209 - 0
components/page-component/app-diy-goods-list/app-diy-composition-image.vue

xqd
@@ -0,0 +1,209 @@
+<template>
+    <view class="app-diy-composition-image">
+        <view class="goods-one dir-left-wrap" :class="imageClass">
+            <block v-for="(item,key) in imageList" :key="key">
+                <image :mode="mode" :src="item"></image>
+            </block>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-diy-composition-image",
+        props: {
+            imageList: Array,
+            mode: String,
+        },
+        computed: {
+            imageClass() {
+                let count = this.imageList.length;
+                switch (count) {
+                    case 1:
+                        return 'goods-one';
+                    case 2:
+                        return 'goods-two';
+                    case 3:
+                        return 'goods-three';
+                    case 4:
+                        return 'goods-four';
+                    case 5:
+                        return 'goods-five';
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+
+    .app-diy-composition-image {
+        height: 100%;
+        width: 100%;
+        position: relative;
+        overflow: hidden;
+
+        .plugin-text-class {
+            position: absolute;
+            z-index: 11;
+        }
+
+        .plugin-image-class {
+            position: absolute;
+            z-index: 10;
+
+            image {
+                height: 100%;
+                width: 100%;
+            }
+        }
+
+        image {
+            display: block;
+        }
+
+        .goods-one {
+            height: 100%;
+            width: 100%;
+
+            image {
+                height: 100%;
+                width: 100%;
+
+            }
+        }
+
+        .goods-two {
+            height: 100%;
+            width: 100%;
+
+            image:nth-child(1) {
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 100%;
+                width: 48.8%;
+            }
+
+            image:nth-child(2) {
+                position: absolute;
+                top: 0;
+                right: 0;
+                height: 100%;
+                width: 48.8%;
+            }
+        }
+
+        .goods-three {
+            height: 100%;
+            width: 100%;
+
+            image:nth-child(1) {
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 100%;
+                width: 48.8%;
+            }
+
+            image:nth-child(2) {
+                position: absolute;
+                top: 0;
+                right: 0;
+                height: 48.8%;
+                width: 48.8%;
+            }
+
+            image:nth-child(3) {
+                position: absolute;
+                bottom: 0;
+                right: 0;
+                height: 48.8%;
+                width: 48.8%;
+            }
+        }
+
+        .goods-four {
+            height: 100%;
+            width: 100%;
+
+            image:nth-child(1) {
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 100%;
+                width: 48.8%;
+            }
+
+            image:nth-child(2) {
+                position: absolute;
+                top: 0;
+                right: 0;
+                height: 48.8%;
+                width: 48.8%;
+            }
+
+            image:nth-child(3) {
+                position: absolute;
+                bottom: 0;
+                left: calc(50% + 4rpx);
+                height: 48.8%;
+                width: calc(24.4% - 4rpx);
+            }
+
+            image:nth-child(4) {
+                position: absolute;
+                bottom: 0;
+                right: 0;
+                height: 48.8%;
+                width: calc(24.4% - 4rpx);
+            }
+        }
+
+        .goods-five {
+            height: 100%;
+            width: 100%;
+            position: relative;
+
+            image:nth-child(1) {
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 100%;
+                width: 48.8%;
+            }
+
+            image:nth-child(2) {
+                position: absolute;
+                top: 0;
+                left: calc(50% + 4rpx);
+                height: 48.8%;
+                width: calc(24.4% - 4rpx);
+            }
+
+            image:nth-child(3) {
+                position: absolute;
+                top: 0;
+                right: 0;
+                height: 48.8%;
+                width: calc(24.4% - 4rpx);
+            }
+
+            image:nth-child(4) {
+                position: absolute;
+                bottom: 0;
+                left: calc(50% + 4rpx);
+                height: 48.8%;
+                width: calc(24.4% - 4rpx);
+            }
+
+            image:nth-child(5) {
+                position: absolute;
+                bottom: 0;
+                right: 0;
+                height: 48.8%;
+                width: calc(24.4% - 4rpx);
+            }
+        }
+    }
+</style>

+ 1055 - 0
components/page-component/app-diy-goods-list/app-diy-goods-list.vue

xqd
@@ -0,0 +1,1055 @@
+<template>
+	<view class="app-goods-list">
+        <view class="goods-list" :class="listClass" v-if="newData.length > 0">
+            <!-- 列表模式 -->
+            <template v-if="listStyle === -1">
+                <block v-for="(goods, index) in newData" :key="index">
+                    <view :style="[{'background-color':`${goodsStyle != 3 ? '#fff' : ''}`, overflow: `${sign === 'composition' ? 'hidden': 'visible'}`}]" class="goods-item app-list-mode dir-left-nowrap" @click="jump(goods)"
+                          :class="goodsStyle === 3 ? 'no-border' : goodsStyle === 2 ? 'border' : ''">
+
+                        <!-- 售罄 -->
+                        <view :style="[{'border-radius': `${lisRadius}`}]" class="out-dialog" v-if="(goods.goods_stock == 0 || (goods.goods && goods.goods.goods_stock == 0)) && appSetting.is_show_stock == '1'">
+                            <image :style="[{'border-radius': `${lisRadius}`}]" :src="appSetting.is_use_stock == '1' ? appImg.plugins_out : appSetting.sell_out_pic"></image>
+                        </view>
+
+                        <view class="box-grow-0">
+                            <app-image
+                                    v-if="sign === 'advance'"
+                                    :borderRadius="lisRadius"
+                                    :img-src="goods.goods.goodsWarehouse.cover_pic"
+                                    width="220rpx"
+                                    height="220rpx"
+                                    :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                            ></app-image>
+                            <div v-else-if="sign === 'composition'" style="width: 328rpx;height: 100%">
+                                <app-diy-composition-image
+                                        :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                                        :image-list="goods.cover_pic_list"></app-diy-composition-image>
+                            </div>
+                            <app-image
+                                    v-else
+                                    :borderRadius="lisRadius"
+                                    :img-src="goods.cover_pic"
+                                    width="220rpx"
+                                    height="220rpx"
+                                    :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                            ></app-image>
+                        </view>
+
+                        <!-- 角标 -->
+                        <view class="goods-tag" v-if="showGoodsTag">
+                            <app-image :img-src="goodsTagPicUrl" width="64rpx" height="64rpx"></app-image>
+                        </view>
+                        <view class="right box-grow-1 dir-top-nowrap main-between"
+                              :style="{
+                                padding: sign === 'advance' || sign=== 'composition' || (sign === 'flash-sale' && ((goods.vip_card_appoint && goods.vip_card_appoint.discount) && (goods.level_show == 1 && goods.is_negotiable != 1))) ? `10rpx 24rpx 6rpx 20rpx` : '28rpx 24rpx 24rpx 20rpx',
+                                height: sign === 'composition' ? '165rpx' : '220rpx',
+                          }">
+                            <view class="box-grow-0 name "
+                                  :class="(sign === 'advance' || sign === 'miaosha' || sign === 'flash-sale') || (goods.vip_card_appoint && goods.vip_card_appoint.discount) || (goods.level_show == 1 && goods.is_negotiable != 1) ? 't-omit name-one' : 't-omit-two name-two'">
+                                <text class="dir-tag-def"
+                                      v-if="tag && sign === 'advance' || sign === 'pick'|| sign === 'miaosha'"
+                                      :style="{'background-color': theme.background_o,'color': theme.color}"
+                                >
+                                    {{goods.people_num ? goods.people_num : ''}}{{tag}}
+                                </text>
+                                <text class="dir-tag-def"
+                                      v-if="tag && sign !== 'advance' && sign !== 'pick' && sign !== 'miaosha'  && ((sign == 'pintuan' && goods.people_num) || sign != 'pintuan')"
+                                      :style="{'background-color': theme.background_o,'color': theme.color}"
+                                >
+                                    {{goods.people_num ? goods.people_num : ''}}{{tag}}
+                                </text>
+
+                                <!-- 商品名字 -->
+                                <text v-if="sign === 'advance'">
+                                    {{showGoodsName ? goods.goods.goodsWarehouse.name : ''}}
+                                </text>
+                                <text v-else>{{showGoodsName ? goods.name : ''}}</text>
+                            </view>
+
+                            <view class=" dir-top-nowrap main-right box-grow-1">
+                                <template v-if="sign === 'flash-sale'">
+                                    <view class="dir-left-nowrap main-between" style="margin-bottom: 10rpx">
+                                        <view
+                                                class="flash-sale"
+                                                :style="{'background-color': theme.background}"
+                                                v-if="goods.discount_type == 1"
+                                        >{{goods.min_discount}}折</view>
+                                        <view
+                                                class="flash-sale"
+                                                :style="{'background-color': theme.background}"
+                                                v-if="goods.discount_type == 2"
+                                        >减{{goods.min_discount}}元</view>
+                                        <view v-if="showProgressBar" style="font-size: 20rpx;color: #999999;">{{goods.sales}}</view>
+                                    </view>
+                                    <view class="app-percentage" v-if="showProgressBar" :style="{'width': '445rpx','margin-bottom': '10rpx','background-color': theme.background_l}">
+                                        <view :style="{width: `${goods.percentage}%`,'background-color': theme.background}"></view>
+                                    </view>
+                                </template>
+                                <view class="dir-left-nowrap cross-bottom">
+                                    <view class="box-grow-1" :style="{'color': theme.color}">
+                                        <!-- 时间 -->
+                                        <template v-if="showTimer && sign !== 'flash-sale'">
+                                            <app-goods-timer
+                                                    v-if="sign === 'advance'"
+                                                    :list-style="listStyle"
+                                                    :theme="theme"
+                                                    :start-date-time="goods.start_prepayment_at"
+                                                    :end-date-time="goods.end_prepayment_at"
+                                                    :sign="sign"
+                                                    :page-hide="pageHide"
+                                            ></app-goods-timer>
+                                            <app-goods-timer
+                                                    v-else
+                                                    :list-style="listStyle"
+                                                    :start-date-time="goods.start_time"
+                                                    :end-date-time="goods.end_time"
+                                                    :sign="sign"
+                                                    :page-hide="pageHide"
+                                                    :theme="theme"
+                                            ></app-goods-timer>
+                                        </template>
+                                        <!-- #ifndef MP-ALIPAY -->
+                                        <text v-if="sign === 'advance'" class="des-price"
+                                              :style="{'color': theme.color,'border-color': theme.color,'margin-top': '5rpx'}">
+                                            定金¥{{goods.deposit}}抵¥{{goods.swell_deposit}}
+                                        </text>
+                                        <!-- #endif -->
+                                        <!-- #ifdef MP-ALIPAY -->
+                                        <text v-if="sign === 'advance'" class="des-price" 
+                                              :style="{'color': theme.color,'border-color': theme.color,'margin-top': '5rpx'}">
+                                            定金¥{{goods.deposit}}抵¥{{goods.swell_deposit}}
+                                        </text>
+                                        <!-- #endif -->
+                                        <text class="tag" v-else-if="sign === 'pick'">
+                                            {{goods.rule_price}}元选{{goods.rule_num}}{{goods.unit}}
+                                        </text>
+
+                                        <template v-if="goods.is_level == 1 && goods.is_negotiable != 1">
+                                            <app-member-price :price="goods.level_price"></app-member-price>
+                                        </template>
+                                        <!-- #ifndef MP-ALIPAY -->
+                                        <app-sup-vip
+                                                :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                                v-if="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                                :discount="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                        ></app-sup-vip>
+                                        <!-- #endif -->
+                                        <!-- #ifdef MP-ALIPAY -->
+                                        <app-sup-vip
+                                                margin="4rpx 0"
+                                                :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                                v-if="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                                :discount="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                        ></app-sup-vip>
+                                        <!-- #endif -->
+                                        <template v-if="sign === 'advance'">
+                                            <view class="dir-left-nowrap main-left cross-center">
+                                                <view class="goods-price ">¥{{showGoodsPrice ? goods.goods.price :''}}
+                                                </view>
+                                                <view class="original-price" style="margin-left: 10upx;"
+                                                      v-if="isUnderLinePrice && goods.goods.goodsWarehouse.original_price">
+                                                    ¥{{goods.goods.goodsWarehouse.original_price}}
+                                                </view>
+                                            </view>
+                                        </template>
+                                        <view v-else
+                                              :class="sign === 'miaosha'|| (sign === 'flash-sale' && showProgressBar) || (goods.vip_card_appoint && goods.vip_card_appoint.discount) || (goods.is_level == 1 && goods.is_negotiable != 1) ? 'dir-left-nowrap main-left cross-center' :  'dir-top-nowrap'">
+                                            <view class="goods-price ">{{showGoodsPrice  ?
+                                                goods.price_content :
+                                                ''}}
+                                            </view>
+                                            <view class="dir-left-nowrap">
+                                                <view class="original-price" style="margin-left: 10upx;"
+                                                      v-if="isShowOriginalPrice(goods)">
+                                                    ¥{{goods.original_price}}
+                                                </view>
+                                            </view>
+                                        </view>
+                                    </view>
+                                    <!--购物车-->
+                                    <view v-if="showBuyBtn && goods.is_negotiable != 1" :style="goods.buy_goods_auth ? btnStyle : disableBtnStyle" :class="[buyBtnClass, 'box-grow-0', 'buy-btn', 'main-center', 'cross-center']">{{buyBtnText}}</view>
+                                </view>
+                            </view>
+                        </view>
+                    </view>
+                </block>
+            </template>
+            <!-- 一行一个 -->
+            <template v-else-if="listStyle === 1">
+                <block v-for="(goods, index) in newData" :key="index">
+                    <view :style="[{'background-color':`${goodsStyle != 3 ? '#fff' : ''}`,overflow: `${sign === 'composition' ? 'hidden': 'visible'}`}]"
+                      class="goods-item app-column-1" :class="[goodsClass, index !== newData.length - 1 ? 'app-column-1-mar' : '']" @click="jump(goods)">
+                    <view style="position: relative;">
+                        <view class="out-dialog"
+                              :style="[{'height':`${coverPicHeight}`,'border-radius': `${imgRadius}`}]"
+                              v-if="(goods.goods_stock == 0 || (goods.goods && goods.goods.goods_stock == 0)) && appSetting.is_show_stock == '1'">
+                            <image v-if="coverPicHeight == '468rpx'"
+                                   :style="[{'height':`${coverPicHeight}`,'border-radius': `${imgRadius}`}]"
+                                   :src="appSetting.is_use_stock == '1' ? appImg.rate_out : appSetting.sell_out_other_pic"></image>
+                            <image v-else
+                                   :style="[{'height':`${coverPicHeight}`,'border-radius': `${imgRadius}`}]"
+                                   :src="appSetting.is_use_stock == '1' ? appImg.one_out : appSetting.sell_out_pic"></image>
+                        </view>
+                        <view>
+                            <app-image :borderRadius="imgRadius" v-if="sign === 'advance'"
+                                       :img-src="goods.goods.goodsWarehouse.cover_pic"
+                                       width="100%"
+                                       :height="coverPicHeight"
+                                       :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                            ></app-image>
+                            <div v-else-if="sign === 'composition'" style="width: 100%;height: 336rpx">
+                                <app-diy-composition-image
+                                        :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                                        :image-list="goods.cover_pic_list"></app-diy-composition-image>
+                            </div>
+                            <app-image :borderRadius="imgRadius" v-else
+                                       :img-src="goods.cover_pic"
+                                       width="100%"
+                                       :height="coverPicHeight"
+                                       :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                            ></app-image>
+                        </view>
+                        <view class="goods-tag" v-if="showGoodsTag">
+                            <app-image :img-src="goodsTagPicUrl"
+                                       width="64rpx"
+                                       height="64rpx"
+                            >
+                            </app-image>
+                        </view>
+                        <view class="box-grow-0 timer" v-if="showTimer">
+                            <template v-if="sign === 'advance'">
+                                <app-goods-timer
+                                        :list-style="listStyle"
+                                        :start-date-time="goods.start_prepayment_at"
+                                        :end-date-time="goods.end_prepayment_at"
+                                        :sign="sign" :page-hide="pageHide"
+                                        :theme="theme"
+                                ></app-goods-timer>
+                            </template>
+                            <template v-else>
+                                <app-goods-timer
+                                        :list-style="listStyle"
+                                        :start-date-time="goods.start_time"
+                                        :end-date-time="goods.end_time"
+                                        :sign="sign" :page-hide="pageHide"
+                                        :theme="theme"
+                                ></app-goods-timer>
+                            </template>
+                        </view>
+                    </view>
+                    <view class="goods-name t-omit-two" v-if="showGoodsName">
+                        <template v-if="sign === 'advance'">
+                            <text>{{showGoodsName ? goods.goods.goodsWarehouse.name : ''}}</text>
+                        </template>
+                        <template v-if="['pick', 'gift', 'exchange', 'wholesale', 'step'].indexOf(sign) !== -1">
+                            <text class="tag-pick"
+                                    :style="{'background-color': theme.background_o,'color': theme.color}"
+                                {{tag}}
+                            </text>
+                            <text>{{showGoodsName ? goods.name : ''}}</text>
+                        </template>
+                        <template v-else-if="sign === 'composition'">
+                            <text class="tag-pick" :style="{'background-color': theme.background_o,'color': theme.color}">
+                                {{goods.tag}}
+                            </text>
+                            <text>{{showGoodsName ? goods.name : ''}}</text>
+                        </template>
+                        <template v-else>
+                            <text class="tag"
+                                  :style="{'border-color': theme.border,'color': theme.color}"
+                                  v-if="tag && sign == 'pintuan' && goods.people_num">
+                                {{goods.people_num ? goods.people_num : ''}}{{tag}}
+                            </text>
+                            <text>{{showGoodsName ? goods.name : ''}}</text>
+                        </template>
+                    </view>
+                    <template v-if="sign === 'advance'">
+                        <text class="des-price" :style="{'border-color': theme.border,'color': theme.color,'margin-left':'24rpx','margin-top':'15rpx'}">
+                            定金¥{{goods.deposit}}抵¥{{goods.swell_deposit}}
+                        </text>
+                    </template>
+                    <template v-if="sign === 'flash-sale'">
+                        <view class="dir-top-nowrap" style="margin-bottom: 10rpx;padding:0 24rpx;">
+                            <view :class="showProgressBar ? 'flash-sale-dis' : ''">
+                                <view class="flash-sale" :style="{'background-color': theme.background}"
+                                      v-if="goods.discount_type == 1">{{goods.min_discount}}折</view>
+                                <view class="flash-sale" :style="{'background-color': theme.background}"
+                                      v-if="goods.discount_type == 2">减{{goods.min_discount}}元</view>
+                            </view>
+                            <view v-if="showProgressBar" class="app-percentage" :style="{'background-color': theme.background_l,'width':'660rpx'}">
+                                <view :style="{width: `${goods.percentage}%`,'background-color': theme.background}"></view>
+                            </view>
+                            <view v-if="sign === 'flash-sale' && showProgressBar" style="font-size: 20rpx;color: #999999;">{{goods.sales}}</view>
+                        </view>
+                    </template>
+                    <template v-if="sign === 'pick'">
+                        <text class="des-price"
+                              :style="{'border-color': theme.border,'color': theme.color,'margin-left':'24rpx','margin-top':'15rpx'}">
+                            {{goods.rule_price}}元选{{goods.rule_num}}{{goods.unit}}
+                        </text>
+                    </template>
+                    <view class="price dir-left-nowrap cross-center"
+                          :style="{marginTop: sign === 'advance' ? '0rpx' : '16rpx'}">
+                        <view class="box-grow-1 t-omit" :style="{'color': theme.color}">
+                            <view v-if="sign === 'advance'" class="dir-left-nowrap cross-center"
+                                  :class="textStyle === 2 ? 'main-center' : ''">
+                                <view v-if="goods.goods.is_level == 1">
+                                    <app-member-price :price="goods.level_price"></app-member-price>
+                                </view>
+                                <app-sup-vip :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                             margin='0 0 0 12rpx' v-if="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                             :discount="goods.vip_card_appoint && goods.vip_card_appoint.discount"></app-sup-vip>
+                            </view>
+                            <template v-if="sign === 'advance'">
+                                <view :class="{'dir-left-nowrap': goods.goods.is_level == 1}"
+                                      v-if="showGoodsPrice">
+                                    <view class="dir-left-nowrap cross-center"
+                                          :class="{'main-center' :textStyle == 2}">
+                                        <view style="font-size: 22rpx;height: 22rpx;line-height: 22rpx">
+                                            {{showGoodsPrice ? `¥${goods.goods.price}` : ''}}
+                                        </view>
+                                    </view>
+                                    <view class="original-price"
+                                          style="font-size: 17rpx;height:17rpx;line-height: 17rpx;"
+                                          v-if="isUnderLinePrice && goods.goods.goodsWarehouse.original_price">
+                                        ¥{{goods.goods.goodsWarehouse.original_price}}
+                                    </view>
+                                </view>
+                            </template>
+                            <template v-else>
+                                <view class="dir-left-nowrap cross-center"
+                                      :class="textStyle === 2 ? 'main-center' : ''">
+                                    <view>{{showGoodsPrice  ? goods.price_content :
+                                        ''}}
+                                    </view>
+                                    <view class="member-price"
+                                          v-if="goods.is_level == 1 && goods.is_negotiable != 1 && sign !== 'pick'">
+                                        <app-member-price margin=""
+                                                          :price="goods.level_price"></app-member-price>
+                                    </view>
+                                    <app-sup-vip :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                                 margin='0 0 0 12rpx'
+                                                 v-if="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                                 :discount="goods.vip_card_appoint && goods.vip_card_appoint.discount"></app-sup-vip>
+                                </view>
+                                <view class="original-price"
+                                      v-if="isShowOriginalPrice(goods)">
+                                    ¥{{goods.original_price}}
+                                </view>
+                            </template>
+                        </view>
+                        <template v-if="showBuyBtn && goods.is_negotiable != 1 && textStyle !== 2">
+                            <view class="box-grow-0 buy-btn main-center cross-center" :style="goods.buy_goods_auth ? btnStyle : disableBtnStyle" :class="buyBtnClass">
+                                {{buyBtnText}}
+                            </view>
+                        </template>
+                    </view>
+                </view>
+                </block>
+            </template>
+            <!-- 一行两个 -->
+            <template v-else-if="listStyle === 2">
+                <block v-for="(goods, index) in newData" :key="index">
+                    <view :style="[{'background-color':`${goodsStyle != 3 ? '#fff' : ''}`, overflow: `${sign === 'composition' ? 'hidden': 'visible'}`}]"
+                      class="box-grow-0 dir-top-nowrap goods-item app-column-2" :class="goodsClass"
+                      @click="jump(goods)">
+                    <view style="position: relative;">
+                        <view :style="[{'border-radius': `${imgRadius}`}]" class="out-dialog"
+                              v-if="(goods.goods_stock == 0 || (goods.goods && goods.goods.goods_stock == 0)) && appSetting.is_show_stock == '1'">
+                            <image :style="[{'border-radius': `${imgRadius}`}]"
+                                   :src="appSetting.is_use_stock == '1' ? appImg.book_out : appSetting.sell_out_pic"></image>
+                        </view>
+                        <view class="box-grow-0">
+                            <template v-if="sign === 'advance'">
+                                <app-image :borderRadius="imgRadius"
+                                           :img-src="goods.goods.goodsWarehouse.cover_pic"
+                                           width="100%"
+                                           height="342rpx"
+                                           :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                                ></app-image>
+                            </template>
+                            <div v-else-if="sign === 'composition'" style="width: 100%;height: 164rpx">
+                                <app-diy-composition-image
+                                        :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"
+                                        :image-list="goods.cover_pic_list"></app-diy-composition-image>
+                            </div>
+                            <template v-else>
+                                <app-image :borderRadius="imgRadius" :img-src="goods.cover_pic" width="100%"
+                                           height="342rpx"
+                                           :mode="fill === 1 ? 'aspectFill' : 'aspectFit'"></app-image>
+                            </template>
+                        </view>
+                        <view class="goods-tag" v-if="showGoodsTag">
+                            <app-image :img-src="goodsTagPicUrl" width="64rpx"
+                                       height="64rpx"></app-image>
+                        </view>
+                        <view class="box-grow-0 timer" v-if="showTimer">
+                            <template v-if="sign === 'advance'">
+                                <app-goods-timer :list-style="listStyle"
+                                                 :start-date-time="goods.start_prepayment_at"
+                                                 :end-date-time="goods.end_prepayment_at"
+                                                 :sign="sign" :page-hide="pageHide"
+                                                 :theme="theme"
+                                ></app-goods-timer>
+                            </template>
+                            <template v-else>
+                                <app-goods-timer :list-style="listStyle" :start-date-time="goods.start_time"
+                                                 :end-date-time="goods.end_time"
+                                                 :sign="sign" :page-hide="pageHide"
+                                                 :theme="theme"
+                                ></app-goods-timer>
+                            </template>
+                        </view>
+                    </view>
+                    <view class="box-grow-1 dir-top-nowrap main-between">
+                        <view class="box-grow-0 goods-name t-omit-two" v-if="showGoodsName">
+                            <template v-if="['advance', 'pick', 'gift', 'exchange', 'composition', 'wholesale', 'step'].indexOf(sign) === -1">
+                                <text class="tag"
+                                      :style="{'border-color': theme.border,'color': theme.color}"
+                                      v-if="tag && ((sign === 'pintuan' && goods.people_num) || sign !== 'pintuan')">
+                                    {{goods.people_num ? goods.people_num : ''}}{{tag}}
+                                </text>
+                            </template>
+                            <template v-if="['pick', 'gift', 'exchange', 'wholesale', 'step'].indexOf(sign) !== -1">
+                                <text class="dir-tag-def"
+                                      :style="{'background-color': theme.background_o,'color': theme.color}">
+                                    {{tag}}
+                                </text>
+                            </template>
+                            <template v-if="sign === 'composition'">
+                                <text class="dir-tag-def"
+                                      :style="{'background-color': theme.background_o,'color': theme.color}">
+                                    {{goods.tag}}
+                                </text>
+                            </template>
+                            <text v-if="sign === 'advance'">{{showGoodsName ?
+                                goods.goods.goodsWarehouse.name : ''}}
+                            </text>
+                            <text v-else>{{showGoodsName ? goods.name : ''}}</text>
+                        </view>
+                        <view v-if="sign === 'advance'">
+                            <text class="des-price"
+                                  :style="{'background-color': theme.background,'border-color': theme.color,'margin-left':'24rpx'}">
+                                定金¥{{goods.deposit}}抵¥{{goods.swell_deposit}}
+                            </text>
+                        </view>
+                        <view v-if="sign === 'pick'">
+                            <text class="des-price"
+                                  :style="{'background-color': theme.background,'border-color': theme.color,'margin-left':'24rpx'}">
+                                {{goods.rule_price}}元选{{goods.rule_num}}{{goods.unit}}
+                            </text>
+                        </view>
+                        <template v-if="sign === 'flash-sale'">
+                            <view :class="showProgressBar ? 'flash-sale-dis' : ''" style="margin-left: 24rpx;">
+                                <view class="flash-sale" v-if="goods.discount_type === 1" :style="{'background-color': theme.background}">{{goods.min_discount}}折</view>
+                                <view class="flash-sale" v-if="goods.discount_type === 2" :style="{'background-color': theme.background}">减{{goods.min_discount}}元</view>
+                            </view>
+                            <view v-if="showProgressBar" class="app-percentage" :style="{'background-color': theme.background_l,'width':'296rpx','margin-left':'24rpx'}">
+                                <view :style="{width: `${goods.percentage}%`,'background-color':theme.background}"></view>
+                            </view>
+                            <text v-if="showProgressBar" style="font-size: 20rpx; color: #999;margin-top: 10rpx;padding-left: 24rpx">{{goods.sales}}</text>
+                        </template>
+                        <view :class="textStyle === 2 ? 'main-center' : 'dir-left-nowrap'"
+                              style="height: 15px;padding-left: 24rpx;"
+                              v-if="sign === 'advance' && goods.goods.is_level == 1 && goods.is_negotiable != 1">
+                            <view class="member-price">
+                                <app-member-price :price="goods.level_price"></app-member-price>
+                            </view>
+                        </view>
+                        <app-sup-vip :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                     style="padding-left: 24rpx;"
+                                     :class="textStyle === 2 ? 'main-center' : ''" margin="4rpx 0"
+                                     v-if="goods.vip_card_appoint && goods.vip_card_appoint.discount && sign === 'advance'"
+                                     :discount="goods.vip_card_appoint && goods.vip_card_appoint.discount"></app-sup-vip>
+                        <view class="box-grow-0 price dir-left-nowrap cross-bottom main-between"
+                              :style="{marginTop: sign === 'advance' ? '0rpx' : '16rpx'}">
+                            <template v-if="sign === 'advance'">
+                                <view :class="textStyle === 2 ? 'main-center advance-center' : ''"
+                                      style="width: 100%" v-if="showGoodsPrice">
+                                    <view class="dir-top-nowrap main-right cross-top"
+                                          :style="{'color': theme.color}">
+                                        <text class="t-omit"
+                                              style="font-size: 22rpx;height: 22rpx; line-height: 22rpx">
+                                            {{showGoodsPrice ? `¥${goods.goods.price}` : ''}}
+                                        </text>
+                                        <text class="original-price"
+                                              style="font-size: 17rpx;height: 22rpx; line-height: 22rpx"
+                                              v-if="isUnderLinePrice && goods.goods.goodsWarehouse.original_price">
+                                            ¥{{goods.goods.goodsWarehouse.original_price}}
+                                        </text>
+                                    </view>
+                                </view>
+                            </template>
+                            <template v-else>
+                                <view class="box-grow-1" :style="{'color': theme.color}">
+                                    <view class="member-price" :class="textStyle === 2 ? 'main-center' : ''"
+                                          v-if="goods.is_level == 1 && goods.is_negotiable != 1 && sign !== 'pick'">
+                                        <app-member-price :price="goods.level_price"></app-member-price>
+                                    </view>
+                                    <view v-if="goods.vip_card_appoint && goods.vip_card_appoint.discount" :class="textStyle === 2 ? 'main-center' : ''">
+                                        <app-sup-vip
+                                            :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                            :margin="textStyle === 2 ? '0':'8rpx 0 0'"
+                                            :discount="goods.vip_card_appoint && goods.vip_card_appoint.discount"
+                                        ></app-sup-vip>
+                                    </view>
+                                    <view class="t-omit">
+                                        {{showGoodsPrice  ? goods.price_content : ''}}
+                                    </view>
+                                    <view class="original-price"
+                                          v-if="isShowOriginalPrice(goods)">
+                                        ¥{{goods.original_price}}
+                                    </view>
+                                </view>
+                            </template>
+                            <view v-if="showBuyBtn && goods.is_negotiable != 1 && textStyle !== 2" class="box-grow-0 buy-btn main-center cross-center" :style="goods.buy_goods_auth ? btnStyle : disableBtnStyle" :class="buyBtnClass">
+                                {{buyBtnText}}
+                            </view>
+                        </view>
+                    </view>
+                </view>
+                </block>
+            </template>
+        </view>
+	</view>
+</template>
+
+<script>
+import { mapGetters, mapState } from "vuex";
+import appPrice from "../../page-component/goods/app-price.vue";
+import appGoodsTimer from "./app-goods-timer.vue";
+import appDiyCompositionImage from '../app-diy-goods-list/app-diy-composition-image';
+
+export default {
+    name: "app-diy-goods-list",
+    components: {
+        'app-price': appPrice,
+        'app-goods-timer': appGoodsTimer,
+        appDiyCompositionImage
+    },
+    props: {
+        list: {
+            type: Array,
+            default() {
+                return [];
+            }
+        },
+        goodsStyle: {
+            type: Number,
+            default() {
+                return 1;
+            }
+        },
+        textStyle: {
+            type: Number,
+            default() {
+                return 1;
+            }
+        },
+        listStyle: {
+            type: Number,
+            default() {
+                return -1;
+            }
+        },
+        showBuyBtn: {
+            type: Boolean,
+            default() {
+                return false;
+            }
+        },
+        buyBtnText: {
+            type: String,
+            default() {
+                return '';
+            }
+        },
+        buyBtnStyle: {
+            type: Number,
+            default() {
+                return 1;
+            }
+        },
+        fill: {
+            type: Number,
+            default() {
+                return 1;
+            }
+        },
+        showGoodsName: {
+            type: Boolean,
+            default() {
+                return true;
+            }
+        },
+        showGoodsPrice: {
+            type: Boolean,
+            default() {
+                return true;
+            }
+        },
+        showGoodsTag: {
+            type: Boolean,
+            default() {
+                return true;
+            }
+        },
+        goodsCoverProportion: {
+            type: String,
+            default() {
+                return '1-1';
+            }
+        },
+        customizeGoodsTag: {
+            type: Boolean,
+            default() {
+                return false;
+            }
+        },
+        goodsTagPicUrl: String,
+        sign: String,
+        buttonColor: {
+            type: String,
+            default() {
+                return '';
+            }
+        },
+        //是否显示划线价
+        isUnderLinePrice: {
+            type: Boolean,
+            default() {
+                return true;
+            }
+        },
+        pageHide: Boolean,
+        theme: Object,
+        showProgressBar: {
+            type: Boolean,
+            default() {
+                return false;
+            }
+        }
+    },
+    data() {
+        return {
+            imgRadius: '16rpx 16rpx 0 0',
+            lisRadius: '16rpx',
+			disableColor: '#999999'
+        };
+    },
+    computed: {
+        ...mapState({
+            appImg: state => state.mallConfig.__wxapp_img.mall,
+            appSetting: state => state.mallConfig.mall.setting,
+            platform: function(state) {
+                return state.gConfig.systemInfo.platform;
+            }
+        }),
+        ...mapGetters('mallConfig',{
+            vip: 'getVip',
+            getVideo: 'getVideo'
+        }),
+        newData() {
+            return this.list;
+        },
+        listClass() {
+            switch (this.listStyle) {
+                case 2:
+                    return `dir-left-wrap column-2 main-between `;
+                default:
+                    return ``;
+            }
+        },
+        buyBtnClass() {
+            let buyBtnClass = ``;
+            if (this.buyBtnStyle === 2 || this.buyBtnStyle === 4) {
+                buyBtnClass += `buy-btn-border `;
+            }
+            if (this.buyBtnStyle === 4 || this.buyBtnStyle === 3) {
+                buyBtnClass += `buy-btn-radius`;
+            }
+            return buyBtnClass;
+        },
+        btnStyle() {
+            let btnStyle = ``;
+            if (this.buyBtnStyle === 1 || this.buyBtnStyle === 3) {
+                btnStyle += `background-color: ${this.buttonColor};color: #ffffff;`;
+            } else {
+                btnStyle += `border-color: ${this.buttonColor};color: ${this.buttonColor};`;
+            }
+            return btnStyle;
+        },
+        disableBtnStyle() {
+            let btnStyle = ``;
+            if (this.buyBtnStyle === 1 || this.buyBtnStyle === 3) {
+                btnStyle += `background-color: ${this.disableColor};color: #ffffff;`;
+            } else {
+                btnStyle += `border-color: ${this.disableColor};color: ${this.disableColor};`;
+            }
+            return btnStyle;
+        },
+        coverPicHeight() {
+            if (this.goodsCoverProportion === '1-1') {
+                return `702rpx`;
+            } else {
+                return `468rpx`;
+            }
+        },
+        goodsClass() {
+            let goodsClass = ``;
+            if (this.goodsStyle === 2) {
+                goodsClass += `border`;
+            }
+            if (this.textStyle === 2) {
+                goodsClass += `text-center`;
+            }
+            return goodsClass;
+        },
+        tag() {
+            let tag = '';
+            switch (this.sign) {
+                case 'miaosha':
+                    tag = '秒杀';
+                    break;
+                case 'bargain':
+                    tag = '砍价';
+                    break;
+                case 'pintuan':
+                    tag = '人团';
+                    break;
+                case 'advance':
+                    tag = '预售';
+                    break;
+                case 'pick':
+                    tag = 'N元任选';
+                    break;
+                case 'composition':
+                    tag = '套餐组合';
+                    break;
+                case 'gift':
+                    tag = '社交送礼';
+                    break;
+                case 'flash-sale':
+                    tag = '限时抢购';
+                    break;
+                case 'wholesale':
+                    tag = '商品批发';
+                    break;
+                case 'exchange':
+                    tag = '礼品卡';
+                    break;
+                case 'step':
+                    tag = '步数宝';
+                    break;
+                default:
+                    tag = null;
+                    break;
+            }
+            return tag;
+        },
+        showTimer() {
+            if (!(this.sign === 'miaosha' || this.sign === 'bargain' || this.sign === 'lottery' || this.sign === 'advance' || this.sign === 'flash-sale')) return false;
+            return true;
+        },
+    },
+    methods: {
+        jump(data) {
+            // #ifndef MP-BAIDU
+            if (data.video_url && this.getVideo == 1 && data.sign !== 'lottery' && data.sign !== 'bargain' && data.sign !== 'wholesale') {
+                let id = data.id;
+                if (data.sign === 'advance') {
+                    id = data.goods_id;
+                }
+                if (data.sign === 'gift') {
+                    id = id + '&is_share=1';
+                }
+
+                // #ifdef MP
+                uni.navigateTo({
+                    url: `/pages/goods/video?goods_id=${id}&sign=${data.sign}`
+                });
+                // #endif
+                // #ifdef H5
+                uni.navigateTo({
+                    url: data.page_url
+                });
+                // #endif
+            } else {
+                uni.navigateTo({
+                    url: data.page_url
+                });
+            }
+            // #endif
+            // #ifdef MP-BAIDU
+            uni.navigateTo({
+                url: data.page_url
+            });
+            // #endif
+        },
+        isShowOriginalPrice(goods) {
+            return this.isUnderLinePrice && goods.original_price && this.showGoodsPrice && goods.is_negotiable !== 1;
+        },
+    }
+}
+</script>
+
+<style scoped lang="scss">
+	.advance-member {
+		margin-right: #{12rpx};
+	}
+	.advance-center .original-price {
+		width: 100%;
+		text-align: center;
+	}
+    .app-goods-list {
+        %background-image {
+            background-repeat: no-repeat;
+            background-size: cover;
+            background-position: center;
+        }
+        .goods-list {
+            padding: #{24rpx 24rpx 24rpx 24rpx};
+            font-size: $uni-font-size-general-one;
+            .border {
+                border: #{1rpx} solid #e2e2e2;
+            }
+            .goods-item {
+                position: relative;
+                .app-button-icon {
+                    width: #{45rpx};
+                    height: #{45rpx};
+                    display: block;
+                    @extend %background-image;
+                }
+                .app-button-icon-cart {
+                    background-image: url('../../../static/image/icon/cats.png');
+                }
+                .app-button-icon-add {
+                    background-image: url('../../../static/image/icon/add-to.png');
+                }
+                .buy-btn {
+                    height: #{48rpx};
+                    padding: 0 #{20rpx};
+                }
+                .buy-btn-radius {
+                    border-radius: #{24rpx};
+                }
+                .buy-btn-border {
+                    border: #{1rpx} solid;
+                }
+                .goods-tag {
+                    position: absolute;
+                    left: 0;
+                    top: 0;
+                    z-index: 10;
+                    width: #{64rpx};
+                    height: #{64rpx};
+                }
+                .original-price {
+                    font-size: $uni-font-size-weak-two;
+                    color: $uni-general-color-two;
+                    text-decoration: line-through;
+                }
+                .tag {
+                    padding: 0 #{10rpx};
+                    margin-right: #{8rpx};
+                    font-size: #{20rpx};
+					line-height: 1;
+                    border-radius: #{28rpx};
+                    border: #{1rpx} solid;
+					transform: rotateZ(360deg);
+                }
+                .timer {
+                    width: 100%;
+                }
+                .pintuan-tag {
+                    font-size: $uni-font-size-weak-two;
+                    margin: #{16rpx} #{12rpx} 0 0;
+                    padding: 0 #{24rpx};
+                    .people {
+                        width: #{70rpx};
+                        text-align: center;
+                        border: #{1rpx} solid;
+                        border-radius: #{2rpx};
+                        padding: #{2rpx} 0;
+                    }
+                }
+            }
+            .app-list-mode {
+                margin-bottom: #{32rpx};
+                position: relative;
+                border-radius: #{16rpx};
+				.name {
+					font-size: #{28rpx};
+					color: #353535;
+                    line-height: 38upx;
+				}
+                .right {
+					height: #{220rpx};
+                }
+                &:last-child {
+                    margin-bottom: 0;
+                }
+                .pintuan-tag {
+                    padding: 0;
+                }
+                .out-dialog {
+			        width: #{220rpx};
+			        height: 100%;
+			        position: absolute;
+			        z-index: 11;
+			        top: 0;
+			        left: 0;
+			        background-color: rgba(0,0,0,.5);
+			        image {
+			            width: #{220rpx};
+			            height: #{220rpx};
+			        }
+                }
+            }
+            .app-column-1 {
+                padding-bottom: #{24rpx};
+                border-radius: #{16rpx};
+			    .out-dialog {
+			        width: 100%;
+			        height: #{702rpx};
+			        z-index: 11;
+			        position: absolute;
+			        top: 0;
+			        left: 0;
+			        background-color: rgba(0,0,0,.5);
+			        image {
+			            width: #{702rpx};
+			            height: #{702rpx};
+			        }
+			    }
+                .goods-name {
+                    margin-top: #{28rpx};
+                    padding: 0 #{24rpx};
+                    font-size: #{28rpx};
+                    color: #353535;
+                    line-height: 38upx;
+                }
+                .price {
+                    margin-top: #{16rpx};
+                    padding: 0 #{24rpx};
+                    max-width: #{702rpx};
+                    .member-price {
+                        margin-left: #{10rpx};
+                    }
+                }
+                .timer {
+                    position: absolute;
+                    left: 0;
+                    bottom: 0;
+                    z-index: 10;
+                }
+                .pintuan-tag {
+                    margin: 0;
+                    padding-left: 0;
+                }
+            }
+            .app-column-1-mar {
+                margin-bottom: #{24rpx};
+            }
+            &.column-2 {
+				padding: #{24rpx} #{23rpx} 0 #{23rpx};
+				.app-column-2 {
+					margin-bottom: #{24rpx};
+                    padding-bottom: #{24rpx};
+                	border-radius: #{16rpx};
+					width: #{346rpx};
+				    .out-dialog {
+				        width: 100%;
+				        height: #{342rpx};
+				        position: absolute;
+				        z-index: 11;
+				        top: 0;
+				        left: 0;
+				        background-color: rgba(0,0,0,.5);
+				        image {
+				            width: #{342rpx};
+				            height: #{342rpx};
+				        }
+				    }
+                    .goods-name {
+                        font-size: #{28rpx};
+                        color: #353535;
+                        margin-top: #{24rpx};
+                        padding: 0 #{24rpx};
+                        width: #{344rpx};
+                        line-height: 38upx;
+                    }
+                    .price {
+                        margin-top: #{16rpx};
+                        padding: 0 #{24rpx};
+                        max-width: #{344rpx};
+                    }
+                    .pintuan-tag ~ .price {
+                        margin-top: #{16rpx};
+                    }
+                    .timer {
+                        position: absolute;
+                        left: 0;
+                        bottom: 0;
+                        z-index: 10;
+                    }
+                }
+            }
+        }
+    }
+    .des-price {
+	    display: inline-block;
+	    padding: #{0rpx 4rpx};
+	    border: #{1rpx} solid;
+	    border-radius: #{8rpx};
+	    font-size: #{20rpx};
+		line-height: 1.2;
+		transform: rotateZ(360deg);
+    }
+    .dir-tag-def {
+	    padding: 0 #{10rpx};
+	    margin-right: #{8rpx};
+	    font-size: $uni-font-size-weak-two;
+	    border-radius: #{28rpx};
+    }
+	.seheight {
+		height: #{110rpx};
+		width: #{430rpx};
+	}
+    .default-text {
+        color: #666666;
+    }
+	.tag-pick {
+		padding: 0 #{10rpx};
+		margin-right: #{8rpx};
+		font-size: $uni-font-size-weak-two;
+		border-radius: #{28rpx};
+	}
+	.goods-price {
+		font-size: #{30upx};
+		line-height: 1.1;
+	}
+    .app-percentage {
+        height: #{20rpx};
+        border-radius: #{12rpx};
+        overflow: hidden;
+        >view {
+            height: #{20rpx};
+            border-radius: #{12rpx};
+        }
+    }
+    .flash-sale {
+        padding: 5upx 10upx;
+        font-size: 20upx;
+        color: #fff;
+        line-height: 1;
+        border-radius: 14upx;
+        margin-right: 10upx;
+        display: inline-block;
+    }
+    .flash-sale-dis {
+        margin-bottom: 10upx;
+    }
+</style>

+ 567 - 0
components/page-component/app-diy-goods-list/app-diy-m-goods-list.vue

xqd
@@ -0,0 +1,567 @@
+<template>
+    <view class="app-diy-m-goods-list" :style="{backgroundColor: backgroundColor}">
+        <view class="m-diy-list__box dir-top-nowrap">
+            <view class="m-label box-grow-0 dir-left-nowrap cross-center main-between"
+                  :style="{background: 'linear-gradient(to right, ' + mBgColor +', '+ ( mBgType === 'gradient' ?  mBgGradientColor:  mBgColor) + ')'}">
+                <view class="box-grow-0 dir-left-nowrap cross-center">
+                    <view class="title" :style="{color: mColor}">{{ mTitle }}</view>
+                    <template v-if="tempData.open_date || after_title">
+                        <view class="desc">{{ timer_text }}</view>
+                        <view v-if="timer" class="time dir-left-nowrap cross-center">
+                            <template v-if="timer.day">
+                                <view class="box main-center cross-center"
+                                      :style="{color: mTimeColor,backgroundColor:mTimeBgColor}">{{ timer.day }}
+                                </view>
+                                <view class="colon main-center cross-center">:</view>
+                            </template>
+                            <view class="box main-center cross-center"
+                                  :style="{color: mTimeColor,backgroundColor:mTimeBgColor}">{{ timer.hour }}
+                            </view>
+                            <view class="colon main-center cross-center">:</view>
+                            <view class="box main-center cross-center"
+                                  :style="{color: mTimeColor,backgroundColor:mTimeBgColor}">{{ timer.min }}
+                            </view>
+                            <view class="colon main-center cross-center">:</view>
+                            <view class="box main-center cross-center"
+                                  :style="{color: mTimeColor,backgroundColor:mTimeBgColor}">{{ timer.sec }}
+                            </view>
+                        </view>
+                        <view v-if="after_title" class="desc">{{ after_title }}</view>
+                    </template>
+                </view>
+                <view class="m-label-right box-grow-0 dir-left-nowrap cross-center" @click="jumpMore">
+                    <view>更多</view>
+                    <view class="bg"></view>
+                </view>
+            </view>
+            <view class="m-goods" :style="{backgroundColor:mGoodsBgColor}">
+                <scroll-view v-if="tempData.list && tempData.list.length" scroll-x>
+                    <view class="dir-left-nowrap">
+                        <view class="m-goods-box dir-top-nowrap box-grow-0" v-for="(goods,index) in tempData.list"
+                              :key="index"
+                              @click="jump(goods)">
+                            <!-- 商品名称 -->
+                            <view class="sell-out-pi">
+                                <view class="u-cover-box">
+                                    <view class="u-out-dialog" v-if="isShowStock(goods)">
+                                        <image class="pic-url"
+                                               :src="appSetting.is_use_stock == '1' ? appImg.plugins_out : appSetting.sell_out_pic"
+                                        ></image>
+                                    </view>
+                                    <image class="pic-url box-grow-0 u-cover" v-bind:src="goods.cover_pic"></image>
+                                </view>
+                            </view>
+                            <view class="goods-end"
+                                  v-if="showProgressBar || showGoodsName || isShowOriginalPrice(goods) || showGoodsPrice">
+                                <view v-if="showGoodsName" class="goods-name t-omit-two">
+                                    <text class="g-tag"
+                                          :style="{'background-color': theme.background_o,'color': theme.color}">
+                                        {{ signAlone.title }}
+                                    </text>
+                                    {{ goods.name }}
+                                </view>
+                                <template v-if="sign === 'flash-sale' && showProgressBar">
+                                    <view class="u-margin goods-fold flash-sale main-center cross-center"
+                                          :style="{'background-color': theme.background}">
+                                        {{goods.discount_type == 1 ? goods.min_discount + '折' : '减' + goods.min_discount + '元'}}
+                                    </view>
+                                    <view class="u-margin goods-progress" :style="{'background-color': theme.background_l}">
+                                        <view class="goods-progress-view"
+                                              :style="{width: `${goods.percentage}%`,'background-color':`${theme.background}`}"></view>
+                                    </view>
+                                    <view class="u-margin goods-num">{{ goods.sales }}</view>
+                                </template>
+                                <template v-if="showGoodsPrice">
+                                    <view class="u-margin" v-if="isShowMemPrice(goods)">
+                                        <app-member-price
+                                            :theme="theme"
+                                            v-bind:price="goods.level_price"
+                                        ></app-member-price>
+                                    </view>
+                                    <view class="u-margin" v-if="isShowVip(goods)">
+                                        <app-sup-vip
+                                            v-bind:is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                            v-bind:discount="goods.vip_card_appoint.discount"
+                                        ></app-sup-vip>
+                                    </view>
+
+                                    <view class="u-margin dir-left-wrap cross-center">
+                                        <view class="goods-price" :style="{'color': theme.color}">
+                                            {{ goods.price_content }}
+                                        </view>
+                                        <view v-if="isShowOriginalPrice(goods)"
+                                              class="goods-under-line-price">¥{{ goods.original_price }}
+                                        </view>
+                                    </view>
+                                </template>
+                            </view>
+                            <view class="tag">
+                                <app-image :img-src="goodsTagPicUrl" width="64rpx" height="64rpx"></app-image>
+                            </view>
+                        </view>
+                    </view>
+                </scroll-view>
+                <view v-else class="empty cross-center dir-left-wrap">
+                    <view class="empty-bg"></view>
+                    <view class="empty-text">{{ signAlone.empty_title }}</view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import {mapGetters, mapState} from "vuex";
+import appPrice from "../../page-component/goods/app-price.vue";
+import appGoodsTimer from "./app-goods-timer.vue";
+import appDiyCompositionImage from './app-diy-composition-image';
+
+export default {
+    name: "app-diy-m-goods-list",
+    components: {
+        'app-price': appPrice,
+        'app-goods-timer': appGoodsTimer,
+        appDiyCompositionImage
+    },
+    props: {
+        list: {
+            type: Array,
+            default() {
+                return [];
+            }
+        },
+        mTitle: String,
+        mColor: {
+            type: String,
+            default: '#FFFFFF',
+        },
+        mBgType: {
+            type: String,
+            default: 'gradient',
+        },
+        mBgColor: {
+            type: String,
+            default: '#FF366F',
+        },
+        mBgGradientColor: {
+            type: String,
+            default: '#FF4242',
+        },
+        mTimeColor: {
+            type: String,
+            default: '#353535',
+        },
+        mTimeBgColor: {
+            type: String,
+            default: '#FFFFFF',
+        },
+        mGoodsBgColor: {
+            type: String,
+            default: '#FFE7E7',
+        },
+        showProgressBar: {
+            type: [Boolean, String],
+            default: false,
+        },
+        showGoodsName: {
+            type: [Boolean, String],
+            default: true,
+        },
+        showGoodsPrice: {
+            type: [Boolean, String],
+            default: true,
+        },
+        showGoodsTag: {
+            type: [Boolean, String],
+            default: false,
+        },
+        customizeGoodsTag: {
+            type: [Boolean, String],
+            default: '#FFFFFF',
+        },
+        goodsTagPicUrl: {
+            type: String,
+            default: '',
+        },
+        isUnderLinePrice: {
+            type: [Boolean, String],
+            default: true,
+        },
+        backgroundColor: {
+            type: String,
+            default: '#FFFFFF',
+        },
+        sign: String,
+        theme: Object,
+        mData: Object,
+    },
+    data() {
+        return {
+            timer: null,
+            timer_text: '',
+
+            tempData: this.mData,
+            after_title: '',
+            mTimeIntegral: null,
+        };
+    },
+    watch: {
+        mData: {
+            handler: function (data) {
+                this.tempData = data;
+            },
+            immediate: true,
+        },
+    },
+    computed: {
+        ...mapState({
+            appImg: state => state.mallConfig.__wxapp_img.mall,
+            appSetting: state => state.mallConfig.mall.setting,
+            platform: function (state) {
+                return state.gConfig.systemInfo.platform;
+            }
+        }),
+        ...mapGetters('mallConfig', {
+            getVideo: 'getVideo'
+        }),
+        newData() {
+            return this.list;
+        },
+        signAlone() {
+            switch (this.sign) {
+                case 'miaosha':
+                    return {
+                        'sign': this.sign,
+                        'title': '秒杀',
+                        'empty_title': '暂无秒杀活动',
+                        'more_url': '/plugins/miaosha/advance/advance',
+                    }
+                case 'flash-sale':
+                    return {
+                        'sign': this.sign,
+                        'title': '限时抢购',
+                        'empty_title': '暂无抢购活动',
+                        'more_url': '/plugins/flash_sale/index/index',
+                    }
+                default:
+                    break;
+            }
+        },
+    },
+    mounted() {
+        this.$nextTick(() => {
+            setTimeout(() => {
+                if (this.signAlone.sign === 'miaosha') {
+                    this.sTime();
+                }
+                if (this.signAlone.sign === 'flash-sale') {
+                    this.fTime();
+                }
+            })
+        })
+    },
+    beforeDestroy() {
+        clearInterval(this.mTimeIntegral);
+    },
+    methods: {
+        // 是否展示会员价
+        isShowMemPrice(goods) {
+            return goods.is_level === 1 && goods.is_negotiable !== 1 ? 1 : 0;
+        },
+        // 是否展示超级会员价
+        isShowVip(goods) {
+            return goods.vip_card_appoint && goods.vip_card_appoint.discount > 0 && goods.is_negotiable !== 1 ? 1 : 0;
+        },
+        // 是否展示售罄
+        isShowStock(goods) {
+            return this.appSetting.is_show_stock === 1 && goods.goods_stock === 0 ? 1 : 0;
+        },
+        set_time(time_at) {
+            clearInterval(this.mTimeIntegral);
+            let timer = new Date(time_at.replace(/-/g, '/'));
+            this.now_time(timer);
+            this.mTimeIntegral = setInterval(() => {
+                this.now_time(timer);
+            }, 1000);
+        },
+        now_time(timer) {
+            let time = timer.getTime() - new Date().getTime();
+            if (time < 0) {
+                clearInterval(this.mTimeIntegral);
+            }
+            let day = parseInt(time / 1000 / 60 / 60 / 24);
+
+            let hou = parseInt((time / 1000 / 60 / 60) % 24);
+            let min = parseInt((time / 1000 / 60) % 60);
+            let sec = parseInt((time / 1000) % 60);
+            this.timer = {
+                day: day < 10 ? "0" + day : day,
+                hour: hou < 10 ? "0" + hou : hou,
+                min: min < 10 ? "0" + min : min,
+                sec: sec < 10 ? "0" + sec : sec
+            };
+        },
+
+        fTime() {
+            if (this.tempData.activity) {
+                this.after_title = '结束';
+                this.set_time(this.tempData.activity.end_at);
+            } else if (this.tempData.next_activity) {
+                this.after_title = '开始';
+                //TODO ddddddd待优化
+                if (this.tempData.next_activity && this.tempData.next_activity.start_at) {
+                    this.set_time(this.tempData.next_activity.start_at);
+                }
+            }
+        },
+        sTime() {
+            let timenow = new Date();//获取当前时间
+            if ((new Date(this.tempData.open_date)).getDate() != timenow.getDate()) {
+                this.timer_text = '预告 ' + this.tempData.open_date + ' ' + this.tempData.open_time + '点场';
+            } else if (this.tempData.open_time != timenow.getHours()) {
+                this.timer_text = '预告 ' + this.tempData.open_time + '点场';
+            } else {
+                this.timer_text = this.tempData.open_time + '点场';
+                let timelog = this.tempData.date_time * 1000 - timenow.getTime();
+                this.mTimeIntegral = setInterval(() => {
+                    timelog -= 1000;
+                    if (timelog <= 0) {
+                        clearInterval(this.mTimeIntegral);
+                        return;
+                    }
+                    let hour = parseInt((timelog / 1000 / 60 / 60));
+                    let min = parseInt((timelog / 1000 / 60) % 60);
+                    let sec = parseInt((timelog / 1000) % 60);
+                    this.timer = {
+                        hour: hour < 10 ? "0" + hour : hour,
+                        min: min < 10 ? "0" + min : min,
+                        sec: sec < 10 ? "0" + sec : sec
+                    };
+                }, 1000);
+            }
+        },
+        jumpMore() {
+            uni.navigateTo({
+                url: this.signAlone.more_url,
+            });
+        },
+        jump(data) {
+            let isNav = false;
+            // #ifdef MP-ALIPAY || MP-WEIXIN || MP-TOUTIAO
+            isNav = true;
+            // #endif
+
+            // #ifdef H5 || MP-BAIDU
+            isNav = false;
+            // #endif
+            if (isNav && data.video_url && this.getVideo == 1) {
+                uni.navigateTo({
+                    url: `/pages/goods/video?goods_id=${id}&sign=${data.sign}`
+                });
+            } else {
+                uni.navigateTo({
+                    url: data.page_url
+                });
+            }
+        },
+        isShowOriginalPrice(goods) {
+            return this.isUnderLinePrice && goods.original_price && this.showGoodsPrice && goods.is_negotiable !== 1;
+        },
+    }
+}
+</script>
+
+<style scoped lang="scss">
+.app-diy-m-goods-list {
+    padding: 20px 0;
+
+    .m-diy-list__box {
+        padding: 0 #{20rpx};
+
+    }
+
+    .m-label {
+        width: 100%;
+        height: #{80rpx};
+        padding: 0 #{24rpx};
+        border-radius: #{16rpx} #{16rpx} 0 0;
+
+        .title {
+            font-size: #{28rpx};
+        }
+
+        .desc {
+            color: #ffffff;
+            font-size: #{24rpx};
+            margin-left: #{20rpx};
+        }
+
+        .time {
+            margin-left: #{12rpx};
+
+            .colon {
+                color: #ffffff;
+                width: #{22rpx};
+            }
+
+            .box {
+                font-size: 11px;
+                height: #{36rpx};
+                width: #{40rpx};
+                border-radius: #{4rpx};
+                background: #FFFFFF;
+                font-weight: bold;
+            }
+        }
+
+        .m-label-right {
+            font-size: #{26rpx};
+            color: #FFFFFF;
+
+            .bg {
+                background-image: url("../../../static/image/icon/arrow-right-white.png");
+                background-repeat: no-repeat;
+                background-size: 100% 100%;
+                width: #{12rpx};
+                height: #{22rpx};
+                display: block;
+                margin-left: #{12rpx};
+            }
+        }
+    }
+
+    .m-goods {
+        padding: #{20rpx} 0 #{20rpx} #{24rpx};
+        width: 100%;
+        border-radius: 0 0 #{16rpx} #{16rpx};
+
+        .empty {
+            background: #FFFFFF;
+            padding: 0 #{56rpx};
+            border-radius: #{16rpx};
+            height: #{180rpx};
+            margin-right: #{20rpx};
+
+            .empty-bg {
+                background-image: url("../../../static/image/icon/empty.png");
+                background-repeat: no-repeat;
+                background-size: 100% 100%;
+                height: #{100rpx};
+                width: #{100rpx}
+            }
+
+            .empty-text {
+                margin-left: #{54rpx};
+                font-size: #{28rpx};
+                color: #353535;
+            }
+        }
+
+        .m-goods-box {
+            margin-right: #{12rpx};
+            position: relative;
+            background-color: #FFFFFF;
+            width: #{260rpx};
+            border-radius: #{16rpx};
+
+            .tag {
+                position: absolute;
+                left: 0;
+                top: 0;
+                z-index: 10;
+                width: #{64rpx};
+                height: #{64rpx};
+            }
+
+            .u-cover-box {
+                position: relative;
+                width: #{260rpx};
+                height: #{260rpx};
+            }
+            .u-margin {
+                margin-top: #{5rpx};
+            }
+            .u-out-dialog {
+                width: #{260rpx};
+                height: #{260rpx};
+                position: absolute;
+                top: 0;
+                left: 0;
+                z-index: 10;
+                background-color: rgba(0, 0, 0, .5);
+                border-radius: #{16rpx} #{16rpx} 0 0;
+            }
+
+            .pic-url {
+                height: #{260rpx};
+                width: 100%;
+                display: block;
+                border-radius: #{16rpx} #{16rpx} 0 0;
+            }
+
+            .goods-end {
+                width: 100%;
+                padding: #{20rpx} #{14rpx};
+
+                .goods-name {
+                    font-size: #{24rpx};
+                    color: #353535;
+                }
+
+                .g-tag {
+                    padding: 0 #{10rpx};
+                    margin-right: #{8rpx};
+                    font-size: $uni-font-size-weak-two;
+                    border-radius: #{28rpx};
+                    text-align: center;
+                }
+
+                .goods-fold {
+                    padding: #{5rpx} #{10rpx};
+                    font-size: #{20rpx};
+                    color: #fff;
+                    line-height: 1;
+                    border-radius: #{14rpx};
+                    margin-right: #{10rpx};
+                    display: inline-block;
+                }
+
+                .goods-progress {
+                    width: 100%;
+                    height: #{20rpx};
+                    border-radius: #{20rpx};
+                    //background: #ff9f9f;
+                    overflow: hidden;
+                    margin-top: #{4rpx};
+
+                    .goods-progress-view {
+                        width: 50%;
+                        height: 100%;
+                        border-radius: inherit;
+                        //background-color: #ff4544;
+                    }
+                }
+
+                .goods-num {
+                    font-size: #{20rpx};
+                    color: #999999;
+                }
+
+
+                .goods-price {
+                    font-size: #{28rpx};
+                }
+
+                .goods-under-line-price {
+                    font-size: #{20rpx};
+                    margin-left: #{10rpx};
+                    color: #999999;
+                    text-decoration: line-through;
+                }
+
+            }
+        }
+
+    }
+}
+</style>

+ 180 - 0
components/page-component/app-diy-goods-list/app-goods-timer.vue

xqd
@@ -0,0 +1,180 @@
+<template>
+    <view class="dir-left-nowrap app-goods-timer cross-center" :class="listClass" :style="{'background-color': listStyle == 1 || listStyle == 2 ?theme.background :''}">
+        <template v-if="listStyle === -1">
+            <!-- 列表模式 -->
+            <image class="box-grow-0 img" src="../../../static/image/icon/time.png"></image>
+            <view class="box-grow-0 timer-1">{{timer}}</view>
+            <view class="box-grow-1" :style="{'color': theme.color}" v-if="timerStr">&nbsp;&nbsp;&nbsp;{{timerStr}}</view>
+        </template>
+        <template v-else-if="listStyle === 1">
+            <!-- 一行一个 -->
+            <view class="dir-left-nowrap app-column-1">
+                <view class="box-grow-1 sign-name">{{signName}}</view>
+                <view class="box-grow-0 timer-1">{{timer}}</view>
+                <view class="box-grow-0" v-if="timerStr">&nbsp;&nbsp;&nbsp;{{timerStr}}</view>
+            </view>
+        </template>
+        <template v-else-if="listStyle === 2">
+            <!-- 一行一个 -->
+            <view class="box-grow-0 dir-left-nowrap app-column-2">
+                <view class="box-grow-0 timer-1">{{timer}}</view>
+                <view class="box-grow-1" v-if="timerStr">&nbsp;&nbsp;&nbsp;{{timerStr}}</view>
+            </view>
+        </template>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: "app-goods-timer",
+        props: {
+            startDateTime: String,
+            endDateTime: String,
+            listStyle: {
+                type: Number,
+                default() {
+                    return -1;
+                }
+            },
+            sign: String,
+            pageHide: Boolean,
+            theme: Object,
+        },
+        data() {
+            return {
+                timeInterval: null,
+                timer: null,
+                timerStr: null
+            };
+        },
+        computed: {
+            time() {
+                return {
+                    startDateTime: this.startDateTime,
+                    endDateTime: this.endDateTime,
+                    pageHide: this.pageHide,
+                };
+            },
+
+            listClass() {
+                let str = ``;
+                if(this.listStyle == 2) {
+                    str = `main-center`
+                }
+                return str;
+            },
+
+            signName() {
+                let name = '';
+                switch (this.sign) {
+                    case 'miaosha':
+                        name = '秒杀';
+                        break;
+                    case 'bargain':
+                        name = '砍价';
+                        break;
+                    case 'lottery':
+                        name = '抽奖';
+                        break;
+                    case 'advance':
+                        name = '预售';
+                        break;
+                    case 'flash-sale':
+                        name = '限时抢购';
+                        break;
+                }
+                return name;
+            },
+        },
+        methods: {
+            timing(startDateTime, endDateTime) {
+                let timerStr = null;
+                let startTime = this.$utils.timeDifference(new Date().getTime(), new Date(startDateTime).getTime());
+                if (startTime) {
+                    this.timer = '距开始 ';
+                    timerStr = (startTime['d'] > 0 ? startTime['d'] + '天' : '') + startTime['h'] + ':' + startTime['m'] + ':' + startTime['s'];
+                }
+                let endTime = null;
+                if (!timerStr) {
+                    endTime = this.$utils.timeDifference(new Date().getTime(), new Date(endDateTime).getTime());
+                    if (endTime) {
+                        this.timer = '距结束 ';
+                        timerStr = (endTime['d'] > 0 ? endTime['d'] + '天' : '') + endTime['h'] + ':' + endTime['m'] + ':' + endTime['s'];
+                    }
+                }
+                if (!timerStr) {
+                    this.timer = '活动已结束';
+                    clearInterval(this.timeInterval);
+                }
+                this.timerStr = timerStr;
+            }
+        },
+        watch: {
+            time: {
+                handler() {
+                    if (this.pageHide) {
+                        clearInterval(this.timeInterval);
+                        return ;
+                    }
+                    let startDateTime = this.startDateTime;
+                    let endDateTime = this.endDateTime;
+                    
+                    startDateTime = startDateTime.replace(/-/g, '/');
+                    endDateTime = endDateTime.replace(/-/g, '/');
+                    this.timing(startDateTime, endDateTime);
+                    this.timeInterval = setInterval(() => {
+                        this.timing(startDateTime, endDateTime);
+                    }, 1000);
+                },
+                immediate: true
+            }
+        },
+        beforeDestroy() {
+            clearInterval(this.timeInterval);
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-goods-timer {
+        font-size: #{24upx};
+        color: #ffffff;
+
+        .img {
+            width: #{24rpx};
+            height: #{24rpx};
+            display: block;
+            margin-right: #{12rpx};
+        }
+        
+        .timer-1 {
+            color: $uni-general-color-two;
+        }
+
+        .app-column-1 {
+            padding: 0 #{24rpx};
+            height: #{80rpx};
+            line-height: #{80rpx};
+            width: 100%;
+
+            .timer-1 {
+                color: #ffffff;
+            }
+
+            .sign-name {
+                text-align: left;
+                font-size: $uni-font-size-import-two;
+            }
+        }
+
+        .app-column-2 {
+            height: #{44rpx};
+            line-height: #{44rpx};
+
+            .timer-1 {
+                color: #ffffff;
+            }
+        }
+    }
+</style>

+ 107 - 0
components/page-component/app-diy-timer/app-diy-timer.vue

xqd
@@ -0,0 +1,107 @@
+<template>
+    <view class="app-timer">
+        <app-jump-button form :url="link.url" :open_type="link.openType" arrangement="column">
+            <view v-if="picUrl">
+                <app-image :img-src="picUrl" mode="widthFix" width="750rpx" height="auto"></app-image>
+            </view>
+            <view :style="{'background-image':  `url(${bgPicUrl ? bgPicUrl : '../../../static/image/icon/icon-timer-bg.png'})`}" class="timer dir-top-nowrap main-center"
+                  v-if="timerStr">
+                <view v-if="startTime">距离活动开始还有</view>
+                <view v-if="startTime === null && endTime">距离活动结束还有</view>
+                <view v-if="startTime === null && endTime === null">活动已结束</view>
+                <view>{{timerStr}}</view>
+            </view>
+        </app-jump-button>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-timer",
+        props: {
+            picUrl: String,
+            link: Object,
+            startDateTime: {
+                type: String,
+                default() {
+                    return '2019-8-30 10:00:00';
+                }
+            },
+            endDateTime: {
+                type: String,
+                default() {
+                    return '2019-8-30 10:00:00';
+                }
+            },
+            pageHide: Boolean,
+            bgPicUrl: String,
+        },
+        data() {
+            return {
+                timeInterval: null,
+                startTime: null,
+                endTime: null,
+                timerStr: null
+            };
+        },
+        computed: {
+            time() {
+                return {
+                    startDateTime: this.startDateTime,
+                    endDateTime: this.endDateTime,
+                    pageHide: this.pageHide,
+                };
+            }
+        },
+        beforeDestroy() {
+            clearInterval(this.timeInterval);
+        },
+        watch: {
+            time: {
+                handler() {
+                    if (this.pageHide) {
+                        clearInterval(this.timeInterval);
+                        return ;
+                    }
+                    let startDateTime = this.startDateTime;
+                    let endDateTime = this.endDateTime;
+
+                    this.timeInterval = setInterval(() => {
+                        let startTime = null, endTime = null, timerStr = null;
+                        if (startDateTime) {
+                            startDateTime = startDateTime.replace(/-/g, '/');
+                            startTime = this.$utils.timeDifference(new Date().getTime(), new Date(startDateTime).getTime());
+                            if (startTime) {
+                                timerStr = (startTime['d'] > 0 ? startTime['d'] + '天' : '') + startTime['h'] + '小时' + startTime['m'] + '分' + startTime['s'] + '秒';
+                            }
+                        }
+                        if (endDateTime && !timerStr) {
+                            endDateTime = endDateTime.replace(/-/g, '/');
+                            endTime = this.$utils.timeDifference(new Date().getTime(), new Date(endDateTime).getTime());
+                            if (endTime) {
+                                timerStr = (endTime['d'] > 0 ? endTime['d'] + '天' : '') + endTime['h'] + '小时' + endTime['m'] + '分' + endTime['s'] + '秒';
+                            }
+                        }
+                        this.startTime = startTime;
+                        this.endTime = endTime;
+                        this.timerStr = timerStr;
+                    }, 1000);
+                },
+                immediate: true
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .timer {
+        width: 100%;
+        height: #{140rpx};
+        padding: #{24rpx};
+        font-size: $uni-font-size-general-one;
+        color: #ffffff;
+        background-repeat: no-repeat;
+        background-size: 100% 100%;
+        background-position: center;
+    }
+</style>

+ 380 - 0
components/page-component/app-exclusive-coupon/app-exclusive-coupon-two.vue

xqd
@@ -0,0 +1,380 @@
+<template>
+    <view class="app-exclusive-coupon-two" :style="{'background-color': `${noneColor ? '' : background}`}">
+        <view class="app-bottom" v-if="coupon_list.length > 0">
+            <scroll-view scroll-x class="app-scroll dir-left-nowrap">
+                <view v-for="(item, index) in coupon_list"
+                      :key="index"
+                      class="app-item"
+                      :style="[couponBoxStyle(item,index)]"
+                >
+                    <app-form-id @click="receive(index)">
+                        <view class="coupon-bg">
+                            <img style="height: 100%;width: 100%" :src="couponBgImg" alt="">
+                        </view>
+                        <view v-if="coupon_list.length === 1" class="dir-left-nowrap" style="height: 100%"
+                             :style="{color: textColor}">
+                            <view style="width: 28%;font-size: 32rpx"
+                                  class="app-text-top main-center app-number cross-center"
+                                  :class="{discount: item.type === `1`, 'app-symbol' : item.type !== `1`}">
+                                {{item.type === `1` ? item.discount : item.sub_price}}
+                            </view>
+                            <view style="width: 71%;font-size: 20rpx" class="app-text-top main-center cross-center">
+                                满{{item.min_price}}元可用
+                            </view>
+                            <view style="width: 20%" class="app-text-right main-center cross-center">
+                                <text>
+                                    {{item.is_receive == 0 ? receiveTip : item.is_receive > 0 ? '已领取' : ''}}
+                                </text>
+                            </view>
+                        </view>
+                        <view v-else class="dir-left-nowrap" style="height:100%" :style="{color: textColor}">
+                            <view style="text-align: center;width: 75%" class="box-grow-0">
+                                <view style="font-size:32rpx;height: 50%;padding-top: 15rpx"
+                                     :class="{discount: item.type === `1`, 'app-symbol' : item.type !== `1`}">
+                                    {{item.type === `1` ? item.discount : item.sub_price}}
+                                </view>
+                                <view style="height: 50%;font-size: 20rpx;padding-top: 15rpx">
+                                    满{{item.min_price}}元可用
+                                </view>
+                            </view>
+                            <view class="box-grow-1 app-text-right main-center cross-center">
+                                <text>
+                                    {{item.is_receive == 0 ? receiveTip : item.is_receive > 0 ? '已领取' : ''}}
+                                </text>
+                            </view>
+                        </view>
+                    </app-form-id>
+                </view>
+            </scroll-view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-exclusive-coupon-two',
+        props: {
+            receiveBg: {
+                type: String,
+                default: function() {
+                    return '';
+                }
+            },
+            list: {
+                type: Array,
+                default: function() {
+                    return [];
+                }
+            },
+            textColor: {
+                type: String,
+                default: function() {
+                    return  "#ffffff";
+                }
+            },
+            unclaimedBg: {
+                type: String,
+                default: function() {
+                    return ''
+                }
+            },
+            index: {
+                type: Number,
+            },
+            sign: {
+                type: String,
+            },
+            noneColor: {
+                type: Boolean,
+                default() {
+                    return false;
+                }
+            },
+            background: String,
+            page_id: Number,
+            template_id: Number,
+            is_required: Boolean,
+            coupon_req: Boolean,
+            couponBg: {
+                type: String,
+                default: '#D9BC8B',
+            },
+            //diy手动添加优惠券标识
+            addType: String,
+            couponBgType: {
+                type: String,
+                default() {
+                    return '#pure';
+                }
+            },
+            dIndex: {
+                type: Array,
+                default() {
+                    return [0, 0];
+                }
+            },
+            mIndex: {
+                type: Array,
+                default() {
+                    return [0, 0];
+                }
+            },
+            dType: String,
+        },
+        data() {
+            return {
+                coupon_list: []
+            };
+        },
+        computed: {
+            receiveTip() {
+                let receiveTip = '立即领取';
+                if (this.sign === 'integral-mall') {
+                    receiveTip = '立即兑换';
+                }
+                return receiveTip;
+            },
+            couponBgImg() {
+                //wechat ../../../
+                switch (this.coupon_list.length) {
+                    case 1:
+                        return '/static/image/diy/bg_coupon_index_1.png';
+                    case 2:
+                        return '/static/image/diy/bg_coupon_index_2.png';
+                    case 3:
+                        return '/static/image/diy/bg_coupon_index_3.png';
+                    default:
+                        return '/static/image/diy/bg_coupon_index_4.png';
+                }
+            },
+
+            couponBoxStyle() {
+                return (data, index) => {
+                    let couponList = this.coupon_list;
+                    let width, background;
+
+                    let screenWidth = uni.getSystemInfoSync().windowWidth;
+                    let p = 750 / screenWidth;
+                    switch (couponList.length) {
+                        case 1:
+                            width = `${parseInt(702 / p)}px`;
+                            break;
+                        case 2:
+                            width = `${parseInt(341 / p)}px`;
+                            break;
+                        case 3:
+                            width = `${parseInt(220 / p)}px`;
+                            break;
+                        default:
+                            width = `${parseInt(274 / p)}px`;
+                            break;
+                    }
+                    let extra = {'margin-left': `${20 / p}px`};
+                    if (index === 0) extra = Object.assign(extra, {'margin-left': `${24 / p}px`});
+                    if (index === this.coupon_list.length - 1) extra = Object.assign(extra, {'margin-right': `${24 / p}px`});
+                    if (data.is_receive > 0) {
+                        background = '#B4B4B4';
+                    } else if (this.couponBgType === 'gradient') {
+                        background = 'linear-gradient(to left, ' + this.couponBg + ',' + this.$utils.colorRgba(this.couponBg, 0.5) + ')';
+                    } else {
+                        background = this.couponBg;
+                    }
+                    return Object.assign(extra, {
+                        background,
+                        'width': width,
+                        'min-width': couponList.length > 3 ? width : 'auto'
+                    });
+                }
+            }
+        },
+        methods: {
+            flushCache(coupon_list) {
+                if (this.page_id == 0) {
+                    let storage = this.$storage.getStorageSync('INDEX_MALL');
+                    let dIndex = this.dIndex;
+                    let mIndex = this.mIndex;
+
+                    if (this.dType === 'module') {
+                        storage.home_pages.navs[mIndex[0]].template.data[mIndex[1]].data.list[dIndex[0]].data[dIndex[1]].data.coupon_list = coupon_list;
+                    } else {
+                        storage.home_pages.navs[dIndex[0]].template.data[dIndex[1]].data.coupon_list = coupon_list;
+                    }
+                    this.$storage.setStorageSync('INDEX_MALL', storage);
+                }
+            },
+            receive(index) {
+                let list = this.coupon_list;
+                if (this.sign == 'integral-mall') {
+                    this.$jump({
+                        url: list[index].page_url,
+                        open_type: 'navigate'
+                    });
+                    return;
+                }
+                if (list[index].is_receive == 1) {
+                    uni.showToast({
+                        mask: true,
+                        title: '已领取',
+                        icon: 'none'
+                    });
+                    return true;
+                }
+                uni.showLoading({
+                    mask: true,
+                    title: '领取中'
+                });
+
+                this.$request({
+                    url: this.$api.coupon.receive,
+                    data: {
+                        coupon_id: list[index].id
+                    }
+                }).then(e => {
+                    uni.hideLoading();
+                    if (e.code === 0) {
+                        if (e.data.rest == 0) {
+                            this.coupon_list[index].is_receive = '1';
+                        }
+                        let tempList = this.coupon_list;
+                        this.flushCache(tempList);
+                        this.$store.dispatch('page/actionSetCoupon', {
+                            list: [Object.assign(tempList[index], e.data)],
+                            type: 'receive'
+                        });
+                    } else {
+                        uni.showToast({title: e.msg, icon: 'none'});
+                    }
+                }).catch(() => {
+                    uni.hideLoading();
+                });
+            },
+
+            loadData() {
+                this.$request({
+                    url: this.$api.index.extra,
+                    data: {
+                        type: 'mall',
+                        key: 'coupon',
+                        page_id: this.page_id,
+                        index: this.index
+                    }
+                }).then(e => {
+                    this.coupon_list = e.data;
+                    if (this.page_id === 0) {
+                        let storage = this.$storage.getStorageSync('INDEX_MALL');
+                        storage.home_pages[this.index].list = this.coupon_list;
+                        this.$storage.setStorageSync('INDEX_MALL', storage);
+                    }
+                })
+            }
+        },
+        mounted() {
+            if (!this.coupon_req) {
+                if (this.is_required) {
+                    this.loadData();
+                } else {
+                    let storage = this.$storage.getStorageSync('INDEX_MALL');
+                    this.coupon_list = storage.home_pages[this.index].list;
+                }
+            } else {
+                this.coupon_list = this.list;
+            }
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-exclusive-coupon-two {
+        width: #{750rpx};
+
+        .coupon-bg {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+        }
+
+        .app-bottom {
+            padding: #{16rpx} 0;
+            height: #{130+16+16rpx};
+            width: #{750rpx};
+
+            .app-scroll {
+                height: #{130+16+16rpx};
+                height: 100%;
+                del-width: #{750-24rpx};
+                white-space: nowrap;
+            }
+
+            .app-item {
+                width: #{256rpx};
+                position: relative;
+                height: #{130rpx};
+                display: inline-block;
+                margin-left: #{20rpx};
+                background-repeat: no-repeat;
+                background-size: 100% 100%;
+
+                .app-number.app-symbol:before {
+                    content: '¥';
+                    font-size: #{20rpx};
+                }
+
+                app-number {
+                    font-size: #{40rpx};
+                }
+
+                .discount:after {
+                    content: '折';
+                    font-size: 75%;
+                }
+
+                .app-text-left {
+                    width: 75%;
+                    height: #{130rpx};
+
+                    .app-text-top {
+                        height: 50%;
+                        padding-top: #{1rpx};
+                        overflow: hidden;
+                        text-align: center;
+
+                        .app-symbol {
+                            display: inline-block;
+                            height: #{78rpx};
+                            font-size: #{20rpx};
+                        }
+
+                        .app-number {
+                            display: inline-block;
+                            height: 100%;
+                        }
+                    }
+
+                    .app-text-bottom {
+                        height: 50%;
+                        text-align: center;
+                        font-size: #{20rpx};
+                        display: block;
+                    }
+                }
+
+                .app-text-right {
+                    width: 25%;
+                    height: #{130rpx};
+
+                    text {
+                        height: #{130rpx};
+                        display: inline-block;
+                        text-align: center;
+                        line-height: #{50rpx};
+                        font-size: #{24rpx};
+                        writing-mode: vertical-rl;
+                        letter-spacing: #{5rpx};
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 353 - 0
components/page-component/app-exclusive-coupon/app-exclusive-coupon.vue

xqd
@@ -0,0 +1,353 @@
+<template>
+    <view class="app-exclusive-coupon" :style="{'background-color': `${noneColor ? '' : background}`}">
+        <view class="app-top main-between" v-if="showTop" @click="route()">
+            <view class="app-left main-between cross-center">
+                <icon class="app-icon" type></icon>
+                <text class="app-title">专享优惠券</text>
+            </view>
+            <view class="app-right main-between cross-center">
+                <text class="app-text">更多</text>
+                <icon class="app-icon" type></icon>
+            </view>
+        </view>
+        <view class="app-bottom">
+            <scroll-view scroll-x class="app-scroll dir-left-nowrap">
+                <view v-for="(item, index) in coupon_list"
+                      :key="index"
+                      class="app-item"
+                      :style="{backgroundImage: `url(${item.is_receive == 0 ? unclaimedBg : item.is_receive == 1 ? receiveBg : ''})`}"
+                >
+                    <view class="main-left" @click="receive(index)">
+                        <view class="app-text-left">
+                            <view class="app-text-top">
+                                <template v-if="item.type == '1'">
+                                    <text class="app-number discount" :style="{color: textColor}">{{item.discount}}</text>
+                                </template>
+                                <template v-else>
+                                    <text class="app-symbol" :style="{color: textColor}">¥</text>
+                                    <text class="app-number" :style="{color: textColor}">{{item.sub_price}}</text>
+                                </template>
+                            </view>
+                            <text class="app-text-bottom" :style="{color: textColor}">满{{item.min_price}}元可用</text>
+                        </view>
+                        <view class="app-text-right">
+                            <text :style="{color: textColor}">{{item.is_receive == 0 ? receiveTip : item.is_receive > 0 ? '已领取' : ''}}</text>
+                        </view>
+                    </view>
+                </view>
+            </scroll-view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-exclusive-coupon',
+        props: {
+            receiveBg: {
+                type: String,
+                default: function() {
+                    return '';
+                },
+                required: false
+            },
+            list: {
+                type: Array,
+                default: function() {
+                    return [];
+                },
+                required: false
+            },
+            textColor: {
+                type: String,
+                default: function() {
+                    return  "#ffffff";
+                },
+                required: false
+            },
+            unclaimedBg: {
+                type: String,
+                default: function() {
+                    return '';
+                },
+                required: false
+            },
+            index: {
+                type: Number,
+                required: false
+            },
+            sign: {
+                type: String,
+                required: false
+            },
+            showTop: {
+                type: Boolean,
+                default() {
+                    return true;
+                },
+                required: false
+            },
+            noneColor: {
+                type: Boolean,
+                default() {
+                    return false;
+                },
+                required: false
+            },
+            background: {
+                type: String,
+                required: false
+            },
+            page_id: {
+                type: Number,
+                required: false
+            },
+            is_required: {
+                type: Boolean,
+                required: false
+            },
+            coupon_req: {
+                type: Boolean,
+                required: false
+            }
+        },
+        data() {
+            return {
+                coupon_list: [],
+                tempList: []
+            };
+        },
+        computed: {
+            receiveTip() {
+                let receiveTip = '立即领取';
+                if (this.sign === 'integral-mall') {
+                    receiveTip = '立即兑换';
+                }
+                return receiveTip;
+            }
+        },
+        methods: {
+            receive(index) {
+                let list = this.coupon_list;
+                if (this.sign === 'integral-mall') {
+                    this.$jump({
+                        url: list[index].page_url,
+                        open_type: 'navigate'
+                    });
+                    return ;
+                }
+                if (list[index].is_receive == 1) {
+                    uni.showToast({
+                        mask: true,
+                        title: '已领取',
+                        icon: 'none'
+                    });
+                    return true;
+                }
+                uni.showLoading({
+                    mask: true,
+                    title: '领取中'
+                });
+                this.$request({
+                    url: this.$api.coupon.receive,
+                    data: {
+                        coupon_id: list[index].id
+                    }
+                }).then(e => {
+                    uni.hideLoading();
+                    if (e.code === 0) {
+                        if (e.data.rest == 0) {
+                            e.data.is_receive = 1;
+                        }
+                        this.coupon_list[index] = Object.assign(list[index], e.data);
+                        let storage = this.$storage.getStorageSync('INDEX_MALL');
+                        storage.home_pages[this.index].list = this.coupon_list;
+                        this.$storage.setStorageSync('INDEX_MALL', storage);
+                        this.$store.dispatch('page/actionSetCoupon', {
+                            list: [this.coupon_list[index]],
+                            type: 'receive'
+                        });
+                    } else {
+                        uni.showModal({
+                            title: '提示',
+                            content: e.msg,
+                            showCancel: false,
+                        });
+                    }
+                }).catch(() => {
+                    uni.hideLoading();
+                });
+            },
+            loadData() {
+                this.$request({
+                    url: this.$api.index.extra,
+                    data: {
+                        type: 'mall',
+                        key: 'coupon',
+                        page_id: this.page_id,
+                        index: this.index
+                    }
+                }).then(e => {
+                    this.coupon_list = e.data;
+                    // this.tempList = this.cloneData(e.data);
+                    // this.splitData();
+                    if (this.page_id === 0) {
+                        let storage = this.$storage.getStorageSync('INDEX_MALL');
+                        storage.home_pages[this.index].list = e.data;
+                        this.$storage.setStorageSync('INDEX_MALL', storage);
+                    }
+                })
+            },
+            cloneData(data) {
+                return JSON.parse(JSON.stringify(data));
+            },
+            splitData() {
+                if (!this.tempList.length) return;
+                let item = this.tempList[0];
+                this.coupon_list.push(item);
+                this.tempList.splice(0, 1);
+                if (this.tempList.length) {
+                    setTimeout(() => {
+                        this.splitData();
+                    }, 300);
+                }
+            },
+            route() {
+                uni.navigateTo({
+                    url: '/pages/coupon/list/list'
+                })
+            }
+        },
+        mounted() {
+            if (!this.coupon_req) {
+                if (this.is_required) {
+                    this.loadData();
+                } else {
+                    let storage = this.$storage.getStorageSync('INDEX_MALL');
+                    this.coupon_list = storage.home_pages[this.index].list;
+                    // this.tempList = this.cloneData(storage.home_pages[this.index].list);
+                    // this.splitData();
+                }
+            } else {
+                this.coupon_list = this.list;
+                // this.tempList = this.cloneData(this.list);
+                // this.splitData();
+            }
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-exclusive-coupon {
+        width: #{750rpx};
+        .app-top {
+            width: #{750rpx};
+            height: #{80rpx};
+            border-bottom: #{1rpx} solid #e2e2e2;
+            .app-icon {
+                background-repeat: no-repeat;
+                background-size: 100% 100%;
+            }
+            .app-left {
+                width: #{215rpx};
+                height: #{80rpx};
+                margin-left: #{24rpx};
+                .app-icon {
+                    width: #{46rpx};
+                    height: #{46rpx};
+                    background-image: url("../../../static/image/icon/coupon-icon.png");
+                }
+                .app-title {
+                    font-size: #{28rpx};
+                    color: #ff8831;
+                }
+            }
+            .app-right {
+                height: #{80rpx};
+                width: #{75rpx};
+                margin-right: #{24rpx};
+                .app-icon {
+                    width: #{12rpx};
+                    height: #{22rpx};
+                    background-image: url("../../../static/image/icon/arrow-right.png");
+                }
+                .app-text {
+                    font-size: #{26rpx};
+                    color: #999999;
+                }
+            }
+        }
+        .app-bottom {
+            padding-top: #{16rpx};
+            padding-left: #{8rpx};
+            padding-bottom: #{16rpx};
+            height: #{162rpx};
+            width: #{750rpx};
+            .app-scroll {
+                height: #{162rpx};
+                width: #{750-24rpx};
+                white-space: nowrap;
+            }
+            .app-item {
+                width: #{256rpx};
+                height: #{130rpx};
+                display: inline-block;
+                margin-left: #{16rpx};
+                background-repeat: no-repeat;
+                background-size: 100% 100%;
+                >view {
+                    width: #{256rpx};
+                    height: #{130rpx};
+                }
+                .app-text-left {
+                    width: #{199rpx};
+                    height: #{130rpx};
+                    .app-text-top {
+                        height: #{78rpx};
+                        padding-top: #{1rpx};
+                        overflow: hidden;
+                        text-align: center;
+                        .app-symbol {
+                            display: inline-block;
+                            height: #{78rpx};
+                            font-size: #{20rpx};
+                        }
+                        .app-number {
+                            display: inline-block;
+                            height: #{78rpx};
+                            padding-top: #{26rpx};
+                            font-size: #{40rpx};
+                        }
+
+                        .discount:after {
+                            content: '折';
+                            font-size: 75%;
+                        }
+                    }
+                    .app-text-bottom {
+                        height: #{50rpx};
+                        width: #{199rpx};
+                        text-align: center;
+                        font-size: #{20rpx};
+                        display: inline-block;
+                    }
+                }
+                .app-text-right {
+                    width: #{50rpx};
+                    height: #{130rpx};
+                    text {
+                        height: #{130rpx};
+                        width: #{50rpx};
+                        display: inline-block;
+                        text-align: center;
+                        line-height: #{50rpx};
+                        font-size: #{20rpx};
+                        margin-left: #{2rpx};
+                        writing-mode: vertical-rl;
+                        letter-spacing: #{5rpx};
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 437 - 0
components/page-component/app-good-shop-recommendation/app-good-shop-recommendation.vue

xqd
@@ -0,0 +1,437 @@
+<template>
+    <view class="app-good-shop-recommendation">
+        <view class="app-top-title dir-left-nowrap main-between cross-center" v-if="!showGoods && type === 'mch'">
+            <view class="app-left dir-left-nowrap cross-center">
+                <icon class="icon" type></icon>
+                <text>好店推荐</text>
+            </view>
+            <view class="app-right dir-left-nowrap cross-center">
+                <app-jump-button form url="/plugins/mch/list/list">
+                    <text>更多</text>
+                    <icon class="icon" type></icon>
+                </app-jump-button>
+            </view>
+        </view>
+        <view class="app-content" v-if="!showGoods">
+            <scroll-view scroll-x class="app-scroll">
+                <view class="app-item" :style="[{'background-color':`${cardStyle < 3 ? '#ffffff': ''}`,'border': `${cardStyle == 2 ? '2rpx solid #e2e2e2': '0'}`}]"
+                      v-for="(item, index) in list"
+                      :key="index"
+                >
+                    <app-jump-button arrangement="column" form :url="'/plugins/mch/shop/shop?mch_id=' + item.id">
+                        <image class="app-image" :src="item.picUrl"></image>
+                        <text class="app-name u-line-1">
+                            {{item.name}}
+                        </text>
+                    </app-jump-button>
+                </view>
+            </scroll-view>
+        </view>
+        <view class="app-goods-shop" v-if="showGoods">
+            <view class="app-shop" :style="[{'background-color':`${cardStyle < 3 ? '#ffffff': ''}`,'border': `${cardStyle == 2 ? '2rpx solid #e2e2e2': '0'}`}]"
+                  v-for="(item, index) in list" :key="index">
+                <view class="app-top dir-left-nowrap main-between" @click.stop="jump(item.id)">
+                    <view class="dir-left-nowrap">
+                        <image class="app-image" :src="item.pic_url"></image>
+                        <view class="app-title">
+                            <text class="app-name t-omit-two">{{item.name}}</text>
+                            <view class="app-number-title">
+                                <text class="app-shops">商品数量: {{item.goods_num}}</text>
+                                <text class="app-sell">已售: {{item.order_num}}</text>
+                            </view>
+                        </view>
+                    </view>
+                    <view v-if="item.distance" class="box-grow-0 distance" >距离{{item.distance}}</view>
+                    <view class="app-button-jump" v-else>
+                        <app-jump-button form >
+                            <view class="app-button">进店逛逛</view>
+                        </app-jump-button>
+                    </view>
+                </view>
+                <view class="app-bottom" v-if="item.goodsList.length !== 0">
+                    <scroll-view class="app-scroll" scroll-x>
+                        <view class="app-item" :style="{marginLeft: number !== 0 ? '8rpx' : '0'}"
+                              v-for="(good, number) in item.goodsList" :key="number">
+                            <view form
+                                             @click.native.stop="router_jump(good, item.id)"
+                                             >
+                                <image class="app-image" :src="good.picUrl"></image>
+                                <text class="app-price" :style="{'color': theme.color}">¥{{good.price}}</text>
+                            </view>
+                        </view>
+                    </scroll-view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapGetters, mapState} from 'vuex';
+
+    export default {
+        name: "app-good-shop-recommendation",
+
+        props: {
+            showGoods: {
+                type: Boolean,
+                default: function () {
+                    return false;
+                },
+                required: false
+            },
+            cardStyle: {
+                type: String,
+                default: function () {
+                    return '1';
+                },
+                required: false
+            },
+            type: {
+                type: String,
+                default() {
+                   return 'mch';
+                },
+                required: false
+            },
+            backgroundColor: {
+                type: String,
+                default() {
+                   return '#fff';
+                },
+                required: false
+            },
+            theme: {
+                type: [String, Object],
+                required: false
+            },
+            page_id: {
+                type: Number,
+                required: false
+            },
+            index: {
+                type: Number,
+                required: false
+            },
+            is_required: {
+                type: Boolean,
+                required: false
+            },
+            mch_list: {
+                type: Array,
+                required: false
+            },
+            coupon_req: {
+                type: Boolean,
+                required: false
+            }
+        },
+        computed: {
+            ...mapGetters('mallConfig',{
+                getVideo: 'getVideo'
+            }),
+            ...mapState({
+                platform: function(state) {
+                    return state.gConfig.systemInfo.platform;
+                }
+            })
+        },
+	    methods: {
+            jump(data) {
+                this.$jump({
+	                url: `/plugins/mch/shop/shop?mch_id=${data}`,
+	                open_type: 'navigate',
+                });
+            },
+            router_jump(data, id) {
+                // #ifndef MP-BAIDU
+                if (data.goodsWarehouse.video_url && this.getVideo == 1) {
+                    // #ifdef MP
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=mch`
+                    });
+                    // #endif
+                    // #ifdef H5
+                    uni.navigateTo({
+                        url: `/plugins/mch/goods/goods?id=${data.id}&mch_id=${id}`,
+                    });
+                    // #endif
+                } else {
+                    uni.navigateTo({
+                        url: `/plugins/mch/goods/goods?id=${data.id}&mch_id=${id}`,
+                    });
+                }
+                // #endif
+
+                // #ifdef MP-BAIDU
+                uni.navigateTo({
+                    url: `/plugins/mch/goods/goods?id=${data.id}&mch_id=${id}`,
+                });
+                // #endif
+            },
+            loadData() {
+                this.$request({
+                    url: this.$api.index.extra,
+                    data: {
+                        type: this.page_id === 0 ?'mall' : 'diy',
+                        key: 'mch',
+                        page_id: this.page_id,
+                        index: this.index,
+                        longitude: 0,
+                        latitude: 0
+                    }
+                }).then(e => {
+                    this.list = e.data;
+                    if (this.page_id === 0) {
+                        let storage = this.$storage.getStorageSync('INDEX_MALL');
+                        storage.home_pages[this.index].list = this.list;
+                        this.$storage.setStorageSync('INDEX_MALL', storage);
+                    }
+                })
+            }
+	    },
+
+        data() {
+            return {
+                list: []
+            }
+        },
+
+        watch: {
+            mch_list: {
+                handler(data) {
+                    if (this.coupon_req) {
+                        this.list = data;
+                    }
+                },
+                deep: true
+            }
+        },
+        mounted() {
+            if (this.coupon_req) {
+                this.list = this.mch_list;
+            } else {
+                if (this.is_required) {
+                    this.loadData();
+                } else {
+                    let storage = this.$storage.getStorageSync('INDEX_MALL');
+                    this.list = storage.home_pages[this.index].list;
+                }
+            }
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-good-shop-recommendation {
+        width: #{750rpx};
+
+        .distance {
+            align-self: flex-start;
+            padding-top: #{36rpx};
+            font-size: #{24rpx};
+            color: #999999;
+            float: right;
+            padding-right: #{50rpx};
+        }
+
+        .app-top-title {
+            width: #{750rpx};
+            height: #{80rpx};
+            border-bottom: #{1rpx} solid #e2e2e2;
+            background-color: #ffffff;
+
+            .icon {
+                background-size: 100% 100%;
+                background-repeat: no-repeat;
+            }
+
+            .app-left {
+                height: #{80rpx};
+
+                .icon {
+                    width: #{46rpx};
+                    height: #{46rpx};
+                    background-image: url("../../../static/image/icon/good-shop.png");
+                    margin-left: #{24rpx};
+
+                }
+
+                text {
+                    font-size: #{26rpx};
+                    color: #ff8831;
+                    margin-left: #{16rpx};
+                }
+            }
+
+            .app-right {
+                height: #{80rpx};
+
+                .icon {
+                    width: #{12rpx};
+                    height: #{22rpx};
+                    background-image: url("../../../static/image/icon/arrow-right.png");
+                    margin-right: #{24rpx};
+                }
+
+                text {
+                    font-size: #{26rpx};
+                    color: #999999;
+                    margin-right: #{12rpx};
+                }
+            }
+        }
+
+        .app-content {
+            width: #{750rpx};
+            padding: #{12rpx} 0;
+
+            .app-scroll {
+                width: #{750rpx};
+                padding-left: #{16rpx};
+                white-space: nowrap;
+                height: #{224+20+20+23rpx};
+
+                .app-item {
+                    display: inline-block;
+                    width: #{224rpx};
+                    height: #{224+20+20+24rpx};
+                    margin: 0 #{8rpx};
+                    border-radius: #{16rpx};
+
+                    .app-image {
+                        width: #{224rpx};
+                        height: #{224rpx};
+                        border-top-left-radius: #{16rpx};
+                        border-top-right-radius: #{16rpx};
+                    }
+
+                    .app-name {
+                        height: #{24+20+20rpx};
+                        /* #ifndef MP-ALIPAY */
+                        width: #{189rpx};
+                        /* #endif */
+                        /* #ifdef MP-ALIPAY */
+                        width: #{195rpx};
+                        /* #endif */
+                        font-size: #{24rpx};
+                        color: #353535;
+                        line-height: #{24+20+20rpx};
+                        display: inline-block;
+                        overflow: hidden;
+                        text-align: center;
+                    }
+                }
+            }
+        }
+
+        .app-goods-shop {
+            width: #{750rpx};
+            padding: #{20rpx};
+
+            .app-shop {
+                width: #{750-20-20rpx};
+                padding: 0 #{24rpx};
+                margin-top: #{20rpx};
+                margin-bottom: #{20rpx};
+                overflow: hidden;
+                border-radius: #{16rpx};
+
+                .app-top {
+                    width: #{750-20-20-24-24rpx};
+                    margin: #{24rpx} 0;
+
+                    .app-image {
+                        width: #{100rpx};
+                        height: #{100rpx};
+                        border-radius: #{8rpx};
+                    }
+
+                    .app-title {
+                        margin-left: #{24rpx};
+                        margin-top: #{36-24rpx};
+                        width: #{370rpx};
+                        .app-name {
+                            font-size: #{28rpx};
+                            color: #353535;
+                        }
+
+                        .app-number-title {
+                            margin-top: #{12rpx};
+                            color: #999999;
+                            font-size: #{24rpx};
+
+                            .app-sell {
+                                margin-left: #{32rpx};
+                            }
+                        }
+                    }
+
+                    .app-button-jump {
+                        float: right;
+                        margin-right: #{24rpx};
+                        width: #{144rpx};
+                        height: #{64rpx};
+                        margin-top: #{18rpx};
+                        margin-bottom: #{18rpx};
+                        .app-button {
+                            width: #{144rpx};
+                            height: #{64rpx};
+                            text-align: center;
+                            border: #{1rpx} solid #cccccc;
+                            font-size: #{26rpx};
+                            color: #666666;
+                            line-height: #{64rpx};
+                            border-radius: #{40rpx};
+                        }
+                    }
+                }
+
+                .app-bottom {
+                    height: #{210+23rpx};
+                    width: #{750-20-20-24-24rpx};
+                    padding-bottom: #{23rpx};
+
+                    .app-scroll {
+                        height: #{210rpx};
+                        width: #{750-20-20-24-24rpx};
+                        white-space: nowrap;
+                        margin-bottom: #{23rpx};
+
+                        .app-item {
+                            /*background-color: red;*/
+                            display: inline-block;
+                            width: #{210rpx};
+                            height: #{210rpx};
+                            margin-left: #{8rpx};
+                            margin-right: #{8rpx};
+                            position: relative;
+
+                            .app-image {
+                                width: #{210rpx};
+                                height: #{210rpx};
+                            }
+
+                            .app-price {
+                                position: absolute;
+                                bottom: 0;
+                                left: 0;
+                                width: #{210rpx};
+                                font-size: #{28rpx};
+                                height: #{50rpx};
+                                text-align: center;
+                                background-color: rgba(245, 245, 246, 0.5);
+                            }
+                        }
+                    }
+                }
+            }
+            .app-shop:first-of-type {
+                margin-top: 0;
+            }
+            .app-shop:last-of-type {
+                margin-bottom: 0;
+            }
+        }
+    }
+</style>

+ 45 - 0
components/page-component/app-goods-detail/app-name.vue

xqd
@@ -0,0 +1,45 @@
+<template>
+	<view class="name">
+		<view class="page-width text">
+			{{name}}
+		</view>
+	</view>
+</template>
+
+<script>
+    export default {
+        name: 'name',
+	    props: {
+            name: String
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.page-width {
+		width: 100%;
+	}
+	
+	.name {
+		background-color: #ffffff;
+		overflow: hidden;
+        width: 702upx;
+        margin: 24upx 24upx 0 24upx;
+        border-radius: 15upx 15upx 0 0;
+	}
+	
+	.text {
+		font-size: 32upx;
+		color: rgb(0,0,0);
+		line-height: 40upx;
+		padding: 30upx 24upx 0 24upx;
+		margin-bottom: 24upx;
+		word-break: break-all;
+		text-overflow: ellipsis;
+		display: -webkit-box;
+		-webkit-box-orient: vertical;
+		-webkit-line-clamp: 2;
+		overflow: hidden;
+		white-space: normal;
+	}
+</style>

+ 713 - 0
components/page-component/app-goods-list/app-goods-list.vue

xqd
@@ -0,0 +1,713 @@
+<template>
+    <view class="app-goods-list" :class="listClass">
+        <block v-for="(goods, index) in list" :key="index">
+            <!-- 列表模式 -->
+            <template v-if="listStyle == 1">
+                <view class="dir-left-nowrap goods-item-1" @click="jump(goods)">
+                    <view class="box-grow-0 cover-pic">
+                        <view class="out-dialog" v-if="goods.goods_stock == 0 && appSetting.is_show_stock == '1'">
+                            <image :src="appSetting.is_use_stock == '1' ? appImg.plugins_out : appSetting.sell_out_pic"></image>
+                        </view>
+                        <app-image :img-src="goods.cover_pic" borderRadius="16rpx" width="200rpx"
+                                   height="200rpx"></app-image>
+                    </view>
+                    <view class="box-grow-1 dir-top-nowrap content main-between" style="padding: 28rpx 24rpx 24rpx 0">
+                        <view class="box-grow-0 goods-name"
+                              :class="(goods.is_level == 1 && goods.is_negotiable != 1 && is_show_member) || goods.vip_card_appoint.discount ? 't-omit' : 't-omit-two'"
+                              v-if="isShowGoodsName == 1">{{goods.name}}</view>
+                        <view class="dir-top-nowrap" :class="isShowGoodsName == 1 ? 'box-grow-0' : 'box-grow-1'">
+                            <view class="box-grow-0 cross-bottom"
+                                  v-if="goods.is_level == 1 && goods.is_negotiable != 1 && is_show_member">
+                                <app-member-price :theme="theme" :price="goods.level_price"></app-member-price>
+                            </view>
+                            <app-sup-vip :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user"
+                                         v-if="goods.vip_card_appoint.discount"
+                                         :discount="goods.vip_card_appoint.discount">
+                            </app-sup-vip>
+                            <view :class="isShowCart == 0 || goods.is_negotiable == 1 ? 'dir-left-nowrap' : 'dir-top-nowrap'"
+                                  class="box-grow-1">
+
+                                <view class="box-grow-1 " :class="(goods.is_level == 1 && goods.is_negotiable != 1 && is_show_member)  || goods.vip_card_appoint.discount ? 'dir-left-nowrap cross-center '+ themeText : 'dir-top-nowrap ' + themeText">
+
+                                    <template v-if="goods.is_negotiable == 1">
+                                        <view :style="{'color': getTheme.color}">价格面议</view>
+                                    </template>
+                                    <template v-else>
+                                        <app-price :theme="getTheme" :price="`${goods.price}`" type="text-price-all"></app-price>
+                                    </template>
+                                    <view v-if="goods.sales && goods.is_negotiable != 1" :style="{marginLeft: (goods.is_level == 1 && goods.is_negotiable != 1 && is_show_member)  || goods.vip_card_appoint.discount ? '14rpx' : '0rpx'}" class="box-grow-1 sales">{{goods.sales}}</view>
+                                </view>
+                                <view class="dir-left-nowrap cross-center sales-box">
+                                    <view @click.stop="specification(goods)" class="box-grow-0 cross-center main-center cart-box"
+                                          :style="{'color': getTheme.color}"
+                                         v-if="goods.is_negotiable != 1 && isShowCart == 1 && goods.goods_stock > 0">
+                                        <app-cart-image class="goods-cart"></app-cart-image>
+                                    </view>
+                                </view>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+            </template>
+            <!-- 一行两个 -->
+            <template v-if="listStyle == 2">
+                <view class="box-grow-0 goods-item-2 dir-top-nowrap" @click="jump(goods)"
+                      :class="index%2 === 0 ? 'mr-14' : ''">
+                    <view class="box-grow-0">
+                        <view class="out-dialog" v-if="goods.goods_stock == 0 && appSetting.is_show_stock == '1'">
+                            <image :src="appSetting.is_use_stock == '1' ? appImg.plugins_out : appSetting.sell_out_pic"></image>
+                        </view>
+                        <app-image :img-src="goods.cover_pic" width="344rpx" height="344rpx"
+                                   :border-radius="`16rpx 16rpx 0 0`"></app-image>
+                    </view>
+                    <view class="box-grow-0 goods-name t-omit-two padding" v-if="isShowGoodsName == 1">
+                        <span v-if="sign === 'pick'" class="dir-tag-def" :style="{'color': getTheme.color,'background-color': getTheme.background_o}">N元任选</span>
+                        {{goods.name}}
+                    </view>
+                    <view v-if="sign === 'pick'">
+                        <text class="des-price" style="margin-left: 24rpx;margin-top: 15rpx;" :style="{'color': getTheme.color,'border-color': getTheme.border}">
+                            {{detail.rule_price}}元选{{detail.rule_num}}件
+                        </text>
+                    </view>
+                    <view class="box-grow-1 dir-top-nowrap main-right">
+
+                        <view class="box-grow-0 cross-bottom padding"
+                              v-if="goods.is_level == 1 && goods.is_negotiable != 1 && is_show_member">
+                            <app-member-price :theme="theme" :price="goods.level_price"></app-member-price>
+                        </view>
+                        <app-sup-vip :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user" margin="4rpx 24rpx 0"
+                                     v-if="goods.vip_card_appoint.discount"
+                                     :discount="goods.vip_card_appoint.discount"></app-sup-vip>
+                        <view class="box-grow-0 dir-left-nowrap content padding">
+                            <view  class="box-grow-0">
+                                <template v-if="goods.is_negotiable == 1">
+                                    <view :style="{'color': getTheme.color}">价格面议</view>
+                                </template>
+                                <template v-else>
+                                    <app-price :theme="getTheme" :price="`${goods.price}`" type="text-price-all"></app-price>
+                                </template>
+                            </view>
+                            <template v-if="goods.is_negotiable != 1 && sign != 'pick'">
+                                <view class="dir-left-nowrap cross-center sales-box box-grow-1"
+                                      :class="[isShowCart == 1 ? '' : 'main-right']"
+                                      v-if="goods.sales">
+                                    <view class="sales">{{goods.sales}}</view>
+                                </view>
+                                <view v-if="isShowCart == 1 && goods.goods_stock > 0" :class="[goods.sales ? 'box-grow-0' : 'box-grow-1 main-right']">
+                                    <app-cart-image @click.stop.native="specification(goods)"></app-cart-image>
+                                </view>
+                            </template>
+                        </view>
+                    </view>
+                </view>
+            </template>
+            <!-- 一行三个 -->
+            <template v-if="listStyle == 3">
+                <view class="box-grow-0 goods-item-3 dir-top-nowrap" @click="jump(goods)"
+                      :class="index%3 === 2 ? '' : 'mr-8'">
+                    <view class="box-grow-0">
+                        <view class="out-dialog" v-if="goods.goods_stock == 0 && appSetting.is_show_stock == '1'">
+                            <image :src="appSetting.is_use_stock == '1' ? appImg.plugins_out : appSetting.sell_out_pic"></image>
+                        </view>
+                        <app-image :img-src="goods.cover_pic" width="238rpx" height="238rpx"
+                                   :border-radius="`16rpx 16rpx 0 0`"></app-image>
+                    </view>
+                    <view class="box-grow-0 goods-name t-omit-two padding" v-if="isShowGoodsName == 1">{{goods.name}}</view>
+                    <view class="box-grow-1 dir-top-nowrap main-right">
+                        <view class="box-grow-0 cross-bottom padding"
+                              v-if="goods.is_level == 1 && goods.is_negotiable != 1 && is_show_member">
+                            <app-member-price :theme="theme" :price="goods.level_price"></app-member-price>
+                        </view>
+                        <app-sup-vip :is_vip_card_user="goods.vip_card_appoint.is_vip_card_user" margin="4rpx 20rpx 0"
+                                     v-if="goods.vip_card_appoint.discount"
+                                     :discount="goods.vip_card_appoint.discount"
+                        ></app-sup-vip>
+                        <view class="padding box-grow-0">
+                            <template v-if="goods.is_negotiable == 1">
+                                <view :style="{'color': getTheme.color}">价格面议</view>
+                            </template>
+                            <template v-else>
+                                <app-price :theme="getTheme" :price="`${goods.price}`" type="text-price-all"></app-price>
+                            </template>
+                        </view>
+                        <view class="sales-box cross-bottom box-grow-1">
+                            <view v-if="goods.sales && goods.is_negotiable != 1" class="box-grow-1 sales">{{goods.sales}}</view>
+                            <view v-if="goods.is_negotiable != 1 && isShowCart == 1 && goods.goods_stock > 0" class="box-grow-0 goods-cart">
+                                <app-cart-image @click.stop.native="specification(goods)"></app-cart-image>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+            </template>
+        </block>
+        <view class="attr">
+            <app-attr ref="attr"
+                      :goods="item"
+                      :select-attr="selectAttr"
+                      :attr-group-list="attrGroup"
+                      :show="show"
+                      :cartShow="cartShow"
+                      :buyText="buyText"
+                      :buyClick="buyBool"
+                      :plugin="plugin"
+                      :previewUrl="previewUrl"
+                      :submitUrl="submitUrl"
+                      @buyClick="buyClick"
+                      @attrtap="onAttr"
+                      @attr="attr"
+                      :theme="getTheme"
+            >
+                <view slot="extra" v-if="detail_sign === 'pintuan'">
+                    <app-pt-attr
+                        v-if="pt"
+                        :theme="getTheme"
+                        :pintuan_groups="item.pintuan_groups"
+                        :selectGroupAttrId="selectGroupAttrId"
+                        @click="setGroupAttrID"
+                    ></app-pt-attr>
+                </view>
+            </app-attr>
+        </view>
+        <view class="pt-btn" v-if="detail_sign === 'pintuan' && show_pt">
+            <app-iphone-x>
+                <view class="pintuan dir-left-nowrap" slot="empty-area">
+                    <view class="single box-grow-1 dir-top-nowrap" :style="{'color': getTheme.color,'background-color':getTheme.background_s}" @click="individual" v-if="item.pintuanGoods.is_alone_buy">
+                        <text class="app-text">单独购买</text>
+                    </view>
+                    <view class="tuan box-grow-1 dir-top-nowrap" :style="{'background-color':getTheme.background}" @click="multiplayer">
+                        <text class="app-text">拼团</text>
+                    </view>
+                </view>
+            </app-iphone-x>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapState, mapGetters} from "vuex";
+    import appPrice from '../../page-component/goods/app-price.vue';
+    import appAttr from '../app-attr/app-attr.vue';
+    import appPtAttr from '../app-pt-attr/app-pt-attr.vue';
+    import appIphoneX from '../../basic-component/app-iphone-x/app-iphone-x.vue';
+    import allPay from '../../../core/all-pay.js';
+
+    export default {
+        name: "app-goods-list",
+        components: {
+            'app-price': appPrice,
+            'app-attr': appAttr,
+            'app-pt-attr': appPtAttr,
+            'app-iphone-x': appIphoneX,
+        },
+        props: {
+            listStyle: {
+                type: String,
+                default() {
+                    return '2';
+                }
+            },
+            list: Array,
+            previewUrl: String,
+            submitUrl: String,
+            theme: Object,
+            is_show_member: {
+                type: Boolean,
+                default() {
+                    return true;
+                }
+            },
+            sign: String,
+            detail: Object
+        },
+        data() {
+            return {
+                show: 0,
+                buyText: '立即购买',
+                detail_sign: '',
+                goods_id: '',
+                cartShow: 1,
+                plugin: '',
+                goods_list: [],
+                buyBool: false,
+                cur_index: 0,
+                item: null,
+                show_pt: false,
+                pt: true,
+                loading: false,
+                selectAttr: {},
+                appAttr: {},
+                attrGroup: [],
+                selectGroupAttrId: -1,
+            }
+        },
+        computed: {
+            ...mapState({
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+                isShowCart: state => state.mallConfig.mall.setting.is_show_cart,
+                isShowGoodsName: state => state.mallConfig.mall.setting.is_show_goods_name,
+                platform: function(state) {
+                    return state.gConfig.systemInfo.platform;
+                }
+            }),
+            themeText:function() {
+                return this.theme.back[0]
+            },
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo',
+                getTheme: 'getTheme'
+            }),
+            listClass() {
+                if (this.listStyle == 3) {
+                    return `list-style-3 dir-left-wrap`;
+                } else if (this.listStyle == 2) {
+                    return `list-style-2 dir-left-wrap`;
+                } else {
+                    return ``;
+                }
+            }
+        },
+        watch: {
+            appAttr: {
+                handler(data) {
+                    if (data.display === 'none') {
+                        this.show_pt = false;
+                    }
+                }
+            }
+        },
+        methods: {
+            setGroupAttrID(id) {
+                this.selectGroupAttrId = id;
+                this.selectAttr = {};
+                this.request_pt(id);
+            },
+            multiplayer() {
+                let that = this;
+                if (that.pt) {
+                    if (that.selectAttr && Object.keys(that.selectAttr).length === 0) {
+                        uni.showToast({
+                            title: '请选择规格',
+                            icon: "none"
+                        })
+                    } else if (that.selectAttr &&  Object.keys(that.selectAttr).length > 0) {
+                        let mch_id = that.item.mch_id;
+                        let mch_list = [];
+                        let goods = {
+                            id: that.item.id,
+                            attr: [],
+                            num: that.appAttr.number,
+                            goods_attr_id: that.selectAttr.id,
+                            cart_id: 0,
+                        };
+                        for (let i = 0; i < that.selectAttr.attr_list.length; i++) {
+                            let attr = {
+                                attr_id: that.selectAttr.attr_list[i].attr_id,
+                                attr_group_id: that.selectAttr.attr_list[i].attr_group_id,
+                            };
+                            goods.attr.push(attr);
+                        }
+                        mch_list.push({
+                            mch_id: mch_id,
+                            pintuan_order_id: 0,
+                            pintuan_group_id: that.selectGroupAttrId,
+                            goods_list: [goods],
+                        });
+                        uni.navigateTo({
+                            url: `/pages/order-submit/order-submit?mch_list=${JSON.stringify(mch_list)}&preview_url=${encodeURIComponent(that.$api.pt.order_preview)}&submit_url=${encodeURIComponent(that.$api.pt.order_submit)}&order_page_url=/plugins/pt/order/order&plugin=pt`
+                        });
+
+                    }
+                } else {
+                    that.selectAttr = {};
+                    that.pt = true;
+                    that.request_pt(that.selectGroupAttrId);
+                    const temp = setInterval(() => {
+                        if (that.loading) {
+                            clearInterval(temp);
+                            if(that.item.pintuan_groups.length == 1) {
+                                that.multiplayer();
+                            }
+                            return;
+                        }
+                    }, 500);
+                }
+
+            },
+            buyClick(data) {
+                allPay(data, this.detail_sign, this, this.goods_list[this.cur_index]);
+            },
+            attr(data) {
+                this.appAttr = data;
+            },
+            specification(goods) {
+                this.goods_id = goods.id;
+                this.detail_sign = goods.sign;
+                if(this.detail_sign != 'pintuan') {
+                    this.item = goods;
+                    this.attrGroup = goods.attr_groups;
+                    this.show = Math.random();
+                }
+                switch (this.detail_sign) {
+                    case "advance":
+                        this.cartShow = 0;
+                        this.buyText = '支付定金';
+                        this.buyBool = true;
+                        break;
+                    case "booking":
+                        this.cartShow = 0;
+                        this.buyText = '立即预约';
+                        this.plugin = 'booking';
+                        break;
+                    case "pintuan":
+                        this.$request({
+                            url: this.$api.pt.detail,
+                            data: {
+                                id: this.goods_id,
+                                group_id: 0,
+                            }
+                        }).then((res) => {
+                            this.item = res.data.detail;
+                            this.selectGroupAttrId = this.item.pintuan_groups[0].id;
+                            this.attrGroup = this.item.attr_groups;
+                            this.show = Math.random();
+                            this.show_pt = true;
+                            this.request_pt(this.selectGroupAttrId);
+                        });
+                        break;
+                    case "integral_mall":
+                        this.cartShow = 0;
+                        this.buyText = '立即兑换';
+                        break;
+                    case "step":
+                        this.cartShow = 0;
+                        this.buyText = '立即兑换';
+                        break;
+                    case "gift":
+                        this.cartShow = 0;
+                        this.buyBool = true;
+                        this.buyText = '加入礼包';
+                        break;
+                    default:
+                        break;
+                }
+            },
+            async request_pt(group_id) {
+                try {
+                    this.loading = false;
+                    uni.showLoading();
+                    const response = await this.$request({
+                        url: this.$api.pt.detail,
+                        data: {
+                            id: this.goods_id,
+                            group_id: group_id,
+                        }
+                    });
+                    if (response.code === 0) {
+                        uni.hideLoading();
+                        this.item = response.data.detail;
+                        this.attrGroup = this.item.attr_groups;
+                        this.loading = true;
+                    }
+                } catch(e) {
+                    throw new Error(e);
+                }
+            },
+            buy() {
+                let goods = this.item;
+                let number = this.appAttr.number;
+                let select_attr = this.selectAttr;
+                let goods_attr_id = select_attr.id;
+                let attr = [];
+                for (let i in select_attr.attr_list) {
+                    attr.push({
+                        attr_id: select_attr.attr_list[i].attr_id,
+                        attr_group_id: select_attr.attr_list[i].attr_group_id,
+                    });
+                }
+                let mch_list = [{
+                    mch_id: goods.mch_id ? goods.mch_id : 0,
+                    goods_list: [{
+                        id: goods.id,
+                        attr: attr,
+                        num: number,
+                        cat_id: 0,
+                        goods_attr_id: goods_attr_id
+                    }]
+                }];
+                uni.navigateTo({
+                    url: '/pages/order-submit/order-submit?mch_list=' + JSON.stringify(mch_list),
+                });
+            },
+            individual() {
+                let that = this;
+                if (that.pt) {
+                    that.pt = false;
+                    that.selectAttr = {};
+                    that.request_pt(0);
+                }
+                const temp = setInterval(() => {
+                    if (that.loading) {
+                        clearInterval(temp);
+                        if (Object.keys(that.selectAttr).length === 0) {
+                            uni.showToast({
+                                title: '请选择规格',
+                                icon: "none"
+                            })
+                        } else {
+                            that.buy();
+                        }
+                        return;
+                    }
+                }, 500);
+            },
+            jump(data) {
+                // #ifndef MP-BAIDU
+                if (data.video_url && this.getVideo == 1) {
+                    // #ifdef MP
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=${data.sign}`
+                    });
+                    // #endif
+                    // #ifdef H5
+                    uni.navigateTo({
+                        url: data.page_url
+                    });
+                    // #endif
+                } else {
+                    uni.navigateTo({
+                        url: data.page_url
+                    });
+                }
+                // #endif
+
+                // #ifdef MP-BAIDU
+                uni.navigateTo({
+                    url: data.page_url
+                });
+                // #endif
+            },
+            onAttr(data) {
+                if (data !== null) {
+                    this.selectAttr = data;
+                } else {
+                    this.selectAttr = {};
+                }
+            },
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .attr {
+        position: fixed;
+        bottom: 0;
+        left: 0;
+        z-index: 1503;
+    }
+    .pintuan {
+        width: #{750upx};
+        height: #{110upx};
+        .single {
+            text-align: center;
+            line-height: #{110upx};
+        }
+        .tuan {
+            color: #ffffff;
+            text-align: center;
+            line-height: #{110upx};
+        }
+    }
+    .app-goods-list {
+        font-size: $uni-font-size-general-one;
+        color: $uni-important-color-black;
+
+        .sales {
+            color: $uni-general-color-two;
+        }
+
+        .goods-item-1 {
+            padding: #{16rpx} #{24rpx};
+            background-color: #ffffff;
+            border-top: #{1rpx} solid #e2e2e2;
+
+            .cover-pic {
+                border-radius: #{16rpx};
+                margin-right: #{20rpx};
+                position: relative;
+                .out-dialog {
+                    width: #{200rpx};
+                    height: #{200rpx};
+                    position: absolute;
+                    top: 0;
+                    left: 0;
+                    z-index: 10;
+                    background-color: rgba(0,0,0,.5);
+                    image {
+                        width: #{200rpx};
+                        height: #{200rpx};
+                    }
+                }
+            }
+
+            .content {
+                padding: #{8rpx} 0;
+
+                .sales {
+                    font-size: #{20rpx};
+                }
+            }
+
+            .cart {
+                width: #{50rpx};
+                height: #{50rpx};
+                display: block;
+            }
+            .sales-box {
+                position: relative;
+            }
+            .cart-box {
+                position: absolute;
+                width:#{56rpx};
+                height:#{56rpx};
+                border-radius: 50%;
+                border: 1px solid;
+                right: #{10rpx};
+                bottom:#{5rpx};
+            }
+        }
+
+        &.list-style-2 {
+            padding: 0 #{24rpx};
+        }
+
+        .goods-item-2 {
+            width: #{344rpx};
+            margin-top: #{14rpx};
+            background-color: #ffffff;
+            border-radius: #{16rpx};
+            overflow: hidden;
+            position: relative;
+            .out-dialog {
+                border-top-left-radius: #{16rpx};
+                border-top-right-radius: #{16rpx};
+                width: #{344rpx};
+                height: #{344rpx};
+                position: absolute;
+                top: 0;
+                left: 0;
+                z-index: 10;
+                background-color: rgba(0,0,0,.5);
+                image {
+                    width: #{344rpx};
+                    height: #{344rpx};
+                }
+            }
+            &.mr-14 {
+                margin-right: #{14rpx};
+            }
+
+            .goods-name {
+                padding-top: #{20rpx};
+                line-height: 1.4;
+                margin-bottom: #{8rpx};
+            }
+
+            .content {
+                margin-bottom: #{14rpx};
+            }
+
+            .padding {
+                padding-left: #{24rpx};
+                padding-right: #{24rpx};
+            }
+
+            .sales {
+                font-size: #{20rpx};
+            }
+            .sales-box {
+                margin-left: #{12rpx};
+            }
+        }
+
+        &.list-style-3 {
+            padding: 0 #{10rpx};
+        }
+
+        .goods-item-3 {
+            width: #{238rpx};
+            margin-top: #{8rpx};
+            background-color: #ffffff;
+            border-radius: #{16rpx};
+            overflow: hidden;
+            position: relative;
+            .out-dialog {
+                border-top-left-radius: #{16rpx};
+                border-top-right-radius: #{16rpx};
+                width: #{238rpx};
+                height: #{238rpx};
+                position: absolute;
+                top: 0;
+                left: 0;
+                z-index: 10;
+                background-color: rgba(0,0,0,.5);
+                image {
+                    width: #{238rpx};
+                    height: #{238rpx};
+                }
+            }
+            &.mr-8 {
+                margin-right: #{8rpx};
+            }
+
+            .goods-name {
+                line-height: 1.4;
+            }
+
+            .padding {
+                padding-left: #{20rpx};
+                padding-right: #{20rpx};
+            }
+
+            .sales {
+                font-size: #{20rpx};
+                padding-top: #{6rpx};
+            }
+
+            & > view:last-child {
+                padding-bottom: #{20rpx};
+            }
+            .sales-box {
+                padding: 0 #{20rpx};
+            }
+
+        }
+        .goods-cart {
+            width: #{36rpx};
+            height: #{36rpx};
+        }
+
+        .goods-price {
+            font-size: #{32rpx};
+        }
+    }
+    .dir-tag-def {
+        padding: 0 #{10rpx};
+        margin-right: #{8rpx};
+        font-size: $uni-font-size-weak-two;
+        border-radius: #{28rpx};
+    }
+    .des-price {
+        display: inline-block;
+        padding: #{4rpx 4rpx};
+        border: #{1rpx} solid;
+        border-radius: #{8rpx};
+        font-size: #{20rpx};
+        line-height: 1;
+        transform: rotateZ(360deg);
+    }
+    .pt-btn {
+        position: relative;
+        z-index: 1600;
+    }
+</style>

+ 156 - 0
components/page-component/app-goods-poster/app-goods-poster-four.vue

xqd
@@ -0,0 +1,156 @@
+<template>
+    <view class="app-goods-poster-four">
+        <view class="box dir-top" :style="{'background': `${background}`,'transform' : `scale(${multiple},${multiple})`}">
+            <view class="user cross-center">
+                <image :src="info.avatar"></image>
+                <view class="cross-center">{{info.nickname}}{{customize}}</view>
+            </view>
+            <view class="goods dir-top-nowrap">
+                <view class="goods-head dir-top-nowrap">
+                    <view class="goods-name t-omit-two">{{info.goods_name}}</view>
+                    <view class="goods-price">
+                        <app-poster-price :info="info"></app-poster-price>
+                    </view>
+                </view>
+                <view class="goods-image box-grow-0">
+                    <app-poster-image :form="form" :info="info"></app-poster-image>
+                </view>
+                <view class="box-grow-1 dir-top-nowrap four-end">
+                    <image :src="info.qrcode_url"></image>
+                    <view>{{(info.extra_mark && info.extra_mark[3]) ? info.extra_mark[3] : `长按识别小程序码进入`}}</view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import appPosterImage from "./app-poster-image";
+    import appPosterPrice from "./app-poster-price"
+
+    export default {
+        name: "app-goods-poster-four",
+        components: {
+            appPosterImage,
+            appPosterPrice,
+        },
+        props: {
+            multiple: {
+                type: Number,
+                required: true,
+            },
+            info: {
+                type: Object,
+                required: true,
+            },
+            form: {
+                type: Object,
+                required: true,
+            },
+        },
+        computed: {
+            customize() {
+                if (this.info.customize_text) {
+                    return this.info.customize_text;
+                } else {
+                    return '向您推荐一个好物';
+                }
+            },
+            background() {
+                if (parseInt(this.form.type) === 2) {
+                    let top = this.form.color;
+                    let bottom = this.$utils.colorRgba(this.form.color, 0.5);
+                    return 'linear-gradient(' + top + ',' + bottom + ')';
+                } else {
+                    return this.form.color;
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-goods-poster-four {
+        height: 100%;
+        width: 100%;
+        padding: #{24rpx} 0;
+
+        .box {
+            transform: scale(0.46, 0.46);
+            transform-origin: center top;
+            /*zoom: 0.46;*/
+            width: #{750rpx};
+            height: #{1334rpx};
+            margin: 0 auto;
+
+            .user {
+                padding: #{24rpx} #{35rpx};
+
+                image {
+                    height: #{100rpx};
+                    width: #{100rpx};
+                    border: #{5rpx} solid #FFFFFF;
+                    border-radius: 50%;
+                    margin-right: #{16rpx};
+                }
+
+                view {
+                    height: #{54rpx};
+                    background: #f1f1f1;
+                    padding: 0 #{24rpx};
+                    font-size: #{24rpx};
+                    border-radius: #{30rpx};
+                }
+            }
+
+            .goods {
+                height: #{1334rpx - 160rpx - 24rpx};
+                width: #{750rpx -  24rpx - 24rpx};
+                background: #ffffff;
+                margin: 0 auto;
+                border-radius: #{16rpx};
+
+                .goods-head {
+                    padding: #{28rpx};
+                    height: #{248rpx};
+                    width: 100%;
+
+                    .goods-name {
+                        font-size: #{34rpx};
+                        color: #353535;
+                    }
+
+                    .goods-price {
+                        margin-top: auto;
+                        margin-bottom: #{30rpx};
+                    }
+                }
+
+                .goods-image {
+                    height: #{650rpx};
+                    width: #{650rpx};
+                    margin: 0 auto;
+                }
+
+                .four-end {
+                    text-align: center;
+                    padding-top: #{32rpx};
+
+                    image {
+                        height: #{150rpx};
+                        width: #{150rpx};
+                        display: block;
+                        margin: 0 auto;
+                    }
+
+                    view {
+                        font-size: #{24rpx};
+                        color: #999999;
+                        padding-top: #{22rpx};
+                        padding-right: #{26rpx};
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 158 - 0
components/page-component/app-goods-poster/app-goods-poster-one.vue

xqd
@@ -0,0 +1,158 @@
+<template>
+    <view class="app-goods-poster-one main-center">
+        <view class="box" :style="{'background': `${background}`,'transform' : `scale(${multiple},${multiple})`}">
+            <view class="head cross-center">
+                <image :src="info.avatar"></image>
+                <view class="nickname main-center cross-center">
+                    {{info.nickname}}{{customize}}
+                </view>
+            </view>
+            <view class="goods-image">
+                <app-poster-image :form="form" :info="info"></app-poster-image>
+            </view>
+            <view class="end dir-left-nowrap">
+                <view class="dir-top-nowrap goods-info">
+                    <view class="goods-name t-omit-two">{{info.goods_name}}</view>
+                    <view class="goods-price">
+                        <app-poster-price :info="info"></app-poster-price>
+                    </view>
+                    <view class="goods-remark">{{(info.extra_mark && info.extra_mark[0]) ? info.extra_mark[0] : `长按识别小程序码进入`}}</view>
+                </view>
+                <view class="poster cross-center">
+                    <image :src="info.qrcode_url"></image>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import appPosterImage from "./app-poster-image.vue";
+    import appPosterPrice from "./app-poster-price.vue";
+
+    export default {
+        name: "app-goods-poster-one",
+        components: {
+            appPosterImage,
+            appPosterPrice
+        },
+        props: {
+            multiple: {
+                type: Number,
+                required: true,
+            },
+            info: {
+                type: Object,
+                required: true,
+            },
+            form: {
+                type: Object,
+                required: true,
+            },
+        },
+        computed: {
+            customize() {
+                if (this.info.customize_text) {
+                    return this.info.customize_text;
+                } else {
+                    return '向您推荐一个好物';
+                }
+            },
+            background() {
+                if (parseInt(this.form.type) === 2) {
+                    return `linear-gradient(${this.form.color},${this.$utils.colorRgba(this.form.color, 0.5)})`;
+                } else {
+                    return this.form.color;
+                }
+            }
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-goods-poster-one {
+        height: 100%;
+        width: 100%;
+        padding: #{24rpx} 0;
+
+        .box {
+            transform: scale(0.46, 0.46);
+            transform-origin: center top;
+            /*zoom: 0.46;*/
+            width: #{750rpx};
+            height: #{1334rpx};
+            padding: 0 #{24rpx};
+            margin: auto;
+
+            .head {
+                padding-top: #{96rpx};
+                margin-bottom: #{39rpx};
+                width: 100%;
+
+                image {
+                    height: #{90rpx};
+                    width: #{90rpx};
+                    display: block;
+                    border-radius: 50%;
+                }
+
+                .nickname {
+                    line-height: #{54rpx};
+                    padding: 0 #{24rpx};
+                    margin-left: #{24rpx};
+                    font-size: #{24rpx};
+                    background: #f1f1f1;
+                    color: #4b4b4b;
+                    border-radius: #{30rpx};
+                }
+            }
+
+            .goods-image {
+                height: #{702rpx};
+                width: 100%;
+                border-radius: #{16rpx} #{16rpx} 0 0;
+                overflow: hidden;
+            }
+
+            .end {
+                background: #ffffff;
+                height: #{311rpx};
+                border-radius: 0 0 #{16rpx} #{16rpx};
+                padding: #{28rpx};
+                margin-top: -1px;
+
+                .goods-info {
+                    max-width: #{450rpx};
+
+                    .goods-name {
+                        font-size: #{34rpx};
+                        color: #353535;
+                    }
+
+                    .goods-price {
+                        margin-top: auto;
+                        padding-bottom: #{65rpx};
+                    }
+
+                    .goods-remark {
+                        line-height: 1;
+                        color: #999999;
+                        padding-bottom: #{40rpx - 28rpx};
+                        font-size: #{28rpx};
+                    }
+                }
+
+                .poster {
+                    height: 100%;
+                    margin-left: auto;
+
+                    image {
+                        height: #{230rpx};
+                        width: #{230rpx};
+                        display: block;
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 166 - 0
components/page-component/app-goods-poster/app-goods-poster-three.vue

xqd
@@ -0,0 +1,166 @@
+<template>
+    <view class="app-goods-poster-three">
+        <view class="box dir-top-nowrap" :style="{'background': `${background}`,'transform' : `scale(${multiple},${multiple})`}">
+            <view class="user dir-left-nowrap box-grow-0">
+                <image :src="info.avatar"></image>
+                <view class="poster-three-love" :class="{'white': colorWhite.indexOf(background) !== -1}">
+                    我看上了这款商品<br>
+                    {{(info.extra_mark && info.extra_mark[4]) ? info.extra_mark[4] : `帮我看看咋样啊~`}}<br>
+                    比心~
+                </view>
+            </view>
+            <view class="goods-image box-grow-0">
+                <app-poster-image :form="form" :info="info"></app-poster-image>
+            </view>
+            <view class="goods cross-center dir-top-nowrap main-center box-grow-1">
+                <view class="goods-name t-omit" :class="{'white': colorWhite.indexOf(background) !== -1}">
+                    {{info.goods_name}}
+                </view>
+                <view class="goods-price">
+                    <app-poster-price :info="info" :white-color="colorWhite.indexOf(background) !== -1"
+                                      :text-color="colorBlock.indexOf(background) !== -1 ? `#353535`: ``"></app-poster-price>
+                </view>
+            </view>
+            <view class="dir-left-nowrap main-center box-grow-0 end-box cross-center">
+                <image :src="info.qrcode_url"></image>
+                <text :class="{'white': colorWhite.indexOf(background) !== -1}">{{(info.extra_mark && info.extra_mark[2]) ? info.extra_mark[2] : `长按识别小程序码 即可查看~`}}</text>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import appPosterImage from "./app-poster-image";
+    import appPosterPrice from "./app-poster-price"
+
+    export default {
+        name: "app-goods-poster-three",
+        components: {
+            appPosterImage,
+            appPosterPrice,
+        },
+        props: {
+            multiple: {
+                type: Number,
+                required: true,
+            },
+            info: {
+                type: Object,
+                required: true,
+            },
+            form: {
+                type: Object,
+                required: true,
+            },
+        },
+        data() {
+            return {
+                colorWhite: ['linear-gradient(#000000,rgba(0,0,0,0.5))', '#000000'],
+                colorBlock: ['linear-gradient(#fc4a3b,rgba(252,74,59,0.5))', '#fc4a3b'],
+            }
+        },
+        computed: {
+            background() {
+                if (parseInt(this.form.type) === 2) {
+                    let top = this.form.color;
+                    let bottom = this.$utils.colorRgba(this.form.color, 0.5);
+                    return 'linear-gradient(' + top + ',' + bottom + ')';
+                } else {
+                    return this.form.color;
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-goods-poster-three {
+        height: 100%;
+        width: 100%;
+        padding: #{24rpx} 0;
+
+        .white {
+            color: #d9d9d9 !important;
+        }
+
+        .box {
+            transform: scale(0.46, 0.46);
+            transform-origin: center top;
+            /*zoom: 0.46;*/
+            width: #{750rpx};
+            height: #{1334rpx};
+            margin: 0 auto;
+
+            .user {
+                padding: #{40rpx} #{35rpx} #{38rpx} #{35rpx};
+
+                image {
+                    display: block;
+                    width: #{97rpx};
+                    height: #{97rpx};
+                    margin-right: #{30rpx};
+                    border-radius: 50%;
+                }
+
+                .poster-three-love {
+                    font-size: #{26rpx};
+                    color: #353535;
+                }
+
+                .poster-three-love:after {
+                    content: "";
+                    background-repeat: no-repeat;
+                    background-size: 100% 100%;
+                    background-image: url("../../../static/image/poster/three-love.png");
+                    height: #{24rpx};
+                    width: #{24rpx};
+                    display: inline-block;
+                    margin-left: #{10rpx};
+                }
+            }
+
+            .goods-image {
+                height: #{680rpx};
+                width: #{680rpx};
+                margin: 0 auto;
+            }
+
+            .goods {
+                width: 100%;
+
+                .goods-name {
+                    font-size: #{34rpx};
+                    color: #353535;
+                    max-width: #{600rpx};
+                    text-align: center;
+                    padding-top: #{28rpx};
+                }
+
+                .goods-price {
+                    margin-top: auto;
+                    margin-bottom: #{55rpx};
+                }
+            }
+
+            .end-box {
+                width: #{702rpx};
+                height: #{230rpx + 24rpx + 24rpx};
+                border-top: 1px solid #c9c9c9;
+                margin: 0 auto;
+
+                image {
+                    height: #{230rpx};
+                    width: #{230rpx};
+                    del-border-radius: 50%;
+                    display: block;
+                }
+
+                text {
+                    color: #353535;
+                    padding-left: #{30rpx};
+                    font-size: #{26rpx};
+                }
+            }
+        }
+    }
+</style>

+ 176 - 0
components/page-component/app-goods-poster/app-goods-poster-two.vue

xqd
@@ -0,0 +1,176 @@
+<template>
+    <view class="app-goods-poster-two main-center">
+        <view class="box" :style="{'background': `${background}`,'transform' : `scale(${multiple},${multiple})`}">
+            <view class="goods-image">
+                <app-poster-image :form="form" :info="info"></app-poster-image>
+            </view>
+            <view class="dir-top-nowrap end-bg">
+                <view class="goods-name t-omit-two">{{info.goods_name}}</view>
+                <view :class="info.sign === 'flash_sale' ? 'flash-sale' : 'goods-price'">
+                    <app-poster-price :info="info"></app-poster-price>
+                </view>
+                <view class="dir-left-nowrap end-info cross-center">
+                    <view class="dir-top-nowrap">
+                        <view class="dir-left-nowrap cross-center user">
+                            <image :src="info.avatar" class="box-grow-0"></image>
+                            <view class="t-omit dir-top-nowrap">
+                                <view>{{info.nickname}}</view>
+                                <view v-if="(info.extra_mark && info.extra_mark[5])">{{info.extra_mark[5]}}</view>
+                            </view>
+                        </view>
+                        <view class="goods-remark dir-left-nowrap">
+                            <view class="dir-left-nowrap cross-center">
+                                <view>{{ (info.extra_mark && info.extra_mark[1]) ? info.extra_mark[1] : `长按识别小程序码进入`}}</view>
+                                <icon class="icon-three-arrow" type></icon>
+                            </view>
+                        </view>
+                    </view>
+                    <view class="poster-qrcode">
+                        <image :src="info.qrcode_url"></image>
+                    </view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import appPosterImage from "./app-poster-image.vue";
+    import appPosterPrice from "./app-poster-price.vue";
+
+    export default {
+        name: "app-goods-poster-two",
+        components: {
+            appPosterImage,
+            appPosterPrice,
+        },
+        props: {
+            multiple: {
+                type: Number,
+                required: true,
+            },
+            info: {
+                type: Object,
+                required: true,
+            },
+            form: {
+                type: Object,
+                required: true,
+            },
+        },
+        computed: {
+            background() {
+                if (parseInt(this.form.type) === 2) {
+                    let top = this.form.color;
+                    let bottom = this.$utils.colorRgba(this.form.color, 0.5);
+                    return 'linear-gradient(' + top + ',' + bottom + ')';
+                } else {
+                    return this.form.color;
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-goods-poster-two {
+        height: 100%;
+        width: 100%;
+        padding: #{24rpx} 0;
+
+        .box {
+            transform-origin: center top;
+            /*zoom: 0.46;*/
+            width: #{750rpx};
+            height: #{1334rpx};
+            margin: 0 auto;
+
+            .goods-image {
+                height: #{750rpx};
+                width: 100%;
+                overflow: hidden;
+            }
+
+            .end-bg {
+                height: #{616rpx};
+                width: #{702rpx};
+                background-image: url("../../../static/image/poster/style-two-end-shadow.png");
+                background-size: 100% 100%;
+                background-repeat: no-repeat;
+                padding: 0 #{26rpx};
+
+                position: absolute;
+                left: #{24rpx};
+                bottom: #{24rpx};
+                z-index: 50;
+
+                .goods-name {
+                    font-size: #{34rpx};
+                    color: #353535;
+                    margin-top: #{36rpx};
+                }
+
+                .goods-price {
+                    margin-top: auto;
+                    margin-bottom: #{400rpx};
+                }
+                .flash-sale {
+                    margin-top: auto;
+                    margin-bottom: #{430rpx};
+                }
+                .end-info {
+                    width: 100%;
+                    justify-content: space-between;
+                    position: absolute;
+                    bottom: #{28rpx};
+                    padding-left: #{40rpx};
+                    padding-right: #{40rpx};
+
+                    .user {
+                        padding-bottom: #{24rpx};
+
+                        image {
+                            height: #{96rpx};
+                            width: #{96rpx};
+                            display: block;
+                            border-radius: 50%;
+                        }
+
+                        > view {
+                            max-width: #{250rpx};
+                            margin-left: #{26rpx};
+                        }
+                    }
+
+                    .poster-qrcode image {
+                        height: #{230rpx};
+                        width: #{230rpx};
+                        display: block;
+                    }
+
+                    .goods-remark {
+                        color: #353535;
+                        height: #{52rpx};
+                        font-size: #{24rpx};
+
+                        > view {
+                            height: 100%;
+                            background: #f1f1f1;
+                            padding: 0 #{20rpx};
+                            border-radius: #{20px};
+                        }
+                    }
+
+                    .icon-three-arrow {
+                        height: #{17rpx};
+                        width: #{15rpx};
+                        background-image: url("../../../static/image/poster/three-arrow.png");
+                        background-size: 100% 100%;
+                        background-repeat: no-repeat;
+                        margin-left: #{10rpx};
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 228 - 0
components/page-component/app-goods-poster/app-poster-image.vue

xqd
@@ -0,0 +1,228 @@
+<template>
+    <view class="app-poster-image">
+
+        <view class="goods-one dir-left-wrap" v-if="form.typesetting==1">
+            <block v-for="(item,key) in info.multi_map" :key="key">
+                <image mode="aspectFill" v-if="key <= 0" :src="item"></image>
+            </block>
+        </view>
+
+        <view class="goods-two dir-left-wrap" v-else-if="form.typesetting==2">
+            <block v-for="(item,key) in info.multi_map" :key="key">
+                <image mode="aspectFill" v-if="key <= 1" :src="item"></image>
+            </block>
+        </view>
+
+        <view class="goods-three dir-left-wrap" v-else-if="form.typesetting==3">
+            <block v-for="(item,key) in info.multi_map" :key="key">
+                <image mode="aspectFill" v-if="key <= 2" :src="item"></image>
+            </block>
+        </view>
+
+        <view class="goods-four dir-left-wrap" v-else-if="form.typesetting==4">
+            <block v-for="(item,key) in info.multi_map" :key="key">
+                <image mode="aspectFill" v-if="key <= 3" :src="item"></image>
+            </block>
+        </view>
+
+        <view class="goods-five dir-left-wrap" v-else-if="form.typesetting==5">
+            <block v-for="(item,key) in info.multi_map" :key="key">
+                <image mode="aspectFill" v-if="key <= 4" :src="item"></image>
+            </block>
+        </view>
+
+        <view v-if="info.extra_multiMap" v-for="(item,key) in info.extra_multiMap" :key="key">
+            <view v-if="item.file_type === 'image'" class="plugin-image-class" :style="[pluginIconStyle(item)]">
+                <image :src="item.image_url"></image>
+            </view>
+            <view v-else-if="item.file_type === 'text'" class="plugin-text-class" :style="[pluginTextStyle(item)]">
+                <text>{{item.text}}</text>
+            </view>
+        </view>
+
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: "app-poster-image",
+        props: {
+            info: {
+                type: Object,
+                required: true,
+            },
+            form: {
+                type: Object,
+                required: true,
+            },
+        },
+        computed: {
+            pluginTextStyle() {
+                return (data) => {
+                    let style = {};
+                    if (data.bottom !== undefined && this.form.style != 2) {
+                        style = {bottom: parseFloat(data.bottom) - 43 + 'rpx'}
+                    }
+                    return Object.assign({}, data, style);
+                }
+            },
+
+            pluginIconStyle() {
+                return (data) => {
+                    let style = {};
+                    if (data.bottom !== undefined && this.form.style != 2) {
+                        style = {bottom: parseFloat(data.bottom) - 40 + 'rpx'};
+                    }
+                    return Object.assign({}, data, style)
+                }
+            },
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+
+    .app-poster-image {
+        height: 100%;
+        width: 100%;
+        position: relative;
+        overflow: hidden;
+
+        .plugin-text-class {
+            position: absolute;
+            z-index: 11;
+        }
+
+        .plugin-image-class {
+            position: absolute;
+            z-index: 10;
+
+            image {
+                height: 100%;
+                width: 100%;
+            }
+        }
+
+        image {
+            display: block;
+        }
+
+        .goods-one {
+            height: 100%;
+            width: 100%;
+
+            image {
+                height: 100%;
+                width: 100%;
+
+            }
+        }
+
+        .goods-two {
+            height: 100%;
+            width: 100%;
+
+            image:nth-child(1) {
+                height: 50%;
+                width: 100%;
+            }
+
+            image:nth-child(2) {
+                height: 50%;
+                width: 100%;
+            }
+        }
+
+        .goods-three {
+            height: 100%;
+            width: 100%;
+
+            image:nth-child(1) {
+                height: 50%;
+                width: 100%;
+            }
+
+            image:nth-child(2) {
+                height: 50%;
+                width: 50%;
+            }
+
+            image:nth-child(3) {
+                height: 50%;
+                width: 50%;
+            }
+        }
+
+        .goods-four {
+            height: 100%;
+            width: 100%;
+
+            image:nth-child(1) {
+                height: 50%;
+                width: 50%;
+            }
+
+            image:nth-child(2) {
+                height: 50%;
+                width: 50%;
+            }
+
+            image:nth-child(3) {
+                height: 50%;
+                width: 50%;
+            }
+
+            image:nth-child(4) {
+                height: 50%;
+                width: 50%;
+            }
+        }
+
+        .goods-five {
+            height: 100%;
+            width: 100%;
+            position: relative;
+
+            image:nth-child(1) {
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 50%;
+                width: 50%;
+            }
+
+            image:nth-child(2) {
+                position: absolute;
+                top: 50%;
+                left: 0;
+                height: 50%;
+                width: 50%;
+            }
+
+            image:nth-child(3) {
+                position: absolute;
+                top: 0;
+                left: 50%;
+                height: 33.333%;
+                width: 50%;
+            }
+
+            image:nth-child(4) {
+                position: absolute;
+                top: 33.33%;
+                left: 50%;
+                height: 33.333%;
+                width: 50%;
+            }
+
+            image:nth-child(5) {
+                position: absolute;
+                top: 66.66%;
+                left: 50%;
+                height: 33.333%;
+                width: 50%;
+            }
+        }
+    }
+</style>

+ 119 - 0
components/page-component/app-goods-poster/app-poster-price.vue

xqd
@@ -0,0 +1,119 @@
+<template>
+    <view class="app-poster-price" :style="{color: textColor}">
+
+        <view v-if="defaultPrice.indexOf(info.sign) !== -1 || info.sign === 'wholesale'">
+            <view v-if="info.is_negotiable === 1">
+                <view class="price">价格面议</view>
+            </view>
+            <view v-else class="price">
+                <app-price :max="`${info.max_price}`" :min="`${info.min_price}`"></app-price>
+            </view>
+        </view>
+
+        <view v-else-if="info.sign === 'pintuan'" class="cross-bottom dir-left-nowrap">
+            <view class="plugin-label">{{info.people_num}}</view>
+            <app-price class="price" :max="`${info.max_price}`" :min="`${info.min_price}`"></app-price>
+        </view>
+
+        <view v-else-if="info.sign === 'integral_mall'" class="cross-bottom dir-left-nowrap">
+            <view class="plugin-label">{{info.integral_num}}积分+</view>
+            <app-price class="price" :max="`${info.max_price}`" :min="`${info.min_price}`"></app-price>
+        </view>
+
+        <view v-else-if="info.sign === 'lottery'" class="cross-bottom dir-left-nowrap lottery">
+            <img :src="info.iconFreeUrl" alt="">
+            <view :class="{'white':whiteColor}">¥{{info.price}}</view>
+        </view>
+
+        <view v-else-if="info.sign === 'step'" class="cross-bottom dir-left-nowrap">
+            <view class="plugin-label">{{info.currency}}{{info.unit}}+</view>
+            <app-price class="price" :price="`${info.price}`"></app-price>
+        </view>
+
+            <view v-else-if="info.sign === 'bargain'" class="cross-bottom dir-left-nowrap">
+            <view class="plugin-label" style="line-height: 1">最低¥</view>
+            <view class="price">{{info.bargain_min_price}}</view>
+        </view>
+
+        <view v-else-if="info.sign === 'flash_sale'" class="cross-bottom dir-left-nowrap flash_sale">
+            <view class="plugin-label">抢购价¥</view>
+            <view class="price">{{info.min_price}}</view>
+        </view>
+
+        <view v-else class="cross-bottom dir-left-nowrap">
+            <app-price :max="`${info.max_price}`" :min="`${info.min_price}`"></app-price>
+        </view>
+
+    </view>
+</template>
+
+<script>
+    import appPrice from '../goods/app-price.vue';
+
+    export default {
+        components: {
+            appPrice
+        },
+        props: {
+            whiteColor: {
+                type: Boolean,
+                required: false,
+            },
+            info: {
+                type: Object,
+                required: true,
+            },
+            textColor: {
+                type: String,
+                default: '#FF4544',
+                required: false,
+            }
+        },
+        data() {
+            return {
+                defaultPrice: ['booking', '', 'miaosha', 'mch', 'pick']
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-poster-price {
+        color: #FF4544;
+
+        .white {
+            color: #d9d9d9 !important;
+        }
+
+        .price {
+            font-size: #{56rpx};
+            line-height: 1;
+        }
+
+        .plugin-label {
+            font-size: #{30rpx};
+            padding-right: #{5rpx};
+        }
+
+        .flash_sale .price {
+            font-size: 40rpx;
+            font-weight: bold;
+        }
+        .lottery {
+            margin-bottom: #{-10rpx};
+
+            img {
+                width: #{120rpx};
+                height: #{60rpx};
+                display: block;
+            }
+
+            view {
+                margin-left: #{10rpx};
+                color: #353535;
+                text-decoration: line-through;
+                text-align: center;
+            }
+        }
+    }
+</style>

+ 79 - 0
components/page-component/app-goods-recommend/app-goods-recommend.vue

xqd
@@ -0,0 +1,79 @@
+<template>
+    <view class="app-goods-recommend" v-if="goodsList && goodsList.length > 0">
+        <view class="recommend-title dir-left-nowrap main-center">
+            <view class="dir-left-nowrap cross-center">
+                <view class="border"></view>
+                <image src="../../../static/image/icon/icon-favorite.png"></image>
+                <view class="text">您或许喜欢</view>
+                <view class="border"></view>
+            </view>
+        </view>
+        <view class="recommend-list">
+            <u-ordinary-list :is-under-line-price="isListUnderlinePrice == 1 ? true : false" :showBuyBtn="sureCart ? 1 : 0" :isShowAttr="sureCart" :list="goodsList" :theme="theme" :list-style="2"></u-ordinary-list>
+        </view>
+    </view>
+</template>
+
+<script>
+    import uOrdinaryList from '../../page-component/u-goods-list/u-ordinary-list.vue';
+    import {mapState} from "vuex";
+
+    export default {
+        name: "app-goods-recommend",
+        components: {
+            uOrdinaryList
+        },
+        props: {
+            goodsList:  {
+                type: Array,
+                default() {
+                    return [];
+                }
+            },
+            theme: Object,
+            sureCart: Boolean,
+            activity: Object,
+            is_show_member: {
+                type: Boolean,
+                default() {
+                    return true;
+                }
+            },
+            sign: String,
+            detail: Object
+        },
+        computed: {
+            ...mapState({
+                isListUnderlinePrice: state => state.mallConfig.mall.setting.is_list_underline_price
+            }),
+        },
+
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-goods-recommend {
+        .recommend-title {
+            margin: #{40rpx} 0 #{32rpx} 0;
+            font-size: $uni-font-size-weak-one;
+            color: $uni-general-color-two;
+            .text {
+                color: #999;
+            }
+            .border {
+                border-top: #{1rpx} solid #bbbbbb;
+                height: 0;
+                width: #{40rpx};
+                margin: 0 #{24rpx};
+            }
+
+            
+            image {
+                width: #{24rpx};
+                height: #{24rpx};
+                display: block;
+                margin-right: #{12rpx};
+            }
+        }
+    }
+</style>

+ 145 - 0
components/page-component/app-head-navigation/app-head-navigation.vue

xqd
@@ -0,0 +1,145 @@
+<template>
+	<view class="bd-nav">
+        <view class="bd-mask" @touchmove.stop.prevent v-if="isSwitch"></view>
+        <view class="bd-head dir-left-nowrap">
+            <scroll-view scroll-x scroll-with-animation :scroll-left="scrollLeft" :scroll-into-view="`scroll-${activeIndex}`" class="app-head-navigation dir-left-nowrap cross-center">
+                <text class="head-text" :id="`scroll-${index}`" :class="item.active ? 'app-active':''" :style="{'background-color': item.active ? theme.background : '#ffffff'}"
+                      v-for="(item, index) in list"
+                      :key="index"
+                      @click="active(item)">
+                    {{item.name}}
+                </text>
+            </scroll-view>
+            <view class="bd-btn" @click="isSwitch = true">
+                <image class="bd-bt-img" src="/static/image/icon/icon-down.png"></image>
+            </view>
+        </view>
+        <view class="bd-content" v-if="isSwitch" @touchmove.stop.prevent>
+            <view class="bd-head dir-left-nowrap">
+                <view class="app-head-navigation">
+                    切换分类
+                </view>
+                <view class="bd-btn" @click="isSwitch = false">
+                    <image class="bd-bt-img" src="/static/image/icon/icon-up.png"></image>
+                </view>
+            </view>
+            <view class="bd-back dir-left-wrap" >
+                <view class="bd-item"  @click="active(item)" :style="{'color': item.active ? theme.color : '#353535', 'border-color':  item.active ? theme.border : '#e5e5e5'}" :key="index" v-for="(item, index) in list">{{item.name}}</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: 'app-head-navigation',
+	    props: {
+            list: {
+                type: Array,
+	            default: function() {
+	                return [];
+	            }
+            },
+			theme: Object,
+	    },
+	    methods: {
+            active(item) {
+                this.isSwitch = false;
+                console.log(item);
+                this.$emit('click', item);
+            }
+	    },
+        data() {
+            return {
+                activeIndex: 0,
+                isSwitch: false,
+                scrollLeft: 0
+            }
+        },
+        watch: {
+            list: {
+                handler(newVal) {
+                    for (let i = 0; i < newVal.length; i++) {
+                        if (newVal[i].active && i > 1) {
+                            this.activeIndex = i - 2;
+                        } else if (newVal[i].active && i <= 1) {
+                            this.activeIndex = 0;
+                        }
+                    }
+                },
+                immediate: true,
+                deep: true
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .bd-head {
+        width: #{750rpx};
+        height: #{100rpx};
+        background-color: #ffffff;
+        border-top: #{1rpx} solid #e2e2e2;
+        border-bottom: #{1rpx} solid #e2e2e2;
+        .app-active {
+            color: #ffffff;
+        }
+    }
+    .bd-btn {
+        width:70upx;
+        height: #{100rpx};
+    }
+	.app-head-navigation {
+        width: 680upx;
+		height: #{100rpx};
+		line-height: #{100rpx};
+		padding: 0 0 0 24upx;
+        font-size: #{28rpx};
+		white-space:nowrap;
+        color: #666666;
+		.head-text {
+			display: inline-block;
+			height: #{56rpx};
+			padding: 0 #{20rpx};
+			line-height: #{56rpx};
+			border-radius: #{28rpx};
+			margin-right: #{32rpx};
+			white-space:nowrap;
+		}
+	}
+    .bd-bt-img {
+        width: 22upx;
+        height:12upx;
+        margin: 44upx 24upx;
+    }
+    .bd-back {
+        background: #f5f5f5;
+        padding: 48upx 0upx 24upx 20upx;
+    }
+    .bd-item {
+        padding: 15upx 30upx;
+        margin-bottom: 24upx;
+        margin-right:19upx;
+        background-color: #ffffff;
+        border-radius: 35upx;
+        border-width: 2upx;
+        border-style: solid;
+        font-size: 26upx;
+    }
+    .bd-nav {
+        position: relative;
+    }
+    .bd-content {
+        position: absolute;
+        top:0;
+        z-index: 100;
+    }
+    .bd-mask {
+        position: fixed;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        background: rgba(0, 0, 0, 0.6);
+    }
+</style>

+ 190 - 0
components/page-component/app-image-ad/app-image-ad.vue

xqd
@@ -0,0 +1,190 @@
+<template>
+	<view class="app-image-ad">
+		<view class="app-one-image" v-if="imageStyle === 0" :style="{height: `${height}rpx`}">
+			<app-jump-button form
+                 :open_type="item.link.open_type"
+                 :url="item.link.value"
+                 :params="item.link.params"
+                 v-for="(item, index) in list"
+                 :key="index"
+			>
+				<view class="bd-background" :style="{
+					left: item.left,
+					width: item.width,
+					top: item.top,
+					backgroundImage: 'url(' + item.pic_url + ')',
+					height: item.height,
+			 	}"
+			></view>
+			</app-jump-button>
+		</view>
+		<view class="app-three-hundred-and-sixty" v-if="imageStyle === 1 || imageStyle === 2 || imageStyle === 3">
+			<view class="app-item" :style="{
+				backgroundImage: 'url(' + item.pic_url + ')',
+			    height: item.height,
+		        left: item.left,
+	            width: item.width,
+	            top: item.top,
+			}"
+		      v-for="(item, index) in list" :key="index"
+			>
+				<app-jump-button form :open_type="item.link.open_type" :url="item.link.value"  :params="item.link.params"></app-jump-button>
+			</view>
+		</view>
+		<view class="app-two-hundred-and-forty"  v-if="imageStyle === 5 || imageStyle === 4">
+			<view class="app-item" :style="[{
+				backgroundImage: 'url(' + item.pic_url + ')',
+			    height: item.height,
+		        left: item.left,
+	            width: item.width,
+	            top: item.top,
+	            
+			}, ]"
+			      
+			      v-for="(item, index) in list" :key="index"
+			>
+				<app-jump-button form :open_type="item.link.open_type"  :url="item.link.value" :params="item.link.params"></app-jump-button>
+			</view>
+		</view>
+		<view class="app-one-hundred-eighty-six" v-if="imageStyle === 6">
+			<view class="app-item" :style="{
+				backgroundImage: 'url(' + item.pic_url + ')',
+			    height: item.height,
+		        left: item.left,
+	            width: item.width,
+	            zIndex: item.zIndex,
+	            top: item.top,
+			}"
+			      v-for="(item, index) in list" :key="index"
+			>
+				<app-jump-button form :open_type="item.link.open_type"  :url="item.link.value" :params="item.link.params"></app-jump-button>
+			</view>
+		</view>
+		<view class="app-three-hundred-seventy-two" v-if="imageStyle === 7">
+			<view class="app-item" :style="{
+				backgroundImage: 'url(' + item.pic_url + ')',
+			    height: item.height,
+		        left: item.left,
+	            width: item.width,
+	            top: item.top,
+			}"
+			      v-for="(item, index) in list" :key="index"
+			>
+				<app-jump-button form :open_type="item.link.open_type"  :url="item.link.value" :params="item.link.params"></app-jump-button>
+			</view>
+		</view>
+		<view class="app-customize" v-if="imageStyle === 8" :style="{height: `${height}rpx`}">
+			<view class="app-item" :style="{
+				backgroundImage: 'url(' + item.pic_url + ')',
+			    height: item.height,
+		        left: item.left,
+	            width: item.width,
+	            top: item.top,
+			}"
+			      v-for="(item, index) in list" :key="index"
+			>
+				<app-jump-button form :open_type="item.link.open_type"  :url="item.link.value" :params="item.link.params"></app-jump-button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+    export default {
+        name: 'app-image-ad',
+	    props: {
+            h: {
+                type: Number,
+	            default: function() {
+	                return 1;
+	            }
+            },
+            height: {
+	            default: function() {
+	                return 'auto';
+	            }
+            },
+            hotspot: {
+                type: Array,
+	            default: function() {
+	                return [];
+	            }
+            },
+            list: {
+                type: Array,
+	            default: function() {
+	                return [];
+	            }
+            },
+            space: {
+                type: Number,
+	            default: function() {
+	                return 0;
+	            }
+            },
+            imageStyle: {
+                type: Number,
+	            default: function() {
+	                return 2;
+	            }
+            },
+            w: {
+                type: Number,
+	            default: function() {
+	                return 25;
+	            }
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+	.bd-background {
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: 100% 100%;
+	}
+	.app-image-ad {
+		width: #{750rpx};;
+		background-color: #ffffff;
+		.app-item {
+			position: absolute;
+			background-repeat: no-repeat;
+			background-position: center;
+			background-size: 100% 100%;
+		}
+		.app-one-image {
+			width: #{750rpx};
+			position: relative;
+			.ren-qu {
+				width: 100%;
+				height: 100%;
+				background-color: red;
+			}
+		}
+		.app-three-hundred-and-sixty {
+			width: #{750rpx};;
+			height: #{360rpx};
+			position: relative;
+		}
+		.app-two-hundred-and-forty {
+			width: #{750rpx};;
+			height: #{240rpx};
+			position: relative;
+		}
+		.app-one-hundred-eighty-six {
+			width: #{750rpx};
+			position: relative;
+			height: #{187.5rpx};
+		}
+		.app-three-hundred-seventy-two {
+			width: #{750rpx};
+			height: #{372rpx};
+			position: relative;
+		}
+		.app-customize {
+			width: #{750rpx};;
+			position:relative;
+		}
+	}
+</style>

+ 130 - 0
components/page-component/app-index-cat/app-index-cat.vue

xqd
@@ -0,0 +1,130 @@
+<template>
+    <view class="u-index-cat">
+        <view v-for="item in newData" :key="item.relation_id">
+            <view class="cross-center cat-top"  @click="route(item.relation_id)">
+                <view class="box-grow-1 main-center">
+                    <image v-if="item.cat_pic_url" class="cat-pic-url" :src="item.cat_pic_url" ></image>
+                    <text class="u-cat-name">{{item.name}}</text>
+                </view>
+                <view class="cross-center u-more">
+                    <text class="u-more-text">更多</text>
+                    <image class="u-arrow-right" src="/static/image/icon/arrow-right.png"></image>
+                </view>
+            </view>
+            <u-ordinary-list @buyProduct="buyProduct" :is-under-line-price="isListUnderlinePrice == 1 ? true : false" :list="item.goods" :theme="theme" :list-style="item.list_style | listStyle"></u-ordinary-list>
+        </view>
+    </view>
+</template>
+
+<script>
+    import uOrdinaryList from '../../page-component/u-goods-list/u-ordinary-list.vue';
+    import {mapState} from "vuex";
+
+    export default {
+        name: "app-index-cat",
+        components: {
+            uOrdinaryList
+        },
+        props: {
+            theme: {
+                type: Object
+            },
+            page_id: {
+                type: Number
+            },
+            index: {
+                type: Number
+            },
+            is_required: {
+                type: Boolean
+            }
+        },
+        data() {
+            return {
+                newData: {}
+            }
+        },
+        methods: {
+            route(relation_id) {
+                uni.navigateTo({
+                    url: `/pages/goods/list?cat_id=${relation_id}`
+                });
+            },
+            loadData() {
+                this.$request({
+                    url: this.$api.index.extra,
+                    data: {
+                        type: 'mall',
+                        key: 'cat',
+                        page_id: this.page_id,
+                        index: this.index
+                    }
+                }).then(e => {
+                    this.newData = e.data;
+                    if (e.code === 0 && e.data) {
+                        let storage = this.$storage.getStorageSync('INDEX_MALL');
+                        console.log(storage);
+                        storage.home_pages[this.index].list = this.newData;
+                        this.$storage.setStorageSync('INDEX_MALL', storage);
+                    }
+                });
+            },
+            getStorage() {
+                let storage = this.$storage.getStorageSync('INDEX_MALL');
+                this.newData = storage.home_pages[this.index].list;
+            },
+            buyProduct(data) {
+                this.$emit('buyProduct', data);
+            }
+        },
+        mounted() {
+            this.is_required ? this.loadData() : this.getStorage();
+        },
+        computed: {
+            ...mapState({
+                isListUnderlinePrice: state => state.mallConfig.mall.setting.is_list_underline_price
+            })
+        },
+        filters: {
+            listStyle(list_style) {
+                return list_style === 1 ? -1 : list_style === 2 ? 2 : list_style === 3 ? 3 : null;
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .u-index-cat {
+        background-color: #ffffff;
+    }
+    .cat-top {
+        height: 80upx;
+        padding: 0 24upx;
+        position: relative;
+    }
+    .u-cat-name {
+        color: #353535;
+        font-size: 28upx;
+    }
+    .cat-pic-url {
+        width: 40upx;
+        height: 40upx;
+        margin-right: 24upx;
+    }
+    .u-more {
+        position: absolute;
+        right: 24upx;
+        top: 0;
+        z-index: 10;
+        height: 100%;
+    }
+    .u-more-text {
+        font-size: 26upx;
+        color: #999999;
+    }
+    .u-arrow-right {
+        width: 12upx;
+        height: 24upx;
+        margin-left: 12upx;
+    }
+</style>

+ 245 - 0
components/page-component/app-index-wholesale/app-index-wholesale.vue

xqd
@@ -0,0 +1,245 @@
+<template>
+    <view class="app-index-wholesale">
+        <view class="top dir-left-nowrap cross-center" @click="jump(`/plugins/wholesale/index/index`)">
+            <image class="box-grow-0" src="../../../static/image/icon/wholesale.png"></image>
+            <view class="box-grow-1">商品批发</view>
+            <view class="dir-left-nowrap cross-center">
+                <view class="box-grow-0 more">更多</view>
+                <image class="box-grow-0 icon" src="../../../static/image/icon/arrow-right.png"></image>
+            </view>
+        </view>
+        <view v-if="style == '1'" class="dir-left-nowrap list">
+            <block v-for="(item, key) in newData" :key="key">
+                <app-form-id @click="jump_router(item)">
+                    <view class="item box-grow-0 dir-top-nowrap">
+                        <view class="cover-pic box-grow-0">
+                            <view class="out-dialog" v-if="item.goods_stock == 0 && appSetting.is_show_stock == '1'">
+                                <image :src="appSetting.is_use_stock == '1' ? appImg.plugins_out : appSetting.sell_out_pic"></image>
+                            </view>
+                            <app-image :img-src="item.cover_pic" width="220rpx" height="220rpx"></app-image>
+                        </view>
+                        <view class="title t-omit-two box-grow-0">{{item.name}}</view>
+                        <view class="box-grow-1 dir-top-nowrap main-right">
+                            <view v-if="item.is_level == 1 && item.is_negotiable == 0">
+                                <app-member-price :theme="getTheme" :price="item.level_price"></app-member-price>
+                            </view>
+                            <app-sup-vip :is_vip_card_user="item.vip_card_appoint.is_vip_card_user" margin="4rpx 0 0" v-if="item.vip_card_appoint.discount > 0 && item.is_negotiable == 0" :discount="item.vip_card_appoint.discount"></app-sup-vip>
+                            <view v-if="item.is_negotiable == 1" class="price" :style="{'color': getTheme.color}">价格面议</view>
+                            <view v-if="item.is_negotiable == 0" :style="{'color': getTheme.color}"  class="price dir-left-nowrap">
+                                <text class="text"  style="font-size: 28rpx;" >¥{{item.price}}</text>
+                            </view>
+                            <view v-if="item.is_negotiable == 0 && isListUnderlinePrice == 1" class="old-price">
+                                ¥{{item.goodsWarehouse.original_price}}
+                            </view>
+                        </view>
+                        <view class="cross-center count">{{item.group_count}}</view>
+                    </view>
+                </app-form-id>
+            </block>
+        </view>
+        <view v-if="style === '2'">
+            <u-ordinary-list :showBuyBtn="false" :theme="getTheme" :list-style="2" :list="newData"></u-ordinary-list>
+        </view>
+    </view>
+</template>
+
+
+<script>
+    import {mapGetters, mapState} from 'vuex';
+    import uOrdinaryList from '@/components/page-component/u-goods-list/u-ordinary-list.vue';
+
+    export default {
+        name: "app-index-wholesale",
+        props: {
+            index: Number,
+            page_id: Number,
+            is_required: Boolean
+        },
+        data() {
+            return {
+                appAttr: {},
+                selectAttr: null,
+                wholesaleDiscount: 0,
+                totalNumber: 0,
+                totalPrice: '0.00',
+                newData: {},
+                style: '1',
+                goods_num: 20,
+                attrGoods: {
+                    goods: null,
+                    attrShow: 0
+                },
+                show: 0,
+                attrShow: false
+            };
+        },
+        computed: {
+            ...mapState({
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+                mall: state => state.mallConfig.mall,
+                isListUnderlinePrice: state => state.mallConfig.mall.setting.is_list_underline_price
+            }),
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+            ...mapGetters('mallConfig',{
+                vip: 'getVip'
+            }),
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo'
+            })
+        },
+        methods: {
+            jump(url) {
+                this.$jump({
+                    url: url,
+                    open_type: 'navigate'
+                })
+            },
+            jump_router(data) {
+                uni.navigateTo({
+                    url: `/plugins/wholesale/goods/goods?id=${data.id}`
+                })
+            },
+            loadData() {
+                let para = {
+                    type: this.page_id === 0 ? 'mall' : 'diy',
+                    key: 'wholesale',
+                    page_id: this.page_id,
+                    index: this.index
+                }
+                if(this.goods_num) {
+                    para.goods_num = this.goods_num
+                }
+                this.$request({
+                    url: this.$api.index.extra,
+                    data: para
+                }).then(info => {
+                    if (info.code === 0 && info.data) {
+                        this.newData = info.data.list;
+                        if (this.page_id === 0) {
+                            let storage = this.$storage.getStorageSync('INDEX_MALL');
+                            storage.home_pages[this.index].list = this.newData;
+                            this.$storage.setStorageSync('INDEX_MALL', storage);
+                        }
+                    }
+                })
+            },
+        },
+        components: {
+            uOrdinaryList
+        },
+        mounted() {
+            let storage = this.$storage.getStorageSync('INDEX_MALL');
+            this.style = storage.home_pages[this.index].style;
+            this.goods_num = storage.home_pages[this.index].goods_num;
+            if (this.is_required) {
+                this.loadData();
+            } else {
+                this.newData = storage.home_pages[this.index].list;
+            }
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .text1 {
+        color: #ff8831;
+        font-size: $uni-font-size-general-one;
+    }
+
+    .app-index-wholesale {
+
+        .top {
+            padding: #{0 24rpx};
+            color: #ff4544;
+            font-size: $uni-font-size-general-one;
+            height: #{72rpx};
+            background-color: white;
+            image {
+                width: #{46rpx};
+                height: #{46rpx};
+                display: block;
+                margin-right: #{8rpx};
+            }
+            
+            .more {
+                font-size: $uni-font-size-general-two;
+                margin-right: #{12rpx};
+                color: $uni-general-color-two;
+            }
+            
+            .icon {
+                width: #{12rpx};
+                height: #{22rpx};
+                display: block;
+            }
+        }
+
+        .list {
+            margin-top: #{8rpx};
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
+
+            .item {
+                background-color: #ffffff;
+                margin-right: #{8rpx};
+                padding: #{20rpx 20rpx 24rpx 20rpx};
+                height: 100%;
+                border-radius: #{8rpx};
+
+                .cover-pic {
+                    width: #{220rpx};
+                    height: #{220rpx};
+                    display: block;
+                    margin-bottom: #{20rpx};
+                    .out-dialog {
+                        width: #{220rpx};
+                        height: #{220rpx};
+                        position: absolute;
+                        top: #{20rpx};
+                        left: #{20rpx};
+                        z-index: 10;
+                        background-color: rgba(0,0,0,.5);
+                        image {
+                            width: #{220rpx};
+                            height: #{220rpx};
+                        }
+                    }
+                }
+
+                .title {
+                    font-size: 26upx;
+                    line-height: #{32upx};
+                    color: $uni-important-color-black;
+                    width: #{220rpx};
+                }
+
+                .price {
+                    .text {
+                        text-overflow: ellipsis;
+                        -webkit-box-orient: vertical;
+                        -webkit-line-clamp: 1;
+                        overflow: hidden;
+                        line-height: 1;
+                        vertical-align: sub;
+                        margin-top: #{10rpx};
+                    }
+                }
+                .old-price {
+                    font-size: #{21upx};
+                    line-height: 1;
+                    color: #999999;
+                    text-decoration:line-through;
+                    margin: #{5upx 0 20upx 0};
+                }
+
+                .count {
+                    font-size: $uni-font-size-weak-one;
+                    color: $uni-general-color-two;
+                }
+            }
+        }
+    }
+</style>

+ 40 - 0
components/page-component/app-iphonex-bottom/app-iphonex-bottom.vue

xqd
@@ -0,0 +1,40 @@
+<template>
+	<view class="app-iphone-bottom" :style="{height: iphone ? `50rpx` : '0rpx'}">
+	</view>
+</template>
+
+<script>
+    import {mapState} from 'vuex';
+    
+    export default {
+        name: 'app-iphone-bottom',
+	    props: {
+            backgroundColor: {
+                type: String,
+	            default: function() {
+	                return 'white';
+	            }
+            }
+	    },
+	    computed: {
+            ...mapState('gConfig',{
+                iphone: (data) => {
+                    return data.iphone;
+                },
+                iphoneHeight: (state) =>{
+                    return  state.iphoneHeight;
+                },
+            })
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+.app-iphone-bottom {
+	background-color: white;
+	width: 100%;
+	position:fixed;
+	left: 0;
+	bottom: 0;
+}
+</style>

+ 64 - 0
components/page-component/app-join-member/app-join-member.vue

xqd
@@ -0,0 +1,64 @@
+<template>
+	<view class="app-goods-join-member">
+		<app-jump-button url="/pages/member/index/index" open_type="navigate">
+			<view class="member dir-left cross-center">
+				<view class="box-grow-0">加入会员,享会员价</view>
+				<view class="box-grow-1 price" :style="{'color': getTheme.color}">
+					<app-price
+						type="text-price-all"
+					   	:max="memberMaxPrice"
+					   	:min="memberMinPrice"
+					   	:default-price="price"
+					></app-price>
+				</view>
+				<view class="dir-left cross-center box-grow-0">
+					<view>了解更多</view>
+					<image src="/static/image/icon/arrow-right.png" class="right"></image>
+				</view>
+			</view>
+		</app-jump-button>
+	</view>
+</template>
+
+<script>
+	import {mapGetters} from 'vuex';
+    import appPrice from "../../page-component/goods/app-price.vue";
+
+    export default {
+        name: "app-goods-join-member",
+        components: {
+            'app-price': appPrice,
+        },
+        props: {
+            memberMaxPrice: String,
+            memberMinPrice: String,
+            price: String,
+        },
+		computed: {
+			...mapGetters('mallConfig', {
+				getTheme: 'getTheme',
+			}),
+		},
+    }
+</script>
+
+<style scoped lang="scss">
+	.member {
+		background-color: #faf2d8;
+		width: 100%;
+		height: #{80rpx};
+		padding: 0 #{24rpx};
+		font-size: $uni-font-size-weak-one;
+		margin: #{24rpx} 0;
+		border-radius: #{16rpx};
+		.right {
+			width: #{12rpx};
+			height: #{22rpx};
+			margin-left: #{16rpx};
+		}
+
+		.price {
+			font-size: $uni-font-size-general-one;
+		}
+	}
+</style>

+ 580 - 0
components/page-component/app-live/app-live.vue

xqd
@@ -0,0 +1,580 @@
+<template>
+    <view v-if="value.live_list.length > 0" class="app-live" :style="{background: value.background}">
+        <template v-if="value.style_type == 1">
+            <view class="live-style-1 shadow"
+              v-for="(item, index) in value.live_list"
+              :key="index"
+              @click="liveClick(item)">
+            <view class="item">
+                <image class="anchor-img" mode="aspectFill" :src="item.anchor_img"></image>
+                <view class="gradient"></view>
+                <view class="label-box">
+                    <view class="label-item"
+                          :class="{'label-item-over': item.live_status == 103,
+                          'label-item-notice': item.live_status == 102}">
+                        <image v-if="item.live_status == 101" class="live-ing"
+                               src="/static/image/icon/liveing.png"></image>
+                        <view v-else class="round"></view>
+                        <span class="label-text">{{item.status_text}}</span>
+                    </view>
+                    <span v-if="item.live_status == 102" class="text-time">{{item.text_time}}</span>
+                </view>
+                <view class="user-info">
+                    <image class="avatar" mode="aspectFill" :src="item.anchor_img"></image>
+                    <span class="nickname ellipsis">{{item.anchor_name}}</span>
+                </view>
+                <image v-if="item.live_status === 103" class="play-icon"
+                       src="/static/image/video-play.png"></image>
+            </view>
+            <span class="title ellipsis">{{item.name}}</span>
+            <view v-if="value.is_show_goods == true && item.goods.length > 0" class="goods-info">
+                <image mode="aspectFill" class="goods-cover" :src="item.goods[0].cover_img"></image>
+                <view class="goods-item">
+                    <span class="goods-name ellipsis">{{item.goods[0].name}}</span>
+                    <span class="goods-price">¥{{item.goods[0].price}}</span>
+                </view>
+            </view>
+        </view>
+        </template>
+        <view v-if="value.style_type == 2" class="wrap-box">
+            <view class="wrap-item" v-for="(valueItem, valueIndex) in newValue" :key="valueIndex">
+                <view class="live-style-2 shadow"
+                      v-for="(item, index) in valueItem.live_list"
+                      :key="index"
+                      @click="liveClick(item)">
+                    <view class="item">
+                        <image mode="aspectFill" class="anchor-img" :src="item.anchor_img"></image>
+                        <view class="gradient"></view>
+                        <view class="label-box">
+                            <view class="label-item"
+                                  :class="{'label-item-over': item.live_status == 103,
+                          'label-item-notice': item.live_status == 102}">
+                                <image v-if="item.live_status == 101" class="live-ing"
+                                       src="/static/image/icon/liveing.png"></image>
+                                <view v-else class="round"></view>
+                                <span class="label-text">{{item.status_text}}</span>
+                            </view>
+                            <span v-if="item.live_status == 102" class="text-time">{{item.text_time}}</span>
+                        </view>
+                        <view class="user-info">
+                            <image class="avatar" mode="aspectFill" :src="item.anchor_img"></image>
+                            <span class="nickname ellipsis">{{item.anchor_name}}</span>
+                        </view>
+                        <image v-if="item.live_status === 103" class="play-icon"
+                               src="/static/image/video-play.png"></image>
+                    </view>
+                    <span class="title ellipsis">{{item.name}}</span>
+                    <view v-if="value.is_show_goods == true && item.goods.length > 0" class="goods-info">
+                        <image mode="aspectFill" class="goods-cover" :src="item.goods[0].cover_img"></image>
+                        <view class="goods-item">
+                            <span class="goods-name ellipsis">{{item.goods[0].name}}</span>
+                            <span class="goods-price">¥{{item.goods[0].price}}</span>
+                        </view>
+                    </view>
+                </view>
+            </view>
+        </view>
+        <template v-if="value.style_type == 3">
+             <view class="live-style-3 shadow"
+              v-for="(item, index) in value.live_list"
+              :key="index"
+              @click="liveClick(item)">
+            <view class="item">
+                <image mode="aspectFill" class="anchor-img" :src="item.anchor_img"></image>
+                <view class="gradient"></view>
+                <view class="label-box">
+                    <view class="label-item"
+                          :class="{'label-item-over': item.live_status == 103,
+                          'label-item-notice': item.live_status == 102}">
+                        <image v-if="item.live_status == 101" class="live-ing"
+                               src="/static/image/icon/liveing.png"></image>
+                        <view v-else class="round"></view>
+                        <span class="label-text">{{item.status_text}}</span>
+                    </view>
+                    <span v-if="item.live_status == 102" class="text-time">{{item.text_time}}</span>
+                </view>
+                <image v-if="item.live_status === 103" class="play-icon"
+                       src="/static/image/video-play.png"></image>
+            </view>
+            <view class="item-2">
+                <span class="title">{{item.name}}</span>
+                <view class="user-info">
+                    <image class="avatar" mode="aspectFill" :src="item.anchor_img"></image>
+                    <span class="nickname ellipsis">{{item.anchor_name}}</span>
+                </view>
+                <view v-if="value.is_show_goods == true && item.goods.length > 0" class="goods-info">
+                    <view class="goods-cover">
+                        <view class="gradient"></view>
+                        <image mode="aspectFill" :src="item.goods[0].cover_img"></image>
+                        <span class="goods-price">¥{{item.goods[0].price}}</span>
+                    </view>
+                    <view class="goods-cover goods-cover-2" v-if="item.goods.length >= 2">
+                        <view class="shade"></view>
+                        <image mode="aspectFill" :src="item.goods[1].cover_img"></image>
+                        <view class="goods-count">
+                            <span class="number">{{item.goods.length}}</span>
+                            <span>宝贝</span>
+                        </view>
+                    </view>
+                </view>
+            </view>
+        </view>
+        </template>
+    </view>
+</template>
+
+<script>
+    import {mapState} from "vuex";
+    export default {
+        name: 'app-live',
+        components: {},
+        props: {
+            value: {
+                type: Object,
+                default: function () {
+                    return {
+                        background: '#f7f7f7',
+                        live_list: [],
+                        is_show_goods: true,
+                        number: 5,
+                        style_type: 1
+                    }
+                }
+            },
+        },
+        data() {
+            return {}
+        },
+        computed: {
+            newValue() {
+                let self = this;
+                let newValue = [
+                    {
+                        background: self.value.background,
+                        is_show_goods: self.value.is_show_goods,
+                        number: self.value.number,
+                        style_type: self.value.style_type,
+                        live_list: [],
+                    }, {
+                        background: self.value.background,
+                        is_show_goods: self.value.is_show_goods,
+                        number: self.value.number,
+                        style_type: self.value.style_type,
+                        live_list: [],
+                    },
+                ];
+
+                let number1 = 0;
+                let number2 = 0;
+                self.value.live_list.forEach((item, index) => {
+                    let sign = item.goods.length > 0 ? true : false;
+                    if (number1 == 0 || number1 <= number2) {
+                        number1 = sign ? number1 + 2 : number1 + 1;
+                        newValue[0].live_list.push(item);
+                    } else {
+                        number2 = sign ? number2 + 2 : number2 + 1;
+                        newValue[1].live_list.push(item);
+                    }
+                });
+                return newValue;
+            },
+            ...mapState({
+                userInfo: state => state.user.info,
+            })
+        },
+        methods: {
+            liveClick(liveData) {
+                let userId = this.userInfo ? this.userInfo.options.user_id : 0;
+                let customParams = {user_id: userId}; // 开发者在直播间页面路径上携带自定义参数
+                uni.navigateTo({
+                    url: `plugin-private://wx2b03c6e691cd7370/pages/live-player-plugin?room_id=${liveData.roomid}&custom_params=${encodeURIComponent(JSON.stringify(customParams))}`
+                })
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-live {
+        padding: 20#{rpx};
+
+        .shadow {
+            box-shadow: 0 0 10#{rpx} 5#{rpx} rgba(0, 0, 0, 0.1);
+        }
+
+        .label-item {
+            border-top-left-radius:16#{rpx};
+            border-top-right-radius: 30#{rpx};
+            border-bottom-right-radius: 30#{rpx};
+            background: #ff4544;
+            padding: 12#{rpx} 20#{rpx};
+            display: flex;
+            align-items: center;
+
+        }
+        .label-item-over {
+            background: #777777;
+        }
+        .label-item-notice {
+            background: #22ac38;
+        }
+
+        .round {
+            width: 12#{rpx};
+            height: 12#{rpx};
+            background: #ffffff;
+            border-radius: 50%;
+        }
+
+        .live-ing {
+            width: 24#{rpx};
+            height: 24#{rpx};
+        }
+
+        .label-text {
+            margin-left: 12#{rpx};
+            color: #ffffff;
+            font-size: 26#{rpx};
+        }
+
+        .label-box {
+            position: absolute;
+            top: 0;
+            left: 0;
+            border-top-left-radius: 16#{rpx};
+            border-top-right-radius: 30#{rpx};
+            border-bottom-right-radius: 30#{rpx};
+            background: rgba(0, 0, 0, 0.5);
+            display: flex;
+            align-items: center;
+            z-index: 20;
+        }
+
+        .ellipsis {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+        }
+
+        .play-icon {
+            width: 100#{rpx};
+            height: 100#{rpx};
+            z-index: 20;
+        }
+
+        .user-info {
+            display: flex;
+            align-items: center;
+            z-index: 20;
+
+            .avatar {
+                width: 40#{rpx};
+                height: 40#{rpx};
+                border-radius: 50%;
+            }
+
+            .nickname {
+                color: #ffffff;
+                font-size: 24#{rpx};
+                margin-left: 12#{rpx};
+                width: 578#{rpx};
+            }
+        }
+
+        .text-time {
+            color: #ffffff;
+            font-size: 24#{rpx};
+            margin: 0 20#{rpx} 0 12#{rpx};
+        }
+
+        .shade {
+            width: 100%;
+            height: 100%;
+            background: rgba(0, 0, 0, 0.3);
+            position: absolute;
+            z-index: 10;
+        }
+
+        .gradient {
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 0;
+            background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.5));
+        }
+
+        /*样式一*/
+        .live-style-1 {
+            width: 100%;
+            border-radius: 16#{rpx};
+            background: #ffffff;
+            padding: 20#{rpx};
+            margin-bottom: 20#{rpx};
+            display: flex;
+            flex-direction: column;
+
+            .item {
+                width: 100%;
+                height: 360#{rpx};
+                border-radius: 16#{rpx};
+                position: relative;
+                overflow: hidden;
+
+                .anchor-img {
+                    position: absolute;
+                    left: 0;
+                    top: 0;
+                    width: 100%;
+                    height: 100%;
+                }
+
+                .user-info {
+                    position: absolute;
+                    bottom: 0;
+                    left: 0;
+                    margin-left: 20#{rpx};
+                    margin-bottom: 20#{rpx};
+                }
+
+                .play-icon {
+                    position: absolute;
+                    top: 130#{rpx};
+                    left: 285#{rpx};
+                }
+            }
+
+            .title {
+                font-size: 32#{rpx};
+                color: #353535;
+                width: 654#{rpx};
+                margin: 28#{rpx} 0 20#{rpx};
+            }
+            .goods-info {
+                width: 100%;
+                border-radius: 8#{rpx};
+                padding: 10#{rpx};
+                display: flex;
+                background: #f7f7f7;
+                .goods-cover {
+                    width: 80#{rpx};
+                    height: 80#{rpx};
+                    border-radius: 8#{rpx};
+                }
+
+                .goods-item {
+                    display: flex;
+                    flex-direction: column;
+                    margin-left: 16#{rpx};
+                    .goods-name {
+                        font-size: 26#{rpx};
+                        color: #666666;
+                        width: 540#{rpx};
+                    }
+                    .goods-price {
+                        font-size: 26#{rpx};
+                        color: #353535;
+                        margin-top: 16#{rpx};
+                    }
+                }
+            }
+        }
+        .live-style-1:last-child {
+            margin-bottom: 0;
+        }
+
+        /*样式二*/
+        .wrap-box {
+            width: 100%;
+            display: flex;
+            justify-content: space-between;
+
+            .wrap-item {
+                display: flex;
+                flex-direction: column;
+
+                .live-style-2 {
+                    background: #ffffff;
+                    border-radius: 16#{rpx};
+                    overflow: hidden;
+                    margin-bottom: 20#{rpx};
+                    width: 100%;
+                    display: flex;
+                    flex-direction: column;
+
+                    .item {
+                        width: 346#{rpx};
+                        height: 346#{rpx};
+                        position: relative;
+                        border-radius: 16#{rpx};
+
+                        .anchor-img {
+                            position: absolute;
+                            width: 100%;
+                            height: 100%;
+                            top: 0;
+                            left: 0;
+                        }
+
+                        .user-info {
+                            position: absolute;
+                            bottom: 20#{rpx};
+                            left: 20#{rpx};
+                            .nickname {
+                                width: 254#{rpx};
+                            }
+                        }
+
+                        .play-icon {
+                            position: absolute;
+                            top: 123#{rpx};
+                            left: 123#{rpx};
+                        }
+                    }
+
+                    .title {
+                        font-size: 28#{rpx};
+                        color: #353535;
+                        width: 290#{rpx};
+                        margin: 28#{rpx};
+                    }
+
+                    .goods-info {
+                        width: 100%;
+                        display: flex;
+                        margin: 0 28#{rpx} 28#{rpx};
+                        .goods-cover {
+                            width: 80#{rpx};
+                            height: 80#{rpx};
+                            border-radius: 8#{rpx};
+                        }
+
+                        .goods-item {
+                            display: flex;
+                            flex-direction: column;
+                            margin-left: 16#{rpx};
+                            .goods-name {
+                                font-size: 26#{rpx};
+                                color: #666666;
+                                width: 194#{rpx};
+                            }
+                            .goods-price {
+                                font-size: 26#{rpx};
+                                color: #353535;
+                                margin-top: 16#{rpx};
+                            }
+                        }
+                    }
+                }
+                .live-style-2:last-child {
+                    margin-bottom: 0;
+                }
+            }
+        }
+        /*样式三*/
+        .live-style-3 {
+            width: 100%;
+            background: #ffffff;
+            border-radius: 16#{rpx};
+            overflow: hidden;
+            display: flex;
+            margin-bottom: 20#{rpx};
+
+            .item {
+                border-radius: 16#{rpx};
+                width: 360#{rpx};
+                height: 360#{rpx};
+                position: relative;
+
+                .anchor-img {
+                    width: 100%;
+                    height:100%;
+                    position: absolute;
+                    top: 0;
+                    left: 0;
+                }
+
+                .play-icon {
+                    position: absolute;
+                    top: 123#{rpx};
+                    left: 123#{rpx};
+                }
+            }
+
+            .item-2 {
+                display: flex;
+                flex-direction: column;
+                padding: 20#{rpx};
+
+                .title {
+                    width: 310#{rpx};
+                    height: 88#{rpx};
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    display: -webkit-box;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 2;
+                    line-clamp: 2;
+                }
+
+                .user-info {
+                    margin: 20#{rpx} 0;
+                }
+
+                .nickname {
+                    width: 258#{rpx};
+                    color: #999999;
+                }
+
+                .goods-info {
+                    display: flex;
+                    justify-content: space-between;
+
+                    .goods-cover {
+                        width: 148#{rpx};
+                        height: 148#{rpx};
+                        border-radius: 16#{rpx};
+                        overflow: hidden;
+                        display: flex;
+                        align-items: flex-end;
+                        justify-content: center;
+                        position: relative;
+
+                        image {
+                            width: 148#{rpx};
+                            height: 148#{rpx};
+                            position: absolute;
+                            top: 0;
+                            left: 0;
+                        }
+                        .goods-price {
+                            color: #ffffff;
+                            font-size: 24#{rpx};
+                            z-index: 10;
+                            margin-bottom: 10#{rpx};
+                        }
+                    }
+
+                    .goods-cover-2 {
+                        align-items: center;
+
+                        .goods-count {
+                            display: flex;
+                            flex-direction: column;
+                            color: #ffffff;
+                            font-size: 24#{rpx};
+                            z-index: 10;
+
+                            .number {
+                                text-align: center;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        .live-style-3:last-child {
+            margin-bottom: 0;
+        }
+    }
+</style>

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff