Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/H5/shehong-vehicle-supervision
| | |
| | | "version": "0.1.0",
|
| | | "private": true,
|
| | | "scripts": {
|
| | | "dev": "vue-cli-service serve",
|
| | | "dev": "vue-cli-service serve --port 8089",
|
| | | "build": "vue-cli-service build",
|
| | | "lint": "vue-cli-service lint"
|
| | | },
|
| | |
| | | "> 1%",
|
| | | "last 2 versions"
|
| | | ]
|
| | | }
|
| | | } |
| | |
| | |
|
| | | .el-message {
|
| | | z-index: 9999 !important;
|
| | | position: fixed !important;
|
| | | right: 20px !important;
|
| | | bottom: 20px !important;
|
| | | top: auto !important;
|
| | | left: auto !important;
|
| | | transform: none !important;
|
| | | min-width: 300px;
|
| | | padding: 15px 20px;
|
| | | border-radius: 8px;
|
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
| | | }
|
| | |
|
| | | #app {
|
| | |
| | | <template> |
| | | <div style="height: 100%;"> |
| | | <video id="video" style="height: 100%;width: 100%;" muted controls></video> |
| | | <div style="height: 100%; position: relative;"> |
| | | <div style="width: 100%; height: 100%; border-radius: 9px; background: #f5f5f5; display: flex; justify-content: center; align-items: center; flex-direction: column"> |
| | | <video id="video" style="width: 100%; height: 100%; border-radius: 9px; display: none" muted controls></video> |
| | | <el-empty description="暂无视频信息" :image-size="80"></el-empty> |
| | | </div> |
| | | <div v-if="showError" class="error-box" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center; z-index: 9999;"> |
| | | <div class="error-content" style="text-align: center; color: #fff;"> |
| | | <i class="el-icon-warning" style="font-size: 48px; color: #E6A23C; margin-bottom: 16px;"></i> |
| | | <p style="margin: 8px 0; font-size: 16px;">视频播放失败</p> |
| | | <p style="margin: 8px 0; font-size: 16px;">请稍后重试</p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | props: { |
| | | serverIp: { |
| | | type: String, |
| | | required: '' |
| | | required: null |
| | | }, |
| | | serverPort: { |
| | | type: Number, |
| | |
| | | type: Number, |
| | | required: null |
| | | }, |
| | | urlLink: { |
| | | type: String, |
| | | required: null, |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | flvPlayer: null, |
| | | timer: null, |
| | | showError: false |
| | | } |
| | | }, |
| | | watch: { |
| | | urlLink: { |
| | | handler(newUrl) { |
| | | if (newUrl) { |
| | | this.destroyPlayer(); |
| | | this.playDetection(); |
| | | } |
| | | }, |
| | | immediate: true |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.playDetection() |
| | | if (this.urlLink) { |
| | | this.playDetection(); |
| | | } |
| | | }, |
| | | beforeDestroy() { |
| | | this.destroyPlayer(); |
| | | }, |
| | | methods: { |
| | | playDetection() { |
| | | if (flvjs.isSupported()) { |
| | | playDetection(this.carId).then(res => { |
| | | this.showError = false; |
| | | if (!flvjs.isSupported()) { |
| | | this.showError = true; |
| | | this.$emit('video-error'); |
| | | return; |
| | | } |
| | | |
| | | playDetection(this.carId).then(res => { |
| | | if (this.flvPlayer) { |
| | | this.destroyPlayer(); |
| | | } |
| | | |
| | | try { |
| | | this.flvPlayer = flvjs.createPlayer({ |
| | | type: 'flv',//视频类型 |
| | | isLive: true,//是否为直播 |
| | | cors: true,//是否开启跨域 |
| | | hasAudio: false,//是否开启音频 |
| | | hasVideo: true,//是否开启视频 |
| | | url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 |
| | | type: 'flv', //视频类型 |
| | | isLive: true, //是否为直播 |
| | | cors: true, //是否开启跨域 |
| | | hasAudio: false, //是否开启音频 |
| | | hasVideo: true, //是否开启视频 |
| | | url: this.urlLink, // 后端拿到的视频路径 |
| | | enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 |
| | | enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 |
| | | stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb |
| | | stashInitialTime: 0.2, // 缓存初始时间。单位:秒。建议针对直播:调整为200毫秒 |
| | | seekType: 'range', // 建议将其设置为“range”模式,以便更快地加载视频数据,提高视频的实时性。 |
| | | seekType: 'range', // 建议将其设置为"range"模式,以便更快地加载视频数据,提高视频的实时性。 |
| | | lazyLoad: false, //关闭懒加载模式,从而提高视频的实时性。建议针对直播:调整为false |
| | | lazyLoadMaxDuration: 0.2, // 懒加载的最大时长。单位:秒。建议针对直播:调整为200毫秒 |
| | | deferLoadAfterSourceOpen: false // 不预先加载视频数据,在 MSE(Media Source Extensions)打开后立即加载数据,提高视频的实时性。建议针对直播:调整为false |
| | | }); |
| | | |
| | | let video = document.getElementById('video'); |
| | | this.flvPlayer.attachMediaElement(video); // video容器 |
| | | if (!video) { |
| | | throw new Error('Video element not found'); |
| | | } |
| | | |
| | | this.flvPlayer.attachMediaElement(video); |
| | | this.flvPlayer.load(); |
| | | this.flvPlayer.play().then(res => { |
| | | |
| | | this.flvPlayer.play().then(() => { |
| | | // 显示视频元素 |
| | | video.style.display = 'block'; |
| | | // 隐藏空状态 |
| | | const emptyElement = video.parentElement.querySelector('.el-empty'); |
| | | if (emptyElement) { |
| | | emptyElement.style.display = 'none'; |
| | | } |
| | | |
| | | this.timer = setInterval(() => { |
| | | playDetection(this.carId) |
| | | }, 5000) |
| | | playDetection(this.carId); |
| | | }, 5000); |
| | | }).catch(err => { |
| | | console.error('视频播放失败:', err); |
| | | this.showError = true; |
| | | this.destroyPlayer(); |
| | | }) |
| | | // 错误监听 |
| | | this.flvPlayer.on('error', (err) => { |
| | | this.destroyPlayer(); |
| | | this.$emit('video-error'); |
| | | }); |
| | | }) |
| | | } |
| | | |
| | | this.flvPlayer.on('error', (err) => { |
| | | console.error('视频播放器错误:', err); |
| | | this.showError = true; |
| | | this.destroyPlayer(); |
| | | this.$emit('video-error'); |
| | | }); |
| | | |
| | | } catch (err) { |
| | | console.error('创建播放器失败:', err); |
| | | this.showError = true; |
| | | this.destroyPlayer(); |
| | | this.$emit('video-error'); |
| | | } |
| | | }).catch(err => { |
| | | console.error('获取视频流失败:', err); |
| | | this.showError = true; |
| | | this.destroyPlayer(); |
| | | this.$emit('video-error'); |
| | | }); |
| | | }, |
| | | destroyPlayer() { |
| | | // 销毁播放器释放资源 |
| | | if (this.flvPlayer) { |
| | | if (this.timer) clearInterval(this.timer) |
| | | closeRealVideo(this.carId).then(res => { |
| | | if (this.timer) { |
| | | clearInterval(this.timer); |
| | | this.timer = null; |
| | | } |
| | | try { |
| | | this.flvPlayer.pause(); |
| | | this.flvPlayer.unload(); |
| | | this.flvPlayer.detachMediaElement(); |
| | | this.flvPlayer.destroy(); |
| | | } catch (err) { |
| | | console.error('销毁播放器失败:', err); |
| | | } finally { |
| | | this.flvPlayer = null; |
| | | }) |
| | | } |
| | | closeRealVideo(this.carId).catch(err => { |
| | | console.error('关闭视频流失败:', err); |
| | | }); |
| | | |
| | | // 恢复空状态的显示 |
| | | const video = document.getElementById('video'); |
| | | if (video) { |
| | | video.style.display = 'none'; |
| | | const emptyElement = video.parentElement.querySelector('.el-empty'); |
| | | if (emptyElement) { |
| | | emptyElement.style.display = 'block'; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style></style> |
| | | <style scoped> |
| | | /* 移除之前的样式,使用内联样式确保样式生效 */ |
| | | </style> |
| | |
| | | <template>
|
| | | <div class="sticky top0 layout">
|
| | | <div class="header relative">
|
| | | <div @click="$router.push('/home')" class="title">
|
| | | <img src="@/assets/logo.png" alt="">
|
| | | 射洪“两客一危”监管平台
|
| | | </div>
|
| | | <div></div>
|
| | | <div class="flex a-center pr--40">
|
| | | <div class="flex a-center mr--72">
|
| | | <img src="@/assets/header/photo.png" class="w--32 h--32 shrink0 mr--10"
|
| | | style="border-radius: 50%;" />
|
| | | <div class="fs-- 18 lh--25 color2">{{ $store.state.userInfo.nickName }}</div>
|
| | | </div>
|
| | | <div class="dropdown" @mouseenter="toggleDropdown(true)" @mouseleave="toggleDropdown(false)">
|
| | | <img src="@/assets/header/more.png" class="w--16 h--16" />
|
| | | <div v-if="isOpen" class="dropdown-menu">
|
| | | <div @click="clickItem(item)" class="dropdown-item" v-for="item in menuItems" :key="item.text">
|
| | | <i :class="item.icon"></i> {{ item.text }}
|
| | | </div>
|
| | | </div>
|
| | | </div>
|
| | | </div>
|
| | | <div class="sticky top0 layout">
|
| | | <div class="header relative" style="align-items: flex-end">
|
| | | <div @click="$router.push('/home')" class="title">
|
| | | <img src="@/assets/logo.png" alt="" />
|
| | | <span> 射洪两客一危监管平台 </span>
|
| | | </div>
|
| | | <div></div>
|
| | | <div class="flex a-center pr--40">
|
| | | <div class="flex a-center mr--72">
|
| | | <img
|
| | | src="@/assets/header/photo.png"
|
| | | class="w--32 h--32 shrink0 mr--10"
|
| | | style="border-radius: 50%"
|
| | | />
|
| | | <div class="fs-- 18 lh--25 color2">
|
| | | {{ $store.state.userInfo.nickName }}
|
| | | </div>
|
| | | </div>
|
| | | <div class="menu w100 bgColor1">
|
| | | <div v-for="(item, index) in routesList" :key="index" class="flex a-center h100">
|
| | | <div v-if="!item.meta || !item.meta.title" class="h100">
|
| | | <template v-for="(item2, index2) in item.children">
|
| | | <div :key="index2" @click="pushPath(item2.path)" v-if="!item2.meta.hide"
|
| | | class="flex a-center j-center h100 w--160 menuItemHover pointer"
|
| | | :class="item2.path == $route.path && 'bgColor2'">
|
| | | <img v-if="item2.meta.icon" :src="require(`@/assets/routerIcon/${item2.meta.icon}.png`)"
|
| | | class="w--15 h--15 mr--12 shrink0" />
|
| | | <div class="color1">
|
| | | {{ item2.meta.title }}
|
| | | </div>
|
| | | </div>
|
| | | </template>
|
| | | </div>
|
| | | <div v-else :class="$route.path.includes('systemManage') && 'bgColor2'"
|
| | | class="h100 w--160 menuItemHover dropdown" @mouseenter="routerDropdown(true)"
|
| | | @mouseleave="routerDropdown(false)">
|
| | | <div class="flex a-center j-center h100">
|
| | | <img :src="require(`@/assets/routerIcon/${item.meta.icon}.png`)"
|
| | | class="w--15 h--15 mr--12 shrink0" />
|
| | | <div class="color1">
|
| | | {{ item.meta.title }}
|
| | | </div>
|
| | | </div>
|
| | | <div v-if="routerIsOpen" class="dropdown-menu positionTwo">
|
| | | <template v-for="(item2, index2) in item.children">
|
| | | <div v-if="!item2.meta.hide" :key="index2" @click="pushPath(item.path + '/' + item2.path)"
|
| | | class="dropdown-item flex a-center">
|
| | | {{ item2.meta.title }}
|
| | | </div>
|
| | | </template>
|
| | | </div>
|
| | | </div>
|
| | | <div
|
| | | class="dropdown"
|
| | | @mouseenter="toggleDropdown(true)"
|
| | | @mouseleave="toggleDropdown(false)"
|
| | | >
|
| | | <img src="@/assets/header/more.png" class="w--16 h--16" />
|
| | | <div v-if="isOpen" class="dropdown-menu">
|
| | | <div
|
| | | @click="clickItem(item)"
|
| | | class="dropdown-item"
|
| | | v-for="item in menuItems"
|
| | | :key="item.text"
|
| | | >
|
| | | <i :class="item.icon"></i> {{ item.text }}
|
| | | </div>
|
| | | </div>
|
| | | </div>
|
| | | <div class="main">
|
| | | <router-view></router-view>
|
| | | </div>
|
| | | <ResetPassword v-if="passwordVisible" :row="row" :dialogVisible="passwordVisible"
|
| | | @close="passwordVisible = false, row = {}" @confirm="passwordConfirm" />
|
| | | </div>
|
| | | </div>
|
| | | <div class="menu w100 bgColor1">
|
| | | <template v-for="(item, index) in routesList">
|
| | | <template v-if="!item.meta || !item.meta.title">
|
| | | <template v-for="(item2, index2) in item.children">
|
| | | <div
|
| | | v-if="!item2.meta.hide"
|
| | | :key="index2"
|
| | | @click="pushPath(item2.path)"
|
| | | class="flex a-center j-center h100 br--18 w--160 menuItemHover pointer"
|
| | | :class="item2.path == $route.path && 'bgColor2'"
|
| | | >
|
| | | <img
|
| | | v-if="item2.meta.icon"
|
| | | :src="require(`@/assets/routerIcon/${item2.meta.icon}.png`)"
|
| | | class="w--40 h--40 mr--12 shrink0"
|
| | | />
|
| | | <div class="color1">
|
| | | {{ item2.meta.title }}
|
| | | </div>
|
| | | </div>
|
| | | </template>
|
| | | </template>
|
| | | <div
|
| | | v-else
|
| | | :key="index"
|
| | | :class="$route.path.includes('systemManage') && 'bgColor2'"
|
| | | class="h100 w--160 br--18 menuItemHover dropdown"
|
| | | @mouseenter="routerDropdown(true)"
|
| | | @mouseleave="routerDropdown(false)"
|
| | | >
|
| | | <div class="flex a-center j-center h100">
|
| | | <img
|
| | | :src="require(`@/assets/routerIcon/${item.meta.icon}.png`)"
|
| | | class="w--40 h--40 mr--12 shrink0"
|
| | | />
|
| | | <div class="color1">
|
| | | {{ item.meta.title }}
|
| | | </div>
|
| | | </div>
|
| | | <div v-if="routerIsOpen" class="dropdown-menu positionTwo">
|
| | | <template v-for="(item2, index2) in item.children">
|
| | | <div
|
| | | v-if="!item2.meta.hide"
|
| | | :key="index2"
|
| | | @click="pushPath(item.path + '/' + item2.path)"
|
| | | class="dropdown-item flex a-center"
|
| | | >
|
| | | {{ item2.meta.title }}
|
| | | </div>
|
| | | </template>
|
| | | </div>
|
| | | </div>
|
| | | </template>
|
| | | </div>
|
| | | <div class="main">
|
| | | <router-view></router-view>
|
| | | </div>
|
| | | <ResetPassword
|
| | | v-if="passwordVisible"
|
| | | :row="row"
|
| | | :dialogVisible="passwordVisible"
|
| | | @close="(passwordVisible = false), (row = {})"
|
| | | @confirm="passwordConfirm"
|
| | | />
|
| | | </div>
|
| | | </template>
|
| | |
|
| | | <script>
|
| | | import routes from '@/router/router'
|
| | | import { mapMutations } from 'vuex';
|
| | | import ResetPassword from '@/view/systemManage/user/components/resetPassWord.vue'
|
| | | import { updatePwd } from '@/view/systemManage/user/service'
|
| | | import routes from "@/router/router";
|
| | | import { mapMutations } from "vuex";
|
| | | import ResetPassword from "@/view/systemManage/user/components/resetPassWord.vue";
|
| | | import { updatePwd } from "@/view/systemManage/user/service";
|
| | | import { getRoleInfo } from "../view/systemManage/role/service";
|
| | | export default {
|
| | | components: {
|
| | | ResetPassword,
|
| | | },
|
| | | data() {
|
| | | return {
|
| | | routesList: routes,
|
| | | isOpen: false,
|
| | | routerIsOpen: false,
|
| | | menuItems: [
|
| | | { text: '密码设置' },
|
| | | { text: '退出登录' },
|
| | | ],
|
| | | passwordVisible: false,
|
| | | row: {}
|
| | | };
|
| | | },
|
| | | created() {
|
| | | },
|
| | | methods: {
|
| | | ...mapMutations(['clearToken']),
|
| | | passwordConfirm(form) {
|
| | | updatePwd(form).then(() => {
|
| | | this.row = {}
|
| | | this.passwordVisible = false
|
| | | this.msgsuccess('修改密码成功')
|
| | | })
|
| | | },
|
| | | clickItem(item) {
|
| | | switch (item.text) {
|
| | | case '密码设置':
|
| | | this.row = this.$store.state.userInfo
|
| | | this.passwordVisible = true
|
| | | break;
|
| | | case '退出登录':
|
| | | this.clearToken()
|
| | | window.location.replace(`/`);
|
| | | break;
|
| | | default:
|
| | | break;
|
| | | }
|
| | | },
|
| | | pushPath(path) {
|
| | | this.$router.push(path)
|
| | | if (this.routerIsOpen) this.routerIsOpen = false
|
| | | },
|
| | | toggleDropdown(state) {
|
| | | this.isOpen = state;
|
| | | },
|
| | | routerDropdown(state) {
|
| | | this.routerIsOpen = state;
|
| | | }
|
| | | components: {
|
| | | ResetPassword,
|
| | | },
|
| | | data() {
|
| | | return {
|
| | | routesList: [],
|
| | | isOpen: false,
|
| | | routerIsOpen: false,
|
| | | menuItems: [{ text: "密码设置" }, { text: "退出登录" }],
|
| | | passwordVisible: false,
|
| | | row: {},
|
| | | };
|
| | | },
|
| | | created() {
|
| | | console.log(this.$store.state.permissions,this.$router.options.routes);
|
| | | |
| | | if (localStorage.getItem("userInfo")) {
|
| | | this.routesList = this.filterRoutes(this.$router.options.routes)
|
| | | } else {
|
| | | this.routesList = routes;
|
| | | }
|
| | | }
|
| | | },
|
| | | methods: {
|
| | | ...mapMutations(["clearToken"]),
|
| | | // 过滤路由函数
|
| | | filterRoutes(routes) {
|
| | | const permissions = this.$store.state.permissions || [];
|
| | | return routes.filter((route) => {
|
| | | // 如果路由有children,递归过滤
|
| | | if (route.children && route.children.length > 0) {
|
| | | const filteredChildren = this.filterRoutes(route.children);
|
| | | route.children = filteredChildren;
|
| | |
|
| | | // 如果过滤后children为空,且父路由有menuId需要权限,则过滤掉该路由
|
| | | if (
|
| | | filteredChildren.length === 0 &&
|
| | | route.meta &&
|
| | | route.meta.menuId &&
|
| | | !permissions.includes(route.meta.menuId)
|
| | | ) {
|
| | | return false;
|
| | | }
|
| | | // 如果过滤后children不为空,保留该父路由
|
| | | if (filteredChildren.length > 0) {
|
| | | return true;
|
| | | }
|
| | | }
|
| | |
|
| | | // 处理没有children的路由
|
| | | // 1. 如果路由没有menuId,保留
|
| | | if (!route.meta || !route.meta.menuId) {
|
| | | return true;
|
| | | }
|
| | | // 2. 如果路由有menuId,检查权限
|
| | | return permissions.includes(route.meta.menuId);
|
| | | });
|
| | | },
|
| | | passwordConfirm(form) {
|
| | | updatePwd(form).then(() => {
|
| | | this.row = {};
|
| | | this.passwordVisible = false;
|
| | | this.msgsuccess("修改密码成功");
|
| | | });
|
| | | },
|
| | | clickItem(item) {
|
| | | switch (item.text) {
|
| | | case "密码设置":
|
| | | this.row = this.$store.state.userInfo;
|
| | | this.passwordVisible = true;
|
| | | break;
|
| | | case "退出登录":
|
| | | this.clearToken();
|
| | | window.location.replace(`/`);
|
| | | break;
|
| | | default:
|
| | | break;
|
| | | }
|
| | | },
|
| | | pushPath(path) {
|
| | | this.$router.push(path);
|
| | | if (this.routerIsOpen) this.routerIsOpen = false;
|
| | | },
|
| | | toggleDropdown(state) {
|
| | | this.isOpen = state;
|
| | | },
|
| | | routerDropdown(state) {
|
| | | this.routerIsOpen = state;
|
| | | },
|
| | | },
|
| | | };
|
| | | </script>
|
| | |
|
| | | <style lang="less" scoped>
|
| | | .layout {
|
| | | display: flex;
|
| | | flex-direction: column;
|
| | | height: 100%;
|
| | | display: flex;
|
| | | flex-direction: column;
|
| | | height: 100%;
|
| | |
|
| | | .main {
|
| | | flex: 1;
|
| | | overflow: auto;
|
| | | }
|
| | | .main {
|
| | | flex: 1;
|
| | | overflow: auto;
|
| | | }
|
| | | }
|
| | |
|
| | | .bgColor1 {
|
| | | background-color: #0E6EFD;
|
| | | background-color: #3367ce;
|
| | | }
|
| | |
|
| | | .bgColor2 {
|
| | | background: #0D55B9;
|
| | | background: #2b5ab6;
|
| | | }
|
| | |
|
| | | .color1 {
|
| | | color: #fff;
|
| | | color: #fff;
|
| | | }
|
| | |
|
| | | .color2 {
|
| | | color: rgba(0, 0, 0, .6);
|
| | | // color: rgba(0, 0, 0, .6);
|
| | | color: #fff;
|
| | | }
|
| | |
|
| | | .header {
|
| | | height: 80px;
|
| | | background: #fff;
|
| | | height: 80px;
|
| | | background: #3367ce;
|
| | | background-image: url("../assets/title.png");
|
| | | background-size: 100% 100%;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | justify-content: space-between;
|
| | |
|
| | | .title {
|
| | | display: flex;
|
| | | justify-content: center;
|
| | | font-weight: 600;
|
| | | font-size: 30px;
|
| | | align-items: center;
|
| | | justify-content: space-between;
|
| | | position: absolute;
|
| | | top: 38%;
|
| | | left: 48.5%;
|
| | | transform: translate(-50%, -50%);
|
| | | // color: rgba(0, 0, 0, .8);
|
| | | color: #fff;
|
| | |
|
| | | .title {
|
| | | display: flex;
|
| | | justify-content: center;
|
| | | font-weight: 600;
|
| | | font-size: 24px;
|
| | | align-items: center;
|
| | | position: absolute;
|
| | | top: 50%;
|
| | | left: 50%;
|
| | | transform: translate(-50%, -50%);
|
| | | color: rgba(0, 0, 0, .8);
|
| | |
|
| | | img {
|
| | | width: 40px;
|
| | | height: 40px;
|
| | | margin-right: 10px;
|
| | | }
|
| | | img {
|
| | | width: 50px;
|
| | | height: 50px;
|
| | | margin-right: 25px;
|
| | | }
|
| | |
|
| | | span {
|
| | | letter-spacing: 4px;
|
| | | text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3); // 新增字体阴影
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | .menu {
|
| | | height: 60px;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | justify-content: center;
|
| | | height: 60px;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | justify-content: space-between;
|
| | | padding: 0 220px;
|
| | | }
|
| | |
|
| | | .menuItemHover {
|
| | | flex: 1;
|
| | | max-width: 160px;
|
| | | height: 100%;
|
| | | display: flex;
|
| | | align-items: center;
|
| | | justify-content: center;
|
| | | cursor: pointer;
|
| | | transition: all 0.3s;
|
| | | }
|
| | |
|
| | | .menuItemHover:hover {
|
| | | background: #0D55B9;
|
| | | border-radius: 18px;
|
| | | background: #2b5ab6;
|
| | | }
|
| | |
|
| | | .dropdown {
|
| | | position: relative;
|
| | | display: inline-block;
|
| | | cursor: pointer;
|
| | | position: relative;
|
| | | display: inline-block;
|
| | | cursor: pointer;
|
| | | }
|
| | |
|
| | | .positionTwo {
|
| | | transform: unset !important;
|
| | | width: 160px !important;
|
| | | transform: unset !important;
|
| | | width: 160px !important;
|
| | | }
|
| | |
|
| | | .dropdown-menu {
|
| | | position: absolute;
|
| | | top: 100%;
|
| | | left: 0;
|
| | | transform: translateX(-50%);
|
| | | background: white;
|
| | | border: 1px solid #ccc;
|
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
| | | z-index: 1000;
|
| | | border-radius: 8px;
|
| | | position: absolute;
|
| | | top: 100%;
|
| | | left: 0;
|
| | | transform: translateX(-50%);
|
| | | background: white;
|
| | | border: 1px solid #ccc;
|
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
| | | z-index: 1000;
|
| | | border-radius: 8px;
|
| | | }
|
| | |
|
| | | .dropdown-item {
|
| | | padding: 8px 16px;
|
| | | white-space: nowrap;
|
| | | /* 防止文本换行 */
|
| | | padding: 8px 16px;
|
| | | white-space: nowrap;
|
| | | /* 防止文本换行 */
|
| | | }
|
| | |
|
| | | .dropdown-item:hover {
|
| | | background-color: #f0f0f0;
|
| | | /* 添加 hover 效果 */
|
| | | background-color: #f0f0f0;
|
| | | /* 添加 hover 效果 */
|
| | | }
|
| | | </style> |
| | |
| | | });
|
| | | }
|
| | |
|
| | | Vue.prototype.$checkPermission = function(permissionId) {
|
| | | const permissions = store.state.permissions || [];
|
| | | if (!permissions.includes(permissionId)) {
|
| | | this.$router.push('/404');
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | };
|
| | |
|
| | | Vue.directive('permission', {
|
| | | inserted: function (el, binding) {
|
| | | const permissions = store.state.permissions || [];
|
| | | if (!permissions.includes(binding.value)) {
|
| | | el.style.display = 'none';
|
| | | }
|
| | | },
|
| | | update: function (el, binding) {
|
| | | const permissions = store.state.permissions || [];
|
| | | if (!permissions.includes(binding.value)) {
|
| | | el.style.display = 'none';
|
| | | } else {
|
| | | el.style.display = '';
|
| | | }
|
| | | }
|
| | | });
|
| | |
|
| | | new Vue({
|
| | | router,
|
| | | store,
|
| | |
| | | }),
|
| | | routes
|
| | | })
|
| | |
|
| | | const router = createRouter()
|
| | |
|
| | | // 路由守卫
|
| | | router.beforeEach((to, from, next) => {
|
| | | const token = store.state.token
|
| | | const permissions = store.state.permissions || []
|
| | | |
| | | if (!token && to.path != '/') {
|
| | | // 如果没有 token 并且不是去登录页,重定向到登录页
|
| | | next('/')
|
| | |
| | | // 如果有 token 并且要去登录页,重定向到首页
|
| | | next('/home')
|
| | | } else {
|
| | | // 检查路由权限
|
| | | if (to.meta && to.meta.menuId) {
|
| | | // 如果路由有menuId,检查是否有权限访问
|
| | | if (permissions.length === 0) {
|
| | | // 如果权限数组为空,说明是刚登录,还未获取权限,允许访问
|
| | | next()
|
| | | } else if (permissions.includes(to.meta.menuId)) {
|
| | | // 有权限,允许访问
|
| | | next()
|
| | | } else {
|
| | | // 无权限,重定向到404或首页
|
| | | next('/404')
|
| | | }
|
| | | } else {
|
| | | // 路由没有menuId,直接放行
|
| | | next()
|
| | | }
|
| | | |
| | | // 清理 localStorage
|
| | | localStorage.removeItem('registerForm')
|
| | | next()
|
| | | }
|
| | | })
|
| | |
|
| | |
| | | import Layout from '@/layouts'
|
| | | /**
|
| | | * icon
|
| | | * home: 首页
|
| | | * alarm: 报警
|
| | | * car: 车辆
|
| | | * company: 公司
|
| | | * complaint: 投诉
|
| | | * sys: 系统
|
| | | * order: 订单
|
| | | * |
| | | * hide: true, // 是否隐藏
|
| | | * |
| | | */
|
| | |
|
| | | export default [
|
| | | {
|
| | | path: '/',
|
| | | component: () => import('@/view/login'),
|
| | | },
|
| | | {
|
| | | path: '/home',
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: '/home',
|
| | | component: () => import('@/view/home'),
|
| | | meta: {
|
| | | title: '首页',
|
| | | icon: 'home'
|
| | | }
|
| | | },
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/company',
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: '/company',
|
| | | component: () => import('@/view/company'),
|
| | | meta: {
|
| | | title: '公司管理',
|
| | | icon: 'company'
|
| | | }
|
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/car',
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: '/car-manage',
|
| | | component: () => import('@/view/car-manage'),
|
| | | meta: {
|
| | | title: '车辆管理',
|
| | | icon: 'car'
|
| | | }
|
| | | },
|
| | | {
|
| | | path: '/car-detail',
|
| | | component: () => import('@/view/car-manage/detail'),
|
| | | meta: {
|
| | | title: '车辆详情',
|
| | | icon: 'car',
|
| | | hide: true
|
| | | }
|
| | | },
|
| | | {
|
| | | path: '/car-playback',
|
| | | component: () => import('@/view/playback'),
|
| | | meta: {
|
| | | title: '车辆回放',
|
| | | hide: true
|
| | | }
|
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/early-warning',
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: '/early-warning',
|
| | | component: () => import('@/view/early-warning'),
|
| | | meta: {
|
| | | title: '报警记录',
|
| | | icon: 'alarm'
|
| | | }
|
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/order',
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: '/order',
|
| | | component: () => import('@/view/order'),
|
| | | meta: {
|
| | | title: '订单记录',
|
| | | icon: 'order'
|
| | | }
|
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/complaint',
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: '/complaint',
|
| | | component: () => import('@/view/complaint/index'),
|
| | | meta: {
|
| | | title: '投诉记录',
|
| | | icon: 'complaint'
|
| | | }
|
| | | }
|
| | | ]
|
| | | },
|
| | | {
|
| | | path: '/systemManage',
|
| | | meta: {
|
| | | title: "系统管理",
|
| | | icon: 'sys',
|
| | | },
|
| | | component: Layout,
|
| | | children: [
|
| | | {
|
| | | path: 'driver',
|
| | | component: () => import('@/view/systemManage/driver'),
|
| | | meta: {
|
| | | title: '驾驶员列表',
|
| | | }
|
| | | },
|
| | | {
|
| | | path: 'type',
|
| | | component: () => import('@/view/car-type'),
|
| | | meta: {
|
| | | title: '车辆分类',
|
| | | }
|
| | | },
|
| | | {
|
| | | path: 'user',
|
| | | component: () => import('@/view/systemManage/user'),
|
| | | meta: {
|
| | | title: '用户管理',
|
| | | }
|
| | | },
|
| | | {
|
| | | path: 'role',
|
| | | component: () => import('@/view/systemManage/role'),
|
| | | meta: {
|
| | | title: '角色管理',
|
| | | }
|
| | | },
|
| | | {
|
| | | path: 'add-role',
|
| | | component: () => import('@/view/systemManage/role/addEdit.vue'),
|
| | | meta: {
|
| | | title: '添加角色',
|
| | | hide: true
|
| | | }
|
| | | },
|
| | | {
|
| | | path: 'role-detail',
|
| | | component: () => import('@/view/systemManage/role/detail'),
|
| | | meta: {
|
| | | title: '角色详情',
|
| | | hide: true
|
| | | }
|
| | | }
|
| | | ]
|
| | | }
|
| | |
|
| | | ]
|
| | | import Layout from "@/layouts"; |
| | | /** |
| | | * icon |
| | | * home: 首页 |
| | | * alarm: 报警 |
| | | * car: 车辆 |
| | | * company: 公司 |
| | | * complaint: 投诉 |
| | | * sys: 系统 |
| | | * order: 订单 |
| | | * |
| | | * hide: true, // 是否隐藏 |
| | | * |
| | | */ |
| | | |
| | | export default [ |
| | | { |
| | | path: "/", |
| | | component: () => import("@/view/login"), |
| | | }, |
| | | { |
| | | path: "/home", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/home", |
| | | component: () => import("@/view/home"), |
| | | meta: { |
| | | title: "首页", |
| | | icon: "home", |
| | | menuId: 1, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/404", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/404", |
| | | component: () => import("@/view/404.vue"), |
| | | meta: { |
| | | title: "404", |
| | | icon: "home", |
| | | hide: true, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/company", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/company", |
| | | component: () => import("@/view/company"), |
| | | meta: { |
| | | title: "公司管理", |
| | | icon: "company", |
| | | menuId: 2, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/car", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/car-manage", |
| | | component: () => import("@/view/car-manage"), |
| | | meta: { |
| | | title: "车辆管理", |
| | | icon: "car", |
| | | menuId: 3, |
| | | }, |
| | | }, |
| | | { |
| | | path: "/car-detail", |
| | | component: () => import("@/view/car-manage/detail"), |
| | | meta: { |
| | | title: "车辆详情", |
| | | icon: "car", |
| | | hide: true, |
| | | menuId: 31, |
| | | }, |
| | | }, |
| | | { |
| | | path: "/car-playback", |
| | | component: () => import("@/view/playback"), |
| | | meta: { |
| | | title: "车辆回放", |
| | | hide: true, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/early-warning", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/early-warning", |
| | | component: () => import("@/view/early-warning"), |
| | | meta: { |
| | | title: "报警记录", |
| | | icon: "alarm", |
| | | menuId: 9, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/order", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/order", |
| | | component: () => import("@/view/order"), |
| | | meta: { |
| | | title: "订单记录", |
| | | icon: "order", |
| | | menuId: 6, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/complaint", |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "/complaint", |
| | | component: () => import("@/view/complaint/index"), |
| | | meta: { |
| | | title: "投诉记录", |
| | | icon: "complaint", |
| | | menuId: 12, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | path: "/systemManage", |
| | | meta: { |
| | | title: "系统管理", |
| | | icon: "sys", |
| | | }, |
| | | component: Layout, |
| | | children: [ |
| | | { |
| | | path: "driver", |
| | | component: () => import("@/view/systemManage/driver"), |
| | | meta: { |
| | | title: "驾驶员列表", |
| | | menuId: 4, |
| | | }, |
| | | }, |
| | | { |
| | | path: "type", |
| | | component: () => import("@/view/car-type"), |
| | | meta: { |
| | | title: "车辆分类", |
| | | menuId: 5, |
| | | }, |
| | | }, |
| | | { |
| | | path: "user", |
| | | component: () => import("@/view/systemManage/user"), |
| | | meta: { |
| | | title: "用户管理", |
| | | menuId: 15, |
| | | }, |
| | | }, |
| | | { |
| | | path: "role", |
| | | component: () => import("@/view/systemManage/role"), |
| | | meta: { |
| | | title: "角色管理", |
| | | menuId: 22, |
| | | }, |
| | | }, |
| | | { |
| | | path: "add-role", |
| | | component: () => import("@/view/systemManage/role/addEdit.vue"), |
| | | meta: { |
| | | title: "添加角色", |
| | | hide: true, |
| | | menuId: 24, |
| | | }, |
| | | }, |
| | | { |
| | | path: "role-detail", |
| | | component: () => import("@/view/systemManage/role/detail"), |
| | | meta: { |
| | | title: "角色详情", |
| | | hide: true, |
| | | menuId: 27, |
| | | }, |
| | | }, |
| | | ], |
| | | }, |
| | | ]; |
| | |
| | | import Vue from 'vue';
|
| | | import Vuex from 'vuex';
|
| | | import Vue from "vue";
|
| | | import Vuex from "vuex";
|
| | |
|
| | | Vue.use(Vuex);
|
| | |
|
| | | export default new Vuex.Store({
|
| | | state: {
|
| | | token: localStorage.getItem('token') || sessionStorage.getItem('token') || '',
|
| | | userInfo: JSON.parse(localStorage.getItem('userInfo')) || {}
|
| | | permissions: JSON.parse(localStorage.getItem("permissions")) || [],
|
| | | token:
|
| | | localStorage.getItem("token") || sessionStorage.getItem("token") || "",
|
| | | userInfo: JSON.parse(localStorage.getItem("userInfo")) || {},
|
| | | },
|
| | | mutations: {
|
| | | SET_PERMISSON(state, arr) {
|
| | | state.permissions = arr;
|
| | | localStorage.setItem("permissions", JSON.stringify(arr));
|
| | | },
|
| | | setToken(state, token) {
|
| | | state.token = token;
|
| | | localStorage.setItem('token', token);
|
| | | localStorage.setItem("token", token);
|
| | | },
|
| | | clearToken(state) {
|
| | | state.token = '';
|
| | | state.userInfo = {}
|
| | | state.token = "";
|
| | | state.userInfo = {};
|
| | | localStorage.clear();
|
| | | sessionStorage.clear();
|
| | | },
|
| | | setUserInfo(state, userInfo) {
|
| | | state.userInfo = userInfo;
|
| | | localStorage.setItem('userInfo', userInfo)
|
| | | }
|
| | | localStorage.setItem("userInfo", userInfo);
|
| | | },
|
| | | },
|
| | | actions: {},
|
| | | modules: {}
|
| | | }); |
| | | modules: {},
|
| | | });
|
| | |
| | | const apiConfig = { |
| | | // 开发环境 |
| | | development: { |
| | | baseURL: "http://192.168.110.85:9000", |
| | | // baseURL: "http://192.168.110.80:9000", |
| | | baseURL: "http://221.182.45.100:9000", |
| | | mapKey: "67968c82f27c7e2cb9f40c1a9aa3042b", |
| | | secretKey: "37ce61ae86efa5ad82b649a277f5097c", |
| | | }, |
| | |
| | | Message({
|
| | | message: res.data.msg || '服务器错误',
|
| | | type: 'error',
|
| | | duration: 2000
|
| | | duration: 4000
|
| | | })
|
| | | return Promise.reject(res.data.data)
|
| | | }
|
New file |
| | |
| | | <template> |
| | | <div class="not-found"> |
| | | <div class="not-found-content"> |
| | | <!-- <img src="@/assets/404.png" alt="404" class="not-found-image"> --> |
| | | <h1 class="not-found-title">404</h1> |
| | | <p class="not-found-text">抱歉,您暂无该页面访问权限,请联系管理员开通</p> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'NotFound', |
| | | methods: { |
| | | goHome() { |
| | | this.$router.push('/home'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less" scoped> |
| | | .not-found { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | height: 100vh; |
| | | background-color: #f5f7fa; |
| | | |
| | | &-content { |
| | | text-align: center; |
| | | padding: 20px; |
| | | } |
| | | |
| | | &-image { |
| | | width: 300px; |
| | | height: 300px; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | &-title { |
| | | font-size: 72px; |
| | | color: #409EFF; |
| | | margin: 0; |
| | | line-height: 1.2; |
| | | } |
| | | |
| | | &-text { |
| | | font-size: 20px; |
| | | color: #606266; |
| | | margin: 20px 0; |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <el-radio-group v-model="tabPosition" style="margin-bottom: 30px;"> |
| | | <el-radio-button label="order">订单信息</el-radio-button> |
| | | <el-radio-button label="track">行程轨迹</el-radio-button> |
| | | <el-radio-button label="monitoring">行程监控</el-radio-button> |
| | | <!-- <el-radio-button label="monitoring">行程监控</el-radio-button> --> |
| | | </el-radio-group> |
| | | <!-- 订单信息 --> |
| | | <div v-show="tabPosition == 'order'"> |
| | |
| | | <div class="mapContainer" id="mapContainers"></div> |
| | | </div> |
| | | <!-- 行程监控 --> |
| | | <div v-if="tabPosition == 'monitoring'"> |
| | | <!-- <div v-if="tabPosition == 'monitoring'"> |
| | | <PlayLive :serverIp="monitoringData.serverIp" :serverPort="monitoringData.serverPort" |
| | | :carId="orderData.carId" /> |
| | | </div> |
| | | :urlLink="monitoringData.url" :carId="orderData.carId" /> |
| | | </div> --> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | methods: { |
| | | initData(orderData = {}, monitoringData = {}, travelData = []) { |
| | | console.log('////////////////////////'); |
| | | |
| | | |
| | | this.orderData = orderData |
| | | this.monitoringData = monitoringData |
| | | this.travelData = travelData |
| | |
| | | <el-descriptions-item label="车辆营运类型">{{ detail.operateType }}</el-descriptions-item> |
| | | <el-descriptions-item label="所属车主">{{ detail.driverName }}</el-descriptions-item> |
| | | <el-descriptions-item label="联系电话">{{ detail.driverPhone }}</el-descriptions-item> |
| | | <el-descriptions-item label="经营区域">{{ detail.operatingArea }}</el-descriptions-item> |
| | | <el-descriptions-item label="车辆年度审验">{{ detail.annualReviewStatus }}</el-descriptions-item> |
| | | <!-- <el-descriptions-item label="经营区域">{{ detail.operatingArea }}</el-descriptions-item> --> |
| | | <!-- <el-descriptions-item label="车辆年度审验">{{ detail.annualReviewStatus }}</el-descriptions-item> --> |
| | | <el-descriptions-item label="驾驶证" v-if="detail.drivingLicense"> |
| | | <img class="img-size" :src="detail.drivingLicense"></img> |
| | | </el-descriptions-item> |
| | |
| | | <img class="img-size" :src="detail.travelLicense"></img> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="info-btn" @click="showDetail"> |
| | | <!-- <div class="info-btn" @click="showDetail"> |
| | | 车辆详细信息<i class="el-icon-arrow-right"></i> |
| | | </div> |
| | | </div> --> |
| | | </div> |
| | | </div> |
| | | <div class="info-right flex2"> |
| | | <div class="info-right flex2" style="margin-top: 40px;"> |
| | | <div style="position: relative; width: 100%; height: 330px"> |
| | | <div |
| | | style="width: 100%; height: 330px; border-radius: 9px; background: #f5f5f5; display: flex; justify-content: center; align-items: center; flex-direction: column"> |
| | | <video style="width: 100%; height: 330px; border-radius: 9px; display: none" id="monitoringCard" |
| | | ref="monitoringCard" :controls="false" autoPlay width="620"> |
| | | <video style="width: 100%;border-radius: 9px; display: none" id="monitoringCard" |
| | | ref="monitoringCard" :controls="false" autoplay> |
| | | </video> |
| | | <el-empty description="暂无视频信息" :image-size="80"></el-empty> |
| | | </div> |
| | | <canvas id="myCanvas" style="display:none"></canvas> |
| | | <el-button type="default" style="position: absolute; top: 10px;right: 60px;" |
| | | @click="goBack()">查看回放</el-button> |
| | | <div style="position: absolute; right: 11px; top: 10px"> |
| | | <!-- <el-button type="default" style="position: absolute; top: 20px;right: 60px;" |
| | | @click="goBack()">查看回放</el-button> --> |
| | | <div style="position: absolute; right: 11px; top: 20px"> |
| | | <div style="display: flex;flex-direction: column;align-items: center;justify-content: center; |
| | | background: #ffffff; padding: 3px 10px; border-radius: 6px;margin-bottom: 10px;" @click="fullScreen()"> |
| | | <img style="width: 20px; height: 20px" :src="require('../../assets//homeImg/full.png')" /> |
| | |
| | | import DetailOrderModal from "./components/detailOrderModal.vue"; |
| | | import { getCarDetail, getCarOrder, getCarWarning, getCarTrack, getCarVideo, getDetail, getOrderInfo, getOrderTravel, getOrderMonitoring, playDetection, closeRealVideo } from './service' |
| | | import moment from "moment"; |
| | | import flvjs from "flv.js"; |
| | | export default { |
| | | name: "detail", |
| | | components: { DetailModal, DetailOrderModal }, |
| | |
| | | showWarnDetail: false, |
| | | info: {}, |
| | | activeInfo: {}, |
| | | map: null |
| | | map: null, |
| | | carId: null |
| | | } |
| | | }, |
| | | mounted() { |
| | | if (this.$route.query.id) { |
| | | this.carId = this.$route.query.id |
| | | getCarDetail({ id: this.$route.query.id }).then(res => { |
| | | this.detail = res; |
| | | this.getList(res.vehicleNumber); |
| | |
| | | |
| | | }, |
| | | |
| | | destroyed() { |
| | | beforeDestroy() { |
| | | this.destroyPlayer(); |
| | | }, |
| | | methods: { |
| | |
| | | // 检查flv.js是否支持 |
| | | if (flvjs.isSupported()) { |
| | | try { |
| | | playDetection(this.carId).then((res) => { |
| | | playDetection(this.$route.query.id).then((res) => { |
| | | this.flvPlayer = flvjs.createPlayer({ |
| | | type: "flv", //视频类型 |
| | | isLive: true, //是否为直播 |
| | | cors: true, //是否开启跨域 |
| | | hasAudio: false, //是否开启音频 |
| | | hasVideo: true, //是否开启视频 |
| | | url: `http://${this.videoObj.serverIp}:${this.videoObj.serverPort}/live?port=1935&app=flv&stream=${this.$route.query.id}`, // 后端拿到的视频路径 |
| | | // url: `http://${this.videoObj.serverIp}:${this.videoObj.serverPort}/live?port=1935&app=flv&stream=${this.$route.query.id}`, // 后端拿到的视频路径 |
| | | url: this.videoObj.url, // 后端拿到的视频路径 |
| | | enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 |
| | | enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 |
| | | stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb |
| | |
| | | } |
| | | |
| | | this.videoTimer = setInterval(() => { |
| | | playDetection(this.carId); |
| | | playDetection(this.$route.query.id); |
| | | }, 5000); |
| | | }) |
| | | .catch((err) => { |
| | |
| | | }); |
| | | }); |
| | | } catch (error) { |
| | | console.error("创建播放器失败:", error); |
| | | console.log("创建播放器失败:", error); |
| | | } |
| | | } else { |
| | | console.error("当前浏览器不支持flv.js"); |
| | |
| | | showDetails(row) { |
| | | this.loading = true |
| | | Promise.all([getOrderInfo(row.id), getOrderTravel({ id: row.id })]).then(res => { |
| | | getOrderMonitoring({ id: row.id }).then(resp => { |
| | | this.$refs.detailOrder.initData(res[0], resp, res[1]) |
| | | this.loading = false |
| | | }).catch(err => { |
| | | this.$refs.detailOrder.initData(res[0], {}, res[1]) |
| | | this.loading = false |
| | | }) |
| | | // getOrderMonitoring({ id: row.id }).then(resp => { |
| | | // this.$refs.detailOrder.initData(res[0], resp, res[1]) |
| | | // this.loading = false |
| | | // }).catch(err => { |
| | | this.$refs.detailOrder.initData(res[0], {}, res[1]) |
| | | this.loading = false |
| | | // }) |
| | | }).catch(err => { |
| | | this.loading = false |
| | | }) |
| | |
| | | pageCurr: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | startTime: moment(this.searchForm.date[0]).format('YYYY-MM-DD 00:00:00'), |
| | | endTime: moment(this.searchForm.date[1]).format('YYYY-MM-DD 23:59:59'), |
| | | startTime: new Date(this.searchForm.date[0]).getTime() / 1000, |
| | | endTime: new Date(this.searchForm.date[1]).getTime() / 1000, |
| | | } |
| | | getCarTrack({ ...this.searchForm, vehicleNumber: this.detail.vehicleNumber }).then(res => { |
| | | this.routeList = res; |
| | |
| | | |
| | | .info-content { |
| | | padding: 30px; |
| | | margin-bottom: 30px; |
| | | } |
| | | |
| | | ::v-deep .el-descriptions-item__container { |
| | |
| | | </el-form-item> |
| | | <el-form-item label="车辆运营类型:" prop="operateType" class="unset_m" style="margin-right: 15px;"> |
| | | <el-select :popper-append-to-body="false" v-model="searchForm.operateType" placeholder="请选择"> |
| | | <el-option v-for="(item,index) in options" :key="index" :label="item.name" :value="item.id"></el-option> |
| | | <el-option v-for="(item,index) in options" :key="index" :label="item.name" :value="item.name"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="车辆状态:" prop="status" class="unset_m" style="margin-right: 15px;"> |
| | |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="100"> |
| | | <template slot-scope="scope"> |
| | | <el-button type="text" @click="handle(scope.$index, scope.row)">详情</el-button> |
| | | <el-button type="text" v-permission="31" @click="handle(scope.$index, scope.row)">详情</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | }; |
| | | }, |
| | | created() { |
| | | this.$checkPermission(30) |
| | | |
| | | getCarType().then(res => { |
| | | this.options = res; |
| | | }); |
| | |
| | | const query = this.$route.query; |
| | | if (query && Object.keys(query).length > 0) { |
| | | if(query.id){ |
| | | this.searchForm.operateType = Number(query.id); |
| | | this.searchForm.operateType = query.id; |
| | | } |
| | | } |
| | | this.getList(); |
| | |
| | | export const getOrderMonitoring = (params) => { |
| | | return axios.get(`/system/order/getOrderMonitoring`, { params }) |
| | | } |
| | | |
| | | |
| | | // 通知后端开始获取视频流 |
| | | export const playDetection = (id) => { |
| | | return axios.get(`/system/car/playDetection/${id}`) |
| | |
| | | // 通知后端开始关闭视频流 |
| | | export const closeRealVideo = (id) => { |
| | | return axios.get(`/system/car/closeRealVideo/${id}`) |
| | | } |
| | | } |
| | |
| | | <div class="form flex a-center j-between mt--23"> |
| | | <div class="form-left ml--30"> |
| | | <el-form :inline="true" :model="searchForm" class="demo-form-inline"> |
| | | <el-form-item label="类型名称:" prop="level" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.level" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <!-- <el-form-item label="驾驶员手机号:" prop="name" class="unset_m" style="margin-right: 15px;"> |
| | | <el-form-item label="类型名称:" prop="name" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="驾驶员姓名:" prop="name" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="紧急联系人姓名:" prop="date" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入"></el-input> |
| | | </el-form-item> --> |
| | | </el-form> |
| | | </div> |
| | | <div class="form-right mr--24 mb--22"> |
| | |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :height="height"> |
| | | <el-table-column prop="date" label="序号"></el-table-column> |
| | | <el-table-column prop="name" label="图标"> |
| | | <el-table-column type="index" label="序号" width="60"></el-table-column> |
| | | <el-table-column prop="icon" label="图标"> |
| | | <template slot-scope="scope"> |
| | | <img :src="scope.row.img" alt="" style="width: 40px;height: 40px;" /> |
| | | <img :src="scope.row.icon" alt="" style="width: 40px;height: 40px;" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="name" label="类型"></el-table-column> |
| | | <el-table-column prop="name" label="车辆数"></el-table-column> |
| | | <!-- <el-table-column prop="name" label="驾驶员通信地址"></el-table-column> |
| | | <el-table-column prop="name" label="紧急情况联系人"></el-table-column> |
| | | <el-table-column prop="name" label="紧急情况联系人电话"></el-table-column> |
| | | <el-table-column prop="name" label="驾驶员手机号"></el-table-column> |
| | | <el-table-column prop="name" label="驾驶员合同签订公司"></el-table-column> |
| | | <el-table-column prop="name" label="操作"> |
| | | <template slot-scope="scope"> |
| | | <el-button @click="showDetail">详情</el-button> |
| | | </template> |
| | | </el-table-column> --> |
| | | <el-table-column prop="carNum" label="车辆数"></el-table-column> |
| | | </el-table> |
| | | <div class="relative mt--23 flex j-end"> |
| | | <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" |
| | | :current-page="searchForm.page" background layout="total,sizes,prev, pager, next,jumper" |
| | | :total="searchForm.total"> |
| | | :current-page="currentPage" :page-size="pageSize" background |
| | | layout="total,sizes,prev, pager, next,jumper" :total="total"> |
| | | </el-pagination> |
| | | </div> |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | |
| | | import { getCarTypeList } from './service' |
| | | export default { |
| | | data() { |
| | | return { |
| | | searchForm: { |
| | | total: 0, |
| | | page: 1, |
| | | pageSize: 10, |
| | | name: '', |
| | | }, |
| | | tableData: [], |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0, |
| | | }; |
| | | }, |
| | | computed: { |
| | | height() { |
| | | return this.$baseTableHeight(); |
| | | }, |
| | | } |
| | | }, |
| | | created() { |
| | | this.getList(); |
| | | }, |
| | | methods: { |
| | | getList() { |
| | | const params = { |
| | | name: this.searchForm.name, |
| | | pageCurr: this.currentPage, |
| | | pageSize: this.pageSize, |
| | | }; |
| | | |
| | | getCarTypeList(params).then(res => { |
| | | if (res && res.records) { |
| | | this.tableData = res.records; |
| | | this.total = res.total; |
| | | } else { |
| | | // 如果返回的是完整数组而不是分页对象,直接使用 |
| | | this.tableData = res || []; |
| | | this.total = (res || []).length; |
| | | } |
| | | }); |
| | | }, |
| | | showDetail() { |
| | | this.$refs.detailModal.dialogVisible = true; |
| | | }, |
| | | reset() {}, |
| | | search() {}, |
| | | handleSizeChange(e) { |
| | | this.searchForm.pageSize = e; |
| | | reset() { |
| | | this.searchForm.name = ''; |
| | | this.currentPage = 1; |
| | | this.getList(); |
| | | }, |
| | | handleCurrentChange(e) { |
| | | this.searchForm.page = e; |
| | | search() { |
| | | this.currentPage = 1; |
| | | this.getList(); |
| | | }, |
| | | handleSizeChange(size) { |
| | | this.pageSize = size; |
| | | this.currentPage = 1; |
| | | this.getList(); |
| | | }, |
| | | handleCurrentChange(page) { |
| | | this.currentPage = page; |
| | | this.getList(); |
| | | }, |
| | | }, |
| | | }; |
New file |
| | |
| | | import axios from '@/utils/request'; |
| | | // 获取车辆类型列表数据 |
| | | export const getCarTypeList = (params) => { |
| | | return axios.get('/system/car/getCarTypeList', {params}) |
| | | } |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog title="公司信息" :visible.sync="dialogVisible" width="50%" :modal-append-to-body="false"> |
| | | <el-descriptions title="" :column="2"> |
| | | <el-descriptions-item label="公司名称">射洪洪达出租车有限公司</el-descriptions-item> |
| | | <el-descriptions-item label="统一社会信用代码">91510922769973987B</el-descriptions-item> |
| | | <el-descriptions-item label="注册地址">四川省遂宁市射洪市太和街道万洪二路33、35、37号</el-descriptions-item> |
| | | <el-descriptions-item label="紧急联系电话">14725836902</el-descriptions-item> |
| | | <el-dialog title="公司信息" :visible.sync="dialogVisible" width="50%" :modal-append-to-body="false" @open="open"> |
| | | <el-descriptions title="" :column="2" class="right-label-descriptions"> |
| | | <el-descriptions-item label="公司名称">{{ companyData.name }}</el-descriptions-item> |
| | | <el-descriptions-item label="统一社会信用代码">{{ companyData.creditIdentifier }}</el-descriptions-item> |
| | | <el-descriptions-item label="注册地址">{{ companyData.mailingAddress }}</el-descriptions-item> |
| | | <el-descriptions-item label="紧急联系电话">{{ companyData.emergencyNumber }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-descriptions title="" :column="1"> |
| | | <el-descriptions-item |
| | | label="经营范围名称">巡游出租汽车经营服务。(依法须经批准的项目,经相关部门批准后方可开展经营活动,具体经营项目以相关部门批准文件或许可证件为准)一般项目:代驾服务;商务代理代办服务;小微型客车租赁经营服务;信息系统集成服务;汽车零配件批发。(除依法须经批准的项目外,凭营业执照依法自主开展经营活动)</el-descriptions-item> |
| | | <el-descriptions-item label="通讯地址">四川省遂宁市射洪市太和街道万洪二路33、35、37号</el-descriptions-item> |
| | | <el-descriptions title="" :column="1" class="right-label-descriptions"> |
| | | <el-descriptions-item label="经营范围名称">{{ companyData.businessScope }}</el-descriptions-item> |
| | | <el-descriptions-item label="通讯地址">{{ companyData.mailingAddress }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-descriptions title="" :column="2"> |
| | | <el-descriptions-item label="经营业户经济类型">有限责任公司(非自然人投资或控股的法人独资)</el-descriptions-item> |
| | | <el-descriptions-item label="注册资本">180万(元)</el-descriptions-item> |
| | | <el-descriptions-item label="法人代表姓名">王强</el-descriptions-item> |
| | | <el-descriptions-item label="法人代表电话">14725836905</el-descriptions-item> |
| | | <el-descriptions-item label="状态">有效</el-descriptions-item> |
| | | <el-descriptions title="" :column="2" class="right-label-descriptions"> |
| | | <el-descriptions-item label="经营业户经济类型">{{ companyData.operationType }}</el-descriptions-item> |
| | | <el-descriptions-item label="注册资本">{{ companyData.registeredCapital }}</el-descriptions-item> |
| | | <el-descriptions-item label="法人代表姓名">{{ companyData.legalRepresentative }}</el-descriptions-item> |
| | | <el-descriptions-item label="法人代表电话">{{ companyData.contactNumber }}</el-descriptions-item> |
| | | <el-descriptions-item label="状态">{{ companyData.status === 1 ? '有效' : '无效' }}</el-descriptions-item> |
| | | <el-descriptions-item label="联系人">{{ companyData.contactPerson }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | // import { getEnterpriseInfo } from '../service' |
| | | export default { |
| | | components: {}, |
| | | props: {}, |
| | | data() { |
| | | return { |
| | | dialogVisible: false |
| | | dialogVisible: false, |
| | | companyData: {} |
| | | }; |
| | | }, |
| | | computed: {}, |
| | |
| | | methods: { |
| | | closeClick() { |
| | | this.dialogVisible = false |
| | | }, |
| | | open() { |
| | | console.log(JSON.stringify(this.companyData)); |
| | | } |
| | | }, |
| | | }; |
| | |
| | | ::v-deep .el-descriptions .el-descriptions-item__cell { |
| | | padding-bottom: 25px; |
| | | } |
| | | |
| | | ::v-deep .el-descriptions-item__label { |
| | | width: 140px !important; |
| | | text-align: right !important; |
| | | justify-content: flex-end !important; |
| | | padding-right: 16px !important; |
| | | } |
| | | |
| | | ::v-deep .el-descriptions__label { |
| | | width: 140px !important; |
| | | text-align: right !important; |
| | | } |
| | | |
| | | ::v-deep .el-descriptions__table td.el-descriptions-item__label { |
| | | width: 140px !important; |
| | | text-align: right !important; |
| | | } |
| | | |
| | | /* 对单列描述项设置更宽的标签宽度 */ |
| | | ::v-deep .el-descriptions[class*="right-label-descriptions"][column="1"] .el-descriptions-item__label { |
| | | width: 160px !important; |
| | | } |
| | | </style> |
| | |
| | | <div class="form flex a-center j-between mt--23"> |
| | | <div class="form-left ml--30"> |
| | | <el-form :inline="true" :model="searchForm" class="demo-form-inline"> |
| | | <el-form-item label="公司名称:" prop="level" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.level" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="经营范围:" prop="name" class="unset_m" style="margin-right: 15px;"> |
| | | <el-form-item label="公司名称:" prop="name" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="通讯地址:" prop="name" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入"></el-input> |
| | | <el-form-item label="经营范围:" prop="businessScope" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.businessScope" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="法人代表:" prop="date" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.name" placeholder="请输入"></el-input> |
| | | <el-form-item label="通讯地址:" prop="mailingAddress" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.mailingAddress" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="车辆运营类型:" prop="type" class="unset_m" style="margin-right: 15px;"> |
| | | <el-select :popper-append-to-body="false" v-model="searchForm.type" placeholder="请选择"> |
| | | <el-option label="公交" value="1"></el-option> |
| | | <el-option label="出租车" value="2"></el-option> |
| | | <el-option label="网约车" value="3"></el-option> |
| | | <el-form-item label="法人代表:" prop="legalRepresentative" class="unset_m" style="margin-right: 15px;"> |
| | | <el-input v-model="searchForm.legalRepresentative" placeholder="请输入"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="车辆运营类型:" prop="operationType" class="unset_m" style="margin-right: 15px;"> |
| | | <el-select :popper-append-to-body="false" v-model="searchForm.operationType" placeholder="请选择"> |
| | | <el-option label="公交" value="公交车"></el-option> |
| | | <el-option label="出租车" value="出租车"></el-option> |
| | | <el-option label="网约车" value="网约车"></el-option> |
| | | <el-option label="客运" value="客运"></el-option> |
| | | <el-option label="郊游" value="郊游"></el-option> |
| | | <el-option label="危险品" value="危险品"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :height="height"> |
| | | <el-table-column prop="date" label="序号"></el-table-column> |
| | | <el-table-column type="index" label="序号"></el-table-column> |
| | | <el-table-column prop="name" label="公司名称"></el-table-column> |
| | | <el-table-column prop="name" label="经营范围"></el-table-column> |
| | | <el-table-column prop="name" label="运营类型"></el-table-column> |
| | | <el-table-column prop="name" label="通讯地址"></el-table-column> |
| | | <el-table-column prop="name" label="注册资本"></el-table-column> |
| | | <el-table-column prop="name" label="法定代表人"></el-table-column> |
| | | <el-table-column prop="name" label="联系人"></el-table-column> |
| | | <el-table-column prop="name" label="联系电话"></el-table-column> |
| | | <el-table-column prop="businessScope" label="经营范围"></el-table-column> |
| | | <el-table-column prop="operationType" label="运营类型"></el-table-column> |
| | | <el-table-column prop="mailingAddress" label="通讯地址"></el-table-column> |
| | | <el-table-column prop="registeredCapital" label="注册资本"></el-table-column> |
| | | <el-table-column prop="legalRepresentative" label="法定代表人"></el-table-column> |
| | | <el-table-column prop="contactPerson" label="联系人"></el-table-column> |
| | | <el-table-column prop="contactNumber" label="联系电话"></el-table-column> |
| | | <el-table-column prop="name" label="操作"> |
| | | <template slot-scope="scope"> |
| | | <el-button @click="showDetail">详情</el-button> |
| | | <el-button v-permission="29" @click="showDetail(scope.row)" size="small" type="primary">详情</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | |
| | | <div class="relative mt--23 flex j-end"> |
| | | <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" |
| | | :current-page="searchForm.page" background layout="total,sizes,prev, pager, next,jumper" |
| | | :total="searchForm.total"> |
| | | :current-page="currentPage" :page-size="pageSize" background |
| | | layout="total,sizes,prev, pager, next,jumper" :total="total"> |
| | | </el-pagination> |
| | | </div> |
| | | </div> |
| | |
| | | |
| | | <script> |
| | | import DetailModal from "./component/detailModal" |
| | | import { getEnterpriseList } from './service' |
| | | |
| | | export default { |
| | | components: { |
| | |
| | | data() { |
| | | return { |
| | | searchForm: { |
| | | total: 0, |
| | | page: 1, |
| | | pageSize: 10 |
| | | name: '', |
| | | businessScope: '', |
| | | mailingAddress: '', |
| | | legalRepresentative: '', |
| | | operationType: '' |
| | | }, |
| | | tableData: [], |
| | | currentPage: 1, |
| | | pageSize: 10, |
| | | total: 0 |
| | | }; |
| | | }, |
| | | computed: { |
| | |
| | | return this.$baseTableHeight() |
| | | }, |
| | | }, |
| | | created() { |
| | | this.$checkPermission(28) |
| | | this.getList() |
| | | }, |
| | | methods: { |
| | | showDetail() { |
| | | this.$refs.detailModal.dialogVisible = true |
| | | getList() { |
| | | const params = { |
| | | ...this.searchForm, |
| | | pageCurr: this.currentPage, |
| | | pageSize: this.pageSize |
| | | }; |
| | | |
| | | getEnterpriseList(params).then(res => { |
| | | if (res && res.records) { |
| | | this.tableData = res.records; |
| | | this.total = res.total; |
| | | } |
| | | }) |
| | | }, |
| | | showDetail(row) { |
| | | this.$refs.detailModal.dialogVisible = true; |
| | | this.$refs.detailModal.companyData = row; |
| | | }, |
| | | reset() { |
| | | this.searchForm = { |
| | | name: '', |
| | | businessScope: '', |
| | | mailingAddress: '', |
| | | legalRepresentative: '', |
| | | operationType: '' |
| | | }; |
| | | this.currentPage = 1; |
| | | this.getList(); |
| | | }, |
| | | search() { |
| | | |
| | | this.currentPage = 1; |
| | | this.getList(); |
| | | }, |
| | | handleSizeChange(e) { |
| | | this.searchForm.pageSize = e |
| | | handleSizeChange(size) { |
| | | this.pageSize = size; |
| | | this.getList(); |
| | | }, |
| | | handleCurrentChange(e) { |
| | | this.searchForm.page = e |
| | | handleCurrentChange(page) { |
| | | this.currentPage = page; |
| | | this.getList(); |
| | | }, |
| | | } |
| | | } |
New file |
| | |
| | | import axios from '@/utils/request'; |
| | | // 获取公司列表数据 |
| | | export const getEnterpriseList = (params) => { |
| | | return axios.get('/system/enterprise/getEnterpriseList', {params}) |
| | | } |
| | | // 获取公司详情 |
| | | export const getEnterpriseInfo = (id) => { |
| | | return axios.get(`/system/enterprise/getEnterpriseInfo/${id}`) |
| | | } |
| | |
| | | |
| | | <div class="table-box-btn mt--23 ml--30"> |
| | | <el-button class="search-button h--40 w--90 fs--14" icon="el-icon-top" type="primary" size="small" |
| | | @click="exportExcell">导出</el-button> |
| | | @click="exportExcell" v-permission="14">导出</el-button> |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :height="height" :element-loading-text="'正在加载...'"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :element-loading-text="'正在加载...'"> |
| | | <el-table-column type="index" width="55" label="序号"></el-table-column> |
| | | <el-table-column prop="carName" label="车辆名称"></el-table-column> |
| | | <el-table-column prop="vehicleNumber" label="车牌号码"></el-table-column> |
| | |
| | | }, |
| | | }, |
| | | created() { |
| | | this.$checkPermission(13) |
| | | |
| | | this.getTableList() |
| | | }, |
| | | methods: { |
| | |
| | | </div>
|
| | |
|
| | | <div class="table-box-btn mt--23 ml--30">
|
| | | <el-button class="search-button h--40 w--90 fs--14" icon="el-icon-top" type="primary" size="small"
|
| | | <el-button v-permission="11" class="search-button h--40 w--90 fs--14" icon="el-icon-top" type="primary" size="small"
|
| | | @click="exportExc">导出</el-button>
|
| | | </div>
|
| | | <div class="table-box ml--30 mt--23 mr--30">
|
| | |
| | | <el-table-column prop="vehicleNumber" label="车牌号码" fixed="left"></el-table-column>
|
| | | <!-- 其余列不固定,可滚动 -->
|
| | | <el-table-column prop="name" label="近15分钟情况">
|
| | | <template #default="{ row }">
|
| | | <img src="@/assets/homeImg/eye-fill.png" alt="" @click="viewDetail(row)"
|
| | | <template #default="{ row }" >
|
| | | <img v-permission="35" src="@/assets/homeImg/eye-fill.png" alt="" @click="viewDetail(row)"
|
| | | style="width: 30px;cursor: pointer;">
|
| | | </template>
|
| | | </el-table-column>
|
| | |
| | | };
|
| | | },
|
| | | created() {
|
| | | this.$checkPermission(10)
|
| | | this.fetchData()
|
| | | },
|
| | | methods: {
|
| | |
| | | <div class="carCount"> |
| | | <div class="title">车辆统计</div> |
| | | <div class="fir"> |
| | | <div |
| | | class="countCard" |
| | | v-for="(item, index) in carCountData.slice(0, 3)" |
| | | :key="item.id" |
| | | @click="toCarManage(item.id)" |
| | | > |
| | | <div class="countCard" v-for="(item, index) in carCountData.slice(0, 3)" :key="item.id" |
| | | @click="toCarManage(item.name)"> |
| | | <img class="iconImg" :src="imgList[index]" /> |
| | | <div> |
| | | <div class="name">{{ item.name || "" }}(辆)</div> |
| | |
| | | </div> |
| | | </div> |
| | | <div class="sec"> |
| | | <div |
| | | class="countCard" |
| | | v-for="(item, index) in carCountData.slice(3, 7)" |
| | | @click="toCarManage(item.id)" |
| | | :key="item.id" |
| | | > |
| | | <div class="countCard" v-for="(item, index) in carCountData.slice(3, 7)" @click="toCarManage(item.name)" |
| | | :key="item.id"> |
| | | <img class="iconImg" :src="imgList[index + 3]" /> |
| | | <div> |
| | | <div class="name">{{ item.name || "" }}(辆)</div> |
| | |
| | | <div class="name">在线</div> |
| | | <div class="num">{{ carStatusData.online || 0 }}</div> |
| | | </div> |
| | | <el-progress |
| | | type="circle" |
| | | :width="20" |
| | | :show-text="false" |
| | | stroke-linecap="butt" |
| | | :percentage="carStatusData.onlinePercent" |
| | | color="rgba(91, 143, 249, 1)" |
| | | define-back-color="rgba(91, 143, 249, 0.25)" |
| | | class="progressCard" |
| | | ></el-progress> |
| | | <el-progress type="circle" :width="20" :show-text="false" stroke-linecap="butt" |
| | | :percentage="carStatusData.onlinePercent" color="rgba(91, 143, 249, 1)" |
| | | define-back-color="rgba(91, 143, 249, 0.25)" class="progressCard"></el-progress> |
| | | </div> |
| | | <div class="statusLine"></div> |
| | | <div class="statusCard"> |
| | |
| | | <div class="name">离线</div> |
| | | <div class="num">{{ carStatusData.offline || 0 }}</div> |
| | | </div> |
| | | <el-progress |
| | | type="circle" |
| | | :width="20" |
| | | :show-text="false" |
| | | stroke-linecap="butt" |
| | | :percentage="carStatusData.offlinePercent" |
| | | color="rgba(93, 112, 146, 1)" |
| | | define-back-color="rgba(93, 112, 146, 0.25)" |
| | | class="progressCard" |
| | | ></el-progress> |
| | | <el-progress type="circle" :width="20" :show-text="false" stroke-linecap="butt" |
| | | :percentage="carStatusData.offlinePercent" color="rgba(93, 112, 146, 1)" |
| | | define-back-color="rgba(93, 112, 146, 0.25)" class="progressCard"></el-progress> |
| | | </div> |
| | | </div> |
| | | <div class="statusSec"> |
| | |
| | | <div class="name">故障</div> |
| | | <div class="num">{{ carStatusData.breakdown || 0 }}</div> |
| | | </div> |
| | | <el-progress |
| | | type="circle" |
| | | :width="20" |
| | | :show-text="false" |
| | | stroke-linecap="butt" |
| | | :percentage="carStatusData.breakdownPercent" |
| | | color="rgba(253, 83, 118, 1)" |
| | | define-back-color="rgba(253, 83, 118, 0.25)" |
| | | class="progressCard" |
| | | ></el-progress> |
| | | <el-progress type="circle" :width="20" :show-text="false" stroke-linecap="butt" |
| | | :percentage="carStatusData.breakdownPercent" color="rgba(253, 83, 118, 1)" |
| | | define-back-color="rgba(253, 83, 118, 0.25)" class="progressCard"></el-progress> |
| | | </div> |
| | | <div class="statusLine"></div> |
| | | <div class="statusCard"> |
| | |
| | | <div class="name">异常</div> |
| | | <div class="num">{{ carStatusData.abnormal || 0 }}</div> |
| | | </div> |
| | | <el-progress |
| | | type="circle" |
| | | :width="20" |
| | | :show-text="false" |
| | | stroke-linecap="butt" |
| | | :percentage="carStatusData.abnormalPercent" |
| | | color="rgba(246, 189, 22, 1)" |
| | | define-back-color="rgba(246, 189, 22, 0.25)" |
| | | class="progressCard" |
| | | ></el-progress> |
| | | <el-progress type="circle" :width="20" :show-text="false" stroke-linecap="butt" |
| | | :percentage="carStatusData.abnormalPercent" color="rgba(246, 189, 22, 1)" |
| | | define-back-color="rgba(246, 189, 22, 0.25)" class="progressCard"></el-progress> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <div class="todayWarn"> |
| | | <div class="title">今日预警</div> |
| | | <div class="warnList" v-if="warnList.length > 0"> |
| | | <div |
| | | class="warnItem" |
| | | v-for="(item, index) in warnList" |
| | | :key="index" |
| | | :class=" |
| | | item.warnLevel |
| | | ? ['oneWarn', 'twoWarn', 'threeWarn', 'fourWarn'][ |
| | | item.warnLevel - 1 |
| | | ] |
| | | : 'fiveWarn' |
| | | " |
| | | > |
| | | <div class="warnItem" v-for="(item, index) in warnList" :key="index" :class="item.warnLevel |
| | | ? ['oneWarn', 'twoWarn', 'threeWarn', 'fourWarn'][ |
| | | item.warnLevel - 1 |
| | | ] |
| | | : 'fiveWarn' |
| | | "> |
| | | <div class="grade"> |
| | | {{ |
| | | item.warnLevel |
| | |
| | | <div class="rankChart" id="rankChart"> |
| | | <div class="rankItem" v-for="(item, index) in rankList" :key="index"> |
| | | <div class="left">{{ item.name }}</div> |
| | | <div |
| | | class="rankRight" |
| | | :class="[0, 1, 2].includes(index) ? 'rankColor' : ''" |
| | | > |
| | | <div class="rankRight" :class="[0, 1, 2].includes(index) ? 'rankColor' : ''"> |
| | | <div class="rank" :style="{ width: item.percentage + '%' }"></div> |
| | | </div> |
| | | </div> |
| | |
| | | import html2canvas from "html2canvas"; |
| | | import AMapLoader from "@amap/amap-jsapi-loader"; |
| | | import flvjs from "flv.js"; |
| | | import moment from 'moment' |
| | | import { |
| | | getCarCount, |
| | | getCarStatusCount, |
| | |
| | | serverIp: "", //监控ip |
| | | serverPort: "", //监控端口 |
| | | carId: "", //监控车辆 |
| | | urlLink: '',//视频地址 |
| | | }; |
| | | }, |
| | | watch: { |
| | |
| | | this.getWarnTop10Data(); |
| | | |
| | | this.initMap(); |
| | | if (this.timer) { |
| | | clearInterval(this.timer); |
| | | } |
| | | // 设置定时器,每分钟刷新一次数据 |
| | | this.timer = setInterval(() => { |
| | | this.getCarCountData(); |
| | |
| | | // 获取预警列表数据 |
| | | async getWarnListData() { |
| | | try { |
| | | const res = await getCarWarnList(); |
| | | const res = await getCarWarnList({ pageNum: 1, pageSize: 100000,startTime:moment().format('YYYY-MM-DD 00:00:00'),endTime:moment().format('YYYY-MM-DD 23:59:59') }); |
| | | this.warnList = res.records; |
| | | } catch (error) { |
| | | this.$message.error("获取预警列表数据失败"); |
| | |
| | | }, |
| | | // 获取视频地址 |
| | | async getVideoUrl(carId) { |
| | | this.carId = carId; |
| | | try { |
| | | const res = await getRealVideo({ id: carId }); |
| | | // 将RTSP流转换为FLV流 |
| | | this.serverIp = res.serverIp; |
| | | this.serverPort = res.serverPort; |
| | | this.carId = carId; |
| | | this.urlLink = res.url; |
| | | } catch (error) { |
| | | console.error("获取视频地址失败", error); |
| | | return {}; |
| | |
| | | |
| | | // 初始化视频播放器 |
| | | initVideoPlayer() { |
| | | console.log('11111',this.serverIp,'2222222222',this.serverPort) |
| | | console.log('11111', this.serverIp, '2222222222', this.serverPort) |
| | | // 先销毁之前的播放器 |
| | | if (this.flvPlayer) { |
| | | this.flvPlayer.destroy(); |
| | |
| | | cors: true, //是否开启跨域 |
| | | hasAudio: false, //是否开启音频 |
| | | hasVideo: true, //是否开启视频 |
| | | url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 |
| | | // url: `http://${this.serverIp}:${this.serverPort}/live?port=1935&app=flv&stream=${this.carId}`, // 后端拿到的视频路径 |
| | | url: this.urlLink, // 后端拿到的视频路径 |
| | | enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程 |
| | | enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。 |
| | | stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb |
| | |
| | | if (emptyElement) { |
| | | emptyElement.style.display = 'none'; |
| | | } |
| | | |
| | | |
| | | this.videoTimer = setInterval(() => { |
| | | playDetection(this.carId); |
| | | }, 5000); |
| | |
| | | }, |
| | | |
| | | destroyPlayer() { |
| | | this.urlLink = "" |
| | | // 销毁播放器释放资源 |
| | | if (this.flvPlayer) { |
| | | if (this.videoTimer) clearInterval(this.videoTimer); |
| | |
| | | |
| | | // 恢复空状态的显示 |
| | | const video = document.getElementById("monitoringCard"); |
| | | if (video) { |
| | | if (video) { |
| | | video.style.display = 'none'; |
| | | const emptyElement = video.parentElement.querySelector('.el-empty'); |
| | | if (emptyElement) { |
| | |
| | | </div> |
| | | </div> |
| | | <div style="display: flex;justify-content: space-between;margin-top: 15px;margin-bottom: 12px;"> |
| | | <div style="font-weight: 500;font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">车牌号:${ |
| | | record.vehicleNumber || "" |
| | | }</div> |
| | | <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${ |
| | | record.driverName || "" |
| | | }</div> |
| | | <div style="font-weight: 500;font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">车牌号:${record.vehicleNumber || "" |
| | | }</div> |
| | | <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${record.driverName || "" |
| | | }</div> |
| | | </div> |
| | | <div style="display: flex; justify-content: space-between"> |
| | | <div style="font-weight: 500; font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;" title="${ |
| | | record.location |
| | | }">位置:${record.location}</div> |
| | | <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">经纬度:${ |
| | | record.longitude + "," + record.latitude |
| | | }</div> |
| | | <div style="font-weight: 500; font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;width: 200px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;" title="${record.location |
| | | }">位置:${record.location}</div> |
| | | <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">经纬度:${record.longitude + "," + record.latitude |
| | | }</div> |
| | | </div> |
| | | <div style="display: flex; justify-content: space-between"> |
| | | <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">当前时速:${ |
| | | record.speed || "" |
| | | }${record.speed && "km/h"}</div> |
| | | <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">驾驶时长:${ |
| | | record.drivingTime |
| | | }</div> |
| | | <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">当前时速:${record.speed || "" |
| | | }${record.speed && "km/h"}</div> |
| | | <div style="font-weight: 500;font-size: 14px; color: rgba(0, 0, 0, 0.65);line-height: 26px;">驾驶时长:${record.drivingTime |
| | | }</div> |
| | | </div> |
| | | <div style="margin-top: 14px;display: flex;justify-content: flex-end;align-items: center;cursor: pointer;" onclick="toCarDetail(${ |
| | | record.id |
| | | <div style="margin-top: 14px;display: flex;justify-content: flex-end;align-items: center;cursor: pointer;" onclick="toCarDetail(${record.id |
| | | })"> |
| | | <div style="font-weight: 400;font-size: 18px; color: rgba(22, 119, 255, 1);line-height: 25px;">车辆详情</div> |
| | | <img style="width:18px;height: 18px;margin-left: 8px;" src="${require("../../assets//homeImg/right.png")}" /> |
| | |
| | | overflow: 'truncate', // 截断模式 |
| | | ellipsis: true, // 超出显示省略号 |
| | | rotate: -40, // 可以根据需要调整角度 |
| | | formatter: function(value) { |
| | | formatter: function (value) { |
| | | if (value.length > 4) { |
| | | return value.substring(0, 4) + '...'; |
| | | } |
| | |
| | | .leftMap { |
| | | // width: 100%; |
| | | height: 100%; |
| | | flex: 1; |
| | | flex: 3; |
| | | display: flex; |
| | | position: relative; |
| | | |
| | |
| | | height: 100%; |
| | | } |
| | | } |
| | | |
| | | .mapTop { |
| | | z-index: 99; |
| | | position: absolute; |
| | |
| | | right: 513px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | width: calc(100% - 570px); |
| | | width: calc(100% - 770px); |
| | | |
| | | .title { |
| | | font-weight: 600; |
| | |
| | | |
| | | .countCard { |
| | | flex: 1; |
| | | background: linear-gradient( |
| | | 180deg, |
| | | rgba(246, 246, 252, 0) 0%, |
| | | #f3f4f8 100% |
| | | ); |
| | | background: linear-gradient(180deg, |
| | | rgba(246, 246, 252, 0) 0%, |
| | | #f3f4f8 100%); |
| | | box-shadow: inset 0px -1px 4px 0px #ffffff; |
| | | border-radius: 10px; |
| | | border: 1px solid #f1f1f1; |
| | |
| | | } |
| | | |
| | | .right { |
| | | width: 493px; |
| | | // width: 493px; |
| | | flex: 1; |
| | | height: calc(100% - 20px); |
| | | margin: 20px 17px 0 20px; |
| | | background: #ffffff; |
| | | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); |
| | | border-radius: 10px 10px 0px 0px; |
| | | padding: 20px; |
| | | overflow-y: auto; |
| | | |
| | | .title { |
| | | margin-top: 30px; |
| | |
| | | text-transform: none; |
| | | margin-bottom: 10px; |
| | | } |
| | | .mt-0{ |
| | | |
| | | .mt-0 { |
| | | margin-top: 0 !important; |
| | | } |
| | | |
| | |
| | | |
| | | .companyCard { |
| | | width: 140px; |
| | | height: 90px; |
| | | // height: 90px; |
| | | background: #f4f4ff; |
| | | border-radius: 0px 10px 10px 0px; |
| | | position: relative; |
| | |
| | | left: 0; |
| | | top: 0; |
| | | width: 4px; |
| | | height: 90px; |
| | | // height: 90px; |
| | | background: #0e6efd; |
| | | border-radius: 2px; |
| | | } |
| | |
| | | color: rgba(82, 196, 26, 1); |
| | | } |
| | | } |
| | | |
| | | .fiveWarn { |
| | | background: rgba(214, 219, 228, 0.3); |
| | | |
| | |
| | | position: relative; |
| | | |
| | | #countChart { |
| | | width: 453px; |
| | | width: 100%; |
| | | height: 180px; |
| | | } |
| | | |
| | |
| | | position: relative; |
| | | |
| | | .rankChart { |
| | | width: 453px; |
| | | height: 300px; |
| | | width: 100%; |
| | | // height: 300px; |
| | | |
| | | .rankItem { |
| | | display: flex; |
| | |
| | | } |
| | | // 获取车辆预警列表 |
| | | export const getCarWarnList = (data) => { |
| | | return axios.get('/system/warn/getCarWarnList', data) |
| | | return axios.get('/system/warn/getCarWarnList', {params:data}) |
| | | } |
| | | // 获取车辆预警情况统计 |
| | | export const getWarnGroupCount = (data) => { |
| | |
| | | <template> |
| | | <div class="bgImg"> |
| | | <div class="login_box"> |
| | | <div class="fs--40 fw-bold"> |
| | | <h1> |
| | | 射洪“两客一危”监管平台 |
| | | </h1> |
| | | <div class="logo"> |
| | | <img src="@/assets/logo.png" alt="" /> |
| | | <div class="fs--25 fw-bold" style="letter-spacing: 4px"> |
| | | 射洪两客一危监管平台 |
| | | </div> |
| | | </div> |
| | | <div class="mt--20 txt-center py--20 px--20"> |
| | | <div class="fs--20 fw-bold">登陆</div> |
| | | <div class="txt-center pb--20 px--20"> |
| | | <div class="mt--20"> |
| | | <el-input class="w100" prefix-icon="el-icon-user" placeholder="账号" v-model="username" /> |
| | | <el-input |
| | | class="w100" |
| | | prefix-icon="el-icon-user" |
| | | placeholder="账号" |
| | | v-model="username" |
| | | /> |
| | | </div> |
| | | <div class="mt--20"> |
| | | <el-input prefix-icon="el-icon-lock" placeholder="密码" v-model="password" show-password /> |
| | | <el-input |
| | | prefix-icon="el-icon-lock" |
| | | placeholder="密码" |
| | | v-model="password" |
| | | show-password |
| | | /> |
| | | </div> |
| | | <div class="mt--20 flex a-center code_box"> |
| | | <div class="flex1"> |
| | | <el-input placeholder="请输入验证码" v-model="code" /> |
| | | </div> |
| | | <div @click="resetCodeStr" class="fs--18 lh--40 border1 pointer">{{ codeStr }}</div> |
| | | <div @click="resetCodeStr" class="fs--18 lh--40 border1 pointer"> |
| | | {{ codeStr }} |
| | | </div> |
| | | </div> |
| | | <el-button :loading="loginLoading" @click="login" |
| | | class="mt--40 w100 br--5 pointer bgcolor1 color1">登录</el-button> |
| | | <el-button |
| | | :loading="loginLoading" |
| | | @click="login" |
| | | class="mt--40 w100 br--5 pointer bgcolor1 color1" |
| | | >登录</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import { generateVerificationCode, generateRandomString } from '@/utils/utils'; |
| | | import { mapMutations } from 'vuex'; |
| | | import { loginPwd } from './service' |
| | | import CryptoJS from 'crypto-js'; |
| | | import { |
| | | Message |
| | | } from 'element-ui' |
| | | import { generateVerificationCode, generateRandomString } from "@/utils/utils"; |
| | | import { mapMutations } from "vuex"; |
| | | import { loginPwd } from "./service"; |
| | | import CryptoJS from "crypto-js"; |
| | | import { getRoleInfo } from "@/view/systemManage/role/service"; |
| | | |
| | | import { Message } from "element-ui"; |
| | | export default { |
| | | components: {}, |
| | | props: {}, |
| | | data() { |
| | | return { |
| | | loginLoading: false,//登录loading |
| | | username: '', |
| | | password: '', |
| | | code: '', |
| | | codeStr: '' |
| | | loginLoading: false, //登录loading |
| | | username: "", |
| | | password: "", |
| | | code: "", |
| | | codeStr: "", |
| | | }; |
| | | }, |
| | | created() { |
| | | this.resetCodeStr() |
| | | this.resetCodeStr(); |
| | | }, |
| | | mounted() { |
| | | document.addEventListener("keydown", this.handleKeyDown); |
| | |
| | | document.removeEventListener("keydown", this.handleKeyDown); |
| | | }, |
| | | methods: { |
| | | ...mapMutations(['setToken', 'setUserInfo']), |
| | | ...mapMutations(["setToken", "setUserInfo"]), |
| | | login() { |
| | | if (!this.rulesLogin()) return |
| | | if (!this.rulesLogin()) return; |
| | | this.loginLoading = true; |
| | | loginPwd({ |
| | | username: this.username, |
| | | password: CryptoJS.MD5(this.password).toString() |
| | | }).then(res => { |
| | | localStorage.setItem('client', generateRandomString(16)); |
| | | this.loginLoading = false; |
| | | this.setToken(res.token.access_token); |
| | | this.setUserInfo(JSON.stringify(res.info.sysUser)); |
| | | this.$router.push('/home') |
| | | }).catch(() => { |
| | | this.resetCodeStr() |
| | | this.loginLoading = false; |
| | | password: CryptoJS.MD5(this.password).toString(), |
| | | }) |
| | | .then((res) => { |
| | | localStorage.setItem("client", generateRandomString(16)); |
| | | this.loginLoading = false; |
| | | this.setToken(res.token.access_token); |
| | | this.setUserInfo(JSON.stringify(res.info.sysUser)); |
| | | getRoleInfo({ id: res.info.sysUser.roles[0].roleId }).then((res) => { |
| | | this.$store.commit("SET_PERMISSON", res.menus); |
| | | this.$router.push("/home"); |
| | | }); |
| | | }) |
| | | .catch(() => { |
| | | this.resetCodeStr(); |
| | | this.loginLoading = false; |
| | | }); |
| | | }, |
| | | rulesLogin() { |
| | | if (!this.username) { |
| | | Message({ |
| | | message: '请输入账号', |
| | | type: 'warning', |
| | | duration: 2000 |
| | | }) |
| | | return false |
| | | message: "请输入账号", |
| | | type: "warning", |
| | | duration: 2000, |
| | | }); |
| | | return false; |
| | | } |
| | | if (!this.password) { |
| | | Message({ |
| | | message: '请输入密码', |
| | | type: 'warning', |
| | | duration: 2000 |
| | | }) |
| | | return false |
| | | message: "请输入密码", |
| | | type: "warning", |
| | | duration: 2000, |
| | | }); |
| | | return false; |
| | | } |
| | | if (!this.code) { |
| | | Message({ |
| | | message: '请输入验证码', |
| | | type: 'warning', |
| | | duration: 2000 |
| | | }) |
| | | return false |
| | | message: "请输入验证码", |
| | | type: "warning", |
| | | duration: 2000, |
| | | }); |
| | | return false; |
| | | } |
| | | if (!this.matchCaseInsensitive(this.codeStr, this.code)) { |
| | | Message({ |
| | |
| | | type: "warning", |
| | | duration: 1500, |
| | | }); |
| | | this.resetCodeStr() |
| | | return false |
| | | this.resetCodeStr(); |
| | | return false; |
| | | } |
| | | return true |
| | | return true; |
| | | }, |
| | | handleKeyDown(event) { |
| | | if (event.key === "Enter") { |
| | | this.login() |
| | | this.login(); |
| | | } |
| | | }, |
| | | // 校验验证码 |
| | | matchCaseInsensitive(str, pattern) { |
| | | return str.match(new RegExp(pattern, "i")); |
| | | return str.toLowerCase() === pattern.toLowerCase(); |
| | | }, |
| | | resetCodeStr() { |
| | | this.codeStr = generateVerificationCode(); |
| | |
| | | </script> |
| | | <style scoped lang="less"> |
| | | .bgImg { |
| | | background-image: url('../../assets/loginBG.png'); |
| | | background-image: url("../../assets/loginBG.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | |
| | | } |
| | | |
| | | .login_box { |
| | | width: 25%; |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | |
| | | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2); |
| | | border-radius: 10px; |
| | | padding: 20px; |
| | | |
| | | .logo { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | |
| | | img { |
| | | width: 50px; |
| | | height: 50px; |
| | | margin-right: 15px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .code_box { |
| | | border-radius: 4px; |
| | | border: 1px solid #DCDFE6; |
| | | border: 1px solid #dcdfe6; |
| | | |
| | | ::v-deep .el-input__inner { |
| | | border: unset !important; |
| | |
| | | } |
| | | |
| | | .bgcolor1 { |
| | | background: #0E6EFD; |
| | | background: #0e6efd; |
| | | } |
| | | |
| | | .color1 { |
| | |
| | | } |
| | | |
| | | .border1 { |
| | | border-left: 1px solid #DCDFE6; |
| | | border-left: 1px solid #dcdfe6; |
| | | min-width: 100px; |
| | | } |
| | | </style> |
| | |
| | | <el-radio-group v-model="tabPosition" style="margin-bottom: 30px;"> |
| | | <el-radio-button label="order">订单信息</el-radio-button> |
| | | <el-radio-button label="track">行程轨迹</el-radio-button> |
| | | <el-radio-button label="monitoring">行程监控</el-radio-button> |
| | | <!-- <el-radio-button label="monitoring">行程监控</el-radio-button> --> |
| | | </el-radio-group> |
| | | <!-- 订单信息 --> |
| | | <div v-show="tabPosition == 'order'"> |
| | |
| | | <div class="mapContainer" id="mapContainer"></div> |
| | | </div> |
| | | <!-- 行程监控 --> |
| | | <div v-if="tabPosition == 'monitoring'"> |
| | | <!-- <div v-if="tabPosition == 'monitoring'"> |
| | | <PlayLive :serverIp="monitoringData.serverIp" :serverPort="monitoringData.serverPort" |
| | | :carId="orderData.carId" /> |
| | | </div> |
| | | :urlLink="monitoringData.url" :carId="orderData.carId" /> |
| | | </div> --> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | |
| | | methods: { |
| | | initData(orderData = {}, monitoringData = {}, travelData = []) { |
| | | this.orderData = orderData |
| | | this.monitoringData = monitoringData |
| | | // this.monitoringData = monitoringData |
| | | this.travelData = travelData |
| | | this.dialogVisible = true |
| | | }, |
| | |
| | | </div> |
| | | <div class="mt--23 ml--30"> |
| | | <el-button class="search-button h--40 w--90 fs--14" icon="el-icon-top" type="primary" size="small" |
| | | @click="exportExcell">导出</el-button> |
| | | @click="exportExcell" v-permission="8">导出</el-button> |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :height="height" |
| | | :element-loading-text="'正在加载...'"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :element-loading-text="'正在加载...'"> |
| | | <el-table-column type="index" width="55" label="序号" /> |
| | | <el-table-column prop="code" label="订单编号" /> |
| | | <el-table-column prop="vehicleNumber" label="车牌号" /> |
| | |
| | | </el-table-column> |
| | | <el-table-column label="操作"> |
| | | <template #default="{ row }"> |
| | | <el-button @click="showDetail(row)">详情</el-button> |
| | | <el-button v-permission="34" @click="showDetail(row)">详情</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | }, |
| | | }, |
| | | created() { |
| | | this.$checkPermission(7) |
| | | this.getTableList() |
| | | }, |
| | | methods: { |
| | |
| | | showDetail(row) { |
| | | this.loading = true |
| | | Promise.all([getOrderInfo(row.id), getOrderTravel({ id: row.id })]).then(res => { |
| | | getOrderMonitoring({ id: row.id }).then(resp => { |
| | | this.$refs.detailModal.initData(res[0], resp, res[1]) |
| | | this.loading = false |
| | | }).catch(err => { |
| | | // getOrderMonitoring({ id: row.id }).then(resp => { |
| | | // this.$refs.detailModal.initData(res[0], resp, res[1]) |
| | | // this.loading = false |
| | | // }).catch(err => { |
| | | this.$refs.detailModal.initData(res[0], {}, res[1]) |
| | | this.loading = false |
| | | }) |
| | | // }) |
| | | }).catch(err => { |
| | | this.loading = false |
| | | }) |
| | |
| | | </el-col> |
| | | <el-col :span="20"> |
| | | <div class="grid-content" style=""> |
| | | <PlayLive :serverPort="serverPort" :serverIp="serverIp" :carId="$route.query.id"/> |
| | | <PlayLive v-if="urlLink" :serverPort="serverPort" :serverIp="serverIp" :carId="$route.query.id" |
| | | :urlLink="urlLink" @video-error="handleVideoError" /> |
| | | <div v-else class="empty-state"> |
| | | <el-empty description="请选择时间范围并点击查询获取视频" :image-size="80"></el-empty> |
| | | </div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | } |
| | | }, |
| | | serverIp: '', |
| | | serverPort: '' |
| | | serverPort: '', |
| | | urlLink: '' |
| | | } |
| | | }, |
| | | created() { |
| | |
| | | return; |
| | | } |
| | | |
| | | // 重置视频链接 |
| | | this.urlLink = ''; |
| | | this.serverIp = ''; |
| | | this.serverPort = ''; |
| | | |
| | | getPlaybackVideo({ |
| | | startTime: new Date(this.searchForm.startTime).getTime(), |
| | | endTime: new Date(this.searchForm.endTime).getTime(), |
| | |
| | | if (res && res.serverIp && res.serverPort) { |
| | | // 构建完整的视频流地址 |
| | | this.serverIp = res.serverIp; |
| | | this.serverPort = res.serverPort |
| | | this.serverPort = res.serverPort; |
| | | this.urlLink = res.url; |
| | | } else { |
| | | this.$message.error('未获取到视频地址'); |
| | | } |
| | |
| | | this.$message.error('获取视频失败,请稍后重试'); |
| | | }); |
| | | }, |
| | | handleVideoError() { |
| | | this.urlLink = ''; |
| | | this.$message.error('视频加载失败,请稍后重试'); |
| | | }, |
| | | resetForm() { |
| | | this.searchForm.startTime = ''; |
| | | this.searchForm.endTime = ''; |
| | |
| | | </div> |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :height="height" :element-loading-text="'正在加载...'"> |
| | | <el-table :data="tableData" border stripe style="width: 100%" :element-loading-text="'正在加载...'"> |
| | | <el-table-column type="index" width="55" label="序号"></el-table-column> |
| | | <el-table-column prop="driverName" label="机动车驾驶员姓名"></el-table-column> |
| | | <el-table-column prop="enterpriseName" label="车辆所属公司"></el-table-column> |
| | |
| | | <el-table-column prop="contractingCompany" label="驾驶员合同签订公司"></el-table-column> |
| | | <el-table-column label="操作"> |
| | | <template #default="{ row }"> |
| | | <el-button @click="showDetail(row)">详情</el-button> |
| | | <el-button v-permission="33" @click="showDetail(row)">详情</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | |
| | | }, |
| | | }, |
| | | created() { |
| | | this.$checkPermission(32) |
| | | this.getTableList() |
| | | }, |
| | | methods: { |
| | |
| | | <div></div> |
| | | <div>角色信息</div> |
| | | </div> |
| | | <el-form ref="form" :inline="true" :model="form" :rules="rules" label-width="80px" class="demo-form-inline"> |
| | | <el-form |
| | | ref="form" |
| | | :inline="true" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="80px" |
| | | class="demo-form-inline" |
| | | > |
| | | <el-form-item label="角色名称" prop="roleName"> |
| | | <el-input v-model="form.roleName" placeholder="请输入角色名称"></el-input> |
| | | <el-input |
| | | v-model="form.roleName" |
| | | placeholder="请输入角色名称" |
| | | ></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="备注" prop="remark"> |
| | | <el-input v-model="form.remark" placeholder="请输入备注"></el-input> |
| | |
| | | <div></div> |
| | | <div>操作权限</div> |
| | | </div> |
| | | <div> |
| | | <div class="header"> |
| | | <div class="w20">模块名称</div> |
| | | <div class="sconed"> |
| | | <div class="subpage"> |
| | | <div class="title">页面名称</div> |
| | | <div class="btns">权限</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-for="item in menu" :key="item.menuId"> |
| | | <div class="row"> |
| | | <div class="w20"> |
| | | <el-checkbox v-model="item.selected" @change="(e) => { |
| | | setCheckStatus1(item.menuId, e) |
| | | }" :checked="item.selected"> |
| | | {{ item.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | <div class="sconed"> |
| | | <div class="subpage" |
| | | v-if="(item.children.length > 0 && item.children[0].children.length > 0) || item.children[0].children.menuType != 'F'"> |
| | | <div v-for="item1 in item.children" :key="item1.menuId" class="two"> |
| | | <div class="left"> |
| | | <el-checkbox v-model="item1.selected" @change="(e) => { |
| | | setCheckStatus2(item1.menuId, e, item.menuId) |
| | | }" :checked="item1.selected"> |
| | | {{ item1.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | |
| | | <div class="right"> |
| | | <div v-for="item2 in item1.children" :key="item2.menuId"> |
| | | <el-checkbox v-model="item2.selected" @change="(e) => { |
| | | setCheckStatus3(item2.menuId, e, item1.menuId, item.menuId) |
| | | }" :checked="item2.selected"> |
| | | {{ item2.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="subpage" v-else> |
| | | <div class="two"> |
| | | <!-- <div class="left"> |
| | | </div> --> |
| | | |
| | | <div class="right"> |
| | | <div v-for="item1 in item.children" :key="item1.menuId"> |
| | | <el-checkbox v-model="item1.selected" @change="(e) => { |
| | | setCheckStatus2(item1.menuId, e, item.menuId,) |
| | | }" :checked="item1.selected"> |
| | | {{ item1.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="tree-container"> |
| | | <el-tree |
| | | ref="permissionTree" |
| | | :data="menu" |
| | | :props="defaultProps" |
| | | show-checkbox |
| | | node-key="menuId" |
| | | default-expand-all |
| | | :check-strictly="true" |
| | | @check="handleCheckChange" |
| | | > |
| | | </el-tree> |
| | | </div> |
| | | |
| | | <div class="btn_box"> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { roleInfoFromUserId, getRoleInfo, add, edit } from './service.js' |
| | | import { roleInfoFromUserId, getRoleInfo, add, edit } from "./service.js"; |
| | | export default { |
| | | components: {}, |
| | | props: {}, |
| | |
| | | ], |
| | | }, |
| | | menu: [], |
| | | defaultProps: { |
| | | children: "children", |
| | | label: "menuName", |
| | | }, |
| | | }; |
| | | }, |
| | | computed: {}, |
| | | watch: {}, |
| | | created() { |
| | | roleInfoFromUserId().then(res => { |
| | | roleInfoFromUserId().then((res) => { |
| | | // 确保菜单数据正确处理 |
| | | this.menu = this.processMenuData(res); |
| | | |
| | | if (this.$route.query.roleId) { |
| | | getRoleInfo({ id: this.$route.query.roleId }).then(resp => { |
| | | this.menu = this.setSelectedIds(res.data.data, resp.data.data.menus || []); |
| | | getRoleInfo({ id: this.$route.query.roleId }).then((resp) => { |
| | | // 设置表单数据 |
| | | const roleData = resp; |
| | | this.form = { |
| | | roleName: resp.data.data.roleName, |
| | | remark: resp.data.data.remark, |
| | | roleId: resp.data.data.roleId |
| | | } |
| | | }) |
| | | } else { |
| | | this.menu = res.data.data |
| | | roleName: roleData.roleName, |
| | | remark: roleData.remark, |
| | | roleId: roleData.roleId, |
| | | }; |
| | | |
| | | // 设置选中的菜单项 |
| | | this.$nextTick(() => { |
| | | if (roleData.menus && roleData.menus.length > 0) { |
| | | // 直接使用后端返回的选中节点 |
| | | this.$refs.permissionTree.setCheckedKeys(roleData.menus); |
| | | } |
| | | }); |
| | | }); |
| | | } |
| | | }) |
| | | }); |
| | | }, |
| | | mounted() { }, |
| | | mounted() {}, |
| | | methods: { |
| | | // 处理菜单数据,为每个节点添加必要的属性 |
| | | processMenuData(menuData) { |
| | | // 如果返回的是嵌套在data.data中的数据 |
| | | if (menuData.data && menuData.data.data) { |
| | | menuData = menuData.data.data; |
| | | } |
| | | |
| | | function traverse(item) { |
| | | // 确保有children属性 |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | |
| | | // 递归处理子节点 |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(traverse); |
| | | } |
| | | } |
| | | |
| | | // 克隆一份数据,避免直接修改原数据 |
| | | const clonedData = JSON.parse(JSON.stringify(menuData)); |
| | | clonedData.forEach(traverse); |
| | | |
| | | return clonedData; |
| | | }, |
| | | |
| | | // 复选框状态变化事件处理 |
| | | handleCheckChange(data, checked) { |
| | | // 获取树实例 |
| | | const tree = this.$refs.permissionTree; |
| | | |
| | | // 如果是父节点被点击 |
| | | if (data.children && data.children.length > 0) { |
| | | // 获取当前节点的选中状态 |
| | | const isChecked = tree.getCheckedKeys().includes(data.menuId); |
| | | |
| | | if (isChecked) { |
| | | // 如果父节点被选中,选中所有子节点 |
| | | const childIds = this.getAllChildIds(data); |
| | | const currentCheckedKeys = tree.getCheckedKeys(); |
| | | const newCheckedKeys = [ |
| | | ...new Set([...currentCheckedKeys, ...childIds]), |
| | | ]; |
| | | tree.setCheckedKeys(newCheckedKeys); |
| | | } else { |
| | | // 如果父节点被取消选中,取消选中所有子节点 |
| | | const childIds = this.getAllChildIds(data); |
| | | const currentCheckedKeys = tree.getCheckedKeys(); |
| | | const newCheckedKeys = currentCheckedKeys.filter( |
| | | (key) => !childIds.includes(key) |
| | | ); |
| | | tree.setCheckedKeys(newCheckedKeys); |
| | | } |
| | | } else { |
| | | // 如果是子节点被点击,检查父节点状态 |
| | | this.updateParentNodeState(data); |
| | | } |
| | | }, |
| | | |
| | | // 获取节点的所有子节点ID |
| | | getAllChildIds(node) { |
| | | let ids = []; |
| | | if (node.children && node.children.length > 0) { |
| | | node.children.forEach((child) => { |
| | | ids.push(child.menuId); |
| | | ids = ids.concat(this.getAllChildIds(child)); |
| | | }); |
| | | } |
| | | return ids; |
| | | }, |
| | | |
| | | // 更新父节点状态 |
| | | updateParentNodeState(node) { |
| | | const tree = this.$refs.permissionTree; |
| | | const parentNode = this.findParentNode(this.menu, node.menuId); |
| | | |
| | | if (parentNode) { |
| | | const allChildren = this.getAllChildIds(parentNode); |
| | | const checkedChildren = allChildren.filter((id) => |
| | | tree.getCheckedKeys().includes(id) |
| | | ); |
| | | |
| | | if (checkedChildren.length === 0) { |
| | | // 如果没有子节点被选中,取消选中父节点 |
| | | tree.setChecked(parentNode.menuId, false); |
| | | } else if (checkedChildren.length === allChildren.length) { |
| | | // 如果所有子节点都被选中,选中父节点 |
| | | tree.setChecked(parentNode.menuId, true); |
| | | } else { |
| | | // 如果部分子节点被选中,设置父节点为半选状态 |
| | | tree.setChecked(parentNode.menuId, false); |
| | | const node = tree.getNode(parentNode.menuId); |
| | | if (node) { |
| | | node.indeterminate = true; |
| | | } |
| | | } |
| | | |
| | | // 递归更新更上层的父节点 |
| | | this.updateParentNodeState(parentNode); |
| | | } |
| | | }, |
| | | |
| | | // 查找节点的父节点 |
| | | findParentNode(nodes, targetId, parent = null) { |
| | | for (let node of nodes) { |
| | | if (node.menuId === targetId) { |
| | | return parent; |
| | | } |
| | | if (node.children && node.children.length > 0) { |
| | | const found = this.findParentNode(node.children, targetId, node); |
| | | if (found) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }, |
| | | |
| | | setSelectedIds(arr, selectKeyList) { |
| | | function traverse(item) { |
| | | item.selected = selectKeyList.includes(item.menuId); |
| | |
| | | arr.forEach(traverse); |
| | | return arr; |
| | | }, |
| | | |
| | | onSubmit() { |
| | | this.$refs['form'].validate((valid) => { |
| | | this.$refs["form"].validate((valid) => { |
| | | if (valid) { |
| | | if (this.getSelectedIds(this.menu).length == 0) { |
| | | this.$baseMessage('请勾选操作权限', 'warning') |
| | | return |
| | | // 获取选中的节点ID列表 |
| | | const checkedKeys = this.$refs.permissionTree.getCheckedKeys(); |
| | | const halfCheckedKeys = |
| | | this.$refs.permissionTree.getHalfCheckedKeys(); |
| | | |
| | | // 合并完全选中和半选中的节点 |
| | | const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]; |
| | | |
| | | if (allCheckedKeys.length === 0) { |
| | | this.$message.warning("请勾选操作权限"); |
| | | return; |
| | | } |
| | | |
| | | let obj = { |
| | | ...this.form, |
| | | menuIds: this.getSelectedIds(this.menu) |
| | | } |
| | | menuIds: allCheckedKeys, |
| | | }; |
| | | |
| | | if (this.$route.query && this.$route.query.roleId) { |
| | | obj.roleId = this.$route.query.roleId |
| | | obj.roleId = this.$route.query.roleId; |
| | | edit(obj).then(() => { |
| | | this.$baseMessage('保存成功', 'success') |
| | | this.$router.go(-1) |
| | | }) |
| | | this.$message.success("保存成功"); |
| | | this.$router.go(-1); |
| | | }); |
| | | } else { |
| | | add(obj).then(() => { |
| | | this.$baseMessage('保存成功', 'success') |
| | | this.$router.go(-1) |
| | | }) |
| | | this.$message.success("保存成功"); |
| | | this.$router.go(-1); |
| | | }); |
| | | } |
| | | } |
| | | }) |
| | | }); |
| | | }, |
| | | |
| | | getSelectedIds(arr) { |
| | | let result = []; |
| | | function traverse(item) { |
| | |
| | | } |
| | | return result; |
| | | }, |
| | | setCheckStatus1(id, status) { //点击第1级 |
| | | if (!status) { |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == id) { |
| | | item.selected = status |
| | | if (item.children.length > 0) { |
| | | item.children = item.children.map(item1 => { |
| | | item1.selected = status |
| | | if (item1.children.length > 0) { |
| | | item1.children = item1.children.map(item2 => { |
| | | item2.selected = status |
| | | return { ...item2 } |
| | | }) |
| | | } |
| | | return { ...item1 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item } |
| | | }) |
| | | } else { |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == id) { |
| | | item.selected = true |
| | | } |
| | | return { ...item } |
| | | }) |
| | | } |
| | | }, |
| | | setCheckStatus2(id, status, aId) { //点击第2级 |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == aId) { |
| | | item.selected = true |
| | | if (item.children.length > 0) { |
| | | item.children = item.children.map(item1 => { |
| | | if (item1.menuId == id) { |
| | | item1.selected = status |
| | | } |
| | | return { ...item1 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item } |
| | | }) |
| | | }, |
| | | setCheckStatus3(id, status, bId, aId) {//点击第3级 |
| | | this.menu = this.menu.map(item => { |
| | | if (item.menuId == aId) { |
| | | item.selected = true |
| | | if (item.children.length > 0) { |
| | | item.children = item.children.map(item1 => { |
| | | if (item1.menuId == bId) { |
| | | item1.selected = true |
| | | if (item1.children.length > 0) { |
| | | item1.children = item1.children.map(item2 => { |
| | | if (item2.menuId == id) { |
| | | item2.selected = status |
| | | } |
| | | return { ...item2 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item1 } |
| | | }) |
| | | } |
| | | } |
| | | return { ...item } |
| | | }) |
| | | } |
| | | }, |
| | | }; |
| | | </script> |
| | |
| | | div:first-child { |
| | | width: 4px; |
| | | height: 16px; |
| | | background: #598DEC; |
| | | background: #598dec; |
| | | margin-right: 8px; |
| | | } |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | .el-checkbox { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .row, |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | .tree-container { |
| | | padding: 20px; |
| | | border: 1px solid #e8e8e8; |
| | | |
| | | .w20 { |
| | | width: 15%; |
| | | padding: 8px 20px; |
| | | } |
| | | |
| | | .sconed { |
| | | flex: 1; |
| | | |
| | | .subpage { |
| | | .title { |
| | | border: 1px solid #e8e8e8; |
| | | border-top: none; |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .two { |
| | | display: flex; |
| | | align-items: center; |
| | | border: 1px solid #e8e8e8; |
| | | border-top: none; |
| | | border-right: none; |
| | | |
| | | .left { |
| | | width: 200px; |
| | | padding: 13px 20px; |
| | | border-right: 1px solid #e8e8e8; |
| | | } |
| | | |
| | | .right { |
| | | display: flex; |
| | | flex: 1; |
| | | |
| | | div { |
| | | padding: 13px 0 13px 20px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .two:last-child { |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .btns { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 0 20px; |
| | | } |
| | | } |
| | | } |
| | | border-radius: 4px; |
| | | margin: 10px 0; |
| | | background-color: #fff; |
| | | max-height: 600px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .header { |
| | | background-color: #e8e8e8; |
| | | /* 树节点样式优化 */ |
| | | ::v-deep .el-tree-node__content { |
| | | height: 40px; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | .subpage { |
| | | display: flex; |
| | | } |
| | | ::v-deep .el-tree-node__label { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .title { |
| | | width: 200px; |
| | | padding: 8px 20px; |
| | | } |
| | | ::v-deep .el-tree-node.is-current > .el-tree-node__content { |
| | | background-color: #f0f7ff; |
| | | } |
| | | |
| | | /* 复选框样式优化 */ |
| | | ::v-deep .el-checkbox__inner { |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | ::v-deep .el-tree-node:hover > .el-tree-node__content { |
| | | background-color: #f5f7fa; |
| | | } |
| | | </style> |
| | |
| | | <el-card> |
| | | <el-tabs v-model="activeName"> |
| | | <el-tab-pane label="操作权限" name="first"> |
| | | <div class="header"> |
| | | <div class="w20">模块名称</div> |
| | | <div class="sconed"> |
| | | <div class="subpage"> |
| | | <div class="title">页面名称</div> |
| | | <div class="btns">权限</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div v-for="item in menu" :key="item.menuId"> |
| | | <div class="row"> |
| | | <div class="w20"> |
| | | <el-checkbox disabled :checked="item.selected"> |
| | | {{ item.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | <div class="sconed"> |
| | | <div class="subpage" |
| | | v-if="(item.children.length > 0 && item.children[0].children.length > 0) || item.children[0].children.menuType != 'F'"> |
| | | <div v-for="item1 in item.children" :key="item1.menuId" class="two"> |
| | | <div class="left"> |
| | | <el-checkbox disabled :checked="item1.selected"> |
| | | {{ item1.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | |
| | | <div class="right"> |
| | | <div v-for="item2 in item1.children" :key="item2.menuId"> |
| | | <el-checkbox disabled :checked="item2.selected"> |
| | | {{ item2.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="subpage" v-else> |
| | | <div class="two"> |
| | | <!-- <div class="left"> |
| | | </div> --> |
| | | <div class="right"> |
| | | <div v-for="item1 in item.children" :key="item1.menuId"> |
| | | <el-checkbox disabled :checked="item1.selected"> |
| | | {{ item1.menuName }} |
| | | </el-checkbox> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="tree-container"> |
| | | <el-tree |
| | | ref="permissionTree" |
| | | :data="menu" |
| | | :props="defaultProps" |
| | | show-checkbox |
| | | node-key="menuId" |
| | | default-expand-all |
| | | :check-strictly="true" |
| | | > |
| | | </el-tree> |
| | | </div> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="人员列表" name="second"> |
| | | |
| | | <el-form :inline="true" class="demo-form-inline"> |
| | | <div style="display: flex;justify-content: space-between;"> |
| | | <div style="display: flex;"> |
| | | <el-form-item label="人员搜索"> |
| | | <el-input v-model="nickNameOrPhone" placeholder="请输入姓名/联系电话"></el-input> |
| | | </el-form-item> |
| | |
| | | form: {}, |
| | | activeName: 'first', |
| | | menu: [], |
| | | defaultProps: { |
| | | children: 'children', |
| | | label: 'menuName', |
| | | disabled: (data,node) => { |
| | | return {...node,disabled:true} |
| | | } |
| | | }, |
| | | data: [], |
| | | nickNameOrPhone: '', |
| | | status: '', |
| | |
| | | watch: {}, |
| | | created() { |
| | | roleInfoFromUserId().then(res => { |
| | | this.menu = this.processMenuData(res); |
| | | |
| | | getRoleInfo({ id: this.$route.query.roleId }).then(resp => { |
| | | this.menu = this.setSelectedIds(res.data.data, resp.data.data.menus); |
| | | this.form = { |
| | | roleName: resp.data.data.roleName, |
| | | remark: resp.data.data.remark, |
| | | } |
| | | }) |
| | | }) |
| | | this.getListData() |
| | | roleName: resp.roleName, |
| | | remark: resp.remark, |
| | | }; |
| | | |
| | | this.$nextTick(() => { |
| | | if (resp.menus && resp.menus.length > 0) { |
| | | this.$refs.permissionTree.setCheckedKeys(resp.menus); |
| | | } |
| | | }); |
| | | }); |
| | | }); |
| | | this.getListData(); |
| | | }, |
| | | mounted() { }, |
| | | methods: { |
| | |
| | | this.listLoading = false |
| | | }, 500) |
| | | }, |
| | | setSelectedIds(arr, selectKeyList) { |
| | | processMenuData(menuData) { |
| | | function traverse(item) { |
| | | item.selected = selectKeyList.includes(item.menuId); |
| | | if (!item.children) { |
| | | item.children = []; |
| | | } |
| | | |
| | | if (item.children && item.children.length > 0) { |
| | | item.children.forEach(traverse); |
| | | } |
| | | } |
| | | arr.forEach(traverse); |
| | | return arr; |
| | | |
| | | const clonedData = JSON.parse(JSON.stringify(menuData)); |
| | | clonedData.forEach(traverse); |
| | | |
| | | return clonedData; |
| | | }, |
| | | reset() { |
| | | this.nickNameOrPhone = '' |
| | |
| | | } |
| | | } |
| | | |
| | | /* 当复选框禁用时覆盖默认样式 */ |
| | | .tree-container { |
| | | padding: 20px; |
| | | border: 1px solid #e8e8e8; |
| | | border-radius: 4px; |
| | | margin: 10px 0; |
| | | background-color: #fff; |
| | | max-height: 600px; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | /* 树节点样式优化 */ |
| | | ::v-deep .el-tree-node__content { |
| | | height: 40px; |
| | | padding: 0 10px; |
| | | } |
| | | |
| | | ::v-deep .el-tree-node__label { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | ::v-deep .el-tree-node.is-current > .el-tree-node__content { |
| | | background-color: #f0f7ff; |
| | | } |
| | | |
| | | /* 禁用状态下的复选框样式 */ |
| | | ::v-deep .el-checkbox__input.is-disabled.is-checked .el-checkbox__inner { |
| | | background-color: #409eff; |
| | | border-color: #409eff; |
| | |
| | | ::v-deep .el-checkbox.is-disabled .el-checkbox__inner::after { |
| | | border-color: #fff; |
| | | } |
| | | |
| | | ::v-deep .el-tree-node__content:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | </style> |
| | |
| | | </el-card> |
| | | <el-card style="margin-top: 20px;"> |
| | | <div class="add_btn"> |
| | | <el-button icon="el-icon-plus" @click="add" type="primary">添加角色</el-button> |
| | | <el-button v-permission="24" icon="el-icon-plus" @click="add" type="primary">添加角色</el-button> |
| | | </div> |
| | | <el-table ref="tableSort" v-loading="listLoading" :height="height" stripe :data="data" |
| | | <el-table ref="tableSort" v-loading="listLoading" stripe :data="data" |
| | | :element-loading-text="elementLoadingText"> |
| | | <el-table-column type="index" width="55" label="序号"></el-table-column> |
| | | <el-table-column prop="roleName" label="角色名称"></el-table-column> |
| | |
| | | <el-table-column label="操作" width="300"> |
| | | <template slot-scope="{row}"> |
| | | <div> |
| | | <el-button type="text" |
| | | <el-button v-permission="27" type="text" |
| | | @click="$router.push(`/systemManage/role-detail?roleId=${row.roleId}`)">详情</el-button> |
| | | <el-button type="text" @click="$router.push(`/systemManage/add-role?roleId=${row.roleId}`)">编辑</el-button> |
| | | <el-button type="text" @click="del(row)">删除</el-button> |
| | | <el-button v-if="row.roleId != 1" v-permission="25" type="text" @click="$router.push(`/systemManage/add-role?roleId=${row.roleId}`)">编辑</el-button> |
| | | <el-button v-if="row.roleId != 1" v-permission="26" type="text" @click="del(row)">删除</el-button> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | }, |
| | | watch: {}, |
| | | created() { |
| | | this.$checkPermission(23) |
| | | this.getListData() |
| | | }, |
| | | mounted() { }, |
| | |
| | | </div> |
| | | </div> |
| | | <div class="table-box-btn mt--23 ml--30"> |
| | | <el-button class="search-button h--40 w--90 fs--14" icon="el-icon-plus" type="primary" size="small" |
| | | <el-button v-permission="17" class="search-button h--40 w--90 fs--14" icon="el-icon-plus" type="primary" size="small" |
| | | @click="dialogVisible = true">添加</el-button> |
| | | <!-- <el-button class="search-button h--40 w--90 fs--14" icon="el-icon-delete" type="danger" size="small" |
| | | @click="dialogVisible = true">删除</el-button> --> |
| | | </div> |
| | | <div class="table-box ml--30 mt--23 mr--30"> |
| | | <el-table :data="data" border stripe style="width: 100%" :height="height" :element-loading-text="'正在加载...'"> |
| | | <el-table :data="data" border stripe style="width: 100%" :element-loading-text="'正在加载...'"> |
| | | <el-table-column prop="nickName" label="姓名"></el-table-column> |
| | | <el-table-column prop="phonenumber" label="联系电话"> |
| | | </el-table-column> |
| | |
| | | <el-table-column label="操作" width="300"> |
| | | <template #default="{ row }"> |
| | | <div v-if="row.userId != 1"> |
| | | <el-button type="text" @click="edit(row)">编辑</el-button> |
| | | <el-button v-if="row.status != 0" type="text" @click="updateStatus(row, true)">启用</el-button> |
| | | <el-button v-if="row.status == 0" type="text" @click="updateStatus(row, false)">禁用</el-button> |
| | | <el-button type="text" @click="detail(row)">重置密码</el-button> |
| | | <el-button type="text" @click="del(row)">删除</el-button> |
| | | <el-button v-permission="19" type="text" @click="edit(row)">编辑</el-button> |
| | | <el-button v-permission="20" v-if="row.status != 0" type="text" @click="updateStatus(row, true)">启用</el-button> |
| | | <el-button v-permission="20" v-if="row.status == 0" type="text" @click="updateStatus(row, false)">禁用</el-button> |
| | | <el-button v-permission="21" type="text" @click="detail(row)">重置密码</el-button> |
| | | <el-button v-permission="18" type="text" @click="del(row)">删除</el-button> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | }, |
| | | watch: {}, |
| | | created() { |
| | | this.$checkPermission(16) |
| | | |
| | | this.getRoleList() |
| | | this.getListData() |
| | | }, |