feat: game cover file upload + fix game visibility rules

- Change game cover field from URL text to file upload (PocketBase migration)
- AddGameDialog now supports drag-and-drop image upload instead of URL input
- Add getGameCoverUrl() helper using pb.files.getUrl() for cover display
- Fix games listRule/viewRule: group.members.id syntax doesn't work in PB 0.22,
  changed to group.members ~ @request.auth.id for correct member visibility
- Update changelog with all v0.3.5 entries

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
congsh
2026-04-23 19:09:57 +08:00
parent 625645dad4
commit ceafc873c7
8 changed files with 130 additions and 23 deletions
+51 -5
View File
@@ -4,6 +4,8 @@ import { addGame } from '@/api/games'
import type { GamePlatform } from '@/types'
import { ElMessage } from 'element-plus'
import { getAllPlatforms } from '@/api/games'
import { Plus } from '@element-plus/icons-vue'
import type { UploadFile } from 'element-plus'
const props = defineProps<{
modelValue: boolean
@@ -23,10 +25,23 @@ const visible = computed({
const name = ref('')
const platform = ref<GamePlatform | ''>('')
const tagsInput = ref('')
const cover = ref('')
const coverFile = ref<File | null>(null)
const coverPreview = ref('')
const loading = ref(false)
const platforms = getAllPlatforms()
function handleFileChange(uploadFile: UploadFile) {
if (uploadFile.raw) {
coverFile.value = uploadFile.raw
coverPreview.value = URL.createObjectURL(uploadFile.raw)
}
}
function handleFileRemove() {
coverFile.value = null
coverPreview.value = ''
}
async function handleSubmit() {
if (!name.value.trim()) {
ElMessage.warning('请输入游戏名称')
@@ -39,13 +54,14 @@ async function handleSubmit() {
name: name.value.trim(),
platform: platform.value || undefined,
tags: tags.length > 0 ? tags : undefined,
cover: cover.value.trim() || undefined
coverFile: coverFile.value || undefined
})
ElMessage.success('添加成功')
name.value = ''
platform.value = ''
tagsInput.value = ''
cover.value = ''
coverFile.value = null
coverPreview.value = ''
emit('created')
visible.value = false
} catch (error: any) {
@@ -74,8 +90,23 @@ async function handleSubmit() {
<el-input v-model="tagsInput" placeholder="逗号分隔,如: MOBA,竞技,5v5" />
</div>
<div class="field">
<label>封面图 URL</label>
<el-input v-model="cover" placeholder="https://example.com/cover.jpg" />
<label>封面图</label>
<el-upload
:auto-upload="false"
:limit="1"
accept="image/*"
:on-change="handleFileChange"
:on-remove="handleFileRemove"
drag
>
<div v-if="coverPreview" class="cover-preview">
<img :src="coverPreview" alt="封面预览" />
</div>
<div v-else class="upload-placeholder">
<el-icon :size="28"><Plus /></el-icon>
<span>点击或拖拽上传封面</span>
</div>
</el-upload>
</div>
</div>
<template #footer>
@@ -89,4 +120,19 @@ async function handleSubmit() {
.form-fields { display: flex; flex-direction: column; gap: 18px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 13px; font-weight: 500; color: var(--gg-text-secondary); }
.cover-preview img {
width: 100%;
max-height: 200px;
object-fit: contain;
border-radius: 6px;
}
.upload-placeholder {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 20px;
color: var(--gg-text-muted);
font-size: 13px;
}
</style>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import type { Game } from '@/types'
import { toggleFavorite, isFavorite, deleteGame } from '@/api/games'
import { toggleFavorite, isFavorite, deleteGame, getGameCoverUrl } from '@/api/games'
import { ElMessage, ElMessageBox } from 'element-plus'
import { TrendCharts, StarFilled, Star, Delete } from '@element-plus/icons-vue'
import GameComments from './GameComments.vue'
@@ -62,7 +62,7 @@ function handleCreateTeam() {
<template v-if="game">
<div class="game-detail">
<div class="detail-cover-wrap">
<img :src="game.cover || '/game-placeholder.svg'" :alt="game.name" class="detail-cover" />
<img :src="getGameCoverUrl(game) || '/game-placeholder.svg'" :alt="game.name" class="detail-cover" />
</div>
<h2 class="detail-name">{{ game.name }}</h2>