feat: add EventList and HeaderBar with organize/undo/download
This commit is contained in:
77
src/components/HeaderBar.vue
Normal file
77
src/components/HeaderBar.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script setup>
|
||||
import { useCalendar } from '../composables/useCalendar.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const { state, canUndo, canRedo, organize, undo, redo, serialize, markSaved } = useCalendar()
|
||||
const fileInput = ref(null)
|
||||
|
||||
function onOrganize() {
|
||||
const stats = organize()
|
||||
if (stats && stats.series === 0) {
|
||||
alert('未发现可合并的重复模式')
|
||||
}
|
||||
}
|
||||
|
||||
function onImport() {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
function onFilePicked(e) {
|
||||
const file = e.target.files[0]
|
||||
if (!file) return
|
||||
const { loadFile } = useCalendar()
|
||||
loadFile(file).catch((err) => alert('解析失败:' + (err.message || err)))
|
||||
e.target.value = ''
|
||||
}
|
||||
|
||||
function onDownload() {
|
||||
if (state.dirty && !confirm('有未保存的更改,仍要下载?')) return
|
||||
const text = serialize()
|
||||
const blob = new Blob([text], { type: 'text/calendar' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
const base = state.fileName ? state.fileName.replace(/\.ics$/i, '') : 'calendar'
|
||||
a.href = url
|
||||
a.download = base + '.edited.ics'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
markSaved()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-primary" @click="onOrganize" :disabled="state.events.length === 0">
|
||||
🔁 整理去重
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="undo" :disabled="!canUndo">↶ 撤销</button>
|
||||
<button class="btn btn-secondary" @click="redo" :disabled="!canRedo">↷ 重做</button>
|
||||
<button class="btn btn-secondary" @click="onImport">📥 导入文件</button>
|
||||
<button class="btn btn-success" @click="onDownload" :disabled="state.events.length === 0">
|
||||
⬇ 下载 ICS
|
||||
</button>
|
||||
<span v-if="state.dirty" class="dirty-dot" title="有未保存更改">●</span>
|
||||
<input ref="fileInput" type="file" accept=".ics" @change="onFilePicked" hidden />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header-actions { display: flex; gap: 10px; align-items: center; }
|
||||
.btn {
|
||||
padding: 7px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn-primary { background: #3498db; color: #fff; }
|
||||
.btn-primary:hover:not(:disabled) { background: #2980b9; }
|
||||
.btn-success { background: #27ae60; color: #fff; }
|
||||
.btn-success:hover:not(:disabled) { background: #229954; }
|
||||
.btn-secondary { background: #ecf0f1; color: #2c3e50; }
|
||||
.btn-secondary:hover:not(:disabled) { background: #d5dbdb; }
|
||||
.dirty-dot { color: #e74c3c; font-size: 16px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user