【功能新增】IOT: 添加插件管理功能,包括插件列表、详情、状态更新及文件上传功能

This commit is contained in:
安浩浩 2024-12-30 12:03:30 +08:00
parent 5d2adcac19
commit e6a5bb0293
7 changed files with 238 additions and 125 deletions

View File

@ -47,5 +47,15 @@ export const PluginInfoApi = {
// 导出IoT 插件信息 Excel // 导出IoT 插件信息 Excel
exportPluginInfo: async (params) => { exportPluginInfo: async (params) => {
return await request.download({ url: `/iot/plugin-info/export-excel`, params }) return await request.download({ url: `/iot/plugin-info/export-excel`, params })
},
// 修改IoT 插件状态
updatePluginStatus: async (data: any) => {
return await request.put({ url: `/iot/plugin-info/update-status`, data })
},
// 上传Jar包
uploadPluginFile: async (data: any) => {
return await request.post({ url: `/iot/plugin-info/upload-file`, data })
} }
} }

View File

@ -638,6 +638,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
activeMenu: '/iot/device' activeMenu: '/iot/device'
}, },
component: () => import('@/views/iot/device/device/detail/index.vue') component: () => import('@/views/iot/device/device/detail/index.vue')
},
{
path: 'plugin/detail/:id',
name: 'IoTPluginDetail',
meta: {
title: '插件详情',
noCache: true,
hidden: true,
activeMenu: '/iot/plugin'
},
component: () => import('@/views/iot/plugin/detail/index.vue')
} }
] ]
} }

View File

@ -0,0 +1,99 @@
<template>
<Dialog v-model="dialogVisible" title="插件导入" width="400">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:action="importUrl + '?id=' + props.id"
:auto-upload="false"
:disabled="formLoading"
:headers="uploadHeaders"
:limit="1"
:on-error="submitFormError"
:on-exceed="handleExceed"
:on-success="submitFormSuccess"
accept=".jar"
drag
>
<Icon icon="ep:upload" />
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
</el-upload>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { getAccessToken, getTenantId } from '@/utils/auth'
defineOptions({ name: 'PluginImportForm' })
const props = defineProps<{ id: number }>() // id props
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const uploadRef = ref()
const importUrl =
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/iot/plugin-info/upload-file'
const uploadHeaders = ref() // Header
const fileList = ref([]) //
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
fileList.value = []
resetForm()
}
defineExpose({ open }) // open
/** 提交表单 */
const submitForm = async () => {
if (fileList.value.length == 0) {
message.error('请上传文件')
return
}
//
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
}
/** 文件上传成功 */
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {
if (response.code !== 0) {
message.error(response.msg)
formLoading.value = false
return
}
message.alert('上传成功')
formLoading.value = false
dialogVisible.value = false
//
emits('success')
}
/** 上传错误提示 */
const submitFormError = (): void => {
message.error('上传失败,请您重新上传!')
formLoading.value = false
}
/** 重置表单 */
const resetForm = async (): Promise<void> => {
//
formLoading.value = false
await nextTick()
uploadRef.value?.clearFiles()
}
/** 文件数超出提示 */
const handleExceed = (): void => {
message.error('最多只能上传一个文件!')
}
</script>

View File

@ -0,0 +1,101 @@
<template>
<div>
<div class="flex items-start justify-between">
<div>
<el-col>
<el-row>
<span class="text-xl font-bold">插件详情</span>
</el-row>
</el-col>
</div>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="2" direction="horizontal">
<el-descriptions-item label="插件名称">
{{ pluginInfo.name }}
</el-descriptions-item>
<el-descriptions-item label="插件ID">
{{ pluginInfo.pluginId }}
</el-descriptions-item>
<el-descriptions-item label="版本号">
{{ pluginInfo.version }}
</el-descriptions-item>
<el-descriptions-item label="状态">
<el-switch
v-model="pluginInfo.status"
:active-value="1"
:inactive-value="0"
@change="handleStatusChange"
/>
</el-descriptions-item>
<el-descriptions-item label="插件描述">
{{ pluginInfo.description }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
<ContentWrap class="mt-10px">
<el-button type="warning" plain @click="handleImport" v-hasPermi="['system:user:import']">
<Icon icon="ep:upload" /> 上传插件包
</el-button>
</ContentWrap>
</div>
<!-- 插件导入对话框 -->
<PluginImportForm
ref="importFormRef"
:id="Number(pluginInfo.id)"
@success="getPluginInfo(Number(pluginInfo.id))"
/>
</template>
<script lang="ts" setup>
import { PluginInfoApi, PluginInfoVO } from '@/api/iot/plugininfo'
import { useRoute } from 'vue-router'
import { ref, onMounted } from 'vue'
import PluginImportForm from './PluginImportForm.vue'
const message = useMessage()
const route = useRoute()
const pluginInfo = ref<PluginInfoVO>({})
const isInitialLoad = ref(true) //
onMounted(() => {
const id = Number(route.params.id)
if (id) {
getPluginInfo(id).then(() => {
isInitialLoad.value = false // false
})
}
})
const getPluginInfo = async (id: number) => {
const data = await PluginInfoApi.getPluginInfo(id)
pluginInfo.value = data
}
/** 处理状态变更 */
const handleStatusChange = async (status: number) => {
if (isInitialLoad.value) {
return
}
try {
//
const text = status === 1 ? '启用' : '停用'
await message.confirm('确认要"' + text + '"插件吗?')
await PluginInfoApi.updatePluginStatus({
id: pluginInfo.value.id,
status
})
message.success('更新状态成功')
getPluginInfo(Number(pluginInfo.value.id))
} catch (error) {
pluginInfo.value.status = status === 1 ? 0 : 1
message.error('更新状态失败')
}
}
/** 插件导入 */
const importFormRef = ref()
const handleImport = () => {
importFormRef.value.open()
}
</script>

View File

@ -45,25 +45,11 @@
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
</el-form-item> </el-form-item>
<!-- 视图切换按钮 -->
<el-form-item class="float-right !mr-0 !mb-0">
<el-button-group>
<el-button :type="viewType === 'card' ? 'primary' : 'default'" @click="viewType = 'card'">
<Icon icon="ep:grid" />
</el-button>
<el-button
:type="viewType === 'table' ? 'primary' : 'default'"
@click="viewType = 'table'"
>
<Icon icon="ep:list" />
</el-button>
</el-button-group>
</el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap v-if="viewType === 'table'"> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="插件名称" align="center" prop="name" /> <el-table-column label="插件名称" align="center" prop="name" />
<el-table-column label="组件id" align="center" prop="pluginId" /> <el-table-column label="组件id" align="center" prop="pluginId" />
@ -88,6 +74,14 @@
/> />
<el-table-column label="操作" align="center" min-width="120px"> <el-table-column label="操作" align="center" min-width="120px">
<template #default="scope"> <template #default="scope">
<el-button
link
type="primary"
@click="openDetail(scope.row.id)"
v-hasPermi="['iot:product:query']"
>
查看
</el-button>
<el-button <el-button
link link
type="primary" type="primary"
@ -104,13 +98,6 @@
> >
删除 删除
</el-button> </el-button>
<el-button
link
type="info"
@click="viewDetail(scope.row.id)"
>
详情
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -123,57 +110,6 @@
/> />
</ContentWrap> </ContentWrap>
<!-- 卡片视图 -->
<ContentWrap v-else>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
<el-card
v-for="item in list"
:key="item.pluginId"
class="cursor-pointer hover:shadow-lg transition-shadow"
>
<div class="flex items-center mb-4">
<div class="flex-1">
<div class="font-bold text-lg">{{ item.name }}</div>
<div class="text-gray-500 text-sm">组件ID: {{ item.pluginId }}</div>
</div>
</div>
<div class="text-sm text-gray-500">
<div>Jar包: {{ item.file }}</div>
<div>版本号: {{ item.version }}</div>
<div
>部署方式: <dict-tag :type="DICT_TYPE.IOT_PLUGIN_DEPLOY_TYPE" :value="item.deployType"
/></div>
<div>状态: <dict-tag :type="DICT_TYPE.IOT_PLUGIN_STATUS" :value="item.status" /></div>
</div>
<div class="flex justify-end mt-4">
<el-button
link
type="primary"
@click.stop="openForm('update', item.id)"
v-hasPermi="['iot:plugin-info:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click.stop="handleDelete(item.id)"
v-hasPermi="['iot:plugin-info:delete']"
>
删除
</el-button>
</div>
</el-card>
</div>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<PluginInfoForm ref="formRef" @success="getList" /> <PluginInfoForm ref="formRef" @success="getList" />
</template> </template>
@ -185,7 +121,7 @@ import { PluginInfoApi, PluginInfoVO } from '@/api/iot/plugininfo'
import PluginInfoForm from './PluginInfoForm.vue' import PluginInfoForm from './PluginInfoForm.vue'
/** IoT 插件信息 列表 */ /** IoT 插件信息 列表 */
defineOptions({ name: 'PluginInfo' }) defineOptions({ name: 'IoTPlugin' })
const message = useMessage() // const message = useMessage() //
const { t } = useI18n() // const { t } = useI18n() //
@ -200,7 +136,6 @@ const queryParams = reactive({
status: undefined status: undefined
}) })
const queryFormRef = ref() // const queryFormRef = ref() //
const viewType = ref<'card' | 'table'>('table') //
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
@ -232,6 +167,12 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
/** 打开详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'IoTPluginDetail', params: { id } })
}
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
@ -245,11 +186,6 @@ const handleDelete = async (id: number) => {
} catch {} } catch {}
} }
/** 查看详情操作 */
const viewDetail = (id: number) => {
router.push({ path: `/iot/plugininfo/detail/${id}` })
}
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -1,44 +0,0 @@
<template>
<ContentWrap>
<el-descriptions title="插件详情" :column="2" border>
<el-descriptions-item label="插件名称">{{ pluginInfo.name }}</el-descriptions-item>
<el-descriptions-item label="组件ID">{{ pluginInfo.pluginId }}</el-descriptions-item>
<el-descriptions-item label="Jar包">{{ pluginInfo.file }}</el-descriptions-item>
<el-descriptions-item label="版本号">{{ pluginInfo.version }}</el-descriptions-item>
<el-descriptions-item label="部署方式">
<dict-tag :type="DICT_TYPE.IOT_PLUGIN_DEPLOY_TYPE" :value="pluginInfo.deployType" />
</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :type="DICT_TYPE.IOT_PLUGIN_STATUS" :value="pluginInfo.status" />
</el-descriptions-item>
</el-descriptions>
<el-button type="primary" @click="goBack">返回</el-button>
</ContentWrap>
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { PluginInfoApi, PluginInfoVO } from '@/api/iot/plugininfo'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const pluginInfo = ref<PluginInfoVO>({})
const getPluginInfo = async (id: number) => {
const data = await PluginInfoApi.getPluginInfo(id)
pluginInfo.value = data
}
const goBack = () => {
router.back()
}
onMounted(() => {
const id = Number(route.params.id)
if (id) {
getPluginInfo(id)
}
})
</script>