feat: add admin permissions voice input analytics and feedback

This commit is contained in:
2026-08-11 17:17:59 +08:00
parent 3b9215cc3d
commit fd2da26d10
46 changed files with 1836 additions and 192 deletions

View File

@@ -49,10 +49,26 @@ const SsoIntegrationView = defineAsyncComponent(
const SystemConfigView = defineAsyncComponent(
() => import("./components/SystemConfigView.vue"),
);
const AdminManagementView = defineAsyncComponent(
() => import("./components/AdminManagementView.vue"),
);
const FeedbackManagementView = defineAsyncComponent(() => import("./components/FeedbackManagementView.vue"));
const admin = ref<AdminProfile | null>(null);
const activeMenu = ref("dashboard");
const loading = ref(false);
const passwordDialogVisible = ref(false);
const passwordSaving = ref(false);
const passwordForm = reactive({ currentPassword: "", newPassword: "", confirmPassword: "" });
const can = (permission: string) => Boolean(admin.value?.isSuperAdmin || admin.value?.permissions.includes(permission));
const menuPermissions: Record<string, string> = {
dashboard: "dashboard.view", users: "users.view", entitlements: "entitlements.view",
knowledge: "knowledge.view", prompt: "prompt.view", models: "models.view",
"content-generation": "content-generation.view", configs: "configs.view", sso: "sso.view",
records: "records.view", retrievals: "retrievals.view", attention: "attention.view", admins: "admins.view",
feedback: "feedback.view",
};
const entitlementPlans = ref<EntitlementPlan[]>([]);
const models = ref<ModelItem[]>([]);
@@ -190,7 +206,8 @@ onMounted(async () => {
if (!getToken()) return;
try {
admin.value = await api.profile();
await loadCurrentMenu();
passwordDialogVisible.value = admin.value.mustChangePassword;
if (!admin.value.mustChangePassword) await enterFirstAvailableMenu();
} catch {
clearToken();
}
@@ -202,7 +219,8 @@ async function login(credentials: { username: string; password: string }) {
const result = await api.login(credentials.username, credentials.password);
saveToken(result.token);
admin.value = result.admin;
await loadCurrentMenu();
passwordDialogVisible.value = result.admin.mustChangePassword;
if (!result.admin.mustChangePassword) await enterFirstAvailableMenu();
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : "登录失败");
} finally {
@@ -220,10 +238,32 @@ async function logout() {
}
async function switchMenu(menu: string) {
if (!can(menuPermissions[menu])) return;
activeMenu.value = menu;
await loadCurrentMenu();
}
async function enterFirstAvailableMenu() {
const first = Object.keys(menuPermissions).find((menu) => can(menuPermissions[menu]));
activeMenu.value = first ?? "dashboard";
if (first) await loadCurrentMenu();
}
async function changeInitialPassword() {
if (passwordForm.newPassword.length < 8) return ElMessage.warning("新密码至少 8 位");
if (passwordForm.newPassword !== passwordForm.confirmPassword) return ElMessage.warning("两次输入的新密码不一致");
passwordSaving.value = true;
try {
await api.changeAdminPassword({ currentPassword: passwordForm.currentPassword, newPassword: passwordForm.newPassword });
if (admin.value) admin.value.mustChangePassword = false;
passwordDialogVisible.value = false;
Object.assign(passwordForm, { currentPassword: "", newPassword: "", confirmPassword: "" });
ElMessage.success("密码已修改,请使用新密码登录");
await logout();
} catch (error) { ElMessage.error(error instanceof Error ? error.message : "密码修改失败"); }
finally { passwordSaving.value = false; }
}
async function previewKnowledge(knowledgeId: number) {
previewKnowledgeId.value = knowledgeId;
await switchMenu("prompt");
@@ -596,77 +636,99 @@ async function clearFeishuCache() {
<aside class="sidebar">
<div class="sidebar-title">大本营千问千答</div>
<button
v-if="can('dashboard.view')"
:class="{ active: activeMenu === 'dashboard' }"
@click="switchMenu('dashboard')"
>
数据看板
</button>
<button
v-if="can('users.view')"
:class="{ active: activeMenu === 'users' }"
@click="switchMenu('users')"
>
用户管理
</button>
<button
v-if="can('entitlements.view')"
:class="{ active: activeMenu === 'entitlements' }"
@click="switchMenu('entitlements')"
>
权益管理
</button>
<button
v-if="can('knowledge.view')"
:class="{ active: activeMenu === 'knowledge' }"
@click="switchMenu('knowledge')"
>
知识库管理
</button>
<button
v-if="can('prompt.view')"
:class="{ active: activeMenu === 'prompt' }"
@click="switchMenu('prompt')"
>
Agent 管理
</button>
<button
v-if="can('models.view')"
:class="{ active: activeMenu === 'models' }"
@click="switchMenu('models')"
>
模型管理
</button>
<button
v-if="can('content-generation.view')"
:class="{ active: activeMenu === 'content-generation' }"
@click="switchMenu('content-generation')"
>
内容生成
</button>
<button
v-if="can('configs.view')"
:class="{ active: activeMenu === 'configs' }"
@click="switchMenu('configs')"
>
系统配置
</button>
<button
v-if="can('sso.view')"
:class="{ active: activeMenu === 'sso' }"
@click="switchMenu('sso')"
>
应用接入
</button>
<button
v-if="can('records.view')"
:class="{ active: activeMenu === 'records' }"
@click="switchMenu('records')"
>
记录审计
</button>
<button
v-if="can('retrievals.view')"
:class="{ active: activeMenu === 'retrievals' }"
@click="switchMenu('retrievals')"
>
检索日志
</button>
<button
v-if="can('feedback.view')"
:class="{ active: activeMenu === 'feedback' }"
@click="switchMenu('feedback')"
>反馈管理</button>
<button
v-if="can('attention.view')"
:class="{ active: activeMenu === 'attention' }"
@click="switchMenu('attention')"
>
人工关注
</button>
<button
v-if="admin.isSuperAdmin"
:class="{ active: activeMenu === 'admins' }"
@click="switchMenu('admins')"
>管理员与权限</button>
</aside>
<section class="workspace">
@@ -1057,8 +1119,20 @@ async function clearFeishuCache() {
<RetrievalLogView v-if="activeMenu === 'retrievals'" />
<AttentionManagementView v-if="activeMenu === 'attention'" />
<FeedbackManagementView v-if="activeMenu === 'feedback'" :can-delete="can('feedback.delete')" />
<RecordAuditView v-if="activeMenu === 'records'" />
<AdminManagementView v-if="activeMenu === 'admins'" />
</section>
</section>
</main>
<el-dialog v-model="passwordDialogVisible" title="首次登录,请修改初始密码" width="460px" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false">
<el-alert title="为保障账号安全,修改密码后需要重新登录。" type="warning" :closable="false" show-icon />
<el-form label-position="top" class="password-form">
<el-form-item label="当前初始密码"><el-input v-model="passwordForm.currentPassword" type="password" show-password /></el-form-item>
<el-form-item label="新密码"><el-input v-model="passwordForm.newPassword" type="password" show-password placeholder="至少 8 位" /></el-form-item>
<el-form-item label="确认新密码"><el-input v-model="passwordForm.confirmPassword" type="password" show-password /></el-form-item>
</el-form>
<template #footer><el-button @click="logout">退出登录</el-button><el-button type="primary" :loading="passwordSaving" @click="changeInitialPassword">确认修改</el-button></template>
</el-dialog>
</template>