This commit is contained in:
Putoo
2026-05-19 18:34:24 +08:00
parent 24c784e6a4
commit 2c85872abd
10 changed files with 341 additions and 61 deletions

View File

@@ -115,6 +115,27 @@ a:focus {
box-shadow: inset 0 1px 1px rgb(0 0 0 / 8%);
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
}
.ipt-btn-gray-m {
width: 60px;
height: 20px;
border: none;
}
.chat {
word-wrap: break-word;
}
.chat img {
margin-right: 2px;
vertical-align: middle;
}
.em {
height: 30px;
width: 30px;
}
.badge {
max-height: 25px;
max-width: 25px;
}
.btn {
display: inline-block;

View File

@@ -1,12 +1,9 @@
<template>
<button class="but" @click="handleClick">{{ text }}</button>
<button class="but" @click="handleClick">
<slot></slot>
</button>
</template>
<script lang="ts" setup>
// 1. 定义接收父组件传来的参数 props
const props = defineProps({
// 字段名、类型、默认值
text: String,
})
const emit = defineEmits(['click'])
// 定义 click 事件
@@ -19,11 +16,16 @@ const handleClick = (): any => {
<style>
.but {
text-decoration: underline;
/* text-decoration: underline; */
color: #1e5494;
font-size: 18px;
cursor: pointer;
background: none;
border: none;
}
.but:hover{
color: #FFFFFF;
background: #1e5494;
}
</style>

View File

@@ -11,7 +11,9 @@ export type HandledRedirectError = {
export class ApiService {
private static initialized = false;
private static isRefreshing = false;
private static refreshWaiters: Array<(success: boolean) => void> = [];
private static get userStore() {
return useUserStore()
}
@@ -29,13 +31,57 @@ export class ApiService {
}
private static redirectToLogin() {
if (typeof localStorage !== "undefined") {
localStorage.removeItem("token");
localStorage.removeItem("userInfo");
this.userStore.offOnline();
window.location.href = "/";
}
private static waitForRefresh(): Promise<boolean> {
return new Promise(resolve => {
this.refreshWaiters.push(resolve);
});
}
private static notifyWaiters(success: boolean): void {
this.refreshWaiters.forEach(resolve => resolve(success));
this.refreshWaiters = [];
}
// 刷新 token支持并发排队多个请求同时 401 时只发一次刷新请求
private static async doRefreshToken(): Promise<boolean> {
if (this.isRefreshing) {
return this.waitForRefresh();
}
if (typeof window !== "undefined") {
PageExtend.Redirect("/home");
this.isRefreshing = true;
try {
const refToken = this.userStore.getRefToken;
const token = this.userStore.getToken
if (!refToken) {
this.notifyWaiters(false);
return false;
}
const result = await this.request.post<IResultData<{ token: string; refToken: string; userId: string }>>(
"/Login/RefreshToken",
{ refToken, token }
);
if (result.code === 0 && result.data) {
this.userStore.setToken(
result.data.userId,
result.data.token,
result.data.refToken ?? this.userStore.refToken
);
this.notifyWaiters(true);
return true;
}
this.notifyWaiters(false);
return false;
} catch {
this.notifyWaiters(false);
return false;
} finally {
this.isRefreshing = false;
}
}
@@ -66,30 +112,17 @@ export class ApiService {
return response;
}
const result = response.data;
if (result.code === 401) {
//401刷新token
console.log(result.data);
} else if (result.code === 40101) {
this.redirectToLogin();
if (result.code === 500) {
throw {
handled: true,
redirectTo: "/",//跳转回首页
message: result.msg || "登录已失效"
} satisfies HandledRedirectError;
} else if (result.code === 500) {
// 跳转错误页面
throw {
handled: true,
redirectTo: "/",//跳转回首页
message: result.msg || "登录已失效"
redirectTo: "/",
message: result.msg || "服务器内部错误"
} satisfies HandledRedirectError;
} else if (result.code === 404) {
// 跳转不存在页面
throw {
handled: true,
redirectTo: "/",//跳转回首页
message: result.msg || "登录已失效"
redirectTo: "/",
message: result.msg || "资源不存在"
} satisfies HandledRedirectError;
}
@@ -107,26 +140,57 @@ export class ApiService {
this.initialized = true;
}
private static executeRequest<T>(
method: HttpMethod,
url: string,
params: RequestParams
): Promise<IResultData<T>> {
switch (method) {
case "get":
return this.request.get<IResultData<T>>(url, { params });
case "post":
return this.request.post<IResultData<T>>(url, params);
case "put":
return this.request.put<IResultData<T>>(url, params);
case "delete":
return this.request.delete<IResultData<T>>(url, { params });
case "patch":
return this.request.patch<IResultData<T>>(url, params);
default:
throw new Error(`不支持的请求方法: ${method}`);
}
}
private static async requestWithRetry<T>(
method: HttpMethod,
url: string,
params: RequestParams,
isRetry: boolean
): Promise<IResultData<T>> {
const result = await this.executeRequest<T>(method, url, params);
if (result.code === 401 && !isRetry) {
const refreshed = await this.doRefreshToken();
if (refreshed) {
return this.requestWithRetry<T>(method, url, params, true);
}
this.redirectToLogin();
throw {
handled: true,
redirectTo: "/",
message: "登录已失效,请重新登录"
} satisfies HandledRedirectError;
}
return result;
}
public static async Request<T = any>(
method: HttpMethod,
url: string,
params: RequestParams = {}
): Promise<IResultData<T>> {
this.ensureInitialized();
switch (method) {
case "get":
return await this.request.get<IResultData<T>>(url, { params });
case "post":
return await this.request.post<IResultData<T>>(url, params);
case "put":
return await this.request.put<IResultData<T>>(url, params);
case "delete":
return await this.request.delete<IResultData<T>>(url, { params });
case "patch":
return await this.request.patch<IResultData<T>>(url, params);
default:
throw new Error(`不支持的请求方法: ${method}`);
}
return this.requestWithRetry<T>(method, url, params, false);
}
}

View File

@@ -1,12 +1,33 @@
<template>
<slot />
<div class="main">
<slot />
</div>
<div class="content ">
<button class="btn btn-ret" @click="GoBack">返回</button><br />
<Abar href="/map">返回游戏</Abar>
</div>
<div class="clear"></div>
<div class="foot">
<div class="common">
<Abar href="/">首页</Abar>-
<Abar href="/">挂机</Abar>-
<a target="_blank" href="https://work.weixin.qq.com/kfid/kfc86bc348120aea3e7">反馈</a>
</div>
<div class="timeService">
小G报时({{ TimeExtend.Now("HH:mm") }})
</div>
<p style="font-weight:bold;font-size:14px">官方QQ群931835791</p>
</div>
</template>
<script setup lang="ts">
const router = useRouter()
//返回
const GoBack = (): void => {
router.back();
}
</script>
<style scoped>
</style>
<style scoped></style>

View File

@@ -6,8 +6,8 @@
</div>
<div class="content">
<div class="item" v-for="(item, index) in userData" :key="index">
<a @click="loginGame(item.userId)">{{ item.areaId }}{{ item.nick }}({{ (item.sex == null || item.sex == '') ?
"未知" : item.sex }})</a>
<Abutton @click="loginGame(item.userId)" >{{ item.areaId }}{{ item.nick }}({{ (item.sex == null || item.sex == '') ?
"未知" : item.sex }})</Abutton>
</div>
<span v-if="userData.length == 0">暂无角色.</span>
</div>

View File

@@ -18,7 +18,7 @@
</div>
<div>
<a :href='"https://3g.fan/Regain/ToBbs?openid=" + AccountInfo.openId + "&bbs=1146"'>游戏论坛</a>&nbsp;&nbsp;
<a @click.stop="offOnline">退出游戏</a>
<Abutton @click="offOnline">退出游戏</Abutton>
</div>
</div>
</div>
@@ -31,8 +31,8 @@
</div>
<div class="content">
<div class="item" v-for="(item, index) in userData" :key="index">
<a @click="loginGame(item.userId)">{{ item.areaId }}{{ item.nick }}({{ (item.sex == null || item.sex == '') ?
"未知" : item.sex }})</a>
<Abutton @click="loginGame(item.userId)" >{{ item.areaId }}{{ item.nick }}({{ (item.sex == null || item.sex == '') ?
"未知" : item.sex }})</Abutton>
</div>
</div>
</div>
@@ -42,7 +42,7 @@
</div>
<div class="content">
<div class="item" v-for="(item, index) in areaData" :key="index">
<a @click="registerGame(item.areaId)">({{ item.areaId }}){{ item.name }}</a>
<Abutton @click="registerGame(item.areaId)">({{ item.areaId }}){{ item.name }}</Abutton>
{{ item.status == 1 ? "(推荐)" : "(繁忙)" }}
</div>
<span v-if="areaData.length == 0">暂无区服.</span>
@@ -164,7 +164,7 @@ const loginGame = async (gameId: string): Promise<void> => {
}
}
else {
MessageExtend.ShowDialog("注册角色", result.msg);
MessageExtend.ShowDialog("登录游戏", result.msg);
}
};

View File

@@ -1 +1,124 @@
<template></template>
<template>
<div class="item" style="font-size:15px;">
<span>[在线奖励]:3分钟后可领取.</span>
</div>
<div class="content">
威尼斯&#xB7;广场
<Abutton @click="Refresh">刷新</Abutton>
<a class="a-nomargin"
href="/Task/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">任务</a><a class=""
href="/Message/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">消息</a>
</div>
<div class="content" style="font-size:14px;font-weight:bold;color:dodgerblue">
</div>
<div class="notification">
<div class="chat">
<div class="item">
[公共]
<span><img src='http://gree.pccsh.com/res/badge/9001.gif' class='user-head' alt='头像' /><a class='a-nodec'
href='/User/Index/Home/250822?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo'><span
class='Nick-Gold'>   航海百曉生    </span></a><img src='http://gree.pccsh.com/res/badge/9001.gif' alt='vip'
class='vip' />&nbsp;<span class='icon-cry'><i class='icon-crystal liangHao'></i></span><img
src='http://gree.pccsh.com/res/badge/9001.gif' alt='心愿星河' class='badge' />&nbsp;</span>
:
<span class=''>出身上天魔套15复仇8白副手10块白四象5块4级审判20一套5级房子材料20</span>
</div>
<div class="item">
[公共]
<span><a class='a-nodec'
href='/User/Index/Home/14637160?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo'>长剑小苍穹失散多年的亲爹</a></span>
:
<span class=''><img src='http://r.kexunkeji.cn/em/unit/1.png' alt='嘟嘴' class='em' /></span>
</div>
</div>
</div>
<div class="content">
您看到:
<a class="" href="/Map/Index/MapUser/16_27?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">
</a>
</div>
<div class="content">
<div class="item"><a href='/Task/Npc/Index?npc=1101006&sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo'>巴萨尼奥</a></div>
<div class="item"><a href='/Map/Npc/Index?npc=1101016&sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo'>海精灵(福利)</a></div>
<div class="item"><a href='/Map/Npc/Index?npc=1101061&sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo'>战场指挥官(攻城)</a></div>
<div class="item"><a href='/Map/Npc/Index?npc=1101064&sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo'>欧若拉(主神)</a></div>
</div>
<div class="content">
</div>
<div class="content">
</div>
<div class="content">请选择出口:<br /><a
href='/Map/17_27?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo&_r=YA9mrXPxwRpy'>市场</a>&nbsp;&nbsp;西<a
href='/Map/15_27?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo&_r=YA9mrXPxwRpy'>教堂</a>&nbsp;&nbsp;<br /><a
href='/Map/16_28?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo&_r=YA9mrXPxwRpy'>银行</a>&nbsp;&nbsp;<a
href='/Map/16_26?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo&_r=YA9mrXPxwRpy'>珠宝店</a>&nbsp;&nbsp;</div>
<div class="content">
<a class="" href="/Map/Index/MapCity/16_27?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">城内地图</a>.<a class=""
href="/Business/Help/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">帮助</a>.<a class=""
href="/Privilege/Purdiam/MapTo?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">传送</a><br />
<div style="font-size:16px;line-height:16px;">
当前坐标X-16&nbsp;&nbsp;Y-27
</div>
</div>
<div class="content">
每逢节假日来找海精灵有惊喜
</div>
<div class="content">
<div class="common">
<a class="" href="/User/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">状态</a>. <a class=""
href="/Bag/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">物品</a>. <a class=""
href="/Chat/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">聊天</a>
</div>
<div class="common">
<a class="" href="/Friend/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">好友</a>. <a class=""
href="/Pet/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">宠物</a>. <a class=""
href="/Team/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">队伍</a>
</div>
<div class="common">
<a class="" href="/Mall/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">商城</a>. <a class=""
href="/Bus/Proffer/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">神殿</a>. <a class=""
href="/Business/Market/Act?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">活动</a>
</div>
<div class="common">
<a class="" href="/Act/Kill/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">天榜</a>. <a class=""
href="/Act/Sky/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">赛事</a>. <a class=""
href="/Rank/Index/Index?sid=W6Wg8iH9gY7wIBNSEdtFcQ3KbI5YiKDo">排行</a>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: layout.default,
middleware: 'page-loading'
})
onMounted(async () => {
try {
await BindData();
}
finally {
PageLoading.Close();
}
})
/**加载方法 */
const BindData = async (): Promise<void> => {
};
/**刷新 */
const Refresh = async (): Promise<void> => {
MessageExtend.LoadingToast("刷新中...");
await BindData();
window.setTimeout(() => {
MessageExtend.LoadingClose();
MessageExtend.Notify("刷新成功!", "success");
}, 1000)
}
</script>