diff --git a/.workbuddy/memory/2026-09-10.md b/.workbuddy/memory/2026-09-10.md index 9dfdd86..86852c0 100644 --- a/.workbuddy/memory/2026-09-10.md +++ b/.workbuddy/memory/2026-09-10.md @@ -333,3 +333,51 @@ RAM 子账号授予 `AliyunYundunGreenWebFullAccess`;②CHANGELOG 条目仍未 - 沙箱内若要用 overlay,注意 **overlay.json 的 Replace 键盘符必须小写**(`c:/...`), 大写 `C:/...` 会被 Go 规范化成 `c:\` 后匹配不上、报 `deleted in overlay`;stub 在 `dev/s3stub/s3_stub.go`。本次用 `C:/Users/Tamakyi Gekka/AppData/Local/Temp/tb-overlay2.json` 跑通。 + +--- + +## 第六轮:设置面板聚合(新增「防护设置」tab)+ 审核严格度排版修复 + +用户诉求:① 把防骚扰、屏蔽词、审核严格度、提问密码聚合到 `/user/profile` 下一个独立 tab; +② 审核严格度档位处「一个字就换行」很难看。 + +### 决策(AskUserQuestion 已确认) + +- 新 tab 名称:**防护设置**(i18n 键 `admin.tabSecurity`,tab id `security`)。 +- **「页面访问密码」一并迁入**(原先在「用户设置」面板)。 +- 旧位置**完全移除,不留跳转提示**。 + +### 改动 + +**后端** +- `route/user/features.go`:新增 `ProfileTabSecurity = "security"`,并纳入 `ProfileTab()` 白名单 + (不加入的话回跳会落到 account 面板)。 +- `route/user/password.go`:`UpdateAskPassword` / `UpdatePagePassword` 的硬编码回跳 + (`profileURLWithTab(ProfileTabQuestion)`、`"/user/profile"`)全部改为 `returnTab(ctx)`, + 跟随表单携带的 `?tab=`。 + +**模板** +- `templates/user/profile.html`:tab 栏新增「防护设置」按钮;新增 + `
`,内含 防骚扰 / 屏蔽词 / + 审核严格度 / 页面访问密码 / 提问密码 五个表单(action 均带 `?tab=security`)。 + 提问/私聊/投票面板中这些表单全部删除(**原先屏蔽词与审核严格度被重复渲染 3 份**)。 + account 面板移除「页面访问密码」卡(原编号 7 让位,账号操作改为 7)。 + JS 面板切换是全泛型(`[data-profile-tab]` / `[data-profile-panel]`),无需改动。 +- `templates/user/moderation-strictness.html`(**只用 shell/脚本改,禁用 Write/Edit**): + 1. 档位表格:档位列 `uk-table-shrink` + `white-space:nowrap`、范围列 `uk-table-expand` + —— 修掉右侧长文案把 4 字档位名(畅所欲言/正常水平/不听坏话)挤成逐字折行。 + 2. 选择区改三行:标题(显式 `display:block`)→ 下拉(独立 div)→ + 「未设置时跟随站点默认档位。」(独立 div)。 + +**i18n**:四语字典新增 `admin.tabSecurity`(防护设置 / 防護設置 / Protection / 防護設定), +插在「功能管理」组之后的**独立组**,未切开任何既有对齐组(避免 gofmt 重排污染 diff)。 + +### 验证 + +- `go test ./templates/`(含 profile 渲染、严格度回显断言 `selectedStrictnessOptions`)ok; + `./internal/i18n/` ok(四语键一致)。 +- `go build` + `go vet` `./route/user/` 均 0。 +- ⚠️ **gofmt 提醒(重要,易误判)**:`gofmt -l` 会列出**所有 CRLF 文件**(含本次完全没碰的 + `route/user/moderation.go`、`vote.go`),因为 gofmt 把 CRLF 规范成 LF。 + 剥离 CR 后比对,本次改动**零格式差异** → 格式是对的。 + **切勿跑 `gofmt -w`**,那会把文件转成 LF、造成全量 diff 污染。 diff --git a/internal/i18n/dict_en.go b/internal/i18n/dict_en.go index b500914..c6784a1 100644 --- a/internal/i18n/dict_en.go +++ b/internal/i18n/dict_en.go @@ -634,6 +634,9 @@ var enDict = map[string]string{ "admin.featureResetQuota": "Reset counter", "admin.featureSave": "Save this feature", + // Protection panel: aggregates anti-harassment / blocked words / moderation strictness / page password / ask password + "admin.tabSecurity": "Protection", + // Feature management: states and messages "feature.name.question": "Questions", "feature.name.chat": "Chats", diff --git a/internal/i18n/dict_ja.go b/internal/i18n/dict_ja.go index 799f8d7..5968abd 100644 --- a/internal/i18n/dict_ja.go +++ b/internal/i18n/dict_ja.go @@ -634,6 +634,9 @@ var jaDict = map[string]string{ "admin.featureResetQuota": "カウントをリセット", "admin.featureSave": "この機能を保存", + // コンテンツ保護パネル:防骚扰 / 屏蔽詞 / 審核の厳格度 / ページアクセスパスワード / 質問パスワード を集約 + "admin.tabSecurity": "防護設定", + // 機能管理:状態とメッセージ "feature.name.question": "質問", "feature.name.chat": "チャット", diff --git a/internal/i18n/dict_zh_cn.go b/internal/i18n/dict_zh_cn.go index 0828df6..166ec01 100644 --- a/internal/i18n/dict_zh_cn.go +++ b/internal/i18n/dict_zh_cn.go @@ -634,6 +634,9 @@ var zhCNDict = map[string]string{ "admin.featureResetQuota": "重置计数", "admin.featureSave": "保存该功能设置", + // 内容防护面板:聚合防骚扰 / 屏蔽词 / 审核严格度 / 页面访问密码 / 提问密码 + "admin.tabSecurity": "防护设置", + // 功能管理:状态与提示 "feature.name.question": "提问", "feature.name.chat": "私聊", diff --git a/internal/i18n/dict_zh_tw.go b/internal/i18n/dict_zh_tw.go index cfe942b..8575d31 100644 --- a/internal/i18n/dict_zh_tw.go +++ b/internal/i18n/dict_zh_tw.go @@ -634,6 +634,9 @@ var zhTWDict = map[string]string{ "admin.featureResetQuota": "重設計數", "admin.featureSave": "儲存該功能設定", + // 內容防護面板:聚合防騷擾 / 屏蔽詞 / 審核嚴格度 / 頁面訪問密碼 / 提問密碼 + "admin.tabSecurity": "防護設置", + // 功能管理:狀態與提示 "feature.name.question": "提問", "feature.name.chat": "私聊", diff --git a/route/user/features.go b/route/user/features.go index 4ec471b..38bbbf8 100644 --- a/route/user/features.go +++ b/route/user/features.go @@ -28,6 +28,10 @@ const ( ProfileTabQuestion = db.FeatureQuestion ProfileTabChat = db.FeatureChat ProfileTabVote = db.FeatureVote + // ProfileTabSecurity 与受控功能无关:聚合「内容防护」类设置 + // (防骚扰、屏蔽词、审核严格度、页面访问密码、提问密码), + // 这些设置横跨多个功能,原先散落在提问/私聊/投票面板(屏蔽词与审核严格度还被重复渲染 3 份)。 + ProfileTabSecurity = "security" ) // profileURLWithTab 账号设置页 + 指定面板的回跳地址,用于保存后停留在原面板。 @@ -38,7 +42,7 @@ func profileURLWithTab(tab string) string { // ProfileTab 归一化账号设置页的 tab 参数,非法值回落到用户设置面板。 func ProfileTab(tab string) string { switch tab { - case ProfileTabQuestion, ProfileTabChat, ProfileTabVote: + case ProfileTabQuestion, ProfileTabChat, ProfileTabVote, ProfileTabSecurity: return tab } return ProfileTabAccount diff --git a/route/user/password.go b/route/user/password.go index 9fb0aab..8466e33 100644 --- a/route/user/password.go +++ b/route/user/password.go @@ -17,7 +17,8 @@ import ( func UpdatePagePassword(ctx context.Context, f form.UpdatePagePassword) { // 反序列化失败(如请求体超限)时零值 Password="" 会把密码静默解除——先中止, // 不触碰现有密码。govalid 校验失败(如密码超长)同样在此拦截,避免绕过前端限制。 - if abortOnFormError(ctx, "/user/profile") { + // 该表单已随「内容防护」聚合迁入 security 面板,回跳地址跟随表单携带的 ?tab=。 + if abortOnFormError(ctx, returnTab(ctx)) { return } @@ -31,13 +32,14 @@ func UpdatePagePassword(ctx context.Context, f form.UpdatePagePassword) { ctx.SetSuccessFlash(i18n.T(ctx.Lang(), "admin.flash.pagePwdUpdated")) } } - ctx.Redirect("/user/profile") + ctx.Redirect(returnTab(ctx)) } // UpdateAskPassword 更新提问密码 func UpdateAskPassword(ctx context.Context, f form.UpdateAskPassword) { // 同上:反序列化失败或校验失败时零值 Password="" 会把提问密码静默解除。 - if abortOnFormError(ctx, profileURLWithTab(ProfileTabQuestion)) { + // 回跳地址跟随表单携带的 ?tab=(该表单已迁入「内容防护」security 面板)。 + if abortOnFormError(ctx, returnTab(ctx)) { return } @@ -51,6 +53,6 @@ func UpdateAskPassword(ctx context.Context, f form.UpdateAskPassword) { ctx.SetSuccessFlash(i18n.T(ctx.Lang(), "admin.flash.askPwdUpdated")) } } - // 提问密码属于提问功能的专属设置,保存后回到「提问」面板 - ctx.Redirect(profileURLWithTab(ProfileTabQuestion)) + // 保存后回到提交时所在的面板(内容防护面板) + ctx.Redirect(returnTab(ctx)) } diff --git a/templates/user/moderation-strictness.html b/templates/user/moderation-strictness.html index 7e227ef..c886b91 100644 --- a/templates/user/moderation-strictness.html +++ b/templates/user/moderation-strictness.html @@ -8,59 +8,65 @@ 本模板只负责展示,不重复实现判定。 --> {{ $root := .Root }} -
-
+
+ {{ $root.CSRFTokenHTML }} - {{t $root.LangCode "admin.moderationStrictness"}} + {{t $root.LangCode "admin.moderationStrictness"}} -

{{t $root.LangCode "admin.moderationStrictnessHint"}}

-

{{t $root.LangCode "admin.moderationStrictnessBaseline"}}

+

{{t $root.LangCode "admin.moderationStrictnessHint"}}

+

{{t $root.LangCode "admin.moderationStrictnessBaseline"}}

- - - - - + +
{{t $root.LangCode "admin.moderationStrictnessColumnLevel"}}{{t $root.LangCode "admin.moderationStrictnessColumnScope"}}
+ + + + - - - - + + + + - - - + + + - - - + + +
{{t $root.LangCode "admin.moderationStrictnessColumnLevel"}}{{t $root.LangCode "admin.moderationStrictnessColumnScope"}}
{{t $root.LangCode "admin.moderationStrictnessOption.lax"}}{{t $root.LangCode "admin.moderationStrictnessScope.lax"}}
{{t $root.LangCode "admin.moderationStrictnessOption.lax"}}{{t $root.LangCode "admin.moderationStrictnessScope.lax"}}
{{t $root.LangCode "admin.moderationStrictnessOption.standard"}}{{t $root.LangCode "admin.moderationStrictnessScope.standard"}}
{{t $root.LangCode "admin.moderationStrictnessOption.standard"}}{{t $root.LangCode "admin.moderationStrictnessScope.standard"}}
{{t $root.LangCode "admin.moderationStrictnessOption.strict"}}{{t $root.LangCode "admin.moderationStrictnessScope.strict"}}
{{t $root.LangCode "admin.moderationStrictnessOption.strict"}}{{t $root.LangCode "admin.moderationStrictnessScope.strict"}}
-
- +
+ {{/* 回显「最终生效」档位而非原始字段值:字段为空(跟随站点默认)时若直接比较, 三个 option 都不匹配,浏览器会默认选中第一项(宽松),与实际生效的严格档不符。 解析走 internal/template 的 moderationStrictness 函数(同一份事实来源)。 */}} {{ $current := moderationStrictness $root.LoggedUser.ModerationStrictness }} - - {{t $root.LangCode "admin.moderationStrictnessUnsetHint"}} +
+ +
+
+ {{t $root.LangCode "admin.moderationStrictnessUnsetHint"}} +
-
- +
+
diff --git a/templates/user/profile.html b/templates/user/profile.html index f0e7fe0..25a0c8e 100644 --- a/templates/user/profile.html +++ b/templates/user/profile.html @@ -501,6 +501,7 @@ +
@@ -764,22 +765,8 @@ - -
-

{{t .LangCode "admin.pagePassword"}}

-
- {{ .CSRFTokenHTML }} -
- - -
-
- -
-
-
- - +

{{t .LangCode "admin.account"}}

- -
- - {{ template "user/global-blockwords" (dict "Root" $ "Action" "/user/blockwords/update?tab=chat") }} - - {{ template "user/moderation-strictness" (dict "Root" $ "Action" "/user/moderation-strictness/update?tab=chat") }}
{{ with .FeatureVote }}{{template "user/feature-control-form" .}}{{ end }} +
+ + +
+
+
+ {{ $.CSRFTokenHTML }} + {{t $.LangCode "admin.harassment"}} +
+
+ +
+
+ +
+
+

- - {{ template "user/global-blockwords" (dict "Root" $ "Action" "/user/blockwords/update?tab=vote") }} + + {{ template "user/global-blockwords" (dict "Root" $ "Action" "/user/blockwords/update?tab=security") }} - - {{ template "user/moderation-strictness" (dict "Root" $ "Action" "/user/moderation-strictness/update?tab=vote") }} -
+ + {{ template "user/moderation-strictness" (dict "Root" $ "Action" "/user/moderation-strictness/update?tab=security") }} + +
+
+
+ {{ $.CSRFTokenHTML }} + {{t $.LangCode "admin.pagePassword"}} +
+ + +
+
+ +
+
+
+ +
+
+
+ {{ $.CSRFTokenHTML }} + {{t $.LangCode "admin.askPassword"}} +
+ + +
+
+ +
+
+
+