feat(personal): add reviewed enterprise publishing
This commit is contained in:
@@ -25,6 +25,18 @@
|
||||
<button v-if="item.sourceType === 'FILE' && item.status === 'READY'" class="secondary-button full-button" :disabled="busy" @click="download">下载原附件</button>
|
||||
</view>
|
||||
<view v-if="item.summary" class="panel employee-card"><text class="panel-title">内容摘要</text><text class="summary">{{ item.summary }}</text></view>
|
||||
<view v-if="item.status === 'READY'" class="panel employee-card publish-card">
|
||||
<text class="panel-title">沉淀企业知识</text>
|
||||
<text class="status-copy">提交后由 HR/运营审核;批准时只生成脱敏企业副本,个人资料仍归你所有。</text>
|
||||
<template v-if="publishRequest">
|
||||
<text class="publish-status">{{ publishStatusText(publishRequest.status) }}</text>
|
||||
<text v-if="publishRequest.reviewComment" class="meta">审核意见:{{ publishRequest.reviewComment }}</text>
|
||||
</template>
|
||||
<template v-if="!publishRequest || publishRequest.status === 'REJECTED'">
|
||||
<input v-model="publishReason" class="publish-input" maxlength="500" placeholder="说明为什么值得沉淀为企业知识" />
|
||||
<button class="primary-button full-button" :disabled="busy || !publishReason.trim()" @click="submitPublishRequest">申请沉淀企业知识</button>
|
||||
</template>
|
||||
</view>
|
||||
<view class="panel danger-panel">
|
||||
<text class="panel-title danger-title">删除资料</text>
|
||||
<text class="danger-copy">删除后,附件、解析正文和搜索索引会清除,且无法恢复。</text>
|
||||
@@ -38,22 +50,25 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app';
|
||||
import type { PersonalItem, PersonalItemSourceType, PersonalItemStatus } from '@/types/api';
|
||||
import { deletePersonalItem, downloadPersonalItem, loadPersonalItem, retryPersonalItem, retryPersonalOcrPages } from '@/services/personal-assistant';
|
||||
import type { PersonalItem, PersonalItemSourceType, PersonalItemStatus, PersonalPublishRequest } from '@/types/api';
|
||||
import { deletePersonalItem, downloadPersonalItem, loadPersonalItem, loadPersonalPublishRequests, requestPersonalPublish, retryPersonalItem, retryPersonalOcrPages } from '@/services/personal-assistant';
|
||||
import { normalizeBackendId } from '@/services/personal-assistant-id';
|
||||
import { isOcrActive, ocrProgressPercent, ocrProgressText } from '@/services/personal-assistant-ocr';
|
||||
|
||||
const itemId = ref(''); const item = ref<PersonalItem>(); const loading = ref(false); const busy = ref(false); const message = ref('');
|
||||
const publishRequest = ref<PersonalPublishRequest>(); const publishReason = ref('');
|
||||
let pollTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
const statusText = (status: PersonalItemStatus) => ({ QUEUED: '等待处理', PARSING: '解析中', READY: '可检索', FAILED: '解析失败', DELETING: '删除中', DELETED: '已删除' })[status];
|
||||
const statusDescription = (status: PersonalItemStatus) => ({ QUEUED: '已进入解析队列。', PARSING: '正在提取正文和建立搜索索引。', READY: '已完成解析,可用于个人助理问答。', FAILED: '本次解析失败,可查看公开原因后重试。', DELETING: '正在清理附件、正文和索引。', DELETED: '资料已删除。' })[status];
|
||||
const sourceText = (source: PersonalItemSourceType) => ({ TEXT: '文字', FILE: '文件', URL: '外部网页' })[source];
|
||||
const stopPolling = () => { if (pollTimer) clearTimeout(pollTimer); pollTimer = undefined; };
|
||||
const schedulePolling = () => { stopPolling(); if (item.value && (['QUEUED', 'PARSING'].includes(item.value.status) || isOcrActive(item.value.ocr))) pollTimer = setTimeout(() => void load(true), 2000); };
|
||||
const load = async (silent = false) => { if (!itemId.value) return; if (!silent) loading.value = true; message.value = ''; try { item.value = await loadPersonalItem(itemId.value); schedulePolling(); } catch (error) { message.value = error instanceof Error ? error.message : '资料不存在或无权访问'; stopPolling(); } finally { if (!silent) loading.value = false; } };
|
||||
const load = async (silent = false) => { if (!itemId.value) return; if (!silent) loading.value = true; message.value = ''; try { item.value = await loadPersonalItem(itemId.value); const requests = await loadPersonalPublishRequests(); publishRequest.value = requests.find((request) => request.itemId === itemId.value); schedulePolling(); } catch (error) { message.value = error instanceof Error ? error.message : '资料不存在或无权访问'; stopPolling(); } finally { if (!silent) loading.value = false; } };
|
||||
const retry = async () => { busy.value = true; message.value = ''; try { item.value = await retryPersonalItem(itemId.value); } catch (error) { message.value = error instanceof Error ? error.message : '重试失败'; } finally { busy.value = false; } };
|
||||
const retryOcrPages = async () => { busy.value = true; message.value = ''; try { item.value = await retryPersonalOcrPages(itemId.value); schedulePolling(); } catch (error) { message.value = error instanceof Error ? error.message : '失败页重试失败'; } finally { busy.value = false; } };
|
||||
const download = async () => { busy.value = true; try { const result = await downloadPersonalItem(itemId.value); if (!result.url) throw new Error('附件下载地址不可用'); if (typeof window !== 'undefined') window.open(result.url, '_blank', 'noopener,noreferrer'); else uni.downloadFile({ url: result.url }); } catch (error) { message.value = error instanceof Error ? error.message : '下载失败'; } finally { busy.value = false; } };
|
||||
const publishStatusText = (status: PersonalPublishRequest['status']) => ({ PENDING: '待审核', APPROVED: '已批准并生成企业知识副本', REJECTED: '已驳回' })[status];
|
||||
const submitPublishRequest = async () => { const reason = publishReason.value.trim(); if (!reason || busy.value) return; busy.value = true; message.value = ''; try { publishRequest.value = await requestPersonalPublish(itemId.value, reason); publishReason.value = ''; message.value = '申请已提交,等待 HR/运营审核'; } catch (error) { message.value = error instanceof Error ? error.message : '申请提交失败'; } finally { busy.value = false; } };
|
||||
const confirmDelete = () => uni.showModal({ title: '确认删除资料', content: '附件、解析正文和搜索索引会清除,删除后无法恢复。', confirmText: '确认删除', confirmColor: '#e70014', success: (result) => { if (result.confirm) void remove(); } });
|
||||
const remove = async () => { busy.value = true; message.value = ''; try { await deletePersonalItem(itemId.value); uni.redirectTo({ url: '/pages/user/assistant/library' }); } catch (error) { message.value = error instanceof Error ? error.message : '删除失败,请重试'; } finally { busy.value = false; } };
|
||||
const goBack = () => uni.navigateBack();
|
||||
@@ -70,4 +85,5 @@ onUnload(stopPolling);
|
||||
|
||||
<style scoped>
|
||||
.item-page { overflow-x: hidden; }.detail-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }.source-badge, .status-badge { padding: 4px 8px; border-radius: var(--radius); background: #eef3f9; color: var(--knowledge); font-size: 12px; }.status-badge.ready { color: var(--success); }.status-badge.failed, .danger-title { color: #b42318; }.item-title, .meta, .summary, .status-copy, .danger-copy { display: block; }.item-title { margin-top: 14px; font-size: 20px; font-weight: 900; }.meta { margin-top: 8px; color: var(--muted); font-size: 13px; overflow-wrap: anywhere; }.status-copy, .summary, .danger-copy { margin-top: 10px; line-height: 1.65; }.summary { white-space: pre-wrap; }.full-button { width: 100%; margin-top: 12px; }.ocr-progress { margin-top: 14px; }.ocr-progress-head { display: flex; justify-content: space-between; gap: 12px; color: #344054; font-size: 13px; }.ocr-progress-track { height: 8px; margin-top: 8px; overflow: hidden; border-radius: 4px; background: #eef1f5; }.ocr-progress-value { height: 100%; border-radius: 4px; background: #e70014; transition: width 180ms ease; }.ocr-failed-pages { display: block; margin-top: 8px; color: #b54708; font-size: 12px; }.danger-panel { border-color: #f5c2c7; }.danger-button { color: #b42318; }
|
||||
.publish-input { width: 100%; min-height: 44px; margin-top: 12px; padding: 0 12px; border: 1px solid var(--line); background: #fff; }.publish-status { display: block; margin-top: 12px; color: var(--brand); font-weight: 800; }
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
PersonalExportOutlineSection,
|
||||
PersonalKnowledgeScope,
|
||||
PersonalPage,
|
||||
PersonalPublishRequest,
|
||||
PersonalSession,
|
||||
PersonalSessionDetail,
|
||||
PersonalSpace
|
||||
@@ -203,3 +204,14 @@ export const downloadPersonalExport = (id: string) => apiRequest<{ url: string;
|
||||
url: `/api/aihr/personal-assistant/exports/${encodeURIComponent(id)}/download-url`,
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
export const requestPersonalPublish = (itemId: string, reason: string) => apiRequest<PersonalPublishRequest>({
|
||||
url: `/api/aihr/personal-assistant/items/${encodeURIComponent(itemId)}/publish-requests`,
|
||||
method: 'POST',
|
||||
data: { targetScope: 'POSITION:生活顾问', reason }
|
||||
});
|
||||
|
||||
export const loadPersonalPublishRequests = () => apiRequest<PersonalPublishRequest[]>({
|
||||
url: '/api/aihr/personal-assistant/publish-requests',
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
@@ -163,6 +163,20 @@ export interface PersonalExportOutline {
|
||||
ossId?: string;
|
||||
}
|
||||
|
||||
export interface PersonalPublishRequest {
|
||||
id: string;
|
||||
itemId: string;
|
||||
title: string;
|
||||
sourceType: PersonalItemSourceType;
|
||||
targetScope: string;
|
||||
reason: string;
|
||||
status: 'PENDING' | 'APPROVED' | 'REJECTED';
|
||||
reviewerUserId?: string;
|
||||
reviewComment?: string;
|
||||
publishedKnowledgeId?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface PersonalSpace {
|
||||
spaceId: string;
|
||||
status: string;
|
||||
|
||||
Reference in New Issue
Block a user