fix: 更新当前界面,支持多公帐号切换
This commit is contained in:
@@ -16,11 +16,45 @@ const rewriteBtn = $("rewriteBtn");
|
||||
const wechatBtn = $("wechatBtn");
|
||||
const imBtn = $("imBtn");
|
||||
const coverUploadBtn = $("coverUploadBtn");
|
||||
const logoutBtn = $("logoutBtn");
|
||||
|
||||
function countText(v) {
|
||||
return (v || "").trim().length;
|
||||
}
|
||||
|
||||
/** 多选子项用顿号拼接,可选补充用分号接在末尾 */
|
||||
function buildMultiPrompt(nameAttr, extraId) {
|
||||
const boxes = document.querySelectorAll(`input[name="${nameAttr}"]:checked`);
|
||||
const parts = Array.from(boxes).map((b) => (b.value || "").trim()).filter(Boolean);
|
||||
const extraEl = extraId ? $(extraId) : null;
|
||||
const extra = extraEl ? (extraEl.value || "").trim() : "";
|
||||
let s = parts.join("、");
|
||||
if (extra) s = s ? `${s};${extra}` : extra;
|
||||
return s;
|
||||
}
|
||||
|
||||
function updateMultiDropdownSummary(nameAttr, summaryId, emptyHint) {
|
||||
const el = $(summaryId);
|
||||
if (!el) return;
|
||||
const parts = Array.from(document.querySelectorAll(`input[name="${nameAttr}"]:checked`)).map((b) =>
|
||||
(b.value || "").trim(),
|
||||
);
|
||||
el.textContent = parts.length ? parts.join("、") : emptyHint;
|
||||
}
|
||||
|
||||
function initMultiDropdowns() {
|
||||
const pairs = [
|
||||
{ name: "audienceChip", summary: "audienceSummary", empty: "点击展开,选择目标读者…" },
|
||||
{ name: "toneChip", summary: "toneSummary", empty: "点击展开,选择语气风格…" },
|
||||
];
|
||||
pairs.forEach(({ name, summary, empty }) => {
|
||||
updateMultiDropdownSummary(name, summary, empty);
|
||||
document.querySelectorAll(`input[name="${name}"]`).forEach((cb) => {
|
||||
cb.addEventListener("change", () => updateMultiDropdownSummary(name, summary, empty));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function updateCounters() {
|
||||
$("sourceCount").textContent = `${countText($("sourceText").value)} 字`;
|
||||
$("summaryCount").textContent = `${countText($("summary").value)} 字`;
|
||||
@@ -51,6 +85,104 @@ async function postJSON(url, body) {
|
||||
return data;
|
||||
}
|
||||
|
||||
async function fetchAuthMe() {
|
||||
const res = await fetch("/api/auth/me");
|
||||
const data = await res.json();
|
||||
if (!data.ok || !data.logged_in) {
|
||||
window.location.href = "/auth?next=/";
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function renderWechatAccountSelect(me) {
|
||||
const sel = $("wechatAccountSelect");
|
||||
const hint = $("wechatAccountStatus");
|
||||
if (!sel) return;
|
||||
const list = Array.isArray(me.wechat_accounts) ? me.wechat_accounts : [];
|
||||
const activeId =
|
||||
me.active_wechat_account && me.active_wechat_account.id
|
||||
? Number(me.active_wechat_account.id)
|
||||
: 0;
|
||||
sel.innerHTML = "";
|
||||
if (!list.length) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = "";
|
||||
opt.textContent = "暂无公众号";
|
||||
sel.appendChild(opt);
|
||||
sel.disabled = true;
|
||||
if (hint) hint.textContent = "请先在「公众号设置」绑定";
|
||||
return;
|
||||
}
|
||||
sel.disabled = false;
|
||||
list.forEach((a) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = String(a.id);
|
||||
const name = (a.account_name || "未命名").trim();
|
||||
const appid = (a.appid || "").trim();
|
||||
opt.textContent = appid ? `${name} (${appid})` : name;
|
||||
if ((activeId && Number(a.id) === activeId) || a.active) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
function flashWechatAccountHint(msg, clearMs = 2600) {
|
||||
const hint = $("wechatAccountStatus");
|
||||
if (!hint) return;
|
||||
hint.textContent = msg;
|
||||
if (clearMs > 0) {
|
||||
window.clearTimeout(flashWechatAccountHint._t);
|
||||
flashWechatAccountHint._t = window.setTimeout(() => {
|
||||
hint.textContent = "";
|
||||
}, clearMs);
|
||||
}
|
||||
}
|
||||
|
||||
const wechatAccountSelect = $("wechatAccountSelect");
|
||||
if (wechatAccountSelect) {
|
||||
wechatAccountSelect.addEventListener("change", async () => {
|
||||
const id = Number(wechatAccountSelect.value || 0);
|
||||
if (!id) return;
|
||||
try {
|
||||
const out = await postJSON("/api/auth/wechat/switch", { account_id: id });
|
||||
if (!out.ok) {
|
||||
flashWechatAccountHint(out.detail || "切换失败", 4000);
|
||||
const me = await fetchAuthMe();
|
||||
if (me) renderWechatAccountSelect(me);
|
||||
return;
|
||||
}
|
||||
const me = await fetchAuthMe();
|
||||
if (me) renderWechatAccountSelect(me);
|
||||
flashWechatAccountHint("已切换");
|
||||
} catch (e) {
|
||||
flashWechatAccountHint(e.message || "切换失败", 4000);
|
||||
const me = await fetchAuthMe();
|
||||
if (me) renderWechatAccountSelect(me);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function initWechatAccountSwitch() {
|
||||
const me = await fetchAuthMe();
|
||||
if (me) renderWechatAccountSelect(me);
|
||||
}
|
||||
|
||||
async function logoutAndGoAuth() {
|
||||
try {
|
||||
await postJSON("/api/auth/logout", {});
|
||||
} catch {
|
||||
// 忽略退出接口异常,直接跳转认证页
|
||||
}
|
||||
window.location.href = "/auth?next=/";
|
||||
}
|
||||
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener("click", async () => {
|
||||
setLoading(logoutBtn, true, "退出登录", "退出中...");
|
||||
await logoutAndGoAuth();
|
||||
});
|
||||
}
|
||||
|
||||
$("rewriteBtn").addEventListener("click", async () => {
|
||||
const sourceText = $("sourceText").value.trim();
|
||||
if (sourceText.length < 20) {
|
||||
@@ -61,11 +193,13 @@ $("rewriteBtn").addEventListener("click", async () => {
|
||||
setStatus("正在改写...");
|
||||
setLoading(rewriteBtn, true, "改写并排版", "改写中...");
|
||||
try {
|
||||
const audience = buildMultiPrompt("audienceChip", "audienceExtra") || "公众号读者";
|
||||
const tone = buildMultiPrompt("toneChip", "toneExtra") || "专业、可信、可读性强";
|
||||
const data = await postJSON("/api/rewrite", {
|
||||
source_text: sourceText,
|
||||
title_hint: $("titleHint").value,
|
||||
tone: $("tone").value,
|
||||
audience: $("audience").value,
|
||||
tone,
|
||||
audience,
|
||||
keep_points: $("keepPoints").value,
|
||||
avoid_words: $("avoidWords").value,
|
||||
});
|
||||
@@ -104,6 +238,11 @@ $("wechatBtn").addEventListener("click", async () => {
|
||||
setStatus("公众号草稿发布成功");
|
||||
} catch (e) {
|
||||
setStatus(`公众号发布失败: ${e.message}`, true);
|
||||
if ((e.message || "").includes("请先登录")) {
|
||||
window.location.href = "/auth?next=/";
|
||||
} else if ((e.message || "").includes("未绑定公众号")) {
|
||||
window.location.href = "/settings";
|
||||
}
|
||||
} finally {
|
||||
setLoading(wechatBtn, false, "发布到公众号草稿箱", "发布中...");
|
||||
}
|
||||
@@ -133,6 +272,11 @@ if (coverUploadBtn) {
|
||||
} catch (e) {
|
||||
if (hint) hint.textContent = "封面上传失败,请看状态提示。";
|
||||
setStatus(`封面上传失败: ${e.message}`, true);
|
||||
if ((e.message || "").includes("请先登录")) {
|
||||
window.location.href = "/auth?next=/";
|
||||
} else if ((e.message || "").includes("未绑定公众号")) {
|
||||
window.location.href = "/settings";
|
||||
}
|
||||
} finally {
|
||||
setLoading(coverUploadBtn, false, "上传封面并绑定", "上传中...");
|
||||
}
|
||||
@@ -161,3 +305,5 @@ $("imBtn").addEventListener("click", async () => {
|
||||
});
|
||||
|
||||
updateCounters();
|
||||
initMultiDropdowns();
|
||||
initWechatAccountSwitch();
|
||||
|
||||
71
app/static/auth.js
Normal file
71
app/static/auth.js
Normal file
@@ -0,0 +1,71 @@
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function setStatus(msg, danger = false) {
|
||||
const el = $("status");
|
||||
if (!el) return;
|
||||
el.style.color = danger ? "#b42318" : "#0f5f3d";
|
||||
el.textContent = msg;
|
||||
}
|
||||
|
||||
function setLoading(button, loading, idleText, loadingText) {
|
||||
if (!button) return;
|
||||
button.disabled = loading;
|
||||
button.textContent = loading ? loadingText : idleText;
|
||||
}
|
||||
|
||||
async function postJSON(url, body) {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.detail || "请求失败");
|
||||
return data;
|
||||
}
|
||||
|
||||
function nextPath() {
|
||||
const nxt = (window.__NEXT_PATH__ || "/").trim();
|
||||
if (!nxt.startsWith("/")) return "/";
|
||||
return nxt;
|
||||
}
|
||||
|
||||
function fields() {
|
||||
return {
|
||||
username: ($("username") && $("username").value.trim()) || "",
|
||||
password: ($("password") && $("password").value) || "",
|
||||
remember_me: Boolean($("rememberMe") && $("rememberMe").checked),
|
||||
};
|
||||
}
|
||||
|
||||
async function authAction(url, button, idleText, loadingText, okMessage) {
|
||||
setLoading(button, true, idleText, loadingText);
|
||||
try {
|
||||
const data = await postJSON(url, fields());
|
||||
if (!data.ok) {
|
||||
setStatus(data.detail || "操作失败", true);
|
||||
return;
|
||||
}
|
||||
setStatus(okMessage);
|
||||
window.location.href = nextPath();
|
||||
} catch (e) {
|
||||
setStatus(e.message || "请求异常", true);
|
||||
} finally {
|
||||
setLoading(button, false, idleText, loadingText);
|
||||
}
|
||||
}
|
||||
|
||||
const loginBtn = $("loginBtn");
|
||||
const registerBtn = $("registerBtn");
|
||||
|
||||
if (loginBtn) {
|
||||
loginBtn.addEventListener("click", async () => {
|
||||
await authAction("/api/auth/login", loginBtn, "登录", "登录中...", "登录成功,正在跳转...");
|
||||
});
|
||||
}
|
||||
|
||||
if (registerBtn) {
|
||||
registerBtn.addEventListener("click", async () => {
|
||||
await authAction("/api/auth/register", registerBtn, "注册", "注册中...", "注册成功,正在跳转...");
|
||||
});
|
||||
}
|
||||
52
app/static/forgot_password.js
Normal file
52
app/static/forgot_password.js
Normal file
@@ -0,0 +1,52 @@
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function setStatus(msg, danger = false) {
|
||||
const el = $("status");
|
||||
if (!el) return;
|
||||
el.style.color = danger ? "#b42318" : "#0f5f3d";
|
||||
el.textContent = msg;
|
||||
}
|
||||
|
||||
function setLoading(button, loading, idleText, loadingText) {
|
||||
if (!button) return;
|
||||
button.disabled = loading;
|
||||
button.textContent = loading ? loadingText : idleText;
|
||||
}
|
||||
|
||||
async function postJSON(url, body) {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.detail || "请求失败");
|
||||
return data;
|
||||
}
|
||||
|
||||
const resetBtn = $("resetBtn");
|
||||
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener("click", async () => {
|
||||
setLoading(resetBtn, true, "重置密码", "提交中...");
|
||||
try {
|
||||
const out = await postJSON("/api/auth/password/forgot", {
|
||||
username: ($("username") && $("username").value.trim()) || "",
|
||||
reset_key: ($("resetKey") && $("resetKey").value.trim()) || "",
|
||||
new_password: ($("newPassword") && $("newPassword").value) || "",
|
||||
});
|
||||
if (!out.ok) {
|
||||
setStatus(out.detail || "重置失败", true);
|
||||
return;
|
||||
}
|
||||
setStatus("密码重置成功,2 秒后跳转登录页。");
|
||||
setTimeout(() => {
|
||||
window.location.href = "/auth?next=/";
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
setStatus(e.message || "重置失败", true);
|
||||
} finally {
|
||||
setLoading(resetBtn, false, "重置密码", "提交中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
153
app/static/settings.js
Normal file
153
app/static/settings.js
Normal file
@@ -0,0 +1,153 @@
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
function setStatus(msg, danger = false) {
|
||||
const el = $("status");
|
||||
if (!el) return;
|
||||
el.style.color = danger ? "#b42318" : "#0f5f3d";
|
||||
el.textContent = msg;
|
||||
}
|
||||
|
||||
function setLoading(button, loading, idleText, loadingText) {
|
||||
if (!button) return;
|
||||
button.disabled = loading;
|
||||
button.textContent = loading ? loadingText : idleText;
|
||||
}
|
||||
|
||||
async function postJSON(url, body) {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.detail || "请求失败");
|
||||
return data;
|
||||
}
|
||||
|
||||
async function authMe() {
|
||||
const res = await fetch("/api/auth/me");
|
||||
const data = await res.json();
|
||||
if (!data.logged_in) {
|
||||
window.location.href = "/auth?next=/settings";
|
||||
return null;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function renderAccounts(me) {
|
||||
const sel = $("accountSelect");
|
||||
if (!sel) return;
|
||||
const list = Array.isArray(me.wechat_accounts) ? me.wechat_accounts : [];
|
||||
const active = me.active_wechat_account && me.active_wechat_account.id ? Number(me.active_wechat_account.id) : 0;
|
||||
sel.innerHTML = "";
|
||||
if (!list.length) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = "";
|
||||
opt.textContent = "暂无公众号,请先绑定";
|
||||
sel.appendChild(opt);
|
||||
return;
|
||||
}
|
||||
list.forEach((a) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = String(a.id);
|
||||
opt.textContent = `${a.account_name} (${a.appid})`;
|
||||
if ((active && a.id === active) || a.active) opt.selected = true;
|
||||
sel.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const me = await authMe();
|
||||
if (!me) return;
|
||||
renderAccounts(me);
|
||||
}
|
||||
|
||||
const accountSelect = $("accountSelect");
|
||||
const bindBtn = $("bindBtn");
|
||||
const logoutBtn = $("logoutBtn");
|
||||
const changePwdBtn = $("changePwdBtn");
|
||||
|
||||
if (accountSelect) {
|
||||
accountSelect.addEventListener("change", async () => {
|
||||
const id = Number(accountSelect.value || 0);
|
||||
if (!id) return;
|
||||
try {
|
||||
const out = await postJSON("/api/auth/wechat/switch", { account_id: id });
|
||||
if (!out.ok) {
|
||||
setStatus(out.detail || "切换失败", true);
|
||||
return;
|
||||
}
|
||||
setStatus("已切换当前公众号。");
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setStatus(e.message || "切换失败", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (bindBtn) {
|
||||
bindBtn.addEventListener("click", async () => {
|
||||
setLoading(bindBtn, true, "绑定并设为当前账号", "绑定中...");
|
||||
try {
|
||||
const out = await postJSON("/api/auth/wechat/bind", {
|
||||
account_name: ($("accountName") && $("accountName").value.trim()) || "",
|
||||
appid: ($("appid") && $("appid").value.trim()) || "",
|
||||
secret: ($("secret") && $("secret").value.trim()) || "",
|
||||
author: "",
|
||||
thumb_media_id: "",
|
||||
thumb_image_path: "",
|
||||
});
|
||||
if (!out.ok) {
|
||||
setStatus(out.detail || "绑定失败", true);
|
||||
return;
|
||||
}
|
||||
setStatus("公众号绑定成功,已切换为当前账号。");
|
||||
if ($("appid")) $("appid").value = "";
|
||||
if ($("secret")) $("secret").value = "";
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setStatus(e.message || "绑定失败", true);
|
||||
} finally {
|
||||
setLoading(bindBtn, false, "绑定并设为当前账号", "绑定中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener("click", async () => {
|
||||
setLoading(logoutBtn, true, "退出登录", "退出中...");
|
||||
try {
|
||||
await postJSON("/api/auth/logout", {});
|
||||
window.location.href = "/auth?next=/";
|
||||
} catch (e) {
|
||||
setStatus(e.message || "退出失败", true);
|
||||
} finally {
|
||||
setLoading(logoutBtn, false, "退出登录", "退出中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (changePwdBtn) {
|
||||
changePwdBtn.addEventListener("click", async () => {
|
||||
setLoading(changePwdBtn, true, "修改密码", "提交中...");
|
||||
try {
|
||||
const out = await postJSON("/api/auth/password/change", {
|
||||
old_password: ($("oldPassword") && $("oldPassword").value) || "",
|
||||
new_password: ($("newPassword") && $("newPassword").value) || "",
|
||||
});
|
||||
if (!out.ok) {
|
||||
setStatus(out.detail || "修改密码失败", true);
|
||||
return;
|
||||
}
|
||||
setStatus("密码修改成功。");
|
||||
if ($("oldPassword")) $("oldPassword").value = "";
|
||||
if ($("newPassword")) $("newPassword").value = "";
|
||||
} catch (e) {
|
||||
setStatus(e.message || "修改密码失败", true);
|
||||
} finally {
|
||||
setLoading(changePwdBtn, false, "修改密码", "提交中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refresh();
|
||||
@@ -22,6 +22,12 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.simple-page {
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
max-width: 1240px;
|
||||
height: 72px;
|
||||
@@ -52,6 +58,120 @@ body {
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.wechat-account-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
max-width: min(420px, 100%);
|
||||
}
|
||||
|
||||
.wechat-account-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topbar-select {
|
||||
min-width: 160px;
|
||||
max-width: 260px;
|
||||
flex: 1 1 auto;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.topbar-select:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.wechat-account-status {
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.topbar-btn {
|
||||
width: auto;
|
||||
margin-top: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.subtle-link {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: var(--accent-2);
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 10px;
|
||||
padding: 8px 12px;
|
||||
background: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.simple-wrap {
|
||||
max-width: 760px;
|
||||
margin: 48px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.simple-panel {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 16px 0 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.actions-inline {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.check-row {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.check-label {
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.check-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.layout {
|
||||
max-width: 1240px;
|
||||
height: calc(100vh - 72px);
|
||||
@@ -106,12 +226,114 @@ label {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.multi-field .field-head label {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.multi-field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.multi-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.multi-dropdown > summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.multi-dropdown > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.multi-dropdown > summary::after {
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 6px solid var(--muted);
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.multi-dropdown[open] > summary::after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.multi-dropdown[open] > summary {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-soft);
|
||||
}
|
||||
|
||||
.multi-dropdown-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.multi-dropdown-body {
|
||||
margin-top: 4px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 6px 4px;
|
||||
background: var(--panel);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.1);
|
||||
}
|
||||
|
||||
.multi-dropdown-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
border-radius: 8px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.multi-dropdown-option:hover {
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
|
||||
.multi-dropdown-option input {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
|
||||
.multi-extra {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
button {
|
||||
width: 100%;
|
||||
@@ -128,6 +350,7 @@ textarea {
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus,
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #93c5fd;
|
||||
@@ -289,4 +512,15 @@ button:disabled {
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.topbar-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.actions-inline {
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user