Complete admin operations management

This commit is contained in:
2026-07-07 13:55:10 +08:00
parent da262f4596
commit 7be9847d65
10 changed files with 510 additions and 27 deletions

View File

@@ -15,6 +15,15 @@ import type {
ModelItem,
} from "./types/api";
type UserKnowledgePermissionRow = {
knowledgeId: number;
name: string;
status: number;
assigned: boolean;
effectiveAt: string;
expiredAt: string;
};
const admin = ref<AdminProfile | null>(null);
const activeMenu = ref("dashboard");
const loading = ref(false);
@@ -34,6 +43,11 @@ const chatDetail = ref<ChatDetail | null>(null);
const chatDetailOpen = ref(false);
const recordTab = ref("chats");
const editingModelId = ref<number | null>(null);
const editingKnowledgeId = ref<number | null>(null);
const userPermissionOpen = ref(false);
const userPermissionLoading = ref(false);
const permissionUser = ref<AdminUser | null>(null);
const userKnowledgePermissionRows = ref<UserKnowledgePermissionRow[]>([]);
const agentDebugging = ref(false);
const agentPreviewMessages = ref<{ role: "user" | "assistant" | "system"; content: string }[]>([
{ role: "assistant", content: "选择模型和知识库后,可以在这里调试 Agent 的真实问答效果。" },
@@ -245,13 +259,103 @@ async function saveUser(row: AdminUser) {
await loadCurrentMenu();
}
async function saveKnowledge() {
await api.createKnowledge({ ...knowledgeForm });
ElMessage.success("知识库已新增");
Object.assign(knowledgeForm, { name: "", feishuSpaceId: "", feishuNodeId: "", status: 1, remark: "" });
async function deleteUser(row: AdminUser) {
if (!window.confirm(`确认删除用户 ${row.phone} 吗?删除后前台将无法继续使用该账号。`)) return;
await api.deleteUser(row.id);
ElMessage.success("用户已删除");
await loadCurrentMenu();
}
async function openUserPermissions(row: AdminUser) {
permissionUser.value = row;
userPermissionOpen.value = true;
userPermissionLoading.value = true;
try {
const [knowledgeRows, detail] = await Promise.all([api.knowledge(), api.userKnowledgePermissions(row.id)]);
knowledge.value = knowledgeRows;
const permissionMap = new Map(detail.permissions.map((item) => [item.knowledgeId, item]));
userKnowledgePermissionRows.value = knowledgeRows.map((item) => {
const permission = permissionMap.get(item.id);
return {
knowledgeId: item.id,
name: item.name,
status: item.status,
assigned: Boolean(permission),
effectiveAt: permission?.effectiveAt ?? "",
expiredAt: permission?.expiredAt ?? "",
};
});
} finally {
userPermissionLoading.value = false;
}
}
async function saveUserPermissions() {
if (!permissionUser.value) return;
await api.saveUserKnowledgePermissions(permissionUser.value.id, {
permissions: userKnowledgePermissionRows.value
.filter((item) => item.assigned)
.map((item) => ({
knowledgeId: item.knowledgeId,
effectiveAt: item.effectiveAt || null,
expiredAt: item.expiredAt || null,
})),
});
ElMessage.success("知识库权限已保存");
userPermissionOpen.value = false;
}
async function saveKnowledge() {
if (!knowledgeForm.name || !knowledgeForm.feishuSpaceId || !knowledgeForm.feishuNodeId) {
ElMessage.error("请填写知识库名称、SpaceID 和 NodeID");
return;
}
if (editingKnowledgeId.value) {
await api.updateKnowledge(editingKnowledgeId.value, { ...knowledgeForm });
ElMessage.success("知识库已更新");
} else {
await api.createKnowledge({ ...knowledgeForm });
ElMessage.success("知识库已新增");
}
resetKnowledgeForm();
await loadCurrentMenu();
}
async function saveKnowledgeRow(row: KnowledgeItem) {
await api.updateKnowledge(row.id, {
name: row.name,
feishuSpaceId: row.feishuSpaceId,
feishuNodeId: row.feishuNodeId,
status: row.status,
remark: row.remark ?? "",
});
ElMessage.success("知识库状态已更新");
}
function editKnowledge(row: KnowledgeItem) {
editingKnowledgeId.value = row.id;
Object.assign(knowledgeForm, {
name: row.name,
feishuSpaceId: row.feishuSpaceId,
feishuNodeId: row.feishuNodeId,
status: row.status,
remark: row.remark ?? "",
});
}
async function deleteKnowledge(row: KnowledgeItem) {
if (!window.confirm(`确认删除知识库 ${row.name} 吗?已分配给用户的授权也会失效。`)) return;
await api.deleteKnowledge(row.id);
ElMessage.success("知识库已删除");
if (editingKnowledgeId.value === row.id) resetKnowledgeForm();
await loadCurrentMenu();
}
function resetKnowledgeForm() {
editingKnowledgeId.value = null;
Object.assign(knowledgeForm, { name: "", feishuSpaceId: "", feishuNodeId: "", status: 1, remark: "" });
}
async function savePrompt() {
const result = await api.savePrompt(promptContent.value);
promptContent.value = result.promptContent;
@@ -473,14 +577,16 @@ async function debugAgent() {
ElMessage.error("请输入调试问题");
return;
}
agentPreviewMessages.value.push({ role: "user", content: agentForm.question.trim() });
const question = agentForm.question.trim();
agentPreviewMessages.value.push({ role: "user", content: question });
agentForm.question = "";
agentDebugging.value = true;
try {
const result: AgentDebugResult = await api.debugAgent({
promptContent: promptContent.value,
modelId: agentForm.modelId,
knowledgeIds: agentForm.knowledgeIds,
question: agentForm.question.trim(),
question,
temperature: agentForm.temperature,
topP: agentForm.topP,
topK: agentForm.topK,
@@ -599,8 +705,10 @@ function buildChatQuery() {
<el-input v-model="userKeyword" placeholder="手机号或姓名" clearable @change="loadCurrentMenu" />
</div>
<el-table :data="users" stripe>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="phone" label="手机号" width="150" />
<el-table-column prop="name" label="姓名" width="140" />
<el-table-column prop="nickname" label="昵称" width="140" />
<el-table-column label="状态" width="130">
<template #default="{ row }"><el-switch v-model="row.status" :active-value="1" :inactive-value="0" /></template>
</el-table-column>
@@ -608,25 +716,55 @@ function buildChatQuery() {
<template #default="{ row }"><el-input-number v-model="row.dailyChatLimit" :min="0" size="small" /></template>
</el-table-column>
<el-table-column prop="dailyChatUsed" label="已用" width="90" />
<el-table-column label="操作" width="110">
<template #default="{ row }"><el-button size="small" @click="saveUser(row)">保存</el-button></template>
<el-table-column prop="lastLoginAt" label="最近登录" width="180" />
<el-table-column label="操作" width="250" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="saveUser(row)">保存</el-button>
<el-button size="small" type="primary" @click="openUserPermissions(row)">知识库权限</el-button>
<el-button size="small" type="danger" @click="deleteUser(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<template v-if="activeMenu === 'knowledge'">
<div class="page-head"><h2>知识库管理</h2><p>维护飞书知识库 SpaceID NodeID</p></div>
<el-form class="inline-form" :model="knowledgeForm">
<el-form class="knowledge-form" :model="knowledgeForm">
<el-input v-model="knowledgeForm.name" placeholder="知识库名称" />
<el-input v-model="knowledgeForm.feishuSpaceId" placeholder="SpaceID" />
<el-input v-model="knowledgeForm.feishuNodeId" placeholder="NodeID" />
<el-button type="primary" @click="saveKnowledge">新增</el-button>
<el-input v-model="knowledgeForm.remark" placeholder="备注" />
<div class="form-switch-row">
<span>启用</span>
<el-switch v-model="knowledgeForm.status" :active-value="1" :inactive-value="0" />
</div>
<div class="form-actions-inline">
<el-button type="primary" @click="saveKnowledge">{{ editingKnowledgeId ? "保存" : "新增" }}</el-button>
<el-button @click="resetKnowledgeForm">清空</el-button>
</div>
</el-form>
<el-table :data="knowledge" stripe>
<el-table-column prop="name" label="名称" />
<el-table-column prop="feishuSpaceId" label="SpaceID" />
<el-table-column prop="feishuNodeId" label="NodeID" />
<el-table-column prop="status" label="状态" width="90" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" min-width="160" />
<el-table-column prop="feishuSpaceId" label="SpaceID" min-width="180" show-overflow-tooltip />
<el-table-column prop="feishuNodeId" label="NodeID" min-width="180" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" min-width="180" show-overflow-tooltip />
<el-table-column label="状态" width="120">
<template #default="{ row }">
<el-switch
v-model="row.status"
:active-value="1"
:inactive-value="0"
@change="saveKnowledgeRow(row)"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="editKnowledge(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteKnowledge(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
@@ -676,17 +814,8 @@ function buildChatQuery() {
<el-input-number v-model="agentForm.frequencyPenalty" :min="-2" :max="2" :step="0.1" />
</el-form-item>
</div>
<el-form-item label="调试问题">
<el-input
v-model="agentForm.question"
type="textarea"
:rows="3"
placeholder="输入一个真实用户可能会问的问题"
/>
</el-form-item>
<div class="actions">
<el-button type="primary" @click="savePrompt">保存 Agent 提示词</el-button>
<el-button type="primary" :loading="agentDebugging" @click="debugAgent">发送调试</el-button>
<el-button @click="clearAgentPreview">清空预览</el-button>
</div>
</el-form>
@@ -707,6 +836,17 @@ function buildChatQuery() {
<pre>{{ message.content }}</pre>
</article>
</div>
<div class="agent-preview-composer">
<el-input
v-model="agentForm.question"
type="textarea"
:rows="2"
resize="none"
placeholder="输入测试问题"
@keydown.enter.exact.prevent="debugAgent"
/>
<el-button type="primary" :loading="agentDebugging" @click="debugAgent">发送</el-button>
</div>
</aside>
</section>
</template>
@@ -907,6 +1047,62 @@ function buildChatQuery() {
</section>
</section>
<el-drawer v-model="userPermissionOpen" size="760px" title="知识库权限分配">
<template v-if="permissionUser">
<section class="permission-summary">
<div>
<span>用户</span>
<strong>{{ permissionUser.name || '-' }}</strong>
</div>
<div>
<span>手机号</span>
<strong>{{ permissionUser.phone }}</strong>
</div>
<div>
<span>每日额度</span>
<strong>{{ permissionUser.dailyChatLimit }}</strong>
</div>
</section>
<el-table v-loading="userPermissionLoading" :data="userKnowledgePermissionRows" stripe>
<el-table-column label="授权" width="90">
<template #default="{ row }">
<el-switch v-model="row.assigned" />
</template>
</el-table-column>
<el-table-column prop="name" label="知识库" min-width="170" />
<el-table-column label="状态" width="90">
<template #default="{ row }">{{ row.status === 1 ? "启用" : "禁用" }}</template>
</el-table-column>
<el-table-column label="生效时间" width="220">
<template #default="{ row }">
<el-date-picker
v-model="row.effectiveAt"
type="datetime"
value-format="YYYY-MM-DDTHH:mm:ss"
placeholder="不限制"
:disabled="!row.assigned"
/>
</template>
</el-table-column>
<el-table-column label="失效时间" width="220">
<template #default="{ row }">
<el-date-picker
v-model="row.expiredAt"
type="datetime"
value-format="YYYY-MM-DDTHH:mm:ss"
placeholder="不限制"
:disabled="!row.assigned"
/>
</template>
</el-table-column>
</el-table>
<div class="drawer-actions">
<el-button type="primary" @click="saveUserPermissions">保存权限</el-button>
<el-button @click="userPermissionOpen = false">取消</el-button>
</div>
</template>
</el-drawer>
<el-drawer v-model="chatDetailOpen" size="760px" title="聊天详情">
<template v-if="chatDetail">
<section class="chat-summary">