feat: 支持按键分离事件与键盘捕获,并优化UI

- 后端与前端新增 keyAction 参数,支持按键按下/抬起独立事件,保持向下兼容
- 前端控制栏支持 pointerdown/pointerup 长按,新增键盘捕获输入框,映射浏览器按键到 Android KeyEvent
- 重构顶栏布局:用户区右置并添加在线指示器、状态文本省略,优化窄屏响应式表现
- 控制栏录制按钮组防拆行,分辨率/帧率选择器自适应收缩,全局样式增强
- 修复 AdminAuthFilter 中 doFilter 被异常捕获导致误返回 401 的问题
- 移除管理后台中用户 admin 角色及相关 UI 与 API
- 更新信令前端环境代理地址,修正路由名称大小写
This commit is contained in:
2026-08-03 11:09:37 +08:00
parent e6ed7c8e3e
commit 78aa3d3c71
16 changed files with 374 additions and 79 deletions

View File

@@ -4,7 +4,7 @@
# 【代理目标】Vite dev server 把 /api/** 转发到这里(仅开发态生效,浏览器不直接访问)。
# 后端 Spring Boot 地址,按需改成本机或远程信令服务器。
VITE_API_TARGET=http://localhost:8080
VITE_API_TARGET=http://192.168.5.224:8080
# 【请求基地址】留空表示走相对路径 /api同源经上面的代理转发——推荐保持留空。
# 仅在需要浏览器直连后端、绕过代理时才填绝对地址,例如:

View File

@@ -130,7 +130,6 @@ export interface UserAccountView {
status: AccountStatus;
statusReason: string | null;
statusUntil: number | null;
admin: boolean;
totpEnabled: boolean;
createdAt: number;
lastLoginAt: number | null;
@@ -158,7 +157,6 @@ export function getUsers(query: UserQuery = {}): Promise<UserAccountView[]> {
export function createUser(payload: {
username: string;
password: string;
admin?: boolean;
}): Promise<{ success: boolean; message?: string; user?: UserAccountView }> {
return request.post('/api/admin/users', payload) as unknown as Promise<{
success: boolean;
@@ -182,12 +180,6 @@ export function resetUserPassword(
}) as unknown as Promise<{ success: boolean; message?: string }>;
}
export function setUserAdmin(userId: string, admin: boolean): Promise<{ success: boolean }> {
return request.post(`/api/admin/users/${encodeURIComponent(userId)}/admin`, {
admin,
}) as unknown as Promise<{ success: boolean }>;
}
/** 封禁账号durationSeconds 省略或 <=0 表示永久。 */
export function banUser(
userId: string,

View File

@@ -41,8 +41,8 @@ request.interceptors.response.use(
if (status === 401) {
localStorage.removeItem('admin_token');
ElMessage.error('登录已失效,请重新登录');
if (router.currentRoute.value.name !== 'Login') {
router.replace({ name: 'Login' });
if (router.currentRoute.value.name !== 'login') {
router.replace({ name: 'login' });
}
} else {
ElMessage.error(error.response?.data?.message || error.message || '请求失败');

View File

@@ -31,9 +31,6 @@
<el-table-column prop="username" label="用户名" min-width="150">
<template #default="{ row }">
<span class="link-user" @click="openBindings(row)">{{ row.username }}</span>
<el-tag v-if="row.admin" type="danger" size="small" style="margin-left: 6px">
管理员
</el-tag>
<el-tag v-if="row.deviceCount > 0" type="info" size="small" style="margin-left: 6px">
{{ row.deviceCount }} 台设备
</el-tag>
@@ -96,9 +93,6 @@
<el-dropdown-item command="bindings">查看绑定设备</el-dropdown-item>
<el-dropdown-item command="sessions">查看会话</el-dropdown-item>
<el-dropdown-item command="reset">重置密码</el-dropdown-item>
<el-dropdown-item command="admin">
{{ row.admin ? '取消管理员' : '设为管理员' }}
</el-dropdown-item>
<el-dropdown-item command="delete" divided>删除账号</el-dropdown-item>
</el-dropdown-menu>
</template>
@@ -118,9 +112,6 @@
<el-form-item label="密码" prop="password">
<el-input v-model="createForm.password" type="password" show-password />
</el-form-item>
<el-form-item label="管理员">
<el-switch v-model="createForm.admin" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createVisible = false">取消</el-button>
@@ -211,7 +202,6 @@ import {
createUser,
deleteUser,
resetUserPassword,
setUserAdmin,
banUser,
unbanUser,
kickUser,
@@ -249,7 +239,7 @@ watch(statusFilter, load);
// ---------- 添加用户 ----------
const createVisible = ref(false);
const createFormRef = ref<FormInstance>();
const createForm = ref({ username: '', password: '', admin: false });
const createForm = ref({ username: '', password: '' });
const createRules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
@@ -262,7 +252,7 @@ const createRules: FormRules = {
};
function openCreate() {
createForm.value = { username: '', password: '', admin: false };
createForm.value = { username: '', password: '' };
createVisible.value = true;
}
@@ -328,19 +318,6 @@ async function handleDelete(row: UserAccountView) {
await load();
}
async function handleToggleAdmin(row: UserAccountView) {
const next = !row.admin;
const ok = await ElMessageBox.confirm(
`确定${next ? '授予' : '取消'}${row.username}」的管理员权限?`,
'权限调整',
{ type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' },
).catch(() => false);
if (!ok) return;
await setUserAdmin(row.userId, next);
ElMessage.success('权限已更新');
await load();
}
// ---------- 重置密码 ----------
const resetVisible = ref(false);
const newPassword = ref('');
@@ -366,6 +343,9 @@ async function submitReset() {
} else {
ElMessage.error(resp.message || '重置失败');
}
} catch {
// 错误提示已由 request 响应拦截器统一弹出,这里仅吞掉异常,
// 避免未捕获的 Promise rejection并保证弹窗保持打开以便重试。
} finally {
submitting.value = false;
}
@@ -418,7 +398,6 @@ function handleCommand(cmd: string, row: UserAccountView) {
if (cmd === 'bindings') openBindings(row);
else if (cmd === 'sessions') openSessions(row);
else if (cmd === 'reset') openReset(row);
else if (cmd === 'admin') handleToggleAdmin(row);
else if (cmd === 'delete') handleDelete(row);
}