feat(server): 集成静态文件服务并优化开发环境配置

- 添加 koa-static 中间件以支持静态资源访问
- 更新 package.json 引入 nodemon 用于服务热重载- 调整项目结构,将前端代码移至 module/web 目录下
- 修改图片引用路径适配新目录结构
- 更新主入口文件引入路由模块的方式- 配置 Vite 构建工具替换原有构建方式- 移除 Turbo依赖并使用 concurrently 管理多进程开发任务- 更新根目录 package.json 的脚本命令指向新的开发启动逻辑
This commit is contained in:
2025-10-19 22:40:56 +08:00
parent 5f4858e991
commit 57aff59b45
20 changed files with 43 additions and 56 deletions

108
module/web/src/App.vue Normal file
View File

@@ -0,0 +1,108 @@
<script setup>
import {RouterView} from 'vue-router'
import Loading from "./components/loading.vue";
import {onMounted, reactive} from "vue";
const mainDiv = reactive({
dataOrientation: "auto",
dataScaleMode: "noScale",
dataFrameRate: 60,
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";
mainDiv.dataFrameRate = 30;
mainDiv.dataContentWidth = 1334;
mainDiv.dataContentHeight = 750;
}
})
</script>
<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">
<RouterView/>
<!-- <loading/> -->
</div>
</template>
<style scoped>
#mainDiv {
height: 100%;
#logDiv {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -0,0 +1,96 @@
@use "layout";
// 1. 变量(可改)
$colors: (
"slate": #64748b,
"sky": #0ea5e9,
"emerald":#10b981,
"rose": #f43f5e
);
$border-color: #DCDFE6;
//$spacer: 0.25rem; // 1 单位 = 4px
$spacer: 1px; // 1 单位 = 1px
// 2. 工具函数
@function size($n) { @return $n * $spacer; }
// 3. 生成 margin / padding
@each $k, $v in $colors {
.bg-#{$k} { background-color: $v; }
.text-#{$k}{ color: $v; }
}
@for $i from 0 through 40 {
.m-#{$i} { margin: size($i); }
.mt-#{$i} { margin-top: size($i); }
.mr-#{$i} { margin-right: size($i); }
.mb-#{$i} { margin-bottom: size($i); }
.ml-#{$i} { margin-left: size($i); }
.mx-#{$i} { margin-left: size($i); margin-right: size($i); }
.my-#{$i} { margin-top: size($i); margin-bottom: size($i); }
.p-#{$i} { padding: size($i); }
.pt-#{$i} { padding-top: size($i); }
.pr-#{$i} { padding-right: size($i); }
.pb-#{$i} { padding-bottom: size($i); }
.pl-#{$i} { padding-left: size($i); }
.px-#{$i} { padding-left: size($i); padding-right: size($i); }
.py-#{$i} { padding-top: size($i); padding-bottom: size($i); }
.font-#{$i} { font-size: size($i); }
.round-#{$i} { border-radius: size($i); overflow: hidden}
.lh-#{$i} { line-height: size($i)!important; }
}
.rounded { border-radius: 0.25rem; }
.shadow { box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.shadow-l { box-shadow: -1px 0 3px rgba(0,0,0,0.2); }
.shadow-r { box-shadow: 1px 0 3px rgba(0,0,0,0.2); }
.shadow-b { box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
.shadow-t { box-shadow: 0 -1px 3px rgba(0,0,0,0.2); }
.border { border: 1px solid #{$border-color}; }
$directions: (
"t": top,
"r": right,
"b": bottom,
"l": left
);
@each $d, $v in $directions {
.border-#{$d} { border-#{$v}: 1px solid #{$border-color}; }
}
@each $v in (60,80,100,120,140,160,180,200,220,240,260,280,300,320,340,360,380) {
.w-#{$v}{width: size($v)!important;}
.h-#{$v}{height: size($v)!important;}
}
.w100{width: 100%!important;}
.h100{height: 100%!important;}
.w50{width: 50%!important;}
.h50{height: 50%!important;}
.w100v{width: 100vw!important;}
.h100v{height: 100vh!important;}
.w50v{width: 50vw!important;}
.h50v{height: 50vh!important;}
/* Chrome, Edge, Safari, Opera */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
.el-form-item__label,.el-descriptions-item__label{
word-break: break-word;
}
.el-table__header-wrapper{
.cell{
word-break: break-word;
}
}

View File

@@ -0,0 +1,155 @@
.grid {
display: grid;
$cols: 2, 3, 4;
@each $size in $cols {
&.col-#{$size} {
grid-template-columns: repeat(#{$size}, 1fr);
}
}
}
.flex {
display: flex;
align-items: center;
&.center {
justify-content: center;
}
&.between {
justify-content: space-between;
}
&.around {
justify-content: space-around;
}
&.column {
flex-direction: column;
.fill {
.el-scrollbar__wrap {
overflow-x: hidden;
}
}
}
&.wrap {
flex-wrap: wrap;
}
&.baseline {
align-items: baseline;
}
&.start {
align-items: start;
}
.fill {
flex: 1;
min-width: 0;
min-height: 0;
}
$gaps: 4, 8, 12, 16, 20, 24, 28, 32, 36, 40;
@each $size in $gaps {
&.gap-#{$size} {
gap: #{$size}px;
}
}
&.el-form {
flex-wrap: wrap;
$cols: 2, 3, 4;
@each $size in $cols {
&.col-#{$size} {
.el-form-item {
width: calc(100% / #{$size});
}
}
}
.el-form-item {
&.row {
width: 100%;
}
.el-date-editor, .el-select,.el-input-number {
width: 100% !important;
}
}
}
}
.shrink {
flex-shrink: 0;
}
.cameraGrid {
&.one {
display: flex;
justify-content: center;
align-items: center;
.video {
width: 100%;
height: 100%;
}
}
&.four {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.video {
width: 49%;
height: 49%;
}
}
&.nine {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.video {
width: calc(100% / 3);
height: calc(100% / 3);
padding: 10px;
text-align: center;
}
}
&.thirteen {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, calc(95% / 4));
grid-gap: 0.67708vw;
place-content: space-around;
.main {
grid-column-start: 2;
grid-column-end: 4;
grid-row-start: 2;
grid-row-end: 4;
position: relative;
}
}
&.eight {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, calc(95% / 4));
grid-gap: 0.67708vw;
place-content: space-around;
.main {
grid-column-start: 1;
grid-column-end: 4;
grid-row-start: 1;
grid-row-end: 4;
}
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@@ -0,0 +1,42 @@
<script setup>
import logoGameCat from "/resource_Publish/assets/login/logoGameCat.png"
</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>
</template>
<style scoped>
</style>

5
module/web/src/config.js Normal file
View File

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

21
module/web/src/main.js Normal file
View File

@@ -0,0 +1,21 @@
import {createApp} from 'vue'
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;
return false;
}
}
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')
})

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

99
module/web/src/style.css Normal file
View File

@@ -0,0 +1,99 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
html, body, #app {
-ms-touch-action: none;
background: #000000;
cursor: default;
padding: 0;
border: 0;
margin: 0;
height: 100%;
}
a {
cursor: pointer;
text-decoration: underline;
}
.link_color {
color: yellow;
}
.font_small, .font_small a {
font-size: 14px;
}
#label {
font-size: 18px;
}
@media screen and (max-height: 480px) {
#label {
font-size: 14px;
}
}
@media screen and (max-width: 480px) {
#label {
font-size: 14px;
}
}

View File

@@ -0,0 +1,21 @@
/**
* 判断当前设备是否是手机
* @returns {boolean} 如果是手机返回true否则返回false
*/
export function isMobile() {
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
// 检查常见的移动设备标识
const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
return mobileRegex.test(userAgent);
}
/**
* 判断当前设备是否是手机的另一种实现方式
* 通过屏幕宽度来判断
* @returns {boolean} 如果是手机返回true否则返回false
*/
export function isMobileByScreen() {
return window.innerWidth <= 768;
}

View File

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

View File

@@ -0,0 +1,52 @@
<script setup>
import {defineOptions} from "vue";
defineOptions({name: 'Login'});
const servers = ref([])
const account = ref('')
const password = ref('')
function handleLogin() {
}
</script>
<template>
<div class="wrapper pagebg">
<div class="dialog account" id="account-login">
<h2 class="title">{{ $gameName }}</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;">
<option value="0">请选择区服</option>
<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>
我已阅读并同意 <a href="javascript:void(0);" id="agree_btn">用户协议及隐私协议</a>
</div>
<a id="submitButton" class="button fit" @click="handleLogin"> </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="/img/linuxdo_logo.png" style="width:60px;height:60px" alt="Linux.Do登录"/>
<div>Linux.do</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"/>
</div>
</template>
<style scoped>
.gamebg {
background-image: url("/img/login_bg.jpg");
}
</style>