Prechádzať zdrojové kódy

Merge branch 'master' of http://192.168.0.115:3000/chenzimin2/ZGZQBWG

aamin 2 rokov pred
rodič
commit
cc46bec979
86 zmenil súbory, kde vykonal 1837 pridanie a 42 odobranie
  1. 2 1
      game/.env.dev
  2. 1 0
      game/.env.mytest
  3. 1 0
      game/.env.prod
  4. 1 0
      game/README.md
  5. 3 1
      game/package.json
  6. BIN
      game/public/favicon.ico
  7. 1 1
      game/public/index.html
  8. 4 1
      game/src/App.vue
  9. 136 14
      game/src/api.js
  10. BIN
      game/src/assets/images/antibody-battle-entry.png
  11. BIN
      game/src/assets/images/avatar.png
  12. BIN
      game/src/assets/images/btn-login-bg.png
  13. BIN
      game/src/assets/images/btn-sign-up-bg.png
  14. BIN
      game/src/assets/images/disaster-relief-entry.png
  15. BIN
      game/src/assets/images/enviroment-protection.png
  16. BIN
      game/src/assets/images/exam-paper-entry.png
  17. BIN
      game/src/assets/images/icon-account.png
  18. BIN
      game/src/assets/images/icon-exit.png
  19. BIN
      game/src/assets/images/icon-lock.png
  20. BIN
      game/src/assets/images/icon-password.png
  21. BIN
      game/src/assets/images/icon-phone.png
  22. BIN
      game/src/assets/images/icon-present.png
  23. BIN
      game/src/assets/images/icon-share.png
  24. BIN
      game/src/assets/images/logo-and-title.png
  25. BIN
      game/src/assets/images/lost-children-entry.png
  26. BIN
      game/src/assets/images/pair-up-entry.png
  27. BIN
      game/src/assets/images/pair-up-logos/万家基金管理有限公司.png
  28. BIN
      game/src/assets/images/pair-up-logos/上海东方证券资产管理有限公司.jpg
  29. BIN
      game/src/assets/images/pair-up-logos/上海东方证券资产管理有限公司.png
  30. BIN
      game/src/assets/images/pair-up-logos/上海证券交易所公益基金会.png
  31. BIN
      game/src/assets/images/pair-up-logos/东方财富证券股份有限公司.gif
  32. BIN
      game/src/assets/images/pair-up-logos/东方财富证券股份有限公司.png
  33. BIN
      game/src/assets/images/pair-up-logos/东海证券股份有限公司.jpg
  34. BIN
      game/src/assets/images/pair-up-logos/东海证券股份有限公司.png
  35. BIN
      game/src/assets/images/pair-up-logos/中信期货有限公司.png
  36. BIN
      game/src/assets/images/pair-up-logos/中信证券股份有限公司.png
  37. BIN
      game/src/assets/images/pair-up-logos/中国期货市场监控中心有限责任公司.png
  38. BIN
      game/src/assets/images/pair-up-logos/中国证券登记结算有限责任公司.png
  39. BIN
      game/src/assets/images/pair-up-logos/中国金融期货交易所.jpg
  40. BIN
      game/src/assets/images/pair-up-logos/中国金融期货交易所.png
  41. BIN
      game/src/assets/images/pair-up-logos/中欧基金管理有限公司.png
  42. BIN
      game/src/assets/images/pair-up-logos/中海基金管理有限公司.png
  43. BIN
      game/src/assets/images/pair-up-logos/中航证券有限公司.png
  44. BIN
      game/src/assets/images/pair-up-logos/中证数据有限责任公司.png
  45. BIN
      game/src/assets/images/pair-up-logos/兴业证券股份有限公司.png
  46. BIN
      game/src/assets/images/pair-up-logos/兴证全球基金管理有限公司.png
  47. BIN
      game/src/assets/images/pair-up-logos/创金合信基金管理有限公司.png
  48. BIN
      game/src/assets/images/pair-up-logos/北京中金公益基金会.png
  49. BIN
      game/src/assets/images/pair-up-logos/华安基金管理有限公司.png
  50. BIN
      game/src/assets/images/pair-up-logos/华泰证券股份有限公司.jpg
  51. BIN
      game/src/assets/images/pair-up-logos/华泰证券股份有限公司.png
  52. BIN
      game/src/assets/images/pair-up-logos/南华期货股份有限公司.png
  53. BIN
      game/src/assets/images/pair-up-logos/国元证券股份有限公司.png
  54. BIN
      game/src/assets/images/pair-up-logos/国盛证券有限责任公司.png
  55. BIN
      game/src/assets/images/pair-up-logos/圆信永丰基金管理有限公司.png
  56. BIN
      game/src/assets/images/pair-up-logos/富国基金管理有限公司.png
  57. BIN
      game/src/assets/images/pair-up-logos/平安证券股份有限公司.png
  58. BIN
      game/src/assets/images/pair-up-logos/广发证券股份有限公司.png
  59. BIN
      game/src/assets/images/pair-up-logos/德邦基金管理有限公司.png
  60. BIN
      game/src/assets/images/pair-up-logos/朱雀基金管理有限公司.png
  61. BIN
      game/src/assets/images/pair-up-logos/汇添富基金管理股份有限公司.jpg
  62. BIN
      game/src/assets/images/pair-up-logos/汇添富基金管理股份有限公司.png
  63. BIN
      game/src/assets/images/pair-up-logos/泰达宏利基金.png
  64. BIN
      game/src/assets/images/pair-up-logos/深圳市银华公益基金会.jpg
  65. BIN
      game/src/assets/images/pair-up-logos/深圳市银华公益基金会.png
  66. BIN
      game/src/assets/images/pair-up-logos/睿远公益基金会.jpg
  67. BIN
      game/src/assets/images/pair-up-logos/睿远公益基金会.png
  68. BIN
      game/src/assets/images/pair-up-logos/郑州商品交易所.jpg
  69. BIN
      game/src/assets/images/pair-up-logos/郑州商品交易所.png
  70. BIN
      game/src/assets/images/pair-up-logos/金信期货有限公司.png
  71. BIN
      game/src/assets/images/pair-up-logos/长江证券股份有限公司.png
  72. BIN
      game/src/assets/images/pair-up-logos/鹏华基金管理有限公司.png
  73. BIN
      game/src/assets/images/pair-up-logos/鹏扬基金管理有限公司.jpg
  74. BIN
      game/src/assets/images/pair-up-logos/鹏扬基金管理有限公司.png
  75. BIN
      game/src/assets/images/plant-tree-entry.png
  76. 106 0
      game/src/pass.js
  77. 30 0
      game/src/router/index.js
  78. 35 5
      game/src/store/index.js
  79. 263 0
      game/src/views/ChangePassword.vue
  80. 235 0
      game/src/views/FindPassword.vue
  81. 46 0
      game/src/views/GameByUnity.vue
  82. 367 2
      game/src/views/HomeView.vue
  83. 245 0
      game/src/views/LoginView.vue
  84. 17 17
      game/src/views/PairUp.vue
  85. 308 0
      game/src/views/SignUp.vue
  86. 36 0
      game/yarn.lock

+ 2 - 1
game/.env.dev

@@ -1,3 +1,4 @@
 VUE_APP_CLI_MODE=dev
 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
 NODE_ENV=production
 PUBLIC_PATH=./
+VUE_APP_API_PREFIX=https://wlgbwg.4dage.com

+ 1 - 0
game/.env.prod

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

+ 1 - 0
game/README.md

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

+ 3 - 1
game/package.json

@@ -11,15 +11,17 @@
   "dependencies": {
     "@vueuse/core": "^10.4.1",
     "axios": "^1.1.3",
+    "clipboard": "^2.0.11",
     "core-js": "^3.8.3",
     "dayjs": "^1.11.7",
+    "js-base64": "^3.7.5",
     "lodash": "^4.17.21",
     "mitt": "^3.0.0",
     "v-viewer": "^3.0.11",
     "vant": "^4.8.2",
     "viewerjs": "^1.11.6",
-    "vue": "^3.2.13",
     "vue-router": "^4.0.3",
+    "vue": "^3.2.13",
     "vuex": "^4.0.0"
   },
   "devDependencies": {

BIN
game/public/favicon.ico


+ 1 - 1
game/public/index.html

@@ -4,7 +4,7 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
-    <link rel="icon" href="<%= BASE_URL %>logo.png">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title>证券博物馆小游戏</title>
   </head>
   <body>

+ 4 - 1
game/src/App.vue

@@ -8,7 +8,7 @@
 import { ref, computed, watch, onMounted } from "vue"
 import { useRoute, useRouter } from "vue-router"
 import { useStore } from "vuex"
-
+import { checkLoginStatusAndProcess } from '@/api.js'
 const {
   windowSizeInCssForRef,
   windowSizeWhenDesignForRef,
@@ -17,6 +17,9 @@ const {
 const route = useRoute()
 const router = useRouter()
 const store = useStore()
+
+checkLoginStatusAndProcess()
+
 </script>
 
 <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/antibody-battle-entry.png


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


BIN
game/src/assets/images/btn-login-bg.png


BIN
game/src/assets/images/btn-sign-up-bg.png


BIN
game/src/assets/images/disaster-relief-entry.png


BIN
game/src/assets/images/enviroment-protection.png


BIN
game/src/assets/images/exam-paper-entry.png


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


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


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


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


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


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


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


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


BIN
game/src/assets/images/lost-children-entry.png


BIN
game/src/assets/images/pair-up-entry.png


BIN
game/src/assets/images/pair-up-logos/万家基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/上海东方证券资产管理有限公司.jpg


BIN
game/src/assets/images/pair-up-logos/上海东方证券资产管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/上海证券交易所公益基金会.png


BIN
game/src/assets/images/pair-up-logos/东方财富证券股份有限公司.gif


BIN
game/src/assets/images/pair-up-logos/东方财富证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/东海证券股份有限公司.jpg


BIN
game/src/assets/images/pair-up-logos/东海证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/中信期货有限公司.png


BIN
game/src/assets/images/pair-up-logos/中信证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/中国期货市场监控中心有限责任公司.png


BIN
game/src/assets/images/pair-up-logos/中国证券登记结算有限责任公司.png


BIN
game/src/assets/images/pair-up-logos/中国金融期货交易所.jpg


BIN
game/src/assets/images/pair-up-logos/中国金融期货交易所.png


BIN
game/src/assets/images/pair-up-logos/中欧基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/中海基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/中航证券有限公司.png


BIN
game/src/assets/images/pair-up-logos/中证数据有限责任公司.png


BIN
game/src/assets/images/pair-up-logos/兴业证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/兴证全球基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/创金合信基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/北京中金公益基金会.png


BIN
game/src/assets/images/pair-up-logos/华安基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/华泰证券股份有限公司.jpg


BIN
game/src/assets/images/pair-up-logos/华泰证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/南华期货股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/国元证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/国盛证券有限责任公司.png


BIN
game/src/assets/images/pair-up-logos/圆信永丰基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/富国基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/平安证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/广发证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/德邦基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/朱雀基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/汇添富基金管理股份有限公司.jpg


BIN
game/src/assets/images/pair-up-logos/汇添富基金管理股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/泰达宏利基金.png


BIN
game/src/assets/images/pair-up-logos/深圳市银华公益基金会.jpg


BIN
game/src/assets/images/pair-up-logos/深圳市银华公益基金会.png


BIN
game/src/assets/images/pair-up-logos/睿远公益基金会.jpg


BIN
game/src/assets/images/pair-up-logos/睿远公益基金会.png


BIN
game/src/assets/images/pair-up-logos/郑州商品交易所.jpg


BIN
game/src/assets/images/pair-up-logos/郑州商品交易所.png


BIN
game/src/assets/images/pair-up-logos/金信期货有限公司.png


BIN
game/src/assets/images/pair-up-logos/长江证券股份有限公司.png


BIN
game/src/assets/images/pair-up-logos/鹏华基金管理有限公司.png


BIN
game/src/assets/images/pair-up-logos/鹏扬基金管理有限公司.jpg


BIN
game/src/assets/images/pair-up-logos/鹏扬基金管理有限公司.png


BIN
game/src/assets/images/plant-tree-entry.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
+  }
+}

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

@@ -1,10 +1,15 @@
 import { createRouter, createWebHashHistory } from 'vue-router'
 import HomeView from '../views/HomeView.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 ExamPaper1 from '../views/ExamPaper1.vue'
 import ExamPaper2 from '../views/ExamPaper2.vue'
 import ExamPaper3 from '../views/ExamPaper3.vue'
 import PairUp from '../views/PairUp.vue'
+import GameByUnity from '../views/GameByUnity.vue'
 // import store from '@/store/index.js'
 
 const routes = [
@@ -14,6 +19,26 @@ const routes = [
     component: HomeView,
   },
   {
+    path: '/sign-up',
+    name: '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',
     name: 'PlantTree',
     component: PlantTree,
@@ -37,6 +62,11 @@ const routes = [
     path: '/pair-up',
     name: '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({
   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: '',
-    userName: '',
-    bonusPoint: 0,
   },
   getters: {
   },
   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) {
       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>

+ 367 - 2
game/src/views/HomeView.vue

@@ -2,23 +2,388 @@
   <div
     class="home"
   >
-    sdfsf
+    <div
+      v-if="!store.state.loginStatus"
+      class="for-visitor"
+    >
+      <button
+        class="login"
+        @click="onClickLogin"
+      >
+        <img
+          class=""
+          src="@/assets/images/btn-login-bg.png"
+          alt=""
+          draggable="false"
+        >
+        <span>登录</span>
+      </button>
+      <button
+        class="sign-up"
+        @click="onClickSignUp"
+      >
+        <img
+          class=""
+          src="@/assets/images/btn-sign-up-bg.png"
+          alt=""
+          draggable="false"
+        >
+        <span>注册</span>
+      </button>
+    </div>
+    <div
+      v-else
+      class="after-login"
+    >
+      <img
+        class="avatar"
+        src="@/assets/images/avatar.png"
+        alt=""
+        draggable="false"
+      >
+      <div class="info-wrap">
+        <div class="user-id">
+          <span class="title">用户ID:</span>
+          <span
+            class="value"
+            :title="store.state.userInfo.userName"
+          >{{ store.state.userInfo.userName }}</span>
+        </div>
+        <div class="point-bonus">
+          <span class="title">当前积分:</span>
+          <span class="value">{{ store.state.userInfo.score }}</span>
+        </div>
+      </div>
+      <button
+        class="change-password"
+        @click="router.push({
+          name: 'ChangePassword',
+        })"
+      >
+        <img
+          class=""
+          src="@/assets/images/icon-password.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <button
+        class="logout"
+        @click="onClickLogout"
+      >
+        <img
+          class=""
+          src="@/assets/images/icon-exit.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+    </div>
+    <div class="entry-list">
+      <!-- 乡村林场 -->
+      <button
+        class="game-entry plant-tree-entry"
+        @click="router.push({
+          name: 'PlantTree',
+        })"
+      >
+        <img
+          class=""
+          src="@/assets/images/plant-tree-entry.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <!-- 助农课堂 -->
+      <button
+        class="game-entry exam-paper-entry"
+        @click="router.push({
+          name: 'ExamPaper1',
+        })"
+      >
+        <img
+          class=""
+          src="@/assets/images/exam-paper-entry.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <!-- 企业翻翻看 -->
+      <button
+        class="game-entry pair-up-entry"
+        @click="router.push({
+          name: 'PairUp',
+        })"
+      >
+        <img
+          class=""
+          src="@/assets/images/pair-up-entry.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <!-- 抗体大作战 -->
+      <button class="game-entry antibody-battle-entry">
+        <img
+          class=""
+          src="@/assets/images/antibody-battle-entry.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <!-- 应急救灾 -->
+      <button
+        class="game-entry disaster-relief-entry"
+        @click="router.push({
+          name: 'GameByUnity',
+          query: {
+            gameName: 'DisasterRelief'
+          },
+        })"
+      >
+        <img
+          class=""
+          src="@/assets/images/disaster-relief-entry.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <!-- 生态保护 -->
+      <button class="game-entry enviroment-protection">
+        <img
+          class=""
+          src="@/assets/images/enviroment-protection.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <!-- 找回走失儿童 -->
+      <button class="game-entry lost-children-entry">
+        <img
+          class=""
+          src="@/assets/images/lost-children-entry.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+    </div>
+
+    <!-- 悬浮按钮 -->
+    <div class="floating-btn-group">
+      <button
+        v-if="store.state.loginStatus"
+        class="redeem"
+      >
+        <img
+          class=""
+          src="@/assets/images/icon-present.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+      <button
+        class="share"
+        :data-clipboard-text="shareText"
+      >
+        <img
+          class=""
+          src="@/assets/images/icon-share.png"
+          alt=""
+          draggable="false"
+        >
+      </button>
+    </div>
   </div>
 </template>
 
 <script setup>
-import { ref, computed, watch, onMounted } from "vue"
+import { ref, computed, watch, onMounted, onUnmounted } from "vue"
 import { useRoute, useRouter } from "vue-router"
 import { useStore } from "vuex"
+import ClipboardJS from 'clipboard'
+import { showDialog } from 'vant'
+import { logout } from '@/api.js'
 
 const route = useRoute()
 const router = useRouter()
 const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+function onClickLogin() {
+  router.push({
+    name: 'LoginView',
+  })
+}
+
+function onClickSignUp() {
+  router.push({
+    name: 'SignUp',
+  })
+}
+
+function onClickLogout() {
+  logout()
+}
+
+const shareText = location.href
+const clipboard = new ClipboardJS('button.share')
+clipboard.on('success', function(e) {
+  showDialog({
+    message: '链接已复制',
+    theme: 'round-button',
+  })
+})
+// clipboard.on('error', function(e) {
+//   showDialog({
+//       message: '请使用浏览器原生功能进行分享',
+//       theme: 'round-button',
+//     })
+// });
+onUnmounted(() => {
+  clipboard.destroy()
+})
 </script>
 
 <style lang="less" scoped>
 .home {
   width: 100%;
   height: 100%;
+  overflow: auto;
+  >.for-visitor{
+    margin-top: calc(18 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding-left: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-right: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >button{
+      width: calc(170 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      position: relative;
+      >img{
+        width: 100%;
+        height: 100%;
+      }
+      >span{
+        position: absolute;
+        left: calc(100 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        top: calc(32 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-size: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: bold;
+        color: #A97C46;
+        line-height: calc(28 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+  }
+  >.after-login{
+    margin-top: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    display: flex;
+    align-items: center;
+    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-bottom: calc(13 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: rgba(226, 201, 163, 0.5);
+    box-shadow: inset -1px -2px 1px 0px #E2C9A3;
+    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{
+      flex: 0 0 auto;
+      width: 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'));
+      object-fit: cover;
+    }
+    >.info-wrap{
+      flex: 0 0 auto;
+      margin-left: 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-weight: 400;
+      color: #A97C46;
+      line-height: 1.5;
+      white-space: pre;
+      >.user-id{
+        >.title{
+          vertical-align: middle;
+          display: inline-block;
+          width: calc(69 / 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'));
+        }
+        >.value{
+          vertical-align: middle;
+          display: inline-block;
+          overflow: hidden;
+          white-space: pre;
+          text-overflow: ellipsis;
+          width: calc(80 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        }
+      }
+      >.point-bonus{
+        >.title{
+          vertical-align: middle;
+          display: inline-block;
+          width: calc(69 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        }
+        >.value{
+          vertical-align: middle;
+          display: inline-block;
+          overflow: hidden;
+          white-space: pre;
+          text-overflow: ellipsis;
+          width: calc(80 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+
+        }
+      }
+    }
+    >button.change-password{
+      flex: 0 0 auto;
+      width: calc(32 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      margin-left: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >img{
+        width: 100%;
+      }
+    }
+    >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{
+        width: 100%;
+      }
+    }
+  }
+  >.entry-list{
+    margin-left: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    margin-right: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >button.game-entry{
+      width: 100%;
+      >img{
+        width: 100%;
+      }
+    }
+  }
+  >.floating-btn-group{
+    position: fixed;
+    right: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    bottom: calc(58 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >button{
+      width: calc(60 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      height: calc(60 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      display: block;
+      >img{
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
 }
 </style>

+ 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>

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

@@ -165,44 +165,44 @@ function replay() {
  * 具体游戏规则
  */
 const logoFileNameList = [
-  '中海基金管理有限公司.png',
-  '中航证券有限公司.png',
-  '中欧基金管理有限公司.png',
-  '中信期货有限公司.png',
-  '中信证券股份有限公司.png',
-  '中证数据有限责任公司.png',
-  '朱雀基金管理有限公司.png',
   '北京中金公益基金会.png',
   '创金合信基金管理有限公司.png',
   '德邦基金管理有限公司.png',
-  '东方财富证券股份有限公司.gif',
-  '东海证券股份有限公司.jpg',
+  '东方财富证券股份有限公司.png',
+  '东海证券股份有限公司.png',
   '富国基金管理有限公司.png',
   '广发证券股份有限公司.png',
   '国盛证券有限责任公司.png',
   '国元证券股份有限公司.png',
   '华安基金管理有限公司.png',
-  '华泰证券股份有限公司.jpg',
-  '汇添富基金管理股份有限公司.jpg',
+  '华泰证券股份有限公司.png',
+  '汇添富基金管理股份有限公司.png',
   '金信期货有限公司.png',
   '南华期货股份有限公司.png',
   '鹏华基金管理有限公司.png',
-  '鹏扬基金管理有限公司.jpg',
+  '鹏扬基金管理有限公司.png',
   '平安证券股份有限公司.png',
-  '睿远公益基金会.jpg',
-  '上海东方证券资产管理有限公司.png',
+  '睿远公益基金会.png',
+  '上海东方证券资产管理有限公司.jpg',
   '上海证券交易所公益基金会.png',
-  '深圳市银华公益基金会.jpg',
+  '深圳市银华公益基金会.png',
   '泰达宏利基金.png',
   '万家基金管理有限公司.png',
   '兴业证券股份有限公司.png',
   '兴证全球基金管理有限公司.png',
   '圆信永丰基金管理有限公司.png',
   '长江证券股份有限公司.png',
-  '郑州商品交易所.jpg',
-  '中国金融期货交易所.jpg',
+  '郑州商品交易所.png',
+  '中国金融期货交易所.png',
   '中国期货市场监控中心有限责任公司.png',
   '中国证券登记结算有限责任公司.png',
+  '中海基金管理有限公司.png',
+  '中航证券有限公司.png',
+  '中欧基金管理有限公司.png',
+  '中信期货有限公司.png',
+  '中信证券股份有限公司.png',
+  '中证数据有限责任公司.png',
+  '朱雀基金管理有限公司.png',
 ]
 const cardList = ref([])
 function setCardList() {

+ 308 - 0
game/src/views/SignUp.vue

@@ -0,0 +1,308 @@
+<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">
+      <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>
+</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 { signUp, login } 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 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>
+
+<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>

+ 36 - 0
game/yarn.lock

@@ -2399,6 +2399,15 @@ cli-spinners@^2.5.0:
   resolved "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41"
   integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==
 
+clipboard@^2.0.11:
+  version "2.0.11"
+  resolved "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5"
+  integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
+  dependencies:
+    good-listener "^1.2.2"
+    select "^1.1.2"
+    tiny-emitter "^2.0.0"
+
 clipboardy@^2.3.0:
   version "2.3.0"
   resolved "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"
@@ -2836,6 +2845,11 @@ delayed-stream@~1.0.0:
   resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
   integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
 
+delegate@^3.1.2:
+  version "3.2.0"
+  resolved "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
+  integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==
+
 depd@2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@@ -3586,6 +3600,13 @@ globby@^11.0.2, globby@^11.0.3:
     merge2 "^1.4.1"
     slash "^3.0.0"
 
+good-listener@^1.2.2:
+  version "1.2.2"
+  resolved "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
+  integrity sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==
+  dependencies:
+    delegate "^3.1.2"
+
 gopd@^1.0.1:
   version "1.0.1"
   resolved "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -4032,6 +4053,11 @@ joi@^17.4.0:
     "@sideway/formula" "^3.0.1"
     "@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:
   version "1.0.7"
   resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47"
@@ -5473,6 +5499,11 @@ select-hose@^2.0.0:
   resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
   integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
 
+select@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
+  integrity sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==
+
 selfsigned@^2.1.1:
   version "2.4.1"
   resolved "https://registry.npmmirror.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
@@ -5937,6 +5968,11 @@ thunky@^1.0.2:
   resolved "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
   integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
 
+tiny-emitter@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
+  integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
+
 to-fast-properties@^2.0.0:
   version "2.0.0"
   resolved "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"