pyt
2025-04-21 dc7262f5f6131c25e9ae0a70e473e68a960ad093
Merge branch 'master' of http://120.76.84.145:10101/gitblit/r/H5/shehong-vehicle-supervision
3个文件已添加
15个文件已修改
926 ■■■■ 已修改文件
src/assets/loginBG.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PlayLive/index.vue 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/PlayLive/service.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/index.vue 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/index.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/car-manage/detail.vue 319 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/car-manage/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/car-manage/service.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/home/index.vue 264 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/home/service.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/login/index.vue 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/order/component/detailModal.vue 81 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/order/index.vue 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/systemManage/user/components/addEdit.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/systemManage/user/components/disb.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/view/systemManage/user/components/resetPassWord.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/loginBG.png
src/components/PlayLive/index.vue
New file
@@ -0,0 +1,91 @@
<template>
    <div>
        <video id="video" width="100%" height="100%" muted controls></video>
    </div>
</template>
<script>
import { playDetection, closeRealVideo } from './service'
import flvjs from 'flv.js'
export default {
    props: {
        serverIp: {
            type: String,
            required: ''
        },
        serverPort: {
            type: Number,
            required: null
        },
        carId: {
            type: Number,
            required: null
        },
    },
    data() {
        return {
            flvPlayer: null,
            timer: null,
        }
    },
    mounted() {
        this.playDetection()
    },
    beforeDestroy() {
        this.destroyPlayer();
    },
    methods: {
        playDetection() {
            if (flvjs.isSupported()) {
                playDetection(this.carId).then(res => {
                    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}`,  // 后端拿到的视频路径
                        enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程
                        enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。
                        stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb
                        stashInitialTime: 0.2, // 缓存初始时间。单位:秒。建议针对直播:调整为200毫秒
                        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容器
                    this.flvPlayer.load();
                    this.flvPlayer.play().then(res => {
                        this.timer = setInterval(() => {
                            playDetection(this.carId)
                        }, 5000)
                    }).catch(err => {
                        this.destroyPlayer();
                    })
                    // 错误监听
                    this.flvPlayer.on('error', (err) => {
                        this.destroyPlayer();
                    });
                })
            }
        },
        destroyPlayer() {
            // 销毁播放器释放资源
            if (this.flvPlayer) {
                if (this.timer) clearInterval(this.timer)
                closeRealVideo(this.carId).then(res => {
                    this.flvPlayer.pause();
                    this.flvPlayer.unload();
                    this.flvPlayer.detachMediaElement();
                    this.flvPlayer.destroy();
                    this.flvPlayer = null;
                })
            }
        }
    }
}
</script>
<style></style>
src/components/PlayLive/service.js
New file
@@ -0,0 +1,11 @@
import axios from '@/utils/request';
// 通知后端开始获取视频流
export const playDetection = (id) => {
    return axios.get(`/system/car/playDetection/${id}`)
}
// 通知后端开始关闭视频流
export const closeRealVideo = (id) => {
    return axios.get(`/system/car/closeRealVideo/${id}`)
}
src/layouts/index.vue
@@ -1,6 +1,6 @@
<template>
    <div class="sticky top0 layout">
        <div class="header relative pointer">
        <div class="header relative">
            <div @click="$router.push('/home')" class="title">
                <img src="@/assets/logo.png" alt="">
                射洪“两客一危”监管平台
@@ -8,13 +8,14 @@
            <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" />
                    <div class="fs-- 18 lh--25 color2">admin</div>
                    <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 class="dropdown-item" v-for="item in menuItems" :key="item.text">
                        <div @click="clickItem(item)" class="dropdown-item" v-for="item in menuItems" :key="item.text">
                            <i :class="item.icon"></i> {{ item.text }}
                        </div>
                    </div>
@@ -60,12 +61,20 @@
        <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'
export default {
    components: {
        ResetPassword,
    },
    data() {
        return {
            routesList: routes,
@@ -74,12 +83,36 @@
            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
@@ -99,11 +132,13 @@
    display: flex;
    flex-direction: column;
    height: 100%;
    .main {
        flex: 1;
        overflow: auto;
    }
}
.bgColor1 {
    background-color: #0E6EFD;
}
src/main.js
@@ -9,13 +9,16 @@
import {
  Message
} from 'element-ui'
import PlayLive from '@/components/PlayLive'
Vue.use(ElementUI)
Vue.prototype.$cookies = cookies;
Vue.prototype.$baseURL = apiConfig.baseURL
Vue.prototype.$mapKey = apiConfig.mapKey
Vue.prototype.$secretKey = apiConfig.secretKey
Vue.prototype.$store = store
Vue.config.productionTip = false
Vue.component('PlayLive', PlayLive)
/* 全局TableHeight */
Vue.prototype.$baseTableHeight = (formType) => {
  let height = window.innerHeight
src/store/index.js
@@ -6,7 +6,7 @@
export default new Vuex.Store({
  state: {
    token: localStorage.getItem('token') || sessionStorage.getItem('token') || '',
    userInfo: localStorage.getItem('userInfo') || {}
    userInfo: JSON.parse(localStorage.getItem('userInfo')) || {}
  },
  mutations: {
    setToken(state, token) {
src/utils/request.js
@@ -10,7 +10,7 @@
const service = axios.create({
  baseURL: apiConfig.baseURL,
  withCredentials: false, // 当跨域请求时发送cookie
  timeout: 30000, // request timeout
  timeout: 60000, // request timeout
})
// 对 POST 请求参数进行排序
const sortPostParams = (params) => {
src/view/car-manage/detail.vue
@@ -36,19 +36,19 @@
                <el-tab-pane label="订单记录" name="first">
                    <div class="table-box mt--23">
                        <el-table :data="tableData" border stripe style="width: 100%">
                            <el-table-column prop="date" 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="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 prop="index" label="序号"></el-table-column>
                            <el-table-column prop="code" label="订单编号"></el-table-column>
                            <el-table-column prop="vehicleNumber" label="车牌号"></el-table-column>
                            <el-table-column prop="licensePlateColor" label="车牌颜色"></el-table-column>
                            <el-table-column prop="enterpriseName" label="车辆所属公司"></el-table-column>
                            <el-table-column prop="boardingPoint" label="上车地点"></el-table-column>
                            <el-table-column prop="dropOffPoint" label="下车地点"></el-table-column>
                            <el-table-column prop="passengerMileage" label="载客里程"></el-table-column>
                            <el-table-column prop="driverName" label="驾驶员姓名"></el-table-column>
                            <el-table-column prop="driverPhone" label="驾驶员电话"></el-table-column>
                            <el-table-column prop="orderDeliveryTime" label="派单时间"></el-table-column>
                            <el-table-column prop="orderAmount" label="订单金额"></el-table-column>
                            <el-table-column prop="option" label="操作">
                                <template slot-scope="scope">
                                    <el-button type="text" @click="handle(scope.$index, scope.row)">详情</el-button>
                                </template>
@@ -66,24 +66,29 @@
                <el-tab-pane label="预警记录" name="second">
                    <div class="table-box mt--23">
                        <el-table :data="tableData" border stripe style="width: 100%">
                            <el-table-column prop="date" label="序号" fixed width="80"></el-table-column>
                            <el-table-column prop="name" label="车辆名称" width="120" fixed></el-table-column>
                            <el-table-column prop="name" label="车牌号码" width="120" fixed></el-table-column>
                            <el-table-column prop="name" label="持续报警" width="120"></el-table-column>
                            <el-table-column prop="name" label="近15分钟情况" width="120"></el-table-column>
                            <el-table-column prop="name" label="驾驶员名称" width="120"></el-table-column>
                            <el-table-column prop="name" label="所属公司" width="120"></el-table-column>
                            <el-table-column prop="name" label="终端编号" width="120"></el-table-column>
                            <el-table-column prop="name" label="开始报警时间" width="120"></el-table-column>
                            <el-table-column prop="name" label="结束报警时间" width="120"></el-table-column>
                            <el-table-column prop="name" label="持续时长" width="120"></el-table-column>
                            <el-table-column prop="name" label="持续里程数" width="120"></el-table-column>
                            <el-table-column prop="name" label="报警类型" width="120"></el-table-column>
                            <el-table-column prop="name" label="报警次数" width="120"></el-table-column>
                            <el-table-column prop="name" label="处理状态" width="120"></el-table-column>
                            <el-table-column prop="name" label="处理人" width="120"></el-table-column>
                            <el-table-column prop="name" label="处理时间" width="120"></el-table-column>
                            <el-table-column prop="name" label="处理描述" width="240"></el-table-column>
                            <el-table-column prop="index" label="序号" fixed width="80"></el-table-column>
                            <el-table-column prop="carName" label="车辆名称" width="120" fixed></el-table-column>
                            <el-table-column prop="vehicleNumber" label="车牌号码" width="120" fixed></el-table-column>
                            <el-table-column prop="keepWarn" label="持续报警" width="120"></el-table-column>
                            <el-table-column prop="name" label="近15分钟情况">
                                <template #default="{ row }">
                                    <img src="@/assets/homeImg/eye-fill.png" alt="" @click="viewDetail(row)"
                                        style="width: 30px;cursor: pointer;">
                                </template>
                            </el-table-column>
                            <el-table-column prop="driverName" label="驾驶员名称" width="120"></el-table-column>
                            <el-table-column prop="enterpriseName" label="所属公司" width="120"></el-table-column>
                            <el-table-column prop="terminalNumber" label="终端编号" width="120"></el-table-column>
                            <el-table-column prop="startTime" label="开始报警时间" width="120"></el-table-column>
                            <el-table-column prop="endTime" label="结束报警时间" width="120"></el-table-column>
                            <el-table-column prop="keepTime" label="持续时长" width="120"></el-table-column>
                            <el-table-column prop="keepDistance" label="持续里程数" width="120"></el-table-column>
                            <el-table-column prop="warnType" label="报警类型" width="120"></el-table-column>
                            <el-table-column prop="warnNumber" label="报警次数" width="120"></el-table-column>
                            <el-table-column prop="treatmentState" label="处理状态" width="120"></el-table-column>
                            <el-table-column prop="treatmentUser" label="处理人" width="120"></el-table-column>
                            <el-table-column prop="treatmentTime" label="处理时间" width="120"></el-table-column>
                            <el-table-column prop="treatmentRemark" label="处理描述" width="240"></el-table-column>
                        </el-table>
                        <div class="pagination-box relative mt--23 flex j-end">
@@ -100,8 +105,9 @@
                            <el-form :inline="true" :model="searchForm" class="demo-form-inline">
                                <el-form-item label="选择轨迹时间范围:" prop="level" class="unset_m"
                                    style="margin-right: 15px;">
                                    <el-date-picker v-model="searchForm.date" type="datetimerange" range-separator="至"
                                        start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd">
                                    <el-date-picker :value-format="'yyyy-MM-dd HH:mm'" v-model="searchForm.date"
                                        type="datetimerange" range-separator="至" start-placeholder="开始日期"
                                        end-placeholder="结束日期">
                                    </el-date-picker>
                                </el-form-item>
                            </el-form>
@@ -118,15 +124,85 @@
                </el-tab-pane>
            </el-tabs>
        </div>
        <DetailModal ref="detailModal" :detail="detail"
  />
        <DetailModal ref="detailModal" :detail="detail" />
        <el-drawer :visible.sync="drawer" append-to-body :size="450" @close="closeDrawer">
            <div class="flex j-between a-center fs--20 pl--15 pr--15">
                <!-- 使用 Tailwind CSS 的内联十六进制颜色类 -->
                <div>{{ info.vehicleNumber }}<span v-if="info.warnList && info.warnList.length > 0">({{
                    info.warnList.length
                }})</span></div><i @click="closeDrawer" class="el-icon-s-unfold color1 pointer"></i>
            </div>
            <hr class="mt--10" />
            <div class="pl--15 pr--15">
                <div class="flex mt--15">
                    <div class="w--100 shrink0 color1">当前司机:</div>
                    <div class="color2">{{ info.driverName }}</div>
                </div>
                <div class="flex mt--15">
                    <div class="w--100 shrink0 color1">当前车速:</div>
                    <div class="color2">{{ info.speed }}km/h</div>
                </div>
                <div class="flex mt--15">
                    <div class="w--100 shrink0 color1">当前位置:</div>
                    <div class="color2">{{ info.nowAddress }}</div>
                </div>
                <div v-if="info.imageUrl" class="flex mt--15">
                    <div class="w--100 shrink0 color1">抓拍照片:</div>
                    <el-image style="height: 100px" :src="info.imageUrl" :preview-src-list="[info.imageUrl]">
                    </el-image>
                </div>
            </div>
            <hr class="mt--10" />
            <div class="flex j-between a-center fs--15 pl--15 pr--15 mt--15 color2">
                近15分钟报警
            </div>
            <div class="block pl--15 pr--15 mt--15">
                <el-timeline>
                    <el-timeline-item v-for="(item, index) in info.warnList" :key="index" color='#0E6EFD'
                        :timestamp="item.warnTime" placement="top">
                        <div @click="initMap1(item)">
                            <el-card class="pointer">
                                <h4>{{ item.warnType }}</h4>
                                <p class="color1">{{ item.speed }}km/h</p>
                            </el-card>
                        </div>
                    </el-timeline-item>
                </el-timeline>
            </div>
            <div v-if="showWarnDetail" class="fixed">
                <div class="card">
                    <div class="title fs--18 color2">视频信号遮挡报警</div>
                    <div id="mapContainers"></div>
                    <div class="">
                        <div class="flex mt--15">
                            <div class="w--100 shrink0 color1">司机:</div>
                            <div class="color2">{{ info.driverName }}</div>
                        </div>
                        <div class="flex mt--15">
                            <div class="w--100 shrink0 color1">车速:</div>
                            <div class="color2">{{ activeInfo.speed }}km/h</div>
                        </div>
                        <div class="flex mt--15">
                            <div class="w--100 shrink0 color1">时间:</div>
                            <div class="color2">{{ activeInfo.warnTime }}</div>
                        </div>
                        <div class="flex mt--15">
                            <div class="w--100 shrink0 color1">地点:</div>
                            <div class="color2">{{ activeInfo.address }}</div>
                        </div>
                    </div>
                </div>
            </div>
        </el-drawer>
    </div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
import DetailModal from "./components/detailModal.vue";
import { getCarDetail,getCarOrder,getCarWarning,getCarTrack } from './service'
import { getCarDetail, getCarOrder, getCarWarning, getCarTrack, getCarVideo,getDetail } from './service'
export default {
    name: "detail",
    components: { DetailModal },
@@ -137,11 +213,18 @@
            searchForm: {
                pageCurr: 1,
                pageSize: 10,
                total: 0
                total: 0,
                date: undefined,
            },
            detail:{},
            activeName: 'first',
            routeList:[],
            videoObj: {},
            drawer: false,
            showWarnDetail: false,
            info: {},
            activeInfo: {},
            map: null
        }
    },
    mounted() {
@@ -150,10 +233,61 @@
                this.detail = res;
                this.getList(res.vehicleNumber);
            })
            getCarVideo({ id: this.$route.query.id }).then(res => {
                this.videoObj = res;
            })
        }
        
    },
    methods: {
        closeDrawer() {
            this.drawer = false
            this.showWarnDetail = false
        },
        // 查看详情
        viewDetail(row) {
            this.drawer = true
            getDetail({ vehicleNumber: row.vehicleNumber }).then(res => {
                this.info = res
            })
        },
        // 初始化地图
        initMap1(row) {
            this.showWarnDetail = true
            this.activeInfo = row
            this.$nextTick(() => {
                window._AMapSecurityConfig = {
                    securityJsCode: this.$secretKey,
                };
                AMapLoader.load({
                    key:this.$mapKey,
                    version: "2.0",
                    plugins: [
                        "AMap.ToolBar",
                        "AMap.AutoComplete",
                        "AMap.Geocoder",
                        "AMap.MarkerCluster",
                    ],
                })
                    .then((AMap) => {
                        this.map = new AMap.Map("mapContainers", {
                            center: [row.lon, row.lat],
                            zoom: 15,
                        });
                        // 添加标记
                        new AMap.Marker({
                            position: [row.lon, row.lat],
                            map: this.map,
                            title: row.warnType
                        });
                    })
                    .catch((e) => {
                        console.log(e);
                    });
            })
        },
        handleClick(e) {
            this.activeName = e.name
            if(e.name != 'third'){
@@ -162,7 +296,12 @@
                    pageSize: 10,
                    total: 0,
                }
                this.getList(this.detail.vehicleNumber)
                //销毁地图
                if (this.map) {
                    this.map.destroy();
                }
            }else{
                getCarTrack({...this.searchForm,vehicleNumber:this.detail.vehicleNumber}).then(res => {
                  this.routeList = res;
@@ -173,9 +312,31 @@
            }
        },
        search() {
            if (this.searchForm.date != undefined) {
                this.searchForm = {
                    pageCurr: 1,
                    pageSize: 10,
                    total: 0,
                    startTime: this.searchForm.date[0],
                    endTime: this.searchForm.date[1],
                }
                getCarTrack({ ...this.searchForm, vehicleNumber: this.detail.vehicleNumber }).then(res => {
                    this.routeList = res;
                    if (res.length > 0) {
                        this.initMap();
                    }
                })
            }
         },
        reset() {
            this.searchForm = {
               pageCurr: 1,
               pageSize: 10,
               total: 0,
               date: undefined,
            }
        },
        getList(vehicleNumber) { 
            if(this.activeName == 'first'){
@@ -213,23 +374,50 @@
                    });
                    this.map.addControl(new AMap.ToolBar());
                    let path = [
                        // new AMap.LngLat(105.57, 30.51),
                        // new AMap.LngLat(116.382122, 39.901176),
                        // new AMap.LngLat(116.387271, 39.912501),
                        // new AMap.LngLat(116.398258, 39.9046),
                    ]
                    let path = []
                    const iconMap = {
                        出租车: {
                            icon: require("../../assets/homeImg/taxi.png"),
                            size: new AMap.Size(75, 37),
                        },
                        公交车: {
                            icon: require("../../assets/homeImg/bus.png"),
                            size: new AMap.Size(62, 34),
                        },
                        危险品: {
                            icon: require("../../assets/homeImg/risk.png"),
                            size: new AMap.Size(69, 32),
                        },
                        郊游: {
                            icon: require("../../assets/homeImg/outing.png"),
                            size: new AMap.Size(61, 31),
                        },
                        货运: {
                            icon: require("../../assets/homeImg/expressage.png"),
                            size: new AMap.Size(60, 31),
                        },
                        网约车: {
                            icon: require("../../assets/homeImg/online.png"),
                            size: new AMap.Size(75, 33),
                        },
                        客运: {
                            icon: require("../../assets/homeImg/passenger.png"),
                            size: new AMap.Size(69, 31),
                        },
                    };
                    this.routeList.forEach(item => {
                        path.push(new AMap.LngLat(item.longitude, item.latitude))
                    })
                    const content = `<div class="custom-content-marker">
                                        <img src="${require("@/assets/logo.png")}">
                                        <img src="${iconMap[this.detail.operateType].icon}">
                                    </div>`;
                    const marker = new AMap.Marker({
                        content: content, //自定义点标记覆盖物内容
                        position: [105.57, 30.51], //基点位置
                        offset: new AMap.Pixel(-30, -15), //相对于基点的偏移位置
                        size: iconMap[this.detail.operateType].size,
                    });
                    this.map.add(marker);
                    let polyline = new AMap.Polyline({
@@ -322,4 +510,47 @@
    width: 100%;
    height: 600px;
}
#mapContainers {
    width: 100%;
    height: 500px;
    margin: 20px 0;
}
::v-deep .el-drawer__body {
    position: relative;
    .fixed {
        right: 470px;
        top: 0;
        bottom: 0;
        margin: auto;
        display: flex;
        align-items: center;
        .card {
            background: #fff;
            width: 500px;
            max-height: 1000px;
            padding: 15px;
        }
    }
}
.color1 {
    color: #0E6EFD;
}
.color2 {
    color: rgb(52, 52, 52);
}
::v-deep .pagination-popper {
    position: fixed !important;
    transform:
        scale(calc(1 / var(--scale))) translate(calc(100px * (1 - 1 / var(--scale))),
            calc(5px * (1 - 1 / var(--scale)))) !important;
    transform-origin: right top !important;
    right: calc(30px * (1 - 1 / var(--scale))) !important;
    margin-top: 5px;
    min-width: 100px !important;
}
</style>
src/view/car-manage/index.vue
@@ -52,10 +52,6 @@
                    size="small">重置</el-button>
            </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-top" type="primary" size="small"
                @click="exportExcell">导出</el-button>
        </div>
        <div class="table-box ml--30 mt--23 mr--30">
            <el-table :data="tableData" border stripe style="width: 100%">
                <el-table-column type="index" label="序号" width="60"></el-table-column>
@@ -125,6 +121,13 @@
        getCarType().then(res => {
            this.options = res;
        });
        // 判断URL参数并赋值
        const query = this.$route.query;
        if (query && Object.keys(query).length > 0) {
            if(query.id){
                this.searchForm.operationType = Number(query.id);
            }
        }
        this.getList();
    },
    methods: {
src/view/car-manage/service.js
@@ -30,3 +30,14 @@
export const getCarTrack = (data) => {
    return axios.get(`/system/car/getCarTravel`, {params:data}) 
}
//获取车辆详情实时视频
export const getCarVideo = (data) => {
    return axios.get(`/system/car/getRealVideo/${data.id}`)
}
// 获取车辆预警详情
export const getDetail = (params) => {
    console.log(params)
    return axios.get('/system/warn/getCarWarnInfo', {params})
}
src/view/home/index.vue
@@ -10,6 +10,7 @@
            class="countCard"
            v-for="(item, index) in carCountData.slice(0, 3)"
            :key="item.id"
            @click="toCarManage(item.id)"
          >
            <img class="iconImg" :src="imgList[index]" />
            <div>
@@ -22,6 +23,7 @@
          <div
            class="countCard"
            v-for="(item, index) in carCountData.slice(3, 7)"
            @click="toCarManage(item.id)"
            :key="item.id"
          >
            <img class="iconImg" :src="imgList[index + 3]" />
@@ -154,8 +156,8 @@
              }}
            </div>
            <div class="info">
              {{ item.vehicleNumber  }} {{ item.warnType  }}
              {{ item.keepTime }} {{ item.startTime }}
              {{ item.vehicleNumber }} {{ item.warnType }} {{ item.keepTime }}
              {{ item.startTime }}
            </div>
          </div>
        </div>
@@ -205,12 +207,15 @@
  getCarWarnList,
  getWarnGroupCount,
  getWarnGroupCountTop10,
  getCarInfoById,
  getRealVideo,
  playDetection,
  closeRealVideo,
} from "./service";
export default {
  data() {
    return {
      flvPlayer: null,
      videoTimer: null,
      activeIndex: "1",
      activeIndex2: "1",
      timer: null,
@@ -233,6 +238,10 @@
      carCountData: [], //车辆统计数据
      carStatusData: {}, //车辆状态数据
      warnList: [], //预警列表数据
      serverIp: "", //监控ip
      serverPort: "", //监控端口
      carId: "", //监控车辆
    };
  },
  watch: {
@@ -251,6 +260,8 @@
      }
    },
  },
  filters: {},
  created() {
    window.toCarDetail = (record) => {
      this.toCarDetail(record);
@@ -286,6 +297,7 @@
    if (this.timer) {
      clearInterval(this.timer);
    }
    this.destroyPlayer();
    if (this.markers && this.markers.length > 0) {
      this.markers.forEach((marker) => {
        marker.setMap(null);
@@ -311,6 +323,12 @@
    next();
  },
  methods: {
    toCarManage(id) {
      this.$router.push({
        path: "/car-manage",
        query: { id },
      });
    },
    // 获取车辆统计数据
    async getCarCountData() {
      try {
@@ -436,16 +454,17 @@
    // 初始化地图
    initMap() {
      window._AMapSecurityConfig = {
        securityJsCode: "37ce61ae86efa5ad82b649a277f5097c",
        securityJsCode: this.$secretKey,
      };
      AMapLoader.load({
        key: "67968c82f27c7e2cb9f40c1a9aa3042b",
        key: this.$mapKey,
        version: "2.0",
        plugins: [
          "AMap.ToolBar",
          "AMap.AutoComplete",
          "AMap.Geocoder",
          "AMap.MarkerCluster",
          "AMap.Geocoder",
        ],
      })
        .then((AMap) => {
@@ -458,6 +477,11 @@
            offset: new AMap.Pixel(30, 30),
            autoMove: true,
            anchor: "top-center",
          });
          // 添加信息弹窗关闭事件监听
          this.infoWindow.on("close", () => {
            console.log("关闭信息弹窗1111111111111111111");
            this.destroyPlayer();
          });
          this.getMapCarData();
        })
@@ -546,9 +570,20 @@
            this.infoWindow.open(this.map, e.target.getPosition());
            try {
              // 并行请求车辆信息和视频地址
              const [carInfoRes, videoRes] = await Promise.all([
                this.getCarInfo(item.id),
              // 使用高德地图API获取地址信息
              const geocoder = new this.AMap.Geocoder();
              const location = [Number(item.longitude), Number(item.latitude)];
              const [addressResult, videoRes] = await Promise.all([
                new Promise((resolve) => {
                  geocoder.getAddress(location, (status, result) => {
                    if (status === "complete" && result.regeocode) {
                      resolve(result.regeocode.formattedAddress);
                    } else {
                      resolve("未知地址");
                    }
                  });
                }),
                this.getVideoUrl(item.id),
              ]);
@@ -556,28 +591,12 @@
              this.infoWindow.setContent(
                this.listRender({
                  ...item,
                  ...carInfoRes.data,
                  videoUrl: videoRes.data.url,
                  drivingTime: this.formatterTime(item.drivingTime || 0),
                  location: addressResult,
                })
              );
              if (flvjs.isSupported()) {
                this.flvPlayer = flvjs.createPlayer({
                  type: "flv",
                  isLive: true,
                  cors: true,
                  hasAudio: true,
                  hasVideo: true,
                  url: videoRes.data.url,
                  enableWorker: true,
                  enableStashBuffer: false,
                  seekType: "range",
                });
                let video = document.getElementById("monitoringCard");
                this.flvPlayer.attachMediaElement(video);
                this.flvPlayer.load();
                this.flvPlayer.play();
              }
              this.initVideoPlayer();
            } catch (error) {
              this.infoWindow.setContent(
                '<div style="padding: 20px;text-align: center;color: red;">获取车辆信息失败</div>'
@@ -590,64 +609,122 @@
        });
      }
    },
    // 获取车辆信息
    async getCarInfo(carId) {
      try {
        const res = await getCarInfoById({ id: carId });
        if (res) {
          return {
            data: {
              ...res,
              id: carId,
            },
          };
        }
        return {
          data: {
            id: carId,
          },
        };
      } catch (error) {
        return {
          data: {
            licensePlate: "",
            driver: "",
            location: "",
            coordinates: "",
            speed: "0km/h",
            drivingTime: "0小时0分钟",
          },
        };
      }
    },
    // 获取视频地址
    async getVideoUrl(carId) {
      // TODO: 替换为实际的API调用
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve({
            data: {
              url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4",
      try {
        const res = await getRealVideo({ id: carId });
        // 将RTSP流转换为FLV流
        this.serverIp = res.serverIp;
        this.serverPort = res.serverPort;
        this.carId = carId;
      } catch (error) {
        console.error("获取视频地址失败", error);
        return {};
      }
            },
    // 初始化视频播放器
    initVideoPlayer(videoUrl) {
      console.log('11111',this.serverIp,'2222222222',this.serverPort)
      // 先销毁之前的播放器
      if (this.flvPlayer) {
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      // 获取video元素
      const video = document.getElementById("monitoringCard");
      if (!video) {
        console.error("Video element not found");
        return;
      }
      // 检查flv.js是否支持
      if (flvjs.isSupported()) {
        try {
          playDetection(this.carId).then((res) => {
            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}`, // 后端拿到的视频路径
              enableWorker: true, //启用 Web Worker 进程来加速视频的解码和处理过程
              enableStashBuffer: false, // 启用数据缓存机制,提高视频的流畅度和稳定性。
              stashInitialSize: 1024, // 初始缓存大小。单位:字节。建议针对直播:调整为1024kb
              stashInitialTime: 0.2, // 缓存初始时间。单位:秒。建议针对直播:调整为200毫秒
              seekType: "range", // 建议将其设置为“range”模式,以便更快地加载视频数据,提高视频的实时性。
              lazyLoad: false, //关闭懒加载模式,从而提高视频的实时性。建议针对直播:调整为false
              lazyLoadMaxDuration: 0.2, // 懒加载的最大时长。单位:秒。建议针对直播:调整为200毫秒
              deferLoadAfterSourceOpen: false, // 不预先加载视频数据,在 MSE(Media Source Extensions)打开后立即加载数据,提高视频的实时性。建议针对直播:调整为false
          });
        }, 500);
            let video = document.getElementById("monitoringCard");
            this.flvPlayer.attachMediaElement(video); // video容器
            this.flvPlayer.load();
            this.flvPlayer
              .play()
              .then((res) => {
                this.videoTimer = setInterval(() => {
                  playDetection(this.carId);
                }, 5000);
              })
              .catch((err) => {
                this.destroyPlayer();
      });
            // 错误监听
            this.flvPlayer.on("error", (err) => {
              this.destroyPlayer();
            });
          });
        } catch (error) {
          console.error("创建播放器失败:", error);
        }
      } else {
        console.error("当前浏览器不支持flv.js");
      }
    },
    destroyPlayer() {
      // 销毁播放器释放资源
      if (this.flvPlayer) {
        if (this.videoTimer) clearInterval(this.videoTimer);
        closeRealVideo(this.carId).then((res) => {
          this.flvPlayer.pause();
          this.flvPlayer.unload();
          this.flvPlayer.detachMediaElement();
          this.flvPlayer.destroy();
          this.flvPlayer = null;
        });
      }
    },
    // 处理视频错误
    handleVideoError(event) {
      console.error("视频加载失败", event);
      if (this.flvPlayer) {
        this.flvPlayer.destroy();
        this.flvPlayer = null;
      }
      this.infoWindow.setContent(
        '<div style="padding: 20px;text-align: center;color: red;">视频加载失败,请稍后重试</div>'
      );
    },
    listRender(record) {
      return `<div style="background: #ffffff; padding: 24px 20px;z-index: 999">
        <div style="position: relative; width: 460px; height: 330px">
          <video
            crossorigin="anonymous"
              ref="video"
            style="width: 460px; height: 330px; border-radius: 9px" 
            id="monitoringCard"
               ref="monitoringCard"
            :controls="false"
            autoplay
            src="${record.videoUrl}"
                autoPlay
            width="620">
          </video>
          <canvas id="myCanvas" style="display:none"></canvas>
          <div style="position: absolute; right: 11px; top: 10px">
            <div style="display: flex;flex-direction: column;align-items: center;justify-content: center;
              background: #ffffff; padding: 3px 10px; border-radius: 6px;margin-bottom: 10px;" onclick="fullScreen()">
@@ -666,21 +743,21 @@
            record.vehicleNumber || ""
          }</div>
          <div style="font-weight: 500; font-size: 18px;color: rgba(0, 0, 0, 0.85);line-height: 25px;">驾驶员:${
            record.driverName||''
            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;">位置:${
          <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
          }</div>
          }">位置:${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
            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
          }</div>
            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>
@@ -692,6 +769,16 @@
          <img style="width:18px;height: 18px;margin-left: 8px;" src="${require("../../assets//homeImg/right.png")}" />
        </div>
      </div>`;
    },
    formatterTime(value) {
      if (!value) return "";
      const hours = Math.floor(value / 60);
      const minutes = value % 60;
      if (hours > 0) {
        return `${hours}小时${minutes}分钟`;
      } else {
        return `${minutes}分钟`;
      }
    },
    // 获取预警情况统计
    getCountList() {
@@ -747,9 +834,19 @@
            itemStyle: {
              borderRadius: [20, 20, 20, 20],
              color: (params) => {
                return ["#5B8FF9", "#5AD8A6", "#F6BD16", "#6DC8EC", "#945FB9"][
                  params.dataIndex
                ];
                return [
                  "#5B8FF9",
                  "#5AD8A6",
                  "#F6BD16",
                  "#6DC8EC",
                  "#945FB9",
                  "rgba(248, 204, 65, 0.5)",
                  "rgba(2, 179, 118, 0.5)",
                  "rgba(254, 41, 94, 0.5)",
                  "rgba(255, 102, 39, 0.5)",
                  "rgba(169, 14, 253, 0.5)",
                  "rgba(109, 200, 236, 0.5)",
                ][params.dataIndex];
              },
            },
            data: this.countList.map((item) => item.num),
@@ -782,6 +879,14 @@
        // IE/Edge
        video.msRequestFullscreen();
      }
    },
    getRandomColor() {
      const letters = "0123456789ABCDEF";
      let color = "#";
      for (let i = 0; i < 6; i++) {
        color += letters[Math.floor(Math.random() * 16)];
      }
      return color;
    },
    shotScreen() {
      // 获取video和canvas元素
@@ -1229,6 +1334,9 @@
            color: rgba(0, 0, 0, 0.45);
            line-height: 17px;
            text-align: right;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
          }
          .rankRight {
src/view/home/service.js
@@ -27,3 +27,17 @@
export const getCarInfoById = (data) => {
    return axios.get(`/system/car/getCarInfo/${data.id}`, data)
}
// 获取监控
export const getRealVideo = (data) => {
    return axios.get(`/system/car/getRealVideo/${data.id}`, data)
}
// 通知后端开始获取视频流
export const playDetection = (id) => {
    return axios.get(`/system/car/playDetection/${id}`)
}
// 通知后端开始关闭视频流
export const closeRealVideo = (id) => {
    return axios.get(`/system/car/closeRealVideo/${id}`)
}
src/view/login/index.vue
@@ -1,12 +1,12 @@
<template>
  <div>
  <div class="bgImg">
    <div class="login_box">
      <div class="fs--40 fw-bold">
        <h1>
          射洪“两客一危”监管平台
        </h1>
      </div>
      <div class="mt--20 txt-center py--20 px--20 br--10 box-s1">
      <div class="mt--20 txt-center py--20 px--20">
        <div class="fs--20 fw-bold">登陆</div>
        <div class="mt--20">
          <el-input class="w100" prefix-icon="el-icon-user" placeholder="账号" v-model="username" />
@@ -57,15 +57,13 @@
    document.removeEventListener("keydown", this.handleKeyDown);
  },
  methods: {
    ...mapMutations(['setToken', 'clearToken', 'setUserInfo']),
    ...mapMutations(['setToken', 'setUserInfo']),
    login() {
      if (!this.rulesLogin()) return
      this.loginLoading = true;
      loginPwd({
        username: this.username,
        password: CryptoJS.HmacMD5(this.password, 'password').toString(
          CryptoJS.enc.Hex,
        )
        password: CryptoJS.MD5(this.password).toString()
      }).then(res => {
        localStorage.setItem('client', generateRandomString(16));
        this.loginLoading = false;
@@ -129,15 +127,24 @@
};
</script>
<style scoped lang="less">
.bgImg {
  background-image: url('../../assets/loginBG.png');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  width: 100%;
  height: 100%;
}
.login_box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.box-s1 {
  background-color: #fff;
  box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
  border-radius: 10px;
  padding: 20px;
}
.code_box {
src/view/order/component/detailModal.vue
@@ -1,6 +1,7 @@
<template>
    <div>
        <el-dialog title="订单详情" :visible.sync="dialogVisible" width="50%" :modal-append-to-body="false">
        <el-dialog title="订单详情" :visible.sync="dialogVisible" width="50%" :modal-append-to-body="false"
            @close="closeClick">
            <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>
@@ -27,11 +28,14 @@
                </el-descriptions>
            </div>
            <!-- 行程轨迹 -->
            <div v-show="tabPosition == 'track'">
            <div v-if="tabPosition == 'track'">
                <div class="mapContainer" id="mapContainer"></div>
            </div>
            <!-- 行程监控 -->
            <div v-show="tabPosition == 'monitoring'"></div>
            <div v-if="tabPosition == 'monitoring'">
                <PlayLive :serverIp="monitoringData.serverIp" :serverPort="monitoringData.serverPort"
                    :carId="orderData.carId" />
            </div>
        </el-dialog>
    </div>
</template>
@@ -45,19 +49,26 @@
            tabPosition: 'order',
            orderData: {},
            monitoringData: {},
            travelData: []
            travelData: [],
        };
    },
    computed: {},
    watch: {},
    created() { },
    watch: {
        tabPosition(val) {
            if (val == 'track') {
                this.$nextTick(() => {
                    this.initMap();
                })
                return
            }
        }
    },
    methods: {
        initData(orderData = {}, monitoringData = {}, travelData = {},) {
        initData(orderData = {}, monitoringData = {}, travelData = []) {
            this.orderData = orderData
            this.monitoringData = monitoringData
            this.travelData = travelData
            this.dialogVisible = true
            this.initMap();
        },
        initMap() {
            window._AMapSecurityConfig = {
@@ -73,42 +84,76 @@
                .then((AMap) => {
                    this.map = new AMap.Map("mapContainer", {
                        center: [this.travelData[this.travelData.length / 2].longitude, this.travelData[this.travelData.length / 2].latitude],
                        zoom: 15,
                        zoom: 12,
                    });
                    this.map.addControl(new AMap.ToolBar());
                    let path = this.travelData.map(item => {
                        return new AMap.LngLat(item.longitude, item.latitude)
                    })
                    const content = `<div class="custom-content-marker">
                                        <img src="${require("@/assets/logo.png")}">
                                        起点
                                    </div>`;
                    const marker = new AMap.Marker({
                    const contentTwo = `<div class="custom-content-marker-two">
                                        终点
                                    </div>`;
                    const marker = [
                        new AMap.Marker({
                        content: content, //自定义点标记覆盖物内容
                        position: [this.travelData[0].longitude, this.travelData[0].latitude], //基点位置
                        offset: new AMap.Pixel(-30, -15), //相对于基点的偏移位置
                    });
                            offset: new AMap.Pixel(-35, -25), //相对于基点的偏移位置
                        }),
                        new AMap.Marker({
                            content: contentTwo, //自定义点标记覆盖物内容
                            position: [this.travelData[this.travelData.length - 1].longitude, this.travelData[this.travelData.length - 1].latitude], //基点位置
                            offset: new AMap.Pixel(-35, -25), //相对于基点的偏移位置
                        })
                    ]
                    this.map.add(marker);
                    let polyline = new AMap.Polyline({
                        path: path,
                        strokeWeight: 2, //线条宽度
                        strokeWeight: 3, //线条宽度
                        strokeColor: "red", //线条颜色
                        lineJoin: "round", //折线拐点连接处样式
                    });
                    this.map.add(polyline);
                    // 强制刷新地图
                    this.$nextTick(() => {
                        this.map.resize();
                    });
                })
                .catch((e) => {
                });
        },
        closeClick() {
            this.dialogVisible = false
        }
            this.tabPosition = 'order'
            this.orderData = {}
            this.monitoringData = {}
            this.travelData = []
        },
    },
};
</script>
<style>
.custom-content-marker {
    width: 30px;
    height: 30px;
    width: 50px;
    height: 50px;
    background-color: blue;
    color: #fff;
    border-radius: 50%;
    line-height: 50px;
    text-align: center;
}
.custom-content-marker-two {
    width: 50px;
    height: 50px;
    background-color: orange;
    color: #fff;
    border-radius: 50%;
    line-height: 50px;
    text-align: center;
}
.custom-content-marker img {
@@ -123,6 +168,6 @@
#mapContainer {
    width: 100%;
    height: 300px;
    height: 500px;
}
</style>
src/view/order/index.vue
@@ -1,5 +1,5 @@
<template>
    <div>
    <div v-loading="loading" element-loading-text="加载中...">
        <div class="form flex a-center j-between mt--23">
            <div class="form-left ml--30">
                <el-form :inline="true" :model="searchForm">
@@ -87,7 +87,7 @@
<script>
import DetailModal from "./component/detailModal"
import { exportExcell } from '@/utils/utils'
import { getOrderList, getOrderInfo, getOrderMonitoring, getOrderTravel } from './service'
import { getOrderList, getOrderInfo, getOrderMonitoring, getOrderTravel, } from './service'
import moment from "moment/moment";
export default {
@@ -102,6 +102,7 @@
                pageSize: 10
            },
            tableData: [],
            loading: false,
        };
    },
    computed: {
@@ -132,7 +133,7 @@
                obj.orderDeliveryTimeEnd = moment(obj.orderDeliveryTime[1]).format('YYYY-MM-DD HH-mm-ss')
                delete obj.orderDeliveryTime
            }
            exportExcell('投诉记录导出', obj, '/system/order/exportOrderList')
            exportExcell('订单记录导出', obj, '/system/order/exportOrderList')
        },
        handleSizeChange(e) {
            this.searchForm.pageSize = e
@@ -156,9 +157,13 @@
            })
        },
        showDetail(row) {
            // Promise.all([getOrderInfo(row.id), getOrderMonitoring({ id: row.id }), getOrderTravel({ id: row.id })]).then(res => {
            Promise.all([getOrderInfo(row.id), getOrderTravel({ id: row.id })]).then(res => {
                this.$refs.detailModal.initData(res[0], {}, res[2])
            this.loading = true
            Promise.all([getOrderInfo(row.id), getOrderMonitoring({ id: row.id }), getOrderTravel({ id: row.id })]).then(res => {
                // Promise.all([getOrderInfo(row.id), getOrderTravel({ id: row.id })]).then(res => {
                this.$refs.detailModal.initData(res[0], res[1], res[2])
                this.loading = false
            }).catch(err => {
                this.loading = false
            })
        },
    }
@@ -184,4 +189,17 @@
        width: 100% !important;
    }
}
::v-deep .el-loading-mask {
    display: flex;
    align-items: center;
    justify-content: center;
    .el-loading-spinner {
        width: unset;
        display: flex;
        flex-direction: column;
        align-items: center;
    }
}
</style>
src/view/systemManage/user/components/addEdit.vue
@@ -1,7 +1,7 @@
<template>
    <div>
        <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" :title="row.deptId ? '编辑人员' : '添加人员'"
            width="30%">
            width="30%"  :modal-append-to-body="false">
            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="姓名" prop="nickName">
                    <el-input v-model="form.nickName" placeholder="请输入" style="width: 50%;"></el-input>
src/view/systemManage/user/components/disb.vue
@@ -1,6 +1,6 @@
<template>
    <div>
        <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="禁用人员" width="30%">
        <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="禁用人员" width="30%"  :modal-append-to-body="false">
            <el-form ref="form" :model="form" label-width="80px">
                <el-form-item label="姓名" prop="nickName">
                    {{ form.nickName }}
src/view/systemManage/user/components/resetPassWord.vue
@@ -1,13 +1,13 @@
<template>
    <div>
        <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="重置密码"
            width="30%">
        <el-dialog :visible.sync="dialogVisible" @close="$emit('close')" title="重置密码" width="30%"
            :modal-append-to-body="false">
            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
                <el-form-item label="姓名" prop="nickName">
                    <el-input :disabled="true" v-model="form.nickName" placeholder="请输入" style="width: 50%;"></el-input>
                </el-form-item>
                <el-form-item label="登陆账号" prop="account">
                    <el-input :disabled="true" v-model="form.account" placeholder="请输入" style="width: 50%;"></el-input>
                    <el-input :disabled="true" v-model="form.userName" placeholder="请输入" style="width: 50%;"></el-input>
                </el-form-item>
                <el-form-item label="新密码" prop="password">
                    <el-input v-model="form.password" placeholder="请输入" style="width: 50%;"></el-input>
@@ -25,6 +25,7 @@
</template>
<script>
import CryptoJS from 'crypto-js';
export default {
    props: {
        dialogVisible: {
@@ -48,7 +49,8 @@
                        } else {
                            callback();
                        }
                }}],
                    }
                }],
            }
        };
    },
@@ -58,7 +60,7 @@
            this.form = {
                userId: this.row.userId,
                nickName: this.row.nickName,
                account: this.row.userName,
                userName: this.row.userName,
            }
        }
    },
@@ -71,6 +73,7 @@
                    this.form.orderNum = 0
                    this.form.ancestors = 0
                    this.form.parentId = 100
                    this.form.password = CryptoJS.MD5(this.form.password).toString()
                    this.$emit('confirm', this.form)
                }
            })