feat(web):重构前端路由和配置管理

- 移除旧版全局变量定义,采用Vue Router管理页面跳转
- 新增配置文件统一管理游戏名称和注册码设置
-优化加载条逻辑,使用onMounted确保DOM元素正确获取
- 添加服务端配置接口,动态加载游戏配置信息
- 升级依赖包,引入vue-router支持单页应用
- 调整项目结构,分离服务器端代码至独立目录
- 配置Vite代理转发API请求到本地开发服务器
- 更新package.json脚本命令,支持前后端联合调试
- 引入pnpm workspace管理模式,提升多包协作效率
This commit is contained in:
kubbo
2025-09-30 18:39:51 +08:00
parent 39f7598b02
commit 5d20a7def3
14 changed files with 249 additions and 100 deletions

View File

@@ -5,14 +5,22 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"dev:server": "pnpm --filter chuanqi-server dev",
"start:server": "pnpm --filter chuanqi-server start",
"build": "vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"vue": "^3.5.21" "vue": "^3.5.21",
"vue-router": "^4.5.1"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.1.7" "vite": "^7.1.7"
},
"pnpm": {
"workspace": [
"server"
]
} }
} }

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
packages:
- server

4
public/config.json Normal file
View File

@@ -0,0 +1,4 @@
{
"reg_code_open": true,
"code_type": ""
}

View File

@@ -1,17 +1,7 @@
/**
* 冰雪传奇H5
* 2022 XX信息科技有限公司
*
* @author 123456
* @wx 123456
* @qq 123456
*/
let host = window.location.hostname || '100.88.157.105'; let host = window.location.hostname || '100.88.157.105';
let port = 80; let port = 80;
const webUrl = getHttp() + host + (port && 80 != port ? ':' + port : ''), const account = getQueryString('account'),
account = getQueryString('account'),
token = getQueryString('token'), token = getQueryString('token'),
noLogin = !account || !token; noLogin = !account || !token;
@@ -20,7 +10,7 @@ if (noLogin) {
loadBarClear(); loadBarClear();
loadBarFull(); loadBarFull();
setTimeout(function () { setTimeout(function () {
window.location.href = webUrl + '/login'; window.location.href = '/login';
}, randomRange(250, 500)); }, randomRange(250, 500));
}, 1e3); }, 1e3);
} }
@@ -79,19 +69,18 @@ window['loginView'] = 'app.MainLoginView';
// 相关URL // 相关URL
window['webHost'] = host; window['webHost'] = host;
window['webUrl'] = webUrl; window['serviceListdUrl'] = '/server';
window['serviceListdUrl'] = webUrl + '/server'; window['setServiceListdUrl'] = '/server';
window['setServiceListdUrl'] = webUrl + '/server'; window['payUrl'] = '/pay';
window['payUrl'] = webUrl + '/pay'; window['apiUrl'] = '/api';
window['apiUrl'] = webUrl + '/api'; window['orderUrl'] = '/api?act=order';
window['orderUrl'] = webUrl + '/api?act=order'; window['reportUrl'] = '/api?act=report'; // 上报接口
window['reportUrl'] = webUrl + '/api?act=report'; // 上报接口 window['errorReportUrl'] = '/api?act=report&do=error';// 错误上报接口
window['errorReportUrl'] = webUrl + '/api?act=report&do=error';// 错误上报接口 window['checkUrl'] = '/api?act=check'; // 验证URL
window['checkUrl'] = webUrl + '/api?act=check'; // 验证URL window['versionUrl'] = '/api?act=version'; // 请求客户端版本
window['versionUrl'] = webUrl + '/api?act=version'; // 请求客户端版本 window['getActorInfoUrl'] = '/api?act=actor';
window['getActorInfoUrl'] = webUrl + '/api?act=actor'; window['roleInfoUrl'] = '/api?act=role';
window['roleInfoUrl'] = webUrl + '/api?act=role'; window['gongGaoUrl'] = '/notice.txt';
window['gongGaoUrl'] = webUrl + '/notice.txt';
// 客服信息 // 客服信息
window['kfQQ'] = '123456'; window['kfQQ'] = '123456';
@@ -341,7 +330,7 @@ function feedbackFunction(info) {
param += key + '=' + msgInfo[key] + '&' param += key + '=' + msgInfo[key] + '&'
} }
param = param.substring(0, param.length - 1) param = param.substring(0, param.length - 1)
let srcStr = webUrl + '/api?' + param; let srcStr = '/api?' + param;
const div = document.createElement('div'); const div = document.createElement('div');
div.id = 'iframDiv'; div.id = 'iframDiv';
div.innerHTML = '<iframe id="main" scrolling="no" noresize="true" frameborder="0" style="width: 90%;height: 90%;padding-left:5%;padding-top:2%;" src=' + srcStr + '></iframe>'; div.innerHTML = '<iframe id="main" scrolling="no" noresize="true" frameborder="0" style="width: 90%;height: 90%;padding-left:5%;padding-top:2%;" src=' + srcStr + '></iframe>';
@@ -369,7 +358,7 @@ function feedbackFunction(info) {
// 防沉迷 // 防沉迷
function IdCardFunction() { function IdCardFunction() {
window.open(webUrl); window.open();
} }
function addQQGrp() { function addQQGrp() {
@@ -378,17 +367,17 @@ function addQQGrp() {
//下载YY游戏大厅 //下载YY游戏大厅
function downYYGameHallFun() { function downYYGameHallFun() {
window.open(webUrl); window.open();
} }
//开通会员 //开通会员
function openYYVip() { function openYYVip() {
window.open(webUrl); window.open();
} }
//开超玩会员 //开超玩会员
function openChaoWanVip() { function openChaoWanVip() {
window.open(webUrl); window.open();
} }
function removeIfram() { function removeIfram() {

0
server/config/index.js Normal file
View File

27
server/index.js Normal file
View File

@@ -0,0 +1,27 @@
import Koa from 'koa';
import Router from 'koa-router';
import config from "./config/index.js"
const app = new Koa();
const router = new Router();
// 简单的路由示例
router.get('/', (ctx) => {
ctx.body = {message: 'Hello from Koa server!'};
});
router.get('/api/test', (ctx) => {
ctx.body = {message: 'This is a test API endpoint'};
});
router.get('/api/config', (ctx) => {
ctx.body = {data: config}
})
app.use(router.routes());
app.use(router.allowedMethods());
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Koa server is running on port ${PORT}`);
});

15
server/package.json Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "chuanqi-server",
"version": "1.0.0",
"description": "A simple Koa server for chuanqi web",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "node index.js"
},
"dependencies": {
"koa": "^2.15.0",
"koa-router": "^12.0.0"
}
}

View File

@@ -1,6 +1,7 @@
<script setup> <script setup>
import {RouterView} from 'vue-router'
import Loading from "./components/loading.vue"; import Loading from "./components/loading.vue";
import {reactive} from "vue"; import {onMounted, reactive} from "vue";
const mainDiv = reactive({ const mainDiv = reactive({
dataOrientation: "auto", dataOrientation: "auto",
@@ -13,83 +14,84 @@ let loadBar1Width = 0, loadBar2Width = 0, setIntervalId = 0;
const loadBar2MaxWidth = 200; const loadBar2MaxWidth = 200;
let loadError = false; let loadError = false;
const loadBox = document.getElementById('loadBox'), onMounted(() => {
logoImg = document.getElementById('logoImg'), const loadBox = document.getElementById('loadBox'),
loadBar1 = document.getElementById('loadBar1'), logoImg = document.getElementById('logoImg'),
loadBar2 = document.getElementById('loadBar2'); loadBar1 = document.getElementById('loadBar1'),
loadBar2 = document.getElementById('loadBar2');
function updateLoadBar() { function updateLoadBar() {
if (loadError) { if (loadError) {
return; return;
} }
const screenWidth = document.documentElement.scrollWidth || document.body.scrollWidth, const screenWidth = document.documentElement.scrollWidth || document.body.scrollWidth,
screenHeight = document.documentElement.scrollHeight || document.body.scrollHeight, screenHeight = document.documentElement.scrollHeight || document.body.scrollHeight,
smallScreen = screenWidth <= 0, smallScreen = screenWidth <= 0,
isHorizontal = isMobile() && screenHeight <= 590; isHorizontal = isMobile() && screenHeight <= 590;
if (loadBox) loadBox.style.paddingTop = (!isHorizontal ? (screenHeight / 4) : 50) + 'px'; if (loadBox) loadBox.style.paddingTop = (!isHorizontal ? (screenHeight / 4) : 50) + 'px';
if (logoImg) logoImg.width = isHorizontal || smallScreen ? 300 : 500; if (logoImg) logoImg.width = isHorizontal || smallScreen ? 300 : 500;
if (loadBar1) { if (loadBar1) {
loadBar1Width += 20; loadBar1Width += 20;
if (loadBar1Width > 100) loadBar1Width = 0; if (loadBar1Width > 100) loadBar1Width = 0;
loadBar1.style.width = loadBar1Width + '%'; loadBar1.style.width = loadBar1Width + '%';
} }
if (loadBar2) { if (loadBar2) {
loadBar2Width += 3; loadBar2Width += 3;
if ((loadBar2Width / loadBar2MaxWidth * 100) > 100) { if ((loadBar2Width / loadBar2MaxWidth * 100) > 100) {
loadBarFull(); loadBarFull();
} else { } else {
loadBar2.style.width = loadBar2Width + 'px'; loadBar2.style.width = loadBar2Width + 'px';
}
} }
} }
}
function startLoadBar() { function startLoadBar() {
setIntervalId = self.setInterval(updateLoadBar, 100); setIntervalId = self.setInterval(updateLoadBar, 100);
} }
window.loadBarFull = function () { window.loadBarFull = function () {
loadBar2.style.width = loadBar2MaxWidth + 'px'; if (loadBar2) loadBar2.style.width = loadBar2MaxWidth + 'px';
} }
window.onload = function () { window.onload = function () {
startLoadBar(); startLoadBar();
if ((typeof (Worker) !== 'undefined')) { if ((typeof (Worker) !== 'undefined')) {
const s = document.createElement('script'); const s = document.createElement('script');
s.type = 'text/javascript'; s.type = 'text/javascript';
s.async = false; s.async = false;
s.addEventListener('load', function (e) { s.addEventListener('load', function (e) {
s.parentNode.removeChild(s); s.parentNode.removeChild(s);
s.removeEventListener('load', e, false); s.removeEventListener('load', e, false);
}, false); }, false);
s.src = 'js/index.js?v=' + Math.random(); s.src = 'js/index.js?v=' + Math.random();
document.body.appendChild(s); 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 { } else {
alert(filterHTML(errorMsg)); 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.loadBarClear = function () { window.clearInterval(setIntervalId);
window.clearInterval(setIntervalId); }
} if (isMobile()) {
if (isMobile()) { mainDiv.dataOrientation = "landscape";
mainDiv.dataOrientation = "landscape"; mainDiv.dataScaleMode = "fixedHeight";
mainDiv.dataScaleMode = "fixedHeight"; mainDiv.dataFrameRate = 30;
mainDiv.dataFrameRate = 30; mainDiv.dataContentWidth = 1334;
mainDiv.dataContentWidth = 1334; mainDiv.dataContentHeight = 750;
mainDiv.dataContentHeight = 750; }
} })
</script> </script>
<template> <template>
<div id="mainDiv" v-bind="mainDiv" data-multi-fingered="2" data-show-fps="false" data-show-log="false" data-show-fps-style="x:0,y:0,size:12,textColor:0xffffff,bgAlpha:0.9"> <div id="mainDiv" v-bind="mainDiv" data-multi-fingered="2" data-show-fps="false" data-show-log="false" data-show-fps-style="x:0,y:0,size:12,textColor:0xffffff,bgAlpha:0.9">
<loading/> <RouterView/>
<!-- <loading/> -->
</div> </div>
</template> </template>

5
src/config.js Normal file
View File

@@ -0,0 +1,5 @@
export default {
gameName: "神临苍月",
reg_code_open: true,
code_type: "email",
}

View File

@@ -1,6 +1,7 @@
import {createApp} from 'vue' import {createApp} from 'vue'
import './style.css' import './style.css'
import App from './App.vue' import App from './App.vue'
import router from './router'
// window.external?.OpenGameWindowNew(window.location.href, '', '', '', false); // window.external?.OpenGameWindowNew(window.location.href, '', '', '', false);
document.onkeydown = document.onkeyup = document.onkeypress = function (e) { document.onkeydown = document.onkeyup = document.onkeypress = function (e) {
@@ -9,4 +10,10 @@ document.onkeydown = document.onkeyup = document.onkeypress = function (e) {
return false; return false;
} }
} }
createApp(App).mount('#app') const app = createApp(App)
fetch("/api/config", {method: "GET"}).then(res => res.json()).then(res => {
app.config.globalProperties.$gameName = res.data.gameName || "神临苍月";
app.config.globalProperties.$_CONFIG = res.data;
app.use(router).mount('#app')
})

22
src/router/index.js Normal file
View File

@@ -0,0 +1,22 @@
import {createRouter, createWebHistory} from 'vue-router'
import Index from "../views/index.vue";
// 示例路由配置
const routes = [
{
path: '/',
name: 'Home',
component: Index
},
{
path: '/login', name: 'Login',
component: () => import('../views/login.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

10
src/views/index.vue Normal file
View File

@@ -0,0 +1,10 @@
<script setup>
import Loading from "../components/loading.vue";
</script>
<template>
<loading/>
</template>
<style scoped>
</style>

49
src/views/login.vue Normal file
View File

@@ -0,0 +1,49 @@
<script setup>
import {reactive} from "vue";
const servers = ref([])
</script>
<template>
<div class="wrapper pagebg">
<div class="dialog account" id="account-login">
<h2 class="title">{{$gameName}}</h2>
<input type="text" id="account" value="<?=isset($_COOKIE['account']) ? $_COOKIE['account'] : ''?>" placeholder="请输入<?=$_CONFIG['account_name']?><?=$_CONFIG['account_name_suffix']?>" onKeyUp="value = value.replace(/[\W]/g, '')" autocomplete="off" disableautocomplete>
<input type="password" id="password" value="<?=isset($_COOKIE['password']) ? $_COOKIE['password'] : ''?>" placeholder="请输入<?=$_CONFIG['account_name']?><?=$_CONFIG['password_name_suffix']?>">
<input type="password" id="password2" placeholder="请再次输入<?=$_CONFIG['account_name']?><?=$_CONFIG['password_name_suffix']?>" style="display: none;">
<select id="serverId" style="border: none; display: none; margin-bottom: 10px;">
<option value="0">请选择区服</option>
<option v-for="item in servers" :value="item.id">{{ item.name}}</option>
</select>
<input type="text" id="email" placeholder="<?php if($_CONFIG['reg_code_open']): ?>请输入<?php if('email' == $_CONFIG['code_type']): ?>邮箱地址<?php else: ?>手机号<?php endif; ?><?php else: ?><?php if('email' == $_CONFIG['code_type']): ?>邮箱地址<?php else: ?>手机号<?php endif; ?>(选填)<?php endif; ?>,用于找回密码" style="display: none;" title="用于找回密码">
<div class="code" id="codeBox" style="display: none;">
<input type="text" id="code" placeholder="请输入验证码">
<a href="javascript:void(0);" id="getCode">获取验证码</a>
</div>
<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>
我已阅读并同意 <a href="javascript:void(0);" id="agree_btn">用户协议及隐私协议<a/>
</div>
<a href="javascript:process_login();" id="submitButton" class="button fit"> </a>
<div style="display:flex;justify-content:center;gap:8px;font-size:12px" >
<div style="display:flex;align-items:center;flex-direction:column;gap:4px;cursor:pointer" id="linuxdoConnect">
<img src="static/img/linuxdo_logo.png" style="width:60px;height:60px" alt="Linux.Do登录"/>
<div>Linux.do</div>
</div>
<div style="display:flex;align-items:center;flex-direction:column;gap:4px;cursor:pointer" id="naixiConnect">
<img src="https://forum.naixi.net/favicon.ico" style="width:60px;height:60px" alt="奶昔登录"/>
<div>奶昔登录</div>
</div>
</div>
<div class="forget_password">
<a href="javascript:void(0);" id="forgetPassword" data-type="2">忘记密码?</a>
<a href="javascript:void(0);" class="pull-right" id="switchBtn" data-type="1">注册</a>
</div>
</div>
<div id="bg" class="gamebg" style="background-image: url(static/img/login_bg.jpg);"></div>
</div>
</template>
<style scoped>
</style>

View File

@@ -4,4 +4,13 @@ import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
server: {
proxy: {
'/api': {
target: 'http://localhost:3001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}) })