|
|
@@ -1,21 +1,21 @@
|
|
|
<template>
|
|
|
- <div style="display: flex">
|
|
|
+ <div class="ai-evaluation-container">
|
|
|
<!-- 1.作文展示区域 -->
|
|
|
- <div style="width: 40%; height: 90vh; margin: 10px; position: relative;">
|
|
|
- <el-breadcrumb :separator-icon="ArrowRight" style="height: 30px; align-content: center; margin-left: 6px">
|
|
|
+ <div class="essay-section">
|
|
|
+ <el-breadcrumb :separator-icon="ArrowRight" class="breadcrumb">
|
|
|
<el-breadcrumb-item :to="{ path: `/home/Assignment/${assignmentId}/edit` }">写作</el-breadcrumb-item>
|
|
|
<el-breadcrumb-item><span style="color: rgba(22,119,255,0.62); font-weight: bold; cursor:pointer;">智能批改</span></el-breadcrumb-item>
|
|
|
</el-breadcrumb>
|
|
|
|
|
|
- <el-card shadow="never">
|
|
|
+ <el-card shadow="never" class="essay-card">
|
|
|
<!-- 原文标识 -->
|
|
|
- <div style="display: flex;justify-content: center;align-items: center;width: 100px; height: 50px; background-color:var(--system-color); position: absolute; top: 40px; left: 0; clip-path: polygon(0 0, 100% 0, 80% 50%, 100% 100%, 0 100%);">
|
|
|
- <span style="margin-left: -20px;color: white">原文</span>
|
|
|
+ <div class="original-text-label">
|
|
|
+ <span>原文</span>
|
|
|
</div>
|
|
|
<!-- 作文内容展示区域 -->
|
|
|
- <div style="white-space: pre-wrap; margin-top: 80px; line-height: 30px">
|
|
|
- <el-scrollbar height="760px">
|
|
|
- <span style="position: relative;">
|
|
|
+ <div class="essay-content-wrapper">
|
|
|
+ <el-scrollbar class="essay-scrollbar">
|
|
|
+ <span class="essay-content">
|
|
|
<template v-if="data.sentenceEvaluationList?.length != 0">
|
|
|
<span v-for="sentenceEvaluation in data.sentenceEvaluationList"
|
|
|
:key="sentenceEvaluation.id"
|
|
|
@@ -43,9 +43,9 @@
|
|
|
|
|
|
|
|
|
<!-- 2.AI评价区域 -->
|
|
|
- <div style="width: 40%; margin: 10px; height: 89vh;">
|
|
|
+ <div class="evaluation-section">
|
|
|
<div class="combined-box">
|
|
|
- <div style="display: flex">
|
|
|
+ <div class="tab-container">
|
|
|
<div
|
|
|
class="trapezoid"
|
|
|
:class="{ active: selected === 'trapezoid' }"
|
|
|
@@ -63,44 +63,58 @@
|
|
|
</div>
|
|
|
<div class="rectangle">
|
|
|
<template v-if="selected === 'trapezoid'">
|
|
|
- <el-scrollbar height="820px">
|
|
|
- <div style="display: flex;align-items: flex-start;margin-bottom: 15px;">
|
|
|
- <div>
|
|
|
- <!--展示头像-->
|
|
|
- <!--<el-avatar src="@/image/seecoderLogo.png" :size="32" :shape="'circle'">-->
|
|
|
- <!--</el-avatar>-->
|
|
|
- <div style="border-radius: 50%; background-color:#fff; width: 40px; height: 40px;display: flex;justify-content: center;align-items: center;">
|
|
|
+ <el-scrollbar height="100%" class="evaluation-scrollbar">
|
|
|
+ <!-- 整体评价加载状态 -->
|
|
|
+ <div v-if="isLoadingOverall" class="loading-container">
|
|
|
+ <el-icon class="is-loading" :size="40" color="#409EFF">
|
|
|
+ <Loading />
|
|
|
+ </el-icon>
|
|
|
+ <div class="loading-text">AI 正在生成整体评价,请稍候...</div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="overall-evaluation-content">
|
|
|
+ <div class="avatar-container">
|
|
|
+ <div class="avatar-wrapper">
|
|
|
<img src="@/image/seecoderLogo.png" width="32" >
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div style="margin-left: 15px">
|
|
|
- <!--名字-->
|
|
|
- <div style="margin-bottom: 2px">SEEAI英语助手</div>
|
|
|
- <!--内容-->
|
|
|
- <div style="background-color: #f0f0f0;padding: 8px 12px;border-radius: 16px;white-space: pre-line;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);">
|
|
|
- {{ data.overallEvaluation?.evaluation }}
|
|
|
+ <div class="evaluation-message">
|
|
|
+ <div class="assistant-name">SEEAI英语助手</div>
|
|
|
+ <div
|
|
|
+ class="message-content markdown-content"
|
|
|
+ v-html="renderMarkdown(data.overallEvaluation?.evaluation || '')"
|
|
|
+ >
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-scrollbar>
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
- <el-scrollbar height="820px">
|
|
|
- <div style="display: flex; flex-direction: column;">
|
|
|
+ <el-scrollbar height="100%" class="evaluation-scrollbar">
|
|
|
+ <!-- 逐句评价加载状态 -->
|
|
|
+ <div v-if="isLoadingSentence" class="loading-container">
|
|
|
+ <el-icon class="is-loading" :size="40" color="#409EFF">
|
|
|
+ <Loading />
|
|
|
+ </el-icon>
|
|
|
+ <div class="loading-text">AI 正在生成逐句评价,请稍候...</div>
|
|
|
+ <div class="loading-tip">由于需要对每个句子进行详细分析,此过程可能需要较长时间</div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="sentence-evaluation-content">
|
|
|
<template v-if="data.sentenceEvaluationList?.length == 0">
|
|
|
- 暂无逐句评价
|
|
|
+ <div class="no-evaluation">暂无逐句评价</div>
|
|
|
</template>
|
|
|
<el-card v-for="sentenceEvaluation in data.sentenceEvaluationList"
|
|
|
+ v-else
|
|
|
:key="sentenceEvaluation.id"
|
|
|
- style="cursor: pointer; margin-bottom: 10px; position: relative;" shadow="hover"
|
|
|
+ class="sentence-card"
|
|
|
+ shadow="hover"
|
|
|
@mouseenter="highlightSentenceId = sentenceEvaluation.id"
|
|
|
@mouseleave="highlightSentenceId = -1"
|
|
|
>
|
|
|
- <div :style="{display: 'flex',justifyContent: 'center',alignItems: 'center',width: '20px', height: '100%', backgroundColor: getTextDecorationColor(sentenceEvaluation), position: 'absolute', top: '0', left: 'calc(100% - 20px)'}">
|
|
|
+ <div class="color-indicator" :style="{backgroundColor: getTextDecorationColor(sentenceEvaluation)}">
|
|
|
</div>
|
|
|
- <div style="padding-right: 30px">
|
|
|
- <div style="font-weight: bold; line-height: 30px">{{ sentenceEvaluation.category }}</div>
|
|
|
- {{ sentenceEvaluation.evaluation }}
|
|
|
+ <div class="sentence-content">
|
|
|
+ <div class="sentence-category">{{ sentenceEvaluation.category }}</div>
|
|
|
+ <div class="sentence-evaluation">{{ sentenceEvaluation.evaluation }}</div>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</div>
|
|
|
@@ -111,44 +125,15 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 3.历史版本选择区域 -->
|
|
|
- <el-card
|
|
|
- style="
|
|
|
- width: 15%; /* 固定宽度代替百分比 */
|
|
|
- height: 90vh;
|
|
|
- margin: 10px 0 10px auto; /* 关键:左侧自动边距 */
|
|
|
- border-left: 1px solid #e4e7ed;
|
|
|
- border-radius: 0 8px 8px 0;
|
|
|
- box-shadow: -2px 0 4px rgba(0,0,0,0.05);
|
|
|
- /*position: sticky; !* 新增固定定位 *!*/
|
|
|
- right: 0;
|
|
|
- "
|
|
|
- shadow="never"
|
|
|
- >
|
|
|
- <div style="
|
|
|
- position: relative;
|
|
|
- height: 100%;
|
|
|
- padding: 20px 10px;
|
|
|
- ">
|
|
|
+ <el-card class="version-section" shadow="never">
|
|
|
+ <div class="version-container">
|
|
|
<!-- 版本选择标题 -->
|
|
|
- <div style="
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: -20px;
|
|
|
- width: 80px;
|
|
|
- height: 40px;
|
|
|
- background: var(--system-color);
|
|
|
- clip-path: polygon(0 0, 100% 0, 90% 50%, 100% 100%, 0 100%);
|
|
|
- color: white;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding-left: 12px;
|
|
|
- font-size: 13px;
|
|
|
- ">
|
|
|
+ <div class="version-header">
|
|
|
版本历史
|
|
|
</div>
|
|
|
|
|
|
<!-- 版本列表 -->
|
|
|
- <el-scrollbar height="800px" style="margin-top: 50px;">
|
|
|
+ <el-scrollbar height="800px" class="version-scrollbar">
|
|
|
<div
|
|
|
v-for="version in data.versionList"
|
|
|
:key="version.id"
|
|
|
@@ -165,14 +150,7 @@
|
|
|
</el-scrollbar>
|
|
|
|
|
|
<!-- 当前版本提示 -->
|
|
|
- <div style="
|
|
|
- position: absolute;
|
|
|
- bottom: 10px;
|
|
|
- left: 10px;
|
|
|
- right: 10px;
|
|
|
- font-size: 12px;
|
|
|
- color: #666;
|
|
|
- ">
|
|
|
+ <div class="current-version">
|
|
|
当前选择:v{{ selectedVersion }}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -181,13 +159,15 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
- import {onMounted, reactive, ref, watch} from "vue";
|
|
|
+ import {onMounted, onUnmounted, reactive, ref, watch} from "vue";
|
|
|
import useApis from "@/apis";
|
|
|
import MyComponent from "@/views/AIEvaluationPage/MyComponent.vue";
|
|
|
import {ElMessage} from "element-plus";
|
|
|
import {useRoute} from "vue-router";
|
|
|
import {useStore} from "@/store";
|
|
|
- import { ArrowRight } from "@element-plus/icons-vue";
|
|
|
+ import { ArrowRight, Loading } from "@element-plus/icons-vue";
|
|
|
+ import { marked } from "marked";
|
|
|
+ import * as DOMPurifyModule from "dompurify";
|
|
|
|
|
|
interface recordVersion {
|
|
|
id: number,
|
|
|
@@ -210,7 +190,46 @@
|
|
|
sentenceEvaluationList: [],
|
|
|
versionList: []
|
|
|
})
|
|
|
- const selectedVersion = ref(1.2)
|
|
|
+ const selectedVersion = ref<number>(1)
|
|
|
+ const currentVersion = ref<number | null>(null)
|
|
|
+ const isLoadingSentence = ref(false) // 逐句评价加载状态
|
|
|
+ const isLoadingOverall = ref(false) // 整体评价加载状态
|
|
|
+ let sentenceLoadingTimer: NodeJS.Timeout | null = null // 逐句评价延迟加载计时器
|
|
|
+ let sentencePollingInterval: NodeJS.Timeout | null = null // 逐句评价轮询计时器
|
|
|
+
|
|
|
+ const sanitizeHtml = (dirty: string) => {
|
|
|
+ const purifierModule = DOMPurifyModule as unknown as {
|
|
|
+ default?: { sanitize: (input: string) => string },
|
|
|
+ sanitize?: (input: string) => string
|
|
|
+ }
|
|
|
+ const purifier = purifierModule.default ?? purifierModule
|
|
|
+ return purifier.sanitize(dirty)
|
|
|
+ }
|
|
|
+
|
|
|
+ marked.setOptions({
|
|
|
+ gfm: true,
|
|
|
+ breaks: true
|
|
|
+ })
|
|
|
+
|
|
|
+ const normalizeMarkdown = (content: string) => {
|
|
|
+ return content
|
|
|
+ .split('\n')
|
|
|
+ .map((line) => {
|
|
|
+ const headingMatch = line.match(/^\s*(#{1,6})\s*(.*)$/)
|
|
|
+ if (!headingMatch) {
|
|
|
+ return line
|
|
|
+ }
|
|
|
+ const [, hashes, text] = headingMatch
|
|
|
+ return `${hashes} ${text.trim()}`
|
|
|
+ })
|
|
|
+ .join('\n')
|
|
|
+ }
|
|
|
+
|
|
|
+ const renderMarkdown = (content: string) => {
|
|
|
+ const normalizedContent = normalizeMarkdown(content ?? '')
|
|
|
+ const html = marked.parse(normalizedContent)
|
|
|
+ return sanitizeHtml(typeof html === 'string' ? html : '')
|
|
|
+ }
|
|
|
|
|
|
const toEvaluation = () => {
|
|
|
apis.overAllEvaluation(assignmentId, store.user.id)
|
|
|
@@ -237,29 +256,133 @@
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ // 轮询获取逐句评价(在120秒内每5秒轮询一次)
|
|
|
+ const pollSentenceEvaluation = (version: number) => {
|
|
|
+ let pollCount = 0
|
|
|
+ const maxPolls = 24 // 120秒 / 5秒 = 24次
|
|
|
+
|
|
|
+ sentencePollingInterval = setInterval(() => {
|
|
|
+ pollCount++
|
|
|
+
|
|
|
+ // 如果已经轮询了24次(120秒),停止轮询
|
|
|
+ if (pollCount >= maxPolls) {
|
|
|
+ if (sentencePollingInterval) {
|
|
|
+ clearInterval(sentencePollingInterval)
|
|
|
+ sentencePollingInterval = null
|
|
|
+ }
|
|
|
+ isLoadingSentence.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 请求逐句评价
|
|
|
+ apis.getSentence(assignmentId, store.user.id, version)
|
|
|
+ .then(res => {
|
|
|
+ if(res.data.code === 200 && res.data.data && res.data.data.length > 0) {
|
|
|
+ // 获取到数据了,停止轮询
|
|
|
+ data.sentenceEvaluationList = res.data.data
|
|
|
+ isLoadingSentence.value = false
|
|
|
+
|
|
|
+ if (sentencePollingInterval) {
|
|
|
+ clearInterval(sentencePollingInterval)
|
|
|
+ sentencePollingInterval = null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查数据完整性
|
|
|
+ const hasEvaluation = res.data.data.some(item => item.evaluation)
|
|
|
+ if(!hasEvaluation) {
|
|
|
+ console.warn('⚠️ 后端返回的逐句评价数据缺少 evaluation 字段')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(err => {
|
|
|
+ console.error('轮询逐句评价失败:', err)
|
|
|
+ })
|
|
|
+ }, 5000) // 每5秒轮询一次
|
|
|
+ }
|
|
|
+
|
|
|
const getEvaluation = (version: number) => {
|
|
|
selectedVersion.value = version
|
|
|
+ console.info('[EVAL][FE] getEvaluation start', {
|
|
|
+ assignmentId,
|
|
|
+ userId: store.user?.id,
|
|
|
+ version
|
|
|
+ })
|
|
|
+
|
|
|
+ // 清除之前的计时器和轮询
|
|
|
+ if (sentenceLoadingTimer) {
|
|
|
+ clearTimeout(sentenceLoadingTimer)
|
|
|
+ sentenceLoadingTimer = null
|
|
|
+ }
|
|
|
+ if (sentencePollingInterval) {
|
|
|
+ clearInterval(sentencePollingInterval)
|
|
|
+ sentencePollingInterval = null
|
|
|
+ }
|
|
|
+
|
|
|
+ // 开始加载整体评价
|
|
|
+ isLoadingOverall.value = true
|
|
|
apis.getOverall(assignmentId,store.user.id,version)
|
|
|
.then(res => {
|
|
|
- // console.log(res)
|
|
|
- data.overallEvaluation = res.data.data
|
|
|
+ if (res.data.code === 200) {
|
|
|
+ data.overallEvaluation = res.data.data
|
|
|
+ console.info('[EVAL][FE] overall ok', {
|
|
|
+ version,
|
|
|
+ hasData: !!res.data.data,
|
|
|
+ evalLen: res.data.data?.evaluation?.length ?? -1
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ data.overallEvaluation = null
|
|
|
+ console.warn('[EVAL][FE] overall non-200', {
|
|
|
+ version,
|
|
|
+ code: res.data.code,
|
|
|
+ msg: res.data.msg
|
|
|
+ })
|
|
|
+ ElMessage.error(res.data.msg || '获取整体评价失败')
|
|
|
+ }
|
|
|
})
|
|
|
.catch(err => {
|
|
|
- console.log(err)
|
|
|
- data.overallEvaluation = {} as OverallEvaluation
|
|
|
+ console.error('[EVAL][FE] overall request failed', { version, err })
|
|
|
+ data.overallEvaluation = null
|
|
|
+ ElMessage.error('整体评价请求失败')
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ isLoadingOverall.value = false
|
|
|
})
|
|
|
+
|
|
|
+ // 开始加载逐句评价
|
|
|
+ isLoadingSentence.value = true
|
|
|
apis.getSentence(assignmentId,store.user.id,version)
|
|
|
.then(res => {
|
|
|
- // console.log(res)
|
|
|
if(res.data.code === 200){
|
|
|
- data.sentenceEvaluationList = res.data.data
|
|
|
+ const responseData = res.data.data
|
|
|
+
|
|
|
+ // 如果返回了数据,直接使用
|
|
|
+ if(responseData && responseData.length > 0) {
|
|
|
+ data.sentenceEvaluationList = responseData
|
|
|
+ isLoadingSentence.value = false
|
|
|
+ console.info('[EVAL][FE] sentence ok', { version, count: responseData.length })
|
|
|
+
|
|
|
+ // 检查数据完整性
|
|
|
+ const hasEvaluation = responseData.some(item => item.evaluation)
|
|
|
+ if(!hasEvaluation) {
|
|
|
+ console.warn('⚠️ 后端返回的逐句评价数据缺少 evaluation 字段,请检查后端 AI 评价生成逻辑')
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果返回空数据,开始轮询(每5秒一次,持续120秒)
|
|
|
+ data.sentenceEvaluationList = []
|
|
|
+ pollSentenceEvaluation(version)
|
|
|
+ }
|
|
|
} else {
|
|
|
+ console.warn('后端返回非200状态码:', res.data.code, res.data.msg)
|
|
|
data.sentenceEvaluationList = []
|
|
|
+ // 非200状态码也开始轮询
|
|
|
+ pollSentenceEvaluation(version)
|
|
|
}
|
|
|
})
|
|
|
.catch(err => {
|
|
|
- console.log(err)
|
|
|
+ console.error('逐句评价API请求失败:', err)
|
|
|
data.sentenceEvaluationList = []
|
|
|
+ // 请求失败也尝试轮询
|
|
|
+ pollSentenceEvaluation(version)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -269,9 +392,14 @@
|
|
|
// 从未批改过
|
|
|
if(res.data.data.length === 0){
|
|
|
data.versionList = []
|
|
|
+ if (currentVersion.value != null) {
|
|
|
+ getEvaluation(currentVersion.value)
|
|
|
+ }
|
|
|
} else {
|
|
|
data.versionList = res.data.data
|
|
|
- getEvaluation(res.data.data[0].version)
|
|
|
+ // 优先展示“当前最新提交版本”,否则回落到列表第一项(最新批改版本)
|
|
|
+ const prefer = currentVersion.value ?? res.data.data[0].version
|
|
|
+ getEvaluation(prefer)
|
|
|
}
|
|
|
})
|
|
|
.catch(err => {
|
|
|
@@ -279,6 +407,19 @@
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ const fetchCurrentVersion = async () => {
|
|
|
+ try {
|
|
|
+ const res = await apis.getCurrentVersionEvaluation(assignmentId, store.user.id)
|
|
|
+ if (res.data.code === 200 && res.data.data?.version) {
|
|
|
+ currentVersion.value = res.data.data.version
|
|
|
+ selectedVersion.value = res.data.data.version
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ // 后端未部署 current 接口/用户未参与等情况,继续走历史列表逻辑
|
|
|
+ console.warn('获取当前版本失败:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
const getBackgroundColor = (sentenceEvaluation: SentenceEvaluation) => {
|
|
|
if (sentenceEvaluation.id === highlightSentenceId.value) {
|
|
|
switch (sentenceEvaluation.type) {
|
|
|
@@ -314,7 +455,21 @@
|
|
|
onMounted(() => {
|
|
|
// toEvaluation()
|
|
|
// getEvaluation()
|
|
|
- getEvaluationVersionList()
|
|
|
+ fetchCurrentVersion().finally(() => {
|
|
|
+ getEvaluationVersionList()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ // 组件卸载时清理定时器
|
|
|
+ onUnmounted(() => {
|
|
|
+ if (sentenceLoadingTimer) {
|
|
|
+ clearTimeout(sentenceLoadingTimer)
|
|
|
+ sentenceLoadingTimer = null
|
|
|
+ }
|
|
|
+ if (sentencePollingInterval) {
|
|
|
+ clearInterval(sentencePollingInterval)
|
|
|
+ sentencePollingInterval = null
|
|
|
+ }
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
@@ -325,34 +480,241 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+/* 主容器 - 响应式布局 */
|
|
|
+.ai-evaluation-container {
|
|
|
+ display: flex;
|
|
|
+ align-items: stretch; /* 让所有子元素高度一致 */
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px;
|
|
|
+ height: calc(100vh - 80px); /* 使用视口高度减去顶部导航栏等空间 */
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+
|
|
|
+/* 作文展示区域 */
|
|
|
+.essay-section {
|
|
|
+ flex: 0 0 32%;
|
|
|
+ min-width: 300px;
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.breadcrumb {
|
|
|
+ height: 30px;
|
|
|
+ align-content: center;
|
|
|
+ margin-left: 6px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.essay-card {
|
|
|
+ flex: 1;
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.essay-card :deep(.el-card__body) {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.original-text-label {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ width: 100px;
|
|
|
+ height: 50px;
|
|
|
+ background-color: #87CEEB;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ clip-path: polygon(0 0, 100% 0, 80% 50%, 100% 100%, 0 100%);
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+
|
|
|
+.original-text-label span {
|
|
|
+ margin-left: -20px;
|
|
|
+ color: white;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.essay-content-wrapper {
|
|
|
+ white-space: pre-wrap;
|
|
|
+ margin-top: 40px;
|
|
|
+ line-height: 30px;
|
|
|
+ flex: 1; /* 自动填充剩余空间 */
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 0; /* 关键:允许flex子元素收缩并显示滚动条 */
|
|
|
+}
|
|
|
+
|
|
|
+.essay-scrollbar {
|
|
|
+ height: 100%;
|
|
|
+ padding-right: 10px; /* 滚动条右移 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保 el-scrollbar 内部元素正确显示 */
|
|
|
+.essay-scrollbar :deep(.el-scrollbar__wrap) {
|
|
|
+ max-height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.essay-scrollbar :deep(.el-scrollbar__view) {
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+
|
|
|
+.essay-content {
|
|
|
+ position: relative;
|
|
|
+ display: block;
|
|
|
+ padding-right: 15px; /* 确保内容不被滚动条遮挡 */
|
|
|
+ padding-bottom: 20px; /* 确保最后一行不被截断 */
|
|
|
+ white-space: pre-wrap; /* 保留换行符并自动换行 */
|
|
|
+ word-wrap: break-word; /* 长单词换行 */
|
|
|
+ overflow-wrap: break-word; /* 确保内容不会溢出 */
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(p) {
|
|
|
+ margin: 0 0 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content {
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(p:last-child) {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(ul),
|
|
|
+.markdown-content :deep(ol) {
|
|
|
+ margin: 8px 0;
|
|
|
+ padding-left: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(li) {
|
|
|
+ margin: 4px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(h1),
|
|
|
+.markdown-content :deep(h2),
|
|
|
+.markdown-content :deep(h3),
|
|
|
+.markdown-content :deep(h4),
|
|
|
+.markdown-content :deep(h5),
|
|
|
+.markdown-content :deep(h6) {
|
|
|
+ margin: 8px 0;
|
|
|
+ line-height: 1.4;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(h1) { font-size: 1.2em; }
|
|
|
+.markdown-content :deep(h2) { font-size: 1.12em; }
|
|
|
+.markdown-content :deep(h3) { font-size: 1.06em; }
|
|
|
+.markdown-content :deep(h4),
|
|
|
+.markdown-content :deep(h5),
|
|
|
+.markdown-content :deep(h6) { font-size: 1em; }
|
|
|
+
|
|
|
+.markdown-content :deep(code) {
|
|
|
+ padding: 2px 6px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background-color: rgba(0, 0, 0, 0.06);
|
|
|
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
|
|
+ font-size: 0.9em;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(pre) {
|
|
|
+ overflow-x: auto;
|
|
|
+ margin: 8px 0;
|
|
|
+ padding: 10px 12px;
|
|
|
+ border-radius: 10px;
|
|
|
+ background-color: rgba(0, 0, 0, 0.03);
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(pre code) {
|
|
|
+ padding: 0;
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(blockquote) {
|
|
|
+ margin: 8px 0;
|
|
|
+ padding-left: 12px;
|
|
|
+ border-left: 3px solid rgba(0, 0, 0, 0.18);
|
|
|
+ color: #555;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(a) {
|
|
|
+ color: #2563eb;
|
|
|
+ text-decoration: underline;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(table) {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ margin: 10px 0;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(th),
|
|
|
+.markdown-content :deep(td) {
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ padding: 6px 8px;
|
|
|
+ text-align: left;
|
|
|
+ vertical-align: top;
|
|
|
+}
|
|
|
+
|
|
|
+.markdown-content :deep(th) {
|
|
|
+ background-color: #f8fafc;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+/* AI评价区域 */
|
|
|
+.evaluation-section {
|
|
|
+ flex: 0 0 48%;
|
|
|
+ min-width: 300px;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
|
|
|
.combined-box {
|
|
|
- width: 100%; /* 整体宽度 */
|
|
|
+ width: 100%;
|
|
|
height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.tab-container {
|
|
|
+ display: flex;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.trapezoid {
|
|
|
- width: 100px; /* 梯形底边长度 */
|
|
|
+ width: 100px;
|
|
|
height: 0;
|
|
|
- border-bottom: 30px solid #cfcfcf; /* 梯形高度和颜色 */
|
|
|
+ border-bottom: 30px solid #cfcfcf;
|
|
|
border-right: 20px solid transparent;
|
|
|
- line-height: 30px; /* 与 border-bottom 高度相等 */
|
|
|
- text-align: center; /* 水平居中 */
|
|
|
+ line-height: 30px;
|
|
|
+ text-align: center;
|
|
|
z-index: 1;
|
|
|
cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.trapezoid2 {
|
|
|
- width: 100px; /* 梯形底边长度 */
|
|
|
+ width: 100px;
|
|
|
height: 0;
|
|
|
- border-bottom: 30px solid #cfcfcf; /* 梯形高度和颜色 */
|
|
|
+ border-bottom: 30px solid #cfcfcf;
|
|
|
border-right: 20px solid transparent;
|
|
|
border-left: 20px solid transparent;
|
|
|
margin-left: -20px;
|
|
|
z-index: 2;
|
|
|
- line-height: 30px; /* 与 border-bottom 高度相等 */
|
|
|
- text-align: center; /* 水平居中 */
|
|
|
+ line-height: 30px;
|
|
|
+ text-align: center;
|
|
|
cursor: pointer;
|
|
|
+ transition: all 0.3s;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.trapezoid.active {
|
|
|
@@ -366,14 +728,178 @@ export default {
|
|
|
}
|
|
|
|
|
|
.rectangle {
|
|
|
- width: 100%; /* 长方形宽度 */
|
|
|
- height: calc(100% - 30px - 2vh); /* 长方形高度 */
|
|
|
- border-top: none; /* 去掉顶部边框,使其与梯形无缝连接 */
|
|
|
- /* 长方形底部的圆角 */
|
|
|
+ width: 100%;
|
|
|
+ flex: 1;
|
|
|
+ border-top: none;
|
|
|
+ background-color: #fff;
|
|
|
+ border-radius: 0 5px 5px 5px;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-height: 0; /* 重要:允许flexbox子元素正确收缩 */
|
|
|
+}
|
|
|
+
|
|
|
+/* 评价内容滚动区域 */
|
|
|
+.evaluation-scrollbar {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.overall-evaluation-content {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 20px;
|
|
|
+ min-height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-container {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-wrapper {
|
|
|
+ border-radius: 50%;
|
|
|
+ background-color: #fff;
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
align-items: center;
|
|
|
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.evaluation-message {
|
|
|
+ margin-left: 15px;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0; /* 允许文本换行 */
|
|
|
+}
|
|
|
+
|
|
|
+.assistant-name {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
+.message-content {
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ padding: 12px 16px;
|
|
|
+ border-radius: 16px;
|
|
|
+ white-space: pre-line;
|
|
|
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
|
+ word-wrap: break-word;
|
|
|
+ overflow-wrap: break-word;
|
|
|
+ line-height: 1.5;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-evaluation-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
padding: 20px;
|
|
|
- background-color: #fff; /* 长方形的背景颜色 */
|
|
|
- border-radius: 0 5px 5px 5px;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.no-evaluation {
|
|
|
+ text-align: center;
|
|
|
+ color: #999;
|
|
|
+ padding: 40px 20px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 加载状态样式 */
|
|
|
+.loading-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-height: 300px;
|
|
|
+ padding: 40px 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-text {
|
|
|
+ margin-top: 20px;
|
|
|
+ font-size: 16px;
|
|
|
+ color: #409EFF;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-tip {
|
|
|
+ margin-top: 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #999;
|
|
|
+ text-align: center;
|
|
|
+ max-width: 80%;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-card {
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.color-indicator {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ width: 20px;
|
|
|
+ height: 100%;
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: calc(100% - 20px);
|
|
|
+ border-radius: 0 4px 4px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-content {
|
|
|
+ padding-right: 30px;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-category {
|
|
|
+ font-weight: bold;
|
|
|
+ line-height: 30px;
|
|
|
+ color: #333;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.sentence-evaluation {
|
|
|
+ line-height: 1.6;
|
|
|
+ color: #666;
|
|
|
+ word-wrap: break-word;
|
|
|
+ overflow-wrap: break-word;
|
|
|
+}
|
|
|
+
|
|
|
+/* 版本历史区域 */
|
|
|
+.version-section {
|
|
|
+ flex: 0 0 20%;
|
|
|
+ min-width: 180px;
|
|
|
+ border-left: 1px solid #e4e7ed;
|
|
|
+ border-radius: 0 8px 8px 0;
|
|
|
+ box-shadow: -2px 0 4px rgba(0,0,0,0.05);
|
|
|
+}
|
|
|
+
|
|
|
+.version-container {
|
|
|
+ position: relative;
|
|
|
+ height: 100%;
|
|
|
+ padding: 20px 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.version-header {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -20px;
|
|
|
+ width: 80px;
|
|
|
+ height: 40px;
|
|
|
+ background: #87CEEB;
|
|
|
+ clip-path: polygon(0 0, 100% 0, 90% 50%, 100% 100%, 0 100%);
|
|
|
+ color: white;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding-left: 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.version-scrollbar {
|
|
|
+ margin-top: 50px;
|
|
|
}
|
|
|
|
|
|
.version-item {
|
|
|
@@ -392,8 +918,8 @@ export default {
|
|
|
}
|
|
|
|
|
|
.version-item.active {
|
|
|
- background: rgba(89, 125, 59, 0.1);
|
|
|
- border: 1px solid rgba(89, 125, 59, 0.3);
|
|
|
+ background: #E0F7FF;
|
|
|
+ border: 1px solid rgba(135, 206, 235, 0.8);
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
@@ -403,19 +929,183 @@ export default {
|
|
|
border-radius: 50%;
|
|
|
background: #cfcfcf;
|
|
|
margin-right: 10px;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.version-item.active .version-marker {
|
|
|
- background: var(--system-color);
|
|
|
+ background: #87CEEB;
|
|
|
+}
|
|
|
+
|
|
|
+.version-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
}
|
|
|
|
|
|
.version-time {
|
|
|
font-size: 12px;
|
|
|
color: #333;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
}
|
|
|
|
|
|
.version-author {
|
|
|
font-size: 10px;
|
|
|
color: #999;
|
|
|
+ margin-top: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.current-version {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 10px;
|
|
|
+ left: 10px;
|
|
|
+ right: 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ text-align: center;
|
|
|
+ padding: 8px;
|
|
|
+ background: #f5f7fa;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+/* 平板设备 */
|
|
|
+@media screen and (max-width: 1200px) {
|
|
|
+ .ai-evaluation-container {
|
|
|
+ flex-wrap: wrap;
|
|
|
+ height: auto; /* 允许换行时自动调整高度 */
|
|
|
+ min-height: calc(100vh - 80px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .essay-section,
|
|
|
+ .evaluation-section {
|
|
|
+ flex: 0 0 calc(50% - 10px);
|
|
|
+ min-width: 280px;
|
|
|
+ height: calc(50vh - 50px); /* 平板模式下各占一半视口 */
|
|
|
+ }
|
|
|
+
|
|
|
+ .essay-content-wrapper {
|
|
|
+ height: calc(100% - 80px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .version-section {
|
|
|
+ flex: 0 0 100%;
|
|
|
+ height: 40vh;
|
|
|
+ min-height: 300px;
|
|
|
+ border-left: none;
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .version-header {
|
|
|
+ left: 0;
|
|
|
+ top: -10px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 移动设备 */
|
|
|
+@media screen and (max-width: 768px) {
|
|
|
+ .ai-evaluation-container {
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 5px;
|
|
|
+ gap: 8px;
|
|
|
+ height: auto; /* 移动端垂直排列,高度自适应 */
|
|
|
+ }
|
|
|
+
|
|
|
+ .essay-section,
|
|
|
+ .evaluation-section,
|
|
|
+ .version-section {
|
|
|
+ flex: 0 0 auto;
|
|
|
+ width: 100%;
|
|
|
+ min-width: auto;
|
|
|
+ }
|
|
|
+
|
|
|
+ .essay-section {
|
|
|
+ height: 45vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ .essay-content-wrapper {
|
|
|
+ height: calc(100% - 80px);
|
|
|
+ }
|
|
|
+
|
|
|
+ .evaluation-section {
|
|
|
+ height: 45vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ .version-section {
|
|
|
+ height: 35vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ .trapezoid,
|
|
|
+ .trapezoid2 {
|
|
|
+ width: 80px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .overall-evaluation-content {
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-wrapper {
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .avatar-wrapper img {
|
|
|
+ width: 24px !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .evaluation-message {
|
|
|
+ margin-left: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .message-content {
|
|
|
+ padding: 10px 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sentence-evaluation-content {
|
|
|
+ padding: 15px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 小屏幕移动设备 */
|
|
|
+@media screen and (max-width: 480px) {
|
|
|
+ .breadcrumb {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .original-text-label {
|
|
|
+ width: 80px;
|
|
|
+ height: 40px;
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .essay-content {
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 24px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .trapezoid,
|
|
|
+ .trapezoid2 {
|
|
|
+ width: 70px;
|
|
|
+ font-size: 11px;
|
|
|
+ border-bottom-width: 25px;
|
|
|
+ line-height: 25px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sentence-category {
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .sentence-evaluation {
|
|
|
+ font-size: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .version-header {
|
|
|
+ width: 70px;
|
|
|
+ height: 35px;
|
|
|
+ font-size: 11px;
|
|
|
+ }
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|