This commit is contained in:
parent
f3bdeb25bc
commit
3516ddd55b
@ -624,7 +624,6 @@ async function mergeTiles(direction: Directions) {
|
||||
await Promise.all(tileAddPromises.concat(addRandomTiles(changed ? 1 : 0).promise));
|
||||
return Object.values(Directions).some((d) => getMergedGrid(d)[1]);
|
||||
}
|
||||
|
||||
function onMaskClick() {
|
||||
hideMask.value = true;
|
||||
}
|
||||
|
400
src/components/game2048/Game2048V2.vue
Normal file
400
src/components/game2048/Game2048V2.vue
Normal file
@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<div class="game-container" :style="gameContainerStyle" ref="gameContainer">
|
||||
<template v-for="y in height">
|
||||
<div
|
||||
v-for="x in width"
|
||||
:key="`${y}-${x}`"
|
||||
:style="[getTilePositionStyle(y - 1, x - 1)]"
|
||||
class="bg-tile"
|
||||
ref="backgroundTiles"
|
||||
></div>
|
||||
</template>
|
||||
<div
|
||||
v-for="tile of tiles"
|
||||
v-show="tile.renderState.exists"
|
||||
class="tile"
|
||||
:data-id="`tile-${tile.id}`"
|
||||
:style="[
|
||||
getTilePositionStyle(tile.renderState.y, tile.renderState.x),
|
||||
getNumberTileStyle(tile.renderState),
|
||||
]"
|
||||
>
|
||||
{{ tile.number }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.game-container {
|
||||
position: relative;
|
||||
width: calc(max-content);
|
||||
height: calc(max-content);
|
||||
background-color: rgb(187, 174, 158);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 10px;
|
||||
opacity: 0;
|
||||
}
|
||||
.tile,
|
||||
.bg-tile {
|
||||
position: absolute;
|
||||
width: v-bind(tileWidthStyle);
|
||||
height: v-bind(tileWidthStyle);
|
||||
border-radius: 6px;
|
||||
opacity: 0;
|
||||
user-select: none;
|
||||
}
|
||||
.tile {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
line-height: v-bind(tileWidthStyle);
|
||||
}
|
||||
.bg-tile {
|
||||
background-color: rgb(205, 193, 180);
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
const tileBackgroundColorMapping: Record<number, string | undefined> = {
|
||||
2: 'rgb(238, 228, 218)',
|
||||
4: 'rgb(237, 224, 200)',
|
||||
8: 'rgb(242, 177, 121)',
|
||||
16: 'rgb(242, 177, 121)',
|
||||
32: 'rgb(246, 124, 95)',
|
||||
64: 'rgb(246, 94, 59)',
|
||||
128: 'rgb(237, 207, 114)',
|
||||
256: 'rgb(237, 204, 97)',
|
||||
512: 'rgb(237, 200, 80)',
|
||||
1024: 'rgb(237, 197, 63)',
|
||||
2048: 'rgb(237, 194, 46)',
|
||||
};
|
||||
namespace TableView {
|
||||
export type GenKey = (first: number, second: number, xFirst: boolean) => string;
|
||||
export type Pos = [y: number, x: number];
|
||||
}
|
||||
class TableView<T> {
|
||||
private map = new Map<string, T>();
|
||||
constructor(
|
||||
private genKey: TableView.GenKey,
|
||||
public size: TableView.Pos,
|
||||
) {}
|
||||
get(first: number, second: number, xFirst: boolean = false) {
|
||||
return this.map.get(this.genKey(first, second, xFirst));
|
||||
}
|
||||
set(first: number, second: number, value: T, xFirst: boolean = false) {
|
||||
this.map.set(this.genKey(first, second, xFirst), value);
|
||||
}
|
||||
delete(first: number, second: number, xFirst: boolean = false) {
|
||||
this.map.delete(this.genKey(first, second, xFirst));
|
||||
}
|
||||
getLine(first: number, xFirst: boolean = false) {
|
||||
return new LineView(this, first, xFirst);
|
||||
}
|
||||
*iter(xFirst: boolean = false) {
|
||||
for (let i = 0; i < this.size[xFirst ? 1 : 0]; i++) {
|
||||
yield this.getLine(i, xFirst);
|
||||
}
|
||||
}
|
||||
toArray(xFirst: boolean = false) {
|
||||
return Array.from(this.iter(xFirst).map((line) => line.toArray()));
|
||||
}
|
||||
clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
getEmptyPositions() {
|
||||
const emptyPositions: TableView.Pos[] = [];
|
||||
for (const [y, line] of entries(this.iter())) {
|
||||
for (const [x, tile] of entries(line)) {
|
||||
if (tile !== undefined) continue;
|
||||
emptyPositions.push([y, x]);
|
||||
}
|
||||
}
|
||||
return emptyPositions;
|
||||
}
|
||||
}
|
||||
class LineView<T> {
|
||||
constructor(
|
||||
private table: TableView<T>,
|
||||
private readonly first: number,
|
||||
private readonly xFirst: boolean,
|
||||
) {}
|
||||
get(second: number) {
|
||||
return this.table.get(this.first, second, this.xFirst);
|
||||
}
|
||||
set(second: number, value: T) {
|
||||
this.table.set(this.first, second, value, this.xFirst);
|
||||
}
|
||||
delete(second: number) {
|
||||
this.table.delete(this.first, second, this.xFirst);
|
||||
}
|
||||
get size() {
|
||||
return this.table.size[this.xFirst ? 0 : 1];
|
||||
}
|
||||
toArray() {
|
||||
return Array.from(this);
|
||||
}
|
||||
*[Symbol.iterator]() {
|
||||
for (let i = 0; i < this.size; i++) {
|
||||
yield this.get(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
const showKeyframesEase = (scale: number = 0) => [
|
||||
{
|
||||
opacity: 0,
|
||||
transform: `scale(${scale})`,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
transform: 'none',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import { useGame2048Store } from '@/stores/2048';
|
||||
import { arrayIncludes, entries, zip } from '@/utils/array';
|
||||
import type { Exposed } from '@/utils/types';
|
||||
import { Directions } from '@/views/Game2048PageV2.vue';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { sample, shuffle } from 'lodash-es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, reactive, ref, useTemplateRef } from 'vue';
|
||||
const game2048Store = useGame2048Store();
|
||||
const { width, height } = storeToRefs(game2048Store);
|
||||
const table = new TableView<Tile>(
|
||||
(first, second, xFirst) => (xFirst ? `${first}-${second}` : `${second}-${first}`),
|
||||
[game2048Store.width, game2048Store.height],
|
||||
);
|
||||
type TileState = {
|
||||
number: number;
|
||||
x: number;
|
||||
y: number;
|
||||
exists: boolean;
|
||||
};
|
||||
class Tile implements TileState {
|
||||
private static lastId = 0;
|
||||
readonly id: number;
|
||||
private _x!: number;
|
||||
private _y!: number;
|
||||
number: number = 0;
|
||||
renderState!: TileState;
|
||||
_exists!: boolean;
|
||||
constructor() {
|
||||
this.id = Tile.lastId++;
|
||||
this.x = -1;
|
||||
this.y = -1;
|
||||
this.syncAllState();
|
||||
}
|
||||
get x() {
|
||||
return this._x;
|
||||
}
|
||||
set x(value) {
|
||||
table.delete(this.y, this.x);
|
||||
this._x = value;
|
||||
table.set(this.y, this.x, this);
|
||||
}
|
||||
get y() {
|
||||
return this._y;
|
||||
}
|
||||
set y(value) {
|
||||
table.delete(this.y, this.x);
|
||||
this._y = value;
|
||||
table.set(this.y, this.x, this);
|
||||
}
|
||||
get el() {
|
||||
return gameContainerRef.value?.querySelector(`.tile[data-id="tile-${this.id}"]`);
|
||||
}
|
||||
get exists() {
|
||||
return this._exists;
|
||||
}
|
||||
set exists(value) {
|
||||
this._exists = value;
|
||||
if (value) {
|
||||
idleTiles.delete(this);
|
||||
} else {
|
||||
idleTiles.add(this);
|
||||
table.delete(this.y, this.x);
|
||||
}
|
||||
}
|
||||
syncAllState() {
|
||||
this.renderState = {
|
||||
number: this.number,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
exists: this._exists,
|
||||
};
|
||||
}
|
||||
finish() {
|
||||
this.el?.getAnimations().forEach((animation) => animation.finish());
|
||||
}
|
||||
async show(fromOthers: boolean) {
|
||||
this.finish();
|
||||
this._exists = true;
|
||||
idleTiles.delete(this);
|
||||
this.syncAllState();
|
||||
await this.el?.animate(
|
||||
fromOthers
|
||||
? [
|
||||
{
|
||||
opacity: 0,
|
||||
transform: `scale(0.5)`,
|
||||
},
|
||||
{
|
||||
transform: `scale(1.1)`,
|
||||
offset: 0.8,
|
||||
},
|
||||
{
|
||||
opacity: 1,
|
||||
transform: `scale(1)`,
|
||||
},
|
||||
]
|
||||
: showKeyframesEase(),
|
||||
keyframeOptions({ duration: 150 }),
|
||||
).finished;
|
||||
}
|
||||
hide() {
|
||||
this.finish();
|
||||
this.exists = false;
|
||||
this.renderState.exists = false;
|
||||
}
|
||||
async move() {
|
||||
if (!this._exists) return;
|
||||
this.finish();
|
||||
const oldKeyframe = getTilePositionStyle(this.renderState.y, this.renderState.x);
|
||||
this.renderState.y = this.y;
|
||||
this.renderState.x = this.x;
|
||||
const newKeyframe = getTilePositionStyle(this.renderState.y, this.renderState.x);
|
||||
await this.el?.animate([oldKeyframe, newKeyframe], keyframeOptions({ duration: 100 })).finished;
|
||||
}
|
||||
}
|
||||
const tiles = reactive<Exposed<Tile>[]>(
|
||||
Array.from({ length: game2048Store.width * game2048Store.height }, () => new Tile()),
|
||||
);
|
||||
const idleTiles = new Set(tiles);
|
||||
const keyframeOptions = ({
|
||||
duration,
|
||||
delay,
|
||||
...options
|
||||
}: KeyframeAnimationOptions): KeyframeAnimationOptions => ({
|
||||
easing: 'ease',
|
||||
fill: 'forwards',
|
||||
duration: animate.value ? duration : 0,
|
||||
delay: animate.value ? delay : 0,
|
||||
...options,
|
||||
});
|
||||
const tileWidth = 105;
|
||||
const borderWidth = 15;
|
||||
const tileWidthStyle = `${tileWidth}px`;
|
||||
const gameContainerStyle = {
|
||||
minWidth: `${width.value * tileWidth + (width.value + 1) * borderWidth}px`,
|
||||
minHeight: `${height.value * tileWidth + (height.value + 1) * borderWidth}px`,
|
||||
};
|
||||
const getTilePositionStyle = (y: number, x: number) => ({
|
||||
left: `${borderWidth + x * (tileWidth + borderWidth)}px`,
|
||||
top: `${borderWidth + y * (tileWidth + borderWidth)}px`,
|
||||
});
|
||||
const getNumberTileStyle = ({ number }: TileState) => ({
|
||||
fontSize: `${number < 128 ? 55 : number < 1024 ? 45 : 35}px`,
|
||||
color: number < 8 ? 'rgb(119, 110, 101)' : 'white',
|
||||
backgroundColor: tileBackgroundColorMapping[number] ?? 'rgb(56, 56, 56)',
|
||||
});
|
||||
const gameContainerRef = useTemplateRef('gameContainer');
|
||||
const backgroundTilesRef = useTemplateRef('backgroundTiles');
|
||||
const addRandomTiles = async (count: number) => {
|
||||
const emptyPositions = shuffle(table.getEmptyPositions());
|
||||
const addedTiles: Exposed<Tile>[] = [];
|
||||
for (const [, tile] of zip(Array(count), Array.from(idleTiles))) {
|
||||
const pos = emptyPositions.pop();
|
||||
if (!pos) break;
|
||||
tile.number = sample([2, 4]);
|
||||
[tile.y, tile.x] = pos;
|
||||
addedTiles.push(tile);
|
||||
}
|
||||
await Promise.all(addedTiles.map((t) => t.show(false)));
|
||||
};
|
||||
const init = async () => {
|
||||
await addRandomTiles(2);
|
||||
console.log(table.toArray());
|
||||
};
|
||||
onMounted(async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
gameContainerRef.value?.animate(showKeyframesEase(0.95), keyframeOptions({ duration: 500 })),
|
||||
backgroundTilesRef.value?.map((el, i) => {
|
||||
const x = i % width.value,
|
||||
y = Math.floor(i / width.value);
|
||||
return el.animate(
|
||||
showKeyframesEase(),
|
||||
keyframeOptions({ duration: 500, delay: (x + y) * 100 }),
|
||||
);
|
||||
}),
|
||||
]
|
||||
.flat()
|
||||
.map((a) => a?.finished),
|
||||
);
|
||||
init();
|
||||
});
|
||||
const firstIndices = (d: Directions) =>
|
||||
Array.from(
|
||||
(function* () {
|
||||
switch (d) {
|
||||
case Directions.UP:
|
||||
case Directions.DOWN:
|
||||
for (let x = 0; x < width.value; x++) {
|
||||
yield x;
|
||||
}
|
||||
break;
|
||||
case Directions.LEFT:
|
||||
case Directions.RIGHT:
|
||||
for (let y = 0; y < height.value; y++) {
|
||||
yield y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})(),
|
||||
);
|
||||
|
||||
const secondIndices = (d: Directions) =>
|
||||
Array.from(
|
||||
(function* () {
|
||||
switch (d) {
|
||||
case Directions.UP:
|
||||
for (let y = height.value - 1; y >= 0; y--) {
|
||||
yield y;
|
||||
}
|
||||
break;
|
||||
case Directions.DOWN:
|
||||
for (let y = 0; y < height.value; y++) {
|
||||
yield y;
|
||||
}
|
||||
break;
|
||||
case Directions.LEFT:
|
||||
for (let x = width.value - 1; x >= 0; x--) {
|
||||
yield x;
|
||||
}
|
||||
break;
|
||||
case Directions.RIGHT:
|
||||
for (let x = 0; x < width.value; x++) {
|
||||
yield x;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})(),
|
||||
);
|
||||
const animate = ref(true);
|
||||
const move = async (key: Directions) => {
|
||||
const first = Array.from(firstIndices(key));
|
||||
const second = Array.from(secondIndices(key));
|
||||
const xFirst = arrayIncludes([Directions.UP, Directions.DOWN], key);
|
||||
for (const y of first) {
|
||||
for (const x of second.toReversed()) {
|
||||
const tile = table.get(y, x, xFirst);
|
||||
if (!tile) continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
useEventListener(document, 'keydown', async (e) => {
|
||||
if (!arrayIncludes(Object.values(Directions), e.key)) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
move(e.key);
|
||||
});
|
||||
</script>
|
38
src/components/table/DataTableV2.vue
Normal file
38
src/components/table/DataTableV2.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<component :is="render()"></component>
|
||||
</template>
|
||||
<style lang="scss" module></style>
|
||||
<script
|
||||
setup
|
||||
lang="tsx"
|
||||
generic="T extends Record<string, unknown>, TCustomKey extends string = never"
|
||||
>
|
||||
import type { Override } from '@/utils/types';
|
||||
import { ElTableV2, type ElTableColumn } from 'element-plus';
|
||||
import { type ComputedRef } from 'vue';
|
||||
import type { ComponentProps } from 'vue-component-type-helpers';
|
||||
|
||||
export type CustomColumn<K> = Override<
|
||||
ComponentProps<typeof ElTableColumn>,
|
||||
{
|
||||
key: K;
|
||||
show?: ComputedRef<boolean>;
|
||||
},
|
||||
'prop'
|
||||
>;
|
||||
export type DataSlot<T> = (props: { row: T }) => unknown;
|
||||
export type DataSlotRecord<T, K extends keyof any = keyof T> = Record<K, DataSlot<T>>;
|
||||
const { rows, customCols = [] } = defineProps<{
|
||||
rows: T[];
|
||||
customCols?: MaybeArray<CustomColumn<TCustomKey>>;
|
||||
selectable?: boolean | ((row: T, index: number) => boolean);
|
||||
}>();
|
||||
type Slots = DataSlotRecord<T, TCustomKey> & {
|
||||
default: () => unknown;
|
||||
};
|
||||
defineSlots<Slots>();
|
||||
defineEmits<{
|
||||
selectionChange: [rows: T[]];
|
||||
}>();
|
||||
const render = () => <ElTableV2 data={rows} columns={rows} width={'123'}></ElTableV2>;
|
||||
</script>
|
@ -7,12 +7,11 @@
|
||||
>
|
||||
<el-table-column prop="id" label="序号" />
|
||||
<el-table-column prop="name" label="用户名" />
|
||||
<!-- FIXME 插槽作用域还是any,疑似LSP的bug-->
|
||||
<template #auth="{ row }">
|
||||
{{ userAuthNameMapping[(row as T).auth.id] ?? row.auth.name }}
|
||||
{{ userAuthNameMapping[row.auth.id] ?? row.auth.name }}
|
||||
</template>
|
||||
<template v-if="showClubAuth" #clubAuth="{ row }">
|
||||
{{ clubAuthNameMapping[(row as T).clubAuth?.id ?? 'NONE'] ?? row.clubAuth.name }}
|
||||
{{ clubAuthNameMapping[row.clubAuth?.id ?? 'NONE'] ?? row.clubAuth?.name }}
|
||||
</template>
|
||||
<slot></slot>
|
||||
<template v-for="{ key } of ensureArray(customCols)" v-slot:[key]="props">
|
||||
|
@ -47,6 +47,10 @@ const routes: RouteRecordRaw[] = [
|
||||
breadcrumb: '首页',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/test',
|
||||
component: () => import('@/components/table/DataTableV2.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user/:id',
|
||||
name: 'User',
|
||||
@ -93,6 +97,11 @@ const routes: RouteRecordRaw[] = [
|
||||
name: '2048',
|
||||
component: () => import('@/views/Game2048Page.vue'),
|
||||
},
|
||||
{
|
||||
path: '/2048v2',
|
||||
name: '2048v2',
|
||||
component: () => import('@/views/Game2048PageV2.vue'),
|
||||
},
|
||||
{
|
||||
path: '/gobang',
|
||||
name: 'GobangList',
|
||||
@ -203,17 +212,17 @@ router.beforeEach(async (to) => {
|
||||
const userStore = useUserStore();
|
||||
const pageStore = usePageStore();
|
||||
pageStore.setNewRouteId(to);
|
||||
if (!userStore.userInfo) {
|
||||
const succeed = await userStore.updateSelfUserInfo();
|
||||
if (!succeed) {
|
||||
return pageStore.createTempErrorRoute(
|
||||
{
|
||||
type: PageErrorType.NETWORK_ERROR,
|
||||
},
|
||||
to,
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (!userStore.userInfo) {
|
||||
// const succeed = await userStore.updateSelfUserInfo();
|
||||
// if (!succeed) {
|
||||
// return pageStore.createTempErrorRoute(
|
||||
// {
|
||||
// type: PageErrorType.NETWORK_ERROR,
|
||||
// },
|
||||
// to,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const type = userStore.isRouteAccessible(to.fullPath);
|
||||
if (type !== true) {
|
||||
|
@ -32,6 +32,12 @@ export function* zip<T extends Iterable<unknown>[]>(...its: T): Generator<Zip<T>
|
||||
yield nextValues.map(({ value }) => value) as any;
|
||||
}
|
||||
}
|
||||
export function* entries<T>(it: Iterable<T>): Generator<[number, T], void, unknown> {
|
||||
let i = 0;
|
||||
for (const value of it) {
|
||||
yield [i++, value];
|
||||
}
|
||||
}
|
||||
export function get2DArrayItem<T>(
|
||||
grid: T[][],
|
||||
first: number,
|
||||
@ -46,3 +52,4 @@ export function ensureArray(obj: unknown) {
|
||||
}
|
||||
export const arrayIncludes = <T, R>(array: T[], value: R): value is T & R =>
|
||||
array.includes(value as any);
|
||||
export const len = (len: number) => ({ length: len });
|
||||
|
@ -82,3 +82,6 @@ export function waitRef<T, const R extends T>(ref: Ref<T>, ...expect: R[]) {
|
||||
*/
|
||||
export const isKeyOf = <T extends object>(obj: T, key: keyof any): key is keyof typeof obj =>
|
||||
key in obj;
|
||||
export const setProps = <T extends object>(obj: T, props: Partial<T>): T => {
|
||||
return Object.assign(obj, props);
|
||||
};
|
||||
|
@ -16,3 +16,6 @@ export type Rename<
|
||||
};
|
||||
export type Nullable<T = never> = T | null | undefined;
|
||||
export type ComparablePrimitive = Exclude<Primitive, symbol>;
|
||||
export type Exposed<T> = {
|
||||
[P in keyof T]: T[P];
|
||||
};
|
||||
|
113
src/views/Game2048PageV2.vue
Normal file
113
src/views/Game2048PageV2.vue
Normal file
@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<el-main class="game-2048-page-wrapper !flex justify-center">
|
||||
<div class="game-2048-page">
|
||||
<div class="game-header">
|
||||
<div class="game-title">
|
||||
<b>{{ game2048Store.successNumber.toString().padStart(4, '0') }}</b>
|
||||
</div>
|
||||
<game2048-score :key="game2048Store.gameKey" :score="game2048Store.maxNumber">
|
||||
最高数字
|
||||
</game2048-score>
|
||||
<game2048-score :key="game2048Store.gameKey" :score="game2048Store.score">
|
||||
得分
|
||||
</game2048-score>
|
||||
</div>
|
||||
<div class="game-header">
|
||||
<div class="game-description">
|
||||
合并数字到达
|
||||
<b>{{ game2048Store.successNumber }}!</b>
|
||||
</div>
|
||||
<game2048-button @click="click"><b>新游戏</b></game2048-button>
|
||||
</div>
|
||||
<game2048-v2 :key="game2048Store.gameKey" class="game" />
|
||||
</div>
|
||||
</el-main>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.game-2048-page-wrapper {
|
||||
--font-color: rgb(119, 110, 101);
|
||||
font-family: 'Arial', '微软雅黑', '黑体', sans-serif;
|
||||
background-color: rgb(250, 248, 239);
|
||||
}
|
||||
|
||||
.game-2048-page {
|
||||
width: max-content;
|
||||
zoom: v-bind(zoomCSS);
|
||||
}
|
||||
|
||||
.game-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.game-header:has(.game-description) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.game-title {
|
||||
font-size: 70px;
|
||||
color: var(--font-color);
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.game-description {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
font-size: var(--normal-font-size);
|
||||
line-height: 18px;
|
||||
color: var(--font-color);
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
/**
|
||||
* 方向键代码.
|
||||
*/
|
||||
export enum Directions {
|
||||
UP = 'ArrowUp',
|
||||
DOWN = 'ArrowDown',
|
||||
LEFT = 'ArrowLeft',
|
||||
RIGHT = 'ArrowRight',
|
||||
}
|
||||
|
||||
export type SwipeEventMap = {
|
||||
swipe: Directions;
|
||||
};
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import Game2048Button from '@/components/game2048/Game2048Button.vue';
|
||||
import Game2048Score from '@/components/game2048/Game2048Score.vue';
|
||||
import Game2048V2 from '@/components/game2048/Game2048V2.vue';
|
||||
import { useGame2048Store } from '@/stores/2048';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const game2048Store = useGame2048Store();
|
||||
const { smLess } = storeToRefs(useMediaStore());
|
||||
const zoomCSS = computed(() => {
|
||||
return smLess.value ? 0.75 : 1;
|
||||
});
|
||||
useEventListener(
|
||||
'touchmove',
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
},
|
||||
{ passive: false },
|
||||
);
|
||||
|
||||
function click() {
|
||||
game2048Store.$reset();
|
||||
console.log(game2048Store.gameKey);
|
||||
}
|
||||
</script>
|
@ -1,5 +1,6 @@
|
||||
import legacy from '@vitejs/plugin-legacy';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import { resolve } from 'node:path';
|
||||
import UnoCSS from 'unocss/vite';
|
||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||
@ -16,6 +17,7 @@ export default defineConfig({
|
||||
modernPolyfills: true,
|
||||
}),
|
||||
vue(),
|
||||
vueJsx(),
|
||||
Components({
|
||||
resolvers: [
|
||||
ElementPlusResolver(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user