d76ecb15d6
- Add aliases field to games (json array), searchable in game library and team session - Add edit functionality inside GameDetailDialog (creator/owner/admin can modify) - Add permission control for delete/edit buttons (only visible to authorized users) - Scope GameSelectDialog to current group only - Support aliases in import/export Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
184 lines
5.4 KiB
Vue
184 lines
5.4 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch } from 'vue'
|
|
import { addGame, updateGame, getGameCoverUrl } from '@/api/games'
|
|
import type { Game, GamePlatform } from '@/types'
|
|
import { ElMessage } from 'element-plus'
|
|
import { getAllPlatforms } from '@/api/games'
|
|
import { Plus } from '@element-plus/icons-vue'
|
|
import type { UploadFile, UploadInstance } from 'element-plus'
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
groupId: string
|
|
game?: Game | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: boolean]
|
|
'saved': []
|
|
}>()
|
|
|
|
const visible = computed({
|
|
get: () => props.modelValue,
|
|
set: (val) => emit('update:modelValue', val)
|
|
})
|
|
|
|
const isEdit = computed(() => !!props.game)
|
|
|
|
const name = ref('')
|
|
const aliasesInput = ref('')
|
|
const platform = ref<GamePlatform | ''>('')
|
|
const tagsInput = ref('')
|
|
const coverFile = ref<File | null>(null)
|
|
const coverPreview = ref('')
|
|
const uploadRef = ref<UploadInstance>()
|
|
const loading = ref(false)
|
|
const platforms = getAllPlatforms()
|
|
|
|
watch(() => props.modelValue, (val) => {
|
|
if (val && props.game) {
|
|
name.value = props.game.name
|
|
aliasesInput.value = (props.game.aliases || []).join(', ')
|
|
platform.value = props.game.platform || ''
|
|
tagsInput.value = (props.game.tags || []).join(', ')
|
|
coverFile.value = null
|
|
coverPreview.value = getGameCoverUrl(props.game) || ''
|
|
} else if (val) {
|
|
name.value = ''
|
|
aliasesInput.value = ''
|
|
platform.value = ''
|
|
tagsInput.value = ''
|
|
coverFile.value = null
|
|
coverPreview.value = ''
|
|
}
|
|
})
|
|
|
|
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('请输入游戏名称')
|
|
return
|
|
}
|
|
try {
|
|
loading.value = true
|
|
const aliases = aliasesInput.value.split(',').map(t => t.trim()).filter(Boolean)
|
|
const tags = tagsInput.value.split(',').map(t => t.trim()).filter(Boolean)
|
|
|
|
if (isEdit.value && props.game) {
|
|
await updateGame(props.game.id, {
|
|
name: name.value.trim(),
|
|
aliases: aliases.length > 0 ? aliases : undefined,
|
|
platform: platform.value || undefined,
|
|
tags: tags.length > 0 ? tags : undefined,
|
|
coverFile: coverFile.value || undefined
|
|
})
|
|
ElMessage.success('修改成功')
|
|
} else {
|
|
await addGame(props.groupId, {
|
|
name: name.value.trim(),
|
|
aliases: aliases.length > 0 ? aliases : undefined,
|
|
platform: platform.value || undefined,
|
|
tags: tags.length > 0 ? tags : undefined,
|
|
coverFile: coverFile.value || undefined
|
|
})
|
|
ElMessage.success('添加成功')
|
|
}
|
|
|
|
name.value = ''
|
|
aliasesInput.value = ''
|
|
platform.value = ''
|
|
tagsInput.value = ''
|
|
coverFile.value = null
|
|
coverPreview.value = ''
|
|
uploadRef.value?.clearFiles()
|
|
emit('saved')
|
|
visible.value = false
|
|
} catch (error: any) {
|
|
ElMessage.error(error.message || (isEdit.value ? '修改失败' : '添加失败'))
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<el-dialog v-model="visible" :title="isEdit ? '编辑游戏' : '添加游戏'" width="440px">
|
|
<div class="form-fields">
|
|
<div class="field">
|
|
<label>游戏名称 *</label>
|
|
<el-input v-model="name" placeholder="输入游戏名称" maxlength="100" />
|
|
</div>
|
|
<div class="field">
|
|
<label>别名</label>
|
|
<el-input v-model="aliasesInput" placeholder="逗号分隔,如: LOL,英雄联盟,撸啊撸" />
|
|
</div>
|
|
<div class="field">
|
|
<label>平台</label>
|
|
<el-select v-model="platform" placeholder="选择平台" clearable style="width: 100%">
|
|
<el-option v-for="p in platforms" :key="p" :label="p" :value="p" />
|
|
</el-select>
|
|
</div>
|
|
<div class="field">
|
|
<label>标签</label>
|
|
<el-input v-model="tagsInput" placeholder="逗号分隔,如: MOBA,竞技,5v5" />
|
|
</div>
|
|
<div class="field">
|
|
<label>封面图</label>
|
|
<el-upload
|
|
ref="uploadRef"
|
|
: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>
|
|
<el-button @click="visible = false">取消</el-button>
|
|
<el-button type="primary" :loading="loading" @click="handleSubmit">{{ isEdit ? '保存' : '添加' }}</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.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>
|