Kaynağa Gözat

初始化提交

abiao 4 yıl önce
ebeveyn
işleme
56efb44a51
100 değiştirilmiş dosya ile 25086 ekleme ve 2 silme
  1. 23 2
      README.md
  2. 3 0
      babel.config.js
  3. 7 0
      config/host.js
  4. 79 0
      config/nginx/prod.conf
  5. 42 0
      config/nginx/test.conf
  6. 4 0
      config/url.js
  7. 12337 0
      package-lock.json
  8. 46 0
      package.json
  9. BIN
      public/favicon.ico
  10. 38 0
      public/index.html
  11. 111 0
      src/App.vue
  12. 9 0
      src/api/article.js
  13. 8 0
      src/api/index.js
  14. 4 0
      src/api/product.js
  15. 18 0
      src/api/user.js
  16. BIN
      src/assets/images/address/address.png
  17. BIN
      src/assets/images/order/location.png
  18. BIN
      src/assets/images/order/order_icon.png
  19. BIN
      src/assets/images/order/service_icon.png
  20. BIN
      src/assets/images/pay/select_icon.png
  21. BIN
      src/assets/images/pay/weixin_icon.png
  22. BIN
      src/assets/img/000.png
  23. BIN
      src/assets/img/bg.png
  24. BIN
      src/assets/img/bg1.png
  25. BIN
      src/assets/img/bg2.png
  26. BIN
      src/assets/img/h.png
  27. BIN
      src/assets/img/icon.png
  28. BIN
      src/assets/img/icon1.png
  29. BIN
      src/assets/img/icon2.png
  30. BIN
      src/assets/img/member.png
  31. BIN
      src/assets/img/member1.png
  32. BIN
      src/assets/img/p2.jpg
  33. BIN
      src/assets/img/p3.png
  34. BIN
      src/assets/img/q.png
  35. BIN
      src/assets/img/right.png
  36. BIN
      src/assets/img/success.png
  37. BIN
      src/assets/img/vip.png
  38. BIN
      src/assets/img/vip1.png
  39. BIN
      src/assets/img/wx.png
  40. BIN
      src/assets/logo.png
  41. 64 0
      src/assets/style/product.less
  42. 96 0
      src/assets/style/reset.css
  43. 99 0
      src/assets/style/reset.less
  44. 73 0
      src/axios.tool.js
  45. 4040 0
      src/common/city.js
  46. 20 0
      src/common/errorHandler.js
  47. 162 0
      src/common/filter.js
  48. 41 0
      src/common/utils.js
  49. 43 0
      src/common/validator.js
  50. 87 0
      src/common/wx.js
  51. 119 0
      src/components/Btn.vue
  52. 49 0
      src/components/Empty.vue
  53. 45 0
      src/components/Footer.vue
  54. 130 0
      src/components/HelloWorld.vue
  55. 85 0
      src/components/dialog/Service.vue
  56. 26 0
      src/main.js
  57. 200 0
      src/router/index.js
  58. 19 0
      src/store/actions/article.js
  59. 8 0
      src/store/actions/index.js
  60. 10 0
      src/store/actions/product.js
  61. 41 0
      src/store/actions/user.js
  62. 3 0
      src/store/getters/article.js
  63. 8 0
      src/store/getters/index.js
  64. 1 0
      src/store/getters/product.js
  65. 11 0
      src/store/getters/user.js
  66. 13 0
      src/store/index.js
  67. 24 0
      src/store/modules/article.js
  68. 10 0
      src/store/modules/index.js
  69. 14 0
      src/store/modules/product.js
  70. 40 0
      src/store/modules/user.js
  71. 12 0
      src/views/Home.vue
  72. 46 0
      src/views/New.vue
  73. 118 0
      src/views/article/Detail.vue
  74. 118 0
      src/views/article/List.vue
  75. 299 0
      src/views/pay/CreateOrder.vue
  76. 305 0
      src/views/pay/Index.vue
  77. 93 0
      src/views/pay/Pay.vue
  78. 185 0
      src/views/pay/PayIntegral.vue
  79. 83 0
      src/views/pay/Success.vue
  80. 115 0
      src/views/pay/components/Address.vue
  81. 202 0
      src/views/pay/components/Btn.vue
  82. 114 0
      src/views/pay/components/Money.vue
  83. 380 0
      src/views/product/Detail.vue
  84. 116 0
      src/views/product/List.vue
  85. 504 0
      src/views/user/User.vue
  86. 282 0
      src/views/user/address/Detail.vue
  87. 262 0
      src/views/user/address/List.vue
  88. 109 0
      src/views/user/address/components/Switch.vue
  89. 275 0
      src/views/user/balance/Account.vue
  90. 69 0
      src/views/user/balance/Success.vue
  91. 298 0
      src/views/user/balance/Withdraw.vue
  92. 151 0
      src/views/user/integral/Exchange.vue
  93. 410 0
      src/views/user/integral/Integral.vue
  94. 527 0
      src/views/user/invite/Income.vue
  95. 281 0
      src/views/user/invite/Invite.vue
  96. 411 0
      src/views/user/invite/Recode.vue
  97. 262 0
      src/views/user/member/Member.vue
  98. 577 0
      src/views/user/order/Detail.vue
  99. 172 0
      src/views/user/order/List.vue
  100. 0 0
      src/views/user/order/components/Items.vue

+ 23 - 2
README.md

@@ -1,3 +1,24 @@
-# fohow_wx
+# family
 
-fohow微信端代码
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 3 - 0
babel.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"]
+};

+ 7 - 0
config/host.js

@@ -0,0 +1,7 @@
+import host from './url.js'
+let index = process.env.VUE_APP_API
+if (process.env.NODE_ENV === 'development') {
+  index = host
+}
+
+export default index

+ 79 - 0
config/nginx/prod.conf

@@ -0,0 +1,79 @@
+server {
+    listen       80;
+    server_name  worldm.labitumall.com;
+    #access_log  /opt/apps/wine-world/labitu-wx/logs/ngx_access.log  main;# buffer=16k;
+    #error_log   /opt/apps/wine-world/labitu-wx/logs/ngx_error.log;
+
+    set $platform pc;
+    if ($http_user_agent ~* "(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|MicroMessenger|WindowsWechat") {
+      set $platform mobile;
+    }
+
+    root /opt/apps/dist/worldm.labitumall.com/;
+
+
+    location /.well-known/pki-validation/ {
+      alias /opt/apps/wine-world/labitu-wx/static/;
+    }
+
+
+    location =/MP_verify_2T3baURk4a7z6l8x.txt {
+      root /opt/apps/wine-world/labitu-wx/static/;
+      #expires  30d;
+    }
+
+
+    location =/favicon.ico {
+      root /opt/apps/dist/worldm.labitumall.com/static/;
+      expires  30d;
+    }
+    location /static/js {
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+      gzip_static on;
+    }
+    location /static/css {
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+      gzip_static on;
+    }
+    location /static/img {
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+    }
+
+    location / {
+      try_files $uri $uri/ /index.html;
+      expires  30d;
+      #rewrite ^(.*)$ /static/maintain.html break;
+      
+    }
+
+}
+
+# HTTPS server
+server {
+    listen       443 ssl;
+    server_name  worldm.labitumall.com;
+    charset utf-8;
+    #ssl on;
+    ssl_certificate      /opt/apps/wine-world/labitu-wx/static/4831150_worldm.labitumall.com.pem;
+    ssl_certificate_key  /opt/apps/wine-world/labitu-wx/static/4831150_worldm.labitumall.com.key;
+
+    ssl_session_cache    shared:SSL:1m;
+    ssl_session_timeout  5m;
+
+    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+    ssl_prefer_server_ciphers  on;
+
+    location / {
+        #proxy_next_upstream off;
+        #proxy_set_header    X-Real-IP           $remote_addr;
+        #proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
+        #proxy_set_header    Host                $host;
+        #proxy_http_version  1.1;
+        #proxy_set_header    Connection  "";
+        proxy_pass           http://worldm.labitumall.com;
+    }
+}

+ 42 - 0
config/nginx/test.conf

@@ -0,0 +1,42 @@
+server {
+    listen       80;
+    server_name  tworldm.labitumall.com;
+
+    set $platform pc;
+        
+  	if ($http_user_agent ~* "(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino") {
+  	    set $platform mobile;
+  	}
+
+    root /opt/apps/dist/worldm.labitumall.com/;
+
+    location /static/js {
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+      gzip_static on;
+    }
+
+    location /static/css {
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+      gzip_static on;
+    }
+    
+    location /static/img {
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+    }
+
+    location =/favicon.ico { 
+      root /opt/apps/dist/worldm.labitumall.com;
+      expires  30d;
+    }
+
+    location / {
+      try_files $uri $uri/ /index.html;
+      expires  30d;
+      #rewrite ^(.*)$ /static/authpay.htm break;
+      
+    }
+
+}

+ 4 - 0
config/url.js

@@ -0,0 +1,4 @@
+// 测试
+export default 'https://tworldapi.labitumall.com/'
+// 正式
+// export default 'https://worldapi.labitumall.com/'

Dosya farkı çok büyük olduğundan ihmal edildi
+ 12337 - 0
package-lock.json


+ 46 - 0
package.json

@@ -0,0 +1,46 @@
+{
+  "name": "family",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve --mode dev",
+    "build:pro": "vue-cli-service build --mode pro",
+    "build:test": "vue-cli-service build --mode test",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "accounting": "^0.4.1",
+    "axios": "^0.21.0",
+    "clipboard": "^2.0.6",
+    "core-js": "^3.6.5",
+    "px2rem-loader": "^0.1.9",
+    "qs.js": "^0.1.12",
+    "vant": "^2.10.14",
+    "vconsole": "^3.3.4",
+    "vue": "^2.6.11",
+    "vue-router": "^3.2.0",
+    "vuex": "^3.4.0",
+    "weixin-js-sdk": "^1.6.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-plugin-eslint": "~4.5.0",
+    "@vue/cli-plugin-router": "~4.5.0",
+    "@vue/cli-plugin-vuex": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "@vue/eslint-config-prettier": "^6.0.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^6.7.2",
+    "eslint-plugin-prettier": "^3.1.3",
+    "eslint-plugin-vue": "^6.2.2",
+    "less": "^3.0.4",
+    "less-loader": "^5.0.0",
+    "lib-flexible": "^0.3.2",
+    "postcss-px2rem": "^0.3.0",
+    "prettier": "^1.19.1",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "optionalDependencies": {
+    "fsevents": "*"
+  }
+}

BIN
public/favicon.ico


+ 38 - 0
public/index.html

@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title></title>
+    <script>
+      (function() {
+        function adapt(designWidth, rem2px){
+          var d = window.document.createElement('div');
+          d.style.width = '1rem';
+          d.style.display = "none";
+          var head = window.document.getElementsByTagName('head')[0];
+          head.appendChild(d);
+          var defaultFontSize = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'));
+          d.remove();
+          document.documentElement.style.fontSize = window.innerWidth / designWidth * rem2px / defaultFontSize * 100 + '%';
+          var st = document.createElement('style');
+          var portrait = "@media screen and (min-width: "+window.innerWidth+"px) {html{font-size:"+ ((window.innerWidth/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}";
+          var landscape = "@media screen and (min-width: "+window.innerHeight+"px) {html{font-size:"+ ((window.innerHeight/(designWidth/rem2px)/defaultFontSize)*100) +"%;}}"
+          st.innerHTML = portrait + landscape;
+          head.appendChild(st);
+          return defaultFontSize
+        };
+        var defaultFontSize = adapt(750, 100);
+      })();
+    </script>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but ox-new doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 111 - 0
src/App.vue

@@ -0,0 +1,111 @@
+<template>
+  <div id="app">
+    <transition name="view-loading">
+      <router-view v-if="show" class="router-view" />
+    </transition>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+import url from '../config/host'
+// import { isMicroMessenger } from '@/common/utils'
+import Weixin from '@/common/wx'
+export default {
+  name: 'app',
+  data () {
+    return {
+      show: false,
+      id: 0,
+      wx: null
+    }
+  },
+  computed: {
+    ...mapGetters(['userCheck'])
+  },
+  created () {
+    if (this.$route.query.invite_id) {
+      this.id = this.$route.query.invite_id
+    }
+    this.$get({
+      url: 'v1/user/check',
+      data: {
+      }
+    }).then((res) => {
+      console.log(res)
+      let isWx = navigator.userAgent.indexOf('MicroMessenger') > -1
+      if (isWx && !res.wx_user_id) {
+        console.log(url + '/login/mp?cb=' + window.location.href)
+        window.location.href = url + '/login/mp?cb=' + encodeURIComponent(window.location.href)
+      }
+      if (res.wx_user_id >= 0) {
+        this.show = true
+        if (this.$route.query.invite_id) {
+          this.getinviter()
+        }
+      }
+    })
+    console.log(process.env.NODE_ENV)
+    this.wx = new Weixin()
+    // this.$store.dispatch('userCheck')
+  },
+  methods: {
+    getinviter () {
+      this.$put({
+        url: `v1/wxuser/inviter/${this.id}`,
+        data: {}
+      }).then((res) => {
+        console.log(res)
+      })
+    },
+  },
+  watch: {
+    'userCheck': function (val) {
+      // let isWx = navigator.userAgent.indexOf('MicroMessenger') > -1
+      // if (isWx && !val.wx_user_id) {
+      //   console.log(url + '/login/mp?cb=' + window.location.href)
+      //   window.location.href = url + '/login/mp?cb=' + encodeURIComponent(window.location.href)
+      // }
+      // if (val.wx_user_id >= 0) {
+      //   this.show = true
+      //   if (this.$route.query.invite_id) {
+      //     this.getinviter()
+      //   }
+      // }
+    }
+  },
+  components: {
+  }
+}
+</script>
+<style lang="less">
+.view-loading-enter-active, .view-loading-leave-active {
+  transition: all 0.5s;
+}
+.view-loading-enter, .view-loading-leave-to {
+  opacity: 0;
+}
+.router-view {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+}
+#app {
+  font-family: Avenir, Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  // text-align: center;
+  color: #2c3e50;
+}
+#nav {
+  padding: 30px;
+  a {
+    font-weight: bold;
+    color: #2c3e50;
+
+    &.router-link-exact-active {
+      color: #42b983;
+    }
+  }
+}
+</style>

+ 9 - 0
src/api/article.js

@@ -0,0 +1,9 @@
+import axios from 'axios'
+
+export const getArticleList = ({ id, page, per_page, cache }, cb) => {
+  axios.get(`v1/artcat/${id}/articles`, {params: {page, per_page, cache}}).then(cb).catch((err) => { console.log(err) })
+}
+
+export const getArticleDetail = ({ id }, cb) => {
+  axios.get(`v1/article/${id}`).then(cb).catch((err) => { console.log(err) })
+}

+ 8 - 0
src/api/index.js

@@ -0,0 +1,8 @@
+const context = require.context('./', false, /\.js$/)
+const keys = context.keys().filter(item => item !== './index.js') || []
+
+const api = keys.reduce((memo, key) => {
+  return Object.assign({}, memo, context(key))
+}, {})
+
+export default api

+ 4 - 0
src/api/product.js

@@ -0,0 +1,4 @@
+import axios from 'axios'
+export const getProductDetail = ({ id }, cb) => {
+  axios.get(`v1/project/${id}/view`).then(cb).catch((err) => { console.log(err) })
+}

+ 18 - 0
src/api/user.js

@@ -0,0 +1,18 @@
+import axios from 'axios'
+export const userCheck = (cb) => {
+  axios.get('v1/user/check').then(cb).catch((err) => { console.log(err) })
+}
+// 订单模块
+export const getOrderDetail = ({ id }, cb) => {
+  axios.get(`v1/spirit/order/${id}/detail`).then(cb).catch((err) => { console.log(err) })
+}
+export const getOrderList = ({ page, per_page, status }, cb) => {
+  axios.get('v1/spirit/order/list', {params: { page, per_page, status }}).then(cb).catch((err) => { console.log(err) })
+}
+
+export const userInfo = (cb) => {
+  axios.get('v1/user/info').then(cb).catch((err) => { console.log(err) })
+}
+export const userBalanceInfo = (cb) => {
+  axios.get('v1/user/cash/balance/info').then(cb).catch((err) => { console.log(err) })
+}

BIN
src/assets/images/address/address.png


BIN
src/assets/images/order/location.png


BIN
src/assets/images/order/order_icon.png


BIN
src/assets/images/order/service_icon.png


BIN
src/assets/images/pay/select_icon.png


BIN
src/assets/images/pay/weixin_icon.png


BIN
src/assets/img/000.png


BIN
src/assets/img/bg.png


BIN
src/assets/img/bg1.png


BIN
src/assets/img/bg2.png


BIN
src/assets/img/h.png


BIN
src/assets/img/icon.png


BIN
src/assets/img/icon1.png


BIN
src/assets/img/icon2.png


BIN
src/assets/img/member.png


BIN
src/assets/img/member1.png


BIN
src/assets/img/p2.jpg


BIN
src/assets/img/p3.png


BIN
src/assets/img/q.png


BIN
src/assets/img/right.png


BIN
src/assets/img/success.png


BIN
src/assets/img/vip.png


BIN
src/assets/img/vip1.png


BIN
src/assets/img/wx.png


BIN
src/assets/logo.png


+ 64 - 0
src/assets/style/product.less

@@ -0,0 +1,64 @@
+.product_info {
+  width: 100%;
+  background: #fff;
+  padding: .2rem;
+  margin-bottom: .2rem;
+  box-sizing: border-box;
+  img {
+    width: 7.1rem;
+    border-radius: 0.08rem;
+  }
+  .pic {
+    position: relative;
+    margin-bottom: .2rem;
+  }
+  .location {
+    position: absolute;
+    right: .2rem;
+    bottom: .3rem;     
+    height: .44rem;
+    line-height: .44rem;
+    background: rgba(0, 0, 0, 0.5);
+    border-radius: .04rem;
+    font-size: .24rem;
+    color: #fff;
+    vertical-align: middle;
+    padding: 0 .16rem;
+    .van-icon {
+      vertical-align: text-top;
+    }
+  }
+  .desc {
+    text-align: left;
+    font-size: .24rem;
+    color: #999;
+    line-height: .42rem;
+    h3 {
+      font-size: .3rem;
+      color: #4c4c4c;
+      font-weight: 600;
+      margin-bottom: .1rem;
+    }
+  }
+  .price {
+    text-align: right;
+    font-size: .36rem;
+    color: #ff484c;
+    font-weight: 600;
+    small {
+      font-size: .24rem;
+      color: #4c4c4c;
+      font-weight: normal;
+    }
+    .origin {
+      font-size: .24rem;
+      color: #999;
+      text-decoration: line-through;
+    }
+  }
+  .sale {
+    font-size: .24rem;
+    color: #999;
+    margin-top: .1rem;
+  }
+}

+ 96 - 0
src/assets/style/reset.css

@@ -0,0 +1,96 @@
+html {
+  -webkit-text-size-adjust: 100%;
+  height: 100%;
+  -webkit-user-select: none;
+  overflow-x: hidden;
+}html,
+body,
+div,
+ul,
+li,
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+figure,
+figcaption {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-weight: normal
+}
+* {
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+li {
+  list-style: none;
+}
+a {
+  text-decoration: none;
+}
+body {
+  font-family: "微软雅黑", "Microsoft Yahei";
+  position: relative;
+  color: #000;
+  -webkit-font-smoothing: antialiased;
+  height: 100%;
+}
+#app{
+  height: 100%;
+}
+input {
+  outline:none;
+}
+.clear {
+  clear:both;
+}
+.clearfix:after {
+  display: block;
+  clear: both;
+  content: '';
+  visibility: hidden;
+  height: 0;
+}
+.f_left {
+  float: left;
+}
+.f_right {
+  float: right;
+}
+.g_bottom {
+  padding-bottom: constant(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom);
+}
+.van-popup {
+  overflow: visible;
+}
+@media screen and (max-width: 320px) {
+  body {
+    font-size: 12px;
+  }
+}
+@media screen and (min-width: 321px) and (max-width: 413px) {
+  body {
+    font-size: 12px;
+  }
+}
+@media screen and (min-width: 414px) and (max-width: 639px) {
+  body {
+    font-size: 17px;
+  }
+}
+@media screen and (min-width: 640px) {
+  body {
+    font-size: 18px;
+  }
+}
+/*@supports (bottom: constant(safe-area-inset-bottom)) {
+  body {
+    margin-bottom: constant(safe-area-inset-bottom);
+    margin-bottom: env(safe-area-inset-bottom);
+  }
+}*/

+ 99 - 0
src/assets/style/reset.less

@@ -0,0 +1,99 @@
+html {
+  -webkit-text-size-adjust: 100%;
+  height: 100%;
+  -webkit-user-select: none;
+  overflow-x: hidden;
+}
+
+html,
+body,
+div,
+ul,
+li,
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+figure,
+figcaption {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-weight: normal
+}
+* {
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+li {
+  list-style: none;
+}
+a {
+  text-decoration: none;
+}
+body {
+  font-family: "微软雅黑", "Microsoft Yahei";
+  position: relative;
+  color: #000;
+  text-align: left;
+  -webkit-font-smoothing: antialiased;
+  height: 100%;
+}
+#app{
+  height: 100%;
+}
+input {
+  outline: none;
+}
+.clear {
+  clear: both;
+}
+.clearfix:after {
+  display: block;
+  clear: both;
+  content: '';
+  visibility: hidden;
+  height: 0;
+}
+.f_left {
+  float: left;
+}
+.f_right {
+  float: right;
+}
+.g_bottom {
+  padding-bottom: constant(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom);
+}
+.van-popup {
+  overflow: visible;
+}
+@media screen and (max-width: 320px) {
+  body {
+    font-size: 12px;
+  }
+}
+@media screen and (min-width: 321px) and (max-width: 413px) {
+  body {
+    font-size: 12px;
+  }
+}
+@media screen and (min-width: 414px) and (max-width: 639px) {
+  body {
+    font-size: 17px;
+  }
+}
+@media screen and (min-width: 640px) {
+  body {
+    font-size: 18px;
+  }
+}
+/*@supports (bottom: constant(safe-area-inset-bottom)) {
+  body {
+    margin-bottom: constant(safe-area-inset-bottom);
+    margin-bottom: env(safe-area-inset-bottom);
+  }
+}*/

+ 73 - 0
src/axios.tool.js

@@ -0,0 +1,73 @@
+import Vue from 'vue'
+import axios from 'axios'
+import qs from 'qs'
+import Host from '../config/host'
+import ErrorHandler from '@/common/errorHandler.js'
+//跨域访问传cookie
+axios.defaults.withCredentials = true
+axios.defaults.headers = {
+  'X-Requested-With': 'XMLHttpRequest',
+  'Content-Type': 'application/x-www-form-urlencoded',
+}
+axios.defaults.baseURL = Host
+// 请求超时的时间限制
+axios.defaults.timeout = 20000
+
+// 请求到结果的拦截处理
+axios.interceptors.response.use((config) => {
+  return config.data
+}, (error) => {
+  return Promise.reject(error)
+})
+
+Vue.prototype.$post = ({ url, data, error = true }) => {
+  return new Promise((resolve, reject) => {
+    axios.post(url, qs.stringify(data)).then((res) => {
+        resolve(res)
+    }).catch((err) => {
+        reject(err)
+        if (error) {
+          ErrorHandler(err)
+        }
+      })
+   })
+}
+
+Vue.prototype.$get = ({ url, data, error = true }) => {
+  return new Promise((resolve, reject) => {
+    axios.get(url, { params: data }).then((res) => {
+        resolve(res)
+    }).catch((err) => {
+        reject(err)
+        if (error) {
+          ErrorHandler(err)
+        }
+    })
+  })
+}
+
+Vue.prototype.$put = ({ url, data, error = true }) => {
+  return new Promise((resolve, reject) => {
+    axios.put(url, qs.stringify(data)).then((res) => {
+      resolve(res)
+    }).catch((err) => {
+      reject(err)
+      if (error) {
+        console.log(err)
+      }
+    })
+  })
+}
+
+Vue.prototype.$del = ({ url, data, error = true }) => {
+  return new Promise((resolve, reject) => {
+    axios.delete(url, { params: data }).then((res) => {
+      resolve(res)
+    }).catch((err) => {
+      reject(err)
+      if (error) {
+        console.log(err)
+      }
+    })
+  })
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 4040 - 0
src/common/city.js


+ 20 - 0
src/common/errorHandler.js

@@ -0,0 +1,20 @@
+import { Toast, Dialog } from 'vant'
+// import router from '../router'
+// import Host from '../../config/host'
+
+const errorHandler = (res, errorCb = null) => {
+  if (res && res.response) {
+    switch (res.response.data.err_code) {
+        case 'notEnoughIntegral':
+          Toast(res.response.data.err_msg)
+        break
+      default:
+        Toast(res.response.data.err_msg)
+    }
+  }
+  if (errorCb) {
+    errorCb()
+  }
+}
+
+export default errorHandler

+ 162 - 0
src/common/filter.js

@@ -0,0 +1,162 @@
+import accounting from 'accounting'
+const formatRelative = function (value, ms = false) {
+  let now = ms ? +new Date() : +new Date() / 1000
+  let day, hour, minute, second, result
+  let _value = value - now
+  if (_value <= 0) {
+    return '0天'
+  }
+  day = parseInt(_value / 86400)
+  hour = parseInt(_value % 86400 / 3600)
+  minute = parseInt(_value % 3600 / 60)
+  second = parseInt(_value % 3600 % 60)
+  result = day > 0 ? `${day}天` : hour > 0 ? `${hour}小时` : minute > 0 ? `${minute}分` : `${second}秒`
+  return result
+}
+
+const formatTime = function (value) {
+  let date = new Date(value * 1000)
+  let year = date.getFullYear()
+  let month = date.getMonth() + 1
+  let day = date.getDate()
+
+  let hour = date.getHours()
+  let minute = date.getMinutes()
+
+  return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute].map(formatNumber).join(':')
+}
+
+const formatChTime = function (value) {
+  let date = new Date(value * 1000)
+  let year = date.getFullYear()
+  let month = date.getMonth() + 1
+  let day = date.getDate()
+
+  let hour = date.getHours()
+  let minute = date.getMinutes()
+  let second = date.getSeconds()
+  return year + '年' + month + '月' + day + '日' + ' , ' + [hour, minute, second].map(formatNumber).join(':')
+}
+
+const formatHhTime = function (value) {
+  let date = new Date(value * 1000)
+  let year = date.getFullYear()
+  let month = date.getMonth() + 1
+  let day = date.getDate()
+  return year + '年' + month + '月' + day + '日'
+}
+
+const formatSTime = function (value) {
+  let date = new Date(value * 1000)
+  let year = date.getFullYear()
+  let month = date.getMonth() + 1
+  let day = date.getDate()
+
+  let hour = date.getHours()
+  let minute = date.getMinutes()
+  let second = date.getSeconds()
+  return [year, month, day].map(formatNumber).join('-') + ' ' + [hour, minute, second].map(formatNumber).join(':')
+}
+
+const formatDate = function (value) {
+  let date = new Date(value * 1000)
+  let year = date.getFullYear()
+  let month = date.getMonth() + 1
+  let day = date.getDate()
+
+  return [year, month, day].map(formatNumber).join('-')
+}
+
+const formatDDate = function (value) {
+  let date = new Date(value * 1000)
+  let year = date.getFullYear()
+  let month = date.getMonth() + 1
+  let day = date.getDate()
+
+  return [year, month, day].map(formatNumber).join('.')
+}
+
+const formatNumber = function (n) {
+  n = n.toString()
+  return n[1] ? n : '0' + n
+}
+
+const format = (time) => {
+  let ymd = ''
+  let mouth = (time.getMonth() + 1) >= 10 ? (time.getMonth() + 1) : ('0' + (time.getMonth() + 1))
+  let day = time.getDate() >= 10 ? time.getDate() : ('0' + time.getDate())
+  ymd += time.getFullYear() + '-' // 获取年份。
+  ymd += mouth + '-' // 获取月份。
+  ymd += day // 获取日。
+  return ymd // 返回日期。
+}
+
+export const getAllDate = (start, end) => {
+  let dateArr = []
+  let startArr = start.split('-')
+  let endArr = end.split('-')
+  let db = new Date()
+  db.setUTCFullYear(startArr[0], startArr[1] - 1, startArr[2])
+  let de = new Date()
+  de.setUTCFullYear(endArr[0], endArr[1] - 1, endArr[2])
+  let unixDb = db.getTime()
+  let unixDe = de.getTime()
+  let stamp
+  const oneDay = 24 * 60 * 60 * 1000
+  for (stamp = unixDb; stamp <= unixDe;) {
+    dateArr.push(format(new Date(parseInt(stamp))))
+    stamp = stamp + oneDay
+  }
+  return dateArr
+}
+
+const getAcounting = function (val, text = '¥', short = true, sym = ',') {
+  let max = 1000000
+  if (val >= max && short) {
+    return text + parseFloat((val / max).toFixed(3)) + '万'
+  } else {
+    return accounting.formatMoney(val / 100, text, val % 100 ? 2 : 0, sym)
+  }
+}
+
+const numFormat = function (num) {
+  if (num >= 10000) {
+    num = Math.round(num / 1000) / 10 + '万'
+    return num
+  } else {
+    return num
+  }
+}
+
+const timeTalk = function (val) {
+  let result
+  let time = Math.round(new Date().getTime() / 1000)
+  if (time - val < 86400 && time - val > 3600) {
+    result = Math.floor((time - val) / 3600)
+    return result + '小时前'
+  } else if (time - val <= 3600 && time - val > 60) {
+    result = Math.floor((time - val) / 60)
+    return result + '分钟前'
+  } else if (time - val <= 60 && time - val > 0) {
+    return '刚刚'
+  } else {
+    result = formatTime(val)
+    return result
+  }
+}
+
+export default {
+  formatRelative,
+  formatTime,
+  formatDate,
+  // getAcounting,
+  timeTalk,
+  formatSTime,
+  getAllDate,
+  format,
+  getAcounting,
+  formatDDate,
+  formatChTime,
+  numFormat,
+  formatHhTime
+}

+ 41 - 0
src/common/utils.js

@@ -0,0 +1,41 @@
+export const isMicroMessenger = function () {
+  return navigator.userAgent.indexOf('MicroMessenger') > -1
+}
+
+export const mul = function (a, b) {
+  let c = 0
+  let d = a.toString()
+  let e = b.toString()
+  try {
+    c += d.split('.')[1].length
+  } catch (f) {
+    // console.log(f)
+  }
+  try {
+    c += e.split('.')[1].length
+  } catch (f) {
+    // console.log(f)
+  }
+  return Number(d.replace('.', '')) * Number(e.replace('.', '')) / Math.pow(10, c)
+}
+
+export const formatRelative = function (value, ms = false) {
+  let now = ms ? +new Date() : +new Date() / 1000
+  let day, hour, minute, second, result
+  let _value = value - now
+  if (_value <= 0) {
+    return '0天'
+  }
+  day = parseInt(_value / 86400)
+  hour = parseInt(_value % 86400 / 3600)
+  minute = parseInt(_value % 3600 / 60)
+  second = parseInt(_value % 3600 % 60)
+  result = day > 0 ? `${day}天` : hour > 0 ? `${hour}小时` : minute > 0 ? `${minute}分` : `${second}秒`
+  return result
+}
+
+export default {
+  isMicroMessenger,
+  mul,
+  formatRelative
+}

+ 43 - 0
src/common/validator.js

@@ -0,0 +1,43 @@
+const required = function (val) {
+  return !!val
+}
+const telphone = function (tel) {
+  return /^1[3456789]\d{9}$/.test(tel)
+}
+const number = function (number) {
+  return /^[0-9]*$/.test(number)
+}
+const isidcard = function (number) {
+  return /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(number)
+}
+const minLen = function (val, minLen) {
+  return val.length >= minLen
+}
+const maxLen = function (val, maxLen) {
+  return val.length <= maxLen
+}
+const confirmed = function (val1, val2) {
+  return (val1 !== '' && val2 !== '' && val1 === val2)
+}
+const money = function (money) {
+  return /(^[1-9]([0-9]+)?(\.[0-9]{1,2})?$)|(^(0){1}$)|(^[0-9]\.[0-9]([0-9])?$)/.test(money)
+}
+const min = function (val, min) {
+  return val >= min
+}
+const max = function (val, max) {
+  return val <= max
+}
+
+export default {
+  required,
+  telphone,
+  max,
+  min,
+  confirmed,
+  money,
+  minLen,
+  maxLen,
+  number,
+  isidcard
+}

+ 87 - 0
src/common/wx.js

@@ -0,0 +1,87 @@
+import wx from 'weixin-js-sdk'
+import axios from 'axios'
+// import qs from 'qs'
+// import router from '../router'
+import { isMicroMessenger } from '@/common/utils'
+import store from "@/store"
+
+export default class Weixin {
+  constructor (ready = {}) {
+    if (!isMicroMessenger()) {
+      return false
+    }
+    this.ready = ready
+    this.config = {
+      debug: false,
+      jsApiList: Object.keys(wx)
+    }
+    let location = window.location
+    let url = location.protocol + '//' + location.host + location.pathname + location.search
+    axios.get('mp/config', { params: { url } }).then((cb) => {
+      let config = { ...this.config, ...cb }
+      this.init(config)
+    })
+  }
+
+  init (val) {
+    wx.config(val)
+    wx.ready(() => {
+      this.onMenuShareTimeline(this.ready)
+      this.onMenuShareAppMessage(this.ready)
+    })
+    wx.error((res) => {
+      console.log(res)
+      // empty
+    })
+  }
+  previewImage (obj) {
+    wx.previewImage({
+      current: obj.current,
+      urls: obj.urls
+    })
+  }
+  onMenuShareTimeline (ops) {
+    let timeline = { ...ops }
+    let check = store.getters.userCheck
+    let location = window.location
+    timeline.title = timeline.title || ''
+    timeline.imgUrl = timeline.imgUrl || 'http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/wine_share.jpg'
+    if (check && check.wx_user_id) {
+      if (!timeline.link) {
+        timeline.link = location.origin + location.pathname + '?invite_id=' + check.wx_user_id
+      } else if (timeline.link === '-') {
+        timeline.link = location.origin + '?invite_id=' + check.wx_user_id
+      }
+    } else {
+      timeline.link = location.origin + location.pathname
+    }
+    timeline.success = () => {
+    }
+    wx.onMenuShareTimeline(timeline)
+  }
+
+  onMenuShareAppMessage (ops) {
+    let group = { ...ops }
+    let check = store.getters.userCheck
+    let location = window.location
+    group.title = group.title || ''
+    group.imgUrl = group.imgUrl || 'http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/wine_share.jpg'
+    group.desc = group.desc || '醇酱香酒之一'
+    if (check && check.wx_user_id) {
+      if (!group.link) {
+        group.link = location.origin + location.pathname + '?invite_id=' + check.wx_user_id
+      } else if (group.link === '-') {
+        group.link = location.origin + '?invite_id=' + check.wx_user_id
+      }
+    } else {
+      group.link = location.origin + location.pathname
+    }
+
+    group.success = () => {
+    }
+    wx.onMenuShareAppMessage(group)
+  }
+  closeWindow () {
+    wx.closeWindow()
+  }
+}

+ 119 - 0
src/components/Btn.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="btn1-bg">
+    <div class="name-bottom" v-if="show">
+      <div class="btn-left" @click="leftclick">{{lefttitle}}</div>
+      <div class="btn-right" @click="rightclick">{{righttitle}}</div>
+      <div class="clear"></div>
+    </div>
+    <div class="name-bottom1" v-if="!show">
+      <div class="btn-title" @click="titleclick">{{title}}</div>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  name: '',
+  props: {
+    lefttitle: {
+      type: String,
+      default: '取消',
+      require: true
+    },
+    righttitle: {
+      type: String,
+      default: '确认',
+      require: true
+    },
+    title: {
+      type: String,
+      default: '确认',
+      require: true
+    },
+    show: {
+      type: Boolean,
+      default: false,
+      require: true
+    }
+  },
+  data () {
+    return {
+    }
+  },
+  components: {},
+  computed: {},
+  beforeMount () {},
+  mounted () {},
+  methods: {
+    leftclick () {
+      this.$emit('leftclick')
+    },
+    rightclick () {
+      this.$emit('rightclick')
+    },
+    titleclick () {
+      this.$emit('titleclick')
+    }
+  },
+  watch: {}
+}
+</script>
+<style lang='less' scoped>
+.btn1-bg {
+  .name-bottom {
+    height: 1rem;
+    background: #fff;
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+    border-top: 0.02rem solid #ccc;
+    z-index: 100;
+    .btn-left {
+      height: 0.6rem;
+      border: 0.02rem solid #cccccc;
+      font-size: 0.28rem;
+      text-align: center;
+      line-height: 0.6rem;
+      border-radius: 0.3rem;
+      float: left;
+      color: #4c4c4c;
+      width: 3.4rem;
+      margin-top: 0.2rem;
+      margin-left: 0.2rem;
+    }
+    .btn-right {
+      height: 0.64rem;
+      font-size: 0.28rem;
+      text-align: center;
+      line-height: 0.64rem;
+      border-radius: 0.3rem;
+      float: right;
+      background:#28cd5e;
+      color: #fff;
+      width: 3.44rem;
+      margin-top: 0.2rem;
+      margin-right: 0.2rem;
+    }
+  }
+  .name-bottom1 {
+    height: 1rem;
+    background: #fff;
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+    z-index: 100;
+    // border-top: 0.02rem solid #ccc;
+    box-sizing: border-box;
+    padding: 0.2rem 0.2rem;
+    .btn-title {
+      // height: 0.64rem;
+      font-size: 0.28rem;
+      text-align: center;
+      line-height: 0.64rem;
+      border-radius: 0.3rem;
+      background: #28cd5e;
+      color: #fff;
+      width: 100%;
+    }
+  }
+}
+</style>

+ 49 - 0
src/components/Empty.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="j4c6-empty" >
+    <van-empty
+      class="custom-image"
+      :image="imgUrl"
+      :description="desc"
+      :style="{'background': background}"
+    />
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Empty } from 'vant'
+export default {
+  data () {
+    return {}
+  },
+  props: {
+    imgUrl: {
+      type: String,
+      default: 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/j4c6/join_none.png'
+    },
+    desc: {
+      type: String,
+      default: '暂无列表'
+    },
+    background: {
+      type: String,
+      default: '#f3f3f3'
+    }
+  },
+  methods: {},
+  created () {
+    Vue.use(Empty)
+  },
+  watch: {}
+}
+</script>
+
+<style lang="less">
+.j4c6-empty {
+  margin-top: 2rem;
+  .van-empty__image, .custom-image img {
+    width: 2.4rem;
+    height: 2.4rem;
+  }
+}
+</style>

+ 45 - 0
src/components/Footer.vue

@@ -0,0 +1,45 @@
+<template>
+  <div class="cow-footer">
+    <van-tabbar route fixed active-color="#28cd5e" inactive-color="#888888">
+      <van-tabbar-item replace to="/product">
+        商品
+        <template #icon="props">
+          <img class="icon" :src="props.active ? 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/product/list_icon.png' : 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/product/list.png'" />
+        </template>
+      </van-tabbar-item>
+      <van-tabbar-item replace to="/article" icon="newspaper-o">
+        资讯
+      </van-tabbar-item>
+      <van-tabbar-item replace to="/user">
+        我的
+        <template #icon="props">
+          <img class="icon" :src="props.active ? 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/product/user_icon.png' : 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/product/user.png'" />
+        </template>
+      </van-tabbar-item>
+    </van-tabbar>
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Tabbar, TabbarItem } from 'vant'
+export default {
+  data () {
+    return {}
+  },
+  methods: {
+  },
+  created () {
+    Vue.use(Tabbar).use(TabbarItem)
+  }
+}
+</script>
+
+<style lang="less">
+.cow-footer {
+  .icon {
+    width: .48rem;
+    height: .48rem;
+  }
+}
+</style>

+ 130 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+    <p>
+      For a guide and recipes on how to configure / customize this project,<br />
+      check out the
+      <a href="https://cli.vuejs.org" target="_blank" rel="noopener"
+        >vue-cli documentation</a
+      >.
+    </p>
+    <h3>Installed CLI Plugins</h3>
+    <ul>
+      <li>
+        <a
+          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
+          target="_blank"
+          rel="noopener"
+          >babel</a
+        >
+      </li>
+      <li>
+        <a
+          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
+          target="_blank"
+          rel="noopener"
+          >router</a
+        >
+      </li>
+      <li>
+        <a
+          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
+          target="_blank"
+          rel="noopener"
+          >vuex</a
+        >
+      </li>
+      <li>
+        <a
+          href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
+          target="_blank"
+          rel="noopener"
+          >eslint</a
+        >
+      </li>
+    </ul>
+    <h3>Essential Links</h3>
+    <ul>
+      <li>
+        <a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
+      </li>
+      <li>
+        <a href="https://forum.vuejs.org" target="_blank" rel="noopener"
+          >Forum</a
+        >
+      </li>
+      <li>
+        <a href="https://chat.vuejs.org" target="_blank" rel="noopener"
+          >Community Chat</a
+        >
+      </li>
+      <li>
+        <a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
+          >Twitter</a
+        >
+      </li>
+      <li>
+        <a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
+      </li>
+    </ul>
+    <h3>Ecosystem</h3>
+    <ul>
+      <li>
+        <a href="https://router.vuejs.org" target="_blank" rel="noopener"
+          >vue-router</a
+        >
+      </li>
+      <li>
+        <a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
+      </li>
+      <li>
+        <a
+          href="https://github.com/vuejs/vue-devtools#vue-devtools"
+          target="_blank"
+          rel="noopener"
+          >vue-devtools</a
+        >
+      </li>
+      <li>
+        <a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
+          >vue-loader</a
+        >
+      </li>
+      <li>
+        <a
+          href="https://github.com/vuejs/awesome-vue"
+          target="_blank"
+          rel="noopener"
+          >awesome-vue</a
+        >
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "HelloWorld",
+  props: {
+    msg: String
+  }
+};
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="less">
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 85 - 0
src/components/dialog/Service.vue

@@ -0,0 +1,85 @@
+<template>
+  <div class="ewm_dialog">
+    <van-popup v-model="isShow" style="border-radius: 0.3rem;" @click-overlay="closed">
+      <div class="ewm-part">
+        <h3 class="ewm-top">
+          {{serviceData.title}}
+        </h3>
+        <div class="ewm-center">
+          <img :src="serviceData.url" alt="">
+        </div>
+        <div class="ewm-bottom">
+          <p>{{serviceData.desc}}</p>
+          <p>{{serviceData.remark}}</p>
+        </div>
+      </div>
+    </van-popup>
+  </div>
+</template>
+<script>
+export default {
+  name: '',
+  props: {
+    show: {
+      type: Boolean,
+      default: false,
+      require: true
+    },
+    serviceData: Object
+  },
+  data () {
+    return {
+      isShow: false
+    }
+  },
+  components: {},
+  computed: {},
+  beforeMount () {},
+  mounted () {},
+  methods: {
+    closed () {
+      this.$emit('closeDialog')
+    }
+  },
+  created () {
+  },
+  watch: {
+    'show': function (val) {
+      this.isShow = val
+    }
+  }
+}
+</script>
+<style lang='less' scoped>
+.ewm_dialog {
+  .ewm-part {
+    width: 5.5rem;
+    height: 5.88rem;
+    background: #fff;
+    border-radius: 0.3rem;
+    overflow: hidden;
+    .ewm-top {
+      background: #28cd5e;
+      height: 0.88rem;
+      width: 100%;
+      color: #fff;
+      text-align: center;
+      font-weight: 600;
+      font-size: 0.36rem;
+      line-height: 0.88rem;
+    }
+    .ewm-center img {
+      height: 3rem;
+      width: 3rem;
+      display: block;
+      margin: .5rem auto .35rem;
+    }
+    .ewm-bottom {
+      line-height: .42rem;
+      font-size: 0.26rem;
+      color: #4c4c4c;
+      text-align: center;
+    }
+  }
+}
+</style>

+ 26 - 0
src/main.js

@@ -0,0 +1,26 @@
+import Vue from "vue"
+import App from "./App.vue"
+import router from "./router"
+import store from "./store"
+import '@/assets/style/reset.less'
+import 'vant/lib/index.css'
+import './axios.tool'
+import filter from '@/common/filter.js'
+import { Button, List, Popup, Dialog, Icon, Field, Area } from 'vant'
+// import Vconsole from 'vconsole'
+// import 'lib-flexible'
+
+Vue.config.productionTip = false;
+Vue.use(Button).use(List).use(Popup).use(Dialog).use(Icon).use(Field).use(Area)
+
+// let vConsole = new Vconsole()
+// Vue.use(vConsole)
+
+for (let i of Object.keys(filter)) {
+  Vue.filter(i, filter[i])
+}
+new Vue({
+  router,
+  store,
+  render: h => h(App)
+}).$mount("#app");

+ 200 - 0
src/router/index.js

@@ -0,0 +1,200 @@
+import Vue from "vue"
+import VueRouter from "vue-router"
+import Home from "../views/Home.vue"
+// 认购流程
+import ProductList from '@/views/product/List'
+import ProductDetail from '@/views/product/Detail'
+// 文章模块
+import ArticleList from '@/views/article/List'
+import ArticleDetail from '@/views/article/Detail'
+// 订单管理
+import OrderList from '@/views/user/order/List'
+import OrderDetail from '@/views/user/order/Detail'
+// 支付订单
+import CreateOrder from '@/views/pay/CreateOrder'
+import Pay from '@/views/pay/Pay'
+import PayIntegral from '@/views/pay/PayIntegral'
+import PaySuccess from '@/views/pay/Success'
+// 我的邀请
+import Invite from '@/views/user/invite/Invite.vue'
+import Income from '@/views/user/invite/Income.vue'
+import Recode from '@/views/user/invite/Recode.vue'
+// 地址管理
+import AddressList from '@/views/user/address/List.vue'
+import AddressEdit from '@/views/user/address/Detail.vue'
+// 我的会员
+import Member from '@/views/user/member/Member.vue'
+//我的积分
+import Integral from '@/views/user/integral/Integral.vue'
+import Exchange from '@/views/user/integral/Exchange.vue'
+// 会员中心
+import User from '@/views/user/User.vue'
+// 提现
+import Account from '@/views/user/balance/Account.vue'
+import Withdraw from '@/views/user/balance/Withdraw.vue'
+import Success from '@/views/user/balance/Success.vue'
+
+import New from "../views/New.vue"
+
+
+Vue.use(VueRouter);
+
+const routes = [
+  {
+    path: "/",
+    name: "Home",
+    component: Home
+  },
+  {
+    meta: { requiresAuth: false, title: '会员中心' },
+    path: '/user',
+    name: 'user',
+    component: User
+  },
+  {
+    meta: { requiresAuth: false, title: '地址列表' },
+    path: '/address/list',
+    name: 'addressList',
+    component: AddressList
+  },
+  {
+    meta: { requiresAuth: false, title: '编辑地址' },
+    path: '/address/edit',
+    name: 'addressEdit',
+    component: AddressEdit
+  },
+  {
+    meta: { requiresAuth: false, title: '我的账户' },
+    path: '/account',
+    name: 'account',
+    component: Account
+  },
+  {
+    meta: { requiresAuth: false, title: '我的积分' },
+    path: '/integral',
+    name: 'integral',
+    component: Integral
+  },
+  {
+    meta: { requiresAuth: false, title: '积分兑换' },
+    path: '/exchange',
+    name: 'exchange',
+    component: Exchange
+  },
+  {
+    meta: { requiresAuth: false, title: '提现' },
+    path: '/withdraw',
+    name: 'withdraw',
+    component: Withdraw
+  },
+  {
+    meta: { requiresAuth: false, title: '提现成功' },
+    path: '/success',
+    name: 'success',
+    component: Success
+  },
+  {
+    meta: { requiresAuth: false, title: '我的邀请' },
+    path: '/invite',
+    name: 'invite',
+    component: Invite
+  },
+  {
+    meta: { requiresAuth: false, title: '我的会员' },
+    path: '/member',
+    name: 'member',
+    component: Member
+  },
+  {
+    meta: { requiresAuth: false, title: '收益明细' },
+    path: '/income',
+    name: 'income',
+    component: Income
+  },
+  {
+    meta: { requiresAuth: false, title: '邀请记录' },
+    path: '/recode',
+    name: 'recode',
+    component: Recode
+  },
+  {
+    meta: { requiresAuth: false, title: '商品列表' },
+    path: '/product',
+    name: 'product',
+    component: ProductList
+  },
+  {
+    meta: { requiresAuth: false, title: '' },
+    path: '/product/:product_id',
+    name: 'productDetail',
+    component: ProductDetail
+  },
+  {
+    meta: { requiresAuth: false, title: '资讯' },
+    path: '/article',
+    name: 'article',
+    component: ArticleList
+  },
+  {
+    meta: { requiresAuth: false, title: '' },
+    path: '/article/:article_id',
+    name: 'articleDetail',
+    component: ArticleDetail
+  },
+  // 订单管理
+  {
+    meta: { requiresAuth: false, title: '' },
+    path: '/order',
+    name: 'order',
+    component: OrderList
+  },
+  {
+    meta: { requiresAuth: false, title: '' },
+    path: '/order/:order_id',
+    name: 'orderDetail',
+    component: OrderDetail
+  },
+  // 支付
+  {
+    meta: { requiresAuth: false, title: '创建订单' },
+    path: '/create/order/:product_id/:count',
+    name: 'createOrder',
+    component: CreateOrder
+  },
+  {
+    meta: { requiresAuth: false, title: '支付订单' },
+    path: '/pay/:order_id',
+    name: 'pay',
+    component: Pay
+  },
+  {
+    meta: {requiresAuth: false, title: '兑换订单'},
+    path: '/pay/integral/:product_id',
+    name: 'payIntegral',
+    component: PayIntegral
+  },
+  {
+    meta: { requiresAuth: true, title: '支付成功' },
+    path: '/pay/:order_id/success',
+    name: 'paySuccess',
+    component: PaySuccess
+  },
+  {
+    path: "/new",
+    name: "new",
+    component: New
+  }
+];
+
+const router = new VueRouter({
+  mode: "history",
+  base: process.env.BASE_URL,
+  routes
+})
+
+router.beforeEach((to, from, next) => {
+  window.document.title = to.meta.title || ''
+  next()
+})
+
+export default router;

+ 19 - 0
src/store/actions/article.js

@@ -0,0 +1,19 @@
+import api from '@/api'
+
+export const getArticleList = ({ commit }, data) => {
+  api.getArticleList(
+    data,
+    (res) => {
+      commit('GET_ARTICLE_LIST_SUCCESS', res)
+    }
+  )
+}
+
+export const getArticleDetail = ({ commit }, data) => {
+  api.getArticleDetail(
+    data,
+    (res) => {
+      commit('GET_ARTICLE_DETAIL_SUCCESS', res)
+    }
+  )
+}

+ 8 - 0
src/store/actions/index.js

@@ -0,0 +1,8 @@
+const context = require.context('./', false, /\.js$/)
+const keys = context.keys().filter(item => item !== './index.js') || []
+
+const actions = keys.reduce((memo, key) => {
+  return Object.assign({}, memo, context(key))
+}, {})
+
+export default actions

+ 10 - 0
src/store/actions/product.js

@@ -0,0 +1,10 @@
+import api from '@/api'
+
+export const getProductDetail = ({ commit }, data) => {
+  api.getProductDetail(
+    data,
+    (res) => {
+      commit('GET_PRODUCT_DETAIL_SUCCESS', res)
+    }
+  )
+}

+ 41 - 0
src/store/actions/user.js

@@ -0,0 +1,41 @@
+import api from '@/api'
+export const userCheck = ({ commit }) => {
+  api.userCheck(
+    (res) => {
+      commit('userCheckData', res)
+    }
+  )
+}
+// 订单模块
+export const getOrderDetail = ({ commit }, data) => {
+  api.getOrderDetail(
+    data,
+    (res) => {
+      commit('GET_ORDER_DETAIL_SUCCESS', res)
+    }
+  )
+}
+export const getOrderList = ({ commit }, data) => {
+  commit('GET_ORDER_LIST_LOADING')
+  api.getOrderList(
+    data,
+    (res) => {
+      commit('GET_ORDER_LIST_SUCCESS', res)
+    }
+  )
+}
+
+export const userInfo = ({ commit }) => {
+  api.userInfo(
+    (res) => {
+      commit('userInfoData', res)
+    }
+  )
+}
+export const userBalanceInfo = ({ commit }) => {
+  api.userBalanceInfo(
+    (res) => {
+      commit('userBalanceInfoData', res)
+    }
+  )
+}

+ 3 - 0
src/store/getters/article.js

@@ -0,0 +1,3 @@
+export const articleData = state => state.articleData
+
+export const articleDetail = state => state.articleDetail

+ 8 - 0
src/store/getters/index.js

@@ -0,0 +1,8 @@
+const context = require.context('./', false, /\.js$/)
+const keys = context.keys().filter(item => item !== './index.js')
+
+const getters = keys.reduce((memo, key) => {
+  return Object.assign({}, memo, context(key))
+}, {})
+
+export default getters

+ 1 - 0
src/store/getters/product.js

@@ -0,0 +1 @@
+export const productDetail = state => state.productDetail

+ 11 - 0
src/store/getters/user.js

@@ -0,0 +1,11 @@
+export const userCheck = state => state.userCheck
+
+export const orderDetail = state => state.orderDetail
+
+export const orders = state => state.orders
+
+export const ordersLoading = state => state.orders.loading
+
+export const userInfo = state => state.userInfo
+
+export const userBalanceInfo = state => state.userBalanceInfo

+ 13 - 0
src/store/index.js

@@ -0,0 +1,13 @@
+import Vue from "vue"
+import Vuex from "vuex"
+import actions from './actions'
+import getters from './getters'
+import { mutations, state } from './modules'
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  actions,
+  getters,
+  mutations,
+  state
+});

+ 24 - 0
src/store/modules/article.js

@@ -0,0 +1,24 @@
+const state = {
+  articleData: {
+    loading: false,
+    list: [],
+    more: false,
+    total: 0
+  },
+  articleDetail: {}
+}
+
+const mutations = {
+  GET_ARTICLE_LIST_SUCCESS (state, res) {
+    state.articleData = res
+  },
+
+  GET_ARTICLE_DETAIL_SUCCESS (state, res) {
+    state.articleDetail = res.article
+  }
+}
+
+export {
+  state,
+  mutations
+}

+ 10 - 0
src/store/modules/index.js

@@ -0,0 +1,10 @@
+const context = require.context('./', false, /\.js$/)
+const keys = context.keys().filter(item => item !== './index.js') || []
+
+export const mutations = keys.reduce((memo, key) => {
+  return Object.assign({}, memo, context(key).mutations)
+}, {})
+
+export const state = keys.reduce((memo, key) => {
+  return Object.assign({}, memo, context(key).state)
+}, {})

+ 14 - 0
src/store/modules/product.js

@@ -0,0 +1,14 @@
+const state = {
+  productDetail: {}
+}
+
+const mutations = {
+  GET_PRODUCT_DETAIL_SUCCESS (state, res) {
+    state.productDetail = res
+  }
+}
+
+export {
+  state,
+  mutations
+}

+ 40 - 0
src/store/modules/user.js

@@ -0,0 +1,40 @@
+const state = {
+  userCheck: {},
+  orderDetail: {},
+  orders: {
+    loading: false,
+    more: '',
+    list: []
+  },
+  userInfo: {},
+  userBalanceInfo: {}
+}
+
+const mutations = {
+  userCheckData (state, res) {
+    state.userCheck = res
+  },
+  GET_ORDER_DETAIL_SUCCESS (state, res) {
+    state.orderDetail = res
+  },
+  GET_ORDER_LIST_LOADING (state) {
+    state.orders.loading = true
+    state.orders.list = []
+  },
+  GET_ORDER_LIST_SUCCESS (state, res) {
+    state.orders.loading = false,
+    state.orders.more = res.list_count
+    state.orders.list = res.list
+  },
+  userInfoData (state, res) {
+    state.userInfo = res
+  },
+  userBalanceInfoData (state, res) {
+    state.userBalanceInfo = res
+  }
+}
+
+export {
+  state,
+  mutations
+}

+ 12 - 0
src/views/Home.vue

@@ -0,0 +1,12 @@
+<template>
+  <div class="home">
+    1
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Home",
+  components: {}
+};
+</script>

+ 46 - 0
src/views/New.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="new">
+    7天
+    <van-button type="primary">主要按钮</van-button>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  name: '',
+  data() {
+    return {};
+  },
+  created() {
+    // this.gettest()
+    this.$store.dispatch('userCheck')
+  },
+  components: {},
+  computed: {
+    ...mapGetters(['userCheck']),
+  },
+  beforeMount() {},
+  mounted() {},
+  methods: {
+    gettest () {
+      this.$get({
+        url: 'v1/user/check',
+        data: {}
+      }).then((res) => {
+        console.log(res)
+      })
+    }
+  },
+  watch: {
+    'userCheck': function (val) {
+      console.log(val)
+    }
+  }
+};
+</script>
+
+<style lang="less" scoped>
+.new {
+  font-size: 0.28rem;
+}
+</style>

+ 118 - 0
src/views/article/Detail.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="family-article__bg">
+    <div class="family-article" v-if="!loadding">
+      <p class="family-article__title">{{articleDetail.title}}</p>
+      <div class="family-article__date">
+        <span class="family-article__time">{{articleDetail.ctime | formatTime}}</span>
+        <span v-if="articleDetail.editor">{{articleDetail.editor}}</span>
+        <span class="family-article__source">{{articleDetail.source}}</span>
+      </div>
+
+      <div class="family-article__text">
+        <img v-if="articleDetail.cover" width="100%" :src="articleDetail.cover">
+        <div v-html="articleDetail.content" class="j4c6-article_content" id="j4c6-article_content"></div>
+      </div>
+
+      <div class="family-article__read">
+        <span>阅读:{{articleDetail.click}}</span>
+        <van-icon name="points" color="#ffc64d" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Weixin from '@/common/wx'
+export default {
+  name: 'articleDetail',
+  data () {
+    return {
+      wx: null,
+      loadding: true,
+      articleId: Number(this.$route.params.article_id)
+    }
+  },
+  computed: {
+    ...mapGetters(['articleDetail', 'userCheck'])
+  },
+  methods: {
+    getBenefit () {
+      this.$get({
+        url: `v1/article/cb/${this.articleId}/${this.$route.query.invite_id}/${this.articleDetail.otime}`
+      })
+    },
+    getArticleDetail () {
+      this.$store.dispatch('getArticleDetail', {id: this.articleId})
+    },
+    shareWx () {
+      this.wx = new Weixin({
+        title: this.articleDetail.title,
+        desc: this.articleDetail.about,
+        imgUrl: this.articleDetail.cover || 'http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/wine_share.jpg',
+        link: window.location.origin + `/article/${this.articleId}?invite_id=` + this.userCheck.wx_user_id
+      })
+    }
+  },
+  created () {
+    setTimeout(() => { this.loadding = false }, 300)
+    this.getArticleDetail()
+  },
+  watch: {
+    'articleDetail': function (val) {
+      this.shareWx()
+      window.document.title = val.source
+      if (this.$route.query.invite_id) {
+        this.getBenefit()
+      }
+    }
+  },
+  components: {}
+}
+</script>
+
+<style lang="less">
+.family {
+  &-article__bg {
+    width: 100%;
+  }
+  &-article{
+    padding: 0.2rem 0.3rem 0.5rem;
+    text-align: left;
+  }
+  &-article__title{
+    font-size: .44rem;
+    color: #4c4c4c;
+    font-weight: 600;
+  }
+  &-article__date{
+    margin-top: 0.2rem;
+    color: #818181;
+    font-size: .26rem;
+  }
+  &-article__time{
+    margin-right: 0.15rem;
+  }
+  &-article__text{
+    width: 100%;
+    box-sizing: border-box;
+    padding-top: 0.2rem;
+    font-size: 0.28rem;
+    color: #515369;
+    line-height: 0.45rem;
+  }
+  &-article__source {
+    color: #6e87a2;
+    margin-left: .1rem;
+  }
+  &-article__read {
+    margin-top: 1rem;
+    font-size: .24rem;
+    color: #4c4c4c;
+    i {
+      float: right;
+      font-size: .28rem;
+    }
+  }
+}
+</style>

+ 118 - 0
src/views/article/List.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="cow-article_list">
+    <van-list v-model="loading" :finished="finished" :finished-text="page > 1 ? '已经到底了' : ''" @load="onLoad">
+      <ul>
+        <li class="product_info" v-for="(item, i) in list" :key="i" @click="goDetail(item.id)">
+          <div class="pic">
+            <van-image :src="item.cover" class="product_img">
+              <template v-slot:loading>
+                <van-loading type="spinner" size="20" />
+              </template>
+            </van-image>
+          </div>
+
+          <van-row type="flex" justify="space-between">
+            <van-col span="16" class="desc">
+              <h3>{{item.title}}</h3>
+              <p>{{item.about}}</p>
+            </van-col>
+            <van-col span="8" class="price">
+              <p><van-icon name="points" color="#fff" /></p>
+              <van-icon name="points" color="#ffc64d" />
+            </van-col>
+          </van-row>
+        </li>
+      </ul>
+    </van-list>
+    <footer-tab />
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Image as VanImage, Col, Row, Loading } from 'vant'
+import { mapGetters } from 'vuex'
+import Weixin from '@/common/wx'
+// import localStorage from '@/common/localStorage'
+import FooterTab from '@/components/Footer'
+const PER_PAGE = 10
+export default {
+  name: 'article',
+  data () {
+    return {
+      page: 1,
+      per_page: PER_PAGE,
+      loading: false,
+      finished: false,
+      list: [],
+      total: 0
+    }
+  },
+  computed: {
+    ...mapGetters(['articleDetail', 'userCheck'])
+  },
+  methods: {
+    onLoad () {
+      if (this.list.length < this.total) {
+        this.loading = true
+        this.page += 1
+        this.getList()
+      }
+    },
+    goDetail (id) {
+      if (this.$route.query.invite_id) {
+        this.$router.push(`/article/${id}?invite_id=${this.$route.query.invite_id}`)
+      } else {
+        this.$router.push(`/article/${id}`)
+      }
+    },
+    getList () {
+      this.$get({
+        url: 'v1/artcat/24/articles',
+        data: { page: this.page, per_page: this.per_page }
+      }).then((res) => {
+        this.loading = false
+        this.shareWx()
+        if (res.articles) {
+          this.list = this.list.concat(res.articles || [])
+          this.total = res.article_count
+        }
+        if (this.list.length >= this.total) {
+          this.finished = true
+        }
+      })
+    },
+    shareWx () {
+      this.wx = new Weixin({
+        title: '资讯',
+        desc: '',
+        imgUrl: 'http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/wine_share.jpg',
+        link: window.location.origin + `/article/${this.articleId}?invite_id=` + this.userCheck.wx_user_id
+      })
+    }
+  },
+  created () {
+    this.getList()
+    Vue.use(VanImage).use(Col).use(Row).use(Loading)
+  },
+  components: {
+    FooterTab
+  }
+}
+</script>
+
+<style lang="less">
+@import url('../../assets/style/product.less');
+.cow-article {
+  &_list {
+    width: 100%;
+    min-height: 100%;
+    background: #f2f2f2;
+    padding-bottom: 1rem;
+    .product_img {
+      width: 7.1rem;
+      height: 7.1rem;
+    }
+  }
+}
+</style>

+ 299 - 0
src/views/pay/CreateOrder.vue

@@ -0,0 +1,299 @@
+<template>
+  <div class="family-createOrder__page" :class="{active: isBuyVip}">
+    <!-- 地址模块 -->
+    <address-part
+    :showIcon="showIcon"
+    :addressDetail="addressDetail"
+    :count="count" />
+    <!-- 商品模块 -->
+    <div class="family-createOrder__product">
+      <div class="product-top clearfix">
+        <van-image class="product-img" fit="cover" :src="productDetail.img_thumb_url" />
+        <div class="product-info">
+          <p class="product-name">{{productDetail.title}}</p>
+          <p class="product-dec">{{productDetail.sub_title}}</p>
+          <p class="product-money">
+            ¥<span class="money-text">{{productDetail.price | getAcounting('')}}</span>
+          </p>
+        </div>
+        <van-divider :style="{ borderColor: '#eee', margin: '0' }" />
+      </div>
+      <div class="product-bottom clearfix">
+        <div class="product-buy">购买数量</div>
+        <div class="product-change">
+          <van-stepper v-model="count" input-width="1rem" disable-input class="count-change" />
+        </div>
+      </div>
+    </div>
+    <!-- 是否使用余额 -->
+    <div class="family-createOrder__payway" v-if="userBalanceInfo && userBalanceInfo.available">
+      <div class="select-item clearfix">
+        <van-divider :style="{ borderColor: '#eee', margin: '0' }" />
+        <div class="select-title">余额抵扣 <span style="font-weight:600">({{userBalanceInfo.available | getAcounting('¥', false)}})</span></div>
+        <div class="select-on"><van-switch v-model="is_balance" size="0.4rem" active-color="#ff484c" inactive-color="#cccccc" /></div>
+      </div>
+    </div>
+    <!-- 抵扣金额 -->
+    <money-part @setBalance="setBalance"
+    :isUseBalance="isUseBalance"
+    :balanceData="userBalanceInfo.available"
+    :productTotal="productTotal"
+    :vipPrice="productDetail.vip_price * count"
+    :isBuyVip="isBuyVip" />
+
+    <!-- 是否开通会员 -->
+    <div class="family-createOrder__member" v-if="!userCheck.is_vip">
+        <van-cell-group>
+          <van-cell :title="`是否开通会员卡 ${vipCardFee}`" clickable @click="isBuyVip = !isBuyVip">
+            <template #right-icon>
+              <van-checkbox v-model="isBuyVip" checked-color="#07c160" />
+            </template>
+          </van-cell>
+        </van-cell-group>
+      <ul class="rules">
+        <li>开通会员卡详情</li>
+        <li>1. 会员卡有效期为<span>1年</span>,支付<span>{{userCheck && userCheck.vip_card_fee | getAcounting}}</span>开通会员卡即刻生效</li>
+        <li>2. 开通会员卡购买平台商品,享受会员价</li>
+      </ul>
+    </div>
+
+    <btn-part :total="payTotal" :type="orderType" :address="addressDetail" :orderData="orderData" title="提交订单" />
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { mapGetters } from 'vuex'
+import { Image as VanImage, Stepper, Switch, Checkbox, CheckboxGroup, Divider, Cell, CellGroup } from 'vant'
+import AddressPart from './components/Address'
+import MoneyPart from './components/Money'
+import BtnPart from './components/Btn'
+import filters from '@/common/filter'
+export default {
+  name: 'createOrder',
+  data () {
+    return {
+      isBuyVip: false,
+      productId: Number(this.$route.params.product_id),
+      count: Number(this.$route.params.count),
+      addressDetail: {},
+      showIcon: false,
+      orderType: 'order',
+      is_balance: false
+    }
+  },
+  computed: {
+    ...mapGetters(['productDetail', 'userBalanceInfo', 'userCheck']),
+    isUseBalance: function () {
+      if (this.is_balance) {
+        return true
+      }
+      return false
+    },
+    vipCardFee () {
+      return filters.getAcounting(this.userCheck.vip_card_fee)
+    },
+    productTotal: function () {
+      let total = this.productDetail.price * this.count
+      return total
+    },
+    payTotal: function () {
+      let total = this.productDetail.price * this.count
+      if (this.isBuyVip) {
+        total = this.productDetail.vip_price * this.count + this.userCheck.vip_card_fee
+      } else if (this.userCheck.is_vip) {
+        total = this.productDetail.vip_price * this.count
+      }
+      if (this.is_balance) {
+        let diff = total - this.userBalanceInfo.available
+        if (diff < 0) {
+          return 0
+        } else {
+          return diff
+        }
+      }
+      return total
+    },
+    orderData: function () {
+      let data = {
+        address_id: this.addressDetail.id || 0,
+        count: this.count,
+        product_id: this.productId,
+        use_balance: this.is_balance,
+        by_vip_user: this.isBuyVip
+      }
+      return data
+    }
+  },
+  methods: {
+    getProductDetail () {
+      this.$store.dispatch('getProductDetail', {id: this.productId})
+    },
+    getBalanceInfo () {
+      this.$store.dispatch('userBalanceInfo')
+    },
+    getAddressDefault () {
+      this.$get({
+        url: 'v1/user/address/default'
+      }).then((resp) => {
+        // console.log(resp)
+        if (resp) {
+          this.addressDetail = resp
+        }
+      })
+    },
+    setBalance () {}
+  },
+  created () {
+    Vue.use(VanImage).use(Stepper).use(Switch).use(Checkbox)
+    .use(CheckboxGroup).use(Divider).use(Cell).use(CellGroup)
+    this.getAddressDefault()
+    if (this.productDetail && !this.productDetail.id) {
+      this.getProductDetail()
+    }
+    this.getBalanceInfo()
+  },
+  watch: {
+    'count': function (val) {},
+    'userBalanceInfo': function (val) {
+      console.log(val)
+    }
+  },
+  components: {
+    AddressPart,
+    MoneyPart,
+    BtnPart
+  }
+}
+</script>
+
+<style lang="less">
+.family-createOrder {
+  &__page {
+    width: 100%;
+    min-height: 100%;
+    background: #f2eeee;
+    overflow: hidden;
+    &.active {
+      padding-bottom: 1rem;
+    }
+  }
+  &__product {
+    width: 100%;
+    padding: 0.3rem 0.2rem;
+    background: #fff;
+    box-sizing: border-box;
+    .product-top {
+      width: 100%;
+      .product-img {
+        height: 1.6rem;
+        width: 1.6rem;
+        background: #7f7f7f;
+        border-radius: 0.2rem;
+        float: left;
+        margin-right: 0.4rem;
+      }
+      .product-info {
+        float: left;
+        .product-name {
+          color: #4c4c4c;
+          font-size: 0.28rem;
+          margin-bottom: 0.12rem;
+          text-overflow: ellipsis;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          overflow: hidden;
+          width: 5rem;
+          max-height: 0.72rem;
+        }
+        .count {
+          font-size: .24rem;
+          color: #999;
+          margin-left: .2rem;
+        }
+        .product-dec {
+          color: #999;
+          font-size: 0.24rem;
+          margin-bottom: 0.12rem;
+          width: 5rem;
+          line-height: 0.35rem;
+          text-overflow: ellipsis;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          overflow: hidden;
+        }
+        .product-money {
+          color: #f14f4f;
+          font-size: 0.28rem;
+          margin-top: 0.05rem;
+          .money-text {
+            font-size: 0.34rem;
+          }
+        }
+      }
+    }
+    .product-bottom {
+      .product-buy {
+        float: left;
+        color: #4c4c4c;
+        font-size: 0.28rem;
+        margin-top: 0.24rem;
+      }
+      .product-change {
+        float: right;
+        color: #4c4c4c;
+        font-size: 0.28rem;
+        margin-top: 0.25rem;
+      }
+    }
+  }
+  &__member {
+    .van-cell {
+      padding: .2rem;
+    }
+    .rules {
+      padding: .2rem;
+      font-size: .26rem;
+      color: #999;
+      line-height: .42rem;
+      span {
+        color: #f14f4f;
+      }
+    }
+  }
+  &__payway {
+    background: #fff;
+    margin-top: .24rem;
+    padding: 0 0.2rem;
+    box-sizing: border-box;
+    .select-item {
+      font-size: 0.28rem;
+      color: #4c4c4c;
+      .select-title {
+        float: left;
+        margin-top: 0.3rem;
+        margin-bottom: 0.3rem;
+      }
+      .select-icon {
+        width: 0.12rem;
+        height: 0.2rem;
+        float: right;
+        margin-top: 0.4rem;
+        margin-left: 0.2rem;
+      }
+      .select-use {
+        float: right;
+        color: #ff696b;
+        margin-top: 0.3rem;
+        margin-bottom: 0.3rem;
+      }
+      .select-on {
+        float: right;
+        margin-top: 0.25rem;
+      }
+    }
+  }
+}
+</style>

+ 305 - 0
src/views/pay/Index.vue

@@ -0,0 +1,305 @@
+<template>
+  <div class="cow-pay">
+    <div class="cow-pay_product">
+      <van-row type="flex">
+        <van-col span="7">
+          <van-image width="1.6rem" height="1.15rem" :src="orderDetail.project_image">
+            <template v-slot:loading>
+              <van-loading type="spinner" size="20" />
+            </template>
+          </van-image>
+        </van-col>
+        <van-col span="17" class="info">
+          <h3>{{orderDetail.project_name}}</h3>
+          <p class="price">{{orderDetail.price | getAcounting('¥', false)}}<span>× {{orderDetail.nums}}</span></p>
+        </van-col>
+      </van-row>
+    </div>
+
+    <div class="cow-pay_tip">
+      <h3>风险提示</h3>
+      <p class="content">请您认购前下载本产品的《代养协议》,该协议内容为最终签约版,协议中条款、数值、比例均已确定(不予修改),请您下载后认真阅读,确认并知晓代养模式(无保本)的投资风险。</p>
+    </div>
+
+    <div class="cow-pay_city" v-if="orderDetail.is_city">
+      <p>已选择代理城市:{{orderDetail.prov + orderDetail.city}}</p>
+    </div>
+
+    <div class="cow-pay_payway">
+      <h3>支付方式</h3>
+      <van-radio-group v-model="selectRadio">
+        <van-cell-group>
+          <van-cell
+            clickable
+            :title="`余额支付(¥${(minUserInfo.total / 100).toFixed(2)})`"
+            @click="toggle"
+            icon="http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/cow_icon/balance_icon.jpg"
+          >
+            <template #right-icon>
+              <van-radio name="1" checked-color="#28cd5e" />
+            </template>
+          </van-cell>
+        </van-cell-group>
+      </van-radio-group>
+    </div>
+
+    <footer class="cow-pay_footer">
+      <div class="agreement">
+        <van-checkbox v-model="agreeChecked" checked-color="#28cd5e">
+          我已阅读并同意
+          <router-link to="/">《牛就对了服务协议》</router-link>
+        </van-checkbox>
+      </div>
+
+      <van-row class="bar">
+        <van-col span="16" class="info">
+          <span>共{{orderDetail.nums}}份,</span>
+          <span class="title">合计:</span>
+          <span class="total">{{orderDetail.total | getAcounting('¥', false)}}</span>
+        </van-col>
+
+        <van-col span="8" class="btn">
+          <van-button type="primary" :color="!this.agreeChecked ? '#999999':''" size="small" round block @click="showDialog">确认支付</van-button>
+        </van-col>
+      </van-row>
+    </footer>
+
+    <!-- 输入平台交易密码弹窗 -->
+    <van-dialog v-model="show" title="支付" show-cancel-button :closeOnClickOverlay="true" confirmButtonColor="#28cd5e" cancelButtonText="忘记密码" @cancel="goSetPwd" @confirm="pay">
+      <van-field class="pwdInput" v-model="pwd" type="password" placeholder="请输入平台交易密码" label-align="center" input-align="center" @focus="inputFocus" @blur="inputBlur" />
+    </van-dialog>
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Image as VanImage, Col, Row, Checkbox, Radio, RadioGroup, Cell, CellGroup } from 'vant'
+import { mapGetters } from 'vuex'
+// import localStorage from '@/common/localStorage'
+export default {
+  name: 'pay',
+  data () {
+    return {
+      timer: null,
+      selectRadio: '1',
+      id: this.$route.params.order_id,
+      orderDetail: {},
+      agreeChecked: false,
+      show: false,
+      pwd: '',
+      payWay: 'balance',
+      bankCardData: {}
+    }
+  },
+  computed: {
+    ...mapGetters(['minUserInfo'])
+  },
+  methods: {
+    getPayOrderDetail () {
+      this.$get({
+        url: `v1/agent/${this.id}`
+      }).then((res) => {
+        if (res) {
+          this.orderDetail = res
+        }
+      })
+    },
+    toggle () {
+      console.log(123)
+    },
+    showDialog () {
+      if (!this.agreeChecked) {
+        this.$toast('请先阅读并同意《牛就对了服务协议》')
+        return
+      }
+      this.show = !this.show
+    },
+    inputFocus () {
+      clearTimeout(this.timer)
+    },
+    inputBlur () {
+      this.timer = setTimeout(() => {
+        window.scrollTo(0, 0)
+      // 间隔设为10,减少页面失去焦点定时器的突兀感,
+      }, 10)
+    },
+    pay () {
+      this.$toast.loading({
+        message: '加载中...',
+        duration: 0
+      })
+      this.show = false
+      this.$post({
+        url: 'v1/llpay',
+        data: {
+          order_id: this.id,
+          pay_way: this.payWay,
+          bank_code: this.bankCardData.bank_code,
+          bank_card: this.bankCardData.bank_card_no,
+          trad_pwd: this.pwd
+        }
+      }).then((res) => {
+        if (res.status === 200) {
+          // localStorage.setItem('pay_price', this.orderDetail.total, 600000)
+          this.$router.replace(`/pay/${this.id}/success`)
+        }
+      })
+      setTimeout(() => { this.$toast.clear() }, 10)
+    },
+    getBankCard () {
+      this.$get({
+        url: 'v1/user/bank_card',
+        data: {
+          full: true
+        }
+      }).then((res) => {
+        if (res) {
+          this.bankCardData = res.result[0]
+        }
+      })
+    },
+    goSetPwd () {
+      this.$router.push(`/user/info/pwd?path=${this.$route.path}`)
+    }
+  },
+  created () {
+    Vue.use(VanImage).use(Col).use(Row).use(Checkbox).use(Radio).use(RadioGroup).use(Cell).use(CellGroup)
+    this.getPayOrderDetail()
+    this.getBankCard()
+    // if (this.minUserInfo && !this.minUserInfo.total) {
+    //   this.$store.dispatch('minUserInfo')
+    // }
+  },
+  destroyed () {
+    clearTimeout(this.timer)
+  }
+}
+</script>
+
+<style lang="less">
+.payPanel () {
+  width: 7.1rem;
+  background: #fff;
+  padding: .2rem;
+  border-radius: .12rem;
+  margin: 0 auto .2rem;
+  box-sizing: border-box;
+}
+.cow-pay {
+  width: 100%;
+  min-height: 100%;
+  background: #f2f2f2;
+  padding-top: .2rem;
+  .van-cell {
+    margin-bottom: .2rem;
+  }
+  .pwdInput {
+    width: 5.4rem;
+    border: 1px solid #f2f2f2;
+    margin: .2rem auto .4rem;
+  }
+  &_product {
+    text-align: left;
+    .payPanel();
+    h3 {
+      height: .8rem;
+      font-weight: 600;
+      font-size: .28rem;
+      color: #4c4c4c;
+    }
+    .price {
+      color: #f14f4f;
+      font-size: .28rem;
+      font-weight: 600;
+    }
+    span {
+      float: right;
+      color: #999;
+      font-size: .24rem;
+      font-weight: normal;
+    }
+  }
+  &_tip {
+    .payPanel();
+    h3 {
+      font-size: .28rem;
+      color: #f14f4f;
+      margin-bottom: .28rem;
+    }
+    .content {
+      text-align: left;
+      font-size: .24rem;
+      color: #999;
+      line-height: .36rem;
+    }
+  }
+  &_city {
+    width: 7.1rem;
+    height: 1rem;
+    line-height: 1rem;
+    font-size: .28rem;
+    color: #362d81;
+    margin: 0 auto .2rem;
+    border-radius: .12rem;
+    background: #d3e1ff;
+    border: solid 1px #3b6bd5;
+  }
+  &_payway {
+    .payPanel();
+    h3 {
+      font-size: .28rem;
+      color: #4c4c4c;
+      margin-bottom: .28rem;
+    }
+    .van-icon__image {
+      width: .5rem;
+      height: .5rem;
+    }
+    .van-cell__title {
+      text-align: left;
+      color: #4c4c4c;
+    }
+    .van-cell {
+      padding: .2rem 0;
+    }
+    .van-hairline--top-bottom::after, .van-hairline-unset--top-bottom::after {
+      border-bottom-color: #fff;
+    }
+  }
+  &_footer {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    .agreement {
+      margin-left: .4rem;
+      font-size: .28rem;
+      color: #4c4c4c;
+      margin-bottom: .3rem;
+      a {
+        color: #00a0e9;
+      }
+    }
+    .bar {
+      height: 1rem;
+      text-align: left;
+      background: #fff;
+      padding: .2rem;
+      box-sizing: border-box;
+    }
+    .info {
+      padding-left: .2rem;
+      line-height: .6rem;
+      font-size: .24rem;
+      color: #999;
+    }
+    .title {
+      color: #4c4c4c;
+    }
+    .total {
+      color: #f14f4f;
+      font-size: .36rem;
+    }
+  }
+}
+</style>

+ 93 - 0
src/views/pay/Pay.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="family-pay__page">
+    <!-- 地址 -->
+    <address-part :addressDetail="orderDetail" :type="orderType" />
+    <!-- 金额 -->
+    <money-part
+    :balanceData="orderDetail.balance"
+    :productTotal="orderDetail.total_price"
+    :type="orderType"
+    :vipPrice="orderDetail.price"
+    :isBuyVip="orderDetail.buy_card_fee?true:false" />
+    <!-- 支付方式 -->
+    <div class="family-pay__weixin clearfix">
+      <img src="@/assets/images/pay/weixin_icon.png" class="weixin-img" alt="">
+      <div class="wieixn-text">微信支付</div>
+      <img src="@/assets/images/pay/select_icon.png" class="weixin-icon" alt="">
+    </div>
+    <!-- 按钮 -->
+    <btn-part :total="payTotal" :type="orderType" title="立即支付" />
+  </div>
+</template>    
+
+<script>
+import { mapGetters } from 'vuex'
+import AddressPart from './components/Address'
+import MoneyPart from './components/Money'
+import BtnPart from './components/Btn'
+export default {
+  name: 'pay',
+  data () {
+    return {
+      orderType: 'payOrder'
+    }
+  },
+  computed: {
+    ...mapGetters(['orderDetail', 'userCheck']),
+    payTotal: function () {
+      let total = this.orderDetail.total_price
+      if (this.orderDetail.balance) {
+        total = total - this.orderDetail.balance
+      }
+      return total
+    }
+  },
+  methods: {
+    getOrderDetail () {
+      this.$store.dispatch('getOrderDetail', { id: this.$route.params.order_id})
+    }
+  },
+  created () {
+    this.getOrderDetail()
+  },
+  watch: {},
+  components: {
+    AddressPart,
+    MoneyPart,
+    BtnPart
+  }
+}
+</script>
+
+<style lang="less">
+.family-pay {
+  &__page {
+    width: 100%;
+    min-height: 100%;
+    background: #f2eeee;
+    overflow: hidden;
+  }
+  &__weixin {
+    width: 100%;
+    background: #ffffff;
+    box-sizing: border-box;
+    padding: 0.2rem;
+    .weixin-img {
+      float: left;
+      height: 0.36rem;
+      width: 0.4rem;
+    }
+    .wieixn-text {
+      float: left;
+      font-size: 0.28rem;
+      color: #4c4c4c;
+      padding-left: 0.2rem;
+    }
+    .weixin-icon {
+      width: 0.34rem;
+      height: 0.34rem;
+      float: right;
+    }
+  }
+}
+</style>

+ 185 - 0
src/views/pay/PayIntegral.vue

@@ -0,0 +1,185 @@
+<template>
+  <div class="family-createOrder__page">
+    <!-- 地址模块 -->
+    <address-part
+    :showIcon="showIcon"
+    :addressDetail="addressDetail"
+    :count="count"
+    :isIntegral="isIntegral" />
+    <!-- 商品模块 -->
+    <div class="family-createOrder__product">
+      <div class="product-top clearfix">
+        <van-image class="product-img" fit="cover" :src="productDetail.img_thumb_url" />
+        <div class="product-info">
+          <p class="product-name">{{productDetail.title}}</p>
+          <p class="product-dec">{{productDetail.sub_title}}</p>
+          <p class="product-money">
+            <span class="money-text">{{!userCheck.is_vip? productDetail.price : productDetail.vip_price}}</span> 积分
+          </p>
+        </div>
+      </div>
+    </div>
+    <!-- 积分消耗 -->
+    <div class="family-createOrder__integral">
+      <p>积分消耗<span>{{!userCheck.is_vip? productDetail.price : productDetail.vip_price}} 积分</span></p>
+    </div>
+
+    <div class="family-createOrder__footer">
+      <div class="pay-bottom1 clearfix">
+        <van-button class="pay-btn" round type="primary" @click="postApi" :disabled="!userCheck.is_vip">{{userCheck.is_vip ? '确认兑换':'非会员不可兑换'}}</van-button>
+        <div class="pay-sum"><span style="color:#4c4c4c">合计</span>: <span class="money-text">{{!userCheck.is_vip? productDetail.price : productDetail.vip_price}}</span> 积分</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { mapGetters } from 'vuex'
+import { Image as VanImage, Divider, Cell, CellGroup } from 'vant'
+import AddressPart from './components/Address'
+import filters from '@/common/filter'
+export default {
+  name: 'payIntegral',
+  data () {
+    return {
+      productId: Number(this.$route.params.product_id),
+      addressId: Number(this.$route.query.address_id),
+      count: 1,
+      addressDetail: {},
+      integralTotal: 0,
+      isIntegral: true,
+      showIcon: false
+    }
+  },
+  computed: {
+    ...mapGetters(['productDetail', 'userCheck']),
+    isEnoughIntegral: function () {
+      if (this.integralTotal >= this.productDetail.vip_price) {
+        return true
+      } else {
+        return false
+      }
+    }
+  },
+  methods: {
+    postApi () {
+      if (!this.isEnoughIntegral) {
+        this.$dialog({
+          title: '提示',
+          message: '您的积分不足,请是否前往获得更多积分',
+        }).then((action) => {
+          if (action === 'confirm') {
+            this.$router.push('/article')
+          }
+        })
+        return
+      }
+      this.$post({
+        url: 'v1/spirit/order/integral',
+        data: {
+          address_id: this.addressDetail.id || 0,
+          quantity: this.count,
+          product_id: this.productId
+        }
+      }).then((resp) => {
+        if (resp) {
+          this.$toast('兑换成功~')
+          this.$router.replace(`/order/${resp.order_id}`)
+        }
+      })
+    },
+    getProductDetail () {
+      this.$store.dispatch('getProductDetail', {id: this.productId})
+    },
+    getIntegralInfo () {
+      this.$get({
+        url: 'v1/user/mall/balance/info'
+      }).then((resp) => {
+        this.integralTotal = resp.total
+      })
+    },
+    getAddressDefault () {
+      this.$get({
+        url: 'v1/user/address/default'
+      }).then((resp) => {
+        if (resp) {
+          this.addressDetail = resp
+        }
+      })
+    },
+    getAddressDetail () {
+      this.$get({
+        url: `v1/address/${this.addressId}`
+      }).then((resp) => {
+        if (resp) {
+          this.addressDetail = resp
+        }
+      })
+    }
+  },
+  created () {
+    Vue.use(VanImage).use(Divider).use(Cell).use(CellGroup)
+    if (this.addressId) {
+      this.getAddressDetail()
+    } else {
+      this.getAddressDefault()
+    }
+    this.getIntegralInfo()
+    if (this.productDetail && !this.productDetail.id) {
+      this.getProductDetail()
+    }
+  },
+  watch: {
+  },
+  components: {
+    AddressPart
+  }
+}
+</script>
+
+<style lang="less">
+.family-createOrder {
+  &__integral {
+    box-sizing: border-box;
+    padding: 0.28rem 0.2rem;
+    background: #fff;
+    margin-bottom: 0.3rem;
+    font-size: .28rem;
+    color: #4c4c4c;
+    margin-top: .24rem;
+    span {
+      float: right;
+      color: #f14f4f;
+    }
+  }
+  &__footer {
+    background: #fff;
+    box-sizing: border-box;
+    padding: 0.2rem;
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+    z-index: 999;
+    .pay-btn {
+      height: 0.74rem;
+      min-width: 2.62rem;
+      background: #07c160;
+      float: right;
+      color: #fff;
+      font-size: 0.28rem;
+      margin-left: 0.4rem;
+      border: 1px solid #07c160;
+    }
+    .pay-sum {
+      color: #f47b7b;
+      font-size: 0.28rem;
+      float: right;
+      margin-top: 0.2rem;
+      .money-text {
+        font-size: 0.34rem;
+      }
+    }
+  }
+}
+</style>

+ 83 - 0
src/views/pay/Success.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="cow-pay_success">
+    <van-empty description="支付成功">
+      <van-divider :style="{ width: '7.1rem' }" />
+      <div class="price">支付金额: <span>{{price | getAcounting('¥', false)}}</span></div>
+      <van-button type="primary" size="small" round block class="btn" @click="goOrder">
+        查看订单
+      </van-button>
+      <template #image>
+        <div class="icon">
+          <van-icon name="success" color="#fff" size="1.4rem" />
+        </div>
+      </template>
+    </van-empty>
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Empty, Divider } from 'vant'
+// import localStorage from '@/common/localStorage'
+export default {
+  data () {
+    return {
+      price: 0
+    }
+  },
+  methods: {
+    goOrder () {
+      this.$router.push('/user/orders')
+    }
+  },
+  created () {
+    Vue.use(Empty).use(Divider)
+    // if (localStorage.getItem('pay_price')) {
+    //   this.price = localStorage.getItem('pay_price')
+    // }
+  }
+}
+</script>
+
+<style lang="less">
+.cow-pay {
+  &_success {
+    width: 100%;
+    .icon {
+      width: 1.6rem;
+      height: 1.6rem;
+      border-radius: 50%;
+      background: #28cd5e;
+      margin: 0 auto;
+      padding-top: .18rem;
+      box-sizing: border-box;
+    }
+    .price {
+      font-size: .24rem;
+      color: #999;
+      margin-bottom: .4rem;
+      span {
+        color: #4c4c4c;
+        font-size: .4rem;
+        font-weight: 600;
+      }
+    }
+    .btn {
+      width: 2.56rem;
+      margin: 0 auto;
+    }
+    .van-empty__image {
+      width: 1.6rem;
+      height: 1.6rem;
+    }
+    .van-empty {
+      padding-top: 1rem;
+    }
+    .van-empty__description {
+      color: #2cd463;
+      font-size: .32rem;
+      font-weight: 600;
+    }
+  }
+}
+</style>

+ 115 - 0
src/views/pay/components/Address.vue

@@ -0,0 +1,115 @@
+<template>
+  <div class="address-bg">
+    <div class="pay-top clearfix" @click="toAddressList" v-if="addressDetail && addressDetail.address">
+      <img src="@/assets/images/address/address.png" class="top-img" alt="" />
+      <div class="top-text">
+        <div class="info-name">
+          {{addressDetail.contact}}
+          <small class="info-tel">{{addressDetail.tel}}</small>
+        </div>
+        <p class="text-address">
+          <span v-if="showIcon">
+            {{addressDetail.province + addressDetail.city + addressDetail.district + addressDetail.address}}
+          </span>
+          <span v-else>
+            {{addressDetail.address}}
+          </span>
+        </p>
+      </div>
+      <van-icon name="arrow" class="top-icon" v-if="showIcon" color="#999" size="20" />
+    </div>
+
+    <div class="pay-top" v-else @click="toAddressCreate">
+      <van-button icon="plus" type="primary" color="#07c160" size="small"/>
+      <span class="add">新增收货地址</span>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  data () {
+    return {
+      showIcon: true,
+      product_id: this.$route.params.product_id
+    }
+  },
+  props: {
+    addressDetail: Object,
+    type: String,
+    count: Number,
+    isIntegral: {
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {},
+  methods: {
+    toAddressList () {
+      this.$router.push(`/address/list?address_id=${this.addressDetail.id}&count=${this.count}&pid=${this.product_id}&is_integral=${this.isIntegral}`)
+    },
+    toAddressCreate () {
+      this.$router.push('/address/edit')
+    }
+  },
+  created () {
+    if (this.type) {
+      this.showIcon = false
+    }
+  },
+  watch: {},
+  components: {}
+}
+</script>
+<style lang="less" scoped>
+.address-bg {
+  margin-bottom: .3rem;
+  .van-button--small {
+    min-width: 30px;
+  }
+  .pay-top {
+    width: 100%;
+    background: #fff;
+    padding: 0.2rem;
+    box-sizing: border-box;
+    overflow: hidden;
+    .add {
+      font-size: .28rem;
+      color: #4c4c4c;
+      margin-left: .2rem;
+    }
+    .top-img {
+      display: block;
+      float: left;
+      width: 0.8rem;
+      height: 0.8rem;
+      margin-top: 0.3rem;
+      margin-right: 0.2rem;
+    }
+    .top-text {
+      float: left;
+      .info-name {
+        color: #4c4c4c;
+        font-size: 0.34rem;
+        margin-top: 0.14rem;
+      }
+      .info-tel {
+        color: #999;
+        font-size: 0.24rem;
+        margin-left: .12rem;
+      }
+      .text-address {
+        margin-top: 0.15rem;
+        color: #4c4c4c;
+        font-size: 0.28rem;
+        width: 5.2rem;
+        margin-bottom: 0.1rem;
+        line-height: 0.4rem;
+      }
+    }
+    .top-icon {
+      margin-top: .6rem;
+      margin-left: .2rem;
+    }
+  }
+}
+</style>

+ 202 - 0
src/views/pay/components/Btn.vue

@@ -0,0 +1,202 @@
+<template>
+  <div class="family-btn">
+    <div class="pay-bottom1 clearfix">
+      <van-button class="pay-btn" round type="primary" v-if="loading" loading :loading-text="title" />
+      <van-button class="pay-btn" round type="primary" v-else @click="postApi">{{title}}</van-button>
+      <div class="pay-sum"><span style="color:#4c4c4c">合计</span>: ¥<span class="money-text">{{total | getAcounting('', false)}}</span></div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  data () {
+    return {
+      oid: this.$route.params.order_id,
+      payData: {},
+      loading: false,
+      countdownFlag: false,
+      userData: {
+        name: '',
+        tel: ''
+      }
+    }
+  },
+  props: {
+    total: Number,
+    title: String,
+    type: String,
+    orderData: Object
+  },
+  computed: {
+    ...mapGetters(['userInfo']),
+    loadingText: function () {
+      if (this.type === 'order' || this.type === 'orderJoin') {
+        return '创建订单中...'
+      } else {
+        return '正在支付中...'
+      }
+    }
+  },
+  methods: {
+    loadClick () {
+      this.loading = true
+      this.$toast.loading({
+        message: this.loadingText,
+        forbidClick: true,
+        duration: 0
+      })
+    },
+    cancelLoading () {
+      this.loading = false
+      this.$toast.clear()
+    },
+    // 创建订单
+    createOrder () {
+      this.$post({
+        url: 'v1/spirit/order/create',
+        data: {
+          product_id: this.orderData.product_id,
+          quantity: this.orderData.count,
+          address_id: this.orderData.address_id,
+          use_balance: this.orderData.use_balance,
+          remark: '',
+          by_vip_user: this.orderData.by_vip_user
+        }
+      }).then((resp) => {
+        this.cancelLoading()
+        if (resp.resp_common && resp.resp_common.code) {
+          this.$toast(resp.resp_common.msg)
+          return
+        }
+        this.oid = resp.order_id
+        // 提交订单为0元,跳支付成功
+        if (!resp.IsPaid) {
+          this.$router.push(`/pay/${resp.order_id}`)
+        } else {
+          this.$toast('支付成功~')
+          this.$router.replace(`/order/${resp.order_id}`)
+        }
+      })
+    },
+    goSuccess () {
+      this.$router.push(`/order/${this.oid}`)
+    },
+    // 微信支付
+    onBridgeReady () {
+      this.$toast.clear()
+      let that = this
+      // eslint-disable-next-line
+      WeixinJSBridge.invoke('getBrandWCPayRequest', {
+        appId: this.payData.appId,
+        timeStamp: this.payData.timeStamp,
+        nonceStr: this.payData.nonceStr,
+        package: this.payData.package,
+        signType: this.payData.signType,
+        paySign: this.payData.paySign
+      },
+      function (res) {
+        if (res.err_msg === 'get_brand_wcpay_request:ok') {
+          that.$toast.loading('支付成功,跳转中···')
+          setTimeout(() => { that.goSuccess() }, 1000)
+        } else {
+          that.$toast('微信支付失败!')
+        }
+      })
+    },
+    pay () {
+      this.$post({
+        url: 'v1/pay',
+        data: {
+          order_id: this.oid,
+          pay_way: 'weixinpay'
+        }
+      }).then((resp) => {
+        this.cancelLoading()
+        this.payData = resp.pay_data
+        // eslint-disable-next-line
+        if (typeof WeixinJSBridge === undefined) {
+          if (document.addEventListener) {
+            document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false)
+          } else if (document.attachEvent) {
+            document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady)
+            document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady)
+          }
+        } else {
+          this.onBridgeReady()
+        }
+      })
+    },
+    validatorOrder () {
+      let msg
+      if (this.type === 'payOrder') {
+        return { isOk: !msg, msg }
+      }
+      if (!this.orderData.address_id) {
+        msg = '请先新增收件地址'
+      }
+      return { isOk: !msg, msg }
+    },
+    postApi () {
+      let { isOk, msg } = this.validatorOrder()
+      if (isOk) {
+        this.loadClick()
+        if (this.type === 'order') {
+          this.createOrder()
+        } else {
+          this.$toast.loading('支付中···')
+          this.pay()
+        }
+      } else {
+        this.$toast(msg)
+      }
+    }
+  },
+  created () {
+  },
+  mounted () {
+  },
+  watch: {
+  },
+  components: {}
+}
+</script>
+<style lang="less" scoped>
+.family-btn {
+  .pay-bottom1 {
+    background: #fff;
+    box-sizing: border-box;
+    padding: 0.2rem;
+    position: fixed;
+    bottom: 0;
+    width: 100%;
+    z-index: 999;
+    .pay-btn {
+      height: 0.74rem;
+      min-width: 2.62rem;
+      background: #07c160;
+      float: right;
+      color: #fff;
+      font-size: 0.28rem;
+      margin-left: 0.4rem;
+      border: 1px solid #07c160;
+    }
+    .pay-btn1 {
+      background: linear-gradient(#ffd01e, #ff8917);
+    }
+    .pay-btn2 {
+      background: #999;
+    }
+    .pay-sum {
+      color: #f47b7b;
+      font-size: 0.28rem;
+      float: right;
+      margin-top: 0.2rem;
+      .money-text {
+        font-size: 0.34rem;
+      }
+    }
+  }
+}
+</style>

+ 114 - 0
src/views/pay/components/Money.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="money-bg">
+    <ul class="pay-detail">
+      <li class="detail-item clearfix" v-if="type === 'payOrder' && !isUserVip">
+        <p class="detail-left">商品金额</p>
+        <p class="detail-right" :class="{active: isUserVip}">{{productTotal | getAcounting('¥', false)}}</p>
+      </li>
+      <li class="detail-item clearfix" v-if="type !== 'payOrder'">
+        <p class="detail-left">商品金额</p>
+        <p class="detail-right" :class="{active: isUserVip}">{{productTotal | getAcounting('¥', false)}}</p>
+      </li>
+      <li class="detail-item clearfix" v-if="isUserVip">
+        <p class="detail-left">会员价</p>
+        <p class="detail-right">{{vipPrice | getAcounting}}</p>
+      </li>
+      <li class="detail-item clearfix" v-if="isBuyVip">
+        <p class="detail-left">会员卡</p>
+        <p class="detail-right">{{userCheck && userCheck.vip_card_fee | getAcounting}}</p>
+      </li>
+      <li class="detail-item clearfix" v-if="isBalance || isUseBalance">
+        <p class="detail-left">余额抵扣</p>
+        <p class="detail-right">-{{balanceDeduct | getAcounting('¥', false)}}</p>
+      </li>
+    </ul>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  data () {
+    return {
+    }
+  },
+  props: {
+    productTotal: Number,
+    balanceData: Number,
+    isUseBalance: {
+      type: Boolean,
+      default: false
+    },
+    isBuyVip: Boolean,
+    type: {
+      type: String,
+      default: 'createOrder'
+    },
+    vipPrice: Number
+  },
+  computed: {
+    ...mapGetters(['userCheck']),
+    isBalance: function () {
+      if (this.type === 'payOrder') {
+        if (this.balanceData > 0) {
+          return true
+        }
+      }
+      return false
+    },
+    isUserVip () {
+      if (this.userCheck && (this.userCheck.is_vip || this.isBuyVip)) {
+        return true
+      }
+      return false
+    },
+    balanceDeduct: function () {
+      let balance = this.balanceData
+      let total = this.productTotal
+      if (this.userCheck && this.userCheck.is_vip) {
+        total = this.vipPrice
+      } else if (this.isBuyVip) {
+        total = this.vipPrice + this.userCheck.vip_card_fee
+      }
+      let differ = total - balance
+      if (differ < 0) {
+        return total
+      } else {
+        return balance
+      }
+    }
+  },
+  methods: {},
+  created () {},
+  watch: {
+  },
+  components: {}
+}
+</script>
+<style lang="less" scoped>
+.money-bg {
+  margin-top: 0.24rem;
+  .pay-detail {
+    box-sizing: border-box;
+    padding: 0.2rem 0.2rem;
+    background: #fff;
+    margin-bottom: 0.3rem;
+    .detail-item {
+      line-height: 0.6rem;
+      .detail-left {
+        float: left;
+        color: #4c4c4c;
+        font-size: 0.28rem;
+      }
+      .detail-right {
+        float: right;
+        color: #ff484c;
+        font-size: 0.28rem;
+        &.active {
+          color: #4c4c4c;
+          text-decoration: line-through;
+        }
+      }
+    }
+  }
+}
+</style>

+ 380 - 0
src/views/product/Detail.vue

@@ -0,0 +1,380 @@
+<template>
+  <div class="cow-product_detail">
+    <div class="product_info">
+      <div class="pic">
+        <van-image :src="productDetail.img_thumb_url" class="product_img">
+          <template v-slot:loading>
+            <van-loading type="spinner" size="20" />
+          </template>
+        </van-image>
+        <div class="location">
+          <span>已售:{{productDetail.sales_quantity}}</span>
+        </div>
+      </div>
+
+      <van-row type="flex" justify="space-between">
+        <van-col span="16" class="desc">
+          <h3>{{productDetail.title}}</h3>
+          <p>{{productDetail.about}}</p>
+        </van-col>
+        <van-col span="8" class="price">
+          {{productDetail.price | getAcounting('¥', false)}} <small>{{productDetail.price_unit}}</small>
+          <p class="origin">市场价:{{productDetail.market_price | getAcounting('¥', false)}}</p>
+        </van-col>
+      </van-row>
+    </div>
+
+    <van-tabs v-model="active" sticky title-active-color="#28cd5e" color="#28cd5e" class="cow-product_tab">
+      <van-tab v-for="(item, i) in details" :title="item" :key="i">
+        <div v-html="productDetail.detail" v-if="!active" class="introduction" @click="previewImg"></div>
+        <div v-html="productDetail.profit_info" v-if="active === 1"></div>
+        <div v-html="productDetail.risk_tips" v-if="active === 2"></div>
+      </van-tab>
+    </van-tabs>
+
+    <footer class="cow-product_footer">
+      <van-row>
+        <van-col span="12">
+          <van-grid :column-num="3" :icon-size="20">
+            <van-grid-item icon="http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/product/list_icon.png" text="商品" @click="goProduct" />
+            <van-grid-item :icon="contractDialog?'comment':'comment-o'" :class="contractDialog?'icon_active':''" text="客服" @click="showContractDialog" />
+            <van-grid-item :icon="productDetail.is_follow?'like':'like-o'" :class="productDetail.is_follow?'icon_active':''" :text="String(productDetail.follow_num)" @click="setFollow" />
+          </van-grid>
+        </van-col>
+
+        <van-col span="12" class="btn">
+          <van-button type="primary" size="small" round block @click="showDialog">立即购买</van-button>
+        </van-col>
+      </van-row>
+    </footer>
+
+    <!-- 认购弹窗 -->
+    <van-popup v-model="show" position="bottom" :style="{ height: '40%' }" class="cow-product_dialog">
+      <van-row type="flex">
+        <van-col span="7">
+          <van-image width="1.8rem" height="1.8rem" :src="productDetail.img_thumb_url" />
+        </van-col>
+        <van-col span="16" class="info">
+          <h3>{{productDetail.title}}</h3>
+          <p class="remain">库存:{{productDetail.remain}}</p>
+          <p class="price">单价:<span>{{productDetail.price | getAcounting('¥', false)}}</span></p>
+        </van-col>
+      </van-row>
+
+      <van-divider />
+
+      <van-row type="flex" justify="space-between">
+        <van-col span="14">
+          <p class="cow-product_count">认购数量<span v-if="false">(起购2份,限购10份)</span></p>
+        </van-col>
+        <van-col span="10" class="cow-product_step">
+          <van-stepper v-model="count" :min="productDetail.limit_min" :max="productDetail.amount" />
+        </van-col>
+      </van-row>
+
+      <div class="btn">
+        <van-button type="primary" size="small" round block @click="toCreateOrder">立即认购</van-button>
+      </div>
+    </van-popup>
+
+    <!-- 联系客服弹窗 -->
+    <van-dialog v-model="contractDialog" title="添加客服" :show-confirm-button="false" :closeOnClickOverlay="true" class="cow-product_service_dialog">
+      <van-image src="http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/service_code.jpg">
+        <template v-slot:loading>
+          <van-loading type="spinner" size="20" />
+        </template>
+      </van-image>
+      <p class="desc">扫描二维码添加官网客服</p>
+      <p class="desc">添加好友时备注"拉比兔"</p>
+    </van-dialog>
+  </div>
+</template>
+
+<script>
+import wx from 'weixin-js-sdk'
+import Vue from 'vue'
+import { Image as VanImage, Col, Row, Grid, GridItem, Divider, Stepper, Tab, Tabs, Loading } from 'vant'
+import { mapGetters } from 'vuex'
+// import localStorage from '@/common/localStorage'
+// import Weixin from '@/common/wx'
+export default {
+  name: 'product',
+  data () {
+    return {
+      wx: null,
+      id: this.$route.params.product_id,
+      active: 0,
+      details: ['商品详情'],
+      show: false,
+      contractDialog: false,
+      count: 1
+    }
+  },
+  computed: {
+    ...mapGetters(['productDetail', 'minUserInfo', 'userCheck']),
+    isCanBuy: function () {
+      let msg = ''
+      let user = this.minUserInfo
+      if (!this.checkUser.user_id) {
+        msg = '您尚未绑定手机号,请先前往绑定'
+      } else if (!user.is_real) {
+        msg = '您尚未实名,请先前往实名'
+      } else if (!user.is_bind) {
+        msg = '您尚未绑卡,请先前往绑卡'
+      } else if (!user.has_trade) {
+        msg = '您尚未设置平台交易密码,请先前往设置'
+      } else if (user.total < this.buyTotal) {
+        msg = '余额不足,请先前往充值'
+      }
+      return { isOk: !msg, msg }
+    },
+    buyTotal: function () {
+      let price = this.productDetail.price
+      let count = this.count
+      let total = price * count
+      return Math.round(total)
+    }
+  },
+  methods: {
+    showDialog () {
+      this.show = !this.show
+    },
+    showContractDialog () {
+      this.contractDialog = !this.contractDialog
+    },
+    getProductDetail () {
+      this.$store.dispatch('getProductDetail', {id: this.id})
+    },
+    createOrder () {
+      this.$toast.loading({
+        message: '创建订单中...',
+        forbidClick: true,
+        duration: 0
+      })
+      this.$post({
+        url: `v1/agent/create/${this.count}/${this.id}/0`
+      }).then((res) => {
+        if (res) {
+          this.$toast.clear()
+          this.$router.push(`/pay/${res.order_id}`)
+        }
+      })
+    },
+    checkInvite (id) {
+      this.$post({
+        url: `v1/agent/save_inite/${id}`
+      }).then((res) => {
+        console.log(res)
+        this.createOrder()
+      })
+    },
+    buy () {
+      let { isOk, msg } = this.isCanBuy
+      if (!isOk) {
+        this.tipDialog(msg)
+        return
+      }
+      if (!this.minUserInfo.has_invite && this.$route.query.invite_id) {
+        this.checkInvite(this.$route.query.invite_id)
+      } else if (localStorage.getItem('invite_id') && !this.minUserInfo.has_invite) {
+        this.checkInvite(localStorage.getItem('invite_id'))
+      } else {
+        this.createOrder()
+      }
+    },
+    toCreateOrder () {
+      this.$router.push(`/create/order/${this.id}/${this.count}`)
+    },
+    tipDialog(msg) {
+      this.$dialog.confirm({
+        title: '提示',
+        message: msg,
+        confirmButtonColor: '#28cd5e',
+        confirmButtonText: '前往'
+      }).then(() => {
+        if (msg === '余额不足,请先前往充值') {
+          this.$router.push('/user/recharge?url=' + this.$route.path)
+        } else {
+          this.$router.push('/binding?url=' + this.$route.path)
+        }
+      })
+    },
+    goProduct () {
+      this.$router.push('/product')
+    },
+    previewImg (e) {
+      if (e.target.nodeName === 'IMG') {
+        // let _images = document.querySelectorAll('.introduction img')
+        // var images = [..._images].map(img => {
+        //   return img.src
+        // })
+        // this.wx.previewImage({
+        //   current: e.target.src,
+        //   urls: images
+        // })
+      }
+    },
+    setFollow () {
+      this.$post({
+        url: `v1/project/${this.id}/follow`
+      }).then((res) => {
+        this.productDetail.is_follow = res.state
+        if (res.state) {
+          this.productDetail.follow_num++
+          this.$toast('已关注')
+        } else {
+          this.productDetail.follow_num--
+          this.$toast('已取关')
+        }
+      })
+    },
+    shareWx () {
+      let product = this.productDetail
+      let link = `${window.location.href}?invite_id=${this.userCheck.wx_user_id}`
+      let data = {
+        title: product.s_title || product.title,
+        imgUrl: product.img_thumb_url,
+        link: link,
+        desc: product.s_about || product.about
+      }
+      wx.onMenuShareTimeline(data)
+      wx.onMenuShareAppMessage(data)
+    }
+  },
+  created () {
+    Vue.use(VanImage).use(Col).use(Row).use(Grid).use(GridItem).use(Divider).use(Stepper).use(Tab).use(Tabs).use(Loading)
+    this.getProductDetail()
+    // if (!!this.checkUser && this.checkUser.user_id) {
+    //   this.$store.dispatch('minUserInfo')
+    // }
+    // 防止inviteId丢失
+    // if (this.$route.query.invite_id) {
+    //   if (!localStorage.getItem('invite_id')) {
+    //     localStorage.setItem('invite_id', this.$route.query.invite_id, 60 * 60 * 1000)
+    //   }
+    // }
+  },
+  watch: {
+    'productDetail': function (val) {
+      if (val) {
+        window.document.title = val.title
+        this.shareWx()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="less">
+.cow-product {
+  &_detail {              
+    width: 100%;
+    min-height: 100%;
+    background: #f2f2f2;
+    .product_img {
+      width: 7.1rem;
+      height: 7.1rem;
+    }
+    .van-dialog {
+      width: 5.4rem;
+    }
+    .van-dialog__header {
+      font-size: .32rem;
+    }
+  }
+  &_tab {
+    width: 100%;
+    background: #fff;
+    margin-bottom: 1.2rem;
+    text-align: left;
+    font-size: .28rem;
+    color: #4c4c4c;
+    img {
+      width: 100%;
+    }
+  }
+  &_footer {
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 1rem;
+    background: #fff;
+    .btn {
+      padding: 0.2rem;
+    }
+    .van-grid-item__content {
+      padding: .16rem;
+      box-sizing: border-box;
+    }
+    .van-grid-item__icon+.van-grid-item__text {
+      margin-top: 0.04rem;
+    }
+    .icon_active {
+      color: #28cd5e;
+    }
+  }
+  &_dialog {
+    text-align: left;
+    padding: .2rem;
+    color: #4c4c4c;
+    font-size: .28rem;
+    box-sizing: border-box;
+    h3 {
+      font-weight: 600;
+      font-size: .28rem;
+    }
+    .remain {
+      font-size: .24rem;
+      color: #999;
+      margin-top: .1rem;
+    }
+    .info {
+      box-sizing: border-box;
+    }
+    .price {
+      font-size: .24rem;
+      margin-top: .1rem;
+      span {
+        font-size: .36rem;
+        font-weight: 600;
+        color: #f14f4f;
+      }
+    }
+    .btn {
+      width: 100%;
+      position: absolute;
+      bottom: .2rem;
+      left: 0;
+      padding: 0 .2rem;
+      box-sizing: border-box;
+    }
+  }
+  &_service_dialog {
+    width: 5.4rem;
+    padding-bottom: .5rem;
+    color: #4c4c4c;
+    line-height: .42rem;
+    text-align: center;
+    img {
+      width: 3.6rem;
+      height: 3.6rem;
+      margin-bottom: .2rem;
+    }
+    .desc {
+      font-size: .26rem;
+    }
+  }
+  &_step {
+    text-align: right;
+  }
+  &_count {
+    font-size: .28rem;
+    color: #4c4c4c;
+    span {
+      margin-left: .08rem;
+      color: #999;
+    }
+  }
+}
+</style>

+ 116 - 0
src/views/product/List.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="cow-product_list">
+    <van-list v-model="loading" :finished="finished" :finished-text="page > 1 ? '已经到底了' : ''" @load="onLoad">
+      <ul>
+        <li class="product_info" v-for="(item, i) in list" :key="i" @click="goDetail(item.id)">
+          <div class="pic">
+            <van-image :src="item.img_thumb_url" class="product_img">
+              <template v-slot:loading>
+                <van-loading type="spinner" size="20" />
+              </template>
+            </van-image>
+            <div class="location">
+              <!-- <van-icon name="location" /> -->
+              <span>已售:{{item.sales_quantity}}</span>
+            </div>
+          </div>
+
+          <van-row type="flex" justify="space-between">
+            <van-col span="16" class="desc">
+              <h3>{{item.title}}</h3>
+              <p>{{item.about}}</p>
+            </van-col>
+            <van-col span="8" class="price">
+              <p>{{item.price | getAcounting('¥', false)}} <small>{{item.price_unit}}</small></p>
+              <p class="origin">{{item.market_price | getAcounting('¥', false)}}</p>
+            </van-col>
+          </van-row>
+        </li>
+      </ul>
+    </van-list>
+    <footer-tab />
+  </div>
+</template>
+
+<script>
+import Vue from 'vue'
+import { Image as VanImage, Col, Row, Loading } from 'vant'
+// import localStorage from '@/common/localStorage'
+import FooterTab from '@/components/Footer'
+const PER_PAGE = 10
+export default {
+  name: 'product',
+  data () {
+    return {
+      page: 1,
+      per_page: PER_PAGE,
+      loading: false,
+      finished: false,
+      list: [],
+      total: 0
+    }
+  },
+  methods: {
+    onLoad () {
+      if (this.list.length < this.total) {
+        this.loading = true
+        this.page += 1
+        this.getList()
+      }
+    },
+    goDetail (id) {
+      if (this.$route.query.invite_id) {
+        this.$router.push(`/product/${id}?invite_id=${this.$route.query.invite_id}`)
+      } else {
+        this.$router.push(`/product/${id}`)
+      }
+    },
+    getList () {
+      this.$get({
+        url: 'v1/project/list',
+        data: { page: this.page, per_page: this.per_page }
+      }).then((res) => {
+        this.loading = false
+        if (res.list) {
+          this.list = this.list.concat(res.list || [])
+          this.total = res.list_count
+        }
+        if (this.list.length >= this.total) {
+          this.finished = true
+        }
+      })
+    }
+  },
+  created () {
+    this.getList()
+    Vue.use(VanImage).use(Col).use(Row).use(Loading)
+    // 防止inviteId丢失
+    // if (this.$route.query.invite_id) {
+    //   if (!localStorage.getItem('invite_id')) {
+    //     localStorage.setItem('invite_id', this.$route.query.invite_id, 60 * 60 * 1000)
+    //   }
+    // }
+    // 设置认购流程默认分享方法
+    // this.GLOBAL.shareWx()
+  },
+  components: {
+    FooterTab
+  }
+}
+</script>
+
+<style lang="less">
+@import url('../../assets/style/product.less');
+.cow-product {
+  &_list {
+    width: 100%;
+    min-height: 100%;
+    background: #f2f2f2;
+    padding-bottom: 1rem;
+    .product_img {
+      width: 7.1rem;
+      height: 7.1rem;
+    }
+  }
+}
+</style>

+ 504 - 0
src/views/user/User.vue

@@ -0,0 +1,504 @@
+<template>
+  <div class="user" v-if="info.wx_user">
+    <div class="user-top">
+      <div class="top-head" v-if="info.wx_user.head">
+        <img :src="info.wx_user.head" class="head-img" alt="">
+      </div>
+      <div class="top-head" v-if="info.wx_user.head === ''">
+        <img src="../../assets/img/wx.png" class="head-img" alt="">
+      </div>
+      <div class="name-part">
+        <div class="top-name" v-if="info.wx_user.nickname">{{info.wx_user.nickname}}</div>
+        <div class="top-name" v-if="!info.wx_user.nickname">微信用户</div>
+        <img src="../../assets/img/member.png" alt="" class="top-member" v-if="info.wx_user.vip">
+        <img src="../../assets/img/member1.png" alt="" class="top-member" v-if="!info.wx_user.vip">
+      </div>
+      <div class="top-id">
+        <div class="id-in" v-if="info.wx_user.id">ID: {{info.wx_user.id}}</div>
+      </div>
+      <!-- <router-link to="/user/info">
+        <div class="top-info">账户信息</div>
+        <img src="@/assets/img/invite/icon2.png" alt="" class="top-icon2">
+      </router-link> -->
+    </div>
+    <div class="user-member" @click="tomember">
+      <img src="../../assets/img/member.png" alt="" class="member-icon">
+      <div class="member-text" v-if="info.wx_user.vip && time >= 1">您的VIP尊享卡还有{{time}}天到期</div>
+      <div class="member-text" v-if="info.wx_user.vip && time < 1">您的VIP尊享卡不足1天</div>
+      <div class="member-text" v-if="!info.wx_user.vip">VIP尊享卡,享会员专享权益</div>
+      <img src="../../assets/img/icon1.png" alt="" class="member-icon1">
+      <div class="member-text1" v-if="info.wx_user.vip">立即续费</div>
+      <div class="member-text1" v-if="!info.wx_user.vip">立即开通</div>
+      <div class="clear"></div>
+    </div>
+    <div class="user-tip">
+      <div class="tip-left">
+        <router-link to="/account">
+          <div class="tip-text">余额账户(元)</div>
+          <div class="tip-count">{{userBalanceInfo.available | getAcounting('', false)}}</div>
+        </router-link>
+      </div>
+      <div class="tip-right">
+        <router-link to="/integral">
+          <div class="tip-text">积分账户</div>
+          <div class="tip-count">{{total}}</div>
+        </router-link>
+      </div>
+      <!-- <div class="info-top">
+        <div class="top-left">我的账户</div>
+        <router-link to="/account">
+          <img src="../../assets/img/icon.png" class="top-icon" alt="">
+          <div class="top-right">提现明细</div>
+        </router-link>
+        <div class="clear"></div>
+      </div>
+      <router-link to="/account">
+        <div class="info-center">
+          <div class="info-money">{{userBalanceInfo.available | getAcounting('¥', false)}}</div>
+          <div class="info-text">账户余额</div>
+        </div>
+      </router-link> -->
+      <!-- <div class="info-bottom">
+        <div class="bottom-left">
+          <div class="left-btn" @click="torecharge">充值</div>
+        </div>
+        <div class="bottom-right">
+          <div class="right-btn" @click="towithdraw">提现</div>
+        </div>
+      </div> -->
+    </div>
+    <div class="user-list" v-for="(items, index) in list" :key="index">
+      <div class="list-item" v-for="(item, i) in items" :key="i" v-show="item.isShow">
+        <router-link :to="item.url">
+          <img :src="item.icon" class="item-img" alt="">
+          <div class="item-title">{{item.title}}</div>
+          <img src="../../assets/img/icon.png" class="item-icon" alt="">
+          <div class="clear"></div>
+        </router-link>
+      </div>
+    </div>
+    <Footer/>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+import { Dialog } from 'vant'
+import Footer from '../../components/Footer.vue'
+export default {
+  name: '',
+  props: [''],
+  data () {
+    return {
+      user: {},
+      info: {},
+      time: '',
+      total: 0
+    }
+  },
+  computed: {
+    ...mapGetters(['userCheck', 'userInfo', 'userBalanceInfo']),
+    returnBindContent: function () {
+      if (this.userInfo) {
+        if (!this.userInfo.real_name) {
+          return '您尚未实名,请先进行实名验证'
+        } else if (!this.userInfo.is_bind_card) {
+          return '您尚未绑银行卡,请先绑定银行卡'
+        } else if (!this.userInfo.has_trade_pwd) {
+          return '您尚未设置交易密码,请先设置平台交易密码'
+        } else {
+          return false
+        }
+      } else {
+        return false
+      }
+    },
+    list: function () {
+      let arr
+      arr = [
+        [
+        // {
+        //   title: '我的积分',
+        //   url: '/integral',
+        //   icon: 'https://cow-fomal.oss-cn-shenzhen.aliyuncs.com/cow_icon/integral_icon.png',
+        //   isShow: true
+        // },
+        {
+          title: '我的订单',
+          url: '/order',
+          icon: 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/cow_icon/order_icon.png',
+          isShow: true
+        }, {
+          title: '推广收益',
+          url: '/invite',
+          icon: 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/cow_icon/invite_icon.png',
+          isShow: true
+        }, 
+        // {
+        //   title: '我的关注',
+        //   url: '/user/attention',
+        //   icon: 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/cow_icon/attention_icon.png',
+        //   isShow: true
+        // }, 
+        {
+          title: '地址管理',
+          url: '/address/list',
+          icon: 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/cow_icon/address_icon.png',
+          isShow: true
+        }]
+      ]
+      return arr
+    }
+  },
+  beforeMount() {},
+  mounted() {
+  },
+  methods: {
+    tomember () {
+      this.$router.push('/member')
+    },
+    towithdraw () {
+      if (!this.returnBindContent) {
+        this.$router.push('/user/withdraw')
+      } else {
+        this.tipClick(this.returnBindContent)
+      }
+    },
+    getintegral () {
+      this.$get({
+        url: 'v1/user/mall/balance/info',
+        data: {}
+      }).then((res) => {
+        console.log(res)
+        this.total = res.total
+      })
+    },
+    torecharge () {
+      if (!this.returnBindContent) {
+        this.$router.push('/user/recharge')
+      } else {
+        this.tipClick(this.returnBindContent)
+      }
+    },
+    tipClick (val) {
+      let msg = '请先完善账户资料'
+      if (val) {
+        msg = val
+      }
+      Dialog.confirm({
+        title: '提示',
+        message: msg,
+        confirmButtonColor: '#29ce5f',
+        confirmButtonText: '前往'
+      }).then(() => {
+        this.$router.push('/binding?url=' + this.$route.path)
+      })
+    },
+    registerClick () {
+      if (this.checkUser && !this.checkUser.user_id) {
+        this.$router.push('/binding?url=' + this.$route.path)
+      } else {
+        this.$store.dispatch('userInfo')
+      }
+    }
+  },
+  created () {
+    this.$store.dispatch('userBalanceInfo')
+    this.$store.dispatch('userInfo')
+    this.getintegral()
+    // setTimeout(() => {
+    //   this.registerClick()
+    // }, 500)
+  },
+  watch: {
+    'userInfo': function (val) {
+      if (val) {
+        this.info = val
+        var newtime = Date.parse(new Date()) / 1000;
+        this.time = Math.floor((val.wx_user.vip_time - newtime) / (3600*24))
+      }
+    }
+  },
+  components: {
+    Footer
+  }
+}
+</script>
+<style lang='less' scoped>
+.user {
+  // background: #eeeeed;
+  min-height: 100%;
+  overflow: hidden;
+  background: url('../../assets/img/bg.png') #eeeeed no-repeat top;
+  background-size: contain;
+  .user-top {
+    height: 3.1rem;
+    width: 100%;
+    // background: #28cd5e;
+    overflow: hidden;
+    position: relative;
+    .top-head {
+      height: 1.54rem;
+      width: 1.54rem;
+      background: #fff;
+      box-sizing: border-box;
+      padding: 0.03rem;
+      border-radius: 50%;
+      margin: 0.6rem auto 0;
+      .head-img {
+        height: 100%;
+        width: 100%;
+        display: block;
+        border-radius: 50%;
+      }
+    }
+    .name-part {
+      position: absolute;
+      top: 1.8rem;
+      left: 50%;
+      transform: translateX(-50%);
+      display: flex;
+      .top-name {
+        height: 0.4rem;
+        padding: 0.01rem 0.32rem;
+        background: #fff;
+        border-radius: 0.1rem;
+        line-height: 0.4rem;
+        font-size: 0.28rem;
+        color: #28cd5e;
+        // margin-right: 0.25rem;
+        margin-left: 0.5rem;
+        white-space:nowrap;
+      }
+      .top-member {
+        width: 0.3rem;
+        height: 0.35rem;
+        margin-left: 0.25rem;
+        margin-top: 0.02rem;
+      }
+    }
+    .top-id {
+      height: 0.4rem;
+      background: #fff;
+      border-radius: 0.1rem;
+      position: absolute;
+      top: 2.4rem;
+      left: 50%;
+      transform: translateX(-50%);
+      padding: 0.02rem;
+      .id-in {
+        height: 100%;
+        border-radius: 0.1rem;
+        font-size: 0.26rem;
+        color: #fff;
+        background: #6fed98;
+        line-height: 0.4rem;
+        padding: 0 0.25rem;
+      }
+    }
+    .top-info {
+      color: #fff;
+      font-size: 0.28rem;
+      position: absolute;
+      top: 1.83rem;
+      right: 0.67rem;
+    }
+    .top-icon2 {
+      width: 0.12rem;
+      height: 0.2rem;
+      position: absolute;
+      top: 1.92rem;
+      right: 0.3rem;
+    }
+  }
+  .user-tip {
+    width: 7.1rem;
+    // height: 3.13rem;
+    background: #fff;
+    margin: 0 auto;
+    box-sizing: border-box;
+    padding: 0.3rem 0.2rem;
+    border-radius: 0.12rem;
+    display: flex;
+    .tip-left {
+      width: 50%;
+      text-align: center;
+      border-right: 0.02rem solid #eee;
+      .tip-text {
+        color: #4c4c4c;
+        font-size: 0.28rem;
+        font-weight: 600;
+      }
+      .tip-count {
+        color: #fe494d;
+        font-size: 0.3rem;
+        padding-top: 0.2rem;
+        font-weight: 600;
+      }
+    }
+    .tip-right {
+      width: 50%;
+      text-align: center;
+      .tip-text {
+        color: #4c4c4c;
+        font-size: 0.28rem;
+        font-weight: 600;
+      }
+      .tip-count {
+        color: #fe494d;
+        font-size: 0.3rem;
+        padding-top: 0.2rem;
+        font-weight: 600;
+      }
+    }
+    .info-top {
+      .top-left {
+        float: left;
+        color: #4c4c4c;
+        font-size: 0.3rem;
+        font-weight: 600;
+        margin-top: 0.3rem;
+      }
+      .top-icon {
+        width: 0.12rem;
+        height: 0.22rem;
+        float: right;
+        margin-top: 0.36rem;
+        margin-left: 0.2rem;
+      }
+      .top-right {
+        float: right;
+        color: #999;
+        font-size: 0.28rem;
+        margin-top: 0.3rem;
+      }
+    }
+    .info-center {
+      text-align: center;
+      .info-money {
+        color: #ff484c;
+        font-size: 0.4rem;
+        font-weight: 600;
+        padding-top: 0.3rem;
+        padding-bottom: 0.2rem;
+      }
+      .info-text {
+        color: #999;
+        font-size: 0.28rem;
+        padding-bottom: 0.5rem;
+      }
+    }
+    .info-bottom {
+      width: 100%;
+      height: 0.96rem;
+      border-top: 0.02rem solid #eeeeed;
+      display: flex;
+      display: -webkit-flex;
+      justify-content: space-between;
+      flex-wrap: nowrap;
+      .bottom-left {
+        flex-grow: 1;
+        height: 0.96rem;
+        border-right: 0.02rem solid #eeeeed;
+        .left-btn {
+          height: 0.45rem;
+          width: 2.4rem;
+          background: #def1ff;
+          color: #00a0e9;
+          font-size: 0.28rem;
+          text-align: center;
+          line-height: 0.45rem;
+          border-radius: 0.2rem;
+          margin: 0.25rem auto 0;
+        }
+      }
+      .bottom-right {
+        flex-grow: 1;
+        height: 0.96rem;
+        .right-btn {
+          height: 0.45rem;
+          width: 2.4rem;
+          background: #def1ff;
+          color: #00a0e9;
+          font-size: 0.28rem;
+          text-align: center;
+          line-height: 0.45rem;
+          border-radius: 0.2rem;
+          margin: 0.25rem auto 0;
+        }
+      }
+    }
+  }
+  .user-member {
+    width: 7.1rem;
+    height: 1rem;
+    background-image: linear-gradient(#ffebc8, #f8ca7a);
+    margin: 0 auto 0.2rem;
+    border-radius: 0.12rem;
+    box-sizing: border-box;
+    padding: 0.3rem 0.2rem 0;
+    .member-icon {
+      width: 0.3rem;
+      height: 0.35rem;
+      float: left;
+      margin-right: 0.2rem;
+    }
+    .member-text {
+      color: #8c4200;
+      font-size: 0.26rem;
+      float: left;
+      margin-top: 0.02rem;
+    }
+    .member-text1 {
+      color:#4c4c4c;
+      font-size: 0.26rem;
+      float: right;
+      margin-top: 0.02rem;
+    }
+    .member-icon1 {
+      width: 0.12rem;
+      height: 0.2rem;
+      float: right;
+      margin-top: 0.1rem;
+      margin-left: 0.15rem;
+    }
+  }
+  .user-list {
+    // height: 3rem;
+    width: 7.1rem;
+    margin: 0.2rem auto 0;
+    background: #fff;
+    border-radius: 0.12rem;
+    .list-item {
+      height: 0.98rem;
+      width: 100%;
+      border-bottom: 0.02rem solid #eeeeed;
+      box-sizing: border-box;
+      padding: 0 0.2rem;
+      .item-img {
+        height: 0.4rem;
+        width: 0.4rem;
+        // background: pink;
+        border-radius: 50%;
+        float: left;
+        margin-top: 0.3rem;
+        margin-right: 0.3rem;
+      }
+      .item-title {
+        color: #4c4c4c;
+        font-size: 0.3rem;
+        float: left;
+        margin-top: 0.3rem;
+      }
+      .item-icon {
+        width: 0.12rem;
+        height: 0.22rem;
+        float: right;
+        margin-top: 0.37rem;
+      }
+    }
+    .list-item:last-child {
+      border-bottom: 0;
+    }
+  }
+}
+</style>

+ 282 - 0
src/views/user/address/Detail.vue

@@ -0,0 +1,282 @@
+<template>
+  <div class="eait-bg">
+    <div class="eait-list">
+      <div class="list-item">
+        <div class="item-title">收货人</div>
+        <div class="item-text">
+          <van-field type="text" v-model="realname" placeholder="请输入收货人姓名" />
+        </div>
+        <!-- <div class="item-text" v-if="show">呸呸呸</div> -->
+        <div class="clear"></div>
+      </div>
+      <div class="list-item">
+        <div class="item-title">手机号</div>
+        <div class="item-text">
+          <van-field type="tel" v-model="tel" placeholder="请输入收货人手机号" />
+        </div>
+        <!-- <div class="item-text" v-if="show">呸呸呸</div> -->
+        <div class="clear"></div>
+      </div>
+      <div class="list-item">
+        <div class="item-title">选择地区</div>
+        <div @click="clickcity">
+          <div class="item-text" :style="cityname === '请选择收货人地区'? 'color: #bbb;': 'color: #4c4c4c;'">{{cityname}}</div>
+            <!-- <van-field type="text" readonly v-model="address" placeholder="请选择收货人地区" /> -->
+          <img src="@/assets/img/icon.png" class="item-icon" alt="">
+          <!-- <div class="item-text" v-if="show">呸呸呸</div> -->
+          <div class="clear">
+          </div>
+        </div>
+      </div>
+      <div class="list-item">
+        <div class="item-title">详细地址</div>
+        <div class="item-text">
+          <van-field type="text" v-model="address1" placeholder="如街道、门牌号、小区、乡镇、村等" />
+        </div>
+        <!-- <div class="item-text" v-if="show">呸呸呸</div> -->
+        <div class="clear"></div>
+      </div>
+      <div class="list-item">
+        <div v-if="id">
+          <van-switch @input="onInput()" :value="state === 0 ? checked : !checked" size="0.4rem" active-color="#28cd5e" class="change-btn" inactive-color="#cccccc" />
+          <div v-if="!checked" class="change-text">设为默认</div>
+          <div v-if="checked" class="change-text1">已设为默认</div>
+        </div>
+        <div v-if="!id">
+          <van-switch @input="onInput()" :value="checked" size="0.4rem" active-color="#28cd5e" class="change-btn" inactive-color="#cccccc" />
+          <div v-if="!checked" class="change-text">设为默认</div>
+          <div v-if="checked" class="change-text1">已设为默认</div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <Btn :title="titlename" @titleclick="issave" />
+    <van-popup v-model="cityshow" position="bottom" :style="{ height: '50%', position: 'absolute' }" >
+      <van-area :area-list="areaList1" @cancel="onCancel" @confirm="onConfirm" />
+    </van-popup>
+  </div>
+</template>
+<script>
+import Vue from 'vue'
+import { Switch, Toast } from 'vant'
+import areaList from '@/common/city'
+import validator from '@/common/validator'
+import Btn from '@/components/Btn.vue'
+export default {
+  name: 'userAddressEdit',
+  data () {
+    return {
+      show: false,
+      realname: '',
+      tel: '',
+      address: '',
+      address1: '',
+      cityshow: false,
+      cityname: '请选择收货人地区',
+      prov: '',
+      city: '',
+      area: '',
+      titlename: '保存',
+      detail: {},
+      state: false,
+      checked: false,
+      id: 0
+    }
+  },
+  computed: {},
+  methods: {
+    validate () {
+      let msg
+      if (!validator.required(this.realname)) {
+        msg = '姓名不能为空'
+      } else if (!validator.required(this.tel)) {
+        msg = '手机号码不能为空'
+      } else if (!validator.telphone(this.tel)) {
+        msg = '手机号码格式不正确'
+      } else if (!validator.required(this.prov)) {
+        msg = '省市区不能为空'
+      } else if (!validator.required(this.address1)) {
+        msg = '详细地址不能为空'
+      }
+      return { isOk: !msg, msg }
+    },
+    issave () {
+      let { isOk, msg } = this.validate()
+      if (isOk) {
+        this.save()
+      } else {
+        Toast({
+          message: msg,
+          duration: 2000
+        })
+      }
+    },
+    onInput () {
+      this.checked = !this.checked
+    },
+    getdetail () {
+      this.$get({
+        url: `v1/address/${Number(this.$route.query.id)}`,
+        data: {
+        }
+      }).then((res) => {
+        this.detail = res
+        this.realname = res.contact
+        this.tel = res.tel
+        this.prov = res.province
+        this.city = res.city
+        this.area = res.district
+        this.cityname = res.province + ' ' + res.city + ' ' + res.district
+        this.address1 = res.address
+        this.state = res.state
+      })
+    },
+    save () {
+      if (this.checked === false) {
+        var ischeacked = 0
+      } else {
+        ischeacked = 1
+      }
+      if (this.id) {
+        this.$put({
+          url: 'v1/address/' + Number(this.id),
+          data: {
+            province: this.prov,
+            city: this.city,
+            district: this.area,
+            address: this.address1,
+            tel: this.tel,
+            contact: this.realname,
+            // state: ischeacked,
+            set_default: ischeacked
+            // address_id: Number(this.id)
+          }
+        }).then((res) => {
+          console.log(res)
+          Toast('保存成功')
+          this.$router.back()
+        })
+      } else {
+        if (this.$route.query.one && ischeacked === 0) {
+          Toast({
+            message: '请把新增的首个地址设为默认地址',
+            duration: 2000
+          })
+        } else {
+          this.$post({
+            url: 'v1/address',
+            data: {
+              province: this.prov,
+              city: this.city,
+              district: this.area,
+              address: this.address1,
+              tel: this.tel,
+              contact: this.realname,
+              set_default: ischeacked
+              // address_id: Number(this.$route.params.id)
+            }
+          }).then((res) => {
+            console.log(res)
+            Toast('保存成功')
+            // this.$router.push({ path: `/user/address/list` })
+            this.$router.back()
+          })
+        }
+      }
+    },
+    clickcity () {
+      this.cityshow = true
+    },
+    onCancel () {
+      this.cityshow = false
+    },
+    onConfirm (e) {
+      var citydata = e
+      this.cityname = citydata[0].name + ' ' + citydata[1].name + ' ' + citydata[2].name
+      this.prov = citydata[0].name
+      this.city = citydata[1].name
+      this.area = citydata[2].name
+      this.cityshow = false
+    }
+  },
+  created () {
+    Vue.use(Switch)
+    this.areaList1 = areaList
+    this.id = this.$route.query.id
+    if (this.$route.query.id) {
+      this.getdetail()
+    }
+  },
+  watch: {},
+  components: {
+    Btn
+  }
+}
+</script>
+<style lang='less' scoped>
+.eait-bg {
+  /deep/ .van-picker__cancel {
+    color: #28cd5e;
+  }
+  /deep/ .van-picker__confirm {
+    color: #28cd5e;
+  }
+  background: #eeeeed;
+  min-height: 100%;
+  overflow: hidden;
+  text-align: left;
+  .eait-list {
+    background: #fff;
+    box-sizing: border-box;
+    padding: 0 0.2rem;
+    .list-item {
+      height: 0.98rem;
+      width: 100%;
+      border-bottom: 0.02rem solid #eeeeee;
+      line-height: 0.98rem;
+      font-size: 0.26rem;
+      color: #4c4c4c;
+      .change-btn {
+        float: left;
+        margin-top: 0.3rem;
+      }
+      .change-text {
+        float: left;
+        color: #999;
+        font-size: 0.26rem;
+        margin-top: 0.05rem;
+        margin-left: 0.15rem;
+      }
+      .change-text1 {
+        float: left;
+        color: #28cd5e;
+        font-size: 0.26rem;
+        margin-top: 0.05rem;
+        margin-left: 0.15rem;
+      }
+      .item-title {
+        float: left;
+        width: 1.6rem;
+        // margin-right: 0.4rem;
+        text-align: left;
+      }
+      .item-text {
+        float: left;
+        width: 5rem;
+        text-align: left;
+        .van-cell {
+          padding-left: 0;
+          padding-top: 0.28rem;
+          height: 100%;
+        }
+      }
+      .item-icon {
+        float: right;
+        width: 0.12rem;
+        height: 0.2rem;
+        margin-top: 0.35rem;
+      }
+    }
+  }
+}
+</style>

+ 262 - 0
src/views/user/address/List.vue

@@ -0,0 +1,262 @@
+<template>
+  <div class="address-bg">
+    <div class="address-list">
+      <!-- <van-list v-model="isUpLoading" :finished="upFinished" finished-text="" @load="onLoadList" :offset="offset"> -->
+        <div class="address-item" v-for="(item, index) in address_list" :key="index">
+          <div class="item-top" @click="toorder(item)">
+            <div class="top-info">
+              <div class="info-name">{{item.contact}}</div>
+              <div class="info-tel">{{item.tel}}</div>
+              <div class="clear"></div>
+            </div>
+            <div class="top-address">
+              {{item.province}} {{item.city}} {{item.district}} {{item.address}}
+            </div>
+            <div class="top-tip" v-if="id && id !== item.id"></div>
+            <img src="@/assets/img/right.png" class="top-icon" v-if="id && id === item.id" alt="">
+          </div>
+          <div class="item-bottom">
+            <!-- <van-switch :value="item.state === 0 ? checked : !checked" @input="onInput(item, index)" size="0.4rem" active-color="#ff484c" class="change-btn" inactive-color="#cccccc" />
+            <div v-if="checked" class="change-text">设为默认</div>
+            <div v-if="!checked" class="change-text1">已设为默认</div> -->
+            <Switch1 :state="item.state" :id="item.id" @getlist="getlist" />
+            <div class="item-change" @click="toedit(item)">编辑</div>
+            <div class="item-cancel" @click="cancel(item)">删除</div>
+            <div class="clear"></div>
+          </div>
+        </div>
+      <!-- </van-list> -->
+    </div>
+    <Btn :title="titlename" @titleclick="plusaddress" />
+  </div>
+</template>
+<script>
+// import Vue from 'vue'
+import { Toast } from 'vant'
+import Btn from '@/components/Btn.vue'
+import Switch1 from '././components/Switch.vue'
+export default {
+  name: 'userAddressList',
+  data () {
+    return {
+      titlename: '+ 添加收货地址',
+      checked: false,
+      cheackedlist: [],
+      chargenum: 0,
+      boxshow: false,
+      isUpLoading: false,
+      upFinished: false,
+      offset: 5,
+      // address_list: [
+      //   {
+      //     id: 5,
+      //     user_id: 0,
+      //     contact: '某某某',
+      //     tel: 13927173344,
+      //     address: '紫马广场',
+      //     remark: '',
+      //     state: 0,
+      //     province: '广东省',
+      //     city: '中山市',
+      //     postcode: '',
+      //     district: '东区街道'
+      //   }
+      // ]
+      address_list: [],
+      cursor: '',
+      id: 0
+    }
+  },
+  computed: {},
+  methods: {
+    onInput (item, index) {
+      console.log(item)
+      console.log(index)
+      this.chargenum = index
+      if (this.chargenum === index) {
+        this.checked = !this.checked
+      }
+    },
+    toorder (item) {
+      // this.$router.push({ path: `/` })
+      if (this.id) {
+        this.id = item.id
+        if (this.$route.query.is_integral === 'true') {
+          this.$router.push({ path: `/pay/integral/${this.$route.query.pid}`, query: { address_id: this.id } })
+        } else {
+          this.$router.push({ path: `/create/order/${this.$route.query.pid}/${this.$route.query.count}`, query: { address_id: this.id } })
+        }
+      }
+    },
+    getlist () {
+      this.$get({
+        url: 'v1/address',
+        data: {
+          // cursor: this.cursor
+        }
+      }).then((res) => {
+        this.address_list = res
+        // this.cursor = res.cursor
+        // this.isUpLoading = false
+        // this.address_list = this.address_list.concat(res.address_list || [])
+        // if (res.cursor === '') {
+        //   this.upFinished = true
+        // }
+      })
+    },
+    toedit (item) {
+      this.$router.push({ path: `/address/edit`, query: { id: item.id } })
+    },
+    plusaddress () {
+      if (this.address_list.length > 0) {
+        this.$router.push({ path: `/address/edit` })
+      } else {
+        this.$router.push({ path: `/address/edit`, query: { one: true } })
+      }
+    },
+    cancleaddress (item) {
+      this.$del({
+        url: `v1/address/${item.id}`,
+        data: {
+        }
+      }).then((res) => {
+        console.log(res)
+        // this.address_list = []
+        this.getlist()
+        Toast('删除成功')
+      })
+    },
+    cancel (item) {
+      this.$dialog.confirm({
+        title: '提示',
+        message: '确认要删除该地址吗',
+        confirmButtonColor: '#ff484c',
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        lockScroll: false
+      }).then(() => {
+        this.cancleaddress(item)
+      }).catch(() => {
+      });
+    }
+  },
+  created () {
+    // Vue.use(Switch)
+    this.getlist()
+    if (this.$route.query.address_id) {
+      this.id = Number(this.$route.query.address_id)
+      console.log(this.id)
+    }
+  },
+  watch: {},
+  components: {
+    Btn,
+    Switch1
+  }
+}
+</script>
+<style lang='less' scoped>
+.address-bg {
+  text-align: left;
+  min-height: 100%;
+  background-color: #f3f3f3;
+  position: relative;
+  box-sizing: border-box;
+  padding-bottom: 1rem;
+  .address-list {
+    .address-item {
+      // height: 2.3rem;
+      box-sizing: border-box;
+      padding: 0.35rem 0.2rem 0;
+      background: #fff;
+      margin-bottom: 0.25rem;
+      .item-top {
+        border-bottom: 0.02rem solid #f3f3f3;
+        position: relative;
+        .top-info {
+          .info-name {
+            font-size: 0.32rem;
+            color: #4c4c4c;
+            float: left;
+            font-weight: 600;
+          }
+          .info-tel {
+            font-size: 0.26rem;
+            color: #999;
+            float: left;
+            margin-top: 0.13rem;
+            margin-left: 0.15rem;
+          }
+        }
+        .top-address {
+          margin-top: 0.15rem;
+          margin-bottom: 0.3rem;
+          color: #4c4c4c;
+          font-size: 0.26rem;
+          line-height: 0.4rem;
+          width: 6rem;
+        }
+        .top-tip {
+          border: 0.02rem solid #4c4c4c;
+          width: 0.3rem;
+          height: 0.3rem;
+          background: #fff;
+          border-radius: 50%;
+          position: absolute;
+          right: 0.2rem;
+          top: 0.3rem;
+        }
+        .top-icon {
+          width: 0.34rem;
+          height: 0.34rem;
+          position: absolute;
+          right: 0.2rem;
+          top: 0.3rem;
+        }
+      }
+      .item-bottom {
+        padding: 0.25rem 0;
+        box-sizing: border-box;
+        .change-btn {
+          float: left;
+        }
+        .change-text {
+          float: left;
+          color: #999;
+          font-size: 0.26rem;
+          margin-top: 0.05rem;
+          margin-left: 0.15rem;
+        }
+        .change-text1 {
+          float: left;
+          color: #ff484c;
+          font-size: 0.26rem;
+          margin-top: 0.05rem;
+          margin-left: 0.15rem;
+        }
+        .item-cancel {
+          float: right;
+          font-size: 0.26rem;
+          border: 0.02rem solid #ccc;
+          color: #999;
+          text-align: center;
+          border-radius: 0.3rem;
+          box-sizing: border-box;
+          padding: 0.05rem 0.25rem;
+        }
+        .item-change {
+          float: right;
+          font-size: 0.26rem;
+          border: 0.02rem solid #ccc;
+          color: #999;
+          text-align: center;
+          border-radius: 0.3rem;
+          box-sizing: border-box;
+          padding: 0.05rem 0.25rem;
+          margin-left: 0.3rem;
+        }
+      }
+    }
+  }
+}
+</style>

+ 109 - 0
src/views/user/address/components/Switch.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="switch-bg">
+    <van-switch :value="checked" @input="onInput()" size="0.4rem" active-color="#28cd5e" class="change-btn" inactive-color="#cccccc" />
+    <!-- <van-switch :value="state === 0 ? checked : !checked" @input="onInput()" size="0.4rem" active-color="#ff484c" class="change-btn" inactive-color="#cccccc" /> -->
+    <div v-show="!checked" class="change-text">设为默认</div>
+    <div v-show="checked" class="change-text1">已设为默认</div>
+  </div>
+</template>
+<script>
+import Vue from 'vue'
+import { Switch } from 'vant'
+export default {
+  name: '',
+  props: {
+    state: {
+      type: Number,
+      default: 0,
+      require: true
+    },
+    id: {
+      type: Number,
+      default: 0,
+      require: true
+    }
+  },
+  data () {
+    return {
+      checked: false
+    }
+  },
+  created () {
+    Vue.use(Switch)
+  },
+  components: {},
+  computed: {},
+  beforeMount () {},
+  mounted () {},
+  methods: {
+    onInput () {
+      this.checked = !this.checked
+      if (this.checked === true) {
+        this.$put({
+          url: `/v1/address/${Number(this.id)}/default`,
+          data: {
+            // address_id: Number(this.id)
+          }
+        }).then((res) => {
+          this.$emit('getlist')
+        })
+      }
+    }
+  },
+  watch: {
+    state: {
+      handler: function (val) {
+        if (val === 1) {
+          this.checked = true
+        }
+        if (val === 0) {
+          this.checked = false
+        }
+      },
+      immediate: true
+    }
+  }
+}
+</script>
+<style lang='less' scoped>
+.switch-bg {
+  .change-btn {
+    float: left;
+  }
+  .change-text {
+    float: left;
+    color: #999;
+    font-size: 0.26rem;
+    margin-top: 0.05rem;
+    margin-left: 0.15rem;
+  }
+  .change-text1 {
+    float: left;
+    color:#28cd5e;
+    font-size: 0.26rem;
+    margin-top: 0.05rem;
+    margin-left: 0.15rem;
+  }
+  .item-cancel {
+    float: right;
+    font-size: 0.26rem;
+    border: 0.02rem solid #ccc;
+    color: #999;
+    text-align: center;
+    border-radius: 0.3rem;
+    box-sizing: border-box;
+    padding: 0.05rem 0.25rem;
+  }
+  .item-change {
+    float: right;
+    font-size: 0.26rem;
+    border: 0.02rem solid #ccc;
+    color: #999;
+    text-align: center;
+    border-radius: 0.3rem;
+    box-sizing: border-box;
+    padding: 0.05rem 0.25rem;
+    margin-left: 0.3rem;
+  }
+}
+</style>

+ 275 - 0
src/views/user/balance/Account.vue

@@ -0,0 +1,275 @@
+<template>
+  <div class="j4c6-balance__list">
+    <div class="money-top">
+      <div class="top-money">{{userBalanceInfo.available | getAcounting('¥', false)}}</div>
+      <div class="top-text">账户余额</div>
+      <div class="top-btn" @click="towithdraw" v-if="userBalanceInfo.available > 0">提现</div>
+      <div class="top-btn1" v-if="userBalanceInfo.available === 0 || userBalanceInfo.available < 0">暂无提现</div>
+    </div>
+    <!-- <div class="money-top" v-show="show">
+      <div class="top-money" :style="show ? 'margin-top: 0.8rem;' : 'margin-top: 0rem;;' ">{{userIntegral.amount * 100 | getAcounting('', false)}}</div>
+      <img src="@/assets/img/000.png" class="top-img" alt="">
+      <div class="top-text">积分余额</div>
+      <div class="top-rules" @click="showrules" v-if="false">积分说明</div>
+      <img src="@/assets/img/shopping/00.png" @click="showrules" class="top-icon" alt=""  v-if="false">
+    </div> -->
+
+    <div class="money-title">提现明细</div>
+
+    <div class="money-list">
+      <van-list v-model="isUpLoading" :finished="upFinished" finished-text="" @load="onLoadList" :offset="offset" v-if="show1">
+        <div class="money-item" v-for="(item, index) in list" :key="index">
+          <div class="item-left">
+            <div class="left-title">{{item.state_cn}}</div>
+            <div class="left-time">{{item.created_at | formatSTime}}</div>
+          </div>
+          <div class="item-right">
+            <div class="right-money">{{item.count | getAcounting('¥', false)}}</div>
+            <!-- <div class="right-yue">{{item.cur_amount | getAcounting('¥', false)}}</div> -->
+          </div>
+          <div class="clear"></div>
+        </div>
+      </van-list>
+      <empty-list desc="暂无提现明细" v-if="!show1" :background='background' />
+    </div>
+
+  </div>
+</template>
+<script>
+import EmptyList from '@/components/Empty'
+import { mapGetters } from 'vuex'
+export default {
+  name: '',
+  data () {
+    return {
+      isShow: false,
+      show: false,
+      balance_type: 'balance',
+      isUpLoading: false,
+      upFinished: false,
+      offset: 5,
+      background: '#f3f3f3',
+      show1: true,
+      page: 1,
+      per_page: 10,
+      list: [],
+      listtotal: 0,
+    }
+  },
+  computed: {
+    ...mapGetters(['userBalanceInfo'])
+  },
+  methods: {
+    onLoadList () {
+      setTimeout(() => {
+        this.getBalanceList()
+      }, 100)
+    },
+    showrules () {
+      this.isShow = true
+    },
+    towithdraw () {
+      this.$router.push({ path: '/withdraw' })
+    },
+    getBalanceList () {
+      this.$get({
+        url: 'v1/user/takecash/flow',
+        data: {
+          page: this.page,
+          per_page: this.per_page,
+        }
+      }).then((res) => {
+        if (this.page === 1) {
+          this.list = []
+          this.listtotal = 0
+        }
+        this.page++
+        this.isUpLoading = false
+        this.list = this.list.concat(res.list || [])
+        this.listtotal = res.list_count
+        if (this.list.length === 0) {
+          this.show1 = false
+        }
+        if (this.list.length >= this.listtotal) {
+          this.upFinished = true
+        }
+      })
+    }
+  },
+  created () {
+    // this.getBalanceList()
+    this.$store.dispatch('userBalanceInfo')
+  },
+  watch: {
+    $route: {
+      // if (this.$route.path.indexOf('integral') > 0) {
+      //   console.log(555)
+      //   this.show = true
+      // }
+      // deep: true
+      handler: function (val) {
+        if (this.$route.path.indexOf('integral') > 0) {
+          this.show = true
+        }
+      },
+      immediate: true
+    }
+  },
+  components: {
+    EmptyList
+  }
+}
+</script>
+<style lang="less" scoped>
+.j4c6-balance__list {
+  text-align: left;
+  min-height: 100%;
+  background-color: #f3f3f3;
+  .money-top {
+    height: 3.5rem;
+    width: 100%;
+    background: url('../../../assets/img/p2.jpg') no-repeat;
+    background-size: cover;
+    overflow: hidden;
+    text-align: center;
+    .top-money {
+      font-size: 0.5rem;
+      color: #fff;
+      // text-align: center;
+      font-weight: 600;
+      margin-top: 0.45rem;
+      display: inline-block;
+      // margin: 0.45rem auto 0;
+    }
+    .top-text {
+      color: #fff;
+      font-size: 0.28rem;
+      text-align: center;
+      margin-top: 0.26rem;
+    }
+    .top-rules {
+      color: #fff;
+      position: absolute;
+      top: 0.15rem;
+      right: 0.6rem;
+      font-size: 0.28rem;
+    }
+    .top-icon {
+      width: 0.24rem;
+      height: 0.24rem;
+      position: absolute;
+      top: 0.22rem;
+      right: 0.2rem;
+    }
+    .top-img {
+      width: 0.31rem;
+      height: 0.32rem;
+      display: inline-block;
+      margin-left: 0.1rem;
+      // position: absolute;
+      // top: 1.2rem;
+      // right: 2.2rem;
+    }
+    .top-btn {
+      width: 2.7rem;
+      height: 0.55rem;
+      background: #fff;
+      border-radius: 0.3rem;
+      color: #28cd5e;
+      font-size: 0.3rem;
+      text-align: center;
+      line-height: 0.55rem;
+      margin: 0.56rem auto 0;
+    }
+    .top-btn1 {
+      width: 2.7rem;
+      height: 0.55rem;
+      background: #fff;
+      border-radius: 0.3rem;
+      color:#999;
+      font-size: 0.3rem;
+      text-align: center;
+      line-height: 0.55rem;
+      margin: 0.56rem auto 0;
+    }
+  }
+  .money-title {
+    height: 0.86rem;
+    color: #4c4c4c;
+    font-size: 0.3rem;
+    line-height: 0.86rem;
+    font-weight: 600;
+    box-sizing: border-box;
+    padding-left: 0.2rem;
+  }
+  .money-list {
+    box-sizing: border-box;
+    padding: 0 0.2rem;
+    background: #fff;
+    .money-item {
+      width: 100%;
+      background: #fff;
+      border-bottom: 0.02rem solid #eee;
+      .item-left {
+        float: left;
+        .left-title {
+          font-size: 0.26rem;
+          color: #4c4c4c;
+          text-align: left;
+          padding-top: 0.2rem;
+          padding-bottom: 0.1rem;
+        }
+        .left-time {
+          font-size: 0.26rem;
+          color: #708fa3;
+          padding-bottom: 0.2rem;
+        }
+      }
+      .item-right {
+        float: right;
+        .right-money {
+          font-size: 0.28rem;
+          text-align: right;
+          color: #ff484c;
+          padding-top: 0.35rem;
+          padding-bottom: 0.1rem;
+        }
+        .right-yue {
+          font-size: 0.24rem;
+          text-align: right;
+          color: #708fa3;
+          padding-bottom: 0.2rem;
+        }
+      }
+    }
+    .money-item:last-child {
+      border-bottom: 0;
+    }
+  }
+  .rules-part {
+    width: 5.5rem;
+    // height: 5.88rem;
+    background: #fff url('../../../assets/img/p3.png') no-repeat;
+    background-size: contain;
+    border-radius: 0.3rem;
+    overflow: hidden;
+    .rules-top {
+      // background: #fd6666;
+      width: 100%;
+      color: #fff;
+      text-align: center;
+      font-weight: 600;
+      font-size: 0.36rem;
+      margin-top: 0.3rem;
+      margin-bottom: 0.5rem;
+    }
+    .rules-center {
+      box-sizing: border-box;
+      padding: 0.4rem;
+      font-size: 0.28rem;
+      line-height: 0.4rem;
+      padding-top: 0.5rem;
+    }
+  }
+}
+</style>

+ 69 - 0
src/views/user/balance/Success.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="success-bg">
+    <img src="@/assets/img/success.png" class="success-img" alt="">
+    <div class="success-text">已成功提现</div>
+    <div class="success-text1">提现金额: ¥{{price}}</div>
+    <router-link to="/account" class="success-btn">查看明细</router-link>
+    <router-link to="/user" class="success-back">返回</router-link>
+  </div>
+</template>
+<script>
+export default {
+  name: '',
+  data () {
+    return {
+      price: this.$route.query.count / 100
+    }
+  },
+  computed: {},
+  methods: {},
+  created () {
+  },
+  watch: {},
+  components: {}
+}
+</script>
+<style lang='less' scoped>
+.success-bg {
+  text-align: left;
+  min-height: 100%;
+  background-color: #f3f3f3;
+  overflow: hidden;
+  .success-img {
+    width: 2rem;
+    height: 2rem;
+    margin: 2rem auto 0.4rem;
+    display: block;
+  }
+  .success-text {
+    color: #4c4c4c;
+    font-size: 0.32rem;
+    text-align: center;
+  }
+  .success-text1 {
+    color: #4c4c4c;
+    font-size: 0.32rem;
+    text-align: center;
+    margin-top: 0.2rem;
+  }
+  .success-btn {
+    display: block;
+    color: #1eacff;
+    font-size: 0.32rem;
+    text-align: center;
+    margin-top: 0.8rem;
+  }
+  .success-back {
+    display: block;
+    width: 2.8rem;
+    background: #28cd5e;
+    height: 0.74rem;
+    color: #fff;
+    font-size: 0.32rem;
+    text-align: center;
+    line-height: 0.74rem;
+    margin: 0.8rem auto 0;
+    border-radius: 0.5rem;
+  }
+}
+</style>

+ 298 - 0
src/views/user/balance/Withdraw.vue

@@ -0,0 +1,298 @@
+<template>
+  <div class="j4c6-withdraw">
+    <div class="withdraw-top">
+      <div class="top-title">
+        <h3 class="title-text">
+          提现金额
+        </h3>
+      </div>
+
+      <div class="top-money">
+        <img src="@/assets/img/q.png" alt="" class="money-left">
+        <div class="money-right">
+          <van-field type="number" v-model="amount" />
+        </div>
+        <div class="clear"></div>
+      </div>
+      <p class="money-tip">当前账户余额{{userBalanceInfo.available | getAcounting()}}</p>
+      <van-button class="top-btn1" round @click="showMoney">提现</van-button>
+    </div>
+
+    <!-- 输入提现校验弹窗 -->
+    <van-popup v-model="show" round >
+      <div class="popup-part">
+        <div class="part-top">提现金额确认</div>
+        <div class="part-center">{{amount * 100 | getAcounting()}}</div>
+        <div class="part-bottom">
+          <div class="bottom-left" @click="cancelDialog">取消</div>
+          <div class="bottom-right" @click="withdrawClick">确定</div>
+        </div>
+      </div>
+    </van-popup>
+  </div>
+</template>
+<script>
+import validator from '@/common/validator'
+import utils from '@/common/utils'
+import { mapGetters } from 'vuex'
+import { Toast } from 'vant'
+import { setTimeout } from 'timers';
+import { connect } from 'tls';
+export default {
+  name: 'userWithdraw',
+  data () {
+    return {
+      countdown: 0,
+      countdownFlag: false,
+      money: '',
+      show: false,
+      withdrawConf: {},
+      codeDialog: false,
+      amount: ''
+    }
+  },
+  computed: {
+    ...mapGetters(['userBalanceInfo'])
+  },
+  methods: {
+    showMoney() {
+      console.log(this.amount)
+      if (Number(this.amount) <= Number(this.userBalanceInfo.available) / 100 && Number(this.amount) > 0) {
+        this.show = true
+      } else if (Number(this.amount) === 0) {
+        Toast('输入金额不能为零,请重新输入')
+      } else if (Number(this.amount) > Number(this.userBalanceInfo.available) / 100 && Number(this.amount) > 0) {
+        Toast('输入金额大于余额,请重新输入')
+      }
+    },
+    cancelDialog() {
+      this.show = false
+    },
+    withdrawClick() {
+      this.$post({
+        url: 'v1/user/take_cash',
+        data: {
+          amount: this.amount * 100
+        }
+      }).then(() => {
+        this.show = false
+        Toast('提现成功')
+        setTimeout(() => {
+          this.$router.push({ path: '/success', query: { count: this.amount * 100 } })
+        }, 1500)
+      }).catch((res) => {
+        Toast(res.response.data.err_msg)
+      })
+    }
+  },
+  created () {
+    this.$store.dispatch('userBalanceInfo')
+  },
+  watch: {
+    'userBalanceInfo': function (val) {
+      // this.amount = val.available / 100
+    }
+  },
+  components: {}
+}
+</script>
+<style lang='less' scoped>
+.j4c6-withdraw {
+  text-align: left;
+  min-height: 100%;
+  background-color: #f3f3f3;
+  overflow: hidden;
+  .money-count {
+    text-align: center;
+    color: #ff5658;
+    margin-top: .2rem;
+  }
+  .withdraw-top {
+    width: 7.1rem;
+    background: #fff;
+    margin: 0.3rem auto 0;
+    box-sizing: border-box;
+    padding: 0.4rem;
+    .top-title {
+      margin-bottom: 0.5rem;
+      .title-text {
+        font-size: 0.3rem;
+        color: #4c4c4c;
+      }
+      .title-tip {
+        font-size: 0.26rem;
+        color: #999;
+        margin-left: 0.2rem;
+      }
+    }
+    .top-money {
+      border-bottom: 0.02rem solid #eee;
+      .money-left {
+        width: 0.45rem;
+        height: 0.63rem;
+        float: left;
+        margin-top: 0.1rem;
+        margin-bottom: 0.5rem;
+      }
+      .money-right {
+        float: left;
+        font-weight: 600;
+      }
+      .van-cell {
+        padding: 0;
+        padding-left: 0.6rem;
+        font-size: 0.8rem;
+        width: 5rem;
+        border: 0;
+      }
+    }
+    .money-tip {
+      color: #999999;
+      font-size: 0.28rem;
+      margin-top: 0.5rem;
+    }
+    .top-btn {
+      height: 0.72rem;
+      width: 100%;
+      background: #cccccc;
+      border-radius: 0.1rem;
+      color: #fff;
+      font-size: 0.32rem;
+      text-align: center;
+      line-height: 0.72rem;
+      margin-top: 0.7rem;
+      margin-bottom: 0.1rem;
+    }
+    .top-btn1 {
+      height: 0.72rem;
+      width: 100%;
+      background: #28cd5e;
+      border-radius: 0.1rem;
+      color: #fff;
+      font-size: 0.32rem;
+      text-align: center;
+      line-height: 0.72rem;
+      margin-top: 0.7rem;
+      margin-bottom: 0.1rem;
+    }
+  }
+  .withdraw-rules {
+    width: 7.1rem;
+    margin: 0.36rem auto 0;
+    font-size: 0.28rem;
+    color: #999;
+    box-sizing: border-box;
+    padding: 0 0.2rem;
+    li:first-child {
+      margin-bottom: .12rem;
+    }
+    .rules-item {
+      line-height: 0.42rem;
+    }
+  }
+  &__dialog {
+    width: 5.5rem;
+    padding: .3rem;
+    text-align: center;
+    h3 {
+      font-size: .32rem;
+      color: #4c4c4c;
+      margin-bottom: .2rem;
+      font-weight: 600;
+    }
+    .content {
+      position: relative;
+      font-size:  .28rem;
+      color: #4c4c4c;
+      span {
+        color: #999;
+      }
+      input {
+        width: 100%;
+        height: .88rem;
+        color: #4c4c4c;
+        background: #f3f3f3;
+        border-radius: .08rem;
+        margin-top: .3rem;
+        padding: 0 .2rem;
+        box-sizing: border-box;
+      }
+      .van-button__text {
+        color: #fff;
+      }
+      .btn {
+        width: 2.2rem;
+        height: .64rem;
+        font-size: .24rem;
+        position: absolute;
+        right: .12rem;
+        bottom: .12rem;
+        color: #ff5658;
+      }
+    }
+    .info {
+      width: 4.8rem;
+      text-align: center;
+      margin: 0 auto;
+      line-height: .42rem;
+    }
+    .footer {
+      width: 5.5rem;
+      overflow: hidden;
+      .button {
+        float: left;
+        width: 2.4rem;
+        height: .64rem;
+        padding: 0;
+        margin-top: .5rem;
+      }
+      .button:last-child {
+        float: right;
+      }
+    }
+  }
+  .popup-part {
+    // height: 3.5rem;
+    width: 5rem;
+    background: #fff;
+    border-radius: 1rem;
+    .part-top {
+      color: #4c4c4c;
+      font-size: 0.28rem;
+      text-align: center;
+      height: 0.86rem;
+      line-height: 0.86rem;
+      border-bottom: 0.02rem solid #eee;
+    }
+    .part-center {
+      color: #ff484c;
+      font-weight: 600;
+      font-size: 0.5rem;
+      text-align: center;
+      height: 1.8rem;
+      line-height: 1.8rem;
+    }
+    .part-bottom {
+      border-top: 0.02rem solid #eee;
+      display: flex;
+      .bottom-left {
+        height: 0.8rem;
+        line-height: 0.8rem;
+        color: #999999;
+        font-size: 0.3rem;
+        text-align: center;
+        width: 50%;
+      }
+      .bottom-right {
+        height: 0.8rem;
+        line-height: 0.8rem;
+        color: #ff484c;
+        font-size: 0.3rem;
+        text-align: center;
+        width: 50%;
+        border-left: 0.02rem solid #eee;
+      }
+    }
+  }
+}
+</style>

+ 151 - 0
src/views/user/integral/Exchange.vue

@@ -0,0 +1,151 @@
+<template>
+  <div class="exchange">
+    <div class="exchange-top" v-if="info.wx_user && !info.wx_user.vip" @click="tomember">
+      <div class="top-left">非会员用户不能兑换酒</div>
+      <img src="../../../assets/img/icon2.png" alt="" class="top-icon">
+      <div class="top-right">立即开通</div>
+      <div class="clear"></div>
+    </div>
+    <div class="exchange-list">
+      <div class="list-item" v-for="(item, index) in list" :key="index">
+        <img :src="item.img_thumb_url" class="item-img" alt="">
+        <div class="item-title">{{item.title}}</div>
+        <div class="item-price">{{item.jf_price}}积分/瓶</div>
+        <div class="item-btn" v-if="info.wx_user && !info.wx_user.vip">兑换</div>
+        <div class="item-btn" @click="topay(item.id)" v-if="info.wx_user && info.wx_user.vip && item.jf_price <= total" style="background:#28cd5e;color:#fff">兑换</div>
+        <div class="item-btn" v-if="info.wx_user && info.wx_user.vip && item.jf_price > total">积分不足</div>
+      </div>
+      <div class="clear"></div>
+    </div>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  name:'',
+  props:[''],
+  data () {
+    return {
+      info: {},
+      page: 1,
+      per_page: 10,
+      list: [],
+      total: 0
+    };
+  },
+  created () {
+    this.$store.dispatch('userInfo')
+    this.getlist()
+    this.getintegral()
+  },
+  components: {},
+  computed: {
+    ...mapGetters(['userInfo']),
+  },
+  beforeMount() {},
+  mounted() {},
+  methods: {
+    tomember () {
+      this.$router.push('/member')
+    },
+    topay (id) {
+      this.$router.push(`/pay/integral/${id}`)
+    },
+    getintegral () {
+      this.$get({
+        url: 'v1/user/mall/balance/info',
+        data: {}
+      }).then((res) => {
+        console.log(res)
+        this.total = res.total
+      })
+    },
+    getlist() {
+      this.$get({
+        url: 'v1/project/list',
+        data: { page: this.page, per_page: this.per_page }
+      }).then((res) => {
+        console.log(res)
+        this.list = res.list
+      })
+    },
+  },
+  watch: {
+    'userInfo': function (val) {
+      if (val) {
+        this.info = val
+      }
+    }
+  }
+}
+</script>
+<style lang='less' scoped>
+.exchange {
+  background: #eeeeed;
+  min-height: 100%;
+  overflow: hidden;
+  .exchange-top {
+    // height: 0.8rem;
+    width: 100%;
+    background-image: linear-gradient(to right,#e4b479,#c38a43);
+    color: #fff;
+    font-size: 0.28rem;
+    box-sizing: border-box;
+    padding: 0.25rem 0.2rem;
+    .top-left {
+      float: left;
+    }
+    .top-right {
+      float: right;
+    }
+    .top-icon {
+      float: right;
+      height: 0.25rem;
+      width: 0.16rem;
+      margin-top: 0.07rem;
+      margin-left: 0.2rem;
+    }
+  }
+  .exchange-list {
+    margin-top: 0.2rem;
+    .list-item {
+      height: 5.6rem;
+      width: 3.45rem;
+      background: #fff;
+      border-radius: 0.1rem;
+      margin-left: 0.2rem;
+      margin-bottom: 0.15rem;
+      display: inline-block;
+      overflow: hidden;
+      .item-img {
+        height: 3.5rem;
+        width: 100%;
+        display: block;
+      }
+      .item-title {
+        color: #4c4c4c;
+        font-size: 0.28rem;
+        text-align: center;
+        margin-top: 0.2rem;
+        margin-bottom: 0.1rem;
+      }
+      .item-price {
+        color: #ff696b;
+        font-size: 0.28rem;
+        text-align: center;
+      }
+      .item-btn {
+        height: 0.48rem;
+        width: 1.4rem;
+        background: #cccccc;
+        font-size: 0.23rem;
+        text-align: center;
+        border-radius: 0.5rem;
+        line-height: 0.48rem;
+        color: #fff;
+        margin: 0.3rem auto 0;
+      }
+    }
+  }
+}
+</style>

+ 410 - 0
src/views/user/integral/Integral.vue

@@ -0,0 +1,410 @@
+<template>
+  <div class="integral">
+    <div class="integral-top">
+      <div class="top-title">当前积分</div>
+      <div class="top-count">{{total}}</div>
+      <div class="top-tip">
+        <div class="tip-left" @click="toexchange">积分兑换</div>
+        <div class="tip-right" @click="getpoints">如何获取积分</div>
+      </div>
+    </div>
+    <div class="integral-detail">
+      <div class="detail-title">积分明细</div>
+      <div class="detail-tabs">
+        <div :class="[ iscur === 0 ? activeClass : '', 'tab' ]" data-current="0" @click="changebar">全部</div>
+        <div :class="[ iscur === 1 ? activeClass : '', 'tab' ]" data-current="1" @click="changebar">收入</div>
+        <div :class="[ iscur === 2 ? activeClass : '', 'tab' ]" data-current="2" @click="changebar">支出</div>
+      </div>
+      <div class="datail-list">
+        <van-list v-model="isUpLoading" :finished="upFinished" finished-text="" @load="onLoadList" :offset="offset" v-if="iscur === 0">
+          <div class="list-item" v-for="(item, index) in list" :key="index">
+            <div class="item-left">
+              <div class="left-text">{{item.source_name}}</div>
+              <div class="left-time">{{item.ctime | formatSTime}}</div>
+            </div>
+            <div class="item-right">
+              <div class="right-text" v-if="item.count >= 0">{{item.count}}积分</div>
+              <div class="right-text" v-if="item.count < 0" style="color:#4eb8ff">{{item.count}}积分</div>
+            </div>
+            <div class="clear"></div>
+          </div>
+          <div class="list-none" v-show="shownone">
+            <img src="../../../assets/img/h.png" class="none-img" alt="">
+            <div class="none-text">当前数据为空</div>
+          </div>
+        </van-list>
+      </div>
+      <div class="datail-list">
+        <van-list v-model="isUpLoading1" :finished="upFinished1" finished-text="" @load="onLoadList1" :offset="offset1" v-if="iscur === 1">
+          <div class="list-item" v-for="(item, index) in list1" :key="index">
+            <div class="item-left">
+              <div class="left-text">{{item.source_name}}</div>
+              <div class="left-time">{{item.ctime | formatSTime}}</div>
+            </div>
+            <div class="item-right">
+              <div class="right-text" v-if="item.count >= 0">{{item.count}}积分</div>
+              <div class="right-text" v-if="item.count < 0" style="color:#4eb8ff">{{item.count}}积分</div>
+            </div>
+            <div class="clear"></div>
+          </div>
+          <div class="list-none" v-show="shownone1">
+            <img src="../../../assets/img/h.png" class="none-img" alt="">
+            <div class="none-text">当前数据为空</div>
+          </div>
+        </van-list>
+      </div>
+      <div class="datail-list">
+        <van-list v-model="isUpLoading2" :finished="upFinished2" finished-text="" @load="onLoadList2" :offset="offset2" v-if="iscur === 2">
+          <div class="list-item" v-for="(item, index) in list2" :key="index">
+            <div class="item-left">
+              <div class="left-text">{{item.source_name}}</div>
+              <div class="left-time">{{item.ctime | formatSTime}}</div>
+            </div>
+            <div class="item-right">
+              <div class="right-text" v-if="item.count >= 0">{{item.count}}积分</div>
+              <div class="right-text" v-if="item.count < 0" style="color:#4eb8ff">{{item.count}}积分</div>
+            </div>
+            <div class="clear"></div>
+          </div>
+          <div class="list-none" v-show="shownone2">
+            <img src="../../../assets/img/h.png" class="none-img" alt="">
+            <div class="none-text">当前数据为空</div>
+          </div>
+        </van-list>
+      </div>
+    </div>
+    <van-popup v-model="show" round >
+      <div class="popup-part">
+        <div class="popup-title">如何获取积分?</div>
+        <div class="popup-list">
+          <div class="popup-item">1、用户通过阅读积分文章可以获取积分</div>
+          <div class="popup-item">2、转发文章,他人点击,分享者可获取积分</div>
+          <!-- <div class="popup-item">3、会员在积分充足时可以兑换酒,非会员不能兑换酒</div>
+          <div class="popup-item">4、每个用户一天最多领取200积分</div> -->
+        </div>
+        <div class="popup-btn" @click="cancel">我知道了</div>
+      </div>
+    </van-popup>
+  </div>
+</template>
+<script>
+export default {
+  name: '',
+  props: [''],
+  data () {
+    return {
+      activeClass: 'active',
+      iscur: 0,
+      show: false,
+      total: 0,
+      page: 1,
+      per_page: 10,
+      offset: 5,
+      isUpLoading: false,
+      upFinished: false,
+      list: [],
+      shownone: false,
+      page1: 1,
+      per_page1: 10,
+      offset1: 5,
+      isUpLoading1: false,
+      upFinished1: false,
+      list1: [],
+      shownone1: false,
+      page2: 1,
+      per_page2: 10,
+      offset2: 5,
+      isUpLoading2: false,
+      upFinished2: false,
+      list2: [],
+      shownone2: false
+    }
+  },
+  created () {
+    this.getintegral()
+  },
+  components: {},
+  computed: {},
+  beforeMount() {},
+  mounted() {},
+  methods: {
+    onLoadList () {
+      setTimeout(() => {
+        this.getlist()
+      }, 100)
+    },
+    onLoadList1 () {
+      setTimeout(() => {
+        this.getlist1()
+      }, 100)
+    },
+    onLoadList2 () {
+      setTimeout(() => {
+        this.getlist2()
+      }, 100)
+    },
+    getlist () {
+      this.$get({
+        url: 'v1/user/mall/balances',
+        data: {
+          page: this.page,
+          per_page: this.per_page,
+        }
+      }).then((res) => {
+        if (this.page === 1) {
+          this.list = []
+          this.listtotal = 0
+        }
+        this.page++
+        this.isUpLoading = false
+        this.list = this.list.concat(res.list || [])
+        this.listtotal = res.list_count
+        if (this.list.length >= this.listtotal) {
+          this.upFinished = true
+        }
+        if (this.listtotal === 0) {
+          setTimeout(() => {
+            this.shownone = true
+          }, 100)
+        } else {
+          this.shownone = false
+        }
+      })
+    },
+    getlist1 () {
+      this.$get({
+        url: 'v1/user/mall/in/balances',
+        data: {
+          page: this.page1,
+          per_page: this.per_page1,
+        }
+      }).then((res) => {
+        if (this.page1 === 1) {
+          this.list1 = []
+          this.listtotal1 = 0
+        }
+        this.page1++
+        this.isUpLoading1 = false
+        this.list1 = this.list1.concat(res.list || [])
+        this.listtotal1 = res.list_count
+        if (this.list1.length >= this.listtotal1) {
+          this.upFinished1 = true
+        }
+        if (this.listtotal1 === 0) {
+          setTimeout(() => {
+            this.shownone1 = true
+          }, 100)
+        } else {
+          this.shownone1 = false
+        }
+      })
+    },
+    getlist2 () {
+      this.$get({
+        url: 'v1/user/mall/out/balances',
+        data: {
+          page: this.page2,
+          per_page: this.per_page2,
+        }
+      }).then((res) => {
+        if (this.page2 === 1) {
+          this.list2 = []
+          this.listtotal2 = 0
+        }
+        this.page2++
+        this.isUpLoading2 = false
+        this.list2 = this.list2.concat(res.list || [])
+        this.listtotal2 = res.list_count
+        if (this.list2.length >= this.listtotal2) {
+          this.upFinished2 = true
+        }
+        if (this.listtotal2 === 0) {
+          setTimeout(() => {
+            this.shownone2 = true
+          }, 100)
+        } else {
+          this.shownone2 = false
+        }
+      })
+    },
+    toexchange () {
+      this.$router.push('/exchange')
+    },
+    changebar (e) {
+      if (this.iscur === parseInt(e.target.dataset.current)) {
+        return false
+      } else {
+        this.iscur = parseInt(e.target.dataset.current)
+      }
+    },
+    getpoints () {
+      this.show = true
+    },
+    cancel () {
+      this.show = false
+    },
+    getintegral () {
+      this.$get({
+        url: 'v1/user/mall/balance/info',
+        data: {}
+      }).then((res) => {
+        console.log(res)
+        this.total = res.total
+      })
+    }
+  },
+  watch: {}
+}
+</script>
+<style lang='less' scoped>
+.integral {
+  min-height: 100%;
+  overflow: hidden;
+  background: url('../../../assets/img/bg1.png') #ffffff no-repeat top;
+  background-size: contain;
+  .integral-top {
+    height: 3.5rem;
+    width: 100%;
+    .top-title {
+      color: #ffffff;
+      font-size: 0.28rem;
+      text-align: center;
+      padding-top: 0.55rem;
+    }
+    .top-count {
+      color: #ffffff;
+      font-size: 0.65rem;
+      text-align: center;
+      padding-top: 0.35rem;
+    }
+    .top-tip {
+      display: flex;
+      color: #ffffff;
+      font-size: 0.28rem;
+      padding-top: 0.6rem;
+      .tip-left {
+        width: 50%;
+        text-align: right;
+        padding-right: 0.4rem;
+        border-right: 0.02rem solid #fff;
+      }
+      .tip-right {
+        width: 50%;
+        text-align: left;
+        padding-left: 0.4rem;
+      }
+    }
+  }
+  .integral-detail {
+    background: #fff;
+    border-top-left-radius: 0.25rem;
+    border-top-right-radius: 0.25rem;
+    .detail-title {
+      color: #999999;
+      font-size: 0.3rem;
+      text-align: center;
+      padding: 0.3rem 0;
+    }
+    .detail-tabs {
+      border: 0.02rem solid #ff484c;
+      border-radius: 0.1rem;
+      display: flex;
+      margin: 0 auto;
+      width: 4.56rem;
+      overflow: hidden;
+      margin-bottom: 0.2rem;
+      .tab {
+        background: #fff;
+        color: #ff484c;
+        text-align: center;
+        height: 0.5rem;
+        width: 33.33%;
+        font-size: 0.28rem;
+        line-height: 0.5rem;
+      }
+      .active {
+        color: #fff;
+        background: #ff484c;
+      }
+    }
+    .datail-list {
+      .list-item {
+        // height: 0.8rem;
+        border-bottom: 0.02rem solid #eeeeed;
+        box-sizing: border-box;
+        padding: 0.2rem;
+        .item-left {
+          float: left;
+          .left-text {
+            color: #4c4c4c;
+            font-size: 0.26rem;
+            padding-bottom: 0.05rem;
+          }
+          .left-time {
+            color: #999;
+            font-size: 0.24rem;
+          }
+        }
+        .item-right {
+          float: right;
+          .right-text {
+            color: #ff484c;
+            font-size: 0.3rem;
+            padding-top: 0.1rem;
+          }
+        }
+      }
+      .list-none {
+        position: absolute;
+        top: 6.5rem;
+        left: 50%;
+        transform: translateX(-50%);
+        .none-img {
+          height: 2.1rem;
+          width: 1.7rem;
+        }
+        .none-text {
+          color: #999;
+          font-size: 0.26rem;
+          padding-top: 0.2rem;
+        }
+      }
+    }
+  }
+  .popup-part {
+    // height: 5.7rem;
+    width: 5.5rem;
+    background: url('../../../assets/img/bg2.png') #ffffff no-repeat top;
+    background-size: contain;
+    .popup-title {
+      font-size: 0.4rem;
+      color: #fff;
+      font-weight: 600;
+      text-align: center;
+      padding-top: 0.3rem;
+    }
+    .popup-list {
+      margin-top: 0.9rem;
+      margin-left: 0.3rem;
+      margin-right: 0.3rem;
+      .popup-item {
+        color: #4c4c4c;
+        font-size: 0.26rem;
+        line-height: 0.5rem;
+      }
+    }
+    .popup-btn {
+      color: #ff484c;
+      border: 0.02rem solid #ff484c;
+      text-align: center;
+      font-size: 0.28rem;
+      height: 0.44rem;
+      line-height: 0.44rem;
+      margin: 0.2rem auto 0;
+      border-radius: 0.5rem;
+      width: 2.15rem;
+      margin-bottom: 0.4rem;
+    }
+  }
+}
+</style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 527 - 0
src/views/user/invite/Income.vue


+ 281 - 0
src/views/user/invite/Invite.vue

@@ -0,0 +1,281 @@
+<template>
+  <div class="invite-bg">
+    <div class="invite-top" v-if="info.wx_user">
+      <img :src="info.wx_user.head" class="top-head" alt="">
+      <div class="top-name" v-if="info.wx_user.nickname">{{info.wx_user.nickname}}</div>
+      <div class="top-name" v-if="!info.wx_user.nickname">微信用户</div>
+      <div class="clear"></div>
+    </div>
+    <div class="invite-center">
+      <div class="center-top">
+        <div class="top-title">我的收益</div>
+        <img src="@/assets/img/icon.png" @click="toincome" alt="" class="top-icon">
+        <div class="top-detail" @click="toincome">查看明细</div>
+        <div class="clear"></div>
+      </div>
+      <div class="center-income">
+        <div class="income-left">
+          <div class="income-title">当前收益</div>
+          <div class="income-count">{{balance.available | getAcounting('¥',false)}}</div>
+        </div>
+        <div class="income-right">
+          <div class="income-title">历史总收益</div>
+          <div class="income-count">{{balance.total | getAcounting('¥',false)}}</div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <div class="invite-bottom">
+      <div class="bottom-top">
+        <div class="top-title">我的邀请</div>
+        <img src="@/assets/img/icon.png" @click="torecode" alt="" class="top-icon">
+        <div class="top-detail" @click="torecode">查看记录</div>
+        <div class="clear"></div>
+      </div>
+      <div class="bottom-center">{{countnumber}}</div>
+      <div class="bottom-btn" :data-clipboard-text="countnumber" @click="copy">点击按钮邀请</div>
+      <!-- <div class="bottom-center">邀请人数</div>
+      <div class="bottom-count">8</div> -->
+    </div>
+  </div>
+</template>
+<script>
+import Clipboard from 'clipboard'
+import { Toast } from 'vant'
+import { mapGetters } from 'vuex'
+import Weixin from '@/common/wx'
+export default {
+  name: '',
+  props: [''],
+  data () {
+    return {
+      balance: {},
+      info: {},
+      countnumber: '',
+      id: 0
+    };
+  },
+  created() {
+    this.$store.dispatch('userInfo')
+    this.getbalanceinfo()
+    // this.getinvitelist()
+  },
+  components: {},
+  computed: {
+    ...mapGetters(['userInfo'])
+  },
+  beforeMount() {},
+  mounted() {},
+  methods: {
+    toincome () {
+      this.$router.push({ path: '/income' })
+    },
+    torecode () {
+      this.$router.push({ path: '/recode', query: { id: this.id } })
+    },
+    copy () {
+      var clipboard = new Clipboard('.bottom-btn')
+      clipboard.on('success', e => {
+        console.log(e)
+        Toast({
+          message: '链接复制成功,快去邀请吧~',
+          duration: 2000
+        })
+        clipboard.destroy()
+      })
+      clipboard.on('error', e => {
+        console.log(e)
+        Toast({
+          message: '该浏览器不支持自动复制',
+          duration: 2000
+        })
+        clipboard.destroy()
+      })
+    },
+    getbalanceinfo () {
+      this.$get({
+        url: 'v1/user/cash/balance/info',
+        data: {}
+      }).then((res) => {
+        console.log(res)
+        this.balance = res
+      })
+    },
+    getinvitelist () {
+      this.$get({
+        url: `v1/invite/1/list`,
+        data: {}
+      }).then((res) => {
+        console.log(res)
+        // this.balance = res
+      })
+    }
+  },
+  watch: {
+    'userInfo': function (val) {
+      console.log(val)
+      this.countnumber = window.location.origin + '/product?invite_id=' + val.wx_user.id
+      this.info = val
+      this.id = val.wx_user.id
+      const wx = new Weixin({
+        title: '天下一家',
+        desc: '醇酱香酒之一',
+        imgUrl: 'http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/wine_share.jpg',
+        link: window.location.origin + '/product?invite_id=' + val.wx_user.id
+      })
+    }
+  }
+}
+</script>
+<style lang='less' scoped>
+.invite-bg {
+  background: #eeeeed;
+  min-height: 100%;
+  width: 100%;
+  overflow: hidden;
+  .invite-top {
+    width: 7.1rem;
+    background: #fff;
+    border-radius: 0.1rem;
+    padding: 0.2rem;
+    box-sizing: border-box;
+    margin: 0.3rem auto 0.3rem;
+    .top-head {
+      height: 0.62rem;
+      width: 0.62rem;
+      background: #ccc;
+      float: left;
+      border-radius: 50%;
+      margin-right: 0.2rem;
+    }
+    .top-name {
+      font-size: 0.28rem;
+      color: #4c4c4c;
+      float: left;
+      margin-top: 0.1rem;
+    }
+  }
+  .invite-center {
+    width: 7.1rem;
+    background: #fff;
+    border-radius: 0.1rem;
+    padding: 0.2rem;
+    padding-top: 0.3rem;
+    box-sizing: border-box;
+    margin: 0 auto 0.3rem;
+    .center-top {
+      margin-bottom: 0.3rem;
+      .top-title {
+        color: #acbac3;
+        float: left;
+        font-weight: 600;
+        font-size: 0.3rem;
+      }
+      .top-detail {
+        float: right;
+        color: #acbac3;
+        font-size: 0.28rem;
+      }
+      .top-icon {
+        float: right;
+        width: 0.12rem;
+        height: 0.2rem;
+        margin-top: 0.08rem;
+        margin-left: 0.2rem;
+      }
+    }
+    .center-income {
+      padding-bottom: 0.2rem;
+      .income-left {
+        float: left;
+        width: 50%;
+        .income-title {
+          color: #999999;
+          font-size: 0.28rem;
+          margin-bottom: 0.1rem;
+        }
+        .income-count {
+          font-size: 0.4rem;
+          font-weight: 600;
+          color: #ff696b;
+        }
+      }
+      .income-right {
+        float: left;
+        width: 50%;
+        .income-title {
+          color: #999999;
+          font-size: 0.28rem;
+          margin-bottom: 0.1rem;
+        }
+        .income-count {
+          font-size: 0.4rem;
+          font-weight: 600;
+          color: #4c4c4c;
+        }
+      }
+    }
+  }
+  .invite-bottom {
+    width: 7.1rem;
+    background: #fff;
+    border-radius: 0.1rem;
+    padding: 0.3rem 0.2rem;
+    box-sizing: border-box;
+    margin: 0 auto 0.3rem;
+    .bottom-top {
+      margin-bottom: 0.2rem;
+      .top-title {
+        color: #acbac3;
+        float: left;
+        font-weight: 600;
+        font-size: 0.3rem;
+      }
+      .top-detail {
+        float: right;
+        color: #acbac3;
+        font-size: 0.28rem;
+      }
+      .top-icon {
+        float: right;
+        width: 0.12rem;
+        height: 0.2rem;
+        margin-top: 0.08rem;
+        margin-left: 0.2rem;
+      }
+    }
+    .bottom-center {
+      background: #ebf3f7;
+      // height:0.8rem;
+      width: 100%;
+      text-align: center;
+      line-height: 0.3rem;
+      font-size: 0.24rem;
+      color: #708fa3;
+      box-sizing: border-box;
+      word-wrap: break-word;
+      word-break: normal;
+      padding: 0.2rem;
+      margin-top: 0.3rem;
+      margin-bottom: 0.2rem;
+      border-radius: 0.05rem;
+    }
+    .bottom-btn {
+      width: 100%;
+      height: 0.8rem;
+      background: #28cd5e;
+      color: #fff;
+      text-align: center;
+      line-height: 0.8rem;
+      border-radius: 0.05rem;
+      font-size: 0.3rem;
+    }
+    // .bottom-count {
+    //   text-align: center;
+    //   font-size: 0.3rem;
+    //   color: #ff696b;
+    //   font-weight: 600;
+    // }
+  }
+}
+</style>

+ 411 - 0
src/views/user/invite/Recode.vue

@@ -0,0 +1,411 @@
+<template>
+  <div class="agent-recode">
+    <div class="recode-top">
+      <div class="top-title">我的邀请</div>
+      <div class="top-info">
+        <div class="info-left">
+          <div class="info-count">{{total_count}}人</div>
+          <div class="info-text">邀请总人数</div>
+        </div>
+      </div>
+    </div>
+    <div class="recode-list">
+      <div class="list-top">
+        <div :class="[ iscur === 0 ? activeClass : '', 'list-left' ]" data-current="0" @click="changebar">一级分销</div>
+        <div :class="[ iscur === 1 ? activeClass : '', 'list-right' ]" data-current="1" @click="changebar">二级分销</div>
+        <div class="list-line" :style="tableft"></div>
+        <div class="clear"></div>
+      </div>
+      <div class="list-center">
+        <van-list v-model="isUpLoading" :finished="upFinished" finished-text="" @load="onLoadList" :offset="offset" v-if="iscur === 0">
+          <div class="list-item" v-for="(item, index) in inlist" :key="index">
+            <div class="item-top">
+              <img :src="item.head" class="item-head" alt="">
+              <div class="item-name" v-if="item.nickname">{{item.nickname}}</div>
+              <div class="item-name" v-if="!item.nickname">微信用户</div>
+              <!-- <div class="item-num">({{item.id}})</div> -->
+              <div class="item-time">{{item.create_time | formatSTime}}</div>
+              <div class="clear"></div>
+            </div>
+          </div>
+          <div class="list-none" v-show="shownone">
+            <img src="@/assets/img/h.png" class="none-img" alt="">
+            <div class="none-text">当前数据为空</div>
+          </div>
+        </van-list>
+      </div>
+      <div class="list-center">
+        <van-list v-model="isUpLoading1" :finished="upFinished1" finished-text="" @load="onLoadList1" :offset="offset1" v-if="iscur === 1">
+          <div class="list-item" v-for="(item, index) in outlist" :key="index">
+            <div class="item-top">
+              <img :src="item.head" class="item-head" alt="" v-if="item.head">
+              <img src="@/assets/img/wx.png" class="item-head" alt="" v-if="!item.head">
+              <div class="item-name" v-if="item.nickname">{{item.nickname}}</div>
+              <div class="item-name" v-if="!item.nickname">微信用户</div>
+              <!-- <div class="item-num">({{item.id}})</div> -->
+              <div class="item-time">{{item.create_time | formatSTime}}</div>
+              <div class="clear"></div>
+            </div>
+          </div>
+          <div class="list-none" v-show="shownone1">
+            <img src="@/assets/img/h.png" class="none-img" alt="">
+            <div class="none-text">当前数据为空</div>
+          </div>
+        </van-list>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  name: '',
+  props: {
+  },
+  data () {
+    return {
+      shownone: false,
+      shownone1: false,
+      inlist: [],
+      intotal: 0,
+      outlist: [],
+      outtotal: 0,
+      activeClass: 'active',
+      iscur: 0,
+      tableft: {
+        left: '0.96rem'
+      },
+      recode: {},
+      page: 1,
+      per_page: 10,
+      page1: 1,
+      per_page1: 10,
+      offset: 5,
+      offset1: 5,
+      isUpLoading: false,
+      upFinished: false,
+      isUpLoading1: false,
+      upFinished1: false,
+      tab: true,
+      tab1: true,
+      total_count: '',
+      id: 0
+    }
+  },
+  created () {
+    // this.$store.dispatch('userInfo')
+    // this.getrecode()
+    // if (this.$route.query.invite_id) {
+    //   this.id = this.$route.query.invite_id
+    // }
+  },
+  components: {},
+  computed: {
+    ...mapGetters(['userInfo'])
+  },
+  beforeMount() {},
+  mounted() {},
+  methods: {
+    in () {
+      this.$get({
+        url: `v1/invite/${this.$route.query.id}/list`,
+        data: {
+          page: this.page,
+          per_page: this.per_page,
+        }
+      }).then((res) => {
+        this.total_count = res.total_count
+        this.tab = false
+        if (this.page === 1) {
+          this.inlist = []
+          this.intotal = 0
+        }
+        this.page++
+        this.isUpLoading = false
+        this.inlist = this.inlist.concat(res.list || [])
+        this.intotal = res.list_count
+        if (this.inlist.length >= this.intotal) {
+          this.upFinished = true
+        }
+        if (this.intotal === 0) {
+          setTimeout(() => {
+            this.shownone = true
+          }, 100)
+        } else {
+          this.shownone = false
+        }
+      })
+    },
+    out () {
+      this.$get({
+        url: `v1/invite/${this.$route.query.id}/frend/list`,
+        data: {
+          page: this.page1,
+          per_page: this.per_page1,
+        }
+      }).then((res) => {
+        this.tab1 = false
+        if (this.page1 === 1) {
+          this.outlist = []
+          this.outtotal = 0
+        }
+        this.page1++
+        this.isUpLoading1 = false
+        this.outlist = this.outlist.concat(res.list || [])
+        this.outtotal = res.list_count
+        if (this.outlist.length >= this.outtotal) {
+          this.upFinished1 = true
+        }
+        if (this.outtotal === 0) {
+          setTimeout(() => {
+            this.shownone1 = true
+          }, 100)
+        } else {
+          this.shownone1 = false
+        }
+      })
+    },
+    onLoadList () {
+      setTimeout(() => {
+        this.in()
+      }, 100)
+    },
+    onLoadList1 () {
+      setTimeout(() => {
+        this.out()
+      }, 100)
+    },
+    getrecode () {
+      this.$get({
+        url: 'v1/agent/invite_mater',
+        data: {
+        }
+      }).then((res) => {
+        this.recode = res
+      })
+    },
+    changebar (e) {
+      if (this.iscur === parseInt(e.target.dataset.current)) {
+        return false
+      } else {
+        this.iscur = parseInt(e.target.dataset.current)
+        if (this.iscur === 0) {
+          this.tableft.left = '0.96rem'
+        } else if (this.iscur === 1) {
+          this.tableft.left = '4.32rem'
+        }
+      }
+    }
+  },
+  watch: {
+    // 'userInfo': function (val) {
+    //   console.log(val)
+    //   this.id = val.wx_user.id
+    // }
+  }
+}
+</script>
+<style lang='less' scoped>
+.agent-recode {
+  background: #eeeeed;
+  min-height: 100%;
+  overflow: hidden;
+  text-align: left;
+  .recode-top {
+    width: 7.1rem;
+    margin: 0.2rem auto;
+    background: #fff;
+    box-sizing: border-box;
+    padding: 0 0.2rem;
+    border-radius: 0.1rem;
+    overflow: hidden;
+    .top-title {
+      color: #acbac3;
+      font-size: 0.32rem;
+      font-weight: 600;
+      padding-top: 0.25rem;
+      padding-bottom: 0.25rem;
+    }
+    .top-info {
+      display: flex;
+      display: -webkit-flex;
+      justify-content: space-between;
+      flex-wrap: nowrap;
+      text-align: center;
+      margin-bottom: 0.3rem;
+      .info-left {
+        flex-grow: 1;
+        height: 1.1rem;
+        border-right: 0.02rem solid #eee;
+        .info-count {
+          font-size: 0.26rem;
+          color: #ff595d;
+          margin-top: 0.1rem;
+          margin-bottom: 0.1rem;
+        }
+        .info-text {
+          font-size: 0.26rem;
+          color: #999999;
+        }
+      }
+      .info-center {
+        flex-grow: 1;
+        height: 1.1rem;
+        border-right: 0.02rem solid #eee;
+        .info-count {
+          font-size: 0.26rem;
+          color: #4c4c4c;
+          margin-top: 0.1rem;
+          margin-bottom: 0.1rem;
+        }
+        .info-text {
+          font-size: 0.26rem;
+          color: #999999;
+        }
+      }
+      .info-right {
+        flex-grow: 1;
+        height: 1.1rem;
+        .info-count {
+          font-size: 0.26rem;
+          color: #4c4c4c;
+          margin-top: 0.1rem;
+          margin-bottom: 0.1rem;
+        }
+        .info-text {
+          font-size: 0.26rem;
+          color: #999999;
+        }
+      }
+    }
+  }
+  .recode-list {
+    background: #fff;
+    border-radius: 0.1rem;
+    box-sizing: border-box;
+    padding: 0 0.2rem 0.2rem;
+    margin: 0.2rem auto 0.2rem;
+    width: 7.1rem;
+    position: relative;
+    .list-top {
+      height: 0.75rem;
+      width: 100%;
+      border-bottom: 0.02rem solid #eee;
+      position: relative;
+      .list-left {
+        width: 50%;
+        color: #acbac3;
+        text-align: center;
+        float: left;
+        font-size: 0.26rem;
+        height: 0.75rem;
+        line-height: 0.75rem;
+      }
+      .list-right {
+        width: 50%;
+        color: #acbac3;
+        text-align: center;
+        float: left;
+        font-size: 0.26rem;
+        height: 0.72rem;
+        line-height: 0.72rem;
+      }
+      .list-line {
+        width: 1.45rem;
+        height: 0.02rem;
+        background: #28cd5e;
+        position: absolute;
+        left: 0;
+        bottom: 0;
+        transition: left 0.4s;
+      }
+      .active {
+        color: #28cd5e;
+      }
+    }
+    .list-center {
+      .list-none {
+        position: absolute;
+        top: 2.5rem;
+        left: 50%;
+        transform: translateX(-50%);
+        .none-img {
+          height: 2.1rem;
+          width: 1.7rem;
+        }
+        .none-text {
+          color: #999;
+          font-size: 0.26rem;
+          padding-top: 0.2rem;
+        }
+      }
+      .list-item {
+        // height: 1.6rem;
+        width: 100%;
+        margin-top: 0.2rem;
+        background: #fff;
+        box-sizing: border-box;
+        padding: 0 0.2rem;
+        .item-top {
+          // height: 0.88rem;
+          border-bottom: 0.02rem solid #ccc;
+          width: 100%;
+          padding-bottom: 0.25rem;
+          .item-head {
+            height: 0.5rem;
+            width: 0.5rem;
+            // background: pink;
+            border-radius: 50%;
+            float: left;
+            margin-right: 0.2rem;
+            margin-top: 0.2rem;
+          }
+          .item-name {
+            color: #4c4c4c;
+            font-size: 0.26rem;
+            margin-right: 0.2rem;
+            float: left;
+            margin-top: 0.26rem;
+            overflow: hidden;
+            text-overflow:ellipsis;
+            white-space: nowrap;
+            max-width: 2.5rem;
+          }
+          .item-num {
+            color: #4c4c4c;
+            font-size: 0.26rem;
+            float: left;
+            margin-top: 0.26rem;
+          }
+          .item-time {
+            color: #708fa3;
+            font-size: 0.26rem;
+            float: right;
+            margin-top: 0.28rem;
+          }
+        }
+        .item-bottom {
+          padding-bottom: 0.3rem;
+          .bottom-left {
+            float: left;
+            color: #4c4c4c;
+            font-size: 0.26rem;
+            margin-top: 0.2rem;
+          }
+          .bottom-right {
+            float: right;
+            color: #4c4c4c;
+            font-size: 0.26rem;
+            margin-top: 0.2rem;
+          }
+          .bottom-bottom {
+            color: #4c4c4c;
+            font-size: 0.26rem;
+            margin-top: 0.2rem;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 262 - 0
src/views/user/member/Member.vue

@@ -0,0 +1,262 @@
+
+<template>
+  <div class="member" v-if="info.wx_user">
+    <div class="member-top">
+      <div class="member-img" :style="{ background: 'url(' + (info.wx_user.vip ? img1 : img2) + ') no-repeat top', 'background-size': 'contain'}">
+        <img :src="info.wx_user.head" v-if="info.wx_user.head" class="img-head" alt="">
+        <img src="../../../assets/img/wx.png" v-if="info.wx_user.head === ''" class="img-head" alt="">
+        <div class="img-info">
+          <div class="info-top">
+            <div class="info-name" v-if="info.wx_user.nickname">{{info.wx_user.nickname}}</div>
+            <div class="info-name" v-if="!info.wx_user.nickname">微信用户</div>
+            <img src="../../../assets/img/member.png" class="info-icon" v-if="info.wx_user.vip" alt="">
+            <img src="../../../assets/img/member1.png" class="info-icon" v-if="!info.wx_user.vip" alt="">
+            <div class="clear"></div>
+          </div>
+          <div class="info-time" v-if="info.wx_user.vip">当前会员有效期至{{info.wx_user.vip_time | formatDate}}</div>
+          <div class="info-time" style="color:#999999" v-if="!info.wx_user.vip">当前还未开通会员</div>
+        </div>
+        <div class="clear"></div>
+      </div>
+    </div>
+    <div class="member-product">
+      <div class="product-left" v-for="(item, index) in list" :key="index" @click="todetail(item.id)">
+        <div class="product-title">{{item.title}}</div>
+        <img :src="item.img_thumb_url" alt="" class="product-img">
+        <div class="product-price">原价: {{item.price | getAcounting('¥', false)}}</div>
+        <div class="product-mprice">会员价: {{item.vip_price | getAcounting('¥', false)}}</div>
+      </div>
+      <div class="clear"></div>
+    </div>
+    <div class="member-tip">
+      <div class="tip-item">会员卡详情:</div>
+      <div class="tip-item">1.会员卡有效期为<span style="color:#f14f4f">一年</span>,开通即日起算,会员卡<span style="color:#f14f4f">9.9元</span>开通。</div>
+      <div class="tip-item">2.开通会员卡后购买商品享受会员价。</div>
+    </div>
+    <div class="member-bottom">
+      <div class="bottom-btn" v-if="info.wx_user.vip" @click="pay">¥{{userCheck.vip_card_fee / 100}}/年 立即续费</div>
+      <div class="bottom-btn" v-if="!info.wx_user.vip" @click="pay">¥{{userCheck.vip_card_fee / 100}}/年 立即开通</div>
+    </div>
+  </div>
+</template>
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  name:'',
+  props:[''],
+  data () {
+    return {
+      info: {},
+      img1: require('../../../assets/img/vip.png'),
+      img2: require('../../../assets/img/vip1.png'),
+      page: 1,
+      per_page: 10,
+      list: [],
+      payData: {},
+      loading: false
+    }
+  },
+  components: {},
+  computed: {
+    ...mapGetters(['userInfo','userCheck']),
+  },
+  beforeMount() {},
+  mounted() {},
+  methods: {
+    todetail (id) {
+      if (this.$route.query.invite_id) {
+        this.$router.push(`/product/${id}?invite_id=${this.$route.query.invite_id}`)
+      } else {
+        this.$router.push(`/product/${id}`)
+      }
+    },
+    getlist() {
+      this.$get({
+        url: 'v1/project/list',
+        data: { page: this.page, per_page: this.per_page }
+      }).then((res) => {
+        console.log(res)
+        this.list = res.list
+      })
+    },
+    cancelLoading () {
+      this.loading = false
+      this.$toast.clear()
+    },
+    pay () {
+      var order_id = ''
+      if (this.info.wx_user.vip) {
+        order_id = 2
+      } else {
+        order_id = 1
+      }
+      this.$post({
+        url: 'v1/pay',
+        data: {
+          order_id: order_id,
+          pay_way: 'weixinpay'
+        }
+      }).then((resp) => {
+        this.cancelLoading()
+        this.payData = resp.pay_data
+        // eslint-disable-next-line
+        if (typeof WeixinJSBridge === undefined) {
+          if (document.addEventListener) {
+            document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false)
+          } else if (document.attachEvent) {
+            document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady)
+            document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady)
+          }
+        } else {
+          this.onBridgeReady()
+        }
+      })
+    },
+    onBridgeReady () {
+      this.$toast.clear()
+      let that = this
+      // eslint-disable-next-line
+      WeixinJSBridge.invoke('getBrandWCPayRequest', {
+        appId: this.payData.appId,
+        timeStamp: this.payData.timeStamp,
+        nonceStr: this.payData.nonceStr,
+        package: this.payData.package,
+        signType: this.payData.signType,
+        paySign: this.payData.paySign
+      },
+      function (res) {
+        if (res.err_msg === 'get_brand_wcpay_request:ok') {
+          that.$toast.loading('支付成功,刷新中···')
+          setTimeout(() => { that.$store.dispatch('userInfo') }, 1000)
+        } else {
+          that.$toast('微信支付失败!')
+        }
+      })
+    },
+  },
+  created () {
+    this.$store.dispatch('userInfo')
+    this.getlist()
+    console.log(this.userCheck)
+  },
+  watch: {
+    'userInfo': function (val) {
+      console.log(val)
+      this.info = val
+    }
+  }
+}
+</script>
+<style lang='less' scoped>
+.member {
+  background: #ffffff;
+  min-height: 100%;
+  width: 100%;
+  overflow: hidden;
+  .member-top {
+    // height: 3.4rem;
+    width: 100%;
+    box-sizing: border-box;
+    padding: 0.2rem;
+    .member-img {
+      height: 3rem;
+      .img-head {
+        width: 0.8rem;
+        height: 0.8rem;
+        border-radius: 50%;
+        float: left;
+        margin: 0.3rem 0.3rem 0;
+      }
+      .img-info {
+        float: left;
+        margin-top: 0.3rem;
+        .info-top {
+          .info-name {
+            float: left;
+            color: #4c4c4c;
+            font-size: 0.28rem;
+          }
+          .info-icon {
+            width: 0.3rem;
+            height: 0.35rem;
+            float: left;
+            margin-left: 0.2rem;
+          }
+        }
+        .info-time {
+          color: #b4803f;
+          font-size: 0.26rem;
+          margin-top: 0.08rem;
+        }
+      }
+    }
+  }
+  .member-product {
+    width: 100%;
+    // box-sizing: border-box;
+    // padding: 0.2rem;
+    padding-top: 0;
+    padding-bottom: 0.2rem;
+    .product-left {
+      float: left;
+      height: 4.9rem;
+      width: 3.45rem;
+      background: #2b2b2b;
+      border-radius: 0.1rem;
+      box-sizing: border-box;
+      padding: 0 0.03rem;
+      margin-left: 0.2rem;
+      .product-title {
+        color: #ffffff;
+        font-size: 0.28rem;
+        text-align: center;
+        height: 0.56rem;
+        line-height: 0.56rem;
+      }
+      .product-img {
+        display: block;
+        // background: #ffffff;
+        width: 100%;
+        height: 3.4rem;
+      }
+      .product-price {
+        color:#ffffff;
+        text-align: center;
+        font-size: 0.28rem;
+        margin-top: 0.08rem;
+      }
+      .product-mprice {
+        color:#ffde00;
+        text-align: center;
+        font-size: 0.28rem;
+      }
+    }
+  }
+  .member-tip {
+    color: #999999;
+    font-size: 0.26rem;
+    line-height: 0.5rem;
+    margin-left: 0.2rem;
+  }
+  .member-bottom {
+    height: 0.98rem;
+    border-top: 0.02rem solid #cccccc;
+    width: 100%;
+    position: fixed;
+    bottom: 0;
+    box-sizing: border-box;
+    padding: 0 0.2rem;
+    .bottom-btn {
+      background-image: linear-gradient(to right,#e4b479,#c28a43);
+      border-radius: 0.5rem;
+      width: 100%;
+      height: 0.63rem;
+      line-height: 0.63rem;
+      margin-top: 0.2rem;
+      text-align: center;
+      font-size: 0.28rem;
+      color: #ffffff;
+    }
+  }
+}
+</style>

+ 577 - 0
src/views/user/order/Detail.vue

@@ -0,0 +1,577 @@
+<template>
+  <div class="order-bg">
+    <div class="order-top clearfix">
+      <div class="order-type">
+        <img src="@/assets/images/order/order_icon.png" class="order-icon" alt="" />
+        <span>{{orderTypeCn}}</span>
+      </div>
+      <p class="order-time" v-if="orderDetail.status === 1 && cancelTime"><van-count-down :time="cancelTime" /> 后自动取消订单</p>
+      <!-- <p class="order-count">订单金额: {{orderDetail.total_price | getAcounting()}}</p> -->
+    </div>
+    <div class="order-address">
+      <img src="@/assets/images/order/location.png" class="address-img" alt="" />
+      <div class="address-info">
+        <div class="info-name">
+          <p>
+            {{orderDetail.contact}}
+            <small class="info-tel">{{orderDetail.tel}}</small>
+          </p>
+        </div>
+      </div>
+      <div class="address-text">
+        {{orderDetail.address}}
+      </div>
+    </div>
+
+    <div class="order-product">
+      <div class="product-top clearfix" @click="toProduct">
+        <van-image class="product-img" fit="cover" :src="orderDetail.project_image" />
+        <div class="product-info">
+          <p class="product-name">{{orderDetail.project_name}}</p>
+          <p class="product-money" v-if="!orderDetail.pay_jf">¥<span class="money-text">{{orderDetail.price  | getAcounting('')}}</span><span class="money-count">x {{orderDetail.count}}</span></p>
+          <p class="product-money"><span class="money-text">{{orderDetail.pay_jf}} 积分</span><span class="money-count">x {{orderDetail.count}}</span></p>
+          <p>{{orderDetail.buy_card_fee || userCheck.is_vip ? '(会员价)' : ''}}</p>
+        </div>
+      </div>
+
+      <van-divider :style="{ borderColor: '#eee', margin: '0' }" />
+
+      <div class="product-bottom">
+        <div class="detail-item clearfix">
+          <div class="detail-left">商品总价{{orderDetail.buy_card_fee || userCheck.is_vip ? '(会员价)' : ''}}</div>
+          <p class="detail-right" v-if="!orderDetail.pay_jf">{{productTotal | getAcounting()}}</p>
+          <p class="detail-right" v-else>{{orderDetail.pay_jf}} 积分</p>
+        </div>
+        <div class="detail-item clearfix" v-if="orderDetail.buy_card_fee">
+          <div class="detail-left">会员卡</div>
+          <div class="detail-right">{{orderDetail.buy_card_fee | getAcounting}}</div>
+        </div>
+        <div class="detail-item clearfix" v-if="orderDetail.pay_balance">
+          <div class="detail-left">余额抵扣</div>
+          <div class="detail-right">-{{orderDetail.pay_balance | getAcounting()}}</div>
+        </div>
+        <div class="detail-item clearfix" v-if="orderDetail.status > 2">
+          <p class="detail-left1" v-if="!orderDetail.pay_jf">{{orderDetail.status > 1 ? '需':'实'}}付款</p>
+          <p class="detail-left1" v-else>积分抵扣</p>
+          <div class="detail-right1">
+            <span v-if="!orderDetail.pay_jf">{{orderDetail.pay_price | getAcounting()}}</span>
+            <span v-else>{{orderDetail.pay_jf}} 积分</span>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="order-dec">
+      <div class="dec-title">订单信息</div>
+      <div class="dec-info">
+        <p class="info-item">订单编号: {{orderDetail.order_id}} <van-button class="dec-copy" round plain type="primary" color="#ff484c" :data-clipboard-text="orderDetail.order_id" @click="copy(1)">复制</van-button></p>
+        <p class="info-item">下单时间: {{orderDetail.create_time_unix | formatTime}}</p>
+        <p class="info-item" v-if="orderDetail.dispatch_no">快递单号:{{orderDetail.dispatch_no}} <van-button class="dec-copy1" round plain type="primary" color="#ff484c" :data-clipboard-text="orderDetail.dispatch_no" @click="copy(2)">复制</van-button></p>
+        <p class="info-item" v-if="orderDetail.dispatch_company">快递公司:{{orderDetail.dispatch_company}}</p>
+        <!-- <p class="info-item">备注:{{orderDetail.remark?orderDetail.remark:'-'}}</p> -->
+        <!-- <div class="info-item" v-if="orderDetail.order_state !== 1">支付方式: 微信支付</div> -->
+        <!-- <div class="info-item" v-if="orderDetail.order_state !== 1">下单时间: {{orderDetail.create_time | formatTime}}</div> -->
+      </div>
+      <van-divider :style="{ borderColor: '#eee', margin: '0' }" />
+      <div class="dec-bottom" @click="serviceDialogClick">
+        <img class="dec-icon" src="@/assets/images/order/service_icon.png" alt="" />
+        <div class="dec-kefu">联系客服</div>
+      </div>
+    </div>
+    <div class="order-bottom" v-if="btnCn || orderDetail.order_state <= 2">
+      <div class="bottom-btn">
+        <van-button class="btn btn1" round plain type="primary" color="#ff484c" v-if="btnCn" @click="postClick">{{btnCn}}</van-button>
+        <van-button class="btn" round plain type="primary" color="#999999" v-if="orderDetail.status === 1" @click="btnClick('cancel')">取消订单</van-button>
+        <!-- <van-button class="btn" round plain type="primary" color="#999999" v-if="orderDetail.status === 2" @click="deleteOrder">删除订单</van-button> -->
+      </div>
+    </div>
+    <service-dialog :show="showServiceDialog" :serviceData="serviceData" />
+  </div>
+</template>
+<script>
+import Vue from 'vue'
+import { mapGetters } from 'vuex'
+import Clipboard from 'clipboard'
+import { Image as VanImage, Divider, CountDown } from 'vant'
+import ServiceDialog from '@/components/dialog/Service'
+// import localStorage from '@/common/localStorage'
+const CHANNEL_KEY = 'channel'
+export default {
+  name: 'userOrderDetail',
+  data () {
+    return {
+      now: new Date().getTime(),
+      showServiceDialog: false,
+      oid: this.$route.params.order_id,
+      payData: {},
+      serviceData: {
+        title: '添加客服',
+        desc: '长按扫描二维码,添加售后客服',
+        remark: '有关商品售后问题可咨询客服',
+        url: 'http://world-wine.oss-cn-shenzhen.aliyuncs.com/shareImg/service_code.jpg'
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['orderDetail', 'userCheck']),
+    cancelTime: function () {
+      let time = Math.round(this.orderDetail.create_time_unix * 1000 + 30 * 60 * 1000 - this.now)
+      if (time < 0) {
+        return 0
+      }
+      return time
+    },
+    orderTypeCn: function () {
+      let type = this.orderDetail.status
+      if (type === 1) {
+        return '等待买家付款'
+      } else if (type === 2) {
+        return '交易已关闭'
+      } else if (type === 4) {
+        return '等待卖家发货'
+      } else if (type === 5) {
+        return '卖家已发货'
+      } else if (type === 6 || type === 7) {
+        return '已完成'
+      } else if (type === 8) {
+        return '退款中'
+      } else if (type === 9) {
+        return '退款成功'
+      } else {
+        return '--'
+      }
+    },
+    btnCn: function () {
+      let type = this.orderDetail.status
+      if (type === 1) {
+        return '付款'
+      } else if (type === 5) {
+        return '确认收货'
+      } else {
+        return ''
+      }
+    },
+    productTotal: function () {
+      let product = this.orderDetail
+      let total = product.price * product.count
+      return total
+    }
+  },
+  methods: {
+    toProduct () {
+      this.$router.push(`/product/${this.orderDetail.product_id}`)
+    },
+    copy (id) {
+      var clipboard
+      if (id === 1) {
+        clipboard = new Clipboard('.dec-copy')
+      } else {
+        clipboard = new Clipboard('.dec-copy1')
+      }
+      clipboard.on('success', e => {
+        this.$toast({
+          message: `复制${id === 1 ? '订单号' : '快递单号'}成功`,
+          duration: 2000
+        })
+        clipboard.destroy()
+      })
+      clipboard.on('error', e => {
+        this.$toast({
+          message: '该浏览器不支持自动复制',
+          duration: 2000
+        })
+        clipboard.destroy()
+      })
+    },
+    getOrderInfo () {
+      this.$store.dispatch('getOrderDetail', {id: this.oid})
+    },
+    serviceDialogClick () {
+      this.showServiceDialog = !this.showServiceDialog
+    },
+    btnClick (type) {
+      let msg
+      if (type === 'cancel') {
+        msg = '是否确认取消该订单'
+      } else {
+        this.$router.push(`/pay/${this.orderDetail.order_id}`)
+        return
+      }
+      this.$dialog.confirm({
+        title: '提示',
+        message: msg
+      }).then(() => {
+        if (type === 'cancel') {
+          this.cancelOrder()
+        }
+      })
+    },
+    confirmOrder () {
+      this.$post({
+        url: `v1/spirit/order/${this.orderDetail.order_id}/confirm`
+      }).then((resp) => {
+        if (resp.result) {
+          this.$toast('收货成功')
+          this.getOrderInfo()
+        }
+      })
+    },
+    postClick: function () {
+      let type = this.orderDetail.status
+      if (type === 1) {
+        this.btnClick()
+      } else if (type === 5) {
+        this.confirmOrder()
+      }
+    },
+    // 微信支付
+    onBridgeReady () {
+      // eslint-disable-next-line
+      WeixinJSBridge.invoke('getBrandWCPayRequest', {
+        appId: this.payData.appId,
+        timeStamp: this.payData.timeStamp,
+        nonceStr: this.payData.nonceStr,
+        package: this.payData.package,
+        signType: this.payData.signType,
+        paySign: this.payData.paySign
+      },
+      function (res) {
+        if (res.err_msg === 'get_brand_wcpay_request:ok') {
+          this.$toast('微信支付成功!')
+          this.goSuccess()
+        } else {
+          this.$toast('微信支付失败!')
+        }
+      })
+    },
+    pay () {
+      this.$post({
+        url: 'v1/pay',
+        data: {
+          order: this.oid
+        }
+      }).then((resp) => {
+        this.payData = resp.pay_data
+        this.cancelLoading()
+        // eslint-disable-next-line
+        if (typeof WeixinJSBridge === undefined) {
+          if (document.addEventListener) {
+            document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady, false)
+          } else if (document.attachEvent) {
+            document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady)
+            document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady)
+          }
+        } else {
+          this.onBridgeReady()
+        }
+      })
+    },
+    cancelOrder () {
+      this.$put({
+        url: `v1/spirit/order/${this.orderDetail.order_id}/cancel`,
+        data: {
+          order_id: this.oid
+        }
+      }).then((resp) => {
+        if (resp.result) {
+          this.$toast('取消成功')
+          this.getOrderInfo()
+        }
+      })
+    },
+    deleteOrder () {
+      this.$post({
+        url: 'v1/order/delete',
+        data: {
+          order_id: this.oid
+        }
+      }).then((resp) => {
+        if (resp.result) {
+          this.$toast('删除订单成功')
+          this.$router.push({ path: '/order', query: { status: 0, index: 0 } })
+        }
+      })
+    }
+  },
+  created () {
+    this.getOrderInfo()
+    Vue.use(VanImage).use(Divider).use(CountDown)
+  },
+  watch: {
+  },
+  components: {
+    ServiceDialog
+  }
+}
+</script>
+<style lang="less" scoped>
+.order-bg {
+  text-align: left;
+  min-height: 100%;
+  background-color: #f3f3f3;
+  .van-button--normal {
+    padding: 0;
+  }
+  .editBtn {
+    &.van-button {
+      height: .64rem;
+      padding: 0 .12rem;
+    }
+  }
+  .order-top {
+    position: relative;
+    width: 100%;
+    background: #a8c0cf;
+    color: #fff;
+    padding: 0.3rem .2rem;
+    margin-bottom: .3rem;
+    box-sizing: border-box;
+    .order-type {
+      float: left;
+      width: 3.2rem;
+      font-size: 0.32rem;
+    }
+    .order-count {
+      font-size: 0.28rem;
+    }
+    .order-time {
+      float: right;
+      font-size: 0.26rem;
+      padding: 0.05rem 0.2rem;
+      border: 0.02rem solid #ffffff;
+      border-radius: 0.2rem;
+      overflow: hidden;
+      box-sizing: border-box;
+      .van-count-down {
+        color: #fff;
+        margin-right: .08rem;
+        float: left;
+      }
+    }
+    .product-code {
+      float: right;
+      font-size: .28rem;
+      .van-icon {
+        font-size: .5rem;
+        vertical-align: middle;
+      }
+    }
+    .order-icon {
+      width: 0.4rem;
+      height: 0.44rem;
+      vertical-align: middle;
+      margin-right: .16rem;
+    }
+  }
+  .order-address {
+    position: relative;
+    width: 100%;
+    background: #fff;
+    box-sizing: border-box;
+    padding: 0.3rem 0.3rem 0.3rem 0.77rem;
+    margin-bottom: .3rem;
+    .address-img {
+      position: absolute;
+      top: 0.4rem;
+      left: 0.2rem;
+      width: 0.4rem;
+      height: 0.4rem;
+    }
+    .address-info {
+      .info-name {
+        color: #4c4c4c;
+        font-size: 0.34rem;
+        margin-right: 0.2rem;
+      }
+      .info-tel {
+        color: #999;
+        font-size: 0.24rem;
+      }
+    }
+    .address-text {
+      width: 5.8rem;
+      line-height: 0.4rem;
+      margin-top: 0.12rem;
+      color: #4c4c4c;
+      font-size: 0.28rem;
+    }
+  }
+  .order-product {
+    width: 100%;
+    background: #fff;
+    .product-top {
+      position: relative;
+      padding: 0.3rem 0.2rem 0.3rem;
+      box-sizing: border-box;
+      .product-img {
+        float: left;
+        background: #7f7f7f;
+        height: 1.6rem;
+        width: 1.6rem;
+        border-radius: 0.2rem;
+        margin-right: 0.2rem;
+      }
+      .product-info {
+        float: left;
+        .product-name {
+          color: #4c4c4c;
+          font-size: 0.28rem;
+          margin-bottom: 0.05rem;
+          text-overflow: ellipsis;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          overflow: hidden;
+          width: 5rem;
+        }
+        .product-dec {
+          color: #999;
+          font-size: 0.24rem;
+          width: 5rem;
+          line-height: 0.35rem;
+          text-overflow: ellipsis;
+          display: -webkit-box;
+          -webkit-box-orient: vertical;
+          -webkit-line-clamp: 2;
+          overflow: hidden;
+          height: 0.72rem;
+        }
+        .product-money {
+          color: #4c4c4c;
+          font-size: 0.28rem;
+          margin-top: 0.24rem;
+          .money-text {
+            font-size: 0.34rem;
+          }
+          .money-count {
+            padding-left: 0.3rem;
+            color: #999;
+          }
+        }
+        .product-size {
+          font-size: .24rem;
+          color: #999;
+          margin-top: .12rem;
+        }
+      }
+      .product-btn {
+        color: #999;
+        border: 0.02rem solid #ccc;
+        text-align: center;
+        font-size: 0.26rem;
+        border-radius: 0.3rem;
+        width: 1.4rem;
+        height: 0.4rem;
+        line-height: 0.4rem;
+        position: absolute;
+        bottom: 0.25rem;
+        right: 0.3rem;
+      }
+    }
+    .product-bottom {
+      box-sizing: border-box;
+      padding: 0.2rem 0.2rem;
+      background: #fff;
+      .detail-item {
+        line-height: 0.6rem;
+        .detail-left {
+          float: left;
+          color: #4c4c4c;
+          font-size: 0.28rem;
+        }
+        .detail-left1 {
+          float: left;
+          color: #4c4c4c;
+          font-size: 0.32rem;
+        }
+        .detail-right {
+          float: right;
+          color: #4c4c4c;
+          font-size: 0.28rem;
+        }
+        .detail-right1 {
+          float: right;
+          color: #f14f4f;
+          font-size: 0.36rem;
+        }
+      }
+    }
+  }
+  .order-dec {
+    // height: 3.16rem;
+    width: 100%;
+    box-sizing: border-box;
+    // padding: 0.3rem 0.2rem 0;
+    margin-top: 0.25rem;
+    background: #fff;
+    position: relative;
+    margin-bottom: 0.25rem;
+    .dec-title {
+      color: #4c4c4c;
+      font-size: 0.3rem;
+      padding-bottom: 0.3rem;
+      padding-top: 0.3rem;
+      padding-left: 0.2rem;
+    }
+    .dec-info {
+      padding-bottom: 0.1rem;
+      .info-item {
+        position: relative;
+        color: #999;
+        font-size: 0.28rem;
+        padding-bottom: 0.25rem;
+        padding-left: 0.2rem;
+        padding-right: 0.3rem;
+        line-height: 0.35rem;
+      }
+    }
+    .dec-copy, .dec-copy1 {
+      position: absolute;
+      // top: 0.95rem;
+      right: 0.2rem;
+      height: .42rem;
+      padding: 0 .1rem;
+      color: #ff484c;
+      font-size: 0.28rem;
+    }
+    .dec-bottom {
+      position: relative;
+      .dec-kefu {
+        color: #4c4c4c;
+        text-align: center;
+        font-size: 0.26rem;
+        height: 0.8rem;
+        line-height: 0.8rem;
+        padding-left: 0.4rem;
+      }
+      .dec-icon {
+        width: 0.4rem;
+        height: 0.38rem;
+        position: absolute;
+        top: 0.18rem;
+        left: 2.8rem;
+      }
+    }
+  }
+  .order-bottom {
+    height: 0.94rem;
+    background: #fff;
+    width: 100%;
+    box-sizing: border-box;
+    padding-top: 0.25rem;
+    padding-right: 0.3rem;
+    .bottom-btn {
+      .btn {
+        color: #999;
+        font-size: 0.26rem;
+        width: 1.4rem;
+        height: 0.4rem;
+        float: right;
+        margin-left: 0.3rem;
+      }
+      .btn1 {
+        color: #ff484c;
+      }
+    }
+  }
+}
+</style>

+ 172 - 0
src/views/user/order/List.vue

@@ -0,0 +1,172 @@
+<template>
+  <div class="family-order__list" >
+    <div class="orders-top">
+      <van-tabs v-model="curActive" swipe-threshold="5" color="#28cd5e" title-inactive-color="#999" title-active-color="#28cd5e" sticky>
+        <van-tab v-for="item in tabList" :key="item.value" :title="item.title">
+          <van-list v-model="loading" :finished="item.finished" :finished-text="orderList[curActive].length ? '已经到底了':''" @load="onLoadList" :offset="offset">
+            <ul class="orders-list">
+              <order-item :orderData="item" v-for="(item, index) in orderList[curActive]" :key="index" @reloadClick="reloadClick" @refresh="refresh" />
+            </ul>
+            <empty-list v-if="!orderList[curActive].length" :imgUrl="imgUrl" desc="暂无订单" />
+          </van-list>
+        </van-tab>
+      </van-tabs>
+    </div>
+    <footer-nav />
+  </div>
+</template>
+<script>
+import Vue from 'vue'
+import { mapGetters } from 'vuex'
+import { Tab, Tabs } from 'vant'
+import OrderItem from './components/Items'
+import EmptyList from '@/components/Empty'
+import FooterNav from '@/components/Footer'
+const LIMIT = 10
+export default {
+  name: 'userOrderList',
+  data () {
+    return {
+      imgUrl: 'http://cow-fomal.oss-cn-shenzhen.aliyuncs.com/j4c6/order_none.png',
+      curActive: 0,
+      status: 0,
+      offset: 5,
+      loading: false,
+      orderList: [[], [], [], [], []],
+      tabList: [{
+        title: '全部',
+        status: 0,
+        page: 0,
+        index: 1,
+        finished: false
+      }, {
+        title: '待付款',
+        status: 1,
+        page: 1,
+        index: 0,
+        finished: false
+      }, {
+        title: '待发货',
+        status: 4,
+        page: 1,
+        index: 0,
+        finished: false
+      }, {
+        title: '待收货',
+        status: 5,
+        page: 1,
+        index: 0,
+        finished: false
+      }]
+    }
+  },
+  computed: {
+    ...mapGetters(['orders', 'ordersLoading'])
+  },
+  methods: {
+    reloadClick (index) {
+      this.curActive = index
+    },
+    refresh () {
+      let active = this.curActive
+      this.tabList[active].finished = false
+      this.tabList[active].page = 1
+      this.orderList[active] = []
+      this.$toast('取消订单成功')
+      this.getOrderList()
+    },
+    onLoadList () {
+      let active = this.curActive
+      if (this.tabList[active] && this.tabList[active].finished) {
+        this.loading = false
+        return
+      }
+      this.loading = true
+      this.tabList[active].page++
+      this.getOrderList()
+    },
+    getOrderList () {
+      let active = this.curActive
+      let tabs = this.tabList[active]
+      this.$store.dispatch('getOrderList', {
+        page: tabs.page,
+        per_page: LIMIT,
+        status: tabs.status
+      })
+    },
+    toList () {
+      let tabs = this.tabList
+      let active = this.curActive
+      this.$router.push({path: '/order', query: {status: tabs[active].value, index: active}})
+    }
+  },
+  created () {
+    Vue.use(Tab).use(Tabs)
+    if (this.$route.query.status) {
+      let query = this.$route.query
+      this.curActive = Number(query.index)
+    }
+  },
+  watch: {
+    'curActive': function (val, oldVal) {
+      let index = this.tabList[val].index
+      if (!index && this.tabList[val].status) {
+        this.$toast.loading('正在加载中...')
+        this.tabList[val].index += 1
+        this.getOrderList()
+      }
+    },
+    'orders.total': function (val) {
+      let active = this.curActive
+      this.tabList[active].total = val
+    },
+    'orders.list': function (val) {
+      if (this.ordersLoading) {
+        return
+      }
+      this.$toast.clear()
+      this.loading = false
+      let list = this.orderList[this.curActive]
+      let len = list.length
+      let active = this.curActive
+      // 容错代码
+      if (!val) {
+        this.tabList[active].finished = true
+        return
+      }
+      if (len > 0) {
+        let arr = list.concat(val || [])
+        this.$set(this.orderList, active, arr)
+      } else {
+        this.$set(this.orderList, active, val)
+      }
+      if (this.orderList[active].length >= this.tabList[active].total) {
+        this.tabList[active].finished = true
+      }
+    }
+  },
+  components: {
+    OrderItem,
+    EmptyList,
+    FooterNav
+  }
+}
+</script>
+<style lang='less' scoped>
+.family-order__list {
+  text-align: left;
+  min-height: 100%;
+  background-color: #f3f3f3;
+  padding-bottom: 1rem;
+  .van-cell {
+    background: #f3f3f3;
+    margin: .2rem 0;
+  }
+  .orders-top {
+    .orders-list {
+      box-sizing: border-box;
+      padding: 0.2rem 0.2rem 0;
+    }
+  }
+}
+</style>

+ 0 - 0
src/views/user/order/components/Items.vue


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor