feat(server): 添加服务器列表接口和用户进入游戏功能

- 在登录模块中添加服务器列表获取接口 /api/server/list
- 实现用户进入游戏功能,记录登录时间和IP地址
- 添加时间工具函数用于格式化时间戳
- 配置Koa代理支持
- 更新白名单路由配置
- 添加MD5加密、Cookie操作和通用工具函数库
This commit is contained in:
2025-12-25 00:46:56 +08:00
parent 293fbd8bc8
commit eb05688ccc
59 changed files with 1019 additions and 138 deletions

View File

@@ -1,7 +1,7 @@
<script setup>
import {RouterView} from 'vue-router'
import Loading from "./components/loading.vue";
import {onMounted, reactive} from "vue";
import {isMobile} from "@/utils";
const mainDiv = reactive({
dataOrientation: "auto",
@@ -10,74 +10,9 @@ const mainDiv = reactive({
dataContentWidth: 1920,
dataContentHeight: 1280,
})
let loadBar1Width = 0, loadBar2Width = 0, setIntervalId = 0;
const loadBar2MaxWidth = 200;
let loadError = false;
onMounted(() => {
const loadBox = document.getElementById('loadBox'),
logoImg = document.getElementById('logoImg'),
loadBar1 = document.getElementById('loadBar1'),
loadBar2 = document.getElementById('loadBar2');
function updateLoadBar() {
if (loadError) {
return;
}
const screenWidth = document.documentElement.scrollWidth || document.body.scrollWidth,
screenHeight = document.documentElement.scrollHeight || document.body.scrollHeight,
smallScreen = screenWidth <= 0,
isHorizontal = isMobile() && screenHeight <= 590;
if (loadBox) loadBox.style.paddingTop = (!isHorizontal ? (screenHeight / 4) : 50) + 'px';
if (logoImg) logoImg.width = isHorizontal || smallScreen ? 300 : 500;
if (loadBar1) {
loadBar1Width += 20;
if (loadBar1Width > 100) loadBar1Width = 0;
loadBar1.style.width = loadBar1Width + '%';
}
if (loadBar2) {
loadBar2Width += 3;
if ((loadBar2Width / loadBar2MaxWidth * 100) > 100) {
loadBarFull();
} else {
loadBar2.style.width = loadBar2Width + 'px';
}
}
}
function startLoadBar() {
setIntervalId = self.setInterval(updateLoadBar, 100);
}
window.loadBarFull = function () {
if (loadBar2) loadBar2.style.width = loadBar2MaxWidth + 'px';
}
window.onload = function () {
startLoadBar();
if ((typeof (Worker) !== 'undefined')) {
const s = document.createElement('script');
s.type = 'text/javascript';
s.async = false;
s.addEventListener('load', function (e) {
s.parentNode.removeChild(s);
s.removeEventListener('load', e, false);
}, false);
s.src = 'js/index.js?v=' + Math.random();
document.body.appendChild(s);
} else {
loadError = true;
const errorMsg = `抱歉!您的浏览器不支持本游戏,请更换浏览器或前往官网 <span class="font_small"><a href="${getHttp() + location.host}" target="_blank" class="link_color">下载${isMobile() ? 'APP' : '微端'}</a></span> 进行游戏!`,
label = document.getElementById('label');
if (label) {
label.innerHTML = errorMsg;
} else {
alert(filterHTML(errorMsg));
}
}
}
window.loadBarClear = function () {
window.clearInterval(setIntervalId);
}
if (isMobile()) {
mainDiv.dataOrientation = "landscape";
mainDiv.dataScaleMode = "fixedHeight";
@@ -98,11 +33,5 @@ onMounted(() => {
<style scoped>
#mainDiv {
height: 100%;
#logDiv {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
</style>

View File

@@ -1,42 +1,94 @@
<script setup>
import logoGameCat from "/resource_Publish/assets/login/logoGameCat.png"
import {onMounted} from "vue";
let loadBar1Width = 0, loadBar2Width = 0, setIntervalId = 0;
const loadBar2MaxWidth = 200;
let loadError = false;
onMounted(()=>{
const loadBox = document.getElementById('loadBox'),
logoImg = document.getElementById('logoImg'),
loadBar1 = document.getElementById('loadBar1'),
loadBar2 = document.getElementById('loadBar2');
function updateLoadBar() {
if (loadError) {
return;
}
const screenWidth = document.documentElement.scrollWidth || document.body.scrollWidth,
screenHeight = document.documentElement.scrollHeight || document.body.scrollHeight,
smallScreen = screenWidth <= 0,
isHorizontal = isMobile() && screenHeight <= 590;
if (loadBox) loadBox.style.paddingTop = (!isHorizontal ? (screenHeight / 4) : 50) + 'px';
if (logoImg) logoImg.width = isHorizontal || smallScreen ? 300 : 500;
if (loadBar1) {
loadBar1Width += 20;
if (loadBar1Width > 100) loadBar1Width = 0;
loadBar1.style.width = loadBar1Width + '%';
}
if (loadBar2) {
loadBar2Width += 3;
if ((loadBar2Width / loadBar2MaxWidth * 100) > 100) {
loadBarFull();
} else {
loadBar2.style.width = loadBar2Width + 'px';
}
}
}
function startLoadBar() {
setIntervalId = self.setInterval(updateLoadBar, 100);
}
window.loadBarFull = function () {
if (loadBar2) loadBar2.style.width = loadBar2MaxWidth + 'px';
}
window.onload = function () {
startLoadBar();
if ((typeof (Worker) !== 'undefined')) {
const s = document.createElement('script');
s.type = 'text/javascript';
s.async = false;
s.addEventListener('load', function (e) {
s.parentNode.removeChild(s);
s.removeEventListener('load', e, false);
}, false);
s.src = 'js/index.js?v=' + Math.random();
document.body.appendChild(s);
} else {
loadError = true;
const errorMsg = `抱歉!您的浏览器不支持本游戏,请更换浏览器或前往官网 <span class="font_small"><a href="${getHttp() + location.host}" target="_blank" class="link_color">下载${isMobile() ? 'APP' : '微端'}</a></span> 进行游戏!`,
label = document.getElementById('label');
if (label) {
label.innerHTML = errorMsg;
} else {
alert(filterHTML(errorMsg));
}
}
}
window.loadBarClear = function () {
window.clearInterval(setIntervalId);
}
})
</script>
<template>
<div id="logDiv" style="position:absolute; width: 100%; height: 100%;">
<table align="center" id="loadBox" style="color: #b4792e; font-size: 20px; padding-top: 25px;">
<tbody>
<tr>
<td align="center">
<img id="logoImg" align="center" width="380" :src="logoGameCat"/>
</td>
</tr>
<tr>
<td id="label" style="padding: 25px 35px 10px; text-align: center;">
首次加载时间较长请耐心等待如长时间无响应 <span class="font_small"><a onClick="window.location.reload()" class="link_color">请点此刷新</a> / <a onClick="window.history.back()" class="link_color">点击返回</a></span>
<br/>
加载完成送大量银两超值礼包白卡特权
</td>
</tr>
<tr>
<td align="center">
<div style="width: 200px; height: 6px; background-color: #ffffff;">
<div id="loadBar1" style="width: 30px; height: 6px; background-color: #8E44AD; float: left"></div>
</div>
</td>
</tr>
<tr>
<td align="center">
<div align="center" style="width: 200px; height: 6px; background-color: #ffffff;">
<div id="loadBar2" style="width: 70px; height: 6px; background-color: #ff7700; float: left"></div>
</div>
</td>
</tr>
</tbody>
</table>
<div id="logDiv" style="position:absolute; top: 31%; left: 50%;transform: translate(-50%,-50%)">
<div style="display: flex;flex-direction: column; align-items: center;gap: 4px;justify-content: center">
<img id="logoImg" align="center" width="380" src="/login/logo.png" class="mb-20"/>
<div id="label" style="color: #b4792e; font-size: 20px;">
首次加载时间较长请耐心等待如长时间无响应 <span class="font_small">
<a onClick="window.location.reload()" class="link_color">请点此刷新</a> /
<a onClick="window.history.back()" class="link_color">点击返回</a></span>
</div>
<div align="center" style="width: 200px; height: 6px; background-color: #ffffff;">
<div id="loadBar1" style="width: 30px; height: 6px; background-color: #8E44AD; float: left"></div>
</div>
<div align="center" style="width: 200px; height: 6px; background-color: #ffffff;">
<div id="loadBar2" style="width: 70px; height: 6px; background-color: #ff7700; float: left"></div>
</div>
</div>
</div>
</template>
<style scoped>
</style>
</style>

View File

@@ -3,7 +3,6 @@ import './style.css'
import App from './App.vue'
import router from './router/index.js'
// window.external?.OpenGameWindowNew(window.location.href, '', '', '', false);
document.onkeydown = document.onkeyup = document.onkeypress = function (e) {
if (e && e.keyCode == 123) {
e.returnValue = false;
@@ -12,10 +11,5 @@ document.onkeydown = document.onkeyup = document.onkeypress = function (e) {
}
const app = createApp(App)
fetch("/api/config", {method: "GET"}).then(res => res.json()).then(res => {
console.log(res)
app.config.globalProperties.$gameName = res.data.gameName || "神临苍月";
app.config.globalProperties.$_CONFIG = res.data;
app.use(router)
app.mount('#app')
})
app.use(router)
app.mount('#app')

View File

@@ -6,7 +6,8 @@ const routes = [
{
path: '/',
name: 'Home',
component: Index
component: Index,
redirect: '/login'
},
{
path: '/login', name: 'Login',

View File

@@ -0,0 +1,10 @@
import axios from "axios";
const ins = axios.create({
baseURL: '/',
timeout: 10000,
})
ins.interceptors.request.use(config => {
return config
})
export default ins

View File

@@ -1,5 +1,6 @@
<script setup>
import {defineOptions} from "vue";
import {defineOptions, onMounted, ref} from "vue";
import request from "@/utils/request";
defineOptions({name: 'Login'});
@@ -11,12 +12,20 @@ function handleLogin() {
}
async function getServers() {
const {data} = await request.get('/api/server/list')
servers.value = data.data
}
onMounted(() => {
getServers()
})
</script>
<template>
<div class="wrapper pagebg">
<div class="dialog account" id="account-login">
<h2 class="title">{{ $gameName }}</h2>
<h2 class="title">神临苍月</h2>
<input type="text" id="account" v-model="account" placeholder="请输入账号" @keyup="v=>account=v.replace(/[\W]/g, '')" autocomplete="off"/>
<input type="password" id="password" v-model="password" placeholder="请输入密码"/>
<select id="serverId" style="border: none; display: none; margin-bottom: 10px;">
@@ -24,8 +33,8 @@ function handleLogin() {
<option v-for="item in servers" :value="item.id">{{ item.name }}</option>
</select>
<div id="agree" class="agree">
<span><img data-v-427e1e01=""
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAVFBMVEWeICicICedISidISibIiadISeeISiOOTmdISeeISj///+aGSGcHiT68vOYFByhKjHRmZzqz9CzVlvx4OG4YWXJh4qpPUPkw8WkMjjdsrS/cXatR00P5JiiAAAACnRSTlPuTT//Gq+6A9iEOAr7KAAAAbBJREFUSMeVlte2gyAQRTHYcBBQiu3///OOMdZALvCUrHX2mYYAqao8Y2VN/l11ybK8qkiVv1hR04hVF+yVVwT1NaFRi9RIkIzVNHrVLCOsIPEAKRgpEwJgiJIk6ZEgUQlxAP5JKhLgnCYAHOg4ygQAwBnjEIsDAEDOSvUgooHRTHowkQCseqWbLh546wPA2e6r/4T6xp8SP/t9+M9vfQCQEtt9MnDqfSlxLpfe9OMVcLveB6x2StllG9D6n5/6dvqeg4BFaT3M46eQm76zywPgHAMMTaOVkQAf/6Hd9QpTvW8N4LJf+41ETwEbzJ296uVzewtwtnsLMDoVgi53PcADAGmmTdAO1gnxpb9H4HtCW0dmF/A/AOz4ocAyJqv8/geALbXdrm9a3Wm//xlh7Xl7EvvPp/+1hgWndCIB/+ukpTOXMgL+90nLxd6CePyvEfDjoc6orv3l//ge8Hjo7aB/+D8BgWnN2wD9/l+HAO65cU2rDfh7ANy1WHs3+P19x8y6sWdrzejz9wOCusWN1OcfOMg4B786CGC7QgRJv7KSL8Xkazf5Yk9+OiQ/TlKfP3/iYTk/HuYxLgAAAABJRU5ErkJggg=="></span>
<span><img
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAVFBMVEWeICicICedISidISibIiadISeeISiOOTmdISeeISj///+aGSGcHiT68vOYFByhKjHRmZzqz9CzVlvx4OG4YWXJh4qpPUPkw8WkMjjdsrS/cXatR00P5JiiAAAACnRSTlPuTT//Gq+6A9iEOAr7KAAAAbBJREFUSMeVlte2gyAQRTHYcBBQiu3///OOMdZALvCUrHX2mYYAqao8Y2VN/l11ybK8qkiVv1hR04hVF+yVVwT1NaFRi9RIkIzVNHrVLCOsIPEAKRgpEwJgiJIk6ZEgUQlxAP5JKhLgnCYAHOg4ygQAwBnjEIsDAEDOSvUgooHRTHowkQCseqWbLh546wPA2e6r/4T6xp8SP/t9+M9vfQCQEtt9MnDqfSlxLpfe9OMVcLveB6x2StllG9D6n5/6dvqeg4BFaT3M46eQm76zywPgHAMMTaOVkQAf/6Hd9QpTvW8N4LJf+41ETwEbzJ296uVzewtwtnsLMDoVgi53PcADAGmmTdAO1gnxpb9H4HtCW0dmF/A/AOz4ocAyJqv8/geALbXdrm9a3Wm//xlh7Xl7EvvPp/+1hgWndCIB/+ukpTOXMgL+90nLxd6CePyvEfDjoc6orv3l//ge8Hjo7aB/+D8BgWnN2wD9/l+HAO65cU2rDfh7ANy1WHs3+P19x8y6sWdrzejz9wOCusWN1OcfOMg4B786CGC7QgRJv7KSL8Xkazf5Yk9+OiQ/TlKfP3/iYTk/HuYxLgAAAABJRU5ErkJggg=="></span>
我已阅读并同意 <a href="javascript:void(0);" id="agree_btn">用户协议及隐私协议</a>
</div>
<a id="submitButton" class="button fit" @click="handleLogin"> </a>
@@ -46,7 +55,7 @@ function handleLogin() {
<style scoped>
.gamebg {
background-image: url("/img/login_bg.jpg");
background-image: url("/login/login_bg.jpg");
}
</style>