Compare commits
No commits in common. "220677785d822297c7225edb79b745a803c3f19a" and "a8b6fb4b8bb41d6abebad025cbd8d1697657fbd6" have entirely different histories.
220677785d
...
a8b6fb4b8b
@ -2,7 +2,7 @@
|
|||||||
VITE_NODE_ENV=production
|
VITE_NODE_ENV=production
|
||||||
|
|
||||||
# API 基础路径 - 实际项目中替换为真实的后端接口地址
|
# API 基础路径 - 实际项目中替换为真实的后端接口地址
|
||||||
VITE_API_BASE_URL=http://127.0.0.1:3000
|
VITE_API_BASE_URL=https://api.your-domain.com
|
||||||
|
|
||||||
# 项目基础路径
|
# 项目基础路径
|
||||||
VITE_BASE_URL=/
|
VITE_BASE_URL=/
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import request from '@/utils/request'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取视频列表
|
|
||||||
* @returns {Promise} 返回视频列表数据
|
|
||||||
*/
|
|
||||||
export function getVideoList() {
|
|
||||||
return request.get('/api/videos/list')
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import AdminLayout from '../layout/AdminLayout.vue'
|
import AdminLayout from '../layout/AdminLayout.vue'
|
||||||
import { useUserStore } from '../stores/user'
|
import { useUserStore } from '../stores/user'
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ Promise.all([
|
|||||||
])
|
])
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
@ -269,15 +269,6 @@ const router = createRouter({
|
|||||||
title: '无人机管理',
|
title: '无人机管理',
|
||||||
keepAlive: true
|
keepAlive: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'videos',
|
|
||||||
name: 'VideoList',
|
|
||||||
component: () => import(/* webpackChunkName: "aipatrol" */ '../views/AIPatrol/videos/index.vue'),
|
|
||||||
meta: {
|
|
||||||
title: '视频列表',
|
|
||||||
keepAlive: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,309 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { Search, View, Download, Refresh, Calendar, Document } from '@element-plus/icons-vue'
|
|
||||||
import { getVideoList } from '@/api/videos'
|
|
||||||
|
|
||||||
// 数据状态
|
|
||||||
const videoList = ref([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const searchText = ref('')
|
|
||||||
const pagination = ref({
|
|
||||||
current: 1,
|
|
||||||
pageSize: 12, // 默认显示12个视频
|
|
||||||
total: 0
|
|
||||||
})
|
|
||||||
|
|
||||||
// 视频查看状态
|
|
||||||
const videoModalVisible = ref(false)
|
|
||||||
const selectedVideo = ref(null)
|
|
||||||
|
|
||||||
// 获取视频列表数据
|
|
||||||
const fetchVideoList = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const response = await getVideoList()
|
|
||||||
videoList.value = response.data
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取视频列表错误:', error)
|
|
||||||
ElMessage.error('获取视频列表失败:' + error.message)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取视频URL
|
|
||||||
const getVideoUrl = (video) => {
|
|
||||||
const url = `${import.meta.env.VITE_API_BASE_URL}${video.videoUrl}`
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
// 事件处理函数
|
|
||||||
const onSearch = () => {
|
|
||||||
pagination.value.current = 1
|
|
||||||
fetchVideoList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
fetchVideoList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSizeChange = (val) => {
|
|
||||||
pagination.value.pageSize = val
|
|
||||||
fetchVideoList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCurrentChange = (val) => {
|
|
||||||
pagination.value.current = val
|
|
||||||
fetchVideoList()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleWatch = (record) => {
|
|
||||||
selectedVideo.value = record
|
|
||||||
videoModalVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDownload = (record) => {
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = getVideoUrl(record)
|
|
||||||
link.download = record.fileName
|
|
||||||
document.body.appendChild(link)
|
|
||||||
link.click()
|
|
||||||
document.body.removeChild(link)
|
|
||||||
ElMessage.success('下载成功')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工具函数
|
|
||||||
const formatFileSize = (bytes) => {
|
|
||||||
if (bytes === 0) return '0 B'
|
|
||||||
const k = 1024
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期钩子
|
|
||||||
onMounted(() => {
|
|
||||||
fetchVideoList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="video-list-container">
|
|
||||||
<!-- 标题区域 -->
|
|
||||||
<div class="page-header">
|
|
||||||
<h2 class="page-title">AI巡检视频列表</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 搜索和过滤区域 -->
|
|
||||||
<div class="table-operations">
|
|
||||||
<el-space>
|
|
||||||
<el-input
|
|
||||||
v-model="searchText"
|
|
||||||
placeholder="搜索视频名称"
|
|
||||||
:prefix-icon="Search"
|
|
||||||
style="width: 200px"
|
|
||||||
@keyup.enter="onSearch"
|
|
||||||
/>
|
|
||||||
<el-button type="primary" @click="handleRefresh">
|
|
||||||
<el-icon><Refresh /></el-icon>
|
|
||||||
刷新
|
|
||||||
</el-button>
|
|
||||||
</el-space>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 视频卡片列表 -->
|
|
||||||
<div v-loading="loading" class="video-grid">
|
|
||||||
<div
|
|
||||||
v-for="video in videoList"
|
|
||||||
:key="video.id"
|
|
||||||
class="video-card"
|
|
||||||
>
|
|
||||||
<!-- 视频预览 -->
|
|
||||||
<div class="video-preview">
|
|
||||||
<video
|
|
||||||
:src="getVideoUrl(video)"
|
|
||||||
controls
|
|
||||||
preload="metadata"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 视频信息 -->
|
|
||||||
<div class="video-info">
|
|
||||||
<h3 class="video-title" :title="video.fileName">{{ video.fileName }}</h3>
|
|
||||||
<div class="video-meta">
|
|
||||||
<el-text class="video-time">
|
|
||||||
<el-icon><Calendar /></el-icon>
|
|
||||||
{{ video.createTime }}
|
|
||||||
</el-text>
|
|
||||||
<el-text class="video-size">
|
|
||||||
<el-icon><Document /></el-icon>
|
|
||||||
{{ formatFileSize(video.fileSize) }}
|
|
||||||
</el-text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<div class="video-actions">
|
|
||||||
<el-button type="primary" @click="handleWatch(video)">
|
|
||||||
<el-icon><View /></el-icon>
|
|
||||||
查看
|
|
||||||
</el-button>
|
|
||||||
<el-button type="info" @click="handleDownload(video)">
|
|
||||||
<el-icon><Download /></el-icon>
|
|
||||||
下载
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination-container">
|
|
||||||
<el-pagination
|
|
||||||
v-model:current-page="pagination.current"
|
|
||||||
v-model:page-size="pagination.pageSize"
|
|
||||||
:total="pagination.total"
|
|
||||||
:page-sizes="[12, 24, 36, 48]"
|
|
||||||
layout="total, sizes, prev, pager, next"
|
|
||||||
@size-change="handleSizeChange"
|
|
||||||
@current-change="handleCurrentChange"
|
|
||||||
v-if="pagination.total > 0"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 视频查看弹窗 -->
|
|
||||||
<el-dialog
|
|
||||||
v-model="videoModalVisible"
|
|
||||||
title="视频查看"
|
|
||||||
width="800px"
|
|
||||||
destroy-on-close
|
|
||||||
>
|
|
||||||
<video
|
|
||||||
v-if="selectedVideo"
|
|
||||||
:src="getVideoUrl(selectedVideo)"
|
|
||||||
style="width: 100%"
|
|
||||||
controls
|
|
||||||
autoplay
|
|
||||||
/>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.video-list-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-operations {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-right: 8px; /* 为滚动条预留空间 */
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s;
|
|
||||||
border: 1px solid #ebeef5;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-card:hover {
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-preview {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
padding-top: 56.25%; /* 16:9 宽高比 */
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-preview video {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-info {
|
|
||||||
padding: 12px 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-title {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #303133;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
color: #909399;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-meta .el-text {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-container {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义滚动条样式 */
|
|
||||||
.video-grid::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-grid::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #dcdfe6;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-grid::-webkit-scrollbar-track {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -4,7 +4,6 @@ import path from 'path'
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: './',
|
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user