Browse Source

add api

tags/v1.0.0^2
zhangtao 2 years ago
parent
commit
1775862606
35 changed files with 1233 additions and 119 deletions
  1. +1
    -1
      .env.development
  2. +7
    -0
      build/vite/plugin/index.js
  3. +4
    -0
      index.html
  4. +1
    -0
      package.json
  5. +49
    -0
      src/api/dashboard/index.js
  6. +51
    -4
      src/api/task/index.js
  7. BIN
      src/assets/icon/arrow.png
  8. +1
    -0
      src/assets/icon/arrow.svg
  9. +5
    -1
      src/components/DataTable/index.vue
  10. +4
    -1
      src/components/DataTable/tools/Action.vue
  11. +2
    -2
      src/components/DataTable/tools/Tags.vue
  12. +73
    -0
      src/components/SvgIcon/index.vue
  13. +123
    -0
      src/components/VideoPlayer/index.vue
  14. +1
    -1
      src/layout/index.vue
  15. +1
    -0
      src/main.js
  16. +2
    -2
      src/router/routes/index.js
  17. +4
    -4
      src/router/routes/modules/index.js
  18. +8
    -7
      src/utils/dictionary.js
  19. +6
    -0
      src/utils/http/interceptors.js
  20. +129
    -0
      src/views/dashboard/components/AirCard.vue
  21. +117
    -0
      src/views/dashboard/components/TaskCard.vue
  22. +128
    -0
      src/views/dashboard/components/VideoCard.vue
  23. +34
    -3
      src/views/dashboard/index.vue
  24. +48
    -2
      src/views/question-manage/question-list/index.vue
  25. +22
    -0
      src/views/question-manage/question-list/tools/search.js
  26. +217
    -0
      src/views/question-manage/question-list/tools/table.js
  27. +2
    -2
      src/views/system-manage/department-manage/components/DepartmentModal.vue
  28. +2
    -1
      src/views/system-manage/menu-manage/components/MenuModal.vue
  29. +4
    -30
      src/views/system-manage/menu-manage/tools/table.js
  30. +1
    -1
      src/views/system-manage/role-manage/components/ConfigModal.vue
  31. +33
    -8
      src/views/task-manage/all-task/components/TaskModal.vue
  32. +5
    -3
      src/views/task-manage/all-task/index.vue
  33. +81
    -20
      src/views/task-manage/all-task/tools/form.js
  34. +3
    -5
      src/views/task-manage/all-task/tools/search.js
  35. +64
    -21
      src/views/task-manage/all-task/tools/table.js

+ 1
- 1
.env.development View File

@@ -5,7 +5,7 @@ VITE_PUBLIC_PATH = '/'
VITE_APP_USE_MOCK = false

# proxy
VITE_PROXY = [["/api","http://192.168.11.11:9011/api"]]
VITE_PROXY = [["/api","http://192.168.11.11:9099/api"]]

# base api
VITE_APP_GLOB_BASE_API = '/api'

+ 7
- 0
build/vite/plugin/index.js View File

@@ -3,6 +3,9 @@ import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'

import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { resolve } from 'path'

import VueSetupExtend from 'vite-plugin-vue-setup-extend'

import { unocss } from './unocss'
@@ -15,6 +18,10 @@ export function createVitePlugins(viteEnv, isBuild) {
Components({
resolvers: [NaiveUiResolver()]
}),
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'src/assets/icon')],
symbolId: 'icon-[dir]-[name]'
}),
VueSetupExtend(),
unocss(),
configHtmlPlugin(viteEnv, isBuild)

+ 4
- 0
index.html View File

@@ -9,6 +9,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title><%= title %></title>

<link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.9.21/skins/default/aliplayer-min.css" />
<script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.9.21/aliplayer-h5-min.js"></script>
</head>
<body>
<div id="app"></div>

+ 1
- 0
package.json View File

@@ -44,6 +44,7 @@
"vite": "^2.6.4",
"vite-plugin-html": "^2.1.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vue-setup-extend": "^0.4.0"
}
}

+ 49
- 0
src/api/dashboard/index.js View File

@@ -0,0 +1,49 @@
import { defAxios as request } from '@/utils/http'

/**
* @description: 获取巡检机场
* @return {*}
*/
export function airportList(params) {
return request({
url: '/inspection/airport',
method: 'GET',
params
})
}

/**
* @description: 获取航线
* @return {*}
*/
export function airportLine(id) {
return request({
url: `/inspection/airport/line/${id}`,
method: 'GET'
})
}

/**
* @description: 获取机场天气
* @param {*} id 机场id
* @return {*}
*/
export function airportWeather(id) {
return request({
url: `/inspection/airport/weather/${id}`,
method: 'GET'
})
}

/**
* @description: 获取飞行轨迹
* @param {*} id 机场id
* @return {*}
*/
export function airportTrack(id) {
return request({
url: `/inspection/track/${id}`,
method: 'GET'
})
}


+ 51
- 4
src/api/task/index.js View File

@@ -1,14 +1,61 @@
import { defAxios as request } from '@/utils/http'

/**
* @description: 创建任务
* @param {*} id 任务id
* @return {*}
*/
export function createTask(data) {
return request({
url: '/mission',
method: 'POST',
data
})
}

/**
* @description: 获取任务列表
* @param {*} data
* @param {*} params
* @return {*}
*/
export function getTaskList(data = {}) {
export function getTaskList(params) {
return request({
url: '/mission/page',
method: 'get',
data
method: 'GET',
params
})
}
/**
* @description: 获取任务详情
* @param {*} id 任务id
* @return {*}
*/
export function getTaskDetail(id) {
return request({
url: `/mission/${id}`,
method: 'GET'
})
}
/**
* @description: 删除任务
* @param {*} id 任务id
* @return {*}
*/
export function taskDelete(id) {
return request({
url: `/mission/${id}`,
method: 'DELETE'
})
}

/**
* @description: 立即执行
* @param {*} id 任务id
* @return {*}
*/
export function implement(id) {
return request({
url: `/inspection/mission/${id}`,
method: 'PUT'
})
}

BIN
src/assets/icon/arrow.png View File

Before After
Width: 24  |  Height: 24  |  Size: 306B

+ 1
- 0
src/assets/icon/arrow.svg View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.b{fill:none;}</style></defs><g transform="translate(-72.592 -80.272)"><path class="a" d="M80.21,96.931a.434.434,0,0,1-.307-.741l3.836-3.837L79.9,88.517a.434.434,0,0,1,.614-.614l4.143,4.143a.433.433,0,0,1,0,.614L80.517,96.8A.433.433,0,0,1,80.21,96.931Z" transform="translate(0 0)"/><path class="a" d="M480.21,96.931a.434.434,0,0,1-.307-.741l3.836-3.837L479.9,88.517a.434.434,0,0,1,.614-.614l4.143,4.143a.434.434,0,0,1,0,.614L480.517,96.8A.433.433,0,0,1,480.21,96.931Z" transform="translate(-395.684 0)"/></g><rect class="b" width="24" height="24"/></svg>

+ 5
- 1
src/components/DataTable/index.vue View File

@@ -28,7 +28,11 @@
:pagination="pagination"
@update:page="updatePage"
@update:page-size="updatePageSize"
/>
>
<template #empty>
<slot name="empty" />
</template>
</n-data-table>
</div>
</template>


+ 4
- 1
src/components/DataTable/tools/Action.vue View File

@@ -50,7 +50,10 @@ export default defineComponent({
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return data.permissionList.includes(action.auth) || action.auth === ''
if (!Object.keys(action).includes('show')) {
action.show = Object.keys(action).includes('hidden') ? !action.hidden : true
}
return (data.permissionList.includes(action.auth) || action.auth === '') && action.show
})
})


+ 2
- 2
src/components/DataTable/tools/Tags.vue View File

@@ -58,10 +58,10 @@ export default defineComponent({
const { filters } = unref(props)
function getFilter(value) {
const data = filters.find(item => {
return item.key === value
return item.value === value
})
return data || {
key: value,
value: value,
label: value
}
}

+ 73
- 0
src/components/SvgIcon/index.vue View File

@@ -0,0 +1,73 @@
<template>
<div v-if="external" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-bind="$attrs" />
<svg v-else :class="svgClass" aria-hidden="true" v-bind="$attrs">
<use :xlink:href="iconName" />
</svg>
</template>

<script>
import { isExternal } from '@/utils/is.js'
import { defineComponent, computed } from 'vue'
export default defineComponent({
name: 'SvgIcon',
props: {
prefix: {
type: String,
default: 'icon'
},
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
setup(props, { emit }) {
const external = computed(() => {
return isExternal(props.iconClass)
})

const iconName = computed(() => {
return `#icon-${props.iconClass}`
})

const svgClass = computed(() => {
if (props.className) {
return 'svg-icon ' + props.className
} else {
return 'svg-icon'
}
})
const styleExternalIcon = computed(() => {
return {
mask: `url(${props.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
}
})
return {
external,
iconName,
svgClass,
styleExternalIcon
}
}
})
</script>

<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}

.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>

+ 123
- 0
src/components/VideoPlayer/index.vue View File

@@ -0,0 +1,123 @@
<template>
<div :id="getPlayerId" />
</template>

<script>

import { ref, computed, watch, onMounted, defineComponent } from 'vue'
export default defineComponent({
name: 'VideoPlayer',
props: {
options: {
type: Object,
default: () => {}
}
},
emits: ['timeUpdate', 'video-status'],
setup(props, { emit }) {
const videoPlayer = ref()
const hasInit = ref(false)
const isSeek = ref(false)
const toolComponent = Aliplayer.Component({
timeupdate(player, e) {
const currentTime = player.getCurrentTime()
const duration = player.getDuration()
emit('timeUpdate', {
status: isSeek.value || Number.isInteger(currentTime) ? 'skip' : 'palying',
currentTime,
duration
})
},
ready(player, e) {
const currentTime = player.getCurrentTime()
const duration = player.getDuration()
emit('video-status', {
status: 'ready',
currentTime,
duration
})
}
})
const getPlayerId = computed(() => {
return props.options?.id || 'player'
})
function init() {
const player = new Aliplayer({
id: 'player',
width: '500px',
height: '260px',
autoplay: true,
...props.options,
components: [
toolComponent
]
}, function(player) {
if (player.getOptions().autoplay) {
player.mute()
}
})
videoPlayer.value = player
player.on('startSeek', () => {
isSeek.value = true
})
player.on('completeSeek', () => {
isSeek.value = false
})
hasInit.value = true
}
function getTime() {
return videoPlayer.value.getCurrentTime()
}
function seekTime(time) {
videoPlayer.value.seek(time)
}
function playVideo() {
videoPlayer.value.play()
}
function pauseVideo() {
videoPlayer.value.pause()
}
function disposeVideo() {
hasInit.value = false
videoPlayer.value.dispose()
}
watch(
() => props.options,
(val) => {
if (props.options.source && !hasInit.value) {
init()
}
if (hasInit.value) {
disposeVideo()
init()
}
}
)
onMounted(() => {
if (props.options.source && !hasInit.value) {
init()
}
})
return {
getPlayerId,
getTime,
seekTime,
playVideo,
pauseVideo,
disposeVideo
}
}
})

</script>
<style>
.prism-player .prism-ErrorMessage .prism-error-operation{
border-bottom: none;
}
.prism-player .prism-ErrorMessage .prism-error-operation a.prism-button.prism-button-refresh{
display: none;
}
.prism-player .prism-ErrorMessage .prism-detect-info.prism-center{
display: none;
}
</style>

+ 1
- 1
src/layout/index.vue View File

@@ -10,7 +10,7 @@
</n-layout>
</n-layout>
</n-layout>
<div class="right_img">copyright©2022拓恒技术有限公司</div>
<!-- <div class="right_img">copyright©2022拓恒技术有限公司</div> -->
</n-space>
</template>


+ 1
- 0
src/main.js View File

@@ -1,5 +1,6 @@
import '@/styles/index.scss'
import 'uno.css'
import 'virtual:svg-icons-register'

import { createApp } from 'vue'
import { setupRouter } from '@/router'

+ 2
- 2
src/router/routes/index.js View File

@@ -14,7 +14,7 @@ export const basicRoutes = [
{
path: '/',
component: Layout,
redirect: '/home',
redirect: '/dashboard',
name: 'dashBoard',
title: '工作台',
meta: {
@@ -22,7 +22,7 @@ export const basicRoutes = [
},
children: [
{
path: 'home',
path: 'dashboard',
title: '首页',
component: Home,
meta: {

+ 4
- 4
src/router/routes/modules/index.js View File

@@ -33,7 +33,7 @@ export default [
children: [
{
path: 'user',
component: () => import('@/views/system/user/index.vue'),
component: () => import('@/views/system-manage/user-manage/index.vue'),
name: 'SystemUser',
title: '用户管理',
meta: {
@@ -42,7 +42,7 @@ export default [
},
{
path: 'role',
component: () => import('@/views/system/role/index.vue'),
component: () => import('@/views/system-manage/role-manage/index.vue'),
name: 'SystemRole',
title: '角色管理',
meta: {
@@ -51,7 +51,7 @@ export default [
},
{
path: 'dept',
component: () => import('@/views/system/dept/index.vue'),
component: () => import('@/views/system-manage/department-manage/index.vue'),
name: 'SystemDept',
title: '部门管理',
meta: {
@@ -60,7 +60,7 @@ export default [
},
{
path: 'menu',
component: () => import('@/views/system/menu/index.vue'),
component: () => import('@/views/system-manage/menu-manage/index.vue'),
name: 'SystemMenu',
title: '菜单管理',
meta: {

+ 8
- 7
src/utils/dictionary.js View File

@@ -1,17 +1,18 @@
export const TASK_STATUS = [
{ label: '待飞行', value: 0 },
{ label: '飞行中', value: 1 },
{ label: '飞行完成', value: 2 },
{ label: '飞行失败', value: 3 }
{ label: '待飞行', value: 1 },
{ label: '飞行中', value: 2 },
{ label: '飞行失败', value: 3 },
{ label: '飞行完成', value: 4 }
]

export const TASK_MODE = [
{ label: '机场服务', value: 0 }
{ label: '机场服务', value: 1 },
{ label: '人工巡检', value: 2 }
]

export const TASK_TYPE = [
{ label: '日常巡检', value: 0 },
{ label: '应急巡检', value: 1 }
{ label: '日常巡检', value: 1 },
{ label: '应急巡检', value: 2 }
]

export const QUESTION_TYPE = [

+ 6
- 0
src/utils/http/interceptors.js View File

@@ -46,6 +46,12 @@ export function setupInterceptor(service) {
case -1:
$message.error(response.data.msg)
break
case 10009:
$message.error(response.data.msg)
break
case 500:
$message.error(response.data.msg)
break
case 401:
// 未登录(可能是token过期或者无效了)
removeToken()

+ 129
- 0
src/views/dashboard/components/AirCard.vue View File

@@ -0,0 +1,129 @@
<template>
<n-card>
<div class="card__title">
<p>
<n-form
inline
:label-width="80"
:model="videoForm"
label-placement="left"
>
<n-form-item label="机场选择:" path="airportId">
<n-select v-model:value="videoForm.airportId" :options="airOptions" @change="getAirportInfo" />
</n-form-item>
</n-form>
</p>
</div>
<div class="card__video">
<div class="video__item">
<!-- <VideoPlayer :options="getVideoOptions.inner" /> -->
</div>
<div class="video__item">
<!-- <VideoPlayer :options="getVideoOptions.outer" /> -->
</div>
</div>
</n-card>
</template>

<script>
import { dataToSelect } from '@/utils/handleData.js'
import { airportList, airportWeather, airportTrack } from '@/api/dashboard/index.js'
import VideoPlayer from '@/components/VideoPlayer/index.vue'
import { reactive, computed, toRefs } from 'vue'

export default {
name: 'TaskCard',
components: { VideoPlayer },
setup() {
const data = reactive({
videoForm: {
airportId: null
},
airOptions: [],
airOptionsAll: []
})

/**
* @description: 加载表格数据
* @param {*} res
* @return {*}
*/
const loadAirport = (async function() {
const res = await airportList({ page: 1, limit: 60 })
if (res.code === 0) {
data.airOptionsAll = res.data
data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' })
data.videoForm.airportId = res.data[0].id
getAirportInfo(res.data[0].id)
}
})()

async function getAirportInfo(id) {
const res = await airportWeather(id)
if (res.code === 0) {
// 1
}

const res2 = await airportTrack(id)
if (res2.code === 0) {
// 1
}
}

const getVideoOptions = computed(() => {
const row = data.airOptionsAll.find((item) => { return item.id === data.videoForm.airportId })
return {
inner: {
id: 'video-inner',
width: '100%',
height: '100%',
source: row?.internalMonitorUrl,
isLive: true
},
outer: {
id: 'video-outer',
width: '100%',
height: '100%',
source: row?.externalMonitorUrl,
isLive: true
}
}
})

return {
...toRefs(data),
loadAirport,
getAirportInfo,
getVideoOptions
}
}
}

</script>
<style scoped lang='scss'>
.card__title{
line-height: 20px;
display: flex;
align-items: center;
margin-bottom: 15px;
flex-direction: row-reverse;
.n-select{
width: 160px;
}
}
.card__video{
display: flex;
height: calc(100% - 55px);
.video__item{
width: calc(50% - 10px);
&:first-child{
margin-right: 20px
}
}
}
::v-deep(.n-form){
.n-form-item-feedback-wrapper{
display: none;
}
}
</style>

+ 117
- 0
src/views/dashboard/components/TaskCard.vue View File

@@ -0,0 +1,117 @@
<template>
<n-card>
<div class="card__title">
<p class="card__title--left">待飞行任务</p>
<p class="card__title--right" @click="handlePreviewMore">查看更多<SvgIcon icon-class="arrow" /></p>
</div>
<div>
<n-data-table
:bordered="false"
:single-column="true"
:columns="columns"
:data="tableData"
:pagination="false"
>
<template #empty>
无数据
</template>
</n-data-table>
</div>
</n-card>
</template>

<script>
import { useRouter } from 'vue-router'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { getTaskList } from '@/api/task/index.js'
import { reactive, toRefs } from 'vue'

export default {
name: 'TaskCard',
components: { SvgIcon },
setup() {
const router = useRouter()
const data = reactive({
tableData: [],
columns: [
{
title: '任务名称',
key: 'name',
align: 'center'
},
{
title: '待飞行时间',
key: 'executionStartTime',
align: 'center'
},
{
title: '创建人',
key: 'inspectionType',
align: 'center'
}
]
})

/**
* @description: 加载表格数据
* @param {*} res
* @return {*}
*/
const loadDataTable = (async function() {
const res = await getTaskList({ page: 1, limit: 6 })
if (res.code === 0) {
data.tableData = res.data.records
}
})()

function handlePreviewMore() {
router.replace('/taskManage/all')
}

return {
...toRefs(data),
loadDataTable,
handlePreviewMore
}
}
}

</script>
<style scoped lang='scss'>
.card__title{
line-height: 20px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.card__title--left{
font-size: 18px;
padding-left: 8px;
border-left: 4px solid rgba(24, 144, 255, 1);
}
.card__title--right{
font-size: 12px;
display: flex;
align-items: center;
cursor: pointer;
svg{
font-size: 24px;
}
&:hover{
color: rgba(24, 144, 255, 1);
}
}
}

::v-deep(.n-data-table){
.n-data-table-tr{
.n-data-table-th{
padding: 8px 12px;
background: rgba(191, 223, 255, 1);
}
.n-data-table-td--last-row{
border-bottom: none;
}
}
}
</style>

+ 128
- 0
src/views/dashboard/components/VideoCard.vue View File

@@ -0,0 +1,128 @@
<template>
<n-card>
<div class="card__title">
<p class="card__title--left">飞行视频</p>
<p class="card__title--right">
<n-form
inline
:label-width="80"
:model="videoForm"
label-placement="left"
>
<n-form-item label="机场选择:" path="airportId">
<n-select v-model:value="videoForm.airportId" :options="airOptions" />
</n-form-item>
<n-form-item label="回放选择:" path="taskId">
<n-select v-model:value="videoForm.taskId" :options="taskOptions" />
</n-form-item>
</n-form>
</p>
</div>
<div class="card__video">
<div class="video__item">
<!-- <VideoPlayer :options="getVideoOptions.origin" /> -->
</div>
<div class="video__item">
<!-- <VideoPlayer :options="getVideoOptions.analyse" /> -->
</div>
</div>
</n-card>
</template>

<script>
import { dataToSelect } from '@/utils/handleData.js'
import { airportList } from '@/api/dashboard/index.js'
import VideoPlayer from '@/components/VideoPlayer/index.vue'
import { reactive, computed, toRefs } from 'vue'

export default {
name: 'TaskCard',
components: { VideoPlayer },
setup() {
const data = reactive({
videoForm: {
airportId: 2,
taskId: ''
},
airOptionsAll: [],
airOptions: [],
taskOptions: []
})

/**
* @description: 加载表格数据
* @param {*} res
* @return {*}
*/
const loadAirport = (async function() {
const res = await airportList({ page: 1, limit: 60 })
if (res.code === 0) {
data.airOptionsAll = res.data
data.airOptions = dataToSelect(res.data, { label: 'name', value: 'id' })
data.videoForm.airportId = res.data[0].id
}
})()

const getVideoOptions = computed(() => {
const row = data.airOptionsAll.find((item) => { return item.id === data.videoForm.airportId })
return {
origin: {
id: 'video-origin',
width: '100%',
height: '100%',
source: row?.internalMonitorUrl,
isLive: true
},
analyse: {
id: 'video-analyse',
width: '100%',
height: '100%',
source: row?.externalMonitorUrl,
isLive: true
}
}
})

return {
...toRefs(data),
loadAirport,
getVideoOptions
}
}
}

</script>
<style scoped lang='scss'>
.card__title{
line-height: 20px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
.card__title--left{
font-size: 18px;
padding-left: 8px;
border-left: 4px solid rgba(24, 144, 255, 1);
}
.card__title--right{
.n-select{
width: 160px;
}
}
}
.card__video{
display: flex;
height: calc(100% - 55px);
.video__item{
width: calc(50% - 10px);
&:first-child{
margin-right: 20px
}
}
}
::v-deep(.n-form){
.n-form-item-feedback-wrapper{
display: none;
}
}
</style>

+ 34
- 3
src/views/dashboard/index.vue View File

@@ -1,13 +1,23 @@
<template>
<div>
工作台
<div class="dashboard__main">
<div class="dashboard__top">
<TaskCard />
<VideoCard />
</div>
<div class="dishboard__bottom">
<AirCard />
</div>
</div>
</template>

<script>
import { useRouter } from 'vue-router'
import TaskCard from './components/TaskCard.vue'
import VideoCard from './components/VideoCard.vue'
import AirCard from './components/AirCard.vue'
export default {
name: 'HomePage',
components: { TaskCard, VideoCard, AirCard },
setup(props) {
const router = useRouter()
function toSystem() {
@@ -20,6 +30,27 @@ export default {
}
</script>
<style lang="scss" scoped>

.dashboard__main{
height: calc(100vh - 80px);
.dashboard__top{
display: flex;
height: 50%;
.n-card{
overflow: hidden;
&:first-child{
width: 50%;
margin-right: 20px;
}
}
}
.dishboard__bottom{
display: flex;
height: calc(50% - 20px);
margin-top: 20px;
}
.n-card{
border-radius: 10px;
}
}
</style>


+ 48
- 2
src/views/question-manage/question-list/index.vue View File

@@ -1,14 +1,60 @@
<template>
<div>
问题列表
<n-card>
<HeadSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<DataTable
ref="tableRef"
:columns="columns"
:request="loadDataTable"
:row-key="(row) => row.id"
size="large"
/>
</n-card>
</div>

</template>

<script>
import table from './tools/table.js'
import search from './tools/search.js'
import HeadSearch from '@/components/Search/index.vue'
import DataTable from '@/components/DataTable/index.vue'
import { reactive, unref, toRefs } from 'vue'
import { getTaskList } from '@/api/task/index.js'

export default {
name: 'QuestionList',
name: 'TaskAll',
components: { HeadSearch, DataTable },
setup() {
const data = reactive({
search,
...toRefs(table)
})

/**
* @description: 加载表格数据
* @param {*} res
* @return {*}
*/
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
return await getTaskList(_params)
}

function handleModal() {
data.rowData = null
data.modalType = 'create'
data.modalShow = true
}

return {
...toRefs(data),
loadDataTable,
handleModal
}
}
}


+ 22
- 0
src/views/question-manage/question-list/tools/search.js View File

@@ -0,0 +1,22 @@
import { reactive } from 'vue'

const data = reactive([
{
label: '选择时间',
key: 'code',
type: 'date',
props: {
type: 'daterange'
}
},
{
label: '搜索任务',
key: 'name',
props: {
placeholder: '请输入任务名称'
}
}
])

export default data


+ 217
- 0
src/views/question-manage/question-list/tools/table.js View File

@@ -0,0 +1,217 @@
import { TASK_MODE, TASK_TYPE, TASK_STATUS } from '@/utils/dictionary.js'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { h, ref, reactive } from 'vue'
import { taskDelete } from '@/api/task/index.js'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
* @param {*} row 单行数据
* @param {*} type 操作类型 create:创建,preview:预览,edit:编辑
* @return {*}
*/
function getRowData(row, type) {
data.rowData = row
data.modalType = type
data.modalShow = true
}

function handleRowDelete(row) {
taskDelete(row.id)
.then(res => {
if (res.code === 0) {
handleSearch()
}
})
}

function handleImplement(row) {
data.rowData = row
}

/* 直播 */
function handleTaskLive(row) {
data.rowData = row
data.liveDrawer = true
}

/* 回放 */
function handleTaskDemand(row) {
data.rowData = row
data.demandDrawer = true
}

/* 问题核实 */
function handleTaskVerify(row) {
data.rowData = row
data.verifyDrawer = true
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,
liveDrawer: false,
demandDrawer: false,
verifyDrawer: false,
handleSearch,

columns: [
{
title: '任务标号',
key: 'code',
align: 'center'
},
{
title: '任务名称',
key: 'name',
align: 'center'
},
{
title: '巡检方式',
key: 'inspectionType',
align: 'center',
render(row) {
return h(TableTags, {
data: row.inspectionType,
filters: TASK_MODE
})
}
},
{
title: '巡检机场',
key: 'airportName',
align: 'center'
},
{
title: '巡检路线',
key: 'inspectionLineName',
align: 'center'
},
{
title: '任务类型',
key: 'type',
align: 'center',
render(row) {
return h(TableTags, {
data: row.type,
filters: TASK_TYPE
})
}
},
{
title: '巡检时间',
key: 'executionStartTime',
align: 'center'
},
{
title: '状态',
key: 'status',
align: 'center',
render(row) {
return h(TableTags, {
data: row.status,
filters: TASK_STATUS
})
}
},

{
title: '操作',
align: 'center',
width: 150,
fixed: 'right',
render(row) {
return h(TableAction, {
actions: [
{
label: '详情',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: getRowData.bind(null, row, 'preview')
},
auth: 'basic_list'
},
{
label: '删除',
type: 'popconfirm',
tip: '是否删除该数据?',
props: {
onClick: handleRowDelete.bind(null, row)
},
ButtonProps: {
text: true,
type: 'primary'
},
auth: 'basic_list'
},
{
label: '立即执行',
type: 'popconfirm',
tip: '是否立即开始执行任务?',
props: {
onClick: handleImplement.bind(null, row)
},
ButtonProps: {
text: true,
type: 'primary'
},
auth: 'basic_list',
show: row.status === 1
},
{
label: '直播',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: handleTaskLive.bind(null, row)
},
auth: 'basic_list',
show: row.status === 2
},
{
label: '轨迹',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: handleTaskDemand.bind(null, row)
},
auth: 'basic_list',
show: row.status === 4
},
{
label: '问题核实',
type: 'button',
props: {
type: 'primary',
text: true,
onClick: handleTaskVerify.bind(null, row)
},
auth: 'basic_list',
show: row.status !== 1
}
],
align: 'center'
})
}
}

]
})

export default data

+ 2
- 2
src/views/system-manage/department-manage/components/DepartmentModal.vue View File

@@ -97,8 +97,8 @@ export default defineComponent({
if (params.id) {
editDept(params).then(res => {
if (res.code === 0) {
handleClose()
emit('reload')
handleClose()
}
}).catch(e => {
console.log(e)
@@ -106,8 +106,8 @@ export default defineComponent({
} else {
addDept(params).then(res => {
if (res.code === 0) {
handleClose()
emit('reload')
handleClose()
}
})
}

+ 2
- 1
src/views/system-manage/menu-manage/components/MenuModal.vue View File

@@ -122,6 +122,7 @@ export default defineComponent({
editMenu(params).then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
}).catch(e => {
console.log(e)
@@ -129,8 +130,8 @@ export default defineComponent({
} else {
addMenu(params).then(res => {
if (res.code === 0) {
handleClose()
emit('reload')
handleClose()
}
})
}

+ 4
- 30
src/views/system-manage/menu-manage/tools/table.js View File

@@ -1,3 +1,4 @@
import { MENU_TYPE, MENU_STATUS, MENU_VISIBLE } from '@/utils/dictionary.js'
import { h, ref, reactive } from 'vue'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
@@ -60,16 +61,7 @@ const data = reactive({
render(row) {
return h(TableTags, {
data: row.type,
filters: [
{
key: 0,
label: '菜单'
},
{
key: 1,
label: '节点'
}
]
filters: MENU_TYPE
})
}
},
@@ -93,16 +85,7 @@ const data = reactive({
render(row) {
return h(TableTags, {
data: row.status,
filters: [
{
key: 1,
label: '在用'
},
{
key: 2,
label: '停用'
}
]
filters: MENU_STATUS
})
}
},
@@ -120,16 +103,7 @@ const data = reactive({
render(row) {
return h(TableTags, {
data: row.hide,
filters: [
{
key: 1,
label: '可见'
},
{
key: 2,
label: '不可见'
}
]
filters: MENU_VISIBLE
})
}
},

+ 1
- 1
src/views/system-manage/role-manage/components/ConfigModal.vue View File

@@ -83,8 +83,8 @@ export default defineComponent({
function handleConfirm() {
savePermission({ roleId: props.data.id, menuIds: data.menuIds }).then(res => {
if (res.code === 0) {
handleClose()
emit('reload')
handleClose()
}
}).catch(e => {
console.log(e)

+ 33
- 8
src/views/task-manage/all-task/components/TaskModal.vue View File

@@ -19,7 +19,7 @@
<n-form-item :label="item.label" :path="item.key">
<n-input v-if="item.type === 'input'" v-model:value="taskForm[item.key]" v-bind="item.props" />
<n-select v-if="item.type === 'select'" v-model:value="taskForm[item.key]" v-bind="item.props" />
<n-date-picker v-if="item.type === 'date'" v-model:value="taskForm[item.key]" v-bind="item.props" />
<n-date-picker v-if="item.type === 'date'" v-model:formatted-value="taskForm[item.key]" v-bind="item.props" />
</n-form-item>
</template>
</n-form>
@@ -28,9 +28,10 @@
</template>

<script>
import { defineComponent, computed, ref, reactive, toRefs } from 'vue'
import { form, getAirOptions, getLineOptions, getOptions } from '../tools/form.js'
import { defineComponent, computed, watch, ref, reactive, toRefs } from 'vue'
import Modal from '@/components/Modal/index.vue'
import form from '../tools/form.js'
import { createTask } from '@/api/task/index.js'
export default defineComponent({
name: 'UserModal',
components: { Modal },
@@ -50,18 +51,25 @@ export default defineComponent({
},
emits: {
'update:visible': null,
'done': null
'reload': null
},
setup(props, { emit }) {
getAirOptions()
const MODAL_TYPE = {
'create': '新建任务',
'preview': '任务详情',
'update': '编辑任务'
}
const formRef = ref()
const { taskForm, taskRules } = form
const data = reactive({
...toRefs(form),
...props.data,
taskForm: {
...taskForm,
...props.data
},
taskRules: {
...taskRules
},
disabled: props.type === 'preview'
})
/* 获取弹窗的属性 */
@@ -81,6 +89,12 @@ export default defineComponent({
}
})

watch(() => data.taskForm.airportId,
(val) => {
data.taskForm.inspectionLine = ''
getLineOptions(val)
})

/**
* @description: 保存&编辑
* @return {*}
@@ -88,14 +102,25 @@ export default defineComponent({
const handleConfirm = () => {
formRef.value?.validate((errors) => {
if (!errors) {
const { airOptions, lineOptions } = getOptions()
const airportInfo = airOptions.value.find((item) => { return (item.value === data.taskForm.airportId) })
const airLineInfo = lineOptions.value.find((item) => { return (item.value === data.taskForm.inspectionLine) })
const params = {
...data.taskForm
...data.taskForm,
airportName: airportInfo.label,
inspectionLineName: airLineInfo.label
}
if (params.id) {
// 1
} else {
/* 新增 */
// 1
createTask(params)
.then(res => {
if (res.code === 0) {
emit('reload')
handleClose()
}
})
}
} else {
$message.error('请先完成校验')

+ 5
- 3
src/views/task-manage/all-task/index.vue View File

@@ -1,7 +1,7 @@
<template>
<div>
<n-card>
<HeadSearch :info="search" />
<HeadSearch :info="search" @search="handleSearch" @reset="handleSearch" />
<DataTable
ref="tableRef"
:columns="columns"
@@ -27,7 +27,7 @@
</div>

<!-- 新增、编辑弹窗 -->
<TaskModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" />
<TaskModal v-if="modalShow" v-model:visible="modalShow" :type="modalType" :data="rowData" @reload="handleSearch" />
<!-- 直播抽屉 -->
<LiveDrawer v-model:visible="liveDrawer" :data="rowData" />
<!-- 轨迹回放 -->
@@ -46,7 +46,7 @@ import TaskModal from './components/TaskModal.vue'
import LiveDrawer from './components/LiveDrawer.vue'
import DemandDrawer from './components/DemandDrawer.vue'
import VerifyDrawer from './components/VerifyDrawer.vue'
import { h, unref, ref, toRefs, reactive } from 'vue'
import { reactive, unref, toRefs } from 'vue'
import { getTaskList } from '@/api/task/index.js'

export default {
@@ -65,8 +65,10 @@ export default {
*/
const loadDataTable = async(res) => {
const _params = {
...unref(data.searchParams),
...res
}
if (_params.status === 'all') _params.status = ''
return await getTaskList(_params)
}


+ 81
- 20
src/views/task-manage/all-task/tools/form.js View File

@@ -1,33 +1,94 @@
import { reactive } from 'vue'
import { ref, reactive } from 'vue'
import { TASK_MODE, TASK_TYPE } from '@/utils/dictionary.js'
import { airportList, airportLine } from '@/api/dashboard/index.js'
import { dataToSelect } from '@/utils/handleData.js'

const data = reactive({
const airOptions = ref([])
const lineOptions = ref([])

function disableTime(ts) {
const DATE = new Date().getDate()
const HOURS = new Date().getHours()
const MINUTES = new Date().getMinutes()
const SECONDS = new Date().getSeconds()
const tDate = new Date(ts).getDate()
const tHours = new Date(ts).getHours()
const tMinutes = new Date(ts).getMinutes()
return {
isHourDisabled: (hour) => {
return tDate === DATE && hour < HOURS
},
isMinuteDisabled: (minute) => {
if (tDate === DATE) {
if (tHours < HOURS) {
return true
} else {
return minute < MINUTES
}
} else {
return false
}
},
isSecondDisabled: (second) => {
if (tDate === DATE) {
if (tHours < HOURS) {
return true
} else if (tMinutes < MINUTES) {
return true
} else {
return second < SECONDS
}
} else {
return false
}
// return tDate === DATE && tHours <= HOURS && tMinutes <= MINUTES && second < SECONDS
}
}
}

export const form = reactive({
taskForm: {
name: '',
name2: '',
name3: '',
name4: '',
name5: '',
name6: null,
name7: ''
inspectionType: '',
airportId: '',
inspectionLine: '',
type: '',
executionStartTime: null,
note: ''
},
taskRules: {
name: [{ required: true, message: '请输入任务名称', trigger: 'blur' }],
name2: [{ required: true, type: 'number', message: '请选择巡检方式', trigger: 'blur' }],
name3: [{ required: true, type: 'number', message: '请选择巡检机场', trigger: 'blur' }],
name4: [{ required: true, type: 'number', message: '请选择巡检路线', trigger: 'blur' }],
name5: [{ required: true, type: 'number', message: '请选择任务类型', trigger: 'blur' }],
name6: [{ required: true, type: 'date', message: '请选择巡检时间', trigger: ['blur', 'change'] }]
inspectionType: [{ required: true, type: 'number', message: '请选择巡检方式', trigger: 'blur' }],
airportId: [{ required: true, type: 'number', message: '请选择巡检机场', trigger: 'blur' }],
inspectionLine: [{ required: true, type: 'number', message: '请选择巡检路线', trigger: 'blur' }],
type: [{ required: true, type: 'number', message: '请选择任务类型', trigger: 'blur' }],
executionStartTime: [{ required: true, type: 'date', message: '请选择巡检时间', trigger: ['blur', 'change'] }]
},
formItem: [
{ type: 'input', key: 'name', label: '任务名称' },
{ type: 'select', key: 'name2', label: '巡检方式', props: { options: TASK_MODE }},
{ type: 'select', key: 'name3', label: '巡检机场', props: { options: [] }},
{ type: 'select', key: 'name4', label: '巡检路线', props: { options: [] }},
{ type: 'select', key: 'name5', label: '任务类型', props: { options: TASK_TYPE }},
{ type: 'date', key: 'name6', label: '巡检时间', props: { type: 'datetime' }},
{ type: 'input', key: 'name7', label: '备注', props: { type: 'textarea', autosize: { minRows: 3, maxRows: 3 }}}
{ type: 'select', key: 'inspectionType', label: '巡检方式', props: { options: TASK_MODE }},
{ type: 'select', key: 'airportId', label: '巡检机场', props: { options: airOptions }},
{ type: 'select', key: 'inspectionLine', label: '巡检路线', props: { options: lineOptions }},
{ type: 'select', key: 'type', label: '任务类型', props: { options: TASK_TYPE }},
{ type: 'date', key: 'executionStartTime', label: '巡检时间', props: { type: 'datetime', valueFormat: 'yyyy-MM-dd HH:mm:ss', format: 'yyyy-MM-dd HH:mm:ss', isDateDisabled: (ts) => { return ts < (Date.now() - 24 * 60 * 60 * 1000) }, isTimeDisabled: disableTime }},
{ type: 'input', key: 'note', label: '备注', props: { type: 'textarea', autosize: { minRows: 3, maxRows: 3 }}}
]
})

export default data
export const getAirOptions = async function() {
const res = await airportList()
airOptions.value = dataToSelect(res.data, { label: 'name', value: 'droneId' })
}

// 获取角色列表
export const getLineOptions = async function(id) {
const res = await airportLine(id)
lineOptions.value = dataToSelect(res.data, { label: 'name', value: 'id' })
}

export const getOptions = function() {
return {
airOptions,
lineOptions
}
}

+ 3
- 5
src/views/task-manage/all-task/tools/search.js View File

@@ -31,7 +31,7 @@ const data = reactive([
},
{
label: '巡检机场',
key: 'plain',
key: 'airportId',
type: 'select',
props: {
placeholder: '请选择任务状态',
@@ -44,7 +44,7 @@ const data = reactive([
},
{
label: '巡检路线',
key: 'route',
key: 'inspectionLine',
type: 'select',
props: {
placeholder: '请选择任务状态',
@@ -60,9 +60,7 @@ const data = reactive([
value: 0,
props: {
placeholder: '请选择任务状态',
options: TASK_TYPE,
onUpdateValue: () => {
}
options: TASK_TYPE
}
}
])

+ 64
- 21
src/views/task-manage/all-task/tools/table.js View File

@@ -1,6 +1,17 @@
// import TableTags from '@/components/DataTable/tools/Tags.vue'
import { TASK_MODE, TASK_TYPE, TASK_STATUS } from '@/utils/dictionary.js'
import TableTags from '@/components/DataTable/tools/Tags.vue'
import TableAction from '@/components/DataTable/tools/Action.vue'
import { h, reactive } from 'vue'
import { h, ref, reactive } from 'vue'
import { taskDelete, implement } from '@/api/task/index.js'

/* 注册table */
const tableRef = ref()
const searchParams = ref()

function handleSearch(params) {
searchParams.value = { ...params }
tableRef.value.reFetch({ searchParams })
}

/**
* @description: 获取数据及操作
@@ -15,11 +26,21 @@ function getRowData(row, type) {
}

function handleRowDelete(row) {

taskDelete(row.id)
.then(res => {
if (res.code === 0) {
handleSearch()
}
})
}

function handleImplement(row) {
data.rowData = row
implement(row.id)
.then(res => {
if (res.code === 0) {
handleSearch()
}
})
}

/* 直播 */
@@ -41,20 +62,20 @@ function handleTaskVerify(row) {
}

const data = reactive({
tableRef,
searchParams,
rowData: {},
modalType: 'create',
modalShow: false,

liveDrawer: false,

demandDrawer: false,

verifyDrawer: false,
handleSearch,

columns: [
{
title: '任务标号',
key: 'name',
key: 'code',
align: 'center'
},
{
@@ -64,33 +85,51 @@ const data = reactive({
},
{
title: '巡检方式',
key: 'name',
align: 'center'
key: 'inspectionType',
align: 'center',
render(row) {
return h(TableTags, {
data: row.inspectionType,
filters: TASK_MODE
})
}
},
{
title: '巡检机场',
key: 'name',
key: 'airportName',
align: 'center'
},
{
title: '巡检路线',
key: 'name',
key: 'inspectionLineName',
align: 'center'
},
{
title: '任务类型',
key: 'name',
align: 'center'
key: 'type',
align: 'center',
render(row) {
return h(TableTags, {
data: row.type,
filters: TASK_TYPE
})
}
},
{
title: '巡检时间',
key: 'name',
key: 'executionStartTime',
align: 'center'
},
{
title: '状态',
key: 'name',
align: 'center'
key: 'status',
align: 'center',
render(row) {
return h(TableTags, {
data: row.status,
filters: TASK_STATUS
})
}
},

{
@@ -135,7 +174,8 @@ const data = reactive({
text: true,
type: 'primary'
},
auth: 'basic_list'
auth: 'basic_list',
show: row.status === 1
},
{
label: '直播',
@@ -145,7 +185,8 @@ const data = reactive({
text: true,
onClick: handleTaskLive.bind(null, row)
},
auth: 'basic_list'
auth: 'basic_list',
show: row.status === 2
},
{
label: '轨迹',
@@ -155,7 +196,8 @@ const data = reactive({
text: true,
onClick: handleTaskDemand.bind(null, row)
},
auth: 'basic_list'
auth: 'basic_list',
show: row.status === 4
},
{
label: '问题核实',
@@ -165,7 +207,8 @@ const data = reactive({
text: true,
onClick: handleTaskVerify.bind(null, row)
},
auth: 'basic_list'
auth: 'basic_list',
show: row.status !== 1
}
],
align: 'center'

Loading…
Cancel
Save