feat: 完成云存的UI及功能
This commit is contained in:
parent
8c19ae5a2e
commit
6f9fb8b2df
57
package-lock.json
generated
57
package-lock.json
generated
@ -12,6 +12,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.7.1",
|
"@commitlint/cli": "^19.7.1",
|
||||||
"@commitlint/config-conventional": "^19.7.1",
|
"@commitlint/config-conventional": "^19.7.1",
|
||||||
|
"@iconify-json/material-symbols": "^1.2.22",
|
||||||
|
"@iconify-json/solar": "^1.2.2",
|
||||||
|
"@iconify/utils": "^2.3.0",
|
||||||
|
"@unocss/preset-icons": "^66.1.2",
|
||||||
"commitizen": "^4.3.1",
|
"commitizen": "^4.3.1",
|
||||||
"crc": "^4.3.2",
|
"crc": "^4.3.2",
|
||||||
"cz-git": "^1.11.0",
|
"cz-git": "^1.11.0",
|
||||||
@ -1049,6 +1053,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@iconify-json/material-symbols": {
|
||||||
|
"version": "1.2.22",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.22.tgz",
|
||||||
|
"integrity": "sha512-raleOIRt8iPtwAkDzmw/c5zb06nIaicsYs5bZ3yfRjBxuYT/UYNa2ZFQQMl3uuTTiZuUXwFa1M8PJW3CFRAN0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iconify-json/solar": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify-json/solar/-/solar-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-lcTb6DWL4HZObiY1W3fHfuxxuQHUc6CFHFeywKEx7Ry0k+dU6POZCMC7oVLr0F8vuf+KgaQ3oOoGO/yFzOwrNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "CC-BY-4.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@iconify/types": {
|
"node_modules/@iconify/types": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||||
@ -1771,20 +1795,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@unocss/preset-icons": {
|
"node_modules/@unocss/preset-icons": {
|
||||||
"version": "65.5.0",
|
"version": "66.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@unocss/preset-icons/-/preset-icons-65.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@unocss/preset-icons/-/preset-icons-66.1.2.tgz",
|
||||||
"integrity": "sha512-lSwMNtj4nufpQDBFoioAM9S6hP8028lA9fLFM3Vw+KmI10/3TaZyOaCXJVH5UdsfNWexGGo/Qo+K1YFWfXLZ8A==",
|
"integrity": "sha512-14390jFBJ2anuKvjX9TeRCm7adNjR/mey0bh0+S/k/5W3VugIY2y0E+OH3m+sx5d/5ZUYbYkUGsmtuKbVNwwxQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/utils": "^2.3.0",
|
"@iconify/utils": "^2.3.0",
|
||||||
"@unocss/core": "65.5.0",
|
"@unocss/core": "66.1.2",
|
||||||
"ofetch": "^1.4.1"
|
"ofetch": "^1.4.1"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@unocss/preset-icons/node_modules/@unocss/core": {
|
||||||
|
"version": "66.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@unocss/core/-/core-66.1.2.tgz",
|
||||||
|
"integrity": "sha512-mN9h1hHEuhDcdbI4z74o7UnxlBZYVsJpYcdC1YLWBKROcLYTkuyZ7hgBzpo1FBNox2Bt3JnrSinVDmc44Bxjow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@unocss/preset-mini": {
|
"node_modules/@unocss/preset-mini": {
|
||||||
"version": "65.5.0",
|
"version": "65.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@unocss/preset-mini/-/preset-mini-65.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@unocss/preset-mini/-/preset-mini-65.5.0.tgz",
|
||||||
@ -9402,6 +9436,21 @@
|
|||||||
"unplugin-transform-class": "^0.5.3"
|
"unplugin-transform-class": "^0.5.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/unocss/node_modules/@unocss/preset-icons": {
|
||||||
|
"version": "65.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@unocss/preset-icons/-/preset-icons-65.5.0.tgz",
|
||||||
|
"integrity": "sha512-lSwMNtj4nufpQDBFoioAM9S6hP8028lA9fLFM3Vw+KmI10/3TaZyOaCXJVH5UdsfNWexGGo/Qo+K1YFWfXLZ8A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/utils": "^2.3.0",
|
||||||
|
"@unocss/core": "65.5.0",
|
||||||
|
"ofetch": "^1.4.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unplugin": {
|
"node_modules/unplugin": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz",
|
||||||
|
|||||||
@ -7,6 +7,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.7.1",
|
"@commitlint/cli": "^19.7.1",
|
||||||
"@commitlint/config-conventional": "^19.7.1",
|
"@commitlint/config-conventional": "^19.7.1",
|
||||||
|
"@iconify-json/material-symbols": "^1.2.22",
|
||||||
|
"@iconify-json/solar": "^1.2.2",
|
||||||
|
"@iconify/utils": "^2.3.0",
|
||||||
|
"@unocss/preset-icons": "^66.1.2",
|
||||||
"commitizen": "^4.3.1",
|
"commitizen": "^4.3.1",
|
||||||
"crc": "^4.3.2",
|
"crc": "^4.3.2",
|
||||||
"cz-git": "^1.11.0",
|
"cz-git": "^1.11.0",
|
||||||
|
|||||||
15
pages.json
15
pages.json
@ -32,22 +32,11 @@
|
|||||||
"disableScroll": true
|
"disableScroll": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "videoDetail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "视频播放"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "videoLog",
|
"path": "videoLog",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "云存"
|
"navigationBarTitleText": "门锁记录",
|
||||||
}
|
"disableScroll": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "videoEdit",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "视频编辑"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -214,7 +214,7 @@
|
|||||||
class="menu-main-image"
|
class="menu-main-image"
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/icon_record.png"
|
src="https://oss-lock.xhjcn.ltd/mp/icon_record.png"
|
||||||
></image>
|
></image>
|
||||||
<view>视频日志</view>
|
<view>门锁记录</view>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
v-if="$bluetooth.currentLockInfo.keyRight === 1"
|
v-if="$bluetooth.currentLockInfo.keyRight === 1"
|
||||||
|
|||||||
@ -1,219 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view>
|
|
||||||
<view v-if="list.length === 0 && requestFinished">
|
|
||||||
<image
|
|
||||||
class="empty-list"
|
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/background_empty_list.png"
|
|
||||||
mode="aspectFill"
|
|
||||||
></image>
|
|
||||||
<view class="empty-list-text">暂无数据</view>
|
|
||||||
</view>
|
|
||||||
<view v-else>
|
|
||||||
<view v-for="(item, index) in list" :key="index" class="mt-4">
|
|
||||||
<view>{{ item.date }}</view>
|
|
||||||
<view class="mt-2 flex flex-wrap gap-[19rpx]">
|
|
||||||
<view v-for="video in item.recordList" :key="video.recordId" @click="handleVideo(video)">
|
|
||||||
<image :src="video.imagesUrl" class="w-224 h-224 rounded-xl relative" mode="aspectFill">
|
|
||||||
<view v-if="type === 'select'" class="absolute inset-0 bg-black bg-opacity-30">
|
|
||||||
<image
|
|
||||||
v-if="selectList.includes(video)"
|
|
||||||
class="w-40 h-40 top-16 right-16 absolute"
|
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/icon_select.png"
|
|
||||||
></image>
|
|
||||||
<image
|
|
||||||
v-else
|
|
||||||
class="w-40 h-40 top-16 right-16 absolute"
|
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/icon_not_select.png"
|
|
||||||
></image>
|
|
||||||
</view>
|
|
||||||
<view
|
|
||||||
v-else
|
|
||||||
class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center"
|
|
||||||
>
|
|
||||||
<up-icon name="play-right-fill" color="#ffffff" size="40rpx"></up-icon>
|
|
||||||
</view>
|
|
||||||
</image>
|
|
||||||
<view class="text-xs">{{ timeFormat(video.operateDate, 'yyyy-mm-dd hh:MM') }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { timeFormat } from 'uview-plus'
|
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { useBasicStore } from '@/stores/basic'
|
|
||||||
import { useBluetoothStore } from '@/stores/bluetooth'
|
|
||||||
import { getVideoList } from '@/api/sdk'
|
|
||||||
|
|
||||||
const $basic = useBasicStore()
|
|
||||||
const $bluetooth = useBluetoothStore()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: 'list'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['change'])
|
|
||||||
|
|
||||||
const requestFinished = ref(false)
|
|
||||||
|
|
||||||
const list = ref([])
|
|
||||||
const selectList = ref([])
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getList()
|
|
||||||
requestFinished.value = true
|
|
||||||
})
|
|
||||||
|
|
||||||
const getList = async () => {
|
|
||||||
const res = await getVideoList({
|
|
||||||
lockId: $bluetooth.currentLockInfo.lockId
|
|
||||||
})
|
|
||||||
if (res.code === 0) {
|
|
||||||
// list.value = res.data
|
|
||||||
|
|
||||||
list.value = [
|
|
||||||
{
|
|
||||||
date: '2025-04-08',
|
|
||||||
recordList: [
|
|
||||||
{
|
|
||||||
recordId: 1,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl:
|
|
||||||
'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2025-04-07',
|
|
||||||
recordList: [
|
|
||||||
{
|
|
||||||
recordId: 2,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl: 'http://vjs.zencdn.net/v/oceans.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
recordId: 3,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl: 'http://www.w3school.com.cn/example/html5/mov_bbb.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2025-04-06',
|
|
||||||
recordList: [
|
|
||||||
{
|
|
||||||
recordId: 4,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl: 'https://www.w3schools.com/html/movie.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
recordId: 5,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl: 'https://media.w3.org/2010/05/sintel/trailer.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
recordId: 6,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl:
|
|
||||||
'https://stream7.iqilu.com/10339/upload_transcode/202002/09/20200209105011F0zPoYzHry.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2025-04-05',
|
|
||||||
recordList: [
|
|
||||||
{
|
|
||||||
recordId: 7,
|
|
||||||
imagesUrl:
|
|
||||||
'https://q0.itc.cn/q_70/images03/20250331/84b2646fc92d4ea0b12f1b134652c807.jpeg',
|
|
||||||
videoUrl: 'http://vjs.zencdn.net/v/oceans.mp4',
|
|
||||||
operateDate: '1745423939000'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: res.message,
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleVideo = item => {
|
|
||||||
if (props.type === 'select') {
|
|
||||||
const isExist = selectList.value.find(data => data.recordId === item.recordId)
|
|
||||||
if (isExist) {
|
|
||||||
selectList.value = selectList.value.filter(data => data.recordId !== item.recordId)
|
|
||||||
} else {
|
|
||||||
selectList.value.push(item)
|
|
||||||
}
|
|
||||||
emit('change', selectList.value)
|
|
||||||
} else {
|
|
||||||
$basic.routeJump({
|
|
||||||
type: props.type === 'list' ? 'navigateTo' : 'redirectTo',
|
|
||||||
name: 'videoDetail',
|
|
||||||
params: {
|
|
||||||
video: JSON.stringify(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectAll = isSelectAll => {
|
|
||||||
if (props.type === 'select') {
|
|
||||||
if (isSelectAll) {
|
|
||||||
list.value.forEach(item => {
|
|
||||||
item.recordList.forEach(video => {
|
|
||||||
selectList.value.push(video)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
selectList.value = []
|
|
||||||
}
|
|
||||||
emit('change', selectList.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const refresh = () => {
|
|
||||||
getList()
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
refresh,
|
|
||||||
selectAll
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.empty-list {
|
|
||||||
width: 150rpx;
|
|
||||||
height: 150rpx;
|
|
||||||
margin: 300rpx auto 20rpx 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-list-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #999999;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view>
|
|
||||||
<view class="flex justify-between items-center px-2.5 py-2">
|
|
||||||
<view>已选{{ selectList.length }}项</view>
|
|
||||||
<view @click="selectAll">{{ isSelectAll ? '取消全选' : '全选' }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="mx-2.5 pb-25">
|
|
||||||
<VideoList ref="videoListRef" type="select" @change="handleChange" />
|
|
||||||
</view>
|
|
||||||
<view class="fixed bottom-0 flex justify-center w-full w-300 mx-auto bg-white">
|
|
||||||
<view class="flex justify-between w-200 mr-4 h-160">
|
|
||||||
<view @click="handleDownload">
|
|
||||||
<view class="flex flex-col items-center">
|
|
||||||
<image
|
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/icon_video_download.png"
|
|
||||||
class="w-50 h-50"
|
|
||||||
></image>
|
|
||||||
<view class="mt-2">下载</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view @click="handleDelete">
|
|
||||||
<view class="flex flex-col items-center">
|
|
||||||
<image
|
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/icon_video_delete.png"
|
|
||||||
class="w-50 h-50"
|
|
||||||
></image>
|
|
||||||
<view class="mt-2">删除</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { getCurrentInstance, ref } from 'vue'
|
|
||||||
import VideoList from './VideoList.vue'
|
|
||||||
import { deleteVideo } from '@/api/sdk'
|
|
||||||
|
|
||||||
const instance = getCurrentInstance().proxy
|
|
||||||
const eventChannel = instance.getOpenerEventChannel()
|
|
||||||
|
|
||||||
const videoListRef = ref(null)
|
|
||||||
|
|
||||||
const isSelectAll = ref(false)
|
|
||||||
|
|
||||||
const selectList = ref([])
|
|
||||||
|
|
||||||
const pending = ref(false)
|
|
||||||
|
|
||||||
const handleChange = list => {
|
|
||||||
selectList.value = list
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectAll = () => {
|
|
||||||
isSelectAll.value = !isSelectAll.value
|
|
||||||
videoListRef.value.selectAll(isSelectAll.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDownload = () => {
|
|
||||||
console.log('下载')
|
|
||||||
if (selectList.value.length === 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请选择要下载的视频',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (selectList.value.length > 1) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '视频暂不支持批量下载',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uni.showLoading({
|
|
||||||
title: '下载中'
|
|
||||||
})
|
|
||||||
pending.value = true
|
|
||||||
const url = selectList.value[0].videoUrl
|
|
||||||
uni.downloadFile({
|
|
||||||
url,
|
|
||||||
success: res => {
|
|
||||||
uni.saveVideoToPhotosAlbum({
|
|
||||||
filePath: res.tempFilePath,
|
|
||||||
success: () => {
|
|
||||||
uni.hideLoading()
|
|
||||||
pending.value = false
|
|
||||||
uni.showToast({
|
|
||||||
title: '下载成功',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.hideLoading()
|
|
||||||
pending.value = false
|
|
||||||
uni.showToast({
|
|
||||||
title: '下载失败',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.hideLoading()
|
|
||||||
pending.value = false
|
|
||||||
uni.showToast({
|
|
||||||
title: '下载失败',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
if (selectList.value.length === 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请选择要删除的视频',
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
uni.showLoading({
|
|
||||||
title: '删除中'
|
|
||||||
})
|
|
||||||
pending.value = true
|
|
||||||
const idList = selectList.value.map(item => item.recordId)
|
|
||||||
const { code, message } = await deleteVideo(idList)
|
|
||||||
uni.hideLoading()
|
|
||||||
pending.value = false
|
|
||||||
if (code === 0) {
|
|
||||||
videoListRef.value.refresh()
|
|
||||||
eventChannel.emit('refresh')
|
|
||||||
uni.showToast({
|
|
||||||
title: '删除成功',
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: message,
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,51 +1,337 @@
|
|||||||
<template>
|
<template>
|
||||||
<view>
|
<view>
|
||||||
<view class="bg-#f5f4f8 flex items-center mx-2.5 my-4 p-4 rounded-xl shadow-sm">
|
<view class="m-4" v-if="info">
|
||||||
<view>
|
<swiper indicator-dots autoplay circular class="h-195">
|
||||||
<view>3天滚动储存</view>
|
<swiper-item>
|
||||||
<view class="text-#999999 mt-2 text-sm">
|
<image
|
||||||
{{ appName }}已为本设备免费提供3天滚动视频储存服务
|
mode="widthFix"
|
||||||
|
src="https://xhj-starlock.oss-cn-shenzhen.aliyuncs.com/mp/swiper_video_ai.png"
|
||||||
|
class="w-full rounded-2xl"
|
||||||
|
/>
|
||||||
|
</swiper-item>
|
||||||
|
<swiper-item>
|
||||||
|
<image
|
||||||
|
mode="widthFix"
|
||||||
|
src="https://xhj-starlock.oss-cn-shenzhen.aliyuncs.com/mp/swiper_video_log.png"
|
||||||
|
class="w-full rounded-2xl"
|
||||||
|
/>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
|
||||||
|
<view class="text-sm mt-2 flex mx-2" :style="{ color: color }">
|
||||||
|
<div class="i-solar:cloud-upload-linear size-40 mr-1"></div>
|
||||||
|
{{ info.desc }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<scroll-view
|
||||||
|
v-if="deviceInfo"
|
||||||
|
scroll-y="true"
|
||||||
|
:lower-threshold="100"
|
||||||
|
:style="{ height: deviceInfo.screenHeight - deviceInfo.safeArea.top - 145 + 'px' }"
|
||||||
|
:refresher-enabled="true"
|
||||||
|
@refresherrefresh="onRefresh"
|
||||||
|
@scrolltolower="onScrollToLower"
|
||||||
|
:refresher-triggered="refresherTriggered"
|
||||||
|
>
|
||||||
|
<view class="pb-40">
|
||||||
|
<view v-if="groupedList.length === 0 && requestFlag">
|
||||||
|
<image
|
||||||
|
class="w-[150rpx] h-[150rpx] mt-[300rpx] mx-auto mb-[20rpx] ml-[50%] -translate-x-1/2"
|
||||||
|
src="https://oss-lock.xhjcn.ltd/mp/background_empty_list.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
></image>
|
||||||
|
<view class="text-[32rpx] text-[#999999] text-center">暂无数据</view>
|
||||||
|
</view>
|
||||||
|
<view v-else>
|
||||||
|
<view v-for="group in groupedList" :key="group.date" class="mb-2">
|
||||||
|
<view class="px-4 py-2 text-gray-600 text-sm font-medium font-bold">
|
||||||
|
{{ group.date }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-for="item in group.items"
|
||||||
|
:key="item.event_id"
|
||||||
|
class="px-4 py-1 border-b border-gray-200 text-base"
|
||||||
|
>
|
||||||
|
<view class="mx-2">
|
||||||
|
<view class="flex items-start">
|
||||||
|
<view class="bg-#63b8af size-15 rounded-full mr-2 mt-1.5"></view>
|
||||||
|
<view class="w-full">
|
||||||
|
<view class="font-bold flex justify-between w-full">
|
||||||
|
<view>{{ item.event_type_name }}</view>
|
||||||
|
<view
|
||||||
|
class="text-sm text-red px-1 py-0.5"
|
||||||
|
@click="deleteEvent(item.event_id)"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="text-sm text-#999999 mt-0.5">
|
||||||
|
{{ formatTime(item.event_time) }}
|
||||||
|
</view>
|
||||||
|
<view class="font-bold mt-1 text-#999999">
|
||||||
|
{{ item.text }}
|
||||||
|
</view>
|
||||||
|
<view v-if="item.video !== '' && item.video_expire_at > Date.now() / 1000">
|
||||||
|
<video :src="item.video" class="w-300"></video>
|
||||||
|
<view class="text-sm text-#999999 mt-1">
|
||||||
|
{{ formatRemainingTime(item.video_expire_at) }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="item.preview_image !== ''">
|
||||||
|
<image :src="item.preview_image" class="w-300" mode="widthFix" />
|
||||||
|
<view v-if="item.video !== ''" class="text-sm text-#999999 mt-1">
|
||||||
|
{{ formatRemainingTime(item.video_expire_at) }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-none whitespace-nowrap mx-1.5">去升级</view>
|
</scroll-view>
|
||||||
<up-icon name="arrow-right" />
|
<view
|
||||||
</view>
|
v-if="list.length > 0"
|
||||||
<view class="mx-2.5 pb-10">
|
class="fixed bottom-70 right-16 rounded-full bg-red p-2"
|
||||||
<view class="flex items-center justify-between">
|
@click="resetLog"
|
||||||
<view>全部视频</view>
|
>
|
||||||
<image
|
<div class="i-material-symbols:delete-outline-rounded size-70 color-white"></div>
|
||||||
src="https://oss-lock.xhjcn.ltd/mp/icon_edit.png"
|
|
||||||
@click="handleEdit"
|
|
||||||
class="w-48 h-48"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<VideoList ref="videoListRef" />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, computed } from 'vue'
|
||||||
import env from '@/config/env'
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import VideoList from './VideoList.vue'
|
import { useBluetoothStore } from '@/stores/bluetooth'
|
||||||
|
import { passthrough } from '@/api/sdk'
|
||||||
import { useBasicStore } from '@/stores/basic'
|
import { useBasicStore } from '@/stores/basic'
|
||||||
|
|
||||||
|
const $bluetooth = useBluetoothStore()
|
||||||
const $basic = useBasicStore()
|
const $basic = useBasicStore()
|
||||||
|
const info = ref()
|
||||||
|
|
||||||
|
const pageSize = 50
|
||||||
|
const list = ref([])
|
||||||
|
const pageNo = ref(1)
|
||||||
|
const total = ref(0)
|
||||||
|
const refresherTriggered = ref(false)
|
||||||
|
|
||||||
|
const requestFlag = ref(false)
|
||||||
|
|
||||||
|
const deviceInfo = ref(null)
|
||||||
|
|
||||||
const appName = ref('')
|
|
||||||
const videoListRef = ref(null)
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
appName.value = await env[await getApp().globalData.getEnvConfig()].appName
|
deviceInfo.value = await $basic.getDeviceInfo()
|
||||||
|
await Promise.all([getInfo(), getList()])
|
||||||
|
|
||||||
|
requestFlag.value = true
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleEdit = () => {
|
onShow(() => {
|
||||||
$basic.routeJump({
|
if (requestFlag.value) {
|
||||||
name: 'videoEdit',
|
getInfo()
|
||||||
events: {
|
}
|
||||||
refresh: () => {
|
})
|
||||||
videoListRef.value.refresh()
|
|
||||||
|
const resetLog = async () => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定删除所有记录吗?',
|
||||||
|
success: async ({ confirm }) => {
|
||||||
|
if (confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
const { code, message } = await passthrough({
|
||||||
|
request_method: 'POST',
|
||||||
|
request_uri: '/api/v1/cloudStorage/clearStorageEvent',
|
||||||
|
post_args: { lockId: $bluetooth.currentLockInfo.lockId }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除成功',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
await getList()
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const deleteEvent = async eventId => {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定删除该记录吗?',
|
||||||
|
success: async ({ confirm }) => {
|
||||||
|
if (confirm) {
|
||||||
|
const { code, message } = await passthrough({
|
||||||
|
request_method: 'POST',
|
||||||
|
request_uri: '/api/v1/cloudStorage/delStorageEvent',
|
||||||
|
post_args: { eventId }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '删除成功',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
await getList()
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = timestamp => {
|
||||||
|
const date = new Date(timestamp * 1000)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(date.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatTime = timestamp => {
|
||||||
|
const date = new Date(timestamp * 1000)
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||||
|
return `${hours}:${minutes}:${seconds}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatRemainingTime = expireTimestamp => {
|
||||||
|
const now = Math.floor(Date.now() / 1000)
|
||||||
|
const diff = expireTimestamp - now
|
||||||
|
|
||||||
|
if (diff <= 0) {
|
||||||
|
return '视频已过期'
|
||||||
|
}
|
||||||
|
|
||||||
|
const days = Math.floor(diff / (24 * 60 * 60))
|
||||||
|
const hours = Math.floor((diff % (24 * 60 * 60)) / (60 * 60))
|
||||||
|
const minutes = Math.floor((diff % (60 * 60)) / 60)
|
||||||
|
|
||||||
|
let result = '视频'
|
||||||
|
|
||||||
|
if (days > 0) {
|
||||||
|
result += `${days}天`
|
||||||
|
}
|
||||||
|
if (hours > 0) {
|
||||||
|
result += `${hours}小时`
|
||||||
|
}
|
||||||
|
if (minutes > 0 && days === 0) {
|
||||||
|
result += `${minutes}分钟`
|
||||||
|
}
|
||||||
|
|
||||||
|
result += '后过期'
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupedList = computed(() => {
|
||||||
|
const groups = []
|
||||||
|
const dateMap = new Map()
|
||||||
|
|
||||||
|
list.value.forEach(item => {
|
||||||
|
const dateKey = formatDate(item.event_time)
|
||||||
|
|
||||||
|
if (!dateMap.has(dateKey)) {
|
||||||
|
const newGroup = {
|
||||||
|
date: dateKey,
|
||||||
|
items: []
|
||||||
|
}
|
||||||
|
groups.push(newGroup)
|
||||||
|
dateMap.set(dateKey, newGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
dateMap.get(dateKey).items.push(item)
|
||||||
|
})
|
||||||
|
|
||||||
|
return groups
|
||||||
|
})
|
||||||
|
|
||||||
|
const color = computed(() => {
|
||||||
|
if (info.value?.status === 1) {
|
||||||
|
return '#63b8af'
|
||||||
|
}
|
||||||
|
if (info.value?.status === 2) {
|
||||||
|
return 'red'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '#999999'
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
refresherTriggered.value = true
|
||||||
|
pageNo.value = 1
|
||||||
|
await getList()
|
||||||
|
refresherTriggered.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScrollToLower = async () => {
|
||||||
|
if (list.value.length < total.value) {
|
||||||
|
getList(pageNo.value + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInfo = async () => {
|
||||||
|
const { code, data, message } = await passthrough({
|
||||||
|
request_method: 'POST',
|
||||||
|
request_uri: '/api/v1/cloudStorage/getStorageServiceInfo',
|
||||||
|
post_args: {
|
||||||
|
lockId: $bluetooth.currentLockInfo.lockId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
info.value = data
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getList = async (pageNo = 1) => {
|
||||||
|
if (pageNo > 1 && (pageNo - 1) * pageSize >= total.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code, data, message } = await passthrough({
|
||||||
|
request_method: 'POST',
|
||||||
|
request_uri: '/api/v1/cloudStorage/getStorageEventList',
|
||||||
|
post_args: {
|
||||||
|
lockId: $bluetooth.currentLockInfo.lockId,
|
||||||
|
pageNo,
|
||||||
|
pageSize
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
if (pageNo === 1) {
|
||||||
|
list.value = data.list
|
||||||
|
} else {
|
||||||
|
list.value = [...list.value, ...data.list]
|
||||||
|
}
|
||||||
|
total.value = data.total
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { code, data, message }
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import presetWeapp from 'unocss-preset-weapp'
|
import presetWeapp from 'unocss-preset-weapp'
|
||||||
import { extractorAttributify, transformerClass } from 'unocss-preset-weapp/transformer'
|
import { extractorAttributify, transformerClass } from 'unocss-preset-weapp/transformer'
|
||||||
|
import presetIcons from '@unocss/preset-icons'
|
||||||
|
|
||||||
const { presetWeappAttributify, transformerAttributify } = extractorAttributify()
|
const { presetWeappAttributify, transformerAttributify } = extractorAttributify()
|
||||||
|
|
||||||
@ -8,7 +9,14 @@ export default {
|
|||||||
// https://github.com/MellowCo/unocss-preset-weapp
|
// https://github.com/MellowCo/unocss-preset-weapp
|
||||||
presetWeapp(),
|
presetWeapp(),
|
||||||
// attributify autocomplete
|
// attributify autocomplete
|
||||||
presetWeappAttributify()
|
presetWeappAttributify(),
|
||||||
|
presetIcons({
|
||||||
|
collections: {
|
||||||
|
solar: () => import('@iconify-json/solar/icons.json').then(i => i.default),
|
||||||
|
'material-symbols': () =>
|
||||||
|
import('@iconify-json/material-symbols/icons.json').then(i => i.default)
|
||||||
|
}
|
||||||
|
})
|
||||||
],
|
],
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user