Browse Source

用户身份相关功能

任一存 1 year ago
parent
commit
e6add32f55

+ 2 - 1
game/.env.dev

@@ -1,3 +1,4 @@
 VUE_APP_CLI_MODE=dev
 VUE_APP_CLI_MODE=dev
 NODE_ENV=development
 NODE_ENV=development
-PUBLIC_PATH=/
+PUBLIC_PATH=/
+VUE_APP_API_PREFIX=http://192.168.20.61:8063

+ 1 - 0
game/.env.mytest

@@ -1,3 +1,4 @@
 VUE_APP_CLI_MODE=test
 VUE_APP_CLI_MODE=test
 NODE_ENV=production
 NODE_ENV=production
 PUBLIC_PATH=./
 PUBLIC_PATH=./
+VUE_APP_API_PREFIX=https://wlgbwg.4dage.com

+ 1 - 0
game/.env.prod

@@ -1,3 +1,4 @@
 VUE_APP_CLI_MODE=prod
 VUE_APP_CLI_MODE=prod
 NODE_ENV=production
 NODE_ENV=production
 PUBLIC_PATH=./
 PUBLIC_PATH=./
+VUE_APP_API_PREFIX=

+ 1 - 0
game/README.md

@@ -7,6 +7,7 @@
 
 
 ## 还缺资源:
 ## 还缺资源:
 
 
+## todo
 
 
 
 
 
 

+ 2 - 1
game/package.json

@@ -14,13 +14,14 @@
     "clipboard": "^2.0.11",
     "clipboard": "^2.0.11",
     "core-js": "^3.8.3",
     "core-js": "^3.8.3",
     "dayjs": "^1.11.7",
     "dayjs": "^1.11.7",
+    "js-base64": "^3.7.5",
     "lodash": "^4.17.21",
     "lodash": "^4.17.21",
     "mitt": "^3.0.0",
     "mitt": "^3.0.0",
     "v-viewer": "^3.0.11",
     "v-viewer": "^3.0.11",
     "vant": "^4.8.2",
     "vant": "^4.8.2",
     "viewerjs": "^1.11.6",
     "viewerjs": "^1.11.6",
-    "vue": "^3.2.13",
     "vue-router": "^4.0.3",
     "vue-router": "^4.0.3",
+    "vue": "^3.2.13",
     "vuex": "^4.0.0"
     "vuex": "^4.0.0"
   },
   },
   "devDependencies": {
   "devDependencies": {

+ 4 - 1
game/src/App.vue

@@ -8,7 +8,7 @@
 import { ref, computed, watch, onMounted } from "vue"
 import { ref, computed, watch, onMounted } from "vue"
 import { useRoute, useRouter } from "vue-router"
 import { useRoute, useRouter } from "vue-router"
 import { useStore } from "vuex"
 import { useStore } from "vuex"
-
+import { checkLoginStatusAndProcess } from '@/api.js'
 const {
 const {
   windowSizeInCssForRef,
   windowSizeInCssForRef,
   windowSizeWhenDesignForRef,
   windowSizeWhenDesignForRef,
@@ -17,6 +17,9 @@ const {
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 const store = useStore()
 const store = useStore()
+
+checkLoginStatusAndProcess()
+
 </script>
 </script>
 
 
 <style lang="less">
 <style lang="less">

+ 136 - 14
game/src/api.js

@@ -1,17 +1,139 @@
-// import axios from "axios"
+import axios from "axios"
+import { encodeStr } from "@/pass.js"
+import { Base64 } from "js-base64"
+import store from "@/store/index.js"
+import router from "@/router"
 
 
-// axios({
-//   method: 'post',
-//   url: `${config.backendDir}visit/saveType`,
-//   headers: {
-//     appId: "CA02F83A5FA162B930AA2F962D202F43B0F6DE0B51AD79FEDB03FA8202BB4909330105B3B347510D87C97060C4288280D4A744E00565A4EC",
-//     "Content-Type": "application/json",
-//   },
-//   data: {
-//     moduleType,
-//     type: 'visit',
-//   },
-// })
+axios.interceptors.response.use(function (response) {
+  // 2xx 范围内的状态码都会触发该函数。
+  if (response.data.code === 5001 || response === 5002) {
+    store.commit('logoutCallback')
+    router.push({ name: 'HomeView' })
+    return Promise.reject('登录态过期')
+  }
+  return response
+}, function (error) {
+  return error
+})
 
 
-export default {
+// export async function reportVisit() {
+//   const res = await axios({
+//     method: 'get',
+//     url: `${process.env.VUE_APP_API_PREFIX}/api/show/addVisit`,
+//   })
+// }
+// export async function fetchVisitInfo() {
+//   const res = await axios({
+//     method: 'get',
+//     url: `${process.env.VUE_APP_API_PREFIX}/api/show/getVisit`,
+//   })
+//   return res.data.data
+// }
+export async function login(account, password) {
+  const pwdEncrypted = encodeStr(Base64.encode(password))
+  const res = await axios({
+    method: 'post',
+    url: `${process.env.VUE_APP_API_PREFIX}/api/show/login`,
+    data: {
+      userName: account,
+      password: pwdEncrypted,
+    },
+  })
+  if (res.data.code !== 0) {
+    throw (`登录失败:${res.data.msg}`)
+  } else {
+    store.commit('setLoginStatus', true)
+    store.commit('setToken', res.data.data.token)
+    store.commit('setUserInfo', res.data.data.user)
+  }
+}
+export async function logout() {
+  const res = await axios({
+    method: 'get',
+    url: `${process.env.VUE_APP_API_PREFIX}/api/cms/game/logout`,
+    headers: {
+      token: store.state.token,
+    }
+  })
+  if (res?.data?.code === 0) {
+    store.commit('logoutCallback')
+  }
+}
+export async function checkLoginStatusAndProcess() {
+  const lastToken = localStorage.getItem('token')
+  const lastUserInfoStr = localStorage.getItem('userInfo')
+  if (lastToken && lastUserInfoStr) {
+    const res = await axios({
+      method: 'get',
+      url: `${process.env.VUE_APP_API_PREFIX}/api/show/checkLogin`,
+      headers: {
+        token: lastToken,
+      }
+    })
+    if (res?.data?.code === 0 && res?.data?.data) {
+      store.commit('setLoginStatus', true)
+      store.commit('setToken', lastToken)
+      store.commit('setUserInfo', JSON.parse(lastUserInfoStr))
+      return true
+    } else {
+      store.commit('logoutCallback')
+      return false
+    }
+  } else {
+    store.commit('logoutCallback')
+    return false
+  }
+}
+export async function signUp(account, phone, password) {
+  const pwdEncrypted = encodeStr(Base64.encode(password))
+  const res = await axios({
+    method: 'post',
+    url: `${process.env.VUE_APP_API_PREFIX}/api/show/register`,
+    data: {
+      password: pwdEncrypted,
+      phone,
+      userName: account,
+      verifyPassword: pwdEncrypted,
+    },
+  })
+  if (res.data.code !== 0) {
+    throw (`注册失败:${res.data.msg}`)
+  } else {
+    return
+  }
+}
+export async function findPassowrd(account, phone) {
+  const res = await axios({
+    method: 'post',
+    url: `${process.env.VUE_APP_API_PREFIX}/api/show/findPass`,
+    data: {
+      phone,
+      userName: account,
+    },
+  })
+  if (res.data.code !== 0) {
+    throw (`找回密码失败:${res.data.msg}`)
+  } else {
+    return
+  }
+}
+export async function changePassword(newPassword, oldPassword) {
+  const pwdNewEncrypted = encodeStr(Base64.encode(newPassword))
+  const pwdOldEncrypted = encodeStr(Base64.encode(oldPassword))
+  const res = await axios({
+    method: 'get',
+    url: `${process.env.VUE_APP_API_PREFIX}/api/cms/game/update/pass`,
+    params: {
+      newPassword: pwdNewEncrypted,
+      oldPassword: pwdOldEncrypted,
+    },
+    headers: {
+      token: store.state.token,
+    }
+  })
+  if (res.data.code !== 0) {
+    throw (`密码修改失败:${res.data.msg}`)
+  } else {
+    return
+  }
 }
 }

BIN
game/src/assets/images/avatar.png


BIN
game/src/assets/images/icon-account.png


BIN
game/src/assets/images/icon-lock.png


BIN
game/src/assets/images/icon-phone.png


BIN
game/src/assets/images/logo-and-title.png


+ 106 - 0
game/src/pass.js

@@ -0,0 +1,106 @@
+/* eslint-disable */
+function NoToChinese (num) {
+  num = String(num)
+  var chnNumChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
+  if (num == 0) {
+    return chnNumChar[0]
+  }
+  let tmp = ''
+  for (let i = 0; i < num.length; i++) {
+    const ele = num.charAt(i)
+    tmp += chnNumChar[ele]
+  }
+
+  return tmp
+}
+
+function randomWord (randomFlag, min, max) {
+  let str = ''
+  let range = min
+  const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min
+  }
+  for (var i = 0; i < range; i++) {
+    const pos = Math.round(Math.random() * (arr.length - 1))
+    str += arr[pos]
+  }
+  return str
+}
+
+module.exports = {
+  formatDate: function (time) {
+    var weekArr = ['日', '一', '二', '三', '四', '五', '六']
+    var date = new Date(time)
+    var year = date.getFullYear()
+    var month = date.getMonth() + 1
+    var day = date.getDate()
+    var week = '星期' + weekArr[date.getDay()]
+    if (window.innerWidth < 1700) {
+      return (
+        year +
+        '年' +
+        (String(month).length > 1 ? month : '0' + month) +
+        '月' +
+        (String(day).length > 1 ? day : '0' + day) +
+        '日' +
+        '<br/>' +
+        week
+      )
+    }
+    return (
+      year +
+      '年' +
+      (String(month).length > 1 ? month : '0' + month) +
+      '月' +
+      (String(day).length > 1 ? day : '0' + day) +
+      '日' +
+      ' ' +
+      week
+    )
+  },
+  smoothscrollpos: function (domName) {
+    if (domName == '/') {
+      return window.scrollTo(0, 0)
+    }
+    const smoothscroll = () => {
+      const dom = document.getElementById(domName)
+      // window.scrollTo({
+      //   top:dom.offsetTop - 100,
+      //   left:0,
+      //   behavior: "smooth"
+      // })
+      dom && window.scrollTo(0, dom.offsetTop - 120)
+    }
+    smoothscroll()
+  },
+
+  formatTime: function (time, fan = false) {
+    let t1 = time.split(' ')[0].split('-')
+    if (fan) {
+      t1 = t1.map((item) => {
+        const t = NoToChinese(item)
+        return t
+      })
+    }
+    return t1
+  },
+  encodeStr: function (str, strv = '') {
+    const NUM = 2
+    const front = randomWord(false, 8)
+    const middle = randomWord(false, 8)
+    const end = randomWord(false, 8)
+
+    const str1 = str.substring(0, NUM)
+    const str2 = str.substring(NUM)
+
+    if (strv) {
+      const strv1 = strv.substring(0, NUM)
+      const strv2 = strv.substring(NUM)
+      return [front + str2 + middle + str1 + end, front + strv2 + middle + strv1 + end]
+    }
+
+    return front + str2 + middle + str1 + end
+  }
+}

+ 24 - 0
game/src/router/index.js

@@ -1,11 +1,15 @@
 import { createRouter, createWebHashHistory } from 'vue-router'
 import { createRouter, createWebHashHistory } from 'vue-router'
 import HomeView from '../views/HomeView.vue'
 import HomeView from '../views/HomeView.vue'
 import SignUp from '../views/SignUp.vue'
 import SignUp from '../views/SignUp.vue'
+import LoginView from '../views/LoginView.vue'
+import FindPassword from '../views/FindPassword.vue'
+import ChangePassword from '../views/ChangePassword.vue'
 import PlantTree from '../views/PlantTree.vue'
 import PlantTree from '../views/PlantTree.vue'
 import ExamPaper1 from '../views/ExamPaper1.vue'
 import ExamPaper1 from '../views/ExamPaper1.vue'
 import ExamPaper2 from '../views/ExamPaper2.vue'
 import ExamPaper2 from '../views/ExamPaper2.vue'
 import ExamPaper3 from '../views/ExamPaper3.vue'
 import ExamPaper3 from '../views/ExamPaper3.vue'
 import PairUp from '../views/PairUp.vue'
 import PairUp from '../views/PairUp.vue'
+import GameByUnity from '../views/GameByUnity.vue'
 // import store from '@/store/index.js'
 // import store from '@/store/index.js'
 
 
 const routes = [
 const routes = [
@@ -20,6 +24,21 @@ const routes = [
     component: SignUp,
     component: SignUp,
   },
   },
   {
   {
+    path: '/login-view',
+    name: 'LoginView',
+    component: LoginView,
+  },
+  {
+    path: '/find-password',
+    name: 'FindPassword',
+    component: FindPassword,
+  },
+  {
+    path: '/change-password',
+    name: 'ChangePassword',
+    component: ChangePassword,
+  },
+  {
     path: '/plant-tree',
     path: '/plant-tree',
     name: 'PlantTree',
     name: 'PlantTree',
     component: PlantTree,
     component: PlantTree,
@@ -43,6 +62,11 @@ const routes = [
     path: '/pair-up',
     path: '/pair-up',
     name: 'PairUp',
     name: 'PairUp',
     component: PairUp,
     component: PairUp,
+  },
+  {
+    path: '/game-by-unity',
+    name: 'GameByUnity',
+    component: GameByUnity,
   }
   }
 ]
 ]
 
 

+ 35 - 5
game/src/store/index.js

@@ -2,16 +2,46 @@ import { createStore } from 'vuex'
 
 
 export default createStore({
 export default createStore({
   state: {
   state: {
-    loginStatus: 0, // 0: 未登录;1:已登录
+    loginStatus: false,
+    token: '',
+    userInfo: {
+      // createTime: "2024-01-08 17:04:43"
+      // creatorId: null
+      // creatorName: ""
+      // id: 4
+      // isEnabled: 1
+      // phone: "17767746248"
+      // realName: ""
+      // score: null
+      // sex: ""
+      // updateTime: "2024-01-08 17:04:43"
+      // userName: "1020363151@qq.com"
+    },
     avatar: '',
     avatar: '',
-    userName: '',
-    bonusPoint: 0,
   },
   },
   getters: {
   getters: {
   },
   },
   mutations: {
   mutations: {
-    setUsingChinese(state, value) {
-      state.usingChinese = value
+    setLoginStatus(state, value) {
+      state.loginStatus = value
+    },
+    setToken(state, value) {
+      state.token = value
+      localStorage.setItem('token', value)
+    },
+    setUserInfo(state, value) {
+      state.userInfo = value
+      localStorage.setItem('userInfo', JSON.stringify(value))
+      if (state.userInfo.score === null) {
+        state.userInfo.score = 0
+      }
+    },
+    logoutCallback(state) {
+      state.loginStatus = false
+      state.token = ''
+      localStorage.removeItem('token')
+      state.userInfo = {}
+      localStorage.removeItem('userInfo')
     },
     },
     changeBonusPoint(state, delta) {
     changeBonusPoint(state, delta) {
       state.avatar += delta
       state.avatar += delta

+ 263 - 0
game/src/views/ChangePassword.vue

@@ -0,0 +1,263 @@
+<template>
+  <div
+    class="sign-up"
+  >
+    <div class="bg-deco" />
+
+    <img
+      class="logo-and-title"
+      src="@/assets/images/logo-and-title.png"
+      alt=""
+      draggable="false"
+    >
+
+    <div class="form-item form-item-password-old">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-lock.png"
+          alt=""
+          draggable="false"
+        >
+        <span>旧密码</span>
+      </div>
+      <input
+        v-model="passwordOld"
+        type="password"
+        placeholder="6-10个字,数字或字母"
+        :class="{
+          invalid: isPasswordInputOver && !isPasswordValid
+        }"
+        @blur="isPasswordInputOver=true"
+      >
+    </div>
+    <div class="form-item form-item-password">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-lock.png"
+          alt=""
+          draggable="false"
+        >
+        <span>密码</span>
+      </div>
+      <input
+        v-model="password"
+        type="password"
+        placeholder="6-10个字,数字或字母"
+        :class="{
+          invalid: isPasswordInputOver && !isPasswordValid
+        }"
+        @blur="isPasswordInputOver=true"
+      >
+    </div>
+    <div class="form-item form-item-password-repeat">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-lock.png"
+          alt=""
+          draggable="false"
+        >
+        <span>确认密码</span>
+      </div>
+      <input
+        v-model="passwordRepeat"
+        type="password"
+        placeholder="6-10个字,数字或字母"
+        :class="{
+          invalid: isPasswordRepeatInputOver && !isPasswordRepeatValid
+        }"
+        @blur="isPasswordRepeatInputOver=true"
+      >
+    </div>
+
+    <button
+      class="submit"
+      @click="submit"
+    >
+      修改密码
+    </button>
+    <button
+      class="return"
+      @click="$router.go(-1)"
+    >
+      返回
+    </button>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+import { showDialog } from 'vant'
+import { changePassword } from '@/api.js'
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const passwordOld = ref('')
+const password = ref('')
+const passwordRepeat = ref('')
+
+const passwordOldTrimed = computed(() => {
+  return passwordOld.value.trim()
+})
+const passwordTrimed = computed(() => {
+  return password.value.trim()
+})
+const passwordRepeatTrimed = computed(() => {
+  return passwordRepeat.value.trim()
+})
+
+const isPasswordOldValid = computed(() => {
+  return passwordOldTrimed.value.length >= 6 && passwordOldTrimed.value.length <= 10 && passwordTrimed.value.search(/^[0-9a-zA-Z].*$/) === 0
+})
+const isPasswordValid = computed(() => {
+  return passwordTrimed.value.length >= 6 && passwordTrimed.value.length <= 10 && passwordTrimed.value.search(/^[0-9a-zA-Z].*$/) === 0
+})
+const isPasswordRepeatValid = computed(() => {
+  return passwordTrimed.value === passwordRepeatTrimed.value
+})
+
+const isPasswordOldInputOver = ref(false)
+const isPasswordInputOver = ref(false)
+const isPasswordRepeatInputOver = ref(false)
+
+function submit() {
+  if (!isPasswordOldValid.value) {
+    showDialog({
+      message: '请正确输入密码',
+      theme: 'round-button',
+    })
+    isPasswordOldInputOver.value = true
+    return
+  }
+  if (!isPasswordValid.value) {
+    showDialog({
+      message: '请正确输入密码',
+      theme: 'round-button',
+    })
+    isPasswordInputOver.value = true
+    return
+  }
+  if (!isPasswordRepeatValid.value) {
+    showDialog({
+      message: '请正确输入确认密码',
+      theme: 'round-button',
+    })
+    isPasswordRepeatInputOver.value = true
+    return
+  }
+
+  changePassword(passwordTrimed.value, passwordOldTrimed.value).then(() => {
+    showDialog({
+      message: '密码修改成功',
+      theme: 'round-button',
+    }).then(() => {
+      router.replace({
+        name: 'HomeView'
+      })
+    })
+  }).catch((err) => {
+    showDialog({
+      message: err,
+      theme: 'round-button',
+    })
+  })
+}
+</script>
+
+<style lang="less" scoped>
+.sign-up{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  >.bg-deco{
+    position: absolute;
+    left: calc(-67 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    bottom: calc(-130 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: #D1B489;
+    filter: blur(calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')));
+  }
+  >.logo-and-title{
+    margin-top: calc(53 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(120 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(38 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >.form-item{
+    width: calc(321 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >.title{
+      width: 100%;
+      display: flex;
+      align-items: center;
+      margin-bottom: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        width: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        height: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >span{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #D1B489;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input{
+      width: 100%;
+      height: calc(29 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      border-bottom: 1px solid #D9D9D9;
+      font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      font-family: Source Han Sans SC, Source Han Sans SC;
+      font-weight: 400;
+      color: black;
+      line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      &::placeholder {
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #B8B8B8;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input.invalid{
+      border-bottom: 1px solid red;
+    }
+  }
+  >button.submit{
+    width: calc(332 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(56 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: rgba(197, 161, 108, 0.8);
+    border-radius: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #FFFFFF;
+    line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >button.return{
+    font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #A97C46;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    opacity: 0.5;
+  }
+}
+</style>

+ 235 - 0
game/src/views/FindPassword.vue

@@ -0,0 +1,235 @@
+<template>
+  <div
+    class="find-password"
+  >
+    <div class="bg-deco" />
+
+    <img
+      class="logo-and-title"
+      src="@/assets/images/logo-and-title.png"
+      alt=""
+      draggable="false"
+    >
+
+    <div class="form-item">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-account.png"
+          alt=""
+          draggable="false"
+        >
+        <span>帐号</span>
+      </div>
+      <input
+        v-model.trim="account"
+        type="text"
+        placeholder="请输入邮箱"
+        aotufocus
+        :class="{
+          invalid: isAccountInputOver && !isAccountValid
+        }"
+        @blur="isAccountInputOver=true"
+      >
+    </div>
+    <div class="form-item">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-phone.png"
+          alt=""
+          draggable="false"
+        >
+        <span>手机号</span>
+      </div>
+      <input
+        v-model="phone"
+        type="number"
+        placeholder="请输入11位手机号"
+        :class="{
+          invalid: isPhoneInputOver && !isPhoneValid
+        }"
+        @blur="isPhoneInputOver=true"
+      >
+    </div>
+
+    <button
+      class="submit"
+      @click="submit"
+    >
+      发送验证邮件
+    </button>
+    <button
+      class="return"
+      @click="$router.push({
+        name: 'HomeView',
+      })"
+    >
+      返回首页
+    </button>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+import { showDialog } from 'vant'
+import { findPassowrd } from '@/api.js'
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const account = ref('')
+const phone = ref('')
+const password = ref('')
+const passwordRepeat = ref('')
+
+const accountTrimed = computed(() => {
+  return account.value.trim()
+})
+const phoneTrimed = computed(() => {
+  return phone.value.toString().trim()
+})
+
+const isAccountValid = computed(() => {
+  return !!accountTrimed.value
+})
+const isPhoneValid = computed(() => {
+  return phoneTrimed.value.length === 11
+})
+
+const isAccountInputOver = ref(false)
+const isPhoneInputOver = ref(false)
+
+
+
+function submit() {
+  if (!isAccountValid.value) {
+    showDialog({
+      message: '请正确输入邮箱',
+      theme: 'round-button',
+    })
+    isAccountInputOver.value = true
+    return
+  }
+  if (!isPhoneValid.value) {
+    showDialog({
+      message: '请正确输入手机号',
+      theme: 'round-button',
+    })
+    isPhoneInputOver.value = true
+    return
+  }
+
+  findPassowrd(accountTrimed.value, phoneTrimed.value).then(() => {
+    showDialog({
+      message: '已发送邮件到您的邮箱',
+      theme: 'round-button',
+    }).then(() => {
+      router.replace({
+        name: 'HomeView'
+      })
+    })
+  }).catch((err) => {
+    showDialog({
+      message: err,
+      theme: 'round-button',
+    })
+  })
+}
+</script>
+
+<style lang="less" scoped>
+.find-password{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  >.bg-deco{
+    position: absolute;
+    left: calc(-67 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    bottom: calc(-130 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: #D1B489;
+    filter: blur(calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')));
+  }
+  >.logo-and-title{
+    margin-top: calc(53 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(120 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(38 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >.form-item{
+    width: calc(321 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >.title{
+      width: 100%;
+      display: flex;
+      align-items: center;
+      margin-bottom: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        width: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        height: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >span{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #D1B489;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input{
+      width: 100%;
+      height: calc(29 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      border-bottom: 1px solid #D9D9D9;
+      font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      font-family: Source Han Sans SC, Source Han Sans SC;
+      font-weight: 400;
+      color: black;
+      line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      &::placeholder {
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #B8B8B8;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input.invalid{
+      border-bottom: 1px solid red;
+    }
+  }
+  >button.submit{
+    width: calc(332 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(56 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: rgba(197, 161, 108, 0.8);
+    border-radius: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #FFFFFF;
+    line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >button.return{
+    font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #A97C46;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    opacity: 0.5;
+  }
+}
+</style>

+ 46 - 0
game/src/views/GameByUnity.vue

@@ -0,0 +1,46 @@
+<template>
+  <div class="game-by-unity">
+    <iframe
+      :src="gameUrl"
+      frameborder="0"
+    />
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+let gameUrlQuery = ''
+if (store.state.loginStatus) {
+  gameUrlQuery = `?userId=${store.state.userInfo.id}&token=${store.state.token}`
+}
+const gameUrlMap = {
+  DisasterRelief: `http://app.4dage.com/test/rc/Build/index.html`,
+}
+
+const gameUrl = gameUrlMap[route.query.gameName] + gameUrlQuery
+
+</script>
+
+<style lang="less" scoped>
+.game-by-unity{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  >iframe{
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 84 - 30
game/src/views/HomeView.vue

@@ -3,10 +3,13 @@
     class="home"
     class="home"
   >
   >
     <div
     <div
-      v-if="store.state.loginStatus === 0"
+      v-if="!store.state.loginStatus"
       class="for-visitor"
       class="for-visitor"
     >
     >
-      <button class="login">
+      <button
+        class="login"
+        @click="onClickLogin"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/btn-login-bg.png"
           src="@/assets/images/btn-login-bg.png"
@@ -34,22 +37,29 @@
     >
     >
       <img
       <img
         class="avatar"
         class="avatar"
-        src="@/assets/images/btn-login-bg.png"
+        src="@/assets/images/avatar.png"
         alt=""
         alt=""
         draggable="false"
         draggable="false"
       >
       >
       <div class="info-wrap">
       <div class="info-wrap">
         <div class="user-id">
         <div class="user-id">
-          <span class="title">用户ID:</span><span
+          <span class="title">用户ID:</span>
+          <span
             class="value"
             class="value"
-            :title="store.state.userName"
-          >{{ store.state.userName }}</span>
+            :title="store.state.userInfo.userName"
+          >{{ store.state.userInfo.userName }}</span>
         </div>
         </div>
         <div class="point-bonus">
         <div class="point-bonus">
-          <span class="title">当前积分:</span><span class="value">{{ store.state.bonusPoint }}</span>
+          <span class="title">当前积分:</span>
+          <span class="value">{{ store.state.userInfo.score }}</span>
         </div>
         </div>
       </div>
       </div>
-      <button class="change-password">
+      <button
+        class="change-password"
+        @click="router.push({
+          name: 'ChangePassword',
+        })"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/icon-password.png"
           src="@/assets/images/icon-password.png"
@@ -57,7 +67,10 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
-      <button class="log-out">
+      <button
+        class="logout"
+        @click="onClickLogout"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/icon-exit.png"
           src="@/assets/images/icon-exit.png"
@@ -67,7 +80,13 @@
       </button>
       </button>
     </div>
     </div>
     <div class="entry-list">
     <div class="entry-list">
-      <button class="game-entry plant-tree-entry">
+      <!-- 乡村林场 -->
+      <button
+        class="game-entry plant-tree-entry"
+        @click="router.push({
+          name: 'PlantTree',
+        })"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/plant-tree-entry.png"
           src="@/assets/images/plant-tree-entry.png"
@@ -75,7 +94,13 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
-      <button class="game-entry exam-paper-entry">
+      <!-- 助农课堂 -->
+      <button
+        class="game-entry exam-paper-entry"
+        @click="router.push({
+          name: 'ExamPaper1',
+        })"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/exam-paper-entry.png"
           src="@/assets/images/exam-paper-entry.png"
@@ -83,7 +108,13 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
-      <button class="game-entry pair-up-entry">
+      <!-- 企业翻翻看 -->
+      <button
+        class="game-entry pair-up-entry"
+        @click="router.push({
+          name: 'PairUp',
+        })"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/pair-up-entry.png"
           src="@/assets/images/pair-up-entry.png"
@@ -91,6 +122,7 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
+      <!-- 抗体大作战 -->
       <button class="game-entry antibody-battle-entry">
       <button class="game-entry antibody-battle-entry">
         <img
         <img
           class=""
           class=""
@@ -99,7 +131,16 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
-      <button class="game-entry disaster-relief-entry">
+      <!-- 应急救灾 -->
+      <button
+        class="game-entry disaster-relief-entry"
+        @click="router.push({
+          name: 'GameByUnity',
+          query: {
+            gameName: 'DisasterRelief'
+          },
+        })"
+      >
         <img
         <img
           class=""
           class=""
           src="@/assets/images/disaster-relief-entry.png"
           src="@/assets/images/disaster-relief-entry.png"
@@ -107,6 +148,7 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
+      <!-- 生态保护 -->
       <button class="game-entry enviroment-protection">
       <button class="game-entry enviroment-protection">
         <img
         <img
           class=""
           class=""
@@ -115,6 +157,7 @@
           draggable="false"
           draggable="false"
         >
         >
       </button>
       </button>
+      <!-- 找回走失儿童 -->
       <button class="game-entry lost-children-entry">
       <button class="game-entry lost-children-entry">
         <img
         <img
           class=""
           class=""
@@ -128,7 +171,7 @@
     <!-- 悬浮按钮 -->
     <!-- 悬浮按钮 -->
     <div class="floating-btn-group">
     <div class="floating-btn-group">
       <button
       <button
-        v-if="store.state.loginStatus === 1"
+        v-if="store.state.loginStatus"
         class="redeem"
         class="redeem"
       >
       >
         <img
         <img
@@ -159,6 +202,7 @@ import { useRoute, useRouter } from "vue-router"
 import { useStore } from "vuex"
 import { useStore } from "vuex"
 import ClipboardJS from 'clipboard'
 import ClipboardJS from 'clipboard'
 import { showDialog } from 'vant'
 import { showDialog } from 'vant'
+import { logout } from '@/api.js'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
@@ -169,12 +213,22 @@ const {
   windowSizeWhenDesignForRef,
   windowSizeWhenDesignForRef,
 } = useSizeAdapt(390, 752)
 } = useSizeAdapt(390, 752)
 
 
+function onClickLogin() {
+  router.push({
+    name: 'LoginView',
+  })
+}
+
 function onClickSignUp() {
 function onClickSignUp() {
   router.push({
   router.push({
     name: 'SignUp',
     name: 'SignUp',
   })
   })
 }
 }
 
 
+function onClickLogout() {
+  logout()
+}
+
 const shareText = location.href
 const shareText = location.href
 const clipboard = new ClipboardJS('button.share')
 const clipboard = new ClipboardJS('button.share')
 clipboard.on('success', function(e) {
 clipboard.on('success', function(e) {
@@ -228,24 +282,26 @@ onUnmounted(() => {
   }
   }
   >.after-login{
   >.after-login{
     margin-top: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     margin-top: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-    width: 100%;
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
-    padding-left: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-    padding-right: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-left: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-right: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     padding-top: calc(13 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     padding-top: calc(13 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     padding-bottom: calc(13 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     padding-bottom: calc(13 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     background: rgba(226, 201, 163, 0.5);
     background: rgba(226, 201, 163, 0.5);
     box-shadow: inset -1px -2px 1px 0px #E2C9A3;
     box-shadow: inset -1px -2px 1px 0px #E2C9A3;
     border-radius: calc(4 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     border-radius: calc(4 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-left: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-right: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
     >img.avatar{
     >img.avatar{
+      flex: 0 0 auto;
       width: calc(54 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       width: calc(54 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       height: calc(54 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       height: calc(54 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       border-radius: calc(27 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       border-radius: calc(27 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-      margin-left: calc(13 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       object-fit: cover;
       object-fit: cover;
     }
     }
     >.info-wrap{
     >.info-wrap{
+      flex: 0 0 auto;
       margin-left: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       margin-left: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       font-family: Source Han Sans SC, Source Han Sans SC;
       font-family: Source Han Sans SC, Source Han Sans SC;
@@ -258,7 +314,7 @@ onUnmounted(() => {
           vertical-align: middle;
           vertical-align: middle;
           display: inline-block;
           display: inline-block;
           width: calc(69 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
           width: calc(69 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-          letter-spacing: calc(3 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          letter-spacing: calc(4 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
           margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
           margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
         }
         }
         >.value{
         >.value{
@@ -267,7 +323,7 @@ onUnmounted(() => {
           overflow: hidden;
           overflow: hidden;
           white-space: pre;
           white-space: pre;
           text-overflow: ellipsis;
           text-overflow: ellipsis;
-          width: calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          width: calc(80 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
         }
         }
       }
       }
       >.point-bonus{
       >.point-bonus{
@@ -283,27 +339,25 @@ onUnmounted(() => {
           overflow: hidden;
           overflow: hidden;
           white-space: pre;
           white-space: pre;
           text-overflow: ellipsis;
           text-overflow: ellipsis;
-          width: calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          width: calc(80 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
 
 
         }
         }
       }
       }
     }
     }
     >button.change-password{
     >button.change-password{
-      width: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-      height: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-      margin-left: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      flex: 0 0 auto;
+      width: calc(32 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      margin-left: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       >img{
       >img{
         width: 100%;
         width: 100%;
-        height: 100%;
       }
       }
     }
     }
-    >button.log-out{
-      width: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-      height: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
-      margin-left: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >button.logout{
+      flex: 0 0 auto;
+      width: calc(32 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      margin-left: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
       >img{
       >img{
         width: 100%;
         width: 100%;
-        height: 100%;
       }
       }
     }
     }
   }
   }

+ 245 - 0
game/src/views/LoginView.vue

@@ -0,0 +1,245 @@
+<template>
+  <div
+    class="login-view"
+  >
+    <div class="bg-deco" />
+
+    <img
+      class="logo-and-title"
+      src="@/assets/images/logo-and-title.png"
+      alt=""
+      draggable="false"
+    >
+
+    <div class="form-item">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-account.png"
+          alt=""
+          draggable="false"
+        >
+        <span>帐号</span>
+      </div>
+      <input
+        v-model.trim="account"
+        type="text"
+        placeholder="请输入邮箱"
+        aotufocus
+        :class="{
+          invalid: isAccountInputOver && !isAccountValid
+        }"
+        @blur="isAccountInputOver=true"
+      >
+    </div>
+    <div class="form-item form-item-password">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-lock.png"
+          alt=""
+          draggable="false"
+        >
+        <span>密码</span>
+      </div>
+      <input
+        v-model="password"
+        type="password"
+        placeholder="6-10个字,数字或字母"
+        :class="{
+          invalid: isPasswordInputOver && !isPasswordValid
+        }"
+        @blur="isPasswordInputOver=true"
+      >
+    </div>
+
+    <button
+      class="forget"
+      @click="router.push({
+        name: 'FindPassword',
+      })"
+    >
+      忘记密码?
+    </button>
+
+    <button
+      class="submit"
+      @click="submit"
+    >
+      登录
+    </button>
+    <button
+      class="sign-up"
+      @click="$router.push({
+        name: 'SignUp',
+      })"
+    >
+      暂无账号,去注册
+    </button>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+import { showDialog } from 'vant'
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const account = ref('')
+const password = ref('')
+
+const accountTrimed = computed(() => {
+  return account.value.trim()
+})
+const passwordTrimed = computed(() => {
+  return password.value.trim()
+})
+
+const isAccountValid = computed(() => {
+  return !!accountTrimed.value
+})
+const isPasswordValid = computed(() => {
+  return passwordTrimed.value.length >= 6 && passwordTrimed.value.length <= 10 && passwordTrimed.value.search(/^[0-9a-zA-Z].*$/) === 0
+})
+
+const isAccountInputOver = ref(false)
+const isPasswordInputOver = ref(false)
+
+function submit() {
+  if (!isAccountValid.value) {
+    showDialog({
+      message: '请正确输入邮箱',
+      theme: 'round-button',
+    })
+    isAccountInputOver.value = true
+    return
+  }
+  if (!isPasswordValid.value) {
+    showDialog({
+      message: '请正确输入密码',
+      theme: 'round-button',
+    })
+    isPasswordInputOver.value = true
+    return
+  }
+
+  api.login(accountTrimed.value, passwordTrimed.value).then(() => {
+    router.replace({
+      name: 'HomeView'
+    })
+  }).catch((err) => {
+    showDialog({
+      message: err,
+      theme: 'round-button',
+    })
+  })
+}
+</script>
+
+<style lang="less" scoped>
+.login-view{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  >.bg-deco{
+    position: absolute;
+    left: calc(-67 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    bottom: calc(-130 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: #D1B489;
+    filter: blur(calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')));
+  }
+  >.logo-and-title{
+    margin-top: calc(53 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(120 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(46 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >.form-item{
+    width: calc(321 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >.title{
+      width: 100%;
+      display: flex;
+      align-items: center;
+      margin-bottom: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        width: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        height: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >span{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #D1B489;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input{
+      width: 100%;
+      height: calc(29 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      border-bottom: 1px solid #D9D9D9;
+      font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      font-family: Source Han Sans SC, Source Han Sans SC;
+      font-weight: 400;
+      color: black;
+      line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      &::placeholder {
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #B8B8B8;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input.invalid{
+      border-bottom: 1px solid red;
+    }
+  }
+  >button.forget{
+    margin-top: calc(-14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-left: auto;
+    margin-right: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #B4B4B4;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >button.submit{
+    margin-top: calc(73 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(332 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(56 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: rgba(197, 161, 108, 0.8);
+    border-radius: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #FFFFFF;
+    line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >button.sign-up{
+    font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #A97C46;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    opacity: 0.5;
+  }
+}
+</style>

+ 8 - 8
game/src/views/PairUp.vue

@@ -165,13 +165,6 @@ function replay() {
  * 具体游戏规则
  * 具体游戏规则
  */
  */
 const logoFileNameList = [
 const logoFileNameList = [
-  '中海基金管理有限公司.png',
-  '中航证券有限公司.png',
-  '中欧基金管理有限公司.png',
-  '中信期货有限公司.png',
-  '中信证券股份有限公司.png',
-  '中证数据有限责任公司.png',
-  '朱雀基金管理有限公司.png',
   '北京中金公益基金会.png',
   '北京中金公益基金会.png',
   '创金合信基金管理有限公司.png',
   '创金合信基金管理有限公司.png',
   '德邦基金管理有限公司.png',
   '德邦基金管理有限公司.png',
@@ -190,7 +183,7 @@ const logoFileNameList = [
   '鹏扬基金管理有限公司.png',
   '鹏扬基金管理有限公司.png',
   '平安证券股份有限公司.png',
   '平安证券股份有限公司.png',
   '睿远公益基金会.png',
   '睿远公益基金会.png',
-  '上海东方证券资产管理有限公司.png',
+  '上海东方证券资产管理有限公司.jpg',
   '上海证券交易所公益基金会.png',
   '上海证券交易所公益基金会.png',
   '深圳市银华公益基金会.png',
   '深圳市银华公益基金会.png',
   '泰达宏利基金.png',
   '泰达宏利基金.png',
@@ -203,6 +196,13 @@ const logoFileNameList = [
   '中国金融期货交易所.png',
   '中国金融期货交易所.png',
   '中国期货市场监控中心有限责任公司.png',
   '中国期货市场监控中心有限责任公司.png',
   '中国证券登记结算有限责任公司.png',
   '中国证券登记结算有限责任公司.png',
+  '中海基金管理有限公司.png',
+  '中航证券有限公司.png',
+  '中欧基金管理有限公司.png',
+  '中信期货有限公司.png',
+  '中信证券股份有限公司.png',
+  '中证数据有限责任公司.png',
+  '朱雀基金管理有限公司.png',
 ]
 ]
 const cardList = ref([])
 const cardList = ref([])
 function setCardList() {
 function setCardList() {

+ 291 - 3
game/src/views/SignUp.vue

@@ -1,6 +1,106 @@
 <template>
 <template>
-  <div class="asdf">
-    sdlfjslkdfj
+  <div
+    class="sign-up"
+  >
+    <div class="bg-deco" />
+
+    <img
+      class="logo-and-title"
+      src="@/assets/images/logo-and-title.png"
+      alt=""
+      draggable="false"
+    >
+
+    <div class="form-item">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-account.png"
+          alt=""
+          draggable="false"
+        >
+        <span>帐号</span>
+      </div>
+      <input
+        v-model.trim="account"
+        type="text"
+        placeholder="请输入邮箱"
+        aotufocus
+        :class="{
+          invalid: isAccountInputOver && !isAccountValid
+        }"
+        @blur="isAccountInputOver=true"
+      >
+    </div>
+    <div class="form-item">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-phone.png"
+          alt=""
+          draggable="false"
+        >
+        <span>手机号</span>
+      </div>
+      <input
+        v-model="phone"
+        type="number"
+        placeholder="请输入11位手机号"
+        :class="{
+          invalid: isPhoneInputOver && !isPhoneValid
+        }"
+        @blur="isPhoneInputOver=true"
+      >
+    </div>
+    <div class="form-item form-item-password">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-lock.png"
+          alt=""
+          draggable="false"
+        >
+        <span>密码</span>
+      </div>
+      <input
+        v-model="password"
+        type="password"
+        placeholder="6-10个字,数字或字母"
+        :class="{
+          invalid: isPasswordInputOver && !isPasswordValid
+        }"
+        @blur="isPasswordInputOver=true"
+      >
+    </div>
+    <div class="form-item form-item-password-repeat">
+      <div class="title">
+        <img
+          src="@/assets/images/icon-lock.png"
+          alt=""
+          draggable="false"
+        >
+        <span>确认密码</span>
+      </div>
+      <input
+        v-model="passwordRepeat"
+        type="password"
+        placeholder="6-10个字,数字或字母"
+        :class="{
+          invalid: isPasswordRepeatInputOver && !isPasswordRepeatValid
+        }"
+        @blur="isPasswordRepeatInputOver=true"
+      >
+    </div>
+
+    <button
+      class="submit"
+      @click="submit"
+    >
+      注册并登录
+    </button>
+    <button
+      class="return"
+      @click="$router.go(-1)"
+    >
+      返回
+    </button>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -8,13 +108,201 @@
 import { ref, computed, watch, onMounted } from "vue"
 import { ref, computed, watch, onMounted } from "vue"
 import { useRoute, useRouter } from "vue-router"
 import { useRoute, useRouter } from "vue-router"
 import { useStore } from "vuex"
 import { useStore } from "vuex"
+import { showDialog } from 'vant'
+import { signUp, login } from '@/api.js'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 const store = useStore()
 const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const account = ref('')
+const phone = ref('')
+const password = ref('')
+const passwordRepeat = ref('')
+
+const accountTrimed = computed(() => {
+  return account.value.trim()
+})
+const phoneTrimed = computed(() => {
+  return phone.value.toString().trim()
+})
+const passwordTrimed = computed(() => {
+  return password.value.trim()
+})
+const passwordRepeatTrimed = computed(() => {
+  return passwordRepeat.value.trim()
+})
+
+const isAccountValid = computed(() => {
+  return !!accountTrimed.value
+})
+const isPhoneValid = computed(() => {
+  return phoneTrimed.value.length === 11
+})
+const isPasswordValid = computed(() => {
+  return passwordTrimed.value.length >= 6 && passwordTrimed.value.length <= 10 && passwordTrimed.value.search(/^[0-9a-zA-Z].*$/) === 0
+})
+const isPasswordRepeatValid = computed(() => {
+  return passwordTrimed.value === passwordRepeatTrimed.value
+})
+
+const isAccountInputOver = ref(false)
+const isPhoneInputOver = ref(false)
+const isPasswordInputOver = ref(false)
+const isPasswordRepeatInputOver = ref(false)
+
+
+
+function submit() {
+  if (!isAccountValid.value) {
+    showDialog({
+      message: '请正确输入邮箱',
+      theme: 'round-button',
+    })
+    isAccountInputOver.value = true
+    return
+  }
+  if (!isPhoneValid.value) {
+    showDialog({
+      message: '请正确输入手机号',
+      theme: 'round-button',
+    })
+    isPhoneInputOver.value = true
+    return
+  }
+  if (!isPasswordValid.value) {
+    showDialog({
+      message: '请正确输入密码',
+      theme: 'round-button',
+    })
+    isPasswordInputOver.value = true
+    return
+  }
+  if (!isPasswordRepeatValid.value) {
+    showDialog({
+      message: '请正确输入确认密码',
+      theme: 'round-button',
+    })
+    isPasswordRepeatInputOver.value = true
+    return
+  }
+
+  signUp(accountTrimed.value, phoneTrimed.value, passwordTrimed.value, passwordRepeatTrimed.value).then(() => {
+    showDialog({
+      message: '注册成功',
+      theme: 'round-button',
+    }).then(() => {
+      login(accountTrimed.value, passwordTrimed.value)
+    }).then(() => {
+      router.replace({
+        name: 'HomeView'
+      })
+    }).catch((err) => {
+      showDialog({
+        message: err,
+        theme: 'round-button',
+      })
+    })
+  }).catch((err) => {
+    showDialog({
+      message: err,
+      theme: 'round-button',
+    })
+  })
+}
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
-.asdf{
+.sign-up{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  >.bg-deco{
+    position: absolute;
+    left: calc(-67 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    bottom: calc(-130 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(250 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: #D1B489;
+    filter: blur(calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')));
+  }
+  >.logo-and-title{
+    margin-top: calc(53 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(120 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(38 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >.form-item{
+    width: calc(321 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-bottom: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >.title{
+      width: 100%;
+      display: flex;
+      align-items: center;
+      margin-bottom: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        width: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        height: calc(22 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >span{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #D1B489;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input{
+      width: 100%;
+      height: calc(29 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      border-bottom: 1px solid #D9D9D9;
+      font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      font-family: Source Han Sans SC, Source Han Sans SC;
+      font-weight: 400;
+      color: black;
+      line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      &::placeholder {
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #B8B8B8;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+    >input.invalid{
+      border-bottom: 1px solid red;
+    }
+  }
+  >button.submit{
+    width: calc(332 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(56 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: rgba(197, 161, 108, 0.8);
+    border-radius: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #FFFFFF;
+    line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >button.return{
+    font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #A97C46;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    opacity: 0.5;
+  }
 }
 }
 </style>
 </style>

+ 5 - 0
game/yarn.lock

@@ -4053,6 +4053,11 @@ joi@^17.4.0:
     "@sideway/formula" "^3.0.1"
     "@sideway/formula" "^3.0.1"
     "@sideway/pinpoint" "^2.0.0"
     "@sideway/pinpoint" "^2.0.0"
 
 
+js-base64@^3.7.5:
+  version "3.7.5"
+  resolved "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca"
+  integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==
+
 js-message@1.0.7:
 js-message@1.0.7:
   version "1.0.7"
   version "1.0.7"
   resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
   resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"