feat(review-meeting): 增加AI状态与构建操作
parent
ee816f95c7
commit
f12b87129e
|
|
@ -22,6 +22,11 @@ export interface ReviewAiSummaryVO {
|
||||||
summaryMarkdown: string | null
|
summaryMarkdown: string | null
|
||||||
sourceFileIds: number[]
|
sourceFileIds: number[]
|
||||||
errorMessage: string | null
|
errorMessage: string | null
|
||||||
|
ready: boolean
|
||||||
|
pendingFileCount: number
|
||||||
|
failedFileCount: number
|
||||||
|
skippedFileCount: number
|
||||||
|
blockReason: string | null
|
||||||
summary: {
|
summary: {
|
||||||
projectOverview: string
|
projectOverview: string
|
||||||
businessGoal: string
|
businessGoal: string
|
||||||
|
|
@ -37,7 +42,7 @@ export interface ReviewAiSummaryVO {
|
||||||
export interface ReviewAiConversationVO {
|
export interface ReviewAiConversationVO {
|
||||||
conversationId: number
|
conversationId: number
|
||||||
title: string
|
title: string
|
||||||
knowledgeId: number
|
knowledgeId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -52,6 +57,10 @@ export const getProjectAiSummary = (reviewMeetingProjectId: number): Promise<Rev
|
||||||
export const rebuildProjectAiSummary = (reviewMeetingProjectId: number) =>
|
export const rebuildProjectAiSummary = (reviewMeetingProjectId: number) =>
|
||||||
request.post({ url: `/project/review-ai/project/${reviewMeetingProjectId}/rebuild` })
|
request.post({ url: `/project/review-ai/project/${reviewMeetingProjectId}/rebuild` })
|
||||||
|
|
||||||
|
/** 重建单个文件 AI */
|
||||||
|
export const rebuildReviewFileAi = (fileId: number) =>
|
||||||
|
request.post({ url: `/project/review-ai/file/${fileId}/rebuild` })
|
||||||
|
|
||||||
/** 打开 / 获取当前项目共享会话 */
|
/** 打开 / 获取当前项目共享会话 */
|
||||||
export const openProjectAiConversation = (
|
export const openProjectAiConversation = (
|
||||||
reviewMeetingProjectId: number
|
reviewMeetingProjectId: number
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ export interface ReviewMeetingProjectRespVO {
|
||||||
reviewResult?: 'PASS' | 'REJECT'
|
reviewResult?: 'PASS' | 'REJECT'
|
||||||
preMeetingMaterialsComplete?: boolean
|
preMeetingMaterialsComplete?: boolean
|
||||||
postMeetingMaterialsComplete?: boolean
|
postMeetingMaterialsComplete?: boolean
|
||||||
|
aiSummaryStatus?: number
|
||||||
|
aiSummaryUpdatedTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const REVIEW_AGENDA_CATEGORY_OPTIONS = ['项目立项', '预验收', '项目终验'] as const
|
export const REVIEW_AGENDA_CATEGORY_OPTIONS = ['项目立项', '预验收', '项目终验'] as const
|
||||||
|
|
@ -67,6 +69,10 @@ export interface ReviewMeetingFileRespVO {
|
||||||
fileUrl: string
|
fileUrl: string
|
||||||
fileSize: number
|
fileSize: number
|
||||||
fileType: string
|
fileType: string
|
||||||
|
aiBuildStatus?: number
|
||||||
|
aiBuildStatusName?: string
|
||||||
|
aiBuildErrorMessage?: string
|
||||||
|
aiBuildTime?: string
|
||||||
creator: string
|
creator: string
|
||||||
createTime: string
|
createTime: string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,44 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="ai-status-card" v-loading="aiLoading">
|
||||||
|
<div class="ai-status-header">
|
||||||
|
<div>
|
||||||
|
<div class="ai-status-title">AI 构建状态</div>
|
||||||
|
<div class="ai-status-sub">{{ aiSummary?.blockReason || 'AI 能力仅影响摘要与助手,不影响主流程' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="ai-status-actions">
|
||||||
|
<span :class="`ai-pill ai-pill-${aiSummary?.status ?? 0}`">{{ aiSummary?.statusName || '待构建' }}</span>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['review:meeting:update']"
|
||||||
|
type="primary"
|
||||||
|
:loading="aiActionLoading"
|
||||||
|
@click="handleBuildAllAi"
|
||||||
|
>
|
||||||
|
构建全部待构建文件
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ai-status-metrics">
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="metric-label">待构建</span>
|
||||||
|
<span class="metric-value">{{ aiSummary?.pendingFileCount ?? 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="metric-label">失败</span>
|
||||||
|
<span class="metric-value metric-danger">{{ aiSummary?.failedFileCount ?? 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="metric-label">无需构建</span>
|
||||||
|
<span class="metric-value">{{ aiSummary?.skippedFileCount ?? 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="metric-item">
|
||||||
|
<span class="metric-label">最近更新时间</span>
|
||||||
|
<span class="metric-value metric-time">{{ aiSummary?.updatedTime || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="material-card" v-loading="loading">
|
<div class="material-card" v-loading="loading">
|
||||||
<div class="group-title">会前资料</div>
|
<div class="group-title">会前资料</div>
|
||||||
<el-table :data="beforeMeetingMaterials" border class="material-table" empty-text="暂无会前资料">
|
<el-table :data="beforeMeetingMaterials" border class="material-table" empty-text="暂无会前资料">
|
||||||
|
|
@ -48,15 +86,31 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="资料文件" min-width="300">
|
<el-table-column label="资料文件" min-width="300">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.latestFile" class="file-name">{{ row.latestFile.fileName }}</span>
|
<div v-if="row.latestFile" class="file-block">
|
||||||
|
<span class="file-name">{{ row.latestFile.fileName }}</span>
|
||||||
|
<span :class="`file-ai-pill file-ai-pill-${row.latestFile.aiBuildStatus ?? 0}`">
|
||||||
|
{{ row.latestFile.aiBuildStatusName || '待构建' }}
|
||||||
|
</span>
|
||||||
|
<span v-if="row.latestFile.aiBuildErrorMessage" class="file-ai-error">
|
||||||
|
{{ row.latestFile.aiBuildErrorMessage }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<span v-else class="file-empty">未上传</span>
|
<span v-else class="file-empty">未上传</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="260" align="center">
|
<el-table-column label="操作" width="340" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<a class="op-link" @click="triggerUpload(row)">{{ row.latestFile ? '替换' : '上传' }}</a>
|
<a class="op-link" @click="triggerUpload(row)">{{ row.latestFile ? '替换' : '上传' }}</a>
|
||||||
<a class="op-link" :class="{ disabled: !row.latestFile }" @click="row.latestFile && handleDownload(row.latestFile)">下载</a>
|
<a class="op-link" :class="{ disabled: !row.latestFile }" @click="row.latestFile && handleDownload(row.latestFile)">下载</a>
|
||||||
<a class="op-link" @click="openHistory(row)">历史版本</a>
|
<a class="op-link" @click="openHistory(row)">历史版本</a>
|
||||||
|
<a
|
||||||
|
v-if="row.latestFile && row.latestFile.aiBuildStatus !== 4"
|
||||||
|
class="op-link"
|
||||||
|
:class="{ disabled: rebuildingFileId === row.latestFile.id }"
|
||||||
|
@click="row.latestFile && handleRebuildFileAi(row.latestFile)"
|
||||||
|
>
|
||||||
|
{{ rebuildingFileId === row.latestFile.id ? '提交中' : '重建AI' }}
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -73,15 +127,31 @@
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="资料文件" min-width="300">
|
<el-table-column label="资料文件" min-width="300">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="row.latestFile" class="file-name">{{ row.latestFile.fileName }}</span>
|
<div v-if="row.latestFile" class="file-block">
|
||||||
|
<span class="file-name">{{ row.latestFile.fileName }}</span>
|
||||||
|
<span :class="`file-ai-pill file-ai-pill-${row.latestFile.aiBuildStatus ?? 0}`">
|
||||||
|
{{ row.latestFile.aiBuildStatusName || '待构建' }}
|
||||||
|
</span>
|
||||||
|
<span v-if="row.latestFile.aiBuildErrorMessage" class="file-ai-error">
|
||||||
|
{{ row.latestFile.aiBuildErrorMessage }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<span v-else class="file-empty">未上传</span>
|
<span v-else class="file-empty">未上传</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="260" align="center">
|
<el-table-column label="操作" width="340" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<a class="op-link" @click="triggerUpload(row)">{{ row.latestFile ? '替换' : '上传' }}</a>
|
<a class="op-link" @click="triggerUpload(row)">{{ row.latestFile ? '替换' : '上传' }}</a>
|
||||||
<a class="op-link" :class="{ disabled: !row.latestFile }" @click="row.latestFile && handleDownload(row.latestFile)">下载</a>
|
<a class="op-link" :class="{ disabled: !row.latestFile }" @click="row.latestFile && handleDownload(row.latestFile)">下载</a>
|
||||||
<a class="op-link" @click="openHistory(row)">历史版本</a>
|
<a class="op-link" @click="openHistory(row)">历史版本</a>
|
||||||
|
<a
|
||||||
|
v-if="row.latestFile && row.latestFile.aiBuildStatus !== 4"
|
||||||
|
class="op-link"
|
||||||
|
:class="{ disabled: rebuildingFileId === row.latestFile.id }"
|
||||||
|
@click="row.latestFile && handleRebuildFileAi(row.latestFile)"
|
||||||
|
>
|
||||||
|
{{ rebuildingFileId === row.latestFile.id ? '提交中' : '重建AI' }}
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
@ -117,10 +187,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { getReviewMeeting } from '@/api/review/meeting'
|
import { getReviewMeeting } from '@/api/review/meeting'
|
||||||
|
import {
|
||||||
|
getProjectAiSummary,
|
||||||
|
rebuildProjectAiSummary,
|
||||||
|
rebuildReviewFileAi,
|
||||||
|
type ReviewAiSummaryVO
|
||||||
|
} from '@/api/review/ai'
|
||||||
import {
|
import {
|
||||||
getProjectMaterialHistory,
|
getProjectMaterialHistory,
|
||||||
getProjectMaterialSummary,
|
getProjectMaterialSummary,
|
||||||
|
|
@ -153,11 +229,16 @@ const projectInfo = ref({
|
||||||
})
|
})
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const aiLoading = ref(false)
|
||||||
|
const aiActionLoading = ref(false)
|
||||||
|
const rebuildingFileId = ref<number>()
|
||||||
|
let aiPollingTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
const materialSummary = ref<ReviewMeetingMaterialSummaryRespVO>({
|
const materialSummary = ref<ReviewMeetingMaterialSummaryRespVO>({
|
||||||
agendaType: '',
|
agendaType: '',
|
||||||
templateAvailable: false,
|
templateAvailable: false,
|
||||||
materials: []
|
materials: []
|
||||||
})
|
})
|
||||||
|
const aiSummary = ref<ReviewAiSummaryVO | null>(null)
|
||||||
|
|
||||||
const uploadInputRef = ref<HTMLInputElement>()
|
const uploadInputRef = ref<HTMLInputElement>()
|
||||||
const uploadTarget = ref<ReviewMeetingMaterialItemRespVO>()
|
const uploadTarget = ref<ReviewMeetingMaterialItemRespVO>()
|
||||||
|
|
@ -209,6 +290,31 @@ const loadSummary = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadAiSummary = async () => {
|
||||||
|
aiLoading.value = true
|
||||||
|
try {
|
||||||
|
aiSummary.value = await getProjectAiSummary(reviewMeetingProjectId)
|
||||||
|
} finally {
|
||||||
|
aiLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAiPolling = () => {
|
||||||
|
if (aiPollingTimer) {
|
||||||
|
clearTimeout(aiPollingTimer)
|
||||||
|
aiPollingTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleAiPolling = () => {
|
||||||
|
clearAiPolling()
|
||||||
|
if (aiSummary.value?.status !== 1) return
|
||||||
|
aiPollingTimer = setTimeout(async () => {
|
||||||
|
await Promise.all([loadAiSummary(), loadSummary()])
|
||||||
|
scheduleAiPolling()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
const triggerUpload = (row: ReviewMeetingMaterialItemRespVO) => {
|
const triggerUpload = (row: ReviewMeetingMaterialItemRespVO) => {
|
||||||
uploadTarget.value = row
|
uploadTarget.value = row
|
||||||
if (!uploadInputRef.value) return
|
if (!uploadInputRef.value) return
|
||||||
|
|
@ -234,7 +340,7 @@ const handleFileSelected = async (event: Event) => {
|
||||||
try {
|
try {
|
||||||
await uploadProjectMaterial(reviewMeetingId, reviewMeetingProjectId, target.materialCode, file)
|
await uploadProjectMaterial(reviewMeetingId, reviewMeetingProjectId, target.materialCode, file)
|
||||||
ElMessage.success('上传成功')
|
ElMessage.success('上传成功')
|
||||||
await loadSummary()
|
await Promise.all([loadSummary(), loadAiSummary()])
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('上传失败')
|
ElMessage.error('上传失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -242,6 +348,38 @@ const handleFileSelected = async (event: Event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleBuildAllAi = async () => {
|
||||||
|
await ElMessageBox.confirm('确认构建当前项目全部待构建资料的 AI 吗?', 'AI 构建确认', {
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: '确认构建'
|
||||||
|
})
|
||||||
|
aiActionLoading.value = true
|
||||||
|
try {
|
||||||
|
await rebuildProjectAiSummary(reviewMeetingProjectId)
|
||||||
|
ElMessage.success('AI 构建任务已提交')
|
||||||
|
await Promise.all([loadAiSummary(), loadSummary()])
|
||||||
|
scheduleAiPolling()
|
||||||
|
} finally {
|
||||||
|
aiActionLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRebuildFileAi = async (file: ReviewMeetingFileRespVO) => {
|
||||||
|
await ElMessageBox.confirm(`确认重建文件「${file.fileName}」的 AI 资产吗?`, '文件 AI 重建确认', {
|
||||||
|
type: 'warning',
|
||||||
|
confirmButtonText: '确认重建'
|
||||||
|
})
|
||||||
|
rebuildingFileId.value = file.id
|
||||||
|
try {
|
||||||
|
await rebuildReviewFileAi(file.id)
|
||||||
|
ElMessage.success('文件 AI 重建任务已提交')
|
||||||
|
await Promise.all([loadAiSummary(), loadSummary()])
|
||||||
|
scheduleAiPolling()
|
||||||
|
} finally {
|
||||||
|
rebuildingFileId.value = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const openHistory = async (row: ReviewMeetingMaterialItemRespVO) => {
|
const openHistory = async (row: ReviewMeetingMaterialItemRespVO) => {
|
||||||
historyVisible.value = true
|
historyVisible.value = true
|
||||||
historyList.value = []
|
historyList.value = []
|
||||||
|
|
@ -274,7 +412,8 @@ const handleBack = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSummary()
|
await Promise.all([loadSummary(), loadAiSummary()])
|
||||||
|
scheduleAiPolling()
|
||||||
getReviewMeeting(reviewMeetingId)
|
getReviewMeeting(reviewMeetingId)
|
||||||
.then((meeting) => {
|
.then((meeting) => {
|
||||||
if (!projectInfo.value.meetingName) {
|
if (!projectInfo.value.meetingName) {
|
||||||
|
|
@ -284,6 +423,10 @@ onMounted(async () => {
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearAiPolling()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
@ -388,6 +531,106 @@ onMounted(async () => {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ai-status-card {
|
||||||
|
background: #fff9ec;
|
||||||
|
border: 1px solid #f2d6a2;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6d4d12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-sub {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #8b6a2b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 74px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-pill-0 {
|
||||||
|
color: #8a6214;
|
||||||
|
background: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-pill-1 {
|
||||||
|
color: #0f766e;
|
||||||
|
background: #ccfbf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-pill-2 {
|
||||||
|
color: #166534;
|
||||||
|
background: #dcfce7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-pill-3 {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-item {
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
border: 1px solid rgba(242, 214, 162, 0.72);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
display: block;
|
||||||
|
color: #8b6a2b;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
display: block;
|
||||||
|
color: #5d4211;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-danger {
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-time {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.group-title {
|
.group-title {
|
||||||
margin: 8px 0 10px;
|
margin: 8px 0 10px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
@ -408,6 +651,52 @@ onMounted(async () => {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-pill-0 {
|
||||||
|
color: #8a6214;
|
||||||
|
background: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-pill-1 {
|
||||||
|
color: #0f766e;
|
||||||
|
background: #ccfbf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-pill-2 {
|
||||||
|
color: #166534;
|
||||||
|
background: #dcfce7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-pill-3 {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-pill-4 {
|
||||||
|
color: #475569;
|
||||||
|
background: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-ai-error {
|
||||||
|
color: #b91c1c;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
.file-empty {
|
.file-empty {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
@ -446,4 +735,17 @@ onMounted(async () => {
|
||||||
:deep(.material-table .el-table__header-wrapper th) {
|
:deep(.material-table .el-table__header-wrapper th) {
|
||||||
background-color: #f5f7fa;
|
background-color: #f5f7fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.ai-status-header,
|
||||||
|
.info-panel-meta,
|
||||||
|
.info-panel-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-metrics {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,13 @@
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="AI状态" width="110" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="`ai-status ai-status-${row.aiSummaryStatus ?? 0}`">
|
||||||
|
{{ AI_STATUS_LABEL[row.aiSummaryStatus ?? 0] }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="评审结果" width="120" align="center">
|
<el-table-column label="评审结果" width="120" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-select
|
||||||
|
|
@ -211,6 +218,7 @@ const tableRef = ref()
|
||||||
let sortableInstance: Sortable | null = null
|
let sortableInstance: Sortable | null = null
|
||||||
|
|
||||||
const STATUS_LABEL: Record<number, string> = { 0: '待召开', 1: '正在召开', 2: '已结束', 3: '已取消' }
|
const STATUS_LABEL: Record<number, string> = { 0: '待召开', 1: '正在召开', 2: '已结束', 3: '已取消' }
|
||||||
|
const AI_STATUS_LABEL: Record<number, string> = { 0: '待构建', 1: '构建中', 2: '已完成', 3: '失败' }
|
||||||
const getMaterialCompleteClass = (complete?: boolean) => {
|
const getMaterialCompleteClass = (complete?: boolean) => {
|
||||||
if (complete) return 'material-complete'
|
if (complete) return 'material-complete'
|
||||||
return 'material-incomplete'
|
return 'material-incomplete'
|
||||||
|
|
@ -527,6 +535,36 @@ onBeforeUnmount(() => {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ai-status {
|
||||||
|
display: inline-flex;
|
||||||
|
min-width: 64px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-0 {
|
||||||
|
color: #8a6214;
|
||||||
|
background: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-1 {
|
||||||
|
color: #0f766e;
|
||||||
|
background: #ccfbf1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-2 {
|
||||||
|
color: #166534;
|
||||||
|
background: #dcfce7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-status-3 {
|
||||||
|
color: #b91c1c;
|
||||||
|
background: #fee2e2;
|
||||||
|
}
|
||||||
|
|
||||||
.inline-time-range {
|
.inline-time-range {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue