From cd0c904b3c3099ff74658c616179916bad69a052 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Thu, 21 Aug 2025 16:13:24 +0800
Subject: [PATCH] feat:图斑管理页面基础
---
src/views/resource/components/patchDetails.vue | 15 +
src/router/views/index.js | 31 ++
src/views/resource/patchManagement.vue | 368 ++++++++++++++++++++++++++++
src/views/resource/patchTypeManagement.vue | 317 ++++++++++++++++++++++++
pnpm-lock.yaml | 22 +
5 files changed, 750 insertions(+), 3 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 90201ae..38f3287 100644
--- a/pnpm-lock.yaml
+++ b/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
diff --git a/src/router/views/index.js b/src/router/views/index.js
index 568b2c8..9b208fb 100644
--- a/src/router/views/index.js
+++ b/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'),
+ },
+ ],
+ },
+]
diff --git a/src/views/resource/components/patchDetails.vue b/src/views/resource/components/patchDetails.vue
new file mode 100644
index 0000000..343df2c
--- /dev/null
+++ b/src/views/resource/components/patchDetails.vue
@@ -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>
\ No newline at end of file
diff --git a/src/views/resource/patchManagement.vue b/src/views/resource/patchManagement.vue
new file mode 100644
index 0000000..19bc2ab
--- /dev/null
+++ b/src/views/resource/patchManagement.vue
@@ -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>
\ No newline at end of file
diff --git a/src/views/resource/patchTypeManagement.vue b/src/views/resource/patchTypeManagement.vue
new file mode 100644
index 0000000..893e486
--- /dev/null
+++ b/src/views/resource/patchTypeManagement.vue
@@ -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>
\ No newline at end of file
--
Gitblit v1.9.3