Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
wangxiaolu-sfa-ui
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
sfa
wangxiaolu-sfa-ui
Commits
edb4a1f9
提交
edb4a1f9
authored
9月 11, 2025
作者:
lidongxu
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'release' into dev
上级
d7c1bbc5
229c1f30
隐藏空白字符变更
内嵌
并排
正在显示
1 个修改的文件
包含
121 行增加
和
25 行删除
+121
-25
index.vue
...y/sales_point_inspection/examine/inspectionTask/index.vue
+121
-25
没有找到文件。
src/views/mobile/pages/audit_activity/sales_point_inspection/examine/inspectionTask/index.vue
浏览文件 @
edb4a1f9
...
...
@@ -84,11 +84,12 @@
ref=
"videoRef"
alt=
""
muted
autoplay
playsinline
preload=
"metadata"
:poster=
"getVideoPoster(url)"
preload=
"auto"
class=
"preview-media"
@
loadeddata=
"onVideoLoadedData(videoRef)"
/>
@
loadeddata=
"onVideoLoadedData(
{ value: videoRef.value, url: url })"
@error="onVideoError(url)" />
<div
class=
"play-hint"
@
click
.
stop=
"showVideoFullscreen(url)"
>
...
...
@@ -447,6 +448,10 @@ const commitStorePhotosRead = async (file) => {
url
:
pictureUrl
,
status
:
'done'
}]
// 上传完成后,为视频生成海报
if
(
getFileType
(
pictureUrl
)
===
'video'
)
{
generateVideoPoster
(
pictureUrl
);
}
await
createInspectionTaskAPI
({
storeCode
:
form
.
storeCode
,
storeName
:
form
.
storeName
,
...
...
@@ -536,17 +541,26 @@ const isPlaying = ref(false);
// 视频相关状态
const
videoPosters
=
ref
({});
const
videoLoadingStatus
=
ref
({});
// 跟踪每个视频的加载状态
const
isWebView
=
ref
(
false
);
// 组件挂载时检测环境
onMounted
(()
=>
{
detectEnvironment
();
// 为已有的视频URL生成海报图
if
(
form
.
commitStorePicture
&&
form
.
commitStorePicture
.
length
>
0
)
{
form
.
commitStorePicture
.
forEach
(
item
=>
{
if
(
item
.
url
&&
getFileType
(
item
.
url
)
===
'video'
)
{
generateVideoPoster
(
item
.
url
);
}
});
}
});
// 检测是否在webView中
const
detectEnvironment
=
()
=>
{
// 检测是否在iOS设备上
const
isiOS
=
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
;
// 检测是否在webView中 - 可以根据实际项目中的webView特征调整
// 这里使用了一些常见的webView特征检测方法
const
isInWebView
=
()
=>
{
...
...
@@ -556,35 +570,117 @@ const detectEnvironment = () => {
const
hasUIWebViewProperty
=
typeof
navigator
.
standalone
!==
'undefined'
&&
!
navigator
.
standalone
;
// 检查userAgent中是否包含特定App的标识
const
hasCustomUserAgent
=
/AppName|CustomWebView/i
.
test
(
navigator
.
userAgent
);
return
hasUIWebViewProperty
||
hasCustomUserAgent
;
}
return
false
;
};
isWebView
.
value
=
isInWebView
();
};
// 生成视频海报图
const
generateVideoPoster
=
async
(
videoUrl
)
=>
{
// 避免重复生成
if
(
videoPosters
.
value
[
videoUrl
]
||
videoLoadingStatus
.
value
[
videoUrl
])
{
return
;
}
// 标记为加载中
videoLoadingStatus
.
value
[
videoUrl
]
=
true
;
try
{
// 创建一个临时video元素用于获取第一帧
const
tempVideo
=
document
.
createElement
(
'video'
);
tempVideo
.
src
=
videoUrl
;
tempVideo
.
muted
=
true
;
tempVideo
.
preload
=
'metadata'
;
// 等待视频元数据加载完成
await
new
Promise
((
resolve
,
reject
)
=>
{
tempVideo
.
onloadedmetadata
=
resolve
;
tempVideo
.
onerror
=
reject
;
// 设置超时,防止加载过久
setTimeout
(()
=>
reject
(
new
Error
(
'Video metadata load timeout'
)),
5000
);
});
// 设置当前时间为0.1秒(有时第0秒是黑屏)
tempVideo
.
currentTime
=
0.1
;
// 等待视频帧加载完成
await
new
Promise
((
resolve
,
reject
)
=>
{
tempVideo
.
onseeked
=
resolve
;
tempVideo
.
onerror
=
reject
;
setTimeout
(()
=>
reject
(
new
Error
(
'Video frame seek timeout'
)),
3000
);
});
// 使用canvas绘制第一帧
const
canvas
=
document
.
createElement
(
'canvas'
);
const
ctx
=
canvas
.
getContext
(
'2d'
);
// 设置canvas尺寸为视频的原始尺寸或适合预览的尺寸
const
targetWidth
=
300
;
// 可以根据需要调整
const
aspectRatio
=
tempVideo
.
videoHeight
/
tempVideo
.
videoWidth
;
canvas
.
width
=
targetWidth
;
canvas
.
height
=
Math
.
floor
(
targetWidth
*
aspectRatio
);
// 绘制视频帧到canvas
ctx
.
drawImage
(
tempVideo
,
0
,
0
,
canvas
.
width
,
canvas
.
height
);
// 转换为图片URL
const
posterUrl
=
canvas
.
toDataURL
(
'image/jpeg'
,
0.9
);
// 0.9是压缩质量
// 缓存海报图
videoPosters
.
value
[
videoUrl
]
=
posterUrl
;
// 清理临时资源
tempVideo
.
src
=
''
;
}
catch
(
error
)
{
console
.
warn
(
'生成视频海报失败,使用默认海报:'
,
error
);
// 生成一个默认的占位海报图
videoPosters
.
value
[
videoUrl
]
=
generateDefaultVideoPoster
();
}
finally
{
// 标记为加载完成
videoLoadingStatus
.
value
[
videoUrl
]
=
false
;
}
};
// 获取视频海报图
// 生成默认的视频占位海报
const
generateDefaultVideoPoster
=
()
=>
{
// 创建一个简单的SVG作为默认海报
return
'data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22300%22 height%3D%22170%22%3E%3Crect width%3D%22100%25%22 height%3D%22100%25%22 fill%3D%22%23222%22%2F%3E%3Ccircle cx%3D%22150%22 cy%3D%2285%22 r%3D%2240%22 fill%3D%22%23444%22%2F%3E%3Cpath d%3D%22M130 65 l40 20 l-40 20 z%22 fill%3D%22%23fff%22%2F%3E%3Ctext x%3D%22150%22 y%3D%22140%22 font-family%3D%22Arial%22 font-size%3D%2214%22 fill%3D%22%23999%22 text-anchor%3D%22middle%22%3E视频预览%3C%2Ftext%3E%3C%2Fsvg%3E'
;
};
// 获取视频海报图(修改之前的实现)
const
getVideoPoster
=
(
url
)
=>
{
// 如果已经
有缓存的海报图,则
直接返回
// 如果已经
生成了海报图,
直接返回
if
(
videoPosters
.
value
[
url
])
{
return
videoPosters
.
value
[
url
];
}
// 为iOS设备生成一个临时海报图
if
(
/iPad|iPhone|iPod/
.
test
(
navigator
.
userAgent
)
&&
!
window
.
MSStream
)
{
// 可以返回一个默认的视频占位图
// 或者更好的方法是使用视频的第一帧作为海报(需要额外处理)
return
'data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22 width%3D%22100%25%22 height%3D%22100%25%22%3E%3Crect width%3D%22100%25%22 height%3D%22100%25%22 fill%3D%22%23000%22%2F%3E%3Cpath d%3D%22M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z%22 fill%3D%22%23999%22%2F%3E%3C%2Fsvg%3E'
;
// 如果正在生成中,返回一个临时占位符
if
(
videoLoadingStatus
.
value
[
url
])
{
return
generateDefaultVideoPoster
();
}
return
''
;
// 否则开始生成海报图
generateVideoPoster
(
url
);
// 立即返回默认海报,生成完成后会自动更新
return
generateDefaultVideoPoster
();
};
// 视频数据加载完成后触发
const
onVideoLoadedData
=
(
videoRef
)
=>
{
const
video
=
videoRef
.
value
;
// 视频加载错误处理
const
onVideoError
=
(
url
)
=>
{
console
.
error
(
'视频加载错误:'
,
url
);
// 设置默认海报图
if
(
!
videoPosters
.
value
[
url
])
{
videoPosters
.
value
[
url
]
=
generateDefaultVideoPoster
();
}
// 重置加载状态
videoLoadingStatus
.
value
[
url
]
=
false
;
};
// 修改onVideoLoadedData函数以接收更多参数
const
onVideoLoadedData
=
({
value
:
video
,
url
})
=>
{
if
(
!
video
)
return
;
// 对于非webView环境,可以考虑预加载视频的第一帧作为海报
...
...
@@ -593,13 +689,13 @@ const onVideoLoadedData = (videoRef) => {
// 尝试获取视频的第一帧作为海报图
const
canvas
=
document
.
createElement
(
'canvas'
);
const
ctx
=
canvas
.
getContext
(
'2d'
);
canvas
.
width
=
video
.
videoWidth
;
canvas
.
height
=
video
.
videoHeight
;
canvas
.
width
=
video
.
videoWidth
||
300
;
canvas
.
height
=
video
.
videoHeight
||
170
;
ctx
.
drawImage
(
video
,
0
,
0
,
canvas
.
width
,
canvas
.
height
);
// 将canvas转换为图片URL并缓存
const
posterUrl
=
canvas
.
toDataURL
(
'image/jpeg'
);
videoPosters
.
value
[
video
.
src
]
=
posterUrl
;
videoPosters
.
value
[
url
]
=
posterUrl
;
video
.
poster
=
posterUrl
;
}
catch
(
error
)
{
console
.
error
(
'生成视频海报失败:'
,
error
);
...
...
@@ -642,7 +738,7 @@ const showVideoFullscreen = async (url) => {
// 跨浏览器请求全屏(保留之前的实现)
const
requestFullscreen
=
(
element
)
=>
{
if
(
!
element
)
return
Promise
.
reject
(
new
Error
(
'Element not found'
));
const
methods
=
[
// 标准API
()
=>
element
.
requestFullscreen
(),
...
...
@@ -657,14 +753,14 @@ const requestFullscreen = (element) => {
return
Promise
.
reject
(
new
Error
(
'webkitEnterFullscreen not supported'
));
}
];
// 尝试各种方法
return
new
Promise
((
resolve
,
reject
)
=>
{
for
(
const
method
of
methods
)
{
try
{
const
result
=
method
();
if
(
result
&&
result
.
then
)
{
result
.
then
(
resolve
).
catch
(()
=>
{});
result
.
then
(
resolve
).
catch
(()
=>
{
});
}
else
{
resolve
();
return
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论