feat: bootstrap dodo phase one
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { CalendarDays, Check, CirclePlus, Inbox, ListTodo, Menu, Search, Settings, Sprout } from 'lucide-vue-next'
|
||||
|
||||
type TaskList = { id:string; name:string; is_inbox:boolean }
|
||||
type Task = { id:string; list_id:string; title:string; description:string; priority:number; completed:boolean; version:number; due_at:string|null }
|
||||
const initialized=ref<boolean|null>(null), authenticated=ref(false), username=ref(''), password=ref(''), lists=ref<TaskList[]>([]), tasks=ref<Task[]>([]), activeList=ref(''), title=ref(''), error=ref('')
|
||||
const activeName=computed(()=>lists.value.find(x=>x.id===activeList.value)?.name||'今天')
|
||||
async function api(path:string, options:RequestInit={}) { const r=await fetch('/api/v1'+path,{credentials:'include',headers:{'Content-Type':'application/json',...(options.headers||{})},...options}); if(!r.ok) throw new Error((await r.json()).detail||'请求失败'); return r.status===204?null:r.json() }
|
||||
async function bootstrap(){try{const s=await api('/setup/status');initialized.value=s.initialized;if(s.initialized){await api('/me');authenticated.value=true;await load()}}catch{authenticated.value=false}}
|
||||
async function submitAuth(){error.value='';try{if(!initialized.value){await api('/setup/initialize',{method:'POST',body:JSON.stringify({username:username.value,password:password.value})});initialized.value=true}else await api('/auth/login',{method:'POST',body:JSON.stringify({username:username.value,password:password.value})});authenticated.value=true;await load()}catch(e){error.value=(e as Error).message}}
|
||||
async function load(){lists.value=await api('/lists');activeList.value ||= lists.value[0]?.id;tasks.value=(await api('/tasks')).items}
|
||||
async function addTask(){if(!title.value.trim()||!activeList.value)return;const t=await api('/tasks',{method:'POST',body:JSON.stringify({title:title.value.trim(),list_id:activeList.value})});tasks.value.push(t);title.value=''}
|
||||
async function toggle(t:Task){const updated=await api(`/tasks/${t.id}`,{method:'PATCH',body:JSON.stringify({completed:!t.completed,version:t.version})});Object.assign(t,updated)}
|
||||
function focusQuick(){document.querySelector<HTMLInputElement>('.quick input')?.focus()}
|
||||
onMounted(bootstrap)
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="initialized===null" class="center">正在打开 dodo…</div>
|
||||
<div v-else-if="!authenticated" class="auth-shell"><section class="auth-card"><div class="brand">do<span>do</span></div><p>{{ initialized?'欢迎回来':'创建你的 dodo' }}</p><input v-model="username" placeholder="用户名"><input v-model="password" type="password" placeholder="密码(至少12位)" @keyup.enter="submitAuth"><button @click="submitAuth">{{ initialized?'登录':'开始使用' }}</button><small v-if="error">{{error}}</small></section></div>
|
||||
<div v-else class="shell">
|
||||
<aside><div class="brand small">do<span>do</span></div><nav><button class="active"><Inbox/>收集箱</button><button><ListTodo/>今天</button><button><CalendarDays/>最近7天</button><button><Sprout/>习惯</button></nav><div class="lists"><label>我的清单</label><button v-for="l in lists" :key="l.id" @click="activeList=l.id"><i></i>{{l.name}}</button></div><button class="settings"><Settings/>设置</button></aside>
|
||||
<main><header><button class="mobile-menu"><Menu/></button><div><p>今天也慢慢来</p><h1>{{activeName}}</h1></div><button class="search"><Search/>搜索</button></header><form @submit.prevent="addTask" class="quick"><CirclePlus/><input v-model="title" placeholder="添加任务"><button>添加</button></form><section class="task-list"><article v-for="t in tasks.filter(x=>x.list_id===activeList)" :key="t.id" :class="{done:t.completed}"><button class="check" @click="toggle(t)"><Check v-if="t.completed"/></button><div><strong>{{t.title}}</strong><p v-if="t.description">{{t.description}}</p></div><span v-if="t.priority" class="priority">P{{4-t.priority}}</span></article><div v-if="!tasks.filter(x=>x.list_id===activeList).length" class="empty"><ListTodo/><b>这里还很安静</b><span>写下第一件想完成的小事吧</span></div></section></main>
|
||||
<aside class="detail"><span>任务详情</span><div class="paper"><b>选中一个任务</b><p>日期、提醒、标签和备注会出现在这里。</p></div></aside>
|
||||
<nav class="bottom"><button><ListTodo/><span>今天</span></button><button><Inbox/><span>任务</span></button><button><CalendarDays/><span>日历</span></button><button><Sprout/><span>习惯</span></button></nav>
|
||||
<button class="fab" @click="focusQuick"><CirclePlus/></button>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user