feat: 新组件ReVxeTableBar搭配vxe-table使用 (#1087)
				
					
				
			| 
		 Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 439 B  | 
| 
		 Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B  | 
| 
		 Before Width: | Height: | Size: 161 B After Width: | Height: | Size: 161 B  | 
| 
		 Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B  | 
| 
		 Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 840 B  | 
@ -18,11 +18,11 @@ import {
 | 
				
			|||||||
  getKeyList
 | 
					  getKeyList
 | 
				
			||||||
} from "@pureadmin/utils";
 | 
					} from "@pureadmin/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import DragIcon from "./svg/drag.svg?component";
 | 
					import DragIcon from "@/assets/table-bar/drag.svg?component";
 | 
				
			||||||
import ExpandIcon from "./svg/expand.svg?component";
 | 
					import ExpandIcon from "@/assets/table-bar/expand.svg?component";
 | 
				
			||||||
import RefreshIcon from "./svg/refresh.svg?component";
 | 
					import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
 | 
				
			||||||
import SettingIcon from "./svg/settings.svg?component";
 | 
					import SettingIcon from "@/assets/table-bar/settings.svg?component";
 | 
				
			||||||
import CollapseIcon from "./svg/collapse.svg?component";
 | 
					import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const props = {
 | 
					const props = {
 | 
				
			||||||
  /** 头部最左边的标题 */
 | 
					  /** 头部最左边的标题 */
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								src/components/ReVxeTableBar/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					import vxeTableBar from "./src/bar";
 | 
				
			||||||
 | 
					import { withInstall } from "@pureadmin/utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** 配合 `vxe-table` 实现快速便捷的表格操作 */
 | 
				
			||||||
 | 
					export const VxeTableBar = withInstall(vxeTableBar);
 | 
				
			||||||
							
								
								
									
										362
									
								
								src/components/ReVxeTableBar/src/bar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,362 @@
 | 
				
			|||||||
 | 
					import Sortable from "sortablejs";
 | 
				
			||||||
 | 
					import { transformI18n } from "@/plugins/i18n";
 | 
				
			||||||
 | 
					import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
				
			||||||
 | 
					import { delay, cloneDeep, getKeyList } from "@pureadmin/utils";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  type PropType,
 | 
				
			||||||
 | 
					  ref,
 | 
				
			||||||
 | 
					  unref,
 | 
				
			||||||
 | 
					  computed,
 | 
				
			||||||
 | 
					  nextTick,
 | 
				
			||||||
 | 
					  defineComponent,
 | 
				
			||||||
 | 
					  getCurrentInstance
 | 
				
			||||||
 | 
					} from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DragIcon from "@/assets/table-bar/drag.svg?component";
 | 
				
			||||||
 | 
					import ExpandIcon from "@/assets/table-bar/expand.svg?component";
 | 
				
			||||||
 | 
					import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
 | 
				
			||||||
 | 
					import SettingIcon from "@/assets/table-bar/settings.svg?component";
 | 
				
			||||||
 | 
					import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = {
 | 
				
			||||||
 | 
					  /** 头部最左边的标题 */
 | 
				
			||||||
 | 
					  title: {
 | 
				
			||||||
 | 
					    type: String,
 | 
				
			||||||
 | 
					    default: "列表"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  vxeTableRef: {
 | 
				
			||||||
 | 
					    type: Object as PropType<any>
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  /** 需要展示的列 */
 | 
				
			||||||
 | 
					  columns: {
 | 
				
			||||||
 | 
					    type: Array as PropType<any>,
 | 
				
			||||||
 | 
					    default: () => []
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  /** 是否为树列表 */
 | 
				
			||||||
 | 
					  tree: {
 | 
				
			||||||
 | 
					    type: Boolean,
 | 
				
			||||||
 | 
					    default: false
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  isExpandAll: {
 | 
				
			||||||
 | 
					    type: Boolean,
 | 
				
			||||||
 | 
					    default: true
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  tableKey: {
 | 
				
			||||||
 | 
					    type: [String, Number] as PropType<string | number>,
 | 
				
			||||||
 | 
					    default: "0"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default defineComponent({
 | 
				
			||||||
 | 
					  name: "VxeTableBar",
 | 
				
			||||||
 | 
					  props,
 | 
				
			||||||
 | 
					  emits: ["refresh"],
 | 
				
			||||||
 | 
					  setup(props, { emit, slots, attrs }) {
 | 
				
			||||||
 | 
					    const size = ref("small");
 | 
				
			||||||
 | 
					    const loading = ref(false);
 | 
				
			||||||
 | 
					    const checkAll = ref(true);
 | 
				
			||||||
 | 
					    const isIndeterminate = ref(false);
 | 
				
			||||||
 | 
					    const instance = getCurrentInstance()!;
 | 
				
			||||||
 | 
					    const isExpandAll = ref(props.isExpandAll);
 | 
				
			||||||
 | 
					    let checkColumnList = getKeyList(cloneDeep(props?.columns), "title");
 | 
				
			||||||
 | 
					    const checkedColumns = ref(getKeyList(cloneDeep(props?.columns), "title"));
 | 
				
			||||||
 | 
					    const dynamicColumns = ref(cloneDeep(props?.columns));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const getDropdownItemStyle = computed(() => {
 | 
				
			||||||
 | 
					      return s => {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          background:
 | 
				
			||||||
 | 
					            s === size.value ? useEpThemeStoreHook().epThemeColor : "",
 | 
				
			||||||
 | 
					          color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const iconClass = computed(() => {
 | 
				
			||||||
 | 
					      return [
 | 
				
			||||||
 | 
					        "text-black",
 | 
				
			||||||
 | 
					        "dark:text-white",
 | 
				
			||||||
 | 
					        "duration-100",
 | 
				
			||||||
 | 
					        "hover:!text-primary",
 | 
				
			||||||
 | 
					        "cursor-pointer",
 | 
				
			||||||
 | 
					        "outline-none"
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const topClass = computed(() => {
 | 
				
			||||||
 | 
					      return [
 | 
				
			||||||
 | 
					        "flex",
 | 
				
			||||||
 | 
					        "justify-between",
 | 
				
			||||||
 | 
					        "pt-[3px]",
 | 
				
			||||||
 | 
					        "px-[11px]",
 | 
				
			||||||
 | 
					        "border-b-[1px]",
 | 
				
			||||||
 | 
					        "border-solid",
 | 
				
			||||||
 | 
					        "border-[#dcdfe6]",
 | 
				
			||||||
 | 
					        "dark:border-[#303030]"
 | 
				
			||||||
 | 
					      ];
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onReFresh() {
 | 
				
			||||||
 | 
					      loading.value = true;
 | 
				
			||||||
 | 
					      emit("refresh");
 | 
				
			||||||
 | 
					      delay(500).then(() => (loading.value = false));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function onExpand() {
 | 
				
			||||||
 | 
					      isExpandAll.value = !isExpandAll.value;
 | 
				
			||||||
 | 
					      isExpandAll.value
 | 
				
			||||||
 | 
					        ? props.vxeTableRef.setAllTreeExpand(true)
 | 
				
			||||||
 | 
					        : props.vxeTableRef.clearTreeExpand();
 | 
				
			||||||
 | 
					      props.vxeTableRef.refreshColumn();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function reloadColumn() {
 | 
				
			||||||
 | 
					      const curCheckedColumns = cloneDeep(dynamicColumns.value).filter(item =>
 | 
				
			||||||
 | 
					        checkedColumns.value.includes(item.title)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      props.vxeTableRef.reloadColumn(curCheckedColumns);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function handleCheckAllChange(val: boolean) {
 | 
				
			||||||
 | 
					      checkedColumns.value = val ? checkColumnList : [];
 | 
				
			||||||
 | 
					      isIndeterminate.value = false;
 | 
				
			||||||
 | 
					      reloadColumn();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function handleCheckedColumnsChange(value: string[]) {
 | 
				
			||||||
 | 
					      checkedColumns.value = value;
 | 
				
			||||||
 | 
					      const checkedCount = value.length;
 | 
				
			||||||
 | 
					      checkAll.value = checkedCount === checkColumnList.length;
 | 
				
			||||||
 | 
					      isIndeterminate.value =
 | 
				
			||||||
 | 
					        checkedCount > 0 && checkedCount < checkColumnList.length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function onReset() {
 | 
				
			||||||
 | 
					      checkAll.value = true;
 | 
				
			||||||
 | 
					      isIndeterminate.value = false;
 | 
				
			||||||
 | 
					      dynamicColumns.value = cloneDeep(props?.columns);
 | 
				
			||||||
 | 
					      checkColumnList = [];
 | 
				
			||||||
 | 
					      checkColumnList = await getKeyList(cloneDeep(props?.columns), "title");
 | 
				
			||||||
 | 
					      checkedColumns.value = getKeyList(cloneDeep(props?.columns), "title");
 | 
				
			||||||
 | 
					      props.vxeTableRef.refreshColumn();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function changeSize(curSize: string) {
 | 
				
			||||||
 | 
					      size.value = curSize;
 | 
				
			||||||
 | 
					      props.vxeTableRef.refreshColumn();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const dropdown = {
 | 
				
			||||||
 | 
					      dropdown: () => (
 | 
				
			||||||
 | 
					        <el-dropdown-menu class="translation">
 | 
				
			||||||
 | 
					          <el-dropdown-item
 | 
				
			||||||
 | 
					            style={getDropdownItemStyle.value("medium")}
 | 
				
			||||||
 | 
					            onClick={() => changeSize("medium")}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            宽松
 | 
				
			||||||
 | 
					          </el-dropdown-item>
 | 
				
			||||||
 | 
					          <el-dropdown-item
 | 
				
			||||||
 | 
					            style={getDropdownItemStyle.value("small")}
 | 
				
			||||||
 | 
					            onClick={() => changeSize("small")}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            默认
 | 
				
			||||||
 | 
					          </el-dropdown-item>
 | 
				
			||||||
 | 
					          <el-dropdown-item
 | 
				
			||||||
 | 
					            style={getDropdownItemStyle.value("mini")}
 | 
				
			||||||
 | 
					            onClick={() => changeSize("mini")}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            紧凑
 | 
				
			||||||
 | 
					          </el-dropdown-item>
 | 
				
			||||||
 | 
					        </el-dropdown-menu>
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 列展示拖拽排序 */
 | 
				
			||||||
 | 
					    const rowDrop = (event: { preventDefault: () => void }) => {
 | 
				
			||||||
 | 
					      event.preventDefault();
 | 
				
			||||||
 | 
					      nextTick(() => {
 | 
				
			||||||
 | 
					        const wrapper: HTMLElement = (
 | 
				
			||||||
 | 
					          instance?.proxy?.$refs[`VxeGroupRef${unref(props.tableKey)}`] as any
 | 
				
			||||||
 | 
					        ).$el.firstElementChild;
 | 
				
			||||||
 | 
					        Sortable.create(wrapper, {
 | 
				
			||||||
 | 
					          animation: 300,
 | 
				
			||||||
 | 
					          handle: ".drag-btn",
 | 
				
			||||||
 | 
					          onEnd: ({ newIndex, oldIndex, item }) => {
 | 
				
			||||||
 | 
					            const targetThElem = item;
 | 
				
			||||||
 | 
					            const wrapperElem = targetThElem.parentNode as HTMLElement;
 | 
				
			||||||
 | 
					            const oldColumn = dynamicColumns.value[oldIndex];
 | 
				
			||||||
 | 
					            const newColumn = dynamicColumns.value[newIndex];
 | 
				
			||||||
 | 
					            if (oldColumn?.fixed || newColumn?.fixed) {
 | 
				
			||||||
 | 
					              // 当前列存在fixed属性 则不可拖拽
 | 
				
			||||||
 | 
					              const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
 | 
				
			||||||
 | 
					              if (newIndex > oldIndex) {
 | 
				
			||||||
 | 
					                wrapperElem.insertBefore(targetThElem, oldThElem);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                wrapperElem.insertBefore(
 | 
				
			||||||
 | 
					                  targetThElem,
 | 
				
			||||||
 | 
					                  oldThElem ? oldThElem.nextElementSibling : oldThElem
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
 | 
				
			||||||
 | 
					            dynamicColumns.value.splice(newIndex, 0, currentRow);
 | 
				
			||||||
 | 
					            reloadColumn();
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const isFixedColumn = (title: string) => {
 | 
				
			||||||
 | 
					      return dynamicColumns.value.filter(
 | 
				
			||||||
 | 
					        item => transformI18n(item.title) === transformI18n(title)
 | 
				
			||||||
 | 
					      )[0].fixed
 | 
				
			||||||
 | 
					        ? true
 | 
				
			||||||
 | 
					        : false;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const rendTippyProps = (content: string) => {
 | 
				
			||||||
 | 
					      // https://vue-tippy.netlify.app/props
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        content,
 | 
				
			||||||
 | 
					        offset: [0, 18],
 | 
				
			||||||
 | 
					        duration: [300, 0],
 | 
				
			||||||
 | 
					        followCursor: true,
 | 
				
			||||||
 | 
					        hideOnClick: "toggle"
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const reference = {
 | 
				
			||||||
 | 
					      reference: () => (
 | 
				
			||||||
 | 
					        <SettingIcon
 | 
				
			||||||
 | 
					          class={["w-[16px]", iconClass.value]}
 | 
				
			||||||
 | 
					          v-tippy={rendTippyProps("列设置")}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return () => (
 | 
				
			||||||
 | 
					      <>
 | 
				
			||||||
 | 
					        <div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
 | 
				
			||||||
 | 
					          <div class="flex justify-between w-full h-[60px] p-4">
 | 
				
			||||||
 | 
					            {slots?.title ? (
 | 
				
			||||||
 | 
					              slots.title()
 | 
				
			||||||
 | 
					            ) : (
 | 
				
			||||||
 | 
					              <p class="font-bold truncate">{props.title}</p>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            <div class="flex items-center justify-around">
 | 
				
			||||||
 | 
					              {slots?.buttons ? (
 | 
				
			||||||
 | 
					                <div class="flex mr-4">{slots.buttons()}</div>
 | 
				
			||||||
 | 
					              ) : null}
 | 
				
			||||||
 | 
					              {props.tree ? (
 | 
				
			||||||
 | 
					                <>
 | 
				
			||||||
 | 
					                  <ExpandIcon
 | 
				
			||||||
 | 
					                    class={["w-[16px]", iconClass.value]}
 | 
				
			||||||
 | 
					                    style={{
 | 
				
			||||||
 | 
					                      transform: isExpandAll.value ? "none" : "rotate(-90deg)"
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    v-tippy={rendTippyProps(
 | 
				
			||||||
 | 
					                      isExpandAll.value ? "折叠" : "展开"
 | 
				
			||||||
 | 
					                    )}
 | 
				
			||||||
 | 
					                    onClick={() => onExpand()}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  <el-divider direction="vertical" />
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					              ) : null}
 | 
				
			||||||
 | 
					              <RefreshIcon
 | 
				
			||||||
 | 
					                class={[
 | 
				
			||||||
 | 
					                  "w-[16px]",
 | 
				
			||||||
 | 
					                  iconClass.value,
 | 
				
			||||||
 | 
					                  loading.value ? "animate-spin" : ""
 | 
				
			||||||
 | 
					                ]}
 | 
				
			||||||
 | 
					                v-tippy={rendTippyProps("刷新")}
 | 
				
			||||||
 | 
					                onClick={() => onReFresh()}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <el-divider direction="vertical" />
 | 
				
			||||||
 | 
					              <el-dropdown
 | 
				
			||||||
 | 
					                v-slots={dropdown}
 | 
				
			||||||
 | 
					                trigger="click"
 | 
				
			||||||
 | 
					                v-tippy={rendTippyProps("密度")}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <CollapseIcon class={["w-[16px]", iconClass.value]} />
 | 
				
			||||||
 | 
					              </el-dropdown>
 | 
				
			||||||
 | 
					              <el-divider direction="vertical" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              <el-popover
 | 
				
			||||||
 | 
					                v-slots={reference}
 | 
				
			||||||
 | 
					                placement="bottom-start"
 | 
				
			||||||
 | 
					                popper-style={{ padding: 0 }}
 | 
				
			||||||
 | 
					                width="200"
 | 
				
			||||||
 | 
					                trigger="click"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
 | 
					                <div class={[topClass.value]}>
 | 
				
			||||||
 | 
					                  <el-checkbox
 | 
				
			||||||
 | 
					                    class="!-mr-1"
 | 
				
			||||||
 | 
					                    label="列展示"
 | 
				
			||||||
 | 
					                    v-model={checkAll.value}
 | 
				
			||||||
 | 
					                    indeterminate={isIndeterminate.value}
 | 
				
			||||||
 | 
					                    onChange={value => handleCheckAllChange(value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  <el-button type="primary" link onClick={() => onReset()}>
 | 
				
			||||||
 | 
					                    重置
 | 
				
			||||||
 | 
					                  </el-button>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div class="pt-[6px] pl-[11px]">
 | 
				
			||||||
 | 
					                  <el-scrollbar max-height="36vh">
 | 
				
			||||||
 | 
					                    <el-checkbox-group
 | 
				
			||||||
 | 
					                      ref={`VxeGroupRef${unref(props.tableKey)}`}
 | 
				
			||||||
 | 
					                      modelValue={checkedColumns.value}
 | 
				
			||||||
 | 
					                      onChange={value => handleCheckedColumnsChange(value)}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                      <el-space
 | 
				
			||||||
 | 
					                        direction="vertical"
 | 
				
			||||||
 | 
					                        alignment="flex-start"
 | 
				
			||||||
 | 
					                        size={0}
 | 
				
			||||||
 | 
					                      >
 | 
				
			||||||
 | 
					                        {checkColumnList.map((item, index) => {
 | 
				
			||||||
 | 
					                          return (
 | 
				
			||||||
 | 
					                            <div class="flex items-center">
 | 
				
			||||||
 | 
					                              <DragIcon
 | 
				
			||||||
 | 
					                                class={[
 | 
				
			||||||
 | 
					                                  "drag-btn w-[16px] mr-2",
 | 
				
			||||||
 | 
					                                  isFixedColumn(item)
 | 
				
			||||||
 | 
					                                    ? "!cursor-no-drop"
 | 
				
			||||||
 | 
					                                    : "!cursor-grab"
 | 
				
			||||||
 | 
					                                ]}
 | 
				
			||||||
 | 
					                                onMouseenter={(event: {
 | 
				
			||||||
 | 
					                                  preventDefault: () => void;
 | 
				
			||||||
 | 
					                                }) => rowDrop(event)}
 | 
				
			||||||
 | 
					                              />
 | 
				
			||||||
 | 
					                              <el-checkbox
 | 
				
			||||||
 | 
					                                key={index}
 | 
				
			||||||
 | 
					                                label={item}
 | 
				
			||||||
 | 
					                                value={item}
 | 
				
			||||||
 | 
					                                onChange={reloadColumn}
 | 
				
			||||||
 | 
					                              >
 | 
				
			||||||
 | 
					                                <span
 | 
				
			||||||
 | 
					                                  title={transformI18n(item)}
 | 
				
			||||||
 | 
					                                  class="inline-block w-[120px] truncate hover:text-text_color_primary"
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
 | 
					                                  {transformI18n(item)}
 | 
				
			||||||
 | 
					                                </span>
 | 
				
			||||||
 | 
					                              </el-checkbox>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                          );
 | 
				
			||||||
 | 
					                        })}
 | 
				
			||||||
 | 
					                      </el-space>
 | 
				
			||||||
 | 
					                    </el-checkbox-group>
 | 
				
			||||||
 | 
					                  </el-scrollbar>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              </el-popover>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          {slots.default({
 | 
				
			||||||
 | 
					            size: size.value,
 | 
				
			||||||
 | 
					            dynamicColumns: dynamicColumns.value
 | 
				
			||||||
 | 
					          })}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -20,11 +20,12 @@ import {
 | 
				
			|||||||
  // 可选组件
 | 
					  // 可选组件
 | 
				
			||||||
  Icon,
 | 
					  Icon,
 | 
				
			||||||
  Column,
 | 
					  Column,
 | 
				
			||||||
 | 
					  Grid,
 | 
				
			||||||
 | 
					  Pager,
 | 
				
			||||||
 | 
					  Select,
 | 
				
			||||||
  // Colgroup,
 | 
					  // Colgroup,
 | 
				
			||||||
  // Grid,
 | 
					 | 
				
			||||||
  // Tooltip,
 | 
					  // Tooltip,
 | 
				
			||||||
  // Toolbar,
 | 
					  // Toolbar,
 | 
				
			||||||
  // Pager,
 | 
					 | 
				
			||||||
  // Form,
 | 
					  // Form,
 | 
				
			||||||
  // FormItem,
 | 
					  // FormItem,
 | 
				
			||||||
  // FormGather,
 | 
					  // FormGather,
 | 
				
			||||||
@ -35,7 +36,6 @@ import {
 | 
				
			|||||||
  // RadioButton,
 | 
					  // RadioButton,
 | 
				
			||||||
  // Switch,
 | 
					  // Switch,
 | 
				
			||||||
  // Input,
 | 
					  // Input,
 | 
				
			||||||
  // Select,
 | 
					 | 
				
			||||||
  // Optgroup,
 | 
					  // Optgroup,
 | 
				
			||||||
  // Option,
 | 
					  // Option,
 | 
				
			||||||
  // Textarea,
 | 
					  // Textarea,
 | 
				
			||||||
@ -76,11 +76,12 @@ export function useVxeTable(app: App) {
 | 
				
			|||||||
    // 可选组件
 | 
					    // 可选组件
 | 
				
			||||||
    .use(Icon)
 | 
					    .use(Icon)
 | 
				
			||||||
    .use(Column)
 | 
					    .use(Column)
 | 
				
			||||||
 | 
					    .use(Grid)
 | 
				
			||||||
 | 
					    .use(Pager)
 | 
				
			||||||
 | 
					    .use(Select)
 | 
				
			||||||
    // .use(Colgroup)
 | 
					    // .use(Colgroup)
 | 
				
			||||||
    // .use(Grid)
 | 
					 | 
				
			||||||
    // .use(Tooltip)
 | 
					    // .use(Tooltip)
 | 
				
			||||||
    // .use(Toolbar)
 | 
					    // .use(Toolbar)
 | 
				
			||||||
    // .use(Pager)
 | 
					 | 
				
			||||||
    // .use(Form)
 | 
					    // .use(Form)
 | 
				
			||||||
    // .use(FormItem)
 | 
					    // .use(FormItem)
 | 
				
			||||||
    // .use(FormGather)
 | 
					    // .use(FormGather)
 | 
				
			||||||
@ -91,7 +92,6 @@ export function useVxeTable(app: App) {
 | 
				
			|||||||
    // .use(RadioButton)
 | 
					    // .use(RadioButton)
 | 
				
			||||||
    // .use(Switch)
 | 
					    // .use(Switch)
 | 
				
			||||||
    // .use(Input)
 | 
					    // .use(Input)
 | 
				
			||||||
    // .use(Select)
 | 
					 | 
				
			||||||
    // .use(Optgroup)
 | 
					    // .use(Optgroup)
 | 
				
			||||||
    // .use(Option)
 | 
					    // .use(Option)
 | 
				
			||||||
    // .use(Textarea)
 | 
					    // .use(Textarea)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import List from "./list.vue";
 | 
					import List from "./list.vue";
 | 
				
			||||||
import TreeList from "./treeList.vue";
 | 
					import TreeList from "./treeList.vue";
 | 
				
			||||||
 | 
					import PageList from "./pageList.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const rendContent = (val: string) =>
 | 
					const rendContent = (val: string) =>
 | 
				
			||||||
  `代码位置:src/views/table/virtual/${val}.vue`;
 | 
					  `代码位置:src/views/table/virtual/${val}.vue`;
 | 
				
			||||||
@ -8,13 +9,19 @@ export const list = [
 | 
				
			|||||||
  {
 | 
					  {
 | 
				
			||||||
    key: "list",
 | 
					    key: "list",
 | 
				
			||||||
    content: rendContent("list"),
 | 
					    content: rendContent("list"),
 | 
				
			||||||
    title: "虚拟列表",
 | 
					    title: "虚拟表格",
 | 
				
			||||||
    component: List
 | 
					    component: List
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    key: "treeList",
 | 
					    key: "treeList",
 | 
				
			||||||
    content: rendContent("treeList"),
 | 
					    content: rendContent("treeList"),
 | 
				
			||||||
    title: "虚拟树",
 | 
					    title: "虚拟树形表格",
 | 
				
			||||||
    component: TreeList
 | 
					    component: TreeList
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: "pageList",
 | 
				
			||||||
 | 
					    content: rendContent("pageList"),
 | 
				
			||||||
 | 
					    title: "分页表格",
 | 
				
			||||||
 | 
					    component: PageList
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,20 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref } from "vue";
 | 
					import { ref } from "vue";
 | 
				
			||||||
 | 
					import { VxeTableBar } from "@/components/ReVxeTableBar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const vxeTableRef = ref();
 | 
				
			||||||
 | 
					const loading = ref(true);
 | 
				
			||||||
const tableData = ref([]);
 | 
					const tableData = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
setTimeout(() => {
 | 
					const columns = [
 | 
				
			||||||
 | 
					  { type: "seq", field: "seq", title: "序号", width: 100 },
 | 
				
			||||||
 | 
					  { field: "name", title: "名称", sortable: true },
 | 
				
			||||||
 | 
					  { field: "role", title: "角色" },
 | 
				
			||||||
 | 
					  { field: "sex", title: "性别" }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function onSearch() {
 | 
				
			||||||
 | 
					  loading.value = true;
 | 
				
			||||||
  // 模拟数据
 | 
					  // 模拟数据
 | 
				
			||||||
  const mockList = [];
 | 
					  const mockList = [];
 | 
				
			||||||
  for (let index = 0; index < 500; index++) {
 | 
					  for (let index = 0; index < 500; index++) {
 | 
				
			||||||
@ -15,21 +26,33 @@ setTimeout(() => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  tableData.value = mockList;
 | 
					  tableData.value = mockList;
 | 
				
			||||||
}, 100);
 | 
					  setTimeout(() => {
 | 
				
			||||||
 | 
					    loading.value = false;
 | 
				
			||||||
 | 
					  }, 500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onSearch();
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <vxe-table
 | 
					  <VxeTableBar
 | 
				
			||||||
    border
 | 
					    :vxeTableRef="vxeTableRef"
 | 
				
			||||||
    show-overflow
 | 
					    :columns="columns"
 | 
				
			||||||
    height="500"
 | 
					    title="虚拟表格"
 | 
				
			||||||
    :row-config="{ isHover: true }"
 | 
					    @refresh="onSearch"
 | 
				
			||||||
    :data="tableData"
 | 
					 | 
				
			||||||
    :scroll-y="{ enabled: true }"
 | 
					 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <vxe-column type="seq" title="序号" width="100" />
 | 
					    <template v-slot="{ size, dynamicColumns }">
 | 
				
			||||||
    <vxe-column field="name" title="名称" sortable />
 | 
					      <vxe-grid
 | 
				
			||||||
    <vxe-column field="role" title="角色" />
 | 
					        ref="vxeTableRef"
 | 
				
			||||||
    <vxe-column field="sex" title="性别" />
 | 
					        v-loading="loading"
 | 
				
			||||||
  </vxe-table>
 | 
					        show-overflow
 | 
				
			||||||
 | 
					        height="500"
 | 
				
			||||||
 | 
					        :size="size"
 | 
				
			||||||
 | 
					        :row-config="{ isHover: true }"
 | 
				
			||||||
 | 
					        :scroll-y="{ enabled: true }"
 | 
				
			||||||
 | 
					        :columns="dynamicColumns"
 | 
				
			||||||
 | 
					        :data="tableData"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					  </VxeTableBar>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										86
									
								
								src/views/table/virtual/pageList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					import { ref, reactive, computed } from "vue";
 | 
				
			||||||
 | 
					import { VxeTableBar } from "@/components/ReVxeTableBar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const vxeTableRef = ref();
 | 
				
			||||||
 | 
					const loading = ref(true);
 | 
				
			||||||
 | 
					const tableData = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getTableHeight = computed(() => {
 | 
				
			||||||
 | 
					  return (size: string) => {
 | 
				
			||||||
 | 
					    switch (size) {
 | 
				
			||||||
 | 
					      case "medium":
 | 
				
			||||||
 | 
					        return 531;
 | 
				
			||||||
 | 
					      case "small":
 | 
				
			||||||
 | 
					        return 482;
 | 
				
			||||||
 | 
					      case "mini":
 | 
				
			||||||
 | 
					        return 433;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pagerConfig = reactive({
 | 
				
			||||||
 | 
					  total: 0,
 | 
				
			||||||
 | 
					  currentPage: 1,
 | 
				
			||||||
 | 
					  pageSize: 10,
 | 
				
			||||||
 | 
					  pageSizes: [5, 10, 15, 20]
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const columns = [
 | 
				
			||||||
 | 
					  { type: "seq", field: "seq", title: "序号", width: 100 },
 | 
				
			||||||
 | 
					  { field: "name", title: "名称", sortable: true },
 | 
				
			||||||
 | 
					  { field: "role", title: "角色" },
 | 
				
			||||||
 | 
					  { field: "sex", title: "性别" }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function onSearch() {
 | 
				
			||||||
 | 
					  loading.value = true;
 | 
				
			||||||
 | 
					  // 模拟数据
 | 
				
			||||||
 | 
					  const mockList = [];
 | 
				
			||||||
 | 
					  for (let index = 0; index < 10; index++) {
 | 
				
			||||||
 | 
					    mockList.push({
 | 
				
			||||||
 | 
					      id: index,
 | 
				
			||||||
 | 
					      name: "Test" + index,
 | 
				
			||||||
 | 
					      role: "Developer",
 | 
				
			||||||
 | 
					      sex: "男"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  pagerConfig.total = 20;
 | 
				
			||||||
 | 
					  tableData.value = mockList;
 | 
				
			||||||
 | 
					  setTimeout(() => {
 | 
				
			||||||
 | 
					    loading.value = false;
 | 
				
			||||||
 | 
					  }, 500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handlePageChange = ({ currentPage, pageSize }) => {
 | 
				
			||||||
 | 
					  pagerConfig.currentPage = currentPage;
 | 
				
			||||||
 | 
					  pagerConfig.pageSize = pageSize;
 | 
				
			||||||
 | 
					  onSearch();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onSearch();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <VxeTableBar
 | 
				
			||||||
 | 
					    :vxeTableRef="vxeTableRef"
 | 
				
			||||||
 | 
					    :columns="columns"
 | 
				
			||||||
 | 
					    title="分页表格"
 | 
				
			||||||
 | 
					    @refresh="onSearch"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <template v-slot="{ size, dynamicColumns }">
 | 
				
			||||||
 | 
					      <vxe-grid
 | 
				
			||||||
 | 
					        ref="vxeTableRef"
 | 
				
			||||||
 | 
					        v-loading="loading"
 | 
				
			||||||
 | 
					        show-overflow
 | 
				
			||||||
 | 
					        :height="getTableHeight(size)"
 | 
				
			||||||
 | 
					        :size="size"
 | 
				
			||||||
 | 
					        :column-config="{ resizable: true }"
 | 
				
			||||||
 | 
					        :columns="dynamicColumns"
 | 
				
			||||||
 | 
					        :pagerConfig="pagerConfig"
 | 
				
			||||||
 | 
					        :data="tableData"
 | 
				
			||||||
 | 
					        @page-change="handlePageChange"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					  </VxeTableBar>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
@ -1,56 +1,51 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref } from "vue";
 | 
					import { ref } from "vue";
 | 
				
			||||||
import treeList from "./tree.json";
 | 
					import treeList from "./tree.json";
 | 
				
			||||||
 | 
					import { VxeTableBar } from "@/components/ReVxeTableBar";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tableRef = ref();
 | 
					const vxeTableRef = ref();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const loading = ref(false);
 | 
					const loading = ref(true);
 | 
				
			||||||
const tableData = ref([]);
 | 
					const tableData = ref([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const loadList = () => {
 | 
					const columns = [
 | 
				
			||||||
 | 
					  { type: "seq", field: "seq", title: "序号", width: 200, treeNode: true },
 | 
				
			||||||
 | 
					  { field: "id", title: "Id" },
 | 
				
			||||||
 | 
					  { field: "name", title: "地点" }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function onSearch() {
 | 
				
			||||||
  loading.value = true;
 | 
					  loading.value = true;
 | 
				
			||||||
 | 
					  tableData.value = treeList;
 | 
				
			||||||
  setTimeout(() => {
 | 
					  setTimeout(() => {
 | 
				
			||||||
    tableData.value = treeList;
 | 
					 | 
				
			||||||
    loading.value = false;
 | 
					    loading.value = false;
 | 
				
			||||||
  }, 200);
 | 
					  }, 500);
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const expandAllEvent = () => {
 | 
					onSearch();
 | 
				
			||||||
  const $table = tableRef.value;
 | 
					 | 
				
			||||||
  if ($table) {
 | 
					 | 
				
			||||||
    $table.setAllTreeExpand(true);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const claseExpandEvent = () => {
 | 
					 | 
				
			||||||
  const $table = tableRef.value;
 | 
					 | 
				
			||||||
  if ($table) {
 | 
					 | 
				
			||||||
    $table.clearTreeExpand();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
loadList();
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <div>
 | 
					  <VxeTableBar
 | 
				
			||||||
    <div class="mb-4">
 | 
					    tree
 | 
				
			||||||
      <el-button @click="expandAllEvent">展开所有</el-button>
 | 
					    title="虚拟树形表格"
 | 
				
			||||||
      <el-button @click="claseExpandEvent">收起所有</el-button>
 | 
					    :isExpandAll="false"
 | 
				
			||||||
    </div>
 | 
					    :vxeTableRef="vxeTableRef"
 | 
				
			||||||
 | 
					    :columns="columns"
 | 
				
			||||||
    <vxe-table
 | 
					    @refresh="onSearch"
 | 
				
			||||||
      ref="tableRef"
 | 
					  >
 | 
				
			||||||
      show-overflow
 | 
					    <template v-slot="{ size, dynamicColumns }">
 | 
				
			||||||
      height="500"
 | 
					      <vxe-grid
 | 
				
			||||||
      :loading="loading"
 | 
					        ref="vxeTableRef"
 | 
				
			||||||
      :tree-config="{ transform: true }"
 | 
					        v-loading="loading"
 | 
				
			||||||
      :scroll-y="{ enabled: true, gt: 20 }"
 | 
					        show-overflow
 | 
				
			||||||
      :data="tableData"
 | 
					        height="500"
 | 
				
			||||||
    >
 | 
					        :size="size"
 | 
				
			||||||
      <vxe-column type="seq" title="序号" width="200" tree-node />
 | 
					        :tree-config="{ transform: true }"
 | 
				
			||||||
      <vxe-column field="id" title="Id" />
 | 
					        :scroll-y="{ enabled: true, gt: 20 }"
 | 
				
			||||||
      <vxe-column field="name" title="地点" />
 | 
					        :columns="dynamicColumns"
 | 
				
			||||||
    </vxe-table>
 | 
					        :data="tableData"
 | 
				
			||||||
  </div>
 | 
					      />
 | 
				
			||||||
 | 
					    </template>
 | 
				
			||||||
 | 
					  </VxeTableBar>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
				
			|||||||