Browse Source

1.7.4上正式

yuwenjun1997 2 years ago
parent
commit
08dea071bd

+ 3 - 2
.env.development

@@ -7,7 +7,7 @@ LOCALHOSE = 'https://zp-b.caimei365.com'
 
 # 接口api地址
 BASE_URL = 'https://zplma-b.caimei365.com'
-# BASE_URL = 'http://192.168.2.68:8012'
+# BASE_URL = 'http://192.168.2.200:8012'
 # BASE_URL = 'http://192.168.2.67:8012'
 
 # 静态资源文件地址
@@ -18,9 +18,10 @@ CIMEI_LOCAL = 'https://www-b.caimei365.com'
 
 # 项目运行地址
 HOST = '192.168.2.92'
+# HOST = '192.168.1.102'
 
 # 项目端口号
 PORT = '8888'
 
 # HTTPS flag
-HTTPS = true
+HTTPS = false

+ 5 - 1
apis/module/auth.js

@@ -1,6 +1,6 @@
 export default ($axios) => {
   const authApis = {}
-  
+
   // 获取已认证机构列表
   authApis.getAuthClubList = (params = {}) =>
     $axios.get('/wx/auth/club/list', { params })
@@ -68,5 +68,9 @@ export default ($axios) => {
   authApis.fetchDetialBySnCode = (params = {}) =>
     $axios.get('/wx/auth/product/info', { params })
 
+  // 获取机构已认证设备
+  authApis.fetchClubAuthProductList = (params = {}) =>
+    $axios.get('/wx/auth/get/product/list', { params })
+
   return authApis
 }

+ 6 - 2
apis/module/common.js

@@ -34,8 +34,12 @@ export default ($axios) => {
     $axios.post('/wx/data/feedback/submit', data)
 
   // 验证token是否到期
-  commonApis.checkToken = (data = {}) =>
-    $axios.post('/wx/user/login/token/check', data)
+  commonApis.checkToken = (params = {}) =>
+    $axios.get('/wx/user/login/token/check', params)
+
+  // 校验token并返回用户信息
+  commonApis.checkTokenResult = (params) =>
+    $axios.get('/wx/data/token/check', params)
 
   // 获取城市列表(树状)
   commonApis.fetchCityList = (params = {}) =>

BIN
assets/theme-images/common/h5-icon-club-logo-default.png


BIN
assets/theme-images/common/pc-icon-club-logo-default.png


BIN
assets/theme-images/ross/pc-icon-center-item-auth-club.png


BIN
assets/theme-images/ross/pc-icon-center-item-device.png


+ 4 - 0
assets/themes/variables/ross.scss

@@ -27,6 +27,10 @@ $rossTuple: (
   pc-icon-navigation: url(~/assets/theme-images/ross/pc-icon-navigation.png),
   pc-icon-feedback-submit:
     url(~/assets/theme-images/ross/pc-icon-feedback-submit.png),
+  pc-icon-center-item-auth-club:
+    url(~/assets/theme-images/ross/pc-icon-center-item-auth-club.png),
+  pc-icon-center-item-device:
+    url(~/assets/theme-images/ross/pc-icon-center-item-device.png),
   //  h5 端
   h5-banner-home: url(~/assets/theme-images/ross/h5-banner-home.png),
   h5-banner-approve: url(~/assets/theme-images/ross/h5-banner-approve.png),

+ 182 - 3
layouts/app-ross.vue

@@ -20,7 +20,7 @@
               </template>
             </div>
             <div class="user-info">
-              <template v-if="accessToken">
+              <!-- <template v-if="accessToken">
                 <span v-text="userInfo.mobile"></span>
                 <span class="underline logout" @click="logout">退出登录</span>
               </template>
@@ -40,6 +40,21 @@
                     注册
                   </div>
                 </div>
+              </template> -->
+              <template v-if="accessToken">
+                <div class="user-center">
+                  <span class="icon el-icon-user-solid"></span>
+                  <span class="icon el-icon-arrow-down"></span>
+                  <div class="drop-down">
+                    <ul class="nav">
+                      <li @click.stop="onUserCenter">个人中心</li>
+                      <li @click.stop="logout">退出登录</li>
+                    </ul>
+                  </div>
+                </div>
+              </template>
+              <template v-else>
+                <div class="login-btn" @click="onLogin">登录</div>
               </template>
             </div>
             <span class="collapse-icon" @click="drawer = true"></span>
@@ -213,8 +228,16 @@ export default {
 
     // 回到首页
     backHome() {
-      if (this.$route.path === this.routePrefix) return
-      this.$router.replace(this.routePrefix)
+      // if (this.$route.path === this.routePrefix) return
+      // this.$router.replace(this.routePrefix)
+      window.location.href = window.location.origin + this.routePrefix
+    },
+
+    // 个人中心
+    onUserCenter() {
+      // const path = `${this.routePrefix}/center`
+      window.location.href =
+        window.location.origin + `${this.routePrefix}/center`
     },
 
     // 响应页面宽度变化
@@ -234,6 +257,18 @@ export default {
 </script>
 
 <style scoped lang="scss">
+@keyframes slide-down {
+  0% {
+    top: 48px;
+    z-index: 9;
+    opacity: 0;
+  }
+  100% {
+    top: 32px;
+    opacity: 1;
+  }
+}
+
 // PC端
 @media screen and (min-width: 768px) {
   .layout {
@@ -335,6 +370,80 @@ export default {
         font-size: 16px;
         margin-left: 48px;
 
+        .login-btn {
+          width: 80px;
+          height: 34px;
+          color: #fff;
+          background: rgba(255, 255, 255, 0.39);
+          font-size: 14px;
+          text-align: center;
+          line-height: 34px;
+          cursor: pointer;
+        }
+        .user-center {
+          position: relative;
+
+          .icon {
+            width: 32px;
+            height: 32px;
+            text-align: center;
+            line-height: 32px;
+
+            &.el-icon-user-solid {
+              font-size: 24px;
+              cursor: pointer;
+            }
+
+            &.el-icon-arrow-down {
+              font-size: 22px;
+              transition: all 0.2s;
+            }
+          }
+
+          &:hover {
+            .drop-down {
+              display: block;
+              animation: slide-down 0.4s linear forwards;
+            }
+
+            .el-icon-arrow-down {
+              transform: rotateZ(180deg);
+            }
+          }
+
+          .drop-down {
+            display: none;
+            opacity: 0;
+            // z-index: -1;
+            right: 0;
+
+            position: absolute;
+            background: transparent;
+            box-sizing: border-box;
+            padding-top: 24px;
+
+            .nav {
+              width: 118px;
+              padding: 8px 0;
+              background: #fff;
+              box-shadow: 0px 6px 16px rgba(40, 40, 40, 0.1);
+              border-radius: 4px;
+              li {
+                font-size: 14px;
+                color: #282828;
+                text-align: center;
+                line-height: 40px;
+                transition: all 0.4s;
+                cursor: pointer;
+
+                &:hover {
+                  color: #f3920d;
+                }
+              }
+            }
+          }
+        }
+
         .login,
         .register,
         .logout {
@@ -408,6 +517,76 @@ export default {
         .logout {
           margin: 0 1.6vw;
         }
+
+        .login-btn {
+          width: 13.4vw;
+          height: 6.4vw;
+          color: #fff;
+          background: rgba(255, 255, 255, 0.39);
+          font-size: 3.4vw;
+          text-align: center;
+          line-height: 6.4vw;
+          margin-right: 2.4vw;
+        }
+
+        .user-center {
+          position: relative;
+
+          .icon {
+            width: 5.6vw;
+            height: 5.6vw;
+            text-align: center;
+            line-height: 5.6vw;
+            margin-right: 4vw;
+
+            &.el-icon-user-solid {
+              font-size: 24px;
+              cursor: pointer;
+            }
+
+            &.el-icon-arrow-down {
+              display: none;
+            }
+          }
+
+          &:hover {
+            .drop-down {
+              display: block;
+              animation: slide-down 0.4s linear forwards;
+            }
+
+            .el-icon-arrow-down {
+              transform: rotateZ(180deg);
+            }
+          }
+
+          .drop-down {
+            display: none;
+            opacity: 0;
+            // z-index: -1;
+            right: 0;
+
+            position: absolute;
+            background: transparent;
+            box-sizing: border-box;
+            padding-top: 1.2vw;
+
+            .nav {
+              width: 26vw;
+              padding: 1vw 0;
+              background: #fff;
+              box-shadow: 0px 0.6vw 20vw rgba(40, 40, 40, 0.1);
+              border-radius: 0.4vw;
+              li {
+                font-size: 3.4vw;
+                color: #282828;
+                text-align: center;
+                line-height: 8.6vw;
+                transition: all 0.4s;
+              }
+            }
+          }
+        }
       }
 
       .collapse-icon {

+ 15 - 13
middleware/intercept.js

@@ -67,20 +67,22 @@ async function initTemplateEntry(context) {
       return
     }
 
-    // 默认模板
-    if (prefix === 'app') {
-      if (isFixedTemplate(authUserId)) {
-        showError(error, { statusCode: 500, message: '模板与供应商不匹配' })
-        return
+    if (dev !== 'development') {
+      // 默认模板
+      if (prefix === 'app') {
+        if (isFixedTemplate(authUserId)) {
+          showError(error, { statusCode: 500, message: '模板与供应商不匹配' })
+          return
+        }
       }
-    }
 
-    // 非默认模板
-    if (['ross', 'ldm'].includes(prefix)) {
-      // 供应商为指定模板
-      if (!isMatchTemplate(authUserId, prefix)) {
-        showError(error, { statusCode: 500, message: '模板与供应商不匹配' })
-        return
+      // 非默认模板
+      if (['ross', 'ldm'].includes(prefix)) {
+        // 供应商为指定模板
+        if (!isMatchTemplate(authUserId, prefix)) {
+          showError(error, { statusCode: 500, message: '模板与供应商不匹配' })
+          return
+        }
       }
     }
 
@@ -99,8 +101,8 @@ async function initTemplateEntry(context) {
 
 export default function (context) {
   const name = context.route.name
-  // console.log(name)
   if (isPublicEntry(name)) return // initPublicEntry(context)
   if (isTemplateEntry(name)) return initTemplateEntry(context)
+  console.log(123)
   context.error({ statusCode: 404, message: '页面不存在' })
 }

File diff suppressed because it is too large
+ 819 - 10
package-lock.json


+ 1 - 0
pages/_template/_.vue

@@ -0,0 +1 @@
+<template></template>

+ 468 - 0
pages/_template/ross/center/club-detail.vue

@@ -0,0 +1,468 @@
+<template>
+  <div class="club-detail page">
+    <div class="page-top"></div>
+    <div class="page-content">
+      <div class="title">机构认证信息</div>
+      <template v-if="!isAuth">
+        <div class="tip">抱歉,您暂未认证机构</div>
+        <div class="btn" @click="toAuth">去认证</div>
+      </template>
+      <template v-else>
+        <div class="row">
+          <div class="col label">机构名称:</div>
+          <div class="col content">{{ clubInfo.authParty }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">联系电话:</div>
+          <div class="col content">{{ clubInfo.mobile }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">运营联系人:</div>
+          <div class="col content">{{ clubInfo.linkMan }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">运营联系人手机号:</div>
+          <div class="col content">{{ clubInfo.linkMobile }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">所在地区:</div>
+          <div class="col content">{{ clubInfo.area }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">详细地址:</div>
+          <div class="col content">{{ clubInfo.address }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">所在位置:</div>
+          <div class="col content">
+            <div class="postion-btn" @click="initMap">查看定位</div>
+          </div>
+        </div>
+        <div class="row block">
+          <div class="col label">logo:</div>
+          <div class="col content">
+            <el-image :src="clubInfo.logo" v-if="clubInfo.logo"></el-image>
+          </div>
+        </div>
+        <div class="row block">
+          <div class="col label">门头照:</div>
+          <div class="col content">
+            <template v-for="(image, index) in clubInfo.bannerList">
+              <el-image :src="image" :key="index"></el-image>
+            </template>
+          </div>
+        </div>
+        <div class="row">
+          <div class="col label">机构类型:</div>
+          <div class="col content">{{ firstClubTypeName }}</div>
+        </div>
+        <div class="row">
+          <div class="col label">医美类型:</div>
+          <div class="col content">{{ secondClubTypeName }}</div>
+        </div>
+        <div class="row block">
+          <div class="col label">医疗许可证:</div>
+          <div class="col content">
+            <el-image
+              :src="clubInfo.medicalLicenseImage"
+              v-if="clubInfo.medicalLicenseImage"
+            ></el-image>
+          </div>
+        </div>
+        <div class="row">
+          <div class="col label">员工人数:</div>
+          <div class="col content">{{ clubInfo.empNum }}</div>
+        </div>
+      </template>
+    </div>
+
+    <div class="position-select" v-if="mapVisiable">
+      <div class="position-select-container">
+        <SimpleAMap ref="aMap" :lnglat="lnglat" />
+        <div class="position-select-footer">
+          <div class="lnglat">当前经纬度:{{ clubInfo.lngAndLat }}</div>
+          <div
+            class="position-confirm postion-control"
+            @click="mapVisiable = false"
+          >
+            确定
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  layout: 'app-ross',
+  data() {
+    return {
+      mapVisiable: false,
+      clubInfo: {},
+    }
+  },
+  computed: {
+    ...mapGetters(['userInfo', 'routePrefix']),
+    isAuth() {
+      return this.clubInfo.auditStatus === 1
+    },
+    firstClubTypeName() {
+      if (!this.clubInfo.firstClubType) return '其他'
+      return ['医美', '生美', '项目公司', '个人', '其他'][
+        this.clubInfo.firstClubType - 1
+      ]
+    },
+    secondClubTypeName() {
+      if (!this.clubInfo.secondClubType) return '其他'
+      return ['诊所', '门诊', '医院', '其他', '美容院', '养生馆', '其他'][
+        this.clubInfo.secondClubType - 1
+      ]
+    },
+    lnglat() {
+      return this.clubInfo.lngAndLat ? this.clubInfo.lngAndLat.split(',') : null
+    },
+  },
+  created() {
+    this.fetchClubDetail()
+  },
+  methods: {
+    // 获取机构详情
+    async fetchClubDetail() {
+      try {
+        const authId = this.userInfo.authId
+        if (!authId) return
+        const res = await this.$http.api.fetchClubAuthInfoData({ authId })
+        this.clubInfo = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 去认证
+    toAuth() {
+      this.$router.push(`${this.routePrefix}/form/club-register`)
+    },
+    // 地图定位
+    initMap() {
+      this.mapVisiable = true
+      this.$nextTick(() => {
+        this.$refs.aMap.init()
+      })
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page {
+    display: flex;
+    justify-content: center;
+  }
+
+  .position-select {
+    width: 100vw;
+    height: 100vh;
+    background: rgba(0, 0, 0, 0.39);
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 999;
+
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    .position-select-container {
+      background: #fff;
+      width: 60%;
+      box-sizing: border-box;
+      padding: 24px;
+
+      .position-select-footer {
+        position: relative;
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+        padding-top: 24px;
+
+        .lnglat {
+          position: absolute;
+          font-size: 14px;
+          color: #666;
+
+          left: 0;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+      }
+
+      .postion-control {
+        width: 120px;
+        height: 40px;
+        font-size: 14px;
+        border-radius: 4px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+        margin-left: 16px;
+
+        &.position-confirm {
+          background: #f56c6c;
+          color: #fff;
+        }
+
+        &.position-cancel {
+          background: #b1b1b1;
+          color: #fff;
+        }
+      }
+    }
+  }
+
+  .page-content {
+    max-width: 760px;
+    padding-bottom: 167px;
+    .title {
+      font-size: 24px;
+      color: #282828;
+      font-weight: bold;
+      text-align: center;
+      padding: 60px 0;
+    }
+
+    .tip {
+      font-size: 16px;
+      color: #b2b2b2;
+      margin-top: 200px;
+      margin-bottom: 24px;
+      text-align: center;
+    }
+
+    .btn {
+      width: 98px;
+      height: 36px;
+      background: #f3920d;
+      border-radius: 4px;
+      text-align: center;
+      line-height: 36px;
+      color: #fff;
+      font-size: 16px;
+      margin: 0 auto;
+      cursor: pointer;
+    }
+
+    .row {
+      display: flex;
+      align-items: flex-start;
+      margin: 32px 0;
+
+      .col {
+        font-size: 16px;
+      }
+      .label {
+        color: #666666;
+        min-width: 100px;
+      }
+      .content {
+        color: #282828;
+
+        .el-image {
+          width: 106px;
+          height: 106px;
+          border: 1px solid #dcdcdc;
+          margin-right: 4px;
+
+          &:last-child {
+            margin-right: 0;
+          }
+        }
+
+        .postion-btn {
+          height: 28px;
+          line-height: 28px;
+          font-size: 14px;
+          color: #fff;
+          background: #1890ff;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          cursor: pointer;
+          border-radius: 4px;
+          padding: 0 8px;
+
+          &::before {
+            content: '';
+            display: inline-block;
+            width: 16px;
+            height: 16px;
+            background: url(~assets/theme-images/common/icon-position.png)
+              no-repeat center;
+            background-size: 16px 16px;
+          }
+        }
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page {
+    display: flex;
+    justify-content: center;
+  }
+
+  .position-select {
+    width: 100vw;
+    height: 100vh;
+    background: rgba(0, 0, 0, 0.39);
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 999;
+
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    .position-select-container {
+      background: #fff;
+      width: 80%;
+      box-sizing: border-box;
+      padding: 3.2vw;
+
+      .position-select-footer {
+        padding-top: 10vw;
+        position: relative;
+        display: flex;
+        justify-content: flex-end;
+        align-items: center;
+
+        .lnglat {
+          position: absolute;
+          font-size: 3.2vw;
+          color: #666;
+
+          left: 0;
+          top: 5vw;
+          transform: translateY(-50%);
+        }
+      }
+
+      .postion-control {
+        width: 16vw;
+        height: 7vw;
+        font-size: 3.4vw;
+        border-radius: 0.4vw;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        cursor: pointer;
+        margin-left: 3.6vw;
+
+        &.position-confirm {
+          background: #f56c6c;
+          color: #fff;
+        }
+
+        &.position-cancel {
+          background: #b1b1b1;
+          color: #fff;
+        }
+      }
+    }
+  }
+
+  .page-content {
+    padding: 0 4vw;
+    .title {
+      font-size: 4.2vw;
+      color: #282828;
+      font-weight: bold;
+      text-align: center;
+      padding: 8vw 0;
+    }
+
+    .tip {
+      font-size: 3vw;
+      color: #b2b2b2;
+      margin-top: 60vw;
+      margin-bottom: 4.8vw;
+      text-align: center;
+    }
+
+    .btn {
+      width: 36vw;
+      height: 8.8vw;
+      background: #f3920d;
+      border-radius: 0.4vw;
+      text-align: center;
+      line-height: 8.8vw;
+      color: #fff;
+      font-size: 3.4vw;
+      margin: 0 auto;
+    }
+
+    .row {
+      display: flex;
+      align-items: flex-start;
+      margin: 5.6vw 0;
+
+      &.block {
+        display: block;
+        .label {
+          margin-bottom: 2.4vw;
+        }
+      }
+
+      .col {
+        font-size: 3.4vw;
+      }
+      .label {
+        color: #666666;
+        min-width: 20.4vw;
+      }
+      .content {
+        color: #282828;
+
+        .el-image {
+          width: 26vw;
+          height: 26vw;
+          border: 0.1vw solid #dcdcdc;
+          margin-right: 0.6vw;
+
+          &:last-child {
+            margin-right: 0;
+          }
+        }
+
+        .postion-btn {
+          height: 6.8vw;
+          line-height: 6.8vw;
+          font-size: 3.2vw;
+          color: #fff;
+          background: #1890ff;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          cursor: pointer;
+          border-radius: 0.4vw;
+          padding: 0 1.2vw;
+
+          &::before {
+            content: '';
+            display: inline-block;
+            width: 3.58vw;
+            height: 3.58vw;
+            background: url(~assets/theme-images/common/icon-position.png)
+              no-repeat center;
+            background-size: 3.58vw;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 250 - 0
pages/_template/ross/center/device/detail.vue

@@ -0,0 +1,250 @@
+<template>
+  <div class="club-detail page">
+    <div class="page-top"></div>
+    <div class="page-content">
+      <div class="title">设备认证信息</div>
+      <div class="row">
+        <div class="col label">设备名称:</div>
+        <div class="col content">{{ productInfo.productName }}</div>
+      </div>
+      <div class="row block">
+        <div class="col label">设备图片:</div>
+        <div class="col content">
+          <el-image
+            v-if="productInfo.productImage"
+            :src="productInfo.productImage"
+            :preview-src-list="[productInfo.productImage]"
+          ></el-image>
+          <span v-else>暂无图片</span>
+        </div>
+      </div>
+      <div class="row">
+        <div class="col label">所属品牌:</div>
+        <div class="col content">{{ productInfo.brandName }}</div>
+      </div>
+      <div class="row">
+        <div class="col label">购买渠道:</div>
+        <div class="col content">{{ productInfo.purchaseWay || '暂无' }}</div>
+      </div>
+      <div class="row block">
+        <div class="col label">发票:</div>
+        <div class="col content">
+          <el-image
+            v-if="productInfo.invoiceImage"
+            :src="productInfo.invoiceImage"
+            :preview-src-list="[productInfo.invoiceImage]"
+          ></el-image>
+          <span v-else>暂无图片</span>
+        </div>
+      </div>
+      <div class="row">
+        <div class="col label">设备SN码:</div>
+        <div class="col content">{{ productInfo.snCode }}</div>
+      </div>
+      <div class="row">
+        <div class="col label">设备参数:</div>
+        <div class="col content params-list">
+          <div
+            class="param"
+            v-for="param in productInfo.paramList"
+            :key="param.productName"
+          >
+            <div class="param-name">{{ param.paramName }}</div>
+            <div class="param-content">{{ param.paramContent }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  layout: 'app-ross',
+  data() {
+    return {
+      productInfo: {},
+    }
+  },
+  created() {
+    this.initData()
+  },
+  methods: {
+    initData() {
+      this.productId = this.$route.query.id
+      this.relationId = this.$route.query.relationId
+      this.getProductDetails()
+    },
+    // 获取认证机构信息
+    async getProductDetails() {
+      try {
+        const res = await this.$http.api.getProductDetails({
+          productId: this.productId,
+          relationId: this.relationId,
+        })
+        this.productInfo = { ...this.productInfo, ...res.data }
+        console.log('res', this.productInfo)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page {
+    display: flex;
+    justify-content: center;
+  }
+
+  .page-content {
+    max-width: 760px;
+    padding-bottom: 167px;
+    .title {
+      font-size: 24px;
+      color: #282828;
+      font-weight: bold;
+      text-align: center;
+      padding: 60px 0;
+    }
+
+    .row {
+      display: flex;
+      align-items: flex-start;
+      margin: 32px 0;
+
+      .col {
+        font-size: 16px;
+      }
+      .label {
+        color: #666666;
+        min-width: 100px;
+      }
+      .content {
+        color: #282828;
+
+        .el-image {
+          width: 106px;
+          height: 106px;
+          border: 1px solid #dcdcdc;
+          margin-right: 4px;
+
+          &:last-child {
+            margin-right: 0;
+          }
+        }
+
+        &.params-list {
+          width: 100%;
+          margin-top: -10px;
+          .param {
+            display: table-row;
+            width: 100%;
+          }
+
+          .param-name,
+          .param-content {
+            display: table-cell;
+            font-size: 16px;
+            padding: 12px 0;
+
+            &:nth-child(2n-1) {
+              color: #999999;
+              white-space: nowrap;
+            }
+
+            &:nth-child(2n) {
+              color: #282828;
+              padding-left: 12px;
+            }
+
+            &:nth-child(3) {
+              padding-left: 100px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-content {
+    padding: 0 7.2vw;
+    .title {
+      font-size: 4.6vw;
+      color: #282828;
+      font-weight: bold;
+      text-align: center;
+      padding: 8vw 0;
+    }
+
+    .row {
+      display: flex;
+      align-items: flex-start;
+      margin-bottom: 5.6vw;
+
+      &.block {
+        display: block;
+        .label {
+          margin-bottom: 2.4vw;
+        }
+      }
+
+      .col {
+        font-size: 3.4vw;
+      }
+      .label {
+        color: #666666;
+        min-width: 20.4vw;
+      }
+      .content {
+        color: #282828;
+
+        .el-image {
+          width: 26vw;
+          height: 26vw;
+          border: 0.1vw solid #dcdcdc;
+          margin-right: 0.6vw;
+
+          &:last-child {
+            margin-right: 0;
+          }
+        }
+
+        &.params-list {
+          width: 100%;
+          margin-top: -1vw;
+          .param {
+            display: table-row;
+            width: 100%;
+          }
+
+          .param-name,
+          .param-content {
+            display: table-cell;
+            font-size: 3.4vw;
+            padding: 1.2vw 0;
+
+            &:nth-child(2n-1) {
+              color: #999999;
+              white-space: nowrap;
+            }
+
+            &:nth-child(2n) {
+              color: #282828;
+              padding-left: 1.2vw;
+            }
+
+            &:nth-child(3) {
+              padding-left: 100px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 268 - 0
pages/_template/ross/center/device/index.vue

@@ -0,0 +1,268 @@
+<template>
+  <div class="page">
+    <div class="page-content">
+      <div class="title">设备认证信息</div>
+      <template v-if="!isAuth">
+        <div class="tip">抱歉,您暂未认证设备</div>
+        <div class="btn" @click="toAuth">去认证</div>
+      </template>
+      <div class="device-list" v-else>
+        <div
+          class="device"
+          @click="toDetail(product)"
+          v-for="product in list"
+          :key="product.productId"
+        >
+          <div class="cover">
+            <img :src="product.image" :alt="product.productName" />
+          </div>
+          <div class="content">
+            <div class="name">{{ product.productName }}</div>
+            <div class="sncode">SN码:{{ product.snCode | formatSnCode }}</div>
+          </div>
+        </div>
+      </div>
+      <div class="to-auth-btn" v-if="isAuth && !isPc" @click="toAuth">去认证</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  layout: 'app-ross',
+  data() {
+    return {
+      list: [],
+      clubInfo: {},
+    }
+  },
+  filters: {
+    formatSnCode(code) {
+      if (!code) return ''
+      return code.replace(/^(\w{2})\w+(\w{4})$/, '$1******$2')
+    },
+  },
+  computed: {
+    ...mapGetters(['routePrefix', 'userInfo', 'authUserId', 'isPc']),
+    isAuth() {
+      return this.clubInfo.auditStatus === 1 && this.list.length > 0
+    },
+  },
+  created() {
+    this.fetchProductList()
+    this.fetchAuthDetail()
+  },
+  methods: {
+    // 产看详情
+    toDetail(row) {
+      const path = `${this.routePrefix}/center/device/detail?productId=${row.productId}&relationId=${row.relationId}`
+      this.$router.push(path)
+    },
+    // 获取机构信息
+    async fetchAuthDetail() {
+      try {
+        if (this.userInfo && !this.userInfo.authId) return
+        const res = await this.$http.api.fetchClubAuthInfoData({
+          authId: this.userInfo.authId,
+        })
+        this.clubInfo = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 获取已认证设备列表
+    async fetchProductList() {
+      try {
+        const authId = this.userInfo.authId
+        if (!authId) return
+        const res = await this.$http.api.fetchClubAuthProductList({
+          authId,
+          authUserId: this.authUserId,
+        })
+        if (res.data) {
+          this.list = res.data
+        }
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 去认证
+    toAuth() {
+      this.$router.push(`${this.routePrefix}/form/club-register`)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@mixin ellipsis($line: 1) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+
+@media screen and (min-width: 768px) {
+  .page-content {
+    width: 1000px;
+    margin: 0 auto;
+    .title {
+      font-size: 24px;
+      text-align: center;
+      font-weight: bold;
+      padding: 60px 0;
+    }
+
+    .tip {
+      font-size: 16px;
+      color: #b2b2b2;
+      margin-top: 200px;
+      margin-bottom: 24px;
+      text-align: center;
+    }
+
+    .btn {
+      width: 98px;
+      height: 36px;
+      background: #f3920d;
+      border-radius: 4px;
+      text-align: center;
+      line-height: 36px;
+      color: #fff;
+      font-size: 16px;
+      margin: 0 auto;
+      cursor: pointer;
+    }
+
+    .device-list {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      flex-wrap: wrap;
+
+      .device {
+        width: 490px;
+        height: 136px;
+        background: #f3f5f6;
+        border-radius: 8px;
+        display: flex;
+        align-items: center;
+        box-sizing: border-box;
+        padding: 16px;
+        margin-bottom: 20px;
+        cursor: pointer;
+
+        .cover {
+          img {
+            display: block;
+            width: 106px;
+            height: 106px;
+          }
+        }
+        .content {
+          width: 320px;
+          margin-left: 16px;
+          .name {
+            font-size: 18px;
+            color: #282828;
+            font-weight: bold;
+            margin-bottom: 24px;
+            @include ellipsis(1);
+          }
+          .sncode {
+            font-size: 16px;
+            color: #666666;
+          }
+        }
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-content {
+    .title {
+      font-size: 4.2vw;
+      text-align: center;
+      font-weight: bold;
+      padding: 8vw 0;
+    }
+
+    .to-auth-btn {
+      width: 85.2vw;
+      height: 12vw;
+      background: #f3920d;
+      border-radius: 0.2vw;
+      text-align: center;
+      line-height: 12vw;
+      color: #ffffff;
+      font-size: 3.6vw;
+      position: fixed;
+      bottom: 24vw;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+
+    .tip {
+      font-size: 3vw;
+      color: #b2b2b2;
+      margin-top: 60vw;
+      margin-bottom: 4.8vw;
+      text-align: center;
+    }
+
+    .btn {
+      width: 36vw;
+      height: 8.8vw;
+      background: #f3920d;
+      border-radius: 0.4vw;
+      text-align: center;
+      line-height: 8.8vw;
+      color: #fff;
+      font-size: 3.4vw;
+      margin: 0 auto;
+    }
+
+    .device-list {
+      padding: 0 3.2vw;
+      padding-bottom: 26vw;
+      .device {
+        height: 26vw;
+        background: #f3f5f6;
+        border-radius: 0.8vw;
+        display: flex;
+        align-items: center;
+        box-sizing: border-box;
+        padding: 3.2vw;
+        margin-bottom: 3.2vw;
+        cursor: pointer;
+
+        .cover {
+          img {
+            display: block;
+            width: 19.6vw;
+            height: 19.6vw;
+          }
+        }
+        .content {
+          width: 59vw;
+          margin-left: 3.2vw;
+          .name {
+            font-size: 3.6vw;
+            color: #282828;
+            font-weight: bold;
+            margin-bottom: 3.2vw;
+            @include ellipsis(1);
+          }
+          .sncode {
+            font-size: 3.6vw;
+            color: #666666;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 479 - 0
pages/_template/ross/center/index.vue

@@ -0,0 +1,479 @@
+<template>
+  <div class="page">
+    <div class="page-top flex flex-col justify-center items-center">
+      <div class="club-logo">
+        <img :src="clubInfo.logo" alt="" v-if="isAuth" />
+        <img
+          src="~/assets/theme-images/common/pc-icon-club-logo-default.png"
+          v-else-if="isPc"
+        />
+        <img
+          src="~/assets/theme-images/common/h5-icon-club-logo-default.png"
+          v-else
+        />
+      </div>
+      <div class="club-mobile">{{ userInfo.mobile }}</div>
+      <div class="club-name" v-if="isAuth">机构:{{ clubInfo.authParty }}</div>
+    </div>
+    <div class="page-content">
+      <div class="section-title">我的认证</div>
+      <div class="section-content">
+        <div class="item club" @click="onToClubDetail">
+          <div class="auth-icon" :class="isAuth ? 'auth' : 'un-auth'">
+            {{ isAuth ? '已认证' : '未认证' }}
+          </div>
+          <div class="tip">机构认证</div>
+          <div class="btn">
+            点击查看<span class="icon el-icon-arrow-right"></span>
+          </div>
+        </div>
+        <div class="item device" @click="onToDeviceList">
+          <div class="auth-icon" :class="isAuthDevice ? 'auth' : 'un-auth'">
+            {{ isAuthDevice ? '已认证' : '未认证' }}
+          </div>
+          <div class="tip">设备认证</div>
+          <div class="btn">
+            点击查看<span class="icon el-icon-arrow-right"></span>
+          </div>
+        </div>
+      </div>
+      <div class="line"></div>
+      <template v-if="isPc">
+        <div class="section-title">账户设置</div>
+        <div class="section-content">
+          <div class="reset-pwd" @click="onResetPassword">修改密码</div>
+        </div>
+      </template>
+      <template v-else>
+        <div class="menu-list">
+          <div class="item" @click="toAccountSubNav">
+            <span>账户设置</span>
+            <span class="el-icon-arrow-right"></span>
+          </div>
+        </div>
+      </template>
+      <div v-if="!isPc" class="logout" @click="logout">退出登录</div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  layout: 'app-ross',
+  data() {
+    return {
+      clubInfo: {},
+      list: [],
+    }
+  },
+  computed: {
+    ...mapGetters(['routePrefix', 'isPc', 'userInfo', 'authUserId']),
+    isAuth() {
+      return this.clubInfo.auditStatus === 1
+    },
+    isAuthDevice() {
+      return this.isAuth && this.list.length > 0
+    },
+  },
+  created() {
+    this.initUserInfo()
+  },
+  methods: {
+    // 初始化用户信息
+    async initUserInfo() {
+      try {
+        const res = await this.$http.api.checkTokenResult()
+        this.$store.dispatch('user/login', res.data)
+        this.$setStorage(this.routePrefix, 'userInfo', res.data)
+        this.fetchAuthDetail()
+        this.fetchProductList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 获取机构信息
+    async fetchAuthDetail() {
+      try {
+        const authId = this.userInfo.authId
+        if (!authId) return
+        const res = await this.$http.api.fetchClubAuthInfoData({
+          authId,
+        })
+        this.clubInfo = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 获取已认证设备列表
+    async fetchProductList() {
+      try {
+        const authId = this.userInfo.authId
+        if (!authId) return
+        const res = await this.$http.api.fetchClubAuthProductList({
+          authId,
+          authUserId: this.authUserId,
+        })
+        if (res.data) {
+          this.list = res.data
+        }
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 去账户设置菜单列表
+    toAccountSubNav() {
+      this.$router.push(`${this.routePrefix}/center/subnav/account`)
+    },
+    // 机构详情
+    onToClubDetail() {
+      this.$router.push(`${this.routePrefix}/center/club-detail`)
+    },
+    // 设备认证
+    onToDeviceList() {
+      this.$router.push(`${this.routePrefix}/center/device`)
+    },
+    // 修改密码
+    onResetPassword() {
+      this.$router.push(`${this.routePrefix}/center/settings/password`)
+    },
+    // 退出登录
+    logout() {
+      this.$store.dispatch('user/logout')
+      this.$removeStorage(this.routePrefix, 'userInfo')
+      this.backHome()
+    },
+    // 回到首页
+    backHome() {
+      if (this.$route.path === this.routePrefix) return
+      this.$router.replace(this.routePrefix)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page {
+    position: relative;
+    min-height: calc(100vh - 80px - 80px);
+    background-color: #fff;
+    overflow: hidden;
+  }
+  .page-top {
+    height: 360px;
+    @include themify($themes) {
+      background-image: themed('pc-banner-doc');
+    }
+    background-size: cover;
+    background-position: center;
+
+    .club-logo {
+      width: 90px;
+      height: 90px;
+
+      img {
+        display: block;
+        width: 100%;
+        height: 100%;
+        border-radius: 50%;
+      }
+    }
+
+    .club-mobile {
+      margin: 8px 0;
+    }
+
+    .club-mobile,
+    .club-name {
+      font-size: 18px;
+      color: #fff;
+    }
+  }
+
+  .page-content {
+    width: 704px;
+    margin: 8px auto 60px;
+
+    .section-title {
+      font-size: 24px;
+      color: #666666;
+      padding: 16px 0;
+    }
+    .section-content {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      .item {
+        position: relative;
+        width: 340px;
+        height: 230px;
+        background: #eee;
+        box-sizing: border-box;
+        padding-left: 24px;
+        cursor: pointer;
+
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+        justify-content: center;
+        background-size: 340px 230px;
+        background-position: center center;
+        background-repeat: no-repeat;
+
+        &.club {
+          @include themify($themes) {
+            background-image: themed('pc-icon-center-item-auth-club');
+          }
+          .icon {
+            color: #f3920d;
+          }
+        }
+
+        &.device {
+          @include themify($themes) {
+            background-image: themed('pc-icon-center-item-device');
+          }
+
+          .icon {
+            color: #0a6eb1;
+          }
+        }
+
+        .auth-icon {
+          position: absolute;
+          right: 16px;
+          top: 16px;
+          width: 72px;
+          height: 32px;
+          line-height: 32px;
+          text-align: center;
+          font-size: 16px;
+          color: #fff;
+          border-radius: 4px;
+
+          &.auth {
+            background: #1890ff;
+          }
+
+          &.un-auth {
+            background: #f94b4b;
+          }
+        }
+        .tip {
+          font-size: 24px;
+          color: #fff;
+          margin-bottom: 8px;
+        }
+        .btn {
+          font-size: 16px;
+          color: #fff;
+
+          .icon {
+            display: inline-block;
+            width: 18px;
+            height: 18px;
+            border-radius: 50%;
+            background: #fff;
+            // background: #0A6EB1;
+            font-size: 12px;
+            text-align: center;
+            line-height: 18px;
+            margin-left: 9px;
+            font-weight: bold;
+            vertical-align: 1px;
+          }
+        }
+      }
+
+      .reset-pwd {
+        width: 98px;
+        height: 36px;
+        background: #f3920d;
+        opacity: 1;
+        border-radius: 4px;
+        font-size: 16px;
+        font-weight: 400;
+        line-height: 36px;
+        color: #ffffff;
+        text-align: center;
+        cursor: pointer;
+      }
+    }
+
+    .line {
+      height: 1px;
+      background: #c2c2c2;
+      margin: 32px 0;
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-top {
+    height: 36vw;
+    @include themify($themes) {
+      background-image: themed('pc-banner-doc');
+    }
+    background-size: cover;
+    background-position: center;
+
+    .club-logo {
+      width: 12.8vw;
+      height: 12.8vw;
+
+      img {
+        display: block;
+        width: 100%;
+        height: 100%;
+        border-radius: 50%;
+      }
+    }
+
+    .club-mobile {
+      margin: 0.8vw 0;
+    }
+
+    .club-mobile,
+    .club-name {
+      font-size: 3.4vw;
+      color: #fff;
+    }
+  }
+
+  .page-content {
+    padding-bottom: 26vw;
+    .section-title {
+      font-size: 4.2vw;
+      color: #666666;
+      padding: 6.4vw 0 3.2vw;
+      padding-left: 4vw;
+    }
+
+    .logout {
+      width: 85.2vw;
+      height: 12vw;
+      background: #f3920d;
+      border-radius: 0.2vw;
+      text-align: center;
+      line-height: 12vw;
+      color: #ffffff;
+      font-size: 3.6vw;
+      position: fixed;
+      bottom: 24vw;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+
+    .section-content {
+      display: flex;
+      justify-content: space-between;
+      align-items: flex-start;
+      padding: 0 4vw;
+      .item {
+        position: relative;
+        width: 44.4vw;
+        height: 30vw;
+        background: #eee;
+        box-sizing: border-box;
+        padding-left: 3.6vw;
+        cursor: pointer;
+
+        display: flex;
+        flex-direction: column;
+        align-items: flex-start;
+        justify-content: center;
+        background-size: 44.4vw 30vw;
+        background-position: center center;
+        background-repeat: no-repeat;
+
+        &.club {
+          @include themify($themes) {
+            background-image: themed('pc-icon-center-item-auth-club');
+          }
+          .icon {
+            color: #f3920d;
+          }
+        }
+
+        &.device {
+          @include themify($themes) {
+            background-image: themed('pc-icon-center-item-device');
+          }
+
+          .icon {
+            color: #0a6eb1;
+          }
+        }
+
+        .auth-icon {
+          position: absolute;
+          right: 1.6vw;
+          top: 1.6vw;
+          width: 12.8vw;
+          height: 5.6vw;
+          line-height: 5.6vw;
+          text-align: center;
+          font-size: 3vw;
+          color: #fff;
+          border-radius: 0.4vw;
+
+          &.auth {
+            background: #1890ff;
+          }
+
+          &.un-auth {
+            background: #f94b4b;
+          }
+        }
+        .tip {
+          font-size: 4vw;
+          color: #fff;
+          margin-bottom: 1.2vw;
+        }
+        .btn {
+          font-size: 3vw;
+          color: #fff;
+
+          .icon {
+            display: inline-block;
+            width: 3.4vw;
+            height: 3.4vw;
+            border-radius: 50%;
+            background: #fff;
+            font-size: 2.8vw;
+            text-align: center;
+            line-height: 3.4vw;
+            margin-left: 0.8vw;
+            font-weight: bold;
+            vertical-align: 1px;
+          }
+        }
+      }
+    }
+
+    .menu-list {
+      padding: 0 4vw;
+      padding-top: 2.4vw;
+      .item {
+        width: 100%;
+        padding: 3vw 0;
+        display: flex;
+        justify-content: space-between;
+        font-size: 3.4vw;
+        border-bottom: 0.1vw solid #c2c2c2;
+
+        .el-icon-arrow-right {
+          font-size: 4vw;
+        }
+      }
+    }
+
+    .line {
+      height: 1.6vw;
+      background: #f7f7f7;
+      margin-top: 6.4vw;
+    }
+  }
+}
+</style>

+ 295 - 0
pages/_template/ross/center/settings/password.vue

@@ -0,0 +1,295 @@
+<template>
+  <div class="page">
+    <div class="page-content">
+      <el-form :model="formData" :rules="rules" label-position="top" ref="form">
+        <el-form-item label="手机号" prop="mobile">
+          <el-input
+            placeholder="请输入手机号"
+            maxlength="11"
+            v-model="formData.mobile"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="验证码" prop="verifyCode">
+          <div class="verify-code">
+            <el-input
+              placeholder="请输入验证码"
+              maxlength="6"
+              v-model="formData.verifyCode"
+            ></el-input>
+            <div class="send" @click="onSend">{{ sendCodeBtnText }}</div>
+          </div>
+        </el-form-item>
+        <el-form-item label="新密码" prop="password">
+          <el-input
+            type="password"
+            placeholder="请输入新密码"
+            v-model="formData.password"
+          ></el-input>
+        </el-form-item>
+        <el-form-item label="确认密码" prop="confirmPwd">
+          <el-input
+            type="password"
+            placeholder="请输入确认密码"
+            v-model="formData.confirmPwd"
+          ></el-input>
+        </el-form-item>
+      </el-form>
+      <div class="submit-button">
+        <div class="back btn" @click="onBack">返回</div>
+        <div class="submit btn" @click="onSubmit">提交</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { isMobile, validPassword } from '~/utils/validator'
+import { mapGetters } from 'vuex'
+export default {
+  layout: 'app-ross',
+  data() {
+    const passwordValidate = (rule, value, callback) => {
+      console.log(value)
+      if (validPassword(value)) {
+        callback()
+      } else {
+        callback(new Error('密码只能包含英文大小写和数字'))
+      }
+    }
+    const confirmPwdValide = (rule, value, callback) => {
+      if (this.formData.password !== value) {
+        callback(new Error('两次输入的密码不一致'))
+      } else {
+        callback()
+      }
+    }
+
+    return {
+      sendStatus: 0,
+      timer: null,
+      formData: {
+        mobile: '',
+        verifyCode: '',
+        password: '',
+        confirmPwd: '',
+      },
+      rules: {
+        mobile: [
+          { required: true, message: '请输入手机号', trigger: ['blur'] },
+        ],
+        verifyCode: [
+          { required: true, message: '请输入验证码', trigger: ['blur'] },
+        ],
+        password: [
+          { required: true, message: '请输入新密码', trigger: ['blur'] },
+          { min: 6, message: '密码长度不能小于6位', trigger: ['blur'] },
+          { validator: passwordValidate, trigger: ['blur'] },
+        ],
+        confirmPwd: [
+          { required: true, message: '请输入确认密码', trigger: ['blur'] },
+          { validator: confirmPwdValide, trigger: ['blur'] },
+        ],
+      },
+    }
+  },
+  computed: {
+    ...mapGetters(['authUserId', 'routePrefix']),
+    sendCodeBtnText() {
+      return this.sendStatus === 0
+        ? '发送验证码'
+        : `再次发送${this.sendStatus}s`
+    },
+  },
+  methods: {
+    // 返回
+    onBack() {
+      this.$router.back()
+    },
+    // 提交
+    async onSubmit() {
+      try {
+        await this.$refs.form.validate()
+        this.onResetPassword()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 忘记密码
+    async onResetPassword() {
+      try {
+        await this.$http.api.clubUserReset({
+          mobile: this.formData.mobile,
+          verifyCode: this.formData.verifyCode,
+          password: this.formData.password,
+          authUserId: this.authUserId,
+        })
+        this.$toast('密码修改成功,请重新登录')
+        setTimeout(() => {
+          this.logout()
+        }, 2000)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 退出登录
+    logout() {
+      this.$store.dispatch('user/logout')
+      this.$removeStorage(this.routePrefix, 'userInfo')
+      this.backHome()
+    },
+
+    // 回到首页
+    backHome() {
+      window.location.href = window.location.origin + this.routePrefix
+    },
+
+    // 发送短信验证码
+    async onSend() {
+      if (this.sendStatus > 0) return
+      // 验证手机号是否合法
+      if (!isMobile(this.formData.mobile)) {
+        this.$toast('请输入正确的手机号')
+        return
+      }
+      try {
+        // 发送验证码
+        await this.$http.api.clubUserCodeSend({
+          mobile: this.formData.mobile,
+          authUserId: this.authUserId,
+          type: this.formType === 'register' ? 1 : 2,
+        })
+        this.$toast('验证码已发送')
+        // 开启倒计时
+        this.countdown()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    countdown() {
+      this.sendStatus = 30
+      this.timer = setInterval(() => {
+        if (this.sendStatus === 0) {
+          clearInterval(this.timer)
+          return
+        }
+        this.sendStatus--
+      }, 1000)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page {
+    position: relative;
+    min-height: calc(100vh - 80px - 80px);
+    background-color: #fff;
+    overflow: hidden;
+  }
+
+  .page-content {
+    width: 518px;
+    margin: 0 auto;
+    margin-top: 100px;
+
+    .verify-code {
+      width: 100%;
+      display: flex;
+      align-items: center;
+
+      .send {
+        flex-shrink: 0;
+        margin-left: 16px;
+        height: 40px;
+        text-align: center;
+        line-height: 40px;
+        width: 118px;
+        background: #f3920d;
+        border-radius: 4px;
+        font-size: 16px;
+        color: #fff;
+        cursor: pointer;
+      }
+    }
+
+    .submit-button {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 32px;
+      .btn {
+        text-align: center;
+        width: 118px;
+        height: 40px;
+        border-radius: 4px;
+        font-size: 16px;
+        cursor: pointer;
+        box-sizing: border-box;
+        margin: 0 16px;
+
+        &.back {
+          border: 1px solid #f3920d;
+          color: #f3920d;
+          line-height: 38px;
+        }
+        &.submit {
+          background: #f3920d;
+          color: #fff;
+          line-height: 40px;
+        }
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-content {
+    padding: 10vw 4vw 0;
+    .verify-code {
+      width: 100%;
+      display: flex;
+      align-items: center;
+
+      .send {
+        flex-shrink: 0;
+        margin-left: 2.4vw;
+        height: 40px;
+        text-align: center;
+        line-height: 40px;
+        width: 24.2vw;
+        background: #f3920d;
+        border-radius: 0.4vw;
+        font-size: 3.4vw;
+        color: #fff;
+        cursor: pointer;
+      }
+    }
+
+    .submit-button {
+      margin-top: 20vw;
+      .btn {
+        text-align: center;
+        height: 40px;
+        border-radius: 0.4vw;
+        font-size: 3.4vw;
+        cursor: pointer;
+        box-sizing: border-box;
+        margin-bottom: 3.2vw;
+
+        &.back {
+          border: 1px solid #f3920d;
+          color: #f3920d;
+          line-height: 38px;
+        }
+        &.submit {
+          background: #f3920d;
+          color: #fff;
+          line-height: 40px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 46 - 0
pages/_template/ross/center/subnav/account.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="sub-nav">
+    <div class="item" @click="onResetPassword">
+      <span>修改密码</span>
+      <span class="el-icon-arrow-right"></span>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+export default {
+  layout: 'app-ross',
+  data() {
+    return {}
+  },
+  computed: {
+    ...mapGetters(['routePrefix']),
+  },
+  methods: {
+    // 修改密码
+    onResetPassword() {
+      this.$router.push(`${this.routePrefix}/center/settings/password`)
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.sub-nav {
+  padding: 0 4vw;
+  padding-top: 2.4vw;
+  .item {
+    width: 100%;
+    padding: 3vw 0;
+    display: flex;
+    justify-content: space-between;
+    font-size: 3.4vw;
+    border-bottom: 0.1vw solid #c2c2c2;
+
+    .el-icon-arrow-right {
+      font-size: 4vw;
+    }
+  }
+}
+</style>

+ 14 - 0
pages/_template/ross/form/club-register.vue

@@ -100,6 +100,7 @@ export default {
     return {
       isRequest: true,
       active: false,
+      isSubmit: false,
       registerType: [3],
       step: 1,
       stepList: [
@@ -180,6 +181,10 @@ export default {
     this.isRequest = true
     this.initPageForm()
   },
+  beforeDestroy() {
+    this.$toast.clear()
+    this.isSubmit = false
+  },
   methods: {
     onConfirm() {
       this.$router.push(this.routePrefix)
@@ -212,6 +217,12 @@ export default {
     },
 
     async onSubmit() {
+      this.$toast.loading({
+        message: '正在提交注册信息,请勿操作...',
+        duration: 0,
+      })
+      if (this.isSubmit) return
+      this.isSubmit = true
       const params = {
         registerType: this.registerType.join(','),
         authUserId: this.authUserId,
@@ -231,6 +242,9 @@ export default {
       } catch (error) {
         console.log(error)
         this.$toast(error.msg)
+      } finally {
+        this.$toast.clear()
+        this.isSubmit = false
       }
     },
 

+ 2 - 0
pages/_template/ross/form/components/form-club-info.vue

@@ -306,6 +306,8 @@ export default {
         provinceId: '',
         cityId: '',
         townId: '',
+        linkMan: '',
+        linkMobile: ''
       },
       rules: {
         name: [

+ 1 - 1
pages/_template/ross/record/club/edit.vue

@@ -51,6 +51,7 @@ export default {
         this.formData.authUserId = this.authUserId
         this.formData.source = 2
         this.formData.authId = this.clubInfo.authId
+        console.log(this.formData)
         await this.$http.api.authClubSave(this.formData)
         this.$toast('保存成功')
         this.$router.push(`${this.routePrefix}/record/club/detail`)
@@ -77,7 +78,6 @@ export default {
     },
 
     onClubInfoFormStep(data) {
-      console.log(data)
       this.formData = data
     },
   },

+ 9 - 0
utils/validator.js

@@ -31,3 +31,12 @@ export function isNumber(arg) {
   const reg = /\d+$/
   return reg.test(arg)
 }
+
+/**
+ * @param {String} arg
+ * @returns {Boolean}
+ */
+export function validPassword(password) {
+  const reg = /^[a-z|A-Z|0-9]{6,32}$/
+  return reg.test(password)
+}

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