feat: add DropZone and App layout skeleton
This commit is contained in:
99
src/components/DropZone.vue
Normal file
99
src/components/DropZone.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useCalendar } from '../composables/useCalendar.js'
|
||||
|
||||
const { loadFile } = useCalendar()
|
||||
const dragOver = ref(false)
|
||||
const error = ref('')
|
||||
|
||||
function onDrop(e) {
|
||||
dragOver.value = false
|
||||
error.value = ''
|
||||
const file = e.dataTransfer.files[0]
|
||||
if (!file) return
|
||||
if (!file.name.toLowerCase().endsWith('.ics')) {
|
||||
error.value = '请选择 .ics 文件'
|
||||
return
|
||||
}
|
||||
loadFile(file).catch((err) => {
|
||||
error.value = '解析失败:' + (err.message || err)
|
||||
})
|
||||
}
|
||||
|
||||
function onPick(e) {
|
||||
error.value = ''
|
||||
const file = e.target.files[0]
|
||||
if (!file) return
|
||||
loadFile(file).catch((err) => {
|
||||
error.value = '解析失败:' + (err.message || err)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="dropzone"
|
||||
:class="{ active: dragOver }"
|
||||
@dragover.prevent="dragOver = true"
|
||||
@dragleave.prevent="dragOver = false"
|
||||
@drop.prevent="onDrop"
|
||||
>
|
||||
<div class="dropzone-content">
|
||||
<div class="icon">📅</div>
|
||||
<p class="hint">拖入 .ics 文件,或</p>
|
||||
<label class="pick-btn">
|
||||
点击选择文件
|
||||
<input type="file" accept=".ics" @change="onPick" hidden />
|
||||
</label>
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dropzone {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px dashed #bdc3c7;
|
||||
border-radius: 12px;
|
||||
margin: 40px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.dropzone.active {
|
||||
border-color: #3498db;
|
||||
background: #eaf4fc;
|
||||
}
|
||||
.dropzone-content {
|
||||
text-align: center;
|
||||
}
|
||||
.icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.hint {
|
||||
color: #7f8c8d;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.pick-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
background: #3498db;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.pick-btn:hover {
|
||||
background: #2980b9;
|
||||
}
|
||||
.error {
|
||||
color: #e74c3c;
|
||||
font-size: 13px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user