DESKTOP-7E9LH09\admin 8 tuntia sitten
vanhempi
sitoutus
682a751759
10 muutettua tiedostoa jossa 514 lisäystä ja 191 poistoa
  1. 4 4
      dist/config.js
  2. 2 2
      dist/index.html
  3. 10 0
      package-lock.json
  4. 1 0
      package.json
  5. 4 4
      public/config.js
  6. 4 4
      src/request/api.js
  7. 277 141
      src/request/index.js
  8. 142 0
      src/utils/apiCrypto.js
  9. 45 23
      src/views/Home/index.vue
  10. 25 13
      src/views/dzGames/index.vue

+ 4 - 4
dist/config.js

@@ -1,8 +1,8 @@
 window.global = {
-  baseURL: "https://api.f1bet888.com/api",
-  // baseURL: 'https://sportapi.sp2509.cc/api',
-  wsURL: "wss://api.f1bet888.com:8443",
-  // wsURL: 'wss://sportapi.sp2509.cc:9501',
+  // baseURL: "https://api.f1bet888.com/api",
+  baseURL: "https://sportapi.sp2509.cc/api",
+  // wsURL: "wss://api.f1bet888.com:8443",
+  wsURL: "wss://sportapi.sp2509.cc:9501",
   botURL: "https://bot-api.f1bet888.com/api/",
   // botURL: "https://bot28api.sp2509.cc/api/",
   // serviceURL: 'https://service.8xbet001.com',

+ 2 - 2
dist/index.html

@@ -12,9 +12,9 @@
       </script>
     <script src="https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako.min.js"></script>
     <title>Vite App</title>
-    <script type="module" crossorigin src="/assets/index-B2h0Z54L.js"></script>
+    <script type="module" crossorigin src="/assets/index-CZD8uthP.js"></script>
     <link rel="modulepreload" crossorigin href="/assets/vue-vendor-ByaAFEoM.js">
-    <link rel="modulepreload" crossorigin href="/assets/element-plus-yJlZfrU1.js">
+    <link rel="modulepreload" crossorigin href="/assets/element-plus-CX7swo72.js">
     <link rel="stylesheet" crossorigin href="/assets/index-B94DN3b4.css">
   </head>
   <body>

+ 10 - 0
package-lock.json

@@ -16,6 +16,7 @@
         "color": "~5.0.0",
         "element-plus": "2.13.5",
         "es-toolkit": "~1.38.0",
+        "node-forge": "^1.4.0",
         "pinia": "~2.0.36",
         "pinia-plugin-persistedstate": "~3.2.1",
         "vue": "^3.5.39",
@@ -4084,6 +4085,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/node-forge": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.4.0.tgz",
+      "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
+      "license": "(BSD-3-Clause OR GPL-2.0)",
+      "engines": {
+        "node": ">= 6.13.0"
+      }
+    },
     "node_modules/node-releases": {
       "version": "2.0.50",
       "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.50.tgz",

+ 1 - 0
package.json

@@ -22,6 +22,7 @@
     "color": "~5.0.0",
     "element-plus": "2.13.5",
     "es-toolkit": "~1.38.0",
+    "node-forge": "^1.4.0",
     "pinia": "~2.0.36",
     "pinia-plugin-persistedstate": "~3.2.1",
     "vue": "^3.5.39",

+ 4 - 4
public/config.js

@@ -1,8 +1,8 @@
 window.global = {
-  baseURL: "https://api.f1bet888.com/api",
-  // baseURL: 'https://sportapi.sp2509.cc/api',
-  wsURL: "wss://api.f1bet888.com:8443",
-  // wsURL: 'wss://sportapi.sp2509.cc:9501',
+  // baseURL: "https://api.f1bet888.com/api",
+  baseURL: "https://sportapi.sp2509.cc/api",
+  // wsURL: "wss://api.f1bet888.com:8443",
+  wsURL: "wss://sportapi.sp2509.cc:9501",
   botURL: "https://bot-api.f1bet888.com/api/",
   // botURL: "https://bot28api.sp2509.cc/api/",
   // serviceURL: 'https://service.8xbet001.com',

+ 4 - 4
src/request/api.js

@@ -726,8 +726,8 @@ export const indexSportCount = (params) => {
 };
 
 // 三方游戏列表
-export const getPlatforms = () => {
-  return request.get("/egame/platforms");
+export const getPlatforms = (params) => {
+  return request.get("/egame/platforms", { params });
 };
 
 //获取三方游戏链接
@@ -751,6 +751,6 @@ export const balanceAll = () => {
 };
 
 //获取电子游戏平台和平台下的游戏列表
-export const dzList = () => {
-  return request.get("/egame/electronicGames");
+export const dzList = (params) => {
+  return request.get("/egame/electronicGames", { params });
 };

+ 277 - 141
src/request/index.js

@@ -1,164 +1,300 @@
-import axios from 'axios'
-import i18n, { defaultLang } from "@/lang/index.js"
-import { ElMessage, ElLoading, ElMessageBox } from 'element-plus/es'
-import router from '@/router'
+import axios from "axios";
+import i18n, { defaultLang } from "@/lang/index.js";
+import { ElMessage, ElLoading, ElMessageBox } from "element-plus/es";
+import router from "@/router";
+import {
+  encryptApiPayload,
+  decryptApiPayload,
+  isEncryptionExcluded,
+} from "@/utils/apiCrypto";
 
-let loadingInstance = null
+let loadingInstance = null;
 
-export const apiUrl = window.global?.botURL; // botapi的地址
+export const apiUrl = window.global?.botURL;
 
 const service = axios.create({
-    baseURL: window.global?.baseURL,
-    timeout: 10000,
-    headers: {
-        'Content-Type': 'application/json;charset=utf-8',
-        'Authorization': localStorage.getItem('token'),
-    }
-})
+  baseURL: window.global?.baseURL,
+  timeout: 10000,
+  headers: {
+    "Content-Type": "application/json;charset=utf-8",
+    Authorization: localStorage.getItem("token"),
+  },
+});
+
+const DEBUG = true;
+
+function log(...args) {
+  if (DEBUG) {
+    console.log("[API DEBUG]", ...args);
+  }
+}
 
 service.interceptors.request.use(
-    config => {
-        const token = localStorage.getItem('token')
-        const localLang = localStorage.getItem('locale')
+  async (config) => {
+    const token = localStorage.getItem("token");
+    const localLang = localStorage.getItem("locale");
 
-        if (token) {
-            config.headers.Authorization = token
-        }
+    if (token) {
+      config.headers.Authorization = token;
+    }
 
-        config.headers.Lang = localLang || defaultLang
+    config.headers.Lang = localLang || defaultLang;
 
-        if (!config.hideLoading) {
-            loadingInstance = ElLoading.service({
-                lock: true,
-                text: i18n.global.t('加载中') + '...',
-                background: 'rgba(0, 0, 0, 0.7)'
-            })
-        }
+    if (!config.hideLoading) {
+      loadingInstance = ElLoading.service({
+        lock: true,
+        text: i18n.global.t("加载中") + "...",
+        background: "rgba(0, 0, 0, 0.7)",
+      });
+    }
 
-        return config
-    },
-    error => {
-        if (loadingInstance) {
-            loadingInstance.close()
+    const url = config.url || "";
+    const excluded = isEncryptionExcluded(url);
+
+    if (!excluded) {
+      let data = config.data || config.params || {};
+      if (data == null || data === "") {
+        data = {};
+      }
+      if (Object.prototype.toString.call(data) !== "[object Object]") {
+        return config;
+      }
+
+      try {
+        const payload = await encryptApiPayload(data, "request");
+
+        config.headers["X-Api-Encrypted"] = "1";
+        config.headers["Content-Type"] = "application/json";
+
+        if (config.method === "get" || config.method === "GET") {
+          config.params = { payload };
+          delete config.data;
+        } else {
+          config.data = { payload };
         }
-        return Promise.reject(error)
+      } catch (error) {
+        console.error("API 加密失败:", error);
+      }
+    }
+
+    return config;
+  },
+  (error) => {
+    if (loadingInstance) {
+      loadingInstance.close();
     }
-)
+    console.error("请求拦截器错误:", error);
+    return Promise.reject(error);
+  },
+);
 
 service.interceptors.response.use(
-    response => {
-        if (loadingInstance) {
-            loadingInstance.close()
-        }
+  async (response) => {
+    if (loadingInstance) {
+      loadingInstance.close();
+    }
 
-        const res = response.data
-
-        if (res.code === 1) {
-            if (response.config.showSuccessTip !== false && res.msg) {
-                ElMessage.success(res.msg)
-            }
-            return res
-        } else if (res.code === 0) {
-            if (response.config.showFailTip !== false && res.msg) {
-                ElMessage.error(res.msg)
-            }
-            return Promise.reject(res)
-        } else if (res.code === 401) {
-            ElMessage.error(res.msg || i18n.global.t('登录已过期,请重新登录'))
-            localStorage.removeItem('token')
-            localStorage.removeItem("youke_token");
-            localStorage.removeItem('user_info')
-            setTimeout(() => {
-                window.location.href = '/login'
-            }, 1500)
-            return Promise.reject(new Error('登录已过期'))
-        } else if (res.code === 10011) {
-            // 资金密码未设置
-            setTimeout(() => {
-                ElMessageBox.confirm(
-                    i18n.global.t('您还未设置资金密码,是否前往设置?'),
-                    i18n.global.t('提示'),
-                    {
-                        confirmButtonText: i18n.global.t('确定'),
-                        cancelButtonText: i18n.global.t('取消'),
-                        type: 'warning',
-                    }
-                ).then(() => {
-                    router.push({ name: 'Safety', query: { active: 'fund_pwd' }})
-                }).catch(() => {})
-            }, 300)
-            return Promise.reject(new Error(res.msg || '未设置资金密码'))
-        } else if (res.code === 10012) {
-            // 系统通知维护弹窗
-            const maintainTime = res.data?.time || ''; 
-
-           const htmlContent = `
-                <div style="line-height: 1.8; font-size: 14px; padding: 10px 5px;">
-                    <div style="font-size: 12px; color: #909399; margin-bottom: 30px; text-align: center; border-bottom: 1px solid #333; padding-bottom: 15px;">
-                        ${i18n.global.t('温馨提示:平台不会主动加好友上分,请谨防被骗,以咨询客服人员为准')}
-                    </div>
-                    
-                    <div style="text-align: left; margin-bottom: 15px; color: #e5e7eb; font-size: 15px; font-weight: 500;">
-                        ${i18n.global.t('尊敬的用户')}:
-                    </div>
-                    
-                    <div style="text-align: left; margin-bottom: 12px; color: #c0c4cc; text-indent: 2em;">
-                        ${i18n.global.t('每日')}(<span style="color: #f56c6c; font-weight: bold; padding: 0 4px;">${maintainTime}</span>)${i18n.global.t('为系统维护时间段')}。
-                    </div>
-                    
-                    <div style="text-align: left; color: #a3a6ad; font-size: 13px; text-indent: 2em;">
-                        ${i18n.global.t('维护期间将暂停此交易服务,请您合理安排操作时间,敬请谅解!')}
-                    </div>
-                </div>
-            `;
-
-            ElMessageBox.alert(htmlContent, i18n.global.t('系统通知'), {
-                dangerouslyUseHTMLString: true,
-                confirmButtonText: i18n.global.t('确认'),
-                center: true,
-                showClose: false,
-                confirmButtonClass: 'notice-confirm-btn',
-                customClass: 'dark-notice-modal' // 添加此 class 方便在全局设定深色背景
-            }).catch(() => {});
-
-            return Promise.reject(new Error(res.msg || '系统重要通知'))
-        } else {
-            if (response.config.showFailTip !== false) {
-                ElMessage.error(res.msg || i18n.global.t('请求失败,请稍后重试'))
-            }
-            return Promise.reject(new Error(res.msg || '请求失败'))
-        }
-    },
-    error => {
-        if (loadingInstance) {
-            loadingInstance.close()
+    let res = response.data;
+
+    // ===== 修改:通过响应数据判断是否加密 =====
+    // 不再依赖响应头,直接检查响应数据中是否有 encrypted 和 payload
+    const isEncrypted =
+      res?.encrypted === true && typeof res?.payload === "string";
+
+    if (isEncrypted) {
+      try {
+        res = await decryptApiPayload(res.payload, "response");
+        response.data = res;
+      } catch (error) {
+        ElMessage.error(i18n.global.t("数据解密失败,请稍后重试"));
+        return Promise.reject(error);
+      }
+    }
+
+    if (res.code === 1) {
+      if (response.config.showSuccessTip !== false && res.msg) {
+        ElMessage.success(res.msg);
+      }
+      return res;
+    } else if (res.code === 0) {
+      if (response.config.showFailTip !== false && res.msg) {
+        ElMessage.error(res.msg);
+      }
+      return Promise.reject(res);
+    } else if (res.code === 401) {
+      ElMessage.error(res.msg || i18n.global.t("登录已过期,请重新登录"));
+      localStorage.removeItem("token");
+      localStorage.removeItem("youke_token");
+      localStorage.removeItem("user_info");
+      setTimeout(() => {
+        window.location.href = "/login";
+      }, 1500);
+      return Promise.reject(new Error("登录已过期"));
+    } else if (res.code === 10011) {
+      setTimeout(() => {
+        ElMessageBox.confirm(
+          i18n.global.t("您还未设置资金密码,是否前往设置?"),
+          i18n.global.t("提示"),
+          {
+            confirmButtonText: i18n.global.t("确定"),
+            cancelButtonText: i18n.global.t("取消"),
+            type: "warning",
+          },
+        )
+          .then(() => {
+            router.push({ name: "Safety", query: { active: "fund_pwd" } });
+          })
+          .catch(() => {});
+      }, 300);
+      return Promise.reject(new Error(res.msg || "未设置资金密码"));
+    } else if (res.code === 10012) {
+      const maintainTime = res.data?.time || "";
+
+      const htmlContent = `
+        <div style="line-height: 1.8; font-size: 14px; padding: 10px 5px;">
+          <div style="font-size: 12px; color: #909399; margin-bottom: 30px; text-align: center; border-bottom: 1px solid #333; padding-bottom: 15px;">
+            ${i18n.global.t("温馨提示:平台不会主动加好友上分,请谨防被骗,以咨询客服人员为准")}
+          </div>
+          <div style="text-align: left; margin-bottom: 15px; color: #e5e7eb; font-size: 15px; font-weight: 500;">
+            ${i18n.global.t("尊敬的用户")}:
+          </div>
+          <div style="text-align: left; margin-bottom: 12px; color: #c0c4cc; text-indent: 2em;">
+            ${i18n.global.t("每日")}(<span style="color: #f56c6c; font-weight: bold; padding: 0 4px;">${maintainTime}</span>)${i18n.global.t("为系统维护时间段")}。
+          </div>
+          <div style="text-align: left; color: #a3a6ad; font-size: 13px; text-indent: 2em;">
+            ${i18n.global.t("维护期间将暂停此交易服务,请您合理安排操作时间,敬请谅解!")}
+          </div>
+        </div>
+      `;
+
+      ElMessageBox.alert(htmlContent, i18n.global.t("系统通知"), {
+        dangerouslyUseHTMLString: true,
+        confirmButtonText: i18n.global.t("确认"),
+        center: true,
+        showClose: false,
+        confirmButtonClass: "notice-confirm-btn",
+        customClass: "dark-notice-modal",
+      }).catch(() => {});
+
+      return Promise.reject(new Error(res.msg || "系统重要通知"));
+    } else {
+      if (response.config.showFailTip !== false) {
+        ElMessage.error(res.msg || i18n.global.t("请求失败,请稍后重试"));
+      }
+      return Promise.reject(new Error(res.msg || "请求失败"));
+    }
+  },
+  (error) => {
+    if (loadingInstance) {
+      loadingInstance.close();
+    }
+
+    console.error("响应错误:", error);
+    log("错误详情:", {
+      message: error.message,
+      response: error.response,
+      config: error.config,
+    });
+
+    let errorMsg = "";
+
+    if (error.code === "ECONNABORTED") {
+      errorMsg = i18n.global.t("请求超时,请稍后重试");
+    } else if (error.response) {
+      log("服务器错误响应:", error.response.data);
+      log("HTTP状态码:", error.response.status);
+
+      // 尝试解密错误响应
+      const errData = error.response.data;
+      if (errData?.encrypted && errData?.payload) {
+        try {
+          // 需要同步处理,这里简化
+          errorMsg = i18n.global.t("服务器错误,请稍后重试");
+        } catch (e) {
+          errorMsg = i18n.global.t("数据解密失败");
         }
+      } else {
+        errorMsg =
+          errData?.msg ||
+          errData?.message ||
+          i18n.global.t("请求失败,请稍后重试");
+      }
+    } else if (error.request) {
+      errorMsg = i18n.global.t("网络异常,请检查网络后重试");
+    } else {
+      errorMsg = error.message || i18n.global.t("请求失败,请稍后重试");
+    }
 
-        ElMessage.error(error.message || i18n.global.t('网络异常,请检查网络后重试'))
-        return Promise.reject(error)
+    if (!error.__handled) {
+      ElMessage.error(errorMsg);
+      error.__handled = true;
     }
-)
+
+    return Promise.reject(error);
+  },
+);
 
 service.get = (url, config = {}) => {
-    return service({
-        url,
-        method: 'GET',
-        params: config.params,
-        hideLoading: config.hideLoading,
-        showSuccessTip: config.showSuccessTip,
-        showFailTip: config.showFailTip
-    })
-}
+  return service({
+    url,
+    method: "GET",
+    params: config.params || {},
+    hideLoading: config.hideLoading,
+    showSuccessTip: config.showSuccessTip,
+    showFailTip: config.showFailTip,
+    ...config,
+  });
+};
 
 service.post = (url, data = {}, config = {}) => {
-    return service({
-        url,
-        method: 'POST',
-        data,
-        hideLoading: config.hideLoading,
-        showSuccessTip: config.showSuccessTip,
-        showFailTip: config.showFailTip
-    })
-}
+  return service({
+    url,
+    method: "POST",
+    data,
+    hideLoading: config.hideLoading,
+    showSuccessTip: config.showSuccessTip,
+    showFailTip: config.showFailTip,
+    ...config,
+  });
+};
+
+service.put = (url, data = {}, config = {}) => {
+  return service({
+    url,
+    method: "PUT",
+    data,
+    hideLoading: config.hideLoading,
+    showSuccessTip: config.showSuccessTip,
+    showFailTip: config.showFailTip,
+    ...config,
+  });
+};
+
+service.delete = (url, config = {}) => {
+  return service({
+    url,
+    method: "DELETE",
+    params: config.params || {},
+    hideLoading: config.hideLoading,
+    showSuccessTip: config.showSuccessTip,
+    showFailTip: config.showFailTip,
+    ...config,
+  });
+};
+
+service.uploadFile = (url, formData, config = {}) => {
+  return service({
+    url,
+    method: "POST",
+    data: formData,
+    headers: {
+      "Content-Type": "multipart/form-data",
+    },
+    hideLoading: config.hideLoading,
+    showSuccessTip: config.showSuccessTip,
+    showFailTip: config.showFailTip,
+    ...config,
+  });
+};
 
-export default service
+export default service;

+ 142 - 0
src/utils/apiCrypto.js

@@ -0,0 +1,142 @@
+import forge from "node-forge";
+
+const VERSION = 1;
+const IV_BYTES = 12;
+const TAG_BYTES = 16;
+const TIMESTAMP_TOLERANCE_MS = 300 * 1000;
+
+// ===== 密钥直接硬编码 =====
+const API_CRYPTO_KEY = "3MRp/hTEz310NX3BJuMmIYAdlby1Q4K4XPeVSxXm5tE=";
+
+function bytesToBase64Url(binaryBytes) {
+  return forge.util
+    .encode64(binaryBytes)
+    .replace(/\+/g, "-")
+    .replace(/\//g, "_")
+    .replace(/=+$/g, "");
+}
+
+function base64UrlToBytes(value) {
+  if (!value || !/^[A-Za-z0-9_-]+$/.test(value)) {
+    throw new Error("base64url 数据格式错误");
+  }
+  const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
+  const padded = base64 + "=".repeat((4 - (base64.length % 4)) % 4);
+  return forge.util.decode64(padded);
+}
+
+function decodeConfiguredKey(value) {
+  if (!value) throw new Error("缺少 API_CRYPTO_KEY");
+
+  let key;
+  if (value.startsWith("hex:")) {
+    const hexValue = value.slice(4);
+    if (!/^[0-9a-fA-F]{64}$/.test(hexValue)) {
+      throw new Error("十六进制 API 加密密钥必须是 64 个十六进制字符");
+    }
+    key = forge.util.hexToBytes(hexValue);
+  } else if (/^[0-9a-fA-F]{64}$/.test(value)) {
+    key = forge.util.hexToBytes(value);
+  } else {
+    const base64Value = value.startsWith("base64:") ? value.slice(7) : value;
+    key = base64UrlToBytes(
+      base64Value.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""),
+    );
+  }
+
+  if (key.length !== 32) throw new Error("API 加密密钥必须为 32 字节");
+  return key;
+}
+
+const cryptoKey = decodeConfiguredKey(API_CRYPTO_KEY);
+
+function secureRandomIv() {
+  if (typeof crypto !== "undefined" && crypto.getRandomValues) {
+    const randomBytes = crypto.getRandomValues(new Uint8Array(IV_BYTES));
+    let iv = "";
+    for (let i = 0; i < randomBytes.length; i += 1) {
+      iv += String.fromCharCode(randomBytes[i]);
+    }
+    return iv;
+  }
+  throw new Error("当前环境不支持安全随机数");
+}
+
+function additionalData(direction, timestamp) {
+  return `api-content:v${VERSION}:${direction}:${timestamp}`;
+}
+
+/**
+ * 加密 API 请求参数
+ */
+export async function encryptApiPayload(value, direction = "request") {
+  const timestamp = Date.now();
+  const iv = secureRandomIv();
+
+  const cipher = forge.cipher.createCipher("AES-GCM", cryptoKey);
+  cipher.start({
+    iv,
+    additionalData: additionalData(direction, timestamp),
+    tagLength: TAG_BYTES * 8,
+  });
+  cipher.update(forge.util.createBuffer(JSON.stringify(value), "utf8"));
+  if (!cipher.finish()) throw new Error("API 请求加密失败");
+
+  const data = cipher.output.getBytes();
+  const tag = cipher.mode.tag.getBytes();
+
+  const envelope = {
+    v: VERSION,
+    ts: timestamp,
+    iv: bytesToBase64Url(iv),
+    data: bytesToBase64Url(data),
+    tag: bytesToBase64Url(tag),
+  };
+
+  return bytesToBase64Url(forge.util.encodeUtf8(JSON.stringify(envelope)));
+}
+
+/**
+ * 解密 API 响应
+ */
+export async function decryptApiPayload(payload, direction = "response") {
+  const envelope = JSON.parse(forge.util.decodeUtf8(base64UrlToBytes(payload)));
+
+  if (envelope.v !== VERSION || !Number.isSafeInteger(envelope.ts)) {
+    throw new Error("API 加密协议版本或时间戳无效");
+  }
+  if (Math.abs(Date.now() - envelope.ts) > TIMESTAMP_TOLERANCE_MS) {
+    throw new Error("API 密文已过期,请检查设备时间");
+  }
+
+  const iv = base64UrlToBytes(envelope.iv);
+  const data = base64UrlToBytes(envelope.data);
+  const tag = base64UrlToBytes(envelope.tag);
+
+  if (iv.length !== IV_BYTES || tag.length !== TAG_BYTES) {
+    throw new Error("API 密文 IV 或认证标签长度错误");
+  }
+
+  const decipher = forge.cipher.createDecipher("AES-GCM", cryptoKey);
+  decipher.start({
+    iv,
+    additionalData: additionalData(direction, envelope.ts),
+    tagLength: TAG_BYTES * 8,
+    tag: forge.util.createBuffer(tag, "raw"),
+  });
+  decipher.update(forge.util.createBuffer(data, "raw"));
+  if (!decipher.finish()) throw new Error("API 响应密文认证失败");
+
+  const plaintext = forge.util.decodeUtf8(decipher.output.getBytes());
+  return JSON.parse(plaintext);
+}
+
+/**
+ * 判断 URL 是否需要加密
+ */
+export function isEncryptionExcluded(url) {
+  const path = String(url)
+    .replace(/^https?:\/\/[^/]+/i, "")
+    .split("?")[0];
+  return path.startsWith("/api/upload/") || path === "/api/egame/play";
+}

+ 45 - 23
src/views/Home/index.vue

@@ -277,30 +277,41 @@
           </div>
         </div>
       </div>
-      <div class="roll-ball-section" v-if="dzGames.length > 0">
+
+      <div class="roll-ball-section" v-for="(item, index) in dzGames" :key="index">
         <div class="roll-ball-header">
           <div class="header-left">
-            <img :src="iconDz" style="width: 20px; height: 20px;" />
-            <span class="roll-ball-title">{{ t("电子") }}</span>
+            <img :src="item.icon" style="width:20px;height:20px;">
+            <span class="roll-ball-title">
+              {{ item.typeName }}
+            </span>
           </div>
-          <div class="more-roll-btn" @click="more">
-            <span class="more-text">{{ t('更多') }} >></span>
+
+          <div class="more-roll-btn" @click="more(item.gameType)">
+            <span class="more-text">
+              {{ t('更多') }} >>
+            </span>
           </div>
         </div>
+
+        <!-- 平台Tab -->
         <div class="lottery-games-scroll pc-scroll-view">
           <div class="lottery-games-flex">
-            <div class="lottery-game-tabs" :class="tabIndex == index ? 'active' : ''" v-for="(item, index) in dzGames"
-              :key="index" @click="tabIndex = index">
-              <span class="lottery-game-tab">{{ item.name }}</span>
+            <div class="lottery-game-tabs" v-for="(platform, tabIndex) in item.platforms" :key="tabIndex"
+              :class="{ active: item.activeTab === tabIndex }" @click="item.activeTab = tabIndex">
+              <span class="lottery-game-tab">
+                {{ platform.name }}
+              </span>
             </div>
           </div>
         </div>
 
+        <!-- 游戏列表 -->
         <div class="other-games-scroll pc-scroll-view">
           <div class="other-games-flex">
-            <div class="other-game-item" v-for="(item, itemIndex) in dzGames[tabIndex].games.slice(0, 21)"
-              :key="itemIndex" @click="goGame1(dzGames[tabIndex].platType, dzGameType, item.gameCode)">
-              <img :src="item.logo" style="width: 140px; height: 140px; border-radius: 8px; object-fit: cover;" />
+            <div class="other-game-item" v-for="(game, gameIndex) in item.platforms[item.activeTab].games.slice(0, 21)"
+              :key="gameIndex" @click="goGame1(item.platforms[item.activeTab].platType, item.gameType, game.gameCode)">
+              <img :src="game.logo" style="width:140px;height:140px;border-radius:8px;object-fit:cover;">
             </div>
           </div>
         </div>
@@ -418,7 +429,6 @@ const gamblingCount = ref(0);
 const limit = ref(10);
 const selectedMap = ref({});
 const skeletonLoading = ref(true);
-const tabIndex = ref(0)
 const lotteryHistory = ref([]);
 
 const lotteryGames = ref([
@@ -435,14 +445,17 @@ const handleLotteryGameClick = (item) => {
   }
 };
 
-const more = () => {
-  router.push('/videoGames');
+const more = (type) => {
+  router.push('/videoGames?type=' + type);
 }
 
 const otherGamesList = ref([]);
 
 const getGames = async () => {
-  const res = await getPlatforms();
+  let params = {
+    device: "pc"
+  }
+  const res = await getPlatforms(params);
   otherGamesList.value = res.data.list
   console.log(res.data.list)
 }
@@ -451,8 +464,14 @@ const dzGames = ref([])
 const dzGameType = ref("")
 
 const getDzList = async () => {
-  const res = await dzList();
-  dzGames.value = res.data.list
+  let params = {
+    device: "pc"
+  }
+  const res = await dzList(params);
+  dzGames.value = res.data.list.map(item => ({
+    ...item,
+    activeTab: 0
+  }))
   dzGameType.value = res.data.gameType
   console.log(res.data.list)
 }
@@ -460,19 +479,20 @@ const getDzList = async () => {
 const goGame = (game, gameType) => {
   let params = {
     platType: game.platType,
-    gameType: gameType
+    gameType: gameType,
+    device: "pc"
   }
   if (localStorage.getItem('youke_token')) {
     getDeomUrl(params).then(res => {
       console.log(res.data)
-      window.open(res.data.url)
+      window.location.href = res.data.url
     })
     // return ElMessage.warning(t('试玩账户不可进入'))
   } else {
 
     getGameUrl(params).then(res => {
       console.log(res.data)
-      window.open(res.data.url)
+      window.location.href = res.data.url
     })
   }
 }
@@ -481,19 +501,21 @@ const goGame1 = (game, gameType, gameCode) => {
   let params = {
     platType: game,
     gameType: gameType,
-    gameCode: gameCode
+    gameCode: gameCode,
+    device: "pc"
   }
   if (localStorage.getItem('youke_token')) {
     getDeomUrl(params).then(res => {
       console.log(res.data)
-      window.open(res.data.url)
+      window.location.href = res.data.url
     })
     // return ElMessage.warning(t('试玩账户不可进入'))
   } else {
 
     getGameUrl(params).then(res => {
       console.log(res.data)
-      window.open(res.data.url)
+      return
+      window.location.href = res.data.url
     })
   }
 }

+ 25 - 13
src/views/dzGames/index.vue

@@ -1,17 +1,17 @@
 <template>
     <div style="background: url('/images/Entertainment-bg.png'); background-size: cover; background-position: center;">
-        <div class="entertainment-list" v-if="dzGames.length > 0">
-            <div class="roll-ball-section" v-if="dzGames.length > 0">
+        <div class="entertainment-list">
+            <div class="roll-ball-section" v-if="Object.keys(dzGames).length > 0">
                 <div class="roll-ball-header">
                     <div class="header-left">
                         <img :src="iconDz" style="width: 20px; height: 20px;" />
-                        <span class="roll-ball-title">{{ t("电子") }}</span>
+                        <span class="roll-ball-title">{{ dzGames.typeName }}</span>
                     </div>
                 </div>
                 <div class="lottery-games-scroll pc-scroll-view">
                     <div class="lottery-games-flex">
                         <div class="lottery-game-tabs" :class="tabIndex == index ? 'active' : ''"
-                            v-for="(item, index) in dzGames" :key="index" @click="tabIndex = index">
+                            v-for="(item, index) in dzGames.platforms" :key="index" @click="tabIndex = index">
                             <span class="lottery-game-tab">{{ item.name }}</span>
                         </div>
                     </div>
@@ -19,7 +19,7 @@
 
                 <div class="other-games-scroll pc-scroll-view">
                     <div class="other-games-flex">
-                        <div class="other-game-item" v-for="(item, itemIndex) in dzGames[tabIndex].games"
+                        <div class="other-game-item" v-for="(item, itemIndex) in dzGames.platforms[tabIndex].games"
                             :key="itemIndex" @click="goGame1(dzGames[tabIndex].platType, dzGameType, item.gameCode)">
                             <img :src="item.logo"
                                 style="width: 140px; height: 140px; border-radius: 8px; object-fit: cover;" />
@@ -41,24 +41,36 @@ import pcFooter from '@/businessComponents/pcFooter/index.vue'
 import { getGameUrl, getDeomUrl, dzList } from "@/request/api.js";
 import iconDz from '@/assets/images/Home/dz.png'
 const { t, locale } = useI18n();
-
-onMounted(() => {
-    getDzList();
-});
+const router = useRouter();
+const route = useRoute();
 const dzGames = ref([])
 const dzGameType = ref("")
 const tabIndex = ref(0)
+onMounted(() => {
+    getDzList();
+
+});
 const getDzList = async () => {
-    const res = await dzList();
-    dzGames.value = res.data.list
-    dzGameType.value = res.data.gameType
+    let params = {
+        device: "pc"
+    }
+    const res = await dzList(params);
+    res.data.list.forEach((item, index) => {
+        console.log(item)
+
+        if (route.query.type == item.gameType) {
+            dzGames.value = item
+        }
+    });
+    dzGameType = route.query.type
     console.log(res.data.list)
 }
 const goGame1 = (game, gameType, gameCode) => {
     let params = {
         platType: game,
         gameType: gameType,
-        gameCode: gameCode
+        gameCode: gameCode,
+        device: "pc"
     }
     if (localStorage.getItem('youke_token')) {
         getDeomUrl(params).then(res => {