feat: 皮肤包上传区支持拖拽上传

- 抽出 uploadSkinFile 复用点击与拖拽两种上传方式
- 拖入时高亮上传区并提示,松开即触发上传

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-07-29 15:20:04 +09:00
co-authored by Claude Opus 4.8
parent 5014376b81
commit 605c09c88a
+24 -6
View File
@@ -527,8 +527,13 @@
</div> </div>
</div> </div>
<div v-else class="skins-hint">暂无皮肤包</div> <div v-else class="skins-hint">暂无皮肤包</div>
<label class="btn btn-outline skin-upload-btn" :class="{ disabled: skinUploading }"> <label class="btn btn-outline skin-upload-btn"
{{ skinUploading ? '上传中...' : '上传皮肤包 (.zip)' }} :class="{ disabled: skinUploading, dragover: skinDragover }"
@dragover.prevent="skinDragover = true"
@dragenter.prevent="skinDragover = true"
@dragleave.prevent="skinDragover = false"
@drop.prevent="onSkinDrop">
{{ skinUploading ? '上传中...' : (skinDragover ? '松开鼠标上传' : '上传皮肤包 (.zip或拖拽到此处)') }}
<input type="file" accept=".zip" @change="uploadSkin" :disabled="skinUploading" hidden> <input type="file" accept=".zip" @change="uploadSkin" :disabled="skinUploading" hidden>
</label> </label>
</div> </div>
@@ -619,6 +624,7 @@ const editingAppId = ref(null)
const appForm = ref({}) const appForm = ref({})
const appSkins = ref([]) const appSkins = ref([])
const skinUploading = ref(false) const skinUploading = ref(false)
const skinDragover = ref(false)
const showSchemeModal = ref(false) const showSchemeModal = ref(false)
const editingSchemeId = ref(null) const editingSchemeId = ref(null)
@@ -866,9 +872,9 @@ const loadSkins = async () => {
} catch { appSkins.value = [] } } catch { appSkins.value = [] }
} }
const uploadSkin = async (event) => { const uploadSkinFile = async (file) => {
const file = event.target.files[0]
if (!file) return if (!file) return
if (!file.name.toLowerCase().endsWith('.zip')) { alert('仅支持 .zip 格式'); return }
const uploadKey = appForm.value.upload_key const uploadKey = appForm.value.upload_key
if (!uploadKey) { alert('请先保存 APP 后再上传皮肤包'); return } if (!uploadKey) { alert('请先保存 APP 后再上传皮肤包'); return }
skinUploading.value = true skinUploading.value = true
@@ -890,7 +896,6 @@ const uploadSkin = async (event) => {
Object.keys(certs).forEach(certType => { Object.keys(certs).forEach(certType => {
if (!certs[certType].theme) certs[certType].theme = skinTheme if (!certs[certType].theme) certs[certType].theme = skinTheme
}) })
event.target.value = ''
} catch (e) { } catch (e) {
alert('上传失败: ' + e.message) alert('上传失败: ' + e.message)
} finally { } finally {
@@ -898,6 +903,18 @@ const uploadSkin = async (event) => {
} }
} }
const uploadSkin = async (event) => {
await uploadSkinFile(event.target.files[0])
event.target.value = ''
}
const onSkinDrop = (event) => {
skinDragover.value = false
if (skinUploading.value) return
const file = event.dataTransfer.files[0]
uploadSkinFile(file)
}
const updateSkin = async (skinName, event) => { const updateSkin = async (skinName, event) => {
const file = event.target.files[0] const file = event.target.files[0]
if (!file) return if (!file) return
@@ -1211,6 +1228,7 @@ const formatJson = () => {
.btn-outline { background: white; border: 1px solid #1890ff; color: #1890ff; cursor: pointer; padding: 6px 16px; border-radius: 6px; font-size: 13px; display: inline-block; } .btn-outline { background: white; border: 1px solid #1890ff; color: #1890ff; cursor: pointer; padding: 6px 16px; border-radius: 6px; font-size: 13px; display: inline-block; }
.btn-outline:hover { background: #e6f7ff; } .btn-outline:hover { background: #e6f7ff; }
.btn-outline.disabled { opacity: 0.5; cursor: not-allowed; } .btn-outline.disabled { opacity: 0.5; cursor: not-allowed; }
.skin-upload-btn { margin-top: 8px; } .skin-upload-btn { margin-top: 8px; transition: background 0.15s, border-color 0.15s; }
.skin-upload-btn.dragover { border-color: #1890ff; border-style: dashed; background: #e6f7ff; color: #1890ff; }
.theme-select { width: 100%; padding: 10px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; } .theme-select { width: 100%; padding: 10px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; }
</style> </style>