feat(web): add Vue workspace with file tree, tabs, autosave and live sync
- Login and first-run setup screens - File tree with inline create/rename, context menu, drag-and-drop move and trash with undo; all operations update the UI first and roll back on failure - Tabs keep a CodeMirror state each; edits autosave after 500 ms with If-Match revisions, unsent edits persist in IndexedDB, and conflicts or remote deletions surface as banners with explicit choices - WebSocket events sync external edits, moves and deletions into open tabs; reconnects trigger a full resync - Pasted or dropped images show instantly and upload to note.assets/ - Responsive layout with a swipe-in sidebar drawer on phones - Switch to TypeScript 6: vue-tsc does not support TypeScript 7 yet
This commit is contained in:
1 parent
c4d2bd4636
commit
02d904056c
28 files changed
+2231
-398
No files matched your search
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { api, ApiError } from './api'
|
||||
|
||||
const props = defineProps<{ mode: 'setup' | 'login' }>()
|
||||
const emit = defineEmits<{ done: [] }>()
|
||||
|
||||
const code = ref('')
|
||||
const password = ref('')
|
||||
const confirm = ref('')
|
||||
const error = ref('')
|
||||
const busy = ref(false)
|
||||
const isSetup = computed(() => props.mode === 'setup')
|
||||
|
||||
async function submit() {
|
||||
error.value = ''
|
||||
if (isSetup.value && password.value !== confirm.value) {
|
||||
error.value = '两次输入的密码不一致'
|
||||
return
|
||||
}
|
||||
busy.value = true
|
||||
try {
|
||||
if (isSetup.value) await api.setup(code.value, password.value)
|
||||
else await api.login(password.value)
|
||||
emit('done')
|
||||
} catch (err) {
|
||||
error.value = err instanceof ApiError ? err.message : '网络错误,请检查连接'
|
||||
} finally {
|
||||
busy.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rm-auth">
|
||||
<form class="rm-auth-card" @submit.prevent="submit">
|
||||
<h1>RawMark</h1>
|
||||
<p v-if="isSetup" class="rm-auth-hint">
|
||||
首次使用,请设置登录密码。启动码位于服务器数据目录的 <code>setup-code.txt</code> 中。
|
||||
</p>
|
||||
<label v-if="isSetup">
|
||||
<span>启动码</span>
|
||||
<input v-model="code" autocomplete="one-time-code" autocapitalize="characters" placeholder="XXXX-XXXX" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>密码</span>
|
||||
<input
|
||||
v-model="password"
|
||||
type="password"
|
||||
:autocomplete="isSetup ? 'new-password' : 'current-password'"
|
||||
:minlength="isSetup ? 8 : undefined"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
</label>
|
||||
<label v-if="isSetup">
|
||||
<span>确认密码</span>
|
||||
<input v-model="confirm" type="password" autocomplete="new-password" required />
|
||||
</label>
|
||||
<p v-if="error" class="rm-auth-error">{{ error }}</p>
|
||||
<button class="rm-btn rm-btn-primary" type="submit" :disabled="busy">{{ isSetup ? '完成设置' : '登录' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user