feat: 实现文件夹和书签的持久排序与拖拽功能
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { apiFetch } from "../../lib/api";
|
||||
import { getToken, setToken } from "../../lib/extStorage";
|
||||
import { setToken } from "../../lib/extStorage";
|
||||
import { clearLocalState, mergeLocalToUser } from "../../lib/localData";
|
||||
|
||||
const router = useRouter();
|
||||
@@ -35,20 +35,13 @@ async function submit() {
|
||||
// After merge, keep extension in cloud mode
|
||||
await clearLocalState();
|
||||
|
||||
await router.push("/my");
|
||||
await router.replace("/");
|
||||
} catch (e) {
|
||||
error.value = e.message || String(e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
const token = await getToken();
|
||||
if (!token) return;
|
||||
await setToken("");
|
||||
await router.push("/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -58,7 +51,6 @@ async function logout() {
|
||||
<div class="row">
|
||||
<button class="tab" :class="{ active: mode === 'login' }" @click="mode = 'login'">登录</button>
|
||||
<button class="tab" :class="{ active: mode === 'register' }" @click="mode = 'register'">注册</button>
|
||||
<button class="tab" @click="logout">退出</button>
|
||||
</div>
|
||||
|
||||
<div class="form">
|
||||
|
||||
74
apps/extension/src/options/pages/MorePage.vue
Normal file
74
apps/extension/src/options/pages/MorePage.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getToken, setToken } from "../../lib/extStorage";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const token = ref("");
|
||||
const loggedIn = computed(() => Boolean(token.value));
|
||||
|
||||
const webBaseUrl = import.meta.env.VITE_WEB_BASE_URL || "http://localhost:5173";
|
||||
|
||||
async function refresh() {
|
||||
token.value = await getToken();
|
||||
}
|
||||
|
||||
function openWeb() {
|
||||
const url = String(webBaseUrl || "").trim();
|
||||
if (!url) return;
|
||||
if (typeof chrome !== "undefined" && chrome.tabs?.create) chrome.tabs.create({ url });
|
||||
else window.open(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await setToken("");
|
||||
await refresh();
|
||||
await router.replace("/login");
|
||||
}
|
||||
|
||||
onMounted(refresh);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="page">
|
||||
<h1 class="h1">更多操作</h1>
|
||||
|
||||
<p v-if="!loggedIn" class="muted">当前未登录,将跳转到登录页。</p>
|
||||
|
||||
<div class="card">
|
||||
<button class="btn" type="button" @click="openWeb">跳转 Web</button>
|
||||
<button class="btn btn--secondary" type="button" @click="logout">退出登录</button>
|
||||
<p class="hint">Web 地址来自环境变量:VITE_WEB_BASE_URL</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page { padding: 14px; }
|
||||
.h1 { margin: 0 0 10px; font-size: 18px; }
|
||||
.card {
|
||||
max-width: 560px;
|
||||
border: 1px solid rgba(255,255,255,0.65);
|
||||
background: var(--bb-card);
|
||||
border-radius: 18px;
|
||||
padding: 12px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.btn {
|
||||
border: 1px solid rgba(255,255,255,0.25);
|
||||
border-radius: 14px;
|
||||
padding: 10px 12px;
|
||||
background: linear-gradient(135deg, var(--bb-primary), var(--bb-cta));
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn--secondary {
|
||||
background: rgba(255,255,255,0.55);
|
||||
color: var(--bb-text);
|
||||
border-color: var(--bb-border);
|
||||
}
|
||||
.muted { color: rgba(19, 78, 74, 0.72); font-size: 12px; }
|
||||
.hint { color: rgba(19, 78, 74, 0.72); font-size: 12px; margin: 0; }
|
||||
</style>
|
||||
@@ -44,6 +44,7 @@ async function add() {
|
||||
|
||||
async function remove(id) {
|
||||
if (mode.value !== "local") return;
|
||||
if (!confirm("确定删除该书签?")) return;
|
||||
await markLocalDeleted(id);
|
||||
await load();
|
||||
}
|
||||
@@ -86,7 +87,16 @@ onMounted(load);
|
||||
.list { list-style: none; padding: 0; display: grid; grid-template-columns: 1fr; gap: 10px; }
|
||||
@media (min-width: 900px) { .list { grid-template-columns: 1fr 1fr; } }
|
||||
.card { border: 1px solid #e5e7eb; border-radius: 14px; padding: 12px; background: white; }
|
||||
.title { color: #111827; font-weight: 700; text-decoration: none; }
|
||||
.title {
|
||||
color: #111827;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.muted { color: #475569; font-size: 12px; overflow-wrap: anywhere; margin-top: 6px; }
|
||||
.error { color: #b91c1c; }
|
||||
.muted { color: #475569; font-size: 12px; }
|
||||
|
||||
@@ -47,7 +47,15 @@ onMounted(load);
|
||||
.list { list-style: none; padding: 0; display: grid; grid-template-columns: 1fr; gap: 10px; }
|
||||
@media (min-width: 900px) { .list { grid-template-columns: 1fr 1fr; } }
|
||||
.card { border: 1px solid #e5e7eb; border-radius: 14px; padding: 12px; background: white; }
|
||||
.title { color: #111827; font-weight: 700; text-decoration: none; }
|
||||
.title {
|
||||
color: #111827;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.muted { color: #475569; font-size: 12px; overflow-wrap: anywhere; margin-top: 6px; }
|
||||
.error { color: #b91c1c; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user