Преглед изворни кода

Merge remote-tracking branch 'origin/master'

liuc пре 5 месеци
родитељ
комит
701f5278e0

+ 5 - 0
src/api/support/data-source-api.js

@@ -0,0 +1,5 @@
+import { getRequest ,postRequest} from '/src/lib/axios';
+
+export const dataSourceApi = ()=>{
+    
+}

+ 1 - 1
src/api/support/job-api.js

@@ -41,6 +41,6 @@ export const jobApi = {
   },
   // 定时任务-删除任务 
   deleteJob: (param) => {
-    return postRequest('/support/job/delete', param);
+    return getRequest('/support/job/delete', param);
   },
 };

+ 8 - 0
src/api/system/table-api.js

@@ -7,3 +7,11 @@ export const getTableDataApi = (url, data) => {
     data,
   });
 };
+
+export const queryListByParams = (data) => {
+  return request({
+    url: '/supports/framework/query',
+    method: 'post',
+    data,
+  });
+};

+ 53 - 0
src/components/BsUi/OrgUserSelector/DEMO.vue

@@ -0,0 +1,53 @@
+<template>
+  <div>
+    <a-form name="form">
+      <a-form-item label="组织人员单选选择器" name="userSelector">
+        <OrgUserSelector  v-model:selected-data="userSelector" :multiple="SELECT_MULTIPLE.ONE" />
+      </a-form-item>
+
+      <a-form-item label="组织人员多选选择器" name="userSelectors">
+        <OrgUserSelector v-model:selected-data="userSelectors" :multiple="SELECT_MULTIPLE.MORE" />
+      </a-form-item>
+
+    </a-form>
+  </div>
+</template>
+
+<script setup>
+import OrgUserSelector from '/@/components/BsUi/OrgUserSelector/index.vue';
+import { ref } from 'vue';
+import { SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+const userSelectors = ref([
+  {
+    name: '韩晓辉0',
+    id: 'eqwifjqiwejf',
+    parentName: '无限畅联',
+    parentId: '1',
+    nodeType: "USER"
+  },
+  {
+    name: '韩晓辉1',
+    id: 'fqioweoiq',
+    parentName: '无限畅联',
+    parentId: '1',
+    nodeType: "USER"
+  },
+  {
+    name: '韩晓辉55',
+    id: 'jfslkf',
+    parentName: '无限畅联',
+    parentId: '1',
+    nodeType: "USER"
+  },
+]);
+
+const userSelector = ref({
+  name: '韩晓辉1',
+  id: '1',
+  parentName: '无限畅联',
+  parentId: '1',
+  nodeType: "USER"
+});
+
+
+</script>

+ 205 - 0
src/components/BsUi/OrgUserSelector/components/ModalSelector.vue

@@ -0,0 +1,205 @@
+<template>
+  <bs-modal
+    :visible="modalOptions.visible"
+    :width="modalOptions.width"
+    :title="modalOptions.title"
+    :modal-extra-props="modalOptions.modalExtraProps"
+    @cancel="handleCancel"
+    @ok="handleOk"
+  >
+    <div class="content">
+      <a-input type="text" placeholder="请输入关键字" v-model:value="keyWord" :allow-clear="true" />
+      <div class="content-bottom">
+        <div class="cb-content">
+          <div class="cb-c-top">
+            <div class="tree" v-show="isEmpty(keyWord)">
+              <org-tree ref="treeRef"  />
+            </div>
+            <org-user-list
+              :keyWord="keyWord"
+              ref="orgUserListRef"
+              :multiple="multiple"
+              :sceneType="sceneType"
+              :idKey="idKey"
+              :label-key="labelKey"
+              :oldSelectData="selectedOldData"
+              @change="handleSelectChange"
+              :selected-tree-id="selectedTreeId"
+              :selected-data="selectedData"
+            />
+          </div>
+
+          <div class="cb-c-bottom" v-if="orgUserListRef">
+            <selected-data
+              :idKey="idKey"
+              :labelKey="labelKey"
+              :selected-data="orgUserListRef.allSelectedData"
+              :selected-keys="orgUserListRef.allSelectedKeys"
+              :multiple="multiple"
+              @reset="handleReset"
+              @remove="handleRemove"
+            />
+          </div>
+        </div>
+      </div>
+    </div>
+  </bs-modal>
+</template>
+<script setup lang="jsx">
+  import BsModal, { useBsModal } from '/@/components/BsUi/Modal/index.js';
+  import OrgTree from '/@/components/BsUi/OrgUserSelector/components/OrgTree.vue';
+  import OrgUserList from '/@/components/BsUi/OrgUserSelector/components/OrgUserList.vue';
+  import SelectedData from '/@/components/BsUi/OrgUserSelector/components/SelectedData.vue';
+  import {computed, nextTick, onMounted, ref, watch} from 'vue';
+  import { isEmpty } from 'lodash';
+  import {SCENE_TYPE, SELECT_MULTIPLE} from '/@/components/BsUi/constant.js';
+
+  const treeRef = ref(null);
+  const emit = defineEmits(['change', 'ok']);
+
+  const props = defineProps({
+    sceneType: {
+      required: false,
+      default: SCENE_TYPE.USER,
+    },
+    multiple: {
+      required: false,
+      default: SELECT_MULTIPLE.MORE,
+    },
+    idKey: {
+      required: false,
+      default: 'id',
+    },
+    selectedOldData: {
+      required: false,
+      default: [],
+    },
+    labelKey: {
+      required: false,
+      default: 'name',
+    },
+    selectedData: {
+      required: true,
+      default: undefined
+    }
+  });
+
+  const {
+    modalOptions,
+    getModalPropsValue: getMVal,
+    setModalPropsValue: setMVal,
+  } = useBsModal({
+    modalOptions: {
+      width: '70%',
+      title: '标题',
+      visible: false,
+      modalExtraProps: {
+        destroyOnClose: true,
+        okButtonProps: {
+          loading: false,
+        },
+        bodyStyle: {
+          height: '100%',
+        },
+      },
+    },
+  });
+
+  const keyWord = ref('');
+  const orgUserListRef = ref(null);
+
+
+  const handleRemove = (node) => {
+    orgUserListRef.value.handleRemove(node);
+  };
+
+  const handleSelectChange = ($event) => {
+    const { selectedKeys } = $event;
+    // setMVal('modalExtraProps.okButtonProps.disabled', selectedKeys.length === 0);
+    emit('change', $event);
+  };
+
+  // 当前选中的树节点
+  const selectedTreeId = computed(() => {
+    if (treeRef.value) {
+      let selectedKeys = treeRef.value?.selectedKeys;
+      return isEmpty(selectedKeys) ? null : selectedKeys[0];
+    }
+    return null;
+  });
+
+  const showModal = () => {
+    setMVal('width', '1200px');
+    setMVal('title', '人员');
+    setMVal('visible', true);
+    // setMVal('modalExtraProps.okButtonProps.disabled', true);
+
+    nextTick(() => {
+      keyWord.value = "";
+    });
+  };
+
+  const handleCancel = () => {
+    setMVal('visible', false);
+  };
+
+  const handleOk = () => {
+    const selectedKeys = orgUserListRef.value.allSelectedKeys;
+    const selectedData = orgUserListRef.value.allSelectedData;
+    emit('ok', { selectedKeys, selectedData });
+    setMVal('visible', false);
+  };
+
+  const handleReset = () => {
+    orgUserListRef.value?.handleReset();
+  };
+
+  // 监听关键字的变化
+  watch(keyWord, () => {});
+
+  watch(
+    () => props.selectedOldData,
+    (val) => {}
+  );
+
+  defineExpose({
+    showModal,
+    orgUserListRef,
+  });
+</script>
+
+<style lang="scss" scoped>
+  .content {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+
+    .content-bottom {
+      width: 100%;
+      display: flex;
+      height: 100%;
+
+      .cb-content {
+        width: 100%;
+        height: 100%;
+        .cb-c-top {
+          width: 100%;
+          height: 500px;
+          display: flex;
+          gap: 20px;
+
+          .tree {
+            width: 300px;
+          }
+        }
+
+        .cb-c-bottom {
+          width: 100%;
+          border-top: 1px solid rgba(0, 0, 0, 0.1);
+          padding: 10px;
+        }
+      }
+    }
+  }
+</style>

+ 251 - 0
src/components/BsUi/OrgUserSelector/components/OrgTree.vue

@@ -0,0 +1,251 @@
+<!--
+  * 部门树形结构
+  *
+  * @Author:    DCCloud
+  * @Date:      2022-08-08 20:46:18
+-->
+<template>
+  <div class="tree-container">
+    <a-row class="smart-margin-bottom10">
+      <a-input v-model:value.trim="keywords" placeholder="请输入部门名称" />
+    </a-row>
+    <a-tree
+      v-if="!_.isEmpty(departmentTreeData)"
+      v-model:selectedKeys="selectedKeys"
+      v-model:checkedKeys="checkedKeys"
+      class="tree"
+      :treeData="departmentTreeData"
+      :fieldNames="{ title: 'name', key: 'departmentId', value: 'departmentId' }"
+      style="width: 100%; overflow-x: auto"
+      :style="[!height ? '' : { height: `${height}px`, overflowY: 'auto' }]"
+      :checkable="props.checkable"
+      :checkStrictly="props.checkStrictly"
+      :selectable="!props.checkable"
+      :defaultExpandAll="true"
+      @select="treeSelectChange"
+    >
+      <template #title="item">
+        <div>{{ item.name }}</div>
+      </template>
+    </a-tree>
+    <div class="no-data" v-else>暂无结果</div>
+  </div>
+</template>
+<script setup>
+  import { onMounted, onUnmounted, ref, watch } from 'vue';
+  import _ from 'lodash';
+  import { departmentApi } from '/@/api/system/department-api';
+  import mitt from 'mitt';
+
+  const departmentEmitter= mitt();
+
+  const DEPARTMENT_PARENT_ID = 0;
+
+  // ----------------------- 组件参数 ---------------------
+
+  const props = defineProps({
+    // 是否可以选中
+    checkable: {
+      type: Boolean,
+      default: false,
+    },
+    // 父子节点选中状态不再关联
+    checkStrictly: {
+      type: Boolean,
+      default: false,
+    },
+    // 树高度 超出出滚动条
+    height: Number,
+    // 显示菜单
+    showMenu: {
+      type: Boolean,
+      default: false,
+    },
+  });
+
+  // ----------------------- 部门树的展示 ---------------------
+  const topDepartmentId = ref();
+  // 所有部门列表
+  const departmentList = ref([]);
+  // 部门树形数据
+  const departmentTreeData = ref([]);
+  // 存放部门id和部门,用于查找
+  const idInfoMap = ref(new Map());
+
+  onMounted(() => {
+    queryDepartmentTree();
+  });
+
+  // 刷新
+  async function refresh() {
+    await queryDepartmentTree();
+    if (currentSelectedDepartmentId.value) {
+      selectTree(currentSelectedDepartmentId.value);
+    }
+  }
+
+  // 查询部门列表并构建 部门树
+  async function queryDepartmentTree() {
+    let res = await departmentApi.queryAllDepartment();
+    let data = res.data;
+    departmentList.value = data;
+    departmentTreeData.value = buildDepartmentTree(data, DEPARTMENT_PARENT_ID);
+
+    data.forEach((e) => {
+      idInfoMap.value.set(e.departmentId, e);
+    });
+
+    // 默认显示 最顶级ID为列表中返回的第一条数据的ID
+    if (!_.isEmpty(departmentTreeData.value) && departmentTreeData.value.length > 0) {
+      topDepartmentId.value = departmentTreeData.value[0].departmentId;
+    }
+
+    selectTree(departmentTreeData.value[0].departmentId);
+  }
+
+  // 构建部门树
+  function buildDepartmentTree(data, parentId) {
+    let children = data.filter((e) => e.parentId === parentId) || [];
+    children.forEach((e) => {
+      e.children = buildDepartmentTree(data, e.departmentId);
+    });
+    updateDepartmentPreIdAndNextId(children);
+    return children;
+  }
+
+  // 更新树的前置id和后置id
+  function updateDepartmentPreIdAndNextId(data) {
+    for (let index = 0; index < data.length; index++) {
+      if (index === 0) {
+        data[index].nextId = data.length > 1 ? data[1].departmentId : undefined;
+        continue;
+      }
+
+      if (index === data.length - 1) {
+        data[index].preId = data[index - 1].departmentId;
+        data[index].nextId = undefined;
+        continue;
+      }
+
+      data[index].preId = data[index - 1].departmentId;
+      data[index].nextId = data[index + 1].departmentId;
+    }
+  }
+
+  // ----------------------- 树的选中 ---------------------
+  const selectedKeys = ref([]);
+  const checkedKeys = ref([]);
+  const breadcrumb = ref([]);
+  const currentSelectedDepartmentId = ref();
+  const selectedDepartmentChildren = ref([]);
+
+  departmentEmitter.on('selectTree', selectTree);
+
+  function selectTree(id) {
+    selectedKeys.value = [id];
+    treeSelectChange(selectedKeys.value);
+  }
+
+  function treeSelectChange(idList) {
+
+
+
+
+    if (_.isEmpty(idList)) {
+      breadcrumb.value = [];
+      selectedDepartmentChildren.value = [];
+      return;
+    }
+    let id = idList[0];
+    selectedDepartmentChildren.value = departmentList.value.filter((e) => e.parentId == id);
+    let filterDepartmentList = [];
+    recursionFilterDepartment(filterDepartmentList, id, true);
+    breadcrumb.value = filterDepartmentList.map((e) => e.name);
+  }
+
+  // -----------------------  筛选 ---------------------
+  const keywords = ref('');
+  watch(
+    () => keywords.value,
+    () => {
+      onSearch();
+    }
+  );
+
+  // 筛选
+  function onSearch() {
+    if (!keywords.value) {
+      departmentTreeData.value = buildDepartmentTree(departmentList.value, DEPARTMENT_PARENT_ID);
+      return;
+    }
+    let originData = departmentList.value.concat();
+    if (!originData) {
+      return;
+    }
+    // 筛选出名称符合的部门
+    let filterDepartment = originData.filter((e) => e.name.indexOf(keywords.value) > -1);
+    let filterDepartmentList = [];
+    // 循环筛选出的部门 构建部门树
+    filterDepartment.forEach((e) => {
+      recursionFilterDepartment(filterDepartmentList, e.departmentId, false);
+    });
+
+    departmentTreeData.value = buildDepartmentTree(filterDepartmentList, DEPARTMENT_PARENT_ID);
+  }
+
+  // 根据ID递归筛选部门
+  function recursionFilterDepartment(resList, id, unshift) {
+    let info = idInfoMap.value.get(id);
+    if (!info || resList.some((e) => e.departmentId == id)) {
+      return;
+    }
+    if (unshift) {
+      resList.unshift(info);
+    } else {
+      resList.push(info);
+    }
+    if (info.parentId && info.parentId != 0) {
+      recursionFilterDepartment(resList, info.parentId, unshift);
+    }
+  }
+
+  onUnmounted(() => {
+    departmentEmitter.all.clear();
+  });
+
+  // ----------------------- 以下是暴露的方法内容 ----------------------------
+  defineExpose({
+    queryDepartmentTree,
+    selectedDepartmentChildren,
+    breadcrumb,
+    selectedKeys,
+    checkedKeys,
+    keywords,
+  });
+</script>
+<style scoped lang="less">
+  .tree-container {
+    height: 100%;
+    border-right: 1px solid rgba(#000, .1);
+    padding-right: 10px;
+
+    .tree {
+      height: 618px;
+      margin-top: 10px;
+      overflow-x: hidden;
+    }
+
+    .sort-flag-row {
+      margin-top: 10px;
+      margin-bottom: 10px;
+    }
+
+    .sort-span {
+      margin-left: 5px;
+    }
+
+    .no-data {
+      margin: 10px;
+    }
+  }
+</style>

+ 387 - 0
src/components/BsUi/OrgUserSelector/components/OrgUserList.vue

@@ -0,0 +1,387 @@
+<template>
+  <div class="org-user-list" v-loading="isLoading">
+    <bs-empty v-if="searchData.length === 0" />
+    <div v-else class="org-user-content">
+      <div class="top">
+        <div class="top-left" v-if="isEmpty(keyWord)">
+          <a-space>
+            <a-checkbox
+              @change="handleSelectAllChange"
+              :indeterminate="isIndeterminate"
+              v-model:checked="isSelectAll"
+              v-if="multiple === SELECT_MULTIPLE.MORE"
+              :disabled="disabled"
+            >
+              全选
+            </a-checkbox>
+            <a-checkbox v-model:checked="isInclude" :disabled="disabled" @change="changeInclude"> 包含下级 </a-checkbox>
+          </a-space>
+        </div>
+        <div class="top-right">
+          <a-space>
+            <span style="font-size: 12px; color: #1677ff">
+              <span>全部</span>
+              <span>({{ searchData.length }})</span>
+            </span>
+
+            <span style="font-size: 12px" v-if="sceneType === SCENE_TYPE.USER">
+              <span>人员</span>
+              <span>({{ searchData.length }})</span>
+            </span>
+          </a-space>
+        </div>
+      </div>
+
+      <div class="bottom">
+        <div class="bot-i-item" v-for="(item, index) in searchData" :key="item[idKey]" @click="selectNode(item)">
+          <a-checkbox
+            v-if="multiple === SELECT_MULTIPLE.MORE"
+            :checked="allSelectedKeys.findIndex((v) => v === item[idKey]) > -1"
+            :disabled="disabled"
+          ></a-checkbox>
+          <a-radio
+            v-if="multiple === SELECT_MULTIPLE.ONE"
+            :checked="allSelectedKeys.findIndex((v) => v === item[idKey]) > -1"
+            :disabled="disabled"
+          ></a-radio>
+          <a-avatar style="color: #fff; background-color: #1677ff">{{ item[labelKey] && item[labelKey][0] }}</a-avatar>
+          <div class="bot-item">
+            <span class="bot-item-top">{{ item[labelKey] }}</span>
+            <span class="bot-item-bottom">{{ item.parentName }}</span>
+          </div>
+        </div>
+      </div>
+
+      <div class="pager-config" v-if="false">
+        <a-pagination
+          size="small"
+          v-model:current="pageInfo.pageNum"
+          :total="pageInfo.total"
+          v-model:pageSize="pageInfo.pageSize"
+          @change="handlePagerChange"
+        />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { cloneDeep, isArray, isEmpty } from 'lodash';
+  import { queryListByParams } from '/@/api/system/table-api.js';
+  import BsEmpty from '/@/components/BsUi/Empty/index.vue';
+  import { computed, nextTick, onMounted, reactive, ref, toRefs, watch } from 'vue';
+  import { SCENE_TYPE, SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  import { removeElement } from '/@/components/BsUi/uitl.js';
+
+  const testData = Array.from({ length: 100 }, (value, index) => ({
+    name: `韩晓辉${index}`,
+    id: index,
+    parentId: 1,
+    parentName: '青岛无限畅联科技有限公司',
+  }));
+
+  const emit = defineEmits(['change']);
+  const props = defineProps({
+    multiple: {
+      required: false,
+      // default: SELECT_MULTIPLE.ONE,
+      default: SELECT_MULTIPLE.MORE,
+    },
+    disabled: {
+      required: false,
+      default: false,
+    },
+    idKey: {
+      required: false,
+      default: 'id',
+    },
+    labelKey: {
+      required: false,
+      default: 'name',
+    },
+    keyWord: {
+      required: false,
+      default: '',
+    },
+    sceneType: {
+      required: false,
+      default: SCENE_TYPE.USER,
+    },
+    oldSelectData: {
+      required: false,
+      default: [],
+    },
+    selectedTreeId: {
+      required: false,
+      default: '',
+    },
+    selectedData: {
+      required: true,
+      default: undefined,
+    },
+  });
+  const isLoading = ref(false);
+  const isSelectAll = ref(false);
+  const isInclude = ref(false);
+  const isIndeterminate = ref(false);
+  const isInit = ref(true);
+
+  const pageInfo = reactive({
+    pageNum: 1,
+    pageSize: 20,
+    total: 100,
+  });
+
+  const searchData = ref([]);
+  const currentSelectedKeys = ref([]);
+  const currentSelectedData = ref([]);
+
+  const uniqueByIdKey = (arr) => {
+    return [...new Map(arr.map((item) => [item[props.idKey], item])).values()];
+  };
+
+  const handleRemove = (node) => {
+    const delIdx = currentSelectedData.value.findIndex((v) => v[props.idKey] === node[props.idKey]);
+    if (delIdx > -1) {
+      currentSelectedData.value.splice(delIdx, 1);
+    }
+  };
+
+  const handleReset = () => {
+    currentSelectedData.value = [];
+  };
+
+  const handlePagerChange = (pageNum, pageSize) => {
+    pageInfo.pageNum = pageNum;
+    pageInfo.pageSize = pageSize;
+    init(false);
+  };
+
+  const selectNode = (node) => {
+    if (props.disabled) {
+      return false;
+    }
+    if (props.multiple === SELECT_MULTIPLE.ONE) {
+      currentSelectedData.value = [node];
+    } else {
+      const delIdx = currentSelectedData.value.findIndex((v) => v[props.idKey] === node[props.idKey]);
+      if (delIdx > -1) {
+        currentSelectedData.value.splice(delIdx, 1);
+      } else {
+        currentSelectedData.value.push(node);
+      }
+    }
+  };
+
+  const handleSelectAllChange = (event) => {
+    const checked = event.target.checked;
+    const cSelData = cloneDeep(currentSelectedData.value);
+    if (checked) {
+      currentSelectedData.value = uniqueByIdKey([...cSelData, ...searchData.value]);
+    } else {
+      searchData.value.forEach((node) => {
+        removeElement(currentSelectedData.value, node, props.idKey);
+      });
+    }
+  };
+
+  const fetchData = (data) => {
+    isLoading.value = true;
+
+    const params = {
+      include: false,
+      nodeType: [props.sceneType],
+      ...data,
+    };
+
+    return new Promise((resolve, reject) => {
+      queryListByParams(params).then((res) => {
+        isLoading.value = false;
+        if (res?.code === 0) {
+          searchData.value = !isEmpty(res?.data) ? res?.data : [];
+          pageInfo.total = searchData.value.length;
+        } else {
+          searchData.value = [];
+          pageInfo.total = 0;
+        }
+        resolve();
+      });
+
+      // setTimeout(() => {
+      //   isLoading.value = false;
+      //   searchData.value = props.keyWord ? testData.filter((v) => v.name.includes(props.keyWord)) : testData.slice(0, 18);
+      //   pageInfo.total = searchData.value.length;
+      //   resolve();
+      // }, 1000);
+    });
+  };
+
+  watch(
+    [currentSelectedData],
+    ([cur]) => {
+      currentSelectedKeys.value = cur.map((v) => v[props.idKey]);
+    },
+    { deep: true }
+  );
+
+  watch([searchData], ([value, allKeys]) => {}, { deep: true });
+
+  const changeInclude = (event) => {
+    const checked = event.target.checked;
+    init(false, {
+      keyword: '',
+      parentId: props.selectedTreeId,
+      include: checked,
+    });
+  };
+
+  watch(
+    () => props.keyWord,
+    (value) => {
+      // 监听关键字变化
+      isInclude.value = false;
+      init(false, {
+        keyword: value,
+        include: false,
+      });
+    }
+  );
+  watch(
+    () => props.selectedTreeId,
+    (val) => {
+      init(isInit.value, {
+        parentId: val,
+      });
+    },
+    {
+      immediate: false,
+    }
+  );
+
+  onMounted(() => {
+    // init(true, {
+    //   parentId: props.selectedTreeId,
+    // });
+  });
+
+  const init = (isInitVal = true, params = {}) => {
+    isInit.value = isInitVal;
+    searchData.value = [];
+    const curSelData = cloneDeep(currentSelectedData.value);
+    return new Promise((resolve, reject) => {
+      fetchData(params).then((res) => {
+        if (props.multiple === SELECT_MULTIPLE.ONE) {
+          if (!isEmpty(curSelData)) {
+            currentSelectedData.value = curSelData;
+          } else {
+            currentSelectedData.value = !isEmpty(props.selectedData) ? [props.selectedData] : [];
+          }
+        } else {
+          if (isInitVal) {
+            currentSelectedData.value = uniqueByIdKey([...curSelData, ...props.selectedData]);
+          } else {
+            currentSelectedData.value = uniqueByIdKey([...curSelData]);
+          }
+        }
+        resolve();
+      });
+    });
+  };
+
+  const allSelectedData = computed(() => {
+    return uniqueByIdKey([...currentSelectedData.value]);
+  });
+
+  const allSelectedKeys = computed(() => {
+    const allKeys = allSelectedData.value.map((v) => v[props.idKey]);
+    // 全选,反选逻辑
+    if (searchData.value.every((v) => allKeys.includes(v[props.idKey]))) {
+      isSelectAll.value = true;
+      isIndeterminate.value = false;
+    } else if (searchData.value.some((v) => allKeys.includes(v[props.idKey]))) {
+      isSelectAll.value = false;
+      isIndeterminate.value = true;
+    } else {
+      isSelectAll.value = false;
+      isIndeterminate.value = false;
+    }
+    return allKeys;
+  });
+
+  defineExpose({
+    fetchData,
+    init,
+    allSelectedData,
+    allSelectedKeys,
+    handleReset,
+    handleRemove,
+  });
+</script>
+
+<style lang="scss" scoped>
+  .org-user-list {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .org-user-content {
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      .top {
+        display: flex;
+        justify-content: space-between;
+      }
+
+      .pager-config {
+        width: 100%;
+        display: flex;
+        justify-content: flex-end;
+      }
+
+      .bottom {
+        margin-top: 10px;
+        display: grid;
+        gap: 10px;
+        flex: 1;
+        overflow: scroll;
+        grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); /* 自动填充列 */
+        grid-template-rows: repeat(auto-fill, minmax(50px, 1fr)); /* 自动填充列 */
+        .bot-i-item {
+          width: 270px;
+          height: 50px;
+          display: flex;
+          gap: 10px;
+          align-items: center;
+          cursor: pointer;
+          .bot-item {
+            display: flex;
+            flex-direction: column;
+
+            .bot-item-top {
+              color: #000;
+            }
+
+            .bot-item-bottom {
+              color: rgba(#000, 0.6);
+            }
+          }
+        }
+      }
+
+      .bb-bottom {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .select-num {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          gap: 5px;
+        }
+      }
+    }
+  }
+</style>

+ 122 - 0
src/components/BsUi/OrgUserSelector/components/SelectedData.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="selected-data">
+    <div class="select-num">
+      <div>已选: {{ selectedKeys.length }}</div>
+      <div>
+        <a-button type="text" size="small" :disabled="selectedKeys.length === 0" @click="isView = !isView"
+          >{{ isView ? '隐藏' : '查看' }}已选</a-button
+        >
+        <a-button type="text" danger size="small" @click="handleReset" :disabled="selectedKeys.length === 0">清空</a-button>
+      </div>
+    </div>
+
+    <div class="select-user" v-if="isView">
+      <div class="selected-user-item" v-for="(user, idx) in selectedData" :key="user[idKey]">
+        <div class="selected-user-icon">
+          <CloseCircleOutlined class="close-icon" @click="handleDel(user)"/>
+          <a-avatar shape="square" style="color: #fff; background-color: #1677ff">{{ user[labelKey] && user[labelKey][0] }}</a-avatar>
+        </div>
+        <div class="title">{{ user[labelKey] }}</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { UserOutlined, CloseCircleOutlined } from '@ant-design/icons-vue';
+  import { SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  import { ref } from 'vue';
+
+  const isView = ref(true);
+
+  const emit = defineEmits(['reset']);
+
+  const props = defineProps({
+    multiple: {
+      required: false,
+      default: SELECT_MULTIPLE.MORE,
+    },
+    selectedKeys: {
+      required: true,
+      default: [],
+    },
+    selectedData: {
+      required: true,
+      default: [],
+    },
+    idKey: {
+      required: false,
+      default: 'id',
+    },
+    labelKey: {
+      required: false,
+      default: 'name',
+    },
+  });
+
+  const handleReset = () => {
+    emit('reset');
+  };
+
+  const handleDel = (user) => {
+    emit('remove', user)
+  }
+</script>
+
+<style scoped lang="less">
+  .selected-data {
+    width: 100%;
+    height: 100%;
+    .select-num {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      gap: 5px;
+    }
+
+    .select-user {
+      width: 100%;
+      display: flex;
+      overflow-x: scroll;
+      align-items: center;
+      gap: 10px;
+      padding: 10px;
+      .selected-user-item {
+        width: 70px;
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        cursor: pointer;
+
+        .title {
+          width: 100%;
+          color: rgba(#000, 0.8);
+          font-size: 12px;
+          white-space: nowrap;
+          text-align: center;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+        .selected-user-icon {
+          position: relative;
+          .close-icon {
+            display: none;
+            position: absolute;
+            top: -5px;
+            right: -10px;
+            color: #f56a00;
+            z-index: 10;
+            &:hover {
+              transform: scale(1.2);
+            }
+          }
+        }
+
+        &:hover .close-icon {
+          display: block;
+        }
+      }
+    }
+  }
+</style>

+ 158 - 0
src/components/BsUi/OrgUserSelector/index.vue

@@ -0,0 +1,158 @@
+<template>
+  <div class="selector">
+    <div class="selector-field">
+      <a-select
+        :value="selVal"
+        :mode="multiple === SELECT_MULTIPLE.MORE ? 'multiple' : undefined"
+        style="width: 100%"
+        placeholder="请选择"
+        :open="false"
+        :allow-clear="true"
+        @change="handleChange"
+        :disabled="disabled"
+        :options="options"
+        :max-tag-count="10"
+        :showArrow="false"
+      >
+        <template #tagRender="{ value: val, label, closable, onClose, option }">
+          <a-tag :closable="closable" style="margin-right: 3px" @close="closeTags(onClose, val, option, label)">
+            {{ label }}
+          </a-tag>
+        </template>
+      </a-select>
+      <a-button @click="handleClkSelector">
+        <template #icon>
+          <UserOutlined />
+        </template>
+      </a-button>
+    </div>
+    <modal-selector
+      ref="modalSelectorRef"
+      :multiple="multiple"
+      :selectedOldData="oldSelectedData"
+      :id-key="idKey"
+      :label-key="labelKey"
+      @change="selectChange"
+      @ok="confirm"
+      :selected-data="selectedData"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { SELECT_MULTIPLE } from '/@/components/BsUi/constant.js';
+  import ModalSelector from '/@/components/BsUi/OrgUserSelector/components/ModalSelector.vue';
+  import { onMounted, ref, watch } from 'vue';
+  import { isArray, isEmpty } from 'lodash';
+  import { UserOutlined } from '@ant-design/icons-vue';
+
+  const oldSelectedData = ref([]);
+  const selVal = ref([]);
+  const options = ref([]);
+
+  const props = defineProps({
+    selectedData: {
+      required: true,
+      default: undefined,
+    },
+    multiple: {
+      required: false,
+      default: SELECT_MULTIPLE.ONE,
+    },
+    disabled: {
+      required: false,
+      default: false,
+    },
+    idKey: {
+      required: false,
+      default: 'id',
+    },
+    labelKey: {
+      required: false,
+      default: 'name',
+    },
+  });
+  const emit = defineEmits(['update:selectedData']);
+
+  const modalSelectorRef = ref(null);
+
+  const selectChange = (value) => {};
+
+  const setOptions = (valObj) => {
+    if (isArray(valObj)) {
+      options.value = valObj.map((v) => ({
+        label: v[props.labelKey],
+        value: v[props.idKey],
+      }));
+    } else {
+      options.value = [
+        {
+          label: valObj[props.labelKey],
+          value: valObj[props.idKey],
+        },
+      ];
+    }
+  };
+
+  const closeTags = (onClose, val, option, label) => {
+    if (isArray(selVal.value)) {
+      selVal.value = selVal.value.filter((v) => v !== val);
+    } else {
+      selVal.value = undefined;
+    }
+    const valObj = oldSelectedData.value.filter((v) => v[props.idKey] !== val);
+    setOptions(valObj);
+    emit('update:selectedData', valObj);
+  };
+
+  const handleChange = (value) => {
+    if (isEmpty(value)) {
+      selVal.value = undefined;
+      emit('update:selectedData', []);
+    }
+  };
+
+  const handleClkSelector = () => {
+    if (props.disabled) {
+      return;
+    }
+    modalSelectorRef.value.showModal();
+  };
+
+  const confirm = ({ selectedKeys, selectedData }) => {
+    setOptions(selectedData);
+    if (props.multiple === SELECT_MULTIPLE.ONE && selectedData.length === 1) {
+      selVal.value = selectedKeys[0];
+      emit('update:selectedData', selectedData[0]);
+    } else {
+      selVal.value = selectedKeys;
+      emit('update:selectedData', selectedData);
+    }
+  };
+
+  watch(
+    () => props.selectedData,
+    (val) => {
+      setOptions(val);
+      if (isEmpty(val)) {
+        oldSelectedData.value = [];
+        selVal.value = [];
+      } else {
+        oldSelectedData.value = isArray(val) ? val : [val];
+        selVal.value = isArray(val) ? val.map((v) => v[props.idKey]) : val[props.idKey];
+      }
+    },
+    { immediate: true }
+  );
+</script>
+
+<style lang="scss" scoped>
+  .selector {
+    width: 100%;
+
+    .selector-field {
+      width: 100%;
+      display: flex;
+    }
+  }
+</style>

+ 7 - 0
src/components/BsUi/constant.js

@@ -12,3 +12,10 @@ export const REQUIRED_STATE = Object.freeze({
   REQUIRED: '1',
   NO_REQUIRED: '0',
 });
+
+export const SCENE_TYPE = Object.freeze({
+  ORG: 'ORG', // 组织
+  USER: 'USER', // 人员
+  GROUP: 'GROUP', // 群组
+  JOB: 'JOB', // 岗位
+})

+ 32 - 1
src/components/BsUi/uitl.js

@@ -1,4 +1,4 @@
-import { isEmpty, isNumber } from 'lodash';
+import { cloneDeep, isEmpty, isNumber } from 'lodash';
 
 export function formatMoney(num, decimalPlaces = 2) {
   if (isEmpty(num)) {
@@ -24,3 +24,34 @@ export function formatPercentage(num, decimalPlaces = 2) {
   const percentage = (num * 100).toFixed(decimalPlaces);
   return `${percentage}%`;
 }
+
+export const getPathName = (params) => {
+  const { sourceTreeData, labelKey = 'name', idKey = 'id', parentIdKey = 'parentId', childId, joinName = '/' } = params;
+
+  // 构建映射表
+  const map = new Map();
+  sourceTreeData.forEach((node) => map.set(node[idKey], node));
+
+  // 定义获取路径函数
+  function getPath(childId) {
+    const path = [];
+    let currentId = childId;
+    while (true) {
+      const node = map.get(currentId);
+      if (!node) break; // 若不存在该节点,终止
+      path.unshift(node[labelKey]); // 从头部插入名称,保证顺序正确
+      if (node[parentIdKey] === '0') break; // 到达根节点,终止
+      currentId = node[parentIdKey]; // 切换到父节点
+    }
+    return path.join(joinName); // 拼接为路径字符串
+  }
+
+  return getPath(childId);
+};
+
+export const removeElement = (arr, node, idKey) => {
+  const delIdx = arr.findIndex((v) => v[idKey] === node[idKey]);
+  if (delIdx > -1) {
+    arr.splice(delIdx, 1);
+  }
+};

+ 52 - 32
src/components/support/table-operator/index.vue

@@ -44,6 +44,7 @@ import SmartTableColumnModal from './smart-table-column-modal.vue';
 import { message } from 'ant-design-vue';
 import { mergeColumn } from './smart-table-column-merge';
 import { smartSentry } from '/@/lib/smart-sentry';
+import { useAppConfigStore } from '/@/store/modules/system/app-config.js';
 const props = defineProps({
   // 表格列数组
   modelValue: {
@@ -96,44 +97,63 @@ async function buildUserTableColumns () {
 const fullScreenFlag = ref(false);
 function fullScreen () {
   if (fullScreenFlag.value) {
-    //取消全屏
-    exitFullscreen(document.querySelector('#smartAdminLayoutContent'));
-    fullScreenFlag.value = false;
-    document.querySelector('#smartAdminPageTag').style.visibility = 'visible';
-  } else {
-    //全屏
-    launchFullScreen(document.querySelector('#smartAdminLayoutContent'));
-    fullScreenFlag.value = true;
-    document.querySelector('#smartAdminPageTag').style.visibility = 'hidden';
-  }
+      // 退出全屏
+      handleExitFullScreen();
+      exitElementFullscreen(document.body);
+    } else {
+      fullScreenFlag.value = true;
+      useAppConfigStore().startFullScreen();
+      launchElementFullScreen(document.body);
+    }
 }
 
 //判断各种浏览器 -全屏
-function launchFullScreen (element) {
-  if (element.requestFullscreen) {
-    element.requestFullscreen();
-  } else if (element.mozRequestFullScreen) {
-    element.mozRequestFullScreen();
-  } else if (element.webkitRequestFullScreen) {
-    element.webkitRequestFullScreen();
-  } else if (element.msRequestFullscreen) {
-    element.msRequestFullscreen();
-  } else {
-    message.error('当前浏览器不支持部分全屏!');
+  function exitElementFullscreen(element) {
+    if (document.exitFullscreen) {
+      document.exitFullscreen();
+    } else if (document.mozCancelFullScreen) {
+      document.mozCancelFullScreen();
+    } else if (document.webkitExitFullscreen) {
+      document.webkitExitFullscreen();
+    } else if (document.msExitFullscreen) {
+      document.msExitFullscreen();
+    }
+  }
+  // 处理退出全屏
+  function handleExitFullScreen() {
+    fullScreenFlag.value = false;
+    useAppConfigStore().exitFullScreen();
+    document.removeEventListener('fullscreenchange', handleFullscreenChange);
+    document.removeEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
+    document.removeEventListener('webkitfullscreenchange', handleFullscreenChange); // Chrome, Safari and Opera
+    document.removeEventListener('MSFullscreenChange', handleFullscreenChange); // Internet Explorer and Edge
+  }
+    function handleFullscreenChange() {
+    if (document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
+      console.log('进入全屏模式');
+    } else {
+      console.log('退出全屏模式');
+      handleExitFullScreen();
+    }
   }
-}
 //判断各种浏览器 -退出全屏
-function exitFullscreen (element) {
-  if (document.exitFullscreen) {
-    document.exitFullscreen();
-  } else if (document.mozCancelFullScreen) {
-    document.mozCancelFullScreen();
-  } else if (document.webkitExitFullscreen) {
-    document.webkitExitFullscreen();
-  } else if (document.msExitFullscreen) {
-    document.msExitFullscreen();
+  function launchElementFullScreen(element) {
+    if (element.requestFullscreen) {
+      element.requestFullscreen();
+    } else if (element.mozRequestFullScreen) {
+      element.mozRequestFullScreen();
+    } else if (element.webkitRequestFullScreen) {
+      element.webkitRequestFullScreen();
+    } else if (element.msRequestFullscreen) {
+      element.msRequestFullscreen();
+    } else {
+      message.error('当前浏览器不支持部分全屏!');
+    }
+    document.addEventListener('fullscreenchange', handleFullscreenChange);
+    document.addEventListener('mozfullscreenchange', handleFullscreenChange); // Firefox
+    document.addEventListener('webkitfullscreenchange', handleFullscreenChange); // Chrome, Safari and Opera
+    document.addEventListener('MSFullscreenChange', handleFullscreenChange); // Internet Explorer and Edge
   }
-}
 
 // ----------------- 弹窗 修改表格列 -------------------
 

+ 7 - 0
src/store/modules/system/app-config.js

@@ -35,6 +35,7 @@ export const useAppConfigStore = defineStore({
   state: () => ({
     // 读取config下的默认配置
     ...state,
+    fullScreenFlag: false,
   }),
   actions: {
     reset() {
@@ -48,5 +49,11 @@ export const useAppConfigStore = defineStore({
     hideHelpDoc() {
       this.helpDocExpandFlag = false;
     },
+    startFullScreen() {
+      this.fullScreenFlag = true;
+    },
+    exitFullScreen() {
+      this.fullScreenFlag = false;
+    },
   },
 });

+ 120 - 79
src/views/support/data-source/components/data-source-drawer.vue

@@ -3,29 +3,21 @@
         :drawer-extra-props="drawerOptions.drawerExtraProps" :visible="drawerOptions.visible" @ok="handleOk"
         @cancel="handleCancel" @close="handleClose">
         <template #footer>
-            <div class="drawer-footer" >
+            <div class="drawer-footer">
                 <a-button type="primary" @click="handleOk">保存</a-button>
                 <a-button type="primary" @click="handleCancel">测试</a-button>
-                <a-dropdown>
+                <a-dropdown v-for="item in moreButton">
                     <template #overlay>
-                        <a-menu @click="handleMenuClick">
-                            <a-menu-item key="1">
-                                <UserOutlined />
-                                1st menu item
-                            </a-menu-item>
-                            <a-menu-item key="2">
-                                <UserOutlined />
-                                2nd menu item
-                            </a-menu-item>
-                            <a-menu-item key="3">
-                                <UserOutlined />
-                                3rd item
+                        <a-menu>
+                            <a-menu-item v-for="children in item.children"  @click="children.onClick">
+                                <component :is="getIcon(children.icon)" />
+                                {{ children.name }}
                             </a-menu-item>
                         </a-menu>
                     </template>
-                    <a-button>
-                        更多
-                        <RightOutlined />
+                    <a-button @click="item.onClick">
+                        {{item.name}}
+                        <component :is="getIcon(item.icon)" />
                     </a-button>
                 </a-dropdown>
             </div>
@@ -37,27 +29,56 @@
 </template>
 
 <script setup lang="jsx">
+import { ref } from 'vue';
+import * as icons from '@ant-design/icons-vue';
+
 import BsDrawer, { useBsDrawer } from '/@/components/BsUi/Drawer/index.js';
 import BsForm, { useBsForm } from '/@/components/BsUi/Form/index.js';
-import { ref } from 'vue';
+import { DISPLAY_STATE, REQUIRED_STATE } from '/@/components/BsUi/constant';
+/* 更多弹窗按钮 */
+const moreButton = [
+    {
+        name: '更多',
+        icon: 'RightOutlined',
+        onClick:()=>{
+        
+        },
+        children: [
+            {
+                key: 0,
+                name: '测试1',
+                icon: 'SafetyCertificateOutlined',
+                onClick: () => {
 
-const bsFormRef = ref(null);
+                }
+            },
+            {
+                key: 1,
+                name: '测试2',
+                icon: '',
+                onClick: () => {
 
-const {
-    setDrawerPropsValue: setDVal,
-    drawerOptions,
-    getDrawerPropsValue: getDVal,
-} = useBsDrawer({
-    drawerOptions: {
-        width: '500px',
-        title: '新增数据源',
-        visible: false,
-        drawerExtraProps: {
-            destroyOnClose: true,
-            maskClosable: true
-        },
-    },
-});
+                }
+            },
+            {
+                key: 2,
+                name: '测试3',
+                icon: '',
+                onClick: () => {
+
+                }
+            },
+        ]
+    }
+]
+
+function getIcon(name) {
+    return icons[name]
+}
+
+/* 表单 */
+const bsFormRef = ref(null);
+const formData = ref([])
 
 const { formOptions } = useBsForm({
     formOptions: {
@@ -69,9 +90,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请输入数据源名称',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -90,9 +111,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请选择数据库类型',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -112,9 +133,9 @@ const { formOptions } = useBsForm({
                     unCheckedChildren: "关闭"
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -133,9 +154,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请输入服务器IP',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -154,9 +175,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请输入端口号',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -175,9 +196,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请输入数据库名称',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -196,9 +217,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请输入用户名',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -217,9 +238,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请输入数据库名称',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -239,9 +260,9 @@ const { formOptions } = useBsForm({
                     unCheckedChildren: "否"
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -261,9 +282,9 @@ const { formOptions } = useBsForm({
                     type: 'number',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -283,9 +304,9 @@ const { formOptions } = useBsForm({
                     type: 'number',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -304,9 +325,9 @@ const { formOptions } = useBsForm({
                     placeholder: '请选择所属机构',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -326,9 +347,9 @@ const { formOptions } = useBsForm({
                     type: 'number',
                 },
                 field: 'name',
-                sort: true,
-                visible: true,
-                required: true,
+                sort: '1',
+                visible: DISPLAY_STATE.VISIBLE,
+                required: REQUIRED_STATE.REQUIRED,
                 formItemExtraProps: {
                     rules: [
                         {
@@ -342,9 +363,7 @@ const { formOptions } = useBsForm({
             },
         ],
 
-        formData: {
-            name: '',
-        },
+        formData: formData.value,
         formId: 'formId',
         formExtraProps: {
             labelCol: { span: 5 },
@@ -352,6 +371,22 @@ const { formOptions } = useBsForm({
         },
     },
 });
+/* 抽屉 */
+const {
+    setDrawerPropsValue: setDVal,
+    drawerOptions,
+    getDrawerPropsValue: getDVal,
+} = useBsDrawer({
+    drawerOptions: {
+        width: '500px',
+        title: '新增数据源',
+        visible: false,
+        drawerExtraProps: {
+            destroyOnClose: true,
+            maskClosable: true
+        },
+    },
+});
 
 const closeDrawer = () => {
     setDVal('visible', false);
@@ -374,9 +409,15 @@ const handleClose = async () => {
     setDVal('visible', false);
 };
 
-const showDrawer = () => {
+const showDrawer = (isEdit, record) => {
     setDVal('visible', true);
-    setDVal('title', '修改标题');
+    if (isEdit) {
+        setDVal('title', '修改数据源');
+        formData.value = record
+    } else {
+        setDVal('title', '新增数据源');
+        formData.value = []
+    }
 };
 
 defineExpose({
@@ -384,7 +425,7 @@ defineExpose({
 });
 </script>
 <style scoped lang="scss">
-.drawer-footer{
+.drawer-footer {
     display: flex;
     gap: 10px;
 }

+ 86 - 26
src/views/support/data-source/index.vue

@@ -16,17 +16,21 @@
 <script setup lang="jsx">
   import { pick } from 'lodash';
   import { ref, onMounted, h } from 'vue';
+  import { Modal } from 'ant-design-vue';
 
   import BsTable, { useBsTable } from '/@/components/BsUi/Table/index.js';
 
   import dataSourceDrawer from './components/data-source-drawer.vue';
+  import { dataSourceApi } from '/@/api/support/data-source-api';
   const dataSourceDrawerRef = ref(null);
   const {
     tableOptions,
     setTablePropsValue: setValue,
     getTablePropsValue: getValue,
+    fetchTableData,
   } = useBsTable({
     tableOptions: {
+      url: '',
       gridOptions: {
         loading: false,
         columns: [
@@ -53,8 +57,7 @@
                   checked-children='连接中'
                   un-checked-children='已禁用'
                   type='primary'
-                >
-                </a-switch>,
+                ></a-switch>,
               ],
             },
           },
@@ -78,7 +81,48 @@
             field: 'name',
             title: '显示顺序',
           },
+          {
+            field: 'opt',
+            title: '操作',
+            width: '120px',
+            fixed: 'right',
+            slots: {
+              default({ row, column }) {
+                return (
+                  <div>
+                    <a-button
+                      type='link'
+                      size='small'
+                      disabled={false}
+                      onClick={() => {
+                        handleEdit(row);
+                      }}
+                    >
+                      编辑
+                    </a-button>
+                    <a-button
+                      type='link'
+                      size='small'
+                      danger
+                      disabled={false}
+                      onClick={() => {
+                        confirmDelete(row);
+                      }}
+                    >
+                      删除
+                    </a-button>
+                  </div>
+                );
+              },
+            },
+          },
         ],
+        data: [
+          {
+            id: 1,
+            name: '测试数据',
+          },
+        ], // 模拟数据源
       },
       searchConfig: {
         enabled: true,
@@ -104,7 +148,7 @@
         enabled: true,
         pageSize: 10,
         pageNum: 1,
-        total: 100,
+        // total: 100,
         onChange: () => {
           fetchTableData();
         },
@@ -114,6 +158,10 @@
           fetchTableData();
         },
       },
+      tableSearchBeforeBiz() {
+        const searchParams = getValue('searchConfig.data');
+        setValue('searchConfig.data', { ...searchParams, otherField: 'abc' });
+      },
     },
   });
   const getSearchParams = () => {
@@ -122,37 +170,49 @@
   const getPageInfo = () => {
     return pick(getValue('pagerConfig.pageInfo'), ['pageNum', 'pageSize']);
   };
+  /* 加载 */
 
-  const fetchTableData = () => {
-    setValue('gridOptions.loading', true);
-
-    const params = {
-      ...getSearchParams(),
-      ...getPageInfo(),
-    };
-
-    setTimeout(() => {
-      setValue('gridOptions.data', [
-        {
-          id: '1111111',
-          name: 'John Doe',
-        },
-      ]);
-
-      setValue('gridOptions.loading', false);
-    }, 1000);
-  };
   onMounted(() => {
     console.log('表格已加载');
     fetchTableData();
   });
   /* 新增按钮 */
   const openEditDrawer = () => {
-    dataSourceDrawerRef.value.showDrawer();
+    openDrawer(false, null);
+  };
+  /* 更改连接数据源状态 */
+  const handleChangeDisabled = (checked, rowData) => {
+    console.log(checked, rowData);
+  };
+  /* 编辑 */
+  const handleEdit = (record) => {
+    openDrawer(true, record);
   };
-  const handleChangeDisabled = (checked,rowData) => { 
-    console.log(checked,rowData);
-    
+  /* 打开抽屉 */
+  const openDrawer = (isEdit, record) => {
+    if (isEdit) {
+      dataSourceDrawerRef.value.showDrawer(isEdit, record);
+    } else {
+      dataSourceDrawerRef.value.showDrawer(isEdit, null);
+    }
+  };
+  /* 删除弹窗 */
+  const confirmDelete = (dataId, dataName) => {
+    Modal.confirm({
+      title: '警告',
+      content: `确定要删除【${dataName}】数据源吗?`,
+      okText: '删除',
+      okType: 'danger',
+      onOk() {
+        deleteDataSource(dataId);
+      },
+      cancelText: '取消',
+      onCancel() {},
+    });
+  };
+  /* 删除事件 */
+  const deleteDataSource = (dataId) => {
+
   };
 </script>
 <style scoped lang="scss"></style>

+ 314 - 332
src/views/support/job/job-list.vue

@@ -1,143 +1,121 @@
 <template>
   <div>
     <a-card size="small" :bordered="false" :hoverable="true">
-      <a-tabs v-model:activeKey="activeKey">
-        <a-tab-pane key="1" tab="有效任务">
-          <a-form class="smart-query-form">
-            <a-row class="smart-query-form-row">
-              <a-form-item label="关键字" class="smart-query-form-item">
-                <a-input style="width: 200px" v-model:value="queryForm.searchWord" placeholder="请输入关键字" :maxlength="30" />
-              </a-form-item>
-              <a-form-item label="触发类型" class="smart-query-form-item">
-                <a-select style="width: 155px" v-model:value="queryForm.triggerType" placeholder="请选择触发类型" allowClear>
-                  <a-select-option v-for="item in $smartEnumPlugin.getValueDescList('TRIGGER_TYPE_ENUM')" :key="item.value" :value="item.value">
-                    {{ item.desc }}
-                  </a-select-option>
-                </a-select>
-              </a-form-item>
-              <a-form-item label="状态" class="smart-query-form-item">
-                <a-select style="width: 150px" v-model:value="queryForm.enabledFlag" placeholder="请选择状态" allowClear>
-                  <a-select-option :key="1"> 开启 </a-select-option>
-                  <a-select-option :key="0"> 停止 </a-select-option>
-                </a-select>
-              </a-form-item>
+      <!-- <a-tabs v-model:activeKey="activeKey"> -->
+      <!-- <a-tab-pane key="1" tab="有效任务"> -->
+      <a-form class="smart-query-form">
+        <a-row class="smart-query-form-row">
+          <a-form-item label="关键字" class="smart-query-form-item">
+            <a-input style="width: 200px" v-model:value="queryForm.searchWord" placeholder="请输入关键字" :maxlength="30" />
+          </a-form-item>
+          <a-form-item label="触发类型" class="smart-query-form-item">
+            <a-select style="width: 155px" v-model:value="queryForm.triggerType" placeholder="请选择触发类型" allowClear>
+              <a-select-option v-for="item in $smartEnumPlugin.getValueDescList('TRIGGER_TYPE_ENUM')" :key="item.value"
+                :value="item.value">
+                {{ item.desc }}
+              </a-select-option>
+            </a-select>
+          </a-form-item>
+          <a-form-item label="状态" class="smart-query-form-item">
+            <a-select style="width: 150px" v-model:value="queryForm.enabledFlag" placeholder="请选择状态" allowClear>
+              <a-select-option :key="1"> 开启 </a-select-option>
+              <a-select-option :key="0"> 停止 </a-select-option>
+            </a-select>
+          </a-form-item>
 
-              <a-form-item class="smart-query-form-item smart-margin-left10">
-                <a-button-group>
-                  <a-button type="primary" @click="onSearch" v-privilege="'support:job:query'">
-                    <template #icon>
-                      <SearchOutlined />
-                    </template>
-                    查询
-                  </a-button>
-                  <a-button @click="resetQuery" v-privilege="'support:job:query'">
-                    <template #icon>
-                      <ReloadOutlined />
-                    </template>
-                    重置
-                  </a-button>
-                </a-button-group>
+          <a-form-item class="smart-query-form-item smart-margin-left10">
+            <a-button-group>
+              <a-button type="primary" @click="onSearch" v-privilege="'support:job:query'">
+                <template #icon>
+                  <SearchOutlined />
+                </template>
+                查询
+              </a-button>
+              <a-button @click="resetQuery" v-privilege="'support:job:query'">
+                <template #icon>
+                  <ReloadOutlined />
+                </template>
+                重置
+              </a-button>
+            </a-button-group>
 
-                <a-button class="smart-margin-left20" type="primary" @click="openUpdateModal()" v-privilege="'support:job:add'">
-                  <template #icon>
-                    <plus-outlined />
-                  </template>
-                  添加任务
-                </a-button>
-              </a-form-item>
-            </a-row>
-          </a-form>
-          <a-flex align="end" vertical>
-            <div class="smart-table-setting-block smart-margin-bottom10">
-              <TableOperator
-                class="smart-margin-bottom5 pull-right"
-                v-model="columns"
-                :tableId="TABLE_ID_CONST.SUPPORT.JOB"
-                :refresh="queryJobList"
-              />
-            </div>
-          </a-flex>
-
-          <a-table
-            :scroll="{ x: 1800 }"
-            size="small"
-            :loading="tableLoading"
-            bordered
-            :dataSource="tableData"
-            :columns="columns"
-            rowKey="jobId"
-            :pagination="false"
-          >
-            <template #bodyCell="{ record, column }">
-              <template v-if="column.dataIndex === 'jobClass'">
-                <a-tooltip>
-                  <template #title>{{ record.jobClass }}</template>
-                  {{ handleJobClass(record.jobClass) }}
-                </a-tooltip>
-              </template>
-              <template v-if="column.dataIndex === 'triggerType'">
-                <a-tag v-if="record.triggerType === TRIGGER_TYPE_ENUM.CRON.value" color="success">{{ record.triggerTypeDesc }}</a-tag>
-                <a-tag v-else-if="record.triggerType === TRIGGER_TYPE_ENUM.FIXED_DELAY.value" color="processing">{{ record.triggerTypeDesc }}</a-tag>
-                <a-tag v-else color="pink">{{ record.triggerTypeDesc }}</a-tag>
-              </template>
-              <template v-if="column.dataIndex === 'lastJob'">
-                <div v-if="record.lastJobLog">
-                  <a-tooltip>
-                    <template #title>{{ handleExecuteResult(record.lastJobLog.executeResult) }}</template>
-                    <CheckOutlined v-if="record.lastJobLog.successFlag" style="color: #39c710" />
-                    <WarningOutlined v-else style="color: #f50" />
-                    {{ record.lastJobLog.executeStartTime }}
-                  </a-tooltip>
-                </div>
-              </template>
-              <template v-if="column.dataIndex === 'nextJob'">
-                <a-tooltip v-if="record.enabledFlag && record.nextJobExecuteTimeList">
-                  <template #title>
-                    <div>下次执行(预估时间)</div>
-                    <div v-for="item in record.nextJobExecuteTimeList" :key="item">{{ item }}</div>
-                  </template>
-                  {{ record.nextJobExecuteTimeList[0] }}
-                </a-tooltip>
+            <a-button class="smart-margin-left20" type="primary" @click="openUpdateModal()"
+              v-privilege="'support:job:add'">
+              <template #icon>
+                <plus-outlined />
               </template>
-              <template v-if="column.dataIndex === 'enabledFlag'">
-                <a-switch
-                  v-model:checked="record.enabledFlag"
-                  checked-children="已启用"
-                  un-checked-children="已禁用"
-                  @change="(checked) => handleEnabledUpdate(checked, record)"
-                  :loading="record.enabledLoading"
-                />
-              </template>
-              <template v-if="column.dataIndex === 'action'">
-                <div class="smart-table-operate">
-                  <a-button v-privilege="'support:job:update'" @click="openUpdateModal(record)" type="link">编辑</a-button>
-                  <a-button v-privilege="'support:job:execute'" type="link" @click="openExecuteModal(record)">执行</a-button>
-                  <a-button v-privilege="'support:job:log:query'" @click="openJobLogModal(record.jobId, record.jobName)" type="link">记录</a-button>
-                  <a-button danger v-privilege="'support:job:log:delete'" @click="confirmDelete(record.jobId, record.jobName)" type="link"
-                    >删除</a-button
-                  >
-                </div>
+              添加任务
+            </a-button>
+          </a-form-item>
+        </a-row>
+      </a-form>
+      <a-flex align="end" vertical>
+        <div class="smart-table-setting-block smart-margin-bottom10">
+          <TableOperator class="smart-margin-bottom5 pull-right" v-model="columns" :tableId="TABLE_ID_CONST.SUPPORT.JOB"
+            :refresh="queryJobList" />
+        </div>
+      </a-flex>
+
+      <a-table :scroll="{ x: 1800 }" size="small" :loading="tableLoading" bordered :dataSource="tableData"
+        :columns="columns" rowKey="jobId" :pagination="false">
+        <template #bodyCell="{ record, column }">
+          <template v-if="column.dataIndex === 'jobClass'">
+            <a-tooltip>
+              <template #title>{{ record.jobClass }}</template>
+              {{ handleJobClass(record.jobClass) }}
+            </a-tooltip>
+          </template>
+          <template v-if="column.dataIndex === 'triggerType'">
+            <a-tag v-if="record.triggerType === TRIGGER_TYPE_ENUM.CRON.value" color="success">{{ record.triggerTypeDesc
+              }}</a-tag>
+            <a-tag v-else-if="record.triggerType === TRIGGER_TYPE_ENUM.FIXED_DELAY.value" color="processing">{{
+              record.triggerTypeDesc }}</a-tag>
+            <a-tag v-else color="pink">{{ record.triggerTypeDesc }}</a-tag>
+          </template>
+          <template v-if="column.dataIndex === 'lastJob'">
+            <div v-if="record.lastJobLog">
+              <a-tooltip>
+                <template #title>{{ handleExecuteResult(record.lastJobLog.executeResult) }}</template>
+                <CheckOutlined v-if="record.lastJobLog.successFlag" style="color: #39c710" />
+                <WarningOutlined v-else style="color: #f50" />
+                {{ record.lastJobLog.executeEndTime }}
+              </a-tooltip>
+            </div>
+          </template>
+          <template v-if="column.dataIndex === 'nextJob'">
+            <a-tooltip v-if="record.enabledFlag && record.nextJobExecuteTimeList">
+              <template #title>
+                <div>下次执行(预估时间)</div>
+                <div v-for="item in record.nextJobExecuteTimeList" :key="item">{{ item }}</div>
               </template>
-            </template>
-          </a-table>
-          <div class="smart-query-table-page">
-            <a-pagination
-              showSizeChanger
-              showQuickJumper
-              show-less-items
-              :pageSizeOptions="PAGE_SIZE_OPTIONS"
-              :defaultPageSize="queryForm.pageSize"
-              v-model:current="queryForm.pageNum"
-              v-model:pageSize="queryForm.pageSize"
-              :total="total"
-              @change="queryJobList"
-              @showSizeChange="queryJobList"
-              :show-total="(total) => `共${total}条`"
-            />
-          </div>
-        </a-tab-pane>
-        <a-tab-pane key="2" tab="已删除任务"><DeletedJobList /></a-tab-pane>
-      </a-tabs>
+              {{ record.nextJobExecuteTimeList[0] }}
+            </a-tooltip>
+          </template>
+          <template v-if="column.dataIndex === 'enabledFlag'">
+            <a-switch v-model:checked="record.enabledFlag" checked-children="已启用" un-checked-children="已禁用"
+              @change="(checked) => deEnabledUpdate(checked, record)" :loading="record.enabledLoading" />
+          </template>
+          <template v-if="column.dataIndex === 'action'">
+            <div class="smart-table-operate">
+              <a-button v-privilege="'support:job:update'" @click="openUpdateModal(record)" type="link">编辑</a-button>
+              <a-button v-privilege="'support:job:execute'" type="link" @click="openExecuteModal(record)">执行</a-button>
+              <a-button v-privilege="'support:job:log:query'" @click="openJobLogModal(record.jobId, record.jobName)"
+                type="link">记录</a-button>
+              <a-button danger v-privilege="'support:job:log:delete'"
+                @click="confirmDelete(record.jobId, record.jobName)" type="link">删除</a-button>
+            </div>
+          </template>
+        </template>
+      </a-table>
+      <div class="smart-query-table-page">
+        <a-pagination showSizeChanger showQuickJumper show-less-items :pageSizeOptions="PAGE_SIZE_OPTIONS"
+          :defaultPageSize="queryForm.pageSize" v-model:current="queryForm.pageNum"
+          v-model:pageSize="queryForm.pageSize" :total="total" @change="queryJobList" @showSizeChange="queryJobList"
+          :show-total="(total) => `共${total}条`" />
+      </div>
+      <!-- </a-tab-pane> -->
+      <!-- <a-tab-pane key="2" tab="已删除任务"><DeletedJobList /></a-tab-pane> -->
+      <!-- </a-tabs> -->
     </a-card>
 
     <!-- 表单操作 -->
@@ -148,223 +126,227 @@
   </div>
 </template>
 <script setup>
-  import { message, Modal } from 'ant-design-vue';
-  import { onMounted, reactive, ref } from 'vue';
-  import { jobApi } from '/@/api/support/job-api';
-  import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
-  import { smartSentry } from '/@/lib/smart-sentry';
-  import TableOperator from '/@/components/support/table-operator/index.vue';
-  import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
-  import DeletedJobList from './components/deleted-job-list.vue';
-  import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const.js';
-  import JobFormModal from './components/job-form-modal.vue';
-  import JobLogListModal from './components/job-log-list-modal.vue';
-  import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
-  const activeKey = ref('1');
-  const columns = ref([
-    {
-      title: 'id',
-      width: 50,
-      dataIndex: 'jobId',
-    },
-    {
-      title: '任务名称',
-      dataIndex: 'jobName',
-      minWidth: 150,
-      ellipsis: true,
-    },
-    {
-      title: '执行类',
-      dataIndex: 'jobClass',
-      minWidth: 180,
-      ellipsis: true,
-    },
-    {
-      title: '触发类型',
-      dataIndex: 'triggerType',
-      width: 110,
-    },
-    {
-      title: '触发配置',
-      dataIndex: 'triggerValue',
-      width: 150,
-    },
-    {
-      title: '上次执行',
-      width: 180,
-      dataIndex: 'lastJob',
-    },
-    {
-      title: '下次执行',
-      width: 150,
-      dataIndex: 'nextJob',
-    },
-    {
-      title: '启用状态',
-      dataIndex: 'enabledFlag',
-      width: 100,
-    },
-    {
-      title: '执行参数',
-      dataIndex: 'param',
-      ellipsis: true,
-    },
-    {
-      title: '任务描述',
-      dataIndex: 'remark',
-      ellipsis: true,
-    },
-    {
-      title: '排序',
-      dataIndex: 'sort',
-      width: 65,
-    },
-    {
-      title: '更新人',
-      dataIndex: 'updateName',
-      width: 90,
-    },
-    {
-      title: '更新时间',
-      dataIndex: 'updateTime',
-      width: 150,
-    },
-    {
-      title: '操作',
-      dataIndex: 'action',
-      fixed: 'right',
-      width: 170,
-    },
-  ]);
+import { message, Modal } from 'ant-design-vue';
+import { onMounted, reactive, ref } from 'vue';
+import { jobApi } from '/@/api/support/job-api';
+import { PAGE_SIZE_OPTIONS } from '/@/constants/common-const';
+import { smartSentry } from '/@/lib/smart-sentry';
+import TableOperator from '/@/components/support/table-operator/index.vue';
+import { TABLE_ID_CONST } from '/@/constants/support/table-id-const';
+import DeletedJobList from './components/deleted-job-list.vue'; //已删除任务
+import { TRIGGER_TYPE_ENUM } from '/@/constants/support/job-const.js';
+import JobFormModal from './components/job-form-modal.vue';
+import JobLogListModal from './components/job-log-list-modal.vue';
+import { SmartLoading } from '/@/components/framework/smart-loading/index.js';
+import { debounce } from 'lodash'
+const activeKey = ref('1');
+const columns = ref([
+  {
+    title: 'id',
+    width: 50,
+    dataIndex: 'jobId',
+  },
+  {
+    title: '任务名称',
+    dataIndex: 'jobName',
+    minWidth: 150,
+    ellipsis: true,
+  },
+  {
+    title: '执行类',
+    dataIndex: 'jobClass',
+    minWidth: 180,
+    ellipsis: true,
+  },
+  {
+    title: '触发类型',
+    dataIndex: 'triggerType',
+    width: 110,
+  },
+  {
+    title: '触发配置',
+    dataIndex: 'triggerValue',
+    width: 150,
+  },
+  {
+    title: '上次执行',
+    width: 180,
+    dataIndex: 'lastJob',
+  },
+  {
+    title: '下次执行',
+    width: 150,
+    dataIndex: 'nextJob',
+  },
+  {
+    title: '启用状态',
+    dataIndex: 'enabledFlag',
+    width: 100,
+  },
+  {
+    title: '执行参数',
+    dataIndex: 'param',
+    ellipsis: true,
+  },
+  {
+    title: '任务描述',
+    dataIndex: 'remark',
+    ellipsis: true,
+  },
+  {
+    title: '排序',
+    dataIndex: 'sort',
+    width: 65,
+  },
+  {
+    title: '更新人',
+    dataIndex: 'updateName',
+    width: 90,
+  },
+  {
+    title: '更新时间',
+    dataIndex: 'updateTime',
+    width: 150,
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    fixed: 'right',
+    width: 170,
+  },
+]);
 
-  // ---------------- 查询数据 -----------------------
+// ---------------- 查询数据 -----------------------
 
-  const queryFormState = {
-    searchWord: '',
-    enabledFlag: null,
-    triggerType: null,
-    deletedFlag: false,
-    pageNum: 1,
-    pageSize: 10,
-  };
-  const queryForm = reactive({ ...queryFormState });
+const queryFormState = {
+  searchWord: '',
+  enabledFlag: null,
+  triggerType: null,
+  deletedFlag: false,
+  pageNum: 1,
+  pageSize: 10,
+};
+const queryForm = reactive({ ...queryFormState });
 
-  const tableLoading = ref(false);
-  const tableData = ref([]);
-  const total = ref(0);
+const tableLoading = ref(false);
+const tableData = ref([]);
+const total = ref(0);
 
-  function resetQuery() {
-    Object.assign(queryForm, queryFormState);
-    queryJobList();
-  }
+function resetQuery() {
+  Object.assign(queryForm, queryFormState);
+  queryJobList();
+}
 
-  function onSearch() {
-    queryForm.pageNum = 1;
-    queryJobList();
-  }
+function onSearch() {
+  queryForm.pageNum = 1;
+  queryJobList();
+}
 
-  // 处理执行类展示 默认返回类
-  function handleJobClass(jobClass) {
-    return jobClass.split('.').pop();
-  }
+// 处理执行类展示 默认返回类
+function handleJobClass(jobClass) {
+  return jobClass.split('.').pop();
+}
 
-  // 上次处理结果展示
-  function handleExecuteResult(result) {
-    let num = 400;
-    return result ? result.substring(0, num) + (result.length > num ? ' ...' : '') : '';
-  }
+// 上次处理结果展示
+function handleExecuteResult(result) {
+  let num = 400;
+  return result ? result.substring(0, num) + (result.length > num ? ' ...' : '') : '';
+}
 
-  async function queryJobList() {
-    try {
-      tableLoading.value = true;
-      let responseModel = await jobApi.queryJob(queryForm);
-      const list = responseModel.data.list;
-      total.value = responseModel.data.total;
-      tableData.value = list;
-    } catch (e) {
-      smartSentry.captureError(e);
-    } finally {
-      tableLoading.value = false;
-    }
+async function queryJobList() {
+  try {
+    tableLoading.value = true;
+    let responseModel = await jobApi.queryJob(queryForm);
+    const list = responseModel.data.list;
+    total.value = responseModel.data.total;
+    tableData.value = list;
+  } catch (e) {
+    smartSentry.captureError(e);
+  } finally {
+    tableLoading.value = false;
   }
+}
 
-  onMounted(queryJobList);
-
-  // 更新状态
-  async function handleEnabledUpdate(checked, record) {
-    record.enabledLoading = true;
-    try {
-      let updateForm = {
-        jobId: record.jobId,
-        enabledFlag: checked,
-      };
-      await jobApi.updateJobEnabled(updateForm);
-      // 重新查询任务详情
-      let jobInfo = await queryJobInfo(record.jobId);
-      Object.assign(record, jobInfo);
-      message.success('更新成功');
-    } catch (e) {
-      record.enabledFlag = !checked;
-      smartSentry.captureError(e);
-    } finally {
-      record.enabledLoading = false;
-    }
+onMounted(queryJobList);
+const deEnabledUpdate = debounce(handleEnabledUpdate, 300)
+// 更新状态
+async function handleEnabledUpdate(checked, record) {
+  record.enabledLoading = true;
+  try {
+    let updateForm = {
+      jobId: record.jobId,
+      enabledFlag: checked,
+    };
+    await jobApi.updateJobEnabled(updateForm);
+    // 重新查询任务详情
+    let jobInfo = await queryJobInfo(record.jobId);
+    Object.assign(record, jobInfo);
+    message.success('更新成功');
+  } catch (e) {
+    record.enabledFlag = !checked;
+    smartSentry.captureError(e);
+  } finally {
+    record.enabledLoading = false;
   }
+}
 
-  // 查询任务详情
-  async function queryJobInfo(jobId) {
-    try {
-      let res = await jobApi.queryJobInfo(jobId);
-      return res.data;
-    } catch (e) {
-      smartSentry.captureError(e);
-    }
+// 查询任务详情
+async function queryJobInfo(jobId) {
+  try {
+    let res = await jobApi.queryJobInfo(jobId);
+    return res.data;
+  } catch (e) {
+    smartSentry.captureError(e);
   }
+}
 
-  // ------------------------------------ 执行记录 -------------------------------------
-  const jobLogModal = ref();
-  function openJobLogModal(jobId, name) {
-    jobLogModal.value.show(jobId, name);
-  }
+// ------------------------------------ 执行记录 -------------------------------------
+const jobLogModal = ref();
+function openJobLogModal(jobId, name) {
+  jobLogModal.value.show(jobId, name);
+}
 
-  // ------------------------------------ 删除操作 -------------------------------------
+// ------------------------------------ 删除操作 -------------------------------------
 
-  function confirmDelete(jobId, jobName) {
-    Modal.confirm({
-      title: '警告',
-      content: `确定要删除【${jobName}】任务吗?`,
-      okText: '删除',
-      okType: 'danger',
-      onOk() {
-        deleteJob(jobId);
-      },
-      cancelText: '取消',
-      onCancel() {},
-    });
-  }
+function confirmDelete(jobId, jobName) {
+  Modal.confirm({
+    title: '警告',
+    content: `确定要删除【${jobName}】任务吗?`,
+    okText: '删除',
+    okType: 'danger',
+    onOk() {
+      deleteJob(jobId);
+    },
+    cancelText: '取消',
+    onCancel() { },
+  });
+}
 
-  async function deleteJob(jobId) {
-    try {
-      SmartLoading.show();
-      await jobApi.deleteJob(jobId);
-      message.success('删除成功!');
-      queryJobList();
-    } catch (e) {
-      smartSentry.captureError(e);
-    } finally {
-      SmartLoading.hide();
-    }
+async function deleteJob(jobId) {
+  const params = {
+    jobId
   }
+  try {
+    SmartLoading.show();
+    await jobApi.deleteJob(params);
+    message.success('删除成功!');
+    queryJobList();
+  } catch (e) {
+    smartSentry.captureError(e);
+  } finally {
+    SmartLoading.hide();
+  }
+}
 
-  // ------------------------------------ 表单操作 -------------------------------------
-  const jobFormModal = ref();
+// ------------------------------------ 表单操作 -------------------------------------
+const jobFormModal = ref();
 
-  // 打开更新表单
-  function openUpdateModal(record) {
-    jobFormModal.value.openUpdateModal(record);
-  }
-  // 打开执行表单
-  function openExecuteModal(record) {
-    jobFormModal.value.openExecuteModal(record);
-  }
+// 打开更新表单
+function openUpdateModal(record) {
+  jobFormModal.value.openUpdateModal(record);
+}
+// 打开执行表单
+function openExecuteModal(record) {
+  jobFormModal.value.openExecuteModal(record);
+}
 </script>