Skip to content

Commit e6a5bb0

Browse files
committed
【功能新增】IOT: 添加插件管理功能,包括插件列表、详情、状态更新及文件上传功能
1 parent 5d2adca commit e6a5bb0

File tree

7 files changed

+238
-125
lines changed

7 files changed

+238
-125
lines changed

src/api/iot/plugininfo/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,15 @@ export const PluginInfoApi = {
4747
// 导出IoT 插件信息 Excel
4848
exportPluginInfo: async (params) => {
4949
return await request.download({ url: `/iot/plugin-info/export-excel`, params })
50+
},
51+
52+
// 修改IoT 插件状态
53+
updatePluginStatus: async (data: any) => {
54+
return await request.put({ url: `/iot/plugin-info/update-status`, data })
55+
},
56+
57+
// 上传Jar包
58+
uploadPluginFile: async (data: any) => {
59+
return await request.post({ url: `/iot/plugin-info/upload-file`, data })
5060
}
5161
}

src/router/modules/remaining.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,6 +638,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
638638
activeMenu: '/iot/device'
639639
},
640640
component: () => import('@/views/iot/device/device/detail/index.vue')
641+
},
642+
{
643+
path: 'plugin/detail/:id',
644+
name: 'IoTPluginDetail',
645+
meta: {
646+
title: '插件详情',
647+
noCache: true,
648+
hidden: true,
649+
activeMenu: '/iot/plugin'
650+
},
651+
component: () => import('@/views/iot/plugin/detail/index.vue')
641652
}
642653
]
643654
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<template>
2+
<Dialog v-model="dialogVisible" title="插件导入" width="400">
3+
<el-upload
4+
ref="uploadRef"
5+
v-model:file-list="fileList"
6+
:action="importUrl + '?id=' + props.id"
7+
:auto-upload="false"
8+
:disabled="formLoading"
9+
:headers="uploadHeaders"
10+
:limit="1"
11+
:on-error="submitFormError"
12+
:on-exceed="handleExceed"
13+
:on-success="submitFormSuccess"
14+
accept=".jar"
15+
drag
16+
>
17+
<Icon icon="ep:upload" />
18+
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
19+
</el-upload>
20+
<template #footer>
21+
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
22+
<el-button @click="dialogVisible = false">取 消</el-button>
23+
</template>
24+
</Dialog>
25+
</template>
26+
<script lang="ts" setup>
27+
import { getAccessToken, getTenantId } from '@/utils/auth'
28+
29+
defineOptions({ name: 'PluginImportForm' })
30+
31+
const props = defineProps<{ id: number }>() // 接收 id 作为 props
32+
33+
const message = useMessage() // 消息弹窗
34+
35+
const dialogVisible = ref(false) // 弹窗的是否展示
36+
const formLoading = ref(false) // 表单的加载中
37+
const uploadRef = ref()
38+
const importUrl =
39+
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/iot/plugin-info/upload-file'
40+
const uploadHeaders = ref() // 上传 Header 头
41+
const fileList = ref([]) // 文件列表
42+
43+
/** 打开弹窗 */
44+
const open = () => {
45+
dialogVisible.value = true
46+
fileList.value = []
47+
resetForm()
48+
}
49+
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
50+
51+
/** 提交表单 */
52+
const submitForm = async () => {
53+
if (fileList.value.length == 0) {
54+
message.error('请上传文件')
55+
return
56+
}
57+
// 提交请求
58+
uploadHeaders.value = {
59+
Authorization: 'Bearer ' + getAccessToken(),
60+
'tenant-id': getTenantId()
61+
}
62+
formLoading.value = true
63+
uploadRef.value!.submit()
64+
}
65+
66+
/** 文件上传成功 */
67+
const emits = defineEmits(['success'])
68+
const submitFormSuccess = (response: any) => {
69+
if (response.code !== 0) {
70+
message.error(response.msg)
71+
formLoading.value = false
72+
return
73+
}
74+
message.alert('上传成功')
75+
formLoading.value = false
76+
dialogVisible.value = false
77+
// 发送操作成功的事件
78+
emits('success')
79+
}
80+
81+
/** 上传错误提示 */
82+
const submitFormError = (): void => {
83+
message.error('上传失败,请您重新上传!')
84+
formLoading.value = false
85+
}
86+
87+
/** 重置表单 */
88+
const resetForm = async (): Promise<void> => {
89+
// 重置上传状态和文件
90+
formLoading.value = false
91+
await nextTick()
92+
uploadRef.value?.clearFiles()
93+
}
94+
95+
/** 文件数超出提示 */
96+
const handleExceed = (): void => {
97+
message.error('最多只能上传一个文件!')
98+
}
99+
</script>

src/views/iot/plugin/detail/index.vue

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<template>
2+
<div>
3+
<div class="flex items-start justify-between">
4+
<div>
5+
<el-col>
6+
<el-row>
7+
<span class="text-xl font-bold">插件详情</span>
8+
</el-row>
9+
</el-col>
10+
</div>
11+
</div>
12+
<ContentWrap class="mt-10px">
13+
<el-descriptions :column="2" direction="horizontal">
14+
<el-descriptions-item label="插件名称">
15+
{{ pluginInfo.name }}
16+
</el-descriptions-item>
17+
<el-descriptions-item label="插件ID">
18+
{{ pluginInfo.pluginId }}
19+
</el-descriptions-item>
20+
<el-descriptions-item label="版本号">
21+
{{ pluginInfo.version }}
22+
</el-descriptions-item>
23+
<el-descriptions-item label="状态">
24+
<el-switch
25+
v-model="pluginInfo.status"
26+
:active-value="1"
27+
:inactive-value="0"
28+
@change="handleStatusChange"
29+
/>
30+
</el-descriptions-item>
31+
<el-descriptions-item label="插件描述">
32+
{{ pluginInfo.description }}
33+
</el-descriptions-item>
34+
</el-descriptions>
35+
</ContentWrap>
36+
<ContentWrap class="mt-10px">
37+
<el-button type="warning" plain @click="handleImport" v-hasPermi="['system:user:import']">
38+
<Icon icon="ep:upload" /> 上传插件包
39+
</el-button>
40+
</ContentWrap>
41+
</div>
42+
<!-- 插件导入对话框 -->
43+
<PluginImportForm
44+
ref="importFormRef"
45+
:id="Number(pluginInfo.id)"
46+
@success="getPluginInfo(Number(pluginInfo.id))"
47+
/>
48+
</template>
49+
50+
<script lang="ts" setup>
51+
import { PluginInfoApi, PluginInfoVO } from '@/api/iot/plugininfo'
52+
import { useRoute } from 'vue-router'
53+
import { ref, onMounted } from 'vue'
54+
import PluginImportForm from './PluginImportForm.vue'
55+
56+
const message = useMessage()
57+
const route = useRoute()
58+
const pluginInfo = ref<PluginInfoVO>({})
59+
const isInitialLoad = ref(true) // 初始化标志位
60+
61+
onMounted(() => {
62+
const id = Number(route.params.id)
63+
if (id) {
64+
getPluginInfo(id).then(() => {
65+
isInitialLoad.value = false // 数据加载完成后,设置标志位为 false
66+
})
67+
}
68+
})
69+
70+
const getPluginInfo = async (id: number) => {
71+
const data = await PluginInfoApi.getPluginInfo(id)
72+
pluginInfo.value = data
73+
}
74+
75+
/** 处理状态变更 */
76+
const handleStatusChange = async (status: number) => {
77+
if (isInitialLoad.value) {
78+
return
79+
}
80+
try {
81+
// 修改状态的二次确认
82+
const text = status === 1 ? '启用' : '停用'
83+
await message.confirm('确认要"' + text + '"插件吗?')
84+
await PluginInfoApi.updatePluginStatus({
85+
id: pluginInfo.value.id,
86+
status
87+
})
88+
message.success('更新状态成功')
89+
getPluginInfo(Number(pluginInfo.value.id))
90+
} catch (error) {
91+
pluginInfo.value.status = status === 1 ? 0 : 1
92+
message.error('更新状态失败')
93+
}
94+
}
95+
96+
/** 插件导入 */
97+
const importFormRef = ref()
98+
const handleImport = () => {
99+
importFormRef.value.open()
100+
}
101+
</script>

src/views/iot/plugininfo/index.vue renamed to src/views/iot/plugin/index.vue

Lines changed: 17 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,11 @@
4545
<Icon icon="ep:plus" class="mr-5px" /> 新增
4646
</el-button>
4747
</el-form-item>
48-
<!-- 视图切换按钮 -->
49-
<el-form-item class="float-right !mr-0 !mb-0">
50-
<el-button-group>
51-
<el-button :type="viewType === 'card' ? 'primary' : 'default'" @click="viewType = 'card'">
52-
<Icon icon="ep:grid" />
53-
</el-button>
54-
<el-button
55-
:type="viewType === 'table' ? 'primary' : 'default'"
56-
@click="viewType = 'table'"
57-
>
58-
<Icon icon="ep:list" />
59-
</el-button>
60-
</el-button-group>
61-
</el-form-item>
6248
</el-form>
6349
</ContentWrap>
6450

6551
<!-- 列表 -->
66-
<ContentWrap v-if="viewType === 'table'">
52+
<ContentWrap>
6753
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
6854
<el-table-column label="插件名称" align="center" prop="name" />
6955
<el-table-column label="组件id" align="center" prop="pluginId" />
@@ -91,80 +77,30 @@
9177
<el-button
9278
link
9379
type="primary"
94-
@click="openForm('update', scope.row.id)"
95-
v-hasPermi="['iot:plugin-info:update']"
96-
>
97-
编辑
98-
</el-button>
99-
<el-button
100-
link
101-
type="danger"
102-
@click="handleDelete(scope.row.id)"
103-
v-hasPermi="['iot:plugin-info:delete']"
104-
>
105-
删除
106-
</el-button>
107-
<el-button
108-
link
109-
type="info"
110-
@click="viewDetail(scope.row.id)"
80+
@click="openDetail(scope.row.id)"
81+
v-hasPermi="['iot:product:query']"
11182
>
112-
详情
83+
查看
11384
</el-button>
114-
</template>
115-
</el-table-column>
116-
</el-table>
117-
<!-- 分页 -->
118-
<Pagination
119-
:total="total"
120-
v-model:page="queryParams.pageNo"
121-
v-model:limit="queryParams.pageSize"
122-
@pagination="getList"
123-
/>
124-
</ContentWrap>
125-
126-
<!-- 卡片视图 -->
127-
<ContentWrap v-else>
128-
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
129-
<el-card
130-
v-for="item in list"
131-
:key="item.pluginId"
132-
class="cursor-pointer hover:shadow-lg transition-shadow"
133-
>
134-
<div class="flex items-center mb-4">
135-
<div class="flex-1">
136-
<div class="font-bold text-lg">{{ item.name }}</div>
137-
<div class="text-gray-500 text-sm">组件ID: {{ item.pluginId }}</div>
138-
</div>
139-
</div>
140-
<div class="text-sm text-gray-500">
141-
<div>Jar包: {{ item.file }}</div>
142-
<div>版本号: {{ item.version }}</div>
143-
<div
144-
>部署方式: <dict-tag :type="DICT_TYPE.IOT_PLUGIN_DEPLOY_TYPE" :value="item.deployType"
145-
/></div>
146-
<div>状态: <dict-tag :type="DICT_TYPE.IOT_PLUGIN_STATUS" :value="item.status" /></div>
147-
</div>
148-
<div class="flex justify-end mt-4">
14985
<el-button
15086
link
15187
type="primary"
152-
@click.stop="openForm('update', item.id)"
88+
@click="openForm('update', scope.row.id)"
15389
v-hasPermi="['iot:plugin-info:update']"
15490
>
15591
编辑
15692
</el-button>
15793
<el-button
15894
link
15995
type="danger"
160-
@click.stop="handleDelete(item.id)"
96+
@click="handleDelete(scope.row.id)"
16197
v-hasPermi="['iot:plugin-info:delete']"
16298
>
16399
删除
164100
</el-button>
165-
</div>
166-
</el-card>
167-
</div>
101+
</template>
102+
</el-table-column>
103+
</el-table>
168104
<!-- 分页 -->
169105
<Pagination
170106
:total="total"
@@ -185,7 +121,7 @@ import { PluginInfoApi, PluginInfoVO } from '@/api/iot/plugininfo'
185121
import PluginInfoForm from './PluginInfoForm.vue'
186122
187123
/** IoT 插件信息 列表 */
188-
defineOptions({ name: 'PluginInfo' })
124+
defineOptions({ name: 'IoTPlugin' })
189125
190126
const message = useMessage() // 消息弹窗
191127
const { t } = useI18n() // 国际化
@@ -200,7 +136,6 @@ const queryParams = reactive({
200136
status: undefined
201137
})
202138
const queryFormRef = ref() // 搜索的表单
203-
const viewType = ref<'card' | 'table'>('table') // 视图类型,默认为表格视图
204139
205140
/** 查询列表 */
206141
const getList = async () => {
@@ -232,6 +167,12 @@ const openForm = (type: string, id?: number) => {
232167
formRef.value.open(type, id)
233168
}
234169
170+
/** 打开详情 */
171+
const { push } = useRouter()
172+
const openDetail = (id: number) => {
173+
push({ name: 'IoTPluginDetail', params: { id } })
174+
}
175+
235176
/** 删除按钮操作 */
236177
const handleDelete = async (id: number) => {
237178
try {
@@ -245,13 +186,8 @@ const handleDelete = async (id: number) => {
245186
} catch {}
246187
}
247188
248-
/** 查看详情操作 */
249-
const viewDetail = (id: number) => {
250-
router.push({ path: `/iot/plugininfo/detail/${id}` })
251-
}
252-
253189
/** 初始化 **/
254190
onMounted(() => {
255191
getList()
256192
})
257-
</script>
193+
</script>

0 commit comments

Comments
 (0)