add: 邦泽参数配置页(音柱 / 告警结构化配置)

新增「参数配置」页(/config),按客户使用场景而非插件边界组织
aibox_sophon_adapter 与 ip_sound_column 的配置:

- IP 音柱报警:音柱设备 IP(属 ip_sound_column)+ 启用/音量/播报文案
  映射(属 aibox_sophon_adapter);两个插件在同一页配完,保存时按归属
  分别下发并重启对应副本。
- 告警与联动:告警转发、心跳与离线、告警留存、豆包分析、IN2 联动。
- 未开放数据库、数据目录、MediaMTX、MQTT 通道等部署类配置,页面脚注
  说明;仍可用 Worker 管理的原始编辑器维护。

关键实现约束(见 src/config/configEdits.ts):
- 后端对 [workers.config] 只做顶层浅合并,被改动的嵌套节必须整节提交,
  否则改一个音量会清掉同级的 enabled/tts(已用真实接口复现)。
- 区块开关默认值取自插件代码默认值,避免「配置里没有该键时开关显示为关
  且被判为已改动」而在改其它字段时误关功能。
- 数值字段留空直接拦下,不把 NaN 下发成 null 触发插件侧回滚。

其他:
- tts 映射的违规类型候选来自历史告警记录(list_alarms),手写拼错不易察觉。
- 登录态抽成 useAdminAuth 与 Worker 管理页共用,token 互通。
- Worker 管理弹窗对这两个插件给出跳转提示,原始编辑器保留为应急通道。
This commit is contained in:
2026-09-18 10:00:32 +08:00
parent 55fe2621f8
commit 5b91548246
11 changed files with 1761 additions and 47 deletions
+74
View File
@@ -0,0 +1,74 @@
/**
* 管理端登录状态(跨页面共享单例)。
*
* 参数配置页与 Worker 管理页共用同一份 token(localStorage 的 cm_admin_token),
* 在任一页面登录后另一页面无需重复登录。抽成模块级单例而非 Pinia store,
* 避免为这点状态引入额外样板。
*/
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { login, parseJwt, TOKEN_KEY, type AuthUser } from '@/api/workerAdmin'
const token = ref(localStorage.getItem(TOKEN_KEY) || '')
const currentUser = ref<AuthUser | null>(null)
const username = ref('')
const password = ref('')
const logging = ref(false)
/** 从持久化 token 恢复身份(进入页面时调用;权限校验仍以后端为准) */
export function restoreAuth(): AuthUser | null {
const t = token.value.trim()
currentUser.value = t ? parseJwt(t) : null
return currentUser.value
}
export function useAdminAuth() {
const isLoggedIn = computed(() => !!token.value.trim())
/**
* 登录并领取管理 Token。
* @returns 登录成功时的用户信息;失败返回 null(已弹出提示)。
*/
const doLogin = async (): Promise<AuthUser | null> => {
const name = username.value.trim()
if (!name || !password.value) {
ElMessage.warning('请输入用户名和密码')
return null
}
logging.value = true
try {
const data = await login(name, password.value)
// 后端登录失败仍返回 HTTP 200,仅 result=fail + 空 token
if (data.result !== 'success' || !data.token) {
ElMessage.error(`登录失败:${data.message || '用户名或密码错误'}`)
return null
}
token.value = data.token
localStorage.setItem(TOKEN_KEY, data.token)
username.value = ''
password.value = ''
const info = parseJwt(data.token)
currentUser.value = info
ElMessage.success(`登录成功:${(info?.username || '').trim() || '未知用户'}`)
if (!info?.is_superuser) {
ElMessage.error('权限不足:仅超级管理员可管理配置,操作将被拒绝')
return null
}
return info
} catch (e: any) {
ElMessage.error(`登录失败:${e.message}`)
return null
} finally {
logging.value = false
}
}
const logout = () => {
localStorage.removeItem(TOKEN_KEY)
token.value = ''
currentUser.value = null
ElMessage.success('已退出登录')
}
return { token, currentUser, username, password, logging, isLoggedIn, doLogin, logout }
}