forked from dsh/panel-auth
refactor: move logout/change-password UI from floating widget to settings
The injected floating buttons overlapped the mobile composer send button. Replace the tapIndex-injected widget with a proper client plugin (panel-auth-ui, ui/ package): registers an account & security section in the panel settings via the settings.section slot, with change-password form and logout action. Host plugin keeps only the /panel-auth/change- password and /panel-auth/logout endpoints. - ui/: ModuleLoader-format client bundle (dsh.client declaration, exports ./client and ./package.json), host no-op stub, bare-package symlink required in the profile node_modules (documented in README). - Host: renderAuthWidget/injectWidget/tapIndex injection removed. - Tests: widget tests removed; change-password flows unchanged.
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
// panel-auth-ui client bundle (ModuleLoader format, served by the client
|
||||
// module system). Registers an "账号与安全" section in the panel settings
|
||||
// with change-password and logout actions. Depends on the panel-auth host
|
||||
// plugin's /panel-auth/change-password and /panel-auth/logout endpoints.
|
||||
window.__ModuleLoader__.load({
|
||||
id: "panel-auth-ui",
|
||||
factory: (require) => {
|
||||
const React = require("react")
|
||||
|
||||
const inputStyle = {
|
||||
width: "100%",
|
||||
boxSizing: "border-box",
|
||||
padding: "9px 11px",
|
||||
fontSize: 14,
|
||||
borderRadius: 10,
|
||||
border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.14))",
|
||||
background: "var(--dsw-alias-bg-module-platform, rgba(255,255,255,0.05))",
|
||||
color: "var(--dsw-alias-label-primary, #e8eaf2)",
|
||||
outline: "none",
|
||||
}
|
||||
const cardStyle = {
|
||||
border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.12))",
|
||||
borderRadius: 16,
|
||||
padding: "18px 16px",
|
||||
}
|
||||
const titleStyle = {
|
||||
margin: "0 0 6px",
|
||||
fontSize: 15,
|
||||
fontWeight: 600,
|
||||
color: "var(--dsw-alias-label-primary, #e8eaf2)",
|
||||
}
|
||||
const hintStyle = {
|
||||
margin: "0 0 14px",
|
||||
fontSize: 12.5,
|
||||
color: "var(--dsw-alias-label-secondary, #9aa1b5)",
|
||||
}
|
||||
const buttonStyle = (tone) => ({
|
||||
padding: "9px 16px",
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
borderRadius: 10,
|
||||
cursor: "pointer",
|
||||
border: tone === "danger" ? "1px solid rgba(248,113,113,0.5)" : "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.16))",
|
||||
background: tone === "danger" ? "rgba(248,113,113,0.12)" : "var(--dsw-alias-bg-module-platform, rgba(255,255,255,0.06))",
|
||||
color: tone === "danger" ? "#f87171" : "var(--dsw-alias-label-primary, #e8eaf2)",
|
||||
})
|
||||
const messageStyle = (ok) => ({
|
||||
margin: "0 0 12px",
|
||||
padding: "8px 11px",
|
||||
fontSize: 12.5,
|
||||
borderRadius: 8,
|
||||
border: ok ? "1px solid rgba(74,222,128,0.4)" : "1px solid rgba(248,113,113,0.4)",
|
||||
background: ok ? "rgba(74,222,128,0.12)" : "rgba(248,113,113,0.12)",
|
||||
color: ok ? "#4ade80" : "#f87171",
|
||||
})
|
||||
|
||||
function Field(props) {
|
||||
return React.createElement(
|
||||
"label",
|
||||
{ style: { display: "block", marginBottom: 12 } },
|
||||
React.createElement("div", { style: { fontSize: 12, marginBottom: 6, color: "var(--dsw-alias-label-secondary, #9aa1b5)" } }, props.label),
|
||||
React.createElement("input", {
|
||||
type: "password",
|
||||
autoComplete: props.autoComplete || "off",
|
||||
value: props.value,
|
||||
onChange: (event) => props.onChange(event.target.value),
|
||||
style: inputStyle,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function PanelAuthSection() {
|
||||
const [oldPw, setOldPw] = React.useState("")
|
||||
const [newPw, setNewPw] = React.useState("")
|
||||
const [confirm, setConfirm] = React.useState("")
|
||||
const [busy, setBusy] = React.useState(false)
|
||||
const [message, setMessage] = React.useState(null)
|
||||
|
||||
const submit = () => {
|
||||
if (busy) return
|
||||
if (!oldPw || !newPw) {
|
||||
setMessage({ ok: false, text: "请填写当前密码和新密码" })
|
||||
return
|
||||
}
|
||||
if (newPw.length < 8) {
|
||||
setMessage({ ok: false, text: "新密码至少 8 位" })
|
||||
return
|
||||
}
|
||||
if (newPw !== confirm) {
|
||||
setMessage({ ok: false, text: "两次输入的新密码不一致" })
|
||||
return
|
||||
}
|
||||
setBusy(true)
|
||||
setMessage(null)
|
||||
fetch("/panel-auth/change-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({ oldPassword: oldPw, newPassword: newPw }),
|
||||
})
|
||||
.then((response) => response.json().then((data) => ({ ok: response.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok) {
|
||||
setOldPw("")
|
||||
setNewPw("")
|
||||
setConfirm("")
|
||||
setMessage({ ok: true, text: (data && data.message) || "密码已更新" })
|
||||
} else {
|
||||
setMessage({ ok: false, text: (data && data.message) || "修改失败" })
|
||||
}
|
||||
})
|
||||
.catch(() => setMessage({ ok: false, text: "网络错误,请稍后再试" }))
|
||||
.finally(() => setBusy(false))
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
window.location.href = "/panel-auth/logout"
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
"div",
|
||||
{ style: { display: "flex", flexDirection: "column", gap: 18 } },
|
||||
React.createElement(
|
||||
"section",
|
||||
{ style: cardStyle },
|
||||
React.createElement("h3", { style: titleStyle }, "修改密码"),
|
||||
React.createElement("p", { style: hintStyle }, "需要输入当前密码验证身份,修改后无需重新登录。"),
|
||||
React.createElement(Field, { label: "当前密码", autoComplete: "current-password", value: oldPw, onChange: setOldPw }),
|
||||
React.createElement(Field, { label: "新密码(至少 8 位)", autoComplete: "new-password", value: newPw, onChange: setNewPw }),
|
||||
React.createElement(Field, { label: "确认新密码", autoComplete: "new-password", value: confirm, onChange: setConfirm }),
|
||||
message === null ? null : React.createElement("div", { style: messageStyle(message.ok) }, message.text),
|
||||
React.createElement("button", { onClick: submit, disabled: busy, style: { ...buttonStyle("primary"), opacity: busy ? 0.6 : 1 } }, busy ? "提交中…" : "确认修改"),
|
||||
),
|
||||
React.createElement(
|
||||
"section",
|
||||
{ style: cardStyle },
|
||||
React.createElement("h3", { style: titleStyle }, "退出登录"),
|
||||
React.createElement("p", { style: hintStyle }, "清除本浏览器的登录 Cookie 并返回登录页。"),
|
||||
React.createElement("button", { onClick: logout, style: buttonStyle("danger") }, "退出登录"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
apply(ctx) {
|
||||
const slots = ctx.get("slots")
|
||||
if (slots === undefined) return
|
||||
slots.inject("settings.section", () => slots.register(
|
||||
{ name: "settings.section", id: "panel-auth", order: 30, label: () => "账号与安全" },
|
||||
(props) => React.createElement(PanelAuthSection, { close: props.close }),
|
||||
))
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
// panel-auth-ui — host-side no-op stub.
|
||||
// The loader mounts every row host-side; the real work lives in client.js,
|
||||
// served by the client module system under the dsh.client declaration.
|
||||
export default {
|
||||
name: 'panel-auth-ui',
|
||||
apply() {},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "panel-auth-ui",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./client": "./client.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"dsh": {
|
||||
"client": {
|
||||
"inject": [
|
||||
"@deepseek-ai/dsh-client-runtime",
|
||||
"@deepseek-ai/dsh-client-ui-settings"
|
||||
],
|
||||
"platform": "web",
|
||||
"immediately": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user