This commit is contained in:
lcw
2025-10-28 18:26:31 +08:00
parent 240934b2c9
commit eb27d2bd11
234 changed files with 467 additions and 464 deletions

View File

@ -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: {

View File

@ -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(() => {

View File

@ -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>

View File

@ -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)

View File

@ -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({

View File

@ -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
}

View File

@ -1,4 +1,4 @@
import path from "path";
import path from 'path'
/*
*获取所有的子集路由
*/

View File

@ -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;

View File

@ -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>

View File

@ -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>