新增「参数配置」页(/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 管理弹窗对这两个插件给出跳转提示,原始编辑器保留为应急通道。
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
/**
|
||
* 管理端登录状态(跨页面共享单例)。
|
||
*
|
||
* 参数配置页与 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 }
|
||
}
|