diff --git a/mock/deviceStatus.ts b/mock/deviceStatus.ts index 8bbfac1..11f3a50 100644 --- a/mock/deviceStatus.ts +++ b/mock/deviceStatus.ts @@ -2,17 +2,17 @@ * @Author: donghao donghao@supervision.ltd * @Date: 2025-03-07 14:57:20 * @LastEditors: donghao donghao@supervision.ltd - * @LastEditTime: 2025-03-07 15:10:10 + * @LastEditTime: 2025-03-13 14:34:44 * @FilePath: \5G-Loading-Bay-Web\mock\deviceStatus.ts * @Description: 设备状态 */ import { MockMethod } from "vite-plugin-mock"; -import { deviceStatusListData } from "./pools/deviceStatusData"; -import { fetchCurrPageByList } from "./utils/apiMock"; +import { deviceStatusListData, deviceHistoryListData } from "./pools/deviceStatusData"; +import { fetchCurrPageByList, fetchMockSuccessFullByOther } from "./utils/apiMock"; export default [ { - url: "/api/getDeviceStatusList", + url: "/api/v1/device/device/", method: "post", response: req => { const { page, pageSize } = req.body; @@ -28,5 +28,14 @@ export default [ }) }; } + }, + { + url: "/api/v1/device/device_history/", + method: "post", + response: req => { + const { page, pageSize } = req.body; + // console.log(req); + return {...fetchMockSuccessFullByOther(deviceHistoryListData)} + } } ] as MockMethod[]; \ No newline at end of file diff --git a/mock/pools/deviceStatusData.ts b/mock/pools/deviceStatusData.ts index d5b17cd..41a8366 100644 --- a/mock/pools/deviceStatusData.ts +++ b/mock/pools/deviceStatusData.ts @@ -2,7 +2,7 @@ * @Author: donghao donghao@supervision.ltd * @Date: 2024-02-22 13:38:04 * @LastEditors: donghao donghao@supervision.ltd - * @LastEditTime: 2025-03-07 15:07:28 + * @LastEditTime: 2025-03-13 14:35:20 * @FilePath: \General-AI-Platform-Web-Client\mock\pools\deviceStatusData.ts * @Description: 设备状态数据 */ @@ -46,13 +46,130 @@ function fetchList(): Record<string, any>[] { // 告警代码MSRF-0 RL0F HTFIF-02 TLOC-1 E1AIS-05 // 设备组:核心检测组001 无尘总装组005 送料监测线02 +const mockHistroyData = [ + { + id: 10, + key: "10", + video_url: "https://www.w3schools.com/html/mov_bbb.mp4", + created_at: "2025-03-12 14:53:36", + device: 1 + }, + { + id: 11, + key: "11", + video_url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + created_at: "2025-03-12 15:20:15", + device: 1 + }, + { + id: 12, + key: "12", + video_url: "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4", + created_at: "2025-03-13 09:45:22", + device: 1 + }, + { + id: 20, + key: "10", + video_url: "https://www.w3schools.com/html/mov_bbb.mp4", + created_at: "2025-03-12 14:53:36", + device: 1 + }, + { + id: 21, + key: "11", + video_url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + created_at: "2025-03-12 15:20:15", + device: 1 + }, + { + id: 22, + key: "12", + video_url: "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4", + created_at: "2025-03-13 09:45:22", + device: 1 + }, + // { + // id: 10, + // key: "10", + // video_url: "https://www.w3schools.com/html/mov_bbb.mp4", + // created_at: "2025-03-12 14:53:36", + // device: 1 + // }, + // { + // id: 11, + // key: "11", + // video_url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + // created_at: "2025-03-12 15:20:15", + // device: 1 + // }, + // { + // id: 12, + // key: "12", + // video_url: "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4", + // created_at: "2025-03-13 09:45:22", + // device: 1 + // }, + // { + // id: 10, + // key: "10", + // video_url: "https://www.w3schools.com/html/mov_bbb.mp4", + // created_at: "2025-03-12 14:53:36", + // device: 1 + // }, + // { + // id: 11, + // key: "11", + // video_url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + // created_at: "2025-03-12 15:20:15", + // device: 1 + // }, + // { + // id: 12, + // key: "12", + // video_url: "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4", + // created_at: "2025-03-13 09:45:22", + // device: 1 + // }, + // { + // id: 10, + // key: "10", + // video_url: "https://www.w3schools.com/html/mov_bbb.mp4", + // created_at: "2025-03-12 14:53:36", + // device: 1 + // }, + // { + // id: 11, + // key: "11", + // video_url: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4", + // created_at: "2025-03-12 15:20:15", + // device: 1 + // }, + // { + // id: 12, + // key: "12", + // video_url: "https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4", + // created_at: "2025-03-13 09:45:22", + // device: 1 + // } +]; + + const currentData = fetchList(); +const currentHistroyData = mockHistroyData; +// export const deviceStatusListData = { data: { - list: currentData, + data: currentData, total: currentData.length, - page: 1, + current: 1, pageSize: 10 } }; + +export const deviceHistoryListData = { + data: currentHistroyData +} + + diff --git a/mock/utils/apiMock.ts b/mock/utils/apiMock.ts index 8445850..9e4d0ea 100644 --- a/mock/utils/apiMock.ts +++ b/mock/utils/apiMock.ts @@ -1,39 +1,47 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2025-03-07 14:58:39 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2025-03-13 14:29:26 + * @FilePath: \5G-Loading-Bay-Web\mock\utils\apiMock.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ import { failMockApiProps, successMockApiProps } from "../typing"; export function fetchMockSuccessFullByOther({ data, - msg + msg, }): successMockApiProps { // return { - // code: 0, // 0 成功 + // code: 200, // 200 成功 // success: true, // true 成功 // data: data || null, // mock业务层数据 // msg: msg | "ok", // 成功提示 // isMock: true // true 标识当前是模拟数据 // } as successMockApiProps; const result: successMockApiProps = { - code: 0, // 0 成功 + code: 200, // 200 成功 success: true, // true 成功 data: data || null, // mock业务层数据 msg: msg as string | "ok", // 成功提示 - isMock: true // true 标识当前是模拟数据 + isMock: true, // true 标识当前是模拟数据 }; return result; } export function fetchMockFailFullByOther({ data, msg }): failMockApiProps { // return { - // code: 599, // 0 成功 + // code: 599, // 200 成功 // success: true, // true 成功 // data: data || null, // mock业务层数据 // msg: msg | "fail", // 成功提示 // isMock: true // true 标识当前是模拟数据 // } as failMockApiProps; const result: failMockApiProps = { - code: 599, // 0 成功 + code: 599, // 200 成功 success: false, // true 成功 data: data || null, // mock业务层数据 msg: msg as string | "fail", // 成功提示 - isMock: true // true 标识当前是模拟数据 + isMock: true, // true 标识当前是模拟数据 }; return result; } @@ -41,11 +49,11 @@ export function fetchMockFailFullByOther({ data, msg }): failMockApiProps { // 分页展示 export function fetchCurrPageByList({ data }): successMockApiProps { // console.log("fetchCurrPageByList_data", data); - const { page, pageSize } = data; - const prevPage = page - 1; + const { current, pageSize } = data; + const prevPage = current - 1; const currPageData = { ...data, - list: data.list.slice(prevPage * pageSize, page * pageSize) + data: data.data.slice(prevPage * pageSize, current * pageSize), }; return fetchMockSuccessFullByOther({ data: currPageData }); } diff --git a/package.json b/package.json index d4223f4..d0cdd6b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dependencies": { "axios": "^1.8.3", "echarts": "^5.6.0", + "moment": "^2.30.1", "postcss-scss": "^4.0.9", "sass": "^1.85.1", "swiper": "^11.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44631d0..7cfe04c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: echarts: specifier: ^5.6.0 version: 5.6.0 + moment: + specifier: ^2.30.1 + version: 2.30.1 postcss-scss: specifier: ^4.0.9 version: 4.0.9(postcss@8.5.3) @@ -1455,6 +1458,9 @@ packages: resolution: {integrity: sha512-eQsKcWzIaZzEZ07NuEyO4Nw65g0hdWAyurVol1IPl1gahRwY+svqzfgfey8U8dahLwG44d6/RwEzuK52rSa/JQ==} hasBin: true + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + mpd-parser@1.3.1: resolution: {integrity: sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==} hasBin: true @@ -3224,6 +3230,8 @@ snapshots: dependencies: commander: 13.1.0 + moment@2.30.1: {} + mpd-parser@1.3.1: dependencies: '@babel/runtime': 7.26.10 diff --git a/src/assets/common/pause_icon.png b/src/assets/common/pause_icon.png new file mode 100644 index 0000000..f7acae2 Binary files /dev/null and b/src/assets/common/pause_icon.png differ diff --git a/src/components/videoPlayer/Player.vue b/src/components/videoPlayer/Player.vue index b6433c6..821d18a 100644 --- a/src/components/videoPlayer/Player.vue +++ b/src/components/videoPlayer/Player.vue @@ -2,10 +2,178 @@ * @Author: donghao donghao@supervision.ltd * @Date: 2025-03-12 13:48:31 * @LastEditors: donghao donghao@supervision.ltd - * @LastEditTime: 2025-03-12 13:56:15 + * @LastEditTime: 2025-03-13 15:01:16 * @FilePath: \5G-Loading-Bay-Web\src\components\videoPlayer\player.vue * @Description: 视频播放器 --> <template> <!-- 视频播放器 --> -</template> \ No newline at end of file + <div class="video-player-box"> + <video ref="videoRef" class="video-element" controls @timeupdate="handleTimeUpdate" + @loadedmetadata="handleLoadedMetadata" @play="handlePlay" @pause="handlePause" @waiting="handleWaiting" + @canplay="handleCanPlay" style="object-fit: fill;"> + <source :src="src" type="video/mp4"> + 您的浏览器不支持视频播放 + </video> + <!-- 加载状态提示 --> + <div v-if="loading" class="loading-overlay"> + <el-icon class="is-loading"> + <Loading /> + </el-icon> + </div> + </div> +</template> + +<script setup lang="ts"> +import { ref, watch, onMounted, onUnmounted } from 'vue' +import { ElIcon } from 'element-plus' +import { Loading } from '@element-plus/icons-vue' + +type ProgressData = { + currentTime: number + duration: number + progress: number +} + +const props = defineProps({ + src: { + type: String, + required: true + }, + isPlaying: { + type: Boolean, + default: false + } +}) + +const emit = defineEmits<{ + (e: 'update:progress', data: ProgressData): void + (e: 'update:duration', duration: number): void + (e: 'play'): void + (e: 'pause'): void +}>() + +const videoRef = ref<HTMLVideoElement | null>(null) +const loading = ref(false) +let currentDuration = 0 + +// 监听播放状态变化 +watch(() => props.isPlaying, (newVal) => { + if (!videoRef.value) return + newVal ? videoRef.value.play() : videoRef.value.pause() +}) + +// 监听视频源变化 +watch(() => props.src, (newVal) => { + if (!videoRef.value) return + videoRef.value.pause() + videoRef.value.src = newVal + videoRef.value.load() + if (props.isPlaying) { + videoRef.value.play() + } +}) + +// 进度更新处理 +const handleTimeUpdate = () => { + if (!videoRef.value) return + + const currentTime = videoRef.value.currentTime + const progress = currentDuration > 0 + ? (currentTime / currentDuration) * 100 + : 0 + + emit('update:progress', { + currentTime, + duration: currentDuration, + progress + }) +} + +// 元数据加载完成 +const handleLoadedMetadata = () => { + if (!videoRef.value) return + currentDuration = videoRef.value.duration + emit('update:duration', currentDuration) +} + +// 播放状态处理 +const handlePlay = () => emit('play') +const handlePause = () => emit('pause') + +// 加载状态处理 +const handleWaiting = () => loading.value = true +const handleCanPlay = () => loading.value = false + + +onMounted(() => { + if (videoRef.value) { + videoRef.value.src = props.src + } +}) + +onUnmounted(() => { + if (videoRef.value) { + videoRef.value.pause() + videoRef.value.removeAttribute('src') + videoRef.value.load() + } +}) + +// 暴露控制方法 +defineExpose({ + play: () => videoRef.value?.play(), + pause: () => videoRef.value?.pause(), + seek: (time: number) => { + if (videoRef.value) { + videoRef.value.currentTime = time + } + } +}) + + +</script> + + + +<style lang="scss"> +.video-player-box { + position: relative; + width: 100%; + margin: 0 auto; + + .video-element { + width: 100%; + height: auto; + border-radius: 8px; + } + + .loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + color: white; + } + + .el-icon.is-loading { + font-size: 32px; + animation: rotating 2s linear infinite; + } + + @keyframes rotating { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } + } +} +</style> diff --git a/src/utils/array.ts b/src/utils/array.ts new file mode 100644 index 0000000..34014af --- /dev/null +++ b/src/utils/array.ts @@ -0,0 +1,32 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2025-03-13 16:12:49 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2025-03-13 16:25:52 + * @FilePath: \5G-Loading-Bay-Web\src\utils\array.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +import moment from 'moment'; + +export function extractUniqueDatesWithMoment(data: { created_at: string }[]): string[] { + const dateSet = new Set<string>(); + data.forEach(item => { + // 使用 moment 解析 created_at 字段并格式化为 YYYY-MM-DD + const formattedDate = moment(item.created_at).format('YYYY-MM-DD'); + dateSet.add(formattedDate); + }); + return Array.from(dateSet); +} + + +export function filterDataByDate(data: { created_at: string }[], targetDate: string): { created_at: string }[] { + // 补全开始时间为目标日期的 0 点 + const startTime = moment(targetDate).startOf('day').format('YYYY-MM-DD HH:mm:ss'); + // 补全结束时间为目标日期的 23:59:59 + const endTime = moment(targetDate).endOf('day').format('YYYY-MM-DD HH:mm:ss'); + + return data.filter(item => { + const createdAt = moment(item.created_at); + return createdAt.isBetween(startTime, endTime, null, '[]'); + }); +} \ No newline at end of file diff --git a/src/utils/request/instance.ts b/src/utils/request/instance.ts index 4de71a8..3732b0a 100644 --- a/src/utils/request/instance.ts +++ b/src/utils/request/instance.ts @@ -1,3 +1,11 @@ +/* + * @Author: donghao donghao@supervision.ltd + * @Date: 2025-03-12 15:12:58 + * @LastEditors: donghao donghao@supervision.ltd + * @LastEditTime: 2025-03-13 13:42:24 + * @FilePath: \5G-Loading-Bay-Web\src\utils\request\instance.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ import Request from './index' import { config } from '@/config' import { ElLoading } from "element-plus"; @@ -20,7 +28,7 @@ const request = new Request({ } // 全局 loading 配置 - if (config.showLoading !== false) { + if (config.showLoading) { const loading = ElLoading.service({ lock: true, text: '加载中...', diff --git a/src/views/dashboard/DeviceStatus.vue b/src/views/dashboard/DeviceStatus.vue index 20bfa5b..481d302 100644 --- a/src/views/dashboard/DeviceStatus.vue +++ b/src/views/dashboard/DeviceStatus.vue @@ -44,7 +44,8 @@ </div> </div> <RealVideoModal v-model:value="isRealOpen" :info="currentRow" @close="isRealOpen = false" /> - <HistoryVideoModal /> + <HistoryVideoModal ref="historyModalRef" v-model:value="isHistoryOpen" :info="currentRow" + :historyVideos="historyVideos" @close="isHistoryOpen = false" /> </div> </template> @@ -59,6 +60,7 @@ import outLineIcon from '@/assets/common/outline_icon.png'; import errorIcon from '@/assets/common/error_icon.png'; import { getDeviceStatusApi } from '@/api/dashboard'; import { isSuccessApi } from "@/utils/forApi"; +import { nextTick } from "vue"; defineOptions({ name: "DeviceStatusIndex" }); @@ -99,6 +101,8 @@ const alarmLevelStatusEnum = { const currentRow = ref<Record<string, any>>({}); const isRealOpen = ref<Boolean>(false); // 实时视频弹窗 const isHistoryOpen = ref<Boolean>(false); // 历史视频弹窗 +const historyVideos = ref<Record<string, any>[]>([]); //历史视频数据 +const historyModalRef = ref(null); const columns = [ { @@ -120,8 +124,6 @@ const columns = [ console.log(val); const currentLevelObj = alarmLevelStatusEnum[val?.device_status] || alarmLevelStatusEnum.notFound; - - return h( "div", { @@ -153,7 +155,6 @@ const columns = [ ); } }, - // TODO 需要封装操作按钮 { type: "action", label: "操作" @@ -197,12 +198,33 @@ function openCurrent(row) { } /**打开历史视频 */ +// TODO mock 获取视频列表数据 +const fetchHistoryList = async () => { + try { + const resAll = await fetch('/api/v1/device/device_history/', { + method: 'POST' + }) + const res = await resAll.json() + if (isSuccessApi(res)) { + historyVideos.value = res.data; + isHistoryOpen.value = true; + nextTick(() => { + historyModalRef.value.loadData() + }) + console.log(res, 'fetchHistoryList_data') + + } + } catch (error) { + console.error('获取数据失败:', error) + } +} + function openHistory(row) { console.log(row, "openHistory"); currentRow.value = row; - // TODO 历史视频模块 - isHistoryOpen.value = true; + fetchHistoryList(); } + onMounted(() => { getList(); }); diff --git a/src/views/dashboard/components/HistoryVideoModal.vue b/src/views/dashboard/components/HistoryVideoModal.vue index 4936ee6..13b380d 100644 --- a/src/views/dashboard/components/HistoryVideoModal.vue +++ b/src/views/dashboard/components/HistoryVideoModal.vue @@ -1,12 +1,260 @@ <template> - <div class="historyVideoModal-wrap"> - - </div> - + <el-dialog class="historyVideoModal-wrap" v-model="show" @close="handleClose"> + <!-- 自定义标题栏 --> + <template #header="{ close, titleId, titleClass }"> + <div class="flex items-center justify-between video-dialog-header"> + <div class="flex items-center justify-center header-left"> + <div class="header-icon mr-[12px]"></div> + <p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{ + info.device_name }}</p> + </div> + </div> + </template> + <div class="flex main-content"> + <!-- 播放器 --> + <Player :src="currentVideo.video_url" :is-playing="isPlaying" @update:progress="handleProgress" + @update:duration="handleDuration" @play="isPlaying = true" @pause="isPlaying = false" /> + <div class="flex video-record-list"> + <!-- 日期筛选 --> + <div class="flex pl-[16px] pr-[24px] items-center"> + <span>时间:</span> + <el-select v-model="selectedDate" placeholder="请选择" class="custom-select record_date_select" + clearable @change="handleDateChange" @clear="handleClear"> + <el-option v-for="(item, index) in dateList" :key="item" :label="item" + :value="item"></el-option> + </el-select> + <!-- <el-date-picker v-model="selectedDate" type="date" placeholder="选择日期" + @change="handleDateChange"></el-date-picker> --> + </div> + <!-- 记录列表 --> + <el-scrollbar style="height: calc(100% - 80px)"> + <ul class="record-list-box"> + <li v-for="(item, index) in recordList" :key="item.id" + :class="{ active: currentVideo?.id === item.id }" @click="handleItemClick(item)" + class="flex items-center justify-between"> + <span class="time">{{ item.created_at }}</span> + <div :class="{ 'play-btn': true, 'playing': isPlaying && currentVideo?.id === item.id }"> + </div> + </li> + </ul> + </el-scrollbar> + </div> + </div> + </el-dialog> </template> +<script lang="ts" setup> +import Player from '@/components/videoPlayer/Player.vue' +import { ElMessage } from 'element-plus'; +import { extractUniqueDatesWithMoment, filterDataByDate } from '@/utils/array'; +interface Props { + /** 弹窗显隐 */ + value: boolean; + info: Record<string, any>; + historyVideos: Record<string, any>[]; +} +interface Emits { + (e: "update:value", val: boolean): void; +} +const props = withDefaults(defineProps<Props>(), { + value: false, + info: {}, + historyVideos: [] +}); + +const emit = defineEmits<Emits>(); + +// 组件状态 +const dateList = ref<string[]>([]); +const selectedDate = ref(); // 默认今天 +const recordList = ref([]); +const currentVideo = ref<Record<string, any>>({}) +const isPlaying = ref(false); + +const togglePlay = () => { + isPlaying.value = !isPlaying.value; +}; + +// TODO 监听进度更新 暂未使用 +const handleProgress = (data: { currentTime: number, duration: number }) => { + console.log((data.currentTime / data.duration) * 100, "handleProgress") + const progressVal = (data.currentTime / data.duration) * 100 +} + +// TODO 处理时长更新 暂未使用 +const handleDuration = (newDuration: number) => { + // duration.value = newDuration +} +// 加载数据 +const loadData = () => { + recordList.value = props.historyVideos + dateList.value = extractUniqueDatesWithMoment(recordList.value); + currentVideo.value = props.historyVideos[0]; // 切换日期时重置选中 +}; + +// 日期选择变化 +const handleDateChange = (date) => { + console.log(date, "handleDateChange_date"); + if (date) { + selectedDate.value = date; + recordList.value = filterDataByDate(toRaw(props.historyVideos), date); + currentVideo.value = recordList.value[0]; // 切换日期时重置选中 + } else { + loadData() + } +}; +// 点击列表项 +const handleItemClick = (item) => { + if (currentVideo.value?.id === item.id) { + togglePlay() // 播放 暂停 + } else { + currentVideo.value = item; + } + +}; + +// 视频播放结束 +const handleVideoEnd = () => { + // currentIndex.value = -1; +}; + +// 时间格式化 +const formatTime = (timeStr) => { + return timeStr.split(' ')[1].substring(0, 5); // 只显示时分 +}; + + +// 处理对话框关闭事件 +const handleClose = () => { + emits('close'); +}; + +const show = computed({ + get() { + return props.value; + }, + set(val: boolean) { + emit("update:value", val); + } +}); +// TODO 后续再做自动播放 +defineExpose({ loadData }) +</script> + +<style lang="scss"> +.historyVideoModal-wrap.el-dialog { + border: none; + overflow: hidden; + box-shadow: none; + background-color: transparent; + background-image: url("@/assets/common/bg_player_dialog.png"); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + width: 1100px; + height: 612px; + padding: 0; + margin-top: calc(50vh - 316px); + + .el-dialog__header.show-close { + padding: 0; + } + + .el-dialog__close { + width: 56px; + height: 56px; + color: white; + font-size: 18px; + padding-top: 4px; + padding-right: 20px; + } + + .video-dialog-header { + color: white; + padding: 0; + padding-top: 4px; + + .header-left { + padding: 0 18px; + font-weight: bold; + font-size: 18px; + + .header-icon { + margin-top: 8px; + width: 48px; + height: 48px; + background-image: url("@/assets/common/dialog_title_icon.png"); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + } + } + } + + .main-content { + padding-left: 24px; + padding-top: 24px; + + // background: red; + .video-player-box { + border-radius: 0px 0px 4px 4px; + overflow: hidden; + width: 752px; + height: 502px; + } + + .video-record-list { + flex-direction: column; + flex: 1; + color: white; + + .record_date_select { + width: 100%; + display: flex; + flex: 1; + + .el-select__wrapper { + width: 100%; + } + } + .record-list-box { + margin-top: 5px; + + li { + color: white; + height: 32px; + cursor: pointer; + padding-left: 16px; + padding-right: 24px; + + &.active, + &:hover { + background: linear-gradient(90deg, rgba(30, 54, 88, 0) 0%, #0C4FAD 53%, rgba(65, 117, 190, 0) 100%); + color: #37DBFF; + } + + .time { + flex: 1; + font-size: 14px; + } + + .play-btn { + width: 18px; + height: 18px; + background: url("@/assets/common/player_icon.png") no-repeat center center; + background-size: contain; + } + + .playing { + background: url("@/assets/common/pause_icon.png") no-repeat center center; + background-size: contain; + } + } + } + } -<style lang="scss" scoped> + } +} </style> \ No newline at end of file diff --git a/src/views/dashboard/components/RealVideoModal.vue b/src/views/dashboard/components/RealVideoModal.vue index 765b062..bb7ac70 100644 --- a/src/views/dashboard/components/RealVideoModal.vue +++ b/src/views/dashboard/components/RealVideoModal.vue @@ -1,11 +1,12 @@ <template> - <el-dialog v-model="show" @close="handleClose"> + <el-dialog class="realVideoModal-wrap" v-model="show" @close="handleClose"> <!-- 自定义标题栏 --> <template #header="{ close, titleId, titleClass }"> <div class="flex items-center justify-between video-dialog-header"> <div class="flex items-center justify-center header-left"> <div class="header-icon mr-[12px]"></div> - <span>{{ info.name }}</span> + <p class="overflow-hidden whitespace-nowrap text-ellipsis max-w-[650px]">{{ + info.device_name }}</p> </div> </div> </template> @@ -28,14 +29,6 @@ interface Props { interface Emits { (e: "update:value", val: boolean): void; } - -// const props = withDefaults(defineProps<{ -// isOpen: boolean; -// info: Record<string, any>; -// }>(), { -// isOpen: false, -// info: {} -// }); const props = withDefaults(defineProps<Props>(), { value: false, info: {} @@ -66,7 +59,7 @@ const show = computed({ </script> <style lang="scss"> -.el-dialog { +.realVideoModal-wrap.el-dialog { border: none; overflow: hidden; box-shadow: none; @@ -78,6 +71,7 @@ const show = computed({ width: 805px; height: 612px; padding: 0; + margin-top: calc(50vh - 316px); .el-dialog__header.show-close { padding: 0; @@ -98,7 +92,7 @@ const show = computed({ padding-top: 4px; .header-left { - padding: 0 20px; + padding: 0 18px; font-weight: bold; font-size: 18px; diff --git a/src/views/login/Login.vue b/src/views/login/Login.vue index 23ac45d..81d2a7d 100644 --- a/src/views/login/Login.vue +++ b/src/views/login/Login.vue @@ -2,7 +2,7 @@ * @Author: donghao donghao@supervision.ltd * @Date: 2025-03-06 17:57:05 * @LastEditors: donghao donghao@supervision.ltd - * @LastEditTime: 2025-03-13 10:50:03 + * @LastEditTime: 2025-03-13 16:55:26 * @FilePath: \5G-Loading-Bay-Web\src\views\login\Login.vue * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE? --> @@ -58,8 +58,8 @@ const router = useRouter() const userStore = useUserStore() const form = reactive({ - username: 'admin', - password: 'admin', + username: '', + password: '', remember: false }) const handleLogin = async () => {