x
This commit is contained in:
@@ -162,6 +162,7 @@ class STPFile(Base):
|
||||
analysis_metrics = relationship("AnalysisMetrics", back_populates="stp_file", uselist=False)
|
||||
feature_detections = relationship("FeatureDetection", back_populates="stp_file")
|
||||
design_recommendations = relationship("DesignRecommendation", back_populates="stp_file")
|
||||
processing_tasks = relationship("ProcessingTask", back_populates="stp_file")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<STPFile(id={self.id}, original_filename='{self.original_filename}', status='{self.status}')>"
|
||||
@@ -294,6 +295,9 @@ class ProcessingTask(Base):
|
||||
# 处理参数
|
||||
parameters = Column(JSON, nullable=True) # 任务参数
|
||||
|
||||
# 关联关系
|
||||
stp_file = relationship("STPFile", back_populates="processing_tasks")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ProcessingTask(id={self.id}, task_id='{self.task_id}', status='{self.status}')>"
|
||||
|
||||
|
||||
@@ -535,8 +535,12 @@ class StorageIntegrationService:
|
||||
limit: int = 50
|
||||
) -> list:
|
||||
"""获取同一文件名的所有上传历史记录"""
|
||||
from models.database import ProcessingTask
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
query = select(STPFile).where(
|
||||
query = select(STPFile).options(
|
||||
joinedload(STPFile.processing_tasks)
|
||||
).where(
|
||||
STPFile.original_filename == filename
|
||||
).order_by(STPFile.upload_time.desc())
|
||||
|
||||
@@ -546,11 +550,12 @@ class StorageIntegrationService:
|
||||
query = query.limit(limit)
|
||||
|
||||
result = await session.execute(query)
|
||||
files = result.scalars().all()
|
||||
files = result.unique().scalars().all()
|
||||
|
||||
return [
|
||||
{
|
||||
'id': f.id,
|
||||
'task_id': f.processing_tasks[0].task_id if f.processing_tasks else None,
|
||||
'upload_batch': f.upload_batch,
|
||||
'upload_time': f.upload_time.isoformat() if f.upload_time else None,
|
||||
'file_size': f.file_size,
|
||||
@@ -572,6 +577,8 @@ class StorageIntegrationService:
|
||||
"""获取所有文件分组(按文件名分组),包含每个文件的最新分析结果"""
|
||||
|
||||
from sqlalchemy import func, desc
|
||||
from sqlalchemy.orm import joinedload
|
||||
from models.database import ProcessingTask
|
||||
|
||||
# 子查询:获取每个文件名的最新上传
|
||||
subquery = (
|
||||
@@ -591,7 +598,9 @@ class StorageIntegrationService:
|
||||
|
||||
# 主查询:获取最新记录和统计信息
|
||||
query = (
|
||||
select(STPFile)
|
||||
select(STPFile).options(
|
||||
joinedload(STPFile.processing_tasks)
|
||||
)
|
||||
.join(
|
||||
subquery,
|
||||
(STPFile.original_filename == subquery.c.original_filename) &
|
||||
@@ -601,7 +610,7 @@ class StorageIntegrationService:
|
||||
)
|
||||
|
||||
result = await session.execute(query)
|
||||
latest_files = result.scalars().all()
|
||||
latest_files = result.unique().scalars().all()
|
||||
|
||||
# 获取每个文件名的上传次数
|
||||
file_groups = []
|
||||
@@ -615,9 +624,12 @@ class StorageIntegrationService:
|
||||
count_result = await session.execute(count_query)
|
||||
upload_count = count_result.scalar()
|
||||
|
||||
task_id = f.processing_tasks[0].task_id if f.processing_tasks else None
|
||||
|
||||
file_groups.append({
|
||||
'filename': f.original_filename,
|
||||
'latest_id': f.id,
|
||||
'latest_task_id': task_id,
|
||||
'latest_upload_time': f.upload_time.isoformat() if f.upload_time else None,
|
||||
'latest_status': f.status,
|
||||
'upload_count': upload_count,
|
||||
|
||||
@@ -525,6 +525,118 @@ body {
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
|
||||
/* ================================
|
||||
历史记录下拉展开
|
||||
================================ */
|
||||
.file-row {
|
||||
cursor: pointer;
|
||||
transition: background-color var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
.file-row:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
transition: transform var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
.history-detail-row {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.history-detail-row td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.history-detail {
|
||||
padding: var(--space-4) var(--space-6);
|
||||
}
|
||||
|
||||
.inner-table {
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.inner-table th {
|
||||
background: var(--bg-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.inner-table td {
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: var(--space-1) var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* ================================
|
||||
进度条
|
||||
================================ */
|
||||
.file-row {
|
||||
cursor: pointer;
|
||||
transition: background-color var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
.file-row:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
color: var(--text-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
transition: transform var(--duration-fast) var(--ease-default);
|
||||
}
|
||||
|
||||
.history-detail-row {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.history-detail-row td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.history-detail {
|
||||
padding: var(--space-4) var(--space-6);
|
||||
}
|
||||
|
||||
.inner-table {
|
||||
background: var(--bg-primary);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-light);
|
||||
}
|
||||
|
||||
.inner-table th {
|
||||
background: var(--bg-tertiary);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.inner-table td {
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* ================================
|
||||
进度条
|
||||
================================ */
|
||||
|
||||
+28
-31
@@ -761,9 +761,7 @@ const MoldInsightView = {
|
||||
dragOver: false,
|
||||
progress: 0,
|
||||
history: null,
|
||||
showFileHistory: false,
|
||||
selectedFileHistory: null,
|
||||
fileHistoryRecords: []
|
||||
expandedFiles: {}
|
||||
});
|
||||
|
||||
const loadHistory = async () => {
|
||||
@@ -774,19 +772,21 @@ const MoldInsightView = {
|
||||
}
|
||||
};
|
||||
|
||||
const viewFileHistory = async (filename) => {
|
||||
const toggleFileHistory = async (filename) => {
|
||||
if (state.expandedFiles[filename]) {
|
||||
state.expandedFiles[filename] = null;
|
||||
} else {
|
||||
try {
|
||||
state.selectedFileHistory = filename;
|
||||
state.fileHistoryRecords = await apiRequest(`/api/history/${encodeURIComponent(filename)}`);
|
||||
state.showFileHistory = true;
|
||||
const records = await apiRequest(`/api/history/${encodeURIComponent(filename)}`);
|
||||
state.expandedFiles[filename] = records;
|
||||
} catch (e) {
|
||||
handleApiError(e, '加载文件历史');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const viewResult = (record) => {
|
||||
state.showFileHistory = false;
|
||||
router.push(`/moldinsight/result/${record.id}`);
|
||||
router.push(`/moldinsight/result/${record.task_id}`);
|
||||
};
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
@@ -899,7 +899,7 @@ const MoldInsightView = {
|
||||
uploadFile,
|
||||
formatFileSize,
|
||||
formatDateTime,
|
||||
viewFileHistory,
|
||||
toggleFileHistory,
|
||||
viewResult
|
||||
};
|
||||
},
|
||||
@@ -968,8 +968,12 @@ const MoldInsightView = {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="file in state.history.files" :key="file.filename">
|
||||
<td>{{ file.filename }}</td>
|
||||
<template v-for="file in state.history.files" :key="file.filename">
|
||||
<tr class="file-row" @click="toggleFileHistory(file.filename)">
|
||||
<td>
|
||||
<span class="expand-icon">{{ state.expandedFiles[file.filename] ? '▼' : '▶' }}</span>
|
||||
{{ file.filename }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-info">{{ file.upload_count }} 次</span>
|
||||
</td>
|
||||
@@ -981,29 +985,17 @@ const MoldInsightView = {
|
||||
</td>
|
||||
<td>{{ formatDateTime(file.latest_upload_time) }}</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button v-if="file.latest_status === 'completed'" class="btn-sm btn-primary" @click="router.push('/moldinsight/result/' + file.latest_id)">
|
||||
<div class="action-buttons" @click.stop>
|
||||
<button v-if="file.latest_status === 'completed'" class="btn-sm btn-primary" @click="viewResult({task_id: file.latest_task_id})">
|
||||
查看最新
|
||||
</button>
|
||||
<button class="btn-sm btn-secondary" @click="viewFileHistory(file.filename)">
|
||||
历史记录
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="state.showFileHistory" class="modal-overlay" @click.self="state.showFileHistory = false">
|
||||
<div class="modal-content" style="max-width: 800px;">
|
||||
<div class="modal-header">
|
||||
<h2>{{ state.selectedFileHistory }} - 历史记录</h2>
|
||||
<button class="modal-close" @click="state.showFileHistory = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="data-table">
|
||||
<tr v-if="state.expandedFiles[file.filename]" class="history-detail-row">
|
||||
<td colspan="6">
|
||||
<div class="history-detail">
|
||||
<table class="data-table inner-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>上传时间</th>
|
||||
@@ -1016,7 +1008,7 @@ const MoldInsightView = {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="record in state.fileHistoryRecords" :key="record.id">
|
||||
<tr v-for="record in state.expandedFiles[file.filename]" :key="record.id">
|
||||
<td>{{ formatDateTime(record.upload_time) }}</td>
|
||||
<td>{{ formatFileSize(record.file_size) }}</td>
|
||||
<td>
|
||||
@@ -1036,6 +1028,11 @@ const MoldInsightView = {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user