229 lines
5.3 KiB
Vue
229 lines
5.3 KiB
Vue
|
|
<!-- src/components-mobile/game/AddGameSheetMobile.vue -->
|
|||
|
|
<!-- 添加游戏表单(绑定到当前群组):名称/别名/平台/标签/封面 -->
|
|||
|
|
<script setup lang="ts">
|
|||
|
|
import { computed, ref, watch } from 'vue'
|
|||
|
|
import { addGame, getAllPlatforms } from '@/api/games'
|
|||
|
|
import type { GamePlatform } from '@/types'
|
|||
|
|
import { showSuccessToast, showFailToast } from 'vant'
|
|||
|
|
|
|||
|
|
const props = defineProps<{
|
|||
|
|
show: boolean
|
|||
|
|
groupId: string
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const emit = defineEmits<{
|
|||
|
|
'update:show': [value: boolean]
|
|||
|
|
'saved': []
|
|||
|
|
}>()
|
|||
|
|
|
|||
|
|
const visible = computed({
|
|||
|
|
get: () => props.show,
|
|||
|
|
set: (val) => emit('update:show', val)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const platforms = getAllPlatforms()
|
|||
|
|
|
|||
|
|
const form = ref({
|
|||
|
|
name: '',
|
|||
|
|
aliases: '',
|
|||
|
|
platform: '' as GamePlatform | '',
|
|||
|
|
tags: ''
|
|||
|
|
})
|
|||
|
|
const coverFile = ref<File | null>(null)
|
|||
|
|
const coverPreview = ref('')
|
|||
|
|
const saving = ref(false)
|
|||
|
|
|
|||
|
|
watch(() => props.show, (val) => {
|
|||
|
|
if (val) {
|
|||
|
|
form.value = { name: '', aliases: '', platform: '', tags: '' }
|
|||
|
|
coverFile.value = null
|
|||
|
|
coverPreview.value = ''
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
function onFileChange(e: Event) {
|
|||
|
|
const input = e.target as HTMLInputElement
|
|||
|
|
if (input.files?.[0]) {
|
|||
|
|
coverFile.value = input.files[0]
|
|||
|
|
coverPreview.value = URL.createObjectURL(input.files[0])
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function clearCover() {
|
|||
|
|
coverFile.value = null
|
|||
|
|
coverPreview.value = ''
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function handleSubmit() {
|
|||
|
|
if (!form.value.name.trim()) {
|
|||
|
|
showFailToast('请输入游戏名称')
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
saving.value = true
|
|||
|
|
const aliases = form.value.aliases.split(',').map(t => t.trim()).filter(Boolean)
|
|||
|
|
const tags = form.value.tags.split(',').map(t => t.trim()).filter(Boolean)
|
|||
|
|
await addGame(props.groupId, {
|
|||
|
|
name: form.value.name.trim(),
|
|||
|
|
aliases: aliases.length > 0 ? aliases : undefined,
|
|||
|
|
platform: form.value.platform || undefined,
|
|||
|
|
tags: tags.length > 0 ? tags : undefined,
|
|||
|
|
coverFile: coverFile.value || undefined
|
|||
|
|
})
|
|||
|
|
showSuccessToast('添加成功')
|
|||
|
|
emit('saved')
|
|||
|
|
} catch (e: any) {
|
|||
|
|
showFailToast(e.message || '添加失败')
|
|||
|
|
} finally {
|
|||
|
|
saving.value = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<van-popup
|
|||
|
|
v-model:show="visible"
|
|||
|
|
position="bottom"
|
|||
|
|
round
|
|||
|
|
closeable
|
|||
|
|
:style="{ height: '80%' }"
|
|||
|
|
>
|
|||
|
|
<div class="add-sheet">
|
|||
|
|
<div class="sheet-title">添加游戏</div>
|
|||
|
|
|
|||
|
|
<van-cell-group inset>
|
|||
|
|
<van-field v-model="form.name" label="名称" placeholder="游戏名称" required />
|
|||
|
|
<van-field v-model="form.aliases" label="别名" placeholder="逗号分隔,如 LOL,英雄联盟" />
|
|||
|
|
<van-field name="platform" label="平台">
|
|||
|
|
<template #input>
|
|||
|
|
<select v-model="form.platform" class="native-select">
|
|||
|
|
<option value="">不指定</option>
|
|||
|
|
<option v-for="p in platforms" :key="p" :value="p">{{ p }}</option>
|
|||
|
|
</select>
|
|||
|
|
</template>
|
|||
|
|
</van-field>
|
|||
|
|
<van-field v-model="form.tags" label="标签" placeholder="逗号分隔,如 MOBA,竞技" />
|
|||
|
|
</van-cell-group>
|
|||
|
|
|
|||
|
|
<!-- 封面上传 -->
|
|||
|
|
<div class="cover-field">
|
|||
|
|
<div class="field-label">封面图</div>
|
|||
|
|
<div class="cover-upload">
|
|||
|
|
<div v-if="coverPreview" class="cover-preview">
|
|||
|
|
<img :src="coverPreview" alt="" />
|
|||
|
|
<button class="cover-clear" @click="clearCover">
|
|||
|
|
<van-icon name="cross" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
<label v-else class="cover-picker">
|
|||
|
|
<van-icon name="photograph" size="28" />
|
|||
|
|
<span>上传封面</span>
|
|||
|
|
<input type="file" accept="image/*" class="file-input" @change="onFileChange" />
|
|||
|
|
</label>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="sheet-actions">
|
|||
|
|
<van-button type="primary" block round :loading="saving" @click="handleSubmit">
|
|||
|
|
添加游戏
|
|||
|
|
</van-button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</van-popup>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.add-sheet {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
height: 100%;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
padding: 16px 0 24px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sheet-title {
|
|||
|
|
text-align: center;
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: 600;
|
|||
|
|
padding: 8px 0 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.native-select {
|
|||
|
|
border: 1px solid var(--gg-border);
|
|||
|
|
border-radius: var(--gg-radius-sm);
|
|||
|
|
padding: 6px 10px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
background: var(--gg-bg-card);
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-field {
|
|||
|
|
padding: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.field-label {
|
|||
|
|
font-size: 13px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
color: var(--gg-text-secondary);
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-upload {
|
|||
|
|
display: flex;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-preview {
|
|||
|
|
position: relative;
|
|||
|
|
width: 90px;
|
|||
|
|
height: 120px;
|
|||
|
|
border-radius: var(--gg-radius-sm);
|
|||
|
|
overflow: hidden;
|
|||
|
|
border: 1px solid var(--gg-border);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-preview img {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
object-fit: cover;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-clear {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 2px;
|
|||
|
|
right: 2px;
|
|||
|
|
width: 20px;
|
|||
|
|
height: 20px;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
background: rgba(0, 0, 0, 0.6);
|
|||
|
|
color: #fff;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-picker {
|
|||
|
|
width: 90px;
|
|||
|
|
height: 120px;
|
|||
|
|
border: 1px dashed var(--gg-border);
|
|||
|
|
border-radius: var(--gg-radius-sm);
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 6px;
|
|||
|
|
color: var(--gg-text-muted);
|
|||
|
|
font-size: 12px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.file-input {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sheet-actions {
|
|||
|
|
padding: 20px 16px 0;
|
|||
|
|
margin-top: auto;
|
|||
|
|
}
|
|||
|
|
</style>
|