无人机管理后台前端(已迁走)
shuishen
2025-08-21 cd0c904b3c3099ff74658c616179916bad69a052
feat:图斑管理页面基础
2 files modified
3 files added
753 ■■■■■ changed files
pnpm-lock.yaml 22 ●●●●● patch | view | raw | blame | history
src/router/views/index.js 31 ●●●●● patch | view | raw | blame | history
src/views/resource/components/patchDetails.vue 15 ●●●●● patch | view | raw | blame | history
src/views/resource/patchManagement.vue 368 ●●●●● patch | view | raw | blame | history
src/views/resource/patchTypeManagement.vue 317 ●●●●● patch | view | raw | blame | history
pnpm-lock.yaml
@@ -41,6 +41,12 @@
  dayjs:
    specifier: ^1.11.13
    version: 1.11.13
  decimal:
    specifier: ^0.0.2
    version: 0.0.2
  decimal.js:
    specifier: ^10.4.3
    version: 10.4.3
  echarts:
    specifier: ^5.6.0
    version: 5.6.0
@@ -71,6 +77,9 @@
  postcss-pxtorem:
    specifier: ^6.1.0
    version: 6.1.0(postcss@8.4.38)
  qrcodejs2-fix:
    specifier: ^0.0.1
    version: 0.0.1
  reconnecting-websocket:
    specifier: ^4.4.0
    version: 4.4.0
@@ -2458,6 +2467,15 @@
      ms: 2.1.2
    dev: true
  /decimal.js@10.4.3:
    resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
    dev: false
  /decimal@0.0.2:
    resolution: {integrity: sha512-puX1+D5GlpXYH0kDejnu8F2jsGLKRePc7eOznNjqMbA2otl7AzvubrvvZVmOvRCHhsqeAPKNcAxHHbevsH/1qg==}
    engines: {node: 0.4.x}
    dev: false
  /deep-equal@1.1.2:
    resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==}
    engines: {node: '>= 0.4'}
@@ -3418,6 +3436,10 @@
      long: 5.3.2
    dev: false
  /qrcodejs2-fix@0.0.1:
    resolution: {integrity: sha512-1R6jB87k1PUwjX65hh24FftDjrghPvryYfsajV3chCp//dQb8QUeL446DVLj2SIeotP8ro9rSYri0wOp7O+7Hg==}
    dev: false
  /queue-microtask@1.2.3:
    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
    dev: true
src/router/views/index.js
@@ -1,5 +1,5 @@
import Layout from '@/page/index/index.vue';
import Store from '@/store/';
import Layout from '@/page/index/index.vue'
import Store from '@/store/'
export default [
  {
@@ -157,4 +157,29 @@
      },
    ],
  },
];
  {
    path: '/resource',
    component: Layout,
    redirect: '/resource/patchManagement',
    children: [
      {
        path: 'patchManagement',
        name: '图斑管理',
        meta: {
          i18n: 'info',
        },
        component: () => import(/* webpackChunkName: "views" */ '@/views/resource/patchManagement.vue'),
      },
      {
        path: 'patchTypeManagement',
        name: '图斑类型管理',
        meta: {
          i18n: 'info',
        },
        component: () => import(/* webpackChunkName: "views" */ '@/views/resource/patchTypeManagement.vue'),
      },
    ],
  },
]
src/views/resource/components/patchDetails.vue
New file
@@ -0,0 +1,15 @@
<template>
    <el-dialog append-to-body v-model="patchDetailsShow">
        <el-tabs v-model="activeTab" type="border-card">
            <el-tab-pane label="图斑详情" name="first">图斑详情</el-tab-pane>
            <el-tab-pane label="图斑类型" name="second">图斑类型</el-tab-pane>
            <el-tab-pane label="图斑区域" name="third">图斑区域</el-tab-pane>
        </el-tabs>
    </el-dialog>
</template>
<script setup>
const patchDetailsShow = ref(true)
</script>
<style lang="scss" scoped></style>
src/views/resource/patchManagement.vue
New file
@@ -0,0 +1,368 @@
<template>
  <basic-container>
    <avue-crud :option="option" :table-loading="loading" :data="data" v-model:page="page" :permission="permissionList"
      v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" @row-del="rowDel" :before-open="beforeOpen"
      @search-change="searchChange" @search-reset="searchReset" @selection-change="selectionChange"
      @current-change="currentChange" @size-change="sizeChange" @refresh-change="refreshChange" @on-load="onLoad">
      <template #menu-left>
        <el-button type="primary" icon="el-icon-upload" @click="uploadPatch">
          上传图斑
        </el-button>
        <el-button type="primary" icon="el-icon-setting" @click="goTypeManagement">
          类型管理
        </el-button>
        <el-button type="success" icon="el-icon-download" @click="downloadPatch">
          导出
        </el-button>
      </template>
      <template #menu="scope">
        <el-button type="primary" text icon="el-icon-video-play" @click="handleDebug(scope.row)">详情
        </el-button>
        <el-button type="primary" text icon="el-icon-video-play" @click="handleDebug(scope.row)">编辑
        </el-button>
        <el-button type="primary" text icon="el-icon-video-play" @click="handleDebug(scope.row)">删除
        </el-button>
      </template>
    </avue-crud>
    <el-dialog title="对象存储上传调试" append-to-body v-model="box" width="550px">
      <avue-form ref="form" :option="debugOption" v-model="debugForm" @submit="handleSubmit" />
    </el-dialog>
    <patchDetails ref="patchDetails" />
  </basic-container>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getListPage, getDetail, add, update, remove, enable, disable } from '@/api/resource/oss'
import func from '@/utils/func'
import patchDetails from '@/views/resource/components/patchDetails.vue'
const store = useStore()
const router = useRouter()
// ===== state =====
const form = ref({})
const query = ref({})
const loading = ref(true)
const box = ref(false)
const page = ref({
  pageSize: 10,
  currentPage: 1,
  total: 0,
})
const selectionList = ref([])
const option = ref({
  addBtn: false,
  tip: false,
  searchShow: true,
  searchMenuSpan: 8,
  searchMenuPosition: 'right',
  border: true,
  index: true,
  selection: true,
  grid: false,
  menuWidth: 240,
  labelWidth: 100,
  dialogWidth: 880,
  dialogClickModal: false,
  height: 'auto',
  calcHeight: 20,
  refreshBtn: false,
  gridBtn: false,
  searchShowBtn: false,
  columnBtn: false,
  viewBtn: false,
  editBtn: false,
  delBtn: false,
  column: [
    {
      label: '文件名称',
      prop: 'name',
      span: 24,
      search: true,
      searchSpan: 4,
      rules: [{ required: true, message: '请输入文件名称', trigger: 'blur' }],
    },
    {
      label: '图斑类型',
      prop: 'ossCode',
      span: 24,
      searchLabelWidth: 100,
      search: true,
      searchSpan: 4,
      rules: [{ required: true, message: '请输入图斑类型', trigger: 'blur' }],
    },
    {
      label: '图斑数量',
      prop: 'ossCode',
      span: 24,
      rules: [{ required: true, message: '请输入图斑数量', trigger: 'blur' }],
    },
    {
      label: '异常图斑数量',
      prop: 'ossCode',
      span: 24,
      rules: [{ required: true, message: '请输入异常图斑数量', trigger: 'blur' }],
    },
    {
      label: '行政区划',
      prop: 'ossCode',
      span: 24,
      searchLabelWidth: 100,
      search: true,
      searchSpan: 4,
      rules: [{ required: true, message: '请输入行政区划', trigger: 'blur' }],
    },
    {
      label: '数据来源',
      prop: 'ossCode',
      span: 24,
      rules: [{ required: true, message: '请输入数据来源', trigger: 'blur' }],
    },
    {
      label: '创建时间',
      prop: 'ossCode',
      span: 24,
      rules: [{ required: true, message: '请输入创建时间', trigger: 'blur' }],
    },
    {
      label: '创建人',
      prop: 'bucketName',
      span: 24,
      searchLabelWidth: 100,
      search: true,
      searchSpan: 4,
      rules: [{ required: true, message: '请输入创建人', trigger: 'blur' }],
    },
  ],
})
const data = ref([])
const debugForm = ref({
  code: '',
  backgroundUrl: '',
})
const debugOption = ref({
  submitText: '提交',
  column: [
    {
      label: '资源编号',
      prop: 'code',
      disabled: true,
      span: 24,
    },
    {
      label: '上传背景',
      prop: 'backgroundUrl',
      type: 'upload',
      listType: 'picture-img',
      dataType: 'string',
      action: '/blade-resource/oss/endpoint/put-file',
      propsHttp: { res: 'data', url: 'link' },
      span: 24,
    },
  ],
})
// ===== refs =====
const crudRef = ref(null)
const uploadPatchDialog = ref(null)
// ===== computed =====
const userInfo = computed(() => store.getters.userInfo)
const permission = computed(() => store.getters.permission)
const permissionList = computed(() => ({
  addBtn: !!permission.value.oss_add,
  viewBtn: !!permission.value.oss_view,
  delBtn: !!permission.value.oss_delete,
  editBtn: !!permission.value.oss_edit,
}))
const ids = computed(() => selectionList.value.map(ele => ele.id).join(','))
// ===== watch =====
watch(
  () => form.value.category,
  () => {
    const category = func.toInt(form.value.category)
    option.value.column.forEach(item => {
      if (item.prop === 'appId') {
        item.display = category === 4
      }
    })
  }
)
watch(
  () => debugForm.value.code,
  () => {
    const column = debugOption.value.column.find(item => item.prop === 'backgroundUrl')
    if (column) {
      column.action = `/blade-resource/oss/endpoint/put-file?code=${debugForm.value.code}`
    }
  }
)
// ===== methods =====
const rowSave = (row, done, loading) => {
  add(row).then(
    () => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      done()
    },
    error => {
      console.log(error)
      loading()
    }
  )
}
const rowUpdate = (row, index, done, loading) => {
  update(row).then(
    () => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      done()
    },
    error => {
      console.log(error)
      loading()
    }
  )
}
const rowDel = row => {
  ElMessageBox.confirm('确定将选择数据删除?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => remove(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
    })
}
const searchReset = () => {
  query.value = {}
  onLoad(page.value)
}
const searchChange = (params, done) => {
  query.value = params
  page.value.currentPage = 1
  onLoad(page.value, params)
  done()
}
const selectionChange = list => {
  selectionList.value = list
}
const selectionClear = () => {
  selectionList.value = []
  crudRef.value?.toggleSelection()
}
const handleEnable = row => {
  ElMessageBox.confirm('是否确定启用这条配置?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => enable(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      crudRef.value?.toggleSelection()
    })
}
const handleDisable = row => {
  ElMessageBox.confirm('是否确定禁用用这条配置?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => disable(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      crudRef.value?.toggleSelection()
    })
}
const handleDebug = row => {
  box.value = true
  debugForm.value.code = row.ossCode
  debugForm.value.backgroundUrl = ''
}
const handleSubmit = (formData, done) => {
  ElMessage.success(`获取到图片地址:[${formData.backgroundUrl}]`)
  done()
}
const beforeOpen = (done, type) => {
  if (['edit', 'view'].includes(type)) {
    getDetail(form.value.id).then(res => {
      form.value = res.data.data
    })
  }
  done()
}
const currentChange = currentPage => {
  page.value.currentPage = currentPage
}
const sizeChange = pageSize => {
  page.value.pageSize = pageSize
}
const refreshChange = () => {
  onLoad(page.value, query.value)
}
const onLoad = (pageInfo, params = {}) => {
  loading.value = true
  getListPage(pageInfo.currentPage, pageInfo.pageSize, Object.assign({}, params, query.value)).then(res => {
    const d = res.data.data
    page.value.total = d.total
    data.value = d.records
    loading.value = false
    selectionClear()
  })
}
// 上传图斑
const uploadPatch = () => {
  uploadPatchDialog.value.open()
}
// 跳转至图斑类型管理页面
const goTypeManagement = () => {
  router.push({ path: '/resource/patchTypeManagement' })
}
// 下载图斑
const downloadPatch = () => {
  crudRef.value?.download()
}
</script>
<style scoped lang="scss"></style>
src/views/resource/patchTypeManagement.vue
New file
@@ -0,0 +1,317 @@
<template>
  <basic-container>
    <avue-crud :option="option" :table-loading="loading" :data="data" v-model:page="page" :permission="permissionList"
      v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" @row-del="rowDel" :before-open="beforeOpen"
      @search-change="searchChange" @search-reset="searchReset" @selection-change="selectionChange"
      @current-change="currentChange" @size-change="sizeChange" @refresh-change="refreshChange" @on-load="onLoad">
      <template #search-menu="{ row, size }">
        <el-button icon="el-icon-back" @click="goPatchManagement">
          返回
        </el-button>
      </template>
      <template #status="{ row }">
        <el-tag>{{ row.statusName }}</el-tag>
      </template>
      <template #category="{ row }">
        <el-tag>{{ row.categoryName }}</el-tag>
      </template>
    </avue-crud>
    <el-dialog title="对象存储上传调试" append-to-body v-model="box" width="550px">
      <avue-form ref="form" :option="debugOption" v-model="debugForm" @submit="handleSubmit" />
    </el-dialog>
  </basic-container>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { getListPage, getDetail, add, update, remove, enable, disable } from '@/api/resource/oss'
import { useStore } from 'vuex'
import func from '@/utils/func'
import { useRouter } from 'vue-router'
const store = useStore()
const router = useRouter()
// ---------------- data ----------------
const form = ref({})
const query = ref({})
const loading = ref(true)
const box = ref(false)
const page = ref({
  pageSize: 10,
  currentPage: 1,
  total: 0,
})
const selectionList = ref([])
const option = ref({
  addBtn: true,
  addBtnText: '新增类型',
  tip: false,
  searchShow: true,
  searchMenuSpan: 16,
  searchMenuPosition: 'right',
  border: true,
  index: true,
  selection: true,
  grid: false,
  menuWidth: 180,
  labelWidth: 100,
  dialogWidth: 880,
  dialogClickModal: false,
  height: 'auto',
  calcHeight: 20,
  refreshBtn: false,
  gridBtn: false,
  searchShowBtn: false,
  columnBtn: false,
  column: [
    {
      label: '类型名称',
      prop: 'name',
      search: true,
      searchSpan: 4,
      rules: [
        { required: true, message: '请输入类型名称', trigger: 'blur' },
      ],
    },
    {
      label: '创建时间',
      prop: 'ossCode',
      rules: [
        { required: true, message: '请输入创建时间', trigger: 'blur' },
      ],
    },
    {
      label: '创建人',
      prop: 'ossCode',
      search: true,
      searchSpan: 4,
      rules: [
        { required: true, message: '请输入创建人', trigger: 'blur' },
      ],
    },
  ],
})
const data = ref([])
const debugForm = ref({
  code: '',
})
const debugOption = ref({
  submitText: '提交',
  column: [
    {
      label: '资源编号',
      prop: 'code',
      disabled: true,
      span: 24,
    },
    {
      label: '上传背景',
      prop: 'backgroundUrl',
      type: 'upload',
      listType: 'picture-img',
      dataType: 'string',
      action: '/blade-resource/oss/endpoint/put-file',
      propsHttp: { res: 'data', url: 'link' },
      span: 24,
    },
  ],
})
// ---------------- watch ----------------
watch(
  () => form.value.category,
  () => {
    const category = func.toInt(form.value.category)
    option.value.column.filter(item => {
      if (item.prop === 'appId') {
        item.display = category === 4
      }
    })
  }
)
watch(
  () => debugForm.value.code,
  () => {
    const column = findObject(debugOption.value.column, 'backgroundUrl')
    column.action = `/blade-resource/oss/endpoint/put-file?code=${debugForm.value.code}`
  }
)
// ---------------- computed ----------------
const permission = computed(() => store.getters.permission)
const permissionList = computed(() => ({
  addBtn: validData(permission.value.oss_add),
  viewBtn: validData(permission.value.oss_view),
  delBtn: validData(permission.value.oss_delete),
  editBtn: validData(permission.value.oss_edit),
}))
const ids = computed(() => {
  return selectionList.value.map(ele => ele.id).join(',')
})
// ---------------- methods ----------------
const rowSave = (row, done, loadingFn) => {
  add(row).then(
    () => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      done()
    },
    error => {
      console.log(error)
      loadingFn()
    }
  )
}
const rowUpdate = (row, index, done, loadingFn) => {
  update(row).then(
    () => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      done()
    },
    error => {
      console.log(error)
      loadingFn()
    }
  )
}
const rowDel = row => {
  ElMessageBox.confirm('确定将选择数据删除?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => remove(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
    })
}
const searchReset = () => {
  query.value = {}
  onLoad(page.value)
}
const searchChange = (params, done) => {
  query.value = params
  page.value.currentPage = 1
  onLoad(page.value, params)
  done()
}
const selectionChange = list => {
  selectionList.value = list
}
const selectionClear = () => {
  selectionList.value = []
  crudRef.value.toggleSelection()
}
const handleEnable = row => {
  ElMessageBox.confirm('是否确定启用这条配置?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => enable(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      crudRef.value.toggleSelection()
    })
}
const handleDisable = row => {
  ElMessageBox.confirm('是否确定禁用这条配置?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => disable(row.id))
    .then(() => {
      onLoad(page.value)
      ElMessage.success('操作成功!')
      crudRef.value.toggleSelection()
    })
}
const handleDebug = row => {
  box.value = true
  debugForm.value.code = row.ossCode
  debugForm.value.backgroundUrl = ''
}
const handleSubmit = (form, done) => {
  ElMessage.success(`获取到图片地址:[${form.backgroundUrl}]`)
  done()
}
const beforeOpen = (done, type) => {
  if (['edit', 'view'].includes(type)) {
    getDetail(form.value.id).then(res => {
      form.value = res.data.data
    })
  }
  done()
}
const currentChange = currentPage => {
  page.value.currentPage = currentPage
}
const sizeChange = pageSize => {
  page.value.pageSize = pageSize
}
const refreshChange = () => {
  onLoad(page.value, query.value)
}
const onLoad = (pageParam, params = {}) => {
  loading.value = true
  getListPage(pageParam.currentPage, pageParam.pageSize, Object.assign(params, query.value)).then(res => {
    const resData = res.data.data
    page.value.total = resData.total
    data.value = resData.records
    loading.value = false
    selectionClear()
  })
}
// ---------------- extra ----------------
const goPatchManagement = () => {
  router.push({ path: '/resource/patchManagement' })
}
const addPatchType = () => {
  // TODO: 新增图斑类型逻辑
}
// ---------------- utils ----------------
function validData (value) {
  return value !== undefined && value !== null && value !== false
}
function findObject (list, prop) {
  return list.find(item => item.prop === prop)
}
const crudRef = ref(null)
</script>
<style scoped lang="scss"></style>