lcw
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-sub-menu v-if="route.children.length > 0" :index="route.path">
|
||||
<el-sub-menu v-if="route.children?.length > 0" :index="route.path">
|
||||
<template #title>
|
||||
<MenuItem :title="route.meta.title" :icon="route.meta.icon"></MenuItem>
|
||||
</template>
|
||||
@ -15,6 +15,9 @@
|
||||
<script setup>
|
||||
import MenuItem from './MenuItem.vue'
|
||||
import { defineProps } from 'vue';
|
||||
// 导入自身组件以支持递归调用
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
const SideBarItem = defineAsyncComponent(() => import('./SideBarItem.vue'))
|
||||
|
||||
defineProps({
|
||||
route: {
|
||||
|
||||
@ -27,17 +27,25 @@ const store = useStore();
|
||||
const router = useRouter();
|
||||
const routes = computed(() => {
|
||||
const fRoutes = filterRoutes(router.getRoutes());
|
||||
console.log(fRoutes);
|
||||
return generateMenus(fRoutes);
|
||||
});
|
||||
if (!store.getters.token) {
|
||||
router.push("/login");
|
||||
}
|
||||
// 改进路由初始化逻辑,避免无限刷新
|
||||
|
||||
if (router.getRoutes().length <= 7 && store.state.permission.routeReady <= 1) {
|
||||
store.commit("user/setIsReady", {});
|
||||
setTimeout(() => {
|
||||
router.go(0);
|
||||
}, 200);
|
||||
// 使用一次性标记避免无限刷新
|
||||
if (!window.routerInitialized) {
|
||||
window.routerInitialized = true;
|
||||
setTimeout(() => {
|
||||
router.go(0);
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
//默认激活项
|
||||
const route = useRoute();
|
||||
const activeMenu = computed(() => {
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
<el-scrollbar>
|
||||
<div class="meunAside noScollLine">
|
||||
<div style="height:100px"></div>
|
||||
<div style="height: calc(100% - 100px);"><SideBarMenu></SideBarMenu></div>
|
||||
|
||||
<div style="height: calc(100% - 100px);">
|
||||
<SideBarMenu></SideBarMenu>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
removeAllItem
|
||||
} from "@/utils/storage";
|
||||
// 白名单
|
||||
const whiteList = ['/login','/', '/oatuh_login', '/sso_redirect', '/editPassword', '/404', '/401']
|
||||
const whiteList = ['/login', '/', '/oatuh_login', '/sso_redirect', '/editPassword', '/404', '/401']
|
||||
/**
|
||||
* 路由前置守卫
|
||||
* to 去哪里
|
||||
@ -22,11 +22,11 @@ router.beforeEach(async (to, from, next) => {
|
||||
// 判断用户资料是否获取
|
||||
// 若不存在用户信息,则需要获取用户信息
|
||||
// 触发获取用户信息的 action,并获取用户当前权限
|
||||
await store.commit('permission/setRouteReady', true)
|
||||
await store.commit('permission/setRouteReady', 1)
|
||||
// 添加完动态路由之后,需要在进行一次主动跳转
|
||||
const afterMenuList = await getItem('menusPermission');
|
||||
// 处理用户权限,筛选出需要添加的权限
|
||||
if (store.state.permission.routes == 0) {
|
||||
if (store.state.permission.routes.length === 0) {
|
||||
const filterRoutes = await store.dispatch('permission/filterRoutes', afterMenuList)
|
||||
filterRoutes.forEach(item => {
|
||||
router.addRoute(item)
|
||||
|
||||
@ -304,11 +304,11 @@ export const publicRoutes = [
|
||||
name: "login",
|
||||
component: () => import("@/views/login/index")
|
||||
},
|
||||
{
|
||||
path: "/homeMy",
|
||||
name: "homeMy",
|
||||
component: () => import("@/views/homeMy/index") //街面巡防
|
||||
},
|
||||
// {
|
||||
// path: "/homeMy",
|
||||
// name: "homeMy",
|
||||
// component: () => import("@/views/homeMy/index") //街面巡防
|
||||
// },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
} from '@/router'
|
||||
|
||||
function filter(data, menus) {
|
||||
var newData = data.filter(x => menus ?.includes(x.name))
|
||||
var newData = data.filter(x => menus?.includes(x.name))
|
||||
newData.forEach(x => x.children && (x.children = filter(x.children, menus)))
|
||||
return newData
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import path from "path";
|
||||
import path from 'path'
|
||||
/*
|
||||
*获取所有的子集路由
|
||||
*/
|
||||
|
||||
@ -73,6 +73,13 @@ const searchConfiger = ref([
|
||||
placeholder: "请输入题目",
|
||||
showType: "input"
|
||||
},
|
||||
{
|
||||
label: "题目类型",
|
||||
prop: "type",
|
||||
placeholder: "请选择题目类型",
|
||||
showType: "select",
|
||||
options: D_BAXX_KTLX
|
||||
},
|
||||
]);
|
||||
|
||||
const queryFrom = ref({});
|
||||
@ -132,11 +139,11 @@ const getList = () => {
|
||||
let data = { ...pageData.pageConfiger, ...queryFrom.value };
|
||||
qcckPost(data, "/mosty-base/baxx/tkgl/page").then((res) => {
|
||||
console.log(res);
|
||||
|
||||
let arr = res.records || []
|
||||
arr.forEach(item => {
|
||||
item.correctAnswer = item.correctAnswer.split(',')
|
||||
item.correctAnswer =item.correctAnswer&&Array.isArray(item.correctAnswer) ? item.correctAnswer.join(',') : item.correctAnswer
|
||||
});
|
||||
console.log(arr);
|
||||
pageData.tableData = arr;
|
||||
pageData.total = res.total;
|
||||
pageData.tableConfiger.loading = false;
|
||||
|
||||
@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<el-dialog v-model="modelValue" width="50%" :show-close="false" center :fullscreen="fullscreen">
|
||||
<!-- 使用title插槽替代header插槽,Element Plus中通常使用title插槽自定义标题 -->
|
||||
<template #title>
|
||||
<div class="dialog-header-content">
|
||||
<div class="dialog-title">{{ videoTitle }}</div>
|
||||
<div class="dialog-header-actions">
|
||||
<el-icon class="header-icon" @click="zoomBox"><FullScreen /></el-icon>
|
||||
<el-icon class="header-icon" @click="handleClose"><Close /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="video-container">
|
||||
<!-- 视频播放区域 -->
|
||||
<div
|
||||
v-if="modelValue"
|
||||
ref="videoPlayerRef"
|
||||
class="video-player"
|
||||
:style="{ height: playerHeight }"
|
||||
>
|
||||
<video
|
||||
ref="videoRef"
|
||||
class="video-element"
|
||||
:src="url"
|
||||
controls
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
preload="auto"
|
||||
@loadeddata="handleVideoLoaded"
|
||||
@error="handleVideoError"
|
||||
@stalled="handleVideoStalled"
|
||||
>
|
||||
</video>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, watch, onMounted, onBeforeUnmount, nextTick, computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import {downFiles} from '@/api/instructCenter'
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 父组件传递的视频信息
|
||||
listQuery: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 视频标题
|
||||
videoTitle: {
|
||||
type: String,
|
||||
default: '测试'
|
||||
}
|
||||
});
|
||||
|
||||
// 定义组件事件
|
||||
const emit = defineEmits(['update:modelValue', 'close']);
|
||||
|
||||
// 响应式数据
|
||||
const videoPlayerRef = ref(null);
|
||||
const videoRef = ref(null);
|
||||
const player = ref(null);
|
||||
const loading = ref(false);
|
||||
const playing = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const playerHeight = ref('60vh');
|
||||
const fullscreen=ref(false);
|
||||
const videoMuted = ref(true);
|
||||
const url=ref('')
|
||||
// 计算属性:获取安全的视频源URL
|
||||
const getVideoSrc = () => {
|
||||
// 确保listQuery.fjid是字符串类型
|
||||
const fjid = JSON.parse(props.listQuery.fjid)?.map(item => item.id).join(',') || '';
|
||||
downFiles(fjid).then(res => {
|
||||
url.value=res.url
|
||||
});
|
||||
}
|
||||
// 监听对话框显示状态
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal) {
|
||||
getVideoSrc()
|
||||
} else {
|
||||
// 对话框关闭时,销毁播放器
|
||||
destroyPlayer();
|
||||
}
|
||||
});
|
||||
|
||||
const zoomBox = () => {
|
||||
fullscreen.value = !fullscreen.value;
|
||||
playerHeight.value = fullscreen.value ? '89vh' : '60vh';
|
||||
};
|
||||
|
||||
|
||||
// 停止播放
|
||||
const stopPlay = () => {
|
||||
try {
|
||||
if (player.value) {
|
||||
// 实际项目中这里应该是播放器的停止方法
|
||||
// player.value.stop();
|
||||
playing.value = false;
|
||||
ElMessage.info('视频已停止');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('停止播放失败:', error);
|
||||
ElMessage.error('停止播放失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 销毁播放器
|
||||
const destroyPlayer = () => {
|
||||
try {
|
||||
if (player.value) {
|
||||
// 实际项目中这里应该是播放器的销毁方法
|
||||
// player.value.destroy();
|
||||
player.value = null;
|
||||
playing.value = false;
|
||||
loading.value = false;
|
||||
errorMessage.value = '';
|
||||
console.log('播放器已销毁');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('销毁播放器失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭对话框
|
||||
const handleClose = () => {
|
||||
stopPlay();
|
||||
emit('update:modelValue', false);
|
||||
emit('close');
|
||||
};
|
||||
|
||||
// 视频加载完成后的处理
|
||||
const handleVideoLoaded = () => {
|
||||
console.log('视频数据加载完成');
|
||||
if (videoRef.value) {
|
||||
// 尝试播放视频
|
||||
videoRef.value.play().then(() => {
|
||||
console.log('视频播放成功');
|
||||
playing.value = true;
|
||||
}).catch(error => {
|
||||
console.error('视频加载后自动播放失败:', error);
|
||||
// 显示用户提示
|
||||
ElMessage.warning('视频需要点击播放按钮开始播放');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 视频错误处理
|
||||
const handleVideoError = (event) => {
|
||||
console.error('视频加载错误:', event.target.error);
|
||||
errorMessage.value = `视频加载失败: ${getErrorMessage(event.target.error.code)}`;
|
||||
loading.value = false;
|
||||
ElMessage.error('视频加载失败,请检查网络或视频链接');
|
||||
};
|
||||
|
||||
// 视频卡顿处理
|
||||
const handleVideoStalled = () => {
|
||||
console.warn('视频加载卡顿,尝试重新加载');
|
||||
if (videoRef.value) {
|
||||
// 尝试重新加载视频
|
||||
videoRef.value.load();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取错误消息
|
||||
const getErrorMessage = (errorCode) => {
|
||||
const errorMessages = {
|
||||
1: '用户中止了获取过程',
|
||||
2: '网络错误导致获取过程失败',
|
||||
3: '解码过程出错',
|
||||
4: '媒体格式不支持',
|
||||
5: '其他未知错误'
|
||||
};
|
||||
return errorMessages[errorCode] || '未知错误';
|
||||
};
|
||||
|
||||
// 切换静音状态
|
||||
const toggleMute = () => {
|
||||
if (videoRef.value) {
|
||||
videoRef.value.muted = !videoRef.value.muted;
|
||||
videoMuted.value = videoRef.value.muted;
|
||||
}
|
||||
};
|
||||
|
||||
// 组件卸载前清理
|
||||
onBeforeUnmount(() => {
|
||||
destroyPlayer();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/css/layout.scss";
|
||||
|
||||
.el-dialog {
|
||||
.el-dialog__header {
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
|
||||
padding: 12px 24px;
|
||||
margin: 0;
|
||||
border-bottom: 2px solid #409eff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 16px 24px 24px;
|
||||
background-color: #0a0a0a;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
// 头部内容样式
|
||||
.dialog-header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 标题样式
|
||||
.dialog-title {
|
||||
color: #0b0b0b;
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
// 头部操作按钮容器
|
||||
.dialog-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
// 头部图标样式
|
||||
.header-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #409eff;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.video-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
background-color: #000;
|
||||
border-radius: 8px;
|
||||
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
// 视频元素样式
|
||||
.video-element {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background-color: #000;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
// 全屏状态下的样式增强
|
||||
:deep(.el-dialog--fullscreen) {
|
||||
.el-dialog__header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.el-dialog__body {
|
||||
margin-top: 60px; // 为固定头部留出空间
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-controls {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -24,6 +24,7 @@
|
||||
<el-link type="primary" link @click="addEdit('edit', row)">编辑</el-link>
|
||||
<el-link type="primary" link @click="addEdit('detail', row)">详情</el-link>
|
||||
<el-link type="danger" link @click="handleDelete([row.id])">删除</el-link>
|
||||
<el-link type="primary" @click="videoDisplays(row)">视频播放</el-link>
|
||||
</template>
|
||||
</MyTable>
|
||||
<Pages @changeNo="changeNo" @changeSize="changeSize" :tableHeight="pageData.tableHeight" :pageConfiger="{
|
||||
@ -34,10 +35,12 @@
|
||||
<!-- 详情 -->
|
||||
<DetailForm ref="detailDiloag" @refresh="getList" />
|
||||
</div>
|
||||
<VideoDisplay v-model="showVideoDisplay" :videoTitle="videoTitle" :listQuery="videoDisplayData"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PageTitle from "@/components/aboutTable/PageTitle.vue";
|
||||
import VideoDisplay from "./components/videoDisplay.vue";
|
||||
import MyTable from "@/components/aboutTable/MyTable.vue";
|
||||
import Pages from "@/components/aboutTable/Pages.vue";
|
||||
import Search from "@/components/aboutTable/Search.vue";
|
||||
@ -45,6 +48,7 @@ import DetailForm from "./components/detailForm.vue";
|
||||
import { qcckGet, qcckPost, qcckDelete } from "@/api/qcckApi.js";
|
||||
import { reactive, ref, onMounted, getCurrentInstance, nextTick } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const showVideoDisplay = ref(false)
|
||||
const detailDiloag = ref();
|
||||
const searchBox = ref(); //搜索框
|
||||
const baseUrl = 'data:image/jpeg;base64,'
|
||||
@ -137,6 +141,14 @@ const tabHeightFn = () => {
|
||||
tabHeightFn();
|
||||
};
|
||||
};
|
||||
const videoDisplayData = ref();
|
||||
const videoTitle=ref('');
|
||||
// 视频播放
|
||||
const videoDisplays = (val) => {
|
||||
// videoTitle.value=val.spbt
|
||||
showVideoDisplay.value = true
|
||||
videoDisplayData.value = val
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
Reference in New Issue
Block a user