feat: reorganize system settings tabs

This commit is contained in:
kuaifan 2026-08-13 09:16:58 +00:00
parent 58f47be68f
commit 139a7a168b
36 changed files with 357 additions and 175 deletions

View File

@ -2729,3 +2729,9 @@ WebDAV 路径冲突
请输入设备名称
撤销应用密码
撤销后,使用此应用密码连接的设备将立即断开。
帐号与安全
基础设置
任务设置
消息设置
文件与存储
上传与媒体处理

View File

@ -39466,5 +39466,77 @@
"fr": "Vous renommez un fichier privé appartenant à (%T1). Cette action modifiera directement le nom du fichier.",
"id": "Anda akan mengganti nama file pribadi milik (%T1). Tindakan ini akan langsung mengubah nama file.",
"ru": "Вы переименовываете личный файл пользователя (%T1). Это действие напрямую изменит имя файла."
},
{
"key": "帐号与安全",
"zh": "",
"zh-CHT": "帳號與安全",
"en": "Account & Security",
"ko": "계정 및 보안",
"ja": "アカウントとセキュリティ",
"de": "Konto und Sicherheit",
"fr": "Compte et securite",
"id": "Akun dan Keamanan",
"ru": "Аккаунт и безопасность"
},
{
"key": "基础设置",
"zh": "",
"zh-CHT": "基礎設置",
"en": "General Settings",
"ko": "기본 설정",
"ja": "基本設定",
"de": "Grundeinstellungen",
"fr": "Parametres generaux",
"id": "Pengaturan Dasar",
"ru": "Основные настройки"
},
{
"key": "任务设置",
"zh": "",
"zh-CHT": "任務設置",
"en": "Task Settings",
"ko": "작업 설정",
"ja": "タスク設定",
"de": "Aufgabeneinstellungen",
"fr": "Parametres des taches",
"id": "Pengaturan Tugas",
"ru": "Настройки задач"
},
{
"key": "消息设置",
"zh": "",
"zh-CHT": "消息設置",
"en": "Message Settings",
"ko": "메시지 설정",
"ja": "メッセージ設定",
"de": "Nachrichteneinstellungen",
"fr": "Parametres des messages",
"id": "Pengaturan Pesan",
"ru": "Настройки сообщений"
},
{
"key": "文件与存储",
"zh": "",
"zh-CHT": "文件與存儲",
"en": "Files & Storage",
"ko": "파일 및 저장소",
"ja": "ファイルとストレージ",
"de": "Dateien und Speicher",
"fr": "Fichiers et stockage",
"id": "File dan Penyimpanan",
"ru": "Файлы и хранилище"
},
{
"key": "上传与媒体处理",
"zh": "",
"zh-CHT": "上傳與媒體處理",
"en": "Upload & Media Processing",
"ko": "업로드 및 미디어 처리",
"ja": "アップロードとメディア処理",
"de": "Upload und Medienverarbeitung",
"fr": "Importation et traitement des medias",
"id": "Unggah dan Pemrosesan Media",
"ru": "Загрузка и обработка медиа"
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,13 +21,13 @@ negative:
- 归档天数限制 1-100 天,超出范围保存时报错
- 子任务不会被独立归档只看顶层任务parent_id = 0
- 项目自身若设了"自定义归档"archive_method=custom系统级开关对该项目不生效
last_verified: v1.7.90
last_verified: v1.8.89
---
# 任务自动归档设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「任务相关」→「自动归档」。
桌面端:左上角头像 →「系统设置」→「任务设置」→「自动归档」。
涉及字段:
- `auto_archived` — 开关:`open` / `close`,默认 `close`
@ -54,7 +54,7 @@ last_verified: v1.7.90
能。归档不是删除:归档任务在项目「已归档」列表里仍可查看、撤销归档(恢复到原列)、或彻底删除。归档动作只是设置 `archived_at` 时间戳与 `archived_userid`
## 操作步骤
1. 进入「系统设置」→「系统设置」→「任务相关
1. 进入「系统设置」→「任务设置
2. 「自动归档」选「开启」
3. 在出现的输入框填天数(默认 71-100 之间)
4. 「提交」保存,下次定时任务执行时按新规则归档

View File

@ -23,13 +23,13 @@ negative:
- 三个开关相互独立,不会联动;要全静音得分别关
- 部门群、项目群等系统群不受 user_group_chat_mute 控制,永远可发言(除非加入全员群禁言)
- 系统管理员永远可发言,不受任何开关限制
last_verified: v1.7.90
last_verified: v1.8.89
---
# 群组与私聊禁言设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「消息相关」。
桌面端:左上角头像 →「系统设置」→「消息设置」。
涉及三个独立字段:
@ -53,7 +53,7 @@ last_verified: v1.7.90
能。任何字段从 `close` 改回 `open` 后立即恢复发言能力,不需要重启或重新登录。对历史已发 / 未发消息没有任何回溯影响。
## 操作步骤
1. 进入「系统设置」→「系统设置」→「消息相关
1. 进入「系统设置」→「消息设置
2. 分别在「全员群组禁言」「私聊禁言」「群聊禁言」选「开放」或「禁言」
3. 「提交」保存,立即生效

View File

@ -22,13 +22,13 @@ negative:
- 不支持按部门 / 项目分类区分模板
- 修改模板不会影响已创建项目,仅影响后续新建
- 保存时空 list 或 list 中没有合法条目会报「参数错误」/「参数为空」
last_verified: v1.7.90
last_verified: v1.8.89
---
# 列模板(创建项目预置列)
## 入口
桌面端:左上角头像 →「系统设置」→「模板」。
桌面端:左上角头像 →「系统设置」→「项目设置」→「项目模板」。
对应后端:`POST api/system/column/template``type=get` 读取,`type=save` 写入(限 admin
## 是什么
@ -61,11 +61,11 @@ last_verified: v1.7.90
任何缺 `name``columns` 的条目会被服务端跳过;最终为空数组直接报「参数为空」。
## 操作步骤
1. 进入「列模板」页
1. 进入「项目设置」tab 的「项目模板」区块
2. 「新增模板」→ 填写模板名
3. 在列输入框输入列名(多个用逗号分隔)
4. 重复添加多个模板
5. 点击「保存」立即生效
5. 点击页面底部「提交」,与同页项目设置一起保存并立即生效
## 与项目创建的关系
用户「新建项目」对话框 →「选择模板」下拉就是这里维护的列表。选某个模板后,项目创建时按列名顺序生成空列,无任务卡。

View File

@ -22,13 +22,13 @@ negative:
- 关键词搜索不能搜到加密传输中的密文内容(已落库的文本可被搜索)
- 不区分私聊 / 群聊:是否走 e2e 取决于接口,而非会话类型
- 撤回 / 编辑消息的时长限制由 msg_rev_limit / msg_edit_limit 决定,与 e2e 无关
last_verified: v1.7.90
last_verified: v1.8.89
---
# 端到端加密设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「消息相关」→「端到端加密」。
桌面端:左上角头像 →「系统设置」→「消息设置」→「端到端加密」。
字段名:`e2e_message`,开关枚举 `open` / `close`,默认 `close`
@ -46,7 +46,7 @@ WebSocket 通道则会在握手后用 PGP 公钥协商一次会话密钥再传
未在白名单内的 HTTP 接口(任务、项目、文件元数据等)仍走明文 + HTTPS。
## 操作步骤
1. 进入「系统设置」→「系统设置」→「消息相关
1. 进入「系统设置」→「消息设置
2. 「端到端加密」选「开启」或「关闭」
3. 页面底部「提交」保存,立即生效(无需重启)

View File

@ -21,13 +21,13 @@ negative:
- 该限制只控制单个文件大小,不是磁盘配额或总量上限
- 留空 = 默认 1G前后端兜底一致填具体值后取该值仍受 PHP / Nginx 层服务级限制
- 不能按用户 / 部门设差异化阈值,全局生效
last_verified: v1.8.45
last_verified: v1.8.89
---
# 单文件上传大小限制
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「其他设置」→「文件上传限制」。
桌面端:左上角头像 →「系统设置」→「文件与存储」→「上传与媒体处理」→「文件上传限制」。
字段名:`file_upload_limit`,整数,单位 **MB**,默认留空(前端 placeholder 显示「默认 1G」
@ -53,7 +53,7 @@ last_verified: v1.8.45
| `file_upload_limit` | 空(按 1G 兜底) | MB |
## 操作步骤
1. 进入「系统设置」→「系统设置」→「其他设置
1. 进入「系统设置」→「文件与存储」→「上传与媒体处理
2. 在「文件上传限制」输入框填正整数(如 `100` = 100MB或留空
3. 「提交」保存,立即生效

View File

@ -21,13 +21,13 @@ negative:
- 这页只控制「文件夹打包下载」的访问名单,不控制单文件下载
- 单文件下载没有黑白名单,权限全部由文件本身的可见范围决定
- SYSTEM_SETTING=disabled 时不能修改
last_verified: v1.7.90
last_verified: v1.8.89
---
# 文件设置(打包下载权限)
## 入口
桌面端:左上角头像 →「系统设置」→「文件」。
桌面端:左上角头像 →「系统设置」→「文件与存储」→「权限设置」。
对应后端:`POST api/system/setting/file`,参数 `type=save`
## 是什么
@ -47,10 +47,10 @@ last_verified: v1.7.90
服务端只接受这 2 个字段,其他键被自动剔除。
## 操作步骤
1. 进入「文件」设置页
1. 进入「文件与存储」tab
2. 选择策略:默认开放 / 仅管理员 / 指定人员 / 关闭
3. 「指定人员」模式下从用户选择器勾选成员
4. 点击「保存」即生效
4. 点击页面底部提交」与媒体处理、WebDAV 设置一起保存
## 影响范围
- 文件中心 / 项目附件 / 群聊文件等所有「文件夹批量下载」入口都遵守此设置

View File

@ -26,13 +26,13 @@ negative:
- SYSTEM_SETTING=disabled 时所有系统设置都不能改(演示环境常用)
- 自动归档天数限制为 1-100 天,超出会报错
- 邀请码留空会自动生成随机码,不能完全关闭"凭码注册"模式
last_verified: v1.7.90
last_verified: v1.8.89
---
# 通用设置
## 入口
桌面端:左上角头像 →「系统设置」→「通用」。
桌面端:左上角头像 →「系统设置」→「基础设置」。其他字段按领域分别位于「帐号与安全」「项目设置」「任务设置」「消息设置」「文件与存储」。
对应后端:`POST api/system/setting`,参数 `type=save` 提交。
## 关键字段(按主题分组)
@ -69,10 +69,10 @@ last_verified: v1.7.90
- `system_alias` / `system_welcome` — 系统名称别名与欢迎语
## 操作步骤
1. 进入「系统设置」→「通用」
1. 进入「系统设置」,选择需要修改的领域 tab
2. 按表单逐项填写(不在白名单内的字段会被服务端自动剔除)
3. 点击「保存」,服务端落库到 `setting` 表(`system` 分组)
4. 提示「保存成功」即生效,无需重启
3. 点击该 tab 底部的「提交」,同一 tab 涉及多个配置接口时会由页面依次保存
4. 提示「修改成功」即生效,无需重启
## 不支持
- 无法在 `SYSTEM_SETTING=disabled` 环境保存(一律拒绝)

View File

@ -22,13 +22,13 @@ negative:
- 超过设定时长后所有人都不能撤回 / 编辑该消息,系统管理员也不例外
- 留空(视作 0= 不限制,不是「禁止撤回」
- 时长以分钟为单位整数,不支持小时 / 秒级粒度
last_verified: v1.7.90
last_verified: v1.8.89
---
# 消息撤回与编辑时长上限
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「消息相关」→「撤回消息限制」/「修改消息限制」。
桌面端:左上角头像 →「系统设置」→「消息设置」→「撤回消息限制」/「修改消息限制」。
涉及字段:
- `msg_rev_limit` — 撤回时长上限(分钟)
@ -50,7 +50,7 @@ last_verified: v1.7.90
不能。超出时长后服务端会抛 `已超过 X 分钟,此消息不可撤回 / 修改` 错误,前端按钮也会变灰或弹错。系统管理员同样受限,没有"管理员强撤"的旁路。
## 操作步骤
1. 进入「系统设置」→「系统设置」→「消息相关
1. 进入「系统设置」→「消息设置
2. 在「撤回消息限制」「修改消息限制」输入框填分钟数(如 `5`)或留空
3. 「提交」保存,立即生效

View File

@ -21,13 +21,13 @@ negative:
- 修改策略不会触发存量用户重置密码,存量密码继续可用
- 不支持自定义正则、最小特殊字符数等细粒度规则,只有 simple / complex 两档
- 密码长度上限固定 32 位,无管理后台开关
last_verified: v1.7.90
last_verified: v1.8.89
---
# 密码策略设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「帐号相关」→「密码策略」。
桌面端:左上角头像 →「系统设置」→「帐号与安全」→「密码策略」。
字段名:`password_policy`,枚举 `simple` / `complex`,默认 `simple`
@ -49,7 +49,7 @@ last_verified: v1.7.90
如果要强制全员升级密码,需要管理员手动通知用户修改,没有内置「强制重置」按钮。
## 操作步骤
1. 进入「系统设置」→「系统设置」→「帐号相关
1. 进入「系统设置」→「帐号与安全
2. 「密码策略」选 `simple``complex`
3. 页面底部「提交」保存

View File

@ -22,13 +22,13 @@ negative:
- 不能给单个项目自定义独立优先级,全局共享一套
- 不支持按优先级自动指派负责人 / 自动通知
- 普通用户调用保存接口会被拒绝(仅 admin
last_verified: v1.7.90
last_verified: v1.8.89
---
# 任务优先级配置
## 入口
桌面端:左上角头像 →「系统设置」→「任务优先级」。
桌面端:左上角头像 →「系统设置」→「任务设置」→「任务优先级」。
对应后端:`POST api/system/priority``type=get` 读取,`type=save` 写入(限 admin
## 是什么
@ -54,10 +54,10 @@ last_verified: v1.7.90
服务端使用 `Setting::normalizeTaskPriorityList()` 校验并归一化(去重 / 截断 / 字段裁剪),空数组会拒绝。
## 操作步骤
1. 进入「任务优先级」页
1. 进入「任务设置」tab 的「任务优先级」区块
2. 「新增」追加一行 → 填写名称 / 颜色 / 天数 / 权重
3. 拖拽或修改 `priority` 字段调整顺序
4. 点击「保存」即时生效,所有项目共享
4. 点击页面底部「提交」,与同页任务设置一起保存并即时生效
## 默认数据
首次访问会返回系统默认的优先级(紧急 / 高 / 普通 / 低)。修改后保存即覆盖默认。

View File

@ -23,13 +23,13 @@ negative:
- 注册策略只有 open / close / invite 三档,不支持按邮箱域名白名单
- 邀请码留空保存后系统会自动生成一个随机码,不能完全关闭"凭码可注册"模式
- 临时帐号一旦创建无法在线一键转为正常帐号
last_verified: v1.7.90
last_verified: v1.8.89
---
# 注册策略设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「帐号相关」→「允许注册」。
桌面端:左上角头像 →「系统设置」→「帐号与安全」→「允许注册」。
涉及字段:
- `reg` — 注册策略
@ -58,7 +58,7 @@ last_verified: v1.7.90
`temp` 时下方可填 `temp_account_alias`(临时帐号别名),用于在用户列表展示区别。
## 操作步骤
1. 进入「系统设置」→「系统设置」→「帐号相关
1. 进入「系统设置」→「帐号与安全
2. 选「允许注册」的三选一(同时配套填邀请码或注册身份)
3. 「提交」保存,立即生效

View File

@ -22,13 +22,13 @@ negative:
- 未安装 ai 插件时,分析逻辑直接跳过,不会有任何产出
- 系统开关 = open 但项目级开关 = close 时,该项目的任务也不会被分析
- 不会回溯分析历史任务,只对新建后进入待分析队列的任务生效
last_verified: v1.7.90
last_verified: v1.8.89
---
# AI 任务自动分析设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「任务相关」→「AI任务分析」。
桌面端:左上角头像 →「系统设置」→「任务设置」→「AI任务分析」。
字段名:`task_ai_auto_analyze`,开关枚举 `open` / `close`,默认 `open`
@ -54,7 +54,7 @@ last_verified: v1.7.90
## 操作步骤
1. 确认应用市场已安装 ai 插件
2. 进入「系统设置」→「系统设置」→「任务相关
2. 进入「系统设置」→「任务设置
3. 「AI任务分析」选「开启」或「关闭」
4. 「提交」保存,立即生效

View File

@ -20,13 +20,13 @@ negative:
- 该字段只控制任务详情 UI 上"可见性"控件是否常驻显示,不直接改任务的可见范围
- 不是「公开 / 项目内 / 仅负责人」三选一,只有 open / close 两档
- 已有任务的实际可见性数据不受本开关影响
last_verified: v1.7.90
last_verified: v1.8.89
---
# 任务可见性选项显示策略
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「任务相关」→「可见性选项」。
桌面端:左上角头像 →「系统设置」→「任务设置」→「可见性选项」。
字段名:`task_visible`,枚举 `open` / `close`,默认 `close`
@ -48,7 +48,7 @@ last_verified: v1.7.90
- 历史任务的可见性范围维持不变
## 操作步骤
1. 进入「系统设置」→「系统设置」→「任务相关
1. 进入「系统设置」→「任务设置
2. 「可见性选项」选「保持」(open) 或「自动」(close)
3. 「提交」保存,下次打开任务详情即按新策略显示

View File

@ -21,13 +21,13 @@ negative:
- 这是消息→待办(聊天里把某条消息变成 todo权限与项目任务无关
- 自己给自己设 / 取消 todo 不受开关限制
- 关闭后任何人都不能"批量给一群人设 todo",包括非自己的成员
last_verified: v1.7.90
last_verified: v1.8.89
---
# 待办设置权限
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「消息相关」→「待办设置权限」。
桌面端:左上角头像 →「系统设置」→「消息设置」→「待办设置权限」。
字段名:`todo_set_permission`,枚举 `open` / `close`,默认 `open`
@ -48,7 +48,7 @@ last_verified: v1.7.90
后端校验点:`WebSocketDialogMsg::setTodoRemind``DialogController::msg__todo_indicate` 两处都会读 `todo_set_permission`,关闭时校验 `checkTodoOwnerPermission`
## 操作步骤
1. 进入「系统设置」→「系统设置」→「消息相关
1. 进入「系统设置」→「消息设置
2. 「待办设置权限」选「允许」或「禁止」
3. 「提交」保存,立即生效

View File

@ -22,13 +22,13 @@ negative:
- 不会回溯处理历史上传的视频,只对新上传文件生效
- 转码 / 压缩失败时回退保留原文件,不会丢消息
- 不能选目标分辨率 / 码率,参数固定
last_verified: v1.7.90
last_verified: v1.8.89
---
# 视频转码与压缩设置
## 入口
桌面端:左上角头像 →「系统设置」→「系统设置」标签 →「消息相关」→「视频转换」/「视频压缩」。
桌面端:左上角头像 →「系统设置」→「文件与存储」→「上传与媒体处理」→「视频转换」/「视频压缩」。
涉及字段:
- `convert_video` — 视频转码开关,默认 `close`
@ -55,7 +55,7 @@ last_verified: v1.7.90
原文件被处理后产物(转码后 MP4 / 压缩后 MP4替代原文件入库**原始 MOV / WEBM 不保留**。开关推送给前端后会在聊天预览处显示「视频处理中」气泡。
## 操作步骤
1. 进入「系统设置」→「系统设置」→「消息相关
1. 进入「系统设置」→「文件与存储」→「上传与媒体处理
2. 「视频转换」「视频压缩」分别选「开启」或「关闭」
3. 「提交」保存,立即对后续上传生效

View File

@ -27,7 +27,7 @@ last_verified: v1.8.89
# 管理员启用和配置 WebDAV
## 入口
桌面端:左上角头像 →「系统设置」→「文件」→「WebDAV」。
桌面端:左上角头像 →「系统设置」→「文件与存储」→「WebDAV」。
## 配置项
- **启用 WebDAV**:全局协议开关。关闭后 `/dav/` 立即停止提供服务,已有应用密码不会被展示为登录密码。
@ -40,7 +40,7 @@ last_verified: v1.8.89
## 启用流程
1. 确认 DooTask 的外部地址使用 HTTPS生产环境的 DAV 协议入口拒绝明文 HTTP。
2. 打开「文件」设置页并检查路径冲突提示。
2. 打开「文件与存储」tab 并检查路径冲突提示。
3. 如存在冲突,点击「查看冲突文件」,可复制完整冲突路径,并根据拥有者和文件 ID 找到记录。管理员可直接重命名任意冲突文件;重命名其他成员的私人文件前会二次确认并记录审计,但不会开放文件内容、移动或删除权限。管理员本人拥有目标文件,或目标文件已通过现有共享权限向管理员开放时,列表才显示「打开位置」,并自动定位到「我的文件」或「共享文件」;没有查看权限时不会显示该入口。保留其中一条并重命名或删除同组内其余文件后,点击「刷新检测」确认冲突清零。服务端会硬性拒绝在冲突未处理时开启。
4. 选择允许范围与凭据限制,开启 WebDAV 后点击「提交」。
5. 通知获准用户从「文件」页右上角「···」创建自己的应用密码,管理员不代替用户生成或查看密码。

View File

@ -19,30 +19,32 @@ related_pages: []
negative:
- 移动端通常不展示「系统设置」入口,需用桌面端 / 网页后台
- 普通成员看不到该页,无入口
- 这 4 个 tab 只是「系统设置」一级菜单内的内容邮件、AI、签到等是同级别的其他左侧菜单项不在这个页内
last_verified: v1.7.90
- 这 6 个 tab 只是「系统设置」一级菜单内的内容邮件、AI、签到等是同级别的其他左侧菜单项不在这个页内
last_verified: v1.8.89
---
# 系统设置页面总览
## 路径
桌面端:左上角头像 → 下拉菜单 →「系统设置」(仅管理员可见)→ 左侧子菜单选「系统设置」一级项。
打开后顶部是 4 个并列的 tab对应同一 URL 下的 4 个子表单。
打开后顶部是 6 个并列的 tab对应同一 URL 下按领域整理的设置表单。
移动端:不展示,需用桌面端或浏览器后台。
## 4 个 tab 一句话目录
## 6 个 tab 一句话目录
| Tab | name | 内容 |
|---|---|---|
| **系统设置** | `setting` | 全站通用开关:注册、密码、消息、视频、上传、欢迎语等几十项;详见 [[system-setting.general.howto]] |
| **任务优先级** | `taskPriority` | 自定义任务优先级的颜色、名称、提前提醒天数;详见 [[system-setting.priority.howto]] |
| **项目模板** | `columnTemplate` | 新建项目时可选的预置「列模板」清单(如 看板列、状态列);详见 [[system-setting.column-template.howto]] |
| **文件设置** | `fileSetting` | 文件相关策略(如打包下载权限、缩略图);详见 [[system-setting.file.howto]] |
| **基础设置** | `general` | 系统别名和仪表盘欢迎语;详见 [[system-setting.general.howto]] |
| **帐号与安全** | `account` | 注册方式、临时帐号、登录验证码和密码策略 |
| **项目设置** | `project` | 项目创建与邀请权限、部门负责人视角、项目模板;详见 [[system-setting.column-template.howto]] |
| **任务设置** | `task` | 任务默认规则、提醒、AI 分析和任务优先级;详见 [[system-setting.priority.howto]] |
| **消息设置** | `message` | 群聊、私聊、匿名消息、加密、撤回和待办权限 |
| **文件与存储** | `file` | 上传与媒体处理、打包下载权限和 WebDAV详见 [[system-setting.file.howto]] |
默认进入「系统设置」tab。切换 tab 不会重置已填未保存的表单值,但每个 tab 单独保存。
默认进入「基础设置」tab。每个 tab 底部只有一组「提交 / 重置」;一个 tab 涉及多类配置时,提交会依次调用对应接口并统一反馈结果
## 同级菜单(不在这 4 tab 内)
## 同级菜单(不在这 6 个 tab 内)
左侧子菜单的其他独立项见 [[system-setting.entry.menu-map]]包括邮件、会议、AI 设置、AI 机器人、签到、APP 推送、第三方接入、License 等。
## 权限要求

View File

@ -30,15 +30,15 @@ const LINKS = {
setting_license: { route: { name: 'manage-setting-license' } },
setting_password: { route: { name: 'manage-setting-password' } },
setting_email: { route: { name: 'manage-setting-email' } },
setting_system: { route: { name: 'manage-setting-system', query: { tab: 'setting' } } },
setting_system: { route: { name: 'manage-setting-system', query: { tab: 'general' } } },
setting_device: { route: { name: 'manage-setting-device' } },
setting_version: { route: { name: 'manage-setting-version' } },
setting_delete: { route: { name: 'manage-setting-delete' } },
// 系统设置内的二级 Tabsystem.vue 据 query.tab 切换)
setting_system_task_priority: { route: { name: 'manage-setting-system', query: { tab: 'taskPriority' } } },
setting_system_column_template: { route: { name: 'manage-setting-system', query: { tab: 'columnTemplate' } } },
setting_system_file: { route: { name: 'manage-setting-system', query: { tab: 'fileSetting' } } },
setting_system_task_priority: { route: { name: 'manage-setting-system', query: { tab: 'task' } } },
setting_system_column_template: { route: { name: 'manage-setting-system', query: { tab: 'project' } } },
setting_system_file: { route: { name: 'manage-setting-system', query: { tab: 'file' } } },
};
export const CATALOG_IDS = Object.keys(LINKS);

View File

@ -1,6 +1,6 @@
<template>
<div class="setting-component-item">
<Form ref="formDatum" label-width="auto" @submit.native.prevent>
<div :class="{'setting-component-item': !embedded}">
<div>
<Row class="setting-template">
<Col span="8">{{$L('名称')}}</Col>
<Col span="16">{{$L('项目模板')}}</Col>
@ -18,9 +18,9 @@
<TagInput v-model="item.columns"/>
</Col>
</Row>
<Button type="default" icon="md-add" @click="addDatum">{{$L('添加模板')}}</Button>
</Form>
<div class="setting-footer">
<Button class="setting-add-action" type="default" icon="md-add" @click="addDatum">{{$L('添加模板')}}</Button>
</div>
<div v-if="!embedded" class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
<Button :loading="loadIng > 0" @click="resetForm">{{$L('重置')}}</Button>
</div>
@ -32,6 +32,9 @@ import {mapState} from "vuex";
export default {
name: 'SystemColumnTemplate',
props: {
embedded: Boolean,
},
data() {
return {
loadIng: 0,
@ -67,11 +70,7 @@ export default {
methods: {
submitForm() {
this.$refs.formDatum.validate((valid) => {
if (valid) {
this.systemSetting(true);
}
})
this.systemSetting(true);
},
resetForm() {
@ -89,16 +88,16 @@ export default {
}
},
systemSetting(save) {
systemSetting(save, silent = false) {
this.loadIng++;
this.$store.dispatch("call", {
return this.$store.dispatch("call", {
url: 'system/column/template?type=' + (save ? 'save' : 'get'),
method: 'post',
data: {
list: this.formDatum
},
}).then(({data}) => {
if (save) {
if (save && !silent) {
$A.messageSuccess('修改成功');
}
this.$store.state.columnTemplate = $A.cloneJSON(data).map(item => {
@ -108,9 +107,12 @@ export default {
return item;
});
}).catch(({msg}) => {
if (save) {
if (save && !silent) {
$A.modalError(msg);
}
if (silent) {
return Promise.reject(msg);
}
}).finally(_ => {
this.loadIng--;
});

View File

@ -1,11 +1,6 @@
<template>
<div class="setting-component-item">
<Form
ref="formData"
:model="formData"
:rules="ruleData"
v-bind="formOptions"
@submit.native.prevent>
<div :class="{'setting-component-item': !embedded}">
<div>
<div class="block-setting-box">
<h3>{{$L('权限设置')}}</h3>
<div class="form-box">
@ -71,8 +66,8 @@
</template>
</div>
</div>
</Form>
<div class="setting-footer">
</div>
<div v-if="!embedded" class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{ $L('提交') }}</Button>
<Button :loading="loadIng > 0" @click="resetForm">{{ $L('重置') }}</Button>
</div>
@ -103,6 +98,9 @@ import UserSelect from "../../../../components/UserSelect.vue";
export default {
name: "SystemFileSetting",
components: {UserSelect},
props: {
embedded: Boolean,
},
data() {
return {
loadIng: 0,
@ -170,7 +168,6 @@ export default {
formData: {
},
ruleData: {},
}
},
@ -179,16 +176,12 @@ export default {
},
computed: {
...mapState(['formOptions', 'userId']),
...mapState(['userId']),
},
methods: {
submitForm() {
this.$refs.formData.validate((valid) => {
if (valid) {
this.systemSetting(true);
}
})
this.systemSetting(true);
},
resetForm() {
@ -271,7 +264,7 @@ export default {
});
},
systemSetting(save) {
systemSetting(save, silent = false) {
this.loadIng++;
const fileRequest = () => this.$store.dispatch("call", {
url: 'system/setting/file?type=' + (save ? 'save' : 'all'),
@ -292,17 +285,20 @@ export default {
webDavStatusRequest(),
])))
: Promise.all([fileRequest(), webDavRequest(), webDavStatusRequest()]);
request.then(([fileSetting, webDavSetting, webDavStatus]) => {
if (save) {
return request.then(([fileSetting, webDavSetting, webDavStatus]) => {
if (save && !silent) {
$A.messageSuccess('修改成功');
}
this.formData = Object.assign({}, fileSetting.data, webDavSetting.data);
this.webDavStatus = webDavStatus.data || {};
this.formDatum_bak = $A.cloneJSON(this.formData);
}).catch(({msg}) => {
if (save) {
if (save && !silent) {
$A.modalError(msg);
}
if (silent) {
return Promise.reject(msg);
}
}).finally(_ => {
this.loadIng--;
});

View File

@ -5,8 +5,8 @@
:model="formDatum"
v-bind="formOptions"
@submit.native.prevent>
<div class="block-setting-box">
<h3>{{ $L('帐号相关') }}</h3>
<div v-if="scope === 'account'" class="block-setting-box">
<h3>{{ $L('帐号与安全') }}</h3>
<div class="form-box">
<FormItem :label="$L('允许注册')" prop="reg">
<RadioGroup v-model="formDatum.reg">
@ -65,8 +65,8 @@
</FormItem>
</div>
</div>
<div class="block-setting-box">
<h3>{{ $L('项目相关') }}</h3>
<div v-if="scope === 'project'" class="block-setting-box">
<h3>{{ $L('项目设置') }}</h3>
<div class="form-box">
<FormItem :label="$L('邀请项目')" prop="projectInvite">
<RadioGroup v-model="formDatum.project_invite">
@ -104,8 +104,14 @@
</FormItem>
</div>
</div>
<div class="block-setting-box">
<h3>{{ $L('任务相关') }}</h3>
<div v-if="scope === 'project'" class="block-setting-box">
<h3>{{ $L('项目模板') }}</h3>
<div class="form-box">
<SystemColumnTemplate ref="columnTemplate" embedded/>
</div>
</div>
<div v-if="scope === 'task'" class="block-setting-box">
<h3>{{ $L('任务设置') }}</h3>
<div class="form-box">
<FormItem :label="$L('自动归档')" prop="autoArchived">
<RadioGroup :value="formDatum.auto_archived" @on-change="formArchived">
@ -171,8 +177,14 @@
</FormItem>
</div>
</div>
<div class="block-setting-box">
<h3>{{ $L('消息相关') }}</h3>
<div v-if="scope === 'task'" class="block-setting-box">
<h3>{{ $L('任务优先级') }}</h3>
<div class="form-box">
<SystemTaskPriority ref="taskPriority" embedded/>
</div>
</div>
<div v-if="scope === 'message'" class="block-setting-box">
<h3>{{ $L('消息设置') }}</h3>
<div class="form-box">
<FormItem :label="$L('自动进入全员群')" prop="allGroupAutoin">
<RadioGroup v-model="formDatum.all_group_autoin">
@ -235,22 +247,6 @@
<div v-if="formDatum.todo_set_permission == 'open'" class="form-tip">{{$L('允许所有成员可设置/取消他人待办')}}</div>
<div v-else class="form-tip">{{$L('禁止:仅本人、系统管理员、群主(含群管理员)、项目负责人(含项目管理员)、任务负责人可设置/取消待办。')}}</div>
</FormItem>
<FormItem :label="$L('视频转换')" prop="convertVideo">
<RadioGroup v-model="formDatum.convert_video">
<Radio label="open">{{$L('开启')}}</Radio>
<Radio label="close">{{$L('关闭')}}</Radio>
</RadioGroup>
<div v-if="formDatum.convert_video == 'open'" class="form-tip">{{$L('将MOVWEBM格式的视频转换为MP4格式')}}</div>
<div v-else class="form-tip">{{$L('关闭视频格式转换功能。')}}</div>
</FormItem>
<FormItem :label="$L('视频压缩')" prop="compressVideo">
<RadioGroup v-model="formDatum.compress_video">
<Radio label="open">{{$L('开启')}}</Radio>
<Radio label="close">{{$L('关闭')}}</Radio>
</RadioGroup>
<div v-if="formDatum.compress_video == 'open'" class="form-tip">{{$L('对MP4格式的视频进行压缩处理')}}</div>
<div v-else class="form-tip">{{$L('关闭视频压缩功能。')}}</div>
</FormItem>
<FormItem :label="$L('端到端加密')" prop="e2eMessage">
<RadioGroup v-model="formDatum.e2e_message">
<Radio label="open">{{$L('开启')}}</Radio>
@ -281,8 +277,8 @@
</FormItem>
</div>
</div>
<div class="block-setting-box">
<h3>{{ $L('其他设置') }}</h3>
<div v-if="scope === 'general'" class="block-setting-box">
<h3>{{ $L('基础设置') }}</h3>
<div class="form-box">
<FormItem :label="$L('系统别名')" prop="system_alias">
<div style="width: 220px;">
@ -296,6 +292,11 @@
</div>
<div class="form-tip">{{$L('仪表盘欢迎词,(*)代表用户昵称', '{username}')}}</div>
</FormItem>
</div>
</div>
<div v-if="scope === 'file'" class="block-setting-box">
<h3>{{ $L('上传与媒体处理') }}</h3>
<div class="form-box">
<FormItem :label="$L('图片优化')" prop="image_compress">
<RadioGroup v-model="formDatum.image_compress">
<Radio label="open">{{$L('开启')}}</Radio>
@ -324,8 +325,25 @@
</div>
<div class="form-tip">{{$L('包含消息发送的文件')}}</div>
</FormItem>
<FormItem :label="$L('视频转换')" prop="convertVideo">
<RadioGroup v-model="formDatum.convert_video">
<Radio label="open">{{$L('开启')}}</Radio>
<Radio label="close">{{$L('关闭')}}</Radio>
</RadioGroup>
<div v-if="formDatum.convert_video == 'open'" class="form-tip">{{$L('将MOVWEBM格式的视频转换为MP4格式')}}</div>
<div v-else class="form-tip">{{$L('关闭视频格式转换功能。')}}</div>
</FormItem>
<FormItem :label="$L('视频压缩')" prop="compressVideo">
<RadioGroup v-model="formDatum.compress_video">
<Radio label="open">{{$L('开启')}}</Radio>
<Radio label="close">{{$L('关闭')}}</Radio>
</RadioGroup>
<div v-if="formDatum.compress_video == 'open'" class="form-tip">{{$L('对MP4格式的视频进行压缩处理')}}</div>
<div v-else class="form-tip">{{$L('关闭视频压缩功能。')}}</div>
</FormItem>
</div>
</div>
<SystemFileSetting v-if="scope === 'file'" ref="fileSetting" embedded/>
</Form>
<div class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
@ -337,11 +355,30 @@
<script>
import {mapState} from "vuex";
import UserSelect from "../../../../components/UserSelect.vue";
import SystemColumnTemplate from "./SystemColumnTemplate.vue";
import SystemFileSetting from "./SystemFileSetting.vue";
import SystemTaskPriority from "./SystemTaskPriority.vue";
const SCOPE_FIELDS = {
general: ['system_alias', 'system_welcome'],
account: ['reg', 'reg_identity', 'reg_invite', 'temp_account_alias', 'login_code', 'password_policy'],
project: ['project_invite', 'project_add_permission', 'project_add_userids', 'department_owner_project_view'],
task: ['auto_archived', 'archived_day', 'task_visible', 'task_default_time', 'task_user_limit', 'unclaimed_task_reminder', 'unclaimed_task_reminder_time', 'task_ai_auto_analyze'],
message: ['chat_information', 'anon_message', 'e2e_message', 'msg_rev_limit', 'msg_edit_limit', 'all_group_mute', 'all_group_autoin', 'user_private_chat_mute', 'user_group_chat_mute', 'todo_set_permission'],
file: ['convert_video', 'compress_video', 'image_compress', 'image_quality', 'image_save_local', 'file_upload_limit'],
};
export default {
name: 'SystemSetting',
components: {UserSelect},
components: {SystemTaskPriority, SystemFileSetting, SystemColumnTemplate, UserSelect},
props: {
scope: {
type: String,
required: true,
},
},
data() {
return {
@ -380,13 +417,56 @@ export default {
submitForm() {
this.$refs.formDatum.validate((valid) => {
if (valid) {
this.systemSetting(true);
this.saveAll();
}
})
},
resetForm() {
this.formDatum = $A.cloneJSON(this.formDatum_bak);
this.$refs.columnTemplate?.resetForm();
this.$refs.taskPriority?.resetForm();
this.$refs.fileSetting?.resetForm();
},
saveAll() {
if (this.loadIng > 0) {
return;
}
const extra = {
project: () => this.$refs.columnTemplate.systemSetting(true, true),
task: () => this.$refs.taskPriority.systemSetting(true, true),
file: () => this.$refs.fileSetting.systemSetting(true, true),
}[this.scope];
this.loadIng++;
this.saveSystemScope()
.then(() => extra?.())
.then(() => {
$A.messageSuccess('修改成功');
this.$store.dispatch("getUserInfo").catch(() => {});
})
.catch(msg => $A.modalError(msg))
.finally(() => {
this.loadIng--;
});
},
saveSystemScope() {
const fields = SCOPE_FIELDS[this.scope];
const currentValues = fields.reduce((data, field) => {
data[field] = $A.cloneJSON(this.formDatum[field]);
return data;
}, {});
return this.$store.dispatch("call", {
url: 'system/setting?type=all',
method: 'post',
}).then(({data}) => this.$store.dispatch("call", {
url: 'system/setting?type=save',
method: 'post',
data: Object.assign({}, data, currentValues),
})).then(({data}) => {
this.setSystemDatum(data);
}).catch(({msg}) => Promise.reject(msg));
},
formArchived(value) {
@ -397,29 +477,36 @@ export default {
this.formDatum = { ...this.formDatum, unclaimed_task_reminder: value };
},
systemSetting(save) {
systemSetting(save, silent = false) {
this.loadIng++;
this.$store.dispatch("call", {
return this.$store.dispatch("call", {
url: 'system/setting?type=' + (save ? 'save' : 'all'),
method: 'post',
data: this.formDatum,
}).then(({data}) => {
if (save) {
if (save && !silent) {
$A.messageSuccess('修改成功');
this.$store.dispatch("getUserInfo").catch(() => {});
}
this.formDatum = data;
this.formDatum_bak = $A.cloneJSON(this.formDatum);
this.$store.state.systemConfig = Object.assign(this.formDatum_bak, {
__state: "success",
})
this.setSystemDatum(data);
}).catch(({msg}) => {
if (save) {
if (save && !silent) {
$A.modalError(msg);
}
if (silent) {
return Promise.reject(msg);
}
}).finally(_ => {
this.loadIng--;
});
},
setSystemDatum(data) {
this.formDatum = data;
this.formDatum_bak = $A.cloneJSON(data);
this.$store.state.systemConfig = Object.assign({}, this.formDatum_bak, {
__state: "success",
});
}
}
}

View File

@ -1,6 +1,6 @@
<template>
<div class="setting-component-item system-task-priority">
<Form ref="formDatum" label-width="auto" @submit.native.prevent>
<div :class="{'setting-component-item': !embedded, 'system-task-priority': true}">
<div>
<Row class="setting-color color-label-box">
<Col span="2">{{$L('默认')}}</Col>
<Col span="10">{{$L('名称')}}</Col>
@ -40,11 +40,11 @@
</Col>
</Row>
</RadioGroup>
<div class="priority-add-action">
<div class="setting-add-action">
<Button type="default" icon="md-add" @click="addDatum">{{$L('添加优先级')}}</Button>
</div>
</Form>
<div class="setting-footer">
</div>
<div v-if="!embedded" class="setting-footer">
<Button :loading="loadIng > 0" type="primary" @click="submitForm">{{$L('提交')}}</Button>
<Button :loading="loadIng > 0" @click="resetForm">{{$L('重置')}}</Button>
</div>
@ -56,6 +56,9 @@ import {mapState} from "vuex";
export default {
name: 'SystemTaskPriority',
props: {
embedded: Boolean,
},
data() {
return {
loadIng: 0,
@ -101,11 +104,7 @@ export default {
methods: {
submitForm() {
this.$refs.formDatum.validate((valid) => {
if (valid) {
this.systemSetting(true);
}
})
this.systemSetting(true);
},
resetForm() {
@ -141,24 +140,27 @@ export default {
})
},
systemSetting(save) {
systemSetting(save, silent = false) {
this.loadIng++;
this.applyDefaultIndex();
this.$store.dispatch("call", {
return this.$store.dispatch("call", {
url: 'system/priority?type=' + (save ? 'save' : 'get'),
method: 'post',
data: {
list: this.formDatum
},
}).then(({data}) => {
if (save) {
if (save && !silent) {
$A.messageSuccess('修改成功');
}
this.$store.state.taskPriority = $A.cloneJSON(data);
}).catch(({msg}) => {
if (save) {
if (save && !silent) {
$A.modalError(msg);
}
if (silent) {
return Promise.reject(msg);
}
}).finally(_ => {
this.loadIng--;
});

View File

@ -1,17 +1,23 @@
<template>
<div class="setting-item submit">
<Tabs v-model="tabAction">
<TabPane :label="$L('系统设置')" name="setting">
<SystemSetting/>
<TabPane :label="$L('基础设置')" name="general">
<SystemSetting scope="general"/>
</TabPane>
<TabPane :label="$L('任务优先级')" name="taskPriority">
<SystemTaskPriority/>
<TabPane :label="$L('帐号与安全')" name="account">
<SystemSetting scope="account"/>
</TabPane>
<TabPane :label="$L('项目模板')" name="columnTemplate">
<SystemColumnTemplate/>
<TabPane :label="$L('项目设置')" name="project">
<SystemSetting scope="project"/>
</TabPane>
<TabPane :label="$L('文件设置')" name="fileSetting">
<SystemFileSetting/>
<TabPane :label="$L('任务设置')" name="task">
<SystemSetting scope="task"/>
</TabPane>
<TabPane :label="$L('消息设置')" name="message">
<SystemSetting scope="message"/>
</TabPane>
<TabPane :label="$L('文件与存储')" name="file">
<SystemSetting scope="file"/>
</TabPane>
</Tabs>
</div>
@ -19,14 +25,17 @@
<script>
import SystemSetting from "./components/SystemSetting";
import SystemTaskPriority from "./components/SystemTaskPriority";
import SystemColumnTemplate from "./components/SystemColumnTemplate";
import SystemFileSetting from "./components/SystemFileSetting";
const VALID_TABS = ['setting', 'taskPriority', 'columnTemplate', 'fileSetting'];
const VALID_TABS = ['general', 'account', 'project', 'task', 'message', 'file'];
const LEGACY_TABS = {
setting: 'general',
taskPriority: 'task',
columnTemplate: 'project',
fileSetting: 'file',
};
export default {
components: {SystemColumnTemplate, SystemTaskPriority, SystemSetting, SystemFileSetting},
components: {SystemSetting},
data() {
return {
tabAction: this.tabFromRoute(),
@ -41,7 +50,10 @@ export default {
methods: {
tabFromRoute() {
const tab = this.$route?.query?.tab;
return VALID_TABS.includes(tab) ? tab : 'setting';
if (VALID_TABS.includes(tab)) {
return tab;
}
return LEGACY_TABS[tab] || 'general';
},
},
}

View File

@ -233,6 +233,9 @@
}
}
}
.setting-add-action {
margin-bottom: 12px;
}
.setting-footer {
display: flex;
align-items: center;