Browse Source

4.3.57商城

yanjie 4 years ago
commit
f65740378a
100 changed files with 18937 additions and 0 deletions
  1. 74 0
      App.vue
  2. 262 0
      components/basic-component/app-button/app-button.vue
  3. 49 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. 199 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. 225 0
      components/basic-component/app-input/app-input.vue
  16. 40 0
      components/basic-component/app-iphone-x/app-iphone-x.vue
  17. 410 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. 277 0
      components/basic-component/app-layout/app-layout.vue
  20. 417 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. 206 0
      components/basic-component/app-layout/app-user-login/app-user-login.vue
  23. 174 0
      components/basic-component/app-layout/u-authorized-iphone/u-authorized-iphone.vue
  24. 24 0
      components/basic-component/app-load-text/app-load-text.vue
  25. BIN
      components/basic-component/app-load-text/image/load.gif
  26. 109 0
      components/basic-component/app-loading/app-loading.vue
  27. 199 0
      components/basic-component/app-model/app-model.vue
  28. BIN
      components/basic-component/app-model/image/close.png
  29. 83 0
      components/basic-component/app-order/app-form-data.vue
  30. 94 0
      components/basic-component/app-prompt-box/app-prompt-box.vue
  31. 150 0
      components/basic-component/app-radio/app-radio-group.vue
  32. 112 0
      components/basic-component/app-radio/app-radio.vue
  33. BIN
      components/basic-component/app-radio/image/yes.png
  34. 112 0
      components/basic-component/app-report-error/app-report-error.vue
  35. 28 0
      components/basic-component/app-rich/components/wxParseAudio.vue
  36. 128 0
      components/basic-component/app-rich/components/wxParseImg.vue
  37. 52 0
      components/basic-component/app-rich/components/wxParseTable.vue
  38. 143 0
      components/basic-component/app-rich/components/wxParseTemplate0.vue
  39. 135 0
      components/basic-component/app-rich/components/wxParseTemplate1.vue
  40. 15 0
      components/basic-component/app-rich/components/wxParseVideo.vue
  41. 234 0
      components/basic-component/app-rich/libs/html2json.js
  42. 126 0
      components/basic-component/app-rich/libs/htmlparser.js
  43. 210 0
      components/basic-component/app-rich/libs/wxDiscode.js
  44. 228 0
      components/basic-component/app-rich/parse.scss
  45. 135 0
      components/basic-component/app-rich/parse.vue
  46. 82 0
      components/basic-component/app-switch-tab/app-switch-tab.vue
  47. 84 0
      components/basic-component/app-switch/app-switch.vue
  48. 84 0
      components/basic-component/app-tab-bar/app-tab-bar.vue
  49. 107 0
      components/basic-component/app-tab-nav/app-tab-nav.vue
  50. 153 0
      components/basic-component/app-tabs/app-tabs.vue
  51. 19 0
      components/basic-component/app-text/app-text.vue
  52. 156 0
      components/basic-component/app-textarea/app-textarea.vue
  53. 55 0
      components/basic-component/app-timer/app-timer.vue
  54. 231 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. 206 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. 337 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. 1132 0
      components/page-component/app-attr/app-attr.vue
  66. 84 0
      components/page-component/app-buy-prompt/app-buy-prompt.vue
  67. 198 0
      components/page-component/app-cash-model/app-cash-model.vue
  68. 95 0
      components/page-component/app-category-list/app-category-list.vue
  69. 88 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. 297 0
      components/page-component/app-comments/app-comments.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. 837 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. 1033 0
      components/page-component/app-diy-goods-list/app-diy-goods-list.vue
  79. 187 0
      components/page-component/app-diy-goods-list/app-goods-timer.vue
  80. 107 0
      components/page-component/app-diy-timer/app-diy-timer.vue
  81. 384 0
      components/page-component/app-exclusive-coupon/app-exclusive-coupon-two.vue
  82. 333 0
      components/page-component/app-exclusive-coupon/app-exclusive-coupon.vue
  83. 405 0
      components/page-component/app-good-shop-recommendation/app-good-shop-recommendation.vue
  84. 45 0
      components/page-component/app-goods-detail/app-name.vue
  85. 717 0
      components/page-component/app-goods-list/app-goods-list.vue
  86. 148 0
      components/page-component/app-goods-poster/app-goods-poster-four.vue
  87. 149 0
      components/page-component/app-goods-poster/app-goods-poster-one.vue
  88. 158 0
      components/page-component/app-goods-poster/app-goods-poster-three.vue
  89. 168 0
      components/page-component/app-goods-poster/app-goods-poster-two.vue
  90. 222 0
      components/page-component/app-goods-poster/app-poster-image.vue
  91. 112 0
      components/page-component/app-goods-poster/app-poster-price.vue
  92. 78 0
      components/page-component/app-goods-recommend/app-goods-recommend.vue
  93. 57 0
      components/page-component/app-head-navigation/app-head-navigation.vue
  94. 184 0
      components/page-component/app-image-ad/app-image-ad.vue
  95. 290 0
      components/page-component/app-index-advance/app-index-advance.vue
  96. 287 0
      components/page-component/app-index-booking/app-index-booking.vue
  97. 129 0
      components/page-component/app-index-cat/app-index-cat.vue
  98. 351 0
      components/page-component/app-index-flash-sale/app-index-flash-sale.vue
  99. 305 0
      components/page-component/app-index-miaosha/app-index-miaosha.vue
  100. 264 0
      components/page-component/app-index-pick/app-index-pick.vue

+ 74 - 0
App.vue

xqd
@@ -0,0 +1,74 @@
+<script>
+    export default {
+        globalData() {
+            return {
+                stystem: {},
+                text: ''
+            }
+        },
+        onLaunch: function (options) {
+            console.log('app onLaunch--->'); // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log(options);            // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log('<---app onLaunch'); // 公众号文章进小程序无底部导航调试,请勿删除
+            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)
+            }
+        },
+        onShow(options) {
+            console.log('app onShow--->'); // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log(options);          // 公众号文章进小程序无底部导航调试,请勿删除
+            console.log('<---app onShow'); // 公众号文章进小程序无底部导航调试,请勿删除
+            if (options && options.scene) {
+                this.$appScene = options.scene;
+            }
+            // #ifdef MP-WEIXIN
+            this.$user.silentLogin();
+            // #endif
+        }
+    };
+</script>
+
+<style lang="scss">
+    /*每个页面公共css */
+    // @import "./uni.scss";
+    @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;
+    }
+</style>

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

xqd
@@ -0,0 +1,262 @@
+<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 : ''}`,
+                    'border-radius': `${roundSize ? roundSize : ''}`,
+                    color: color,
+                    padding: padding
+                }
+            ]"
+            @click="handleClick"
+            :class="[
+                'app-button',
+                `app-important`,
+              `${round ? 'is-round' : ''}`,
+               `${disabled ? 'app-important-disabled is-disabled' : ''}`,
+               `${theme}-m-back `,
+              theme,
+              `${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 : ''}`,
+                'border-radius': `${roundSize ? roundSize : ''}`,
+                'border-color': `${borderColor ? borderColor : ''}`,
+                color: color,
+                padding: padding
+            }"
+              :class="[
+                'app-button',
+                `app-secondary`,
+              `${round ? 'is-round' : ''}`,
+               `${disabled ? 'app-secondary-disabled is-disabled' : ''}`,
+              `${theme}-m-border`,
+               `${theme}-m-text`,
+               `${theme}-m-back `,
+               theme,
+               `${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: String,
+        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>

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

xqd
@@ -0,0 +1,49 @@
+<template>
+    <image
+            :style="{width: imageWidth,height: imageHeight}"
+            :src="src" v-show="is_loading" @load="imgLoad" :class="sign !== 'gift' ? getTheme + '-m-back ' + getTheme : theme + '-background'"
+    ></image>
+</template>
+
+<script>
+    import {mapGetters} from "vuex";
+
+    export default {
+        name: "app-cart-image",
+
+        data() {
+            return {
+                is_loading: false,
+            }
+        },
+
+        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
+        },
+
+        computed: {
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            })
+        },
+        methods: {
+            imgLoad() {
+                this.is_loading = true;
+            }
+        }
+
+    }
+</script>

+ 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>

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

xqd
@@ -0,0 +1,199 @@
+<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" :class="getTheme + '-m-text ' + getTheme">继续浏览</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,
+                default() {
+                    return 0;
+                }
+            },
+            mch_list: {
+                type: String,
+                default() {
+                    return ''
+                }
+            }
+	    },
+        data() {
+            return {
+                mallStatus: {
+                    is_open: 0,
+                    auto_open_text: ''
+                },
+                isMall: true,
+                list: ''
+            }
+        },
+        created() {
+            let para = {};
+            if(this.mch_list) {
+                this.mch_list = JSON.parse(this.mch_list);
+                if(this.mch_list.length > 0) {
+                    para.mch_id_list = this.mch_list;
+                    if(this.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 => {
+                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(this.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" :class="theme + '-m-text ' + theme">固定</view>
+                <view class="app-composition-type" v-if="item.type == 2 && !large" :class="theme + '-m-text ' + theme">搭配</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" :class="theme + '-m-text ' + theme">固定套餐</view>
+            <view class="app-composition-type" v-if="item.type == 2 && large" :class="theme + '-m-text ' + theme">搭配套餐</view>
+            <view class="app-composition-price">
+                套餐价<text :class="theme + '-m-text ' + theme">¥{{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: String
+	    },
+        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>

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

xqd
@@ -0,0 +1,225 @@
+<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 === 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"
+                   :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%'}"></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: {
+                default: 'color: #999999',
+            },
+            maxLength: String,
+            value: {
+                default: '',
+            },
+            round: Boolean,
+            border: Number,
+            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>

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

xqd
@@ -0,0 +1,410 @@
+<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';
+    import {clearStorage} from '../../../core/cache.js';
+
+    export default {
+        name: 'app-jump-button',
+        props: {
+            item: Object,
+            arrangement: {
+                type: String,
+                default: function () {
+                    return 'row';
+                }
+            },
+            backgroundColor: String,
+            form: {
+                type: Boolean,
+                default: function () {
+                    return true;
+                }
+            },
+            height: {
+                type: String,
+                default: function () {
+                    return '100';
+                }
+            },
+            width: {
+                type: String,
+                default: function () {
+                    return '100';
+                }
+            },
+            open_type: {
+                type: String,
+                default: function () {
+                    return 'navigate';
+                }
+            },
+            url: {
+                type: String,
+                default: function () {
+                    return '';
+                }
+            },
+            params: {
+                type: Array,
+                default: function () {
+                    return [];
+                }
+            },
+            number: {
+                type: String,
+                default: function () {
+                    return '';
+                }
+            },
+            appId: {
+                type: String,
+                default: function () {
+                    return '';
+                }
+            },
+            path: {
+                type: String,
+                default: function () {
+                    return '';
+                }
+            },
+            latitude: {
+                type: String,
+                default: function () {
+                    return '0';
+                }
+            },
+            longitude: {
+                type: String,
+                default: function () {
+                    return '0';
+                }
+            },
+            address: {
+                type: String,
+                default: function () {
+                    return '';
+                }
+            }
+        },
+        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.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 */
+                            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;
+                    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;
+                    case 'clear_cache':
+                        uni.showModal({
+                            content: '确定要清理缓存?',
+                            cancelText: '取消',
+                            confirmText: '确认',
+                            success: (e) => {
+                                if (e.confirm) {
+                                    // uni.showLoading({
+                                    //     title: '清理缓存...',
+                                    // });
+                                    clearStorage();
+                                    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 => {
+                    //     }
+                    // });
+                }
+            }
+        }
+    }
+</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>

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

xqd
@@ -0,0 +1,277 @@
+<template>
+    <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: getNavHei + 'rpx'}" class="nav-margin "
+                      :class="haveBackground ? 'app-layout-background' : ''"></view>
+            </view>
+            <app-tab-bar :page-count="page_count"></app-tab-bar>
+        </template>
+   </view>
+</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
+
+   export default {
+       name: "app-layout",
+       data() {
+           return {
+               currentRoute: '',
+               tabbarbool: true,
+               navigationBarTitle: '',
+               page_count: getCurrentPages().length,
+           };
+       },
+       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
+       },
+       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() {
+               return this.$store.state.user.accessToken === '' || this.$store.state.user.accessToken === null;
+           },
+           ...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',
+           }),
+            ...mapGetters('iPhoneX', {
+                getNavHei: 'getNavHei',
+            }),
+           layoutStyle() {
+               if (this.overflow) {
+                   return {
+                       overflow: 'hidden'
+                   }
+               } else {
+                   return ''
+               }
+           }
+       },
+       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,
+           }
+       },
+       created() {
+           this.$store.dispatch('mallConfig/actionGetConfig');
+           this.$nextTick(() => {
+               let currentRoute = this.$platDiff.route();
+               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();
+
+           // #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
+       },
+       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 = this.$utils.deleteUrlParam(currentRoute, ['appid', 'appmsg_compact_url', 'wxwork_userid'], true);
+               }
+               for (let i = 0; i < this.tabBarNavs.length; i++) {
+                   if (currentRoute == this.tabBarNavs[i].url) {
+                       return this.tabbarbool = true;
+                   }
+               }
+               return this.tabbarbool = false;
+           },
+       },
+   }
+</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>

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

xqd
@@ -0,0 +1,417 @@
+<!-- 全局支付组件 -->
+<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">
+                        <app-radio v-if="item.checked" :theme="getTheme" v-model="item.checked" type="round"></app-radio>
+                    </view>
+                </view>
+            </view>
+            <view class="footer">
+                <app-button type="important" :theme="getTheme" @click="confirm" round>提交订单</app-button>
+            </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},
+        data() {
+            return {};
+        },
+        computed: {
+            ...mapState({
+                showPayment: state => state.payment.showPayment,
+                payData: state => state.payment.payData,
+            }),
+            ...mapGetters('mallConfig',{
+                getTheme: 'getTheme',
+            }),
+        },
+        created() {
+            this.setPayment();
+        },
+        methods: {
+            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: '请求支付...',
+                });
+                this.$request({
+                    url: this.$api.payment.pay_data,
+                    data: {
+                        id: this.$store.state.payment.id,
+                        pay_type: this.$store.state.payment.payType,
+                    }
+                }).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;
+                            default:
+                                console.log('debug payment, getPayData 2,', this.$store.state.payment.resolve);
+                                this.callPlatformPayment(response.data);
+                                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) {
+                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) {
+                                this.payByBalance();
+                            } else {
+                                return this.$store.state.payment.reject({
+                                    errMsg: '支付取消.',
+                                });
+                            }
+                        }
+                    });
+                }
+            },
+            payByBalance() {
+                uni.showLoading({
+                    mask: true,
+                    title: '支付中...',
+                });
+                this.$request({
+                    url: this.$api.payment.pay_buy_balance,
+                    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 => {
+                    e.errMsg = e.msg || '';
+                    return this.$store.state.payment.reject(e);
+                });
+            },
+            callHuodao(data) {
+                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);
+                });
+            },
+            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
+                });
+            },
+        },
+    }
+</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;
+    }
+</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>

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

xqd
@@ -0,0 +1,206 @@
+<template>
+    <view class="login dir-left-nowrap main-center cross-center" :class="showLoginModal ? 'show' : ''">
+        <view class="login-content">
+            <image :src="auth_page.pic_url" class="login-img"></image>
+            <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">
+                    <button class="login-btn"
+                            :open-type="openType"
+                            scope="userInfo"
+                            @getAuthorize="getUserInfo"
+                            @getuserinfo="getUserInfo"
+                            @click="getUserInfoClick"
+                    >
+                    </button>
+                </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: state => state.user.showLoginModal,
+            }),
+        },
+        created() {
+            const vm = this;
+            Vue.use({
+                install(Vue, options) {
+                    Vue.prototype.$layout = {
+                        getUserInfo() {
+                            vm.showLoginModal = true;
+                            return new Promise((resolve, reject) => {
+                                vm.getUserInfo = (e) => {
+                                };
+                            });
+                        },
+                    };
+                },
+            });
+        },
+        methods: {
+            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'];
+                let url = this.$platDiff.route();
+                if (list.includes(url)) {
+                    url = this.$platDiff.routeWithOption();
+                    uni.redirectTo({
+                        url: url
+                    });
+                } else if (pages.length >= 2) {
+                    uni.navigateBack({
+                        delta: 1
+                    });
+                } else {
+                    uni.redirectTo({
+                        url: '/pages/index/index'
+                    });
+                }
+            },
+            getUserInfoClick(e) {
+                // #ifdef MP-TOUTIAO
+                this.getUserInfo(e);
+                // #endif
+            },
+            getUserInfo(e) {
+                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
+                if (e.detail.errMsg !== 'getUserInfo:ok') {
+                    return reject(e.detail.errMsg);
+                } else {
+                    return resolve(e);
+                }
+                // #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
+            },
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    $login-padding: #{200rpx} #{50rpx};
+
+    .login {
+        box-sizing: border-box;
+        position: fixed;
+        top: 0;
+        left: 0;
+        z-index: 1502;
+        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.show {
+        visibility: visible;
+        opacity: 1;
+    }
+</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"></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"></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"></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"></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


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

xqd
@@ -0,0 +1,83 @@
+<template>
+    <view class="app-form-data">
+        <view class='info-title'>表单内容</view>
+        <block v-for="(goods,key) in detail" :key="key">
+            <view class="goods-info">
+                <app-order-goods-info style="width:100%;" :goods="goods.goods_info"></app-order-goods-info>
+            </view>
+            <view v-if="form.value  && [`,`,``, null].indexOf(String(form.value)) === -1"
+                  v-for="(form, keyA) in goods.form_data" :key="keyA" class="form-value"
+                  :class="{'dir-left-wrap' : form.key != `img_upload`}">
+                <view class="label">{{form.label}}:</view>
+                <view v-if="form.key === `img_upload`">
+                    <view v-if="Array.isArray(form.value)">
+                        <block v-for="(img,keyB) in form.value" v-if="img" :key="keyB">
+                            <image :src="img" @click="showImgg(img)"></image>
+                        </block>
+                    </view>
+                    <view v-else>
+                        <image :src="form.value" @click="showImgg(form.value)"></image>
+                    </view>
+                </view>
+                <view v-else>{{form.value}}</view>
+            </view>
+        </block>
+    </view>
+</template>
+
+<script>
+    import appOrderGoodsInfo from "../../page-component/app-order-goods-info/app-order-goods-info";
+
+    export default {
+        components: {
+            appOrderGoodsInfo,
+        },
+        props: {
+            detail: {
+                type: Array,
+                default: [],
+            }
+        },
+        data() {
+            return {}
+        },
+        methods: {
+            showImgg(e) {
+                uni.previewImage({
+                    current: e, // 当前显示图片的http链接
+                    urls: [e] // 需要预览的图片http链接列表
+                })
+            },
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .app-form-data {
+        .info-title {
+            font-size: #{24rpx};
+            color: #353535;
+            margin-bottom: #{20rpx};
+        }
+
+        .form-value {
+            margin-bottom: #{10rpx};
+            font-size: #{24rpx};
+            color: #353535;
+
+            .label {
+                color: #999;
+                font-size: #{24rpx};
+                margin-right: #{10rpx};
+                margin-bottom: #{10rpx};
+            }
+
+            image {
+                height: #{120rpx};
+                width: #{120rpx};
+                display: inline-block;
+            }
+        }
+
+    }
+</style>

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

xqd
@@ -0,0 +1,94 @@
+<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">
+				<app-form-id>
+					<view class="app-button app-close" @click="close(false)">取消</view>
+				</app-form-id>
+				<view class="app-line"></view>
+				<app-form-id>
+					<view class="app-button app-sure" @click="close(true)">确认</view>
+				</app-form-id>
+			</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>

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

xqd
@@ -0,0 +1,112 @@
+<template>
+    <view class="app-default" :style="{'width': `${width}rpx`, 'height': `${height}rpx`}" @click.stop="radioSelection">
+        <view
+            v-if="value"
+            class="app-default-active"
+            :class="[
+            {'round-active' : type === 'round'},
+            sign ? theme +'-background' : theme+ '-m-back',
+            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: {
+                type: String,
+                default: 'classic-red',
+            },
+            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', this.active, this.sign);
+                this.$emit('click', this.active, 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
+                uni.setClipboardData({
+                    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>

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

xqd
@@ -0,0 +1,128 @@
+<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() {
+				console.log(this.parentNode)
+				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) {
+				console.log(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">
+			<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'" :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  :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">
+				<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="(node, index) of node.nodes" :key="index">
+				<wx-parse-template :node="node" />
+			</block>
+		</view>
+		
+		<!-- section标签 -->
+		<view v-else-if="node.tag === 'section'" >
+			<block v-for="(node, index) of node.nodes" :key="index">
+				<wx-parse-template :node="node" />
+			</block>
+		</view>
+		
+		<!--其他标签-->
+		<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>
+	
+	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: {
+			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>

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

xqd
@@ -0,0 +1,135 @@
+<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.replace(/^\s+|\s+$/g, '')" :parent-node="node"/>
+			</block>
+		</view>
+	</block>
+	
+	<!--判断是否是文本节点-->
+	<block v-else-if="node.node === 'text'">
+		<text>{{node.text}}</text>
+	</block>
+</template>
+
+<script>
+	import wxParseTemplate from './wxParseTemplate0';
+	import wxParseImg from './wxParseImg';
+	import wxParseVideo from './wxParseVideo';
+	import wxParseAudio from './wxParseAudio';
+	import wxParseTable from './wxParseTable';
+	
+	export default {
+		name: 'wxParseTemplate1',
+		props: {
+			node: {},
+            parentNode: {}
+		},
+		components: {
+			wxParseTemplate,
+			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;
+}

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

xqd
@@ -0,0 +1,135 @@
+
+<template>
+	<!--基础元素-->
+	<view class="wxParse" :class="className" :style="'user-select:' + userSelect">
+		<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'//none |text| all | element
+		},
+		imgOptions:{
+			type:[Object,Boolean],
+			default:function(){
+				return {
+					loop: false,
+					indicator:'number',
+					longPressActions:false
+				}
+			}
+		},
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		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(){
+			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>

+ 84 - 0
components/basic-component/app-switch/app-switch.vue

xqd
@@ -0,0 +1,84 @@
+<template>
+    <view class="app-view app-background" @click.stop="switchChange" >
+        <view :class="[themeColor]" class="app" :style="{'width': `${x}rpx`,'opacity': `${x === 0? '0':'1'}`,'transition': 'all .3s'}"></view>
+        <view class="app-tab" @click.stop="switchChange" :style="{'transform': `translateX(${x}%)`, 'transition': 'all .5s'}"></view>
+    </view>
+</template>
+
+<script>
+
+    export default {
+        name: 'app-switch',
+        data() {
+            return {
+                x: 0,
+                switch: this.value,
+            }
+        },
+        props: {
+            theme: String,
+            value: {
+                default: false,
+            }
+        },
+        methods: {
+            switchChange() {
+                
+                this.switch = !this.switch;
+                this.$emit('input', this.switch);
+            }
+        },
+        computed: {
+            themeColor: function() {
+                if (this.x === 88) {
+                    return `${this.theme}-background`;
+                } else {
+                    return '';
+                }
+            }
+        },
+       watch: {
+            value: {
+                handler: function(res) {
+                    if (res === false) {
+                        this.x = 0;
+                    } else if (res === true){
+                        this.x = 88;
+                    }
+                },
+                immediate: true
+            }
+       }
+    }
+</script>
+
+<style lang="scss">
+    .app {
+        width: 0;
+        height: #{48rpx};
+        border-radius: #{50rpx};
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+    .app-view {
+        width: #{88rpx};
+        height: #{48rpx};
+        border-radius: #{50rpx};
+        border: #{1rpx} solid $uni-general-color-three;
+        position: relative;
+    }
+    .app-tab {
+        width: #{50rpx};
+        height: #{50rpx};
+        background-color:white;
+        border-radius: 50%;
+        border: #{1rpx} solid $uni-general-color-three;
+        position: absolute;
+        top: 0;
+        left: 0;
+    }
+    .app-background {
+        background-color: #ffffff;
+    }
+</style>

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

xqd
@@ -0,0 +1,84 @@
+<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.param"
+                             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,
+            }),
+        },
+	    created() {
+            this.router = this.$platDiff.tabBarUrl(null, this.pageCount);
+        }
+    };
+</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>

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

xqd
@@ -0,0 +1,107 @@
+<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 ? setTop : 0}rpx`,
+                    '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 ${theme}-m-text ` + theme : '']"
+                    :style="[
+                        {
+                            '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: String,
+            padding: {
+                default: '45',
+                type: String,
+            },
+            setHeight: String,
+            placeHeight: String,
+            fontSize: String,
+            theme: {
+                default: 'default',
+                type: String,
+            },
+            border: {
+                default: true,
+                type: Boolean,
+            },
+            shadow: {
+                default: true,
+                type: Boolean,
+            },
+            activeItem: 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 {
+            border-bottom: #{4rpx} solid;
+        }
+    }
+    .app-nav.shadow {
+        box-shadow: 0 #{2rpx} #{10rpx} rgba(0, 0, 0, 0.06);
+    }
+    .default-m-text {
+        color: #ff4544;
+    }
+    .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 lang="less">
+	.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>

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

xqd
@@ -0,0 +1,19 @@
+<template>
+    <text :style="{'font-size': `${fontSize ? fontSize : 32}rpx`, 'color': `${color}`}" :class="[theme ? `${theme}-color` : '']">
+        <slot></slot>
+    </text>
+</template>
+
+<script>
+    export default {
+        name: 'app-text',
+        props: {
+            theme: String,
+            fontSize: String,
+            color: String,
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+</style>

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

xqd
@@ -0,0 +1,156 @@
+<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: {
+                default: '',
+            },
+            placeholderClass: {
+                default: [],
+            },
+            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>

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

xqd
@@ -0,0 +1,231 @@
+<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>
+export default {
+    name: 'app-upload-image',
+    props: {
+        value: {
+            default: null,
+        },
+        defaultImg: {
+            // 添加图片的默认背景图片
+            type: String,
+            default: '/static/image/icon/icon-image.png'
+        },
+        maxNum: {
+            // 可添加最大图片数量
+            type: Number,
+            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;
+            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
+                    })
+                }
+            })
+        },
+        // 图片预览
+        previewImage(index) {
+            let imageList = this.imageList;
+            uni.previewImage({
+                current: imageList[index],
+                urls: imageList
+            })
+        },
+    },
+    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};
+}
+
+.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,
+				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"  >
+        <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,
+                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>

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

xqd
@@ -0,0 +1,206 @@
+<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' }"
+			 :key="index"
+				  :class="index === current ? theme + '-m-back ' + theme : 'default-back'"
+				  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: String
+		},
+		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;
+	}
+	.default-back {
+		background-color: #e2e2e2;
+	}
+</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':''"></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,
+            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>

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

xqd
@@ -0,0 +1,337 @@
+<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 {
+                area_picker_show: '',
+                list: null,
+                multiIndex: [],
+                multiArray: [],
+                place: '',
+            }
+        },
+        created: function () {
+            this.ids = this.ids.concat();
+        },
+        watch: {
+            ids: function (newData, oldData) {
+                const self = this;
+                self.before((data) => {
+                    self.init(data);
+                })
+            }
+        },
+
+        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 = uni.getStorageSync("_DISTRICT");
+                if (district) {
+                    cb(district);
+                } else {
+                    this.$request({
+                        url: self.$api.default.district,
+                    }).then(info => {
+                        if (info.code === 0) {
+                            uni.setStorageSync("_DISTRICT", info.data.list);
+                            cb(info.data.list);
+                        }
+                    })
+                }
+            },
+
+            init: function (list) {
+                const null_status = this.ids.length === 3 && this.ids[0] != 0;
+                const ids = null_status ? this.ids : [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"></icon>
+			</view>
+		</app-jump-button>
+	</view>
+</template>
+
+<script>
+    import {mapState, mapGetters} 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>

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

xqd
@@ -0,0 +1,1132 @@
+<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="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" :class="theme+'-m-text ' + theme" v-if="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="themeObject"></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="wholesale-attr-group t-omit-two">{{item.attr_group_name}}</view>
+                                <scroll-view @scroll="scrollGet($event,index)" :scroll-left="item.scrollLeft" class="wholesale-attr-item" scroll-x="true">
+                                    <view class="attr-name" v-for="(attr, key) in item.attr_list" :key="key" :class="attr.active ? theme + '-m-back ' + theme : 'attr-background'" @click="chooseAttr(index,attr)">{{attr.attr_name}}
+                                        <view class="attr-number" :style="{'right':`${attr.length + 'rpx'}`}" :class="theme + '-m-back ' + theme" 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="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="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"
+									 :class="attr.checked && sign !== 'gift' ? theme + '-m-back active ' + theme : attr.checked && sign === 'gift' ?  theme + '-background active' : 'attr-item-default' + (attr.attr_num_0 ? ' attr_num_0' : '')"
+									 @click="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="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="numberAdd"></image>
+					   </view>
+				   </view>
+                    <view v-if="sign =='wholesale'" class="total">已选<text>{{totalNumber}}</text>件 总计<text>¥{{totalPrice > 0? totalPrice : '0.00'}}</text></view>
+				   <view class="three dir-left-nowrap">
+					   <view class="box-grow-1 main-center cross-center" v-if="cartShow"
+							 :class="sign === 'pick' ? theme + '-m-back ' + 'buy ' + theme : theme === 'b' || theme === 'f'  || theme === 'a' ? theme + '-s-back ' + 'buy ' + theme : theme + '-s-back ' + theme+ '-m-text ' + theme" @click="cart">{{addText}}
+					   </view>
+					   <view v-if="is_show_buy" class="box-grow-1 main-center cross-center buy" :class="sign !== 'gift' ? theme + '-m-back '+ theme : theme + '-background'" @click="buy">
+						   {{buyText}}
+					   </view>
+				   </view>
+			   </view>
+		   </view>
+	   </view>
+    </view>
+</template>
+
+<script>
+    import { mapState, mapGetters } from "vuex";
+    import appPrice from "../../page-component/goods/app-price.vue";
+    import appImage from "../../basic-component/app-image/app-image.vue";
+    import appMemberMark from '../../page-component/app-member-mark/app-member-mark.vue';
+    export default {
+        name: "app-attr",
+        components: {
+            appPrice,
+            'app-image': appImage,
+            appMemberMark
+        },
+        props: {
+            goods: Object,
+            attrGroupList: Array,
+            attrCart: {
+                type: Array,
+                default() {
+                    return [];
+                }
+            },
+            cartShow: {
+                type: Boolean,
+                default() {
+                    return true
+                }
+            },
+            previewUrl: String,
+            submitUrl: String,
+            goodsId: {
+                type:Number,
+                default() {
+                    return 0
+                }
+            },
+            show: Number,
+            buyText: {
+                type: String,
+                default() {
+                    return '立即购买';
+                }
+            },
+            plugin: {
+                default: '',
+            },
+	        theme: {
+                type: String,
+		        default: 'a',
+	        },
+            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,
+            }
+        },
+        data() {
+            return {
+                display: 'none',
+                number: 1,
+                selectAttr: null,
+                newAttrGroupList: null,
+				pic_url: null,
+                // 商品批发
+                activeAttr: [],
+                goodsAttr: []
+            };
+        },
+        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();
+                    }
+                },
+		        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.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', that.goods, 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.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]
+                });
+            },
+        },
+        computed: {
+            ...mapState({
+                gConfig: state => state.gConfig,
+            }),
+            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
+                }
+            },
+            themeObject:function() {
+                return {
+                    back: this.theme + '-m-back ' + this.theme,
+                    theme: this.theme,
+                    color: this.theme + '-m-text ' + this.theme,
+                    sBack: this.theme + '-s-back ' + this.theme
+                }
+            },
+	        ...mapGetters('iPhoneX', {
+                boolEmpty: 'getBoolEmpty'
+	        })
+        }
+    }
+
+</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%;
+				font-size: $uni-font-size-general-one;
+			}
+		}
+    }
+	.buy {
+		color: #ffffff;
+	}
+    .wholesale {      
+        .wholesale-attr-list {
+            height: 88rpx;
+            margin: 0 24rpx;
+            border-bottom: 1rpx solid #e2e2e2;
+            position: relative;
+            &:first-of-type {
+                border-top: 1rpx solid #e2e2e2;
+            }
+            .wholesale-attr-group {
+                max-width: 200rpx;
+                flex-shrink: 0;
+                margin-right: 4rpx;
+                font-size: 20rpx;
+                color: #666666;
+            }
+            .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%;
+                white-space : nowrap;
+                .attr-name {
+                    margin-top: 16rpx;
+                    color: #fff;
+                    padding: 0 22rpx;
+                    height: 56rpx;
+                    line-height: 56rpx;
+                    border-radius: 8rpx;
+                    font-size: 26rpx;
+                    margin-left: 20rpx;
+                    display: inline-block;
+                    position: relative;
+                    .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;
+    }
+</style>

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

xqd
@@ -0,0 +1,84 @@
+<template>
+    <view v-if="buy_data && buy_data.length" class="buy-prompt-box">
+        <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>
+    export default {
+        name: "app-buy-prompt",
+        data() {
+            return {
+                buy_data: null,
+            }
+        },
+        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: 1003;
+    }
+
+    .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};
+    }
+</style>

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

xqd
@@ -0,0 +1,198 @@
+<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" :class="theme + '-m-back ' + theme"></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" :class="theme + '-m-back ' + theme"></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" :class="theme + '-m-back ' + theme"></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" :class="theme + '-m-back ' + theme"></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" :class="theme + '-m-back ' + theme"></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: String,
+                default() {
+                    return 'a'
+                }
+            }
+        },
+        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>

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

xqd
@@ -0,0 +1,95 @@
+<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" :class="item.active === true ? theme+ '-m-back ' + theme : ''"></view>
+			<view class="app-text"  :class=" item.active === true ? 'app-active-f ' + theme+ '-m-text ' + theme : ''">{{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: String
+	    },
+	    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-active-f {
+		background-color: #ffffff !important;;
+	}
+	.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>

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

xqd
@@ -0,0 +1,88 @@
+<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>

+ 297 - 0
components/page-component/app-comments/app-comments.vue

xqd
@@ -0,0 +1,297 @@
+<template>
+    <view class="app-comments" v-if="mall.setting.is_comment == 1">
+        <view class="empty" v-if="showType !== 'detail'">
+            <view class="app-top dir-left-nowrap cross-center">
+                <view class="box box-grow-1 main-center cross-center" @click="clickStatus(item.index)"
+                      v-for="(item, index) in commentCount"
+                      :key="index"
+                      :class="status === item.index ? getTheme + '-m-back u-text ' + getTheme : 'background'">
+                    {{item.name}}({{item.count}})
+                </view>
+            </view>
+        </view>
+        <view class="list" v-if="list.length > 0">
+            <view class="dir-left-nowrap block cross-center" v-if="showType === 'detail'">
+                <view class="box-grow-1">评价</view>
+                <view class="box-grow-0 more" @click="goto">查看更多</view>
+                <image class="box-grow-0" src="../../../static/image/icon/arrow-right.png"></image>
+            </view>
+            <view class="comments" v-for="(item, index) in list" :key="index" :class="showType === 'detail' ? 'bt' : 'bb'">
+                <view class="title dir-left-nowrap cross-center">
+                    <image class="box-grow-0" :src="item.avatar"></image>
+                    <view class="box-grow-1">{{item.nickname}}</view>
+                    <view class="more box-grow-0">{{item.time}}</view>
+                </view>
+                <view class="c-attr-name">{{ item.attr_name }}</view>
+                <view :class="showType === 'detail' ? 'content' : ''">{{item.content}}</view>
+                <view class="dir-left-wrap pic-list">
+                    <image :src="pic_url" v-for="(pic_url,pic_url_index) in item.pic_url" :key="pic_url_index"
+                           @click="imgPreview(index, pic_url_index)"></image>
+                </view>
+                <view class="replay" v-if="showType !== 'detail' && item.reply_content">
+                    <view>
+	                    <text :class="getTheme + '-m-text ' + getTheme">商家:</text>
+	                    {{item.reply_content}}
+                    </view>
+                </view>
+            </view>
+        </view>
+        <view class="block cross-center" v-else>暂无评价</view>
+    </view>
+</template>
+
+<script>
+    import {mapGetters, mapState} from 'vuex';
+
+    let page = 1;
+    let is_loading = false;
+    let is_no_more = false;
+    
+    export default {
+        name: 'app-comments',
+        props: {
+            goodsId: Number,
+            url: {
+                type: String,
+                default() {
+                    return '';
+                }
+            },
+            showType: {
+                type: String,
+                default() {
+                    return 'detail'
+                }
+            },
+            reachBottom: Number,
+        },
+        watch: {
+            goodsId: {
+               handler() {
+                   this.loadData();
+               }
+            },
+            reachBottom: {
+              handler(){
+                  if (is_no_more) return;
+                  this.loadData();
+              }
+            }
+        },
+        computed: {
+            ...mapState({
+                mall: state => state.mallConfig.mall
+            }),
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+        },
+        methods: {
+            loadData() {
+                if (this.mall.setting.is_comment == 0) return;
+                if (is_loading) return;
+                is_loading = true;
+                if (this.showType !== 'detail') {
+                    uni.showLoading({
+                        title: '加载中'
+                    });
+                }
+                this.$request({
+                    url: this.url ? this.url : this.$api.goods.comments_list,
+                    data: {
+                        goods_id: this.goodsId,
+                        page: page,
+                        status: this.status,
+                    }
+                }).then(response => {
+                    is_loading = false;
+                    uni.hideLoading();
+                    if (response.code === 0) {
+                        this.commentCount = response.data.comment_count;
+                        if (page === 1) {
+                            this.list = [];
+                        }
+                        let list = response.data.comments;
+                        if (list.length > 0) {
+                            if (this.showType === 'detail') {
+                                list = list.splice(0, 2)
+                            }
+                            this.list = [...this.list, ...list];
+                            page++;
+                        } else {
+                            is_no_more = true;
+                        }
+                    }
+                }).catch(() => {
+                    is_loading = false;
+                    uni.hideLoading();
+                });
+            },
+            goto() {
+                uni.navigateTo({
+                    url: `/pages/comments/comments?goods_id=${this.goodsId}`
+                })
+            },
+            clickStatus(status) {
+                this.status = status;
+                page = 1;
+                is_no_more = false;
+                this.loadData();
+            },
+            imgPreview(index, pic_index) {
+                if (this.list && this.list[index] && this.list[index].pic_url && this.list[index].pic_url.length > 0) {
+                    uni.previewImage({
+                        current: pic_index,
+                        urls: this.list[index].pic_url
+                    });
+                }
+            },
+        },
+        data() {
+            return {
+                commentCount: [],
+                list: [],
+                status: 0,
+            };
+        },
+        created() {
+            page = 1;
+            is_loading = false;
+            is_no_more = false;
+        },
+	    
+        mounted() {
+            if (this.goodsId) this.loadData();
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .app-comments {
+
+        .c-attr-name {
+            color: #999999;
+            font-size: #{24rpx};
+        }
+
+
+        .more {
+            font-size: $uni-font-size-weak-one;
+            color: $uni-general-color-two;
+        }
+
+        .block {
+            width: 100%;
+            height: #{80rpx};
+            font-size: $uni-font-size-general-two;
+            background-color: #FFFFFF;
+            padding: 0 #{24rpx};
+
+            image {
+                width: #{12rpx};
+                height: #{22rpx};
+                display: block;
+                margin-left: #{12rpx};
+            }
+        }
+
+        .empty {
+            width: 100%;
+            height: #{100rpx};
+            margin-bottom: #{20rpx};
+        }
+
+        .app-top {
+            padding: #{24rpx};
+            background-color: #FFFFFF;
+            width: 100%;
+            height: #{100rpx};
+            position: fixed;
+            left: 0;
+            top: 0;
+
+            .box {
+                padding: 0 #{20rpx};
+                margin-right: #{16rpx};
+                border-radius: #{26rpx};
+                font-size: $uni-font-size-general-two;
+                height: 100%;
+
+                &.background {
+                    background-color: #f1f1f1;
+                    color: $uni-general-color-one;
+                }
+            }
+        }
+
+        .list {
+            background-color: #ffffff;
+            padding: 0 #{24rpx};
+            .block {
+                padding: 0;
+            }
+
+            .comments {
+                padding: #{28rpx} 0;
+                width: 100%;
+                word-break: break-all;
+
+                .title {
+                    font-size: $uni-font-size-general-one;
+                    color: $uni-general-color-two;
+                    margin-bottom: #{26rpx};
+
+                    image {
+                        width: #{56rpx};
+                        height: #{56rpx};
+                        display: block;
+                        margin-right: #{26rpx};
+	                    border-radius: #{28upx};
+                    }
+                }
+
+                &.bt {
+                    border-top: #{1rpx} solid #e2e2e2;
+                }
+
+                &.bb {
+                    border-bottom: #{1rpx} solid #e2e2e2;
+                }
+
+                &.bb:last-child {
+                    border: none;
+                }
+
+                .content {
+                    word-break: break-all;
+                    text-overflow: ellipsis;
+                    display: -webkit-box;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 2;
+                    overflow: hidden;
+                }
+
+                .pic-list {
+                    image {
+                        width: #{214rpx};
+                        height: #{214rpx};
+                        display: inline-block;
+                        margin: #{20rpx} #{20rpx} 0 0;
+                    }
+                }
+
+                .replay {
+                    width: 100%;
+                    background-color: $uni-weak-color-two;
+                    padding: #{28rpx};
+                    border-radius: #{16rpx};
+                    font-size: $uni-font-size-general-one;
+                    color: $uni-general-color-one;
+                }
+            }
+        }
+    }
+    .u-text {
+        color: #ffffff;
+    }
+</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})`}"></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>

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

xqd
@@ -0,0 +1,837 @@
+<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.default"
+                                       :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"
+                                        @image-event="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"
+                                        @image-event="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"
+                                        @image-event="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"
+                                        @image-event="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.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: this.showScrollBtn ? false : 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();
+            },
+            datetimeChange() {
+                this.outputData();
+            },
+            checkChange() {
+                setTimeout(() => {
+                    this.outputData();
+                }, 10);
+            },
+            handleImageUpload(e) {
+                const index = parseInt(e.sign);
+                if (e.imageList.length === 1) {
+                    this.myList[index].value = e.imageList[0];
+                } else if (e.imageList.length > 0) {
+                    this.myList[index].value = e.imageList;
+                } else {
+                    this.myList[index].value = '';
+                }
+                this.outputData();
+            },
+            handleUserIdFrontUpload(e) {
+                const index = parseInt(e.sign);
+                if (e.imageList.length > 0) {
+                    this.myList[index].value[0] = e.imageList[0];
+                } else {
+                    this.myList[index].value[0] = '';
+                }
+                this.outputData();
+            },
+            handleUserIdBackUpload(e) {
+                const index = parseInt(e.sign);
+                if (e.imageList.length > 0) {
+                    this.myList[index].value[1] = e.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 === '') {
+                            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', this.validateResult, 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', this.myList, 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: #{12rpx} #{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>

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

xqd
@@ -0,0 +1,1033 @@
+<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_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.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') || 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'"
+                                      :class="theme + '-m-text ' + theme + ' ' + theme + '-m-back-o '"
+                                >
+                                    {{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')"
+                                      :class="theme + '-m-text ' + theme + ' ' + theme + '-m-back-o '"
+                                >
+                                    {{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="theme + '-m-back flash-sale ' + theme "
+                                                v-if="goods.discount_type == 1"
+                                        >{{goods.min_discount}}折</view>
+                                        <view
+                                                :class="theme + '-m-back flash-sale ' + theme"
+                                                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" :class="theme + '-m-back-l ' +theme" style="width: 445rpx;margin-bottom: 10rpx">
+                                        <view :class="theme + '-m-back ' +theme" :style="{width: `${goods.percentage}%`}"></view>
+                                    </view>
+                                </template>
+                                <view class="dir-left-nowrap cross-bottom">
+                                    <view class="box-grow-1" :class="theme + '-m-text ' + theme">
+                                        <!-- 时间 -->
+                                        <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="margin-top: 5rpx"
+                                              :class="theme + '-m-text ' + theme + '-m-border ' + theme">
+                                            定金¥{{goods.deposit}}抵¥{{goods.swell_deposit}}
+                                        </text>
+                                        <!-- #endif -->
+                                        <!-- #ifdef MP-ALIPAY -->
+                                        <text v-if="sign === 'advance'" class="des-price" style="margin: 5rpx 0"
+                                              :class="theme + '-m-text ' + theme + '-m-border ' + theme">
+                                            定金¥{{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.discount"
+                                                :discount="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.discount"
+                                                :discount="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.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="btnStyle" :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_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" v-if="showGoodsName" :class="theme">
+                        <template v-if="sign === 'advance'">
+                            <text>{{showGoodsName ? goods.goods.goodsWarehouse.name : ''}}</text>
+                        </template>
+                        <template v-if="sign === 'pick' || sign === 'gift' ||  sign === 'exchange' || sign == 'wholesale'">
+                            <text class="tag-pick"
+                                  :class="theme + '-m-text ' + theme + '-m-back-o ' + theme">
+                                {{tag}}
+                            </text>
+                            <text>{{showGoodsName ? goods.name : ''}}</text>
+                        </template>
+                        <template v-else-if="sign === 'composition'">
+                            <text class="tag-pick"
+                                  :class="theme + '-m-text ' + theme + '-m-back-o ' + theme">
+                                {{goods.tag}}
+                            </text>
+                            <text>{{showGoodsName ? goods.name : ''}}</text>
+                        </template>
+                        <template v-else>
+                            <text class="tag"
+                                  :class="theme + '-m-text ' + theme + ' ' + theme + '-m-border '"
+                                  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" :class="theme + '-m-text ' + theme + '-m-border ' + theme"
+                              style="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" :class="theme + '-m-back ' + theme"
+                                      v-if="goods.discount_type == 1">{{goods.min_discount}}折</view>
+                                <view class="flash-sale" :class="theme + '-m-back ' + theme"
+                                      v-if="goods.discount_type == 2">减{{goods.min_discount}}元</view>
+                            </view>
+                            <view v-if="showProgressBar" class="app-percentage" style="width: 660rpx" :class="theme + '-m-back-l ' +theme">
+                                <view :class="theme + '-m-back ' +theme" :style="{width: `${goods.percentage}%`}"></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="margin-left: 24rpx;margin-top: 15rpx;"
+                              :class="theme + '-m-text ' + theme + '-m-border ' + theme">
+                            {{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" :class="theme + '-m-text ' + theme">
+                            <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.discount"
+                                             :discount="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.discount"
+                                                 :discount="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="btnStyle" :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_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" :class="theme">
+                        <view class="box-grow-0 goods-name t-omit-two" v-if="showGoodsName" :class="theme">
+                            <template
+                                    v-if="sign !== 'advance' && sign !== 'pick' && sign !== 'gift' && sign !== 'exchange' && sign !== 'composition' && sign != 'wholesale'">
+                                <text class="tag"
+                                      :class="theme + '-m-text ' + theme + ' ' + theme + '-m-border '"
+                                      v-if="tag && ((sign === 'pintuan' && goods.people_num) || sign !== 'pintuan')">
+                                    {{goods.people_num ? goods.people_num : ''}}{{tag}}
+                                </text>
+                            </template>
+                            <template v-if="sign === 'pick' || sign === `gift` || sign === 'exchange' || sign === 'wholesale'">
+                                <text class="dir-tag-def"
+                                      :class="theme + '-m-text ' + theme + ' ' + theme + '-m-back-o '">
+                                    {{tag}}
+                                </text>
+                            </template>
+                            <template v-if="sign === 'composition'">
+                                <text class="dir-tag-def"
+                                      :class="theme + '-m-text ' + theme + ' ' + theme + '-m-back-o '">
+                                    {{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="margin-left: 24rpx;"
+                                  :class="theme + '-m-text ' + theme + '-m-border ' + theme">
+                                定金¥{{goods.deposit}}抵¥{{goods.swell_deposit}}
+                            </text>
+                        </view>
+                        <view v-if="sign === 'pick'">
+                            <text class="des-price" style="margin-left: 24rpx;"
+                                  :class="theme + '-m-text ' + theme + '-m-border ' + theme">
+                                {{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" :class="theme + '-m-back ' + theme">{{goods.min_discount}}折</view>
+                                <view class="flash-sale" v-if="goods.discount_type === 2" :class="theme + '-m-back ' + theme">减{{goods.min_discount}}元</view>
+                            </view>
+                            <view v-if="showProgressBar" class="app-percentage" style="width: 296rpx;margin-left: 24rpx;" :class="theme + '-m-back-l ' +theme">
+                                <view :class="theme + '-m-back ' +theme" :style="{width: `${goods.percentage}%`}"></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.discount && sign === 'advance'"
+                                     :discount="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"
+                                          :class="theme + '-m-text ' + theme">
+                                        <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" :class="theme + '-m-text ' + theme">
+                                    <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.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.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="btnStyle" :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: String,
+        showProgressBar: {
+            type: Boolean,
+            default() {
+                return false;
+            }
+        }
+    },
+    data() {
+        return {
+            imgRadius: '16rpx 16rpx 0 0',
+            lisRadius: '16rpx'
+        };
+    },
+    computed: {
+        ...mapState({
+            appImg: state => state.mallConfig.__wxapp_img.mall,
+            appSetting: state => state.mallConfig.mall.setting
+        }),
+        ...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;
+        },
+        coverPicHeight() {
+            if (this.goodsCoverProportion === '1-1') {
+                return `702rpx`;
+            } else {
+                return `468rpx`;
+            }
+        },
+        goodsClass() {
+            let goodsClass = ``;
+            if (this.goodsStyle === 2) {
+                goodsClass += `border ${this.theme} `;
+            }
+            if (this.textStyle === 2) {
+                goodsClass += `text-center ${this.theme} `;
+            }
+            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;
+                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';
+                }
+                uni.navigateTo({
+                    url: `/pages/goods/video?goods_id=${id}&sign=${data.sign}`
+                });
+            } 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>

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

xqd
@@ -0,0 +1,187 @@
+<template>
+    <view class="dir-left-nowrap app-goods-timer cross-center" :class="listClass">
+        <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" :class="theme + '-m-text ' + theme" 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: String,
+        },
+        data() {
+            return {
+                timeInterval: null,
+                timer: null,
+                timerStr: null
+            };
+        },
+        computed: {
+            time() {
+                return {
+                    startDateTime: this.startDateTime,
+                    endDateTime: this.endDateTime,
+                    pageHide: this.pageHide,
+                };
+            },
+            listClass() {
+                let str = ``;
+                switch (this.listStyle) {
+                    case 1:
+                        str = `${this.theme}-m-back ${this.theme}`;
+                        break;
+                    case 2:
+                        str = `${this.theme}-m-back ${this.theme} main-center`;
+                        break;
+                    default:
+                        str = '';
+                        break;
+                }
+                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>

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

xqd
@@ -0,0 +1,384 @@
+<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)">
+                        <div class="coupon-bg">
+                            <img style="height: 100%;width: 100%" :src="couponBgImg" alt="">
+                        </div>
+                        <div 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>
+                        </div>
+                        <div v-else class="dir-left-nowrap" style="height:100%" :style="{color: textColor}">
+                            <div style="text-align: center;width: 75%" class="box-grow-0">
+                                <div 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}}
+                                </div>
+                                <div style="height: 50%;font-size: 20rpx;padding-top: 15rpx">
+                                    满{{item.min_price}}元可用
+                                </div>
+                            </div>
+                            <div class="box-grow-1 app-text-right main-center cross-center">
+                                <text>
+                                    {{item.is_receive == 0 ? receiveTip : item.is_receive > 0 ? '已领取' : ''}}
+                                </text>
+                            </div>
+                        </div>
+                    </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() {
+                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%;
+
+                > view {
+                    width: #{256rpx};
+                    height: #{130rpx};
+                }
+
+                .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>

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

xqd
@@ -0,0 +1,333 @@
+<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"></icon>
+                <text class="app-title">专享优惠券</text>
+            </view>
+            <view class="app-right main-between cross-center">
+                <text class="app-text">更多</text>
+                <icon class="app-icon"></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 '';
+                }
+            },
+            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,
+            },
+            showTop: {
+                type: Boolean,
+                default() {
+                    return true;
+                }
+            },
+            noneColor: {
+                type: Boolean,
+                default() {
+                    return false;
+                }
+            },
+            background: String,
+            page_id: Number,
+            is_required: Boolean,
+            coupon_req: Boolean
+        },
+        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>

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

xqd
@@ -0,0 +1,405 @@
+<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"></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"></icon>
+                </app-jump-button>
+            </view>
+        </view>
+        <view class="app-content" :style="[{'background-color':`${temp.data.backgroundColor}`}]" 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">
+                            <app-jump-button form
+                                             @click.native.stop="router_jump(good, item.id)"
+                                             >
+                                <image class="app-image" :src="good.picUrl"></image>
+                                <text class="app-price" :class="theme.color">¥{{good.price}}</text>
+                            </app-jump-button>
+                        </view>
+                    </scroll-view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapGetters} from 'vuex';
+
+    export default {
+        name: "app-good-shop-recommendation",
+
+        props: {
+            showGoods: {
+                type: Boolean,
+                default: function () {
+                    return false;
+                }
+            },
+            cardStyle: {
+                type: String,
+                default: function () {
+                    return '1';
+                }
+            },
+            type: {
+                type: String,
+                default() {
+                   return 'mch';
+                }
+            },
+            backgroundColor: {
+                type: String,
+                default() {
+                   return '#fff';
+                }
+            },
+            theme: Object,
+            page_id: Number,
+            index: {
+                type: Number,
+            },
+            is_required: Boolean,
+            mch_list: Array,
+            coupon_req: Boolean
+        },
+        computed: {
+            ...mapGetters('mallConfig',{
+                getVideo: 'getVideo'
+            }),
+        },
+	    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) {
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=mch`
+                    });
+                } 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>

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

xqd
@@ -0,0 +1,717 @@
+<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 :class="themeText+ '-m-text ' + themeText">价格面议</view>
+                                    </template>
+                                    <template v-else>
+                                        <app-price :theme="themeText" :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"
+                                          :class="themeText+ '-m-text ' + themeText"
+                                         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" :class="themeText + '-m-text ' + themeText + ' ' + themeText + '-m-back-o '">N元任选</span>
+                        {{goods.name}}
+                    </view>
+                    <view v-if="sign === 'pick'">
+                        <text class="des-price" style="margin-left: 24rpx;margin-top: 15rpx;" :class="themeText + '-m-text ' + themeText + '-m-border ' + themeText">
+                            {{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 :class="themeText + '-m-text ' + themeText">价格面议</view>
+                                </template>
+                                <template v-else>
+                                    <app-price :theme="themeText" :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="theme" class="padding box-grow-0">
+                            <template v-if="goods.is_negotiable == 1">
+                                <view :class="themeText + '-m-text ' + themeText">价格面议</view>
+                            </template>
+                            <template v-else>
+                                <app-price :theme="themeText" :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="themeText"
+            >
+                <view slot="extra" v-if="detail_sign === 'pintuan'">
+                    <app-pt-attr
+                        v-if="pt"
+                        :theme="themeText"
+                        :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" :class="themeText + '-s-back ' + themeText + ' ' + themeText + '-m-text '" @click="individual" v-if="item.pintuanGoods.is_alone_buy">
+                        <text class="app-text">单独购买</text>
+                    </view>
+                    <view class="tuan box-grow-1 dir-top-nowrap" :class="themeText+ '-m-back ' + themeText" @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 appMemberPrice from "../app-member-mark/app-member-price.vue";
+    import appSupVip from '../app-sup-vip/app-sup-vip.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-member-price': appMemberPrice,
+            'app-sup-vip': appSupVip,
+            '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,
+            }),
+            themeText:function() {
+                return this.theme.back[0]
+            },
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo'
+            }),
+            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;
+                        // this.selectAttr = {};
+                    }
+                }
+            }
+        },
+        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 = [];
+                let mch = {
+                    mch_id: goods.mch_id ? goods.mch_id : 0,
+                    goods_list: []
+                };
+                mch.goods_list.push({
+                    id: goods.id,
+                    attr: attr,
+                    num: number,
+                    cat_id: 0,
+                    goods_attr_id: goods_attr_id
+                });
+                mch_list.push(mch);
+                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) {
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=${data.sign}`
+                    });
+                } 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 = {};
+                }
+            },
+        },
+        created() {
+        }
+    }
+</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};
+        }
+
+        .sales-box {
+            /*position: relative;*/
+        }
+    }
+    .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>

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

xqd
@@ -0,0 +1,148 @@
+<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[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: {
+            iconPlugin: Object,
+            multiple: Number,
+            info: Object,
+            form: Object,
+        },
+        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>

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

xqd
@@ -0,0 +1,149 @@
+<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[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: Number,
+            info: Object,
+            form: Object
+        },
+        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>

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

xqd
@@ -0,0 +1,158 @@
+<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[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[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: {
+            iconPlugin: Object,
+            multiple: Number,
+            info: Object,
+            form: Object,
+        },
+        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>

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

xqd
@@ -0,0 +1,168 @@
+<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[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[1] ? info.extra_mark[1] : `长按识别小程序码进入`}}</view>
+                                <icon class="icon-three-arrow"></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: {
+            iconPlugin: Object,
+            multiple: Number,
+            info: Object,
+            form: Object,
+        },
+        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>

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

xqd
@@ -0,0 +1,222 @@
+<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: Object,
+            form: Object
+        },
+        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>

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

xqd
@@ -0,0 +1,112 @@
+<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: Boolean,
+            info: Object,
+            textColor: {
+                type: String,
+                default: '#FF4544',
+            }
+        },
+        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>

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

xqd
@@ -0,0 +1,78 @@
+<template>
+    <view class="app-goods-recommend" v-if="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 :theme-object="themeObject" :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';
+
+    export default {
+        name: "app-goods-recommend",
+        components: {
+            uOrdinaryList
+        },
+        props: {
+            goodsList: Array,
+            theme: String,
+            sureCart: Boolean,
+            activity: Object,
+            is_show_member: {
+                type: Boolean,
+                default() {
+                    return true;
+                }
+            },
+            sign: String,
+            detail: Object
+        },
+        computed: {
+            themeObject:function() {
+                return {
+                    back: this.theme + '-m-back ' + this.theme,
+                    theme: this.theme,
+                    color: this.theme + '-m-text ' + this.theme,
+                    sBack: this.theme + '-s-back ' + this.theme
+                }
+            }
+        },
+
+    }
+</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>

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

xqd
@@ -0,0 +1,57 @@
+<template>
+	<scroll-view scroll-x class="app-head-navigation dir-left-nowrap cross-center">
+		<text class="head-text" :class="item.active === true ? 'app-active ' + theme + '-m-back ' + theme : ''"
+			  v-for="(item, index) in list"
+			  :key="index"
+			  @click="active(item)">
+			{{item.name}}
+		</text>
+	</scroll-view>
+</template>
+
+<script>
+    export default {
+        name: 'app-head-navigation',
+	    props: {
+            list: {
+                type: Array,
+	            default: function() {
+	                return [];
+	            }
+            },
+			theme: String
+	    },
+	    methods: {
+            active(item) {
+                this.$emit('click', item);
+            }
+	    }
+    }
+</script>
+
+<style scoped lang="scss">
+	.app-head-navigation {
+		width: #{750rpx};
+		height: #{100rpx};
+		line-height: #{100rpx};
+		border-top: #{1rpx} solid #e2e2e2;
+		border-bottom: #{1rpx} solid #e2e2e2;
+		padding: #{0 24rpx};
+		white-space:nowrap;
+		background-color: #ffffff;
+		.head-text {
+			display: inline-block;
+			height: #{56rpx};
+			padding: 0 #{20rpx};
+			font-size: #{28rpx};
+			line-height: #{56rpx};
+			border-radius: #{28rpx};
+			margin-right: #{32rpx};
+			white-space:nowrap;
+		}
+
+	}
+	.app-active {
+		color: white;
+	}
+</style>

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

xqd
@@ -0,0 +1,184 @@
+<template>
+	<view class="app-image-ad">
+		<view class="app-one-image" v-if="imageStyle === 0">
+			<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"
+			>
+				<image :style="{
+			    left: item.left,
+			    width: item.width,
+			    top: item.top,
+			 }"
+		       :src="item.pic_url" mode="widthFix"
+			></image>
+			</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">
+	.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>

+ 290 - 0
components/page-component/app-index-advance/app-index-advance.vue

xqd
@@ -0,0 +1,290 @@
+<template>
+<!--	<view>-->
+<!--		<u-index-plugins :list="newData">-->
+<!--			<template slot="u-top-name">-->
+<!--				<view class="cross-center u-top">-->
+<!--					<image class="u-icon" src="../../../static/image/icon/advance.png"></image>-->
+<!--					<view class="box-grow-1">预售</view>-->
+<!--				</view>-->
+<!--			</template>-->
+<!--		</u-index-plugins>-->
+		<view class="index-advance">
+			<view class="top dir-left-nowrap cross-center" @click="jump(`/plugins/advance/index/index`)">
+				<image class="box-grow-0" src="../../../static/image/icon/advance.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">
+					<view class="box-grow-0 item" @click="jump_router(item)" >
+						<view class="cover-pic">
+							<app-image :img-src="item.goodsWarehouse.cover_pic" width="220rpx" height="220rpx"></app-image>
+							<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>
+							<view class="title  t-omit-two">
+								{{item.name}}
+							</view>
+							<view class="des-price" :class="getTheme+ '-m-back ' + getTheme">
+								定金¥{{Number(item.advanceGoods.deposit)}}抵¥{{Number(item.advanceGoods.swell_deposit)}}
+							</view>
+						</view>
+						<view class="content dir-top-nowrap main-right">
+							<view class="member-price" v-if="item.is_level == 1">
+								<app-member-price :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 8rpx" v-if="item.vip_card_appoint.discount > 0" :discount="item.vip_card_appoint.discount"></app-sup-vip>
+							<view :class="getTheme+ '-m-text ' + getTheme"  class="price dir-left-nowrap">
+								<text class="text" style="font-size: 22rpx;width: 75rpx;">预售价</text>
+								<text class="text"  style="font-size: 28rpx;" >¥{{item.price}}</text>
+							</view>
+							<view class="old-price">
+								¥{{item.goodsWarehouse.original_price}}
+							</view>
+						</view>
+					</view>
+				</block>
+			</view>
+	        <view v-if="style === '2'">
+	            <app-goods-list :theme="getTheme" :list="newData" sign="advance"></app-goods-list>
+	        </view>
+		</view>
+<!--	</view>-->
+</template>
+
+<script>
+    import {mapGetters, mapState} from 'vuex';
+	// import uIndexPlugins from '../u-index-plugins/u-index-plugins.vue';
+    import appGoodsList from "../app-goods-list/app-goods-list.vue";
+
+    export default {
+        name: "app-index-advance",
+        props: {
+            theme: String,
+            index: Number,
+            page_id: Number,
+            is_required: Boolean
+        },
+        data() {
+            return {
+				newData: {},
+                style: '1',
+                goods_num: 20
+            };
+        },
+		// components: {
+		// 	uIndexPlugins
+		// },
+        components: {
+            appGoodsList
+        },
+        computed: {
+            ...mapState({
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+                mall: state => state.mallConfig.mall,
+            }),
+            ...mapGetters('mallConfig',{
+                vip: 'getVip'
+            }),
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+			...mapGetters('mallConfig', {
+				getVideo: 'getVideo'
+			})
+        },
+        methods: {
+            jump(url) {
+                this.$jump({
+                    url: url,
+                    open_type: 'navigate'
+                })
+            },
+            jump_router(data) {
+				// #ifndef MP-BAIDU
+				if (data.goodsWarehouse.video_url && this.getVideo == 1) {
+					uni.navigateTo({
+						url: `/pages/goods/video?goods_id=${data.id}&sign=advance`
+					});
+				} else {
+					uni.navigateTo({
+						url: `/plugins/advance/detail/detail?id=${data.id}`
+					})
+				}
+				// #endif
+
+				// #ifdef MP-BAIDU
+				uni.navigateTo({
+					url: `/plugins/advance/detail/detail?id=${data.id}`
+				})
+				// #endif
+            },
+			loadData() {
+				let para = {
+                    type: this.page_id === 0 ? 'mall' : 'diy',
+                    key: 'advance',
+                    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);
+                        }
+                    }
+                })
+            }
+        },
+        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">
+	.index-advance {
+		background-color: #f7f7f7;
+		margin-bottom: #{20upx};
+		.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 {
+			overflow-x: auto;
+			-webkit-overflow-scrolling: touch;
+			background-color: #f7f7f7;
+			margin-top: #{4rpx};
+			.item {
+				margin-right: #{4rpx};
+				font-size: $uni-font-size-general-one;
+				width: #{260rpx};
+				padding:  #{0 20rpx 20upx 20rpx};
+				background-color: white;
+				overflow: hidden;
+				.cover-pic {
+					width: #{220rpx};
+					height: #{331upx};
+					display: block;
+					margin-top: #{20rpx};
+					position: relative;
+					.out-dialog {
+						width: #{220rpx};
+						height: #{220rpx};
+						position: absolute;
+						top: 0;
+						left: 0;
+						z-index: 10;
+						will-change: transform;
+						background-color: rgba(0,0,0,.5);
+						image {
+							width: #{220rpx};
+							height: #{220rpx};
+						}
+					}
+				}
+				
+				.title {
+					width: #{224rpx};
+					height: #{64rpx};
+					font-size: #{25rpx};
+					line-height: #{32upx};
+					margin-top: #{10upx};
+					color: $uni-important-color-black;
+				}
+				.content {
+					height: calc(100% - #{331upx});
+				}
+				.member-price {
+					height: #{28upx};
+					margin-bottom: #{8upx};
+					margin-top: #{4upx};
+				}
+				.price {
+
+					.text {
+						text-overflow: ellipsis;
+						-webkit-box-orient: vertical;
+						-webkit-line-clamp: 1;
+						overflow: hidden;
+						line-height: 1;
+						vertical-align: sub;
+					}
+				}
+				.old-price {
+					font-size: #{21upx};
+					line-height: 1;
+					color: #999999;
+					text-decoration:line-through;
+					margin: #{5upx 0 20upx 0};
+				}
+				.des-price {
+					display: inline-block;
+					font-size: #{19rpx};
+					color: #ffffff;
+					/*line-height: 1;*/
+					border-radius: #{7rpx};
+					padding: #{0rpx 5rpx};
+					word-break: break-all;
+					text-overflow: ellipsis;
+					-webkit-box-orient: vertical;
+					-webkit-line-clamp: 1;
+					overflow: hidden;
+					margin: #{4upx 0};
+				}
+			}
+		}
+	}
+	/*.u-icon {*/
+	/*	width: 46upx;*/
+	/*	height: 46upx;*/
+	/*	margin-right: 8upx;*/
+	/*}*/
+	/*.u-top {*/
+	/*	font-size: 28upx;*/
+	/*	color: #ff4544;*/
+	/*}*/
+</style>

+ 287 - 0
components/page-component/app-index-booking/app-index-booking.vue

xqd
@@ -0,0 +1,287 @@
+<template>
+    <view class="app-index-booking">
+        <view class="top dir-left-nowrap cross-center">
+            <image class="box-grow-0" src="../../../static/image/icon/icon-home-booking.png"></image>
+            <view class="box-grow-1">预约</view>
+            <app-form-id @click="jump(`/plugins/book/index/index`)">
+                <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>
+            </app-form-id>
+        </view>
+        <view v-if="style == '1'" class="dir-left-nowrap list">
+            <block v-for="(book, key) in newData.list" :key="key">
+                <app-form-id @click="router_jump(book)">
+                    <view class="box-grow-0 dir-top-nowrap item">
+                        <view class="box-grow-0 cover-pic">
+                            <view class="out-dialog" v-if="book.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="book.cover_pic" width="224rpx" height="224rpx"></app-image>
+                        </view>
+                        <view class="box-grow-0 title t-omit-two">{{book.name}}</view>
+                        <view class="content dir-top-nowrap main-right">
+                            <view class="box-grow-1 dir-top-nowrap main-right price">
+                                <view v-if="book.is_level == 1">
+                                    <app-member-price :price="book.level_price"></app-member-price>
+                                </view>
+                                <app-sup-vip :is_vip_card_user="book.vip_card_appoint.is_vip_card_user" margin="4rpx 0 0" v-if="book.vip_card_appoint.discount > 0" :discount="book.vip_card_appoint.discount"></app-sup-vip>
+                                <view :class="getTheme + '-m-text ' + getTheme">{{book.price_content}}</view>
+                            </view>
+                        </view>
+                    </view>
+                </app-form-id>
+            </block>
+        </view>
+        <view v-if="style === '2'">
+            <app-goods-list :theme="getTheme" :list="newData.list" sign="booking"></app-goods-list>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapState, mapGetters} from 'vuex';
+    import appGoodsList from "../app-goods-list/app-goods-list.vue";
+
+    export default {
+        name: "app-index-booking",
+        props: {
+            theme: String,
+            index: Number,
+            page_id: Number,
+            is_required: Boolean
+        },
+        data() {
+            return {
+                newData: {},
+                style: '1',
+                goods_num: 20
+            };
+        },
+        components: {
+            appGoodsList
+        },
+        computed: {
+            ...mapState({
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+            }),
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo'
+            }),
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+        },
+        methods: {
+            jump(url) {
+                this.$jump({
+                    url: url,
+                    open_type: 'navigate'
+                })
+            },
+            router_jump(data) {
+                // #ifndef MP-BAIDU
+                if (data.video_url && this.getVideo == 1) {
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=booking`
+                    });
+                } else {
+                    uni.navigateTo({
+                        url: data.page_url
+                    });
+                }
+                // #endif
+
+                // #ifdef MP-BAIDU
+                uni.navigateTo({
+                    url: data.page_url
+                });
+                // #endif
+            },
+
+            loadData() {
+                let para = {
+                    type: this.page_id === 0 ? 'mall' : 'diy',
+                    key: 'booking',
+                    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(e => {
+                    this.newData = e.data;
+                    if (e.code ===0 && e.data && 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);
+                    }
+                })
+            }
+        },
+
+        created() {
+            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">
+    .vip-price {
+        width: #{148upx};
+        height: #{27upx};
+        margin-top: #{4upx};
+        .vip-item {
+            height: #{27upx};
+            width: 50%;
+        }
+        .vip-left {
+            border-top-left-radius: #{13upx};
+            border-bottom-left-radius: #{13upx};
+            background-color: #4e4040;
+            position: relative;
+        }
+        .vip-right {
+            border-top-right-radius: #{13upx};
+            border-bottom-right-radius: #{13upx};
+            background: linear-gradient(45deg, #edc9a8, #fdebde);
+            font-size: #{18upx};
+            line-height: #{27upx};
+            text-align: center;
+            color: #4e4040;
+        }
+        .vip-icon {
+            width: #{51upx};
+            height: #{14upx};
+            position:absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+        }
+    }
+    .app-index-booking {
+        background-color: #f7f7f7;
+        margin-bottom: #{20upx};
+        .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 {
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
+            background-color: #f7f7f7;
+            margin-top: #{4rpx};
+            .item {
+                margin-right: #{4rpx};
+                font-size: $uni-font-size-general-one;
+                width: #{260rpx};
+                padding:  #{0 20rpx 20upx 20rpx};
+                background-color: white;
+                overflow: hidden;
+                .cover-pic {
+                    width: #{224rpx};
+                    height: #{224rpx};
+                    display: block;
+                    margin-top: #{20rpx};
+                    position: relative;
+                    .out-dialog {
+                        width: #{220rpx};
+                        height: #{220rpx};
+                        position: absolute;
+                        top: 0;
+                        left: 0;
+                        z-index: 10;
+                        will-change: transform;
+                        background-color: rgba(0,0,0,.5);
+                        image {
+                            width: #{220rpx};
+                            height: #{220rpx};
+                        }
+                    }
+                }
+
+                .title {
+                    width: #{224rpx};
+                    height: #{64rpx};
+                    font-size: #{25rpx};
+                    line-height: #{32upx};
+                    margin-top: #{10upx};
+                    color: $uni-important-color-black;
+                }
+                .content {
+                    height: #{100rpx};
+                }
+                .member-price {
+                    height: #{28upx};
+                    margin-bottom: #{8upx};
+                    margin-top: #{4upx};
+                }
+                .price {
+                    .text {
+                        text-overflow: ellipsis;
+                        -webkit-box-orient: vertical;
+                        -webkit-line-clamp: 1;
+                        overflow: hidden;
+                        line-height: 1;
+                        vertical-align: sub;
+                    }
+                }
+                .old-price {
+                    font-size: #{21upx};
+                    line-height: 1;
+                    color: #999999;
+                    text-decoration:line-through;
+                    margin: #{5upx 0 20upx 0};
+                }
+                .des-price {
+                    display: inline-block;
+                    font-size: #{19rpx};
+                    color: #ffffff;
+                    line-height: 1;
+                    border-radius: #{7rpx};
+                    padding: #{5rpx 5rpx};
+                    word-break: break-all;
+                    text-overflow: ellipsis;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 1;
+                    overflow: hidden;
+                    margin: #{4upx 0};
+                }
+            }
+        }
+    }
+</style>

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

xqd
@@ -0,0 +1,129 @@
+<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.theme" :themeObject="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');
+                        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>

+ 351 - 0
components/page-component/app-index-flash-sale/app-index-flash-sale.vue

xqd
@@ -0,0 +1,351 @@
+<template>
+    <view class="index-advance">
+        <view class="top dir-left-nowrap cross-center" @click="jump(`/plugins/flash_sale/index/index`)">
+            <image class="box-grow-0" src="../../../static/image/icon/flash_sale.png"></image>
+            <view class="box-grow-1 dir-left-nowrap">
+                <view>限时抢购</view>
+                <template v-if="timing && (newData.activity || newData.next_activity)">
+                    <view class="dir-left-nowrap time-box">
+                        <view class="main-center cross-center time">{{time_str.day}}</view>
+                        <view class="main-center cross-center maohao">:</view>
+                        <view class="main-center cross-center time">{{time_str.hou}}</view>
+                        <view class="main-center cross-center maohao">:</view>
+                        <view class="main-center cross-center time">{{time_str.min}}</view>
+                        <view class="main-center cross-center maohao">:</view>
+                        <view class="main-center cross-center time">{{time_str.sec}}</view>
+                    </view>
+                    <view :class="time_str ? 'box-grow-0' : 'box-grow-1'" style="color: #353535;margin-left: 10rpx;font-size: 22rpx;">{{newData.str}}</view>
+                </template>
+            </view>
+            <view class="box-grow-0">
+                <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>
+        <view v-if="style == '1'" class="dir-left-nowrap list">
+
+            <block v-for="(item, key) in newData.list" :key="key">
+                <view class="box-grow-0 item" @click="jump_router(item)" >
+                    <view class="cover-pic">
+                        <app-image :img-src="item.goodsWarehouse.cover_pic" width="220rpx" height="220rpx"></app-image>
+                        <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>
+                        <view class="title  t-omit-two">
+                            {{item.name}}
+                        </view>
+                        <view class="des-price" :class="theme+ '-m-back ' + theme">
+                            {{item.discount_type == 1 ? item.min_discount + '折' : '减' + item.min_discount + '元'}}
+                        </view>
+                    </view>
+                    <view class="content dir-top-nowrap main-right">
+                        <view class="member-price" v-if="item.is_level == 1">
+                            <app-member-price :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 8rpx"
+                                     v-if="item.vip_card_appoint.discount"
+                                     :discount="item.vip_card_appoint.discount"></app-sup-vip>
+                        <view :class="theme+ '-m-text ' + theme"  class="price">
+                            <text class="text">{{item.price_content}}</text>
+                        </view>
+                        <view class="old-price">
+                            ¥{{item.goodsWarehouse.original_price}}
+                        </view>
+                    </view>
+                </view>
+            </block>
+        </view>
+        <view v-if="style === '2'">
+            <app-goods-list :theme="theme" :list="newData" sign="flash_sale"></app-goods-list>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapState, mapGetters} from 'vuex';
+    import appGoodsList from "../app-goods-list/app-goods-list.vue";
+
+    export default {
+        name: "app-index-flash-sale",
+        props: {
+            theme: String,
+            index: Number,
+            page_id: Number,
+            is_required: Boolean,
+            pageHide: Boolean,
+        },
+        components: {
+            appGoodsList
+        },
+        data() {
+            return {
+                newData: {},
+                style: '1',
+                goods_num: 20,
+                time_str:{
+                    day: '00',
+                    hou: '00',
+                    min: '00',
+                    sec: '00'
+                },
+                timing: null,
+            };
+        },
+        computed: {
+            ...mapState({
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+                mall: state => state.mallConfig.mall,
+            }),
+            ...mapGetters('mallConfig',{
+                vip: 'getVip'
+            }),
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo'
+            })
+        },
+        methods: {
+            jump(url) {
+                uni.navigateTo({
+                    url: url
+                });
+            },
+            jump_router(data) {
+                // #ifndef MP-BAIDU
+                if (data.goodsWarehouse.video_url && this.getVideo == 1) {
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=flash_sale`
+                    });
+                } else {
+                    uni.navigateTo({
+                        url: `/plugins/flash_sale/goods/goods?id=${data.id}`
+                    })
+                }
+                // #endif
+
+                // #ifdef MP-BAIDU
+                uni.navigateTo({
+                    url: `/plugins/flash_sale/goods/goods?id=${data.id}`
+                })
+                // #endif
+            },
+            loadData() {
+                let para = {
+                    type: this.page_id === 0 ? 'mall' : 'diy',
+                    key: 'flash_sale',
+                    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(e => {
+                    this.newData = e.data;
+                    if (this.newData.activity) {
+                        this.newData.str = '结束';
+                        this.set_time(this.newData.activity.end_at);
+                    } else {
+                        this.newData.str = '开始';
+                        this.set_time(this.newData.next_activity.start_at);
+                    }
+                });
+            },
+            set_time(time_at) {
+                clearInterval(this.timing);
+                let time_str = new Date(time_at.replace(/-/g, '/'));
+                this.now_time(time_str);
+                this.timing = setInterval(() => {
+                    this.now_time(time_str);
+                }, 1000);
+            },
+            now_time(time_str) {
+                let time = time_str.getTime() - new Date().getTime();
+                if (time < 0) {
+                    clearInterval(this.timing);
+                    this.timing = null;
+                }
+                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.time_str.day = day < 10 ? '0' + day : day;
+                this.time_str.hou = hou < 10 ? '0' + hou : hou;
+                this.time_str.min = min < 10 ? '0' + min : min;
+                this.time_str.sec = sec < 10 ? '0' + sec : sec;
+            },
+        },
+        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;
+            this.loadData();
+        },
+        beforeDestroy() {
+            clearInterval(this.timing);
+        },
+        watch: {
+            pageHide: {
+                handler(v) {
+                    if (v) {
+                        clearInterval(this.timing);
+                        return ;
+                    } else {
+                        if (this.newData.activity) {
+                            this.newData.str = '结束';
+                            this.set_time(this.newData.activity.end_at);
+                        } else if (this.newData.next_activity) {
+                            this.newData.str = '开始';
+                            this.set_time(this.newData.next_activity.start_at);
+                        }
+                    }
+                },
+                immediate: true
+            },
+>>>>>>> dev
+        },
+    }
+</script>
+
+<style scoped lang="scss">
+    .index-advance {
+        background-color: #f7f7f7;
+        margin-bottom: #{20upx};
+        .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};
+                background-color: #ff4544;
+            }
+
+            .more {
+                font-size: $uni-font-size-general-two;
+                margin-right: #{12rpx};
+                color: $uni-general-color-two;
+            }
+
+            .icon {
+                width: #{12rpx};
+                height: #{22rpx};
+                display: block;
+            }
+        }
+
+        .list {
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
+            background-color: #f7f7f7;
+            margin-top: #{4rpx};
+            .item {
+                margin-right: #{4rpx};
+                font-size: $uni-font-size-general-one;
+                width: #{260rpx};
+                padding:  #{0 20rpx 20upx 20rpx};
+                background-color: white;
+                overflow: hidden;
+                .cover-pic {
+                    width: #{220rpx};
+                    height: #{331upx};
+                    display: block;
+                    margin-top: #{20rpx};
+                    position: relative;
+                    .out-dialog {
+                        width: #{220rpx};
+                        height: #{220rpx};
+                        position: absolute;
+                        top: 0;
+                        left: 0;
+                        z-index: 10;
+                        will-change: transform;
+                        background-color: rgba(0,0,0,.5);
+                        image {
+                            width: #{220rpx};
+                            height: #{220rpx};
+                        }
+                    }
+                }
+
+                .title {
+                    width: #{224rpx};
+                    height: #{64rpx};
+                    font-size: #{25rpx};
+                    line-height: #{32upx};
+                    margin-top: #{10upx};
+                    color: $uni-important-color-black;
+                }
+                .content {
+                    height: calc(100% - #{355upx});
+                }
+                .member-price {
+                    height: #{28upx};
+                    margin-bottom: #{8upx};
+                    margin-top: #{4upx};
+                }
+                .price {
+                    font-size: 28upx;
+                    .text {
+                        text-overflow: ellipsis;
+                        -webkit-box-orient: vertical;
+                        -webkit-line-clamp: 1;
+                        overflow: hidden;
+                        line-height: 1;
+                        vertical-align: sub;
+                    }
+                }
+                .old-price {
+                    font-size: #{21upx};
+                    line-height: 1;
+                    color: #999999;
+                    text-decoration:line-through;
+                    margin: #{5upx 0 0upx 0};
+                }
+                .des-price {
+                    display: inline-block;
+                    font-size: #{19rpx};
+                    color: #ffffff;
+                    /*line-height: 1;*/
+                    border-radius: #{7rpx};
+                    padding: #{0rpx 5rpx};
+                    word-break: break-all;
+                    text-overflow: ellipsis;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 1;
+                    overflow: hidden;
+                    margin: #{4upx 0};
+                }
+            }
+        }
+    }
+
+    .time-box {
+        margin-left: #{23rpx};
+
+        .time {
+            width: #{32rpx};
+            height: #{34rpx};
+            background-color: #4c4c4c;
+            color: #ffffff;
+            font-size: #{20rpx};
+            border-radius: #{4rpx};
+        }
+
+        .maohao {
+            width: #{20rpx};
+            height: #{34rpx};
+            color: #353535;
+        }
+    }
+</style>

+ 305 - 0
components/page-component/app-index-miaosha/app-index-miaosha.vue

xqd
@@ -0,0 +1,305 @@
+<template>
+    <view class="app-index-miaosha">
+        <view class="dir-left-nowrap cross-center top" @click="jump(`/plugins/miaosha/advance/advance`)">
+            <image class="box-grow-0 img" src="../../../static/image/icon/icon-home-miaosha.png"></image>
+            <view class="box-grow-0 text1">整点秒杀</view>
+            <template v-if="newData.open_date">
+                <view :class="timer ? 'box-grow-0' : 'box-grow-1'">{{newData.str}}</view>
+                <view class="box-grow-1 dir-left-nowrap time-box" v-if="timer">
+                    <view class="main-center cross-center time">{{timer.hour}}</view>
+                    <view class="main-center cross-center maohao">:</view>
+                    <view class="main-center cross-center time">{{timer.min}}</view>
+                    <view class="main-center cross-center maohao">:</view>
+                    <view class="main-center cross-center time">{{timer.sec}}</view>
+                </view>
+            </template>
+            <view class="box-grow-0">
+                <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>
+        <view v-if="style === '1'" class="dir-left-nowrap list">
+            <block v-for="(item, key) in newData.list" :key="key">
+                <app-form-id @click="router_jump(item)">
+                    <view class="box-grow-0 dir-top-nowrap item">
+                        <view class="box-grow-0 cover-pic">
+                            <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="box-grow-0 t-omit-two goods-name">{{item.name}}</view>
+                        <view class="box-grow-1 dir-top-nowrap main-right">
+                            <view v-if="item.is_level == 1">
+                                <app-member-price :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" :discount="item.vip_card_appoint.discount"></app-sup-vip>
+                            <view :class="[getTheme, getTheme + '-m-text']">{{item.price_content}}</view>
+                        </view>
+                    </view>
+                </app-form-id>
+            </block>
+        </view>
+        <view v-if="style === '2'">
+            <app-goods-list :theme="getTheme" :list="newData.list" sign="miaosha"></app-goods-list>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapState, mapGetters} from 'vuex';
+    import routeJump from '../../../core/routeJump.js';
+    import appGoodsList from "../app-goods-list/app-goods-list.vue";
+
+    export default {
+        name: "app-index-miaosha",
+        props: {
+            value: {
+                type: Object,
+                default() {
+                    return {
+                        open_date: null,
+                        list: []
+                    };
+                }
+            },
+            pageHide: Boolean,
+            theme: Object,
+            index: Number,
+            page_id: Number,
+            is_required: Boolean
+        },
+        components: {
+            appGoodsList
+        },
+        data() {
+            return {
+                newData: {
+                    str: ''
+                },
+                style: '1',
+                goods_num: 20,
+                timer: null,
+                time: null,
+                is_vip: true,
+            };
+        },
+        computed: {
+            ...mapState({
+                mall: state => state.mallConfig.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+            }),
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo'
+            }),
+            ...mapGetters('mallConfig', {
+                getTheme: 'getTheme',
+            }),
+        },
+        beforeDestroy() {
+            clearInterval(this.time);
+        },
+        watch: {
+            pageHide: {
+                handler(v) {
+                    if (v) {
+                        clearInterval(this.time);
+                        return ;
+                    }
+
+                },
+                immediate: true
+            },
+        },
+        methods: {
+            jump(url) {
+                routeJump({
+                    open_type: 'navigate',
+                    page_url: url,
+                    params: []
+                })
+            },
+            router_jump(data) {
+                // #ifndef MP-BAIDU
+                if (data.video_url && this.getVideo == 1) {
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=miaosha`
+                    });
+                } else {
+                    uni.navigateTo({
+                        url: data.page_url
+                    });
+                }
+                // #endif
+
+                // #ifdef MP-BAIDU
+                uni.navigateTo({
+                    url: data.page_url
+                });
+                // #endif
+            },
+
+            loadData() {
+                let para = {
+                    type: this.page_id === 0 ? 'mall' : 'diy',
+                    key: 'miaosha',
+                    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(e => {
+                    if (e.code === 0 && e.data) {
+                        this.newData = e.data;
+                        this.newData.str = '00:00:00 点场';
+                        let timenow = new Date();//获取当前时间
+                        if ((new Date(this.newData.open_date)).getDate() != timenow.getDate()) {
+                            this.newData.str = '预告 ' + this.newData.open_date + ' ' + this.newData.open_time + '点场';
+                        } else if (this.newData.open_time != timenow.getHours()) {
+                            this.newData.str = '预告 ' + this.newData.open_time + '点场';
+                        } else {
+                            let timelog = this.newData.date_time * 1000 - timenow.getTime();//时间差的所有毫秒数
+                            this.time = setInterval(() => {
+                                timelog -= 1000;
+                                this.newData.str = this.newData.open_time + '点场';
+                                if (timelog <= 0) {
+                                    clearInterval(this.time);
+                                    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);
+                        }
+                    }
+                })
+            }
+        },
+
+        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;
+            this.loadData();
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .more {
+        font-size: $uni-font-size-general-two;
+        margin-right: #{12rpx};
+        color: $uni-general-color-two;
+    }
+
+    .icon {
+        width: #{12rpx};
+        height: #{22rpx};
+        display: block;
+    }
+
+    .app-index-miaosha {
+        background-color: $uni-weak-color-two;
+
+        .top {
+            height: #{80rpx};
+            padding: 0 #{24rpx};
+            background-color: #ffffff;
+            font-size: $uni-font-size-weak-one;
+
+            .img {
+                width: #{46rpx};
+                height: #{46rpx};
+                display: block;
+                margin-right: #{16rpx};
+            }
+
+            .text1 {
+                color: #ff8831;
+                font-size: $uni-font-size-general-one;
+                margin-right: #{20rpx};
+            }
+
+            .time-box {
+                margin-left: #{23rpx};
+
+                .time {
+                    width: #{32rpx};
+                    height: #{34rpx};
+                    background-color: #4c4c4c;
+                    color: #ffffff;
+                    font-size: #{20rpx};
+                    border-radius: #{4rpx};
+                }
+
+                .maohao {
+                    width: #{20rpx};
+                    height: #{34rpx};
+                }
+            }
+        }
+
+        .list {
+            margin-top: #{8rpx};
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
+
+            .item {
+                background-color: #ffffff;
+                margin-right: #{8rpx};
+                font-size: $uni-font-size-general-one;
+                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};
+                        }
+                    }
+                }
+
+                .goods-name {
+                    width: #{220rpx};
+                    font-size: $uni-font-size-general-two;
+                }
+
+                .price {
+                    text-decoration: line-through;
+                    color: $uni-general-color-two;
+                    margin-bottom: #{15rpx};
+                    font-size: $uni-font-size-weak-one;
+
+                    &:before {
+                        content: '¥';
+                    }
+                }
+            }
+        }
+    }
+</style>

+ 264 - 0
components/page-component/app-index-pick/app-index-pick.vue

xqd
@@ -0,0 +1,264 @@
+<template>
+    <view class="index-advance">
+        <view class="top dir-left-nowrap cross-center" @click="jump(`/plugins/pick/index/index`)">
+            <image class="box-grow-0" src="../../../static/image/icon/pick.png"></image>
+            <view class="box-grow-1">N元任选</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.list" :key="key">
+                <view class="box-grow-0 item" @click="jump_router(item)" >
+                    <view class="cover-pic">
+                        <app-image :img-src="item.goodsWarehouse.cover_pic" width="220rpx" height="220rpx"></app-image>
+                        <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>
+                        <view class="title  t-omit-two">
+                            {{item.name}}
+                        </view>
+                        <view class="des-price" v-if="item.use_attr == 0" :class="theme + '-m-back ' + theme">
+                            {{newData.activity.rule_price}}元任选{{newData.activity.rule_num}}件
+                        </view>
+                        <view class="des-price" v-else-if="item.use_attr == 1" :class="theme + '-m-back ' + theme">
+                            {{newData.activity.rule_price}}元任选{{newData.activity.rule_num}}件
+                        </view>
+                    </view>
+                    <view class="content dir-top-nowrap main-right">
+                        <view  class="price" :class="theme + '-m-text ' + theme">
+                            <text class="text"  style="font-size: 28rpx;">¥{{item.price}}</text>
+                        </view>
+                        <view class="old-price">
+                            ¥{{item.original_price}}
+                        </view>
+                    </view>
+                </view>
+            </block>
+        </view>
+        <view v-if="style === '2'">
+            <app-goods-list :detail="newData.activity" :theme="theme" :list="newData.list" sign="pick"></app-goods-list>
+        </view>
+    </view>
+</template>
+
+<script>
+    import {mapState, mapGetters} from 'vuex';
+    import appGoodsList from "../app-goods-list/app-goods-list.vue";
+
+    export default {
+        name: "app-index-pick",
+        props: {
+
+            activity: {
+                type: Object,
+                default() {
+                    return {}
+                }
+            },
+            theme: String,
+            index: Number,
+            is_required:Boolean,
+            page_id: Number
+        },
+        components: {
+            appGoodsList
+        },
+        data() {
+            return {
+                newData: [],
+                style: '1',
+                goods_num: 20
+            };
+        },
+        computed: {
+            ...mapState({
+                appImg: state => state.mallConfig.__wxapp_img.mall,
+                appSetting: state => state.mallConfig.mall.setting,
+                mall: state => state.mallConfig.mall,
+            }),
+            ...mapGetters('mallConfig',{
+                vip: 'getVip'
+            }),
+            ...mapGetters('mallConfig', {
+                getVideo: 'getVideo'
+            }),
+        },
+        methods: {
+            jump(url) {
+                this.$jump({
+                    url: url,
+                    open_type: 'navigate'
+                })
+            },
+            jump_router(data) {
+                // #ifndef MP-BAIDU
+                if (data.goodsWarehouse.video_url && this.getVideo == 1) {
+                    uni.navigateTo({
+                        url: `/pages/goods/video?goods_id=${data.id}&sign=pick`
+                    });
+                } else {
+                    uni.navigateTo({
+                        url: `/plugins/pick/detail/detail?goods_id=${data.id}`
+                    })
+                }
+                // #endif
+
+                // #ifdef MP-BAIDU
+                uni.navigateTo({
+                    url: `/plugins/pick/detail/detail?goods_id=${data.id}`
+                })
+                // #endif
+            },
+            loadData() {
+                let para = {
+                    type: this.page_id === 0 ? 'mall' : 'diy',
+                    key: 'pick',
+                    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(e => {
+                    this.newData = e.data;
+                    if (e.code ===0 && e.data && this.page_id === 0) {
+                        let storage = this.$storage.getStorageSync('INDEX_MALL');
+                        storage.home_pages[this.index].data = this.newData;
+                        this.$storage.setStorageSync('INDEX_MALL', storage);
+                    }
+                })
+            }
+        },
+        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].data;
+            }
+        }
+    }
+</script>
+
+<style scoped lang="scss">
+    .index-advance {
+        background-color: #f7f7f7;
+        margin-bottom: #{20upx};
+        .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 {
+            overflow-x: auto;
+            -webkit-overflow-scrolling: touch;
+            background-color: #f7f7f7;
+            margin-top: #{4rpx};
+            .item {
+                margin-right: #{4rpx};
+                font-size: $uni-font-size-general-one;
+                width: #{260rpx};
+                padding:  #{0 20rpx 20upx 20rpx};
+                background-color: white;
+                overflow: hidden;
+                .cover-pic {
+                    width: #{220rpx};
+                    height: #{331upx};
+                    display: block;
+                    margin-top: #{20rpx};
+                    position: relative;
+                    .out-dialog {
+                        width: #{220rpx};
+                        height: #{220rpx};
+                        position: absolute;
+                        top: 0;
+                        left: 0;
+                        z-index: 10;
+                        will-change: transform;
+                        background-color: rgba(0,0,0,.5);
+                        image {
+                            width: #{220rpx};
+                            height: #{220rpx};
+                        }
+                    }
+                }
+
+                .title {
+                    width: #{224rpx};
+                    height: #{64rpx};
+                    font-size: #{25rpx};
+                    line-height: #{32upx};
+                    margin-top: #{10upx};
+                    color: $uni-important-color-black;
+                }
+                .content {
+                    height: calc(100% - #{331upx});
+                }
+                .member-price {
+                    height: #{28upx};
+                    margin-bottom: #{8upx};
+                    margin-top: #{4upx};
+                }
+                .price {
+                    .text {
+                        text-overflow: ellipsis;
+                        -webkit-box-orient: vertical;
+                        -webkit-line-clamp: 1;
+                        overflow: hidden;
+                        line-height: 1;
+                        vertical-align: sub;
+                    }
+                }
+                .old-price {
+                    font-size: #{21upx};
+                    line-height: 1;
+                    color: #999999;
+                    text-decoration:line-through;
+                    margin: #{5upx 0 20upx 0};
+                }
+                .des-price {
+                    display: inline-block;
+                    font-size: #{19rpx};
+                    color: #ffffff;
+                    line-height: 1;
+                    border-radius: #{7rpx};
+                    padding: #{5rpx 5rpx};
+                    word-break: break-all;
+                    text-overflow: ellipsis;
+                    -webkit-box-orient: vertical;
+                    -webkit-line-clamp: 1;
+                    overflow: hidden;
+                    margin: #{4upx 0};
+                }
+            }
+        }
+    }
+</style>

Some files were not shown because too many files changed in this diff