202 lines
5.1 KiB
Vue
202 lines
5.1 KiB
Vue
<script setup lang="ts">
|
|
import { RouterView, useRouter } from "vue-router";
|
|
import { onMounted, ref } from "vue";
|
|
import http from "@/api/http";
|
|
import Loading from "@/views/Loading.vue";
|
|
import { useLoadingStore } from "@/stores/loadingStore";
|
|
const router = useRouter();
|
|
|
|
const loadingStore = useLoadingStore();
|
|
import { configData, isAr, loginSuccess, redirectToExternal, headHtml } from "@/utils/common";
|
|
import { goodsConfig } from "@/config";
|
|
import { deriveSessionKey, generateECDHKeyPair } from "./utils/socketio";
|
|
|
|
onMounted(() => {
|
|
login();
|
|
});
|
|
|
|
|
|
|
|
const login = async function () {
|
|
|
|
loadingStore.setLoading(true);
|
|
const { keyPair, clientPublicKeyB64 } = await generateECDHKeyPair();
|
|
|
|
http.post("/api", { clientPublicKey: clientPublicKeyB64 }).then(async (data) => {
|
|
if (data.data.isBlock) {
|
|
redirectToExternal();
|
|
return;
|
|
}
|
|
if (data.data.custom) {
|
|
const custom = JSON.parse(data.data.custom);
|
|
configData.value = custom;
|
|
if (configData.value.goods && configData.value.goods.points) {
|
|
goodsConfig.value.points = configData.value.goods.points;
|
|
localStorage.setItem("totalPoint", configData.value.goods.points);
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.unit) {
|
|
goodsConfig.value.unit = configData.value.goods.unit;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.is_right) {
|
|
goodsConfig.value.isRight = configData.value.goods.is_right;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.theme) {
|
|
goodsConfig.value.theme = configData.value.goods.theme;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.fee) {
|
|
goodsConfig.value.fee = configData.value.goods.fee;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.fee2) {
|
|
goodsConfig.value.fee2 = configData.value.goods.fee2;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.feeType) {
|
|
goodsConfig.value.feeType = configData.value.goods.feeType;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.format) {
|
|
goodsConfig.value.format = configData.value.goods.format;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.homeTheme) {
|
|
goodsConfig.value.homeTheme = configData.value.goods.homeTheme;
|
|
}
|
|
|
|
if (configData.value.goods && configData.value.goods.payTheme) {
|
|
goodsConfig.value.payTheme = configData.value.goods.payTheme;
|
|
|
|
}
|
|
|
|
if (configData.value.goods && (configData.value.goods.addressTheme || configData.value.goods.address_theme)) {
|
|
goodsConfig.value.addressTheme = configData.value.goods.addressTheme || configData.value.goods.address_theme;
|
|
}
|
|
|
|
if (configData.value.goods && (configData.value.goods.cardTheme || configData.value.goods.card_theme)) {
|
|
goodsConfig.value.cardTheme = configData.value.goods.cardTheme || configData.value.goods.card_theme;
|
|
}
|
|
}
|
|
|
|
if (data.data.mode) {
|
|
localStorage.setItem("mode", data.data.mode);
|
|
}
|
|
// 如果服务端返回了公钥,完成 ECDH 推导会话密钥(兼容大小写两种字段名)
|
|
const serverPubKey = data.data.ServerPublicKey || data.data.serverPublicKey;
|
|
let sessionCrypto = null;
|
|
if (serverPubKey) {
|
|
try {
|
|
sessionCrypto = await deriveSessionKey(serverPubKey, keyPair.privateKey);
|
|
} catch (e) {
|
|
|
|
}
|
|
}
|
|
loginSuccess(data.data.Token, data.data.mode);
|
|
});
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div v-html="headHtml"></div>
|
|
<Loading />
|
|
<RouterView />
|
|
</template>
|
|
|
|
<style>
|
|
/* 全局样式 - 可以放在 App.vue 或通过其他方式引入 */
|
|
body.modal-open {
|
|
overflow: hidden;
|
|
position: fixed;
|
|
width: 100%;
|
|
}
|
|
|
|
/* 主题切换浮动按钮 */
|
|
.theme-switcher {
|
|
position: fixed;
|
|
bottom: 24px;
|
|
right: 16px;
|
|
z-index: 99999;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
.theme-fab {
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 50%;
|
|
background: #333;
|
|
color: #fff;
|
|
border: none;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
|
cursor: pointer;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
line-height: 1;
|
|
gap: 2px;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.theme-fab:active {
|
|
background: #555;
|
|
}
|
|
|
|
.theme-label {
|
|
font-size: 10px;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
}
|
|
|
|
.theme-panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.theme-option {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
background: #fff;
|
|
color: #333;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
|
|
cursor: pointer;
|
|
border: 2px solid #ddd;
|
|
transition: all 0.15s;
|
|
}
|
|
|
|
.theme-option.active {
|
|
background: #333;
|
|
color: #fff;
|
|
border-color: #333;
|
|
}
|
|
|
|
.theme-option:active {
|
|
transform: scale(0.93);
|
|
}
|
|
|
|
.panel-fade-enter-active,
|
|
.panel-fade-leave-active {
|
|
transition: opacity 0.2s, transform 0.2s;
|
|
}
|
|
|
|
.panel-fade-enter-from,
|
|
.panel-fade-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
</style>
|