Pārlūkot izejas kodu

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

aamin 1 gadu atpakaļ
vecāks
revīzija
6191743925
97 mainītis faili ar 1950 papildinājumiem un 122 dzēšanām
  1. 1 1
      game/.env.dev
  2. 1 1
      game/.env.mytest
  3. 1 1
      game/.env.prod
  4. 1 21
      game/README.md
  5. BIN
      game/public/unity/antibody-battle/Build/Build.data.unityweb
  6. BIN
      game/public/unity/antibody-battle/Build/Build.framework.js.unityweb
  7. 1 0
      game/public/unity/antibody-battle/Build/Build.loader.js
  8. BIN
      game/public/unity/antibody-battle/Build/Build.wasm.unityweb
  9. 33 0
      game/public/unity/antibody-battle/ServiceWorker.js
  10. BIN
      game/public/unity/antibody-battle/TemplateData/bg.jpg
  11. BIN
      game/public/unity/antibody-battle/TemplateData/favicon.ico
  12. BIN
      game/public/unity/antibody-battle/TemplateData/progress-bar-empty-dark.png
  13. BIN
      game/public/unity/antibody-battle/TemplateData/progress-bar-empty-light.png
  14. BIN
      game/public/unity/antibody-battle/TemplateData/progress-bar-full-dark.png
  15. BIN
      game/public/unity/antibody-battle/TemplateData/progress-bar-full-light.png
  16. 7 0
      game/public/unity/antibody-battle/TemplateData/style.css
  17. 116 0
      game/public/unity/antibody-battle/index.html
  18. 15 0
      game/public/unity/antibody-battle/manifest.webmanifest
  19. BIN
      game/public/unity/disaster-relief/Build/Build.data.unityweb
  20. BIN
      game/public/unity/disaster-relief/Build/Build.framework.js.unityweb
  21. 1 0
      game/public/unity/disaster-relief/Build/Build.loader.js
  22. BIN
      game/public/unity/disaster-relief/Build/Build.wasm.unityweb
  23. 33 0
      game/public/unity/disaster-relief/ServiceWorker.js
  24. BIN
      game/public/unity/disaster-relief/TemplateData/bg.jpg
  25. BIN
      game/public/unity/disaster-relief/TemplateData/favicon.ico
  26. BIN
      game/public/unity/disaster-relief/TemplateData/icons/unity-logo-dark.png
  27. BIN
      game/public/unity/disaster-relief/TemplateData/icons/unity-logo-light.png
  28. BIN
      game/public/unity/disaster-relief/TemplateData/progress-bar-empty-dark.png
  29. BIN
      game/public/unity/disaster-relief/TemplateData/progress-bar-empty-light.png
  30. BIN
      game/public/unity/disaster-relief/TemplateData/progress-bar-full-dark.png
  31. BIN
      game/public/unity/disaster-relief/TemplateData/progress-bar-full-light.png
  32. 11 0
      game/public/unity/disaster-relief/TemplateData/style.css
  33. BIN
      game/public/unity/disaster-relief/TemplateData/unity-logo-dark.png
  34. BIN
      game/public/unity/disaster-relief/TemplateData/unity-logo-light.png
  35. 105 0
      game/public/unity/disaster-relief/index.html
  36. 15 0
      game/public/unity/disaster-relief/manifest.webmanifest
  37. 12 0
      game/public/unity/disaster-relief/unity.js
  38. BIN
      game/public/unity/enviroment-protection/Build/Build.data.unityweb
  39. BIN
      game/public/unity/enviroment-protection/Build/Build.framework.js.unityweb
  40. 1 0
      game/public/unity/enviroment-protection/Build/Build.loader.js
  41. BIN
      game/public/unity/enviroment-protection/Build/Build.wasm.unityweb
  42. 33 0
      game/public/unity/enviroment-protection/ServiceWorker.js
  43. BIN
      game/public/unity/enviroment-protection/TemplateData/bg.jpg
  44. BIN
      game/public/unity/enviroment-protection/TemplateData/favicon.ico
  45. BIN
      game/public/unity/enviroment-protection/TemplateData/progress-bar-empty-dark.png
  46. BIN
      game/public/unity/enviroment-protection/TemplateData/progress-bar-empty-light.png
  47. BIN
      game/public/unity/enviroment-protection/TemplateData/progress-bar-full-dark.png
  48. BIN
      game/public/unity/enviroment-protection/TemplateData/progress-bar-full-light.png
  49. 7 0
      game/public/unity/enviroment-protection/TemplateData/style.css
  50. 116 0
      game/public/unity/enviroment-protection/index.html
  51. 15 0
      game/public/unity/enviroment-protection/manifest.webmanifest
  52. BIN
      game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.data.unityweb
  53. BIN
      game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.framework.js.unityweb
  54. 1 0
      game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.loader.js
  55. BIN
      game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.wasm.unityweb
  56. BIN
      game/public/unity/lost-children/Build/寻找丢失儿童.data.unityweb
  57. BIN
      game/public/unity/lost-children/Build/寻找丢失儿童.framework.js.unityweb
  58. 1 0
      game/public/unity/lost-children/Build/寻找丢失儿童.loader.js
  59. BIN
      game/public/unity/lost-children/Build/寻找丢失儿童.wasm.unityweb
  60. 33 0
      game/public/unity/lost-children/ServiceWorker.js
  61. 1 0
      game/public/unity/lost-children/StreamingAssets/UnityServicesProjectConfiguration.json
  62. BIN
      game/public/unity/lost-children/TemplateData/bg.jpg
  63. BIN
      game/public/unity/lost-children/TemplateData/favicon.ico
  64. BIN
      game/public/unity/lost-children/TemplateData/icons/unity-logo-dark.png
  65. BIN
      game/public/unity/lost-children/TemplateData/icons/unity-logo-light.png
  66. BIN
      game/public/unity/lost-children/TemplateData/progress-bar-empty-dark.png
  67. BIN
      game/public/unity/lost-children/TemplateData/progress-bar-empty-light.png
  68. BIN
      game/public/unity/lost-children/TemplateData/progress-bar-full-dark.png
  69. BIN
      game/public/unity/lost-children/TemplateData/progress-bar-full-light.png
  70. 11 0
      game/public/unity/lost-children/TemplateData/style.css
  71. BIN
      game/public/unity/lost-children/TemplateData/unity-logo-dark.png
  72. BIN
      game/public/unity/lost-children/TemplateData/unity-logo-light.png
  73. 104 0
      game/public/unity/lost-children/index.html
  74. 15 0
      game/public/unity/lost-children/manifest.webmanifest
  75. 12 0
      game/public/unity/lost-children/unity.js
  76. 6 5
      game/src/App.vue
  77. 92 13
      game/src/api.js
  78. BIN
      game/src/assets/images/icon-gift.png
  79. BIN
      game/src/assets/images/icon-return.png
  80. BIN
      game/src/assets/images/icon-success.png
  81. BIN
      game/src/assets/images/lost-children-entry.png
  82. BIN
      game/src/assets/images/no-stock.png
  83. BIN
      game/src/assets/images/redeem-banner.png
  84. BIN
      game/src/assets/images/redeem-paper.png
  85. BIN
      game/src/assets/images/tab-item-bottom-line.png
  86. 112 0
      game/src/components/GameRule.vue
  87. 41 0
      game/src/components/NotifyBonusPointReachedLimit.vue
  88. 62 0
      game/src/components/NotifyComp.vue
  89. 19 7
      game/src/router/index.js
  90. 1 1
      game/src/store/index.js
  91. 40 16
      game/src/views/ExamPaper2.vue
  92. 112 5
      game/src/views/GameByUnity.vue
  93. 16 12
      game/src/views/HomeView.vue
  94. 41 11
      game/src/views/PairUp.vue
  95. 51 28
      game/src/views/PlantTree.vue
  96. 269 0
      game/src/views/RedeemForm.vue
  97. 385 0
      game/src/views/ShopView.vue

+ 1 - 1
game/.env.dev

@@ -1,4 +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=https://sit-cnzhengquan.4dage.com/
+VUE_APP_DEPLOY_ORIGIN=https://sit-cnzhengquan.4dage.com

+ 1 - 1
game/.env.mytest

@@ -1,4 +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://sit-cnzhengquan.4dage.com
+VUE_APP_DEPLOY_ORIGIN=https://sit-cnzhengquan.4dage.com

+ 1 - 1
game/.env.prod

@@ -1,4 +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=
+VUE_APP_DEPLOY_ORIGIN=

+ 1 - 21
game/README.md

@@ -10,24 +10,4 @@ https://sit-cnzhengquan.4dage.com/game/index.html#/
 
 
 ## 还缺资源:
 ## 还缺资源:
 
 
-## todo
-积分达到上限时的提示文字样式,每个游戏都要改,而且新增积分始终为0。
-
-
-
-游戏规则
-
-
-
-
-
-
-查看积分记录
-
-积分商城
-
-
-
-
-
-
+## todo

BIN
game/public/unity/antibody-battle/Build/Build.data.unityweb


BIN
game/public/unity/antibody-battle/Build/Build.framework.js.unityweb


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
game/public/unity/antibody-battle/Build/Build.loader.js


BIN
game/public/unity/antibody-battle/Build/Build.wasm.unityweb


+ 33 - 0
game/public/unity/antibody-battle/ServiceWorker.js

@@ -0,0 +1,33 @@
+const cacheName = "4dage-VirusWar-0.1.0";
+const contentToCache = [
+    "Build/Build.loader.js",
+    "Build/Build.framework.js.unityweb",
+    "Build/Build.data.unityweb",
+    "Build/Build.wasm.unityweb",
+    "TemplateData/style.css"
+
+];
+
+self.addEventListener('install', function (e) {
+    console.log('[Service Worker] Install');
+    
+    e.waitUntil((async function () {
+      const cache = await caches.open(cacheName);
+      console.log('[Service Worker] Caching all: app shell and content');
+      await cache.addAll(contentToCache);
+    })());
+});
+
+self.addEventListener('fetch', function (e) {
+    e.respondWith((async function () {
+      let response = await caches.match(e.request);
+      console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
+      if (response) { return response; }
+
+      response = await fetch(e.request);
+      const cache = await caches.open(cacheName);
+      console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
+      cache.put(e.request, response.clone());
+      return response;
+    })());
+});

BIN
game/public/unity/antibody-battle/TemplateData/bg.jpg


BIN
game/public/unity/antibody-battle/TemplateData/favicon.ico


BIN
game/public/unity/antibody-battle/TemplateData/progress-bar-empty-dark.png


BIN
game/public/unity/antibody-battle/TemplateData/progress-bar-empty-light.png


BIN
game/public/unity/antibody-battle/TemplateData/progress-bar-full-dark.png


BIN
game/public/unity/antibody-battle/TemplateData/progress-bar-full-light.png


+ 7 - 0
game/public/unity/antibody-battle/TemplateData/style.css

@@ -0,0 +1,7 @@
+body { padding: 0; margin: 0 }
+#tuanjie-container { position: fixed; width: 100%; height: 100%; }
+#tuanjie-canvas { width: 100%; height: 100%; background: url('bg.jpg') no-repeat center; background-size: cover; }
+#tuanjie-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
+#tuanjie-progress-bar-empty { margin-left: auto; margin-right: auto; width: 141px; height: 18px; margin-top: 10px; background: url('progress-bar-empty-dark.png') no-repeat center }
+#tuanjie-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
+#tuanjie-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }

+ 116 - 0
game/public/unity/antibody-battle/index.html

@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>抗体大作战</title>
+    <link rel="shortcut icon" href="TemplateData/favicon.ico">
+    <link rel="stylesheet" href="TemplateData/style.css">
+    <link rel="manifest" href="manifest.webmanifest">
+  </head>
+  <body>
+    <div id="tuanjie-container">
+      <canvas id="tuanjie-canvas" width=960 height=600 tabindex="-1"></canvas>
+      <div id="tuanjie-loading-bar">
+        <div id="tuanjie-logo"></div>
+        <div id="tuanjie-progress-bar-empty">
+          <div id="tuanjie-progress-bar-full"></div>
+        </div>
+      </div>
+      <div id="tuanjie-warning"> </div>
+    </div>
+    <script>
+	  window.getQueryVariable = function getQueryVariable(variable) {
+		var query = window.location.search.substring(1);
+		var vars = query.split("&");
+		for (var i = 0; i < vars.length; i++) {
+			var pair = vars[i].split("=");
+			if (pair[0] == variable) {
+				return pair[1];
+			}
+		}
+		return false;
+		}
+
+	
+      window.addEventListener("load", function () {
+        if ("serviceWorker" in navigator) {
+          navigator.serviceWorker.register("ServiceWorker.js");
+        }
+      });
+
+      var container = document.querySelector("#tuanjie-container");
+      var canvas = document.querySelector("#tuanjie-canvas");
+      var loadingBar = document.querySelector("#tuanjie-loading-bar");
+      var progressBarFull = document.querySelector("#tuanjie-progress-bar-full");
+      var warningBanner = document.querySelector("#tuanjie-warning");
+
+      // Shows a temporary message banner/ribbon for a few seconds, or
+      // a permanent error message on top of the canvas if type=='error'.
+      // If type=='warning', a yellow highlight color is used.
+      // Modify or remove this function to customize the visually presented
+      // way that non-critical warnings and error messages are presented to the
+      // user.
+      function unityShowBanner(msg, type) {
+        function updateBannerVisibility() {
+          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
+        }
+        var div = document.createElement('div');
+        div.innerHTML = msg;
+        warningBanner.appendChild(div);
+        if (type == 'error') div.style = 'background: red; padding: 10px;';
+        else {
+          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
+          setTimeout(function() {
+            warningBanner.removeChild(div);
+            updateBannerVisibility();
+          }, 5000);
+        }
+        updateBannerVisibility();
+      }
+
+      var buildUrl = "Build";
+      var loaderUrl = buildUrl + "/Build.loader.js";
+      var config = {
+        dataUrl: buildUrl + "/Build.data.unityweb",
+        frameworkUrl: buildUrl + "/Build.framework.js.unityweb",
+        codeUrl: buildUrl + "/Build.wasm.unityweb",
+        streamingAssetsUrl: "StreamingAssets",
+        companyName: "4dage",
+        productName: "VirusWar",
+        productVersion: "0.1.0",
+        showBanner: unityShowBanner,
+      };
+
+      // By default Tuanjie keeps WebGL canvas render target size matched with
+      // the DOM size of the canvas element (scaled by window.devicePixelRatio)
+      // Set this to false if you want to decouple this synchronization from
+      // happening inside the engine, and you would instead like to size up
+      // the canvas DOM size and WebGL render target sizes yourself.
+      // config.matchWebGLToCanvasSize = false;
+
+      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
+        // Mobile device style: fill the whole browser client area with the game canvas:
+        var meta = document.createElement('meta');
+        meta.name = 'viewport';
+        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
+        document.getElementsByTagName('head')[0].appendChild(meta);
+      }
+
+      loadingBar.style.display = "block";
+
+      var script = document.createElement("script");
+      script.src = loaderUrl;
+      script.onload = () => {
+        createTuanjieInstance(canvas, config, (progress) => {
+          progressBarFull.style.width = 100 * progress + "%";
+        }).then((tuanjieInstance) => {
+          loadingBar.style.display = "none";
+        }).catch((message) => {
+          alert(message);
+        });
+      };
+      document.body.appendChild(script);
+    </script>
+  </body>
+</html>

+ 15 - 0
game/public/unity/antibody-battle/manifest.webmanifest

@@ -0,0 +1,15 @@
+{
+    "name": "VirusWar",
+    "short_name": "VirusWar",
+    "start_url": "index.html",
+    "display": "fullscreen",
+    "background_color": "#231F20",
+    "theme_color": "#000",
+    "description": "",
+    "icons": [{
+      "src": "TemplateData/icons/tuanjie-logo-dark.png",
+      "sizes": "144x144",
+      "type": "image/png",
+      "purpose": "any maskable"
+    }]
+  }

BIN
game/public/unity/disaster-relief/Build/Build.data.unityweb


BIN
game/public/unity/disaster-relief/Build/Build.framework.js.unityweb


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
game/public/unity/disaster-relief/Build/Build.loader.js


BIN
game/public/unity/disaster-relief/Build/Build.wasm.unityweb


+ 33 - 0
game/public/unity/disaster-relief/ServiceWorker.js

@@ -0,0 +1,33 @@
+const cacheName = "4dage-RacingCar-0.1.0";
+const contentToCache = [
+    "Build/Build.loader.js",
+    "Build/Build.framework.js.unityweb",
+    "Build/Build.data.unityweb",
+    "Build/Build.wasm.unityweb",
+    "TemplateData/style.css"
+
+];
+
+self.addEventListener('install', function (e) {
+    console.log('[Service Worker] Install');
+    
+    e.waitUntil((async function () {
+      const cache = await caches.open(cacheName);
+      console.log('[Service Worker] Caching all: app shell and content');
+      await cache.addAll(contentToCache);
+    })());
+});
+
+self.addEventListener('fetch', function (e) {
+    e.respondWith((async function () {
+      let response = await caches.match(e.request);
+      console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
+      if (response) { return response; }
+
+      response = await fetch(e.request);
+      const cache = await caches.open(cacheName);
+      console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
+      cache.put(e.request, response.clone());
+      return response;
+    })());
+});

BIN
game/public/unity/disaster-relief/TemplateData/bg.jpg


BIN
game/public/unity/disaster-relief/TemplateData/favicon.ico


BIN
game/public/unity/disaster-relief/TemplateData/icons/unity-logo-dark.png


BIN
game/public/unity/disaster-relief/TemplateData/icons/unity-logo-light.png


BIN
game/public/unity/disaster-relief/TemplateData/progress-bar-empty-dark.png


BIN
game/public/unity/disaster-relief/TemplateData/progress-bar-empty-light.png


BIN
game/public/unity/disaster-relief/TemplateData/progress-bar-full-dark.png


BIN
game/public/unity/disaster-relief/TemplateData/progress-bar-full-light.png


+ 11 - 0
game/public/unity/disaster-relief/TemplateData/style.css

@@ -0,0 +1,11 @@
+body { padding: 0; margin: 0 }
+#unity-container { position: fixed; width: 100%; height: 100%; }
+#unity-canvas { width: 100%;
+    height: 100%;
+    background: url('bg.jpg') no-repeat center;
+    background-size: cover; }
+#unity-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
+/*#unity-logo { width: 154px; height: 130px; background: url('unity-logo-dark.png') no-repeat center }*/
+#unity-progress-bar-empty { margin-left: auto; margin-right: auto; width: 141px; height: 18px; margin-top: 10px; background: url('progress-bar-empty-dark.png') no-repeat center }
+#unity-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
+#unity-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }

BIN
game/public/unity/disaster-relief/TemplateData/unity-logo-dark.png


BIN
game/public/unity/disaster-relief/TemplateData/unity-logo-light.png


+ 105 - 0
game/public/unity/disaster-relief/index.html

@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>应急救灾</title>
+    <link rel="shortcut icon" href="TemplateData/favicon.ico">
+    <link rel="stylesheet" href="TemplateData/style.css">
+    <link rel="manifest" href="manifest.webmanifest">
+  </head>
+  <body>
+    <div id="unity-container">
+      <canvas id="unity-canvas" width=960 height=600 tabindex="-1"></canvas>
+      <div id="unity-loading-bar">
+        <div id="unity-logo"></div>
+        <div id="unity-progress-bar-empty">
+          <div id="unity-progress-bar-full"></div>
+        </div>
+      </div>
+      <div id="unity-warning"> </div>
+    </div>
+    <script src="unity.js">//添加 unity.js 的引用</script>
+    <script>
+      window.addEventListener("load", function () {
+        if ("serviceWorker" in navigator) {
+          navigator.serviceWorker.register("ServiceWorker.js");
+        }
+      });
+
+      var container = document.querySelector("#unity-container");
+      var canvas = document.querySelector("#unity-canvas");
+      var loadingBar = document.querySelector("#unity-loading-bar");
+      var progressBarFull = document.querySelector("#unity-progress-bar-full");
+      var warningBanner = document.querySelector("#unity-warning");
+
+      // Shows a temporary message banner/ribbon for a few seconds, or
+      // a permanent error message on top of the canvas if type=='error'.
+      // If type=='warning', a yellow highlight color is used.
+      // Modify or remove this function to customize the visually presented
+      // way that non-critical warnings and error messages are presented to the
+      // user.
+      function unityShowBanner(msg, type) {
+        function updateBannerVisibility() {
+          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
+        }
+        var div = document.createElement('div');
+        div.innerHTML = msg;
+        warningBanner.appendChild(div);
+        if (type == 'error') div.style = 'background: red; padding: 10px;';
+        else {
+          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
+          setTimeout(function() {
+            warningBanner.removeChild(div);
+            updateBannerVisibility();
+          }, 5000);
+        }
+        updateBannerVisibility();
+      }
+
+      var buildUrl = "Build";
+      var loaderUrl = buildUrl + "/Build.loader.js";
+      var config = {
+        arguments: [],
+        dataUrl: buildUrl + "/Build.data.unityweb",
+        frameworkUrl: buildUrl + "/Build.framework.js.unityweb",
+        codeUrl: buildUrl + "/Build.wasm.unityweb",
+        streamingAssetsUrl: "StreamingAssets",
+        companyName: "4dage",
+        productName: "RacingCar",
+        productVersion: "0.1.0",
+        showBanner: unityShowBanner,
+      };
+
+      // By default Unity keeps WebGL canvas render target size matched with
+      // the DOM size of the canvas element (scaled by window.devicePixelRatio)
+      // Set this to false if you want to decouple this synchronization from
+      // happening inside the engine, and you would instead like to size up
+      // the canvas DOM size and WebGL render target sizes yourself.
+      // config.matchWebGLToCanvasSize = false;
+
+      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
+        // Mobile device style: fill the whole browser client area with the game canvas:
+        var meta = document.createElement('meta');
+        meta.name = 'viewport';
+        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
+        document.getElementsByTagName('head')[0].appendChild(meta);
+      }
+
+      loadingBar.style.display = "block";
+
+      var script = document.createElement("script");
+      script.src = loaderUrl;
+      script.onload = () => {
+        createUnityInstance(canvas, config, (progress) => {
+          progressBarFull.style.width = 100 * progress + "%";
+        }).then((unityInstance) => {
+          loadingBar.style.display = "none";
+        }).catch((message) => {
+          alert(message);
+        });
+      };
+      document.body.appendChild(script);
+    </script>
+  </body>
+</html>

+ 15 - 0
game/public/unity/disaster-relief/manifest.webmanifest

@@ -0,0 +1,15 @@
+{
+    "name": "RacingCar",
+    "short_name": "RacingCar",
+    "start_url": "index.html",
+    "display": "fullscreen",
+    "background_color": "#231F20",
+    "theme_color": "#000",
+    "description": "",
+    "icons": [{
+      "src": "TemplateData/icons/unity-logo-dark.png",
+      "sizes": "144x144",
+      "type": "image/png",
+      "purpose": "any maskable"
+    }]
+  }

+ 12 - 0
game/public/unity/disaster-relief/unity.js

@@ -0,0 +1,12 @@
+
+function getQueryVariable(variable) {
+	var query = window.location.search.substring(1);
+	var vars = query.split("&");
+	for (var i = 0; i < vars.length; i++) {
+		var pair = vars[i].split("=");
+		if (pair[0] == variable) {
+			return pair[1];
+		}
+	}
+	return false;
+}

BIN
game/public/unity/enviroment-protection/Build/Build.data.unityweb


BIN
game/public/unity/enviroment-protection/Build/Build.framework.js.unityweb


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
game/public/unity/enviroment-protection/Build/Build.loader.js


BIN
game/public/unity/enviroment-protection/Build/Build.wasm.unityweb


+ 33 - 0
game/public/unity/enviroment-protection/ServiceWorker.js

@@ -0,0 +1,33 @@
+const cacheName = "4dage-EcologicalProtection-0.1.0";
+const contentToCache = [
+    "Build/Build.loader.js",
+    "Build/Build.framework.js.unityweb",
+    "Build/Build.data.unityweb",
+    "Build/Build.wasm.unityweb",
+    "TemplateData/style.css"
+
+];
+
+self.addEventListener('install', function (e) {
+    console.log('[Service Worker] Install');
+    
+    e.waitUntil((async function () {
+      const cache = await caches.open(cacheName);
+      console.log('[Service Worker] Caching all: app shell and content');
+      await cache.addAll(contentToCache);
+    })());
+});
+
+self.addEventListener('fetch', function (e) {
+    e.respondWith((async function () {
+      let response = await caches.match(e.request);
+      console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
+      if (response) { return response; }
+
+      response = await fetch(e.request);
+      const cache = await caches.open(cacheName);
+      console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
+      cache.put(e.request, response.clone());
+      return response;
+    })());
+});

BIN
game/public/unity/enviroment-protection/TemplateData/bg.jpg


BIN
game/public/unity/enviroment-protection/TemplateData/favicon.ico


BIN
game/public/unity/enviroment-protection/TemplateData/progress-bar-empty-dark.png


BIN
game/public/unity/enviroment-protection/TemplateData/progress-bar-empty-light.png


BIN
game/public/unity/enviroment-protection/TemplateData/progress-bar-full-dark.png


BIN
game/public/unity/enviroment-protection/TemplateData/progress-bar-full-light.png


+ 7 - 0
game/public/unity/enviroment-protection/TemplateData/style.css

@@ -0,0 +1,7 @@
+body { padding: 0; margin: 0 }
+#tuanjie-container { position: fixed; width: 100%; height: 100%; }
+#tuanjie-canvas { width: 100%; height: 100%; background: url('bg.jpg') no-repeat center; background-size: cover; }
+#tuanjie-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
+#tuanjie-progress-bar-empty { margin-left: auto; margin-right: auto; width: 141px; height: 18px; margin-top: 10px; background: url('progress-bar-empty-dark.png') no-repeat center }
+#tuanjie-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
+#tuanjie-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }

+ 116 - 0
game/public/unity/enviroment-protection/index.html

@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>生态保护</title>
+    <link rel="shortcut icon" href="TemplateData/favicon.ico">
+    <link rel="stylesheet" href="TemplateData/style.css">
+    <link rel="manifest" href="manifest.webmanifest">
+  </head>
+  <body>
+    <div id="tuanjie-container">
+      <canvas id="tuanjie-canvas" width=960 height=600 tabindex="-1"></canvas>
+      <div id="tuanjie-loading-bar">
+        <div id="tuanjie-logo"></div>
+        <div id="tuanjie-progress-bar-empty">
+          <div id="tuanjie-progress-bar-full"></div>
+        </div>
+      </div>
+      <div id="tuanjie-warning"> </div>
+    </div>
+    <script>
+	  window.getQueryVariable = function getQueryVariable(variable) {
+		var query = window.location.search.substring(1);
+		var vars = query.split("&");
+		for (var i = 0; i < vars.length; i++) {
+			var pair = vars[i].split("=");
+			if (pair[0] == variable) {
+				return pair[1];
+			}
+		}
+		return false;
+		}
+
+	
+      window.addEventListener("load", function () {
+        if ("serviceWorker" in navigator) {
+          navigator.serviceWorker.register("ServiceWorker.js");
+        }
+      });
+
+      var container = document.querySelector("#tuanjie-container");
+      var canvas = document.querySelector("#tuanjie-canvas");
+      var loadingBar = document.querySelector("#tuanjie-loading-bar");
+      var progressBarFull = document.querySelector("#tuanjie-progress-bar-full");
+      var warningBanner = document.querySelector("#tuanjie-warning");
+
+      // Shows a temporary message banner/ribbon for a few seconds, or
+      // a permanent error message on top of the canvas if type=='error'.
+      // If type=='warning', a yellow highlight color is used.
+      // Modify or remove this function to customize the visually presented
+      // way that non-critical warnings and error messages are presented to the
+      // user.
+      function unityShowBanner(msg, type) {
+        function updateBannerVisibility() {
+          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
+        }
+        var div = document.createElement('div');
+        div.innerHTML = msg;
+        warningBanner.appendChild(div);
+        if (type == 'error') div.style = 'background: red; padding: 10px;';
+        else {
+          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
+          setTimeout(function() {
+            warningBanner.removeChild(div);
+            updateBannerVisibility();
+          }, 5000);
+        }
+        updateBannerVisibility();
+      }
+
+      var buildUrl = "Build";
+      var loaderUrl = buildUrl + "/Build.loader.js";
+      var config = {
+        dataUrl: buildUrl + "/Build.data.unityweb",
+        frameworkUrl: buildUrl + "/Build.framework.js.unityweb",
+        codeUrl: buildUrl + "/Build.wasm.unityweb",
+        streamingAssetsUrl: "StreamingAssets",
+        companyName: "4dage",
+        productName: "EcologicalProtection",
+        productVersion: "0.1.0",
+        showBanner: unityShowBanner,
+      };
+
+      // By default Tuanjie keeps WebGL canvas render target size matched with
+      // the DOM size of the canvas element (scaled by window.devicePixelRatio)
+      // Set this to false if you want to decouple this synchronization from
+      // happening inside the engine, and you would instead like to size up
+      // the canvas DOM size and WebGL render target sizes yourself.
+      // config.matchWebGLToCanvasSize = false;
+
+      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
+        // Mobile device style: fill the whole browser client area with the game canvas:
+        var meta = document.createElement('meta');
+        meta.name = 'viewport';
+        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
+        document.getElementsByTagName('head')[0].appendChild(meta);
+      }
+
+      loadingBar.style.display = "block";
+
+      var script = document.createElement("script");
+      script.src = loaderUrl;
+      script.onload = () => {
+        createTuanjieInstance(canvas, config, (progress) => {
+          progressBarFull.style.width = 100 * progress + "%";
+        }).then((tuanjieInstance) => {
+          loadingBar.style.display = "none";
+        }).catch((message) => {
+          alert(message);
+        });
+      };
+      document.body.appendChild(script);
+    </script>
+  </body>
+</html>

+ 15 - 0
game/public/unity/enviroment-protection/manifest.webmanifest

@@ -0,0 +1,15 @@
+{
+    "name": "EcologicalProtection",
+    "short_name": "EcologicalProtection",
+    "start_url": "index.html",
+    "display": "fullscreen",
+    "background_color": "#231F20",
+    "theme_color": "#000",
+    "description": "",
+    "icons": [{
+      "src": "TemplateData/icons/tuanjie-logo-dark.png",
+      "sizes": "144x144",
+      "type": "image/png",
+      "purpose": "any maskable"
+    }]
+  }

BIN
game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.data.unityweb


BIN
game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.framework.js.unityweb


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.loader.js


BIN
game/public/unity/lost-children/Build/H5Game-ChinaSecuritiesMuseumMaze.wasm.unityweb


BIN
game/public/unity/lost-children/Build/寻找丢失儿童.data.unityweb


BIN
game/public/unity/lost-children/Build/寻找丢失儿童.framework.js.unityweb


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
game/public/unity/lost-children/Build/寻找丢失儿童.loader.js


BIN
game/public/unity/lost-children/Build/寻找丢失儿童.wasm.unityweb


+ 33 - 0
game/public/unity/lost-children/ServiceWorker.js

@@ -0,0 +1,33 @@
+const cacheName = "4dkk-Maze-0.1";
+const contentToCache = [
+    "Build/H5Game-ChinaSecuritiesMuseumMaze.loader.js",
+    "Build/H5Game-ChinaSecuritiesMuseumMaze.framework.js.unityweb",
+    "Build/H5Game-ChinaSecuritiesMuseumMaze.data.unityweb",
+    "Build/H5Game-ChinaSecuritiesMuseumMaze.wasm.unityweb",
+    "TemplateData/style.css"
+
+];
+
+self.addEventListener('install', function (e) {
+    console.log('[Service Worker] Install');
+    
+    e.waitUntil((async function () {
+      const cache = await caches.open(cacheName);
+      console.log('[Service Worker] Caching all: app shell and content');
+      await cache.addAll(contentToCache);
+    })());
+});
+
+self.addEventListener('fetch', function (e) {
+    e.respondWith((async function () {
+      let response = await caches.match(e.request);
+      console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
+      if (response) { return response; }
+
+      response = await fetch(e.request);
+      const cache = await caches.open(cacheName);
+      console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
+      cache.put(e.request, response.clone());
+      return response;
+    })());
+});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
game/public/unity/lost-children/StreamingAssets/UnityServicesProjectConfiguration.json


BIN
game/public/unity/lost-children/TemplateData/bg.jpg


BIN
game/public/unity/lost-children/TemplateData/favicon.ico


BIN
game/public/unity/lost-children/TemplateData/icons/unity-logo-dark.png


BIN
game/public/unity/lost-children/TemplateData/icons/unity-logo-light.png


BIN
game/public/unity/lost-children/TemplateData/progress-bar-empty-dark.png


BIN
game/public/unity/lost-children/TemplateData/progress-bar-empty-light.png


BIN
game/public/unity/lost-children/TemplateData/progress-bar-full-dark.png


BIN
game/public/unity/lost-children/TemplateData/progress-bar-full-light.png


+ 11 - 0
game/public/unity/lost-children/TemplateData/style.css

@@ -0,0 +1,11 @@
+body { padding: 0; margin: 0 }
+#unity-container { position: fixed; width: 100%; height: 100%; }
+#unity-canvas { width: 100%;
+    height: 100%;
+    background: url('bg.jpg') no-repeat center;
+    background-size: cover; }
+#unity-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
+/*#unity-logo { width: 154px; height: 130px; background: url('unity-logo-dark.png') no-repeat center }*/
+#unity-progress-bar-empty { margin-left: auto; margin-right: auto; width: 141px; height: 18px; margin-top: 10px; background: url('progress-bar-empty-dark.png') no-repeat center }
+#unity-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
+#unity-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }

BIN
game/public/unity/lost-children/TemplateData/unity-logo-dark.png


BIN
game/public/unity/lost-children/TemplateData/unity-logo-light.png


+ 104 - 0
game/public/unity/lost-children/index.html

@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>找回走失儿童</title>
+    <link rel="shortcut icon" href="TemplateData/favicon.ico">
+    <link rel="stylesheet" href="TemplateData/style.css">
+    <link rel="manifest" href="manifest.webmanifest">
+  </head>
+  <body>
+    <div id="unity-container">
+      <canvas id="unity-canvas" width=960 height=600 tabindex="-1"></canvas>
+      <div id="unity-loading-bar">
+        <div id="unity-logo"></div>
+        <div id="unity-progress-bar-empty">
+          <div id="unity-progress-bar-full"></div>
+        </div>
+      </div>
+      <div id="unity-warning"> </div>
+    </div>
+    <script src="unity.js"></script>
+    <script>
+      window.addEventListener("load", function () {
+        if ("serviceWorker" in navigator) {
+          navigator.serviceWorker.register("ServiceWorker.js");
+        }
+      });
+
+      var container = document.querySelector("#unity-container");
+      var canvas = document.querySelector("#unity-canvas");
+      var loadingBar = document.querySelector("#unity-loading-bar");
+      var progressBarFull = document.querySelector("#unity-progress-bar-full");
+      var warningBanner = document.querySelector("#unity-warning");
+
+      // Shows a temporary message banner/ribbon for a few seconds, or
+      // a permanent error message on top of the canvas if type=='error'.
+      // If type=='warning', a yellow highlight color is used.
+      // Modify or remove this function to customize the visually presented
+      // way that non-critical warnings and error messages are presented to the
+      // user.
+      function unityShowBanner(msg, type) {
+        function updateBannerVisibility() {
+          warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
+        }
+        var div = document.createElement('div');
+        div.innerHTML = msg;
+        warningBanner.appendChild(div);
+        if (type == 'error') div.style = 'background: red; padding: 10px;';
+        else {
+          if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
+          setTimeout(function() {
+            warningBanner.removeChild(div);
+            updateBannerVisibility();
+          }, 5000);
+        }
+        updateBannerVisibility();
+      }
+
+      var buildUrl = "Build";
+      var loaderUrl = buildUrl + "/H5Game-ChinaSecuritiesMuseumMaze.loader.js";
+      var config = {
+        dataUrl: buildUrl + "/H5Game-ChinaSecuritiesMuseumMaze.data.unityweb",
+        frameworkUrl: buildUrl + "/H5Game-ChinaSecuritiesMuseumMaze.framework.js.unityweb",
+        codeUrl: buildUrl + "/H5Game-ChinaSecuritiesMuseumMaze.wasm.unityweb",
+        streamingAssetsUrl: "StreamingAssets",
+        companyName: "4dkk",
+        productName: "Maze",
+        productVersion: "0.1",
+        showBanner: unityShowBanner,
+      };
+
+      // By default Unity keeps WebGL canvas render target size matched with
+      // the DOM size of the canvas element (scaled by window.devicePixelRatio)
+      // Set this to false if you want to decouple this synchronization from
+      // happening inside the engine, and you would instead like to size up
+      // the canvas DOM size and WebGL render target sizes yourself.
+      // config.matchWebGLToCanvasSize = false;
+
+      if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
+        // Mobile device style: fill the whole browser client area with the game canvas:
+        var meta = document.createElement('meta');
+        meta.name = 'viewport';
+        meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
+        document.getElementsByTagName('head')[0].appendChild(meta);
+      }
+
+      loadingBar.style.display = "block";
+
+      var script = document.createElement("script");
+      script.src = loaderUrl;
+      script.onload = () => {
+        createUnityInstance(canvas, config, (progress) => {
+          progressBarFull.style.width = 100 * progress + "%";
+        }).then((unityInstance) => {
+          loadingBar.style.display = "none";
+        }).catch((message) => {
+          alert(message);
+        });
+      };
+      document.body.appendChild(script);
+    </script>
+  </body>
+</html>

+ 15 - 0
game/public/unity/lost-children/manifest.webmanifest

@@ -0,0 +1,15 @@
+{
+    "name": {{{ JSON.stringify(PRODUCT_NAME) }}},
+    "short_name": {{{ JSON.stringify(PRODUCT_NAME) }}},
+    "start_url": "index.html",
+    "display": "fullscreen",
+    "background_color": "{{{ BACKGROUND_COLOR }}}",
+    "theme_color": "{{{  SPLASH_SCREEN_STYLE.toLowerCase() === 'light' ? '#fff' : '#000' }}}",
+    "description": {{{ JSON.stringify(PRODUCT_DESCRIPTION) }}},
+    "icons": [{
+      "src": "TemplateData/icons/unity-logo-{{{ SPLASH_SCREEN_STYLE.toLowerCase() }}}.png",
+      "sizes": "144x144",
+      "type": "image/png",
+      "purpose": "any maskable"
+    }]
+  }

+ 12 - 0
game/public/unity/lost-children/unity.js

@@ -0,0 +1,12 @@
+
+function getQueryVariable(variable) {
+	var query = window.location.search.substring(1);
+	var vars = query.split("&");
+	for (var i = 0; i < vars.length; i++) {
+		var pair = vars[i].split("=");
+		if (pair[0] == variable) {
+			return pair[1];
+		}
+	}
+	return false;
+}

+ 6 - 5
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, getGameRuleList, getScoreLimit } from '@/api.js'
+import { checkLoginStatusAndProcess, getUserFromStorageIfNeed, getGameRuleList, getScoreLimit } from '@/api.js'
 
 
 const {
 const {
   windowSizeInCssForRef,
   windowSizeInCssForRef,
@@ -19,7 +19,8 @@ const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 const store = useStore()
 const store = useStore()
 
 
-checkLoginStatusAndProcess()
+// checkLoginStatusAndProcess()
+getUserFromStorageIfNeed()
 
 
 getGameRuleList().then((res) => {
 getGameRuleList().then((res) => {
   store.commit('setGameRuleList', res)
   store.commit('setGameRuleList', res)
@@ -69,9 +70,9 @@ html, body {
 // }
 // }
 
 
 // 滚动条,只设置某一项可能导致不生效。
 // 滚动条,只设置某一项可能导致不生效。
-// ::-webkit-scrollbar { background: #dddecc; width: 6px; height: 6px; }
-// ::-webkit-scrollbar-thumb { background: #828a5b; border-radius: 3px; }
-// ::-webkit-scrollbar-corner { background: #dddecc; }
+::-webkit-scrollbar { background: #dddecc; width: 6px; height: 6px; }
+::-webkit-scrollbar-thumb { background: #828a5b; border-radius: 3px; }
+::-webkit-scrollbar-corner { background: #dddecc; }
 
 
 // vue组件过渡效果
 // vue组件过渡效果
 .fade-out-leave-active {
 .fade-out-leave-active {

+ 92 - 13
game/src/api.js

@@ -16,16 +16,28 @@ axios.interceptors.response.use(function (response) {
   return error
   return error
 })
 })
 
 
+export function getUserFromStorageIfNeed() {
+  if (!store.state.token || !store.state.userInfo) {
+    const lastToken = localStorage.getItem('token')
+    const lastUserInfoStr = localStorage.getItem('userInfo')
+    if (lastToken && lastUserInfoStr) {
+      store.commit('setLoginStatus', true)
+      store.commit('setToken', lastToken)
+      store.commit('setUserInfo', JSON.parse(lastUserInfoStr))
+    }
+  }
+}
+
 // export async function reportVisit() {
 // export async function reportVisit() {
 //   const res = await axios({
 //   const res = await axios({
 //     method: 'get',
 //     method: 'get',
-//     url: `${process.env.VUE_APP_API_PREFIX}/api/show/addVisit`,
+//     url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/addVisit`,
 //   })
 //   })
 // }
 // }
 // export async function fetchVisitInfo() {
 // export async function fetchVisitInfo() {
 //   const res = await axios({
 //   const res = await axios({
 //     method: 'get',
 //     method: 'get',
-//     url: `${process.env.VUE_APP_API_PREFIX}/api/show/getVisit`,
+//     url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/getVisit`,
 //   })
 //   })
 //   return res.data.data
 //   return res.data.data
 // }
 // }
@@ -33,7 +45,7 @@ export async function login(account, password) {
   const pwdEncrypted = encodeStr(Base64.encode(password))
   const pwdEncrypted = encodeStr(Base64.encode(password))
   const res = await axios({
   const res = await axios({
     method: 'post',
     method: 'post',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/show/login`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/login`,
     data: {
     data: {
       userName: account,
       userName: account,
       password: pwdEncrypted,
       password: pwdEncrypted,
@@ -51,7 +63,7 @@ export async function login(account, password) {
 export async function logout() {
 export async function logout() {
   const res = await axios({
   const res = await axios({
     method: 'get',
     method: 'get',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/cms/game/logout`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/logout`,
     headers: {
     headers: {
       token: store.state.token,
       token: store.state.token,
     }
     }
@@ -66,7 +78,7 @@ export async function checkLoginStatusAndProcess() {
   if (lastToken && lastUserInfoStr) {
   if (lastToken && lastUserInfoStr) {
     const res = await axios({
     const res = await axios({
       method: 'get',
       method: 'get',
-      url: `${process.env.VUE_APP_API_PREFIX}/api/show/checkLogin`,
+      url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/checkLogin`,
       headers: {
       headers: {
         token: lastToken,
         token: lastToken,
       }
       }
@@ -89,7 +101,7 @@ export async function signUp(account, phone, password) {
   const pwdEncrypted = encodeStr(Base64.encode(password))
   const pwdEncrypted = encodeStr(Base64.encode(password))
   const res = await axios({
   const res = await axios({
     method: 'post',
     method: 'post',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/show/register`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/register`,
     data: {
     data: {
       password: pwdEncrypted,
       password: pwdEncrypted,
       phone,
       phone,
@@ -106,7 +118,7 @@ export async function signUp(account, phone, password) {
 export async function findPassowrd(account, phone) {
 export async function findPassowrd(account, phone) {
   const res = await axios({
   const res = await axios({
     method: 'post',
     method: 'post',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/show/findPass`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/findPass`,
     data: {
     data: {
       phone,
       phone,
       userName: account,
       userName: account,
@@ -123,7 +135,7 @@ export async function changePassword(newPassword, oldPassword) {
   const pwdOldEncrypted = encodeStr(Base64.encode(oldPassword))
   const pwdOldEncrypted = encodeStr(Base64.encode(oldPassword))
   const res = await axios({
   const res = await axios({
     method: 'post',
     method: 'post',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/cms/game/update/pass`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/update/pass`,
     data: {
     data: {
       newPassword: pwdNewEncrypted,
       newPassword: pwdNewEncrypted,
       oldPassword: pwdOldEncrypted,
       oldPassword: pwdOldEncrypted,
@@ -142,7 +154,7 @@ export async function changePassword(newPassword, oldPassword) {
 export async function getGameRuleList() {
 export async function getGameRuleList() {
   const res = await axios({
   const res = await axios({
     method: 'get',
     method: 'get',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/show/rule/getList?rnd=${Math.random()}`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/rule/getList?rnd=${Math.random()}`,
     params: {
     params: {
       type: 'game',
       type: 'game',
     },
     },
@@ -156,7 +168,7 @@ export async function getGameRuleList() {
 export async function getScoreLimit() {
 export async function getScoreLimit() {
   const res = await axios({
   const res = await axios({
     method: 'get',
     method: 'get',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/show/rule/getList?rnd=${Math.random()}`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/rule/getList?rnd=${Math.random()}`,
     params: {
     params: {
       type: 'day',
       type: 'day',
     },
     },
@@ -170,7 +182,7 @@ export async function getScoreLimit() {
 export async function getScore() {
 export async function getScore() {
   const res = await axios({
   const res = await axios({
     method: 'get',
     method: 'get',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/cms/game/getPoint?rnd=${Math.random()}`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/getPoint?rnd=${Math.random()}`,
     headers: {
     headers: {
       token: store.state.token,
       token: store.state.token,
     }
     }
@@ -184,7 +196,7 @@ export async function getScore() {
 export async function addScore(score) {
 export async function addScore(score) {
   const res = await axios({
   const res = await axios({
     method: 'post',
     method: 'post',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/cms/game/point/add`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/point/add`,
     data: {
     data: {
       score,
       score,
       type: 'game',
       type: 'game',
@@ -204,11 +216,78 @@ export async function addScore(score) {
 export async function getExamQuestionList() {
 export async function getExamQuestionList() {
   const res = await axios({
   const res = await axios({
     method: 'get',
     method: 'get',
-    url: `${process.env.VUE_APP_API_PREFIX}/api/show/question/getList`,
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/show/question/getList`,
   })
   })
   if (res.data.code !== 0) {
   if (res.data.code !== 0) {
     throw (`获取答题列表失败:${res.data.msg}`)
     throw (`获取答题列表失败:${res.data.msg}`)
   } else {
   } else {
     return res.data.data
     return res.data.data
   }
   }
+}
+/*
+createTime: "2024-01-08 19:07:01"
+creatorId: 1
+creatorName: ""
+id: 11
+isEnabled: 1
+name: "可乐"
+recordDate: null
+rtf: "<p>可乐</p><p>123</p><p></p><div class=\"media-wrap image-wrap\"><img src=\"https://sit-cnzhengquan.4dage.com/prize/11/img/20240109_0935085055.png\"/></div><p></p>"
+score: 10
+stock: 10
+thumb: "/prize/11/thumb/20240109_0929514233.png"
+updateTime: "2024-01-09 09:35:10"
+*/
+export async function getPrizeList(pageNum, pageSize) {
+  const res = await axios({
+    method: 'post',
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/prize/pageList`,
+    data: {
+      pageNum,
+      pageSize,
+    },
+    headers: {
+      token: store.state.token,
+    }
+  })
+  if (res.data.code !== 0) {
+    throw (`获取奖品列表失败:${res.data.msg}`)
+  } else {
+    return res.data.data
+  }
+}
+export async function getBonusPointRecord() {
+  const res = await axios({
+    method: 'get',
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/point/getList`,
+    headers: {
+      token: store.state.token,
+    }
+  })
+  if (res.data.code !== 0) {
+    throw (`获取积分记录失败:${res.data.msg}`)
+  } else {
+    return res.data.data
+  }
+}
+export async function redeem(description, name, phone, prizeId, score) {
+  const res = await axios({
+    method: 'post',
+    url: `${process.env.VUE_APP_DEPLOY_ORIGIN}/api/cms/game/prize/redeem`,
+    headers: {
+      token: store.state.token,
+    },
+    data: {
+      description,
+      name,
+      phone,
+      prizeId,
+      score: -1 * score,
+    }
+  })
+  if (res.data.code !== 0) {
+    throw (`奖品兑换失败:${res.data.msg}`)
+  } else {
+    return res.data.data
+  }
 }
 }

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


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


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


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


BIN
game/src/assets/images/no-stock.png


BIN
game/src/assets/images/redeem-banner.png


BIN
game/src/assets/images/redeem-paper.png


BIN
game/src/assets/images/tab-item-bottom-line.png


+ 112 - 0
game/src/components/GameRule.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="game-rule">
+    <div class="paper">
+      <h1>
+        {{ gameTitle }}
+      </h1>
+      <hr>
+      <div
+        class="rich-text"
+        v-html="richText"
+      />
+    </div>
+    <button
+      class="close"
+      @click="emit('close')"
+    />
+  </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()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const emit = defineEmits(['close'])
+
+const props = defineProps(['gameTitle', 'richText'])
+
+</script>
+
+<style lang="less" scoped>
+.game-rule{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0,0,0,0.7);
+  backdrop-filter: blur(calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')));
+  >.paper{
+    position: absolute;
+    left: 50%;
+    top: calc(66 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    transform: translate(-50%, 0);
+    width: calc(336 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(573 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: #FFFFFF;
+    border-radius: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding-top: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-bottom: calc(34 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-left: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-right: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >h1{
+      flex: 0 0 auto;
+      font-size: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      font-family: Source Han Sans SC, Source Han Sans SC;
+      font-weight: bold;
+      color: #414141;
+      line-height: calc(28 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    }
+    >hr{
+      flex: 0 0 auto;
+      height: 0;
+      width: 100%;
+      border-top: 1px dashed #CFBB9C;
+      margin-top: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      margin-bottom: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));;
+    }
+    >.rich-text{
+      flex: 1 0 1px;
+      width: calc(100% + 0.5em);
+      overflow: auto;
+      padding-right: 0.5em;
+      :deep(p){
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #414141;
+        line-height: calc(28 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        text-indent: 2em;
+      }
+      :deep(.media-wrap){
+        width: 100%;
+      }
+    }
+  }
+  >button.close{
+    position: absolute;
+    left: 50%;
+    top: calc(664 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    transform: translate(-50%, 0);
+    width: calc(52 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(52 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background-image: url(@/assets/images/icon-close.png);
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center center;
+  }
+}
+</style>

+ 41 - 0
game/src/components/NotifyBonusPointReachedLimit.vue

@@ -0,0 +1,41 @@
+<template>
+  <div class="notify-bonus-point-reached-limit">
+    已达本日上限
+  </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()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+</script>
+
+<style lang="less" scoped>
+.notify-bonus-point-reached-limit{
+  position: absolute;
+  left: 50%;
+  top: 25%;
+  transform: translate(-50%, -50%);
+  width: calc(302 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  height: calc(42 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  background: linear-gradient(90deg, rgba(197,161,108,0) 0%, rgba(147,123,88,0.4) 25%, rgba(147,123,88,0.8) 52%, rgba(147,123,88,0.4) 75%, rgba(197,161,108,0) 100%);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  font-family: Source Han Sans SC, Source Han Sans SC;
+  font-weight: 400;
+  color: #FFFFFF;
+  line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+}
+</style>

+ 62 - 0
game/src/components/NotifyComp.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="notify">
+    <img
+      class="icon"
+      src="@/assets/images/icon-success.png"
+      alt=""
+      draggable="false"
+    >
+    <div class="text">
+      {{ props.text }}
+    </div>
+  </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()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const props = defineProps(['text'])
+
+</script>
+
+<style lang="less" scoped>
+.notify{
+  position: fixed;
+  left: 50%;
+  top: calc(8 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  transform: translate(-50%, 0);
+  z-index: 10;
+  width: calc(320 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  height: calc(58 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(255,255,255,0.5);
+  border-radius: calc(2 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  border: 1px solid #FFE6A5;
+  backdrop-filter: blur(calc(5 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')));
+  >img.icon{
+    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'));
+  }
+  >.text{
+    font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 400;
+    color: #414141;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+}
+</style>

+ 19 - 7
game/src/router/index.js

@@ -10,6 +10,8 @@ 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 GameByUnity from '../views/GameByUnity.vue'
+import ShopView from '../views/ShopView.vue'
+import RedeemForm from '../views/RedeemForm.vue'
 // import store from '@/store/index.js'
 // import store from '@/store/index.js'
 
 
 const routes = [
 const routes = [
@@ -67,7 +69,17 @@ const routes = [
     path: '/game-by-unity',
     path: '/game-by-unity',
     name: 'GameByUnity',
     name: 'GameByUnity',
     component: GameByUnity,
     component: GameByUnity,
-  }
+  },
+  {
+    path: '/shop-view',
+    name: 'ShopView',
+    component: ShopView,
+  },
+  {
+    path: '/redeem-form',
+    name: 'RedeemForm',
+    component: RedeemForm,
+  },
 ]
 ]
 
 
 const router = createRouter({
 const router = createRouter({
@@ -75,11 +87,11 @@ const router = createRouter({
   routes
   routes
 })
 })
 
 
-router.beforeEach((to, from) => {
-  // 生产环境下强制每次都从首页进入
-  if (process.env.NODE_ENV === 'production' && !from.name && to.name !== 'HomeView') {
-    return '/'
-  }
-})
+// router.beforeEach((to, from) => {
+//   // 生产环境下强制每次都从首页进入
+//   if (process.env.NODE_ENV === 'production' && !from.name && to.name !== 'HomeView' && !to.query['no-redirect']) {
+//     return '/'
+//   }
+// })
 
 
 export default router
 export default router

+ 1 - 1
game/src/store/index.js

@@ -2,7 +2,7 @@ import { createStore } from 'vuex'
 
 
 export default createStore({
 export default createStore({
   state: {
   state: {
-    loginStatus: undefined,
+    loginStatus: false,
     token: '',
     token: '',
     userInfo: {
     userInfo: {
       // createTime: "2024-01-08 17:04:43"
       // createTime: "2024-01-08 17:04:43"

+ 40 - 16
game/src/views/ExamPaper2.vue

@@ -12,7 +12,11 @@
         class="return-home"
         class="return-home"
         @click="onClickReturnHome"
         @click="onClickReturnHome"
       />
       />
-      <button class="game-rule" />
+      <button
+        v-if="store.state.gameRuleList[1].rtf"
+        class="game-rule"
+        @click="isShowRule = true"
+      />
     </div>
     </div>
 
 
     <div
     <div
@@ -81,7 +85,7 @@
           alt=""
           alt=""
           draggable="false"
           draggable="false"
         >
         >
-        <span class="number">{{ bonusPointForShow }}</span>
+        <span class="number">{{ bonusPoint }}</span>
       </div>
       </div>
       <div class="info-item time-count">
       <div class="info-item time-count">
         <img
         <img
@@ -99,11 +103,23 @@
     >
     >
       下一题
       下一题
     </button>
     </button>
+
     <van-loading
     <van-loading
       v-show="isShowLoading"
       v-show="isShowLoading"
       class="loading"
       class="loading"
       type="spinner"
       type="spinner"
     />
     />
+
+    <transition name="fade-out">
+      <NotifyBonusPointReachedLimit v-if="isShowNotifyBonusPointReachedLimit" />
+    </transition>
+
+    <GameRule
+      v-show="isShowRule"
+      game-title="助农课堂"
+      :rich-text="store.state.gameRuleList[1].rtf"
+      @close="isShowRule=false"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -115,6 +131,8 @@ import { useStore } from "vuex"
 import dayjs from 'dayjs'
 import dayjs from 'dayjs'
 import { shuffle } from 'lodash'
 import { shuffle } from 'lodash'
 import { addScore, getScore, getExamQuestionList } from '@/api.js'
 import { addScore, getScore, getExamQuestionList } from '@/api.js'
+import GameRule from '@/components/GameRule.vue'
+import NotifyBonusPointReachedLimit from '@/components/NotifyBonusPointReachedLimit.vue'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
@@ -125,6 +143,8 @@ const {
   windowSizeWhenDesignForRef,
   windowSizeWhenDesignForRef,
 } = useSizeAdapt(390, 752)
 } = useSizeAdapt(390, 752)
 
 
+const isShowRule = ref(false)
+
 const isShowLoading = ref(true)
 const isShowLoading = ref(true)
 getExamQuestionList().then((res) => {
 getExamQuestionList().then((res) => {
   questionList.value = res.map((questionItem) => {
   questionList.value = res.map((questionItem) => {
@@ -146,6 +166,9 @@ getExamQuestionList().then((res) => {
   isShowLoading.value = false
   isShowLoading.value = false
 
 
   timeCountIntervalId = setInterval(() => {
   timeCountIntervalId = setInterval(() => {
+    if (isShowRule.value) {
+      return
+    }
     timeCount.value--
     timeCount.value--
     if (timeCount.value === 0) {
     if (timeCount.value === 0) {
       clearInterval(timeCountIntervalId)
       clearInterval(timeCountIntervalId)
@@ -180,13 +203,7 @@ const timeCountForShow = computed(() => {
 
 
 // 积分
 // 积分
 const bonusPoint = ref(0)
 const bonusPoint = ref(0)
-const bonusPointForShow = computed(() => {
-  if (!store.state.ifScoreLimitReached) {
-    return bonusPoint.value
-  } else {
-    return `已达本日上限${store.state.scoreLimit}分`
-  }
-})
+
 // 答对计数
 // 答对计数
 const correntCount = ref(0)
 const correntCount = ref(0)
 
 
@@ -227,13 +244,12 @@ const currentQuestionIdx = ref(0)
 const isSubmitted = computed(() => {
 const isSubmitted = computed(() => {
   return selectedIdx.value !== null
   return selectedIdx.value !== null
 })
 })
-const isCorrect = computed(() => {
-  return true
-})
 function onClickOption(idx) {
 function onClickOption(idx) {
-  this.selectedIdx = idx
-  if (this.selectedIdx === questionList.value[currentQuestionIdx.value].rightOptionIdx) {
-    bonusPoint.value += store.state.gameRuleList[1].score
+  selectedIdx.value = idx
+  if (selectedIdx.value === questionList.value[currentQuestionIdx.value].rightOptionIdx) {
+    if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
+      bonusPoint.value += store.state.gameRuleList[1].score
+    }
     correntCount.value++
     correntCount.value++
   }
   }
 }
 }
@@ -242,7 +258,7 @@ function onClickNext() {
     currentQuestionIdx.value++
     currentQuestionIdx.value++
     selectedIdx.value = null
     selectedIdx.value = null
   } else {
   } else {
-    if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
+    if (store.state.loginStatus && !store.state.ifScoreLimitReached && bonusPoint.value !== 0) {
       addScore(bonusPoint.value).then(() => {
       addScore(bonusPoint.value).then(() => {
         getScore().then((res) => {
         getScore().then((res) => {
           store.commit('setScore', res.total)
           store.commit('setScore', res.total)
@@ -259,6 +275,14 @@ function onClickNext() {
     })
     })
   }
   }
 }
 }
+
+const isShowNotifyBonusPointReachedLimit = ref(false)
+if (store.state.ifScoreLimitReached) {
+  isShowNotifyBonusPointReachedLimit.value = true
+  setTimeout(() => {
+    isShowNotifyBonusPointReachedLimit.value = false
+  }, 2000)
+}
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>

+ 112 - 5
game/src/views/GameByUnity.vue

@@ -5,6 +5,35 @@
       :src="gameUrl"
       :src="gameUrl"
       frameborder="0"
       frameborder="0"
     />
     />
+    <!-- 左上角按钮 -->
+    <div class="btn-group">
+      <button
+        class="return-home"
+        :style="{
+          backgroundImage: `url(${returnHomeBtnBgImgUrl})`,
+        }"
+        @click="onClickReturnHome"
+      />
+      <button
+        v-if="store.state.gameRuleList[gameRuleConfig.idxInGameRule].rtf"
+        class="game-rule"
+        :style="{
+          backgroundImage: `url(${gameRuleBtnBgImgUrl})`,
+        }"
+        @click="isShowRule = true"
+      />
+    </div>
+
+    <transition name="fade-out">
+      <NotifyBonusPointReachedLimit v-if="isShowNotifyBonusPointReachedLimit" />
+    </transition>
+
+    <GameRule
+      v-show="isShowRule"
+      :game-title="gameRuleConfig.title"
+      :rich-text="store.state.gameRuleList[gameRuleConfig.idxInGameRule].rtf"
+      @close="isShowRule=false"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -12,24 +41,80 @@
 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 GameRule from '@/components/GameRule.vue'
+import NotifyBonusPointReachedLimit from '@/components/NotifyBonusPointReachedLimit.vue'
+import { onBeforeUnmount } from "vue"
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
 const store = useStore()
 const store = useStore()
 
 
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
 let gameUrlQuery = ''
 let gameUrlQuery = ''
 if (store.state.loginStatus) {
 if (store.state.loginStatus) {
   gameUrlQuery = `?userId=${store.state.userInfo.id}&token=${store.state.token}`
   gameUrlQuery = `?userId=${store.state.userInfo.id}&token=${store.state.token}`
 }
 }
 const gameUrlMap = {
 const gameUrlMap = {
-  AntibodyBattle: `https://app.4dage.com/test/rc/VirusWar/index.html`,
-  DisasterRelief: `http://app.4dage.com/test/rc/Build/index.html`,
-  EnviromentProtection: `https://app.4dage.com/test/rc/EcologicalProtection/index.html`,
-  LostChildren: `https://app.4dage.com/WebGLTestyhz/MiniGame/H5Game-ChinaSecuritiesMuseumMaze/index.html`,
+  AntibodyBattle: `unity/antibody-battle/index.html${gameUrlQuery}#/`,
+  DisasterRelief: `unity/disaster-relief/index.html${gameUrlQuery}#/`,
+  EnviromentProtection: `unity/enviroment-protection/index.html${gameUrlQuery}#/`,
+  LostChildren: `unity/lost-children/index.html${gameUrlQuery}#/`,
 }
 }
+const gameUrl = gameUrlMap[route.query.gameName]
 
 
-const gameUrl = gameUrlMap[route.query.gameName] + gameUrlQuery
+const floatingBtnFileNameAffixMap = {
+  AntibodyBattle: ``,
+  DisasterRelief: ``,
+  EnviromentProtection: ``,
+  LostChildren: `_dark`,
+}
+const floatingBtnFileNameAffix = floatingBtnFileNameAffixMap[route.query.gameName]
+const returnHomeBtnBgImgUrl = require(`@/assets/images/icon_home${floatingBtnFileNameAffix}.png`)
+const gameRuleBtnBgImgUrl = require(`@/assets/images/icon_rules${floatingBtnFileNameAffix}.png`)
+
+function onClickReturnHome() {
+  router.push({
+    name: 'HomeView',
+  })
+}
 
 
+const gameRuleConfigMap = {
+  AntibodyBattle: {
+    title: '抗体大作战',
+    idxInGameRule: 3,
+  },
+  DisasterRelief: {
+    title: '应急救灾',
+    idxInGameRule: 4,
+  },
+  EnviromentProtection: {
+    title: '生态保护',
+    idxInGameRule: 5,
+  },
+  LostChildren: {
+    title: '找回走失儿童',
+    idxInGameRule: 6,
+  },
+}
+const gameRuleConfig = gameRuleConfigMap[route.query.gameName]
+const isShowRule = ref(false)
+
+const isShowNotifyBonusPointReachedLimit = ref(false)
+if (store.state.ifScoreLimitReached) {
+  isShowNotifyBonusPointReachedLimit.value = true
+  setTimeout(() => {
+    isShowNotifyBonusPointReachedLimit.value = false
+  }, 2000)
+}
+
+document.querySelector('#app > .top-wrapper').style.width = '100%'
+onBeforeUnmount(() => {
+  document.querySelector('#app > .top-wrapper').style.width = ''
+})
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>
@@ -46,5 +131,27 @@ const gameUrl = gameUrlMap[route.query.gameName] + gameUrlQuery
     width: 100%;
     width: 100%;
     height: 100%;
     height: 100%;
   }
   }
+  >.btn-group{
+    position: absolute;
+    top: calc(36 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    right: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    display: flex;
+    flex-direction: column;
+    gap: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >button.return-home{
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center center;
+      width: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      height: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    }
+    >button.game-rule{
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center center;
+      width: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      height: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    }
+  }
 }
 }
 </style>
 </style>

+ 16 - 12
game/src/views/HomeView.vue

@@ -174,6 +174,9 @@
       <button
       <button
         v-if="store.state.loginStatus"
         v-if="store.state.loginStatus"
         class="redeem"
         class="redeem"
+        @click="router.push({
+          name: 'ShopView'
+        })"
       >
       >
         <img
         <img
           class=""
           class=""
@@ -247,16 +250,12 @@ const scoreLimit = computed(() => {
   return store.state.scoreLimit
   return store.state.scoreLimit
 })
 })
 
 
-watch(loginStatus, (vNew) => {
-  if (vNew) {
-    getScore().then((res) => {
-      store.commit('setScore', res.total)
-      store.commit('setIfScoreLimitReached', res.hasOver)
-    })
-  }
-}, {
-  immediate: true,
-})
+if (loginStatus.value) {
+  getScore().then((res) => {
+    store.commit('setScore', res.total)
+    store.commit('setIfScoreLimitReached', res.hasOver)
+  })
+}
 
 
 const isShowLoading = ref(true)
 const isShowLoading = ref(true)
 let hideLoadingCallback = null
 let hideLoadingCallback = null
@@ -378,11 +377,14 @@ function onLoginChoiceClose() {
   isShowLoginChoice.value = false
   isShowLoginChoice.value = false
 }
 }
 
 
-// 登录、注册完毕后回到首页,可能需要自动进入游戏页
+// 如果是点击游戏入口触发了登录、注册,且登录、注册完毕后回到首页,需要自动进入游戏页。
+// 如果是点击游戏入口触发了登录,但登录、注册未成功即回到首页,则不应自动进入游戏页。
 if (store.state.gameToPlayIdx !== null) {
 if (store.state.gameToPlayIdx !== null) {
   const gameIdx = store.state.gameToPlayIdx
   const gameIdx = store.state.gameToPlayIdx
   store.state.gameToPlayIdx = null
   store.state.gameToPlayIdx = null
-  entryFunctionList[gameIdx]()
+  if (store.state.loginStatus) {
+    entryFunctionList[gameIdx]()
+  }
 }
 }
 
 
 // 如果通过url要求自动进入游戏页
 // 如果通过url要求自动进入游戏页
@@ -399,6 +401,8 @@ if (route.query.gameIdx) {
     onClickGameEntry(gameIdx)
     onClickGameEntry(gameIdx)
   }
   }
 }
 }
+
+console.log(route.query)
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>

+ 41 - 11
game/src/views/PairUp.vue

@@ -18,7 +18,11 @@
         class="return-home"
         class="return-home"
         @click="onClickReturnHome"
         @click="onClickReturnHome"
       />
       />
-      <button class="game-rule" />
+      <button
+        v-if="store.state.gameRuleList[2].rtf"
+        class="game-rule"
+        @click="isShowRule = true"
+      />
     </div>
     </div>
     <!-- 卡牌列表 -->
     <!-- 卡牌列表 -->
     <div
     <div
@@ -69,7 +73,7 @@
           alt=""
           alt=""
           draggable="false"
           draggable="false"
         >
         >
-        <span class="number">{{ bonusPointForShow }}</span>
+        <span class="number">{{ bonusPoint }}</span>
       </div>
       </div>
       <div class="info-item time-count">
       <div class="info-item time-count">
         <img
         <img
@@ -81,12 +85,24 @@
         <span class="number">{{ timeCountForShow }}</span>
         <span class="number">{{ timeCountForShow }}</span>
       </div>
       </div>
     </div>
     </div>
+
     <PairUpOver
     <PairUpOver
       v-show="isOver"
       v-show="isOver"
       :corp-count="recognizedCorpList.length"
       :corp-count="recognizedCorpList.length"
       :bonus-count="bonusPoint"
       :bonus-count="bonusPoint"
       @replay="replay"
       @replay="replay"
     />
     />
+
+    <transition name="fade-out">
+      <NotifyBonusPointReachedLimit v-if="isShowNotifyBonusPointReachedLimit" />
+    </transition>
+
+    <GameRule
+      v-show="isShowRule"
+      game-title="企业翻翻看"
+      :rich-text="store.state.gameRuleList[2].rtf"
+      @close="isShowRule=false"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -99,6 +115,8 @@ import dayjs from 'dayjs'
 import { shuffle } from 'lodash'
 import { shuffle } from 'lodash'
 import PairUpOver from '@/components/PairUpOver.vue'
 import PairUpOver from '@/components/PairUpOver.vue'
 import { addScore, getScore } from '@/api.js'
 import { addScore, getScore } from '@/api.js'
+import GameRule from '@/components/GameRule.vue'
+import NotifyBonusPointReachedLimit from '@/components/NotifyBonusPointReachedLimit.vue'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
@@ -109,6 +127,8 @@ const {
   windowSizeWhenDesignForRef,
   windowSizeWhenDesignForRef,
 } = useSizeAdapt(390, 752)
 } = useSizeAdapt(390, 752)
 
 
+const isShowRule = ref(false)
+
 function onClickReturnHome() {
 function onClickReturnHome() {
   router.push({
   router.push({
     name: 'HomeView',
     name: 'HomeView',
@@ -123,6 +143,9 @@ const isOver = ref(false)
 const timeCount = ref(store.state.gameRuleList[2].second)
 const timeCount = ref(store.state.gameRuleList[2].second)
 let timeCountIntervalId = null
 let timeCountIntervalId = null
 timeCountIntervalId = setInterval(() => {
 timeCountIntervalId = setInterval(() => {
+  if (isShowRule.value) {
+    return
+  }
   timeCount.value--
   timeCount.value--
   if (timeCount.value === 0) {
   if (timeCount.value === 0) {
     clearInterval(timeCountIntervalId)
     clearInterval(timeCountIntervalId)
@@ -140,13 +163,7 @@ const timeCountForShow = computed(() => {
  * 本次游戏积分
  * 本次游戏积分
  */
  */
 const bonusPoint = ref(0)
 const bonusPoint = ref(0)
-const bonusPointForShow = computed(() => {
-  if (!store.state.ifScoreLimitReached) {
-    return bonusPoint.value
-  } else {
-    return `已达本日上限${store.state.scoreLimit}分`
-  }
-})
+
 /**
 /**
  * 本次游戏识别企业数
  * 本次游戏识别企业数
  */
  */
@@ -160,6 +177,9 @@ function replay() {
   bonusPoint.value = 0
   bonusPoint.value = 0
   timeCount.value = store.state.gameRuleList[2].second
   timeCount.value = store.state.gameRuleList[2].second
   timeCountIntervalId = setInterval(() => {
   timeCountIntervalId = setInterval(() => {
+    if (isShowRule.value) {
+      return
+    }
     timeCount.value--
     timeCount.value--
     if (timeCount.value === 0) {
     if (timeCount.value === 0) {
       clearInterval(timeCountIntervalId)
       clearInterval(timeCountIntervalId)
@@ -171,7 +191,7 @@ function replay() {
 
 
 watch(isOver, (vNew) => {
 watch(isOver, (vNew) => {
   if (vNew) {
   if (vNew) {
-    if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
+    if (store.state.loginStatus && !store.state.ifScoreLimitReached && bonusPoint.value !== 0) {
       addScore(bonusPoint.value).then(() => {
       addScore(bonusPoint.value).then(() => {
         getScore().then((res) => {
         getScore().then((res) => {
           store.commit('setScore', res.total)
           store.commit('setScore', res.total)
@@ -278,7 +298,9 @@ function onClickCard(card, cardIdx) {
         if (cardList.value.every((item) => {
         if (cardList.value.every((item) => {
           return item.isDone
           return item.isDone
         })) {
         })) {
-          bonusPoint.value += store.state.gameRuleList[2].score
+          if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
+            bonusPoint.value += store.state.gameRuleList[2].score
+          }
           setCardList()
           setCardList()
         }
         }
       }, 400)
       }, 400)
@@ -298,6 +320,14 @@ function onClickCard(card, cardIdx) {
     }, 400)
     }, 400)
   }
   }
 }
 }
+
+const isShowNotifyBonusPointReachedLimit = ref(false)
+if (store.state.ifScoreLimitReached && !isOver.value) {
+  isShowNotifyBonusPointReachedLimit.value = true
+  setTimeout(() => {
+    isShowNotifyBonusPointReachedLimit.value = false
+  }, 2000)
+}
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>

+ 51 - 28
game/src/views/PlantTree.vue

@@ -12,7 +12,11 @@
         class="return-home"
         class="return-home"
         @click="onClickReturnHome"
         @click="onClickReturnHome"
       />
       />
-      <button class="game-rule" />
+      <button
+        v-if="store.state.gameRuleList[0].rtf"
+        class="game-rule"
+        @click="isShowRule = true"
+      />
     </div>
     </div>
     <!-- 树苗 -->
     <!-- 树苗 -->
     <transition
     <transition
@@ -126,9 +130,20 @@
           alt=""
           alt=""
           draggable="false"
           draggable="false"
         >
         >
-        <span class="number">{{ bonusPointForShow }}</span>
+        <span class="number">{{ bonusPoint }}</span>
       </div>
       </div>
     </div>
     </div>
+
+    <transition name="fade-out">
+      <NotifyBonusPointReachedLimit v-if="isShowNotifyBonusPointReachedLimit" />
+    </transition>
+
+    <GameRule
+      v-show="isShowRule"
+      game-title="乡村林场"
+      :rich-text="store.state.gameRuleList[0].rtf"
+      @close="isShowRule=false"
+    />
   </div>
   </div>
 </template>
 </template>
 
 
@@ -141,6 +156,8 @@ import dayjs from 'dayjs'
 import { showDialog } from 'vant'
 import { showDialog } from 'vant'
 import { shuffle } from 'lodash'
 import { shuffle } from 'lodash'
 import { addScore, getScore } from '@/api.js'
 import { addScore, getScore } from '@/api.js'
+import GameRule from '@/components/GameRule.vue'
+import NotifyBonusPointReachedLimit from '@/components/NotifyBonusPointReachedLimit.vue'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 const router = useRouter()
@@ -157,17 +174,13 @@ function onClickReturnHome() {
   })
   })
 }
 }
 
 
+const isShowRule = ref(false)
+
 /**
 /**
- * 游戏规则
+ * 游戏逻辑
  */
  */
 const bonusPoint = ref(0)
 const bonusPoint = ref(0)
-const bonusPointForShow = computed(() => {
-  if (!store.state.ifScoreLimitReached) {
-    return bonusPoint.value
-  } else {
-    return `已达本日上限${store.state.scoreLimit}分`
-  }
-})
+
 const toolList = ref([
 const toolList = ref([
   {
   {
     toolIdx: 0,
     toolIdx: 0,
@@ -220,32 +233,32 @@ const stepList = [
   },
   },
 ]
 ]
 const currentStepIdx = ref(0)
 const currentStepIdx = ref(0)
-const lastPlayTimeStr = localStorage.getItem('plant-tree-last-time')
-if (process.env.VUE_APP_CLI_MODE !== 'dev') {
-  if (lastPlayTimeStr) {
-    const lastPlayTime = new Date(Number(lastPlayTimeStr))
-    const lastPlayYear = lastPlayTime.getFullYear()
-    const lastPlayMonth = lastPlayTime.getMonth()
-    const lastPlayDay = lastPlayTime.getDay()
+const lastPlayTimeStr = localStorage.getItem(`plant-tree-last-time-${store.state.token}`)
+// if (process.env.VUE_APP_CLI_MODE !== 'dev') {
+if (lastPlayTimeStr) {
+  const lastPlayTime = new Date(Number(lastPlayTimeStr))
+  const lastPlayYear = lastPlayTime.getFullYear()
+  const lastPlayMonth = lastPlayTime.getMonth()
+  const lastPlayDay = lastPlayTime.getDay()
 
 
-    const currentTime = new Date()
-    const currentYear = currentTime.getFullYear()
-    const currentMonth = currentTime.getMonth()
-    const currentDay = currentTime.getDay()
+  const currentTime = new Date()
+  const currentYear = currentTime.getFullYear()
+  const currentMonth = currentTime.getMonth()
+  const currentDay = currentTime.getDay()
 
 
-    if (lastPlayYear === currentYear && lastPlayMonth === currentMonth && lastPlayDay === currentDay) {
-      currentStepIdx.value = stepList.length - 1
-      bonusPoint.value = 4 * store.state.gameRuleList[0].score
-    }
+  if (lastPlayYear === currentYear && lastPlayMonth === currentMonth && lastPlayDay === currentDay) {
+    currentStepIdx.value = stepList.length - 1
+    bonusPoint.value = 4 * store.state.gameRuleList[0].score
   }
   }
 }
 }
+// }
 const isOver = computed(() => {
 const isOver = computed(() => {
   return currentStepIdx.value === stepList.length - 1
   return currentStepIdx.value === stepList.length - 1
 })
 })
 watch(isOver, (vNew) => {
 watch(isOver, (vNew) => {
   if (vNew) {
   if (vNew) {
-    localStorage.setItem('plant-tree-last-time', (new Date()).getTime())
-    if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
+    localStorage.setItem(`plant-tree-last-time-${store.state.token}`, (new Date()).getTime())
+    if (store.state.loginStatus && !store.state.ifScoreLimitReached && bonusPoint.value !== 0) {
       addScore(bonusPoint.value).then(() => {
       addScore(bonusPoint.value).then(() => {
         getScore().then((res) => {
         getScore().then((res) => {
           store.commit('setScore', res.total)
           store.commit('setScore', res.total)
@@ -264,10 +277,20 @@ function onClickTool(toolIdx) {
   } else {
   } else {
     if (currentStepIdx.value < stepList.length - 1) {
     if (currentStepIdx.value < stepList.length - 1) {
       currentStepIdx.value++
       currentStepIdx.value++
-      bonusPoint.value += store.state.gameRuleList[0].score
+      if (store.state.loginStatus && !store.state.ifScoreLimitReached) {
+        bonusPoint.value += store.state.gameRuleList[0].score
+      }
     }
     }
   }
   }
 }
 }
+
+const isShowNotifyBonusPointReachedLimit = ref(false)
+if (store.state.ifScoreLimitReached && !isOver.value) {
+  isShowNotifyBonusPointReachedLimit.value = true
+  setTimeout(() => {
+    isShowNotifyBonusPointReachedLimit.value = false
+  }, 2000)
+}
 </script>
 </script>
 
 
 <style lang="less" scoped>
 <style lang="less" scoped>

+ 269 - 0
game/src/views/RedeemForm.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="redeem-form">
+    <div class="paper">
+      <div class="form-item">
+        <h3>您的称呼</h3>
+        <input
+          v-model="name"
+          type="text"
+          autofocus
+          placeholder="请输入内容,20字以内"
+        >
+      </div>
+      <div class="form-item">
+        <h3>联系方式</h3>
+        <input
+          v-model="contact"
+          type="text"
+          placeholder="请输入内容,20字以内"
+        >
+      </div>
+      <div class="form-item last">
+        <h3>地址及留言</h3>
+        <textarea
+          v-model="detail"
+          placeholder="请输入省市区街道,50字以内."
+        />
+      </div>
+
+      <hr>
+
+      <div class="number-info-item">
+        <div class="key">
+          您的积分
+        </div>
+        <div class="value">
+          {{ store.state.score }}
+        </div>
+      </div>
+      <div class="number-info-item">
+        <div class="key">
+          礼品消费
+        </div>
+        <div class="value red">
+          -{{ prizeInfo.score }}
+        </div>
+      </div>
+      <div class="number-info-item">
+        <div class="key bold">
+          合计
+        </div>
+        <div class="value">
+          {{ result }}
+        </div>
+      </div>
+    </div>
+
+    <button
+      class="submit"
+      :class="{
+        needInfo: !name || !contact || !detail,
+        notEnough: result < 0,
+        over: haveSubmitted,
+      }"
+      @click="submit"
+    >
+      发起兑换
+    </button>
+
+    <transition name="fade-in-out">
+      <NotifyComp
+        v-show="ifShowNotify"
+        text="提交成功,工作人员会与您及时联系"
+      />
+    </transition>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+import { redeem, getScore } from '@/api.js'
+import NotifyComp from '@/components/NotifyComp.vue'
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+const prizeInfo = JSON.parse(route.query.prizeInfo)
+
+const result = ref(store.state.score - prizeInfo.score)
+
+const name = ref('')
+const contact = ref('')
+const detail = ref('')
+
+const ifShowNotify = ref(false)
+const haveSubmitted = ref(false)
+function submit() {
+  redeem(detail.value, name.value, contact.value, prizeInfo.id, prizeInfo.score).then((res) => {
+    ifShowNotify.value = true
+    haveSubmitted.value = true
+    getScore().then((res) => {
+      store.commit('setScore', res.total)
+      store.commit('setIfScoreLimitReached', res.hasOver)
+      setTimeout(() => {
+        ifShowNotify.value = false
+        setTimeout(() => {
+          router.go(-1)
+        }, 1000)
+      }, 1000)
+
+    })
+  })
+}
+</script>
+
+<style lang="less" scoped>
+.redeem-form{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(207, 187, 156, 1);
+  >.paper{
+    position: absolute;
+    width: calc(348 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(582 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    top: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    left: 50%;
+    transform: translate(-50%, 0);
+    background-image: url(@/assets/images/redeem-paper.png);
+    background-size: contain;
+    background-repeat: no-repeat;
+    background-position: center center;
+    padding-top: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-left: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    padding-right: calc(25 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    >.form-item{
+      margin-bottom: calc(42 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >h3{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: bold;
+        color: #A97C46;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        margin-bottom: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >input{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: rgba(169, 124, 70, 1);
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        width: 100%;
+        border-bottom: 1px dashed #CFBB9C;
+        padding-bottom: calc(9 / 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'));
+        }
+      }
+      >textarea{
+        width: 100%;
+        height: calc(175 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        border-radius: calc(2 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        border: 1px dashed #CFBB9C;
+        padding-top: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        padding-left: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        padding-right: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        padding-bottom: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        resize: none;
+        color: rgba(169, 124, 70, 1);
+        &::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'));
+        }
+      }
+    }
+    >.form-item.last{
+      margin-bottom: calc(60 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    }
+    >hr{
+      position: absolute;
+      top: calc(468 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      left: 50%;
+      transform: translate(-50%, 0);
+      width: calc(306 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      height: 0;
+      border-top: 1px dashed #CFBB9C;
+    }
+    >.number-info-item{
+      margin-bottom: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      >.key{
+        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'));
+      }
+      >.key.bold{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: bold;
+        color: #A97C46;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >.value{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: bold;
+        color: #A97C46;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+      >.value.red{
+        font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: bold;
+        color: #FF4040;
+        line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      }
+    }
+  }
+  >button.submit{
+    position: absolute;
+    width: calc(348 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(60 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    bottom: calc(59 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    left: 50%;
+    transform: translate(-50%, 0);
+    background-color: #FFE6A5;
+    border-radius: calc(3 / 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: bold;
+    color: #A97C46;
+    line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >button.submit.needInfo{
+    opacity: 0.5;
+    pointer-events: none;
+  }
+  >button.submit.notEnough{
+    opacity: 1;
+    color: #7B7B7B;
+    background: #CECECE;
+    pointer-events: none;
+  }
+  >button.over{
+    pointer-events: none;
+  }
+}
+</style>

+ 385 - 0
game/src/views/ShopView.vue

@@ -0,0 +1,385 @@
+<template>
+  <div class="shop-view">
+    <img
+      class="banner"
+      src="@/assets/images/redeem-banner.png"
+      alt=""
+      draggable="false"
+    >
+    <button
+      class="return"
+      @click="router.go(-1)"
+    />
+    <div class="my-bonus-point-title">
+      我的积分
+    </div>
+    <div class="my-bonus-point-value">
+      {{ store.state.score }}
+    </div>
+    <div class="main-wrap">
+      <div class="tabbar">
+        <button
+          class="redeem"
+          :class="{
+            active: currentTabIdx === 0,
+          }"
+          @click="currentTabIdx = 0"
+        >
+          兑换
+          <div
+            class="bottom-line"
+            :class="{
+              active: currentTabIdx === 0,
+            }"
+          />
+        </button>
+        <button
+          class="record"
+          :class="{
+            active: currentTabIdx === 1,
+          }"
+          @click="currentTabIdx = 1"
+        >
+          记录
+          <div
+            class="bottom-line"
+            :class="{
+              active: currentTabIdx === 1,
+            }"
+          />
+        </button>
+      </div>
+      <div
+        v-if="prizeList.length && currentTabIdx === 0"
+        class="prize-list"
+      >
+        <article
+          v-for="prizeItem in prizeList"
+          :key="prizeItem.id"
+          class="prize-item"
+          :class="{
+            disabled: prizeItem.stock === 0
+          }"
+          @click="onClickPrizeItem(prizeItem)"
+        >
+          <img
+            class="thumb"
+            :src="prizeItem.thumb"
+            alt=""
+            draggable="false"
+          >
+          <div class="title">
+            {{ prizeItem.name }}
+          </div>
+          <div class="remaining">
+            剩余:{{ prizeItem.stock }}
+          </div>
+          <div class="price">
+            <span class="number">{{ prizeItem.score }}</span>
+            <span class="not-number">积分</span>
+          </div>
+          <img
+            v-show="prizeItem.isEnabled && prizeItem.stock > 0"
+            class="icon-enabled"
+            src="@/assets/images/icon-gift.png"
+            alt=""
+            draggable="false"
+          >
+          <img
+            v-show="prizeItem.isEnabled && prizeItem.stock === 0"
+            class="icon-no-stock"
+            src="@/assets/images/no-stock.png"
+            alt=""
+            draggable="false"
+          >
+        </article>
+      </div>
+      <div
+        v-if="recordList.length && currentTabIdx === 1"
+        class="record-list"
+      >
+        <div
+          v-for="recordItem in recordList"
+          :key="recordItem.id"
+          class="record-item"
+        >
+          <div class="left">
+            <div class="type">
+              {{ recordItem.type }}
+            </div>
+            <div class="time">
+              {{ recordItem.updateTime }}
+            </div>
+          </div>
+          <div
+            class="right"
+            :class="{
+              negative: recordItem.type === '奖品兑换',
+            }"
+          >
+            {{ recordItem.type === '奖品兑换' ? '' : '+' }}{{ recordItem.score }}
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, watch, onMounted } from "vue"
+import { useRoute, useRouter } from "vue-router"
+import { useStore } from "vuex"
+import { getScore, getPrizeList, getBonusPointRecord } from '@/api.js'
+
+const route = useRoute()
+const router = useRouter()
+const store = useStore()
+
+const {
+  windowSizeInCssForRef,
+  windowSizeWhenDesignForRef,
+} = useSizeAdapt(390, 752)
+
+getScore().then((res) => {
+  store.commit('setScore', res.total)
+  store.commit('setIfScoreLimitReached', res.hasOver)
+})
+
+const currentTabIdx = ref(0)
+
+const prizeList = ref([])
+getPrizeList(0, 20).then((res) => {
+  prizeList.value = res.records
+  console.log(prizeList.value)
+})
+function onClickPrizeItem(prizeItem) {
+  router.push({
+    name: 'RedeemForm',
+    query: {
+      prizeInfo: JSON.stringify(prizeItem),
+    }
+  })
+}
+
+const recordList = ref([])
+getBonusPointRecord().then((res) => {
+  recordList.value = res.reverse()
+})
+
+</script>
+
+<style lang="less" scoped>
+.shop-view{
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  >img.banner{
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+  }
+  >button.return {
+    position: absolute;
+    top: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    left: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    width: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    height: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background-image: url(@/assets/images/icon-return.png);
+    background-size: cover;
+    background-repeat: no-repeat;
+    background-position: center center;
+  }
+  >.my-bonus-point-title{
+    position: absolute;
+    top: calc(70 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    left: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: heiti;
+    font-weight: 400;
+    color: #FFFFFF;
+    line-height: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >.my-bonus-point-value{
+    position: absolute;
+    top: calc(80 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    left: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-size: calc(40 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    font-family: Source Han Sans SC, Source Han Sans SC;
+    font-weight: 800;
+    color: #FFFFFF;
+    line-height: calc(47 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+  }
+  >.main-wrap{
+    position: absolute;
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    height: calc(598 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+    background: #F6F6F6;
+    border-radius: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) 0 0;
+    >.tabbar{
+      height: calc(60 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      display: flex;
+      justify-content: space-around;
+      align-items: center;
+      margin-bottom: calc(10 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >button{
+        font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: 400;
+        color: #414141;
+        line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        position: relative;
+        >.bottom-line{
+          position: absolute;
+          top: 100%;
+          left: 50%;
+          transform: translate(-50%, 0);
+          width: calc(33 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          height: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          background-image: url(@/assets/images/tab-item-bottom-line.png);
+          background-size: cover;
+          background-repeat: no-repeat;
+          background-position: center center;
+          display: none;
+        }
+      }
+      >button.active{
+        font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        font-family: Source Han Sans SC, Source Han Sans SC;
+        font-weight: bold;
+        color: #414141;
+        line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        >.bottom-line{
+          display: initial;
+        }
+      }
+    }
+    >div.prize-list{
+      height: calc(513 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      overflow: auto;
+      padding-left: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >article.prize-item{
+        display: inline-block;
+        width: calc(168 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        height: calc(200 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        background: #FFFFFF;
+        border-radius: calc(11 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        padding: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef')) calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        position: relative;
+        margin-right: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        margin-bottom: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        cursor: pointer;
+        >img.thumb{
+          width: 100%;
+          height: calc(92 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          object-fit: cover;
+        }
+        >.title{
+          margin-top: calc(3 / 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: #414141;
+          line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          overflow: hidden;
+          white-space: pre;
+          text-overflow: ellipsis;
+        }
+        >.remaining{
+          margin-top: calc(5 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          font-size: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          font-family: Source Han Sans SC, Source Han Sans SC;
+          font-weight: 400;
+          color: #414141;
+          opacity: 0.5;
+          line-height: calc(14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          overflow: hidden;
+          white-space: pre;
+          text-overflow: ellipsis;
+        }
+        >.price{
+          margin-top: calc(3 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          >.number{
+            font-size: calc(24 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            font-family: heiti;
+            font-weight: 800;
+            color: #C5A16C;
+            line-height: calc(28 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          }
+          >.not-number{
+            font-size: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            font-family: Source Han Sans SC, Source Han Sans SC;
+            font-weight: 400;
+            color: #C5A16C;
+            line-height: calc(14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            margin-left: 0.3em;
+          }
+        }
+        >img.icon-enabled{
+          position: absolute;
+          right: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          bottom: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          width: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          height: calc(30 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        }
+        >img.icon-no-stock{
+          position: absolute;
+          right: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          bottom: calc(7 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          width: calc(64 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          height: calc(64 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        }
+      }
+      >article.prize-item.disabled{
+        pointer-events: none;
+      }
+    }
+    >div.record-list{
+      height: calc(515 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      overflow: auto;
+      padding-left: calc(26 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      padding-right: calc(26 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+      >.record-item{
+        width: 100%;
+        margin-bottom: calc(15 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        >.left{
+          >.type{
+            font-size: calc(16 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            font-family: Source Han Sans SC, Source Han Sans SC;
+            font-weight: 400;
+            color: #414141;
+            line-height: calc(19 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          }
+          >.time{
+            margin-top: calc(3 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            font-size: calc(12 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            font-family: Source Han Sans SC, Source Han Sans SC;
+            font-weight: 400;
+            color: #414141;
+            line-height: calc(14 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+            opacity: 0.5;
+          }
+        }
+        >.right{
+          font-size: calc(20 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          font-family: heiti;
+          font-weight: bold;
+          color: #FFCC45;
+          line-height: calc(23 / v-bind('windowSizeWhenDesignForRef') * v-bind('windowSizeInCssForRef'));
+          letter-spacing: 0.05em;
+        }
+        >.right.negative{
+          color: #FF4040;
+        }
+      }
+    }
+  }
+}
+</style>