Procházet zdrojové kódy

默认模板添加修改

yuwenjun1997 před 2 roky
rodič
revize
69832de5ca

+ 6 - 6
layouts/app-normal.vue

@@ -104,12 +104,12 @@ export default {
           path: '/form/club-register',
           icon: 'icon-register',
         },
-        {
-          id: 2,
-          name: '产品资料',
-          path: '/docs/0',
-          icon: 'icon-doc',
-        },
+        // {
+        //   id: 2,
+        //   name: '产品资料',
+        //   path: '/docs/0',
+        //   icon: 'icon-doc',
+        // },
         {
           id: 3,
           name: '意见反馈',

+ 319 - 46
layouts/app-ph.vue

@@ -4,31 +4,28 @@
       <div class="header" v-show="showHeader">
         <div class="navbar flex justify-between items-center">
           <div class="logo flex items-center" @click="backHome">
-            <img src="~/assets/theme-images/common/icon-logo.png" />
+            <img src="~/assets/theme-images/normal/pc/logo.png" />
             <span>认证通</span>
           </div>
-          <div class="user-info">
-            <template v-if="accessToken">
-              <span v-text="userInfo.mobile"></span>
-              <span class="underline logout" @click="logout">退出登录</span>
-            </template>
-            <template v-else>
-              <div class="flex justify-center">
-                <div
-                  class="login pr-3 pl-3 rounded-sm border-white leading-6"
-                  @click="onLogin"
-                >
-                  登录
+          <div class="flex justify-center items-center">
+            <div class="user-info">
+              <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>
-                |
-                <div
-                  class="register pr-3 pl-3 rounded-sm border-white leading-6"
-                  @click="onRegister"
-                >
-                  注册
-                </div>
-              </div>
-            </template>
+              </template>
+              <template v-else>
+                <div class="login-btn" @click="onLogin">登录</div>
+              </template>
+            </div>
+            <span class="collapse-icon" @click="drawer = true"></span>
           </div>
         </div>
       </div>
@@ -79,26 +76,6 @@ export default {
       formType: 'login',
       drawer: false,
       isMounted: false,
-      list: [
-        {
-          id: 1,
-          name: '授权申请',
-          path: '/form/club-register',
-          icon: 'icon-register',
-        },
-        {
-          id: 2,
-          name: '产品资料',
-          path: '/docs/0',
-          icon: 'icon-doc',
-        },
-        {
-          id: 3,
-          name: '意见反馈',
-          path: '/feedback',
-          icon: 'icon-feedback',
-        },
-      ],
     }
   },
   mounted() {
@@ -111,6 +88,32 @@ export default {
     this.refreshCacheData()
   },
   methods: {
+    // 跳转
+    onJumpTo(item) {
+      this.drawer = false
+      const hasLogin = this.$store.getters.accessToken
+      // 保存登录重定向路由
+      this.$setStorage(
+        this.routePrefix,
+        'login_redicret',
+        this.routePrefix + item.path
+      )
+      if (item.id > 2 && !hasLogin) {
+        this.$toast({ message: '请先登录', duration: 1000 })
+        this.formType = 'login'
+        this.$store.commit('app/SHOW_LOGIN')
+        return
+      }
+
+      if (item.id === 0) {
+        const url = this.routePrefix + item.path
+        this.$router.push(url)
+      } else {
+        const url = this.routePrefix + item.path
+        this.$router.push(url)
+      }
+    },
+
     // 点击登录
     onLoginClick(type) {
       this.formType = type
@@ -160,8 +163,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`
     },
 
     // 响应页面宽度变化
@@ -181,6 +192,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 {
@@ -223,9 +246,140 @@ export default {
         }
       }
 
+      .nav {
+        .link {
+          display: inline;
+          margin-left: 32px;
+          cursor: pointer;
+
+          &:hover {
+            .text {
+              @include themify($themes) {
+                color: themed('color');
+              }
+            }
+
+            .icon {
+              &.icon-register {
+                background-image: url(~assets/theme-images/normal/pc/link-entry-register-active.png);
+              }
+              &.icon-doc {
+                background-image: url(~assets/theme-images/normal/pc/link-entry-doc-active.png);
+              }
+              &.icon-feedback {
+                background-image: url(~assets/theme-images/normal/pc/link-entry-feedback-active.png);
+              }
+            }
+          }
+        }
+
+        .icon {
+          width: 20px;
+          height: 20px;
+          display: inline-block;
+          vertical-align: -4px;
+          margin-right: 4px;
+
+          background-size: 20px;
+          background-repeat: no-repeat;
+          background-position: center;
+
+          &.icon-register {
+            background-image: url(~assets/theme-images/normal/pc/link-entry-register.png);
+          }
+          &.icon-doc {
+            background-image: url(~assets/theme-images/normal/pc/link-entry-doc.png);
+          }
+          &.icon-feedback {
+            background-image: url(~assets/theme-images/normal/pc/link-entry-feedback.png);
+          }
+        }
+        .text {
+          font-size: 16px;
+          color: #fff;
+        }
+      }
+
       .user-info {
         color: #fff;
         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 {
+                  @include themify($themes) {
+                    color: themed('color');
+                  }
+                }
+              }
+            }
+          }
+        }
 
         .login,
         .register,
@@ -265,7 +419,7 @@ export default {
       left: 0;
       z-index: 999;
       width: 100%;
-      padding: 0 2.4vw;
+      padding: 0 4vw;
       height: 12.8vw;
       box-sizing: border-box;
       background: linear-gradient(90deg, #101010 0%, #404040 100%);
@@ -284,7 +438,7 @@ export default {
             width: 12.2vw;
             height: 3.9vw;
             margin-right: 1.9vw;
-            transform: translateY(-1vw);
+            transform: translateY(-0.6vw);
           }
         }
         span {
@@ -296,6 +450,88 @@ export default {
       .user-info {
         color: #fff;
         font-size: 3vw;
+
+        .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;
+            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 {
+        display: block;
+        width: 5.6vw;
+        height: 5.6vw;
+        background: url(~assets/theme-images/common/h5-icon-collapse.png)
+          no-repeat center;
+        background-size: 5.6vw;
       }
     }
     .content {
@@ -309,5 +545,42 @@ export default {
       font-size: 3vw;
     }
   }
+
+  .nav {
+    width: 63vw;
+    box-sizing: border-box;
+    padding: 0 6.4vw;
+    .link {
+      display: flex;
+      justify-content: flex-start;
+      align-items: center;
+      border-bottom: 0.1vw solid #c2c2c2;
+      padding-bottom: 3vw;
+      padding-top: 6vw;
+      .icon {
+        width: 5.6vw;
+        height: 5.6vw;
+        vertical-align: -1.2vw;
+        margin-right: 2.4vw;
+        background-size: 5.6vw;
+        background-repeat: no-repeat;
+        background-position: center;
+
+        &.icon-register {
+          background-image: url(~assets/theme-images/normal/h5/link-entry-register-active.png);
+        }
+        &.icon-doc {
+          background-image: url(~assets/theme-images/normal/h5/link-entry-doc-active.png);
+        }
+        &.icon-feedback {
+          background-image: url(~assets/theme-images/normal/h5/link-entry-feedback-active.png);
+        }
+      }
+      .text {
+        font-size: 3.4vw;
+        color: #282828;
+      }
+    }
+  }
 }
 </style>

+ 1 - 1
pages/_template/ph/approve/club/star-list.vue

@@ -47,7 +47,7 @@
 <script>
 import clubStarListMixin from '@/mixins/clubStarList'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   mixins: [clubStarListMixin],
 }
 </script>

+ 468 - 0
pages/_template/ph/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-ph',
+  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: #BC1724;
+      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: #BC1724;
+      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/ph/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-ph',
+  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/ph/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-ph',
+  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: #BC1724;
+      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: #BC1724;
+      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: #BC1724;
+      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>

+ 472 - 0
pages/_template/ph/center/index.vue

@@ -0,0 +1,472 @@
+<template>
+  <div class="page">
+    <div class="page-top">
+      <SimpleCenterCover />
+      <div class="club-logo">
+        <img :src="clubInfo.logo" alt="" v-if="isAuth" />
+        <img
+          src="~/assets/theme-images/normal/pc/icon-club-logo-default.png"
+          v-else-if="isPc"
+        />
+        <img
+          src="~/assets/theme-images/normal/h5/icon-club-logo-default.png"
+          v-else
+        />
+        <div class="club-mobile">{{ userInfo.mobile }}</div>
+        <div class="club-name" v-if="isAuth">
+          机构:{{ clubInfo.authParty }}
+        </div>
+      </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-ph',
+  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 {
+    position: relative;
+    height: 360px;
+    // background-image: url(~assets/theme-images/normal/pc/banner-doc.png);
+
+    .club-logo {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      text-align: center;
+
+      img {
+        display: inline-block;
+        width: 90px;
+        height: 90px;
+        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 {
+          background-image: url(~assets/theme-images/normal/pc/icon-center-item-auth-club.png);
+          .icon {
+            color: #bc1724;
+          }
+        }
+
+        &.device {
+          background-image: url(~assets/theme-images/normal/pc/icon-center-item-device.png);
+
+          .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: #bc1724;
+        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;
+    position: relative;
+
+    .club-logo {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      text-align: center;
+
+      img {
+        display: inline-block;
+        width: 12.8vw;
+        height: 12.8vw;
+        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: #bc1724;
+      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 {
+          background-image: url(~assets/theme-images/normal/pc/icon-center-item-auth-club.png);
+          .icon {
+            color: #bc1724;
+          }
+        }
+
+        &.device {
+          background-image: url(~assets/theme-images/normal/pc/icon-center-item-device.png);
+          .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/ph/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-ph',
+  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: #BC1724;
+        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 #BC1724;
+          color: #BC1724;
+          line-height: 38px;
+        }
+        &.submit {
+          background: #BC1724;
+          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: #BC1724;
+        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 #BC1724;
+          color: #BC1724;
+          line-height: 38px;
+        }
+        &.submit {
+          background: #BC1724;
+          color: #fff;
+          line-height: 40px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 46 - 0
pages/_template/ph/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-ph',
+  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>

+ 1 - 1
pages/_template/ph/feedback/index.vue

@@ -27,7 +27,7 @@
 <script>
 import feedbackMixin from '@/mixins/feedback'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   mixins: [feedbackMixin],
 }
 </script>

+ 2 - 2
pages/_template/ph/record/club/detail.vue

@@ -147,7 +147,7 @@
 import SimpleEmpty from '@/components/SimpleEmpty'
 import { mapGetters } from 'vuex'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   components: {
     SimpleEmpty,
   },
@@ -178,7 +178,7 @@ export default {
         const res = await this.$http.api.fetchClubAuthInfoData({
           authId: result.data.auth.authId,
         })
-        
+
         this.clubInfo = res.data
       } catch (error) {
         console.log(error)

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

@@ -25,7 +25,7 @@ import SimpleRadio from '@/components/SimpleRadio'
 import { mapGetters } from 'vuex'
 import FormClubInfo from '../../form/components/form-club-info.vue'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   components: {
     SimpleUploadImage,
     SimpleRadio,

+ 1 - 1
pages/_template/ph/record/device/detail.vue

@@ -95,7 +95,7 @@
 <script>
 import { mapGetters } from 'vuex'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   data() {
     return {
       relationId: '',

+ 1 - 1
pages/_template/ph/record/device/edit.vue

@@ -27,7 +27,7 @@
 import FormClubDevice from '../../form/components/form-club-device.vue'
 import { mapGetters } from 'vuex'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   components: {
     FormClubDevice,
   },

+ 1 - 1
pages/_template/ph/record/device/index.vue

@@ -51,7 +51,7 @@
 import SimpleEmpty from '@/components/SimpleEmpty'
 import { mapGetters } from 'vuex'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   components: {
     SimpleEmpty,
   },

+ 1 - 1
pages/_template/ph/record/message.vue

@@ -14,7 +14,7 @@
 <script>
 import { mapGetters } from 'vuex'
 export default {
-  layout: 'app-ross',
+  layout: 'app-ph',
   computed: {
     ...mapGetters(['supplierInfo', 'authUserId', 'routePrefix', 'accessToken']),
   },