Parcourir la source

Merge branch 'CRM_12.9'

hbchen il y a 2 ans
Parent
commit
1f3c9497f6

+ 3 - 1
.env.development

@@ -3,4 +3,6 @@ VITE_APP_API_URL="http://8.136.199.33:8302/api"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名
-VITE_APP_OUTDIR="dist"
+VITE_APP_OUTDIR="dist"
+# PC端中文研报跳转地址
+VITE_CN_YB_PC="https://ybpctest.hzinsights.com/report/index"

+ 3 - 1
.env.production

@@ -3,4 +3,6 @@ VITE_APP_API_URL="https://ybpcen.hzinsights.com/api"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名
-VITE_APP_OUTDIR="hongze_yb_en"
+VITE_APP_OUTDIR="hongze_yb_en"
+# PC端中文研报跳转地址
+VITE_CN_YBPC="https://ybpc.hzinsights.com/report/index"

+ 3 - 1
.env.test

@@ -3,4 +3,6 @@ VITE_APP_API_URL="http://8.136.199.33:8302/api"
 # 路由根地址
 VITE_APP_BASE_URL="/"
 # 打包输入文件名
-VITE_APP_OUTDIR="hongze_yb_en"
+VITE_APP_OUTDIR="hongze_yb_en"
+# PC端中文研报跳转地址
+VITE_CN_YB_PC="https://ybpctest.hzinsights.com/report/index"

+ 2 - 0
package.json

@@ -13,9 +13,11 @@
     "@vueuse/core": "^9.6.0",
     "axios": "^1.2.0",
     "element-plus": "^2.2.25",
+    "md5": "^2.3.0",
     "normalize.css": "^8.0.1",
     "vconsole": "^3.15.0",
     "vue": "^3.2.41",
+    "vue-demi": "^0.13.11",
     "vue-router": "^4.1.6"
   },
   "devDependencies": {

+ 82 - 0
src/api/auth.js

@@ -0,0 +1,82 @@
+/**
+ * 注册、登录、修改密码、绑定手机号等
+ */
+import {get,post} from './http'
+
+/**
+ * 发送邮箱验证码
+ * @param params.Name  用户姓名
+ * @param params.Email  邮箱
+ */
+export const emailCodeSend=params=>{
+    return get('/auth/emailCode',params)
+}
+
+/**
+ * 短信验证码
+ * @param params.Mobile  手机号
+ * @param params.Area_num  区号
+ */
+export const smsCodeSend=params=>{
+    return get('/auth/smsCode',params)
+}
+
+/**
+ * 注册
+ * @param params.CompanyName 公司名称  
+ * @param params.Name 用户名称  
+ * @param params.Password 密码 md5加密后  
+ * @param params.Email 用户邮箱  
+ * @param params.SmsCode 邮箱验证码  
+ */
+export const registerApi=params=>{
+    return post('/auth/register',params)
+}
+
+/**
+ * 登录
+ * @param params.Account 用户
+ * @param params.Type 类型 1:邮箱 2:手机  
+ * @param params.Password 密码 md5加密后  
+ */
+export const loginApi=params=>{
+    return post('/auth/login',params)
+}
+
+/**
+ * 绑定手机号
+ * @param params.Mobile 手机号  
+ * @param params.SmsCode 验证码  
+ */
+export const bindMobile=params=>{
+    return post('/auth/bindMobile',params)
+}
+
+/**
+ * 换绑手机号
+ * @param params.OldMobile 原手机号  
+ * @param params.Mobile 手机号  
+ * @param params.SmsCode 验证码  
+ */
+export const modifyMobile=params=>{
+    return post('/auth/modifyMobile',params)
+}
+
+/**
+ * 修改密码
+ * @param params.OldPwd 旧密码 MD5加密后  
+ * @param params.NewPwd 新密码 MD5加密后  
+ */
+export const psdModify=params=>{
+    return post('/auth/modifyPwd',params)
+}
+
+/**
+ * 忘记密码
+ * @param params.Account 账户
+ * @param params.SmsCode 验证码
+ * @param params.NewPwd 新密码 MD5加密后  
+ */
+export const psdMissingApi=params=>{
+    return post('/auth/forgetPwd',params)
+}

+ 82 - 2
src/api/http.js

@@ -1,7 +1,8 @@
 "use strict";
 import axios from "axios";
-import { ElMessage } from 'element-plus'
+import {ElMessage,ElMessageBox} from 'element-plus'
 import CryptoJS from './crypto'
+import router from '@/router/index'
 
 let config = {
   baseURL: import.meta.env.VITE_APP_API_URL,
@@ -10,9 +11,32 @@ let config = {
 
 const _axios = axios.create(config);
 
+const isPhone=()=>{
+  return !(window.innerWidth>768)
+}
+const copyEmail=()=>{
+  if (navigator.clipboard && window.isSecureContext) {
+    // navigator clipboard 向剪贴板写文本
+    navigator.clipboard.writeText('stephanie@hzinsights.com').then(() => {
+      ElMessage.success('Email address copied')
+    });
+  }else{
+    const input = document.createElement('input');
+    input.setAttribute('readonly', 'readonly');
+    input.setAttribute('value', 'stephanie@hzinsights.com');
+    document.body.appendChild(input);
+    input.setSelectionRange(0, input.value.length);
+    input.select();
+    document.execCommand('copy');
+    document.body.removeChild(input);
+    ElMessage.success('Email address copied')
+  }
+}
+
 _axios.interceptors.request.use(
   function (config) {
     config.headers.shareEmailId=sessionStorage.getItem('shareId')||0
+    config.headers.Authorization=localStorage.getItem('yben_token')||''
     return config;
   },
   function (error) {
@@ -31,8 +55,64 @@ _axios.interceptors.response.use(
       data=response.data
     }
 
-    if(data.code === 400) {
+    if(data.code === 400 || data.code === 500) {
       ElMessage.error(data.msg)
+    }else if(data.code==401){
+      // 非法访问
+      ElMessage({
+        type:'error',
+        message:data.msg,
+        duration:1000
+      })
+      setTimeout(()=>{
+        // 清除token,重新登录
+        localStorage.removeItem('yben_token')
+        router.replace('/login')
+      },1000)
+    }else if(data.code==4013){
+      // 未注册
+      ElMessageBox.confirm("This email adress is not registered. Please try again.","Prompt",
+      {
+        customClass:isPhone()?'mobile-message-confirm':'PC-message-confirm',
+        confirmButtonClass:isPhone()?'mobile-confirm-button':'',
+        confirmButtonText: 'Create account',
+        showCancelButton:false,
+      }).then(res=>{
+        router.push('/register')
+      }).catch(()=>{})
+    }else if(data.code==4014){
+      // 未绑定
+      ElMessageBox.confirm("This phone number is not registered. Please try again.","Prompt",
+      {
+        customClass:isPhone()?'mobile-message-confirm':'PC-message-confirm',
+        confirmButtonClass:isPhone()?'mobile-confirm-button':'',
+        confirmButtonText: 'Got it',
+        showCancelButton:false,
+      }).then(res=>{
+        console.log('This phone number is not registered. Please try again.');
+      }).catch(()=>{})
+    }else if(data.code==4012){
+      // 权限到期
+      ElMessageBox.confirm("Your trial has ended Enjoyed your experience with us? Contact us at stephanie@hzinsights.com to extend your trial.","Prompt",
+      {
+        customClass:isPhone()?'mobile-message-confirm':'PC-message-confirm',
+        confirmButtonClass:isPhone()?'mobile-confirm-button':'',
+        confirmButtonText: 'Get the contact email',
+        showCancelButton:false,
+      }).then(res=>{
+        copyEmail()
+      }).catch(()=>{})
+    }else if(data.code==4015){
+      // 已注册
+      ElMessageBox.confirm("There is already a user account associated with this email address. Please log in instead.","Prompt",
+      {
+        customClass:isPhone()?'mobile-message-confirm':'PC-message-confirm',
+        confirmButtonClass:isPhone()?'mobile-confirm-button':'',
+        confirmButtonText: 'Log in',
+        showCancelButton:false,
+      }).then(res=>{
+        router.push('/login')
+      }).catch(()=>{})
     }
     return data;
   },

+ 3 - 0
src/assets/icons/add-circle.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13ZM8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM7.5 8.5H5V7.5H7.5V5H8.5V7.5H11V8.5H8.5V11H7.5V8.5Z" fill="#1856A7"/>
+</svg>

+ 4 - 0
src/assets/icons/add-square.svg

@@ -0,0 +1,4 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.4793 16.9609H15.0393V21.5209C15.0393 21.6529 15.1473 21.7609 15.2793 21.7609H16.7193C16.8513 21.7609 16.9593 21.6529 16.9593 21.5209V16.9609H21.5193C21.6513 16.9609 21.7593 16.8529 21.7593 16.7209V15.2809C21.7593 15.1489 21.6513 15.0409 21.5193 15.0409H16.9593V10.4809C16.9593 10.3489 16.8513 10.2409 16.7193 10.2409H15.2793C15.1473 10.2409 15.0393 10.3489 15.0393 10.4809V15.0409H10.4793C10.3473 15.0409 10.2393 15.1489 10.2393 15.2809V16.7209C10.2393 16.8529 10.3473 16.9609 10.4793 16.9609Z" fill="#1856A7"/>
+<path d="M27.04 4H4.96C4.429 4 4 4.429 4 4.96V27.04C4 27.571 4.429 28 4.96 28H27.04C27.571 28 28 27.571 28 27.04V4.96C28 4.429 27.571 4 27.04 4ZM25.84 25.84H6.16V6.16H25.84V25.84Z" fill="#1856A7"/>
+</svg>

+ 3 - 0
src/assets/icons/avatar.svg

@@ -0,0 +1,3 @@
+<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24.2812 26.7861C21.6382 28.873 18.3676 30.0055 15 29.9999C11.6324 30.0055 8.36173 28.873 5.71873 26.7861C6.04002 24.5536 7.15524 22.5118 8.85992 21.0349C10.5646 19.5579 12.7445 18.745 15 18.745C17.2555 18.745 19.4354 19.5579 21.14 21.0349C22.8447 22.5118 23.9599 24.5536 24.2812 26.7861ZM11.1937 17.5349C9.46919 18.157 7.92239 19.1903 6.68737 20.5452C5.45235 21.9002 4.56638 23.5358 4.10623 25.3105C2.0912 23.1812 0.744237 20.5082 0.231835 17.6218C-0.280566 14.7354 0.0640518 11.7621 1.2231 9.06944C2.38214 6.37676 4.3048 4.08272 6.75344 2.47087C9.20209 0.859013 12.0694 0 15.0009 0C17.9325 0 20.7997 0.859013 23.2484 2.47087C25.697 4.08272 27.6197 6.37676 28.7787 9.06944C29.9378 11.7621 30.2824 14.7354 29.77 17.6218C29.2576 20.5082 27.9106 23.1812 25.8956 25.3105C25.4351 23.5354 24.5485 21.8995 23.3128 20.5445C22.0771 19.1896 20.5296 18.1565 18.8043 17.5349C19.9379 16.7281 20.7857 15.5819 21.2253 14.2619C21.6649 12.9418 21.6737 11.5162 21.2502 10.1909C20.8267 8.86553 19.9929 7.70909 18.8693 6.88854C17.7457 6.06799 16.3904 5.62578 14.999 5.62578C13.6077 5.62578 12.2524 6.06799 11.1288 6.88854C10.0051 7.70909 9.17139 8.86553 8.74791 10.1909C8.32443 11.5162 8.33313 12.9418 8.77275 14.2619C9.21238 15.5819 10.0602 16.7281 11.1937 17.5349ZM15 7.49987C15.6156 7.49982 16.2252 7.62103 16.7939 7.85658C17.3627 8.09212 17.8795 8.43739 18.3148 8.87267C18.7501 9.30795 19.0954 9.82472 19.331 10.3935C19.5666 10.9622 19.6878 11.5718 19.6878 12.1874C19.6878 12.803 19.5666 13.4125 19.331 13.9813C19.0954 14.55 18.7501 15.0668 18.3148 15.5021C17.8795 15.9373 17.3627 16.2826 16.7939 16.5182C16.2252 16.7537 15.6156 16.8749 15 16.8749C13.7568 16.8748 12.5647 16.3809 11.6857 15.5018C10.8067 14.6227 10.3128 13.4305 10.3128 12.1874C10.3128 10.9442 10.8067 9.752 11.6857 8.87294C12.5647 7.99387 13.7568 7.49997 15 7.49987Z" fill="#00459F"/>
+</svg>

+ 8 - 0
src/assets/icons/edit.svg

@@ -0,0 +1,8 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g opacity="0.9">
+<path d="M14.1321 4.95049L10.8888 1.70711L11.5959 1L14.8393 4.24338L14.1321 4.95049Z" fill="#00459F"/>
+<path d="M5.97196 13.1108L2.359 13.8334C2.14909 13.8754 1.96401 13.6903 2.00599 13.4804L2.72858 9.86742L10.0262 2.56982L13.2696 5.8132L5.97196 13.1108ZM11.8554 5.8132L10.0262 3.98403L3.64979 10.3604L3.19249 12.6469L5.47895 12.1896L11.8554 5.8132Z" fill="#00459F"/>
+<path d="M15 11H11V12H15V11Z" fill="#00459F"/>
+<path d="M15 13H8.5V14H15V13Z" fill="#00459F"/>
+</g>
+</svg>

+ 3 - 0
src/assets/icons/transition-success.svg

@@ -0,0 +1,3 @@
+<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M30 56.25C44.4975 56.25 56.25 44.4975 56.25 30C56.25 15.5025 44.4975 3.75 30 3.75C15.5025 3.75 3.75 15.5025 3.75 30C3.75 44.4975 15.5025 56.25 30 56.25ZM16.875 30.7744L19.5244 28.125L26.25 34.8488L40.4719 20.625L43.125 23.2781L26.25 40.1512L16.875 30.7744Z" fill="#67C23A"/>
+</svg>

+ 3 - 0
src/assets/icons/warning.svg

@@ -0,0 +1,3 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M16.875 9C16.875 4.65076 13.3492 1.125 9 1.125C4.65076 1.125 1.125 4.65076 1.125 9C1.125 13.3492 4.65076 16.875 9 16.875C13.3492 16.875 16.875 13.3492 16.875 9ZM9.5625 4.50034V10.6875H8.4375V4.50034H9.5625ZM8.31857 12.375H9.66857V13.725H8.31857V12.375Z" fill="#FF8A00"/>
+</svg>

BIN
src/assets/icons/yb-cn.png


BIN
src/assets/login_bci.png


+ 47 - 1
src/router/index.js

@@ -30,7 +30,41 @@ const routes=[
     name: "RoadshowDetail",
     component: ()=>import("@/views/roadShow/Detail.vue"),
   },
-  
+  {
+    path: "/login",
+    name: "Login",
+    component: ()=>import("@/views/verification/login.vue"),
+  },
+  {
+    path: "/register",
+    name: "Register",
+    component: ()=>import("@/views/verification/register.vue"),
+  },
+  {
+    path: "/bindPhoneNo",
+    name: "BindPhoneNo",
+    component: ()=>import("@/views/verification/bindPhoneNo.vue"),
+  },
+  {
+    path: "/passwordMissing",
+    name: "PasswordMissing",
+    component: ()=>import("@/views/verification/passwordMissing.vue"),
+  },
+  {
+    path: "/passwordChange",
+    name: "PasswordChange",
+    component: ()=>import("@/views/verification/passwordChange.vue"),
+  },
+  {
+    path: "/transitionPage",
+    name: "TransitionPage",
+    component: ()=>import("@/views/verification/transitionPage.vue"),
+  },
+  {
+    path: "/my",
+    name: "MobileMyPage",
+    component: ()=>import("@/views/verification/mobileMyPage.vue"),
+  },
   {
     path: '/:pathMatch(.*)',
     name: 'error',
@@ -55,8 +89,20 @@ const router=createRouter({
     }
   }
 })
+// 白名单
+const whitelist=['/login','/passwordMissing','/register']
+// 需要重定向的路由
+const shouldRedireact=['/report/index','/report/detail','/roadshow/detail']
 
 router.beforeEach((to, from, next) => {
+  // console.log(to.path);
+  // 做登录的权限判断
+  if(!localStorage.getItem('yben_token') && !whitelist.includes(to.path)){
+    if(shouldRedireact.includes(to.path)) sessionStorage.setItem('login_redirect',to.fullPath)
+
+    next({path:'/login',replace:true})
+    return 
+  }
   if(to.query.ShareEmail){
     sessionStorage.setItem('shareId',to.query.ShareEmail)
   }

+ 112 - 1
src/style/global.scss

@@ -129,4 +129,115 @@ img {
 .disclaimers-box{
   font-size: 16px;
   line-height: 1.7;
-}
+}
+// 沾满全屏宽度的 固定头
+.fixed-header{
+  position: fixed;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 68px;
+  z-index: 100;
+  width: 100vw;
+  background-color: white;
+  border-bottom: 1px solid #DCDFE6 ;
+  box-shadow: 0px 4px 20px rgba(180, 180, 180, 0.16);
+  span{
+    font-weight: 600;
+    font-size: 20px;
+    line-height: 28px;
+    color: #1856A7;
+  }
+}
+
+
+//PC端提示框样式修改
+.PC-message-confirm{
+  .el-message-box__title::before{
+    content: '';
+    display: inline-block;
+    background-image: url('@/assets/icons/warning.svg');
+    height: 18px;
+    width: 18px;
+    position: relative;
+    top: 2px;
+    margin-right: 8px;
+  }
+}
+.el-message-box__header{
+  border-bottom: solid 1px #DCDFE6;
+}
+.el-message-box__content{
+  padding: 30px 20px;
+}
+.el-message-box__btns{
+  .el-button--primary{
+    background-color: #1856A7;
+    height: 40px;
+    min-width: 120px;
+  }
+}
+// 移动端
+@media screen and (max-width: 768px) {
+  .fixed-header{
+    height: 50px;
+    span{
+      font-weight: 700;
+      font-size: 17px;
+      line-height: 20px;
+      color: #003675;
+    }
+  }
+  // 移动端表单样式
+  #mobile-form-field{
+    .el-form-item{
+      border-bottom: 1px solid #DCDFE6;
+    }
+    .el-form-item__label{
+      padding: 0;
+    }
+    .el-form-item{
+      margin-bottom: 15px;
+    }
+    .el-input__wrapper{
+      box-shadow: none!important;
+    }
+    .el-select{
+      .el-input__wrapper{
+        padding: 0;
+      }
+      .el-input__inner{
+        color: #1856A7;
+      }
+      .el-input__suffix{
+        .el-icon{
+          color: #1856A7;
+        }
+      }
+    }
+  }
+  // 移动端提示框样式修改
+  .mobile-message-confirm{
+    padding: 0;
+    .el-message-box__btns{
+      padding: 0;
+      .mobile-confirm-button{
+        background-color: white;
+        color: #1856A7;
+        width: 100%;
+        border-left: none;
+        border-right: none;
+        border-bottom: none;
+        border-top: solid 1px #DCDFE6;
+        height: 48px;
+        border-radius: 0;
+      }
+    }
+  }
+  .el-message{
+    width: 100%;
+  }
+}
+

+ 11 - 0
src/utils/constant.js

@@ -0,0 +1,11 @@
+// 电话区号
+export const TEL_CODE_ARR=[
+  { value: '86', label: '+86' },
+  { value: '852', label: "+852" },
+  { value: '886', label: '+886' },
+  { value: '1', label: '+1' },
+  { value: '65', label: '+65' },
+  { value: '62', label: '+62' },
+  { value:'081',label: '+081' },
+  { value:'44',label: '+44' }
+]

+ 132 - 4
src/views/report/Detail.vue

@@ -6,13 +6,16 @@ import {apiReportDetail,apiReportClassifyList} from '@/api/report'
 const route=useRoute()
 const router=useRouter()
 
+const userInfo=localStorage.getItem('user_info')?JSON.parse(localStorage.getItem('user_info')):{}
+
+
 //分类
 let reportClassify=ref([])
 async function getReportClassify(){
     const res=await apiReportClassifyList()
     if(res.code===200){
         const arr=res.data.list||[]
-        reportClassify.value=[...arr,{id: -1, classify_name: "Online Video"}]
+        reportClassify.value=[...arr,{id: -1, classify_name: "Online Video"},{id: 0,classify_name: "ABOUT US"}]
     }
 }
 getReportClassify()
@@ -114,20 +117,78 @@ function handleChangeSecClassify(item,pitem){
 onMounted(()=>{
     wwidth.value=window.innerWidth
 })
+
+const goYBPCCN=()=>{
+    let href = import.meta.env.VITE_CN_YB_PC
+    window.open(href,'_blank');
+}
+
+const userInfoGo=()=>{
+    let {href} = router.resolve("/my");
+    window.open(href,'_blank');
+}
+
+const bindPhone=()=>{
+    let {href} = router.resolve({path:"/bindPhoneNo"});
+    window.open(href,'_blank');
+}
+const loginOut=()=>{
+    localStorage.removeItem('yben_token')
+    router.push('/login')
+}
+const passwordChange=()=>{
+    let {href} = router.resolve({path:"/passwordChange"});
+    window.open(href,'_blank');
+}
+
 </script>
 
 <template>
     <div class="report-detail-page" v-if="info">
         <div class="header-wrap">
-            <div>
+            <div class="navigation-row">
                 <span style="color:#00459F;margin-right:30px;">MORE CONTENT>></span>
                 <span v-for="item in reportClassify" :key="item.id" style="margin-right:20px;cursor: pointer;" @click="goIndex(item)">{{item.classify_name}}</span>
             </div>
-            <div style="color:#00459F;cursor: pointer;" @click="$router.replace('/')">ABOUT US</div>
+            <!-- <div style="color:#00459F;cursor: pointer;" @click="$router.replace('/')">ABOUT US</div> -->
+            <div class="header-right">
+                <img @click="goYBPCCN" class="ybCN-jump" src="@/assets/icons/yb-cn.png" />
+                <el-popover
+                    popper-class="user-info-popper"
+                    placement="top-start"
+                    trigger="click">
+                    <template #reference>
+                        <div class="user-info-hover">
+                            <img src="@/assets/icons/avatar.svg" />
+                            <span>{{userInfo.Name}}</span>
+                        </div>
+                    </template>
+                    <div class="user-info-message">
+                        <div class="user-info-item">User Name:<span><span>{{userInfo.Name}}</span></span></div>
+                        <div class="user-info-item">Email Address:<span>{{userInfo.Email}}</span></div>
+                        <div class="user-info-item">Mobile Phone No:
+                            <!-- <span style="display: inline-flex;align-items: center;" v-if="userInfo.Mobile" @click="bindPhone">
+                                <span>{{ userInfo.CountryCode+' '+userInfo.Mobile }}</span>
+                                <img src="@/assets/icons/edit.svg" style="margin-left: 20px;height: 16px;cursor: pointer;" />
+                            </span> -->
+                            <span style="display: inline-flex;align-items: center;color: #1856A7;cursor: pointer;"
+                            @click="bindPhone">
+                                <img src="@/assets/icons/add-circle.svg" style="margin-right: 8px;height: 16px;" />
+                                <span>Add a phone number to your account</span>
+                            </span>
+                        </div>
+                        <div style="margin-top: 30px;display: flex;align-items: center;justify-content: space-between;">
+                            <el-button type="primary" @click="loginOut" size="large" style="min-width: 100px;background-color:#1856A7 ;">Sign out</el-button>
+                            <el-button plain @click="passwordChange" size="large" style="min-width: 100px;padding: 12px;">Update password</el-button>
+                        </div>
+                    </div>
+                </el-popover>
+            </div>
         </div>
         <div class="mobile-header-wrap">
             <img class="menu-icon" @click="showFilter=true;filterSize='100%'" src="@/assets/menu.svg" alt="">
             <span @click="$router.replace('/')">HORIZON INSIGHTS</span>
+            <img src="@/assets/icons/avatar.svg" class="user-info-avatar" @click="userInfoGo" />
         </div>
         <div class="no-select-text detail-wrap">
             <h2 class="title">【No.{{info.stage}}|FICC】{{info.title}} ({{info.create_time.substring(5, 7)}}{{info.create_time.substring(8, 10)}})</h2>
@@ -184,6 +245,21 @@ onMounted(()=>{
         margin-bottom: 0;
     }
 }
+.user-info-popper{
+    width: unset!important;
+    .user-info-message{
+        padding: 28px;
+        .user-info-item{
+            font-weight: 400;
+            font-size: 14px;
+            line-height: 20px;
+            color: #333333;
+            margin-bottom: 20px;
+            display: flex;
+            align-items: center;
+        }
+    }
+}
 </style>
 
 <style lang="scss" scoped>
@@ -210,6 +286,49 @@ onMounted(()=>{
     font-size: 20px;
     z-index: 99;
     background-color: #fff;
+    .header-right{
+        display: flex;
+        align-items: center;
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        right: 17px;
+        z-index: 30;
+        cursor: pointer;
+        .ybCN-jump{
+            height: 30px;
+            cursor: pointer;
+            margin-right: 20px;
+        }
+        .user-info-hover{
+            display: flex;
+            align-items: center;
+            img{
+                height: 30px;
+                width: 30px;
+                margin-right: 10px;
+            }
+            span{
+                font-weight: 500;
+                font-size: 16px;
+                line-height: 18px;
+                color: #666666;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                max-width: 130px;
+            }
+        }
+    }
+}
+@media (max-width: 900px){
+    .header-wrap{
+        .navigation-row{
+            span{
+                font-size: 16px;
+            }
+        }
+    }
 }
 .mobile-header-wrap{
     font-size: 17px;
@@ -227,11 +346,20 @@ onMounted(()=>{
     position: relative;
     .menu-icon{
         position: absolute;
-        left: 20px;
+        left: 17px;
         top: 50%;
         transform: translateY(-50%);
         width: 17px;
     }
+    .user-info-avatar{
+        display: flex;
+        align-items: center;
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        right: 17px;
+        height: 18px;
+    }
 }
 @media (max-width: 768px){
     .header-wrap{

+ 143 - 2
src/views/report/Index.vue

@@ -1,5 +1,5 @@
 <script setup>
-import {ref,reactive, onMounted} from 'vue'
+import {ref,reactive} from 'vue'
 import { useRoute, useRouter } from "vue-router";
 import {apiReportClassifyList,apiReportList} from '@/api/report'
 import { useWindowSize } from '@vueuse/core'
@@ -11,6 +11,8 @@ const { width, height } = useWindowSize()
 const router=useRouter()
 const route=useRoute()
 
+const userInfo=localStorage.getItem('user_info')?JSON.parse(localStorage.getItem('user_info')):{}
+
 //分类
 let navList=ref([])
 async function getReportClassify(){
@@ -158,6 +160,29 @@ function goReportDetail(item){
 let showFilter=ref(false)
 let filterSize=ref('30%')
 
+const goYBPCCN=()=>{
+    let href = import.meta.env.VITE_CN_YB_PC
+    window.open(href,'_blank');
+}
+
+const userInfoGo=()=>{
+    let {href} = router.resolve("/my");
+    window.open(href,'_blank');
+}
+
+const bindPhone=()=>{
+    let {href} = router.resolve({path:"/bindPhoneNo"});
+    window.open(href,'_blank');
+}
+const loginOut=()=>{
+    localStorage.removeItem('yben_token')
+    router.push('/login')
+}
+const passwordChange=()=>{
+    let {href} = router.resolve({path:"/passwordChange"});
+    window.open(href,'_blank');
+}
+
 </script>
 
 <template>
@@ -169,6 +194,41 @@ let filterSize=ref('30%')
                 <img src="@/assets/search.svg" alt="">
                 <span style="margin-left:10px">search for</span>
             </div>
+            <img src="@/assets/icons/avatar.svg" class="user-info-avatar" @click="userInfoGo" />
+            <div class="header-right">
+                <img @click="goYBPCCN" class="ybCN-jump" src="@/assets/icons/yb-cn.png" />
+                <el-popover
+                    popper-class="user-info-popper"
+                    placement="top-start"
+                    trigger="click">
+                    <template #reference>
+                        <div class="user-info-hover">
+                            <img src="@/assets/icons/avatar.svg" />
+                            <span>{{userInfo.Name}}</span>
+                        </div>
+                    </template>
+                    <div class="user-info-message">
+                        <div class="user-info-item">User Name:<span>{{userInfo.Name}}</span></div>
+                        <div class="user-info-item">Email Address:<span>{{userInfo.Email}}</span></div>
+                        <div class="user-info-item">Mobile Phone No:
+                            <span style="display: inline-flex;align-items: center;" v-if="userInfo.Mobile" @click="bindPhone">
+                                <span>{{ userInfo.CountryCode+' '+userInfo.Mobile }}</span>
+                                <img src="@/assets/icons/edit.svg" style="margin-left: 20px;height: 16px;cursor: pointer;" />
+                            </span>
+                            <span style="display: inline-flex;align-items: center;color: #1856A7;cursor: pointer;"
+                            @click="bindPhone" v-else>
+                                <img src="@/assets/icons/add-circle.svg" style="margin-right: 8px;height: 16px;" />
+                                <span>Add a phone number to your account</span>
+                            </span>
+                        </div>
+                        <div style="margin-top: 30px;display: flex;align-items: center;justify-content: space-between;">
+                            <el-button type="primary" @click="loginOut" size="large" 
+                            style="min-width: 100px;background-color:#1856A7;">Sign out</el-button>
+                            <el-button plain @click="passwordChange" size="large" style="min-width: 100px;padding: 12px;">Update password</el-button>
+                        </div>
+                    </div>
+                </el-popover>
+            </div>
         </div>
         <div class="content-wrap">
         <div class="nav-wrap">
@@ -304,6 +364,23 @@ let filterSize=ref('30%')
         margin-bottom: 0;
     }
 }
+.user-info-popper{
+    // min-width: 300px!important;
+    width: unset!important;
+    .user-info-message{
+        padding: 28px;
+        .user-info-item{
+            font-weight: 400;
+            font-size: 14px;
+            line-height: 20px;
+            color: #333333;
+            margin-bottom: 20px;
+            display: flex;
+            align-items: center;
+        }
+    }
+}
+
 </style>
 <style lang="scss" scoped>
 .report-index-page{
@@ -334,7 +411,7 @@ let filterSize=ref('30%')
         position: absolute;
         top: 50%;
         transform: translateY(-50%);
-        right: 20px;
+        left: 20px;
         font-size: 16px;
         color: #333;
         font-weight: 500;
@@ -343,6 +420,44 @@ let filterSize=ref('30%')
             vertical-align: middle;
         }
     }
+    .user-info-avatar{
+        display: none;
+    }
+    .header-right{
+        display: flex;
+        align-items: center;
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        right: 17px;
+        z-index: 30;
+        cursor: pointer;
+        .ybCN-jump{
+            height: 30px;
+            cursor: pointer;
+            margin-right: 20px;
+        }
+        .user-info-hover{
+            display: flex;
+            align-items: center;
+            img{
+                height: 30px;
+                width: 30px;
+                margin-right: 10px;
+            }
+            span{
+                font-weight: 500;
+                font-size: 16px;
+                line-height: 18px;
+                color: #666666;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                max-width: 130px;
+            }
+        }
+
+    }
     .menu-icon{
         position: absolute;
         left: 20px;
@@ -351,6 +466,14 @@ let filterSize=ref('30%')
         display: none;
     }
 }
+
+@media (max-width: 940px){
+    .header-wrap{
+        font-size: 26px;
+    }
+}
+
+
 @media (max-width: 768px){
     .header-wrap{
         border: none;
@@ -358,12 +481,30 @@ let filterSize=ref('30%')
         box-shadow: 0px 4px 20px rgba(180, 180, 180, 0.16);
         padding: 15px 0;
         .search-box{
+            left: unset;
+            right: 55px;
             span{
                 display: none;
             }
         }
+        .user-info-avatar{
+            display: flex;
+            align-items: center;
+            position: absolute;
+            top: 50%;
+            transform: translateY(-50%);
+            right: 17px;
+            height: 18px;
+        }
+        // .ybCN-jump{
+        //     display: none;
+        // }
+        .header-right{
+            display: none;
+        }
         .menu-icon{
             display: block;
+            left: 17px;
             width: 17px;
         }
     }

+ 132 - 4
src/views/roadShow/Detail.vue

@@ -8,13 +8,15 @@ import { useRoute, useRouter } from 'vue-router'
 const route=useRoute()
 const router=useRouter()
 
+const userInfo=localStorage.getItem('user_info')?JSON.parse(localStorage.getItem('user_info')):{}
+
 //分类
 let reportClassify=ref([])
 async function getReportClassify(){
     const res=await apiReportClassifyList()
     if(res.code===200){
         const arr=res.data.list||[]
-        reportClassify.value=[...arr,{id: -1, classify_name: "Online Video"}]
+        reportClassify.value=[...arr,{id: -1, classify_name: "Online Video"},{id: 0,classify_name: "ABOUT US"}]
     }
 }
 getReportClassify()
@@ -120,21 +122,78 @@ function handleChangeSecClassify(item,pitem){
 
 }
 
+const goYBPCCN=()=>{
+    let href = import.meta.env.VITE_CN_YB_PC
+    window.open(href,'_blank');
+}
+
+const userInfoGo=()=>{
+    let {href} = router.resolve("/my");
+    window.open(href,'_blank');
+}
+
+// TODO 绑定手机号
+const bindPhone=()=>{
+    let {href} = router.resolve({path:"/bindPhoneNo"});
+    window.open(href,'_blank');
+}
+const loginOut=()=>{
+    localStorage.removeItem('yben_token')
+    router.push('/login')
+}
+const passwordChange=()=>{
+    let {href} = router.resolve({path:"/passwordChange"});
+    window.open(href,'_blank');
+}
 
 </script>
 
 <template>
     <div class="roadshow-video-detail-page">
         <div class="header-wrap">
-            <div>
+            <div class="navigation-row">
                 <span style="color:#00459F;margin-right:30px;">MORE CONTENT>></span>
                 <span v-for="item in reportClassify" :key="item.id" style="margin-right:20px;cursor: pointer;" @click="goIndex(item)">{{item.classify_name}}</span>
             </div>
-            <div style="color:#00459F;cursor: pointer;" @click="$router.replace('/')">ABOUT US</div>
+            <!-- <div style="color:#00459F;cursor: pointer;" @click="$router.replace('/')">ABOUT US</div> -->
+            <div class="header-right">
+                <img @click="goYBPCCN" class="ybCN-jump" src="@/assets/icons/yb-cn.png" />
+                <el-popover
+                    popper-class="user-info-popper"
+                    placement="top-start"
+                    trigger="click">
+                    <template #reference>
+                        <div class="user-info-hover">
+                            <img src="@/assets/icons/avatar.svg" />
+                            <span>{{userInfo.Name}}</span>
+                        </div>
+                    </template>
+                    <div class="user-info-message">
+                        <div class="user-info-item">User Name:<span>{{userInfo.Name}}</span></div>
+                        <div class="user-info-item">Email Address:<span>{{userInfo.Email}}</span></div>
+                        <div class="user-info-item">Mobile Phone No:
+                            <span style="display: inline-flex;align-items: center;" v-if="userInfo.Mobile" @click="bindPhone">
+                                <span>{{ userInfo.CountryCode+' '+userInfo.Mobile }}</span>
+                                <img src="@/assets/icons/edit.svg" style="margin-left: 20px;height: 16px;cursor: pointer;" />
+                            </span>
+                            <span style="display: inline-flex;align-items: center;color: #1856A7;cursor: pointer;"
+                            @click="bindPhone" v-else>
+                                <img src="@/assets/icons/add-circle.svg" style="margin-right: 8px;height: 16px;" />
+                                <span>Add a phone number to your account</span>
+                            </span>
+                        </div>
+                        <div style="margin-top: 30px;display: flex;align-items: center;justify-content: space-between;">
+                            <el-button type="primary" @click="loginOut" size="large" style="width: 100px;background-color:#1856A7 ;">Sign out</el-button>
+                            <el-button plain @click="passwordChange" size="large" style="min-width: 100px;padding: 12px;">Update password</el-button>
+                        </div>
+                    </div>
+                </el-popover>
+            </div>
         </div>
         <div class="mobile-header-wrap">
             <img class="menu-icon" @click="showFilter=true;filterSize='100%'" src="@/assets/menu.svg" alt="">
             <span @click="$router.replace('/')">HORIZON INSIGHTS</span>
+            <img src="@/assets/icons/avatar.svg" class="user-info-avatar" @click="userInfoGo" />
         </div>
         <div class="detail-wrap" v-if="info">
             <div class="title">{{info.title}}</div>
@@ -172,6 +231,49 @@ function handleChangeSecClassify(item,pitem){
     font-size: 20px;
     z-index: 99;
     background-color: #fff;
+    .header-right{
+        display: flex;
+        align-items: center;
+        position: absolute;
+        top: 50%;
+        transform: translateY(-50%);
+        right: 17px;
+        z-index: 30;
+        cursor: pointer;
+        .ybCN-jump{
+            height: 30px;
+            cursor: pointer;
+            margin-right: 20px;
+        }
+        .user-info-hover{
+            display: flex;
+            align-items: center;
+            img{
+                height: 30px;
+                width: 30px;
+                margin-right: 10px;
+            }
+            span{
+                font-weight: 500;
+                font-size: 16px;
+                line-height: 18px;
+                color: #666666;
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                max-width: 130px;
+            }
+        }
+    }
+}
+@media (max-width: 900px){
+    .header-wrap{
+        .navigation-row{
+            span{
+                font-size: 16px;
+            }
+        }
+    }
 }
 .mobile-header-wrap{
     font-size: 17px;
@@ -189,7 +291,7 @@ function handleChangeSecClassify(item,pitem){
     position: relative;
     .menu-icon{
         position: absolute;
-        left: 20px;
+        left: 17px;
         top: 50%;
         transform: translateY(-50%);
         width: 17px;
@@ -201,6 +303,15 @@ function handleChangeSecClassify(item,pitem){
     }
     .mobile-header-wrap{
         display: block;
+        .user-info-avatar{
+            display: flex;
+            align-items: center;
+            position: absolute;
+            top: 50%;
+            transform: translateY(-50%);
+            right: 17px;
+            height: 18px;
+        }
     }
 }
 .detail-wrap{
@@ -237,4 +348,21 @@ function handleChangeSecClassify(item,pitem){
         }
     }
 }
+</style>
+<style lang="scss">
+.user-info-popper{
+    width: unset!important;
+    .user-info-message{
+        padding: 28px;
+        .user-info-item{
+            font-weight: 400;
+            font-size: 14px;
+            line-height: 20px;
+            color: #333333;
+            margin-bottom: 20px;
+            display: flex;
+            align-items: center;
+        }
+    }
+}
 </style>

+ 279 - 0
src/views/verification/bindPhoneNo.vue

@@ -0,0 +1,279 @@
+<script setup>
+import { reactive, ref } from 'vue';
+import {ElMessage,ElMessageBox} from 'element-plus'
+import { useRouter } from 'vue-router';
+import {CaretBottom} from '@element-plus/icons-vue'
+import {smsCodeSend,bindMobile,modifyMobile} from '@/api/auth.js'
+import { TEL_CODE_ARR } from '../../utils/constant';
+
+  const router = useRouter()
+
+  const bindPhoneRef = ref(null)
+  const bindPhoneMobileRef = ref(null)
+  
+  const newPhoneNoRef = ref(null)
+  const newPhoneNoMobileRef = ref(null)
+  
+  const userInfo = localStorage.getItem('user_info')?JSON.parse(localStorage.getItem('user_info')):{}
+
+  const bindPhone=reactive({
+    form:{
+      newPhoneNo:'',
+      newPhoneNoPre:'86',
+      verificationCode:''
+    },
+    rules:{
+      newPhoneNo:[{required: true, message:'Mobile phone No. cannot be null', trigger: 'blur'},
+      {validator:(rule,value,callback)=>{
+        if(value!='' && !Number(value) || value.indexOf('.')!=-1){
+          callback(new Error('Must be number'))
+        }else{
+          callback()
+        }
+      }, trigger: 'blur'}],
+      verificationCode:{required: true, message:'Verification code cannot be null', trigger: 'blur'}
+    }
+  })
+
+  let codeTimer=null
+  const codeInfo=reactive({
+    timeout:60,// 秒
+    isRequesting:false
+  })
+// ----------------------------------------------------方法
+  const sendVerCode=()=>{
+    let refTemp = ''
+    if(isPhone()){
+      refTemp=bindPhoneMobileRef.value
+    }else{
+      refTemp=bindPhoneRef.value
+    }
+    refTemp.validateField(['newPhoneNo']).then(res=>{
+      let codeParams={
+        Mobile:bindPhone.form.newPhoneNo,
+        Area_num:bindPhone.form.newPhoneNoPre || '86'
+      }
+      smsCodeSend(codeParams).then(res=>{
+        if(res.code!=200) return 
+        ElMessage.success('Verification code sent')
+        codeInfo.isRequesting=true
+        codeInfo.timeout--
+        codeTimer=setInterval(()=>{
+          if(codeInfo.timeout==1){
+            codeInfo.isRequesting=false
+            codeInfo.timeout=60
+            codeTimer=null
+          }
+          codeInfo.timeout--
+        },1000)
+      })
+    }).catch(err=>{
+      let errMessage=''
+      errMessage=err.newPhoneNo?err.newPhoneNo[0].message:''
+      if(errMessage){
+        ElMessage.warning(errMessage)
+      }
+    })
+  }
+
+  const bindSubmit=()=>{
+    let isPhoneVar = isPhone()
+    let refTemp = ''
+    if(isPhoneVar){
+      refTemp=bindPhoneMobileRef.value
+    }else{
+      refTemp=bindPhoneRef.value
+    }
+    refTemp.validate(valid=>{
+      if(valid){
+        let params=''
+        let mobileApi=''
+        if(userInfo.Mobile){
+          // 换绑
+          params={
+            OldMobile:userInfo.Mobile,
+            NewMobile:bindPhone.form.newPhoneNo,
+            SmsCode:bindPhone.form.verificationCode,
+            CountryCode:bindPhone.form.newPhoneNoPre || '86'
+          }
+          mobileApi = modifyMobile
+        }else{
+          params={
+            Mobile:bindPhone.form.newPhoneNo,
+            SmsCode:bindPhone.form.verificationCode,
+            CountryCode:bindPhone.form.newPhoneNoPre || '86'
+          }
+          mobileApi= bindMobile
+        }
+
+        mobileApi(params).then(res=>{
+          if(res.code==200){
+            userInfo.Mobile=bindPhone.form.newPhoneNo
+            localStorage.setItem('user_info',JSON.stringify(userInfo))
+            sessionStorage.setItem('transitionPageMessage','bindPhone')
+            router.replace('/transitionPage')
+          }
+          else if(res.code==4016){
+            if(isPhoneVar){
+              newPhoneNoMobileRef.value.validateMessage='Has been bound to another account.'
+              newPhoneNoMobileRef.value.validateState='error'
+            }else{
+              newPhoneNoRef.value.validateMessage='This mobile phone No. has been bound to another account.'
+              newPhoneNoRef.value.validateState='error'
+            }
+          }
+        })
+      }
+    })
+  }
+
+  const isPhone=()=>{
+    return !(window.innerWidth>768)
+  }
+
+</script>
+
+<template>
+    <div class="bind-phone-container">
+      <div class="fixed-header">
+        <span>
+          HORIZON INSIGHTS
+        </span>
+      </div>
+      <div class="page-title">
+        Add a phone number to your account
+      </div>
+      <div class="bind-phone-form-box" id="bind-phone-form-box">
+        <!-- PC端的表单 -->
+        <el-form :model="bindPhone.form" class="bind-phone-form" :rules="bindPhone.rules" ref="bindPhoneRef">
+          <el-form-item label="Original Mobile Phone No." label-width="176px" style="border: none;" v-if="userInfo.Mobile">
+            {{ userInfo.CountryCode+' '+userInfo.Mobile }}
+          </el-form-item>
+          <el-form-item prop="newPhoneNo" ref="newPhoneNoRef" size="large">
+            <div class="phone-item">
+              <el-select v-model="bindPhone.form.newPhoneNoPre" size="large" style="max-width: 90px;margin-right: 10px;" placeholder="Please select">
+                <el-option :label="item.label" :value="item.value" v-for="item in TEL_CODE_ARR" />
+              </el-select>
+              <el-input v-model.trim="bindPhone.form.newPhoneNo" size="large" style="flex-grow: 1;" placeholder="Please enter your phone number">
+                <template #suffix>
+                  <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                  @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                  <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+                </template>
+              </el-input>
+            </div>
+          </el-form-item>
+          <el-form-item prop="verificationCode" style="margin-bottom: 0;" size="large">
+            <el-input v-model="bindPhone.form.verificationCode" size="large" style="width: 360px;" placeholder="Please enter the verification code" class="form-item">
+            </el-input>
+          </el-form-item>
+        </el-form>
+        <!-- 移动端的表单 -->
+        <el-form :model="bindPhone.form" class="mobile-bind-phone-form" id="mobile-form-field" hide-required-asterisk label-position="top"
+        :rules="bindPhone.rules" label-width="70px" ref="bindPhoneMobileRef">
+          <el-form-item label="Original Mobile Phone No." label-width="165px" v-if="userInfo.Mobile">
+            <span style="margin-left: 15px;">{{ userInfo.CountryCode+' '+userInfo.Mobile }}</span>
+          </el-form-item>
+          <el-form-item prop="newPhoneNo" ref="newPhoneNoMobileRef" size="large" label-position="left">
+            <template #label>
+              <span style="margin-right: 10px;">Area Code</span>
+              <el-select v-model="bindPhone.form.newPhoneNoPre" :suffix-icon="CaretBottom"
+              style="max-width: 60px;" placeholder="Please select" size="large">
+                <el-option :label="item.label" :value="item.value" v-for="item in TEL_CODE_ARR" />
+              </el-select>
+            </template>
+              <el-input v-model.trim="bindPhone.form.newPhoneNo" size="large" style="flex-grow: 1;" placeholder="Please enter your phone number">
+                <template #suffix>
+                  <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                  @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                  <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+                </template>
+              </el-input>
+            <!-- </div> -->
+          </el-form-item>
+          <el-form-item prop="verificationCode" label="Verification code" style="margin-bottom: 0;" size="large">
+            <el-input v-model="bindPhone.form.verificationCode" size="large" style="width: 360px;" placeholder="Please enter the verification code" class="form-item">
+            </el-input>
+          </el-form-item>
+        </el-form>
+        <el-button type="primary" @click="bindSubmit" class="submit-button" size="large">Submit</el-button>
+        <div class="question-hint">For technical assistance, please contact stephanie@hzinsights.com</div>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped> 
+  .bind-phone-container{
+    padding-top: 60px;
+    .page-title{
+      text-align: center;
+      font-weight: 600;
+      font-size: 20px;
+      line-height: 28px;
+      color: #1856A7;
+      padding: 56px 0 48px;
+    }
+    .bind-phone-form-box{
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+      .bind-phone-form{
+        width: 360px;
+        .phone-item{
+          width: 100%;
+          display: flex;
+          align-items: center;
+        }
+      }
+      .mobile-bind-phone-form{
+        display: none;
+      }
+      .submit-button{
+        margin: 40px 0 30px;
+        width: 360px;
+        background-color: #1856A7;
+      }
+      .question-hint{
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 20px;
+        color: #999999;
+      }
+    }
+  }
+  @media screen and (max-width: 768px) {
+    .bind-phone-container{
+      padding: 50px 30px 30px;
+      .page-title{
+        text-align: left;
+        padding: 50px 0 30px;
+        font-weight: 400;
+        font-size: 16px;
+        line-height: 22px;
+        color: #333333;
+      }
+      .bind-phone-form-box{
+        display: flex;
+        justify-content: center;
+        flex-direction: column;
+        align-items: center;
+        .bind-phone-form{
+          display: none;
+        }
+        .mobile-bind-phone-form{
+          display: block;
+          width: 100%;
+        }
+        .submit-button{
+          margin: 20px 0;
+          width: 100%;
+        }
+        .question-hint{
+          font-size: 12px;
+          line-height: 17px;
+        }
+      }
+    }
+  }
+</style>

+ 571 - 0
src/views/verification/login.vue

@@ -0,0 +1,571 @@
+<script setup>
+import {ref,reactive} from 'vue'
+import {ElMessageBox,ElMessage} from 'element-plus'
+import md5 from 'md5';
+import { useRouter } from 'vue-router';
+import {CaretBottom} from '@element-plus/icons-vue'
+import {loginApi,registerApi,emailCodeSend} from '@/api/auth.js'
+import { passwordDigitValidator } from './utils/validators';
+// passwordDigitValidator-密码6位
+import { TEL_CODE_ARR } from '../../utils/constant';
+
+
+  const router = useRouter()
+  const loginFormRef=ref(null)
+  const loginFormMobileRef=ref(null)
+  
+  const isPhone=()=>{
+    return !(window.innerWidth>768)
+  }
+  const loginForm=reactive({
+    email:'',
+    phone:'',
+    phonePre:'86',
+    password:'',
+  })
+
+  // 隐私政策是否阅读
+  const hasChecked=ref(false)
+
+  const loginFormRules=reactive({
+    email:[{required: true, message:'Email address cannot be null', trigger: 'blur'},
+    {type:'email',message:'Email address format is incorrect', trigger: 'blur'}],
+    phone:[{required: true, message:'Mobile phone No. cannot be null', trigger: 'blur'},
+    {validator:(rule,value,callback)=>{
+      if(!Number(value) || value.indexOf('.')!=-1){
+        callback(new Error('Must be number'))
+      }else{
+        callback()
+      }
+    }, trigger: 'blur'}],
+    password:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+    {
+      validator:passwordDigitValidator,trigger:'blur'
+    }]
+  })
+
+  const login=()=>{
+    let refTemp = ''
+    if(isPhone()){
+      refTemp=loginFormMobileRef.value
+    }else{
+      refTemp=loginFormRef.value
+    }
+    refTemp.validate(valid =>{
+      if(valid){
+        // if(!hasChecked.value){
+        //   ElMessage.warning('Please check the Privacy policy')
+        //   return
+        // }
+        let params={
+          CountryCode:loginForm.phonePre,
+          Account:loginType.value==1?loginForm.email:loginForm.phone,
+          Type:loginType.value,
+          Password:md5(loginForm.password)
+        }
+        loginApi(params).then(res=>{
+          // console.log(res);
+          if(res.code == 200){
+            localStorage.setItem('yben_token',res.data.access_token)
+            let userInfo = {
+              user_id:res.data.user_id,
+              Name:res.data.Name,
+              Email:res.data.Email,
+              Mobile:res.data.Mobile,
+              CountryCode:res.data.CountryCode
+            }
+            localStorage.setItem('user_info',JSON.stringify(userInfo))
+
+            let redirectPath=sessionStorage.getItem('login_redirect') || '/'
+            sessionStorage.removeItem('login_redirect')
+            router.replace(redirectPath)
+          }
+        })
+      }
+    })
+  }
+  // 去注册页
+  const registerPageGo=()=>{
+    let {href} = router.resolve("/register");
+    window.open(href,'_blank');
+  }
+  const psdMissingPageGo=()=>{
+    let {href} = router.resolve("/passwordMissing");
+    window.open(href,'_blank');
+  } 
+  // 登录方式 1-邮箱 2-手机号
+  const loginType=ref(1)
+  // 操作方式 1-登录 2-注册
+  const operationType=ref(1)
+  const changeLoginType=(type)=>{
+    if(type == loginType.value) return 
+    loginType.value=type
+  }
+  const changeOperationType=(type)=>{
+    if(type == operationType.value) return 
+    operationType.value=type
+  }
+
+  // --------------------------------------------------移动端 - 注册
+  // 第几步
+  const step=ref(1)
+
+  const registerForm=reactive({
+    userName:'',
+    companyName:'',
+    email:'',
+    verificationCode:'',
+    password:'',
+    passwordConfirm:''
+  })
+  const registerFormRef=ref(null)
+  const registerFormRules=reactive({
+    userName:{required: true, message:'User name cannot be null', trigger: 'blur'},
+    companyName:{required: true, message:'Company name cannot be null', trigger: 'blur'},
+    email:[{required: true, message:'Email address cannot be null', trigger: 'blur'},
+    {type:'email',message:'Email address format is incorrect', trigger: 'blur'}],
+    verificationCode:{required: true, message:'Verification code cannot be null', trigger: 'blur'},
+    password:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+    {
+      validator:passwordDigitValidator,trigger:'blur'
+    }],
+    passwordConfirm:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+    {validator:(rule,value,callback)=>{
+      if(registerForm.passwordConfirm!==registerForm.password){
+        callback(new Error('Passwords do not match'))
+      }else{
+        callback()
+      }
+    }, trigger: 'blur'}]
+  })
+
+  let codeTimer=null
+  const codeInfo=reactive({
+    timeout:60,// 秒
+    isRequesting:false
+  })
+
+  const sendVerCode=()=>{
+    registerFormRef.value.validateField(['userName','email']).then(res=>{
+      let codeParams={
+        Name:registerForm.userName,
+        Email:registerForm.email
+      }
+      emailCodeSend(codeParams).then(res=>{
+        if(res.code!=200) return  
+        ElMessage.success('Verification code sent')
+        codeInfo.isRequesting=true
+        codeInfo.timeout--
+        codeTimer=setInterval(()=>{
+          if(codeInfo.timeout==1){
+            codeInfo.isRequesting=false
+            codeInfo.timeout=60
+            codeTimer=null
+          }
+          codeInfo.timeout--
+        },1000)
+      })
+    }).catch(err=>{
+      let errMessage=''
+      errMessage=err.userName?err.userName[0].message:
+                  err.email?err.email[0].message:''
+      if(errMessage){
+        ElMessage.warning(errMessage)
+      }
+    })
+  }
+
+  const register=()=>{
+    registerFormRef.value.validate(valid=>{
+      if(valid){
+        if(step.value==1){
+          // 进入下一步
+          step.value=2
+          return
+        }
+        // if(!hasChecked.value){
+        //   ElMessage.warning('Please check the Privacy policy')
+        //   return
+        // }
+        let params={
+          CompanyName:registerForm.companyName,
+          Name:registerForm.userName,
+          Email:registerForm.email,
+          SmsCode:registerForm.verificationCode,
+          Password:md5(registerForm.password)
+        }
+        registerApi(params).then(res=>{
+          console.log(res.code);
+          if(res.code==200){
+            localStorage.setItem('yben_token',res.data.access_token)
+            let userInfo = {
+              user_id:res.data.user_id,
+              Name:res.data.Name,
+              Email:res.data.Email,
+              Mobile:res.data.Mobile,
+              CountryCode:res.data.CountryCode
+            }
+            localStorage.setItem('user_info',JSON.stringify(userInfo))
+            sessionStorage.setItem('transitionPageMessage','register')
+            router.replace('/transitionPage')
+          }
+        })
+      }
+    })
+  }
+
+</script>
+<template>
+  <div class="login-container" id="login-container">
+    <!-- PC端 -->
+    <div id="PC-part">
+      <div class="login_background_image">
+      </div>
+      <div class="login-container-main">
+        <div class="login-container-right">
+          <div class="login-container-header">
+            <span>HORIZON INSIGHTS</span>
+          </div>
+          <div class="login-container-body">
+            <div class="login-type-tab">
+              <span :class="loginType==1?'active-tab':''" @click="changeLoginType(1)">Login with email</span>
+              <div class="tab-split"></div>
+              <span :class="loginType==2?'active-tab':''" @click="changeLoginType(2)">phone number</span>
+            </div>
+            <el-form :model="loginForm" class="login-form" :rules="loginFormRules" ref="loginFormRef" style="width: 360px;">
+              <el-form-item prop="email" v-if="loginType==1">
+                <el-input v-model.trim="loginForm.email" size="large" placeholder="Please enter your email address" style="width: 360px;"></el-input>
+              </el-form-item>
+              <el-form-item prop="phone" v-else >
+                <div class="phone-item">
+                  <el-select v-model="loginForm.phonePre" style="max-width: 90px;margin-right: 10px;" placeholder="Please select" size="large">
+                    <el-option :label="item.label" :value="item.value" v-for="item in TEL_CODE_ARR" />
+                  </el-select>
+                  <el-input v-model.trim="loginForm.phone" style="flex-grow: 1;" placeholder="Please enter your phone number" size="large">
+                  </el-input>
+                </div>
+              </el-form-item>
+              <el-form-item prop="password" style="width: 360px;">
+                <el-input v-model="loginForm.password" size="large" type="password" placeholder="Please enter a password"></el-input>
+              </el-form-item>
+            </el-form>
+            <div class="register-message-row">
+              <div class="register-message">
+                New to Horizon Insights? <span @click="registerPageGo">Create an account</span>
+              </div>
+              <div class="password-miss" @click="psdMissingPageGo">
+                Forgot your password?
+              </div>
+            </div>
+            <el-button class="submit-button" type="primary" size="large" @click="login">Log in now</el-button>
+            <!-- <div class="privacy-policy">
+              <el-checkbox v-model="hasChecked" label="I have read and agree" style="color:#333333"></el-checkbox>
+              <span @click="privacyPolicy" class="policy">the Horizon Insights Privacy Policy</span>
+            </div> -->
+            <div class="login-hint">For technical assistance, please contact stephanie@hzinsights.com</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- 移动端 -->
+    <div id="mobile-part">
+      <div class="fixed-header">
+        <span>
+          HORIZON INSIGHTS
+        </span>
+      </div>
+      <div class="verification-body">
+        <div class="login-type-tab" v-if="step==1 || operationType==1">
+          <span :class="operationType==1?'active-tab':''" @click="changeOperationType(1)">Login</span>
+          <div class="tab-split"></div>
+          <span :class="operationType==2?'active-tab':''" @click="changeOperationType(2)">Create account</span>
+        </div>
+        <div class="register-psd-hint" v-else>
+          Please set password
+        </div>
+        <!-- 登录 -->
+        <div class="mobile-login-box" v-if="operationType==1">
+          <el-form :model="loginForm" label-width="55px" label-position="top" id="mobile-form-field" :rules="loginFormRules" 
+          ref="loginFormMobileRef" hide-required-asterisk>
+            <el-form-item prop="email" v-if="loginType==1" label="Email Address" size="large" >
+              <el-input v-model.trim="loginForm.email" size="large" placeholder="Please enter your email address"></el-input>
+            </el-form-item>
+            <el-form-item prop="phone" v-else >
+              <template #label>
+                <span style="margin-right: 10px;">Area Code</span>
+                <el-select v-model="loginForm.phonePre" :suffix-icon="CaretBottom"
+                style="max-width: 55px;" placeholder="Please select" size="large">
+                  <el-option :label="item.label" :value="item.value" v-for="item in TEL_CODE_ARR" />
+                </el-select>
+              </template>
+              <el-input v-model.trim="loginForm.phone" style="flex-grow: 1;" placeholder="Please enter your phone number" size="large"></el-input>
+            </el-form-item>
+            <el-form-item prop="password" label="Password" size="large">
+              <el-input v-model="loginForm.password" size="large"  type="password" placeholder="Please enter a password"></el-input>
+            </el-form-item>
+          </el-form>
+          <div class="mobile-login-type-change">
+            <span @click="changeLoginType(loginType==1?2:1)">Login with {{ loginType==1?"phone number":"email" }} instead</span>
+            <span @click="psdMissingPageGo" style="color:#1856A7">Forgot your password?</span>
+          </div>
+          <el-button type="primary" class="submit-button" @click="login" size="large">Log in now</el-button>
+          <!-- <div class="privacy-policy">
+            <el-checkbox v-model="hasChecked" label="I have read and agree"></el-checkbox>
+            <span @click="privacyPolicy" class="policy">the Horizon Insights Privacy Policy</span>
+          </div> -->
+          <div class="mobile-login-hint">For technical assistance, please contact stephanie@hzinsights.com</div>
+        </div>
+        <!-- 注册 -->
+        <div class="mobile-register-box" v-else>
+          <el-form :model="registerForm" label-position="top" id="mobile-form-field" 
+          :rules="registerFormRules" ref="registerFormRef" hide-required-asterisk>
+            <el-form-item prop="userName" label="Name" size="large" v-if="step==1">
+              <el-input v-model.trim="registerForm.userName" size="large" placeholder="Please enter your name"></el-input>
+            </el-form-item>
+            <el-form-item prop="companyName" label="Company" size="large" v-if="step==1">
+              <el-input v-model="registerForm.companyName" size="large" placeholder="Please enter the company name"></el-input>
+            </el-form-item>
+            <el-form-item prop="email" label="Email Address" size="large" v-if="step==1">
+              <el-input v-model="registerForm.email" size="large" placeholder="Please enter your email address">
+                <template #suffix>
+                  <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                  @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                  <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+                </template>
+              </el-input>
+            </el-form-item>
+            <el-form-item prop="verificationCode" label="Verification code" size="large" v-if="step==1">
+              <el-input v-model="registerForm.verificationCode" size="large" placeholder="Please enter the verification code"></el-input>
+            </el-form-item>
+            <el-form-item prop="password" label="Password" size="large" v-if="step==2">
+              <el-input v-model="registerForm.password" size="large" type="password" placeholder="Please enter at least 6 passwords"></el-input>
+            </el-form-item>
+            <el-form-item prop="passwordConfirm" style="margin-bottom: 0;" label="Password" size="large" v-if="step==2">
+              <el-input v-model="registerForm.passwordConfirm" size="large" type="password" placeholder="Please enter the password again"></el-input>
+            </el-form-item>
+          </el-form>
+          <el-button type="primary" class="submit-button" @click="register" size="large" v-if="step==1">Next</el-button>
+          <el-button type="primary" class="submit-button" size="large" v-if="step==2" @click="register">Submit</el-button>
+          <!-- <div class="privacy-policy" v-if="step==2">
+            <el-checkbox v-model="hasChecked" label="I have read and agree" style="color:#333333" ></el-checkbox>
+            <span @click="privacyPolicy" class="policy">the Horizon Insights Privacy Policy</span>
+          </div> -->
+          <div class="mobile-login-hint">For technical assistance, please contact stephanie@hzinsights.com</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+  
+<style lang="scss" scoped>
+  .login-container{
+    .login-type-tab{
+      display: flex;
+      align-items: center;
+      margin-bottom: 36px;
+      .tab-split{
+        height: 18px;
+        width: 1px;
+        background-color: #1856A7;
+        margin: 0 20px;
+      }
+      span{
+        display: inline-block;
+        cursor: pointer;
+        font-size: 18px;
+        line-height: 25px;
+        color: #999999;
+      }
+      .active-tab{
+        color: #1856A7;
+        font-weight: 600;
+      }
+    }
+    .privacy-policy{
+      font-weight: 400;
+      font-size: 14px;
+      line-height: 22px;
+      margin-top: 25px;
+      display: flex;
+      align-items: center;
+      flex-wrap: wrap;
+      .policy{
+        color: #1856A7;
+        cursor: pointer;
+        margin-left: 12px;
+      }
+    }
+    .submit-button{
+      margin-top: 26px;
+      width: 100%;
+      background-color: #1856A7;
+    }
+    #PC-part{
+      width: 100%;
+      height:100vh;
+      .login_background_image{
+        position: fixed;
+        top: 0;
+        left: 0;
+        height: 100vh;
+        width: 100vw;
+        background-color: rgba(24, 86, 167, 0.2);
+        z-index: -1;
+        background-image: url('@/assets/login_bci.png');
+        background-repeat: no-repeat;
+        background-size: cover;
+      }
+      .login-container-main{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+
+        .login-container-right{
+          background-color: white;
+          width: 650px;
+          height: 550px;
+          opacity: 0.95;
+          border: 1px solid #DCDFE6;
+          border-radius: 8px;
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          .login-container-header{
+            height: 68px;
+            border-bottom:  solid 1px #DCDFE6;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 100%;
+            span{
+              font-weight: 600;
+              font-size: 20px;
+              color: #1856A7;
+            }
+          }
+          .login-container-body{
+            width: 430px;
+            padding-top: 45px;
+            .login-form{
+              .phone-item{
+                width: 100%;
+                display: flex;
+                align-items: center;
+              }
+            }
+            .register-message-row{
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+              .register-message{
+                font-size: 14px;
+                color: #333333;
+                span{
+                  color: #1856A7;
+                  cursor: pointer;
+                }
+              }
+              .password-miss{
+                font-size: 14px;
+                // color: #333333;;
+                cursor: pointer;
+                color: #1856A7;
+              }
+            }      
+            .login-hint{
+              font-size: 14px;
+              line-height: 20px;
+              color: #999999;
+              margin-top: 25px;
+            }
+          }
+        }
+      }
+    }
+    #mobile-part{
+      display: none;
+    }
+  }
+  // 移动端样式
+  @media screen and (max-width: 768px) {
+    .login-container{
+      #PC-part{
+        display: none;
+      }
+      #mobile-part{
+        background-color: white;
+        display:block ;
+        width: 100%;
+        height:100%;
+        min-height: 100%;
+        .verification-body{
+          width: 100%;
+          height: 100%;
+          padding: 100px 30px 30px;
+          .login-type-tab{
+            margin-bottom: 30px;
+            .tab-split{
+              height: 16px;
+              width: 2px;
+              background-color: #1856A7;
+              margin: 0 20px;
+            }
+            span{
+              font-size: 16px;
+              line-height: 22px;
+            }
+          }
+          .register-psd-hint{
+            font-weight: 400;
+            font-size: 16px;
+            line-height: 22px;
+            color: #333333;
+            margin-bottom: 40px;
+          }
+          .mobile-login-box{
+            .mobile-login-type-change{
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+              span{
+                font-weight: 400;
+                font-size: 12px;
+                line-height: 17px;
+                color: #1856A7;
+              }
+            }
+            .mobile-login-hint{
+              font-weight: 400;
+              font-size: 12px;
+              line-height: 17px;
+              color: #999999;
+              margin-top: 20px;
+            }
+          }
+          .mobile-register-box{
+            .mobile-login-type-change{
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+              span{
+                font-weight: 400;
+                font-size: 12px;
+                line-height: 17px;
+                color: #1856A7;
+              }
+            }
+            .mobile-login-hint{
+              font-weight: 400;
+              font-size: 12px;
+              line-height: 17px;
+              color: #999999;
+              margin-top: 20px;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 100 - 0
src/views/verification/mobileMyPage.vue

@@ -0,0 +1,100 @@
+<script setup>
+import { useRouter } from 'vue-router';
+
+const router = useRouter()
+
+  const userForm=localStorage.getItem('user_info')?JSON.parse(localStorage.getItem('user_info')):{}
+
+
+  const bindPhone=()=>{
+    let {href} = router.resolve({path:"/bindPhoneNo"});
+    window.open(href,'_blank');
+  }
+  const loginOut=()=>{
+      localStorage.removeItem('yben_token')
+      router.push('/login')
+  }
+  const passwordChange=()=>{
+      let {href} = router.resolve({path:"/passwordChange"});
+      window.open(href,'_blank');
+  }
+</script>
+
+<template>
+  <div class="user-container" id="user-container">
+    <div class="fixed-header">
+      <span>
+        HORIZON INSIGHTS
+      </span>
+    </div>
+    <div class="user-info-box">
+      <img src="@/assets/icons/avatar.svg" class="user-avatar">
+      <div class="user-info">
+        <el-form :model="userForm" id="mobile-form-field" label-position="top">
+          <el-form-item label="User Name" size="large">
+            {{ userForm.Name }}
+          </el-form-item>
+          <el-form-item label="Email Address" size="large">
+            {{ userForm.Email }}
+          </el-form-item>
+          <el-form-item label="Mobile Phone No" size="large">
+            <div class="phone-form-item" @click="bindPhone">
+              <template v-if="!userForm.Mobile">
+                <img src="@/assets/icons/add-square.svg" style="height: 16px;margin-right: 8px;">
+                <span>Add a phone number to your account</span>
+              </template>
+              <template v-else>
+                {{ userForm.CountryCode+' '+userForm.Mobile }}
+                <img src="@/assets/icons/edit.svg" style="height: 16px;margin-left: 20px;">
+              </template>
+            </div>
+          </el-form-item>
+        </el-form>
+        <el-button type="primary" class="form-button" size="large" @click="loginOut">Sign out</el-button>
+        <el-button class="form-button" size="large" style="margin-left: 0;" @click="passwordChange">Update password</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+  
+<style lang="scss" scoped>
+  .user-container{
+    padding: 50px 30px 30px;
+    .user-info-box{
+      padding-top: 50px;
+      text-align: center;
+      .user-avatar{
+        height: 60px;
+      }
+      .user-info{
+        margin-top: 20px;
+        .phone-form-item{
+          display: flex;
+          align-items: center;
+          span{
+            font-weight: 400;
+            font-size: 14px;
+            line-height: 20px;
+            color: #1856A7;
+          }
+        }
+        .form-button{
+          width: 100%;
+          margin-top: 20px;
+        }
+      }
+    }
+  }
+</style>
+<style lang="scss">
+.user-container{
+  .el-form-item__label{
+    padding: 0;
+    margin-right: 14px;
+    color: #333333;
+  }
+  .el-form-item__content{
+    color: #333333;
+  }
+}
+</style>

+ 188 - 0
src/views/verification/passwordChange.vue

@@ -0,0 +1,188 @@
+<script setup>
+import { reactive, ref } from 'vue';
+import {ElMessage,ElMessageBox} from 'element-plus'
+import { useRouter } from 'vue-router';
+import {psdModify} from '@/api/auth.js'
+import md5 from 'md5';
+import { passwordDigitValidator } from './utils/validators';
+// passwordDigitValidator-密码6位
+
+  const router = useRouter()
+
+  const psdChangeRef = ref(null)
+  const psdChangeMobileRef = ref(null)
+
+  const isPhone=()=>{
+    return !(window.innerWidth>768)
+  }
+
+  const psdChange=reactive({
+    form:{
+      passwordOrigin:'',
+      password:'',
+      passwordConfirm:'',
+    },
+    rules:{
+      passwordOrigin:{required: true, message:'Original password cannot be null', trigger: 'blur'},
+      password:[{required: true, message:'New password cannot be null', trigger: 'blur'},
+      {
+        validator:passwordDigitValidator,trigger:'blur'
+      }],
+      passwordConfirm:[{required: true, message:'New Password cannot be null', trigger: 'blur'},
+      {validator:(rule,value,callback)=>{
+        if(psdChange.form.passwordConfirm!==psdChange.form.password){
+          callback(new Error('Passwords do not match'))
+        }else{
+          callback()
+        }
+      }, trigger: 'blur'}],
+    }
+  })
+
+
+// ----------------------------------------------------方法
+
+  const psdMissingPageGo=()=>{
+    let {href} = router.resolve("/passwordMissing");
+    window.open(href,'_blank');
+  }
+  const submit=()=>{
+    let refTemp = ''
+    if(isPhone()){
+      refTemp=psdChangeMobileRef.value
+    }else{
+      refTemp=psdChangeRef.value
+    }
+    refTemp.validate(valid=>{
+      if(valid){
+        let params={
+          OldPwd:md5(psdChange.form.passwordOrigin),
+          NewPwd:md5(psdChange.form.password)
+        }
+        psdModify(params).then(res=>{
+          if(res.code == 200){
+            ElMessage({
+              message: 'Modify successfully, please log in again',
+              type: 'success',
+              duration:1000
+            })
+            setTimeout(()=>{
+              localStorage.removeItem('yben_token')
+              router.replace('/login')
+            },1000)
+          }
+        })
+      }
+    })
+  }
+
+</script>
+
+<template>
+    <div class="psdChange-container">
+      <div class="fixed-header">
+        <span>
+          HORIZON INSIGHTS
+        </span>
+      </div>
+      <div class="page-title">
+        Update password
+      </div>
+      <div class="regitser-form-box" id="regitser-form-box">
+        <!-- PC端表单 -->
+        <el-form :model="psdChange.form" class="regitser-form" :rules="psdChange.rules" ref="psdChangeRef">
+          <el-form-item prop="passwordOrigin">
+            <el-input v-model="psdChange.form.passwordOrigin" size="large" type="password" placeholder="Please enter the original password">
+              <template #suffix>
+                <span style=" color:#1856A7;cursor: pointer;white-space: nowrap;" 
+                @click="psdMissingPageGo">Forgot your password?</span>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item prop="password">
+            <el-input v-model="psdChange.form.password" size="large" type="password" placeholder="Please enter at least 6 passwords"></el-input>
+          </el-form-item>
+          <el-form-item prop="passwordConfirm" style="margin-bottom: 0;">
+            <el-input v-model="psdChange.form.passwordConfirm" size="large" type="password" placeholder="Please enter the new password again"></el-input>
+          </el-form-item>
+        </el-form>
+        <!-- 移动端表单 -->
+        <el-form :model="psdChange.form" class="mobile-regitser-form" id="mobile-form-field"
+        :rules="psdChange.rules" ref="psdChangeMobileRef" hide-required-asterisk label-position="top">
+          <el-form-item prop="passwordOrigin" label="Original Password" size="large">
+            <el-input v-model="psdChange.form.passwordOrigin" size="large" type="password" placeholder="Please enter the original password">
+              <template #suffix>
+                <span style=" color:#1856A7;cursor: pointer;white-space: nowrap;" 
+                @click="psdMissingPageGo">Forgot your password?</span>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item prop="password" size="large" label="Password">
+            <el-input v-model="psdChange.form.password" size="large" type="password" placeholder="Please enter at least 6 passwords"></el-input>
+          </el-form-item>
+          <el-form-item prop="passwordConfirm" style="margin-bottom: 0;" size="large" label="Password">
+            <el-input v-model="psdChange.form.passwordConfirm" size="large" type="password" placeholder="Please enter the new password again"></el-input>
+          </el-form-item>
+        </el-form>
+        <el-button type="primary" @click="submit" class="psdChange-button" size="large">Submit</el-button>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped> 
+  .psdChange-container{
+    padding-top: 68px;
+    .page-title{
+      text-align: center;
+      font-weight: 600;
+      font-size: 20px;
+      line-height: 28px;
+      color: #1856A7;
+      padding: 56px 0 48px;
+    }
+    .regitser-form-box{
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+      .regitser-form{
+        width: 360px;
+      }
+      .mobile-regitser-form{
+        display: none;
+      }
+      .psdChange-button{
+        margin-top: 40px;
+        width: 360px;
+        background-color: #1856A7;
+      }
+    }
+  }
+  @media screen and (max-width: 768px) {
+    .psdChange-container{
+      padding: 50px 30px 30px;
+      .page-title{
+        text-align: left;
+        padding: 50px 0 30px;
+        font-weight: 400;
+        font-size: 16px;
+        line-height: 22px;
+        color: #333333;
+      }
+      .regitser-form-box{
+        .regitser-form{
+          display: none;
+        }
+        .mobile-regitser-form{
+          display: block;
+          width: 100%;
+        }
+        .psdChange-button{
+          margin-top: 40px;
+          width: 100%;
+          background-color: #1856A7;
+        }
+      }
+    }
+  }
+</style>

+ 388 - 0
src/views/verification/passwordMissing.vue

@@ -0,0 +1,388 @@
+<script setup>
+import { reactive, ref } from 'vue';
+import {ElMessage,ElMessageBox} from 'element-plus'
+import { useRouter } from 'vue-router';
+import {CaretBottom} from '@element-plus/icons-vue'
+import {smsCodeSend,emailCodeSend,psdMissingApi} from '@/api/auth.js'
+import md5 from 'md5';
+import { passwordDigitValidator } from './utils/validators';
+import { TEL_CODE_ARR } from '../../utils/constant';
+
+  const router = useRouter()
+
+  const psdMissingRef = ref(null)
+  const psdMissingMobileRef = ref(null)
+  const isPhone=()=>{
+    return !(window.innerWidth>768)
+  }
+
+  const psdMissing=reactive({
+    form:{
+      email:'',
+      phone:'',
+      phonePre:'86',
+      password:'',
+      passwordConfirm:'',
+      verificationCode:''
+    },
+    rules:{
+      email:[{required: true, message:'Email address cannot be null', trigger: 'blur'},
+      {type:'email',message:'Email address format is incorrect', trigger: 'blur'}],
+      phone:[{required: true,message:'Mobile phone No. cannot be null', trigger: 'blur'},
+      {validator:(rule,value,callback)=>{
+        if(value!='' && !Number(value) || value.indexOf('.')!=-1){
+          callback(new Error('Must be number'))
+        }else{
+          callback()
+        }
+      }, trigger: 'blur'}],
+      password:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+      {
+        validator:passwordDigitValidator,trigger:'blur'
+      }],
+      passwordConfirm:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+      {validator:(rule,value,callback)=>{
+        if(psdMissing.form.passwordConfirm!==psdMissing.form.password){
+          callback(new Error('Passwords do not match'))
+        }else{
+          callback()
+        }
+      }, trigger: 'blur'}],
+      verificationCode:{required: true, message:'Verification code cannot be null', trigger: 'blur'}
+
+    }
+  })
+
+  let codeTimer=null
+  const codeInfo=reactive({
+    timeout:60,// 秒
+    isRequesting:false
+  })
+
+// ----------------------------------------------------方法
+
+const loginType=ref(1)
+const changeLoginType=(type)=>{
+  if(type == loginType.value) return 
+  loginType.value=type
+  // 重置数据
+  psdMissing.form={
+    email:'',
+    phone:'',
+    phonePre:'86',
+    password:'',
+    passwordConfirm:'',
+    verificationCode:''
+  }
+  codeInfo.timeout=60
+  codeInfo.isRequesting=false
+  codeTimer=null
+  let refTemp;
+  if(isPhone()){
+    refTemp=psdMissingMobileRef.value
+  }else{
+    refTemp=psdMissingRef.value
+  }
+  refTemp.clearValidate()
+}
+
+  const sendVerCode=()=>{
+    let refTemp = ''
+    if(isPhone()){
+      refTemp=psdMissingMobileRef.value
+    }else{
+      refTemp=psdMissingRef.value
+    }
+    refTemp.validateField(['phone','email']).then(async res=>{
+      let resp=''
+      // 邮箱验证
+      if(loginType.value==1){
+        let params={
+          Name:psdMissing.form.userName||'',
+          Email:psdMissing.form.email
+        }
+        resp=await emailCodeSend(params)
+      }else{
+        let params={
+          Mobile:psdMissing.form.phone,
+          Area_num:psdMissing.form.phonePre ||'86'
+        }
+        resp=await smsCodeSend(params)
+      }
+      if(resp.code==200){
+      codeInfo.isRequesting=true
+      codeInfo.timeout--
+      codeTimer=setInterval(()=>{
+        if(codeInfo.timeout==1){
+          codeInfo.isRequesting=false
+          codeInfo.timeout=60
+          codeTimer=null
+        }
+        codeInfo.timeout--
+      },1000)
+      // TODO 发送验证码
+      ElMessage.success('Verification code sent')
+      }
+    }).catch(err=>{
+      let errMessage=''
+      errMessage=err.phone?err.phone[0].message:
+                  err.email?err.email[0].message:''
+      if(errMessage){
+        ElMessage.warning(errMessage)
+      }
+    })
+  }
+
+  const psdMissingSubmit=()=>{
+    let refTemp = ''
+    if(isPhone()){
+      refTemp=psdMissingMobileRef.value
+    }else{
+      refTemp=psdMissingRef.value
+    }
+    refTemp.validate(valid=>{
+      if(valid){
+        let params={
+          CountryCode:loginType.value==1?undefined:psdMissing.form.phonePre,
+          type:loginType.value,
+          Account:loginType.value==1?psdMissing.form.email:psdMissing.form.phone,
+          Password:md5(psdMissing.form.password),
+          SmsCode:psdMissing.form.verificationCode
+        }
+        psdMissingApi(params).then(res=>{
+          if(res.code==200){
+            ElMessage({
+              message: 'Modified successfully',
+              type: 'success',
+              duration:1000
+            })
+            setTimeout(()=>{
+              localStorage.removeItem('yben_token')
+              router.replace('/login')
+            },1000)
+          }
+        })
+      }
+    })
+  }
+  const loginPageGo=()=>{
+    let {href} = router.resolve("/login");
+    window.open(href,'_blank');
+  }
+
+</script>
+
+<template>
+    <div class="psdMissing-container">
+      <div class="fixed-header">
+        <span>
+          HORIZON INSIGHTS
+        </span>
+      </div>
+      <div class="login-type-tab">
+        <span :class="loginType==1?'active-tab':''" @click="changeLoginType(1)">Email verification</span>
+        <div class="tab-split"></div>
+        <span :class="loginType==2?'active-tab':''" @click="changeLoginType(2)">Phone number verification</span>
+      </div>
+      <div class="regitser-form-box" id="regitser-form-box">
+        <!-- PC端表单 -->
+        <el-form :model="psdMissing.form" class="missing-psd-form" :rules="psdMissing.rules" ref="psdMissingRef">
+          <el-form-item prop="phone" v-if="loginType==2">
+            <div class="phone-item">
+              <el-select v-model="psdMissing.form.phonePre" size="large" style="max-width: 90px;margin-right: 10px;" placeholder="Please select">
+                <el-option :label="item.label" :value="item.value" v-for="item in TEL_CODE_ARR" />
+              </el-select>
+              <el-input v-model.trim="psdMissing.form.phone" size="large" style="flex-grow: 1;height: 40px;" placeholder="Please enter your phone number">
+                <template #suffix>
+                  <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                  @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                  <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+                </template>
+              </el-input>
+            </div>
+          </el-form-item>
+          <el-form-item prop="email" v-else>
+            <el-input v-model="psdMissing.form.email" size="large" style="height: 40px;" placeholder="Please enter your email address" class="form-item">
+              <template #suffix>
+                <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item prop="verificationCode" >
+            <el-input v-model="psdMissing.form.verificationCode" size="large" placeholder="Please enter the verification code" class="form-item"></el-input>
+          </el-form-item>
+          <el-form-item prop="password">
+            <el-input v-model="psdMissing.form.password" size="large" type="password" placeholder="Please enter at least 6 passwords" class="form-item"></el-input>
+          </el-form-item>
+          <el-form-item prop="passwordConfirm" style="margin-bottom: 0;">
+            <el-input v-model="psdMissing.form.passwordConfirm" size="large" type="password" placeholder="Please enter the password again" class="form-item"></el-input>
+          </el-form-item>
+        </el-form>
+        <!-- 移动端表单 -->
+        <el-form :model="psdMissing.form" class="mobile-missing-psd-form" id="mobile-form-field"
+        label-position="top" :rules="psdMissing.rules" ref="psdMissingMobileRef" hide-required-asterisk>
+          <el-form-item prop="phone" v-if="loginType==2" size="large">
+            <template #label>
+              <span style="margin-right: 10px;">Area Code</span>
+              <el-select v-model="psdMissing.form.phonePre" :suffix-icon="CaretBottom"
+              style="max-width: 55px;" placeholder="Please select" size="large">
+                <el-option :label="item.label" :value="item.value" v-for="item in TEL_CODE_ARR" />
+              </el-select>
+            </template>
+            <el-input v-model.trim="psdMissing.form.phone" size="large" style="flex-grow: 1;height: 40px;" placeholder="Please enter your phone number">
+              <template #suffix>
+                <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item prop="email" v-else label="Email Address" size="large">
+            <el-input v-model="psdMissing.form.email" size="large" style="height: 40px;" placeholder="Please enter your email address" class="form-item">
+              <template #suffix>
+                <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item prop="verificationCode" size="large" label="Verification Code">
+            <el-input v-model="psdMissing.form.verificationCode" size="large" placeholder="Please enter the verification code" class="form-item"></el-input>
+          </el-form-item>
+          <el-form-item prop="password" size="large" label="Password">
+            <el-input v-model="psdMissing.form.password" size="large" type="password" placeholder="Please enter at least 6 passwords" class="form-item"></el-input>
+          </el-form-item>
+          <el-form-item prop="passwordConfirm" style="margin-bottom: 0;" size="large" label="Password">
+            <el-input v-model="psdMissing.form.passwordConfirm" size="large" type="password" placeholder="Please enter the password again" class="form-item"></el-input>
+          </el-form-item>
+        </el-form>
+        <el-button type="primary" @click="psdMissingSubmit" class="psdMissing-button" size="large">Submit</el-button>
+        <div class="login-row">Remember your password? <span @click="loginPageGo">Log in instead</span></div>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped> 
+  .psdMissing-container{
+    padding-top: 68px;
+    .login-type-tab{
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin: 60px 48px;
+      .tab-split{
+        height: 18px;
+        width: 1px;
+        background-color: #1856A7;
+        margin: 0 20px;
+      }
+      span{
+        display: inline-block;
+        cursor: pointer;
+        font-size: 18px;
+        line-height: 25px;
+        color: #999999;
+      }
+      .active-tab{
+        color: #1856A7;
+        font-weight: 600;
+      }
+    }
+    .regitser-form-box{
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+      .missing-psd-form{
+        width: 360px;
+        .phone-item{
+          width: 100%;
+          display: flex;
+          align-items: center;
+        }
+      }
+      .mobile-missing-psd-form{
+        display: none;
+      }
+      .psdMissing-button{
+        margin-top: 40px;
+        // width: 100%;
+        width: 360px;
+        background-color: #1856A7;
+      }
+      .login-row{
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 20px;
+        color: #333333;
+        margin: 20px 0 30px;
+        span{
+          color: #1856A7;
+          cursor: pointer;
+          margin-left: 12px;
+        }
+      }
+    }
+  }
+  // 移动端样式
+  @media screen and (max-width: 768px) {
+    .psdMissing-container{
+      padding: 100px 30px 30px;
+      .login-type-tab{
+        display: flex;
+        justify-content: flex-start;
+        margin: 0 0 30px 0;
+        .tab-split{
+          height: 18px;
+          width: 1px;
+          background-color: #1856A7;
+          margin: 0 20px;
+        }
+        span{
+          display: inline-block;
+          cursor: pointer;
+          font-size: 18px;
+          line-height: 25px;
+          color: #999999;
+        }
+        .active-tab{
+          color: #1856A7;
+          font-weight: 600;
+        }
+      }
+      .regitser-form-box{
+        display: flex;
+        justify-content: center;
+        flex-direction: column;
+        align-items: center;    
+        .missing-psd-form{
+          display: none;
+        }
+        .mobile-missing-psd-form{
+          display: block;
+          width: 100%;
+        }
+        .psdMissing-button{
+          margin-top: 40px;
+          width: 100%;
+          // width: 360px;
+          background-color: #1856A7;
+        }
+        .login-row{
+          font-weight: 400;
+          font-size: 14px;
+          line-height: 20px;
+          color: #333333;
+          margin: 20px 0 30px;
+          span{
+            color: #1856A7;
+            cursor: pointer;
+            margin-left: 12px;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 237 - 0
src/views/verification/register.vue

@@ -0,0 +1,237 @@
+<script setup>
+import { reactive, ref } from 'vue';
+import {ElMessage,ElMessageBox} from 'element-plus'
+import { useRouter } from 'vue-router';
+import {registerApi,emailCodeSend} from '@/api/auth.js'
+import md5 from 'md5'
+import { passwordDigitValidator } from './utils/validators';
+// passwordDigitValidator-密码6位
+
+  const router = useRouter()
+
+  const registerRef = ref(null)
+  
+  // 隐私政策是否阅读
+  const hasChecked=ref(false)
+  const register=reactive({
+    form:{
+      userName:'',
+      companyName:'',
+      email:'',
+      password:'',
+      passwordConfirm:'',
+      verificationCode:''
+    },
+    rules:{
+      userName:{required: true, message:'User name cannot be null', trigger: 'blur'},
+      companyName:{required: true, message:'Company name cannot be null', trigger: 'blur'},
+      email:[{required: true, message:'Email address cannot be null', trigger: 'blur'},
+      {type:'email',message:'Email address format is incorrect', trigger: 'blur'}],
+      password:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+      {
+        // 密码6位检验
+        validator:passwordDigitValidator,trigger:'blur'
+      }],
+      passwordConfirm:[{required: true, message:'Password cannot be null', trigger: 'blur'},
+      {validator:(rule,value,callback)=>{
+        if(register.form.passwordConfirm!==register.form.password){
+          callback(new Error('Passwords do not match'))
+        }else{
+          callback()
+        }
+      }, trigger: 'blur'}],
+      verificationCode:{required: true, message:'Verification code cannot be null', trigger: 'blur'}
+    }
+  })
+
+  let codeTimer=null
+  const codeInfo=reactive({
+    timeout:60,// 秒
+    isRequesting:false
+  })
+
+// ----------------------------------------------------方法
+  const sendVerCode=()=>{
+    registerRef.value.validateField(['userName','email']).then(res=>{
+      let codeParams={
+        Name:register.form.userName,
+        Email:register.form.email
+      }
+      emailCodeSend(codeParams).then(res=>{
+        if(res.code!=200) return  
+        ElMessage.success('Verification code sent')
+        codeInfo.isRequesting=true
+        codeInfo.timeout--
+        codeTimer=setInterval(()=>{
+          if(codeInfo.timeout==1){
+            codeInfo.isRequesting=false
+            codeInfo.timeout=60
+            codeTimer=null
+          }
+          codeInfo.timeout--
+        },1000)
+      })
+    }).catch(err=>{
+      let errMessage=''
+      errMessage=err.userName?err.userName[0].message:
+                  err.email?err.email[0].message:''
+      if(errMessage){
+        ElMessage.warning(errMessage)
+      }
+    })
+  }
+
+  const registerSubmit=()=>{
+    registerRef.value.validate(valid=>{
+      if(valid){
+        // if(!hasChecked.value){
+        //   ElMessage.warning('Please check the Privacy policy')
+        //   return
+        // }
+        let params={
+          CompanyName:register.form.companyName,
+          Name:register.form.userName,
+          Email:register.form.email,
+          SmsCode:register.form.verificationCode,
+          Password:md5(register.form.password)
+        }
+        registerApi(params).then(res=>{
+          if(res.code == 200){
+            localStorage.setItem('yben_token',res.data.access_token)
+            let userInfo = {
+              user_id:res.data.user_id,
+              Name:res.data.Name,
+              Email:res.data.Email,
+              Mobile:res.data.Mobile,
+              CountryCode:res.data.CountryCode
+            }
+            localStorage.setItem('user_info',JSON.stringify(userInfo))
+            sessionStorage.setItem('transitionPageMessage','register')
+            router.replace('/transitionPage')
+          }
+        })
+      }
+    })
+  }
+  const loginPageGo=()=>{
+    let {href} = router.resolve("/login");
+    window.open(href,'_blank');
+  }
+
+  const privacyPolicy=()=>{
+    //TODO 去查看隐私政策
+  }
+
+</script>
+
+<template>
+    <div class="register-container">
+      <div class="fixed-header">
+        <span>
+          HORIZON INSIGHTS
+        </span>
+      </div>
+      <div class="page-title">
+        Create account
+      </div>
+      <div class="regitser-form-box" id="regitser-form-box">
+        <el-form :model="register.form" :rules="register.rules" ref="registerRef">
+          <el-form-item prop="userName">
+            <el-input v-model="register.form.userName" size="large" placeholder="Please enter your name"></el-input>
+          </el-form-item>
+          <el-form-item prop="companyName">
+            <el-input v-model="register.form.companyName" size="large" placeholder="Please enter the company name"></el-input>
+          </el-form-item>
+          <el-form-item prop="email">
+            <el-input v-model="register.form.email" size="large" placeholder="Please enter your email address">
+              <template #suffix>
+                <span style=" color: var(--el-color-primary);cursor: pointer;white-space: nowrap;" 
+                @click="sendVerCode" v-if="!codeInfo.isRequesting">Get verification code</span>
+                <span style=" color: #999999;white-space: nowrap;" v-else>{{ codeInfo.timeout }}s</span>
+              </template>
+            </el-input>
+          </el-form-item>
+          <el-form-item prop="verificationCode" >
+            <el-input v-model.trim="register.form.verificationCode" size="large" placeholder="Please enter the email verification code"></el-input>
+          </el-form-item>
+          <el-form-item prop="password">
+            <el-input v-model.trim="register.form.password" size="large" type="password" placeholder="Please enter at least 6 passwords"></el-input>
+          </el-form-item>
+          <el-form-item prop="passwordConfirm" style="margin-bottom: 0;">
+            <el-input v-model.trim="register.form.passwordConfirm" size="large" type="password" placeholder="Please enter the password again"></el-input>
+          </el-form-item>
+        </el-form>
+        <el-button type="primary" @click="registerSubmit" class="register-button" size="large">Submit</el-button>
+        <div class="login-row">Existing account <span @click="loginPageGo">Log in instead</span></div>
+        <!-- <div class="privacy-policy">
+          <el-checkbox v-model="hasChecked" label="I have read and agree" style="color:#333333"></el-checkbox>
+          <span @click="privacyPolicy" class="policy">the Horizon Insights Privacy Policy</span>
+        </div> -->
+        <div class="question-hint">For technical assistance, please contact stephanie@hzinsights.com</div>
+      </div>
+    </div>
+</template>
+  
+<style lang="scss" scoped> 
+  .register-container{
+    padding-top: 68px;
+    .page-title{
+      text-align: center;
+      font-weight: 600;
+      font-size: 20px;
+      line-height: 28px;
+      color: #1856A7;
+      padding: 56px 0 48px;
+    }
+    .regitser-form-box{
+      display: flex;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+      .register-button{
+        margin-top: 40px;
+        width: 100%;
+        width: 360px;
+        background-color: #1856A7;
+      }
+      .login-row{
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 20px;
+        color: #333333;
+        margin: 20px 0 30px;
+        span{
+          color: #1856A7;
+          cursor: pointer;
+          margin-left: 12px;
+        }
+      }
+      .privacy-policy{
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 22px;
+        margin-bottom: 30px;
+        display: flex;
+        align-items: center;
+        .policy{
+          color: #1856A7;
+          cursor: pointer;
+          margin-left: 12px;
+        }
+      }
+      .question-hint{
+        font-weight: 400;
+        font-size: 14px;
+        line-height: 20px;
+        color: #999999;
+      }
+    }
+  }
+</style>
+<style lang="scss">
+  #regitser-form-box{
+    .el-input,.el-input__wrapper{
+      width: 360px;
+    }
+  }
+</style>

+ 114 - 0
src/views/verification/transitionPage.vue

@@ -0,0 +1,114 @@
+<script setup>
+    import { useRouter} from 'vue-router';
+    import {onUnmounted, ref} from 'vue'
+    const router = useRouter()
+    /**
+     * register - 注册成功
+     * bindPhone - 绑定手机号
+     */
+    const transitionType=ref()
+    
+    let timer=null
+    const time = ref(5)
+    const bindPhonePageGo=()=>{
+        router.replace('/bindPhoneNo')
+    }
+
+    const ybPageGo=()=>{
+        let redirectPath=sessionStorage.getItem('login_redirect') || '/'
+        sessionStorage.removeItem('login_redirect')
+        router.replace(redirectPath)
+    }
+
+    onUnmounted(()=>{
+        timer=null
+    })
+
+    // -------------------created
+
+    transitionType.value = sessionStorage.getItem('transitionPageMessage')
+    console.log(transitionType.value);
+    // 判断是否是正常操作后的跳转,而不是人工在地址栏做的跳转
+    if(!transitionType.value){
+        router.replace('/')
+    }else{
+        if(transitionType.value =='bindPhone'){
+            // 设置定时器
+            timer=setInterval(()=>{
+                if(time.value==1){
+                    ybPageGo()
+                    return 
+                }
+                time.value--
+            },1000)
+        }
+    }
+    // 清除
+    sessionStorage.removeItem('transitionPageMessage')
+    
+</script>
+
+<template>
+    <div class="hint-box" v-show="transitionType">
+        <div class="fixed-header">
+            <span>
+            HORIZON INSIGHTS
+            </span>
+        </div>
+        <img src="@/assets/icons/transition-success.svg" class="success-svg" />
+        <p v-if="transitionType=='register'" class="transitioin-text">You have successfully registered</p>
+        <p v-else-if="transitionType=='bindPhone'" class="transitioin-text">Registration succeeded. You will be automatically redirected in {{ time }} seconds.</p>
+        <div class="register-buttons" v-if="transitionType=='register'" >
+            <el-button type="primary" class="transitioin-button" @click="bindPhonePageGo" 
+            style="background-color: #1856A7;">Add a phone number to your account</el-button>
+            <el-button class="transitioin-button" @click="ybPageGo" style="margin-left: 0;margin-top: 20px;">Skip</el-button>
+        </div>
+        <el-button type="primary" class="transitioin-button" @click="ybPageGo" 
+        v-else-if="transitionType=='bindPhone'" style="background-color: #1856A7;">Got it</el-button>
+    </div>
+</template>
+  
+<style lang="scss" scoped>
+    .hint-box{
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+        padding: 120px 30px;
+        .success-svg{
+            height: 60px;
+        }
+        .transitioin-text{
+            margin: 20px 0 40px;
+            font-weight: 400;
+            font-size: 18px;
+            line-height: 25px;
+            color: #333333;
+        }
+        .register-buttons{
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            width: 100%;
+        }
+        .transitioin-button{
+            width: 360px;
+            height: 40px;
+        }
+    }
+    @media screen and (max-width: 768px) {
+        .hint-box{
+            padding: 100px 30px;
+            .success-svg{
+                height: 100px;
+            }
+            .transitioin-text{
+                font-size: 15px;
+            }
+            .transitioin-button{
+                width: 100%;
+            }
+        }
+
+    }
+</style>

+ 7 - 0
src/views/verification/utils/validators.js

@@ -0,0 +1,7 @@
+export const passwordDigitValidator=(rule,value,callback)=>{
+  if(value.length<6){
+    callback(new Error('Enter at least 6 digits'))
+  }else{
+    callback()
+  }
+}