소스 검색

feat(release): mqtt: mqtt认证鉴权

Yi 1 년 전
부모
커밋
5f7cdf3f32

+ 1 - 0
.gitignore

@@ -34,3 +34,4 @@ stats.html
 /src/typings/components.d.ts
 package-lock.json
 yarn.lock
+.vscode

+ 0 - 126
README.md

@@ -1,73 +1,3 @@
-<div align="center">
-	<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybean.svg" style="width: 160px;"/>
-	<h1>Soybean Admin</h1>
-</div>
-
-[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE) ![](https://img.shields.io/github/stars/honghuangdc/soybean-admin) ![](https://img.shields.io/github/forks/honghuangdc/soybean-admin)
-
-## 简介
-
-[Soybean Admin](https://github.com/honghuangdc/soybean-admin) 是一个基于 Vue3、Vite3、TypeScript、NaiveUI、Pinia 和 UnoCSS 的清新优雅的中后台模版,它使用了最新流行的前端技术栈,内置丰富的主题配置,有着极高的代码规范,基于文件的路由系统以及基于 Mock 的动态权限路由,开箱即用的中后台前端解决方案,也可用于学习参考。
-
-## 特性
-
-- **最新流行技术栈**:使用 Vue3/Vite 等前端前沿技术开发, 使用高效率的 npm 包管理器 pnpm
-- **TypeScript**: 应用程序级 JavaScript 的语言
-- **主题**:丰富可配置的主题、暗黑模式,基于原子 css 框架 - UnoCss 的动态主题颜色
-- **代码规范**:丰富的规范插件及极高的代码规范
-- **文件路由系统**:基于文件的路由系统,根据页面文件自动生成路由声明、路由导入和路由模块
-- **权限路由**:提供前端静态和后端动态两种路由模式,基于 mock 的动态路由能快速实现后端动态路由
-- **请求函数**:基于 axios 的完善的请求函数封装,提供 Promise 和 hooks 两种请求函数,加入请求结果数据转换的适配器
-
-## 在线预览
-
-- [Soybean Admin 预览地址](https://soybean.pro/)
-
-## 文档
-
-- [项目文档预览地址](https://docs.soybean.pro)
-
-## 代码仓库
-
-- [github](https://github.com/honghuangdc/soybean-admin)
-- [tauri 版](https://github.com/honghuangdc/soybean-admin/tree/tauri)
-- [精简版](https://github.com/honghuangdc/soybean-admin/tree/thin)
-- [gitee](https://gitee.com/honghuangdc/soybean-admin)
-- [tauri 版](https://gitee.com/honghuangdc/soybean-admin/tree/tauri)
-- [精简版](https://gitee.com/honghuangdc/soybean-admin/tree/thin)
-
-## 更新日志
-
-[CHANGELOG](./CHANGELOG.md)
-
-## 后端服务
-
-- [soybean-admin-java](https://github.com/honghuangdc/soybean-admin-java)
-
-## 项目示例图
-
-![](https://s2.loli.net/2022/05/16/keOtgFH27r9nqYS.png)
-
-![](https://s2.loli.net/2022/05/18/bW7mftiQexkvSTG.png)
-
-![](https://s2.loli.net/2022/05/16/uV5nzjb3gYptAEl.png)
-
-![](https://s2.loli.net/2022/05/16/rSnNHLdpuvkKxWq.png)
-
-![](https://s2.loli.net/2022/05/18/Mt6YZqmDxO8v4uR.png)
-
-![](https://s2.loli.net/2022/05/16/ktH5dcG3fuFOoKA.png)
-
-![](https://s2.loli.net/2022/05/16/VPl6Ru1iCAhLcS4.png)
-
-![](https://s2.loli.net/2022/05/16/bRlAKuHW7ZVh9DT.png)
-
-![](https://s2.loli.net/2022/06/07/rY8TyAftM5dxspv.png)
-
-![](https://s2.loli.net/2022/06/07/5GNBAd31IzQVjLP.png)
-
-![](https://s2.loli.net/2022/06/07/rRSG6mEZpujOACT.png)
-
 ## 安装使用
 
 - 环境配置
@@ -109,62 +39,6 @@ docker run --name soybean -p 80:80 -d soybeanjs/soybean-admin:v0.9.6
 
 打开本地浏览器访问`http://localhost`
 
-## 如何贡献
-
-非常欢迎您的加入![提一个 Issue](https://github.com/honghuangdc/soybean-admin/issues/new) 或者提交一个 Pull Request。
-
-## Git 贡献提交规范
-
-项目已经内置 Angular 提交规范,直接执行 commit 命令即可生成符合 Angular 提交规范的 commit。
-
-项目已用 simple-git-hooks 代替了 husky, 旧版本用了 husky,执行 pnpm soy init-git-hooks 进行初始化配置
-
-## 基于 SoybeanAdmin 二次开发的项目
-[electron-mock-admin](https://github.com/lixin59/electron-mock-api): 一个 Mock Api 管理系统,帮助前端开发伙伴快速实现接口的mock。
-
-## 浏览器支持
-
-本地开发推荐使用`Chrome 90+` 浏览器
-
-支持现代浏览器, 不支持 IE
-
-| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px"  />](http://godban.github.io/browsers-support-badges/)IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Safari |
-| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
-|                                                                                                                not support                                                                                                                |                                                                                          last 2 versions                                                                                          |                                                                                               last 2 versions                                                                                                |                                                                                             last 2 versions                                                                                              |                                                                                             last 2 versions                                                                                              |
-
-## 开源作者
-
-[@Soybean](https://github.com/honghuangdc)
-
-## 交流
-
-`Soybean Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供微信和 QQ 交流群,使用问题欢迎在群内提问。
-
-  <div style="display:flex;">
-  	<div style="padding-right:24px;">
-  		<p>微信交流群</p>
-      <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybeanjs-wechat2.jpeg" style="width:200px" />
-  	</div>
-  	<div style="padding-right:24px;">
-  		<p>QQ交流群</p>
-      <img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/qq-soybean-admin.jpg" style="width:200px" />
-  	</div>
-		<div>
-			<p>添加本人微信,欢迎来技术交流,业务咨询</p>
-			<img src="https://soybeanjs-1300612522.cos.ap-guangzhou.myqcloud.com/uPic/soybeanjs.jpeg" style="width:180px" />
-		</div>
-  </div>
-
-## 捐赠
-
-如果你觉得这个项目对你有帮助,可以请 Soybean 喝杯饮料表示支持,Soybean 开源的动力离不开各位的支持和鼓励。
-
-![赞助](https://s2.loli.net/2022/01/24/i9cpq7lTCrKUoFf.png)
-
-## License
-
-[MIT © Soybean-2021](./LICENSE)
-
 
 ## TODOLIST
 

+ 4 - 4
package.json

@@ -1,11 +1,11 @@
 {
-  "name": "soybean-admin",
+  "name": "kpt-event-system",
   "version": "0.9.9",
   "description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
   "author": {
-    "name": "Soybean",
-    "email": "honghuangdc@gmail.com",
-    "url": "https://github.com/honghuangdc"
+    "name": "ping",
+    "email": "857067837@qq.com",
+    "url": "https://github.com/xuyiping"
   },
   "license": "MIT",
   "homepage": "https://github.com/honghuangdc/soybean-admin",

+ 3 - 3
src/router/modules/background.ts

@@ -1,4 +1,4 @@
-const dashboard: AuthRoute.Route = {
+const background: AuthRoute.Route = {
   name: 'background',
   path: '/background',
   component: 'basic',
@@ -47,8 +47,8 @@ const dashboard: AuthRoute.Route = {
   meta: {
     title: '后台管理',
     icon: 'mdi:monitor-dashboard',
-    order: 1
+    order: 11
   }
 };
 
-export default dashboard;
+export default background;

+ 24 - 0
src/router/modules/mqtt.ts

@@ -0,0 +1,24 @@
+const mqtt: AuthRoute.Route = {
+  name: 'mqtt',
+  path: '/mqtt',
+  component: 'basic',
+  children: [
+    {
+      name: 'mqtt_authentication',
+      path: '/mqtt/authentication',
+      component: 'self',
+      meta: {
+        title: '认证鉴权',
+        requiresAuth: true,
+        icon: 'icon-park-outline:workbench'
+      }
+    }
+  ],
+  meta: {
+    title: 'mqtt管理',
+    icon: 'mdi:monitor-dashboard',
+    order: 12
+  }
+};
+
+export default mqtt;

+ 11 - 0
src/service/api/mqtt.adapter.ts

@@ -0,0 +1,11 @@
+export function adapterOfMqttAuthList(data: ApiBackground.MqttAuth[] | null): BackgroundMqttAuth.Auth[] {
+  if (!data) return [];
+  return data.map(item => {
+    const field: BackgroundMqttAuth.Auth = {
+      index: item.id,
+      key: item.id,
+      ...item
+    };
+    return field;
+  });
+}

+ 28 - 0
src/service/api/mqtt.ts

@@ -0,0 +1,28 @@
+import { adapter } from '@/utils';
+import { adapterOfMqttAuthList } from '@/service/api/mqtt.adapter';
+import { backgroundRequest } from '../request';
+
+/** 获取用户鉴权列表 */
+export const fetchMqttAuthList = async (page: number, pageSize: number, topic: string) => {
+  const data = await backgroundRequest.post<ApiBackground.MqttAuth[] | null>(
+    '/mqtt/auth/list',
+    { topic },
+    { headers: { page, page_size: pageSize } }
+  );
+  return adapter(adapterOfMqttAuthList, data);
+};
+
+/** 删除用户鉴权列表 */
+export const mqttAuthDelete = (authId: number) => {
+  return backgroundRequest.delete<ApiBoolean.OK | null>(`/mqtt/auth/delete/${authId}`);
+};
+
+/** 添加用户鉴权列表 */
+export const mqttAuthAdd = (params: ApiBackground.MqttAuth) => {
+  return backgroundRequest.post<ApiBoolean.OK | null>('/mqtt/auth/add', params);
+};
+
+/** 编辑用户鉴权列表 */
+export const mqttAuthEdit = (param: ApiBackground.MqttAuth) => {
+  return backgroundRequest.post<ApiBoolean.OK | null>('/mqtt/auth/edit', param);
+};

+ 1 - 1
src/service/request/index.ts

@@ -9,4 +9,4 @@ export const request = createRequest({ baseURL: isHttpProxy ? proxyPattern : url
 
 export const mockRequest = createRequest({ baseURL: '/mock' });
 
-export const backgroundRequest = createRequest({ baseURL: 'http://192.168.1.70:8000/api/v1' });
+export const backgroundRequest = createRequest({ baseURL: 'http://192.168.1.96:8001/api/v1' });

+ 17 - 0
src/typings/business.d.ts

@@ -128,3 +128,20 @@ declare namespace BackgroundWorkflow {
    */
   type IsShowKey = NonNullable<Workflow['is_show']>;
 }
+
+declare namespace BackgroundMqttAuth {
+  interface Auth extends ApiBackground.MqttAuth {
+    /** 序号 */
+    index: number;
+    /** 表格的key(id) */
+    key: number;
+  }
+
+  /**
+   * 是否启动
+   * 0 无效
+   * 1:是
+   * 2: 否
+   */
+  type IsShowKey = NonNullable<Auth['is_show']>;
+}

+ 14 - 0
src/typings/mqtt-auth.d.ts

@@ -0,0 +1,14 @@
+declare namespace ApiBackground {
+  interface MqttAuth {
+    /** id */
+    id: number;
+    /** 字段名 */
+    client_id: string | null;
+    mount_point: string | null;
+    user_name: string | null;
+    publish_acl: string | null;
+    subscribe_acl: string | null;
+    is_show: number | null;
+    remark: string | null;
+  }
+}

+ 3 - 0
src/typings/page-route.d.ts

@@ -57,6 +57,8 @@ declare namespace PageRoute {
     | 'management_role'
     | 'management_route'
     | 'management_user'
+    | 'mqtt'
+    | 'mqtt_authentication'
     | 'multi-menu'
     | 'multi-menu_first'
     | 'multi-menu_first_second-new'
@@ -115,6 +117,7 @@ declare namespace PageRoute {
     | 'management_role'
     | 'management_route'
     | 'management_user'
+    | 'mqtt_authentication'
     | 'multi-menu_first_second-new_third'
     | 'multi-menu_first_second'
     | 'plugin_charts_antv'

+ 1 - 0
src/views/index.ts

@@ -37,6 +37,7 @@ export const views: Record<
   management_role: () => import('./management/role/index.vue'),
   management_route: () => import('./management/route/index.vue'),
   management_user: () => import('./management/user/index.vue'),
+  mqtt_authentication: () => import('./mqtt/authentication/index.vue'),
   'multi-menu_first_second-new_third': () => import('./multi-menu/first/second-new/third/index.vue'),
   'multi-menu_first_second': () => import('./multi-menu/first/second/index.vue'),
   plugin_charts_antv: () => import('./plugin/charts/antv/index.vue'),

+ 173 - 0
src/views/mqtt/authentication/components/table-action-modal.vue

@@ -0,0 +1,173 @@
+<template>
+  <n-modal v-model:show="modalVisible" preset="card" :title="title" class="w-700px">
+    <n-form ref="formRef" label-placement="left" :label-width="80" :model="formModel" :rules="rules">
+      <n-grid :cols="48" :x-gap="18">
+        <n-form-item-grid-item :span="25" label="挂载点" path="mount_point">
+          <n-input v-model:value="formModel.mount_point" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="25" label="客户端ID" path="client_id">
+          <n-input v-model:value="formModel.client_id" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="25" label="用户名称" path="user_name">
+          <n-input v-model:value="formModel.user_name" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="25" label="发布topic" path="publish_acl">
+          <n-input v-model:value="formModel.publish_acl" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="25" label="订阅topic" path="subscribe_acl">
+          <n-input v-model:value="formModel.subscribe_acl" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="25" label="是否显示" path="is_show">
+          <n-select v-model:value="formModel.is_show" :options="EventIsShowOptions" />
+        </n-form-item-grid-item>
+        <n-form-item-grid-item :span="25" label="描述" path="remarks">
+          <n-input v-model:value="formModel.remark" type="textarea" placeholder="请输入描述" />
+        </n-form-item-grid-item>
+      </n-grid>
+      <n-space class="w-full pt-16px" :size="24" justify="end">
+        <n-button class="w-72px" @click="closeModal">取消</n-button>
+        <n-button class="w-72px" type="primary" @click="handleSubmit">确定</n-button>
+      </n-space>
+    </n-form>
+  </n-modal>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, reactive, watch } from 'vue';
+import type { FormInst, FormItemRule } from 'naive-ui';
+import { EventIsShowOptions } from '@/constants';
+import { createRequiredFormRule } from '@/utils';
+import { mqttAuthAdd, mqttAuthEdit } from '@/service/api/mqtt';
+export interface Props {
+  /** 弹窗可见性 */
+  visible: boolean;
+  /**
+   * 弹窗类型
+   * add: 新增
+   * edit: 编辑
+   */
+  type?: 'add' | 'edit';
+  /** 编辑的表格行数据 */
+  editData?: BackgroundMqttAuth.Auth | null;
+}
+
+export type ModalType = NonNullable<Props['type']>;
+
+defineOptions({ name: 'TableActionModal' });
+
+const props = withDefaults(defineProps<Props>(), {
+  type: 'add',
+  editData: null
+});
+
+interface Emits {
+  (e: 'update:visible', visible: boolean): void;
+}
+
+const emit = defineEmits<Emits>();
+
+const modalVisible = computed({
+  get() {
+    return props.visible;
+  },
+  set(visible) {
+    emit('update:visible', visible);
+  }
+});
+const closeModal = () => {
+  modalVisible.value = false;
+};
+const titles: Record<ModalType, string> = {
+  add: '添加鉴权',
+  edit: '编辑鉴权'
+};
+
+const title = computed(() => {
+  return titles[props.type];
+});
+
+const formRef = ref<HTMLElement & FormInst>();
+
+type FormModel = Pick<
+  ApiBackground.MqttAuth,
+  'id' | 'client_id' | 'mount_point' | 'user_name' | 'publish_acl' | 'subscribe_acl' | 'remark' | 'is_show'
+>;
+
+const formModel = reactive<FormModel>(createDefaultFormModel());
+
+const rules: Record<keyof FormModel, FormItemRule | FormItemRule[]> = {
+  client_id: createRequiredFormRule('请输入客户端id名称'),
+  is_show: createRequiredFormRule(),
+  publish_acl: createRequiredFormRule('请输入发布主题topic'),
+  subscribe_acl: createRequiredFormRule('请输入订阅主题topic'),
+  remark: createRequiredFormRule('请输入描述'),
+  mount_point: createRequiredFormRule('请输入挂载点'),
+  user_name: createRequiredFormRule('请输入用户名称'),
+  id: createRequiredFormRule()
+};
+
+function createDefaultFormModel(): FormModel {
+  return {
+    id: 0,
+    client_id: '',
+    user_name: '',
+    mount_point: '',
+    publish_acl: '',
+    subscribe_acl: '',
+    remark: '',
+    is_show: 1
+  };
+}
+
+function handleUpdateFormModel(model: Partial<FormModel>) {
+  Object.assign(formModel, model);
+}
+
+function handleUpdateFormModelByModalType() {
+  const handlers: Record<ModalType, () => void> = {
+    add: () => {
+      const defaultFormModel = createDefaultFormModel();
+      handleUpdateFormModel(defaultFormModel);
+    },
+    edit: () => {
+      if (props.editData) {
+        handleUpdateFormModel(props.editData);
+      }
+    }
+  };
+  handlers[props.type]();
+}
+
+async function handleSubmit() {
+  await formRef.value?.validate();
+  if (props.type === 'add') {
+    const data = mqttAuthAdd(formModel);
+    data.then(res => {
+      if (res.data?.success) {
+        window.$message?.success(`${titles[props.type]}成功!`);
+      }
+    });
+  }
+
+  if (props.type === 'edit') {
+    const data = mqttAuthEdit(formModel);
+    data.then(res => {
+      if (res.data?.success) {
+        window.$message?.success(`${titles[props.type]}成功!`);
+      }
+    });
+  }
+  closeModal();
+}
+
+watch(
+  () => props.visible,
+  newValue => {
+    if (newValue) {
+      handleUpdateFormModelByModalType();
+    }
+  }
+);
+</script>
+
+<style scoped></style>

+ 190 - 0
src/views/mqtt/authentication/index.vue

@@ -0,0 +1,190 @@
+<template>
+  <div class="h-full overflow-hidden">
+    <n-card title="认证鉴权" :bordered="false" class="rounded-16px shadow-sm">
+      <n-space class="pb-12px" justify="space-between">
+        <n-space>
+          <n-button type="primary" @click="handleAddTable">
+            <icon-ic-round-plus class="mr-4px text-20px" />
+            新增
+          </n-button>
+        </n-space>
+        <n-space align="center" :size="18">
+          <n-input-group>
+            <n-input v-model:value="topicName" />
+            <n-button type="primary" @click="handleSearch">搜索</n-button>
+          </n-input-group>
+          <n-button size="small" type="primary" @click="getTableData">
+            <icon-mdi-refresh class="mr-4px text-16px" :class="{ 'animate-spin': loading }" />
+            刷新表格
+          </n-button>
+        </n-space>
+      </n-space>
+      <n-data-table :columns="columns" :data="tableData" :loading="loading" :pagination="pagination" />
+      <table-action-modal v-model:visible="visible" :type="modalType" :edit-data="editData" />
+    </n-card>
+  </div>
+</template>
+
+<script setup lang="tsx">
+import { reactive, ref } from 'vue';
+import type { Ref } from 'vue';
+import { NButton, NPopconfirm, NSpace } from 'naive-ui';
+import type { DataTableColumns, PaginationProps } from 'naive-ui';
+import { useBoolean, useLoading } from '@/hooks';
+import { fetchMqttAuthList, mqttAuthDelete } from '@/service/api/mqtt';
+import TableActionModal from '../authentication/components/table-action-modal.vue';
+import type { ModalType } from '../authentication/components/table-action-modal.vue';
+
+const { loading, startLoading, endLoading } = useLoading(false);
+const { bool: visible, setTrue: openModal } = useBoolean();
+const topicName = ref('');
+const tableData = ref<BackgroundMqttAuth.Auth[]>([]);
+function setTableData(data: BackgroundMqttAuth.Auth[]) {
+  tableData.value = data;
+}
+
+async function getTableData() {
+  startLoading();
+  const { data } = await fetchMqttAuthList(1, 10, topicName.value);
+  if (data) {
+    setTimeout(() => {
+      setTableData(data);
+      endLoading();
+    }, 1000);
+  } else {
+    endLoading();
+  }
+}
+
+const columns: Ref<DataTableColumns<BackgroundMqttAuth.Auth>> = ref([
+  {
+    type: 'selection',
+    align: 'center'
+  },
+  {
+    key: 'index',
+    title: '序号',
+    align: 'center'
+  },
+  {
+    key: 'mount_point',
+    title: '挂载点',
+    align: 'center'
+  },
+  {
+    key: 'client_id',
+    title: '客户端ID',
+    align: 'center'
+  },
+  {
+    key: 'user_name',
+    title: '用户名称',
+    align: 'center'
+  },
+  {
+    key: 'publish_acl',
+    title: '发布topic',
+    align: 'center'
+  },
+  {
+    key: 'subscribe_acl',
+    title: '订阅topic',
+    align: 'center'
+  },
+  {
+    key: '是否允许',
+    title: '描述',
+    align: 'center'
+  },
+  {
+    key: 'actions',
+    title: '操作',
+    align: 'center',
+    render: row => {
+      return (
+        <NSpace justify={'center'}>
+          <NButton type="info" size={'small'} onClick={() => handleEditTable(row.id)}>
+            编辑
+          </NButton>
+          <NPopconfirm onPositiveClick={() => handleDeleteTable(row.id)}>
+            {{
+              default: () => '确认删除',
+              trigger: () => (
+                <NButton type="error" size={'small'}>
+                  删除
+                </NButton>
+              )
+            }}
+          </NPopconfirm>
+        </NSpace>
+      );
+    }
+  }
+]) as Ref<DataTableColumns<BackgroundMqttAuth.Auth>>;
+
+const editData = ref<BackgroundMqttAuth.Auth | null>(null);
+const modalType = ref<ModalType>('add');
+
+function setModalType(type: ModalType) {
+  modalType.value = type;
+}
+
+function setEditData(data: BackgroundMqttAuth.Auth | null) {
+  editData.value = data;
+}
+
+function handleAddTable() {
+  openModal();
+  setModalType('add');
+}
+
+function handleEditTable(rowId: number) {
+  const findItem = tableData.value.find(item => item.id === rowId);
+  if (findItem) {
+    setEditData(findItem);
+  }
+  setModalType('edit');
+  openModal();
+}
+
+function handleDeleteTable(rowId: number) {
+  const data = mqttAuthDelete(rowId);
+  data.then(res => {
+    if (res.data) {
+      window.$message?.success('删除成功!');
+    }
+  });
+  init();
+}
+
+const pagination: PaginationProps = reactive({
+  page: 1,
+  pageSize: 10,
+  showSizePicker: true,
+  pageSizes: [10, 15, 20, 25, 30],
+  onChange: (page: number) => {
+    pagination.page = page;
+  },
+  onUpdatePageSize: (pageSize: number) => {
+    pagination.pageSize = pageSize;
+    pagination.page = 1;
+  }
+});
+
+function handleSearch() {
+  if (!topicName.value) {
+    window.$message?.warning('请输入主题名称');
+  } else {
+    startLoading();
+  }
+}
+
+function init() {
+  getTableData();
+}
+
+// 初始化
+init();
+</script>
+
+<style scoped></style>