浏览代码

[Feat 0000] 用户管理 APi管理模块开发

bobo04052021@163.com 3 天之前
父节点
当前提交
f545e6fce7

+ 16 - 4
src/hooks/system/useListPage.ts

@@ -337,14 +337,26 @@ export function useListTable(tableProps: TableProps): [
     return Object.assign({ column: 'createTime' }, params);
   }
 
-  // 合并方法
+  // // 合并方法
+  // Object.assign(defaultTableProps, { beforeFetch });
+  // if (typeof tableProps.beforeFetch === 'function') {
+  //   defaultTableProps.beforeFetch = function (params) {
+  //     params = beforeFetch(params);
+  //     // @ts-ignore
+  //     tableProps.beforeFetch(params);
+  //     return params;
+  //   };
+  // }
+
+  // 合并方法 - 修改版本
   Object.assign(defaultTableProps, { beforeFetch });
   if (typeof tableProps.beforeFetch === 'function') {
     defaultTableProps.beforeFetch = function (params) {
       params = beforeFetch(params);
-      // @ts-ignore
-      tableProps.beforeFetch(params);
-      return params;
+      // 调用自定义的beforeFetch并获取其返回值
+      const customParams = tableProps.beforeFetch(params);
+      // 如果自定义函数返回了值,使用该值;否则使用原始params
+      return customParams !== undefined ? customParams : params;
     };
   }
 

+ 47 - 0
src/views/vent/dataCenter/APICenter/ApiAddModal.vue

@@ -0,0 +1,47 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :width="800" :title="isAdd ? '添加API信息' : '编辑API信息'" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+import { ref, computed, unref } from 'vue';
+import { BasicModal, useModalInner } from '/@/components/Modal';
+import { BasicForm, useForm } from '/@/components/Form/index';
+import { formAddSchema } from './apiManger.data';
+import { apiManageList, AddOrEdit, apiManageQueryByID } from './apiManger.api';
+const isAdd = ref(true);
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+//表单配置
+const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+  schemas: formAddSchema,
+  showActionButtonGroup: false,
+});
+//表单赋值
+const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+  //重置表单
+  await resetFields();
+  setModalProps({ confirmLoading: false });
+  //获取详情
+  data.record = await apiManageQueryByID({ id: data?.record.id });
+  isAdd.value = !!data?.isUpdate;
+  await setFieldsValue({
+    ...data.record,
+  });
+});
+//表单提交事件
+async function handleSubmit() {
+  try {
+    let values = await validate();
+    setModalProps({ confirmLoading: true });
+    //提交表单
+    await AddOrEdit(values, isAdd.value);
+    //关闭弹窗
+    closeModal();
+    //刷新列表
+    emit('success');
+  } finally {
+    setModalProps({ confirmLoading: false });
+  }
+}
+</script>

+ 31 - 0
src/views/vent/dataCenter/APICenter/apiManger.api.ts

@@ -0,0 +1,31 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  apiManageList = '/dataCenter/apimanage/list',
+  apiManageAdd = '/dataCenter/apimanage/add',
+  apiManageEdit = '/dataCenter/apimanage/edit',
+  apiManageDelete = '/dataCenter/apimanage/delete',
+  apiManageQueryByID = '/dataCenter/apimanage/queryById',
+}
+// 分站查询接口
+export const apiManageList = (params) =>
+  defHttp.get({
+    url: Api.apiManageList,
+    params,
+  });
+// API接口添加
+// API接口编辑
+export const AddOrEdit = (params, isAdd) => {
+  const url = isAdd ? Api.apiManageAdd : Api.apiManageEdit;
+  return isAdd ? defHttp.post({ url: url, params }) : defHttp.post({ url: url, params });
+};
+// API接口通过id删除
+export const apiManageDelete = (params) => {
+  return defHttp.delete({ url: Api.apiManageDelete, params }, { joinParamsToUrl: true });
+};
+// API接口通过id查询
+export const apiManageQueryByID = (params) =>
+  defHttp.get({
+    url: Api.apiManageQueryByID,
+    params,
+  });

+ 61 - 0
src/views/vent/dataCenter/APICenter/apiManger.data.ts

@@ -0,0 +1,61 @@
+import { FormSchema, BasicColumn } from '/@/components/Table';
+export const tableColumns: BasicColumn[] = [
+  {
+    title: 'API名称',
+    dataIndex: 'apiName',
+    key: 'apiName',
+    align: 'center',
+  },
+  {
+    title: '请求路径',
+    dataIndex: 'reqUrl',
+    key: 'reqUrl',
+    align: 'center',
+  },
+  {
+    title: 'HTTP方法',
+    dataIndex: 'reqMethod',
+    key: 'reqMethod',
+    align: 'center',
+  },
+  {
+    title: '参数',
+    dataIndex: 'reqParam',
+    key: 'reqParam',
+    align: 'center',
+  },
+  {
+    title: '状态',
+    dataIndex: 'apiStatus',
+    key: 'apiStatus',
+    align: 'center',
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    width: 120,
+    align: 'center',
+    slots: { customRender: 'action' },
+  },
+];
+
+export const formAddSchema: FormSchema[] = [
+  {
+    field: 'apiName',
+    label: '接口名称',
+    required: true,
+    component: 'Input',
+  },
+  {
+    field: 'reqUrl',
+    label: '请求路径',
+    required: true,
+    component: 'Input',
+  },
+  {
+    field: 'reqMethod',
+    label: '请求方式',
+    required: true,
+    component: 'Input',
+  },
+];

+ 90 - 0
src/views/vent/dataCenter/APICenter/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <div class="safetyList">
+    <customHeader>数据中心-API管理</customHeader>
+    <div class="device-manager-box">
+      <!-- 分站监测 -->
+      <BasicTable @register="registerTable">
+        <template #tableTitle>
+          <a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
+        </template>
+        <template #action="{ record }">
+          <a class="table-action-link" @click="handlerEdit(record)">编辑</a>
+          <a-popconfirm title="删除内容无法恢复,是否删除" ok-text="确定" cancel-text="取消" @confirm="handleDelete(record)">
+            <a class="table-action-link">删除</a>
+          </a-popconfirm>
+        </template>
+      </BasicTable>
+    </div>
+    <ApiAddModal @register="registerAddModal" @success="reload" />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, nextTick, computed, reactive, onMounted, onUnmounted, inject } from 'vue';
+import { BasicTable } from '/@/components/Table';
+import { usePermission } from '/@/hooks/web/usePermission';
+import customHeader from '/@/components/vent/customHeader.vue';
+import { apiManageList, apiManageDelete } from './apiManger.api';
+import { useModal } from '/@/components/Modal';
+import { tableColumns } from './apiManger.data';
+import ApiAddModal from './ApiAddModal.vue';
+import { useListPage } from '/@/hooks/system/useListPage';
+const { hasPermission } = usePermission();
+const [registerAddModal, { openModal: openAddModal }] = useModal();
+// 列表页面公共参数、方法
+const { prefixCls, tableContext } = useListPage({
+  designScope: 'API-list',
+  tableProps: {
+    title: 'Api列表',
+    api: apiManageList,
+    columns: tableColumns,
+    size: 'small',
+    actionColumn: {
+      width: 120,
+    },
+    beforeFetch(params) {
+      return Object.assign(
+        {
+          column: 'createTime',
+        },
+        params
+      );
+    },
+    showActionColumn: false,
+  },
+});
+const [registerTable, { reload, updateTableDataRecord }, { selectedRows }] = tableContext;
+// 注册table数据
+
+// async function getAPIList(params) {
+//   const result = await apiManageList(params);
+//   // tableData.value = result.records;
+// }
+function handleCreate() {
+  openAddModal(true, { isAdd: true });
+  reload();
+}
+async function handlerEdit(record) {
+  openAddModal(true, { record, isAdd: false });
+}
+async function handleDelete(record) {
+  await apiManageDelete({ id: record.id });
+  reload();
+}
+onMounted(async () => {
+  // await getAPIList({
+  //   pageNo: 1,
+  //   pageSize: 1000,
+  // });
+});
+onUnmounted(() => {});
+</script>
+
+<style lang="less" scoped>
+.safetyList {
+  width: calc(100% - 20px);
+  height: calc(100% - 80px);
+  position: relative;
+  margin: 40px 10px 10px 10px;
+}
+</style>

+ 80 - 129
src/views/vent/dataCenter/deviceCenter/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="safetyList">
-    <customHeader>数据中心-设备管理</customHeader>
+    <customHeader>数据中心-设备监测</customHeader>
     <div class="content">
       <a-tabs class="tab-box" v-model:activeKey="activeKey" @change="onChangeTab">
         <a-tab-pane tab="设备监测" key="device" />
@@ -13,11 +13,12 @@
             <div class="device-select">
               <div class="device-select-box">
                 <a-tree
+                  v-if="treeData && treeData.length > 0"
                   :show-line="true"
                   :tree-data="treeData"
                   v-model:selectedKeys="selectedKeys"
-                  :autoExpandParent="true"
                   v-model:expandedKeys="expandedKeys"
+                  :defaultExpandAll="true"
                   @select="onSelect"
                 >
                 </a-tree>
@@ -30,8 +31,8 @@
               size="small"
               :scroll="{ y: 650 }"
               :columns="outerColumns"
-              :data-source="paginatedData2"
-              :pagination="paginationConfig2"
+              :data-source="deviceList"
+              :pagination="false"
               :row-key="(record) => record.id"
               :expand-row-by-click="true"
               :expanded-row-keys="expandedRowKeys"
@@ -59,8 +60,8 @@
                 <a-table
                   size="small"
                   :columns="innerColumns"
-                  :data-source="paginatedData"
-                  :pagination="paginationConfig"
+                  :data-source="monitorList"
+                  :pagination="false"
                   :loading="loadingMap[record.id]"
                   bordered
                   :scroll="{ y: 410 }"
@@ -97,12 +98,14 @@ import { getDeviceTypeList, getDeviceListByType, getDevMonitorListById } from '.
 import { RightCircleTwoTone, DownCircleTwoTone } from '@ant-design/icons-vue';
 import { alignElement } from 'dom-align';
 import { active } from 'sortablejs';
+import { stubTrue } from 'lodash';
 
 const { hasPermission } = usePermission();
 let activeKey = ref('device');
 const treeData = ref<TreeProps['treeData']>([]);
 const selectedKeys = ref<string[]>([]);
 const expandedKeys = ref<string[]>([]);
+const AllNodeKeys = ref<string[]>([]);
 const deviceType = ref(''); // 监测设备类型
 const systemType = ref('');
 const systemID = ref(''); // 系统监测时,系统id
@@ -142,46 +145,46 @@ const paginatedData2 = computed(() => {
   return deviceList.value.slice(start, end);
 });
 // 分页器配置 - 修复响应式问题
-const paginationConfig = computed(() => {
-  return {
-    current: paginationState.value.current,
-    pageSize: paginationState.value.pageSize,
-    total: monitorList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState.value.current = page;
-      paginationState.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState.value.current = 1;
-      paginationState.value.pageSize = size;
-    },
-  };
-});
-const paginationConfig2 = computed(() => {
-  return {
-    current: paginationState2.value.current,
-    pageSize: paginationState2.value.pageSize,
-    total: deviceList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState2.value.current = page;
-      paginationState2.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState2.value.current = 1;
-      paginationState2.value.pageSize = size;
-    },
-  };
-});
+// const paginationConfig = computed(() => {
+//   return {
+//     current: paginationState.value.current,
+//     pageSize: paginationState.value.pageSize,
+//     total: monitorList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState.value.current = page;
+//       paginationState.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState.value.current = 1;
+//       paginationState.value.pageSize = size;
+//     },
+//   };
+// });
+// const paginationConfig2 = computed(() => {
+//   return {
+//     current: paginationState2.value.current,
+//     pageSize: paginationState2.value.pageSize,
+//     total: deviceList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState2.value.current = page;
+//       paginationState2.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState2.value.current = 1;
+//       paginationState2.value.pageSize = size;
+//     },
+//   };
+// });
 // 切换tab页面
 async function onChangeTab(tab) {
   activeKey.value = tab;
@@ -207,96 +210,44 @@ const onSelect: TreeProps['onSelect'] = (keys, e) => {
   }
   getDeviceList(deviceType.value);
 };
-
-async function findTreeDataValue(obj) {
-  const findDeviceType = (data: any[], obj, flag = true) => {
-    return data.find((item: any) => {
-      if (item.children.length > 0) {
-        findDeviceType(item.children, obj);
+// 获取所有节点key的函数
+const getAllNodeKeys = (nodes) => {
+  const keys = [];
+  const traverse = (nodeList) => {
+    nodeList.forEach((node) => {
+      keys.push(node.key);
+      if (node.children && node.children.length > 0) {
+        traverse(node.children);
       }
-      // debugger;
-      if (obj.deviceType && obj.deviceType.startsWith('sys_')) {
-        // debugger;
-        if (item.type == obj.deviceid) {
-          deviceType.value = 'sys';
-          systemID.value = obj.deviceid;
-          selectedKeys.value = [item.key];
-          expandedKeys.value = [item.key];
-          treeNodeTitle.value = item.title;
-        }
-      } else {
-        if (!flag) {
-          if (obj.deviceType && item.type.startsWith(obj.deviceType)) {
-            deviceType.value = item.type;
-            selectedKeys.value = [item.key];
-            expandedKeys.value = [item.key];
-            treeNodeTitle.value = item.title;
-            return true;
-          }
-          return false;
-        } else {
-          if (obj.deviceType && item.type == obj.deviceType) {
-            deviceType.value = item.type;
-            selectedKeys.value = [item.key];
-            expandedKeys.value = [item.key];
-            treeNodeTitle.value = item.title;
-            return true;
-          }
-          return false;
-        }
-      }
-      return false;
     });
   };
-  const flag = findDeviceType(treeData.value, obj);
-  if (!flag) {
-    findDeviceType(treeData.value, obj, false);
-  }
-  // 无类型时
-  if (!treeNodeTitle.value && treeData.value && treeData.value[0] && treeData.value[0]['children']) {
-    const defaultData = treeData.value[0]['children'][0];
-    if (deviceType.value !== defaultData.type) deviceType.value = defaultData.type;
-    selectedKeys.value = [defaultData.key as string];
-    expandedKeys.value = [defaultData.key as string];
-    treeNodeTitle.value = defaultData.title;
-  }
-}
+  traverse(nodes);
+  return keys;
+};
+
 // 获取树形菜单数据
-async function getDeviceType(sysType?) {
-  // if (treeData.value?.length > 0) return;
+async function getDeviceType() {
   const result = await getDeviceTypeList({});
   if (result.length > 0) {
-    const dataSource = <TreeProps['treeData']>[];
+    const dataSource = [];
     let key = '0';
     const getData = (resultList, dataSourceList, keyVal) => {
       resultList.forEach((item, index) => {
-        if (item.deviceType != 'sys' && item.children && item.children.length > 0) {
-          const children = getData(item.children, [], `${keyVal}-${index}`);
-          // 判断关键阻力路线
-          if (item.itemValue.startsWith(sysType) && children[0]) {
-            systemID.value = item.children[0]['itemValue'];
-          }
-
-          dataSourceList.push({
-            children: children,
-            title: item.itemText,
-            key: `${keyVal}-${index}`,
-            type: item.itemValue,
-            parentKey: `${keyVal}`,
-          });
-        } else {
-          dataSourceList.push({
-            children: [],
-            title: item.itemText,
-            key: `${keyVal}-${index}`,
-            type: item.itemValue,
-            parentKey: `${keyVal}`,
-          });
-        }
+        const children = item.children ? getData(item.children, [], `${keyVal}-${index}`) : [];
+        dataSourceList.push({
+          children: children,
+          title: item.itemText,
+          key: `${keyVal}-${index}`,
+          type: item.itemValue,
+          parentKey: `${keyVal}`,
+        });
       });
       return dataSourceList;
     };
+
     treeData.value = getData(result, dataSource, key);
+    // 数据就绪后设置展开key数组
+    expandedKeys.value = getAllNodeKeys(treeData.value);
   }
 }
 // 根据选择设备获取设备列表
@@ -418,8 +369,8 @@ async function refreshData(deviceId: string) {
   monitorList.value = Object.values(result.readData);
 }
 
-onMounted(async () => {
-  await getDeviceType();
+onMounted(() => {
+  getDeviceType();
 });
 onUnmounted(() => {
   if (timer) {
@@ -500,7 +451,7 @@ watch(
         align-items: center;
 
         .left-box {
-          width: 25%;
+          width: 20%;
           height: 100%;
           margin-right: 15px;
           padding: 10px;
@@ -510,7 +461,7 @@ watch(
         }
 
         .right-box {
-          width: calc(75% - 15px);
+          width: calc(80% - 15px);
           height: 100%;
           padding: 10px;
           box-sizing: border-box;
@@ -607,7 +558,7 @@ watch(
 
 .device-select-box {
   margin-top: 60px;
-  width: 208px;
+  width: 220px;
   height: calc(100% - 70px);
   overflow-y: auto;
   color: #fff;
@@ -645,7 +596,7 @@ watch(
   }
 }
 .device-select {
-  width: 250px;
+  width: 280px !important;
   height: calc(100% - 70px);
   background: var(--image-tree-bg) no-repeat;
   position: fixed;

+ 0 - 8
src/views/vent/dataCenter/stationCenter/device.api.ts

@@ -10,20 +10,12 @@ export const subStationList = (params) => defHttp.get({ url: Api.subStationList,
 //根据设备类型获取设备列表
 export const getDeviceListByType = (params) =>
   defHttp.post({
-    headers: {
-      'X-Access-Token':
-        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5ODQxMTcwfQ.fHaM3l-d88tUSLOs8sstXsDuSCn6Agbj_EMZMgV6waw',
-    },
     url: Api.deviceList,
     params,
   });
 //根据设备id获取设备监控数据
 export const getDevMonitorListById = (params) =>
   defHttp.post({
-    headers: {
-      'X-Access-Token':
-        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5ODQxMTcwfQ.fHaM3l-d88tUSLOs8sstXsDuSCn6Agbj_EMZMgV6waw',
-    },
     url: Api.devMonitorList,
     params,
   });

+ 84 - 79
src/views/vent/dataCenter/stationCenter/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="safetyList">
-    <customHeader>数据中心-分站管理</customHeader>
+    <customHeader>数据中心-分站监测</customHeader>
     <div class="content">
       <a-tabs class="tab-box" v-model:activeKey="activeKey" @change="onChangeTab">
         <a-tab-pane tab="分站监测" key="device" />
@@ -23,12 +23,12 @@
               size="small"
               :scroll="{ y: 650 }"
               :columns="outerColumns"
-              :data-source="paginatedData2"
-              :pagination="paginationConfig2"
+              :data-source="deviceList"
+              :pagination="false"
               :row-key="(record) => record.id"
               :expand-row-by-click="true"
-              :expanded-row-keys="expandedRowKeys"
-              @expand="onExpand"
+              :expandedRowKeys="expandedRowKeys"
+              @expand="handleExpand"
             >
               <!-- 自定义展开图标 -->
               <template #expandIcon="{ expanded, onExpand, record }">
@@ -52,8 +52,8 @@
                 <a-table
                   size="small"
                   :columns="innerColumns"
-                  :data-source="paginatedData"
-                  :pagination="paginationConfig"
+                  :data-source="monitorList"
+                  :pagination="false"
                   :loading="loadingMap[record.id]"
                   bordered
                   :scroll="{ y: 410 }"
@@ -93,70 +93,71 @@ const deviceList = ref<any[]>([]);
 const openNum = ref(0);
 const clsoeNum = ref(0);
 const monitorList = ref<any[]>([]); // 监测数据列表
-// 分页参数
-const paginationState = ref({
-  current: 1,
-  pageSize: 10,
-  total: 0,
-});
-const paginationState2 = ref({
-  current: 1,
-  pageSize: 10,
-  total: 0,
-});
-// 计算分页后的数据
-const paginatedData = computed(() => {
-  const start = (paginationState.value.current - 1) * paginationState.value.pageSize;
-  const end = start + paginationState.value.pageSize;
-  return monitorList.value.slice(start, end);
-});
-// 计算分页后的数据
-const paginatedData2 = computed(() => {
-  const start = (paginationState2.value.current - 1) * paginationState2.value.pageSize;
-  const end = start + paginationState2.value.pageSize;
-  return deviceList.value.slice(start, end);
-});
-// 分页器配置 - 修复响应式问题
-const paginationConfig = computed(() => {
-  return {
-    current: paginationState.value.current,
-    pageSize: paginationState.value.pageSize,
-    total: monitorList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState.value.current = page;
-      paginationState.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState.value.current = 1;
-      paginationState.value.pageSize = size;
-    },
-  };
-});
-const paginationConfig2 = computed(() => {
-  return {
-    current: paginationState2.value.current,
-    pageSize: paginationState2.value.pageSize,
-    total: deviceList.value.length,
-    showSizeChanger: true,
-    showQuickJumper: true,
-    showTotal: (total) => `共 ${total} 条`,
-    pageSizeOptions: ['10', '20', '50', '100'],
-    size: 'small',
-    onChange: (page, pageSize) => {
-      paginationState2.value.current = page;
-      paginationState2.value.pageSize = pageSize;
-    },
-    onShowSizeChange: (current, size) => {
-      paginationState2.value.current = 1;
-      paginationState2.value.pageSize = size;
-    },
-  };
-});
+const expandedRowKeys = ref([]);
+// // 分页参数
+// const paginationState = ref({
+//   current: 1,
+//   pageSize: 10,
+//   total: 0,
+// });
+// const paginationState2 = ref({
+//   current: 1,
+//   pageSize: 10,
+//   total: 0,
+// });
+// // 计算分页后的数据
+// const paginatedData = computed(() => {
+//   const start = (paginationState.value.current - 1) * paginationState.value.pageSize;
+//   const end = start + paginationState.value.pageSize;
+//   return monitorList.value.slice(start, end);
+// });
+// // 计算分页后的数据
+// const paginatedData2 = computed(() => {
+//   const start = (paginationState2.value.current - 1) * paginationState2.value.pageSize;
+//   const end = start + paginationState2.value.pageSize;
+//   return deviceList.value.slice(start, end);
+// });
+// // 分页器配置 - 修复响应式问题
+// const paginationConfig = computed(() => {
+//   return {
+//     current: paginationState.value.current,
+//     pageSize: paginationState.value.pageSize,
+//     total: monitorList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState.value.current = page;
+//       paginationState.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState.value.current = 1;
+//       paginationState.value.pageSize = size;
+//     },
+//   };
+// });
+// const paginationConfig2 = computed(() => {
+//   return {
+//     current: paginationState2.value.current,
+//     pageSize: paginationState2.value.pageSize,
+//     total: deviceList.value.length,
+//     showSizeChanger: true,
+//     showQuickJumper: true,
+//     showTotal: (total) => `共 ${total} 条`,
+//     pageSizeOptions: ['10', '20', '50', '100'],
+//     size: 'small',
+//     onChange: (page, pageSize) => {
+//       paginationState2.value.current = page;
+//       paginationState2.value.pageSize = pageSize;
+//     },
+//     onShowSizeChange: (current, size) => {
+//       paginationState2.value.current = 1;
+//       paginationState2.value.pageSize = size;
+//     },
+//   };
+// });
 //获取分站信息
 async function getSubStationList() {
   let res = await subStationList({ pageSize: 1000, pageNo: 1 });
@@ -178,8 +179,6 @@ function cardClick(item, ind) {
   activeIndex1.value = ind;
   getDeviceList(item.id);
 }
-// 当前展开的行key数组
-const expandedRowKeys = ref([]);
 
 // 加载状态映射
 const loadingMap = reactive({});
@@ -258,16 +257,22 @@ const innerColumns = [
 ];
 
 // 切换展开状态
-const toggleExpand = (deviceId) => {
-  const index = expandedRowKeys.value.indexOf(deviceId);
+const toggleExpand = (id) => {
+  const index = expandedRowKeys.value.indexOf(id);
 
   if (index > -1) {
-    // 如果已经展开,则关闭
     expandedRowKeys.value.splice(index, 1);
   } else {
-    // 如果未展开,则打开
-    expandedRowKeys.value.push(deviceId);
-    loadMonitoringData(deviceId);
+    expandedRowKeys.value = [id];
+    loadMonitoringData(id);
+  }
+};
+const handleExpand = (expanded, record) => {
+  // 确保展开状态与 expandedRowKeys 同步
+  if (expanded) {
+    expandedRowKeys.value = [record.id];
+  } else {
+    expandedRowKeys.value = [];
   }
 };
 // 加载监测数据

+ 42 - 0
src/views/vent/dataCenter/user/PasswordModal.vue

@@ -0,0 +1,42 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" title="修改密码" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" name="PassWordModal" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { formPasswordSchema } from './user.data';
+  import { changePassword } from './user.api';
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  //表单配置
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    schemas: formPasswordSchema,
+    showActionButtonGroup: false,
+  });
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    //表单赋值
+    await setFieldsValue({ ...data });
+  });
+  //表单提交事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await changePassword(values);
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>

+ 45 - 0
src/views/vent/dataCenter/user/UserAgentModal.vue

@@ -0,0 +1,45 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :width="800" title="用户代理" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, computed, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { formAgentSchema } from './user.data';
+  import { getUserAgent, saveOrUpdateAgent } from './user.api';
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  //表单配置
+  const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({
+    schemas: formAgentSchema,
+    showActionButtonGroup: false,
+  });
+  //表单赋值
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    //重置表单
+    await resetFields();
+    setModalProps({ confirmLoading: false });
+    //查询获取表单数据
+    const res = await getUserAgent({ userName: data.userName });
+    data = res.result ? res.result : data;
+    //表单赋值
+    await setFieldsValue({ ...data });
+  });
+  //表单提交事件
+  async function handleSubmit() {
+    try {
+      const values = await validate();
+      setModalProps({ confirmLoading: true });
+      //提交表单
+      await saveOrUpdateAgent(values);
+      //关闭弹窗
+      closeModal();
+      //刷新列表
+      emit('success');
+    } finally {
+      setModalProps({ confirmLoading: false });
+    }
+  }
+</script>

+ 136 - 0
src/views/vent/dataCenter/user/UserDrawer.vue

@@ -0,0 +1,136 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    :title="getTitle"
+    :width="adaptiveWidth"
+    @ok="handleSubmit"
+    :showFooter="showFooter"
+    destroyOnClose
+  >
+    <BasicForm @register="registerForm" />
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
+import { BasicForm, useForm } from '/@/components/Form/index';
+import { formSchema } from './user.data';
+import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+import { saveOrUpdateUser, getUserRoles, getUserDepartList } from './user.api';
+import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+const attrs = useAttrs();
+const isUpdate = ref(true);
+const rowId = ref('');
+const departOptions = ref([]);
+//表单配置
+const [registerForm, { setProps, resetFields, setFieldsValue, validate, updateSchema }] = useForm({
+  labelWidth: 90,
+  schemas: formSchema,
+  showActionButtonGroup: false,
+});
+// TODO [VUEN-527] https://www.teambition.com/task/6239beb894b358003fe93626
+const showFooter = ref(true);
+//表单赋值
+const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+  await resetFields();
+  showFooter.value = data?.showFooter ?? true;
+  setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
+  isUpdate.value = !!data?.isUpdate;
+  if (unref(isUpdate)) {
+    rowId.value = data.record.id;
+    //租户信息定义成数组
+    if (data.record.relTenantIds && !Array.isArray(data.record.relTenantIds)) {
+      data.record.relTenantIds = data.record.relTenantIds.split(',');
+    } else {
+      //【issues/I56C5I】用户管理中连续点两次编辑租户配置就丢失了
+      //data.record.relTenantIds = [];
+    }
+
+    //查角色/赋值/try catch 处理,不然编辑有问题
+    try {
+      const userRoles = await getUserRoles({ userid: data.record.id });
+      if (userRoles && userRoles.length > 0) {
+        data.record.selectedroles = userRoles;
+      }
+    } catch (error) {}
+
+    //查所属部门/赋值
+    const userDepart = await getUserDepartList({ userId: data.record.id });
+    if (userDepart && userDepart.length > 0) {
+      data.record.selecteddeparts = userDepart;
+      let selectDepartKeys = Array.from(userDepart, ({ key }) => key);
+      data.record.selecteddeparts = selectDepartKeys.join(',');
+      departOptions.value = userDepart.map((item) => {
+        return { label: item.title, value: item.key };
+      });
+    }
+    //负责部门/赋值
+    data.record.departIds && !Array.isArray(data.record.departIds) && (data.record.departIds = data.record.departIds.split(','));
+    //update-begin---author:zyf   Date:20211210  for:避免空值显示异常------------
+    data.record.departIds = data.record.departIds == '' ? [] : data.record.departIds;
+    //update-begin---author:zyf   Date:20211210  for:避免空值显示异常------------
+  }
+  //处理角色用户列表情况(和角色列表有关系)
+  data.selectedroles && (await setFieldsValue({ selectedroles: data.selectedroles }));
+  //编辑时隐藏密码/角色列表隐藏角色信息/我的部门时隐藏所属部门
+  updateSchema([
+    {
+      field: 'password',
+      show: !unref(isUpdate),
+    },
+    {
+      field: 'confirmPassword',
+      ifShow: !unref(isUpdate),
+    },
+    {
+      field: 'selectedroles',
+      show: !data.isRole,
+    },
+    {
+      field: 'departIds',
+      componentProps: { options: departOptions },
+    },
+    {
+      field: 'selecteddeparts',
+      show: !data?.departDisabled ?? false,
+    },
+    {
+      field: 'selectedroles',
+      show: !data?.departDisabled ?? false,
+    },
+  ]);
+  // 无论新增还是编辑,都可以设置表单值
+  if (typeof data.record === 'object') {
+    setFieldsValue({
+      ...data.record,
+    });
+  }
+  // 隐藏底部时禁用整个表单
+  //update-begin-author:taoyan date:2022-5-24 for: VUEN-1117【issue】0523周开源问题
+  setProps({ disabled: !showFooter.value });
+  //update-end-author:taoyan date:2022-5-24 for: VUEN-1117【issue】0523周开源问题
+});
+//获取标题
+const getTitle = computed(() => (!unref(isUpdate) ? '新增用户' : '编辑用户'));
+const { adaptiveWidth } = useDrawerAdaptiveWidth();
+
+//提交事件
+async function handleSubmit() {
+  try {
+    let values = await validate();
+    setDrawerProps({ confirmLoading: true });
+    values.userIdentity === 1 && (values.departIds = '');
+    let isUpdateVal = unref(isUpdate);
+    //提交表单
+    await saveOrUpdateUser(values, isUpdateVal);
+    //关闭弹窗
+    closeDrawer();
+    //刷新列表
+    emit('success', { isUpdateVal, values });
+  } finally {
+    setDrawerProps({ confirmLoading: false });
+  }
+}
+</script>

+ 77 - 0
src/views/vent/dataCenter/user/UserPermissionDrawer.vue

@@ -0,0 +1,77 @@
+<template>
+  <BasicDrawer
+    v-bind="$attrs"
+    @register="registerDrawer"
+    title="API授权"
+    :width="adaptiveWidth"
+    @ok="handleSubmit"
+    :showFooter="showFooter"
+    destroyOnClose
+  >
+    <BasicTree
+      :checkedKeys="checkedKeys"
+      :expandedKeys="expandedKeys"
+      :selectedKeys="selectedKeys"
+      checkable
+      :tree-data="treeData"
+      ref="treeRef"
+      :clickRowToExpand="false"
+      @check="onCheck"
+      @select="onTreeNodeSelect"
+    >
+      <template #title="{ title, key }">
+        <span v-if="key === '0-0-1-0'" style="color: #1890ff">{{ title }}</span>
+        <template v-else>{{ title }}</template>
+      </template>
+    </BasicTree>
+  </BasicDrawer>
+</template>
+<script lang="ts" setup>
+import { defineComponent, ref, computed, unref, useAttrs } from 'vue';
+import { BasicTree, TreeItem } from '/src/components/Tree';
+import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+import { getAPILIst } from './user.api';
+import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+const showFooter = ref(true);
+const selectedKeys = ref([]);
+const expandedKeys = ref([]);
+const checkedKeys = ref([]);
+const treeData = [
+  {
+    title: 'parent 1',
+    key: '0-0',
+  },
+  {
+    title: '测试api',
+    key: '0-1',
+  },
+  {
+    title: '测试api1',
+    key: '0-2',
+  },
+  {
+    title: '测试api2',
+    key: '0-3',
+  },
+];
+//表单赋值
+const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+  showFooter.value = data?.showFooter ?? true;
+  setDrawerProps({ confirmLoading: false, showFooter: showFooter.value });
+});
+const { adaptiveWidth } = useDrawerAdaptiveWidth();
+
+//提交事件
+async function handleSubmit() {
+  try {
+    setDrawerProps({ confirmLoading: true });
+    closeDrawer();
+    //刷新列表
+    emit('success');
+  } finally {
+    setDrawerProps({ confirmLoading: false });
+  }
+}
+</script>

+ 138 - 0
src/views/vent/dataCenter/user/UserRecycleBinModal.vue

@@ -0,0 +1,138 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" title="用户回收站" :showOkBtn="false" width="1000px" destroyOnClose>
+    <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <!--插槽:table标题-->
+      <template #tableTitle>
+        <a-dropdown v-if="checkedKeys.length > 0">
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="batchHandleDelete">
+                <Icon icon="ant-design:delete-outlined"></Icon>
+                批量删除
+              </a-menu-item>
+              <a-menu-item key="1" @click="batchHandleRevert">
+                <Icon icon="ant-design:redo-outlined"></Icon>
+                批量还原
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <a-button
+            >批量操作
+            <Icon icon="ant-design:down-outlined"></Icon>
+          </a-button>
+        </a-dropdown>
+      </template>
+      <!--操作栏-->
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction :actions="getTableAction(record)" />
+        </template>
+      </template>
+    </BasicTable>
+  </BasicModal>
+</template>
+<script lang="ts" setup>
+  import { ref, toRaw, unref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicTable, useTable, TableAction } from '/@/components/Table';
+  import { recycleColumns } from './user.data';
+  import { getRecycleBinList, putRecycleBin, deleteRecycleBin } from './user.api';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { createConfirm } = useMessage();
+  // 声明Emits
+  const emit = defineEmits(['success', 'register']);
+  const checkedKeys = ref<Array<string | number>>([]);
+  const [registerModal] = useModalInner(() => {
+    checkedKeys.value = [];
+  });
+  //注册table数据
+  const [registerTable, { reload }] = useTable({
+    api: getRecycleBinList,
+    columns: recycleColumns,
+    rowKey: 'id',
+    striped: true,
+    useSearchForm: false,
+    showTableSetting: false,
+    clickToRowSelect: false,
+    bordered: true,
+    showIndexColumn: false,
+    pagination: true,
+    tableSetting: { fullScreen: true },
+    canResize: false,
+    actionColumn: {
+      width: 150,
+      title: '操作',
+      dataIndex: 'action',
+      // slots: { customRender: 'action' },
+      fixed: undefined,
+    },
+  });
+  /**
+   * 选择列配置
+   */
+  const rowSelection = {
+    type: 'checkbox',
+    columnWidth: 50,
+    selectedRowKeys: checkedKeys,
+    onChange: onSelectChange,
+  };
+  /**
+   * 选择事件
+   */
+  function onSelectChange(selectedRowKeys: (string | number)[]) {
+    checkedKeys.value = selectedRowKeys;
+  }
+  /**
+   * 还原事件
+   */
+  async function handleRevert(record) {
+    await putRecycleBin({ userIds: record.id }, reload);
+    emit('success');
+  }
+  /**
+   * 批量还原事件
+   */
+  function batchHandleRevert() {
+    handleRevert({ id: toRaw(unref(checkedKeys)).join(',') });
+  }
+  /**
+   * 删除事件
+   */
+  async function handleDelete(record) {
+    await deleteRecycleBin({ userIds: record.id }, reload);
+  }
+  /**
+   * 批量删除事件
+   */
+  function batchHandleDelete() {
+    createConfirm({
+      iconType: 'warning',
+      title: '删除',
+      content: '确定要永久删除吗?删除后将不可恢复!',
+      onOk: () => handleDelete({ id: toRaw(unref(checkedKeys)).join(',') }),
+      onCancel() {},
+    });
+  }
+  //获取操作栏事件
+  function getTableAction(record) {
+    return [
+      {
+        label: '取回',
+        icon: 'ant-design:redo-outlined',
+        popConfirm: {
+          title: '是否确认还原',
+          confirm: handleRevert.bind(null, record),
+        },
+      },
+      {
+        label: '彻底删除',
+        icon: 'ant-design:scissor-outlined',
+        popConfirm: {
+          title: '是否确认删除',
+          confirm: handleDelete.bind(null, record),
+        },
+      },
+    ];
+  }
+</script>

+ 253 - 0
src/views/vent/dataCenter/user/index.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="device-manager-box">
+    <!--引用表格-->
+    <BasicTable @register="registerTable">
+      <!--插槽:table标题-->
+      <template #tableTitle>
+        <a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
+        <!-- <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
+        <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button> -->
+        <!-- <a-button type="primary" @click="handleSyncUser" preIcon="ant-design:sync-outlined"> 同步流程</a-button> -->
+        <!-- <a-button type="primary" @click="openModal(true, {})" preIcon="ant-design:hdd-outlined"> 回收站</a-button> -->
+        <JThirdAppButton biz-type="user" :selected-row-keys="selectedRowKeys" syncToApp syncToLocal @sync-finally="onSyncFinally" />
+        <a-dropdown v-if="selectedRowKeys.length > 0">
+          <template #overlay>
+            <a-menu>
+              <a-menu-item key="1" @click="batchHandleDelete">
+                <Icon icon="ant-design:delete-outlined" />
+                删除
+              </a-menu-item>
+            </a-menu>
+          </template>
+          <a-button
+            >批量操作
+            <Icon icon="mdi:chevron-down" />
+          </a-button>
+        </a-dropdown>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" :dropDownActions="getDropDownAction(record)" />
+      </template>
+    </BasicTable>
+    <!--用户抽屉-->
+    <UserDrawer @register="registerDrawer" @success="handleSuccess" />
+    <!--修改密码-->
+    <PasswordModal @register="registerPasswordModal" @success="reload" />
+    <!--用户代理-->
+    <UserAgentModal @register="registerAgentModal" @success="reload" />
+    <!--回收站-->
+    <UserPermissionDrawer @register="registerPermissionModal" @success="reload" />
+  </div>
+</template>
+
+<script lang="ts" name="system-user" setup>
+//ts语法
+import { ref, computed, unref } from 'vue';
+import { BasicTable, TableAction, ActionItem } from '/@/components/Table';
+import UserDrawer from './UserDrawer.vue';
+// import UserRecycleBinModal from './UserRecycleBinModal.vue';
+import PasswordModal from './PasswordModal.vue';
+import UserAgentModal from './UserAgentModal.vue';
+import JThirdAppButton from '/@/components/jeecg/thirdApp/JThirdAppButton.vue';
+import { useDrawer } from '/@/components/Drawer';
+import { useListPage } from '/@/hooks/system/useListPage';
+import { useModal } from '/@/components/Modal';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { columns, searchFormSchema } from './user.data';
+import { list, deleteUser, batchDeleteUser, frozenBatch, syncUser } from './user.api';
+import UserPermissionDrawer from './UserPermissionDrawer.vue';
+// import { usePermission } from '/@/hooks/web/usePermission'
+// const { hasPermission } = usePermission();
+
+const { createMessage, createConfirm } = useMessage();
+
+//注册drawer
+const [registerDrawer, { openDrawer }] = useDrawer();
+//回收站model
+// const [registerModal, { openModal }] = useModal();
+//密码model
+const [registerPasswordModal, { openModal: openPasswordModal }] = useModal();
+//代理人model
+const [registerAgentModal, { openModal: openAgentModal }] = useModal();
+// 授权model
+const [registerPermissionModal, { openDrawer: openPermissionModal }] = useDrawer();
+
+// 列表页面公共参数、方法
+const { prefixCls, tableContext } = useListPage({
+  designScope: 'user-list',
+  tableProps: {
+    title: '用户列表',
+    api: list,
+    columns: columns,
+    size: 'small',
+    formConfig: {
+      // labelWidth: 200,
+      schemas: searchFormSchema,
+    },
+    actionColumn: {
+      width: 120,
+    },
+    beforeFetch(params) {
+      return Object.assign(
+        {
+          column: 'createTime',
+          order: 'desc',
+          thirdType: '_thirdPartyUser',
+        },
+        params
+      );
+    },
+  },
+  // exportConfig: {
+  //   name: '用户列表',
+  //   url: getExportUrl,
+  // },
+  // importConfig: {
+  //   url: getImportUrl,
+  // },
+});
+
+//注册table数据
+const [registerTable, { reload }, { selectedRows, selectedRowKeys }] = tableContext;
+
+/**
+ * 新增事件
+ */
+function handleCreate() {
+  openDrawer(true, {
+    isUpdate: false,
+    showFooter: true,
+  });
+}
+/**
+ * 编辑事件
+ */
+async function handleEdit(record: Recordable) {
+  openDrawer(true, {
+    record,
+    isUpdate: true,
+    showFooter: true,
+  });
+}
+/**
+ * 详情
+ */
+async function handleDetail(record: Recordable) {
+  openDrawer(true, {
+    record,
+    isUpdate: true,
+    showFooter: false,
+  });
+}
+/**
+ * 删除事件
+ */
+async function handleDelete(record) {
+  if ('admin' == record.username) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
+  }
+  await deleteUser({ id: record.id }, reload);
+}
+/**
+ * 批量删除事件
+ */
+async function batchHandleDelete() {
+  let hasAdmin = unref(selectedRows).filter((item) => item.username == 'admin');
+  if (unref(hasAdmin).length > 0) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
+  }
+  await batchDeleteUser({ ids: selectedRowKeys.value }, () => {
+    selectedRowKeys.value = [];
+    reload();
+  });
+}
+/**
+ * 成功回调
+ */
+function handleSuccess() {
+  reload();
+}
+
+/**
+ * 打开修改密码弹窗
+ */
+function handleChangePassword(username) {
+  openPasswordModal(true, { username });
+}
+/**
+ * 打开代理人弹窗
+ */
+function handleAgentSettings(userName) {
+  openAgentModal(true, { userName });
+}
+/**
+ * 打开授权弹窗
+ */
+function handlePermissionModel() {
+  openPermissionModal(true);
+}
+
+/**
+ * 冻结解冻
+ */
+async function handleFrozen(record, status) {
+  if ('admin' == record.username) {
+    createMessage.warning('管理员账号不允许此操作!');
+    return;
+  }
+  await frozenBatch({ ids: record.id, status: status }, reload);
+}
+/**
+ *同步钉钉和微信回调
+ */
+function onSyncFinally({ isToLocal }) {
+  // 同步到本地时刷新下数据
+  if (isToLocal) {
+    reload();
+  }
+}
+/**
+ * 操作栏
+ */
+function getTableAction(record): ActionItem[] {
+  return [
+    {
+      label: '编辑',
+      onClick: handleEdit.bind(null, record),
+      // ifShow: () => hasPermission('system:user:edit'),
+    },
+    {
+      label: '授权',
+      onClick: handlePermissionModel.bind(null),
+      // ifShow: () => hasPermission('system:user:edit'),
+    },
+  ];
+}
+/**
+ * 下拉操作栏
+ */
+function getDropDownAction(record): ActionItem[] {
+  return [
+    {
+      label: '详情',
+      onClick: handleDetail.bind(null, record),
+    },
+    {
+      label: '密码',
+      onClick: handleChangePassword.bind(null, record.username),
+    },
+    {
+      label: '删除',
+      popConfirm: {
+        title: '是否确认删除',
+        confirm: handleDelete.bind(null, record),
+      },
+    },
+  ];
+}
+</script>
+
+<style scoped></style>

+ 177 - 0
src/views/vent/dataCenter/user/user.api.ts

@@ -0,0 +1,177 @@
+import { defHttp } from '/@/utils/http/axios';
+import { Modal } from 'ant-design-vue';
+
+enum Api {
+  list = '/sys/user/list',
+  save = '/sys/user/add',
+  edit = '/sys/user/edit',
+  agentSave = '/sys/sysUserAgent/add',
+  agentEdit = '/sys/sysUserAgent/edit',
+  getUserRole = '/sys/user/queryUserRole',
+  duplicateCheck = '/sys/duplicate/check',
+  deleteUser = '/sys/user/delete',
+  deleteBatch = '/sys/user/deleteBatch',
+  // importExcel = '/sys/user/importExcel',
+  // exportXls = '/sys/user/exportXls',
+  recycleBinList = '/sys/user/recycleBin',
+  putRecycleBin = '/sys/user/putRecycleBin',
+  deleteRecycleBin = '/sys/user/deleteRecycleBin',
+  allRolesList = '/sys/role/queryall',
+  allTenantList = '/sys/tenant/queryList',
+  allPostList = '/sys/position/list',
+  userDepartList = '/sys/user/userDepartList',
+  changePassword = '/sys/user/changePassword',
+  frozenBatch = '/sys/user/frozenBatch',
+  getUserAgent = '/sys/sysUserAgent/queryByUserName',
+  syncUser = '/act/process/extActProcess/doSyncUser',
+  allAPIList = '/compute/apiManage/list',
+}
+
+/**
+ * 查询api列表
+ * @param params
+ */
+export const getAPILIst = (params) => {
+  defHttp.get({
+    url: Api.allAPIList,
+    params,
+  });
+};
+/**
+ * 列表接口
+ * @param params
+ */
+export const list = (params) =>
+  defHttp.get({
+    url: Api.list,
+    params,
+  });
+
+/**
+ * 用户角色接口
+ * @param params
+ */
+export const getUserRoles = (params) => defHttp.get({ url: Api.getUserRole, params }, { errorMessageMode: 'none' });
+
+/**
+ * 删除用户
+ */
+export const deleteUser = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteUser, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 批量删除用户
+ * @param params
+ */
+export const batchDeleteUser = (params, handleSuccess) => {
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否删除选中数据',
+    okText: '确认',
+    cancelText: '取消',
+    onOk: () => {
+      return defHttp.delete({ url: Api.deleteBatch, data: params }, { joinParamsToUrl: true }).then(() => {
+        handleSuccess();
+      });
+    },
+  });
+};
+/**
+ * 保存或者更新用户
+ * @param params
+ */
+export const saveOrUpdateUser = (params, isUpdate) => {
+  const url = isUpdate ? Api.edit : Api.save;
+  return defHttp.post({
+    url: url,
+    params,
+  });
+};
+/**
+ * 唯一校验
+ * @param params
+ */
+export const duplicateCheck = (params) => defHttp.get({ url: Api.duplicateCheck, params }, { isTransformResponse: false });
+/**
+ * 获取全部角色
+ * @param params
+ */
+export const getAllRolesList = (params) => defHttp.get({ url: Api.allRolesList, params });
+/**
+ * 获取全部租户
+ */
+export const getAllTenantList = (params) => defHttp.get({ url: Api.allTenantList, params });
+/**
+ * 获取指定用户负责部门
+ */
+export const getUserDepartList = (params) => defHttp.get({ url: Api.userDepartList, params }, { successMessageMode: 'none' });
+/**
+ * 获取全部职务
+ */
+export const getAllPostList = (params) => {
+  return new Promise((resolve) => {
+    defHttp.get({ url: Api.allPostList, params }).then((res) => {
+      resolve(res.records);
+    });
+  });
+};
+/**
+ * 回收站列表
+ * @param params
+ */
+export const getRecycleBinList = (params) => defHttp.get({ url: Api.recycleBinList, params });
+/**
+ * 回收站还原
+ * @param params
+ */
+export const putRecycleBin = (params, handleSuccess) => {
+  return defHttp.put({ url: Api.putRecycleBin, params }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 回收站删除
+ * @param params
+ */
+export const deleteRecycleBin = (params, handleSuccess) => {
+  return defHttp.delete({ url: Api.deleteRecycleBin, params }, { joinParamsToUrl: true }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 修改密码
+ * @param params
+ */
+export const changePassword = (params) => {
+  return defHttp.put({ url: Api.changePassword, params });
+};
+/**
+ * 冻结解冻
+ * @param params
+ */
+export const frozenBatch = (params, handleSuccess) => {
+  return defHttp.put({ url: Api.frozenBatch, params }).then(() => {
+    handleSuccess();
+  });
+};
+/**
+ * 获取用户代理
+ * @param params
+ */
+export const getUserAgent = (params) => defHttp.get({ url: Api.getUserAgent, params }, { isTransformResponse: false });
+/**
+ * 保存或者更新用户代理
+ * @param params
+ */
+export const saveOrUpdateAgent = (params) => {
+  const url = params.id ? Api.agentEdit : Api.agentSave;
+  return defHttp.post({ url: url, params });
+};
+
+/**
+ * 用户同步流程
+ * @param params
+ */
+export const syncUser = () => defHttp.put({ url: Api.syncUser });

+ 246 - 0
src/views/vent/dataCenter/user/user.data.ts

@@ -0,0 +1,246 @@
+import { BasicColumn } from '/@/components/Table';
+import { FormSchema } from '/@/components/Table';
+import { getAllRolesList, getAllTenantList } from './user.api';
+import { rules } from '/@/utils/helper/validator';
+import { render } from '/@/utils/common/renderUtils';
+
+export const columns: BasicColumn[] = [
+  {
+    title: '用户账号',
+    dataIndex: 'username',
+    width: 80,
+  },
+  {
+    title: '用户姓名',
+    dataIndex: 'realname',
+    width: 80,
+  },
+  {
+    title: '令牌',
+    dataIndex: 'thirdId',
+    width: 150,
+  },
+  {
+    title: '有效期',
+    dataIndex: 'birthday',
+    width: 80,
+  },
+  {
+    title: '状态',
+    dataIndex: 'status_dictText',
+    width: 80,
+  },
+];
+
+export const recycleColumns: BasicColumn[] = [
+  {
+    title: '用户账号',
+    dataIndex: 'username',
+    width: 100,
+  },
+  {
+    title: '用户姓名',
+    dataIndex: 'realname',
+    width: 100,
+  },
+  {
+    title: '头像',
+    dataIndex: 'avatar',
+    width: 80,
+    customRender: render.renderAvatar,
+  },
+  {
+    title: '性别',
+    dataIndex: 'sex',
+    width: 80,
+    sorter: true,
+    customRender: ({ text }) => {
+      return render.renderDict(text, 'sex');
+    },
+  },
+];
+
+export const searchFormSchema: FormSchema[] = [
+  {
+    label: '账号',
+    field: 'username',
+    component: 'JInput',
+    colProps: { span: 6 },
+  },
+  {
+    label: '名字',
+    field: 'realname',
+    component: 'JInput',
+    colProps: { span: 6 },
+  },
+  // {
+  //   label: '性别',
+  //   field: 'sex',
+  //   component: 'JDictSelectTag',
+  //   componentProps: {
+  //     dictCode: 'sex',
+  //     placeholder: '请选择性别',
+  //     stringToNumber: true,
+  //   },
+  //   colProps: { span: 6 },
+  // },
+  // {
+  //   label: '手机号码',
+  //   field: 'phone',
+  //   component: 'Input',
+  //   colProps: { span: 6 },
+  // },
+  // {
+  //   label: '用户状态',
+  //   field: 'status',
+  //   component: 'JDictSelectTag',
+  //   componentProps: {
+  //     dictCode: 'user_status',
+  //     placeholder: '请选择状态',
+  //     stringToNumber: true,
+  //   },
+  //   colProps: { span: 6 },
+  // },
+];
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    label: '用户账号',
+    field: 'username',
+    component: 'Input',
+    dynamicDisabled: ({ values }) => {
+      return !!values.id;
+    },
+    dynamicRules: ({ model, schema }) => rules.duplicateCheckRule('sys_user', 'username', model, schema, true),
+  },
+  {
+    label: '登录密码',
+    field: 'password',
+    component: 'StrengthMeter',
+    rules: [
+      {
+        required: true,
+        message: '请输入登录密码',
+      },
+    ],
+  },
+  {
+    label: '确认密码',
+    field: 'confirmPassword',
+    component: 'InputPassword',
+    dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  },
+  {
+    label: '用户姓名',
+    field: 'realname',
+    required: true,
+    component: 'Input',
+  },
+  {
+    label: '有效期',
+    field: 'birthday',
+    component: 'DatePicker',
+  },
+  {
+    label: '第三方用户',
+    field: 'thirdType',
+    component: 'Input',
+    defaultValue: '_thirdPartyUser',
+  },
+];
+
+export const formPasswordSchema: FormSchema[] = [
+  {
+    label: '用户账号',
+    field: 'username',
+    component: 'Input',
+    componentProps: { readOnly: true },
+  },
+  {
+    label: '登录密码',
+    field: 'password',
+    component: 'StrengthMeter',
+    componentProps: {
+      placeholder: '请输入登录密码',
+    },
+    rules: [
+      {
+        required: true,
+        message: '请输入登录密码',
+      },
+    ],
+  },
+  {
+    label: '确认密码',
+    field: 'confirmPassword',
+    component: 'InputPassword',
+    dynamicRules: ({ values }) => rules.confirmPassword(values, true),
+  },
+];
+
+export const formAgentSchema: FormSchema[] = [
+  {
+    label: '',
+    field: 'id',
+    component: 'Input',
+    show: false,
+  },
+  {
+    field: 'userName',
+    label: '用户名',
+    component: 'Input',
+    componentProps: {
+      readOnly: true,
+      allowClear: false,
+    },
+  },
+  {
+    field: 'agentUserName',
+    label: '代理人用户名',
+    required: true,
+    component: 'JSelectUser',
+    componentProps: {
+      rowKey: 'username',
+      labelKey: 'realname',
+      maxSelectCount: 10,
+    },
+  },
+  {
+    field: 'startTime',
+    label: '代理开始时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理开始时间',
+    },
+  },
+  {
+    field: 'endTime',
+    label: '代理结束时间',
+    component: 'DatePicker',
+    required: true,
+    componentProps: {
+      showTime: true,
+      valueFormat: 'YYYY-MM-DD HH:mm:ss',
+      placeholder: '请选择代理结束时间',
+    },
+  },
+  {
+    field: 'status',
+    label: '状态',
+    component: 'JDictSelectTag',
+    defaultValue: '1',
+    componentProps: {
+      dictCode: 'valid_status',
+      type: 'radioButton',
+    },
+  },
+];

+ 54 - 0
src/views/vent/dataCenter/user/userDetails.vue

@@ -0,0 +1,54 @@
+<template>
+  <Description @register="register" class="mt-4" />
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Description, DescItem, useDescription } from '/@/components/Description/index';
+  const mockData = {
+    username: 'test',
+    nickName: 'VB',
+    age: '123',
+    phone: '15695909xxx',
+    email: '190848757@qq.com',
+    addr: '厦门市思明区',
+    sex: '男',
+    certy: '3504256199xxxxxxxxx',
+    tag: 'orange',
+  };
+  const schema: DescItem[] = [
+    {
+      field: 'username',
+      label: '用户名',
+    },
+    {
+      field: 'nickName',
+      label: '昵称',
+      render: (curVal, data) => {
+        return `${data.username}-${curVal}`;
+      },
+    },
+    {
+      field: 'phone',
+      label: '联系电话',
+    },
+    {
+      field: 'email',
+      label: '邮箱',
+    },
+    {
+      field: 'addr',
+      label: '地址',
+    },
+  ];
+  export default defineComponent({
+    components: { Description },
+    setup() {
+      const [register] = useDescription({
+        title: 'useDescription',
+        data: mockData,
+        schema: schema,
+      });
+      return { mockData, schema, register };
+    },
+  });
+</script>