fix: 活动评审弹窗总分按维度权重加权计算
Made-with: Cursor
This commit is contained in:
parent
e054895c81
commit
6c3b2f4a1d
@ -1,13 +1,6 @@
|
||||
<template>
|
||||
<a-drawer
|
||||
:open="open"
|
||||
:title="drawerTitle"
|
||||
placement="right"
|
||||
:width="1100"
|
||||
:closable="true"
|
||||
:destroy-on-close="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<a-drawer :open="open" :title="drawerTitle" placement="right" :width="1100" :closable="true" :destroy-on-close="true"
|
||||
@close="handleClose">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="review-drawer-content">
|
||||
<!-- 左侧:作品信息 -->
|
||||
@ -35,28 +28,15 @@
|
||||
<div class="section">
|
||||
<div class="section-label">作品详情</div>
|
||||
<div class="work-tabs" v-if="modelItems.length > 1">
|
||||
<a-button
|
||||
v-for="(item, index) in modelItems"
|
||||
:key="index"
|
||||
:type="currentFileIndex === index ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="currentFileIndex = index"
|
||||
>
|
||||
<a-button v-for="(item, index) in modelItems" :key="index"
|
||||
:type="currentFileIndex === index ? 'primary' : 'default'" size="small"
|
||||
@click="currentFileIndex = index">
|
||||
作品{{ index + 1 }}
|
||||
</a-button>
|
||||
</div>
|
||||
<!-- 预览区域 -->
|
||||
<div
|
||||
class="preview-area"
|
||||
@mouseenter="isHovering = true"
|
||||
@mouseleave="isHovering = false"
|
||||
>
|
||||
<img
|
||||
v-if="currentPreviewImage"
|
||||
:src="currentPreviewImage"
|
||||
alt="作品预览"
|
||||
class="preview-image"
|
||||
/>
|
||||
<div class="preview-area" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
|
||||
<img v-if="currentPreviewImage" :src="currentPreviewImage" alt="作品预览" class="preview-image" />
|
||||
<div v-else class="preview-placeholder">
|
||||
<FileImageOutlined />
|
||||
<span>暂无预览</span>
|
||||
@ -65,7 +45,9 @@
|
||||
<transition name="fade">
|
||||
<div v-show="isHovering && currentModelUrl" class="preview-overlay">
|
||||
<a-button type="primary" @click="handlePreview3DModel">
|
||||
<template #icon><EyeOutlined /></template>
|
||||
<template #icon>
|
||||
<EyeOutlined />
|
||||
</template>
|
||||
3D预览
|
||||
</a-button>
|
||||
</div>
|
||||
@ -81,20 +63,12 @@
|
||||
<div class="section" v-if="workDetail?.attachments?.length">
|
||||
<div class="section-label">作品附件</div>
|
||||
<div class="attachments-list">
|
||||
<div
|
||||
v-for="attachment in workDetail.attachments"
|
||||
:key="attachment.id"
|
||||
class="attachment-group"
|
||||
>
|
||||
<div v-for="attachment in workDetail.attachments" :key="attachment.id" class="attachment-group">
|
||||
<div class="attachment-name">附件名称:{{ getAttachmentCategory(attachment) }}</div>
|
||||
<div class="attachment-item">
|
||||
<PaperClipOutlined class="attachment-icon" />
|
||||
<span class="file-name">{{ attachment.fileName }}</span>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="downloadAttachment(attachment)"
|
||||
>
|
||||
<a-button type="link" size="small" @click="downloadAttachment(attachment)">
|
||||
<DownloadOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
@ -117,161 +91,98 @@
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- 评分标准说明(有评审规则时显示) -->
|
||||
<div class="scoring-standard" v-if="reviewRule">
|
||||
<div class="standard-title">评分标准</div>
|
||||
<div class="standard-content">
|
||||
<div
|
||||
v-for="(dim, index) in reviewRule.dimensions"
|
||||
:key="index"
|
||||
class="dimension-desc"
|
||||
>
|
||||
<span class="dim-name">{{ dim.name }}({{ dim.percentage }}分):</span>
|
||||
<span class="dim-desc">{{ dim.description || '无说明' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 已评审:显示保存的维度评分数据 -->
|
||||
<template v-if="isReviewed && savedDimensionScores.length > 0">
|
||||
<div class="dimension-scores">
|
||||
<div
|
||||
v-for="(dim, index) in savedDimensionScores"
|
||||
:key="index"
|
||||
class="dimension-item"
|
||||
>
|
||||
<div class="dimension-label">{{ dim.name }}(满分{{ dim.maxScore }}分)</div>
|
||||
<div class="dimension-input">
|
||||
<a-input-number
|
||||
:value="dim.score"
|
||||
:disabled="true"
|
||||
size="small"
|
||||
class="score-input"
|
||||
/>
|
||||
<span class="score-unit">分</span>
|
||||
<!-- 评分标准说明(有评审规则时显示) -->
|
||||
<div class="scoring-standard" v-if="reviewRule">
|
||||
<div class="standard-title">评分标准</div>
|
||||
<div class="standard-content">
|
||||
<div v-for="(dim, index) in reviewRule.dimensions" :key="index" class="dimension-desc">
|
||||
<span class="dim-name">{{ dim.name }}({{ dim.percentage }}%):</span>
|
||||
<span class="dim-desc">{{ dim.description || '无说明' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 未评审:显示当前评审规则的维度评分输入 -->
|
||||
<template v-else-if="reviewRule && !isReviewed">
|
||||
<!-- 各维度评分 -->
|
||||
<div class="dimension-scores">
|
||||
<div
|
||||
v-for="(dim, index) in reviewRule.dimensions"
|
||||
:key="index"
|
||||
class="dimension-item"
|
||||
>
|
||||
<div class="dimension-label">{{ dim.name }}(满分{{ dim.percentage }}分)</div>
|
||||
<div class="dimension-input">
|
||||
<a-button
|
||||
size="small"
|
||||
@click="decreaseScore(index)"
|
||||
>-</a-button>
|
||||
<a-input-number
|
||||
v-model:value="dimensionScores[index]"
|
||||
:min="0"
|
||||
:max="dim.percentage"
|
||||
:precision="0"
|
||||
size="small"
|
||||
class="score-input"
|
||||
/>
|
||||
<span class="score-unit">分</span>
|
||||
<a-button
|
||||
size="small"
|
||||
@click="increaseScore(index, dim.percentage)"
|
||||
>+</a-button>
|
||||
<!-- 已评审:显示保存的维度评分数据 -->
|
||||
<template v-if="isReviewed && savedDimensionScores.length > 0">
|
||||
<div class="dimension-scores">
|
||||
<div v-for="(dim, index) in savedDimensionScores" :key="index" class="dimension-item">
|
||||
<div class="dimension-label">{{ dim.name }}(权重{{ dim.maxScore }}%)</div>
|
||||
<div class="dimension-input">
|
||||
<a-input-number :value="dim.score" :disabled="true" size="small" class="score-input" />
|
||||
<span class="score-unit">分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 未评审:显示当前评审规则的维度评分输入 -->
|
||||
<template v-else-if="reviewRule && !isReviewed">
|
||||
<!-- 各维度评分 -->
|
||||
<div class="dimension-scores">
|
||||
<div v-for="(dim, index) in reviewRule.dimensions" :key="index" class="dimension-item">
|
||||
<div class="dimension-label">{{ dim.name }}(权重{{ dim.percentage }}%)</div>
|
||||
<div class="dimension-input">
|
||||
<a-button size="small" @click="decreaseScore(index)">-</a-button>
|
||||
<a-input-number v-model:value="dimensionScores[index]" :min="0" :max="100" :precision="0"
|
||||
size="small" class="score-input" />
|
||||
<span class="score-unit">分</span>
|
||||
<a-button size="small" @click="increaseScore(index, 100)">+</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 简单评分模式(无评审规则时) -->
|
||||
<div class="simple-score" v-else>
|
||||
<div class="dimension-label">评分(满分100分)</div>
|
||||
<div class="dimension-input">
|
||||
<a-input-number v-model:value="simpleScore" :min="0" :max="100" :precision="2" :disabled="isReviewed"
|
||||
style="width: 150px" />
|
||||
<span class="score-unit">分</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 简单评分模式(无评审规则时) -->
|
||||
<div class="simple-score" v-else>
|
||||
<div class="dimension-label">评分(满分100分)</div>
|
||||
<div class="dimension-input">
|
||||
<a-input-number
|
||||
v-model:value="simpleScore"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
:disabled="isReviewed"
|
||||
style="width: 150px"
|
||||
/>
|
||||
<span class="score-unit">分</span>
|
||||
<!-- 总得分 -->
|
||||
<div class="total-score">
|
||||
<span class="total-label">总得分</span>
|
||||
<span class="total-value">{{ totalScore }}</span>
|
||||
<span class="total-unit">分</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 总得分 -->
|
||||
<div class="total-score">
|
||||
<span class="total-label">总得分</span>
|
||||
<span class="total-value">{{ totalScore }}</span>
|
||||
<span class="total-unit">分</span>
|
||||
</div>
|
||||
<!-- 老师点评 -->
|
||||
<div class="comments-section">
|
||||
<div class="comments-title">老师点评</div>
|
||||
<a-select v-model:value="selectedPresetComment" placeholder="选择预设点评"
|
||||
style="width: 100%; margin-bottom: 12px" :disabled="isReviewed" allow-clear
|
||||
@change="handlePresetSelect">
|
||||
<a-select-option v-for="preset in presetComments" :key="preset.id" :value="preset.id">
|
||||
{{ preset.content }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-textarea v-model:value="comments" placeholder="请输入评语或选择预设评语" :rows="4" :maxlength="500" show-count
|
||||
:disabled="isReviewed" />
|
||||
</div>
|
||||
|
||||
<!-- 老师点评 -->
|
||||
<div class="comments-section">
|
||||
<div class="comments-title">老师点评</div>
|
||||
<a-select
|
||||
v-model:value="selectedPresetComment"
|
||||
placeholder="选择预设点评"
|
||||
style="width: 100%; margin-bottom: 12px"
|
||||
:disabled="isReviewed"
|
||||
allow-clear
|
||||
@change="handlePresetSelect"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="preset in presetComments"
|
||||
:key="preset.id"
|
||||
:value="preset.id"
|
||||
>
|
||||
{{ preset.content }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-textarea
|
||||
v-model:value="comments"
|
||||
placeholder="请输入评语或选择预设评语"
|
||||
:rows="4"
|
||||
:maxlength="500"
|
||||
show-count
|
||||
:disabled="isReviewed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons" v-if="!isReviewed">
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
提交
|
||||
</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
<a-button danger @click="handleViolation">违规</a-button>
|
||||
</div>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons" v-if="!isReviewed">
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
提交
|
||||
</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
<a-button danger @click="handleViolation">违规</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<a-modal
|
||||
v-model:open="violationModalOpen"
|
||||
title="标记违规"
|
||||
ok-text="确定"
|
||||
cancel-text="取消"
|
||||
:confirm-loading="violationSubmitting"
|
||||
destroy-on-close
|
||||
@ok="handleViolationConfirm"
|
||||
>
|
||||
<a-modal v-model:open="violationModalOpen" title="标记违规" ok-text="确定" cancel-text="取消"
|
||||
:confirm-loading="violationSubmitting" destroy-on-close @ok="handleViolationConfirm">
|
||||
<p class="violation-modal-hint">
|
||||
确定标记后,作品状态将变为「违规」,并结束您在本作品上的评审任务。
|
||||
</p>
|
||||
<a-textarea
|
||||
v-model:value="violationReason"
|
||||
placeholder="可选:违规说明"
|
||||
:rows="3"
|
||||
:maxlength="500"
|
||||
show-count
|
||||
/>
|
||||
<a-textarea v-model:value="violationReason" placeholder="可选:违规说明" :rows="3" :maxlength="500" show-count />
|
||||
</a-modal>
|
||||
</a-drawer>
|
||||
</template>
|
||||
@ -407,17 +318,27 @@ const currentPreviewImage = computed(() => {
|
||||
return item?.previewUrl || workDetail.value?.previewUrl || null;
|
||||
});
|
||||
|
||||
// 总分计算
|
||||
// 总分计算:多维度时各维度输入 0~100 分,总得分 = Σ(维度得分 × 权重% / 100)
|
||||
const totalScore = computed(() => {
|
||||
// 如果已评审,直接返回数据库存储的总分
|
||||
if (isReviewed.value && existingScore.value?.totalScore !== null) {
|
||||
if (
|
||||
isReviewed.value &&
|
||||
existingScore.value?.totalScore !== null &&
|
||||
existingScore.value?.totalScore !== undefined
|
||||
) {
|
||||
return Number(existingScore.value.totalScore);
|
||||
}
|
||||
// 否则根据当前输入计算
|
||||
if (reviewRule.value) {
|
||||
return dimensionScores.value.reduce((sum, score) => sum + (score || 0), 0);
|
||||
if (reviewRule.value?.dimensions?.length) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < reviewRule.value.dimensions.length; i++) {
|
||||
const dim = reviewRule.value.dimensions[i];
|
||||
const s = Number(dimensionScores.value[i] ?? 0);
|
||||
const w = Number(dim.percentage ?? 0) / 100;
|
||||
sum += s * w;
|
||||
}
|
||||
return Math.round(sum * 100) / 100;
|
||||
}
|
||||
return simpleScore.value || 0;
|
||||
return Number(simpleScore.value ?? 0);
|
||||
});
|
||||
|
||||
// 格式化日期
|
||||
@ -445,7 +366,7 @@ const downloadAttachment = (attachment: any) => {
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// 增加分数
|
||||
// 增加分数(各维度独立 0~100)
|
||||
const increaseScore = (index: number, maxScore: number) => {
|
||||
const current = dimensionScores.value[index] || 0;
|
||||
if (current < maxScore) {
|
||||
@ -468,7 +389,7 @@ const handlePresetSelect = (presetId: number | undefined) => {
|
||||
if (preset) {
|
||||
comments.value = preset.content;
|
||||
// 增加使用次数
|
||||
presetCommentsApi.incrementUseCount(presetId).catch(() => {});
|
||||
presetCommentsApi.incrementUseCount(presetId).catch(() => { });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -594,12 +515,12 @@ const handleSubmit = async () => {
|
||||
|
||||
// 验证评分
|
||||
if (reviewRule.value) {
|
||||
const hasInvalidScore = dimensionScores.value.some((score, idx) => {
|
||||
const dim = reviewRule.value!.dimensions[idx];
|
||||
return score === undefined || score === null || score < 0 || score > dim.percentage;
|
||||
const hasInvalidScore = dimensionScores.value.some((score) => {
|
||||
const s = score === undefined || score === null ? NaN : Number(score);
|
||||
return Number.isNaN(s) || s < 0 || s > 100;
|
||||
});
|
||||
if (hasInvalidScore) {
|
||||
message.warning("请为所有评分维度填写有效分数");
|
||||
message.warning("请为所有评分维度填写 0~100 的有效分数");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@ -622,7 +543,8 @@ const handleSubmit = async () => {
|
||||
if (reviewRule.value?.dimensions) {
|
||||
scoreData.dimensionScores = reviewRule.value.dimensions.map((dim, idx) => ({
|
||||
name: dim.name,
|
||||
score: dimensionScores.value[idx] || 0,
|
||||
score: dimensionScores.value[idx] ?? 0,
|
||||
/** 权重占比(%),总得分由前端按 Σ(score×percentage/100) 计算后写入 totalScore */
|
||||
maxScore: dim.percentage,
|
||||
}));
|
||||
}
|
||||
@ -948,7 +870,7 @@ $primary: #0958d9;
|
||||
gap: 8px;
|
||||
|
||||
.score-input {
|
||||
width: 60px;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user