304 lines
6.5 KiB
Vue
304 lines
6.5 KiB
Vue
|
|
<template>
|
||
|
|
<ElDialog
|
||
|
|
:model-value="open"
|
||
|
|
append-to-body
|
||
|
|
width="420px"
|
||
|
|
:show-close="false"
|
||
|
|
:close-on-click-modal="!busy"
|
||
|
|
:close-on-press-escape="!busy"
|
||
|
|
class="operation-feedback-dialog"
|
||
|
|
modal-class="operation-feedback-overlay"
|
||
|
|
@update:model-value="handleModelUpdate"
|
||
|
|
@closed="resetForm"
|
||
|
|
>
|
||
|
|
<section class="operation-feedback">
|
||
|
|
<header class="operation-feedback-head">
|
||
|
|
<span class="operation-feedback-icon">
|
||
|
|
<i class="mdi mdi-message-star-outline"></i>
|
||
|
|
</span>
|
||
|
|
<div>
|
||
|
|
<h3>评价本轮处理</h3>
|
||
|
|
<p>请给本轮智能体处理结果打分。</p>
|
||
|
|
</div>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<div class="operation-feedback-stars" role="radiogroup" aria-label="本轮处理评分">
|
||
|
|
<button
|
||
|
|
v-for="score in scores"
|
||
|
|
:key="score"
|
||
|
|
type="button"
|
||
|
|
class="operation-feedback-star"
|
||
|
|
:class="{ active: score <= rating }"
|
||
|
|
:disabled="busy"
|
||
|
|
:aria-checked="rating === score ? 'true' : 'false'"
|
||
|
|
role="radio"
|
||
|
|
@click="rating = score"
|
||
|
|
@mouseenter="hoverRating = score"
|
||
|
|
@mouseleave="hoverRating = 0"
|
||
|
|
>
|
||
|
|
<i :class="score <= displayRating ? 'mdi mdi-star' : 'mdi mdi-star-outline'"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<Transition name="feedback-reason-slide">
|
||
|
|
<label v-if="showReasonInput" class="operation-feedback-reason">
|
||
|
|
<span>不满意的原因</span>
|
||
|
|
<textarea
|
||
|
|
v-model="reason"
|
||
|
|
maxlength="500"
|
||
|
|
rows="3"
|
||
|
|
:disabled="busy"
|
||
|
|
placeholder="例如:意图识别不准、信息提取遗漏、流程引导不清晰..."
|
||
|
|
></textarea>
|
||
|
|
<small>{{ reason.length }}/500</small>
|
||
|
|
</label>
|
||
|
|
</Transition>
|
||
|
|
|
||
|
|
<p v-if="errorMessage" class="operation-feedback-error">{{ errorMessage }}</p>
|
||
|
|
|
||
|
|
<footer class="operation-feedback-actions">
|
||
|
|
<button type="button" class="operation-feedback-secondary" :disabled="busy" @click="emit('close')">
|
||
|
|
稍后评价
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
type="button"
|
||
|
|
class="operation-feedback-primary"
|
||
|
|
:disabled="busy || !rating"
|
||
|
|
@click="submit"
|
||
|
|
>
|
||
|
|
<i v-if="busy" class="mdi mdi-loading mdi-spin"></i>
|
||
|
|
<span>{{ busy ? '提交中...' : '提交评价' }}</span>
|
||
|
|
</button>
|
||
|
|
</footer>
|
||
|
|
</section>
|
||
|
|
</ElDialog>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup>
|
||
|
|
import { computed, ref, watch } from 'vue'
|
||
|
|
import { ElDialog } from 'element-plus/es/components/dialog/index.mjs'
|
||
|
|
|
||
|
|
const props = defineProps({
|
||
|
|
open: { type: Boolean, default: false },
|
||
|
|
busy: { type: Boolean, default: false },
|
||
|
|
errorMessage: { type: String, default: '' }
|
||
|
|
})
|
||
|
|
|
||
|
|
const emit = defineEmits(['close', 'submit'])
|
||
|
|
const scores = [1, 2, 3, 4, 5]
|
||
|
|
const rating = ref(0)
|
||
|
|
const hoverRating = ref(0)
|
||
|
|
const reason = ref('')
|
||
|
|
const displayRating = computed(() => hoverRating.value || rating.value)
|
||
|
|
const showReasonInput = computed(() => rating.value > 0 && rating.value <= 3)
|
||
|
|
|
||
|
|
function resetForm() {
|
||
|
|
rating.value = 0
|
||
|
|
hoverRating.value = 0
|
||
|
|
reason.value = ''
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleModelUpdate(value) {
|
||
|
|
if (!value) {
|
||
|
|
emit('close')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function submit() {
|
||
|
|
emit('submit', {
|
||
|
|
rating: rating.value,
|
||
|
|
reason: reason.value
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
watch(
|
||
|
|
() => props.open,
|
||
|
|
(open) => {
|
||
|
|
if (open) {
|
||
|
|
resetForm()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
)
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<style scoped>
|
||
|
|
:deep(.operation-feedback-dialog) {
|
||
|
|
border-radius: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.operation-feedback-dialog .el-dialog__header) {
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.operation-feedback-dialog .el-dialog__body) {
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 18px;
|
||
|
|
padding: 2px 2px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-head {
|
||
|
|
display: flex;
|
||
|
|
gap: 12px;
|
||
|
|
align-items: flex-start;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-icon {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
width: 38px;
|
||
|
|
height: 38px;
|
||
|
|
border-radius: 6px;
|
||
|
|
background: #eef4ff;
|
||
|
|
color: #1d4ed8;
|
||
|
|
font-size: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback h3 {
|
||
|
|
margin: 0;
|
||
|
|
color: #172033;
|
||
|
|
font-size: 17px;
|
||
|
|
font-weight: 700;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback p {
|
||
|
|
margin: 5px 0 0;
|
||
|
|
color: #667085;
|
||
|
|
font-size: 13px;
|
||
|
|
line-height: 1.6;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-stars {
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
gap: 8px;
|
||
|
|
padding: 6px 0 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-star {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
width: 42px;
|
||
|
|
height: 42px;
|
||
|
|
border: 1px solid #d6deea;
|
||
|
|
border-radius: 6px;
|
||
|
|
background: #fff;
|
||
|
|
color: #9aa8bb;
|
||
|
|
cursor: pointer;
|
||
|
|
font-size: 24px;
|
||
|
|
transition: border-color 180ms ease, color 180ms ease, background 180ms ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-star:hover,
|
||
|
|
.operation-feedback-star.active {
|
||
|
|
border-color: #f59e0b;
|
||
|
|
background: #fff7ed;
|
||
|
|
color: #f59e0b;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-star:disabled {
|
||
|
|
cursor: not-allowed;
|
||
|
|
opacity: 0.7;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-reason {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-reason span {
|
||
|
|
color: #344054;
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-reason textarea {
|
||
|
|
width: 100%;
|
||
|
|
box-sizing: border-box;
|
||
|
|
resize: vertical;
|
||
|
|
min-height: 82px;
|
||
|
|
border: 1px solid #d0d5dd;
|
||
|
|
border-radius: 6px;
|
||
|
|
padding: 10px 12px;
|
||
|
|
color: #172033;
|
||
|
|
font: inherit;
|
||
|
|
line-height: 1.5;
|
||
|
|
outline: none;
|
||
|
|
transition: border-color 180ms ease, box-shadow 180ms ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-reason textarea:focus {
|
||
|
|
border-color: #2563eb;
|
||
|
|
box-shadow: 0 0 0 3px rgb(37 99 235 / 12%);
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-reason small {
|
||
|
|
align-self: flex-end;
|
||
|
|
color: #98a2b3;
|
||
|
|
font-size: 12px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-error {
|
||
|
|
margin: 0;
|
||
|
|
color: #b42318;
|
||
|
|
font-size: 13px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-actions {
|
||
|
|
display: flex;
|
||
|
|
justify-content: flex-end;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-secondary,
|
||
|
|
.operation-feedback-primary {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
gap: 6px;
|
||
|
|
min-width: 88px;
|
||
|
|
height: 34px;
|
||
|
|
border-radius: 6px;
|
||
|
|
border: 1px solid #d0d5dd;
|
||
|
|
padding: 0 14px;
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 600;
|
||
|
|
cursor: pointer;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-secondary {
|
||
|
|
background: #fff;
|
||
|
|
color: #344054;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-primary {
|
||
|
|
border-color: #2563eb;
|
||
|
|
background: #2563eb;
|
||
|
|
color: #fff;
|
||
|
|
}
|
||
|
|
|
||
|
|
.operation-feedback-secondary:disabled,
|
||
|
|
.operation-feedback-primary:disabled {
|
||
|
|
cursor: not-allowed;
|
||
|
|
opacity: 0.6;
|
||
|
|
}
|
||
|
|
|
||
|
|
.feedback-reason-slide-enter-active,
|
||
|
|
.feedback-reason-slide-leave-active {
|
||
|
|
transition: opacity 180ms ease, transform 180ms ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.feedback-reason-slide-enter-from,
|
||
|
|
.feedback-reason-slide-leave-to {
|
||
|
|
opacity: 0;
|
||
|
|
transform: translateY(-6px);
|
||
|
|
}
|
||
|
|
</style>
|