Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
wangxiaolu-sfa-ui
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
sfa
wangxiaolu-sfa-ui
Commits
d5750400
提交
d5750400
authored
9月 11, 2025
作者:
lidongxu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
test(inspectiontask): 测试:勤策移动端_售点稽查_视频播放
上级
229c1f30
显示空白字符变更
内嵌
并排
正在显示
1 个修改的文件
包含
58 行增加
和
286 行删除
+58
-286
index.vue
...y/sales_point_inspection/examine/inspectionTask/index.vue
+58
-286
没有找到文件。
src/views/mobile/pages/audit_activity/sales_point_inspection/examine/inspectionTask/index.vue
浏览文件 @
d5750400
...
@@ -67,11 +67,10 @@
...
@@ -67,11 +67,10 @@
<!-- 自定义预览内容 -->
<!-- 自定义预览内容 -->
<
template
#
preview-cover=
"{ url, index }"
>
<
template
#
preview-cover=
"{ url, index }"
>
<div
class=
"preview-container"
<div
class=
"preview-container"
@
click
.
stop
>
@
click
.
stop
>
<!-- 图片预览 -->
<!-- 图片预览 -->
<van-image
v-if=
"get
File
Type(url) === 'image'"
<van-image
v-if=
"get
Media
Type(url) === 'image'"
:src=
"url"
:src=
"url"
alt=
""
alt=
""
class=
"preview-media"
class=
"preview-media"
...
@@ -79,21 +78,23 @@
...
@@ -79,21 +78,23 @@
@
click=
"previewImage(url)"
/>
@
click=
"previewImage(url)"
/>
<!-- 视频预览 -->
<!-- 视频预览 -->
<
video
v-else-if=
"getFileType(url) === 'video'
"
<
div
class=
"video-wrap
"
:src=
"url"
v-else-if=
"getMediaType(url) === 'video'"
>
ref=
"videoRef
"
<video
:src=
"url
"
alt=
""
alt=
""
ref=
"videoRef"
muted
muted
autoplay
autoplay
playsinline
playsinline
preload=
"auto"
preload=
"auto"
class=
"preview-media"
class=
"preview-media"
@
loadeddata=
"onVideoLoadedData(
{ value: videoRef.value, url: url })"
@
loadeddata=
"handleVideoLoaded"
/>
@error="onVideoError(url)" />
<div
class=
"play-hint"
<div
class=
"play-hint"
@
click
.
stop=
"showVideoFullscreen(url)"
>
@
click
.
stop=
"showVideoFullscreen(url)"
>
</div>
</div>
</div>
<!-- 删除按钮 -->
<!-- 删除按钮 -->
<van-icon
name=
"clear"
<van-icon
name=
"clear"
...
@@ -101,7 +102,7 @@
...
@@ -101,7 +102,7 @@
@
click
.
stop=
"deleteCommitStorePhotos"
/>
@
click
.
stop=
"deleteCommitStorePhotos"
/>
<!-- 类型标识 -->
<!-- 类型标识 -->
<span
v-if=
"get
File
Type(url) === 'video'"
<span
v-if=
"get
Media
Type(url) === 'video'"
class=
"video-tag"
>
class=
"video-tag"
>
视频
视频
</span>
</span>
...
@@ -223,29 +224,6 @@
...
@@ -223,29 +224,6 @@
size=
"40"
size=
"40"
text-color=
"#f12528"
>
加载中...
</van-loading>
text-color=
"#f12528"
>
加载中...
</van-loading>
</van-overlay>
</van-overlay>
<!-- 视频全屏预览 Dialog -->
<!-- <van-dialog v-model:show="videoDialogVisible"
title="视频预览"
:show-cancel-button="false"
:show-confirm-button="false"
:before-close="closeVideoFullscreen"
class="fullscreen-video-dialog"
@closed="closeVideoFullscreen">
<div class="video-container">
<div class="close-button"
@click.stop="videoDialogVisible = false">×</div>
<video id="fullscreen-video"
v-if="currentVideo"
:src="currentVideo"
alt=""
controls
autoplay
class="fullscreen-video"
@click="handleVideoClick" />
</div>
</van-dialog> -->
</div>
</div>
</template>
</template>
...
@@ -262,9 +240,7 @@ import { getFileTypeExt, getMediaType } from '@/utils'
...
@@ -262,9 +240,7 @@ import { getFileTypeExt, getMediaType } from '@/utils'
// 引入必要的组件
// 引入必要的组件
import
{
showImagePreview
}
from
'vant'
;
import
{
showImagePreview
}
from
'vant'
;
const
previewImage
=
(
url
)
=>
{
showImagePreview
([
url
]);
}
const
router
=
useRouter
()
const
router
=
useRouter
()
const
route
=
useRoute
()
const
route
=
useRoute
()
...
@@ -448,10 +424,6 @@ const commitStorePhotosRead = async (file) => {
...
@@ -448,10 +424,6 @@ const commitStorePhotosRead = async (file) => {
url
:
pictureUrl
,
url
:
pictureUrl
,
status
:
'done'
status
:
'done'
}]
}]
// 上传完成后,为视频生成海报
if
(
getFileType
(
pictureUrl
)
===
'video'
)
{
generateVideoPoster
(
pictureUrl
);
}
await
createInspectionTaskAPI
({
await
createInspectionTaskAPI
({
storeCode
:
form
.
storeCode
,
storeCode
:
form
.
storeCode
,
storeName
:
form
.
storeName
,
storeName
:
form
.
storeName
,
...
@@ -473,256 +445,69 @@ const deleteCommitStorePhotos = async () => {
...
@@ -473,256 +445,69 @@ const deleteCommitStorePhotos = async () => {
if
(
!
route
.
query
.
storePicture
)
form
.
storePicture
=
""
if
(
!
route
.
query
.
storePicture
)
form
.
storePicture
=
""
showNotify
({
type
:
'success'
,
message
:
'门头照,删除成功'
})
showNotify
({
type
:
'success'
,
message
:
'门头照,删除成功'
})
}
}
// 文件大小限制
const
onOversize
=
(
file
)
=>
{
const
onOversize
=
(
file
)
=>
{
showNotify
({
type
:
'danger'
,
message
:
'文件大小不能超过 5 MB'
})
showNotify
({
type
:
'danger'
,
message
:
'文件大小不能超过 5 MB'
})
}
}
const
getFileType
=
(
url
)
=>
{
// 图片预览
return
getMediaType
(
url
)
const
previewImage
=
(
url
)
=>
{
showImagePreview
({
images
:
[
url
],
})
}
}
// // 判断是否为视频文件
// 视频相关状态
// isVideo(file) {
const
videoRef
=
ref
(
null
)
// return file.type && file.type.startsWith('video/');
const
isPlaying
=
ref
(
true
)
// },
// 视频拍照
// 加载完第一帧暂停
// 视频全屏预览相关状态
const
handleVideoLoaded
=
()
=>
{
const
videoDialogVisible
=
ref
(
false
);
isPlaying
.
value
=
false
const
currentVideo
=
ref
(
null
);
videoRef
.
value
.
pause
()
}
// 显示视频全屏预览
const
videoRef
=
ref
(
null
);
const
isPlaying
=
ref
(
false
);
// const showVideoFullscreen = async (url) => {
// console.log('播放视频')
// const video = videoRef.value;
// if (!video) return;
// try {
// // 控制播放/暂停
// if (video.paused) {
// await video.play();
// isPlaying.value = true;
// } else {
// video.pause();
// isPlaying.value = false;
// }
// // 请求全屏(跨浏览器兼容)
// if (!document.fullscreenElement) {
// // 尝试标准API和各种浏览器前缀
// if (video.requestFullscreen) {
// await video.requestFullscreen().catch(err => {
// console.error('标准全屏API失败:', err);
// });
// }
// // iOS Safari兼容
// else if (video.webkitEnterFullscreen) {
// // iOS 12及以下版本专用API
// video.webkitEnterFullscreen();
// }
// else if (video.webkitRequestFullscreen) {
// await video.webkitRequestFullscreen().catch(err => {
// console.error('WebKit全屏API失败:', err);
// });
// }
// // 如果所有全屏API都失败,至少确保视频可以播放
// }
// } catch (err) {
// console.error('视频操作失败:', err);
// // 失败时提供降级体验
// try {
// await video.play();
// isPlaying.value = true;
// } catch (innerErr) {
// console.error('降级播放也失败:', innerErr);
// }
// }
// };
// 视频相关状态
const
videoPosters
=
ref
({});
const
videoPosters
=
ref
({});
const
videoLoadingStatus
=
ref
({});
// 跟踪每个视频的加载状态
const
videoLoadingStatus
=
ref
({});
// 跟踪每个视频的加载状态
const
isWebView
=
ref
(
false
);
const
isWebView
=
ref
(
false
);
// 组件挂载时检测环境
// 组件挂载时检测环境
onMounted
(()
=>
{
// onMounted(() => {
detectEnvironment
();
// 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
=
()
=>
{
// iOS webView检测
if
(
isiOS
)
{
// 检查是否存在UIWebView特有的属性
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
];
}
// 如果正在生成中,返回一个临时占位符
if
(
videoLoadingStatus
.
value
[
url
])
{
return
generateDefaultVideoPoster
();
}
// 否则开始生成海报图
generateVideoPoster
(
url
);
// 立即返回默认海报,生成完成后会自动更新
return
generateDefaultVideoPoster
();
};
// 视频加载错误处理
// // 检测是否在webView中
const
onVideoError
=
(
url
)
=>
{
// const detectEnvironment = () => {
console
.
error
(
'视频加载错误:'
,
url
);
// // 检测是否在iOS设备上
// 设置默认海报图
// const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if
(
!
videoPosters
.
value
[
url
])
{
videoPosters
.
value
[
url
]
=
generateDefaultVideoPoster
();
// // 检测是否在webView中 - 可以根据实际项目中的webView特征调整
}
// // 这里使用了一些常见的webView特征检测方法
// 重置加载状态
// const isInWebView = () => {
videoLoadingStatus
.
value
[
url
]
=
false
;
// // iOS webView检测
};
// if (isiOS) {
// // 检查是否存在UIWebView特有的属性
// const hasUIWebViewProperty = typeof navigator.standalone !== 'undefined' && !navigator.standalone;
// // 检查userAgent中是否包含特定App的标识
// const hasCustomUserAgent = /AppName|CustomWebView/i.test(navigator.userAgent);
// return hasUIWebViewProperty || hasCustomUserAgent;
// }
// return false;
// };
// 修改onVideoLoadedData函数以接收更多参数
// isWebView.value = isInWebView();
const
onVideoLoadedData
=
({
value
:
video
,
url
})
=>
{
// };
if
(
!
video
)
return
;
// 对于非webView环境,可以考虑预加载视频的第一帧作为海报
if
(
!
isWebView
.
value
)
{
try
{
// 尝试获取视频的第一帧作为海报图
const
canvas
=
document
.
createElement
(
'canvas'
);
const
ctx
=
canvas
.
getContext
(
'2d'
);
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
[
url
]
=
posterUrl
;
video
.
poster
=
posterUrl
;
}
catch
(
error
)
{
console
.
error
(
'生成视频海报失败:'
,
error
);
}
}
// 如果是webView环境,确保视频不会自动播放
if
(
isWebView
.
value
)
{
video
.
pause
();
}
};
// 修改showVideoFullscreen函数,添加环境检测逻辑
// 修改showVideoFullscreen函数,添加环境检测逻辑
const
showVideoFullscreen
=
async
(
url
)
=>
{
const
showVideoFullscreen
=
async
(
url
)
=>
{
console
.
log
(
'播放视频'
);
const
video
=
videoRef
.
value
;
const
video
=
videoRef
.
value
;
if
(
!
video
)
return
;
if
(
!
video
)
return
;
try
{
try
{
// 先尝试播放视频
// 先尝试播放视频
if
(
video
.
paused
)
{
await
video
.
play
();
await
video
.
play
();
isPlaying
.
value
=
true
;
}
else
{
video
.
pause
();
isPlaying
.
value
=
false
;
}
// 然后尝试全屏(跨浏览器兼容)
// 然后尝试全屏(跨浏览器兼容)
if
(
!
document
.
fullscreenElement
)
{
if
(
!
document
.
fullscreenElement
)
{
...
@@ -774,26 +559,6 @@ const requestFullscreen = (element) => {
...
@@ -774,26 +559,6 @@ const requestFullscreen = (element) => {
});
});
};
};
// 关闭视频全屏预览
const
closeVideoFullscreen
=
()
=>
{
videoDialogVisible
.
value
=
false
;
// 确保视频停止播放
const
videoElement
=
document
.
getElementById
(
'fullscreen-video'
);
if
(
videoElement
)
{
videoElement
.
pause
();
}
};
// 处理视频点击事件
const
handleVideoClick
=
(
event
)
=>
{
// 判断点击的是否是视频控件区域
// 当点击视频控件时,event.target 会指向具体的控件元素而不是 video 标签本身
if
(
event
.
target
===
event
.
currentTarget
)
{
// 只有点击视频播放区域(非控件)才切换播放/暂停状态
toggleVideoPlayback
();
}
};
/*************** 渠道类型 ***************/
/*************** 渠道类型 ***************/
const
typeOptions
=
ref
(
typeOption
)
// 渠道类型
const
typeOptions
=
ref
(
typeOption
)
// 渠道类型
const
showTypePopup
=
ref
(
false
)
const
showTypePopup
=
ref
(
false
)
...
@@ -1020,13 +785,13 @@ const deleteLongTimePictureArr = async (file, { name, index }) => {
...
@@ -1020,13 +785,13 @@ const deleteLongTimePictureArr = async (file, { name, index }) => {
}
}
::v-deep
(
.van-uploader__file
)
{
::v-deep
(
.van-uploader__file
)
{
/*
.van-badge__wrapper {
.van-badge__wrapper
{
display
:
none
;
display
:
none
;
}
}
.van-ellipsis
{
.van-ellipsis
{
display
:
none
;
display
:
none
;
}
*/
}
}
}
.upload-text
{
.upload-text
{
...
@@ -1046,6 +811,10 @@ const deleteLongTimePictureArr = async (file, { name, index }) => {
...
@@ -1046,6 +811,10 @@ const deleteLongTimePictureArr = async (file, { name, index }) => {
border-radius
:
8px
;
border-radius
:
8px
;
overflow
:
hidden
;
overflow
:
hidden
;
.video-wrap
{
width
:
100%
;
height
:
100%
;
.play-hint
{
.play-hint
{
position
:
absolute
;
position
:
absolute
;
left
:
0
;
left
:
0
;
...
@@ -1054,6 +823,9 @@ const deleteLongTimePictureArr = async (file, { name, index }) => {
...
@@ -1054,6 +823,9 @@ const deleteLongTimePictureArr = async (file, { name, index }) => {
height
:
100%
;
height
:
100%
;
}
}
}
}
}
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论