Merge branch 'codex/personal-assistant-phase2' into codex/multi-tenant-knowledge-platform

# Conflicts:
#	backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/domain/AihrSopDto.java
#	backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrModelSeedService.java
#	backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java
#	backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrModelSeedServiceTest.java
#	backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/service/AihrSopSeedServiceTest.java
#	docs/API_INTEGRATION.md
#	docs/个人AI助理阶段二专项TechSpec.md
#	docs/个人AI助理阶段二开发推进计划.md
#	frontend/src/views/knowledge/processing.vue
#	mobile-uni/src/pages/user/sop/index.vue
#	mobile-uni/src/services/api.ts
#	mobile-uni/src/services/personal-assistant.ts
#	mobile-uni/tests/personal-assistant.test.mjs
#	scripts/demo-check.sh
#	scripts/personal-assistant-smoke.sh
This commit is contained in:
2026-07-22 08:54:04 +08:00
72 changed files with 13883 additions and 61 deletions
+28
View File
@@ -0,0 +1,28 @@
import request from '@/utils/request';
export interface PersonalPublishRequestRow {
id: string;
itemId: string;
title: string;
sourceType: string;
targetScope: string;
reason: string;
sanitizedPreview?: string;
status: 'PENDING' | 'APPROVED' | 'REJECTED';
reviewComment?: string;
publishedKnowledgeId?: string;
createdAt?: string;
}
export const listPersonalPublishRequests = () =>
request<PersonalPublishRequestRow[]>({
url: '/api/aihr/personal-assistant/admin/publish-requests',
method: 'get'
});
export const reviewPersonalPublishRequest = (id: string, payload: { approved: boolean; comment: string }) =>
request<PersonalPublishRequestRow>({
url: `/api/aihr/personal-assistant/admin/publish-requests/${encodeURIComponent(id)}/review`,
method: 'post',
data: payload
});
+49 -2
View File
@@ -233,16 +233,37 @@
<el-table-column prop="detail" label="详情" min-width="260" />
</el-table>
</section>
<section class="panel event-panel">
<div class="panel-title compact">
<h2>个人资料入库审核</h2>
<el-button link :loading="publishLoading" @click="loadPublishRequests">刷新</el-button>
</div>
<el-table :data="publishRequests" size="small" empty-text="暂无待审核申请">
<el-table-column prop="title" label="资料" min-width="200" show-overflow-tooltip />
<el-table-column prop="sourceType" label="来源" width="90" />
<el-table-column prop="targetScope" label="目标范围" min-width="140" />
<el-table-column prop="reason" label="申请说明" min-width="220" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="90" />
<el-table-column label="操作" width="150">
<template #default="{ row }">
<el-button v-if="row.status === 'PENDING'" link type="success" @click="reviewPublish(row, true)">批准</el-button>
<el-button v-if="row.status === 'PENDING'" link type="danger" @click="reviewPublish(row, false)">驳回</el-button>
</template>
</el-table-column>
</el-table>
</section>
</div>
</template>
<script setup name="KnowledgeProcessing" lang="ts">
import { CircleCheck, Document, Files, Operation, Refresh, Search, Upload, WarningFilled } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { ElMessage, ElMessageBox } from 'element-plus';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { getProcessingOverview, type ProcessingOverview, type ProcessingTask } from '@/api/aihr/processing';
import { uploadKnowledgeDocAsync, listUploadItems, retryUploadItem, type UploadItem } from '@/api/aihr/sop';
import { listKnowledgeSpaces, type KnowledgeSpace } from '@/api/aihr/space';
import { listPersonalPublishRequests, reviewPersonalPublishRequest, type PersonalPublishRequestRow } from '@/api/aihr/personal';
const acceptTypes = '.txt,.md,.markdown,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png,.gif,.webp,.bmp,.mp4,.mov,.avi,.mkv,.webm,.m4v,.zip';
const supportedPattern = /\.(txt|md|markdown|pdf|doc|docx|xls|xlsx|ppt|pptx|jpg|jpeg|png|gif|webp|bmp|mp4|mov|avi|mkv|webm|m4v|zip)$/i;
@@ -257,6 +278,8 @@ const browserBatchWarnSize = 200 * 1024 * 1024;
const fileInput = ref<HTMLInputElement>();
const loading = ref(false);
const uploading = ref(false);
const publishLoading = ref(false);
const publishRequests = ref<PersonalPublishRequestRow[]>([]);
const overview = ref<ProcessingOverview | null>(null);
const overviewFailed = ref(false);
const categoryKeyword = ref('');
@@ -411,9 +434,33 @@ const pagedTasks = computed(() => {
return filteredTasks.value.slice(start, start + taskPageSize.value);
});
onMounted(async () => {
await Promise.all([loadOverview(), loadManageableSpaces()]);
await Promise.all([loadOverview(), loadManageableSpaces(), loadPublishRequests()]);
});
async function loadPublishRequests() {
publishLoading.value = true;
try {
publishRequests.value = (await listPersonalPublishRequests()).data || [];
} catch {
publishRequests.value = [];
} finally {
publishLoading.value = false;
}
}
async function reviewPublish(row: PersonalPublishRequestRow, approved: boolean) {
try {
const { value } = await ElMessageBox.prompt(approved ? '填写批准意见' : '填写驳回原因', approved ? '批准入库' : '驳回入库', {
inputValidator: (input) => Boolean(input?.trim()) || '请填写审核意见'
});
await reviewPersonalPublishRequest(row.id, { approved, comment: value.trim() });
ElMessage.success(approved ? '已批准' : '已驳回');
await loadPublishRequests();
} catch (error) {
if (error !== 'cancel' && error !== 'close') ElMessage.error('审核失败,请重试');
}
}
onUnmounted(() => {
clearBatchPoll();
// 置空批次 ID,让在途的轮询请求返回后直接丢弃,不再重新挂起定时器