Ver código fonte

perf(components): 优化下拉表格组件,支持搜索

shizhongming 2 anos atrás
pai
commit
74982207b6

+ 147 - 25
src/components/Form/src/smart-boot/components/SmartPulldownTable.tsx

@@ -6,6 +6,7 @@ import { Icon } from '@/components/Icon';
 
 import './style/SmartPullownTable.less';
 import { useRuleFormItem } from '@/hooks/component/useFormItem';
+import { Row, Col } from 'ant-design-vue';
 
 /**
  * 下拉表格
@@ -28,14 +29,74 @@ export default defineComponent({
     api: {
       type: Function as PropType<() => Promise<any>>,
     },
+    showSearch: propTypes.bool.def(true),
+    allowClear: propTypes.bool.def(true),
+    filterOption: {
+      type: Function as PropType<(searchValue: string, row: Recordable) => boolean>,
+    },
+    searchIgnoreCase: propTypes.bool.def(true),
+    // 弹窗高度
+    height: propTypes.number,
   },
-  emits: ['select', 'change', 'update:value'],
+  emits: ['select', 'change', 'update:value', 'visible-change'],
   setup(props, { emit, slots, attrs }) {
-    const { showField, dropdownWidth, value: valueRef } = toRefs(props);
+    const {
+      showField,
+      dropdownWidth,
+      value: valueRef,
+      showSearch: showSearchRef,
+      allowClear: allowClearRef,
+      height: heightRef,
+    } = toRefs(props);
     const pulldownRef = ref<VxePulldownInstance>();
     const dataLoadingRef = ref<boolean>(false);
     const tableDataRef = ref<Recordable[]>([]);
     const tableRef = ref();
+    const searchValueRef = ref<string>('');
+
+    const filterOptionFunction = props.filterOption
+      ? props.filterOption
+      : (searchValue: string, row: Recordable) => {
+          if (!searchValue || searchValue.trim().length === 0) {
+            return true;
+          }
+          const searchValueTrim = searchValue.trim();
+          let showValue = '';
+          if (props.showFunction) {
+            showValue = props.showFunction(row);
+          } else {
+            showValue = row[props.showField];
+          }
+          if (
+            (props.searchIgnoreCase ? showValue.toUpperCase() : showValue).includes(searchValueTrim)
+          ) {
+            return true;
+          }
+          const fieldValue: string = row[props.valueField].toString();
+          return (props.searchIgnoreCase ? fieldValue.toUpperCase() : fieldValue).includes(
+            searchValueTrim,
+          );
+        };
+    const computeTableData = computed(() => {
+      const tableData = unref(tableDataRef);
+      if (tableData.length === 0) {
+        return tableData;
+      }
+      const searchValue = unref(searchValueRef);
+      if (!searchValue) {
+        return tableData;
+      }
+      return tableData.filter((item) =>
+        filterOptionFunction(
+          props.searchIgnoreCase ? searchValue.toUpperCase() : searchValue,
+          item,
+        ),
+      );
+    });
+    /**
+     * 下拉框是否显示
+     */
+    const isVisibleRef = ref(false);
     // 选中的行
     const selectRowRef = ref<Recordable | null>(null);
     const computedSelectRowValue = computed(() => {
@@ -85,46 +146,67 @@ export default defineComponent({
      * 下拉容器样式
      */
     const computedDropdownContainerStyle = computed(() => {
-      return {
+      const style: Recordable = {
         width: unref(dropdownWidth) + 'px',
       };
+      const height = unref(heightRef);
+      if (height) {
+        style.height = `${height}px`;
+      }
+      return style;
     });
 
     watch([valueRef, tableDataRef], ([value, tableData]) => {
-      const tableInstance: VxeGridInstance = unref(tableRef)?.tableAction.getTableInstance();
       let row: Recordable | null = null;
-      if (!value) {
-        tableInstance && tableInstance.clearCurrentRow();
-        selectRowRef.value = null;
-        return;
-      }
-      const selectRows = tableData.filter(
-        (item) => item[props.valueField].toString() === value.toString(),
-      );
-      if (selectRows.length === 0) {
-        tableInstance && tableInstance.clearCurrentRow();
-      } else {
-        row = selectRows[0];
-        tableInstance && tableInstance.setCurrentRow(selectRows[0]);
+      if (value) {
+        const selectRows = tableData.filter(
+          (item) => item[props.valueField].toString() === value.toString(),
+        );
+        if (selectRows.length > 0) {
+          row = selectRows[0];
+        }
       }
       selectRowRef.value = row;
+      setCurrentRow();
     });
 
     /**
      * 显示弹窗
      */
     const handleShow = async () => {
+      isVisibleRef.value = true;
       unref(pulldownRef)?.showPanel();
       if (props.alwaysLoad) {
         await loadTableData();
       }
+      emit('visible-change', true);
+      setCurrentRow();
+    };
+
+    /**
+     * 设置当前行
+     */
+    const setCurrentRow = () => {
       nextTick(() => {
         const tableInstance: VxeGridInstance = unref(tableRef)?.tableAction.getTableInstance();
+        if (!tableInstance) {
+          return;
+        }
         const row = unref(selectRowRef);
-        row && tableInstance && tableInstance.setCurrentRow(row);
+        if (!row) {
+          tableInstance.clearCurrentRow();
+        } else {
+          tableInstance.setCurrentRow(row);
+        }
       });
     };
 
+    const handleHide = () => {
+      isVisibleRef.value = false;
+      searchValueRef.value = '';
+      emit('visible-change', false);
+    };
+
     const dropdownTableProps = {
       size: 'mini',
       ...props.tableProps,
@@ -135,6 +217,7 @@ export default defineComponent({
       onCellClick: ({ row }) => {
         changeValue(row);
         unref(pulldownRef)?.hidePanel();
+        handleHide();
       },
     };
 
@@ -143,20 +226,58 @@ export default defineComponent({
       state.value = row?.[props.valueField];
     };
 
-    const inputIconSlots = {
-      addonAfter: () => <Icon onClick={handleShow} icon="ant-design:table-outlined" />,
-      clearIcon: () => <Icon onClick={() => changeValue()} icon="ant-design:close-circle-filled" />,
+    /**
+     * 搜索框变化触发
+     * @param value
+     */
+    const handleSearchChange = (value: string) => {
+      searchValueRef.value = value;
+      setCurrentRow();
+    };
+
+    // const inputIconSlots = {
+    //   addonAfter: () => <Icon onClick={handleShow} icon="ant-design:table-outlined" />,
+    //   clearIcon: () => <Icon onClick={() => changeValue()} icon="ant-design:close-circle-filled" />,
+    // };
+    //
+    // const selectSlots = {
+    //   suffixIcon: () => <Icon onClick={handleShow} icon="ant-design:table-outlined" />,
+    // };
+
+    const handleUpdateValue = () => {
+      changeValue();
+      handleShow();
     };
 
     const pulldownSlots = {
       default: () => (
-        <a-input onFocus={handleShow} value={unref(computedSelectRowValue)} {...attrs}>
-          {inputIconSlots}
-        </a-input>
+        // <div>
+        //   <a-input onFocus={handleShow} value={unref(computedSelectRowValue)} {...attrs}>
+        //     {inputIconSlots}
+        //   </a-input>
+        //   <span>abc</span>
+        // </div>
+        <Row type="flex" class={unref(isVisibleRef) ? 'smart-pulldown-open' : ''}>
+          <Col class="select">
+            <a-select
+              allowClear={unref(allowClearRef)}
+              showSearch={unref(showSearchRef)}
+              onFocus={handleShow}
+              open={false}
+              onUpdate:value={handleUpdateValue}
+              searchValue={unref(searchValueRef)}
+              onSearch={handleSearchChange}
+              value={unref(computedSelectRowValue)}
+            ></a-select>
+          </Col>
+          <Col class="suffix-icon">
+            <Icon onClick={handleShow} icon="ant-design:table-outlined" />
+          </Col>
+        </Row>
       ),
       dropdown: () => (
         <div style={unref(computedDropdownContainerStyle)}>
-          <SmartTable data={unref(tableDataRef)} ref={tableRef} {...dropdownTableProps} />
+          <SmartTable data={unref(computeTableData)} ref={tableRef} {...dropdownTableProps} />
         </div>
       ),
       ...slots,
@@ -164,6 +285,7 @@ export default defineComponent({
     return () => (
       <vxe-pulldown
         ref={pulldownRef}
+        onHidePanel={handleHide}
         {...attrs}
         popup-class-name="smart-pullown-table"
         class-name="smart-pullown-input"

+ 27 - 0
src/components/Form/src/smart-boot/components/style/SmartPullownTable.less

@@ -15,6 +15,33 @@
   }
 }
 
+@suffix-icon-width: 30px;
+
 .smart-pullown-input {
   width: 100%;
+
+  .select {
+    width: calc(100% - @suffix-icon-width);
+  }
+
+  .suffix-icon {
+    width: @suffix-icon-width;
+    border: 1px solid #d9d9d9;
+    border-left: 0;
+    border-radius: 0 4px 4px 0;
+    background-color: rgb(0 0 0 / 2%);
+    text-align: center;
+    cursor: pointer;
+  }
+
+  .ant-select-selector {
+    border-top-right-radius: 0 !important;
+    border-bottom-right-radius: 0 !important;
+  }
+
+  .smart-pulldown-open {
+    .ant-select-selection-item {
+      color: rgb(0 0 0 / 25%);
+    }
+  }
 }