提交 fb5296da authored 作者: 王鹏飞's avatar 王鹏飞

chore: 修改AI打分功能

上级 a215af9e
<script setup> <script setup>
import VueMarkdown from 'vue-markdown-render'
import Demo from '../../test/components/Demo.vue' import Demo from '../../test/components/Demo.vue'
import RecordView from '../../test/components/RecordView.vue'
const props = defineProps({ defineProps({
data: { type: Object }, data: { type: Object },
}) })
const detail = computed(() => {
return { ...props.data, live_info: JSON.parse(props.data.live_info) }
})
function formatDuration(seconds) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = (seconds % 60).toFixed(2)
return minutes > 0 ? `${minutes}m${remainingSeconds}s` : `${remainingSeconds}s`
}
</script> </script>
<template> <template>
<div <RecordView
class="live-view" :recordId="data.id"
:isView="false"
style="border: 1px solid #eee; border-radius: 10px; margin-bottom: 20px; overflow: hidden" style="border: 1px solid #eee; border-radius: 10px; margin-bottom: 20px; overflow: hidden"
v-if="detail"> v-if="data" />
<AppCard>
<div class="live-info">
<div class="live-info-header">
<h2>练习表现</h2>
<p>评分等级</p>
<h3>{{ detail.ai_level || '--' }}</h3>
<p>主播完成质量良好,需要关注细节勤加练习</p>
</div>
<div class="live-info-item">
<div>
<p>主播姓名</p>
<p>{{ detail.created_operator.real_name || detail.created_operator.nickname }}</p>
</div>
<div>
<p>直播时长</p>
<p>{{ formatDuration(detail.live_duration) }}</p>
</div>
<div>
<p>开始时间</p>
<p>{{ detail.live_start_time }}</p>
</div>
<div>
<p>结束时间</p>
<p>{{ detail.live_end_time }}</p>
</div>
<div>
<p>视频状态</p>
<p>已上传</p>
</div>
<div>
<p>观众总人数</p>
<p>{{ detail.live_info.stats.totalViewers }}</p>
</div>
<div>
<p>最高峰人数</p>
<p>{{ detail.live_info.stats.peakViewers }}</p>
</div>
<div>
<p>点赞数</p>
<p>{{ detail.live_info.stats.totalLikes }}</p>
</div>
<div>
<p>刷礼物人数</p>
<p>{{ detail.live_info.stats.totalGiftViewers }}</p>
</div>
<div>
<p>刷礼物总数</p>
<p>{{ detail.live_info.stats.totalGifts }}</p>
</div>
<div>
<p>下单量</p>
<p>{{ detail.orders_count }}</p>
</div>
</div>
</div>
</AppCard>
<AppCard>
<h2 class="live-title">违规情况</h2>
<div class="live-markdown">
<VueMarkdown :source="detail.illegal_word" v-if="detail.illegal_word" />
</div>
</AppCard>
<AppCard>
<h2 class="live-title">卖点讲解</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<AppCard>
<h2 class="live-title">产生订单</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
</div>
<Demo :id="detail.live_practice_id" :recordId="detail.id" :isView="true" />
</template>
<style lang="scss" scoped>
.live-view {
.live-info {
display: flex;
gap: 12px;
p {
font-size: 14px;
color: #525252;
margin: 10px 0;
}
.live-info-header {
display: flex;
flex-direction: column;
border-right: 1px solid #e5e5e5;
padding-right: 12px;
h2 {
font-size: 24px;
font-weight: bold;
}
h3 { <Demo :id="data.live_practice_id" :recordId="data.id" :isView="true" />
font-size: 40px; </template>
font-weight: bold;
}
}
.live-info-item {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 12px;
div {
width: 180px;
}
}
}
.live-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
.live-tips {
margin-top: 10px;
font-size: 14px;
color: #000;
b {
font-size: 14px;
font-weight: bold;
margin-right: 20px;
}
}
.el-step__description {
margin-bottom: 20px;
}
.live-markdown {
line-height: 1.8;
li {
list-style: disc;
margin-left: 20px;
}
p {
margin: 10px 0;
}
strong,
b {
font-weight: bold;
}
}
}
</style>
...@@ -53,7 +53,7 @@ const handleExport = () => { ...@@ -53,7 +53,7 @@ const handleExport = () => {
</script> </script>
<template> <template>
<AppCard title="直播成绩管理"> <AppCard title="成绩管理">
<AppList v-bind="listOptions" ref="appList"> <AppList v-bind="listOptions" ref="appList">
<template #header-buttons> <template #header-buttons>
<!-- <el-button type="primary" @click="handleSubmit">提交成绩</el-button> --> <!-- <el-button type="primary" @click="handleSubmit">提交成绩</el-button> -->
......
...@@ -234,7 +234,7 @@ const handlePublishScore = () => { ...@@ -234,7 +234,7 @@ const handlePublishScore = () => {
} }
</script> </script>
<template> <template>
<AppCard title="直播成绩管理" v-if="detail"> <AppCard title="成绩管理" v-if="detail">
<div class="score-header"> <div class="score-header">
<el-form label-suffix=":" inline class="info"> <el-form label-suffix=":" inline class="info">
<el-form-item label="姓名">{{ detail.student_name }}</el-form-item> <el-form-item label="姓名">{{ detail.student_name }}</el-form-item>
......
...@@ -71,3 +71,8 @@ export function pushSubtitle(params: { subtitle: string; selling_point: string; ...@@ -71,3 +71,8 @@ export function pushSubtitle(params: { subtitle: string; selling_point: string;
export function updateImprovementPlan(data: { id: string; improvement_plan: string }) { export function updateImprovementPlan(data: { id: string; improvement_plan: string }) {
return httpRequest.post('/api/lab/v1/experiment/live-practice/submit-improvement-plan', data) return httpRequest.post('/api/lab/v1/experiment/live-practice/submit-improvement-plan', data)
} }
// AI 打分
export function aiScore(data: { id: string }) {
return httpRequest.post('/api/dev/v1/experiment/live-practice/score-live-practice-record', data)
}
...@@ -55,13 +55,13 @@ const listOptions = { ...@@ -55,13 +55,13 @@ const listOptions = {
return row.ai_status || '--' return row.ai_status || '--'
}, },
}, },
{ // {
label: '评级', // label: '评级',
prop: 'ai_level', // prop: 'ai_level',
computed({ row }) { // computed({ row }) {
return row.ai_level || '--' // return row.ai_level || '--'
}, // },
}, // },
{ label: '操作', slots: 'table-x', width: 140 }, { label: '操作', slots: 'table-x', width: 140 },
], ],
} }
......
<script setup>
import VueMarkdown from 'vue-markdown-render'
import Demo from '../components/Demo.vue'
import ImprovementPlan from '../components/ImprovementPlan.vue'
import { getRecord, aiScore } from '../api'
const props = defineProps({
isView: { type: Boolean, default: true },
recordId: { type: String },
})
const detail = ref(null)
const fetchRecord = async () => {
const res = await getRecord({ id: props.recordId })
const resDetail = res.data.detail
detail.value = { ...resDetail, live_info: JSON.parse(resDetail.live_info) }
}
onMounted(() => {
fetchRecord()
})
const id = computed(() => {
return detail.value?.live_practice_info?.id
})
const dialogVisible = ref(false)
const result = computed(() => {
try {
return JSON.parse(detail.value.illegal_word) || {}
} catch (error) {
console.log(error)
}
return {}
})
const tipsList = {
A: [
{ title: '卖点讲解', content: '强化产品独特性与情感场景化表达。' },
{ title: '违规情况', content: '关注平台新规并规避敏感词。' },
{ title: '讲解时长', content: '精准分配时间,促销环节压缩冗余。' },
{ title: '其他建议', content: '模拟实战并复盘转化数据。' },
],
B: [
{ title: '卖点讲解', content: '场景化设计减少参数罗列。' },
{ title: '违规情况', content: '熟记合规词库并规避竞品对比。' },
{ title: '讲解时长', content: '控制节奏,促销环节提速。' },
{ title: '其他建议', content: '学习高感染力话术并准备互动模板。' },
],
C: [
{ title: '卖点讲解', content: '结构化表达并突出核心卖点。' },
{ title: '违规情况', content: '系统学习规则并使用AI审核。' },
{ title: '讲解时长', content: '分段练习并制定时间分配表。' },
{ title: '其他建议', content: '参加基础培训并拆解优秀案例。' },
],
D: [
{ title: '卖点讲解', content: '模板化练习并记忆关键词。' },
{ title: '违规情况', content: '精读合规手册并替换敏感词。' },
{ title: '讲解时长', content: '分段训练并使用计时器。' },
{ title: '其他建议', content: '录制回放并建立反馈机制。' },
],
}
const tips = computed(() => {
return tipsList[detail.value?.ai_level || 'A']
})
const successWords = computed(() => {
return detail.value?.words?.filter((item) => item.time) || []
})
const failedWords = computed(() => {
return detail.value?.words?.filter((item) => !item.time) || []
})
const handleAIScore = () => {
aiScore({ id: props.recordId }).then((res) => {
console.log(res)
})
}
function formatDuration(seconds) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = (seconds % 60).toFixed(2)
return minutes > 0 ? `${minutes}m${remainingSeconds}s` : `${remainingSeconds}s`
}
</script>
<template>
<div class="live-view" v-if="detail">
<AppCard>
<div class="live-info">
<div class="live-info-header">
<h2 @dblclick="handleAIScore">我的练习表现</h2>
<h6>{{ detail.live_practice_info?.live_commodity?.title }}</h6>
<div v-if="false">
<p>评分等级</p>
<h3>{{ detail.ai_level || '--' }}</h3>
<p>主播完成质量良好,需要关注细节勤加练习</p>
</div>
</div>
<div class="live-info-item">
<div>
<p>主播姓名</p>
<p>{{ detail.created_operator.real_name || detail.created_operator.nickname }}</p>
</div>
<div>
<p>直播时长</p>
<p>{{ formatDuration(detail.live_duration) }}</p>
</div>
<div>
<p>开始时间</p>
<p>{{ detail.live_start_time }}</p>
</div>
<div>
<p>结束时间</p>
<p>{{ detail.live_end_time }}</p>
</div>
<div>
<p>视频状态</p>
<p>已上传</p>
</div>
<div>
<p>观众总人数</p>
<p>{{ detail.live_info.stats.totalViewers }}</p>
</div>
<div>
<p>最高峰人数</p>
<p>{{ detail.live_info.stats.peakViewers }}</p>
</div>
<div>
<p>点赞数</p>
<p>{{ detail.live_info.stats.totalLikes }}</p>
</div>
<div>
<p>刷礼物人数</p>
<p>{{ detail.live_info.stats.totalGiftViewers }}</p>
</div>
<div>
<p>刷礼物总数</p>
<p>{{ detail.live_info.stats.totalGifts }}</p>
</div>
<div>
<p>下单量</p>
<p>{{ detail.orders_count }}</p>
</div>
<div v-if="isView">
<p>操作</p>
<el-button type="primary" link @click="dialogVisible = true">查看直播回放</el-button>
<ImprovementPlan :id="recordId" :content="detail.improvement_plan" v-if="recordId" />
</div>
</div>
</div>
</AppCard>
<AppCard v-if="false">
<h2 class="live-title">练习优化建议</h2>
<div v-for="(item, index) in tips" :key="index">
<p class="live-tips">
<b>{{ item.title }}</b> {{ item.content }}
</p>
</div>
</AppCard>
<AppCard title="卖点讲解覆盖情况">
<ul class="live-ul">
<li>已讲解:{{ successWords.length }}个卖点</li>
<li>未讲解:{{ failedWords.length }}个卖点</li>
<li>覆盖率:{{ ((successWords.length / detail.words?.length) * 100).toFixed(2) }}%</li>
</ul>
<div class="live-markdown">
<VueMarkdown :source="result.sellingPoints" v-if="result.sellingPoints" />
</div>
<h2 class="live-title">已讲解卖点:</h2>
<el-steps direction="vertical" :active="1">
<el-step v-for="(item, index) in successWords" :key="index" :title="item.name" status="success">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
<h2 class="live-title">未讲解卖点:</h2>
<el-steps direction="vertical" :active="1">
<el-step v-for="(item, index) in failedWords" :key="index" :title="item.name" status="error">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<AppCard title="违禁词分析">
<div class="live-markdown">
<VueMarkdown :source="result.forbiddenWords" v-if="result.forbiddenWords" />
</div>
</AppCard>
<AppCard title="语速分析">
<div class="live-markdown">
<VueMarkdown :source="result.speechSpeed" v-if="result.speechSpeed" />
</div>
</AppCard>
<AppCard title="直播优化建议">
<div class="live-markdown">
<VueMarkdown :source="result.optimizationTips" v-if="result.optimizationTips" />
</div>
</AppCard>
<AppCard v-if="false">
<h2 class="live-title">违规情况</h2>
<div class="live-markdown">
<VueMarkdown :source="result.illegal_word" v-if="result.illegal_word" />
</div>
</AppCard>
<AppCard v-if="false">
<h2 class="live-title">卖点讲解</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<AppCard v-if="false">
<h2 class="live-title">产生订单</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<el-dialog v-model="dialogVisible" title="直播回放" width="1200px">
<div style="height: 740px">
<Demo :id="id" :recordId="recordId" :isView="true" />
</div>
</el-dialog>
</div>
</template>
<style lang="scss">
.live-view {
.live-info {
display: flex;
gap: 12px;
p {
font-size: 14px;
color: #525252;
margin: 10px 0;
}
.live-info-header {
display: flex;
flex-direction: column;
justify-content: space-around;
border-right: 1px solid #e5e5e5;
padding-right: 12px;
h2 {
font-size: 24px;
font-weight: bold;
}
h3 {
font-size: 40px;
font-weight: bold;
}
h6 {
font-size: 20px;
font-weight: bold;
}
}
.live-info-item {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 12px;
div {
width: 200px;
}
}
}
.live-title {
font-size: 14px;
font-weight: bold;
margin: 20px 0;
}
.live-tips {
margin-top: 10px;
font-size: 14px;
color: #000;
b {
font-size: 14px;
font-weight: bold;
margin-right: 20px;
}
}
.el-step__description {
margin-bottom: 20px;
}
.live-markdown {
line-height: 1.8;
li {
list-style: disc;
margin-left: 20px;
}
p {
margin: 10px 0;
}
strong,
b {
font-weight: bold;
}
}
.live-ul {
display: flex;
margin-bottom: 20px;
gap: 30px;
}
}
</style>
<script setup> <script setup>
import VueMarkdown from 'vue-markdown-render' import RecordView from '../components/RecordView.vue'
import Demo from '../components/Demo.vue'
import { getRecord } from '../api'
import ImprovementPlan from '../components/ImprovementPlan.vue'
const route = useRoute() const route = useRoute()
const id = route.query.id
const recordId = route.query.record_id const recordId = route.query.record_id
const dialogVisible = ref(false)
const detail = ref(null)
const fetchRecord = async () => {
const res = await getRecord({ id: recordId })
const resDetail = res.data.detail
detail.value = { ...resDetail, live_info: JSON.parse(resDetail.live_info) }
}
onMounted(() => {
fetchRecord()
})
const tipsList = {
A: [
{ title: '卖点讲解', content: '强化产品独特性与情感场景化表达。' },
{ title: '违规情况', content: '关注平台新规并规避敏感词。' },
{ title: '讲解时长', content: '精准分配时间,促销环节压缩冗余。' },
{ title: '其他建议', content: '模拟实战并复盘转化数据。' },
],
B: [
{ title: '卖点讲解', content: '场景化设计减少参数罗列。' },
{ title: '违规情况', content: '熟记合规词库并规避竞品对比。' },
{ title: '讲解时长', content: '控制节奏,促销环节提速。' },
{ title: '其他建议', content: '学习高感染力话术并准备互动模板。' },
],
C: [
{ title: '卖点讲解', content: '结构化表达并突出核心卖点。' },
{ title: '违规情况', content: '系统学习规则并使用AI审核。' },
{ title: '讲解时长', content: '分段练习并制定时间分配表。' },
{ title: '其他建议', content: '参加基础培训并拆解优秀案例。' },
],
D: [
{ title: '卖点讲解', content: '模板化练习并记忆关键词。' },
{ title: '违规情况', content: '精读合规手册并替换敏感词。' },
{ title: '讲解时长', content: '分段训练并使用计时器。' },
{ title: '其他建议', content: '录制回放并建立反馈机制。' },
],
}
const tips = computed(() => {
return tipsList[detail.value?.ai_level || 'A']
})
function formatDuration(seconds) {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = (seconds % 60).toFixed(2)
return minutes > 0 ? `${minutes}m${remainingSeconds}s` : `${remainingSeconds}s`
}
</script> </script>
<template> <template>
<div class="live-view" v-if="detail"> <RecordView :recordId="recordId" />
<AppCard>
<div class="live-info">
<div class="live-info-header">
<h2>我的练习表现</h2>
<p>评分等级</p>
<h3>{{ detail.ai_level || '--' }}</h3>
<p>主播完成质量良好,需要关注细节勤加练习</p>
</div>
<div class="live-info-item">
<div>
<p>主播姓名</p>
<p>{{ detail.created_operator.real_name || detail.created_operator.nickname }}</p>
</div>
<div>
<p>直播时长</p>
<p>{{ formatDuration(detail.live_duration) }}</p>
</div>
<div>
<p>开始时间</p>
<p>{{ detail.live_start_time }}</p>
</div>
<div>
<p>结束时间</p>
<p>{{ detail.live_end_time }}</p>
</div>
<div>
<p>视频状态</p>
<p>已上传</p>
</div>
<div>
<p>观众总人数</p>
<p>{{ detail.live_info.stats.totalViewers }}</p>
</div>
<div>
<p>最高峰人数</p>
<p>{{ detail.live_info.stats.peakViewers }}</p>
</div>
<div>
<p>点赞数</p>
<p>{{ detail.live_info.stats.totalLikes }}</p>
</div>
<div>
<p>刷礼物人数</p>
<p>{{ detail.live_info.stats.totalGiftViewers }}</p>
</div>
<div>
<p>刷礼物总数</p>
<p>{{ detail.live_info.stats.totalGifts }}</p>
</div>
<div>
<p>下单量</p>
<p>{{ detail.orders_count }}</p>
</div>
<div>
<p>操作</p>
<el-button type="primary" link @click="dialogVisible = true">查看直播回放</el-button>
<ImprovementPlan :id="recordId" :content="detail.improvement_plan" v-if="recordId" />
</div>
</div>
</div>
</AppCard>
<AppCard>
<h2 class="live-title">练习优化建议</h2>
<div v-for="(item, index) in tips" :key="index">
<p class="live-tips">
<b>{{ item.title }}</b> {{ item.content }}
</p>
</div>
</AppCard>
<AppCard>
<h2 class="live-title">违规情况</h2>
<div class="live-markdown">
<VueMarkdown :source="detail.illegal_word" v-if="detail.illegal_word" />
</div>
</AppCard>
<AppCard>
<h2 class="live-title">卖点讲解</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<AppCard>
<h2 class="live-title">产生订单</h2>
<el-steps direction="vertical" :active="1">
<el-step
v-for="(item, index) in detail.words"
:key="index"
:title="item.name"
:status="item.time ? 'success' : 'error'">
<template #description> {{ item.time }} {{ item.subtitle }} </template>
</el-step>
</el-steps>
</AppCard>
<el-dialog v-model="dialogVisible" title="直播回放" width="1200px">
<div style="height: 740px">
<Demo :id="id" :recordId="recordId" :isView="true" />
</div>
</el-dialog>
</div>
</template> </template>
<style lang="scss">
.live-view {
.live-info {
display: flex;
gap: 12px;
p {
font-size: 14px;
color: #525252;
margin: 10px 0;
}
.live-info-header {
display: flex;
flex-direction: column;
border-right: 1px solid #e5e5e5;
padding-right: 12px;
h2 {
font-size: 24px;
font-weight: bold;
}
h3 {
font-size: 40px;
font-weight: bold;
}
}
.live-info-item {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 12px;
div {
width: 180px;
}
}
}
.live-title {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
.live-tips {
margin-top: 10px;
font-size: 14px;
color: #000;
b {
font-size: 14px;
font-weight: bold;
margin-right: 20px;
}
}
.el-step__description {
margin-bottom: 20px;
}
.live-markdown {
line-height: 1.8;
li {
list-style: disc;
margin-left: 20px;
}
p {
margin: 10px 0;
}
strong,
b {
font-weight: bold;
}
}
}
</style>
...@@ -378,7 +378,7 @@ const adminMenus: IMenuItem[] = [ ...@@ -378,7 +378,7 @@ const adminMenus: IMenuItem[] = [
{ {
id: 203, id: 203,
name: '直播成绩管理', name: '成绩管理',
path: '/live/score', path: '/live/score',
icon: markRaw(DocumentChecked), icon: markRaw(DocumentChecked),
role: [5, 6], role: [5, 6],
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论