This commit is contained in:
149
package-lock.json
generated
149
package-lock.json
generated
@@ -10,10 +10,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-cpp": "^6.0.3",
|
"@codemirror/lang-cpp": "^6.0.3",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@vue-flow/background": "^1.3.2",
|
|
||||||
"@vue-flow/controls": "^1.1.3",
|
|
||||||
"@vue-flow/core": "^1.47.0",
|
|
||||||
"@vue-flow/minimap": "^1.5.4",
|
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
"@vueuse/router": "^13.9.0",
|
"@vueuse/router": "^13.9.0",
|
||||||
"@wangeditor-next/editor": "^5.6.46",
|
"@wangeditor-next/editor": "^5.6.46",
|
||||||
@@ -1726,151 +1722,6 @@
|
|||||||
"integrity": "sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ==",
|
"integrity": "sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vue-flow/background": {
|
|
||||||
"version": "1.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vue-flow/background/-/background-1.3.2.tgz",
|
|
||||||
"integrity": "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue-flow/core": "^1.23.0",
|
|
||||||
"vue": "^3.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/controls": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vue-flow/controls/-/controls-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue-flow/core": "^1.23.0",
|
|
||||||
"vue": "^3.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core": {
|
|
||||||
"version": "1.47.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.47.0.tgz",
|
|
||||||
"integrity": "sha512-w+qrm/xjQP5NUeKUOMIbQvpOeivTbGZtY2lGffK5kHiN3ZLyEazhESc8OeIV9NZkK2T5DIeyX/nhHxCC45HLiw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@vueuse/core": "^10.5.0",
|
|
||||||
"d3-drag": "^3.0.0",
|
|
||||||
"d3-interpolate": "^3.0.1",
|
|
||||||
"d3-selection": "^3.0.0",
|
|
||||||
"d3-zoom": "^3.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"vue": "^3.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core/node_modules/@types/web-bluetooth": {
|
|
||||||
"version": "0.0.20",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
|
||||||
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core/node_modules/@vueuse/core": {
|
|
||||||
"version": "10.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
|
|
||||||
"integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/web-bluetooth": "^0.0.20",
|
|
||||||
"@vueuse/metadata": "10.11.1",
|
|
||||||
"@vueuse/shared": "10.11.1",
|
|
||||||
"vue-demi": ">=0.14.8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core/node_modules/@vueuse/core/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core/node_modules/@vueuse/metadata": {
|
|
||||||
"version": "10.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
|
|
||||||
"integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core/node_modules/@vueuse/shared": {
|
|
||||||
"version": "10.11.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
|
|
||||||
"integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"vue-demi": ">=0.14.8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/core/node_modules/@vueuse/shared/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
|
||||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue-flow/minimap": {
|
|
||||||
"version": "1.5.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@vue-flow/minimap/-/minimap-1.5.4.tgz",
|
|
||||||
"integrity": "sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"d3-selection": "^3.0.0",
|
|
||||||
"d3-zoom": "^3.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue-flow/core": "^1.23.0",
|
|
||||||
"vue": "^3.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.5.22",
|
"version": "3.5.22",
|
||||||
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
|
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.22.tgz",
|
||||||
|
|||||||
@@ -12,10 +12,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-cpp": "^6.0.3",
|
"@codemirror/lang-cpp": "^6.0.3",
|
||||||
"@codemirror/lang-python": "^6.2.1",
|
"@codemirror/lang-python": "^6.2.1",
|
||||||
"@vue-flow/background": "^1.3.2",
|
|
||||||
"@vue-flow/controls": "^1.1.3",
|
|
||||||
"@vue-flow/core": "^1.47.0",
|
|
||||||
"@vue-flow/minimap": "^1.5.4",
|
|
||||||
"@vueuse/core": "^13.9.0",
|
"@vueuse/core": "^13.9.0",
|
||||||
"@vueuse/router": "^13.9.0",
|
"@vueuse/router": "^13.9.0",
|
||||||
"@wangeditor-next/editor": "^5.6.46",
|
"@wangeditor-next/editor": "^5.6.46",
|
||||||
|
|||||||
@@ -1,467 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flowchart-submission-detail">
|
|
||||||
<div class="detail-header">
|
|
||||||
<h2>流程图提交详情</h2>
|
|
||||||
<n-space>
|
|
||||||
<n-button @click="refreshDetail" :loading="loading">
|
|
||||||
<Icon icon="mdi:refresh" />
|
|
||||||
刷新
|
|
||||||
</n-button>
|
|
||||||
<n-button
|
|
||||||
v-if="submission?.status === FlowchartSubmissionStatus.FAILED"
|
|
||||||
type="warning"
|
|
||||||
@click="retrySubmission"
|
|
||||||
>
|
|
||||||
<Icon icon="mdi:restart" />
|
|
||||||
重试评分
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="loading" class="loading-container">
|
|
||||||
<n-spin size="large" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="submission" class="detail-content">
|
|
||||||
<!-- 基本信息 -->
|
|
||||||
<n-card title="基本信息" class="info-card">
|
|
||||||
<n-descriptions :column="2" bordered>
|
|
||||||
<n-descriptions-item label="提交ID">
|
|
||||||
{{ submission.id }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item label="用户ID">
|
|
||||||
{{ submission.user }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item label="题目ID">
|
|
||||||
{{ submission.problem }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item label="状态">
|
|
||||||
<n-tag :type="getStatusType(submission.status)">
|
|
||||||
{{ getStatusText(submission.status) }}
|
|
||||||
</n-tag>
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item label="提交时间">
|
|
||||||
{{ formatTime(submission.create_time) }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item
|
|
||||||
label="评分时间"
|
|
||||||
v-if="submission.evaluation_time"
|
|
||||||
>
|
|
||||||
{{ formatTime(submission.evaluation_time) }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
</n-descriptions>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- AI评分结果 -->
|
|
||||||
<n-card
|
|
||||||
v-if="submission.status === FlowchartSubmissionStatus.COMPLETED"
|
|
||||||
title="AI评分结果"
|
|
||||||
class="score-card"
|
|
||||||
>
|
|
||||||
<div class="score-content">
|
|
||||||
<div class="score-main">
|
|
||||||
<div class="score-value">
|
|
||||||
<span class="score-number">{{
|
|
||||||
submission.ai_score?.toFixed(1) || 0
|
|
||||||
}}</span>
|
|
||||||
<span class="score-unit">分</span>
|
|
||||||
</div>
|
|
||||||
<div class="score-grade">
|
|
||||||
<n-tag :type="getGradeType(submission.ai_grade)" size="large">
|
|
||||||
{{ submission.ai_grade || "N/A" }}
|
|
||||||
</n-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="score-details">
|
|
||||||
<n-descriptions :column="1" size="small">
|
|
||||||
<n-descriptions-item label="AI提供商">
|
|
||||||
{{ submission.ai_provider || "deepseek" }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item label="AI模型">
|
|
||||||
{{ submission.ai_model || "deepseek-chat" }}
|
|
||||||
</n-descriptions-item>
|
|
||||||
<n-descriptions-item
|
|
||||||
label="处理时间"
|
|
||||||
v-if="submission.processing_time"
|
|
||||||
>
|
|
||||||
{{ submission.processing_time.toFixed(2) }}秒
|
|
||||||
</n-descriptions-item>
|
|
||||||
</n-descriptions>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- AI反馈 -->
|
|
||||||
<n-card
|
|
||||||
v-if="submission.ai_feedback"
|
|
||||||
title="AI反馈"
|
|
||||||
class="feedback-card"
|
|
||||||
>
|
|
||||||
<div class="feedback-content">
|
|
||||||
<p>{{ submission.ai_feedback }}</p>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- AI建议 -->
|
|
||||||
<n-card
|
|
||||||
v-if="submission.ai_suggestions"
|
|
||||||
title="AI建议"
|
|
||||||
class="suggestions-card"
|
|
||||||
>
|
|
||||||
<div class="suggestions-content">
|
|
||||||
<p>{{ submission.ai_suggestions }}</p>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- 详细评分标准 -->
|
|
||||||
<n-card
|
|
||||||
v-if="
|
|
||||||
submission.ai_criteria_details &&
|
|
||||||
Object.keys(submission.ai_criteria_details).length > 0
|
|
||||||
"
|
|
||||||
title="详细评分标准"
|
|
||||||
class="criteria-card"
|
|
||||||
>
|
|
||||||
<div class="criteria-content">
|
|
||||||
<n-collapse>
|
|
||||||
<n-collapse-item
|
|
||||||
v-for="(detail, key) in submission.ai_criteria_details"
|
|
||||||
:key="key"
|
|
||||||
:title="key"
|
|
||||||
:name="key"
|
|
||||||
>
|
|
||||||
<div class="criteria-detail">
|
|
||||||
<pre>{{ JSON.stringify(detail, null, 2) }}</pre>
|
|
||||||
</div>
|
|
||||||
</n-collapse-item>
|
|
||||||
</n-collapse>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- 流程图预览 -->
|
|
||||||
<n-card title="流程图预览" class="flowchart-card">
|
|
||||||
<div class="flowchart-preview">
|
|
||||||
<div class="mermaid-container" ref="mermaidContainer"></div>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
|
|
||||||
<!-- Mermaid代码 -->
|
|
||||||
<n-card title="Mermaid代码" class="code-card">
|
|
||||||
<div class="code-content">
|
|
||||||
<n-input
|
|
||||||
v-model:value="mermaidCode"
|
|
||||||
type="textarea"
|
|
||||||
:rows="10"
|
|
||||||
readonly
|
|
||||||
placeholder="Mermaid代码将在这里显示"
|
|
||||||
/>
|
|
||||||
<div class="code-actions">
|
|
||||||
<n-space>
|
|
||||||
<n-button @click="copyCode">
|
|
||||||
<Icon icon="mdi:content-copy" />
|
|
||||||
复制代码
|
|
||||||
</n-button>
|
|
||||||
<n-button @click="downloadCode">
|
|
||||||
<Icon icon="mdi:download" />
|
|
||||||
下载文件
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="error-container">
|
|
||||||
<n-result status="error" title="加载失败" description="无法加载提交详情">
|
|
||||||
<template #footer>
|
|
||||||
<n-button @click="refreshDetail">重试</n-button>
|
|
||||||
</template>
|
|
||||||
</n-result>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, nextTick, computed } from "vue"
|
|
||||||
import { useRoute } from "vue-router"
|
|
||||||
import {
|
|
||||||
NCard,
|
|
||||||
NButton,
|
|
||||||
NSpace,
|
|
||||||
NDescriptions,
|
|
||||||
NDescriptionsItem,
|
|
||||||
NTag,
|
|
||||||
NInput,
|
|
||||||
NCollapse,
|
|
||||||
NCollapseItem,
|
|
||||||
NSpin,
|
|
||||||
NResult,
|
|
||||||
useMessage,
|
|
||||||
} from "naive-ui"
|
|
||||||
import { Icon } from "@iconify/vue"
|
|
||||||
import { getFlowchartSubmission, retryFlowchartSubmission } from "../api"
|
|
||||||
import { FlowchartSubmission, FlowchartSubmissionStatus } from "utils/types"
|
|
||||||
import mermaid from "mermaid"
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const submissionId = computed(() => route.params.id as string)
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const loading = ref(false)
|
|
||||||
const submission = ref<FlowchartSubmission | null>(null)
|
|
||||||
const mermaidCode = ref("")
|
|
||||||
const mermaidContainer = ref<HTMLElement | null>(null)
|
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
const loadSubmission = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const response = await getFlowchartSubmission(submissionId.value)
|
|
||||||
submission.value = response.data
|
|
||||||
|
|
||||||
// 设置 Mermaid 代码
|
|
||||||
if (submission.value) {
|
|
||||||
mermaidCode.value = submission.value.mermaid_code
|
|
||||||
|
|
||||||
// 渲染 Mermaid 图表
|
|
||||||
await nextTick()
|
|
||||||
if (mermaidContainer.value && submission.value.mermaid_code) {
|
|
||||||
mermaidContainer.value.innerHTML = ""
|
|
||||||
mermaid
|
|
||||||
.render("detail-mermaid", submission.value.mermaid_code)
|
|
||||||
.then(({ svg }) => {
|
|
||||||
if (mermaidContainer.value) {
|
|
||||||
mermaidContainer.value.innerHTML = svg
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("加载提交详情失败:", error)
|
|
||||||
message.error("加载提交详情失败")
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshDetail = () => {
|
|
||||||
loadSubmission()
|
|
||||||
}
|
|
||||||
|
|
||||||
const retrySubmission = async () => {
|
|
||||||
if (!submission.value) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await retryFlowchartSubmission(submission.value.id)
|
|
||||||
message.success("重试请求已发送")
|
|
||||||
loadSubmission()
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("重试失败:", error)
|
|
||||||
message.error("重试失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusType = (status: number) => {
|
|
||||||
const statusMap: Record<
|
|
||||||
number,
|
|
||||||
"default" | "primary" | "success" | "info" | "warning" | "error"
|
|
||||||
> = {
|
|
||||||
[FlowchartSubmissionStatus.PENDING]: "warning",
|
|
||||||
[FlowchartSubmissionStatus.PROCESSING]: "info",
|
|
||||||
[FlowchartSubmissionStatus.COMPLETED]: "success",
|
|
||||||
[FlowchartSubmissionStatus.FAILED]: "error",
|
|
||||||
}
|
|
||||||
return statusMap[status] || "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusText = (status: number) => {
|
|
||||||
const statusMap: Record<number, string> = {
|
|
||||||
[FlowchartSubmissionStatus.PENDING]: "等待评分",
|
|
||||||
[FlowchartSubmissionStatus.PROCESSING]: "评分中",
|
|
||||||
[FlowchartSubmissionStatus.COMPLETED]: "评分完成",
|
|
||||||
[FlowchartSubmissionStatus.FAILED]: "评分失败",
|
|
||||||
}
|
|
||||||
return statusMap[status] || "未知"
|
|
||||||
}
|
|
||||||
|
|
||||||
const getGradeType = (grade: string | undefined) => {
|
|
||||||
if (!grade) return "default"
|
|
||||||
const gradeMap: Record<
|
|
||||||
string,
|
|
||||||
"default" | "primary" | "success" | "info" | "warning" | "error"
|
|
||||||
> = {
|
|
||||||
S: "success",
|
|
||||||
A: "info",
|
|
||||||
B: "warning",
|
|
||||||
C: "error",
|
|
||||||
}
|
|
||||||
return gradeMap[grade] || "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatTime = (time: string) => {
|
|
||||||
return new Date(time).toLocaleString()
|
|
||||||
}
|
|
||||||
|
|
||||||
const copyCode = async () => {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(mermaidCode.value)
|
|
||||||
message.success("代码已复制到剪贴板")
|
|
||||||
} catch (err) {
|
|
||||||
message.error("复制失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadCode = () => {
|
|
||||||
const blob = new Blob([mermaidCode.value], { type: "text/plain" })
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
const a = document.createElement("a")
|
|
||||||
a.href = url
|
|
||||||
a.download = `flowchart-${submissionId.value}.mmd`
|
|
||||||
a.click()
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
message.success("文件下载成功")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期
|
|
||||||
onMounted(() => {
|
|
||||||
loadSubmission()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.flowchart-submission-detail {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header h2 {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card,
|
|
||||||
.score-card,
|
|
||||||
.feedback-card,
|
|
||||||
.suggestions-card,
|
|
||||||
.criteria-card,
|
|
||||||
.flowchart-card,
|
|
||||||
.code-card {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-content {
|
|
||||||
display: flex;
|
|
||||||
gap: 30px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-main {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-value {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-number {
|
|
||||||
font-size: 48px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #1890ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-unit {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-grade {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-details {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-content,
|
|
||||||
.suggestions-content {
|
|
||||||
padding: 15px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 6px;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.criteria-content {
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.criteria-detail {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: "Courier New", monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flowchart-preview {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fafafa;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mermaid-container {
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flowchart-submission-list">
|
|
||||||
<div class="list-header">
|
|
||||||
<h2>流程图提交记录</h2>
|
|
||||||
<n-space>
|
|
||||||
<n-button @click="refreshList" :loading="loading">
|
|
||||||
<Icon icon="mdi:refresh" />
|
|
||||||
刷新
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 筛选器 -->
|
|
||||||
<div class="filters">
|
|
||||||
<n-space>
|
|
||||||
<n-select
|
|
||||||
v-model:value="filters.problem_id"
|
|
||||||
placeholder="选择题目"
|
|
||||||
clearable
|
|
||||||
:options="problemOptions"
|
|
||||||
style="width: 200px"
|
|
||||||
/>
|
|
||||||
<n-select
|
|
||||||
v-model:value="filters.user_id"
|
|
||||||
placeholder="选择用户"
|
|
||||||
clearable
|
|
||||||
:options="userOptions"
|
|
||||||
style="width: 200px"
|
|
||||||
/>
|
|
||||||
<n-select
|
|
||||||
v-model:value="filters.status"
|
|
||||||
placeholder="选择状态"
|
|
||||||
clearable
|
|
||||||
:options="statusOptions"
|
|
||||||
style="width: 150px"
|
|
||||||
/>
|
|
||||||
<n-button type="primary" @click="applyFilters">
|
|
||||||
<Icon icon="mdi:filter" />
|
|
||||||
筛选
|
|
||||||
</n-button>
|
|
||||||
</n-space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 提交列表 -->
|
|
||||||
<div class="submission-list">
|
|
||||||
<n-data-table
|
|
||||||
:columns="columns"
|
|
||||||
:data="submissions"
|
|
||||||
:loading="loading"
|
|
||||||
:pagination="pagination"
|
|
||||||
@update:page="handlePageChange"
|
|
||||||
@update:page-size="handlePageSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, reactive, onMounted, computed, h } from "vue"
|
|
||||||
import { useRouter } from "vue-router"
|
|
||||||
import {
|
|
||||||
NButton,
|
|
||||||
NSpace,
|
|
||||||
NSelect,
|
|
||||||
NDataTable,
|
|
||||||
NTag,
|
|
||||||
useMessage,
|
|
||||||
} from "naive-ui"
|
|
||||||
import { Icon } from "@iconify/vue"
|
|
||||||
import { getFlowchartSubmissions, retryFlowchartSubmission } from "../api"
|
|
||||||
import {
|
|
||||||
FlowchartSubmissionListItem,
|
|
||||||
FlowchartSubmissionStatus,
|
|
||||||
} from "utils/types"
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const loading = ref(false)
|
|
||||||
const submissions = ref<FlowchartSubmissionListItem[]>([])
|
|
||||||
const total = ref(0)
|
|
||||||
const currentPage = ref(1)
|
|
||||||
const pageSize = ref(20)
|
|
||||||
|
|
||||||
// 筛选器
|
|
||||||
const filters = reactive({
|
|
||||||
problem_id: null as number | null,
|
|
||||||
user_id: null as number | null,
|
|
||||||
status: null as number | null,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 选项数据
|
|
||||||
const problemOptions = ref<Array<{ label: string; value: number }>>([])
|
|
||||||
const userOptions = ref<Array<{ label: string; value: number }>>([])
|
|
||||||
const statusOptions = [
|
|
||||||
{ label: "等待评分", value: FlowchartSubmissionStatus.PENDING },
|
|
||||||
{ label: "评分中", value: FlowchartSubmissionStatus.PROCESSING },
|
|
||||||
{ label: "评分完成", value: FlowchartSubmissionStatus.COMPLETED },
|
|
||||||
{ label: "评分失败", value: FlowchartSubmissionStatus.FAILED },
|
|
||||||
]
|
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
// 分页配置
|
|
||||||
const pagination = computed(() => ({
|
|
||||||
page: currentPage.value,
|
|
||||||
pageSize: pageSize.value,
|
|
||||||
itemCount: total.value,
|
|
||||||
showSizePicker: true,
|
|
||||||
pageSizes: [10, 20, 50, 100],
|
|
||||||
showQuickJumper: true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
// 表格列配置
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: "ID",
|
|
||||||
key: "id",
|
|
||||||
width: 120,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
return row.id.substring(0, 8) + "..."
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "用户",
|
|
||||||
key: "username",
|
|
||||||
width: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "题目",
|
|
||||||
key: "problem_title",
|
|
||||||
width: 200,
|
|
||||||
ellipsis: {
|
|
||||||
tooltip: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "状态",
|
|
||||||
key: "status",
|
|
||||||
width: 100,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
const statusMap: Record<
|
|
||||||
number,
|
|
||||||
{
|
|
||||||
text: string
|
|
||||||
type: "default" | "primary" | "success" | "info" | "warning" | "error"
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
[FlowchartSubmissionStatus.PENDING]: {
|
|
||||||
text: "等待评分",
|
|
||||||
type: "warning",
|
|
||||||
},
|
|
||||||
[FlowchartSubmissionStatus.PROCESSING]: {
|
|
||||||
text: "评分中",
|
|
||||||
type: "info",
|
|
||||||
},
|
|
||||||
[FlowchartSubmissionStatus.COMPLETED]: {
|
|
||||||
text: "评分完成",
|
|
||||||
type: "success",
|
|
||||||
},
|
|
||||||
[FlowchartSubmissionStatus.FAILED]: { text: "评分失败", type: "error" },
|
|
||||||
}
|
|
||||||
const status = statusMap[row.status] || { text: "未知", type: "default" }
|
|
||||||
return h(NTag, { type: status.type }, { default: () => status.text })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "AI评分",
|
|
||||||
key: "ai_score",
|
|
||||||
width: 100,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
if (row.ai_score !== null && row.ai_score !== undefined) {
|
|
||||||
return `${row.ai_score.toFixed(1)}分`
|
|
||||||
}
|
|
||||||
return "-"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "等级",
|
|
||||||
key: "ai_grade",
|
|
||||||
width: 80,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
if (row.ai_grade) {
|
|
||||||
const gradeColors: Record<
|
|
||||||
string,
|
|
||||||
"default" | "primary" | "success" | "info" | "warning" | "error"
|
|
||||||
> = {
|
|
||||||
S: "success",
|
|
||||||
A: "info",
|
|
||||||
B: "warning",
|
|
||||||
C: "error",
|
|
||||||
}
|
|
||||||
return h(
|
|
||||||
NTag,
|
|
||||||
{ type: gradeColors[row.ai_grade] || "default" },
|
|
||||||
{ default: () => row.ai_grade },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return "-"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "处理时间",
|
|
||||||
key: "processing_time",
|
|
||||||
width: 100,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
if (row.processing_time) {
|
|
||||||
return `${row.processing_time.toFixed(2)}s`
|
|
||||||
}
|
|
||||||
return "-"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "提交时间",
|
|
||||||
key: "create_time",
|
|
||||||
width: 160,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
return new Date(row.create_time).toLocaleString()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "操作",
|
|
||||||
key: "actions",
|
|
||||||
width: 120,
|
|
||||||
render: (row: FlowchartSubmissionListItem) => {
|
|
||||||
return h(NSpace, null, {
|
|
||||||
default: () => [
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
size: "small",
|
|
||||||
type: "primary",
|
|
||||||
onClick: () => viewSubmission(row),
|
|
||||||
},
|
|
||||||
{ default: () => "查看" },
|
|
||||||
),
|
|
||||||
row.status === FlowchartSubmissionStatus.FAILED &&
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
size: "small",
|
|
||||||
type: "warning",
|
|
||||||
onClick: () => retrySubmission(row),
|
|
||||||
},
|
|
||||||
{ default: () => "重试" },
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
const loadSubmissions = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const params: any = {
|
|
||||||
offset: (currentPage.value - 1) * pageSize.value,
|
|
||||||
limit: pageSize.value,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filters.problem_id !== null) {
|
|
||||||
params.problem_id = filters.problem_id
|
|
||||||
}
|
|
||||||
if (filters.user_id !== null) {
|
|
||||||
params.user_id = filters.user_id
|
|
||||||
}
|
|
||||||
if (filters.status !== null) {
|
|
||||||
params.status = filters.status
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await getFlowchartSubmissions(params)
|
|
||||||
submissions.value = response.data.results
|
|
||||||
total.value = response.data.total
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("加载提交列表失败:", error)
|
|
||||||
message.error("加载提交列表失败")
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshList = () => {
|
|
||||||
currentPage.value = 1
|
|
||||||
loadSubmissions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const applyFilters = () => {
|
|
||||||
currentPage.value = 1
|
|
||||||
loadSubmissions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
|
||||||
currentPage.value = page
|
|
||||||
loadSubmissions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePageSizeChange = (size: number) => {
|
|
||||||
pageSize.value = size
|
|
||||||
currentPage.value = 1
|
|
||||||
loadSubmissions()
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewSubmission = (submission: FlowchartSubmissionListItem) => {
|
|
||||||
router.push({
|
|
||||||
name: "flowchart-detail",
|
|
||||||
params: { id: submission.id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const retrySubmission = async (submission: FlowchartSubmissionListItem) => {
|
|
||||||
try {
|
|
||||||
await retryFlowchartSubmission(submission.id)
|
|
||||||
message.success("重试请求已发送")
|
|
||||||
loadSubmissions()
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("重试失败:", error)
|
|
||||||
message.error("重试失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期
|
|
||||||
onMounted(() => {
|
|
||||||
loadSubmissions()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.flowchart-submission-list {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-header h2 {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submission-list {
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user