Jelajahi Sumber

更新项目名称为“有声合成平台”,添加多个依赖项以支持新功能,包括@element-plus/icons-vue、@tailwindcss/postcss、@types/lodash-es等;调整package.json和pnpm-lock.yaml以反映新的依赖关系;优化tsconfig.json以支持新的类型定义;更新样式变量文件以添加命名空间;重构全局类型定义以增强类型安全性;在存储工具中添加常量以管理存储键。

xbx 1 Minggu lalu
induk
melakukan
17e17dfd8b
41 mengubah file dengan 12092 tambahan dan 951 penghapusan
  1. 5487 124
      package-lock.json
  2. 4 1
      package.json
  3. 21 0
      pnpm-lock.yaml
  4. 6 0
      src/components/Descriptions/index.ts
  5. 215 0
      src/components/Descriptions/src/Descriptions.vue
  6. 34 0
      src/components/Descriptions/src/DescriptionsItemLabel.vue
  7. 37 0
      src/components/Descriptions/src/types/index.ts
  8. 28 0
      src/components/Descriptions/src/useDescription.ts
  9. 163 0
      src/components/Table/components/TableAction.vue
  10. 439 0
      src/components/Table/components/columnSetting.vue
  11. 3 0
      src/components/Table/components/index.ts
  12. 17 0
      src/components/Table/components/redoSetting.vue
  13. 41 0
      src/components/Table/components/ztTableFooter.vue
  14. 69 0
      src/components/Table/components/ztTableHeader.vue
  15. 47 0
      src/components/Table/components/ztTableHeaderHelp.vue
  16. 46 0
      src/components/Table/components/ztTableHeaderTitle.vue
  17. 114 0
      src/components/Table/components/ztTableImg.vue
  18. 53 0
      src/components/Table/components/ztTableSetting.vue
  19. 216 0
      src/components/Table/hooks/useColumns.ts
  20. 21 0
      src/components/Table/hooks/useLoading.ts
  21. 68 0
      src/components/Table/hooks/usePagination.ts
  22. 123 0
      src/components/Table/hooks/useRender.ts
  23. 115 0
      src/components/Table/hooks/useRowSelect.ts
  24. 145 0
      src/components/Table/hooks/useTable.ts
  25. 19 0
      src/components/Table/hooks/useTableContext.ts
  26. 270 0
      src/components/Table/hooks/useTableData.ts
  27. 6 0
      src/components/Table/index.ts
  28. 79 0
      src/components/Table/src/const.ts
  29. 174 0
      src/components/Table/src/props.ts
  30. 346 0
      src/components/Table/src/ztTable.vue
  31. 17 0
      src/hooks/web/useDesign.ts
  32. 22 0
      src/hooks/web/useEmitt.ts
  33. 1 0
      src/styles/variables.scss
  34. 0 1
      src/types/components.d.ts
  35. 90 4
      src/types/global.d.ts
  36. 292 0
      src/types/table.d.ts
  37. 43 0
      src/utils/drag.ts
  38. 40 0
      src/utils/permission.ts
  39. 11 0
      src/utils/storage.ts
  40. 7 2
      tsconfig.json
  41. 3163 819
      yarn.lock

File diff ditekan karena terlalu besar
+ 5487 - 124
package-lock.json


+ 4 - 1
package.json

@@ -25,10 +25,12 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
     "@tailwindcss/postcss": "^4.1.11",
+    "@types/lodash-es": "^4.17.12",
     "@vueuse/core": "^13.5.0",
     "axios": "^1.9.0",
     "dayjs": "^1.11.13",
     "element-plus": "^2.10.3",
+    "lodash-es": "^4.17.21",
     "mitt": "^3.0.1",
     "motion-v": "^1.5.0",
     "pinia": "^3.0.3",
@@ -39,7 +41,8 @@
     "svg-sprite-loader": "^6.0.11",
     "tailwindcss": "^4.1.11",
     "vue": "^3.5.13",
-    "vue-router": "^4.5.1"
+    "vue-router": "^4.5.1",
+    "vuedraggable": "^2.24.3"
   },
   "devDependencies": {
     "@rspack/cli": "^1.3.10",

+ 21 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       '@tailwindcss/postcss':
         specifier: ^4.1.11
         version: 4.1.11
+      '@types/lodash-es':
+        specifier: ^4.17.12
+        version: 4.17.12
       '@vueuse/core':
         specifier: ^13.5.0
         version: 13.5.0(vue@3.5.14(typescript@5.8.3))
@@ -26,6 +29,9 @@ importers:
       element-plus:
         specifier: ^2.10.3
         version: 2.10.3(vue@3.5.14(typescript@5.8.3))
+      lodash-es:
+        specifier: ^4.17.21
+        version: 4.17.21
       mitt:
         specifier: ^3.0.1
         version: 3.0.1
@@ -59,6 +65,9 @@ importers:
       vue-router:
         specifier: ^4.5.1
         version: 4.5.1(vue@3.5.14(typescript@5.8.3))
+      vuedraggable:
+        specifier: ^2.24.3
+        version: 2.24.3
     devDependencies:
       '@rspack/cli':
         specifier: ^1.3.10
@@ -3268,6 +3277,9 @@ packages:
   sockjs@0.3.24:
     resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==}
 
+  sortablejs@1.10.2:
+    resolution: {integrity: sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==}
+
   source-map-js@1.2.1:
     resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
     engines: {node: '>=0.10.0'}
@@ -3723,6 +3735,9 @@ packages:
       typescript:
         optional: true
 
+  vuedraggable@2.24.3:
+    resolution: {integrity: sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==}
+
   watchpack@2.4.2:
     resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==}
     engines: {node: '>=10.13.0'}
@@ -7289,6 +7304,8 @@ snapshots:
       uuid: 8.3.2
       websocket-driver: 0.7.4
 
+  sortablejs@1.10.2: {}
+
   source-map-js@1.2.1: {}
 
   source-map-resolve@0.5.3:
@@ -7814,6 +7831,10 @@ snapshots:
     optionalDependencies:
       typescript: 5.8.3
 
+  vuedraggable@2.24.3:
+    dependencies:
+      sortablejs: 1.10.2
+
   watchpack@2.4.2:
     dependencies:
       glob-to-regexp: 0.4.1

+ 6 - 0
src/components/Descriptions/index.ts

@@ -0,0 +1,6 @@
+import Descriptions from './src/Descriptions.vue'
+import DescriptionsItemLabel from './src/DescriptionsItemLabel.vue'
+import { useDescription } from './src/useDescription'
+
+
+export { Descriptions, DescriptionsItemLabel, useDescription }

+ 215 - 0
src/components/Descriptions/src/Descriptions.vue

@@ -0,0 +1,215 @@
+<script lang="tsx">
+import { ElCollapseTransition, ElTooltip, ElRow, ElCol } from 'element-plus'
+import { useDesign } from '@/hooks/web/useDesign'
+import { propTypes } from '@/utils/propTypes'
+import { ref, unref, PropType, computed, defineComponent } from 'vue'
+import { QuestionFilled, ArrowDown, ArrowUp } from '@element-plus/icons-vue'
+
+import { DescriptionsSchema, DescriptionsProps, DescInstance } from './types'
+// 移除 Icon 导入
+import { get } from 'lodash-es'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('descriptions')
+
+const defaultData = '-'
+
+export default defineComponent({
+  name: 'Descriptions',
+  props: {
+    title: { type: String, default: '' },
+    message: { type: String, default: '' },
+    collapse: { type: Boolean, default: true },
+    border: { type: Boolean, default: true },
+    column: { type: Number, default: 2 },
+    size: { type: String, default: 'default', validator: (val: string) => ['large', 'default', 'small'].includes(val) },
+    direction: { type: String, default: 'horizontal', validator: (val: string) => ['horizontal', 'vertical'].includes(val) },
+    extra: { type: String, default: '' },
+    schema: {
+      type: Array as PropType<DescriptionsSchema[]>,
+      default: () => []
+    },
+    data: {
+      type: Object as PropType<Record<string, any>>,
+      default: () => ({})
+    }
+  },
+  setup(props, { attrs, emit }) {
+    const propsRef = ref<Partial<DescriptionsProps> | null>(null)
+
+    const getMergeProps = computed(() => {
+      return {
+        ...props,
+        ...(unref(propsRef) as Record<string, any>)
+      } as DescriptionsProps
+    })
+
+    const getBindValue = computed((): any => {
+      const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
+      const obj = { ...attrs, ...unref(getMergeProps) } as Record<string, any>
+      for (const key in obj) {
+        if (delArr.indexOf(key) !== -1) {
+          delete obj[key]
+        }
+      }
+
+      return obj
+    })
+
+    const getBindItemValue = (item: DescriptionsSchema) => {
+      const delArr: string[] = ['field']
+      const obj = { ...item } as Record<string, any>
+      for (const key in obj) {
+        if (delArr.indexOf(key) !== -1) {
+          delete obj[key]
+        }
+      }
+      return {
+        labelClassName: `${prefixCls}-label`,
+        ...obj
+      }
+    }
+
+    function setDescProps(descProps: Partial<DescriptionsProps>): void {
+      // Keep the last setDrawerProps
+      propsRef.value = { ...(unref(propsRef) as Record<string, any>), ...descProps } as Record<string, any>
+    }
+
+    // 折叠
+    const show = ref(true)
+
+    const toggleClick = () => {
+      if (props.collapse) {
+        show.value = !unref(show)
+      }
+    }
+    const methods: DescInstance = {
+      setDescProps
+    }
+
+    emit('register', methods)
+
+    return () => {
+      return (
+        <div
+          class={[
+            prefixCls,
+            'bg-[var(--el-color-white)] dark:bg-[var(--el-bg-color)] dark:border-[var(--el-border-color)] dark:border-1px'
+          ]}
+        >
+          {unref(getMergeProps).title ? (
+            <div
+              class={[
+                `${prefixCls}-header`,
+                'relative h-50px flex justify-between items-center layout-border__bottom px-10px cursor-pointer'
+              ]}
+              onClick={toggleClick}
+            >
+              <div class={[`${prefixCls}-header__title`, 'relative font-18px font-bold ml-10px']}>
+                <div class="flex items-center">
+                  {unref(getMergeProps).title}
+                  {unref(getMergeProps).message ? (
+                    <ElTooltip content={unref(getMergeProps).message} placement="right">
+                      <el-icon class="ml-5px" size={14}><QuestionFilled /></el-icon>
+                    </ElTooltip>
+                  ) : null}
+                </div>
+              </div>
+              {unref(getMergeProps).collapse ? (
+                <el-icon>{show.value ? <ArrowDown /> : <ArrowUp />}</el-icon>
+              ) : null}
+            </div>
+          ) : null}
+
+          <ElCollapseTransition>
+            <div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
+              <ElRow
+                gutter={0}
+                {...unref(getBindValue)}
+                class="outline-1px outline-[var(--el-border-color-lighter)] outline-solid"
+              >
+                {unref(getMergeProps).schema.map((item) => {
+                  return (
+                    <ElCol
+                      key={item.field}
+                      span={item.span || 24 / unref(getMergeProps).column!}
+                      class="flex items-stretch"
+                    >
+                      {unref(getMergeProps).direction === 'horizontal' ? (
+                        <div class="flex items-stretch bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
+                          <div
+                            {...getBindItemValue(item)}
+                            class={[`w-${item.width}px`,'text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-r-1px border-r-[var(--el-border-color-lighter)] border-r-solid']}
+                            style={{
+                              width: `${item.width}px`
+                            }}
+                          >
+                            {item.label}
+                          </div>
+                          <div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
+                            {item.slots?.default
+                              ? item.slots?.default(unref(getMergeProps).data)
+                              : get(unref(getMergeProps).data, item.field) ?? defaultData}
+                          </div>
+                        </div>
+                      ) : (
+                        <div class="bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
+                          <div
+                            {...getBindItemValue(item)}
+                            class="text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-b-1px border-b-[var(--el-border-color-lighter)] border-b-solid"
+                          >
+                            {item.label}
+                          </div>
+                          <div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
+                            {item.slots?.default
+                              ? item.slots?.default(unref(getMergeProps).data)
+                              : get(unref(getMergeProps).data, item.field) ?? defaultData}
+                          </div>
+                        </div>
+                      )}
+                    </ElCol>
+                  )
+                })}
+              </ElRow>
+            </div>
+          </ElCollapseTransition>
+        </div>
+      )
+    }
+  }
+})
+</script>
+
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-descriptions;
+/* @prefix-cls: ~'@{adminNamespace}-descriptions'; */
+
+:deep(.#{$elNamespace}-descriptions__header) {
+  display: none !important;
+}
+
+.#{prefix-cls}-header {
+  &__title {
+    &::after {
+      position: absolute;
+      top: 3px;
+      left: -10px;
+      width: 4px;
+      height: 70%;
+      background: var(--el-color-primary);
+      content: '';
+    }
+  }
+}
+
+:deep(.#{prefix-cls}-label) {
+  width: 150px !important;
+}
+
+// .@{prefix-cls}-content {
+//   :deep(.@{elNamespace}-descriptions__cell) {
+//     width: 0;
+//   }
+// }
+</style>

+ 34 - 0
src/components/Descriptions/src/DescriptionsItemLabel.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+import { Component } from 'vue'
+
+// 修改props,接收Vue组件而非字符串
+const props = defineProps({
+  label: {
+    type: String,
+    required: true
+  },
+  icon: {
+    type: Object as () => Component,
+    required: false
+  }
+})
+</script>
+
+<template>
+  <div class="cell-item">
+    <el-icon v-if="icon" :size="18" style="vertical-align: middle; margin-right: 4px;">
+      <component :is="icon" />
+    </el-icon>
+    {{ label }}
+  </div>
+</template>
+
+<style scoped lang="scss">
+.cell-item {
+  display: inline;
+}
+
+.cell-item::after {
+  content: ':';
+}
+</style>

+ 37 - 0
src/components/Descriptions/src/types/index.ts

@@ -0,0 +1,37 @@
+export interface DescriptionsSchema {
+  span?: number // 占多少分
+  field: string // 字段名
+  label?: string // label名
+  width?: string | number
+  minWidth?: string | number
+  align?: 'left' | 'center' | 'right'
+  labelAlign?: 'left' | 'center' | 'right'
+  className?: string
+  labelClassName?: string
+  slots?: {
+    default?: (...args: any[]) => JSX.Element | null
+    label?: (...args: any[]) => JSX.Element | null
+  }
+}
+
+export interface DescriptionsProps {
+  schema: DescriptionsSchema[],
+  title?: string,
+  message?: string,
+  collapse?: boolean,
+  border?: boolean,
+  column?: number,
+  size?: 'large' | 'default' | 'small',
+  direction: 'horizontal' | 'vertical',
+  extra?: string,
+  data: Recordable
+
+}
+
+export type Register = (descInstance: DescInstance) => void
+
+export interface DescInstance {
+  setDescProps(descProps: Partial<DescriptionsProps>): void
+}
+
+export type UseDescReturnType = [Register, DescInstance]

+ 28 - 0
src/components/Descriptions/src/useDescription.ts

@@ -0,0 +1,28 @@
+import { isProdMode } from '@/utils'
+import type { DescriptionsProps, DescInstance, UseDescReturnType } from './types'
+import { ref, getCurrentInstance, unref } from 'vue'
+
+export function useDescription(props?: Partial<DescriptionsProps>): UseDescReturnType {
+  if (!getCurrentInstance()) {
+    throw new Error('useDescription() can only be used inside setup() or functional components!')
+  }
+  const desc = ref<Nullable<DescInstance>>(null)
+  const loaded = ref(false)
+
+  function register(instance: DescInstance) {
+    if (unref(loaded) && isProdMode()) {
+      return
+    }
+    desc.value = instance
+    props && instance.setDescProps(props)
+    loaded.value = true
+  }
+
+  const methods: DescInstance = {
+    setDescProps: (descProps: Partial<DescriptionsProps>): void => {
+      unref(desc)?.setDescProps(descProps)
+    }
+  }
+
+  return [register, methods]
+}

+ 163 - 0
src/components/Table/components/TableAction.vue

@@ -0,0 +1,163 @@
+<template>
+  <div :class="[prefixCls, getAlign]">
+    <template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
+      <XTextButton type="primary" v-if="!action.popConfirm" v-bind="action" />
+      <el-popconfirm v-bind="action" :title="action.text" v-else>
+        <template #reference>
+          <XTextButton type="primary" :title="action.title" />
+        </template>
+      </el-popconfirm>
+
+      <el-divider
+        direction="vertical"
+        class="action-divider"
+        v-if="divider && index < getActions.length - 1"
+      />
+    </template>
+    <el-dropdown :hide-on-click="false" v-if="dropDownActions && dropDownActions.length > 0" class="custom-drop">
+      <XTextButton type="primary" title="更多" postIcon="ep:arrow-down" />
+
+      <template #dropdown>
+        <el-dropdown-menu>
+          <el-dropdown-item v-for="action in getDropdownList" :key="action.label">
+            <XTextButton type="primary" v-if="!action.popConfirm" v-bind="action" />
+            <el-popconfirm v-bind="action" :title="action.text" v-else>
+              <template #reference>
+                <XTextButton type="primary" :title="action.title" />
+              </template>
+            </el-popconfirm>
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </template>
+    </el-dropdown>
+  </div>
+</template>
+<script lang="ts" setup>
+import { useDesign } from '@/hooks/web/useDesign'
+import { ACTION_COLUMN_FLAG } from '@/components/Table/src/const'
+import { useTableContext } from '../hooks/useTableContext'
+import { ActionItem } from '../src/props'
+import { isBoolean, isFunction, isString, isArray } from '@/utils/is'
+// 尝试使用相对路径导入
+import { checkPermi } from '@/utils/permission'
+
+defineOptions({ name: 'TableAction' })
+
+const props = withDefaults(
+  defineProps<{
+    actions: ActionItem[] | null
+    dropDownActions?: ActionItem[] | null
+    divider?: boolean
+  }>(),
+  {
+    actions: null,
+    dropDownActions: null,
+    divider: true
+  }
+)
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('table-action')
+
+let table: any = {}
+
+table = useTableContext()
+
+function isIfShow(action: ActionItem): boolean {
+  const ifShow = action.ifShow
+
+  let isIfShow = true
+
+  if (isBoolean(ifShow)) {
+    isIfShow = ifShow
+  }
+  if (isFunction(ifShow)) {
+    isIfShow = ifShow(action)
+  }
+  return isIfShow
+}
+
+//获取action
+const getActions = computed(() => {
+  return (toRaw(props.actions) || [])
+    .filter((item) => {
+      return (
+        isIfShow(item) &&
+        checkPermi(item.auth ? (isArray(item.auth) ? item.auth : [item.auth]) : [''])
+      )
+    })
+    .map((action) => {
+      const { popConfirm } = action
+      return {
+        ...action,
+        title: action.label,
+        ...(popConfirm || {}),
+        onConfirm: popConfirm?.confirm,
+        onCancel: popConfirm?.cancel,
+        enable: !!popConfirm
+      }
+    })
+})
+
+//获取下拉action
+const getDropdownList = computed(() => {
+  return (toRaw(props.dropDownActions) || [])
+    .filter((item) => {
+      return (
+        isIfShow(item) &&
+        checkPermi(item.auth ? (isArray(item.auth) ? item.auth : [item.auth]) : [''])
+      )
+    })
+    .map((action) => {
+      const { label, popConfirm } = action
+      return {
+        ...action,
+        title: action.label,
+        ...popConfirm,
+        onConfirm: popConfirm?.confirm,
+        onCancel: popConfirm?.cancel
+      }
+    })
+})
+
+
+const getAlign = computed(() => {
+  const columns = (table as any)?.getColumns?.() || []
+  const actionColumn = columns.find((item: any) => item.filed === ACTION_COLUMN_FLAG)
+  return actionColumn?.align ?? 'center'
+})
+</script>
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-table-action;
+.#{$prefix-cls} {
+  display: flex;
+  align-items: center;
+
+  .action-divider {
+    display: table;
+  }
+  .el-divider {
+    margin: 0 3px;
+  }
+  &.left {
+    justify-content: flex-start;
+  }
+
+  &.center {
+    justify-content: center;
+  }
+
+  &.right {
+    justify-content: flex-end;
+  }
+}
+
+.custom-drop{
+  :deep(.el-button--primary){
+    &:focus-visible{
+      outline: none;
+    }
+  } 
+}
+</style>

+ 439 - 0
src/components/Table/components/columnSetting.vue

@@ -0,0 +1,439 @@
+<template>
+  <ElTooltip placement="top" content="自定义列">
+    <el-icon :size="20" @click="dialogVisible = true"><Operation /></el-icon>
+  </ElTooltip>
+  <ElDialog v-model="dialogVisible" title="自定义列" width="840">
+    <div class="customized-column-content zt-column-content">
+      <div class="column-area">
+        <div class="column-select-panel">
+          <div class="panel-title-line !dark:bg-[var(--el-bg-color)]"> 可添加的列 </div>
+          <div class="panel-content">
+            <div class="column-select-area column-select-area0">
+              <div class="column-group clearfix">
+                <div class="column-items clearfix">
+                  <div class="column-item check-group">
+                    <el-checkbox
+                      v-model="checkedadAll"
+                      :indeterminate="isIndeterminate"
+                      @change="onCheckAllChange"
+                    >
+                      全选
+                    </el-checkbox>
+                  </div>
+                  <el-checkbox-group v-model="checkedList" @change="onCheckChange">
+                    <div
+                      class="column-item"
+                      v-for="(columnItem, index) in originColumns"
+                      :key="columnItem.prop"
+                    >
+                      <el-checkbox
+                        :key="columnItem.prop"
+                        @change="
+                          (e: any) => {
+                            checkOne(e, columnItem, index)
+                          }
+                        "
+                        :label="columnItem.label"
+                        :value="columnItem.prop"
+                      />
+                    </div>
+                  </el-checkbox-group>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="column-selected">
+          <div class="panel-title-line !dark:bg-[var(--el-bg-color)]">
+            <span
+              >已选{{ dragColumns.length }}列&nbsp;
+              <el-tooltip class="item" effect="dark" content="拖动可以排序" placement="top">
+                <i class="el-icon-question"></i>
+              </el-tooltip>
+            </span>
+
+            <span class="delete-all" @click="clearAll">清空全部</span>
+          </div>
+          <div class="panel-selected" ref="draged">
+            <draggable
+              class="page-prop-area drag-area"
+              v-model="dragColumns"
+              item-key="prop"
+              :animation="200"
+              ghost-class="draggable-ghost"
+              :force-fallback="true"
+              @change="handleColumnChange"
+            >
+              <template #item="{ element, index }">
+                <div
+                  class="selected-column-item select-none dark:border-solid !dark:bg-[var(--el-bg-color)] dark:border dark:border-zinc-400"
+                >
+                  <div class="column-title">
+                    <el-icon><Operation /></el-icon>
+                    <p>{{ element.label }}</p>
+                  </div>
+                  <el-icon class="el-icon-close close" @click="removeColumn(element, index)"
+                    ><CircleClose
+                  /></el-icon>
+                </div>
+              </template>
+            </draggable>
+          </div>
+        </div>
+      </div>
+    </div>
+    <template #footer>
+      <XButton title="取消" @click="resetColumn" />
+      <XButton title="应用自定义列" type="primary" @click="applyColumn" />
+    </template>
+  </ElDialog>
+</template>
+<script lang="ts" setup>
+import { nextTick } from 'vue'
+import { ElDialog } from 'element-plus'
+import { useTableContext } from '../hooks/useTableContext'
+import draggable from 'vuedraggable'
+import { cloneDeep, differenceBy } from 'lodash-es'
+import { ColumnProps } from '@/types/table'
+import { Operation, CircleClose } from '@element-plus/icons-vue'
+import { onDragFalg } from '@/utils/drag'
+import { STORAGE_KEY, localStorage } from '@/utils/storage'
+
+// 定义一个通用的 Recordable 类型用于不确定的对象
+type Recordable<T = any> = Record<string, T>;
+
+defineOptions({ name: 'ColumnSetting' })
+
+const table = useTableContext()
+
+// 不再需要 useCache 钩子
+// const { wsCache } = useCache()
+
+//是否初始化
+let inited = false
+// 是否当前的setColums触发的
+let isSetColumnsFromThis = false
+// 是否当前组件触发的setProps
+let isSetPropsFromThis = false
+
+//不确定状态
+const isIndeterminate = ref(false)
+//是否全选
+const checkedadAll = ref(false)
+//原始列
+const originColumns = ref<ColumnProps[]>([])
+//拖拽后的值
+const dragColumns = ref<ColumnProps[]>([])
+//选中列的值列表
+const checkedList = ref<string[]>([])
+
+//其他列
+const otherColumns = ref<ColumnProps[]>([])
+
+const dialogVisible = ref(false)
+
+const getValues = computed(() => {
+  return unref(table?.getBindValues) || {}
+})
+
+watchEffect(() => {
+  //const columns = table.getColumns()
+
+  setTimeout(() => {
+    if (isSetColumnsFromThis) {
+      isSetColumnsFromThis = false
+    } else {
+      initTable()
+    }
+  }, 0)
+})
+
+watch(dialogVisible, (val) => {
+  if (val) {
+    initTable()
+  }
+})
+
+//全选事件
+const onCheckAllChange = (val: string) => {
+  isSetPropsFromThis = true
+  isSetColumnsFromThis = true
+  if (val) {
+    checkedList.value = unref(originColumns).map((item: any) => {
+      return item.prop as string
+    })
+    /* originColumns.value.forEach((item) => {
+      item.defaultHidden = false
+    }) */
+    dragColumns.value = cloneDeep(unref(originColumns))
+  } else {
+    checkedList.value = []
+    dragColumns.value = []
+    isIndeterminate.value = false
+    /* originColumns.value.forEach((item) => {
+      item.defaultHidden = true
+    }) */
+  }
+}
+
+//组改变
+const onCheckChange = (val: string) => {
+  let checkedCount = val.length
+  checkedadAll.value = checkedCount === unref(originColumns).length
+  isIndeterminate.value = checkedCount > 0 && checkedCount < unref(originColumns).length
+}
+
+//单选某一项
+const checkOne = (val: boolean, column: ColumnProps, index: number) => {
+  isSetPropsFromThis = true
+  isSetColumnsFromThis = true
+  if (val) {
+    dragColumns.value.push(column)
+    //originColumns.value[index].defaultHidden = false
+  } else {
+    
+    let idx = unref(dragColumns).findIndex((r: any) => r.prop === column.prop)
+    if (idx > -1) {
+      dragColumns.value.splice(idx, 1)
+    }
+    //originColumns.value[index].defaultHidden = true
+  }
+}
+
+//重制列表
+const resetColumn = () => {
+  dialogVisible.value = false
+  isSetPropsFromThis = false
+  isSetColumnsFromThis = false
+  setTimeout(() => {
+    initTable()
+  }, 0)
+}
+
+//应用列表
+const applyColumn = () => {
+  isSetPropsFromThis = true
+  isSetColumnsFromThis = true
+  //吧otherColumn 填回去 因为固定列不参与自定义,最后要赋值回去,不然回显有bug
+  unref(otherColumns)
+    .filter((r: any) => r.type)
+    .reverse()
+    .forEach((item: any) => {
+      dragColumns.value.unshift(item)
+    })
+
+  /* unref(otherColumns)
+    .filter((r) => !r.type)
+    .forEach((item) => {
+      dragColumns.value.push(item)
+    }) */
+
+  //找到所有没有勾选的,加default
+
+  let allColumns: Record<string, any> = localStorage.get(STORAGE_KEY.COLUMNS) ?? {}
+  allColumns[getValues.value.tableKey] = dragColumns.value
+  localStorage.set(STORAGE_KEY.COLUMNS, allColumns)
+  //table.setColumns(dragColumns.value)
+  dialogVisible.value = false
+  table.setLocalColumns(dragColumns.value)
+  
+}
+
+//移除某一项
+const removeColumn = (column: any, index: number) => {
+  dragColumns.value.splice(index, 1)
+  let idx = unref(checkedList).findIndex((r) => r === column.prop)
+  if (idx > -1) {
+    checkedList.value.splice(idx, 1)
+  }
+  /* let idx2 = originColumns.value.findIndex((r) => r.prop === column.prop)
+  originColumns.value[idx2].defaultHidden = true */
+
+  if (!unref(checkedList).length) {
+    isIndeterminate.value = false
+    checkedadAll.value = false
+  }
+}
+
+async function initTable() {
+  //初始化
+  otherColumns.value = []
+  originColumns.value = []
+  checkedList.value = []
+  let dragColumn: any[] = []
+
+  //获取左侧所有的cloumn,固定的
+  let column = table
+    .getCacheColumns()
+    .map((column: any) => {
+      if (column.type || column.prop == 'action') {
+        otherColumns.value.push(column)
+      } else {
+        return column
+      }
+    })
+    .filter((column: any) => {
+      return column
+    })
+
+  originColumns.value = [...column]
+
+  //用来排序显示的列表要去掉fixed的 序号/多选/和操作还有没权限的
+  //获取当前显示的去掉序号等
+  
+  await nextTick()
+  
+  let curColumn = unref(table.getViewColumns).filter((column: any) => !column.type && column.prop != 'action')
+  curColumn.forEach((item: any) => {
+    checkedList.value.push(item.prop)
+  })
+  
+  dragColumns.value = [...curColumn]
+  //渲染是否全部/部分选中
+  checkedadAll.value = checkedList.value.length === unref(originColumns).length
+  isIndeterminate.value =
+    checkedList.value.length > 0 && checkedList.value.length < unref(originColumns).length
+}
+
+//请空
+function clearAll() {
+  checkedList.value = []
+  dragColumns.value = []
+  checkedadAll.value = false
+  isIndeterminate.value = false
+}
+
+// 拖拽改变
+const handleColumnChange = (e: any) => {
+  let arr = onDragFalg(e, unref(dragColumns))
+  if (arr) dragColumns.value = arr
+}
+</script>
+<style scoped lang="scss">
+.column-area {
+  height: 445px;
+
+  .ad-d-flex {
+    display: flex;
+    align-items: center;
+  }
+
+  .panel-title-line {
+    background-color: #f9fafd;
+    border: #e4e9ed 1px solid;
+    box-sizing: border-box;
+    line-height: 22px;
+    padding: 8px 12px;
+  }
+
+  .column-selected {
+    width: 246px;
+    float: left;
+    height: 445px;
+    padding-bottom: 9px;
+
+    .panel-selected {
+      border: #e4e9ed 1px solid;
+      border-top: 0;
+      box-sizing: border-box;
+      height: 395px;
+      padding: 0 7px 12px 7px;
+      overflow-y: auto;
+
+      .selected-column-item {
+        width: 100%;
+        height: auto;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        background: #f8f8f8;
+        border-radius: 4px;
+        margin-top: 8px;
+        line-height: 18px;
+        padding: 5px 12px;
+
+        .column-title {
+          display: flex;
+          align-items: center;
+        }
+
+        .close {
+          cursor: pointer;
+        }
+
+        p {
+          padding: 3px 5px;
+          margin: 0;
+          cursor: move;
+        }
+      }
+    }
+
+    .delete-all {
+      cursor: pointer;
+      font-size: 14px;
+      color: #338aff;
+      float: right;
+    }
+  }
+
+  .column-select-panel {
+    width: 500px;
+    float: left;
+    margin-right: 16px;
+    height: 445px;
+
+    .panel-content {
+      border: #e4e9ed 1px solid;
+      border-top: 0;
+      box-sizing: border-box;
+      height: 395px;
+      padding: 0;
+      overflow: hidden;
+
+      .column-select-area {
+        height: 395px;
+        overflow-y: auto;
+        width: 100%;
+
+        .column-group {
+          padding: 0 16px;
+
+          .column-items {
+            margin-bottom: 4px;
+          }
+
+          .check-group {
+            margin-right: 10px;
+          }
+
+          .group-title {
+            cursor: pointer;
+            line-height: 46px;
+            font-size: 16px;
+            color: #333;
+            position: relative;
+          }
+
+          .column-item {
+            margin-top: 10px;
+            width: 208px;
+            float: left;
+            min-height: 25px;
+
+            &:nth-child(2n) {
+              margin-right: 10px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+.selected-column-item {
+  .anticon {
+    cursor: pointer;
+  }
+}
+</style>

+ 3 - 0
src/components/Table/components/index.ts

@@ -0,0 +1,3 @@
+import ZtTableHeader from "./ztTableHeader.vue";
+import ztTableHeaderHelp from "./ztTableHeaderHelp.vue";
+export { ZtTableHeader,ztTableHeaderHelp }

+ 17 - 0
src/components/Table/components/redoSetting.vue

@@ -0,0 +1,17 @@
+<template>
+  <ElTooltip placement="top" content="刷新列表">
+    <el-icon :size="20" @click="redo"><Refresh /></el-icon>
+  </ElTooltip>
+</template>
+<script lang="ts" setup>
+import { useTableContext } from '../hooks/useTableContext'
+import { Refresh } from '@element-plus/icons-vue'
+
+defineOptions({ name: 'RedoSetting' })
+
+const table = useTableContext()
+
+function redo() {
+  table.reload()
+}
+</script>

+ 41 - 0
src/components/Table/components/ztTableFooter.vue

@@ -0,0 +1,41 @@
+
+
+<template>
+  <div class="zt-table-footer">
+    <Descriptions @register="register" />
+  </div>
+</template>
+<script lang="ts" setup>
+import { Descriptions , useDescription } from '@/components/Descriptions';
+import { propTypes } from '@/utils/propTypes'
+import { DescriptionsSchema, DescriptionsProps, DescInstance } from '@/components/Descriptions/src/types'
+defineOptions({ name: 'BasicTableFooter' })
+
+const props = defineProps({
+  summaryData: {
+    type: Object
+  },
+  schema: {
+    type: Array as PropType<DescriptionsSchema[]>
+  }
+})
+
+const curData = ref()
+
+watchEffect(() => {
+
+  curData.value = props.summaryData
+})
+
+const [register] = useDescription({
+  title: '汇总信息',
+  column: 3,
+  data: curData,
+  schema: props.schema
+})
+
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 69 - 0
src/components/Table/components/ztTableHeader.vue

@@ -0,0 +1,69 @@
+<template>
+  <div class="w-full" :class="`${prefixCls}__header`">
+    <div class="flex items-center">
+      <slot name="tableTitle" v-if="$slots.tableTitle"></slot>
+      <ZtTableHeaderTitle
+        :helpMessage="titleHelpMessage"
+        :title="title"
+        v-if="!$slots.tableTitle && title"
+      />
+      <div :class="`${prefixCls}__toolbar`">
+        <slot name="toolbar"></slot>
+        <ElDivider direction="vertical" v-if="$slots.toolbar && showTableSetting"  class="setting-vertical"/>
+        <ZtTableSetting :setting="tableSetting" v-if="showTableSetting" />
+        <!--  <TableSettingComponent :setting="tableSetting" v-if="showTableSetting" @columns-change="handleColumnChange" /> -->
+      </div>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup>
+import { useDesign } from '@/hooks/web/useDesign'
+import ZtTableHeaderTitle from './ztTableHeaderTitle.vue'
+import { TableSetting } from '@/types/table'
+import ZtTableSetting from './ztTableSetting.vue'
+import { ElDivider } from 'element-plus'
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('table')
+
+defineOptions({
+  name: 'ZtTableHeader',
+  inheritAttrs: false
+})
+
+const props = withDefaults(
+  defineProps<{
+    title?: string | ((data: Recordable) => string) //标题
+    tableSetting?: TableSetting // 设置显示的按钮
+    titleHelpMessage?: string | string[]
+    showTableSetting?: boolean
+  }>(),
+  {
+    title: '',
+    tableSetting: undefined,
+    titleHelpMessage: '',
+    showTableSetting: true
+  }
+)
+
+
+</script>
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-table;
+
+.#{$prefix-cls}__header {
+  background-color: var(--el-table-bg-color);
+  padding-bottom: 8px;
+}
+
+.#{$prefix-cls}__toolbar {
+  display: flex;
+  flex: 1;
+  align-items: center;
+  justify-content: flex-end;
+  .setting-vertical{
+    margin: 0 10px;
+  }
+}
+</style>

+ 47 - 0
src/components/Table/components/ztTableHeaderHelp.vue

@@ -0,0 +1,47 @@
+<template>
+  <div :class="`${prefixCls}__head-cell`">
+    <span>{{ title }}</span>
+    <BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" placement="top"/>
+  </div>
+</template>
+<script lang="ts" setup>
+import { BasicHelp } from '@/components/helpMessage'
+import { useDesign } from '@/hooks/web/useDesign'
+
+const props = withDefaults(
+  defineProps<{
+    title?: string | ((data: Recordable) => string) //标题
+    helpMessage?: string | string[]
+  }>(),
+  {
+    title: '',
+    helpMessage: ''
+  }
+)
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('table')
+
+defineOptions({
+  name: 'ZtTableHeaderHelp',
+  inheritAttrs: false
+})
+</script>
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-table__head-cell;
+
+.#{$prefix-cls} {
+  display: flex;
+  position: relative;
+  justify-content: center;
+  align-items: center;
+  padding-left: 7px;
+  color: currentColor;
+
+  font-weight: 500;
+  line-height: 24px;
+  cursor: pointer;
+  user-select: none;
+}
+</style>

+ 46 - 0
src/components/Table/components/ztTableHeaderTitle.vue

@@ -0,0 +1,46 @@
+<template>
+  <div :class="`${prefixCls}__title`">
+    <span>{{ title }}</span>
+    <BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
+  </div>
+</template>
+<script lang="ts" setup>
+import { BasicHelp } from '@/components/helpMessage'
+import { useDesign } from '@/hooks/web/useDesign'
+
+const props = withDefaults(
+  defineProps<{
+    title?: string | ((data: Recordable) => string) //标题
+    helpMessage?: string | string[]
+  }>(),
+  {
+    title: '',
+    helpMessage: ''
+  }
+)
+
+const { getPrefixCls } = useDesign()
+
+const prefixCls = getPrefixCls('table')
+
+defineOptions({
+  name: 'ZtTableHeaderTitle',
+  inheritAttrs: false
+})
+</script>
+<style lang="scss" scoped>
+$prefix-cls: #{$namespace}-table__title;
+
+.#{$prefix-cls} {
+  display: flex;
+  position: relative;
+  align-items: center;
+  padding-left: 7px;
+  color: var(--el-table-text-color);
+  font-size: 16px;
+  font-weight: 500;
+  line-height: 24px;
+  cursor: pointer;
+  user-select: none;
+}
+</style>

File diff ditekan karena terlalu besar
+ 114 - 0
src/components/Table/components/ztTableImg.vue


+ 53 - 0
src/components/Table/components/ztTableSetting.vue

@@ -0,0 +1,53 @@
+<template>
+  <div class="table-settings">
+    <RedoSetting v-if="getSetting.redo" />
+    <ColumnSetting v-if="getSetting.form" />
+  </div>
+</template>
+<script lang="ts" setup>
+import type { TableSetting } from '@/types/table'
+import { PropType } from 'vue'
+import RedoSetting from './redoSetting.vue'
+import ColumnSetting from "./columnSetting.vue"
+
+defineOptions({
+  name: 'ZtTableSetting'
+})
+
+//todo 自定义列和列表全屏 后面在做
+const props = defineProps({
+  setting: {
+    type: Object as PropType<TableSetting>,
+    default: () => ({})
+  }
+})
+
+
+
+const getSetting = computed<TableSetting>(() => {
+  return Object.assign(
+    {
+      redo: true,
+      form: false,
+      setting: true,
+      fullScreen: false
+    },
+    props.setting
+  ) as TableSetting
+})
+</script>
+<style lang="scss">
+.table-settings {
+  display: flex;
+  align-items: center;
+  & > * {
+    margin-right: 12px;
+  }
+
+ 
+
+  .zt-icon{
+    cursor: pointer;
+  }
+}
+</style>

+ 216 - 0
src/components/Table/hooks/useColumns.ts

@@ -0,0 +1,216 @@
+import { BasicTableProps, ColumnProps, Pagination } from '@/types/table'
+import type { ComputedRef } from 'vue'
+import { computed, Ref, ref, reactive, toRaw, unref, watch } from 'vue'
+import { checkPermi } from '@/utils/permission'
+import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import { isArray, isBoolean, isFunction } from '@/utils/is'
+import { cloneDeep } from 'lodash-es'
+
+import { DEFAULT_ALIGN, INDEX_COLUMN_FLAG, PAGE_SIZE } from '../src/const'
+
+
+const { wsCache } = useCache()
+//生成序号列
+function handleIndexColumn(
+  propsRef: ComputedRef<BasicTableProps>,
+  getPaginationRef: ComputedRef<Pagination>,
+  columns: ColumnProps[]
+) {
+  const { showIndexColumn, indexColumnProps, reserveSelection, headerAlign } = unref(propsRef)
+
+  let pushIndexColumns = false, indIndex;
+
+  columns.forEach(() => {
+    indIndex = columns.findIndex((column) => column.type === INDEX_COLUMN_FLAG)
+    if (showIndexColumn) {
+      pushIndexColumns = indIndex === -1
+    } else if (!showIndexColumn && indIndex !== -1) {
+      columns.splice(indIndex, 1)
+    }
+  })
+
+  if (!pushIndexColumns) return
+
+  const isFixedLeft = columns.some((item) => item.fixed === 'left')
+  const getPagination = unref(getPaginationRef)
+  const { currentPage = 1, pageSize = PAGE_SIZE } = getPagination
+
+  columns.unshift({
+    type: INDEX_COLUMN_FLAG,
+    width: 70,
+    label: '序号',
+    align: 'center',
+    reserveSelection,
+    headerAlign,
+    index: columns[indIndex]?.index ? columns[indIndex].index : (index: number) => ((currentPage < 1 ? 1 : currentPage) - 1) * pageSize + index + 1,
+    ...(isFixedLeft
+      ? {
+        fixed: 'left'
+      }
+      : {}),
+    ...indexColumnProps
+  })
+
+  //找到type='selection'的在插入到序号前面
+  const selectionIdx = columns.findIndex((item) => item?.type === 'selection');
+  let selectionColunm = columns.splice(selectionIdx, 1)
+  if (selectionIdx >= 0) {
+    columns.unshift({
+      prop: '',
+      type: 'selection',
+      selectable: selectionColunm[0].selectable,
+      reserveSelection: selectionColunm[0].reserveSelection,
+      label: '全选',
+      fixed: 'left'
+    })
+
+  }
+
+
+}
+
+
+function sortFixedColumn(columns: ColumnProps[]) {
+  const fixedLeftColumns: ColumnProps[] = []
+  const fixedRightColumns: ColumnProps[] = []
+  const defColumns: ColumnProps[] = []
+  for (const column of columns) {
+    if (column.fixed === 'left') {
+      fixedLeftColumns.push(column)
+      continue
+    }
+    if (column.fixed === 'right') {
+      fixedRightColumns.push(column)
+      continue
+    }
+    defColumns.push(column)
+  }
+  return [...fixedLeftColumns, ...defColumns, ...fixedRightColumns].filter((item) => !item.defaultHidden)
+}
+
+
+export function useColumns(propsRef: ComputedRef<BasicTableProps>, getPaginationRef: ComputedRef<boolean | Pagination>) {
+  const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<ColumnProps[]>
+  let cacheColumns = unref(propsRef).columns;
+  let localColumns = ref<any[] | null>(null)
+
+
+
+
+  const getColumnsRef = computed(() => {
+    const columns = cloneDeep(unref(columnsRef))
+
+    handleIndexColumn(propsRef, getPaginationRef as ComputedRef<Pagination>, columns)
+    if (!columns) {
+      return []
+    }
+
+    columns.forEach((item) => {
+      item.align = item.align || DEFAULT_ALIGN;
+    })
+
+    return columns
+  })
+
+  const getCacheColumnsRef = computed(() => {
+    const columns = cloneDeep(unref(localColumns))
+    if (!columns) {
+      return []
+    }
+    handleIndexColumn(propsRef, getPaginationRef as ComputedRef<Pagination>, columns)
+
+    columns.forEach((item) => {
+      item.align = item.align || DEFAULT_ALIGN;
+    })
+    return columns
+  })
+
+  //判断是否可见
+  function isIfShow(column: ColumnProps): boolean {
+    const ifShow = column.ifShow
+
+    let isIfShow = true
+
+    if (isBoolean(ifShow)) {
+      isIfShow = ifShow
+    }
+    if (isFunction(ifShow)) {
+      isIfShow = ifShow(column)
+    }
+    return isIfShow
+  }
+
+  const getViewColumns = computed(() => {
+    const viewColumns = localColumns.value ? sortFixedColumn(unref(getCacheColumnsRef)) : sortFixedColumn(unref(getColumnsRef))
+
+    const columns = cloneDeep(viewColumns);
+
+    return columns.filter(column => isIfShow(column) && checkPermi(column.auth))
+
+  })
+
+
+  //监听columns改变
+  watch(
+    () => unref(propsRef).columns,
+    (columns) => {
+      columnsRef.value = columns
+      cacheColumns = columns
+      //cacheColumns = columns?.filter((item) => !item.type && item.prop!='action') ?? []
+    }
+  )
+
+  watch(
+    () => unref(propsRef).tableKey,
+    () => {
+      const temColumn = wsCache.get(CACHE_KEY.COLUMNS);
+      if (temColumn && temColumn[unref(propsRef).tableKey] && Array.isArray(temColumn[unref(propsRef).tableKey])) {
+        localColumns.value = temColumn[unref(propsRef).tableKey]
+      } else {
+        localColumns.value = null
+      }
+    }
+  )
+
+  //设置列
+  function setColumns(columnList: Partial<ColumnProps>[] | (string | string[])[]) {
+    const columns = cloneDeep(columnList)
+    if (!isArray(columns)) return
+    if (columns.length <= 0) {
+      columnsRef.value = []
+      return
+    }
+    columnsRef.value = columns as ColumnProps[]
+  }
+
+  function setLocalColumns(columns: ColumnProps[]) {
+    if (!isArray(columns)) return
+    localColumns.value = columns
+  }
+
+  //获取列
+  function getColumns() {
+    let columns = toRaw(unref(getColumnsRef))
+    return columns
+  }
+  function getCacheColumns() {
+    return cacheColumns.filter(column => isIfShow(column) && checkPermi(column.auth))
+  }
+  function setCacheColumns(columns: ColumnProps[]) {
+    if (!isArray(columns)) return
+    //cacheColumns = columns.filter((item) => !item.type)
+    cacheColumns = columns
+  }
+
+  return {
+    getColumnsRef,
+    getCacheColumns,
+    getColumns,
+    setColumns,
+    getViewColumns,
+    setCacheColumns,
+    setLocalColumns
+  }
+
+}
+

+ 21 - 0
src/components/Table/hooks/useLoading.ts

@@ -0,0 +1,21 @@
+import { ref, ComputedRef, unref, computed, watch } from 'vue'
+import { BasicTableProps } from '@/types/table'
+
+export function useLoading(props: ComputedRef<BasicTableProps>) {
+  const loadingRef = ref(unref(props).loading)
+
+  watch(
+    () => unref(props).loading,
+    (loading) => {
+      loadingRef.value = loading
+    }
+  )
+
+  const getLoading = computed(() => unref(loadingRef))
+
+  function setLoading(loading: boolean) {
+    loadingRef.value = loading
+  }
+
+  return { getLoading, setLoading }
+}

+ 68 - 0
src/components/Table/hooks/usePagination.ts

@@ -0,0 +1,68 @@
+import type { Pagination, BasicTableProps } from '@/types/table'
+import { computed, unref, ref, ComputedRef, watch } from 'vue'
+import { isBoolean } from '@/utils/is'
+import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../src/const'
+
+export function usePagination(refProps: ComputedRef<BasicTableProps>) {
+  const configRef = ref<Pagination>({})
+  const show = ref(true)
+
+  watch(
+    () => unref(refProps).pagination,
+    (pagination) => {
+      if (!isBoolean(pagination) && pagination) {
+        configRef.value = {
+          ...unref(configRef),
+          ...(pagination ?? {})
+        }
+      }
+    }
+  )
+
+  //获取分页配置
+  const getPaginationInfo = computed((): Pagination | false => {
+    const { pagination } = unref(refProps)
+
+    if (!unref(show) || (isBoolean(pagination) && !pagination)) {
+      return false
+    }
+
+    return Object.assign(
+      {
+        currentPage: 1,
+        pageSize: PAGE_SIZE,
+        defaultPageSize: PAGE_SIZE,
+        pageSizes: PAGE_SIZE_OPTIONS,
+        total: 0,
+        layout: 'total, sizes, prev, pager, next, jumper',
+        hideOnSinglePage: false
+      },
+      { ...unref(configRef) }
+    )
+  })
+
+  //设置分页
+  const setPagination = (info: Partial<Pagination>) => {
+    const paginationInfo = unref(getPaginationInfo)
+    configRef.value = {
+      ...(!isBoolean(paginationInfo) ? paginationInfo : {}),
+      ...info
+    }
+  }
+
+  //对外暴露
+  function getPagination() {
+    return unref(getPaginationInfo)
+  }
+
+  //对外暴露是否展分页
+  function getShowPagination() {
+    return unref(show)
+  }
+
+  async function setShowPagination(flag: boolean) {
+    show.value = flag
+  }
+
+  return { getPagination, getPaginationInfo, setShowPagination, getShowPagination, setPagination }
+}

+ 123 - 0
src/components/Table/hooks/useRender.ts

@@ -0,0 +1,123 @@
+import { h } from 'vue'
+import dayjs from 'dayjs'
+import { ElButton, ElTag } from 'element-plus'
+import { isArray, isString } from '@/utils/is'
+import { DictTag } from '@/components/DictTag'
+import { Icon } from '@/components/Icon'
+import ZtTableImage from '../components/ztTableImg.vue'
+
+export const useRender = {
+  /**
+   * 渲染图片
+   * @param text 图片地址
+   * @returns image标签
+   */
+  renderImg: (text, props = {}) => {
+    if (text) {
+      if (isArray(text)) {
+        return h(ZtTableImage, { imgList: text, ...props })
+      } else if (isString(text)) {
+        return h(ZtTableImage, { imgList: [text], ...props })
+      }
+    }
+    return ''
+  },
+  /**
+   * 渲染链接
+   * @param url 链接地址
+   * @param text 文字说明
+   * @returns link 按钮
+   */
+  renderLink: (url, text?: string, props: any = {}) => {
+    if (url) {
+      return h(ElButton, { link: true, onClick: () => window.open(url, '_blank'), ...props }, () => text || '')
+    }
+    return ''
+  },
+  /**
+   * 渲染文本,将text与val 拼接到一起
+   * @param text 文本1
+   * @param val 文本2
+   * @returns 文本1 + 文本2
+   */
+  renderText: (text, val) => {
+    if (text) {
+      return text + ' ' + val
+    } else {
+      return ''
+    }
+  },
+  /**
+   * 渲染标签
+   * @param text 标签文本
+   * @param color 标签颜色
+   * @returns 标签
+   */
+  renderTag: (text, color?: string, props: any = {}) => {
+    if (color) {
+      //ts-ignore
+      return h(ElTag, { color, ...props }, () => text)
+    } else {
+      //ts-ignore
+      return h(ElTag, props, () => text)
+    }
+  },
+  /**
+  * 渲染多标签
+  * @param texts 文本
+  * @returns 多标签
+  */
+  renderTags: (texts: string[]) => {
+    if (texts) {
+      return h('div', null, [
+        texts.map((text) => {
+          return h(ElTag, null, () => text)
+        })
+      ])
+    }
+    return ''
+  },
+  /**
+   * 渲染日期
+   * @param text 日期
+   * @param format 格式化
+   * @returns 格式化后日期
+   */
+  renderDate: (text, format?) => {
+    if (!text) {
+      return ''
+    }
+    if (!format) {
+      return dayjs(text).format('YYYY-MM-DD HH:mm:ss')
+    } else {
+      return dayjs(text).format(format)
+    }
+  },
+  /**
+   * 渲染字典
+   * @param text 字典值
+   * @param dictType 字典类型
+   * @returns 字典标签
+   */
+  renderDict: (text, dictType) => {
+
+    if (dictType) {
+      return h(DictTag, { type: dictType, value: text })
+    }
+    return ''
+  },
+
+
+  /**
+   * 渲染图标icon
+   * @param text icon
+   * @returns icon
+   */
+  renderIcon: (text) => {
+    if (text) {
+      return h(Icon, { icon: text })
+    }
+  },
+
+
+}

+ 115 - 0
src/components/Table/hooks/useRowSelect.ts

@@ -0,0 +1,115 @@
+import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue'
+import { BasicTableProps, TableRowSelection } from '@/types/table'
+import { ElTable } from 'element-plus'
+export function useRowSelection(
+  propsRef: ComputedRef<BasicTableProps>,
+  tableData: Ref<Recordable[]>,
+  emit: EmitType,
+  tableElRef: Ref<InstanceType<typeof ElTable>>
+) {
+  //选中的keys
+  const selectedRowKeysRef = ref<string[]>([])
+  //选中的行
+  const selectedRowRef = ref<Recordable[]>([])
+
+  const getRowSelectionRef = computed((): TableRowSelection | null => {
+    //找到配置没有就代表不需要多选
+    const { rowSelection } = unref(propsRef)
+    if (!rowSelection) {
+      return null
+    }
+
+    return {
+      selectedRowKeys: unref(selectedRowKeysRef),
+      ...rowSelection
+    }
+  })
+
+  //监听传入值变化重新选中要选的玩意
+  watch(
+    () => unref(propsRef).rowSelection?.selectedRowKeys,
+    (v: string[]) => {
+      setSelectedRowKeys(v)
+    }
+  )
+
+  //监听对选中的变化并emit出去
+  watch(
+    () => unref(selectedRowKeysRef),
+    () => {
+      nextTick(() => {
+        //const { rowSelection } = unref(propsRef)
+        emit('selecet-key-change', {
+          keys: getSelectRowKeys(),
+          rows: tableElRef.value?.getSelectionRows()
+        })
+      })
+    },
+    { deep: true }
+  )
+
+  //设置选中值
+  function setSelectedRowKeys(rowKeys: string[]) {
+
+    selectedRowKeysRef.value = rowKeys
+    let rowkey = unref(propsRef).rowKey
+    //找到对应的节点 包含当前的table 和 历史比如说上一页下一页的row
+    let trueList = unref(tableData).filter((item) => {
+      return rowKeys.includes(item[rowkey as string])
+    })
+    if (trueList) {
+      trueList.forEach((row) => {
+        unref(tableElRef)!.toggleRowSelection(row, undefined)
+      })
+    }
+    selectedRowRef.value = trueList
+  }
+
+  //获取选中行
+  function getSelectRowKeys() {
+    return unref(selectedRowKeysRef)
+  }
+
+  //手动设置选中行
+  function setSelectedRows(rows: Recordable[]) {
+    selectedRowRef.value = rows
+  }
+
+  //清空选中行
+  function clearSelectedRowKeys() {
+    selectedRowRef.value = []
+    selectedRowKeysRef.value = []
+    tableElRef.value.clearSelection()
+  }
+
+  function deleteSelectRowByKey(key: string) {
+    const selectedRowKeys = unref(selectedRowKeysRef)
+    const index = selectedRowKeys.findIndex((item) => item === key)
+    if (index !== -1) {
+      unref(selectedRowKeysRef).splice(index, 1)
+    }
+  }
+
+
+  function getSelectRows<T = Recordable>() {
+    // const ret = toRaw(unref(selectedRowRef)).map((item) => toRaw(item));
+    return unref(selectedRowRef) as T[]
+  }
+
+  function getRowSelection() {
+    return unref(getRowSelectionRef)!
+  }
+
+
+
+  return {
+    getRowSelection,
+    getRowSelectionRef,
+    getSelectRows,
+    getSelectRowKeys,
+    setSelectedRowKeys,
+    clearSelectedRowKeys,
+    deleteSelectRowByKey,
+    setSelectedRows
+  }
+}

+ 145 - 0
src/components/Table/hooks/useTable.ts

@@ -0,0 +1,145 @@
+import type {
+  BasicTableProps,
+  FetchParams,
+  ColumnProps,
+  Pagination,
+  DynamicProps
+} from '@/types/table'
+import { getDynamicProps } from '@/utils'
+import { ref, onUnmounted, unref, watch, toRaw } from 'vue'
+import type { WatchStopHandle } from 'vue'
+type Props = Partial<DynamicProps<BasicTableProps>>
+
+export function useZtTable(tableProps?: Props): [(instance: any) => void, any] {
+  const tableRef = ref<any>(null)
+  const loadedRef = ref<Nullable<boolean>>(false)
+  const formRef = ref<any>(null)
+
+  //卸载清空
+
+
+  let stopWatch: WatchStopHandle;
+
+  function register(instance: any) {
+
+    onUnmounted(() => {
+      tableRef.value = null
+      loadedRef.value = null
+    })
+
+    if (unref(loadedRef) && instance === unref(tableRef)) return
+
+    tableRef.value = instance
+
+    tableProps && instance.setProps(getDynamicProps(tableProps))
+    loadedRef.value = true
+
+    stopWatch?.()
+
+
+    stopWatch = watch(
+      () => tableProps,
+      () => {
+        tableProps && instance.setProps(getDynamicProps(tableProps))
+      },
+      {
+        immediate: true,
+        deep: true
+      }
+    )
+  }
+
+
+  function getTableInstance() {
+    const table = unref(tableRef)
+    if (!table) {
+      throw new Error('尚未获取table实例!')
+    }
+    return table
+  }
+
+  const methods = {
+    reload: async (opt?: FetchParams) => {
+      return await getTableInstance().reload(opt)
+    },
+    setProps: (props: Partial<BasicTableProps>) => {
+      getTableInstance().setProps(props)
+    },
+
+    setSelectedRows: (rows: Recordable[]) => {
+      return toRaw(getTableInstance().setSelectedRows(rows))
+    },
+    setLoading: (loading: boolean) => {
+      getTableInstance().setLoading(loading)
+    },
+    getDataSource: () => {
+      return getTableInstance().getDataSource()
+    },
+    getRawDataSource: () => {
+      return getTableInstance().getRawDataSource()
+    },
+    getColumns: ({ ignoreIndex = false }: { ignoreIndex?: boolean } = {}) => {
+      const columns = getTableInstance().getColumns({ ignoreIndex }) || []
+      return toRaw(columns)
+    },
+    setColumns: (columns: ColumnProps[]) => {
+      getTableInstance().setColumns(columns)
+    },
+    setTableData: (values: any[]) => {
+      return getTableInstance().setTableData(values)
+    },
+    setPagination: (info: Partial<Pagination>) => {
+      return getTableInstance().setPagination(info)
+    },
+    deleteSelectRowByKey: (key: string) => {
+      getTableInstance().deleteSelectRowByKey(key)
+    },
+    getSelectRowKeys: () => {
+      return toRaw(getTableInstance().getSelectRowKeys())
+    },
+    getSelectRows: () => {
+      return toRaw(getTableInstance().getSelectRows())
+    },
+    clearSelectedRowKeys: () => {
+      getTableInstance().clearSelectedRowKeys()
+    },
+    setSelectedRowKeys: (keys: string[] | number[]) => {
+      getTableInstance().setSelectedRowKeys(keys)
+    },
+    getPaginationRef: () => {
+      return getTableInstance().getPaginationRef()
+    },
+
+    updateTableData: (index: number, key: string, value: any) => {
+      return getTableInstance().updateTableData(index, key, value)
+    },
+    deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
+      return getTableInstance().deleteTableDataRecord(rowKey)
+    },
+    insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
+      return getTableInstance().insertTableDataRecord(record, index)
+    },
+    updateTableDataRecord: (rowKey: string | number, value:any,record: Recordable) => {
+      return getTableInstance().updateTableDataRecord(rowKey,value, record)
+    },
+    findTableDataRecord: (rowKey: string | number) => {
+      return getTableInstance().findTableDataRecord(rowKey)
+    },
+    getRowSelection: () => {
+      return toRaw(getTableInstance().getRowSelection())
+    },
+    getCacheColumns: () => {
+      return toRaw(getTableInstance().getCacheColumns())
+    },
+
+    setShowPagination: async (show: boolean) => {
+      getTableInstance().setShowPagination(show)
+    },
+    getShowPagination: () => {
+      return toRaw(getTableInstance().getShowPagination())
+    },
+
+  }
+
+  return [register, methods]
+}

+ 19 - 0
src/components/Table/hooks/useTableContext.ts

@@ -0,0 +1,19 @@
+import type { Ref } from 'vue'
+import type { BasicTableProps } from '@/types/table'
+import { provide, inject, ComputedRef } from 'vue'
+
+const key = Symbol('basic-table')
+
+type Instance = any;
+
+type RetInstance = Omit<Instance, 'getBindValues'> & {
+  getBindValues: ComputedRef<BasicTableProps>
+}
+
+export function createTableContext(instance: Instance) {
+  provide(key, instance)
+}
+
+export function useTableContext(): RetInstance {
+  return inject(key) as RetInstance
+}

+ 270 - 0
src/components/Table/hooks/useTableData.ts

@@ -0,0 +1,270 @@
+import { BasicTableProps, ColumnProps, FetchParams, Pagination, SorterResult } from '@/types/table'
+
+import { ref, unref, ComputedRef, computed, onMounted, watch, reactive, Ref, watchEffect } from 'vue'
+
+import { useTimeoutFn } from '@vueuse/core'
+import { buildUUID } from '@/utils/uuid'
+import { isFunction, isBoolean, isObject } from '@/utils/is'
+import { get, cloneDeep, merge, omit } from 'lodash-es'
+import { FETCH_SETTING, PAGE_SIZE } from '../src/const'
+
+
+
+interface ActionType {
+  getPaginationInfo: ComputedRef<boolean | Pagination>
+  setPagination: (info: Partial<Pagination>) => void
+  setLoading: (loading: boolean) => void
+  clearSelectedRowKeys: () => void
+  tableData: Ref<Recordable[]>
+}
+
+interface SearchState {
+  sortInfo: Recordable
+}
+
+export function useTableData(propsRef: ComputedRef<BasicTableProps>, { getPaginationInfo, setPagination, setLoading, clearSelectedRowKeys, tableData }: ActionType, emit: EmitType) {
+
+  const searchState = reactive<SearchState>({
+    sortInfo: {},
+  })
+
+  const dataSourceRef = ref<Recordable[]>([])
+  const rawDataSourceRef = ref<Recordable>({})
+
+  //监听数据
+  watchEffect(() => {
+  
+    tableData.value = unref(dataSourceRef)
+  })
+
+
+  watch(
+    () => unref(propsRef).dataSource,
+    () => {
+      //没有api的话就直接赋值
+      const { dataSource, api } = unref(propsRef)
+      !api && dataSource && (dataSourceRef.value = dataSource)
+    },
+    {
+      immediate: true
+    }
+  )
+
+  function handleTableChange(pagination: Pagination, sorter?: SorterResult) {
+    const { clearSelectOnPageChange, sortFn } = unref(propsRef)
+
+    //清除选中状态当切换页码的时候
+    if (clearSelectOnPageChange) {
+      clearSelectedRowKeys()
+    }
+
+    //填充页码
+    setPagination(pagination)
+
+    const params: Recordable = {}
+    if (sorter && isFunction(sortFn)) {
+      const sortInfo = sortFn(sorter)
+      searchState.sortInfo = sortInfo
+      params.sortInfo = sortInfo
+    }
+
+    fetch(params)
+
+  }
+
+
+
+  const getDataSourceRef = computed(() => {
+    return unref(dataSourceRef)
+  })
+
+  //更新table
+  async function updateTableData(index: number, key: string, value: any) {
+    const record = dataSourceRef.value[index]
+    if (record) {
+      dataSourceRef.value[index][key] = value
+    }
+    return dataSourceRef.value[index]
+  }
+
+
+  //更新table 中的某一行
+  function updateTableDataRecord(key: string | number, value: any, record: Recordable): Recordable | undefined {
+
+
+    const row = findTableDataRecord(key, value)
+
+
+    if (row) {
+      for (const field in row) {
+
+        if (Reflect.has(record, field)) row[field] = record[field]
+      }
+      return row
+    }
+  }
+
+  //删除指定行
+  function deleteTableDataRecord(index: number | number[]) {
+    if (!dataSourceRef.value || dataSourceRef.value.length == 0) return
+    const idxs = !Array.isArray(index) ? [index] : index;
+    for (const idx of idxs) {
+      dataSourceRef.value.splice(idx, 1);
+      unref(propsRef).dataSource?.splice(idx, 1)
+
+    }
+    setPagination({
+      total: unref(propsRef).dataSource?.length
+    })
+  }
+
+
+  function insertTableDataRecord(record: Recordable | Recordable[], index: number): Recordable[] | undefined {
+    index = index ?? dataSourceRef.value?.length
+    const _record = isObject(record) ? [record as Recordable] : (record as Recordable[])
+    unref(dataSourceRef).splice(index, 0, ..._record)
+    return unref(dataSourceRef)
+  }
+
+  function findTableDataRecord(key: string | number, value) {
+    if (!dataSourceRef.value || dataSourceRef.value.length == 0) return
+
+    //后续新增tree 表格
+    let ret = dataSourceRef.value.find(r => {
+      return r[key] === value
+    })
+
+    return ret;
+  }
+
+
+
+  async function fetch(opt?: FetchParams) {
+    const { api, searchInfo, defSort, fetchSetting, beforeFetch, afterFetch, pagination } = unref(propsRef);
+
+    if (!api || !isFunction(api)) return
+
+    try {
+      setLoading(true)
+      const { pageField, sizeField, listField, totalField } = Object.assign({}, FETCH_SETTING, fetchSetting)
+      let pageParams: Recordable = {}
+
+      const { currentPage = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as Pagination
+
+      if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) {
+        pageParams = {}
+      } else {
+        pageParams[pageField] = (opt && opt.page) || currentPage
+        pageParams[sizeField] = pageSize
+      }
+
+      const { sortInfo = {} } = searchState
+
+      let params: Recordable = merge(
+        pageParams,
+        searchInfo,
+        opt?.searchInfo ?? {},
+        defSort,
+        sortInfo,
+        opt?.sortInfo ?? {},
+      )
+
+      if (beforeFetch && isFunction(beforeFetch)) {
+        params = (await beforeFetch(params)) || params
+      }
+
+      const res = await api(params)
+      rawDataSourceRef.value = res
+
+      //如果返回的直接是数组
+      const isArrayResult = Array.isArray(res)
+      let resultItems: Recordable[] = isArrayResult ? res : get(res, listField)
+
+      
+      const resultTotal: number = isArrayResult ? res.length : get(res, totalField)
+      const otherValue: Recordable = omit(res, [listField, totalField])
+
+      // 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行
+      if (Number(resultTotal)) {
+        const currentTotalPage = Math.ceil(resultTotal / pageSize)
+        if (currentPage > currentTotalPage) {
+          setPagination({
+            currentPage: currentTotalPage
+          })
+          return await fetch(opt)
+        }
+      }
+
+      if (afterFetch && isFunction(afterFetch)) {
+
+        resultItems = (await afterFetch(resultItems, otherValue)) || resultItems
+      }
+
+       
+
+      dataSourceRef.value = resultItems
+      setPagination({
+        total: resultTotal || 0
+      })
+      if (opt && opt.page) {
+        setPagination({
+          currentPage: opt.page || 1
+        })
+      }
+      emit('fetch-success', {
+        items: unref(resultItems),
+        total: resultTotal
+      })
+      return resultItems
+
+
+    } catch (error) {
+      emit('fetch-error', error)
+      dataSourceRef.value = []
+      setPagination({
+        total: 0
+      })
+    } finally {
+      setLoading(false)
+    }
+  }
+
+
+  function setTableData<T extends Ref<Recordable<any>[]>>(values: T[]) {
+    dataSourceRef.value = values
+  }
+
+  function getDataSource<T = Recordable>() {
+    return getDataSourceRef.value as T[]
+  }
+
+  function getRawDataSource<T = Recordable>() {
+    return rawDataSourceRef.value as T
+  }
+
+  async function reload(opt?: FetchParams) {
+    return await fetch(opt)
+  }
+
+  onMounted(() => {
+    useTimeoutFn(() => {
+      unref(propsRef).immediate && fetch()
+    }, 16)
+  })
+
+  return {
+    getDataSourceRef,
+    getDataSource,
+    getRawDataSource,
+    setTableData,
+    fetch,
+    reload,
+    updateTableData,
+    updateTableDataRecord,
+    deleteTableDataRecord,
+    insertTableDataRecord,
+    findTableDataRecord,
+    handleTableChange
+  }
+
+}

+ 6 - 0
src/components/Table/index.ts

@@ -0,0 +1,6 @@
+import ZtTable from './src/ztTable.vue'
+import TableAction from './components/TableAction.vue'
+import { useZtTable } from './hooks/useTable'
+import { useRender } from "./hooks/useRender"
+
+export { ZtTable, useZtTable, useRender,TableAction }

+ 79 - 0
src/components/Table/src/const.ts

@@ -0,0 +1,79 @@
+import { SorterResult } from '@/types/table'
+
+//
+
+
+export const table = {
+  // 表格接口请求通用配置,可在组件prop覆盖
+  // 支持 xxx.xxx.xxx格式
+  fetchSetting: {
+    // 传给后台的当前页字段
+    pageField: 'pageNo',
+    // 传给后台的每页显示多少条的字段
+    sizeField: 'pageSize',
+    // 接口返回表格数据的字段
+    listField: 'list',
+    // 接口返回表格总数的字段
+    totalField: 'total'
+  },
+  // 可选的分页选项
+  pageSizeOptions: [10, 20, 50, 80, 100],
+  // 默认每页显示多少条
+  defaultPageSize: 20,
+  // 默认排序方法
+  defaultSize: 'middle',
+  // 自定义通用排序功能
+  defaultSortFn: (sortInfo: SorterResult) => {
+    const { field, order } = sortInfo
+    if (field && order) {
+      return {
+        // 排序字段
+        field,
+        // 排序方式 asc/desc
+        order
+      }
+    } else {
+      return {}
+    }
+  },
+  // 自定义过滤方法
+  defaultFilterFn: (data: Partial<Recordable<string[]>>) => {
+    return data
+  }
+}
+
+const {
+  pageSizeOptions,
+  defaultPageSize,
+  fetchSetting,
+  defaultSize,
+  defaultSortFn,
+  defaultFilterFn
+} = table
+
+export const ROW_KEY = 'columnKey'
+
+
+export const PAGE_SIZE_OPTIONS = pageSizeOptions
+
+
+export const PAGE_SIZE = defaultPageSize
+
+
+export const FETCH_SETTING = fetchSetting
+
+
+export const DEFAULT_SIZE = defaultSize
+
+
+export const DEFAULT_SORT_FN = defaultSortFn
+
+export const DEFAULT_FILTER_FN = defaultFilterFn
+
+
+
+export const DEFAULT_ALIGN = 'center'
+
+export const INDEX_COLUMN_FLAG = 'index'
+
+export const ACTION_COLUMN_FLAG = 'action'

+ 174 - 0
src/components/Table/src/props.ts

@@ -0,0 +1,174 @@
+import { Fn, ColumnProps, TableRowSelection, Pagination, TableCustomRecord } from '@/types/table'
+import { propTypes } from '@/utils/propTypes'
+import { PropType } from 'vue'
+import type { ElButton } from "element-plus"
+import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const'
+import { SorterResult } from "@/types/table"
+
+type TableType = 'default' | 'selection' | 'index' | 'expand'
+
+
+
+export const tableProps = {
+  clickToRowSelect: { type: Boolean, default: true },
+  inset: Boolean,
+  showIndexColumn: Boolean,
+  type: String as PropType<TableType>,
+  sortFn: {
+    type: Function as PropType<(sortInfo: SorterResult) => any>,
+    default: DEFAULT_SORT_FN
+  },
+  filterFn: {
+    type: Function as PropType<(data: Partial<Recordable<string[]>>) => any>,
+    default: DEFAULT_FILTER_FN
+  },
+  schema: { type: Array as PropType<any[]>, default: [] },
+  totalData: Object,
+
+  showTableSetting: Boolean,
+  striped: { type: Boolean, default: true },
+  showSummary: Boolean,
+  summaryFunc: {
+    type: [Function, Array] as PropType<(...arg: any[]) => any[]>,
+    default: null
+  },
+  summaryData: {
+    type: Array as PropType<Recordable[]>,
+    default: null
+  },
+  tableKey: {
+    type: String as PropType<string>,
+    default: ''
+  },
+  indentSize: propTypes.number.def(24),
+  // 对齐方式
+  align: propTypes.string
+    .validate((v: string) => ['left', 'center', 'right'].includes(v))
+    .def('center'),
+  api: {
+    type: Function as PropType<(...arg: any[]) => Promise<any>>,
+    default: null
+  },
+  beforeFetch: {
+    type: Function as PropType<Fn>,
+    default: null
+  },
+  afterFetch: {
+    type: Function as PropType<Fn>,
+    default: null
+  },
+  searchInfo: {
+    type: Object as PropType<Recordable>,
+    default: () => { }
+  },
+
+  fetchSetting: {
+    type: Object as PropType<FetchSetting>,
+    default: () => {
+      return FETCH_SETTING
+    }
+  },
+  // 立即请求接口
+  immediate: { type: Boolean, default: true },
+  emptyDataIsShowTable: { type: Boolean, default: true },
+  // 默认的排序参数
+  defSort: {
+    type: Object as PropType<Recordable>,
+    default: null
+  },
+  stripe: {
+    type: Boolean,
+    default: false
+  },
+
+  columns: {
+    type: Array as PropType<ColumnProps[]>,
+    default: () => []
+  },
+  indexColumnProps: {
+    type: Object as PropType<ColumnProps>,
+    default: null
+  },
+  actionColumn: {
+    type: Object as PropType<ColumnProps>,
+    default: null
+  },
+  headerAlign: {
+    type: String as PropType<'left' | 'center' | 'right'>,
+    default: 'center'
+  },
+  title: {
+    type: [String, Function] as PropType<string | ((data: Recordable) => string)>,
+    default: null
+  },
+  titleHelpMessage: {
+    type: [String, Array] as PropType<string | string[]>
+  },
+  maxHeight: propTypes.number,
+  reserveSelection: propTypes.bool.def(false),
+  dataSource: {
+    type: Array as PropType<Recordable[]>,
+    default: null
+  },
+  rowKey: {
+    type: [String, Function] as PropType<string | ((record: Recordable) => string)>,
+    default: ''
+  },
+  customRender: {
+    type: Function as PropType<Fn>,
+  },
+
+  currentRowKey: {
+    type: [String, Number],
+    default: ""
+  },
+  rowSelection: {
+    type: Object as PropType<TableRowSelection>,
+    default: null
+  },
+  bordered: propTypes.bool,
+  pagination: {
+    type: [Object, Boolean] as PropType<Pagination | Boolean>,
+    default: null
+  },
+  loading: propTypes.bool,
+  rowClassName: {
+    type: Function as PropType<(record: TableCustomRecord<any>, index: number) => string>
+  },
+
+
+
+}
+
+export interface PopConfirm {
+  title: string
+  confirmButtonText?: string
+  cancelButtonText?: string
+  confirm: Fn
+  cancel?: Fn
+}
+
+export interface ActionItem {
+  onClick?: Fn
+  label?: string
+  type?: 'success' | 'danger' | 'warning' | 'info'
+  popConfirm?: PopConfirm
+  disabled?: boolean
+  divider?: boolean
+  // 权限编码控制是否显示
+  auth?: string | string[]
+  text?: string
+  // 业务控制是否显示
+  ifShow?: boolean | ((action: ActionItem) => boolean)
+}
+
+export interface FetchSetting {
+  // 请求接口当前页数
+  pageField: string
+  // 每页显示多少条
+  sizeField: string
+  // 请求结果列表字段  支持 a.b.c
+  listField: string
+  // 请求结果总数字段  支持 a.b.c
+  totalField: string
+}

+ 346 - 0
src/components/Table/src/ztTable.vue

@@ -0,0 +1,346 @@
+<script lang="tsx">
+import { ElTable, ElTableColumn, ElPagination, ElEmpty } from 'element-plus'
+import { defineComponent, ref, computed, unref, watch, onMounted } from 'vue'
+import { tableProps } from './props'
+import { BasicTableProps, ColumnProps, TableSlotDefault, Pagination } from '@/types/table'
+import { ZtTableHeader, ztTableHeaderHelp,ztTableFooter } from '../components/index'
+import { createTableContext } from '../hooks/useTableContext'
+import { getSlot } from '@/utils/tsxHelper'
+import { useDesign } from '@/hooks/web/useDesign'
+import { useLoading } from '../hooks/useLoading'
+import { usePagination } from '../hooks/usePagination'
+import { useRowSelection } from '../hooks/useRowSelect'
+import { useTableData } from '../hooks/useTableData'
+import { formatToDate } from '@/utils/dateUtil'
+import { PAGE_SIZE } from './const'
+import { useColumns } from '../hooks/useColumns'
+import { omit } from 'lodash-es'
+
+import { isFunction } from '@/utils/is'
+
+export default defineComponent({
+  // eslint-disable-next-line vue/no-reserved-component-names
+  name: 'ZtTable',
+  inheritAttrs: false,
+  props: tableProps,
+  emits: [
+    //请求成功
+    'fetch-success',
+    //请求失败
+    'fetch-error',
+    //注册ref
+    'register',
+    //单选回调
+    'current-change',
+    //多选回调
+    'selecet-key-change',
+    'row-mouseenter',
+    'row-mouseleave',
+    'change'
+  ],
+  setup(props, { emit, expose }) {
+    const slots = useSlots()
+    const attrs = useAttrs()
+
+    //获取css前缀
+    const { getPrefixCls } = useDesign()
+    const prefixCls = getPrefixCls('table')
+
+    //table 的ref实例
+    const tableElRef = ref<InstanceType<typeof ElTable>>()
+
+    const wrapRef = ref(null)
+    //内部默认props
+    const innerPropsRef = ref<Partial<BasicTableProps>>()
+
+    //获取合并的props
+    const getProps = computed(() => {
+      return { ...props, ...unref(innerPropsRef) } as BasicTableProps
+    })
+
+    //ts-ignore
+    const pageSizeRef = ref(props.pagination?.pageSize ?? PAGE_SIZE)
+    //ts-ignore
+    const currentPageRef = ref(props.pagination?.currentPage ?? 1)
+
+    //获取loading状态
+    const { getLoading, setLoading } = useLoading(getProps)
+    //获取分页信息
+    const {
+      getPaginationInfo,
+      getPagination,
+      setPagination,
+      setShowPagination,
+      getShowPagination
+    } = usePagination(getProps)
+
+    //列表数据
+    const tableData = ref<Recordable[]>([])
+
+    //获取选中信息
+    const { getSelectRowKeys, getSelectRows, clearSelectedRowKeys, setSelectedRows } =
+      useRowSelection(
+        getProps,
+        tableData,
+        emit,
+        // @ts-ignore
+        tableElRef
+      )
+
+    const {
+      handleTableChange: onTableChange,
+      getDataSourceRef,
+      getDataSource,
+      getRawDataSource,
+      setTableData,
+      updateTableDataRecord,
+      deleteTableDataRecord,
+      insertTableDataRecord,
+      findTableDataRecord,
+      fetch,
+      reload,
+      updateTableData
+    } = useTableData(
+      getProps,
+      {
+        tableData,
+        getPaginationInfo,
+        setLoading,
+        setPagination,
+        clearSelectedRowKeys
+      },
+      emit
+    )
+
+    /*watch(
+      () => pageSizeRef,
+      (newVal) => {
+        console.log(newVal, 'newValnewValnewVal')
+      }
+    )
+
+    watch(
+      () => currentPageRef,
+      (newVal) => {
+        console.log(newVal, '11212121')
+      }
+    )*/
+
+    function handleTableChange(data) {
+      emit('change', data)
+      onTableChange(data)
+      /* onTableChange.call(undefined, ...args)
+      
+      // 解决通过useTable注册onChange时不起作用的问题
+      const { onChange } = unref(getProps)
+      onChange && isFunction(onChange) && onChange.call(undefined, ...args) */
+    }
+
+    const {
+      getViewColumns,
+      getColumns,
+      setCacheColumns,
+      setColumns,
+      setLocalColumns,
+      getCacheColumns
+    } = useColumns(getProps, getPaginationInfo)
+
+    const getBindValues = computed(() => {
+      let propsData: Recordable = {
+        ...attrs,
+        ...unref(getProps),
+        tableLayout: 'fixed'
+      }
+      propsData = omit(propsData, [
+        'class',
+        'onChange',
+        'columns',
+        'sortFn',
+        'dataSource',
+        'fetchSetting',
+        'schema',
+        'filterFn'
+      ])
+
+      return propsData
+    })
+
+    function setProps(props: Partial<BasicTableProps>) {
+      innerPropsRef.value = { ...unref(innerPropsRef), ...props }
+    }
+
+    function columnChange(columns: ColumnProps[]) {}
+
+    const tableAction: any = {
+      reload,
+      getSelectRows,
+      setSelectedRows,
+      clearSelectedRowKeys,
+      getSelectRowKeys,
+      setPagination,
+      setTableData,
+      updateTableDataRecord,
+      deleteTableDataRecord,
+      insertTableDataRecord,
+      findTableDataRecord,
+      setColumns,
+      setLoading,
+      getDataSource,
+      getRawDataSource,
+      setProps,
+      getPaginationRef: getPagination,
+      getColumns,
+      getViewColumns,
+      setLocalColumns,
+      getCacheColumns,
+      emit,
+      updateTableData,
+      setCacheColumns
+    }
+
+    expose(tableAction)
+    emit('register', tableAction)
+    createTableContext({ ...tableAction, wrapRef, getBindValues })
+    /******************tsx渲染***********************/
+
+    //渲染头部数据
+    const renderTableHeader = () => {
+      const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(getProps)
+
+      return (
+        <ZtTableHeader
+          title={title}
+          showTableSetting={showTableSetting}
+          titleHelpMessage={titleHelpMessage}
+          tableSetting={tableSetting}
+          v-slots={{
+            toolbar: slots.toolbar
+          }}
+          onColumnChange={(columns) => {
+            columnChange(columns)
+          }}
+        ></ZtTableHeader>
+      )
+    }
+
+    //渲染自定义合计数据活着其他数据(官方合计只能是列合计,太具有局限性)
+    const renderTableFooter = () => {
+      //TODO
+      const getIsEmptyData = computed(() => {
+        return (unref(getDataSourceRef) || []).length === 0
+      })
+
+      if (!unref(getProps).showDescSummary) return
+
+      const { summaryData,schema } = unref(getProps)
+
+      return <ztTableFooter schema={schema}  summaryData={summaryData} ></ztTableFooter>
+    }
+
+    //渲染空白数据
+    const renderTableEmpty = () => {
+      return <ElEmpty description="暂无数据"></ElEmpty>
+    }
+
+    //渲染树节点
+    const renderTreeTableColumn = (columns: ColumnProps[]) => {
+      return <div></div>
+    }
+
+    //渲染自定义列表头
+    const renderCutomTableHeader = (column: ColumnProps) => {
+      //自定义最优先。其次是默认的help ,最后是默认的
+      if (getSlot(slots, `${column.prop}-header`)) {
+        return getSlot(slots, `${column.prop}-header`)
+      } else if (column.helpMessage) {
+        return (
+          <ztTableHeaderHelp
+            helpMessage={column.helpMessage}
+            title={column.label}
+          ></ztTableHeaderHelp>
+        )
+      } else {
+        return column.label
+      }
+    }
+    //渲染列表格
+    const renderElTableColumn = () => {
+      return unref(getViewColumns).map((column) => {
+        let columnProps = omit(column, ['label', 'prop'])
+
+        return (
+          <ElTableColumn label={column.label} prop={column.prop} {...columnProps} >
+            {{
+              default: (data: TableSlotDefault) =>
+                column.children && column.children.length
+                  ? renderTreeTableColumn(column.children)
+                  : // @ts-ignore
+                    getSlot(slots, column.prop, data) ||
+                    //自定义渲染支持任何tsx返回
+                    column?.customRender?.(
+                      data.row,
+                      data.column,
+                      data.row[column.prop],
+                      data.$index
+                    ) ||
+                    data.row[column.prop!],
+              // @ts-ignore
+              header: () => renderCutomTableHeader(column)
+            }}
+          </ElTableColumn>
+        )
+      })
+    }
+
+    //这个computed是因为 分页组件的pageSize和currentPage 不能和el-table的v-model绑定,否则会导致分页无法点击
+    const paginationSettingRef = computed(() => {
+      let tem = getPaginationInfo.value
+      if (tem) {
+        //omit 会导致 分页无法点击
+        //omit(tem,['pageSize','currentPage'])
+        delete tem.pageSize
+        delete tem.currentPage
+      }
+
+      return tem
+    })
+
+    //获取用户自定义append
+    let appendSlots = getSlot(slots, 'append')
+    return () => (
+      <div class={['el-table', ' p-[5px]', prefixCls]} ref={wrapRef}>
+        {renderTableHeader()}
+        <ElTable
+          border
+          ref={tableElRef}
+          data={unref(getDataSourceRef)}
+          v-loading={unref(getLoading)}
+          v-slots={{
+            append: appendSlots ? appendSlots : null,
+            empty: () => renderTableEmpty()
+          }}
+          {...unref(getBindValues)}
+        >
+          {renderElTableColumn()}
+        </ElTable>
+        {renderTableFooter()}
+        {getPaginationInfo && unref(getPaginationInfo) ? (
+          // update by 芋艿:保持和 Pagination 组件一致
+          //垃圾element 结构直接不生效
+          //ts-ignore
+          <ElPagination
+            background
+            v-model:pageSize={pageSizeRef.value}
+            v-model:currentPage={currentPageRef.value}
+            class="float-right mb-15px mt-15px"
+            {...paginationSettingRef.value}
+            onChange={(currentPage, pageSize) => {
+              handleTableChange({ currentPage, pageSize })
+            }}
+          ></ElPagination>
+        ) : undefined}
+      </div>
+    )
+  }
+})
+</script>

+ 17 - 0
src/hooks/web/useDesign.ts

@@ -0,0 +1,17 @@
+
+
+export const useDesign = () => {
+ 
+
+  /**
+   * @param scope 类名
+   * @returns 返回空间名-类名
+   */
+  const getPrefixCls = (scope: string) => {
+    return `zt-${scope}`
+  }
+
+  return {
+    getPrefixCls
+  }
+}

+ 22 - 0
src/hooks/web/useEmitt.ts

@@ -0,0 +1,22 @@
+import mitt from 'mitt'
+
+interface Option {
+  name: string // 事件名称
+  callback: Fn // 回调
+}
+
+const emitter = mitt()
+
+export const useEmitt = (option?: Option) => {
+  if (option) {
+    emitter.on(option.name, option.callback)
+
+    onBeforeUnmount(() => {
+      emitter.off(option.name)
+    })
+  }
+
+  return {
+    emitter
+  }
+}

+ 1 - 0
src/styles/variables.scss

@@ -46,6 +46,7 @@ $screen-xxl: 1536px; // Tailwind 2xl: 1536px
 }
 
 $elNamespace: el;
+$namespace: zt;
 
 // 常用颜色变量
 $primary-color: #646cff;

+ 0 - 1
src/types/components.d.ts

@@ -15,7 +15,6 @@ declare module 'vue' {
     ElDialog: typeof import('element-plus/es')['ElDialog']
     ElIcon: typeof import('element-plus/es')['ElIcon']
     ElInput: typeof import('element-plus/es')['ElInput']
-    ElProgress: typeof import('element-plus/es')['ElProgress']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
     ElSpace: typeof import('element-plus/es')['ElSpace']

+ 90 - 4
src/types/global.d.ts

@@ -1,9 +1,95 @@
+import type {
+  ComponentRenderProxy,
+  VNode,
+  VNodeChild,
+  ComponentPublicInstance,
+  FunctionalComponent,
+  PropType as VuePropType,
+} from 'vue';
+import 'vue/jsx';
+
 declare global {
-  type Nullable<T> = T | null
+  interface Fn<T = any> {
+    (...arg: T[]): T;
+  }
+
+  // vue
+  declare type PropType<T> = VuePropType<T>;
+  declare type VueNode = VNodeChild | JSX.Element;
+  type ComponentRef<T extends HTMLElement = HTMLDivElement> =
+    ComponentElRef<T> | null;
+
+  type Nullable<T> = T | null;
+
+  type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;
+
+  type Recordable<T = any, K = string> = Record<
+    K extends null | undefined ? string : K,
+    T
+  >;
+
+  type ComponentRef<T> = InstanceType<T>;
+
+  type LocaleType = 'zh-CN' | 'en';
+
+  declare type EmitType = (event: string, ...args: any[]) => void;
+
+  declare type TimeoutHandle = ReturnType<typeof setTimeout>;
+  declare type IntervalHandle = ReturnType<typeof setInterval>;
+
+  type AxiosHeaders =
+    | 'application/json'
+    | 'application/x-www-form-urlencoded'
+    | 'multipart/form-data';
+
+  type AxiosMethod =
+    | 'get'
+    | 'post'
+    | 'delete'
+    | 'put'
+    | 'GET'
+    | 'POST'
+    | 'DELETE'
+    | 'PUT';
+
+  type AxiosResponseType =
+    | 'arraybuffer'
+    | 'blob'
+    | 'document'
+    | 'json'
+    | 'text'
+    | 'stream';
+
+  interface AxiosConfig {
+    params?: any;
+    data?: any;
+    url?: string;
+    method?: AxiosMethod;
+    headersType?: string;
+    responseType?: AxiosResponseType;
+  }
+
+  interface IResponse<T = any> {
+    code: string;
+    data: T extends any ? T : T & any;
+  }
 
-  type RefElement = Nullable<HTMLElement>
-} 
+  interface PageParam {
+    pageSize?: number;
+    pageNo?: number;
+  }
 
+  interface Tree {
+    id: number;
+    name: string;
+    children?: Tree[] | any[];
+  }
+  // 分页数据公共返回
+  interface PageResult<T> {
+    list: T; // 数据
+    total: number; // 总量
+  }
+}
 
 // 扩展require.context类型
 declare interface RequireContext {
@@ -12,4 +98,4 @@ declare interface RequireContext {
   <T>(id: string): T;
   resolve(id: string): string;
   id: string;
-}
+}

+ 292 - 0
src/types/table.d.ts

@@ -0,0 +1,292 @@
+
+
+
+export type VxeTableColumn = {
+  field: string
+  title?: string
+  children?: TableColumn[]
+} & Recordable
+
+export type CellFormat = string | ((text: string, record: Recordable, index: number) => string | number) | Map<string | number, any>
+
+export type TableSlotDefault = {
+  row: Recordable
+  column: TableColumn
+  $index: number
+} & Recordable
+
+export interface Pagination {
+  small?: boolean
+  background?: boolean
+  pageSize?: number
+  defaultPageSize?: number
+  total?: number
+  pageCount?: number
+  pagerCount?: number
+  currentPage?: number
+  defaultCurrentPage?: number
+  layout?: string
+  pageSizes?: number[]
+  popperClass?: string
+  prevText?: string
+  nextText?: string
+  disabled?: boolean
+  hideOnSinglePage?: boolean
+}
+
+export interface ExpandedRowRenderRecord<T> extends TableCustomRecord<T> {
+  indent?: number
+  expanded?: boolean
+}
+
+export interface TableCustomRecord<T> {
+  record?: T
+  index?: number
+}
+
+export type SizeType = 'default' | 'middle' | 'small' | 'large'
+
+export interface TableSetPropsType {
+  field: string
+  path: string
+  value: any
+}
+
+export declare type SortOrder = 'ascending' | 'descending'
+
+type Fn = (...arg: any) => any
+
+export declare type CustomRenderFunction<T> = (record: RecordProps<T>) => VNodeChild | JSX.Element
+
+export interface SorterResult {
+  column: ColumnProps
+  order: SortOrder
+  field: string
+  columnKey: string
+}
+
+
+// https://element-plus-docs.bklab.cn/zh-CN/component/table.html#%E5%9F%BA%E7%A1%80%E8%A1%A8%E6%A0%BC
+export interface ColumnProps {
+  type?: 'default' | 'selection' | 'index' | 'expand',
+  index?: number | ((index: number) => number),
+  label: string,
+  columnKey?: string,
+  prop: string,
+  width?: string | number,
+  minWidth?: string | number,
+
+  reserveSelection?:boolean,
+  sortable?: boolean | string,
+  renderHeader?: CustomRenderFunction<T> | VNodeChild | JSX.Element,
+  sortMethod?: <T = any>(a: T, b: T) => number,
+  sortBy?: (row: any, index: number) => string | string | string[],
+  sortOrders?: SortOrder[],
+  
+  resizable?: boolean,
+  formatter?: (row: any) => VNode | string,
+  customRender?: (row: any, column: any, cellValue: any, index: number) => VNode | string,
+  showOverflowTooltip?: boolean,
+  align?: 'left' | 'center' | 'right',
+  headerAlign?: 'left' | 'center' | 'right',
+  className?: string,
+  labelClassName?: string,
+  selectable?: (row: any, index: number) => boolean,
+  fixed?: 'left' | 'right'
+  children?: TableColumn[],
+
+  // 帮助文档
+  helpMessage?: string | string[],
+  //默认不显示
+  defaultHidden?: boolean,
+  //格式化
+  format?: CellFormat,
+  // 权限编码控制是否显示
+  auth?: RoleEnum | RoleEnum[] | string | string[]
+  // 业务控制是否显示
+  ifShow?: boolean | ((column: BasicColumn) => boolean)
+}
+
+
+export interface TableRowSelection<T = any> {
+
+  //清空选择
+  clearSelection?: () => void
+
+  //返回选择行
+  getSelectionRows?: () => any
+
+  //切换某一行的选中状态
+  toggleRowSelection?: (row: T, selected: boolean) => void
+
+  //切换全选和全部不选
+  toggleAllSelection?: () => void
+
+  //设置单选的高亮
+  setCurrentRow?: (row: T) => void
+
+  //后端给定的选中的值
+  selectedRowKeys?: string[] | number[]
+
+  //后端选中值对应的字段key
+  rowSelectKeys: string;
+
+}
+
+export interface FetchParams {
+  searchInfo?: Recordable
+  page?: number
+  sortInfo?: Recordable
+}
+
+
+export interface TableSetting {
+  redo?: boolean
+  form?: boolean
+  setting?: boolean
+  fullScreen?: boolean
+}
+
+export interface GetColumnsParams {
+  ignoreIndex?: boolean
+  ignoreAction?: boolean
+  sort?: boolean
+}
+
+
+export interface BasicTableProps<T = any> {
+  //是否显示序号
+  showIndexColumn:boolean,
+  // 点击行选中
+  clickToRowSelect?: boolean
+  isTreeTable?: boolean
+  // 自定义排序方法
+  sortFn?: (sortInfo: SorterResult) => any
+  //align 对齐方式
+  align?: 'left' | 'center' | 'right',
+  //表头对齐方式
+  headerAlign?: 'left' | 'center' | 'right',  
+  // 排序方法
+  tableKey:string, //用于存放自定义列缓存的key标识
+  filterFn?: (data: Partial<Recordable<string[]>>) => any
+  // 取消表格的默认padding
+  inset?: boolean
+  // 显示表格设置
+  showTableSetting?: boolean
+  tableSetting?: TableSetting
+  // 斑马纹
+  stripe?: boolean
+  rowKey?: string | ((record: Recordable) => string)
+
+  currentRowKey?: boolean
+  // 计算合计行的方法
+  summaryFunc?: (...arg: any) => Recordable[]
+  // 自定义合计表格内容
+  summaryData?: Recordable[]
+  // 是否显示自定义合计行
+  showDescSummary?: boolean
+  // 表格schema
+  schema?: any[]
+  //请求的额外参数
+  searchInfo?: Recordable,
+  // 接口请求对象
+  api?: (...arg: any) => Promise<any>
+  // 请求之前处理参数
+  beforeFetch?: Fn
+  // 自定义处理接口返回参数
+  afterFetch?: Fn
+  // 查询条件请求之前处理
+  handleSearchInfoFn?: Fn
+  // 请求接口配置
+  fetchSetting?: Partial<FetchSetting>
+  // 立即请求接口
+  immediate?: boolean
+  // 在开起搜索表单的时候,如果没有数据是否显示表格
+  emptyDataIsShowTable?: boolean
+  //数据刷新后是否保留选项,仅对  type=selection 的列有效, 请注意, 需指定 row-key 来让这个功能生效。
+  reserveSelection?: boolean
+  // 默认的排序参数
+  defSort?: Recordable
+  // 列配置
+  columns: BasicColumn[]
+  // 序号列配置
+  indexColumnProps?: BasicColumn
+  actionColumn?: BasicColumn
+  // 文本超过宽度是否显示。。。
+  ellipsis?: boolean
+
+
+
+  // 在分页改变的时候清空选项
+  clearSelectOnPageChange?: boolean
+  //
+
+  // 数据
+  dataSource?: Recordable[]
+  // 标题右侧提示
+  titleHelpMessage?: string | string[]
+  // 表格滚动最大高度
+  maxHeight?: number
+  // 是否显示边框
+  bordered?: boolean
+  // 分页配置
+  pagination?: Pagination | boolean
+  // loading加载
+  loading?: boolean
+
+  rowSelection?: TableRowSelection<T>
+
+  /**
+   * Indent size in pixels of tree data
+   * @default 15
+   * @type number
+   */
+  indentSize?: number
+
+
+
+  /**
+   * Row's className
+   * @type Function
+   */
+  rowClassName?: (record: TableCustomRecord<T>, index: number) => string
+
+
+  /**
+   * Table title renderer
+   * @type Function | ScopedSlot
+   */
+  title?: VNodeChild | JSX.Element | string | ((data: Recordable) => string)
+
+
+
+
+  /**
+   * `table-layout` attribute of table element
+   * `fixed` when header/columns are fixed, or using `column.ellipsis`
+   *
+   * @see https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout
+   * @version 1.5.0
+   */
+  tableLayout?: 'auto' | 'fixed' | string
+
+
+
+
+  /**
+   * Callback executed when pagination, filters or sorter is changed
+   * @param pagination
+   * @param filters
+   * @param sorter
+   * @param currentDataSource
+   */
+  onChange?: (pagination: any, filters: any, sorter: any, extra: any) => void
+
+}
+
+
+
+
+export type DynamicProps<T> = {
+  [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>
+}

+ 43 - 0
src/utils/drag.ts

@@ -0,0 +1,43 @@
+const onDragFalg = (dragResult: any, list: any[]) => {
+  //目前只有move 需求
+ 
+  const { moved } = dragResult;
+  if (moved) {
+
+    const { newIndex, oldIndex } = moved;
+    if (newIndex == oldIndex) {
+      return false;
+    }
+    let arr = applyDrag(list, dragResult);
+    return arr;
+  } else
+    return false;
+}
+
+
+/**
+    * 拖拽切换位置
+    * @param {*} list 原始数据
+    * @param {*} dragResult 处理后数据
+    */
+function applyDrag(list: any[], dragResult: any) {
+  const { newIndex, oldIndex, element } = dragResult;
+  if (!newIndex && !oldIndex) return list;
+  const result = [...list];
+  let itemToAdd = element;
+
+  if (oldIndex !== null) {
+    itemToAdd = result.splice(oldIndex, 1)[0];
+  }
+
+  if (newIndex !== null) {
+    result.splice(newIndex, 0, itemToAdd);
+  }
+
+  return result;
+}
+
+
+export {
+  onDragFalg
+}

+ 40 - 0
src/utils/permission.ts

@@ -0,0 +1,40 @@
+
+
+/**
+ * 检查用户是否拥有指定权限
+ * @param permission 权限标识符,可以是单个字符串或字符串数组
+ * @returns 是否拥有权限,
+ */
+export function checkPermi(permission: string | string[]): boolean {
+  // 暂时不做真实权限检查,始终返回 true
+  return true;
+}
+
+/**
+ * 检查用户是否拥有指定角色
+ * @param role 角色标识符,可以是单个字符串或字符串数组
+ * @returns 是否拥有角色
+ */
+export function checkRole(role: string | string[]): boolean {
+  // 暂时不做真实角色检查,始终返回 true
+  return true;
+}
+
+/**
+ * 判断当前用户是否拥有管理员权限
+ * @returns 是否为管理员,
+ */
+export function isAdmin(): boolean {
+  
+  return true;
+}
+
+/**
+ * 判断元素是否显示
+ * @param permission 权限标识符
+ * @returns 是否应该显示元素
+ */
+export function hasPermission(permission: string | string[]): boolean {
+
+  return true;
+} 

+ 11 - 0
src/utils/storage.ts

@@ -12,6 +12,17 @@ interface StorageData<T = any> {
 // 存储类型
 type StorageType = 'localStorage' | 'sessionStorage';
 
+
+export const STORAGE_KEY = {
+  // 用户相关
+  USER_INFO: 'user_info',      // 用户信息
+  TOKEN: 'token',              // 认证令牌
+  
+  // 业务相关
+  COLUMNS: 'columns',          // 表格列配置
+  
+} as const;
+
 /**
  * 存储类,提供对 localStorage 和 sessionStorage 的封装
  * 支持设置过期时间、JSON对象存储等功能

+ 7 - 2
tsconfig.json

@@ -16,11 +16,16 @@
     "sourceMap": true,
     "baseUrl": ".",
     "paths": {
-      "@/*": ["./src/*"]
+      "@/*": ["src/*"]
     },
     "typeRoots": ["./node_modules/@types", "./src/types"]
   },
-  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+ "include": [
+    "src",
+    "types/**/*.d.ts",
+    "src/types/auto-imports.d.ts",
+    "src/types/auto-components.d.ts"
+  ],
   "exclude": ["./rspack.*.config.ts", "rspack.config.ts"],
   "ts-node": {
     "compilerOptions": {

File diff ditekan karena terlalu besar
+ 3163 - 819
yarn.lock