🎈 perf: 完善五子棋主框架

This commit is contained in:
Litrix2 2024-12-13 23:02:39 +08:00
parent 24ff08605b
commit b9b1063d5b
7 changed files with 86 additions and 26 deletions

2
.gitignore vendored
View File

@ -31,3 +31,5 @@ coverage
*.lock
*.lockb
components.d.ts

5
components.d.ts vendored
View File

@ -7,9 +7,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
'菜单': typeof import('./src/assets/icons/菜单.svg')['default']
AppHeaderMenu: typeof import('./src/components/app/AppHeaderMenu.vue')['default']
AppHeaderUser: typeof import('./src/components/app/AppHeaderUser.vue')['default']
'Components.d': typeof import('./components.d.ts')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElContainer: typeof import('element-plus/es')['ElContainer']
@ -37,7 +35,6 @@ declare module 'vue' {
IconCsValidate: typeof import('~icons/cs/validate')['default']
IconEpLoading: typeof import('~icons/ep/loading')['default']
IconEpUserFilled: typeof import('~icons/ep/user-filled')['default']
Menu: typeof import('./src/assets/icons/Menu.svg')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

View File

@ -9,7 +9,7 @@
<h3 v-show="!smallMobileScreen" class="app__title" @click="navigate">社团展示系统</h3>
</router-link>
<app-header-menu v-show="!mobileScreen" mode="horizontal" />
<el-dropdown v-if="!mobileScreen">
<el-dropdown v-if="!mobileScreen && userStore.logined">
<app-header-user
@login="showLoginRegisterDialog = true"
@logout="logout"
@ -51,6 +51,7 @@
@login="showLoginRegisterDialog = true"
@click="mobileScreen && (showVerticalHeaderMenu = true)"
/>
<div v-else class="app-vertical-drawer__not-login-title flex center">尚未登录</div>
<el-button
v-if="userStore.logined"
class="app-vertical-drawer__logout"
@ -71,9 +72,12 @@
display: flex;
flex-direction: column;
}
.app-header-user {
> :first-child:not(.app-header-menu) {
margin-top: 10px;
}
&__not-login-title {
font-weight: bold;
}
&__logout {
margin: 0 10px;
}
@ -110,6 +114,7 @@
<script setup lang="ts">
import axiosInstance, { type RawResp } from '@/api/index';
import AppHeaderMenu from '@/components/app/AppHeaderMenu.vue';
import AppHeaderUser from '@/components/app/AppHeaderUser.vue';
import LoginRegisterDialog, {
loginImplKey,
registerImplKey,
@ -162,13 +167,10 @@ async function logout() {
await userStore.updateSelfUserInfo(true);
}
const showVerticalHeaderMenu = ref(false);
watch(
() => mobileScreen,
(v) => {
if (v) return;
showVerticalHeaderMenu.value = false;
},
);
watch(mobileScreen, (v) => {
if (v) return;
showVerticalHeaderMenu.value = false;
});
// onMounted(() => {
// userStore.token = null;
// userStore.updateSelfUserInfo(true);

View File

@ -20,6 +20,9 @@ body {
.flex-column {
flex-direction: column;
}
.flex-stretch {
flex: 1;
}
.align-center {
align-items: center;
}

View File

@ -3,6 +3,7 @@ import { useUserStore } from '@/stores/user';
import { PageErrorType, type PageErrorReason } from '@/views/ErrorPage.vue';
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
import { RoutePermissionId } from './permissions';
import { timeout } from '@/utils';
declare module 'vue-router' {
interface RouteMeta {

View File

@ -28,8 +28,8 @@ function createResponseSchema<T extends z.ZodTypeAny>(data: T) {
]);
}
export type SucceedResponse<T> = Extract<T, { type: 'success' }>;
export type ErrorResponse<T> = Exclude<SucceedResponse<T>, T>;
export type SucceedResponseOf<T> = Extract<T, { type: 'success' }>;
export type ErrorResponseOf<T> = Exclude<SucceedResponseOf<T>, T>;
export const ordinarySchema = createResponseSchema(z.literal(true));
export const loginResponseSchema = ordinarySchema;
export const registerResponseSchema = ordinarySchema;
@ -53,7 +53,7 @@ export const userInfoResponseSchema = createResponseSchema(
),
}),
);
export type SucceedUserInfoResponse = SucceedResponse<z.infer<typeof userInfoResponseSchema>>;
export type SucceedUserInfoResponse = SucceedResponseOf<z.infer<typeof userInfoResponseSchema>>;
export type UserInfo = SucceedUserInfoResponse['data'];
export const verifyResponseSchema = createResponseSchema(
z

View File

@ -1,11 +1,20 @@
<template>
<el-main class="gobang-page__wrapper">
<el-container>
<el-main class="gobang-page__wrapper flex">
<el-container class="flex-stretch">
<el-header class="gobang-page-header flex align-center">
<div class="gobang-page-header__title">五子棋</div>
<el-button type="success">创建房间</el-button>
<el-button type="primary" :loading="loading" @click="refresh">刷新</el-button>
<el-button type="primary">单人游戏</el-button>
</el-header>
<el-main class="gobang-page-main"></el-main>
<el-main
v-loading="loading"
class="gobang-page-main flex-column"
:class="{ flex: !rooms.length, center: !rooms.length }"
>
<div class="gobang-page-main__no-rooms-title">没有房间</div>
<el-button type="primary" :loading="loading" @click="refresh">刷新</el-button>
</el-main>
</el-container>
</el-main>
</template>
@ -24,13 +33,41 @@
gap: 10px;
border-bottom: 1px solid var(--el-border-color);
}
.gobang-page-main {
gap: 10px;
&__no-rooms-title {
font-size: 25px;
font-weight: bold;
}
}
</style>
<script lang="ts" setup>
import { ref } from 'vue';
interface Room {}
import type { SucceedUserInfoResponse } from '@/schemas';
import { useUserStore } from '@/stores/user';
import { useWebSocket } from '@vueuse/core';
import { onMounted, ref, watch } from 'vue';
const userStore = useUserStore();
enum RoomState {
CREATED,
WAITING,
GAMING,
}
interface Room {
/** 房间UUID */
id: string;
state: RoomState;
pieces: unknown;
isWhiteAcceptRestart: boolean;
isBlackAcceptRestart: boolean;
canWhiteDown: boolean;
}
type GobangResp =
| {
name: 'RoomInfo';
name: 'UserInfo';
payload: SucceedUserInfoResponse;
}
| {
name: 'RoomList';
payload: {
rooms: Room[];
};
@ -43,16 +80,34 @@ type GobangResp =
};
type RespOf<T extends GobangResp['name']> = Extract<GobangResp, { name: T }>;
type RespHandlerMap = {
[P in GobangResp['name']]: (payload: RespOf<P>['payload']) => void;
[P in GobangResp['name']]?: (payload: RespOf<P>['payload']) => void;
};
const respHandlers: RespHandlerMap = {
RoomInfo(payload) {
RoomList(payload) {
rooms.value = payload.rooms;
loading.value = false;
},
PlayerJoin: (payload) => {
rooms.value = payload.rooms;
},
};
const rooms = ref<Room[]>();
// const { data } = useWebSocket<GobangResp>(`ws://127.0.0.1:58080/chess/${token}`);
const rooms = ref<Room[]>([]);
const loading = ref(false);
watch(loading, (loading) => {
if (!loading) return;
send(JSON.stringify({ name: 'RoomList' }));
console.log(1);
});
const { send } = useWebSocket<string>(`ws://wzpmc.cn:18080/chess/${userStore.token}`, {
onMessage(_, { data }) {
const resp: GobangResp = JSON.parse(data);
respHandlers[resp.name]?.(resp.payload as any);
},
});
function refresh() {
loading.value = true;
}
onMounted(() => {
refresh();
});
</script>