✨ feat: 添加用户显示
This commit is contained in:
parent
1123a9494e
commit
c632665d6e
@ -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;
|
||||
}
|
||||
|
@ -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 }}
|
||||
|
15
src/components/gobang/GobangUser.vue
Normal file
15
src/components/gobang/GobangUser.vue
Normal 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>
|
@ -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,
|
||||
// },
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user