Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-dml
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-dml
Commits
9ae415d8
提交
9ae415d8
authored
11月 17, 2025
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: update
上级
29c633cd
显示空白字符变更
内嵌
并排
正在显示
7 个修改的文件
包含
1102 行增加
和
31 行删除
+1102
-31
CompetitionLiveProd.vue
src/modules/live/score/components/CompetitionLiveProd.vue
+421
-0
prompt.ts
src/modules/live/score/prompt.ts
+15
-3
prompt2.ts
src/modules/live/score/prompt2.ts
+627
-0
Index.vue
src/modules/live/score/views/Index.vue
+9
-9
View.vue
src/modules/live/score/views/View.vue
+19
-13
Demo.vue
src/modules/live/test/components/Demo.vue
+1
-1
Index.vue
src/modules/live/test/views/Index.vue
+10
-5
没有找到文件。
src/modules/live/score/components/CompetitionLiveProd.vue
0 → 100644
浏览文件 @
9ae415d8
<
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
>
src/modules/live/score/prompt.ts
浏览文件 @
9ae415d8
...
...
@@ -499,16 +499,24 @@ ${JSON.stringify(data.speeches)}
}
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分)
选手提交内容:
${
JSON
.
stringify
(
data
.
practice_records
[
0
]
)}
${
JSON
.
stringify
(
firstLivePracticeRecord
)}
`
}
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
(
data
.
practice_records
[
0
]
?.
improvement_plan
)}
${
JSON
.
stringify
(
firstLivePracticeRecord
?.
improvement_plan
)}
评分标准:
-
必须提出至少
5
条改进措施
...
...
@@ -518,10 +526,14 @@ export const generateImprovementPlanPrompt = (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 `
请根据选手提交的二次直播演练内容进行评分(满分
15
分)
选手提交内容:
$
{
JSON
.
stringify
(
data
.
practice_records
[
1
]
)}
$
{
JSON
.
stringify
(
secondLivePracticeRecord
)}
`
}
...
...
src/modules/live/score/prompt2.ts
0 → 100644
浏览文件 @
9ae415d8
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"
:
"这是评语"
},
}
`
}
src/modules/live/score/views/Index.vue
浏览文件 @
9ae415d8
...
...
@@ -8,7 +8,7 @@ const appList = ref(null)
const
handleRefresh
=
()
=>
{
appList
.
value
?.
refetch
()
}
const
listParams
=
reactive
({
name
:
''
,
status
:
''
})
const
listParams
=
reactive
({
name
:
''
,
check_
status
:
''
})
// 列表配置
const
listOptions
=
computed
(()
=>
{
...
...
@@ -18,11 +18,12 @@ const listOptions = computed(() => {
{
label
:
'姓名'
,
prop
:
'name'
,
type
:
'input'
},
{
label
:
'状态'
,
prop
:
'status'
,
prop
:
'
check_
status'
,
type
:
'select'
,
options
:
[
{
label
:
'未批改'
,
value
:
'1'
},
{
label
:
'已批改'
,
value
:
'2'
},
{
label
:
'暂未评分'
,
value
:
'0'
},
{
label
:
'评分中'
,
value
:
'1'
},
{
label
:
'已评分'
,
value
:
'2'
},
],
},
],
...
...
@@ -33,10 +34,11 @@ const listOptions = computed(() => {
{
label
:
'专业'
,
prop
:
'specialty_name'
},
{
label
:
'班级'
,
prop
:
'class_name'
},
{
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
:
'操作'
,
slots
:
'table-x'
,
width
:
20
0
},
{
label
:
'操作'
,
slots
:
'table-x'
,
width
:
12
0
},
],
}
})
...
...
@@ -65,9 +67,7 @@ const handleExport = () => {
</
template
>
<
template
#
table-x=
"{ row }"
>
<el-button
text
type=
"primary"
>
<router-link
:to=
"
{ path: '/live/score/view', query: { ...$route.query, id: row.id } }" target="_blank"
>评分
</router-link
>
<router-link
:to=
"
{ path: '/live/score/view', query: { ...$route.query, id: row.id } }">评分
</router-link>
</el-button>
</
template
>
</AppList>
...
...
src/modules/live/score/views/View.vue
浏览文件 @
9ae415d8
...
...
@@ -3,8 +3,10 @@ import { getScoreDetail, updateScore } from '../api'
import
{
ElMessageBox
,
ElMessage
}
from
'element-plus'
import
{
useChat
}
from
'@ezijing/ai-vue'
import
{
generatePrompt
}
from
'../prompt'
import
{
generatePrompt
as
generatePrompt2
}
from
'../prompt2'
const
CompetitionLive
=
defineAsyncComponent
(()
=>
import
(
'../components/CompetitionLive.vue'
))
const
CompetitionLiveProd
=
defineAsyncComponent
(()
=>
import
(
'../components/CompetitionLiveProd.vue'
))
const
CompetitionOperations
=
defineAsyncComponent
(()
=>
import
(
'../components/CompetitionOperations.vue'
))
const
route
=
useRoute
()
const
id
=
route
.
query
.
id
...
...
@@ -31,14 +33,15 @@ onMounted(() => {
const
handleAIScore
=
async
()
=>
{
console
.
log
(
'AI一键评分'
)
const
result
=
await
generateText
({
prompt
:
generatePrompt
(
detail
.
value
.
live_data
),
response_format
:
{
type
:
'json_object'
},
})
const
prompt
=
detail
.
value
.
competition_rule
?.
competition
==
1
?
generatePrompt
(
detail
.
value
.
live_data
)
:
generatePrompt2
(
detail
.
value
.
live_data
)
const
result
=
await
generateText
({
prompt
,
response_format
:
{
type
:
'json_object'
}
})
try
{
const
parsed
=
JSON
.
parse
(
result
.
content
)
Object
.
assign
(
scoreDetails
,
parsed
)
commitScore
(
0
).
then
(()
=>
{
commitScore
().
then
(()
=>
{
ElMessage
.
success
(
'保存成功'
)
})
}
catch
(
error
)
{
...
...
@@ -49,15 +52,15 @@ const handleAIScore = async () => {
}
const
handleSave
=
()
=>
{
commitScore
(
0
).
then
(()
=>
{
commitScore
().
then
(()
=>
{
ElMessage
.
success
(
'保存成功'
)
})
}
const
commitScore
=
(
status
=
0
)
=>
{
const
commitScore
=
(
status
=
1
)
=>
{
return
updateScore
({
id
:
route
.
query
.
id
,
score
_status
:
status
,
check
_status
:
status
,
total_score
:
totalScore
.
value
,
score_details
:
JSON
.
stringify
(
scoreDetails
),
})
...
...
@@ -69,7 +72,7 @@ const handlePublishScore = () => {
cancelButtonText
:
'取消'
,
type
:
'warning'
,
}).
then
(
async
()
=>
{
await
commitScore
(
1
)
await
commitScore
(
2
)
await
fetchDetail
()
ElMessage
.
success
(
'发布成绩成功'
)
})
...
...
@@ -82,18 +85,18 @@ const handlePublishScore = () => {
<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
.
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>
<el-button
type=
"primary"
:disabled=
"detail.status == '2'"
:disabled=
"detail.
check_
status == '2'"
:loading=
"isLoading"
@
click=
"handleAIScore"
v-if=
"detail.competition_rule?.competition == 1"
>
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>
<div
class=
"score-box"
>
...
...
@@ -101,11 +104,14 @@ const handlePublishScore = () => {
</div>
</div>
<el-divider
/>
<template
v-if=
"detail.competition_rule?.competition == 1"
>
<CompetitionLive
:detail=
"detail"
@
save=
"handleSave"
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
/>
</AppCard>
</template>
...
...
src/modules/live/test/components/Demo.vue
浏览文件 @
9ae415d8
...
...
@@ -112,7 +112,7 @@ const onStatsChange = (stats) => {
</
template
>
</div>
<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
placement=
"top"
v-for=
"(item, index) in timelines"
...
...
src/modules/live/test/views/Index.vue
浏览文件 @
9ae415d8
...
...
@@ -11,6 +11,7 @@ const liveUploadWay = useMapStore().getMapValuesByKey('live_upload_way')
const
FormDialog
=
defineAsyncComponent
(()
=>
import
(
'../components/FormDialog.vue'
))
const
RecordDialog
=
defineAsyncComponent
(()
=>
import
(
'../components/RecordDialog.vue'
))
const
router
=
useRouter
()
const
appList
=
ref
(
null
)
// 刷新
const
handleRefresh
=
()
=>
{
...
...
@@ -70,6 +71,14 @@ const handleRemove = async (row) => {
ElMessage
.
success
(
'删除成功'
)
handleRefresh
()
}
const
handleStart
=
async
(
row
)
=>
{
await
ElMessageBox
.
confirm
(
'本次考试共提供两次直播机会——首次直播演练和第二次直播演练。您确定要现在开始直播吗?'
,
'提示'
)
router
.
push
({
path
:
'test/demo'
,
query
:
{
id
:
row
.
id
}
})
}
</
script
>
<
template
>
...
...
@@ -82,11 +91,7 @@ const handleRemove = async (row) => {
<LiveProductCategory
v-model=
"listParams.live_commodity_type_id"
@
change=
"handleRefresh"
></LiveProductCategory>
</
template
>
<
template
#
table-x=
"{ row }"
>
<el-button
text
type=
"primary"
>
<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=
"handleStart(row)"
v-if=
"row.practice_count == 0"
>
开始直播
</el-button>
<el-button
text
type=
"primary"
@
click=
"handelView(row)"
>
查看
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleRemove(row)"
>
删除
</el-button>
</
template
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论