Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-dml
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-dml
Commits
636c5107
提交
636c5107
authored
11月 11, 2024
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: update
上级
63ad166f
显示空白字符变更
内嵌
并排
正在显示
22 个修改的文件
包含
674 行增加
和
108 行删除
+674
-108
product.png
public/live/product.png
+0
-0
useCountdown.ts
src/composables/useCountdown.ts
+79
-0
Index.vue
src/modules/live/product/attr/views/Index.vue
+2
-1
FormDialog.vue
src/modules/live/product/category/components/FormDialog.vue
+1
-1
Index.vue
src/modules/live/product/category/views/Index.vue
+3
-2
api.ts
src/modules/live/product/management/api.ts
+10
-0
FormBaseInfo.vue
...dules/live/product/management/components/FormBaseInfo.vue
+12
-3
FormCategory.vue
...dules/live/product/management/components/FormCategory.vue
+58
-4
FormGraphicInfo.vue
...es/live/product/management/components/FormGraphicInfo.vue
+59
-6
FormPrice.vue
src/modules/live/product/management/components/FormPrice.vue
+23
-4
FormService.vue
...odules/live/product/management/components/FormService.vue
+2
-2
Upload.vue
src/modules/live/product/management/components/Upload.vue
+169
-0
Index.vue
src/modules/live/product/management/views/Index.vue
+5
-4
Update.vue
src/modules/live/product/management/views/Update.vue
+31
-12
api.ts
src/modules/live/talk/api.ts
+6
-1
FormDialog.vue
src/modules/live/talk/components/FormDialog.vue
+79
-5
api.ts
src/modules/live/test/api.ts
+15
-1
FormDialog.vue
src/modules/live/test/components/FormDialog.vue
+15
-3
Live.vue
src/modules/live/test/components/Live.vue
+39
-22
Demo.vue
src/modules/live/test/views/Demo.vue
+43
-4
Index.vue
src/modules/live/test/views/Index.vue
+20
-30
dictionary.ts
src/utils/dictionary.ts
+3
-3
没有找到文件。
public/live/product.png
0 → 100644
浏览文件 @
636c5107
159.8 KB
src/composables/useCountdown.ts
0 → 100644
浏览文件 @
636c5107
import
{
ref
,
computed
,
onUnmounted
,
type
Ref
}
from
'vue'
// 定义参数类型
interface
CountdownOptions
{
initialTime
:
number
autoStart
?:
boolean
onEnd
?:
()
=>
void
}
// 定义返回类型接口
interface
UseCountdown
{
timeLeft
:
Ref
<
number
>
formattedTime
:
Ref
<
string
>
isRunning
:
Ref
<
boolean
>
start
:
()
=>
void
stop
:
()
=>
void
reset
:
(
newTime
?:
number
)
=>
void
}
export
function
useCountdown
({
initialTime
=
0
,
autoStart
=
false
,
onEnd
}:
CountdownOptions
):
UseCountdown
{
const
timeLeft
=
ref
(
initialTime
)
// 剩余时间(秒)
const
isRunning
=
ref
(
false
)
// 是否在倒计时
let
timer
:
number
|
null
=
null
// 格式化为 分钟:秒
const
formattedTime
=
computed
(()
=>
{
const
minutes
=
Math
.
floor
(
timeLeft
.
value
/
60
)
.
toString
()
.
padStart
(
2
,
'0'
)
const
seconds
=
(
timeLeft
.
value
%
60
).
toString
().
padStart
(
2
,
'0'
)
return
`
${
minutes
}
:
${
seconds
}
`
})
// 开始倒计时
const
start
=
()
=>
{
if
(
isRunning
.
value
||
timeLeft
.
value
<=
0
)
return
isRunning
.
value
=
true
timer
=
window
.
setInterval
(()
=>
{
if
(
timeLeft
.
value
>
0
)
{
timeLeft
.
value
--
}
else
{
stop
()
if
(
onEnd
)
onEnd
()
// 倒计时结束后调用回调函数
}
},
1000
)
}
// 停止倒计时
const
stop
=
()
=>
{
isRunning
.
value
=
false
if
(
timer
!==
null
)
{
clearInterval
(
timer
)
timer
=
null
}
}
// 重置倒计时
const
reset
=
(
newTime
:
number
=
initialTime
)
=>
{
stop
()
timeLeft
.
value
=
newTime
if
(
autoStart
)
start
()
}
// 组件卸载时清除定时器
onUnmounted
(()
=>
{
stop
()
})
if
(
autoStart
)
start
()
// 自动开始倒计时
return
{
timeLeft
,
formattedTime
,
isRunning
,
start
,
stop
,
reset
,
}
}
src/modules/live/product/attr/views/Index.vue
浏览文件 @
636c5107
...
@@ -54,7 +54,8 @@ const listOptions = computed(() => {
...
@@ -54,7 +54,8 @@ const listOptions = computed(() => {
label
:
'状态'
,
label
:
'状态'
,
prop
:
'status'
,
prop
:
'status'
,
computed
({
row
})
{
computed
({
row
})
{
return
getNameByValue
(
row
.
status
,
statusList
)
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'
},
{
label
:
'更新时间'
,
prop
:
'updated_time'
},
...
...
src/modules/live/product/category/components/FormDialog.vue
浏览文件 @
636c5107
...
@@ -51,7 +51,7 @@ async function handleUpdate() {
...
@@ -51,7 +51,7 @@ async function handleUpdate() {
<el-dialog
:title=
"title"
:close-on-click-modal=
"false"
@
closed=
"$emit('update:modelValue', false)"
>
<el-dialog
:title=
"title"
:close-on-click-modal=
"false"
@
closed=
"$emit('update:modelValue', false)"
>
<el-row
justify=
"center"
>
<el-row
justify=
"center"
>
<el-col
:sm=
"24"
:md=
"16"
>
<el-col
:sm=
"24"
:md=
"16"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-position=
"top"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-position=
"top"
@
submit
.
prevent=
"handleSubmit"
>
<el-form-item
label=
"类别名称"
prop=
"name"
>
<el-form-item
label=
"类别名称"
prop=
"name"
>
<el-input
v-model=
"form.name"
placeholder=
"请输入"
/>
<el-input
v-model=
"form.name"
placeholder=
"请输入"
/>
</el-form-item>
</el-form-item>
...
...
src/modules/live/product/category/views/Index.vue
浏览文件 @
636c5107
...
@@ -30,7 +30,8 @@ const listOptions = {
...
@@ -30,7 +30,8 @@ const listOptions = {
label
:
'状态'
,
label
:
'状态'
,
prop
:
'status'
,
prop
:
'status'
,
computed
({
row
})
{
computed
({
row
})
{
return
getNameByValue
(
row
.
status
,
statusList
)
const
color
=
row
.
status
===
'1'
?
'var(--main-success-color)'
:
'var(--main-color)'
return
`<span style="color:
${
color
}
">
${
getNameByValue
(
row
.
status
,
statusList
)}
</span>`
},
},
},
},
{
label
:
'操作'
,
slots
:
'table-x'
,
width
:
160
},
{
label
:
'操作'
,
slots
:
'table-x'
,
width
:
160
},
...
@@ -65,8 +66,8 @@ const handleRemove = async (row) => {
...
@@ -65,8 +66,8 @@ const handleRemove = async (row) => {
<el-button
type=
"primary"
@
click=
"handleAdd()"
>
新增类别
</el-button>
<el-button
type=
"primary"
@
click=
"handleAdd()"
>
新增类别
</el-button>
</
template
>
</
template
>
<
template
#
table-x=
"{ row }"
>
<
template
#
table-x=
"{ row }"
>
<el-button
text
type=
"primary"
@
click=
"handleUpdate(row)"
>
编辑
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleAdd(
{ pid: row.id })">新增
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleAdd(
{ pid: row.id })">新增
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleUpdate(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
>
</AppList>
</AppList>
...
...
src/modules/live/product/management/api.ts
浏览文件 @
636c5107
...
@@ -30,6 +30,16 @@ export function getCategoryList(params?: { name?: string }) {
...
@@ -30,6 +30,16 @@ export function getCategoryList(params?: { name?: string }) {
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-commodity-type/trees'
,
{
params
})
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-commodity-type/trees'
,
{
params
})
}
}
// 获取最近添加的直播商品品类
export
function
getCategoryTopicList
()
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-commodity/recent-create-live-commodity-types'
)
}
// 搜索直播商品品类
export
function
searchCategory
(
params
:
{
name
:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-commodity-type/search'
,
{
params
})
}
// 获取直播商品品类下的所有属性
// 获取直播商品品类下的所有属性
export
function
getAttrList
(
params
:
{
live_commodity_type_id
:
string
})
{
export
function
getAttrList
(
params
:
{
live_commodity_type_id
:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-commodity-attr/search'
,
{
params
})
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-commodity-attr/search'
,
{
params
})
...
...
src/modules/live/product/management/components/FormBaseInfo.vue
浏览文件 @
636c5107
...
@@ -33,19 +33,27 @@ function handleChange(value, item, attr) {
...
@@ -33,19 +33,27 @@ function handleChange(value, item, attr) {
<
template
>
<
template
>
<div>
<div>
<el-form-item
label=
"商品标题"
prop=
"title"
:rules=
"[
{ required: true, message: '请输入', trigger: 'blur' }]">
<el-form-item
label=
"商品标题"
prop=
"title"
:rules=
"[
{ required: true, message: '请输入', trigger: 'blur' }]">
<div
class=
"form-tips"
>
标题不规范会有可能引起商品下架,影响您的正常销售,请点击学习商品发布规范认真填写
</div>
<el-input
placeholder=
"至少输入8个字(16个字符)以上,30个字(60个字符)以下"
size=
"large"
v-model=
"form.title"
/>
<el-input
placeholder=
"至少输入8个字(16个字符)以上,30个字(60个字符)以下"
size=
"large"
v-model=
"form.title"
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"导购短标题"
prop=
"shopping_guide_short_title"
>
<el-form-item
label=
"导购短标题"
prop=
"shopping_guide_short_title"
>
<div
class=
"form-tips"
>
短标题可用于商品搜索、首页推荐、物流单等场景,请提炼商品关键信息,客观准确填写
</div>
<div
style=
"display: flex; width: 100%; align-items: center; gap: 20px"
>
<el-input
<el-input
placeholder=
"建议填写简明准确的标题内容,避免重复表达"
placeholder=
"建议填写简明准确的标题内容,避免重复表达"
size=
"large"
size=
"large"
v-model=
"form.shopping_guide_short_title"
/>
v-model=
"form.shopping_guide_short_title"
style=
"flex: 1"
/>
<el-button
type=
"primary"
plain
>
一键智能推荐
</el-button>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"重要属性"
prop=
"live_commodity_attrs.importance_attrs"
>
<el-form-item
:label=
"`重要属性 0/$
{attrs.importance_attrs.total}`" prop="live_commodity_attrs.importance_attrs">
<div
class=
"form-tips"
>
错误填写属性,会引起商品下架,请认真准确填写。
</div>
<el-form-item
<el-form-item
v-for=
"item in attrs.importance_attrs.items"
v-for=
"item in attrs.importance_attrs.items"
:key=
"item.id"
:key=
"item.id"
:label=
"item.name"
:label=
"item.name"
:required=
"item.is_importance == 1"
style=
"width: 200px; margin-right: 20px"
>
style=
"width: 200px; margin-right: 20px"
>
<el-input
<el-input
placeholder=
"请输入"
placeholder=
"请输入"
...
@@ -53,11 +61,12 @@ function handleChange(value, item, attr) {
...
@@ -53,11 +61,12 @@ function handleChange(value, item, attr) {
@
input=
"(value) => handleChange(value, item, 'importance_attrs')"
></el-input>
@
input=
"(value) => handleChange(value, item, 'importance_attrs')"
></el-input>
</el-form-item>
</el-form-item>
</el-form-item>
</el-form-item>
<el-form-item
label=
"非重要属性
"
prop=
"live_commodity_attrs.unimportance"
>
<el-form-item
:label=
"`非重要属性 0/$
{attrs.unimportance_attrs.total}`
" prop="live_commodity_attrs.unimportance">
<el-form-item
<el-form-item
v-for=
"item in attrs.unimportance_attrs.items"
v-for=
"item in attrs.unimportance_attrs.items"
:key=
"item.id"
:key=
"item.id"
:label=
"item.name"
:label=
"item.name"
:required=
"item.is_importance == 1"
style=
"width: 200px; margin-right: 20px"
>
style=
"width: 200px; margin-right: 20px"
>
<el-input
<el-input
placeholder=
"请输入"
placeholder=
"请输入"
...
...
src/modules/live/product/management/components/FormCategory.vue
浏览文件 @
636c5107
<
script
setup
>
<
script
setup
>
import
{
Search
}
from
'@element-plus/icons-vue'
const
form
=
inject
(
'form'
)
const
form
=
inject
(
'form'
)
import
{
getCategory
List
}
from
'../api'
import
{
getCategory
TopicList
,
getCategoryList
,
searchCategory
}
from
'../api'
const
options
=
ref
([])
const
options
=
ref
([])
async
function
fetchList
()
{
async
function
fetchList
()
{
const
res
=
await
getCategoryList
()
const
res
=
await
getCategoryList
()
options
.
value
=
res
.
data
.
trees
options
.
value
=
res
.
data
.
trees
}
}
watchEffect
(()
=>
{
const
topicList
=
ref
([])
async
function
fetchTopicList
()
{
const
res
=
await
getCategoryTopicList
()
topicList
.
value
=
res
.
data
.
items
}
onMounted
(()
=>
{
fetchList
()
fetchList
()
fetchTopicList
()
})
})
const
keyword
=
ref
(
''
)
const
querySearchAsync
=
async
()
=>
{
const
res
=
await
searchCategory
({
name
:
keyword
.
value
})
return
res
.
data
.
items
}
const
handleSelect
=
(
item
)
=>
{
form
.
live_commodity_type_id
=
item
.
id
}
</
script
>
</
script
>
<
template
>
<
template
>
<div>
<div>
<el-form-item
label=
"选择商品类目"
prop=
"live_commodity_type_id"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-form-item
label=
"选择商品类目"
prop=
"live_commodity_type_id"
:rules=
"[
{ required: true, message: '请选择' }]">
<!--
<el-input
placeholder=
"请输入关键词搜索商品分类"
size=
"large"
/>
-->
<div
class=
"form-tips"
>
请按照商品类别谨慎选择对应类目,若是错放类目将可能会导致商品封禁。
</div>
<el-autocomplete
clearable
placeholder=
"请输入关键词搜索商品分类"
size=
"large"
value-key=
"name"
v-model=
"keyword"
:prefix-icon=
"Search"
:fetch-suggestions=
"querySearchAsync"
@
select=
"handleSelect"
>
<template
#
default=
"
{ item }">
<div
class=
"value"
>
{{
item
.
name
}}
</div>
</
template
></el-autocomplete
>
<p
class=
"topic-category"
>
最近创建:
<
template
v-for=
"(item, index) in topicList"
:key=
"item.id"
>
<i
v-if=
"index !== 0"
>
|
</i>
<span
@
click=
"form.live_commodity_type_id = item.id"
>
{{
item
.
name
}}
</span>
</
template
>
</p>
<el-cascader-panel
<el-cascader-panel
v-model=
"form.live_commodity_type_id"
v-model=
"form.live_commodity_type_id"
:options=
"options"
:options=
"options"
...
@@ -24,7 +64,21 @@ watchEffect(() => {
...
@@ -24,7 +64,21 @@ watchEffect(() => {
size=
"large"
size=
"large"
filterable
filterable
clearable
clearable
style="width: 100%
; margin-top: 20px
" />
style=
"width: 100%"
/>
</el-form-item>
</el-form-item>
</div>
</div>
</template>
</template>
<
style
lang=
"scss"
>
.topic-category
{
line-height
:
40px
;
color
:
#92939a
;
span
{
margin
:
0
10px
;
cursor
:
pointer
;
&
:hover
{
color
:
var
(
--
main-color
);
}
}
}
</
style
>
src/modules/live/product/management/components/FormGraphicInfo.vue
浏览文件 @
636c5107
<
script
setup
>
<
script
setup
>
import
AppUpload
from
'@/components/base/App
Upload.vue'
import
Upload
from
'./
Upload.vue'
const
form
=
inject
(
'form'
)
const
form
=
inject
(
'form'
)
const
handleCopy
=
()
=>
{
form
.
picture_34_addreses
=
form
.
picture_addreses
}
</
script
>
</
script
>
<
template
>
<
template
>
<div>
<div>
<el-form-item
label=
"主图"
required
>
<el-form-item
label=
"主图"
prop=
"picture_addreses"
:rules=
"[
{ required: true, message: '该项为必填项' }]">
<AppUpload
v-model=
"form.picture_addreses"
></AppUpload>
<div
class=
"form-tips"
>
影响商品曝光引流效果
</div>
<div>
<Upload
v-model=
"form.picture_addreses"
:limit=
"5"
accept=
"image/*"
first-title=
"商品正面图"
first-tips=
"上传主图"
tips=
"上传辅助图"
></Upload>
<div
class=
"upload-tips"
>
<p>
1、仅支持png/jpg/jpeg格式,宽高至少600*600px,大小2M内
</p>
<p>
2、低质图无法获得平台免费推荐机会,更多图片上传规范要求可点击
</p>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"主图3:4"
>
<el-form-item
label=
""
>
<AppUpload
v-model=
"form.picture_34_addreses"
></AppUpload>
<template
#
label
>
<span>
主图3:4
</span>
<el-button
type=
"primary"
plain
size=
"small"
style=
"margin-left: 430px"
@
click=
"handleCopy"
>
从主图一键填入
</el-button
>
</
template
>
<div
class=
"form-tips"
>
有机会在搜索、推荐等场景展示,提升转化
</div>
<div>
<Upload
v-model=
"form.picture_34_addreses"
:limit=
"5"
accept=
"image/*"
itemHeight=
"134px"
first-title=
"商品正面图"
first-tips=
"上传主图"
tips=
"上传辅助图"
></Upload>
<div
class=
"upload-tips"
>
<p>
1、仅支持png/jpg/jpeg格式,图片宽高不低于375*500(750*1000最佳),大小2M内
</p>
<p>
2、可优先上传3:4或1:1图,系统将有概率生成对应比例图填充至上传入口(如1:1图满足智能生成条件,系统可自动生成3:4图)
点击发布后方可生效
</p>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"主图视频"
>
<el-form-item
label=
"主图视频"
>
<AppUpload
v-model=
"form.video_url"
></AppUpload>
<div
class=
"form-tips"
>
有机会在搜索、推荐等场景展示,提升转化
</div>
<Upload
v-model=
"form.video_url"
accept=
"video/*"
tips=
"主图视频"
isVideo
></Upload>
<div
class=
"upload-tips"
style=
"margin-left: 20px"
>
<p>
1、仅支持mp4格式上传,大小50M内,比例支持1:1、3:4 (分辨率不低于720P)
</p>
<p>
2、5s
<
=时长
<
=60s(30秒内最佳),画面整洁声音流畅,出镜商品与实际商品为同款且主体突出
</p>
</div>
</el-form-item>
</el-form-item>
</div>
</div>
</template>
</template>
<
style
lang=
"scss"
>
.upload-tips
{
padding
:
10px
0
;
color
:
#999
;
line-height
:
24px
;
}
</
style
>
src/modules/live/product/management/components/FormPrice.vue
浏览文件 @
636c5107
<
script
setup
>
<
script
setup
>
import
{
Plus
,
QuestionFilled
}
from
'@element-plus/icons-vue'
import
{
deliveryMode
,
deliveryTime
,
orderStockCount
}
from
'@/utils/dictionary'
import
{
deliveryMode
,
deliveryTime
,
orderStockCount
}
from
'@/utils/dictionary'
const
form
=
inject
(
'form'
)
const
form
=
inject
(
'form'
)
</
script
>
</
script
>
...
@@ -6,12 +7,21 @@ const form = inject('form')
...
@@ -6,12 +7,21 @@ const form = inject('form')
<
template
>
<
template
>
<div>
<div>
<el-form-item
label=
"发货模式"
prop=
"info.delivery_mode"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-form-item
label=
"发货模式"
prop=
"info.delivery_mode"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-radio-group
v-model=
"form.info.delivery_mode"
>
<div
class=
"form-tips"
>
<el-radio
v-for=
"item in deliveryMode"
:key=
"item.value"
v-bind=
"item"
></el-radio>
注:现货商品切勿虚设为预售商品,请合理配置发货模式,以免影响成交转化。若切换发货模式,系统将不会回增原发货模式下被取消订单库存。
</div>
<el-radio-group
v-model=
"form.info.delivery_mode"
style=
"display: block"
>
<el-radio
v-for=
"item in deliveryMode"
:key=
"item.value"
v-bind=
"item"
style=
"display: block"
></el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品规格"
>
</el-form-item>
<el-form-item
label=
"商品规格"
>
<div
class=
"form-tips"
>
准确填写规格信息,有助于商品在搜索场景获取更多流量
</div>
<el-button
size=
"large"
:icon=
"Plus"
>
添加规格(1/3)
</el-button>
</el-form-item>
<el-form-item
label=
"发货时效"
prop=
"info.delivery_time"
>
<el-form-item
label=
"发货时效"
prop=
"info.delivery_time"
>
<div
class=
"form-tips"
>
发货时效计算:买家支付后开始计算发货时效,请合理配置发货时效(注!现货库存切勿虚设为预售时效)
</div>
<el-radio-group
v-model=
"form.info.delivery_time"
>
<el-radio-group
v-model=
"form.info.delivery_time"
>
<el-radio
v-for=
"item in deliveryTime"
:key=
"item.value"
v-bind=
"item"
></el-radio>
<el-radio
v-for=
"item in deliveryTime"
:key=
"item.value"
v-bind=
"item"
></el-radio>
</el-radio-group>
</el-radio-group>
...
@@ -31,7 +41,16 @@ const form = inject('form')
...
@@ -31,7 +41,16 @@ const form = inject('form')
</el-table>
</el-table>
</el-form-item>
</el-form-item>
<el-form-item
label=
"参考价"
prop=
"info.reference_price"
>
<el-form-item
label=
"参考价"
prop=
"info.reference_price"
>
<el-input
v-model=
"form.info.reference_price"
placeholder=
"请输入"
/>
<
template
#
label
>
<span>
参考价
</span>
<el-popover
:width=
"200"
trigger=
"hover"
content=
"this is content, this is content, this is content"
>
<img
src=
"/live/product.png"
style=
"width: 100%"
/>
<template
#
reference
>
<QuestionFilled
style=
"width: 20px"
></QuestionFilled>
</
template
>
</el-popover>
</template>
¥
<el-input
v-model=
"form.info.reference_price"
placeholder=
"请输入"
style=
"width: 300px; margin: 0 10px"
/>
元
</el-form-item>
</el-form-item>
<el-form-item
label=
"订单库存计数"
prop=
"info.order_stock_count"
>
<el-form-item
label=
"订单库存计数"
prop=
"info.order_stock_count"
>
<el-radio-group
v-model=
"form.info.order_stock_count"
>
<el-radio-group
v-model=
"form.info.order_stock_count"
>
...
...
src/modules/live/product/management/components/FormService.vue
浏览文件 @
636c5107
...
@@ -6,12 +6,12 @@ const form = inject('form')
...
@@ -6,12 +6,12 @@ const form = inject('form')
<
template
>
<
template
>
<div>
<div>
<el-form-item
label=
"运费模板"
prop=
"info.shipping_template"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-form-item
label=
"运费模板"
prop=
"info.shipping_template"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-select
v-model=
"form.info.shipping_template"
>
<el-select
v-model=
"form.info.shipping_template"
style=
"width: 300px"
>
<el-option
v-for=
"item in shippingTemplate"
:key=
"item.value"
v-bind=
"item"
></el-option>
<el-option
v-for=
"item in shippingTemplate"
:key=
"item.value"
v-bind=
"item"
></el-option>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item
label=
"售后政策"
prop=
"info.after_sales_policy"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-form-item
label=
"售后政策"
prop=
"info.after_sales_policy"
:rules=
"[
{ required: true, message: '请选择' }]">
<el-select
v-model=
"form.info.after_sales_policy"
>
<el-select
v-model=
"form.info.after_sales_policy"
style=
"width: 300px"
>
<el-option
v-for=
"item in afterSalesPolicy"
:key=
"item.value"
v-bind=
"item"
></el-option>
<el-option
v-for=
"item in afterSalesPolicy"
:key=
"item.value"
v-bind=
"item"
></el-option>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
...
...
src/modules/live/product/management/components/Upload.vue
0 → 100644
浏览文件 @
636c5107
<
script
setup
>
import
{
Plus
,
UploadFilled
,
Picture
}
from
'@element-plus/icons-vue'
import
{
useFileDialog
}
from
'@vueuse/core'
import
{
upload
}
from
'@/utils/upload'
import
dayjs
from
'dayjs'
const
props
=
defineProps
({
modelValue
:
{
type
:
[
Array
,
String
],
default
:
()
=>
[]
},
multiple
:
{
type
:
Boolean
,
default
:
false
},
accept
:
{
type
:
String
,
default
:
''
},
limit
:
{
type
:
Number
,
default
:
1
},
title
:
{
type
:
String
},
firstTitle
:
{
type
:
String
},
tips
:
{
type
:
String
,
default
:
''
},
firstTips
:
{
type
:
String
},
itemHeight
:
{
type
:
String
,
default
:
'100px'
},
isVideo
:
{
type
:
Boolean
,
default
:
false
},
})
const
emit
=
defineEmits
([
'update:modelValue'
])
const
index
=
ref
(
0
)
const
{
open
,
onChange
}
=
useFileDialog
({
accept
:
props
.
accept
,
multiple
:
props
.
multiple
,
})
onChange
(
async
(
files
)
=>
{
const
[
file
]
=
files
const
res
=
await
upload
(
file
)
const
result
=
{
name
:
file
.
name
,
size
:
file
.
size
,
type
:
file
.
type
,
url
:
res
,
upload_time
:
dayjs
().
format
(
'YYYY-MM-DD HH:mm:ss'
),
}
if
(
typeof
props
.
modelValue
===
'string'
)
{
emit
(
'update:modelValue'
,
result
.
url
)
}
else
{
const
updatedValue
=
[...
props
.
modelValue
]
updatedValue
[
index
.
value
]
=
result
emit
(
'update:modelValue'
,
updatedValue
)
}
})
const
handleOpen
=
(
i
)
=>
{
index
.
value
=
i
open
()
}
const
getTitle
=
(
i
)
=>
(
i
===
0
&&
props
.
firstTitle
)
||
props
.
title
const
getTips
=
(
i
)
=>
(
i
===
0
&&
props
.
firstTips
)
||
props
.
tips
</
script
>
<
template
>
<div
class=
"upload-wrapper"
>
<template
v-if=
"Array.isArray(props.modelValue)"
>
<div
v-for=
"(item, i) in props.limit"
:key=
"i"
>
<el-popover
placement=
"top"
width=
"160px"
trigger=
"hover"
>
<ul
class=
"upload-popover"
>
<li
@
click=
"handleOpen(i)"
>
<i
class=
"el-icon"
><UploadFilled
/></i>
本地上传
</li>
<li
v-if=
"!isVideo"
>
<i
class=
"el-icon"
><Picture
/></i>
图库选择
</li>
</ul>
<template
#
reference
>
<div
class=
"upload-item"
>
<template
v-if=
"props.modelValue[i]"
>
<a
:href=
"props.modelValue[i]?.url"
target=
"_blank"
>
<img
:src=
"props.modelValue[i]?.url"
/>
</a>
</
template
>
<
template
v-else
>
<div
class=
"upload-item__title"
>
{{
getTitle
(
i
)
}}
</div>
<i
class=
"el-icon"
><Plus
/></i>
<div
class=
"upload-item__tips"
>
{{
getTips
(
i
)
}}
</div>
</
template
>
</div>
</template>
</el-popover>
</div>
</template>
<
template
v-else
>
<el-popover
placement=
"top"
width=
"160px"
trigger=
"hover"
>
<ul
class=
"upload-popover"
>
<li
@
click=
"handleOpen(0)"
>
<i
class=
"el-icon"
><UploadFilled
/></i>
本地上传
</li>
<li
v-if=
"!isVideo"
>
<i
class=
"el-icon"
><Picture
/></i>
图库选择
</li>
</ul>
<template
#
reference
>
<div
class=
"upload-item"
:style=
"
{ height: props.itemHeight, width: props.itemHeight }">
<template
v-if=
"props.modelValue"
>
<a
:href=
"props.modelValue"
target=
"_blank"
>
<video
:src=
"props.modelValue"
v-if=
"isVideo"
></video>
<img
:src=
"props.modelValue"
v-else
/>
</a>
</
template
>
<
template
v-else
>
<div
class=
"upload-item__title"
>
{{
props
.
firstTitle
||
props
.
title
}}
</div>
<i
class=
"el-icon"
><Plus
/></i>
<div
class=
"upload-item__tips"
>
{{
props
.
firstTips
||
props
.
tips
}}
</div>
</
template
>
</div>
</template>
</el-popover>
</template>
</div>
</template>
<
style
lang=
"scss"
>
.upload-wrapper
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
20px
;
}
.upload-popover
{
display
:
flex
;
justify-content
:
space-around
;
li
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
cursor
:
pointer
;
.el-icon
{
font-size
:
20px
;
}
}
}
.upload-item
{
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
center
;
align-items
:
center
;
width
:
100px
;
height
:
v-bind
(
itemHeight
);
border
:
1px
dashed
var
(
--
el-border-color-darker
);
border-radius
:
6px
;
cursor
:
pointer
;
overflow
:
hidden
;
a
,
video
,
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
cover
;
}
&
__title
{
position
:
absolute
;
top
:
0
;
width
:
100%
;
background-color
:
var
(
--
main-color
);
font-size
:
12px
;
color
:
#fff
;
text-align
:
center
;
line-height
:
20px
;
}
&
__tips
{
font-size
:
12px
;
}
}
</
style
>
src/modules/live/product/management/views/Index.vue
浏览文件 @
636c5107
...
@@ -26,7 +26,7 @@ const listOptions = computed(() => {
...
@@ -26,7 +26,7 @@ const listOptions = computed(() => {
const
list
=
data
.
list
?.
map
((
item
)
=>
{
const
list
=
data
.
list
?.
map
((
item
)
=>
{
item
.
picture_addreses
=
JSON
.
parse
(
item
.
picture_addreses
)
item
.
picture_addreses
=
JSON
.
parse
(
item
.
picture_addreses
)
item
.
picture_url
=
item
.
picture_addreses
[
0
]?.
url
item
.
picture_url
=
item
.
picture_addreses
[
0
]?.
url
item
.
picture_url_list
=
item
.
picture_addreses
?.
map
((
i
)
=>
i
.
url
)
item
.
picture_url_list
=
item
.
picture_addreses
?.
filter
((
i
)
=>
i
).
map
((
i
)
=>
i
.
url
)
return
item
return
item
})
})
return
{
...
data
,
list
}
return
{
...
data
,
list
}
...
@@ -68,17 +68,18 @@ const handleRemove = async (row) => {
...
@@ -68,17 +68,18 @@ const handleRemove = async (row) => {
</
template
>
</
template
>
<
template
#
table-picture=
"{ row }"
>
<
template
#
table-picture=
"{ row }"
>
<el-image
<el-image
fit=
"cover"
preview-teleported
:src=
"row.picture_url"
:src=
"row.picture_url"
style=
"width: 100px; height: 100px"
:previewSrcList=
"row.picture_url_list"
:previewSrcList=
"row.picture_url_list"
preview-teleported
></el-image>
style=
"width: 100px; height: 100px"
></el-image>
</
template
>
</
template
>
<
template
#
table-x=
"{ row }"
>
<
template
#
table-x=
"{ row }"
>
<el-button
text
type=
"primary"
>
<el-button
text
type=
"primary"
>
<router-link
:to=
"
{ path: 'management/view', query: { id: row.id } }">查看
</router-link>
<router-link
:to=
"
{ path: 'management/view', query: { id: row.id } }">查看
</router-link>
</el-button>
</el-button>
<el-button
text
type=
"primary"
>
<el-button
text
type=
"primary"
>
<router-link
:to=
"
{ path: 'management/
view
', query: { id: row.id } }">编辑
</router-link>
<router-link
:to=
"
{ path: 'management/
update
', query: { id: row.id } }">编辑
</router-link>
</el-button>
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleRemove(row)"
>
删除
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleRemove(row)"
>
删除
</el-button>
</
template
>
</
template
>
...
...
src/modules/live/product/management/views/Update.vue
浏览文件 @
636c5107
...
@@ -39,16 +39,26 @@ const form = reactive({
...
@@ -39,16 +39,26 @@ const form = reactive({
})
})
provide
(
'form'
,
form
)
provide
(
'form'
,
form
)
const
array2json
=
(
arr
)
=>
{
const
result
=
{}
arr
.
forEach
((
item
)
=>
{
result
[
item
.
id
]
=
{
...
item
}
})
return
result
}
async
function
fetchInfo
()
{
async
function
fetchInfo
()
{
const
res
=
await
getProduct
({
id
:
route
.
query
.
id
})
const
res
=
await
getProduct
({
id
:
route
.
query
.
id
})
Object
.
assign
(
form
,
res
.
data
.
detail
,
{
const
detail
=
res
.
data
.
detail
// live_commodity_attrs: {
const
attrs
=
JSON
.
parse
(
detail
.
live_commodity_attrs
)
// importance_attrs: Object.values(form.live_commodity_attrs.importance_attrs),
Object
.
assign
(
form
,
detail
,
{
// unimportance_attrs: Object.values(form.live_commodity_attrs.unimportance_attrs),
live_commodity_attrs
:
{
// },
importance_attrs
:
array2json
(
attrs
.
importance_attrs
),
picture_addreses
:
JSON
.
parse
(
form
.
picture_addreses
),
unimportance_attrs
:
array2json
(
attrs
.
unimportance_attrs
),
picture_34_addreses
:
JSON
.
parse
(
form
.
picture_34_addreses
),
},
info
:
JSON
.
parse
(
form
.
info
),
picture_addreses
:
JSON
.
parse
(
detail
.
picture_addreses
),
picture_34_addreses
:
JSON
.
parse
(
detail
.
picture_34_addreses
),
info
:
JSON
.
parse
(
detail
.
info
),
})
})
}
}
watchEffect
(()
=>
{
watchEffect
(()
=>
{
...
@@ -107,9 +117,9 @@ async function handleUpdate(params) {
...
@@ -107,9 +117,9 @@ async function handleUpdate(params) {
<el-tab-pane
label=
"服务与履约"
:name=
"5"
></el-tab-pane>
<el-tab-pane
label=
"服务与履约"
:name=
"5"
></el-tab-pane>
</el-tabs>
</el-tabs>
<el-row
justify=
"center"
style=
"min-height: 400px"
>
<el-row
justify=
"center"
style=
"min-height: 400px"
>
<el-col
:
sm=
"24"
:md
=
"16"
>
<el-col
:
xl
=
"16"
>
<!--
<el-card
shadow=
"never"
>
-->
<!--
<el-card
shadow=
"never"
>
-->
<el-form
label-position=
"top"
:model=
"form"
ref=
"formRef"
>
<el-form
label-position=
"top"
:model=
"form"
:disabled=
"action == 'view'"
ref=
"formRef"
@
submit
.
prevent
>
<!-- 商品类目 -->
<!-- 商品类目 -->
<FormCategory
v-if=
"activeName === 1"
></FormCategory>
<FormCategory
v-if=
"activeName === 1"
></FormCategory>
<!-- 基础信息 -->
<!-- 基础信息 -->
...
@@ -121,11 +131,11 @@ async function handleUpdate(params) {
...
@@ -121,11 +131,11 @@ async function handleUpdate(params) {
<!-- 服务与履约 -->
<!-- 服务与履约 -->
<FormService
v-if=
"activeName === 5"
></FormService>
<FormService
v-if=
"activeName === 5"
></FormService>
</el-form>
</el-form>
<el-row
justify=
"center"
>
<el-row
justify=
"center"
style=
"margin: 100px 0 50px"
>
<el-button
type=
"primary"
plain
v-if=
"activeName === 1"
@
click=
"$router.back()"
>
取消
</el-button>
<el-button
type=
"primary"
plain
v-if=
"activeName === 1"
@
click=
"$router.back()"
>
取消
</el-button>
<el-button
type=
"primary"
plain
@
click=
"handlePrev"
v-if=
"activeName > 1"
>
上一步
</el-button>
<el-button
type=
"primary"
plain
@
click=
"handlePrev"
v-if=
"activeName > 1"
>
上一步
</el-button>
<el-button
type=
"primary"
@
click=
"handleNext"
v-if=
"activeName
<
5
"
>
下一步
</el-button>
<el-button
type=
"primary"
@
click=
"handleNext"
v-if=
"activeName
<
5
"
>
下一步
</el-button>
<el-button
type=
"primary"
@
click=
"handleSubmit"
v-if=
"activeName === 5"
>
保存
</el-button>
<el-button
type=
"primary"
@
click=
"handleSubmit"
v-if=
"activeName === 5
&& action !== 'view'
"
>
保存
</el-button>
</el-row>
</el-row>
<!--
</el-card>
-->
<!--
</el-card>
-->
</el-col>
</el-col>
...
@@ -147,5 +157,14 @@ async function handleUpdate(params) {
...
@@ -147,5 +157,14 @@ async function handleUpdate(params) {
.el-card
{
.el-card
{
background-color
:
rgba
(
242
,
242
,
242
,
0
.49
);
background-color
:
rgba
(
242
,
242
,
242
,
0
.49
);
}
}
.form-tips
{
position
:
absolute
;
right
:
0
;
top
:
-30px
;
z-index
:
100
;
line-height
:
22px
;
color
:
#92939a
;
}
}
}
</
style
>
</
style
>
src/modules/live/talk/api.ts
浏览文件 @
636c5107
import
httpRequest
from
'@/utils/axios'
import
httpRequest
from
'@/utils/axios'
// 获取实验直播话术的列表
// 获取实验直播话术的列表
export
function
getTalkList
(
params
?:
{
name
?:
string
})
{
export
function
getTalkList
(
params
?:
{
name
?:
string
live_commodity_id
?:
string
live_commodity_type_id
?:
string
live_commodity_title
?:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-speeches/list'
,
{
params
})
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-speeches/list'
,
{
params
})
}
}
...
...
src/modules/live/talk/components/FormDialog.vue
浏览文件 @
636c5107
...
@@ -19,7 +19,7 @@ const form = reactive({
...
@@ -19,7 +19,7 @@ const form = reactive({
live_commodity_id
:
''
,
live_commodity_id
:
''
,
selling_point
:
''
,
selling_point
:
''
,
marketing_campaign
:
''
,
marketing_campaign
:
''
,
duration
:
'10
分钟
'
,
duration
:
'10'
,
content
:
''
,
content
:
''
,
})
})
watchEffect
(()
=>
{
watchEffect
(()
=>
{
...
@@ -63,10 +63,12 @@ async function handleUpdate() {
...
@@ -63,10 +63,12 @@ async function handleUpdate() {
emit
(
'update'
)
emit
(
'update'
)
emit
(
'update:modelValue'
,
false
)
emit
(
'update:modelValue'
,
false
)
}
}
const
show
=
ref
(
false
)
</
script
>
</
script
>
<
template
>
<
template
>
<el-dialog
:title=
"title"
:close-on-click-modal=
"false"
@
closed=
"$emit('update:modelValue', false)"
>
<el-dialog
:title=
"title"
:close-on-click-modal=
"false"
@
closed=
"$emit('update:modelValue', false)"
width=
"1000px"
>
<el-row
justify=
"center"
:gutter=
"40"
>
<el-row
justify=
"center"
:gutter=
"40"
>
<el-col
:sm=
"24"
:md=
"12"
>
<el-col
:sm=
"24"
:md=
"12"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-position=
"top"
:disabled=
"action === 'view'"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-position=
"top"
:disabled=
"action === 'view'"
>
...
@@ -77,13 +79,18 @@ async function handleUpdate() {
...
@@ -77,13 +79,18 @@ async function handleUpdate() {
<LiveProductSelect
v-model=
"form.live_commodity_id"
></LiveProductSelect>
<LiveProductSelect
v-model=
"form.live_commodity_id"
></LiveProductSelect>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品卖点"
prop=
"selling_point"
>
<el-form-item
label=
"商品卖点"
prop=
"selling_point"
>
<el-input
type=
"textarea"
v-model=
"form.selling_point"
placeholder=
"多个卖点请使用“;”分割。"
></el-input>
<el-input
type=
"textarea"
v-model=
"form.selling_point"
placeholder=
"多个卖点请使用“;”分割。"
:rows=
"4"
></el-input>
</el-form-item>
</el-form-item>
<el-form-item
label=
"营销活动"
prop=
"marketing_campaign"
>
<el-form-item
label=
"营销活动"
prop=
"marketing_campaign"
>
<el-input
<el-input
type=
"textarea"
type=
"textarea"
v-model=
"form.marketing_campaign"
v-model=
"form.marketing_campaign"
placeholder=
"多个营销活动请使用“;”分割。"
></el-input>
placeholder=
"多个营销活动请使用“;”分割。"
:rows=
"4"
></el-input>
</el-form-item>
</el-form-item>
<el-form-item
label=
"话术时长"
prop=
"duration"
>
<el-form-item
label=
"话术时长"
prop=
"duration"
>
<el-radio-group
v-model=
"form.duration"
>
<el-radio-group
v-model=
"form.duration"
>
...
@@ -95,7 +102,52 @@ async function handleUpdate() {
...
@@ -95,7 +102,52 @@ async function handleUpdate() {
<el-col
:sm=
"24"
:md=
"12"
style=
"border-left: 1px solid #dcdfe6"
>
<el-col
:sm=
"24"
:md=
"12"
style=
"border-left: 1px solid #dcdfe6"
>
<div
style=
"text-align: center"
>
<div
style=
"text-align: center"
>
<h2
style=
"margin-bottom: 20px"
>
直播话术
</h2>
<h2
style=
"margin-bottom: 20px"
>
直播话术
</h2>
<el-button
type=
"primary"
size=
"large"
>
AI生成直播话术
</el-button>
<el-button
type=
"primary"
size=
"large"
@
click=
"show = true"
>
{{
show
?
'再次生成直播话术'
:
'AI生成直播话术'
}}
</el-button>
</div>
<div
class=
"live-talk-content"
v-if=
"show"
>
<p
class=
"t1"
>
商品引入
</p>
<p>
<span>
开场互动
</span>
亲爱的家人们,欢迎来到默认主播的直播间呀!今天可是有超棒的宝贝要给大家分享哦,大家有没有对电脑硬件感兴趣的呀?快来跟主播互动一下吧!
</p>
<p>
<span>
引出话题
</span>
说到电脑,那可是我们生活和工作中不可或缺的一部分呢。大家平时使用电脑的时候有没有遇到过卡顿、运行不流畅的情况呀?今天我带来的这款
1DIY 兼容机,就能完美解决这些问题哦!
</p>
<p>
<span>
引出商品
</span>
这款 1DIY
兼容机,它可是经过精心设计和配置的呢。它能够满足大家各种不同的使用需求,无论是日常办公、学习,还是玩大型游戏,都能轻松应对。
</p>
<p>
<span>
目标人群场景锁定
</span>
对于那些对电脑性能有较高要求的朋友们,这款兼容机绝对是你们的最佳选择。比如那些经常需要处理大量数据、进行图形设计的专业人士,或者是喜欢玩高画质游戏的游戏爱好者们,它都能给你们带来超棒的体验哦!
</p>
<p
class=
"t1"
>
商品讲解
</p>
<p>
<span>
产品卖点讲解
</span>
咱们这款兼容机的第一个卖点就是它的强大性能。它配备了最新的处理器和高性能显卡,运行速度那叫一个快,让你在使用过程中感受不到丝毫卡顿。而且内存和硬盘的容量也非常大,能够存储大量的文件和数据。第二个卖点就是它的兼容性非常好,能够兼容各种软件和硬件,不用担心出现不兼容的情况。
</p>
<p>
<span>
观众互动
</span>
家人们,你们觉得什么样的电脑配置才是最适合自己的呢?快在评论区留言告诉主播哦,主播会根据大家的需求给大家更详细的介绍。
</p>
<p
class=
"t1"
>
引导转化
</p>
<p>
<span>
催单话术
</span>
这么好的一款兼容机,大家是不是已经心动啦?别再犹豫啦,赶紧下单把它带回家吧!现在下单还有特别的优惠活动哦,错过可就太可惜啦!
</p>
<p>
<span>
强调购买方式
</span>
购买的方式非常简单哦,大家只需要点击屏幕下方的购买按钮,按照提示填写好收货信息就可以啦。我们会尽快安排发货,让大家尽快收到心仪的电脑。
</p>
<p>
<span>
结尾互动
</span>
好啦,今天的直播就到这里啦,感谢家人们的陪伴和支持。如果大家还有什么问题或者建议,都可以随时在直播间里留言哦。下次直播我们还会有更多惊喜好物等着大家,再见啦,家人们!
</p>
</div>
</div>
</el-col>
</el-col>
</el-row>
</el-row>
...
@@ -109,3 +161,25 @@ async function handleUpdate() {
...
@@ -109,3 +161,25 @@ async function handleUpdate() {
</
template
>
</
template
>
</el-dialog>
</el-dialog>
</template>
</template>
<
style
lang=
"scss"
>
.live-talk-content
{
font-size
:
12px
;
p
{
line-height
:
22px
;
}
.t1
{
padding
:
0
5px
;
display
:
inline-block
;
background-color
:
rgba
(
25
,
102
,
255
,
0
.08
);
color
:
rgb
(
25
,
102
,
255
);
border
:
1px
solid
transparent
;
}
span
{
padding
:
0
5px
;
color
:
rgb
(
25
,
102
,
255
);
border
:
1px
solid
rgba
(
25
,
102
,
255
,
0
.12
);
}
}
</
style
>
src/modules/live/test/api.ts
浏览文件 @
636c5107
import
httpRequest
from
'@/utils/axios'
import
httpRequest
from
'@/utils/axios'
// 获取实验直播练习的列表
// 获取实验直播练习的列表
export
function
getTestList
(
params
?:
{
name
?:
string
})
{
export
function
getTestList
(
params
?:
{
live_commodity_id
?:
string
live_commodity_type_id
?:
string
live_commodity_title
?:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-practice/list'
,
{
params
})
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-practice/list'
,
{
params
})
}
}
...
@@ -24,3 +28,13 @@ export function updateTest(data: { id: string; name: string; status: string }) {
...
@@ -24,3 +28,13 @@ export function updateTest(data: { id: string; name: string; status: string }) {
export
function
deleteTest
(
data
:
{
id
:
string
})
{
export
function
deleteTest
(
data
:
{
id
:
string
})
{
return
httpRequest
.
post
(
'/api/lab/v1/experiment/live-practice/delete'
,
data
)
return
httpRequest
.
post
(
'/api/lab/v1/experiment/live-practice/delete'
,
data
)
}
}
// 获取实验直播话术的列表
export
function
getTalkList
(
params
?:
{
name
?:
string
live_commodity_id
?:
string
live_commodity_type_id
?:
string
live_commodity_title
?:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/experiment/live-speeches/list'
,
{
params
})
}
src/modules/live/test/components/FormDialog.vue
浏览文件 @
636c5107
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
import
{
ElMessage
}
from
'element-plus'
import
{
ElMessage
}
from
'element-plus'
import
{
liveTestDuration
,
liveTestUploadWay
}
from
'@/utils/dictionary'
import
{
liveTestDuration
,
liveTestUploadWay
}
from
'@/utils/dictionary'
import
LiveProductSelect
from
'@/components/LiveProductSelect.vue'
import
LiveProductSelect
from
'@/components/LiveProductSelect.vue'
import
{
createTest
,
updateTest
}
from
'../api'
import
{
createTest
,
updateTest
,
getTalkList
}
from
'../api'
const
props
=
defineProps
([
'data'
])
const
props
=
defineProps
([
'data'
])
...
@@ -15,7 +15,7 @@ const formRef = ref()
...
@@ -15,7 +15,7 @@ const formRef = ref()
const
form
=
reactive
({
const
form
=
reactive
({
live_commodity_id
:
''
,
live_commodity_id
:
''
,
live_speech_id
:
''
,
live_speech_id
:
''
,
duration
:
'10
分钟
'
,
duration
:
'10'
,
upload_way
:
'1'
,
upload_way
:
'1'
,
})
})
...
@@ -26,6 +26,16 @@ const rules = ref({
...
@@ -26,6 +26,16 @@ const rules = ref({
upload_way
:
[{
required
:
true
,
message
:
'请选择'
}],
upload_way
:
[{
required
:
true
,
message
:
'请选择'
}],
})
})
const
options
=
ref
([])
watch
(
()
=>
form
.
live_commodity_id
,
()
=>
{
form
.
live_speech_id
=
''
getTalkList
({
live_commodity_id
:
form
.
live_commodity_id
}).
then
((
res
)
=>
{
options
.
value
=
res
.
data
.
list
})
}
)
// 提交
// 提交
async
function
handleSubmit
()
{
async
function
handleSubmit
()
{
await
formRef
.
value
?.
validate
()
await
formRef
.
value
?.
validate
()
...
@@ -57,7 +67,9 @@ async function handleUpdate() {
...
@@ -57,7 +67,9 @@ async function handleUpdate() {
<LiveProductSelect
v-model=
"form.live_commodity_id"
></LiveProductSelect>
<LiveProductSelect
v-model=
"form.live_commodity_id"
></LiveProductSelect>
</el-form-item>
</el-form-item>
<el-form-item
label=
"选择直播话术"
prop=
"live_speech_id"
>
<el-form-item
label=
"选择直播话术"
prop=
"live_speech_id"
>
<el-select
v-model=
"form.live_speech_id"
style=
"width: 100%"
placeholder=
"请选择"
></el-select>
<el-select
v-model=
"form.live_speech_id"
style=
"width: 100%"
placeholder=
"请选择"
>
<el-option
v-for=
"item in options"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item
label=
"选择练习时长"
prop=
"duration"
>
<el-form-item
label=
"选择练习时长"
prop=
"duration"
>
<el-radio-group
v-model=
"form.duration"
>
<el-radio-group
v-model=
"form.duration"
>
...
...
src/modules/live/test/components/Live.vue
浏览文件 @
636c5107
...
@@ -3,45 +3,55 @@ import { useUserStore } from '@/stores/user'
...
@@ -3,45 +3,55 @@ import { useUserStore } from '@/stores/user'
import
LiveCover
from
'./LiveCover.vue'
import
LiveCover
from
'./LiveCover.vue'
import
LiveStream
from
'./LiveStream.vue'
import
LiveStream
from
'./LiveStream.vue'
import
LivePlayback
from
'./LivePlayback.vue'
import
LivePlayback
from
'./LivePlayback.vue'
import
{
ref
,
watch
,
defineExpose
,
defineProps
}
from
'vue'
defineProps
({
const
props
=
defineProps
({
isView
:
{
type
:
Boolean
,
default
:
false
},
isView
:
{
type
:
Boolean
,
default
:
false
},
onStart
:
{
type
:
Function
,
default
:
()
=>
{}
},
onStop
:
{
type
:
Function
,
default
:
()
=>
{}
},
})
})
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
const
live
=
ref
(
null
)
const
live
=
ref
(
null
)
const
enabled
=
ref
(
false
)
const
enabled
=
ref
(
false
)
function
start
()
{
const
toggleLive
=
()
=>
{
if
(
live
.
value
)
{
if
(
enabled
.
value
)
{
enabled
.
value
=
true
live
.
value
?.
start
()
live
.
value
.
start
()
props
.
onStart
?.()
}
else
{
live
.
value
?.
stop
()
props
.
onStop
?.()
}
}
}
}
watch
(
enabled
,
toggleLive
)
function
start
()
{
if
(
live
.
value
)
enabled
.
value
=
true
}
function
stop
()
{
function
stop
()
{
if
(
live
.
value
)
{
if
(
live
.
value
)
enabled
.
value
=
false
enabled
.
value
=
false
live
.
value
.
stop
()
}
}
}
defineExpose
({
enabled
,
start
,
stop
})
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"live"
>
<div
class=
"live"
>
<div
class=
"live-hd"
>
<div
class=
"live-hd"
>
<p>
主播:
{{
userStore
.
user
.
name
}}
</p>
<p>
主播:
{{
userStore
.
user
.
name
}}
</p>
<img
:src=
"enabled ? '/live/live2.png' : '/live/live1.png'"
style=
"height: 20px"
v-if=
"!isView
"
/>
<img
:src=
"enabled ? '/live/live2.png' : '/live/live1.png'"
v-if=
"!isView"
alt=
"直播状态"
class=
"live-icon
"
/>
</div>
</div>
<div
class=
"live-bd"
>
<div
class=
"live-bd"
>
<LiveStream
ref=
"live"
v-if=
"isView"
></LiveStream>
<component
:is=
"isView ? LivePlayback : LiveStream"
ref=
"live"
/>
<LivePlayback
ref=
"live"
v-else
></LivePlayback>
<LiveCover
v-if=
"enabled"
/>
<LiveCover
v-if=
"enabled"
/>
</div>
</div>
<div
class=
"live-ft"
v-if=
"!isView"
>
<div
class=
"live-ft"
v-if=
"!isView"
>
<el-button
type=
"primary"
size=
"large"
round
style=
"width: 80%"
@
click=
"start"
v-if=
"!enabled"
<el-button
type=
"primary"
size=
"large"
round
style=
"width: 80%"
@
click=
"enabled ? stop() : start()"
>
>
开始练习
</el-button
{{
enabled
?
'结束直播练习'
:
'开始练习'
}}
>
</el-button>
<el-button
type=
"primary"
size=
"large"
round
style=
"width: 80%"
@
click=
"stop"
v-else
>
结束直播练习
</el-button>
</div>
</div>
</div>
</div>
</
template
>
</
template
>
...
@@ -49,20 +59,27 @@ function stop() {
...
@@ -49,20 +59,27 @@ function stop() {
<
style
lang=
"scss"
>
<
style
lang=
"scss"
>
.live
{
.live
{
width
:
375px
;
width
:
375px
;
}
.live
-hd
{
&
-hd
{
display
:
flex
;
display
:
flex
;
justify-content
:
space-between
;
justify-content
:
space-between
;
}
.live-bd
{
.live-icon
{
height
:
20px
;
}
}
&
-bd
{
position
:
relative
;
position
:
relative
;
margin
:
10px
0
;
margin
:
10px
0
;
height
:
667px
;
height
:
667px
;
border-radius
:
8px
;
border-radius
:
8px
;
overflow
:
hidden
;
overflow
:
hidden
;
background-color
:
#000
;
background-color
:
#000
;
}
}
.live-ft
{
&
-ft
{
text-align
:
center
;
text-align
:
center
;
}
}
}
</
style
>
</
style
>
src/modules/live/test/views/Demo.vue
浏览文件 @
636c5107
<
script
setup
>
<
script
setup
>
import
Live
from
'../components/Live.vue'
import
Live
from
'../components/Live.vue'
import
{
getTest
}
from
'../api'
import
{
useCountdown
}
from
'@/composables/useCountdown'
defineProps
({
defineProps
({
isView
:
{
type
:
Boolean
,
default
:
false
},
isView
:
{
type
:
Boolean
,
default
:
false
},
})
})
const
route
=
useRoute
()
const
hotList
=
ref
([
'一键开启'
,
'高效便捷'
,
'一键开启'
,
'高效便捷'
,
'一键开启'
,
'高效便捷'
,
'一键开启'
,
'高效便捷'
])
const
{
timeLeft
,
formattedTime
,
start
,
stop
}
=
useCountdown
({
const
actList
=
ref
([
'7天无理由退货'
,
'7天无理由退货'
,
'7天无理由退货'
,
'7天无理由退货'
,
'7天无理由退货'
])
// 倒计时结束
onEnd
:
()
=>
{
live
.
value
?.
stop
()
},
})
const
live
=
ref
(
null
)
const
detail
=
ref
(
null
)
async
function
fetchInfo
()
{
const
res
=
await
getTest
({
id
:
route
.
query
.
id
})
detail
.
value
=
res
.
data
.
detail
timeLeft
.
value
=
parseInt
(
detail
.
value
?.
duration
)
||
0
}
watchEffect
(()
=>
{
fetchInfo
()
})
// 商品卖点
const
hotList
=
computed
(()
=>
{
return
detail
.
value
?.
live_speech
.
selling_point
.
split
(
';'
)
})
// 营销活动
const
actList
=
computed
(()
=>
{
return
detail
.
value
?.
live_speech
.
marketing_campaign
.
split
(
';'
)
})
</
script
>
</
script
>
<
template
>
<
template
>
<AppCard
title=
"直播练习"
full
>
<AppCard
title=
"直播练习"
full
>
<div
class=
"live-row"
>
<div
class=
"live-row"
>
<div
class=
"live-col"
><Live
isView
/></div>
<div
class=
"live-col"
><Live
ref=
"live"
:isView=
"isView"
:onStart=
"start"
:onStop=
"stop"
/></div>
<div
class=
"live-col"
style=
"flex: 1"
>
<div
class=
"live-col"
style=
"flex: 1"
>
<h2
class=
"h2-title"
>
直播话术
</h2>
<h2
class=
"h2-title"
>
直播话术
</h2>
<div
class=
"live-talk-content"
>
<div
class=
"live-talk-content"
>
...
@@ -62,6 +89,7 @@ const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理
...
@@ -62,6 +89,7 @@ const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理
<div
class=
"live-col"
style=
"width: 350px"
>
<div
class=
"live-col"
style=
"width: 350px"
>
<div
class=
"live-col-box"
v-if=
"isView"
>
<div
class=
"live-col-box"
v-if=
"isView"
>
<h2
class=
"h2-title"
>
直播数据
</h2>
<h2
class=
"h2-title"
>
直播数据
</h2>
<div
class=
"live-data"
>
<dl>
<dl>
<dt>
观众总人数:
</dt>
<dt>
观众总人数:
</dt>
<dd>
100
</dd>
<dd>
100
</dd>
...
@@ -83,9 +111,10 @@ const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理
...
@@ -83,9 +111,10 @@ const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理
<dd>
1200
</dd>
<dd>
1200
</dd>
</dl>
</dl>
</div>
</div>
</div>
<div
class=
"live-col-box"
v-else
>
<div
class=
"live-col-box"
v-else
>
<h2
class=
"h2-title"
>
倒计时
</h2>
<h2
class=
"h2-title"
>
倒计时
</h2>
<h3
class=
"live-time"
>
14 : 55
</h3>
<h3
class=
"live-time"
>
{{
formattedTime
}}
</h3>
</div>
</div>
<div
class=
"live-col-box"
>
<div
class=
"live-col-box"
>
<h2
class=
"h2-title"
>
商品卖点
</h2>
<h2
class=
"h2-title"
>
商品卖点
</h2>
...
@@ -131,6 +160,16 @@ const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理
...
@@ -131,6 +160,16 @@ const actList = ref(['7天无理由退货', '7天无理由退货', '7天无理
border
:
1px
solid
rgba
(
105
,
113
,
140
,
0
.12
);
border
:
1px
solid
rgba
(
105
,
113
,
140
,
0
.12
);
}
}
}
}
.live-data
{
padding
:
0
0
20px
10px
;
dl
{
margin
:
10px
0
;
display
:
flex
;
}
dt
{
font-weight
:
bold
;
}
}
.live-time
{
.live-time
{
height
:
140px
;
height
:
140px
;
font-size
:
72px
;
font-size
:
72px
;
...
...
src/modules/live/test/views/Index.vue
浏览文件 @
636c5107
<
script
setup
>
<
script
setup
>
import
{
ElMessage
}
from
'element-plus'
import
LiveProductCategory
from
'@/components/LiveProductCategory.vue'
import
LiveProductCategory
from
'@/components/LiveProductCategory.vue'
import
{
getTestList
}
from
'../api'
import
{
getTestList
}
from
'../api'
import
{
useFileDialog
}
from
'@vueuse/core'
import
{
upload
}
from
'@/utils/upload'
const
FormDialog
=
defineAsyncComponent
(()
=>
import
(
'../components/FormDialog.vue'
))
const
FormDialog
=
defineAsyncComponent
(()
=>
import
(
'../components/FormDialog.vue'
))
...
@@ -10,7 +13,7 @@ const handleRefresh = () => {
...
@@ -10,7 +13,7 @@ const handleRefresh = () => {
appList
.
value
?.
refetch
()
appList
.
value
?.
refetch
()
}
}
const
listParams
=
reactive
({
nam
e
:
''
,
live_commodity_type_id
:
''
})
const
listParams
=
reactive
({
live_commodity_titl
e
:
''
,
live_commodity_type_id
:
''
})
// 列表配置
// 列表配置
const
listOptions
=
computed
(()
=>
{
const
listOptions
=
computed
(()
=>
{
...
@@ -26,49 +29,36 @@ const listOptions = computed(() => {
...
@@ -26,49 +29,36 @@ const listOptions = computed(() => {
},
},
filters
:
[
filters
:
[
{
label
:
'商品品类'
,
prop
:
'live_commodity_type_id'
,
slots
:
'filter-category'
},
{
label
:
'商品品类'
,
prop
:
'live_commodity_type_id'
,
slots
:
'filter-category'
},
{
label
:
'商品标题'
,
prop
:
'
product_nam
e'
,
type
:
'input'
},
{
label
:
'商品标题'
,
prop
:
'
live_commodity_titl
e'
,
type
:
'input'
},
],
],
columns
:
[
columns
:
[
{
label
:
'序号'
,
type
:
'index'
,
width
:
60
},
{
label
:
'序号'
,
type
:
'index'
,
width
:
60
},
{
label
:
'直播ID'
,
prop
:
'id'
},
{
label
:
'直播ID'
,
prop
:
'id'
},
{
label
:
'商品标题'
,
prop
:
'
product_nam
e'
},
{
label
:
'商品标题'
,
prop
:
'
live_commodity_titl
e'
},
{
label
:
'直播话术'
,
prop
:
'name'
},
{
label
:
'直播话术'
,
prop
:
'
live_commodity_speeches_
name'
},
{
label
:
'所属商品品类'
,
prop
:
'
product_category
'
},
{
label
:
'所属商品品类'
,
prop
:
'
live_commodity_type_full_name
'
},
{
label
:
'更新时间'
,
prop
:
'updated_time'
},
{
label
:
'更新时间'
,
prop
:
'updated_time'
},
{
label
:
'操作'
,
slots
:
'table-x'
,
width
:
200
},
{
label
:
'操作'
,
slots
:
'table-x'
,
width
:
200
},
],
],
data
:
[
{
id
:
1
,
product_name
:
'手机'
,
name
:
'手机销售'
,
product_category
:
'智能手机'
,
updated_time
:
'2021-07-30 14:59:56'
,
},
{
id
:
1
,
product_name
:
'手机'
,
name
:
'手机销售'
,
product_category
:
'智能手机'
,
updated_time
:
'2021-07-30 14:59:56'
,
},
{
id
:
1
,
product_name
:
'手机'
,
name
:
'手机销售'
,
product_category
:
'智能手机'
,
updated_time
:
'2021-07-30 14:59:56'
,
},
],
}
}
})
})
const
formVisible
=
ref
(
false
)
const
formVisible
=
ref
(
false
)
const
handleUpload
=
()
=>
{
const
dialog
=
useFileDialog
()
dialog
.
open
({
accept
:
'video/mp4'
})
dialog
.
onChange
(
async
([
file
])
=>
{
const
res
=
await
upload
(
file
)
console
.
log
(
file
,
res
)
ElMessage
.
success
(
'上传成功'
)
})
}
</
script
>
</
script
>
<
template
>
<
template
>
<AppCard
title=
"直播练习"
>
<AppCard
title=
"直播练习"
>
<AppList
v-bind=
"listOptions"
re=
"appList"
>
<AppList
v-bind=
"listOptions"
re
f
=
"appList"
>
<template
#
header-buttons
>
<template
#
header-buttons
>
<el-button
type=
"primary"
@
click=
"formVisible = true"
>
添加直播练习
</el-button>
<el-button
type=
"primary"
@
click=
"formVisible = true"
>
添加直播练习
</el-button>
</
template
>
</
template
>
...
@@ -76,7 +66,7 @@ const formVisible = ref(false)
...
@@ -76,7 +66,7 @@ const formVisible = ref(false)
<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>
<el-button
text
type=
"primary"
@
click=
"handleUpload"
>
上传
</el-button>
<el-button
text
type=
"primary"
>
<el-button
text
type=
"primary"
>
<router-link
:to=
"
{ path: 'test/demo', query: { id: row.id } }">练习
</router-link>
<router-link
:to=
"
{ path: 'test/demo', query: { id: row.id } }">练习
</router-link>
</el-button>
</el-button>
...
...
src/utils/dictionary.ts
浏览文件 @
636c5107
...
@@ -158,9 +158,9 @@ export const textPurposeList = [
...
@@ -158,9 +158,9 @@ export const textPurposeList = [
// 直播练习时长
// 直播练习时长
export
const
liveTestDuration
=
[
export
const
liveTestDuration
=
[
{
label
:
'10分钟'
,
value
:
'10
分钟
'
},
{
label
:
'10分钟'
,
value
:
'10'
},
{
label
:
'15分钟 '
,
value
:
'15
分钟
'
},
{
label
:
'15分钟 '
,
value
:
'15'
},
{
label
:
'20分钟 '
,
value
:
'20
分钟
'
},
{
label
:
'20分钟 '
,
value
:
'20'
},
]
]
// 直播练习时长
// 直播练习时长
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论