feat(aihr): 重构运营后台与租户知识治理

- 重构运营总览、侧栏和内容运营工作台\n- 增加大喇叭、成果审核与运营统计链路\n- 补齐租户、知识空间、分类和知识维护闭环\n- 加固标签页租户上下文、停用租户写保护与迁移预检
This commit is contained in:
2026-07-23 01:24:22 +08:00
parent 333c365c6f
commit 163b08ecfe
94 changed files with 8610 additions and 1114 deletions
+71
View File
@@ -0,0 +1,71 @@
import request from '@/utils/request';
type ApiResult<T> = {
code: number;
msg: string;
data: T;
};
export type BroadcastStatus = 'PUBLISHED' | 'WITHDRAWN';
export type BroadcastAdminMessage = {
id: number;
title: string;
content: string;
status: BroadcastStatus;
currentVersion: number;
publishedBy: number;
publishedAt: string;
withdrawnBy?: number;
withdrawnAt?: string;
withdrawReason?: string;
};
export type BroadcastAdminPage = {
/** Effective request tenant, including an administrator's dynamic-tenant selection. */
tenantId: string;
total: number;
pageNum: number;
pageSize: number;
rows: BroadcastAdminMessage[];
};
export type PublishBroadcastRequest = {
requestId: string;
title: string;
content: string;
expectedTenantId: string;
};
export type WithdrawBroadcastRequest = {
reason: string;
expectedTenantId: string;
};
export function listBroadcastAdminMessages(params: {
pageNum: number;
pageSize: number;
status?: BroadcastStatus;
}): Promise<ApiResult<BroadcastAdminPage>> {
return request({
url: '/api/aihr/broadcast/admin/messages',
method: 'get',
params
});
}
export function publishBroadcast(data: PublishBroadcastRequest): Promise<ApiResult<{ id: number; version: number }>> {
return request({
url: '/api/aihr/broadcast/messages',
method: 'post',
data
});
}
export function withdrawBroadcast(id: number, data: WithdrawBroadcastRequest): Promise<ApiResult<void>> {
return request({
url: `/api/aihr/broadcast/messages/${id}/withdraw`,
method: 'post',
data
});
}
+45 -37
View File
@@ -6,27 +6,44 @@ type ApiResult<T> = {
data: T;
};
export type DashboardHeroEntry = {
export type DashboardState = 'success' | 'warning' | 'danger' | 'info';
export type DashboardRecordState = DashboardState | 'active' | 'done';
export type DashboardTodo = {
key: string;
status: string;
type: string;
note: string;
};
export type DashboardStage = {
step: number;
name: string;
desc: string;
label: string;
state: string;
tagType: string;
count: number;
detail: string;
path: string;
actionLabel: string;
tone: DashboardState;
available?: boolean;
};
export type DashboardRisk = {
name: string;
owner: string;
status: string;
type: string;
export type DashboardPilotProgress = {
key: string;
label: string;
value: string;
detail: string;
state: DashboardState;
available?: boolean;
};
export type DashboardAcceptance = {
key: string;
label: string;
detail: string;
state: DashboardState;
};
export type DashboardKnowledge = {
key: string;
label: string;
value: string;
detail: string;
tone: DashboardState;
path: string;
available?: boolean;
};
export type DashboardTrainingRecord = {
@@ -34,36 +51,27 @@ export type DashboardTrainingRecord = {
scene: string;
score: number;
status: string;
state: string;
state: DashboardRecordState;
time: string;
};
export type DashboardPipelineStep = {
name: string;
count: string;
state: string;
};
export type DashboardMetric = {
export type DashboardHealth = {
key: string;
label: string;
value: string;
target: string;
type: string;
detail: string;
state: DashboardState;
available?: boolean;
};
export type DashboardOverview = {
snapshotTime: string;
heroEntries: DashboardHeroEntry[];
stages: DashboardStage[];
risks: DashboardRisk[];
todos: DashboardTodo[];
pilotProgress: DashboardPilotProgress[];
acceptance: DashboardAcceptance[];
knowledge: DashboardKnowledge[];
records: DashboardTrainingRecord[];
pipeline: DashboardPipelineStep[];
pipelineSummary: {
todayNew: string;
pending: string;
completed: string;
};
metrics: DashboardMetric[];
health: DashboardHealth[];
};
export function getDashboardOverview(): Promise<ApiResult<DashboardOverview>> {
+17 -8
View File
@@ -1,5 +1,5 @@
import axios from 'axios';
import request, { globalHeaders } from '@/utils/request';
import request, { globalHeaders, reconcileTenantContextBlobResponse } from '@/utils/request';
type ApiResult<T> = {
code: number;
@@ -210,13 +210,22 @@ export function getPracticeReview(id: number): Promise<ApiResult<ReviewDetail>>
}
export async function getPracticeAudioBlobUrl(ossId: number | string): Promise<string> {
const response = await axios({
method: 'get',
url: `${import.meta.env.VITE_APP_BASE_API}/api/aihr/mobile/oss/${ossId}`,
responseType: 'blob',
headers: globalHeaders()
});
return URL.createObjectURL(response.data);
try {
const response = await axios({
method: 'get',
url: `${import.meta.env.VITE_APP_BASE_API}/api/aihr/mobile/oss/${ossId}`,
responseType: 'blob',
headers: globalHeaders()
});
const contentType = String(response.headers['content-type'] || '');
if (contentType.includes('application/json') && (await reconcileTenantContextBlobResponse(response.data))) {
throw new Error('租户上下文已变更,请刷新后重试');
}
return URL.createObjectURL(response.data);
} catch (error) {
await reconcileTenantContextBlobResponse(axios.isAxiosError(error) ? error.response?.data : undefined);
throw error;
}
}
export function markPracticeReviewed(id: number, data: ReviewAdviceRequest): Promise<ApiResult<null>> {
+30 -4
View File
@@ -29,11 +29,24 @@ export type KnowledgeDocument = {
name: string;
type: string;
status: string;
categoryId?: number | null;
categoryName?: string | null;
ossId?: number | null;
fragmentCount: number;
updateTime?: string | null;
};
export type KnowledgeCategory = {
id: number;
code: string;
name: string;
status: 'ACTIVE' | 'DISABLED';
sortOrder: number;
documentCount: number;
};
export type CategoryForm = Pick<KnowledgeCategory, 'code' | 'name' | 'status' | 'sortOrder'>;
export type UnbindDocumentResult = {
attachId: number;
spaceCode: string;
@@ -68,8 +81,7 @@ export type AppForm = {
export type IssuedToken = { appCode: string; token: string; expiresTime?: string | null };
export type CreatedApp = { app: KnowledgeApp; token?: string | null };
export const listKnowledgeSpaces = (): Promise<ApiResult<KnowledgeSpace[]>> =>
request({ url: '/api/knowledge/admin/spaces', method: 'get' });
export const listKnowledgeSpaces = (): Promise<ApiResult<KnowledgeSpace[]>> => request({ url: '/api/knowledge/admin/spaces', method: 'get' });
export const createKnowledgeSpace = (data: SpaceForm): Promise<ApiResult<KnowledgeSpace>> =>
request({ url: '/api/knowledge/admin/spaces', method: 'post', data });
@@ -80,17 +92,31 @@ export const updateKnowledgeSpace = (id: number, data: SpaceForm): Promise<ApiRe
export const listSpaceDocuments = (id: number): Promise<ApiResult<KnowledgeDocument[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/documents`, method: 'get' });
export const assignSpaceDocumentCategory = (id: number, attachId: number, categoryId?: number | null): Promise<ApiResult<KnowledgeDocument>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/documents/${attachId}/category`, method: 'put', data: { categoryId: categoryId ?? null } });
export const unbindSpaceDocument = (id: number, attachId: number): Promise<ApiResult<UnbindDocumentResult>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/documents/${attachId}`, method: 'delete' });
export const listKnowledgeCategories = (id: number): Promise<ApiResult<KnowledgeCategory[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/categories`, method: 'get' });
export const createKnowledgeCategory = (id: number, data: CategoryForm): Promise<ApiResult<KnowledgeCategory>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/categories`, method: 'post', data });
export const updateKnowledgeCategory = (id: number, categoryId: number, data: CategoryForm): Promise<ApiResult<KnowledgeCategory>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/categories/${categoryId}`, method: 'put', data });
export const deleteKnowledgeCategory = (id: number, categoryId: number): Promise<ApiResult<void>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/categories/${categoryId}`, method: 'delete' });
export const listSpaceGrants = (id: number): Promise<ApiResult<SpaceGrant[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/grants`, method: 'get' });
export const replaceSpaceGrants = (id: number, grants: SpaceGrant[]): Promise<ApiResult<SpaceGrant[]>> =>
request({ url: `/api/knowledge/admin/spaces/${id}/grants`, method: 'put', data: { grants } });
export const listKnowledgeApps = (): Promise<ApiResult<KnowledgeApp[]>> =>
request({ url: '/api/knowledge/admin/apps', method: 'get' });
export const listKnowledgeApps = (): Promise<ApiResult<KnowledgeApp[]>> => request({ url: '/api/knowledge/admin/apps', method: 'get' });
export const createKnowledgeApp = (data: AppForm): Promise<ApiResult<CreatedApp>> =>
request({ url: '/api/knowledge/admin/apps', method: 'post', data });
+53
View File
@@ -0,0 +1,53 @@
import request from '@/utils/request';
type ApiResult<T> = {
code: number;
msg: string;
data: T;
};
export type WorkReport = {
id: number;
type: string;
typeLabel: string;
title: string;
content: string;
attachmentName?: string;
attachmentAvailable?: boolean;
attachmentUrl?: string;
status: string;
statusLabel: string;
submitterName: string;
reviewNote?: string;
createdAt: string;
reviewedAt?: string;
};
export type WorkReportReview = {
decision: 'APPROVE' | 'REJECT';
note?: string;
};
export function listWorkReports(status?: string): Promise<ApiResult<WorkReport[]>> {
return request({
url: '/api/aihr/work-report/reports',
method: 'get',
params: { status }
});
}
export function reviewWorkReport(id: number, data: WorkReportReview): Promise<ApiResult<WorkReport>> {
return request({
url: `/api/aihr/work-report/reports/${id}/review`,
method: 'post',
data
});
}
export function getWorkReportAttachment(url: string): Promise<Blob> {
return request({
url,
method: 'get',
responseType: 'blob'
}) as Promise<Blob>;
}
+70
View File
@@ -0,0 +1,70 @@
import request from '@/utils/request';
type ApiResult<T> = {
code: number;
msg: string;
data: T;
};
export type WorkResultProject = {
projectCode: string;
projectName: string;
};
export type WorkResultItem = {
captureId: number;
suggestedType: string;
category: string;
summary: string;
businessStatus: string;
deliveryStatus: string;
occurredAt: string;
sourceType: string;
sourceUrl: string;
};
export type MemberWorkResult = {
ownerUserId: number;
ownerName: string;
projectCode: string;
projectName: string;
workDate: string;
version: number;
generatedAt: string;
sections: Record<string, WorkResultItem[]>;
total: number;
pending: number;
};
export type ProjectWorkResult = {
projectCode: string;
projectName: string;
workDate: string;
contributorCount: number;
resultCount: number;
pendingCount: number;
highPriorityCount: number;
members: MemberWorkResult[];
pageNum: number;
pageSize: number;
};
export function listWorkResultProjects(): Promise<ApiResult<WorkResultProject[]>> {
return request({
url: '/api/aihr/work-results/projects',
method: 'get'
});
}
export function getProjectWorkResult(params: {
projectCode: string;
workDate: string;
pageNum: number;
pageSize: number;
}): Promise<ApiResult<ProjectWorkResult>> {
return request({
url: '/api/aihr/work-results/project',
method: 'get',
params
});
}
+8
View File
@@ -71,6 +71,14 @@ export function dynamicTenant(tenantId: string | number) {
});
}
// 查询当前超级管理员的动态租户上下文
export function getDynamicTenant(): AxiosPromise<string | null> {
return request({
url: '/system/tenant/dynamic',
method: 'get'
});
}
// 清除动态租户
export function dynamicClear() {
return request({
+1
View File
@@ -51,6 +51,7 @@ export interface TenantVO {
companyName: string;
domain: any;
tenantId: string;
status?: string;
}
export interface TenantInfo {