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

chore: update

上级 29c633cd
<script setup>
import { useMapStore } from '@/stores/map'
import {
getNameByValue,
importType,
requiredType,
deliveryMode,
deliveryTime,
shippingTemplate,
afterSalesPolicy,
} from '@/utils/dictionary'
import ScoreCard from './ScoreCard.vue'
import ScoreCardLive from './ScoreCardLive.vue'
import Preview from '@/components/Preview.vue'
const statusList = useMapStore().getMapValuesByKey('system_status')
const props = defineProps({
detail: { type: Object, default: () => ({}) },
})
const scoreDetails = defineModel('scoreDetails', {
required: true,
default: () => ({}),
})
// 定义需要的所有 key 及其默认值
const requiredKeys = {
base: { score: 5, comment: '案例背景导入' },
commodity_type: { score: null, comment: '' },
commodity_attr: { score: null, comment: '' },
commodity: { score: null, comment: '' },
speech: { score: null, comment: '' },
practice_record1: { score: null, comment: '' },
improvement_plan: { score: null, comment: '' },
practice_record2: { score: null, comment: '' },
report: { score: null, comment: '' },
}
// 确保所有必需的 key 都存在,如果不存在则初始化
watchEffect(() => {
Object.keys(requiredKeys).forEach((key) => {
if (!scoreDetails.value[key]) {
scoreDetails.value[key] = { ...requiredKeys[key] }
} else if (!scoreDetails.value[key].hasOwnProperty('score') || !scoreDetails.value[key].hasOwnProperty('comment')) {
// 如果存在但结构不完整(缺少 score 或 comment),则合并默认值
scoreDetails.value[key] = { ...requiredKeys[key], ...scoreDetails.value[key] }
}
})
})
defineEmits(['save'])
const firstLivePractice = computed(() => {
return props.detail.live_data.practices[0]
})
const firstLivePracticeRecord = computed(() => {
return props.detail.live_data.practice_records.find((item) => item.live_practice_id === firstLivePractice.value?.id)
})
const secondLivePractice = computed(() => {
return props.detail.live_data.practices[1]
})
const secondLivePracticeRecord = computed(() => {
return props.detail.live_data.practice_records.find((item) => item.live_practice_id === secondLivePractice.value?.id)
})
// 商品类别
const categoryTableOptions = {
columns: [
{ label: '商品品类名称', prop: 'name', align: 'left' },
{ label: '层级', prop: 'level' },
{
label: '状态',
prop: 'status',
computed({ row }) {
const color = row.status === '1' ? 'var(--main-success-color)' : 'var(--main-color)'
return `<span style="color: ${color}">${getNameByValue(row.status, statusList)}</span>`
},
},
],
}
// 商品属性
const attrsTableOptions = {
columns: [
{ label: '序号', type: 'index', width: 60 },
{ label: '商品属性名称', prop: 'name' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{
label: '重要性',
prop: 'is_importance',
computed({ row }) {
return getNameByValue(row.is_importance, importType)
},
},
{
label: '必要性',
prop: 'is_essential',
computed({ row }) {
return getNameByValue(row.is_essential, requiredType)
},
},
{
label: '状态',
prop: 'status',
computed({ row }) {
const color = row.status === '1' ? 'var(--main-success-color)' : 'var(--main-color)'
return `<span style="color: ${color}">${getNameByValue(row.status, statusList)}</span>`
},
},
{ label: '更新时间', prop: 'updated_time' },
],
}
// 商品管理
const productList = computed(() => {
return props.detail.live_data?.commodities?.map((item) => {
try {
item.info = JSON.parse(item.info)
item.info.delivery_mode_name = getNameByValue(item.info.delivery_mode, deliveryMode)
item.info.delivery_time_name = getNameByValue(item.info.delivery_time, deliveryTime)
item.info.shipping_template_name = getNameByValue(item.info.shipping_template, shippingTemplate)
item.info.after_sales_policy_name = getNameByValue(item.info.after_sales_policy, afterSalesPolicy)
item.live_commodity_attrs = JSON.parse(item.live_commodity_attrs)
item.picture_34_addreses = JSON.parse(item.picture_34_addreses)
item.picture_addreses = JSON.parse(item.picture_addreses)
} catch (error) {
console.log(error)
}
console.log(item)
return item
})
})
const productTableOptions = {
columns: [
{
type: 'expand',
slots: 'table-expand',
},
{ label: '序号', type: 'index', width: 60 },
{ label: '商品标题', prop: 'title' },
{ label: '所属商品品类', prop: 'live_commodity_type_full_name' },
{ label: '参考价(¥)', prop: 'info.reference_price' },
{ label: '发货模式', prop: 'info.delivery_mode_name' },
{ label: '发货时效', prop: 'info.delivery_time_name' },
{ label: '运费模板', prop: 'info.shipping_template_name' },
{ label: '售后政策', prop: 'info.after_sales_policy_name' },
],
}
// 商品规格
const productSpecsTableOptions = {
columns: [
{ label: '规格名称', prop: 'name' },
{
label: '规格值',
prop: 'value',
computed({ row }) {
return row.values.join(' / ')
},
},
],
}
// 商品sku
const productSkuTableOptions = {
columns: [
{
label: '规格',
prop: 'name',
computed({ row }) {
return row.specs.join(' / ')
},
},
{ label: '价格(¥)', prop: 'price' },
{ label: '库存', prop: 'stock' },
],
}
// 直播话术
const talkTableOptions = {
columns: [
{ type: 'expand', slots: 'table-expand' },
{ label: '序号', type: 'index', width: 60 },
{ label: '直播话术名称', prop: 'name' },
{ label: '直播主题标题', prop: 'live_commodity_title' },
{
label: '话术时长',
prop: 'duration',
computed({ row }) {
return `${row.duration}分钟`
},
},
{ label: '所属直播主题品类', prop: 'live_commodity_type_full_name' },
],
}
const activeTab = ref(1)
const handleNext = () => {
activeTab.value++
}
</script>
<template>
<el-tabs stretch v-model="activeTab" class="score-tabs">
<el-tab-pane label="商品品类管理" :name="1">
<ScoreCard
:maxScore="3"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.commodity_type.score"
v-model:comment="scoreDetails.commodity_type.comment"
@save="$emit('save')"
@next="handleNext">
<AppList
v-bind="categoryTableOptions"
row-key="id"
:data="detail.live_data.commodity_types"
default-expand-all />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="商品属性管理" :name="2" lazy>
<ScoreCard
:maxScore="2"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.commodity_attr.score"
v-model:comment="scoreDetails.commodity_attr.comment"
@save="$emit('save')"
@next="handleNext">
<AppList v-bind="attrsTableOptions" :data="detail.live_data.commodity_attrs" />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="商品管理" :name="3" lazy>
<ScoreCard
:maxScore="10"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.commodity.score"
v-model:comment="scoreDetails.commodity.comment"
@save="$emit('save')"
@next="handleNext">
<AppList v-bind="productTableOptions" :data="productList" default-expand-all>
<template #table-expand="{ row }">
<div class="table-expand-box">
<el-form label-position="top">
<el-row>
<el-col :span="12">
<el-form-item label="主图">
<el-image
:src="item.url"
v-for="item in row.picture_addreses"
:key="item.url"
fit="cover"
preview-teleported
:preview-src-list="row.picture_addreses.map((item) => item.url)"
style="width: 100px; height: 100px; margin-right: 10px"></el-image>
</el-form-item>
<el-form-item label="主图3:4">
<el-image
:src="item.url"
v-for="item in row.picture_34_addreses"
:key="item.url"
fit="cover"
preview-teleported
:preview-src-list="row.picture_addreses.map((item) => item.url)"
style="width: 100px; height: 134px; margin-right: 10px"></el-image>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主图视频">
<video
:src="row.video_url"
controls
style="width: 100%; height: 282px"
v-if="row.video_url"></video>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="商品规格">
<AppList v-bind="productSpecsTableOptions" :data="row.info?.specs" style="width: 100%"></AppList>
</el-form-item>
<el-form-item label="价格与库存">
<AppList v-bind="productSkuTableOptions" :data="row.info?.sku" style="width: 100%"></AppList>
</el-form-item>
</el-form>
</div>
</template>
</AppList>
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="直播话术管理" :name="4" lazy>
<ScoreCard
:maxScore="15"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.speech.score"
v-model:comment="scoreDetails.speech.comment"
@save="$emit('save')"
@next="handleNext">
<AppList v-bind="talkTableOptions" :data="detail.live_data.speeches" default-expand-all>
<template #table-expand="{ row }">
<div class="table-expand-box">
<el-form label-position="top">
<el-form-item label="直播主题卖点">
<div class="form-box" v-html="row.selling_point"></div>
</el-form-item>
<el-form-item label="营销活动">
<div class="form-box" v-html="row.marketing_campaign"></div>
</el-form-item>
<el-form-item label="直播话术内容">
<div class="form-box" v-html="row.content"></div>
</el-form-item>
</el-form>
</div>
</template>
</AppList>
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="首次直播演练" :name="5" lazy>
<ScoreCard
:maxScore="15"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.practice_record1.score"
v-model:comment="scoreDetails.practice_record1.comment"
@save="$emit('save')"
@next="handleNext">
<ScoreCardLive
:livePracticeId="firstLivePractice.id"
:recordId="firstLivePracticeRecord.id"
v-if="firstLivePracticeRecord" />
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="直播复盘分析" :name="6" lazy>
<ScoreCard
:maxScore="10"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.improvement_plan.score"
v-model:comment="scoreDetails.improvement_plan.comment"
@save="$emit('save')"
@next="handleNext">
<el-form label-position="top" v-if="firstLivePracticeRecord?.improvement_plan">
<el-form-item label="改进方案">
<div class="form-box" v-html="firstLivePracticeRecord?.improvement_plan"></div>
</el-form-item>
</el-form>
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="二次直播演练" :name="7" lazy>
<ScoreCard
:maxScore="30"
:hasSaveButton="detail.status != '2'"
v-model:score="scoreDetails.practice_record2.score"
v-model:comment="scoreDetails.practice_record2.comment"
@save="$emit('save')"
@next="handleNext">
<ScoreCardLive
:livePracticeId="secondLivePractice.id"
:recordId="secondLivePracticeRecord.id"
v-if="secondLivePracticeRecord" />
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
<el-tab-pane label="直播总结汇报" :name="8" lazy>
<ScoreCard
:maxScore="10"
:hasSaveButton="detail.status != '2'"
:hasNextButton="false"
v-model:score="scoreDetails.report.score"
v-model:comment="scoreDetails.report.comment"
@save="$emit('save')"
@next="handleNext">
<template v-if="detail.live_data.reports.length > 0">
<div v-for="item in detail.live_data.reports" :key="item.report_url" style="height: 600px">
<Preview :url="item.report_url" />
</div>
</template>
<el-empty v-else />
</ScoreCard>
</el-tab-pane>
</el-tabs>
</template>
<style lang="scss" scoped>
.score-tabs {
:deep(.el-tabs__header) {
width: 1000px;
margin: 0 auto 20px;
}
:deep(.el-tabs__nav-wrap::after) {
display: none;
}
}
.score-header {
display: flex;
align-items: center;
.el-form {
flex: 1;
}
}
.score-box {
width: 100px;
border: 1px solid #dcdfe6;
padding: 10px;
border-radius: 10px;
font-size: 30px;
color: var(--main-color);
display: flex;
align-items: center;
justify-content: center;
}
.table-expand-box {
border: 1px solid #dcdfe6;
padding: 20px;
border-radius: 10px;
}
.form-box {
width: 100%;
border: 1px solid #dcdfe6;
padding: 10px;
border-radius: 6px;
}
</style>
...@@ -499,16 +499,24 @@ ${JSON.stringify(data.speeches)} ...@@ -499,16 +499,24 @@ ${JSON.stringify(data.speeches)}
} }
export const generatePracticeRecordPrompt = (data: any) => { export const generatePracticeRecordPrompt = (data: any) => {
const firstLivePractice = data.practices[0]
const firstLivePracticeRecord = data.practice_records.find(
(item: any) => item.live_practice_id === firstLivePractice?.id
)
return `请根据选手提交的内容进行评分(满分20分) return `请根据选手提交的内容进行评分(满分20分)
选手提交内容: 选手提交内容:
${JSON.stringify(data.practice_records[0])} ${JSON.stringify(firstLivePracticeRecord)}
` `
} }
export const generateImprovementPlanPrompt = (data: any) => { export const generateImprovementPlanPrompt = (data: any) => {
const firstLivePractice = data.practices[0]
const firstLivePracticeRecord = data.practice_records.find(
(item: any) => item.live_practice_id === firstLivePractice?.id
)
return `请根据选手提交的改进方案进行评分(满分10分): return `请根据选手提交的改进方案进行评分(满分10分):
选手提交的改进方案: 选手提交的改进方案:
${JSON.stringify(data.practice_records[0]?.improvement_plan)} ${JSON.stringify(firstLivePracticeRecord?.improvement_plan)}
评分标准: 评分标准:
- 必须提出至少5条改进措施 - 必须提出至少5条改进措施
...@@ -518,10 +526,14 @@ export const generateImprovementPlanPrompt = (data: any) => { ...@@ -518,10 +526,14 @@ export const generateImprovementPlanPrompt = (data: any) => {
} }
export const generatePracticeRecord2Prompt = (data: any) => { export const generatePracticeRecord2Prompt = (data: any) => {
const secondLivePractice = data.practices[1]
const secondLivePracticeRecord = data.practice_records.find(
(item: any) => item.live_practice_id === secondLivePractice?.id
)
return ` return `
请根据选手提交的二次直播演练内容进行评分(满分15分) 请根据选手提交的二次直播演练内容进行评分(满分15分)
选手提交内容: 选手提交内容:
${JSON.stringify(data.practice_records[1])} ${JSON.stringify(secondLivePracticeRecord)}
` `
} }
......
export const generateCommodityTypePrompt = (data: any) => {
return `请根据选手提交的商品品类信息进行评分(满分3分):
选手提交内容:
${JSON.stringify(data.commodity_types)}
评分规则
一级品类创建(2分)
判定标准:
1.必须创建三个一级品类:速食面品类、鲜切面品类、调料配菜品类
2.状态必须为"有效"
计分规则:
●全部满足:2分
●缺少1个一级品类:扣1分
●状态非"有效":扣0.5分
●扣分可累计,最低0分
二级品类创建(1分)
判定标准:
速食面品类下:
●至少2个二级品类
●必须包含:“复合式速煮面”、“高端速食拌面/干面”
●状态为"有效"
鲜切面品类下:
●至少2个二级品类
●必须包含:“生鲜手擀/拉面”、“地方特色鲜面”
●状态为"有效"
调料配菜品类下:
●至少2个二级品类
●必须包含:“复合调味料包”、“即食真材配菜”
●状态为"有效"
计分规则:
●每个一级品类下必选项齐全且状态正确:+0.33分(共1分)
●必选项缺失:每项-0.1分
●状态非"有效":每处-0.05分
●明显不合理命名:每处-0.05分
●扣分可累计,最低0分
【整体评价】
(请对选手整体表现进行1-2句总结性评价)`
}
export const generateCommodityAttrsPrompt = (data: any) => {
return `
请根据选手提交的商品属性配置进行评分(满分2分):
选手提交内容:
${JSON.stringify(data.commodity_attrs)}
评分规则
速食面品类属性(1分)
必须包含且名称合理的5项属性:
1.产品规格
2.口味类型
3.辣度等级
4.保质期
5.烹饪时长
计分规则:
●每项0.2分
●存在且名称合理即得分,不存在或名称不合理即0分
●不设部分分
●同义词、近似表达不计分(如"容量"不等于"产品规格")
鲜切面品类属性(0.5分)
必须包含且名称合理的5项属性:
1.产品规格
2.面条类型
3.保存方式
4.保质期
5.适合人数
计分规则:
●每项0.1分
●存在且名称合理即得分,不存在或名称不合理即0分
●不设部分分
●同义词、近似表达不计分
调料配菜品类属性(0.5分)
必须包含且名称合理的5项属性:
1.产品规格
2.配料成分
3.辣度等级
4.保质期
5.适用场景
计分规则:
●每项0.1分
●存在且名称合理即得分,不存在或名称不合理即0分
●不设部分分
●同义词、近似表达不计分
判定说明
1.精确匹配原则:本模块计分以"属性名是否存在且名称合理"作为依据
2.同义词处理:同义词、近似表达不计分(如"容量"不等于"产品规格")
3.其他字段:重要性、必要性、状态设置是否合理不影响本模块计分,但可在评价中提及`
}
export const generateCommoditiesPrompt = (data: any) => {
return `
请根据选手提交的商品信息维护内容进行评分(满分10分)
选手提交内容:
${JSON.stringify(data.commodities)}
评分规则
1. 商品类目关联(1分)
判定标准:
●一级品类判定:
○"襄阳牛肉面2桶装(有牛肉)"应关联到"速食面品类"一级品类
●二级品类判定:
○应关联到"速食面品类"下的“复合式速煮面”二级品类
计分规则:
●同时正确关联一级品类和二级品类:1分
●只关联一级品类或只关联二级品类:0.5分
●错误或缺失:0分
2. 商品主图(2分)
判定标准:
●每个商品至少2张1:1比例主图
●仅以张数为准,不要求质量
计分规则:
●满足2张及以上1:1主图:2分
●只有1张:-1分(得1分)
●不足1张:-2分(得0分)
3. 3:4比例主图(1分)
判定标准:
●每个商品至少1张3:4比例主图
●仅以张数为准,不要求质量
计分规则:
●满足要求:1分
●否则:0分
4. 价格库存配置(4分)
判定标准与计分:
商品的规格、价格、库存、发货设置等信息配置完整准确。
●包含:价格、参考价、库存数量;每缺一要素-1分
●发货模式:必须为"现货发货";不符合-1分
●扣分累计,但本项最低0分
5. 服务履约配置(2分)
判定标准与计分:
●运费模板:从以下选项中任选一项即可得分:阶梯计价运费、固定运费、卖家包邮、指定条件包邮、限制买家下单区域、按件数计价、按重量计价、指定地区运费;缺失-1分。
●售后政策:从以下选项中任选一项即可得分:7天无理由退换货、全额退款、仅退款政策、预付邮费标签、全球购物保障计划、无忧退换货、闪电退货、售后补寄;缺失-1分。
●扣分累计,但本项最低0分
【整体评价】
(请对选手整体表现进行1-2句总结性评价)`
}
export const generateSpeechPrompt = (data: any) => {
return `
请根据选手提交的直播话术进行评分(满分15分)
选手提交内容内容:
${JSON.stringify(data.speeches)}
评分规则
1. 直播话术名称(3分)
判定标准:
●5-20字
●主题明确,体现直播主题
计分规则:
●符合要求:3分
●不符合:-1.5分(得1.5分)
2. 直播主题卖点(3分)
判定标准:
●每个商品≥3个核心卖点
●具体有说服力
●针对目标用户痛点
计分规则:
●每缺1个卖点:-1分
●描述空泛:每处-0.5分(上限-1分)
●扣分累计,最低0分
3. 营销活动设计(3分)
判定标准:
●至少3个活动
●活动形式多样化
●优惠力度合理
●必须包含:限时秒杀活动、组合优惠活动、互动抽奖活动
计分规则:
●不足3个活动:-1分
●缺1类必选活动:-1分
●规则不清:-0.5分
●扣分累计,最低0分
4. 直播话术脚本撰写(4分)
必须包含以下环节,缺环节-0.5分/项:
1.开场白环节:热情问候、主题引入、福利预告、引导关注
2.品牌介绍环节:品牌故事、品牌实力、产品线介绍
3.主推产品讲解环节:产品展示、成分讲解、卖点阐述、使用场景、案例分享、价格优惠、促单话术
4.互动环节:回应评论、互动问答、抽奖活动
5.促销推动环节:强调组合优惠、满额包邮、催单话术
6.结束语环节:总结要点、感谢支持、预告下播、最后提醒
话术质量要求:
●语言自然流畅,口语化表达
●逻辑清晰,节奏合理
●卖点讲解充分,有说服力
●互动环节设计合理,能调动氛围
●促销话术到位,但不过度推销
●无违禁词
常见违禁词快速对照表
类别1:绝对化用语(共20个)
最、最佳、最好、最优、最大、最小、最新、最先进
第一、首个、首选、唯一、独一无二、前无古人
极致、完美、顶级、至尊、终极、巅峰
类别2:夸大宣传(共15个)
100%、百分百、保证、必然、绝对、肯定
立竿见影、立即见效、马上见效、即刻
永久、终身、永远、一劳永逸、彻底
类别3:医疗用语(共20个)
治疗、治愈、医治、疗效、药用、处方
抗炎、消炎、杀菌、灭菌、防癌、抗癌
降血压、降血糖、降血脂、治痤疮、祛痘、去斑
修复、再生(需谨慎使用)
类别4:虚假宣传(共10个)
国家级、世界级、全球级、宇宙级
最高级、最低价、史无前例、前所未有
销量第一、市场占有率第一
计分规则:
●缺环节:-0.5分/项
●明显质量问题:每处-0.5分
●扣分累计,最低0分
5. 时长设置(2分)
判定标准:
●设置为15分钟
●时间分配合理(重点突出、互动穿插)
计分规则:
●符合要求:2分
●未设置或明显不合理:0分
【整体评价】
(请对选手话术整体质量进行1-2句总结性评价)`
}
export const generatePracticeRecordPrompt = (data: any) => {
const firstLivePractice = data.practices[0]
const firstLivePracticeRecord = data.practice_records.find(
(item: any) => item.live_practice_id === firstLivePractice?.id
)
return `请根据选手提交的内容进行评分(满分15分)
选手提交内容:
${JSON.stringify(firstLivePracticeRecord)}
评分规则
1. 开场与氛围营造(2分)
判定标准:
●开场白吸引人
●快速进入状态
●营造良好直播氛围
计分规则:
●符合要求:2分
●表现一般:-1分(得1分)
●表现不足:-2分(得0分)
2. 商品讲解能力(3分)
判定标准:
●商品介绍清晰准确
●重点突出
●讲解有说服力
计分规则:
●符合要求:3分
●表现一般:-1.5分(得1.5分)
●表现不足:-3分(得0分)
3. 互动回应能力(2分)
判定标准:
●及时回应观众评论和问题
●互动自然流畅
计分规则:
●符合要求:2分
●表现一般:-1分(得1分)
●表现不足:-2分(得0分)
4. 促销引导能力(2分)
判定标准:
●有效引导观众关注、点赞、下单
●转化技巧运用得当
计分规则:
●符合要求:2分
●表现一般:-1分(得1分)
●表现不足:-2分(得0分)
5. 语言表达与形象(4分)
判定标准:
●普通话标准
●语速适中(理想区间:180-220字/分钟)
●表情自然
●形象得体
计分规则:
●全部符合:4分
●语速偏离:-1分
●普通话不标准:-1分
●表情不自然:-1分
●形象不得体:-1分
●扣分累计,最低0分
语速标准参考
理想语速:180-220 字/分钟
- 观众能够轻松跟上
- 有足够时间理解信息
- 节奏舒适,不紧不慢
偏快语速:221-240 字/分钟
- 部分观众可能跟不上
- 影响信息吸收效果
- 建议适当放慢
过快语速:>240 字/分钟
- 大部分观众难以跟上
- 严重影响直播效果
- 必须改进
偏慢语速:150-179 字/分钟
- 节奏稍显拖沓
- 可能影响直播间活跃度
- 建议适当加快
过慢语速:<150 字/分钟
- 节奏过于拖沓
- 观众容易失去耐心
- 必须改进
6. 内容合规性(2分)
判定标准:
●严格遵守《网络主播行为规范》
●无违规内容
●无违禁词
计分规则:
●完全合规:2分
●有轻微违规:-1分(得1分)
●有明显违规:-2分(得0分)
《网络主播行为规范》31项禁止行为(重点摘要):
(一)政治与意识形态类
1.恶搞、歪曲、丑化党和国家形象;
2.散布谣言、煽动颠覆国家政权、分裂国家领土;
3.宣传封建迷信、邪教、恐怖主义等内容。
(二)低俗庸俗类
1.展示淫秽、色情、暴力、血腥画面;
2.着装暴露、动作挑逗、打“擦边球”;
3.使用粗俗语言、侮辱性词汇、地域歧视等。
(三)虚假宣传与消费误导类
1.虚假宣传商品功效、产地、成分、质量;
2.使用“国家级”“最优惠”“第一品牌”等绝对化用语;
3.伪造销量、用户评价、直播间人气;
4.谎称“最后X件”“仅限今天”制造抢购氛围;
5.诱导未成年人非理性消费或打赏;
6.未标明广告属性,以“自用推荐”名义进行商业推广。
(四)知识产权与版权类
1.未经授权播放音乐、影视、游戏画面;
2.盗用他人直播内容、脚本、创意;
3.使用他人肖像、声音、商标未经许可。
(五)食品安全与健康风险类
1.无资质宣传医疗、药品、保健食品功效;
2.推荐“三无产品”、过期食品、违禁品;
3.试吃来源不明、无检验合格证明的食品;
4.宣称普通食品具有“治疗疾病”“增强免疫力”等医疗效果。
(六)其他违规行为
1.酒后直播、醉酒状态出镜;
2.直播赌博、诈骗、传销等活动;
3.泄露他人隐私、个人信息;
4.冒充专家、医生、律师等专业人士;
5.恶意炒作、拉踩同行、制造对立;
6.在公共场所直播影响公共秩序;
7.利用直播进行非法集资、金融诈骗;
8.传播伪科学、反智言论;
9.侵犯消费者合法权益(如不退不换、虚假售后);
10.不配合平台或监管部门调查;
11.未取得相关资质从事特定领域直播(如医疗、教育、金融);
12.其他违反法律法规及公序良俗的行为。
特别针对“电商/带货主播”的强化要求
1.商品审核义务
○主播应对所售商品基本信息(成分、产地、保质期、适用人群)进行核实;
○不得为“三无产品”、假冒伪劣、违禁品带货。
1.广告标识义务
○凡涉及商业推广的内容,应明确标注“广告”“推广”字样;
○不得以“自用推荐”“朋友家产品”等模糊方式规避广告责任。
1.售后与责任承担
○若主播以自身名义推荐商品(如“XX严选”“主播同款”),可能被认定为广告代言人,需对产品质量承担连带责任;
○应配合平台处理消费者投诉,不得推诿、搪塞。
1.数据真实性
○禁止刷单、刷评、虚构交易额、观看量;
○平台将对异常数据进行监测并处罚。`
}
export const generateImprovementPlanPrompt = (data: any) => {
const firstLivePractice = data.practices[0]
const firstLivePracticeRecord = data.practice_records.find(
(item: any) => item.live_practice_id === firstLivePractice?.id
)
return `请根据选手提交的改进方案进行评分(满分10分):
选手提交的改进方案:
${JSON.stringify(firstLivePracticeRecord?.improvement_plan)}
评分规则
1. 改进方案数量(10分)
判定标准:
1.数量要求:至少5条具体的改进措施
2.内容要求:每条改进措施必须包括:
○问题描述
○改进目标
○改进方法
1.维度要求:改进方案需涵盖语速、违禁词、卖点讲解、互动、促销等多个维度
计分规则:
●不足5条:每缺1-2
●每条改进措施缺少三要素之一:每处-0.5
●未覆盖多维度:-1
●扣分累计,但本项最低0`
}
export const generatePracticeRecord2Prompt = (data: any) => {
const secondLivePractice = data.practices[1]
const secondLivePracticeRecord = data.practice_records.find(
(item: any) => item.live_practice_id === secondLivePractice?.id
)
return `
请根据选手提交的二次直播演练内容进行评分(满分30分)
选手提交内容:
${JSON.stringify(secondLivePracticeRecord)}
评分说明
以改进方案列出的措施和首次直播AI评价报告中的问题为基准,逐条对照二次直播字幕/话术确认改进落实与否。
评分规则
1. 话术与表现优化(5分)
判定标准:
●话术修改到位
●删除所有违禁词
●补充遗漏卖点
计分规则:
●完全符合:5
●部分符合:-2.5分(得2.5分)
●不符合:-5分(得0分)
2. 直播完成度(2分)
判定标准:
●完成完整的二次直播流程
●时长符合要求(15分钟,±5%
计分规则:
●符合要求:2
●不达标:-2分(得0分)
3. 语速控制改进(6分)
判定标准:
●平均语速控制在180-220/分钟
●相比首次直播有明显改善
计分规则:
●达标且较首次优化显著:6
●仅达标无改善:-2分(得4分)
●未达标:-3分(得3分)
语速标准参考
理想语速:180-220 /分钟
- 观众能够轻松跟上
- 有足够时间理解信息
- 节奏舒适,不紧不慢
偏快语速:221-240 /分钟
- 部分观众可能跟不上
- 影响信息吸收效果
- 建议适当放慢
过快语速:>240 /分钟
- 大部分观众难以跟上
- 严重影响直播效果
- 必须改进
偏慢语速:150-179 /分钟
- 节奏稍显拖沓
- 可能影响直播间活跃度
- 建议适当加快
过慢语速:<150 /分钟
- 节奏过于拖沓
- 观众容易失去耐心
- 必须改进
4. 违禁词改进(5分)
判定标准:
●二次直播零违禁词
●所有表达符合平台规范
计分规则:
●零违禁词:5
●若仍出现违禁词:直接0分,并记录违禁词
常见违禁词快速对照表
类别1:绝对化用语(共20个)
最、最佳、最好、最优、最大、最小、最新、最先进
第一、首个、首选、唯一、独一无二、前无古人
极致、完美、顶级、至尊、终极、巅峰
类别2:夸大宣传(共15个)
100%、百分百、保证、必然、绝对、肯定
立竿见影、立即见效、马上见效、即刻
永久、终身、永远、一劳永逸、彻底
类别3:医疗用语(共20个)
治疗、治愈、医治、疗效、药用、处方
抗炎、消炎、杀菌、灭菌、防癌、抗癌
降血压、降血糖、降血脂、治痤疮、祛痘、去斑
修复、再生(需谨慎使用)
类别4:虚假宣传(共10个)
国家级、世界级、全球级、宇宙级
最高级、最低价、史无前例、前所未有
销量第一、市场占有率第一
5. 卖点覆盖改进(3分)
判定标准:
●所有商品卖点覆盖率达到100%
●相比首次直播有提升
计分规则:
●覆盖率达到100%且较首次提升:3
●缺失:-1.5分(得1.5分)
●无提升:-1分(得2分)
6. 互动技巧改进(3分)
判定标准:
●互动频次提升
●互动方式多样
●相比首次直播有明显提升
计分规则:
●符合要求且较首次提升:3
●无提升:-1.5分(得1.5分)
●不足:-2分(得1分)
7. 促销话术改进(3分)
判定标准:
●促销话术更加自然真诚
●紧迫感营造到位
●相比首次直播有改进
计分规则:
●符合要求且较首次提升:3
●无提升:-1.5分(得1.5分)
●明显不足:-2分(得1分)
8. 整体表现提升(3分)
判定标准:
●语言表达、状态自信度、节奏把控等整体表现有显著提升
●结合改进方案落实情况说明证据
计分规则:
●符合要求且有证据支撑:3
●证据不足:-1.5分(得1.5分)
●无明显提升:-2分(得1分)
`
}
export const generateReportPrompt = (data: any) => {
return `请根据选手提交的直播总结报告进行评分(满分10分)
选手提交内容:
${JSON.stringify(data.reports)}
`
}
export const generatePrompt = (data: any) => {
return `
${generateCommodityTypePrompt(data)}
${generateCommodityAttrsPrompt(data)}
${generateCommoditiesPrompt(data)}
${generateSpeechPrompt(data)}
${generatePracticeRecordPrompt(data)}
${generateImprovementPlanPrompt(data)}
${generateReportPrompt(data)}
EXAMPLE JSON OUTPUT:
{
"commodity_type": {
"score": 5,
"comment": "这是评语"
},
"commodity_attr": {
"score": 5,
"comment": "这是评语"
},
"commodity": {
"score": 5,
"comment": "这是评语"
},
"speech": {
"score": 5,
"comment": "这是评语"
},
"practice_record1": {
"score": 5,
"comment": "这是评语"
},
"improvement_plan": {
"score": 5,
"comment": "这是评语"
},
"practice_record2": {
"score": 5,
"comment": "这是评语"
},
"report": {
"score": 5,
"comment": "这是评语"
},
}
`
}
...@@ -8,7 +8,7 @@ const appList = ref(null) ...@@ -8,7 +8,7 @@ const appList = ref(null)
const handleRefresh = () => { const handleRefresh = () => {
appList.value?.refetch() appList.value?.refetch()
} }
const listParams = reactive({ name: '', status: '' }) const listParams = reactive({ name: '', check_status: '' })
// 列表配置 // 列表配置
const listOptions = computed(() => { const listOptions = computed(() => {
...@@ -18,11 +18,12 @@ const listOptions = computed(() => { ...@@ -18,11 +18,12 @@ const listOptions = computed(() => {
{ label: '姓名', prop: 'name', type: 'input' }, { label: '姓名', prop: 'name', type: 'input' },
{ {
label: '状态', label: '状态',
prop: 'status', prop: 'check_status',
type: 'select', type: 'select',
options: [ options: [
{ label: '未批改', value: '1' }, { label: '暂未评分', value: '0' },
{ label: '已批改', value: '2' }, { label: '评分中', value: '1' },
{ label: '已评分', value: '2' },
], ],
}, },
], ],
...@@ -33,10 +34,11 @@ const listOptions = computed(() => { ...@@ -33,10 +34,11 @@ const listOptions = computed(() => {
{ label: '专业', prop: 'specialty_name' }, { label: '专业', prop: 'specialty_name' },
{ label: '班级', prop: 'class_name' }, { label: '班级', prop: 'class_name' },
{ label: '成绩', prop: 'score_display' }, { label: '成绩', prop: 'score_display' },
{ label: '状态', prop: 'status_name' }, { label: '状态', prop: 'check_status_name' },
{ label: '评分人', prop: 'checker_name' },
{ label: '提交时间', prop: 'commit_time' }, { label: '提交时间', prop: 'commit_time' },
{ label: '批改时间', prop: 'commit_time' }, { label: '批改时间', prop: 'commit_time' },
{ label: '操作', slots: 'table-x', width: 200 }, { label: '操作', slots: 'table-x', width: 120 },
], ],
} }
}) })
...@@ -65,9 +67,7 @@ const handleExport = () => { ...@@ -65,9 +67,7 @@ const handleExport = () => {
</template> </template>
<template #table-x="{ row }"> <template #table-x="{ row }">
<el-button text type="primary"> <el-button text type="primary">
<router-link :to="{ path: '/live/score/view', query: { ...$route.query, id: row.id } }" target="_blank" <router-link :to="{ path: '/live/score/view', query: { ...$route.query, id: row.id } }">评分</router-link>
>评分</router-link
>
</el-button> </el-button>
</template> </template>
</AppList> </AppList>
......
...@@ -3,8 +3,10 @@ import { getScoreDetail, updateScore } from '../api' ...@@ -3,8 +3,10 @@ import { getScoreDetail, updateScore } from '../api'
import { ElMessageBox, ElMessage } from 'element-plus' import { ElMessageBox, ElMessage } from 'element-plus'
import { useChat } from '@ezijing/ai-vue' import { useChat } from '@ezijing/ai-vue'
import { generatePrompt } from '../prompt' import { generatePrompt } from '../prompt'
import { generatePrompt as generatePrompt2 } from '../prompt2'
const CompetitionLive = defineAsyncComponent(() => import('../components/CompetitionLive.vue')) const CompetitionLive = defineAsyncComponent(() => import('../components/CompetitionLive.vue'))
const CompetitionLiveProd = defineAsyncComponent(() => import('../components/CompetitionLiveProd.vue'))
const CompetitionOperations = defineAsyncComponent(() => import('../components/CompetitionOperations.vue')) const CompetitionOperations = defineAsyncComponent(() => import('../components/CompetitionOperations.vue'))
const route = useRoute() const route = useRoute()
const id = route.query.id const id = route.query.id
...@@ -31,14 +33,15 @@ onMounted(() => { ...@@ -31,14 +33,15 @@ onMounted(() => {
const handleAIScore = async () => { const handleAIScore = async () => {
console.log('AI一键评分') console.log('AI一键评分')
const result = await generateText({ const prompt =
prompt: generatePrompt(detail.value.live_data), detail.value.competition_rule?.competition == 1
response_format: { type: 'json_object' }, ? generatePrompt(detail.value.live_data)
}) : generatePrompt2(detail.value.live_data)
const result = await generateText({ prompt, response_format: { type: 'json_object' } })
try { try {
const parsed = JSON.parse(result.content) const parsed = JSON.parse(result.content)
Object.assign(scoreDetails, parsed) Object.assign(scoreDetails, parsed)
commitScore(0).then(() => { commitScore().then(() => {
ElMessage.success('保存成功') ElMessage.success('保存成功')
}) })
} catch (error) { } catch (error) {
...@@ -49,15 +52,15 @@ const handleAIScore = async () => { ...@@ -49,15 +52,15 @@ const handleAIScore = async () => {
} }
const handleSave = () => { const handleSave = () => {
commitScore(0).then(() => { commitScore().then(() => {
ElMessage.success('保存成功') ElMessage.success('保存成功')
}) })
} }
const commitScore = (status = 0) => { const commitScore = (status = 1) => {
return updateScore({ return updateScore({
id: route.query.id, id: route.query.id,
score_status: status, check_status: status,
total_score: totalScore.value, total_score: totalScore.value,
score_details: JSON.stringify(scoreDetails), score_details: JSON.stringify(scoreDetails),
}) })
...@@ -69,7 +72,7 @@ const handlePublishScore = () => { ...@@ -69,7 +72,7 @@ const handlePublishScore = () => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}).then(async () => { }).then(async () => {
await commitScore(1) await commitScore(2)
await fetchDetail() await fetchDetail()
ElMessage.success('发布成绩成功') ElMessage.success('发布成绩成功')
}) })
...@@ -82,18 +85,18 @@ const handlePublishScore = () => { ...@@ -82,18 +85,18 @@ const handlePublishScore = () => {
<el-form-item label="姓名">{{ detail.student_name }}</el-form-item> <el-form-item label="姓名">{{ detail.student_name }}</el-form-item>
<el-form-item label="专业">{{ detail.specialty_name }}</el-form-item> <el-form-item label="专业">{{ detail.specialty_name }}</el-form-item>
<el-form-item label="班级">{{ detail.class_name }}</el-form-item> <el-form-item label="班级">{{ detail.class_name }}</el-form-item>
<el-form-item label="状态">{{ detail.status_name }}</el-form-item> <el-form-item label="状态">{{ detail.check_status_name }}</el-form-item>
<el-form-item label="提交时间">{{ detail.commit_time }}</el-form-item> <el-form-item label="提交时间">{{ detail.commit_time }}</el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button
type="primary" type="primary"
:disabled="detail.status == '2'" :disabled="detail.check_status == '2'"
:loading="isLoading" :loading="isLoading"
@click="handleAIScore" @click="handleAIScore"
v-if="detail.competition_rule?.competition == 1" v-if="detail.competition_rule?.competition == 1"
>AI一键评分</el-button >AI一键评分</el-button
> >
<el-button type="primary" :disabled="detail.status == '2'" @click="handlePublishScore">发布成绩</el-button> <el-button type="primary" :disabled="detail.check_status == '2'" @click="handlePublishScore">发布成绩</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="score-box"> <div class="score-box">
...@@ -101,11 +104,14 @@ const handlePublishScore = () => { ...@@ -101,11 +104,14 @@ const handlePublishScore = () => {
</div> </div>
</div> </div>
<el-divider /> <el-divider />
<template v-if="detail.competition_rule?.competition == 1">
<CompetitionLive <CompetitionLive
:detail="detail" :detail="detail"
@save="handleSave" @save="handleSave"
v-model:scoreDetails="scoreDetails" v-model:scoreDetails="scoreDetails"
v-if="detail.competition_rule?.competition == 1" /> v-if="detail.competition_rule?.questions == 1" />
<CompetitionLiveProd :detail="detail" @save="handleSave" v-model:scoreDetails="scoreDetails" v-else />
</template>
<CompetitionOperations :detail="detail" @save="handleSave" v-model:scoreDetails="scoreDetails" v-else /> <CompetitionOperations :detail="detail" @save="handleSave" v-model:scoreDetails="scoreDetails" v-else />
</AppCard> </AppCard>
</template> </template>
......
...@@ -112,7 +112,7 @@ const onStatsChange = (stats) => { ...@@ -112,7 +112,7 @@ const onStatsChange = (stats) => {
</template> </template>
</div> </div>
<div class="live-col" style="flex: 1" v-if="isView"> <div class="live-col" style="flex: 1" v-if="isView">
<el-timeline style="max-width: 600px"> <el-timeline style="max-height: 600px">
<el-timeline-item <el-timeline-item
placement="top" placement="top"
v-for="(item, index) in timelines" v-for="(item, index) in timelines"
......
...@@ -11,6 +11,7 @@ const liveUploadWay = useMapStore().getMapValuesByKey('live_upload_way') ...@@ -11,6 +11,7 @@ const liveUploadWay = useMapStore().getMapValuesByKey('live_upload_way')
const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue')) const FormDialog = defineAsyncComponent(() => import('../components/FormDialog.vue'))
const RecordDialog = defineAsyncComponent(() => import('../components/RecordDialog.vue')) const RecordDialog = defineAsyncComponent(() => import('../components/RecordDialog.vue'))
const router = useRouter()
const appList = ref(null) const appList = ref(null)
// 刷新 // 刷新
const handleRefresh = () => { const handleRefresh = () => {
...@@ -70,6 +71,14 @@ const handleRemove = async (row) => { ...@@ -70,6 +71,14 @@ const handleRemove = async (row) => {
ElMessage.success('删除成功') ElMessage.success('删除成功')
handleRefresh() handleRefresh()
} }
const handleStart = async (row) => {
await ElMessageBox.confirm(
'本次考试共提供两次直播机会——首次直播演练和第二次直播演练。您确定要现在开始直播吗?',
'提示'
)
router.push({ path: 'test/demo', query: { id: row.id } })
}
</script> </script>
<template> <template>
...@@ -82,11 +91,7 @@ const handleRemove = async (row) => { ...@@ -82,11 +91,7 @@ const handleRemove = async (row) => {
<LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory> <LiveProductCategory v-model="listParams.live_commodity_type_id" @change="handleRefresh"></LiveProductCategory>
</template> </template>
<template #table-x="{ row }"> <template #table-x="{ row }">
<el-button text type="primary"> <el-button text type="primary" @click="handleStart(row)" v-if="row.practice_count == 0">开始直播</el-button>
<router-link :to="{ path: 'test/demo', query: { id: row.id } }" v-if="row.practice_count == 0"
>开始直播</router-link
>
</el-button>
<el-button text type="primary" @click="handelView(row)">查看</el-button> <el-button text type="primary" @click="handelView(row)">查看</el-button>
<el-button text type="primary" @click="handleRemove(row)">删除</el-button> <el-button text type="primary" @click="handleRemove(row)">删除</el-button>
</template> </template>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论