feat: 添加用户显示

This commit is contained in:
Litrix 2024-12-18 13:03:30 +08:00
parent 1123a9494e
commit c632665d6e
6 changed files with 128 additions and 61 deletions

View File

@ -41,6 +41,12 @@ body {
@extend .justify-center;
@extend .align-center;
}
.absolute-self-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.text-center {
text-align: center;
}

View File

@ -8,7 +8,7 @@
<!-- 仅收紧类型 -->
<template v-if="userInfo && userStore.logined">
<div class="flex align-center">
<el-avatar :icon="userInfo.avatar ?? undefined"></el-avatar>
<el-avatar :icon="userInfo.avatar ?? undefined" />
</div>
<div class="app-header-user__username">
{{ userInfo.name }}

View File

@ -0,0 +1,15 @@
<template>
<div class="gobang-play-page-aside-user flex center" :class="{ 'flex-column': !footer }">
<el-avatar :size="40" :icon="user.avatar ?? undefined" />
<div class="gobang-play-page-aside-user__name">{{ user.name }}</div>
</div>
</template>
<style lang="scss" scoped>
.gobang-play-page-aside-user {
gap: 10px;
}
</style>
<script setup lang="ts">
import type { UserInfo } from '@/schemas';
const props = defineProps<{ user: UserInfo; footer: boolean }>();
</script>

View File

@ -9,7 +9,7 @@ export interface SimplePart<N extends string> {
name: N;
}
type Payload = Record<string, unknown>;
export interface PayloadPart<N extends string, P extends Payload> extends SimplePart<N> {
export interface PayloadPart<N extends string, P extends Payload = {}> extends SimplePart<N> {
payload: P;
}
type BaseResp = PayloadPart<string, Record<string, unknown>>;
@ -21,6 +21,8 @@ export interface UseGameSocketOptions<
TResp extends BaseResp,
TRelations extends Relations<TRequest['name'], TResp['name']>,
> {
url: string;
relations: TRelations;
succeed: {
[P in TResp['name']]?: (payload: Extract<TResp, { name: P }>['payload']) => void;
};
@ -34,9 +36,9 @@ export interface UseGameSocketOptions<
}
export function useGameSocket<TRequest extends SimplePart<string>, TResp extends BaseResp>() {
return <TRelations extends Relations<TRequest['name'], TResp['name']>>(
relations: TRelations,
options: UseGameSocketOptions<TRequest, TResp, TRelations>,
) => {
const { relations } = options;
interface ErrorResp extends PayloadPart<'Error', { reason: string }> {
originalPacketName: TRequest['name'];
}
@ -51,7 +53,7 @@ export function useGameSocket<TRequest extends SimplePart<string>, TResp extends
}
const userStore = useUserStore();
const relationMap = new Map<keyof TRelations, [TRequest, Set<string>]>();
const ws = useWebSocket<string>(`wss://wzpmc.cn:18080/chess/${userStore.token}`, {
const ws = useWebSocket<string>(options.url, {
// autoReconnect: {
// delay: 500,
// },

View File

@ -1,6 +1,6 @@
<template>
<el-main class="gobang-list-page__wrapper flex">
<el-container direction="vertical">
<el-container>
<gobang-header class="justify-between">
<div class="flex align-center">
<el-button type="primary" :loading="loading" @click="refresh">刷新</el-button>
@ -26,8 +26,13 @@
<el-table-column prop="briefId" :formatter="getPlayerCount" label="房间人数" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button type="success" :loading="row.joining" @click="onJoinButtonClick(row)">
加入
<el-button
type="success"
:disabled="(row as RoomRender).state === RoomState.GAMING"
@click="onJoinButtonClick(row)"
>
<template v-if="(row as RoomRender).state === RoomState.GAMING">已满</template>
<template v-else>加入</template>
</el-button>
</template>
</el-table-column>
@ -93,33 +98,51 @@ export type Resp =
| PayloadPart<'RoomList', { rooms: Room[] }>
| PayloadPart<'RoomCreated', { roomId: RoomId }>
| PayloadPart<'PlayerSideAllocation', { isWhite: boolean }>
| PayloadPart<'RoomInfo', RoomDetail>;
| PayloadPart<'RoomInfo', RoomDetail>
| PayloadPart<'PlayerJoinRoom', { room: Room; user: UserInfo }>
| PayloadPart<'PlayerLeave'>;
export const relations = {
RoomList: 'RoomList',
CreateRoom: 'RoomCreated',
PlayerJoin: ['PlayerSideAllocation', 'RoomInfo'],
} as const;
export function useGobangSocket(
options: Omit<UseGameSocketOptions<Request, Resp, typeof relations>, 'url' | 'relations'>,
) {
return useGameSocket<Request, Resp>()(
Object.assign(options, {
url: `wss://wzpmc.cn:18080/chess/${useUserStore().token}`,
relations,
}),
);
}
</script>
<script lang="ts" setup>
import CreateGobangRoomDialog from '@/components/gobang/CreateGobangRoomDialog.vue';
import GobangHeader from '@/components/gobang/GobangHeader.vue';
import router from '@/router';
import type { UserInfo } from '@/schemas';
import { useGameSocket, type PayloadPart, type SimplePart } from '@/utils/game-socket';
import { useUserStore } from '@/stores/user';
import {
useGameSocket,
type PayloadPart,
type SimplePart,
type UseGameSocketOptions,
} from '@/utils/game-socket';
import { onMounted, ref } from 'vue';
interface RoomInfoRender extends Room {
interface RoomRender extends Room {
briefId: string;
}
function toRoomRender(room: Room): RoomInfoRender {
function toRoomRender(room: Room): RoomRender {
return Object.assign(room, {
briefId: room.id.slice(0, 8),
});
}
const showDialog = ref(false);
const rooms = ref<RoomInfoRender[]>([]);
const rooms = ref<RoomRender[]>([]);
const loading = ref(false);
const loadingText = ref<string>();
const { send } = useGameSocket<Request, Resp>()(relations, {
const { send } = useGobangSocket({
succeed: {
RoomList(p) {
rooms.value = p.rooms.map(toRoomRender);
@ -142,7 +165,7 @@ function refresh() {
onMounted(() => {
refresh();
});
function getPlayerCount(row: RoomInfoRender) {
function getPlayerCount(row: RoomRender) {
switch (row.state) {
case RoomState.CREATED:
return '0/2';
@ -152,7 +175,7 @@ function getPlayerCount(row: RoomInfoRender) {
return '2/2';
}
}
function onJoinButtonClick(room: RoomInfoRender) {
function onJoinButtonClick(room: RoomRender) {
play(room.id);
}
function play(roomId: RoomId) {

View File

@ -3,7 +3,7 @@
v-loading="state !== 'GAMING' && state !== 'WAITING'"
class="gobang-play-page__wrapper flex"
>
<el-container v-if="state !== 'FIRST_LOADING'">
<el-container direction="vertical" v-if="state !== 'FIRST_LOADING'">
<gobang-header class="justify-between">
<div class="flex align-center">
<template v-if="roomId">
@ -13,34 +13,46 @@
<h2 v-else>单人游戏</h2>
</div>
</gobang-header>
<el-main class="gobang-play-page-main center flex flex-column">
<div class="gobang__state">
<!-- {{ stateDisplayMap[state] }} -->
</div>
<div class="gobang-user">
<div class="gobang-user__name">{{}}</div>
</div>
<div class="gobang-chessboard">
<canvas class="gobang-chessboard__background" ref="canvas"></canvas>
<template v-for="(row, y) of grid">
<template v-for="(cell, x) of row">
<!-- eslint-disable-next-line vue/require-v-for-key -->
<div
v-if="state !== 'WAITING'"
class="gobang-chessboard__cell"
:class="[
cell
? cell.isWhite
? 'gobang-chessboard__cell--white'
: 'gobang-chessboard__cell--black'
: undefined,
]"
:style="getCellStyle(cell, x, y)"
></div>
<el-container direction="horizontal">
<el-main class="gobang-play-page-main center flex flex-column">
<div class="gobang__state">
<!-- {{ stateDisplayMap[state] }} -->
</div>
<div class="gobang-user">
<div class="gobang-user__name">{{}}</div>
</div>
<div class="gobang-chessboard">
<canvas class="gobang-chessboard__background" ref="canvas"></canvas>
<template v-for="(row, y) of grid">
<template v-for="(cell, x) of row">
<!-- eslint-disable-next-line vue/require-v-for-key -->
<div
v-if="state !== 'WAITING'"
class="gobang-chessboard__cell"
:class="[
cell
? cell.isWhite
? 'gobang-chessboard__cell--white'
: 'gobang-chessboard__cell--black'
: undefined,
]"
:style="getCellStyle(cell, x, y)"
></div>
</template>
</template>
</template>
</div>
</el-main>
</div>
</el-main>
<el-aside
v-if="!mdLess"
class="gobang-play-page-aside flex flex-column align-center justify-between"
>
<gobang-user v-if="otherUser" :footer="false" :user="otherUser" />
<div class="gobang-play-page-aside__vs absolute-self-center">VS</div>
</el-aside>
</el-container>
<el-footer v-if="mdLess" class="gobang-play-page-footer flex align-center">
<gobang-user v-if="userStore.userInfo" footer :user="userStore.userInfo" />
</el-footer>
</el-container>
</el-main>
</template>
@ -57,16 +69,31 @@
position: absolute;
inset: 0;
}
&-main {
--el-main-padding: 0;
margin-top: var(--header-height);
position: relative;
}
&-aside {
position: relative;
margin-top: var(--header-height);
border-left: 1px solid var(--el-border-color);
width: 200px;
padding: 80px 0;
&__vs {
font-size: 50px;
font-weight: bold;
}
}
&-footer {
border-top: 1px solid var(--el-border-color);
--el-footer-height: var(--header-height);
}
}
.gobang-play-page-main {
--el-main-padding: 0 0 0 0;
margin-top: var(--header-height);
position: relative;
}
.gobang-chessboard {
position: relative;
zoom: v-bind(boardZoomCSS);
zoom: v-bind(boardZoom);
&__cell {
--size: 24px;
position: absolute;
@ -94,23 +121,17 @@
}
</style>
<script setup lang="ts">
import GobangUser from '@/components/gobang/GobangUser.vue';
import GobangHeader from '@/components/gobang/GobangHeader.vue';
import router from '@/router';
import { useMediaStore } from '@/stores/media';
import { useUserStore } from '@/stores/user';
import { useGameSocket } from '@/utils/game-socket';
import {
relations,
type Request,
type Resp,
type RoomDetail,
type RoomId,
} from '@/views/GobangListPage.vue';
import { useGobangSocket, type RoomDetail, type RoomId } from '@/views/GobangListPage.vue';
import { storeToRefs } from 'pinia';
import { computed, onMounted, ref, watchEffect, type CSSProperties } from 'vue';
import { useRoute } from 'vue-router';
const userStore = useUserStore();
const { smLess } = storeToRefs(useMediaStore());
const { smLess, mdLess } = storeToRefs(useMediaStore());
const roomId = useRoute().params.id as RoomId | undefined;
const canvas = ref<HTMLCanvasElement>();
const boardBackground = '#E2CFA9';
@ -126,7 +147,7 @@ const boardPadding = 15;
const boardSize = computed(
() => gridSize * (boardLength - 1) + boardBorderSize * 2 + boardPadding * 2,
);
const boardZoomCSS = computed(() => (smLess.value ? 0.75 : 1));
const boardZoom = computed(() => (smLess.value ? 0.75 : 1));
watchEffect(() => {
if (!canvas.value) return;
const ctx = canvas.value.getContext('2d');
@ -188,7 +209,7 @@ const room = ref<RoomDetail>();
const otherUser = computed(() => {
if (!room.value) return;
const { whiteUser, blackUser } = room.value;
return [whiteUser, blackUser].find((v) => v && v?.id !== userStore.userInfo!.id);
return [whiteUser, blackUser].find((v) => v && v.id !== userStore.userInfo!.id);
});
const grid = computed<Grid | undefined>(() =>
room.value?.pieces.map((row) =>
@ -197,7 +218,7 @@ const grid = computed<Grid | undefined>(() =>
);
const state = ref<State>('FIRST_LOADING');
const isWhite = ref(false);
const { send } = useGameSocket<Request, Resp>()(relations, {
const { send } = useGobangSocket({
succeed: {
UserInfo() {
if (!roomId) return;