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

refactor(system): 重构菜单管理页面

- 移除了不必要的组件和引用
- 优化了菜单数据结构和字段名称
- 简化了表单结构,移除了冗余的字段和逻辑
- 调整了表格列的显示内容和格式
- 删除了未使用的数据规则相关代码
zhangtao пре 2 месеци
родитељ
комит
c7defc31a5
100 измењених фајлова са 8031 додато и 0 уклоњено
  1. 117 0
      src/views/demo/charts/Line.vue
  2. 75 0
      src/views/demo/charts/Map.vue
  3. 135 0
      src/views/demo/charts/Pie.vue
  4. 100 0
      src/views/demo/charts/SaleRadar.vue
  5. 87 0
      src/views/demo/charts/china.json
  6. 189 0
      src/views/demo/charts/data.ts
  7. 45 0
      src/views/demo/charts/map/Baidu.vue
  8. 47 0
      src/views/demo/charts/map/Gaode.vue
  9. 52 0
      src/views/demo/charts/map/Google.vue
  10. 67 0
      src/views/demo/codemirror/index.vue
  11. 110 0
      src/views/demo/comp/button/index.vue
  12. 32 0
      src/views/demo/comp/card-list/index.vue
  13. 42 0
      src/views/demo/comp/count-to/index.vue
  14. 94 0
      src/views/demo/comp/cropper/index.vue
  15. 79 0
      src/views/demo/comp/desc/index.vue
  16. 13 0
      src/views/demo/comp/drawer/Drawer1.vue
  17. 17 0
      src/views/demo/comp/drawer/Drawer2.vue
  18. 35 0
      src/views/demo/comp/drawer/Drawer3.vue
  19. 53 0
      src/views/demo/comp/drawer/Drawer4.vue
  20. 13 0
      src/views/demo/comp/drawer/Drawer5.vue
  21. 69 0
      src/views/demo/comp/drawer/index.vue
  22. 19 0
      src/views/demo/comp/lazy/TargetContent.vue
  23. 77 0
      src/views/demo/comp/lazy/Transition.vue
  24. 52 0
      src/views/demo/comp/lazy/index.vue
  25. 101 0
      src/views/demo/comp/loading/index.vue
  26. 58 0
      src/views/demo/comp/modal/Modal1.vue
  27. 23 0
      src/views/demo/comp/modal/Modal2.vue
  28. 15 0
      src/views/demo/comp/modal/Modal3.vue
  29. 81 0
      src/views/demo/comp/modal/Modal4.vue
  30. 112 0
      src/views/demo/comp/modal/index.vue
  31. 117 0
      src/views/demo/comp/qrcode/index.vue
  32. 59 0
      src/views/demo/comp/scroll/Action.vue
  33. 64 0
      src/views/demo/comp/scroll/VirtualScroll.vue
  34. 31 0
      src/views/demo/comp/scroll/index.vue
  35. 32 0
      src/views/demo/comp/strength-meter/index.vue
  36. 44 0
      src/views/demo/comp/time/index.vue
  37. 91 0
      src/views/demo/comp/transition/index.vue
  38. 54 0
      src/views/demo/comp/upload/index.vue
  39. 33 0
      src/views/demo/comp/verify/Rotate.vue
  40. 97 0
      src/views/demo/comp/verify/index.vue
  41. 81 0
      src/views/demo/document/form/BasicFiledsLayotForm.vue
  42. 70 0
      src/views/demo/document/form/BasicFixedWidthForm.vue
  43. 143 0
      src/views/demo/document/form/BasicFormAdd.vue
  44. 66 0
      src/views/demo/document/form/BasicFormBtn.vue
  45. 95 0
      src/views/demo/document/form/BasicFormCleanRule.vue
  46. 58 0
      src/views/demo/document/form/BasicFormCompact.vue
  47. 34 0
      src/views/demo/document/form/BasicFormComponent.vue
  48. 63 0
      src/views/demo/document/form/BasicFormConAttribute.vue
  49. 32 0
      src/views/demo/document/form/BasicFormCustom.vue
  50. 32 0
      src/views/demo/document/form/BasicFormCustomComponent.vue
  51. 64 0
      src/views/demo/document/form/BasicFormCustomSlots.vue
  52. 80 0
      src/views/demo/document/form/BasicFormDynamicsRules.vue
  53. 70 0
      src/views/demo/document/form/BasicFormFieldShow.vue
  54. 55 0
      src/views/demo/document/form/BasicFormFieldTip.vue
  55. 105 0
      src/views/demo/document/form/BasicFormFooter.vue
  56. 63 0
      src/views/demo/document/form/BasicFormLayout.vue
  57. 84 0
      src/views/demo/document/form/BasicFormModal.vue
  58. 90 0
      src/views/demo/document/form/BasicFormRander.vue
  59. 58 0
      src/views/demo/document/form/BasicFormRules.vue
  60. 99 0
      src/views/demo/document/form/BasicFormSchemas.vue
  61. 116 0
      src/views/demo/document/form/BasicFormSearch.vue
  62. 63 0
      src/views/demo/document/form/BasicFormSlots.vue
  63. 94 0
      src/views/demo/document/form/BasicFormValue.vue
  64. 63 0
      src/views/demo/document/form/BasicFunctionForm.vue
  65. 393 0
      src/views/demo/document/form/example.data.ts
  66. 452 0
      src/views/demo/document/form/exampleCustom.data.ts
  67. 24 0
      src/views/demo/document/form/index.ts
  68. 114 0
      src/views/demo/document/form/tabIndex.vue
  69. 135 0
      src/views/demo/document/table/AuthColumnDemo.vue
  70. 94 0
      src/views/demo/document/table/BasicTableBorder.vue
  71. 81 0
      src/views/demo/document/table/BasicTableDemo.vue
  72. 157 0
      src/views/demo/document/table/BasicTableDemoAjax.vue
  73. 106 0
      src/views/demo/document/table/CustomerCellDemo.vue
  74. 217 0
      src/views/demo/document/table/EditCellTableDemo.vue
  75. 261 0
      src/views/demo/document/table/EditRowTableDemo.vue
  76. 119 0
      src/views/demo/document/table/ExpandTableDemo.vue
  77. 131 0
      src/views/demo/document/table/ExportTableDemo.vue
  78. 98 0
      src/views/demo/document/table/FixedHeaderColumn.vue
  79. 131 0
      src/views/demo/document/table/InnerTableDemo.vue
  80. 70 0
      src/views/demo/document/table/MergeHeaderDemo.vue
  81. 144 0
      src/views/demo/document/table/MergeTableDemo.vue
  82. 80 0
      src/views/demo/document/table/SelectTableDemo.vue
  83. 124 0
      src/views/demo/document/table/TreeTableDemo.vue
  84. 15 0
      src/views/demo/document/table/index.ts
  85. 87 0
      src/views/demo/document/table/tabIndex.vue
  86. 91 0
      src/views/demo/editor/json/index.vue
  87. 53 0
      src/views/demo/editor/markdown/Editor.vue
  88. 55 0
      src/views/demo/editor/markdown/index.vue
  89. 53 0
      src/views/demo/editor/tinymce/Editor.vue
  90. 21 0
      src/views/demo/editor/tinymce/index.vue
  91. 13 0
      src/views/demo/feat/breadcrumb/ChildrenList.vue
  92. 10 0
      src/views/demo/feat/breadcrumb/ChildrenListDetail.vue
  93. 13 0
      src/views/demo/feat/breadcrumb/FlatList.vue
  94. 8 0
      src/views/demo/feat/breadcrumb/FlatListDetail.vue
  95. 43 0
      src/views/demo/feat/click-out-side/index.vue
  96. 85 0
      src/views/demo/feat/context-menu/index.vue
  97. 40 0
      src/views/demo/feat/copy/index.vue
  98. 0 0
      src/views/demo/feat/download/imgBase64.ts
  99. 59 0
      src/views/demo/feat/download/index.vue
  100. 45 0
      src/views/demo/feat/full-screen/index.vue

+ 117 - 0
src/views/demo/charts/Line.vue

@@ -0,0 +1,117 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { getLineData } from './data';
+
+  export default defineComponent({
+    props: {
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const chartRef = ref<HTMLDivElement | null>(null);
+      const { setOptions, echarts } = useECharts(chartRef as Ref<HTMLDivElement>);
+      const { barData, lineData, category } = getLineData;
+      onMounted(() => {
+        setOptions({
+          backgroundColor: '#0f375f',
+          tooltip: {
+            trigger: 'axis',
+            axisPointer: {
+              type: 'shadow',
+              label: {
+                show: true,
+                backgroundColor: '#333',
+              },
+            },
+          },
+          legend: {
+            data: ['line', 'bar'],
+            textStyle: {
+              color: '#ccc',
+            },
+          },
+          xAxis: {
+            data: category,
+            axisLine: {
+              lineStyle: {
+                color: '#ccc',
+              },
+            },
+          },
+          yAxis: {
+            splitLine: { show: false },
+            axisLine: {
+              lineStyle: {
+                color: '#ccc',
+              },
+            },
+          },
+          series: [
+            {
+              name: 'line',
+              type: 'line',
+              smooth: true,
+              showAllSymbol: 'auto',
+              symbol: 'emptyCircle',
+              symbolSize: 15,
+              data: lineData,
+            },
+            {
+              name: 'bar',
+              type: 'bar',
+              barWidth: 10,
+              itemStyle: {
+                borderRadius: 5,
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  { offset: 0, color: '#14c8d4' },
+                  { offset: 1, color: '#43eec6' },
+                ]),
+              },
+              data: barData,
+            },
+            {
+              name: 'line',
+              type: 'bar',
+              barGap: '-100%',
+              barWidth: 10,
+              itemStyle: {
+                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                  { offset: 0, color: 'rgba(20,200,212,0.5)' },
+                  { offset: 0.2, color: 'rgba(20,200,212,0.2)' },
+                  { offset: 1, color: 'rgba(20,200,212,0)' },
+                ]),
+              },
+              z: -12,
+              data: lineData,
+            },
+            {
+              name: 'dotted',
+              type: 'pictorialBar',
+              symbol: 'rect',
+              itemStyle: {
+                color: '#0f375f',
+              },
+              symbolRepeat: true,
+              symbolSize: [12, 4],
+              symbolMargin: 1,
+              z: -10,
+              data: lineData,
+            },
+          ],
+        });
+      });
+      return { chartRef };
+    },
+  });
+</script>

+ 75 - 0
src/views/demo/charts/Map.vue

@@ -0,0 +1,75 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+  import { mapData } from './data';
+  import { registerMap } from 'echarts';
+
+  export default defineComponent({
+    props: {
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const chartRef = ref<HTMLDivElement | null>(null);
+      const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+
+      onMounted(async () => {
+        const json = (await (await import('./china.json')).default) as any;
+        registerMap('china', json);
+        setOptions({
+          visualMap: [
+            {
+              min: 0,
+              max: 1000,
+              left: 'left',
+              top: 'bottom',
+              text: ['高', '低'],
+              calculable: false,
+              orient: 'horizontal',
+              inRange: {
+                color: ['#e0ffff', '#006edd'],
+                symbolSize: [30, 100],
+              },
+            },
+          ],
+          tooltip: {
+            trigger: 'item',
+            backgroundColor: 'rgba(0, 0, 0, .6)',
+            textStyle: {
+              color: '#fff',
+              fontSize: 12,
+            },
+          },
+          series: [
+            {
+              name: 'iphone4',
+              type: 'map',
+              map: 'china',
+              label: {
+                show: true,
+                color: 'rgb(249, 249, 249)',
+                fontSize: 10,
+              },
+              itemStyle: {
+                areaColor: '#2f82ce',
+                borderColor: '#0DAAC1',
+              },
+              data: mapData,
+            },
+          ],
+        });
+      });
+      return { chartRef };
+    },
+  });
+</script>

+ 135 - 0
src/views/demo/charts/Pie.vue

@@ -0,0 +1,135 @@
+<template>
+  <div ref="chartRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, PropType, ref, Ref, onMounted } from 'vue';
+
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  export default defineComponent({
+    props: {
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const chartRef = ref<HTMLDivElement | null>(null);
+      const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+      const dataAll = [389, 259, 262, 324, 232, 176, 196, 214, 133, 370];
+      const yAxisData = ['原因1', '原因2', '原因3', '原因4', '原因5', '原因6', '原因7', '原因8', '原因9', '原因10'];
+      onMounted(() => {
+        setOptions({
+          backgroundColor: '#0f375f',
+          title: [
+            {
+              text: '各渠道投诉占比',
+              left: '2%',
+              top: '1%',
+              textStyle: {
+                color: '#fff',
+                fontSize: 14,
+              },
+            },
+            {
+              text: '投诉原因TOP10',
+              left: '40%',
+              top: '1%',
+              textStyle: {
+                color: '#fff',
+                fontSize: 14,
+              },
+            },
+            {
+              text: '各级别投诉占比',
+              left: '2%',
+              top: '50%',
+              textStyle: {
+                color: '#fff',
+                fontSize: 14,
+              },
+            },
+          ],
+          grid: [{ left: '50%', top: '7%', width: '45%', height: '90%' }],
+          tooltip: {
+            formatter: '{b} ({c})',
+          },
+          xAxis: [
+            {
+              gridIndex: 0,
+              axisTick: { show: false },
+              axisLabel: { show: false },
+              splitLine: { show: false },
+              axisLine: { show: false },
+            },
+          ],
+          yAxis: [
+            {
+              gridIndex: 0,
+              interval: 0,
+              data: yAxisData.reverse(),
+              axisTick: { show: false },
+              axisLabel: { show: true },
+              splitLine: { show: false },
+              axisLine: { show: true, lineStyle: { color: '#6173a3' } },
+            },
+          ],
+          series: [
+            {
+              name: '各渠道投诉占比',
+              type: 'pie',
+              radius: '30%',
+              center: ['22%', '25%'],
+              data: [
+                { value: 335, name: '客服电话' },
+                { value: 310, name: '奥迪官网' },
+                { value: 234, name: '媒体曝光' },
+                { value: 135, name: '质检总局' },
+                { value: 105, name: '其他' },
+              ],
+              labelLine: { show: false },
+              label: {
+                show: true,
+                formatter: '{b} \n ({d}%)',
+                color: '#B1B9D3',
+              },
+            },
+            {
+              name: '各级别投诉占比',
+              type: 'pie',
+              radius: '30%',
+              center: ['22%', '75%'],
+              labelLine: { show: false },
+              data: [
+                { value: 335, name: 'A级' },
+                { value: 310, name: 'B级' },
+                { value: 234, name: 'C级' },
+                { value: 135, name: 'D级' },
+              ],
+              label: {
+                show: true,
+                formatter: '{b} \n ({d}%)',
+                color: '#B1B9D3',
+              },
+            },
+            {
+              name: '投诉原因TOP10',
+              type: 'bar',
+              xAxisIndex: 0,
+              yAxisIndex: 0,
+              barWidth: '45%',
+              itemStyle: { color: '#86c9f4' },
+              label: { show: true, position: 'right', color: '#9EA7C4' },
+              data: dataAll.sort(),
+            },
+          ],
+        });
+      });
+      return { chartRef };
+    },
+  });
+</script>

+ 100 - 0
src/views/demo/charts/SaleRadar.vue

@@ -0,0 +1,100 @@
+<template>
+  <Card title="销售统计" :loading="loading">
+    <div ref="chartRef" :style="{ width, height }"></div>
+  </Card>
+</template>
+<script lang="ts">
+  import type { Ref } from 'vue';
+  import { defineComponent, ref, watch } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import { useECharts } from '/@/hooks/web/useECharts';
+
+  export default defineComponent({
+    components: { Card },
+    props: {
+      loading: Boolean,
+      width: {
+        type: String as PropType<string>,
+        default: '100%',
+      },
+      height: {
+        type: String as PropType<string>,
+        default: '400px',
+      },
+    },
+    setup(props) {
+      const chartRef = ref<HTMLDivElement | null>(null);
+      const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
+      watch(
+        () => props.loading,
+        () => {
+          if (props.loading) {
+            return;
+          }
+          setOptions({
+            legend: {
+              bottom: 0,
+              data: ['Visits', 'Sales'],
+            },
+            tooltip: {},
+            radar: {
+              radius: '60%',
+              splitNumber: 8,
+              indicator: [
+                {
+                  name: '2017',
+                },
+                {
+                  name: '2017',
+                },
+                {
+                  name: '2018',
+                },
+                {
+                  name: '2019',
+                },
+                {
+                  name: '2020',
+                },
+                {
+                  name: '2021',
+                },
+              ],
+            },
+            series: [
+              {
+                type: 'radar' as 'custom',
+                symbolSize: 0,
+                areaStyle: {
+                  shadowBlur: 0,
+                  shadowColor: 'rgba(0,0,0,.2)',
+                  shadowOffsetX: 0,
+                  shadowOffsetY: 10,
+                  opacity: 1,
+                },
+                data: [
+                  {
+                    value: [90, 50, 86, 40, 50, 20],
+                    name: 'Visits',
+                    itemStyle: {
+                      color: '#9f8ed7',
+                    },
+                  },
+                  {
+                    value: [70, 75, 70, 76, 20, 85],
+                    name: 'Sales',
+                    itemStyle: {
+                      color: '#1edec5',
+                    },
+                  },
+                ],
+              },
+            ],
+          });
+        },
+        { immediate: true }
+      );
+      return { chartRef };
+    },
+  });
+</script>

Разлика између датотеке није приказан због своје велике величине
+ 87 - 0
src/views/demo/charts/china.json


+ 189 - 0
src/views/demo/charts/data.ts

@@ -0,0 +1,189 @@
+export const mapData: any = [
+  {
+    name: '北京',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '天津',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '上海',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '重庆',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '河北',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '河南',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '云南',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '辽宁',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '黑龙江',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '湖南',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '安徽',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '山东',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '新疆',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '江苏',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '浙江',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '江西',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '湖北',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '广西',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '甘肃',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '山西',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '内蒙古',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '陕西',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '吉林',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '福建',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '贵州',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '广东',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '青海',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '西藏',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '四川',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '宁夏',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '海南',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '台湾',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '香港',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+  {
+    name: '澳门',
+    value: Math.round(Math.random() * 1000),
+    tipData: [Math.round(Math.random() * 1000), Math.round(Math.random() * 1000)],
+  },
+];
+
+export const getLineData = (() => {
+  const category: any[] = [];
+  let dottedBase = +new Date();
+  const lineData: any[] = [];
+  const barData: any[] = [];
+
+  for (let i = 0; i < 20; i++) {
+    const date = new Date((dottedBase += 1000 * 3600 * 24));
+    category.push([date.getFullYear(), date.getMonth() + 1, date.getDate()].join('-'));
+    const b = Math.random() * 200;
+    const d = Math.random() * 200;
+    barData.push(b);
+    lineData.push(d + b);
+  }
+  return { barData, category, lineData };
+})();

+ 45 - 0
src/views/demo/charts/map/Baidu.vue

@@ -0,0 +1,45 @@
+<template>
+  <div ref="wrapRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, unref, onMounted } from 'vue';
+
+  import { useScript } from '/@/hooks/web/useScript';
+
+  const BAI_DU_MAP_URL = 'https://api.map.baidu.com/getscript?v=3.0&ak=OaBvYmKX3pjF7YFUFeeBCeGdy9Zp7xB2&services=&t=20210201100830&s=1';
+  export default defineComponent({
+    name: 'BaiduMap',
+    props: {
+      width: {
+        type: String,
+        default: '100%',
+      },
+      height: {
+        type: String,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const wrapRef = ref<HTMLDivElement | null>(null);
+      const { toPromise } = useScript({ src: BAI_DU_MAP_URL });
+
+      async function initMap() {
+        await toPromise();
+        await nextTick();
+        const wrapEl = unref(wrapRef);
+        if (!wrapEl) return;
+        const BMap = (window as any).BMap;
+        const map = new BMap.Map(wrapEl);
+        const point = new BMap.Point(116.404, 39.915);
+        map.centerAndZoom(point, 15);
+        map.enableScrollWheelZoom(true);
+      }
+
+      onMounted(() => {
+        initMap();
+      });
+
+      return { wrapRef };
+    },
+  });
+</script>

+ 47 - 0
src/views/demo/charts/map/Gaode.vue

@@ -0,0 +1,47 @@
+<template>
+  <div ref="wrapRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, unref, onMounted } from 'vue';
+
+  import { useScript } from '/@/hooks/web/useScript';
+
+  const A_MAP_URL = 'https://webapi.amap.com/maps?v=2.0&key=06313eb9c6563b674a8fd789db0692c3';
+
+  export default defineComponent({
+    name: 'AMap',
+    props: {
+      width: {
+        type: String,
+        default: '100%',
+      },
+      height: {
+        type: String,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const wrapRef = ref<HTMLDivElement | null>(null);
+      const { toPromise } = useScript({ src: A_MAP_URL });
+
+      async function initMap() {
+        await toPromise();
+        await nextTick();
+        const wrapEl = unref(wrapRef);
+        if (!wrapEl) return;
+        const AMap = (window as any).AMap;
+        new AMap.Map(wrapEl, {
+          zoom: 11,
+          center: [116.397428, 39.90923],
+          viewMode: '3D',
+        });
+      }
+
+      onMounted(() => {
+        initMap();
+      });
+
+      return { wrapRef };
+    },
+  });
+</script>

+ 52 - 0
src/views/demo/charts/map/Google.vue

@@ -0,0 +1,52 @@
+<template>
+  <div ref="wrapRef" :style="{ height, width }"></div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick, unref, onMounted } from 'vue';
+
+  import { useScript } from '/@/hooks/web/useScript';
+
+  const MAP_URL = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyBQWrGwj4gAzKndcbwD5favT9K0wgty_0&signed_in=true';
+
+  export default defineComponent({
+    name: 'GoogleMap',
+    props: {
+      width: {
+        type: String,
+        default: '100%',
+      },
+      height: {
+        type: String,
+        default: 'calc(100vh - 78px)',
+      },
+    },
+    setup() {
+      const wrapRef = ref<HTMLDivElement | null>(null);
+      const { toPromise } = useScript({ src: MAP_URL });
+
+      async function initMap() {
+        await toPromise();
+        await nextTick();
+        const wrapEl = unref(wrapRef);
+        if (!wrapEl) return;
+        const google = (window as any).google;
+        const latLng = { lat: 116.404, lng: 39.915 };
+        const map = new google.maps.Map(wrapEl, {
+          zoom: 4,
+          center: latLng,
+        });
+        new google.maps.Marker({
+          position: latLng,
+          map: map,
+          title: 'Hello World!',
+        });
+      }
+
+      onMounted(() => {
+        initMap();
+      });
+
+      return { wrapRef };
+    },
+  });
+</script>

+ 67 - 0
src/views/demo/codemirror/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <div>
+    <textarea ref="textarea">
+白日依山尽,黄河入海流。
+欲穷千里目,更上一层楼。
+        </textarea
+    >
+  </div>
+</template>
+
+<script lang="ts">
+  import { defineComponent, onMounted, ref, reactive } from 'vue';
+  // 引入全局实例
+  import _CodeMirror from 'codemirror';
+
+  // 核心样式
+  import 'codemirror/lib/codemirror.css';
+  // 引入主题后还需要在 options 中指定主题才会生效
+  import 'codemirror/theme/cobalt.css';
+
+  // 需要引入具体的语法高亮库才会有对应的语法高亮效果
+  // codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
+  // 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
+  import 'codemirror/mode/javascript/javascript.js';
+  import 'codemirror/mode/css/css.js';
+  import 'codemirror/mode/xml/xml.js';
+  import 'codemirror/mode/clike/clike.js';
+  import 'codemirror/mode/markdown/markdown.js';
+  import 'codemirror/mode/python/python.js';
+  import 'codemirror/mode/r/r.js';
+  import 'codemirror/mode/shell/shell.js';
+  import 'codemirror/mode/sql/sql.js';
+  import 'codemirror/mode/swift/swift.js';
+  import 'codemirror/mode/vue/vue.js';
+  // 尝试获取全局实例
+
+  export default defineComponent({
+    components: {},
+    setup() {
+      const CodeMirror = window.CodeMirror || _CodeMirror;
+
+      const textarea = ref(null);
+      const options = reactive({
+        // 缩进格式
+        tabSize: 2,
+        // 主题,对应主题库 JS 需要提前引入
+        theme: 'cobalt',
+        // 显示行号
+        lineNumbers: true,
+        line: true,
+      });
+      onMounted(() => {
+        init();
+      });
+
+      function init() {
+        CodeMirror.fromTextArea(textarea.value, options);
+      }
+
+      return {
+        textarea,
+      };
+    },
+  });
+</script>
+
+<style lang="css" scoped></style>

+ 110 - 0
src/views/demo/comp/button/index.vue

@@ -0,0 +1,110 @@
+<template>
+  <PageWrapper
+    contentFullHeight
+    title="基础组件"
+    content=" 基础组件依赖于ant-design-vue,组件库已有的基础组件,项目中不会再次进行demo展示(二次封装组件除外)"
+  >
+    <a-row :gutter="[20, 20]">
+      <a-col :xl="10" :lg="24">
+        <a-card title="BasicButton Color">
+          <div class="my-2">
+            <h3>success</h3>
+            <div class="py-2">
+              <a-button color="success"> 成功 </a-button>
+              <a-button color="success" class="ml-2" disabled> 禁用 </a-button>
+              <a-button color="success" class="ml-2" loading> loading </a-button>
+              <a-button color="success" type="link" class="ml-2"> link </a-button>
+              <a-button color="success" type="link" class="ml-2" loading> loading link </a-button>
+              <a-button color="success" type="link" class="ml-2" disabled> disabled link </a-button>
+            </div>
+          </div>
+
+          <div class="my-2">
+            <h3>warning</h3>
+            <a-button color="warning"> 警告 </a-button>
+            <a-button color="warning" class="ml-2" disabled> 禁用 </a-button>
+            <a-button color="warning" class="ml-2" loading> loading </a-button>
+            <a-button color="warning" type="link" class="ml-2"> link </a-button>
+            <a-button color="warning" type="link" class="ml-2" loading> loading link </a-button>
+            <a-button color="warning" type="link" class="ml-2" disabled> disabled link </a-button>
+          </div>
+
+          <div class="my-2">
+            <h3>error</h3>
+            <a-button color="error"> 错误 </a-button>
+            <a-button color="error" class="ml-2" disabled> 禁用 </a-button>
+            <a-button color="error" class="ml-2" loading> loading </a-button>
+            <a-button color="error" type="link" class="ml-2"> link </a-button>
+            <a-button color="error" type="link" class="ml-2" loading> loading link </a-button>
+            <a-button color="error" type="link" class="ml-2" disabled> disabled link </a-button>
+          </div>
+
+          <div class="my-2">
+            <h3>ghost</h3>
+            <a-button ghost color="success" class="ml-2"> 幽灵成功 </a-button>
+            <a-button ghost color="warning" class="ml-2"> 幽灵警告 </a-button>
+            <a-button ghost color="error" class="ml-2"> 幽灵错误 </a-button>
+            <a-button ghost type="dashed" color="warning" class="ml-2"> 幽灵警告dashed </a-button>
+            <a-button ghost danger class="ml-2"> 幽灵危险 </a-button>
+          </div>
+        </a-card>
+      </a-col>
+      <a-col :xl="14" :lg="24">
+        <a-card title="BasicButton Types">
+          <div class="my-2">
+            <h3>primary</h3>
+            <a-button type="primary" preIcon="mdi:page-next-outline"> 主按钮 </a-button>
+            <a-button type="primary" class="ml-2" disabled> 禁用 </a-button>
+            <a-button type="primary" class="ml-2" danger preIcon="mdi:page-next-outline"> 危险 </a-button>
+            <a-button type="primary" class="ml-2" loading> loading </a-button>
+            <a-button type="link" class="ml-2"> link </a-button>
+            <a-button type="link" class="ml-2" loading> loading link </a-button>
+            <a-button type="link" class="ml-2" disabled> disabled link </a-button>
+          </div>
+
+          <div class="my-2">
+            <h3>default</h3>
+            <a-button type="default"> 默认 </a-button>
+            <a-button type="default" class="ml-2" disabled> 禁用 </a-button>
+            <a-button type="default" class="ml-2" danger> 危险 </a-button>
+            <a-button type="default" class="ml-2" loading> loading </a-button>
+            <a-button type="link" class="ml-2"> link </a-button>
+            <a-button type="link" class="ml-2" loading> loading link </a-button>
+            <a-button type="link" class="ml-2" disabled> disabled link </a-button>
+          </div>
+
+          <div class="my-2">
+            <h3>dashed</h3>
+            <a-button type="dashed"> dashed </a-button>
+            <a-button type="dashed" class="ml-2" disabled> 禁用 </a-button>
+            <a-button type="dashed" class="ml-2" danger> 危险 </a-button>
+            <a-button type="dashed" class="ml-2" loading> loading </a-button>
+          </div>
+
+          <div class="my-2">
+            <h3>ghost 常规幽灵按钮通常用于有色背景下</h3>
+            <div class="bg-gray-400 py-2">
+              <a-button ghost type="primary" class="ml-2"> 幽灵主要 </a-button>
+              <a-button ghost type="default" class="ml-2"> 幽灵默认 </a-button>
+              <a-button ghost type="dashed" class="ml-2"> 幽灵dashed </a-button>
+              <a-button ghost type="primary" class="ml-2" disabled> 禁用 </a-button>
+              <a-button ghost type="primary" class="ml-2" loading> loading </a-button>
+            </div>
+            <!-- antd 按钮不能同时使用ghost和link -->
+            <!--      <a-button ghost type="link" class="ml-2"> link </a-button>-->
+            <!--      <a-button ghost type="link" class="ml-2" loading> loading link </a-button>-->
+            <!--      <a-button ghost type="link" class="ml-2" disabled> disabled link </a-button>-->
+          </div>
+        </a-card>
+      </a-col>
+    </a-row>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+  import { Card, Row, Col } from 'ant-design-vue';
+  export default defineComponent({
+    components: { PageWrapper, ACard: Card, ARow: Row, ACol: Col },
+  });
+</script>

+ 32 - 0
src/views/demo/comp/card-list/index.vue

@@ -0,0 +1,32 @@
+<template>
+  <PageWrapper title="卡片列表示例" content="基础封装">
+    <CardList :params="params" :api="demoListApi" @getMethod="getMethod" @delete="handleDel">
+      <template #header>
+        <Button type="primary" color="error"> 按钮1 </Button>
+        <Button type="primary" color="success"> 按钮2 </Button>
+      </template>
+    </CardList>
+  </PageWrapper>
+</template>
+<script lang="ts" setup>
+  import { CardList } from '/@/components/CardList';
+  import { Button } from '/@/components/Button';
+  import { PageWrapper } from '/@/components/Page';
+  import { demoListApi } from '/@/api/demo/table';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  const { notification } = useMessage();
+  // 请求api时附带参数
+  const params = {};
+
+  let reload = () => {};
+  // 获取内部fetch方法;
+  function getMethod(m: any) {
+    reload = m;
+  }
+  //删除按钮事件
+  function handleDel(id) {
+    console.log(id);
+    notification.success({ message: `成功删除${id}` });
+    reload();
+  }
+</script>

+ 42 - 0
src/views/demo/comp/count-to/index.vue

@@ -0,0 +1,42 @@
+<template>
+  <PageWrapper title="数字动画示例">
+    <Card>
+      <CardGrid class="count-to-demo-card">
+        <CountTo prefix="$" :color="'#409EFF'" :startVal="1" :endVal="200000" :duration="8000" />
+      </CardGrid>
+      <CardGrid class="count-to-demo-card">
+        <CountTo suffix="$" :color="'red'" :startVal="1" :endVal="300000" :decimals="2" :duration="6000" />
+      </CardGrid>
+      <CardGrid class="count-to-demo-card">
+        <CountTo suffix="$" :color="'rgb(0,238,0)'" :startVal="1" :endVal="400000" :duration="7000" />
+      </CardGrid>
+      <CardGrid class="count-to-demo-card">
+        <CountTo separator="-" :color="'rgba(138,43,226,.6)'" :startVal="10000" :endVal="500000" :duration="8000" />
+      </CardGrid>
+    </Card>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Card } from 'ant-design-vue';
+  import { CountTo } from '/@/components/CountTo/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: {
+      Card,
+      CardGrid: Card.Grid,
+      CountTo,
+      PageWrapper,
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .count-to-demo {
+    &-card {
+      width: 50%;
+      font-size: 30px;
+      text-align: center;
+    }
+  }
+</style>

+ 94 - 0
src/views/demo/comp/cropper/index.vue

@@ -0,0 +1,94 @@
+<template>
+  <PageWrapper title="图片裁剪示例" content="需要开启测试接口服务才能进行上传测试!">
+    <CollapseContainer title="头像裁剪">
+      <CropperAvatar :uploadApi="uploadApi" :value="avatar" />
+    </CollapseContainer>
+
+    <CollapseContainer title="矩形裁剪" class="my-4">
+      <div class="container p-4">
+        <div class="cropper-container mr-10">
+          <CropperImage ref="refCropper" :src="img" @cropend="handleCropend" style="width: 40vw" />
+        </div>
+        <img :src="cropperImg" class="croppered" v-if="cropperImg" alt="" />
+      </div>
+      <p v-if="cropperImg">裁剪后图片信息:{{ info }}</p>
+    </CollapseContainer>
+
+    <CollapseContainer title="圆形裁剪">
+      <div class="container p-4">
+        <div class="cropper-container mr-10">
+          <CropperImage ref="refCropper" :src="img" @cropend="handleCircleCropend" style="width: 40vw" circled />
+        </div>
+        <img :src="circleImg" class="croppered" v-if="circleImg" />
+      </div>
+      <p v-if="circleImg">裁剪后图片信息:{{ circleInfo }}</p>
+    </CollapseContainer>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+  import { CollapseContainer } from '/@/components/Container';
+  import { CropperImage, CropperAvatar } from '/@/components/Cropper';
+  import { uploadApi } from '/@/api/sys/upload';
+  import img from '/@/assets/images/header.jpg';
+  import { useUserStore } from '/@/store/modules/user';
+
+  export default defineComponent({
+    components: {
+      PageWrapper,
+      CropperImage,
+      CollapseContainer,
+      CropperAvatar,
+    },
+    setup() {
+      const info = ref('');
+      const cropperImg = ref('');
+      const circleInfo = ref('');
+      const circleImg = ref('');
+      const userStore = useUserStore();
+      const avatar = ref(userStore.getUserInfo?.avatar || '');
+      function handleCropend({ imgBase64, imgInfo }) {
+        info.value = imgInfo;
+        cropperImg.value = imgBase64;
+      }
+
+      function handleCircleCropend({ imgBase64, imgInfo }) {
+        circleInfo.value = imgInfo;
+        circleImg.value = imgBase64;
+      }
+
+      return {
+        img,
+        info,
+        circleInfo,
+        cropperImg,
+        circleImg,
+        handleCropend,
+        handleCircleCropend,
+        avatar,
+        uploadApi: uploadApi as any,
+      };
+    },
+  });
+</script>
+
+<style scoped>
+  .container {
+    display: flex;
+    width: 100vw;
+    align-items: center;
+  }
+
+  .cropper-container {
+    width: 40vw;
+  }
+
+  .croppered {
+    height: 360px;
+  }
+
+  p {
+    margin: 10px;
+  }
+</style>

+ 79 - 0
src/views/demo/comp/desc/index.vue

@@ -0,0 +1,79 @@
+<template>
+  <PageWrapper title="详情组件示例">
+    <Description title="基础示例" :collapseOptions="{ canExpand: true, helpMessage: 'help me' }" :column="3" :data="mockData" :schema="schema" />
+
+    <Description
+      class="mt-4"
+      title="垂直示例"
+      layout="vertical"
+      :collapseOptions="{ canExpand: true, helpMessage: 'help me' }"
+      :column="2"
+      :data="mockData"
+      :schema="schema"
+    />
+
+    <Description @register="register" class="mt-4" />
+    <Description @register="register1" class="mt-4" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Description, DescItem, useDescription } from '/@/components/Description/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  const mockData: Recordable = {
+    username: 'test',
+    nickName: 'VB',
+    age: '123',
+    phone: '15695909xxx',
+    email: '190848757@qq.com',
+    addr: '厦门市思明区',
+    sex: '男',
+    certy: '3504256199xxxxxxxxx',
+    tag: 'orange',
+  };
+  const schema: DescItem[] = [
+    {
+      field: 'username',
+      label: '用户名',
+    },
+    {
+      field: 'nickName',
+      label: '昵称',
+      render: (curVal, data) => {
+        return `${data.username}-${curVal}`;
+      },
+    },
+    {
+      field: 'phone',
+      label: '联系电话',
+    },
+    {
+      field: 'email',
+      label: '邮箱',
+    },
+    {
+      field: 'addr',
+      label: '地址',
+    },
+  ];
+  export default defineComponent({
+    components: { Description, PageWrapper },
+    setup() {
+      const [register] = useDescription({
+        title: 'useDescription',
+        data: mockData,
+        schema: schema,
+      });
+
+      const [register1] = useDescription({
+        title: '无边框',
+        bordered: false,
+        data: mockData,
+        schema: schema,
+      });
+
+      return { mockData, schema, register, register1 };
+    },
+  });
+</script>

+ 13 - 0
src/views/demo/comp/drawer/Drawer1.vue

@@ -0,0 +1,13 @@
+<template>
+  <BasicDrawer v-bind="$attrs" title="Drawer Title" width="50%"> Drawer Info. </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer } from '/@/components/Drawer';
+  export default defineComponent({
+    components: { BasicDrawer },
+    setup() {
+      return {};
+    },
+  });
+</script>

+ 17 - 0
src/views/demo/comp/drawer/Drawer2.vue

@@ -0,0 +1,17 @@
+<template>
+  <BasicDrawer v-bind="$attrs" @register="register" title="Drawer Title" width="50%">
+    Drawer Info.
+    <a-button type="primary" @click="closeDrawer"> 内部关闭drawer </a-button>
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+  export default defineComponent({
+    components: { BasicDrawer },
+    setup() {
+      const [register, { closeDrawer }] = useDrawerInner();
+      return { register, closeDrawer };
+    },
+  });
+</script>

+ 35 - 0
src/views/demo/comp/drawer/Drawer3.vue

@@ -0,0 +1,35 @@
+<template>
+  <BasicDrawer v-bind="$attrs" title="Modal Title" width="50%" showFooter @ok="handleOk">
+    <p class="h-20" v-for="index in 40" :key="index"> 根据屏幕高度自适应 </p>
+    <template #insertFooter>
+      <a-button> btn</a-button>
+    </template>
+    <template #centerFooter>
+      <a-button> btn2</a-button>
+    </template>
+
+    <template #appendFooter>
+      <a-button> btn3</a-button>
+    </template>
+
+    <!-- <template #footer>
+      <a-button> customerFooter</a-button>
+    </template> -->
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer } from '/@/components/Drawer';
+  export default defineComponent({
+    components: { BasicDrawer },
+    setup() {
+      return {
+        handleOk: () => {
+          console.log('=====================');
+          console.log('ok');
+          console.log('======================');
+        },
+      };
+    },
+  });
+</script>

+ 53 - 0
src/views/demo/comp/drawer/Drawer4.vue

@@ -0,0 +1,53 @@
+<template>
+  <BasicDrawer v-bind="$attrs" @register="register" title="Drawer Title" width="50%">
+    <div>
+      <BasicForm @register="registerForm" />
+    </div>
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Input',
+      label: '字段1',
+      colProps: {
+        span: 12,
+      },
+      defaultValue: '111',
+    },
+    {
+      field: 'field2',
+      component: 'Input',
+      label: '字段2',
+      colProps: {
+        span: 12,
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicDrawer, BasicForm },
+    setup() {
+      const [registerForm, { setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+      const [register] = useDrawerInner((data) => {
+        // 方式1
+        setFieldsValue({
+          field2: data.data,
+          field1: data.info,
+        });
+      });
+      return { register, schemas, registerForm };
+    },
+  });
+</script>

+ 13 - 0
src/views/demo/comp/drawer/Drawer5.vue

@@ -0,0 +1,13 @@
+<template>
+  <BasicDrawer v-bind="$attrs" :isDetail="true" title="Drawer Title5">
+    <p class="h-20"> Content Message </p>
+    <template #titleToolbar> toolbar </template>
+  </BasicDrawer>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicDrawer } from '/@/components/Drawer';
+  export default defineComponent({
+    components: { BasicDrawer },
+  });
+</script>

+ 69 - 0
src/views/demo/comp/drawer/index.vue

@@ -0,0 +1,69 @@
+<template>
+  <PageWrapper title="抽屉组件使用示例">
+    <Alert message="使用 useDrawer 进行抽屉操作" show-icon />
+    <a-button type="primary" class="my-4" @click="openDrawerLoading"> 打开Drawer </a-button>
+
+    <Alert message="内外同时控制显示隐藏" show-icon />
+    <a-button type="primary" class="my-4" @click="openDrawer2(true)"> 打开Drawer </a-button>
+    <Alert message="自适应高度/显示footer" show-icon />
+    <a-button type="primary" class="my-4" @click="openDrawer3(true)"> 打开Drawer </a-button>
+
+    <Alert message="内外数据交互" show-icon />
+    <a-button type="primary" class="my-4" @click="send"> 打开Drawer并传递数据 </a-button>
+    <Alert message="详情页模式" show-icon />
+    <a-button type="primary" class="my-4" @click="openDrawer5(true)"> 打开详情Drawer </a-button>
+    <Drawer1 @register="register1" />
+    <Drawer2 @register="register2" />
+    <Drawer3 @register="register3" />
+    <Drawer4 @register="register4" />
+    <Drawer5 @register="register5" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Alert } from 'ant-design-vue';
+  import { useDrawer } from '/@/components/Drawer';
+  import Drawer1 from './Drawer1.vue';
+  import Drawer2 from './Drawer2.vue';
+  import Drawer3 from './Drawer3.vue';
+  import Drawer4 from './Drawer4.vue';
+  import Drawer5 from './Drawer5.vue';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { Alert, PageWrapper, Drawer1, Drawer2, Drawer3, Drawer4, Drawer5 },
+    setup() {
+      const [register1, { openDrawer: openDrawer1, setDrawerProps }] = useDrawer();
+      const [register2, { openDrawer: openDrawer2 }] = useDrawer();
+      const [register3, { openDrawer: openDrawer3 }] = useDrawer();
+      const [register4, { openDrawer: openDrawer4 }] = useDrawer();
+      const [register5, { openDrawer: openDrawer5 }] = useDrawer();
+      function send() {
+        openDrawer4(true, {
+          data: 'content',
+          info: 'Info',
+        });
+      }
+      function openDrawerLoading() {
+        openDrawer1();
+        setDrawerProps({ loading: true });
+        setTimeout(() => {
+          setDrawerProps({ loading: false });
+        }, 2000);
+      }
+      return {
+        register1,
+        openDrawer1,
+        register2,
+        openDrawer2,
+        register3,
+        openDrawer3,
+        register4,
+        register5,
+        openDrawer5,
+        send,
+        openDrawerLoading,
+      };
+    },
+  });
+</script>

+ 19 - 0
src/views/demo/comp/lazy/TargetContent.vue

@@ -0,0 +1,19 @@
+<template>
+  <Card hoverable :style="{ width: '240px', background: '#fff' }">
+    <template #cover>
+      <img alt="example" src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png" />
+    </template>
+    <CardMeta title="懒加载组件" />
+  </Card>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Card } from 'ant-design-vue';
+
+  export default defineComponent({
+    components: { CardMeta: Card.Meta, Card },
+    setup() {
+      return {};
+    },
+  });
+</script>

+ 77 - 0
src/views/demo/comp/lazy/Transition.vue

@@ -0,0 +1,77 @@
+<template>
+  <PageWrapper title="懒加载自定义动画示例" content="懒加载组件显示动画">
+    <div class="lazy-base-demo-wrap">
+      <h1>向下滚动</h1>
+
+      <div class="lazy-base-demo-box">
+        <LazyContainer transitionName="custom">
+          <TargetContent />
+        </LazyContainer>
+      </div>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import TargetContent from './TargetContent.vue';
+  import { LazyContainer } from '/@/components/Container/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { LazyContainer, TargetContent, PageWrapper },
+  });
+</script>
+<style lang="less">
+  .lazy-base-demo {
+    &-wrap {
+      display: flex;
+      width: 50%;
+      height: 2000px;
+      margin: 20px auto;
+      text-align: center;
+      background-color: @component-background;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    &-box {
+      width: 300px;
+      height: 300px;
+    }
+
+    h1 {
+      height: 1300px;
+      margin: 20px 0;
+    }
+  }
+
+  .custom-enter {
+    opacity: 0;
+    transform: scale(0.4) translate(100%);
+  }
+
+  .custom-enter-to {
+    opacity: 1;
+  }
+
+  .custom-enter-active {
+    position: absolute;
+    top: 0;
+    width: 100%;
+    transition: all 0.5s;
+  }
+
+  .custom-leave {
+    opacity: 1;
+  }
+
+  .custom-leave-to {
+    opacity: 0;
+    transform: scale(0.4) translate(-100%);
+  }
+
+  .custom-leave-active {
+    transition: all 0.5s;
+  }
+</style>

+ 52 - 0
src/views/demo/comp/lazy/index.vue

@@ -0,0 +1,52 @@
+<template>
+  <PageWrapper title="懒加载基础示例" content="向下滚动到可见区域才会加载组件">
+    <div class="lazy-base-demo-wrap">
+      <h1>向下滚动</h1>
+
+      <div class="lazy-base-demo-box">
+        <LazyContainer>
+          <TargetContent />
+          <template #skeleton>
+            <Skeleton :rows="10" />
+          </template>
+        </LazyContainer>
+      </div>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Skeleton } from 'ant-design-vue';
+  import TargetContent from './TargetContent.vue';
+  import { LazyContainer } from '/@/components/Container/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { LazyContainer, PageWrapper, TargetContent, Skeleton },
+  });
+</script>
+<style lang="less">
+  .lazy-base-demo {
+    &-wrap {
+      display: flex;
+      width: 50%;
+      height: 2000px;
+      margin: 20px auto;
+      text-align: center;
+      background-color: @component-background;
+      justify-content: center;
+      flex-direction: column;
+      align-items: center;
+    }
+
+    &-box {
+      width: 300px;
+      height: 300px;
+    }
+
+    h1 {
+      height: 1300px;
+      margin: 20px 0;
+    }
+  }
+</style>

+ 101 - 0
src/views/demo/comp/loading/index.vue

@@ -0,0 +1,101 @@
+<template>
+  <PageWrapper v-loading="loadingRef" loading-tip="加载中..." title="Loading组件示例">
+    <div ref="wrapEl">
+      <a-alert message="组件方式" />
+      <a-button class="my-4 mr-4" type="primary" @click="openCompFullLoading"> 全屏 Loading </a-button>
+      <a-button class="my-4" type="primary" @click="openCompAbsolute"> 容器内 Loading </a-button>
+      <Loading :loading="loading" :absolute="absolute" :theme="theme" :background="background" :tip="tip" />
+
+      <a-alert message="函数方式" />
+
+      <a-button class="my-4 mr-4" type="primary" @click="openFnFullLoading"> 全屏 Loading </a-button>
+      <a-button class="my-4" type="primary" @click="openFnWrapLoading"> 容器内 Loading </a-button>
+
+      <a-alert message="指令方式" />
+      <a-button class="my-4 mr-4" type="primary" @click="openDirectiveLoading"> 打开指令Loading </a-button>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, toRefs, ref } from 'vue';
+  import { Loading, useLoading } from '/@/components/Loading';
+  import { PageWrapper } from '/@/components/Page';
+  import { Alert } from 'ant-design-vue';
+
+  export default defineComponent({
+    components: { Loading, PageWrapper, [Alert.name]: Alert },
+    setup() {
+      const wrapEl = ref<ElRef>(null);
+
+      const loadingRef = ref(false);
+      const compState = reactive({
+        absolute: false,
+        loading: false,
+        theme: 'dark',
+        background: 'rgba(111,111,111,.7)',
+        tip: '加载中...',
+      });
+      const [openFullLoading, closeFullLoading] = useLoading({
+        tip: '加载中...',
+      });
+
+      const [openWrapLoading, closeWrapLoading] = useLoading({
+        target: wrapEl,
+        props: {
+          tip: '加载中...',
+          absolute: true,
+        },
+      });
+
+      function openLoading(absolute: boolean) {
+        compState.absolute = absolute;
+        compState.loading = true;
+        setTimeout(() => {
+          compState.loading = false;
+        }, 2000);
+      }
+
+      function openCompFullLoading() {
+        openLoading(false);
+      }
+
+      function openCompAbsolute() {
+        openLoading(true);
+      }
+
+      function openFnFullLoading() {
+        openFullLoading();
+
+        setTimeout(() => {
+          closeFullLoading();
+        }, 2000);
+      }
+
+      function openFnWrapLoading() {
+        openWrapLoading();
+
+        setTimeout(() => {
+          closeWrapLoading();
+        }, 2000);
+      }
+
+      function openDirectiveLoading() {
+        loadingRef.value = true;
+        setTimeout(() => {
+          loadingRef.value = false;
+        }, 2000);
+      }
+
+      return {
+        openCompFullLoading,
+        openFnFullLoading,
+        openFnWrapLoading,
+        openCompAbsolute,
+        wrapEl,
+        loadingRef,
+        openDirectiveLoading,
+        ...toRefs(compState),
+      };
+    },
+  });
+</script>

+ 58 - 0
src/views/demo/comp/modal/Modal1.vue

@@ -0,0 +1,58 @@
+<template>
+  <BasicModal v-bind="$attrs" destroyOnClose @register="register" title="Modal Title" :helpMessage="['提示1', '提示2']" @visible-change="handleShow">
+    <template #insertFooter>
+      <a-button type="primary" danger @click="setLines" :disabled="loading">点我更新内容</a-button>
+    </template>
+    <template v-if="loading">
+      <div class="empty-tips"> 加载中,稍等3秒…… </div>
+    </template>
+    <template v-if="!loading">
+      <ul>
+        <li v-for="index in lines" :key="index">加载完成{{ index }}!</li>
+      </ul>
+    </template>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, watch } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  export default defineComponent({
+    components: { BasicModal },
+    setup() {
+      const loading = ref(true);
+      const lines = ref(10);
+      const [register, { setModalProps, redoModalHeight }] = useModalInner();
+
+      watch(
+        () => lines.value,
+        () => {
+          redoModalHeight();
+        }
+      );
+
+      function handleShow(visible: boolean) {
+        if (visible) {
+          loading.value = true;
+          setModalProps({ loading: true, confirmLoading: true });
+          setTimeout(() => {
+            lines.value = Math.round(Math.random() * 30 + 5);
+            loading.value = false;
+            setModalProps({ loading: false, confirmLoading: false });
+          }, 3000);
+        }
+      }
+
+      function setLines() {
+        lines.value = Math.round(Math.random() * 20 + 10);
+      }
+      return { register, loading, handleShow, lines, setLines };
+    },
+  });
+</script>
+<style scoped>
+  .empty-tips {
+    height: 100px;
+    line-height: 100px;
+    text-align: center;
+  }
+</style>

+ 23 - 0
src/views/demo/comp/modal/Modal2.vue

@@ -0,0 +1,23 @@
+<template>
+  <BasicModal @register="register" title="Modal Title" :helpMessage="['提示1', '提示2']" :okButtonProps="{ disabled: true }">
+    <a-button type="primary" @click="closeModal" class="mr-2"> 从内部关闭弹窗 </a-button>
+    <a-button type="primary" @click="setModalProps"> 从内部修改title </a-button>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  export default defineComponent({
+    components: { BasicModal },
+    setup() {
+      const [register, { closeModal, setModalProps }] = useModalInner();
+      return {
+        register,
+        closeModal,
+        setModalProps: () => {
+          setModalProps({ title: 'Modal New Title' });
+        },
+      };
+    },
+  });
+</script>

+ 15 - 0
src/views/demo/comp/modal/Modal3.vue

@@ -0,0 +1,15 @@
+<template>
+  <BasicModal v-bind="$attrs" title="Modal Title" :helpMessage="['提示1', '提示2']" width="700px">
+    <p class="h-20" v-for="index in 20" :key="index"> 根据屏幕高度自适应 </p>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicModal } from '/@/components/Modal';
+  export default defineComponent({
+    components: { BasicModal },
+    setup() {
+      return {};
+    },
+  });
+</script>

+ 81 - 0
src/views/demo/comp/modal/Modal4.vue

@@ -0,0 +1,81 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="register" title="Modal Title" @visible-change="handleVisibleChange">
+    <div class="pt-3px pr-3px">
+      <BasicForm @register="registerForm" :model="model" />
+    </div>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, nextTick } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Input',
+      label: '字段1',
+      colProps: {
+        span: 24,
+      },
+      defaultValue: '111',
+    },
+    {
+      field: 'field2',
+      component: 'Input',
+      label: '字段2',
+      colProps: {
+        span: 24,
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    setup(props) {
+      const modelRef = ref({});
+      const [
+        registerForm,
+        {
+          // setFieldsValue,
+          // setProps
+        },
+      ] = useForm({
+        labelWidth: 120,
+        schemas,
+        showActionButtonGroup: false,
+        actionColOptions: {
+          span: 24,
+        },
+      });
+
+      const [register] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+
+      function onDataReceive(data) {
+        console.log('Data Received', data);
+        // 方式1;
+        // setFieldsValue({
+        //   field2: data.data,
+        //   field1: data.info,
+        // });
+
+        // // 方式2
+        modelRef.value = { field2: data.data, field1: data.info };
+
+        // setProps({
+        //   model:{ field2: data.data, field1: data.info }
+        // })
+      }
+
+      function handleVisibleChange(v) {
+        v && props.userData && nextTick(() => onDataReceive(props.userData));
+      }
+
+      return { register, schemas, registerForm, model: modelRef, handleVisibleChange };
+    },
+  });
+</script>

+ 112 - 0
src/views/demo/comp/modal/index.vue

@@ -0,0 +1,112 @@
+<template>
+  <PageWrapper title="modal组件使用示例">
+    <Alert
+      message="使用 useModal 进行弹窗操作,默认可以拖动,可以通过 draggable
+    参数进行控制是否可以拖动/全屏,并演示了在Modal内动态加载内容并自动调整高度"
+      show-icon
+    />
+    <a-button type="primary" class="my-4" @click="openModalLoading"> 打开弹窗,加载动态数据并自动调整高度(默认可以拖动/全屏) </a-button>
+
+    <Alert message="内外同时同时显示隐藏" show-icon />
+    <a-button type="primary" class="my-4" @click="openModal2"> 打开弹窗</a-button>
+    <Alert message="自适应高度" show-icon />
+    <a-button type="primary" class="my-4" @click="openModal3"> 打开弹窗</a-button>
+
+    <Alert message="内外数据交互" show-icon />
+    <a-button type="primary" class="my-4" @click="send"> 打开弹窗并传递数据</a-button>
+
+    <Alert message="使用动态组件的方式在页面内使用多个弹窗" show-icon />
+    <a-space>
+      <a-button type="primary" class="my-4" @click="openTargetModal(1)"> 打开弹窗1</a-button>
+      <a-button type="primary" class="my-4" @click="openTargetModal(2)"> 打开弹窗2</a-button>
+      <a-button type="primary" class="my-4" @click="openTargetModal(3)"> 打开弹窗3</a-button>
+      <a-button type="primary" class="my-4" @click="openTargetModal(4)"> 打开弹窗4</a-button>
+    </a-space>
+
+    <component :is="currentModal" v-model:visible="modalVisible" :userData="userData" />
+
+    <Modal1 @register="register1" :minHeight="100" />
+    <Modal2 @register="register2" />
+    <Modal3 @register="register3" />
+    <Modal4 @register="register4" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, shallowRef, ComponentOptions, ref, nextTick } from 'vue';
+  import { Alert, Space } from 'ant-design-vue';
+  import { useModal } from '/@/components/Modal';
+  import Modal1 from './Modal1.vue';
+  import Modal2 from './Modal2.vue';
+  import Modal3 from './Modal3.vue';
+  import Modal4 from './Modal4.vue';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { Alert, Modal1, Modal2, Modal3, Modal4, PageWrapper, ASpace: Space },
+    setup() {
+      const currentModal = shallowRef<Nullable<ComponentOptions>>(null);
+      const [register1, { openModal: openModal1 }] = useModal();
+      const [register2, { openModal: openModal2 }] = useModal();
+      const [register3, { openModal: openModal3 }] = useModal();
+      const [register4, { openModal: openModal4 }] = useModal();
+      const modalVisible = ref<Boolean>(false);
+      const userData = ref<any>(null);
+
+      function send() {
+        openModal4(true, {
+          data: 'content',
+          info: 'Info',
+        });
+      }
+
+      function openModalLoading() {
+        openModal1(true);
+        // setModalProps({ loading: true });
+        // setTimeout(() => {
+        //   setModalProps({ loading: false });
+        // }, 2000);
+      }
+
+      function openTargetModal(index) {
+        switch (index) {
+          case 1:
+            currentModal.value = Modal1;
+            break;
+          case 2:
+            currentModal.value = Modal2;
+            break;
+          case 3:
+            currentModal.value = Modal3;
+            break;
+          default:
+            currentModal.value = Modal4;
+            break;
+        }
+        nextTick(() => {
+          // `useModal` not working with dynamic component
+          // passing data through `userData` prop
+          userData.value = { data: Math.random(), info: 'Info222' };
+          // open the target modal
+          modalVisible.value = true;
+        });
+      }
+
+      return {
+        register1,
+        openModal1,
+        register2,
+        openModal2,
+        register3,
+        openModal3,
+        register4,
+        openModal4,
+        modalVisible,
+        userData,
+        openTargetModal,
+        send,
+        currentModal,
+        openModalLoading,
+      };
+    },
+  });
+</script>

+ 117 - 0
src/views/demo/comp/qrcode/index.vue

@@ -0,0 +1,117 @@
+<template>
+  <PageWrapper title="二维码组件使用示例">
+    <div class="flex flex-wrap">
+      <CollapseContainer title="基础示例" :canExpan="true" class="text-center mb-6 qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" />
+      </CollapseContainer>
+
+      <CollapseContainer title="渲染成img标签示例" class="text-center mb-6 qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" tag="img" />
+      </CollapseContainer>
+
+      <CollapseContainer title="配置样式示例" class="text-center mb-6 qrcode-demo-item">
+        <QrCode
+          :value="qrCodeUrl"
+          :options="{
+            color: { dark: '#55D187' },
+          }"
+        />
+      </CollapseContainer>
+
+      <CollapseContainer title="本地logo示例" class="text-center mb-6 qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" :logo="LogoImg" />
+      </CollapseContainer>
+
+      <CollapseContainer title="在线logo示例" class="text-center mb-6 qrcode-demo-item">
+        <QrCode
+          :value="qrCodeUrl"
+          logo="http://jeecg.com/images/logo.png"
+          :options="{
+            color: { dark: '#55D187' },
+          }"
+        />
+      </CollapseContainer>
+
+      <CollapseContainer title="logo配置示例" class="text-center mb-6 qrcode-demo-item">
+        <QrCode
+          :value="qrCodeUrl"
+          :logo="{
+            src: 'http://jeecg.com/images/logo.png',
+            logoSize: 0.2,
+            borderSize: 0.05,
+            borderRadius: 50,
+            bgColor: 'blue',
+          }"
+        />
+      </CollapseContainer>
+
+      <CollapseContainer title="下载示例" class="text-center qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" ref="qrRef" :logo="LogoImg" />
+        <a-button class="mb-2" type="primary" @click="download"> 下载 </a-button>
+        <div class="msg"> (在线logo会导致图片跨域,需要下载图片需要自行解决跨域问题) </div>
+      </CollapseContainer>
+
+      <CollapseContainer title="配置大小示例" class="text-center qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" :width="300" />
+      </CollapseContainer>
+
+      <CollapseContainer title="扩展绘制示例" class="text-center qrcode-demo-item">
+        <QrCode :value="qrCodeUrl" :width="200" :options="{ margin: 5 }" ref="qrDiyRef" :logo="LogoImg" @done="onQrcodeDone" />
+        <a-button class="mb-2" type="primary" @click="downloadDiy"> 下载 </a-button>
+        <div class="msg"> 要进行扩展绘制则不能将tag设为img </div>
+      </CollapseContainer>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { QrCode, QrCodeActionType } from '/@/components/Qrcode/index';
+  import LogoImg from '/@/assets/images/logo.png';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  const qrCodeUrl = 'https://www.vvbin.cn';
+  export default defineComponent({
+    components: { CollapseContainer, QrCode, PageWrapper },
+    setup() {
+      const qrRef = ref<Nullable<QrCodeActionType>>(null);
+      const qrDiyRef = ref<Nullable<QrCodeActionType>>(null);
+      function download() {
+        const qrEl = unref(qrRef);
+        if (!qrEl) return;
+        qrEl.download('文件名');
+      }
+      function downloadDiy() {
+        const qrEl = unref(qrDiyRef);
+        if (!qrEl) return;
+        qrEl.download('Qrcode');
+      }
+
+      function onQrcodeDone({ ctx }: any) {
+        if (ctx instanceof CanvasRenderingContext2D) {
+          // 额外绘制
+          ctx.fillStyle = 'black';
+          ctx.font = '16px "微软雅黑"';
+          ctx.textBaseline = 'bottom';
+          ctx.textAlign = 'center';
+          ctx.fillText('你帅你先扫', 100, 195, 200);
+        }
+      }
+      return {
+        onQrcodeDone,
+        qrCodeUrl,
+        LogoImg,
+        download,
+        downloadDiy,
+        qrRef,
+        qrDiyRef,
+      };
+    },
+  });
+</script>
+<style scoped>
+  .qrcode-demo-item {
+    width: 30%;
+    margin-right: 1%;
+  }
+</style>

+ 59 - 0
src/views/demo/comp/scroll/Action.vue

@@ -0,0 +1,59 @@
+<template>
+  <PageWrapper title="滚动组件函数示例" content="基于el-scrollbar">
+    <div class="my-4">
+      <a-button @click="scrollTo(100)" class="mr-2"> 滚动到100px位置 </a-button>
+      <a-button @click="scrollTo(800)" class="mr-2"> 滚动到800px位置 </a-button>
+      <a-button @click="scrollTo(0)" class="mr-2"> 滚动到顶部 </a-button>
+      <a-button @click="scrollBottom()" class="mr-2"> 滚动到底部 </a-button>
+    </div>
+    <div class="scroll-wrap">
+      <ScrollContainer class="mt-4" ref="scrollRef">
+        <ul class="p-3">
+          <template v-for="index in 100" :key="index">
+            <li class="p-2" :style="{ border: '1px solid #eee' }">
+              {{ index }}
+            </li>
+          </template>
+        </ul>
+      </ScrollContainer>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { ScrollContainer, ScrollActionType } from '/@/components/Container/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { ScrollContainer, PageWrapper },
+    setup() {
+      const scrollRef = ref<Nullable<ScrollActionType>>(null);
+      const getScroll = () => {
+        const scroll = unref(scrollRef);
+        if (!scroll) {
+          throw new Error('scroll is Null');
+        }
+        return scroll;
+      };
+
+      function scrollTo(top: number) {
+        getScroll().scrollTo(top);
+      }
+      function scrollBottom() {
+        getScroll().scrollBottom();
+      }
+      return {
+        scrollTo,
+        scrollRef,
+        scrollBottom,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .scroll-wrap {
+    width: 50%;
+    height: 300px;
+    background-color: @component-background;
+  }
+</style>

+ 64 - 0
src/views/demo/comp/scroll/VirtualScroll.vue

@@ -0,0 +1,64 @@
+<template>
+  <PageWrapper class="virtual-scroll-demo">
+    <Divider>基础滚动示例</Divider>
+    <div class="virtual-scroll-demo-wrap">
+      <VScroll :itemHeight="41" :items="data" :height="300" :width="300">
+        <template #default="{ item }">
+          <div class="virtual-scroll-demo__item">
+            {{ item.title }}
+          </div>
+        </template>
+      </VScroll>
+    </div>
+
+    <Divider>即使不可见,也预先加载50条数据,防止空白</Divider>
+    <div class="virtual-scroll-demo-wrap">
+      <VScroll :itemHeight="41" :items="data" :height="300" :width="300" :bench="50">
+        <template #default="{ item }">
+          <div class="virtual-scroll-demo__item">
+            {{ item.title }}
+          </div>
+        </template>
+      </VScroll>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { VScroll } from '/@/components/VirtualScroll/index';
+
+  import { Divider } from 'ant-design-vue';
+  import { PageWrapper } from '/@/components/Page';
+  const data: Recordable[] = (() => {
+    const arr: Recordable[] = [];
+    for (let index = 1; index < 20000; index++) {
+      arr.push({
+        title: '列表项' + index,
+      });
+    }
+    return arr;
+  })();
+  export default defineComponent({
+    components: { VScroll: VScroll, Divider, PageWrapper },
+    setup() {
+      return { data: data };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .virtual-scroll-demo {
+    &-wrap {
+      display: flex;
+      margin: 0 30%;
+      background-color: @component-background;
+      justify-content: center;
+    }
+
+    &__item {
+      height: 40px;
+      padding: 0 20px;
+      line-height: 40px;
+      border-bottom: 1px solid @border-color-base;
+    }
+  }
+</style>

+ 31 - 0
src/views/demo/comp/scroll/index.vue

@@ -0,0 +1,31 @@
+<template>
+  <PageWrapper title="滚动组件示例" content="基于el-scrollbar">
+    <div class="scroll-wrap">
+      <ScrollContainer class="mt-4">
+        <ul class="p-3">
+          <template v-for="index in 100" :key="index">
+            <li class="p-2" :style="{ border: '1px solid #eee' }">
+              {{ index }}
+            </li>
+          </template>
+        </ul>
+      </ScrollContainer>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { ScrollContainer } from '/@/components/Container/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { ScrollContainer, PageWrapper },
+  });
+</script>
+<style lang="less" scoped>
+  .scroll-wrap {
+    width: 50%;
+    height: 300px;
+    background-color: @component-background;
+  }
+</style>

+ 32 - 0
src/views/demo/comp/strength-meter/index.vue

@@ -0,0 +1,32 @@
+<template>
+  <PageWrapper title="密码强度校验组件">
+    <div class="flex justify-center">
+      <div class="demo-wrap p-10">
+        <StrengthMeter placeholder="默认" />
+        <StrengthMeter placeholder="禁用" disabled />
+        <br />
+        <StrengthMeter placeholder="隐藏input" :show-input="false" value="!@#qwe12345" />
+      </div>
+    </div>
+  </PageWrapper>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { StrengthMeter } from '/@/components/StrengthMeter';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: {
+      StrengthMeter,
+      PageWrapper,
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .demo-wrap {
+    width: 50%;
+    background-color: @component-background;
+    border-radius: 10px;
+  }
+</style>

+ 44 - 0
src/views/demo/comp/time/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <PageWrapper title="时间组件示例">
+    <CollapseContainer title="基础示例">
+      <Time :value="time1" />
+      <br />
+      <Time :value="time2" />
+    </CollapseContainer>
+
+    <CollapseContainer title="定时更新" class="my-4">
+      <Time :value="now" :step="1" />
+      <br />
+      <Time :value="now" :step="5" />
+    </CollapseContainer>
+
+    <CollapseContainer title="定时更新">
+      <Time :value="now" mode="date" />
+      <br />
+      <Time :value="now" mode="datetime" />
+      <br />
+      <Time :value="now" />
+    </CollapseContainer>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, toRefs } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+  import { Time } from '/@/components/Time';
+  import { CollapseContainer } from '/@/components/Container/index';
+
+  export default defineComponent({
+    components: { PageWrapper, Time, CollapseContainer },
+    setup() {
+      const now = new Date().getTime();
+      const state = reactive({
+        time1: now - 60 * 3 * 1000,
+        time2: now - 86400 * 3 * 1000,
+      });
+      return {
+        ...toRefs(state),
+        now,
+      };
+    },
+  });
+</script>

+ 91 - 0
src/views/demo/comp/transition/index.vue

@@ -0,0 +1,91 @@
+<template>
+  <PageWrapper title="动画组件示例">
+    <div class="flex">
+      <Select :options="options" v-model:value="value" placeholder="选择动画" :style="{ width: '150px' }" />
+      <a-button type="primary" class="ml-4" @click="start"> start </a-button>
+    </div>
+    <component :is="`${value}Transition`">
+      <div class="box" v-show="show"></div>
+    </component>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { Select } from 'ant-design-vue';
+  import { PageWrapper } from '/@/components/Page';
+  import {
+    FadeTransition,
+    ScaleTransition,
+    SlideYTransition,
+    ScrollYTransition,
+    SlideYReverseTransition,
+    ScrollYReverseTransition,
+    SlideXTransition,
+    ScrollXTransition,
+    SlideXReverseTransition,
+    ScrollXReverseTransition,
+    ScaleRotateTransition,
+    ExpandXTransition,
+    ExpandTransition,
+  } from '/@/components/Transition';
+
+  const transitionList = [
+    'Fade',
+    'Scale',
+    'SlideY',
+    'ScrollY',
+    'SlideYReverse',
+    'ScrollYReverse',
+    'SlideX',
+    'ScrollX',
+    'SlideXReverse',
+    'ScrollXReverse',
+    'ScaleRotate',
+    'ExpandX',
+    'Expand',
+  ];
+  const options = transitionList.map((item) => ({
+    label: item,
+    value: item,
+    key: item,
+  }));
+
+  export default defineComponent({
+    components: {
+      Select,
+      PageWrapper,
+      FadeTransition,
+      ScaleTransition,
+      SlideYTransition,
+      ScrollYTransition,
+      SlideYReverseTransition,
+      ScrollYReverseTransition,
+      SlideXTransition,
+      ScrollXTransition,
+      SlideXReverseTransition,
+      ScrollXReverseTransition,
+      ScaleRotateTransition,
+      ExpandXTransition,
+      ExpandTransition,
+    },
+    setup() {
+      const value = ref('Fade');
+      const show = ref(true);
+      function start() {
+        show.value = false;
+        setTimeout(() => {
+          show.value = true;
+        }, 300);
+      }
+      return { options, value, start, show };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .box {
+    width: 150px;
+    height: 150px;
+    margin-top: 20px;
+    background-color: rgb(126, 170, 236);
+  }
+</style>

+ 54 - 0
src/views/demo/comp/upload/index.vue

@@ -0,0 +1,54 @@
+<template>
+  <PageWrapper title="上传组件示例">
+    <a-alert message="基础示例" />
+    <BasicUpload :maxSize="20" :maxNumber="10" @change="handleChange" :api="uploadApi" class="my-5" :accept="['image/*']" />
+
+    <a-alert message="嵌入表单,加入表单校验" />
+
+    <BasicForm @register="register" class="my-5" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicUpload } from '/@/components/Upload';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { PageWrapper } from '/@/components/Page';
+  import { Alert } from 'ant-design-vue';
+  import { uploadApi } from '/@/api/sys/upload';
+
+  const schemas: FormSchema[] = [
+    {
+      field: 'field1',
+      component: 'Upload',
+      label: '字段1',
+      colProps: {
+        span: 8,
+      },
+      rules: [{ required: true, message: '请选择上传文件' }],
+      componentProps: {
+        api: uploadApi,
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicUpload, BasicForm, PageWrapper, [Alert.name]: Alert },
+    setup() {
+      const { createMessage } = useMessage();
+      const [register] = useForm({
+        labelWidth: 120,
+        schemas,
+        actionColOptions: {
+          span: 16,
+        },
+      });
+      return {
+        handleChange: (list: string[]) => {
+          createMessage.info(`已上传文件${JSON.stringify(list)}`);
+        },
+        uploadApi,
+        register,
+      };
+    },
+  });
+</script>

+ 33 - 0
src/views/demo/comp/verify/Rotate.vue

@@ -0,0 +1,33 @@
+<template>
+  <PageWrapper title="旋转校验示例">
+    <div class="flex justify-center p-4 items-center bg-gray-700">
+      <RotateDragVerify :src="img" ref="el" @success="handleSuccess" />
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { RotateDragVerify } from '/@/components/Verify/index';
+
+  import img from '/@/assets/images/header.jpg';
+
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { RotateDragVerify, PageWrapper },
+    setup() {
+      const handleSuccess = () => {
+        console.log('success!');
+      };
+      return {
+        handleSuccess,
+        img,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .bg-gray-700 {
+    background-color: #4a5568;
+  }
+</style>

+ 97 - 0
src/views/demo/comp/verify/index.vue

@@ -0,0 +1,97 @@
+<template>
+  <PageWrapper title="拖动校验示例">
+    <div class="flex justify-center p-4 items-center bg-gray-700">
+      <BasicDragVerify ref="el1" @success="handleSuccess" />
+      <a-button type="primary" class="ml-2" @click="handleBtnClick(el1)"> 还原 </a-button>
+    </div>
+
+    <div class="flex justify-center p-4 items-center bg-gray-700">
+      <BasicDragVerify ref="el2" @success="handleSuccess" circle />
+      <a-button type="primary" class="ml-2" @click="handleBtnClick(el2)"> 还原 </a-button>
+    </div>
+
+    <div class="flex justify-center p-4 items-center bg-gray-700">
+      <BasicDragVerify
+        ref="el3"
+        @success="handleSuccess"
+        text="拖动以进行校验"
+        successText="校验成功"
+        :barStyle="{
+          backgroundColor: '#018ffb',
+        }"
+      />
+      <a-button type="primary" class="ml-2" @click="handleBtnClick(el3)"> 还原 </a-button>
+    </div>
+
+    <div class="flex justify-center p-4 items-center bg-gray-700">
+      <BasicDragVerify ref="el4" @success="handleSuccess">
+        <template #actionIcon="isPassing">
+          <BugOutlined v-if="isPassing" />
+          <RightOutlined v-else />
+        </template>
+      </BasicDragVerify>
+      <a-button type="primary" class="ml-2" @click="handleBtnClick(el4)"> 还原 </a-button>
+    </div>
+
+    <div class="flex justify-center p-4 items-center bg-gray-700">
+      <BasicDragVerify ref="el5" @success="handleSuccess">
+        <template #text="isPassing">
+          <div v-if="isPassing">
+            <BugOutlined />
+            成功
+          </div>
+          <div v-else>
+            拖动
+            <RightOutlined />
+          </div>
+        </template>
+      </BasicDragVerify>
+      <a-button type="primary" class="ml-2" @click="handleBtnClick(el5)"> 还原 </a-button>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicDragVerify, DragVerifyActionType, PassingData } from '/@/components/Verify/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { BugOutlined, RightOutlined } from '@ant-design/icons-vue';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { BasicDragVerify, BugOutlined, RightOutlined, PageWrapper },
+    setup() {
+      const { createMessage } = useMessage();
+      const el1 = ref<Nullable<DragVerifyActionType>>(null);
+      const el2 = ref<Nullable<DragVerifyActionType>>(null);
+      const el3 = ref<Nullable<DragVerifyActionType>>(null);
+      const el4 = ref<Nullable<DragVerifyActionType>>(null);
+      const el5 = ref<Nullable<DragVerifyActionType>>(null);
+
+      function handleSuccess(data: PassingData) {
+        const { time } = data;
+        createMessage.success(`校验成功,耗时${time}秒`);
+      }
+
+      function handleBtnClick(elRef: Nullable<DragVerifyActionType>) {
+        if (!elRef) {
+          return;
+        }
+        elRef.resume();
+      }
+      return {
+        handleSuccess,
+        el1,
+        el2,
+        el3,
+        el4,
+        el5,
+        handleBtnClick,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .bg-gray-700 {
+    background-color: #4a5568;
+  }
+</style>

+ 81 - 0
src/views/demo/document/form/BasicFiledsLayotForm.vue

@@ -0,0 +1,81 @@
+<!-- 标题与字段布局 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" @submit="handleSubmit" style="margin: 20px auto"/>
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      label: '姓名',
+      field: 'name',
+      component: 'Input',
+    },
+    {
+      label: '年龄',
+      field: 'password',
+      component: 'InputNumber',
+    },
+    {
+      label: '生日',
+      field: 'birthday',
+      component: 'DatePicker',
+    },
+    {
+      label: '头像',
+      field: 'avatar',
+      component: 'JImageUpload',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showActionButtonGroup: false,
+    actionColOptions: { span: 12 },
+    //控制标题宽度占比
+    labelCol: {
+      xs: 2,
+      sm: 2,
+      md: 2,
+      lg: 9,
+      xl: 3,
+      xxl: 2,
+    },
+    //控制组件宽度占比
+    wrapperCol: {
+      xs: 15,
+      sm: 14,
+      md: 16,
+      lg: 17,
+      xl: 19,
+      xxl: 20,
+    },
+  });
+
+  /**
+   * 点击提交按钮的value值
+   * @param values
+   */
+  function handleSubmit(values: any) {
+    console.log('提交按钮数据::::', values);
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 70 - 0
src/views/demo/document/form/BasicFixedWidthForm.vue

@@ -0,0 +1,70 @@
+<!-- 固定label标题的宽度 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" @submit="handleSubmit" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      label: '姓名',
+      field: 'name',
+      component: 'Input',
+    },
+    {
+      label: '年龄',
+      field: 'password',
+      component: 'InputNumber',
+    },
+    {
+      label: '生日',
+      field: 'birthday',
+      component: 'DatePicker',
+    },
+    {
+      label: '头像',
+      field: 'avatar',
+      component: 'JImageUpload',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+    //使用labelWidth控制标题宽度
+    labelWidth: '150px',
+    //使用labelCol的样式属性来控制标题宽度
+    labelCol: { style: { width: '150px' } },
+    //标题对齐方式(left:左对齐,right:右对齐),默认右对齐
+    labelAlign: 'right',
+  });
+
+  /**
+   * 点击提交按钮的value值
+   * @param values
+   */
+  function handleSubmit(values: any) {
+    console.log('提交按钮数据::::', values);
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 143 - 0
src/views/demo/document/form/BasicFormAdd.vue

@@ -0,0 +1,143 @@
+<!-- 动态增减表单 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px;" @submit="handleSubmit">
+    <!--  添加input的插槽  -->
+    <template #addForm="{ field }">
+      <a-button v-if="Number(field) === 0" @click="addField" style="width: 50px">+</a-button>
+      <a-button v-if="Number(field) > 0" @click="delField(field)" style="width: 50px">-</a-button>
+    </template>
+  </BasicForm>
+  <!--  <div style="margin: 20px auto; text-align: center">
+    <a-button @click="addField">添加表单项</a-button>
+  </div>-->
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { CollapseContainer } from '/@/components/Container';
+  import { ref } from 'vue';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'name1',
+      label: '姓名1',
+      component: 'Input',
+      // ifShow:false,
+      colProps: {
+        span: 8,
+      },
+    },
+    {
+      field: 'age1',
+      label: '年龄1',
+      component: 'InputNumber',
+      // ifShow:false,
+      colProps: {
+        span: 8,
+      },
+    },
+    {
+      field: '0',
+      component: 'Input',
+      // ifShow:false,
+      label: ' ',
+      colProps: {
+        span: 8,
+      },
+      slot: 'addForm',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   * appendSchemaByField:增加表单项(字段)
+   *
+   * removeSchemaByFiled:减少表单项(字段)
+   */
+  const [registerForm, { appendSchemaByField, removeSchemaByFiled }] = useForm({
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    // showSubmitButton:false
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+
+  //组件个数
+  let n = ref<number>(2);
+
+  /**
+   * 添加字段
+   * appendSchemaByField类型: ( schema: FormSchema, prefixField: string | undefined, first?: boolean | undefined ) => Promise<void>
+   * 说明: 插入到指定 filed 后面,如果没传指定 field,则插入到最后,当 first = true 时插入到第一个位置
+   */
+  async function addField() {
+    //添加表单字段,里面为schemas对应的属性,可自行配置
+    await appendSchemaByField(
+      {
+        field: `name${n.value}`,
+        component: 'Input',
+        label: '字段' + n.value,
+        colProps: {
+          span: 8,
+        },
+      },
+      ''
+    );
+    await appendSchemaByField(
+      {
+        field: `sex${n.value}`,
+        component: 'InputNumber',
+        label: '字段' + n.value,
+        colProps: {
+          span: 8,
+        },
+      },
+      ''
+    );
+
+    await appendSchemaByField(
+      {
+        field: `${n.value}`,
+        component: 'Input',
+        label: ' ',
+        colProps: {
+          span: 8,
+        },
+        slot: 'addForm',
+      },
+      ''
+    );
+    n.value++;
+  }
+
+  /**
+   * 删除字段
+   * 类型: (field: string | string[]) => Promise<void>
+   * 说明: 根据 field 删除 Schema
+   * @param field 当前字段名称
+   */
+  function delField(field) {
+    //移除指定字段
+    removeSchemaByFiled([`name${field}`, `sex${field}`, `${field}`]);
+    n.value--;
+  }
+
+  /**
+   * 点击提交按钮的value值
+   * @param values
+   */
+  function handleSubmit(values: any) {
+    console.log('提交按钮数据::::', values);
+  }
+</script>
+
+<style scoped>
+  /** 数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+</style>

+ 66 - 0
src/views/demo/document/form/BasicFormBtn.vue

@@ -0,0 +1,66 @@
+<!-- 操作按钮 -->
+<template>
+  <div style="margin: 20px auto; text-align: center">
+    <!-- 通过setProps 可以设置 userForm 中的属性 -->
+    <!--  showActionButtonGroup 显示或者隐藏查询、重置按钮  -->
+    <a-button @click="setProps({ showActionButtonGroup: false })" class="mr-2"> 隐藏操作按钮 </a-button>
+    <a-button @click="setProps({ showActionButtonGroup: true })" class="mr-2"> 显示操作按钮 </a-button>
+    <!--  showActionButtonGroup 显示或者隐藏重置按钮  -->
+    <a-button @click="setProps({ showResetButton: false })" class="mr-2"> 隐藏重置按钮 </a-button>
+    <a-button @click="setProps({ showResetButton: true })" class="mr-2"> 显示重置按钮 </a-button>
+    <!--  showActionButtonGroup 显示或者隐藏查询按钮  -->
+    <a-button @click="setProps({ showSubmitButton: false })" class="mr-2"> 隐藏查询按钮 </a-button>
+    <a-button @click="setProps({ showSubmitButton: true })" class="mr-2"> 显示查询按钮 </a-button>
+  </div>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" @submit="handleSubmit" style="margin-top: 50px; margin-left: 50px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { CollapseContainer } from '/@/components/Container';
+
+  /**
+   * BasicForm绑定注册;
+   * setProps方法可以动态设置useForm中的属性
+   */
+  const [registerForm, { setProps }] = useForm({
+    //自定义查询按钮的文本和图标
+    submitButtonOptions: { text: '查询', preIcon: '' },
+    //自定义重置按钮的文本和图标
+    resetButtonOptions: { text: '重置', preIcon: '' },
+    //操作按钮的位置
+    actionColOptions: { span: 17 },
+    //提交按钮的自定义事件
+    submitFunc: customSubmitFunc,
+    //重置按钮的自定义时间
+    resetFunc: customSubmitFunc,
+    //显示操作按钮
+    showActionButtonGroup: true,
+  });
+
+  /**
+   * 查询按钮点击事件
+   */
+  async function customSubmitFunc() {
+    console.log('查询按钮点击事件,此处处理查询按钮的逻辑');
+  }
+
+  /**
+   * 重置按钮点击事件
+   */
+  async function customResetFunc() {
+    console.log('重置按钮点击事件,此处处理重置按钮的逻辑');
+  }
+
+  /**
+   * 点击提交按钮的value值
+   * @param values
+   */
+  function handleSubmit(values: any) {
+    console.log('提交按钮数据::::', values);
+  }
+</script>
+
+<style scoped></style>

+ 95 - 0
src/views/demo/document/form/BasicFormCleanRule.vue

@@ -0,0 +1,95 @@
+<!-- 操作禁用表单 -->
+<template>
+  <div style="margin: 20px auto; text-align: center">
+    <!-- all 触发或清空所有验证,visitor 只触发或者清空来访人员验证 -->
+    <a-button @click="triggerFormRule('all')" class="mr-2"> 触发表单验证 </a-button>
+    <a-button @click="cancelFormRule('all')" class="mr-2"> 清空表单验证 </a-button>
+    <a-button @click="triggerFormRule('visitor')" class="mr-2"> 只验证来访人员 </a-button>
+    <a-button @click="cancelFormRule('visitor')" class="mr-2"> 只清空来访人员验证 </a-button>
+  </div>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px;" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'visitor',
+      label: '来访人员',
+      component: 'Input',
+      required: true,
+    },
+    {
+      field: 'accessed',
+      label: '来访日期',
+      component: 'DatePicker',
+      required: true,
+    },
+    {
+      field: 'phone',
+      label: '来访人手机号',
+      component: 'Input',
+      required: true,
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   * clearValidate 清除所有验证,支持取消验证其中几个字段 如 clearValidate(['visitor',...])
+   * validate 验证所有,支持验证其中几个字段,validate(['visitor',...])
+   * validateFields 只支持验证其中几个字段,如validateFields(['visitor',...])
+   */
+  const [registerForm, { clearValidate, validateFields, validate }] = useForm({
+    schemas: formSchemas,
+    labelWidth: '150px',
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+    //默认聚焦第一个,只支持input
+    autoFocusFirstItem: true,
+  });
+
+  /**
+   * 触发表单验证
+   * @param type all 所有验证 visitor 只验证来访人员
+   */
+  async function triggerFormRule(type) {
+    if (type == 'all') {
+      //触发所有验证
+      await validate();
+    } else {
+      //触发来访人员验证
+      //visitor 来访人员的对应formSchemas field字段
+      await validateFields(['visitor']);
+    }
+  }
+
+  /**
+   * 触发表单验证
+   * @param type all 所有验证 visitor 只验证来访人员
+   */
+  async function cancelFormRule(type) {
+    if (type == 'all') {
+      //取消全部验证
+      await clearValidate();
+    } else {
+      //只取消来访人员的验证
+      //visitor 来访人员的对应formSchemas field字段
+      await clearValidate(['visitor']);
+    }
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 58 - 0
src/views/demo/document/form/BasicFormCompact.vue

@@ -0,0 +1,58 @@
+<!-- 操作禁用表单 -->
+<template>
+  <div style="margin: 20px auto; text-align: center">
+    <!-- 通过setProps 可以设置 userForm 中的属性 -->
+    <!-- 表单大小,默认为 default   -->
+    <a-button @click="setProps({ size: 'large' })" class="mr-2"> 更改大小为最大 </a-button>
+    <a-button @click="setProps({ size: 'default' })" class="mr-2"> 还原大小 </a-button>
+    <a-button @click="setProps({ size: 'small' })" class="mr-2"> 更改大小为最小 </a-button>
+    <!--  disabled表单禁用  -->
+    <a-button @click="setProps({ disabled: true })" class="mr-2"> 禁用表单 </a-button>
+    <a-button @click="setProps({ disabled: false })" class="mr-2"> 解除禁用 </a-button>
+    <!--  compact 是否为紧凑表单  -->
+    <a-button @click="setProps({ compact: true })" class="mr-2"> 紧凑表单 </a-button>
+    <a-button @click="setProps({ compact: false })" class="mr-2"> 还原正常间距 </a-button>
+  </div>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px;" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { CollapseContainer } from '/@/components/Container';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'visitor',
+      label: '来访人员',
+      component: 'Input',
+    },
+    {
+      field: 'accessed',
+      label: '来访日期',
+      component: 'DatePicker',
+    },
+    {
+      field: 'phone',
+      label: '来访人手机号',
+      component: 'Input',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   * setProps方法可以动态设置useForm中的属性
+   */
+  const [registerForm, { setProps }] = useForm({
+    schemas: formSchemas,
+    labelWidth: '150px',
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+    //默认聚焦第一个,只支持input
+    autoFocusFirstItem: true,
+  });
+</script>
+
+<style scoped></style>

+ 34 - 0
src/views/demo/document/form/BasicFormComponent.vue

@@ -0,0 +1,34 @@
+<!-- 操作表单值 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px;" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { schemas } from './example.data';
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm, { getFieldsValue, setFieldsValue, resetFields, validate }] = useForm({
+    schemas: schemas,
+    labelWidth: '150px',
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+    //默认聚焦第一个,只支持input
+    autoFocusFirstItem: true,
+  });
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 63 - 0
src/views/demo/document/form/BasicFormConAttribute.vue

@@ -0,0 +1,63 @@
+<!-- 控件属性 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      label: '员工姓名',
+      field: 'name',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+      defaultValue: '张三',
+    },
+    {
+      label: '性别',
+      field: 'sex',
+      component: 'Select',
+      //填写组件的属性
+      componentProps: {
+        options: [
+          { label: '男', value: 1 },
+          { label: '女', value: 2 },
+          { label: '未知', value: 3 },
+        ],
+      },
+      //默认值
+      defaultValue: 3,
+    },
+    {
+      label: '年龄',
+      field: 'age',
+      component: 'Input',
+    },
+    {
+      label: '入职时间',
+      subLabel: '( 选填 )',
+      field: 'entryTime',
+      component: 'TimePicker',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    labelWidth: '150px',
+    showResetButton: false,
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+</script>
+
+<style scoped></style>

+ 32 - 0
src/views/demo/document/form/BasicFormCustom.vue

@@ -0,0 +1,32 @@
+<!-- 自定义组件 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'custom',
+      label: '自定义组件',
+      //引入自定义组件
+      component: 'JInput',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    labelWidth: '150px',
+    showActionButtonGroup: false,
+  });
+</script>
+
+<style scoped></style>

+ 32 - 0
src/views/demo/document/form/BasicFormCustomComponent.vue

@@ -0,0 +1,32 @@
+<!-- 操作表单值 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { schemas } from './exampleCustom.data';
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm, { getFieldsValue, setFieldsValue, resetFields, validate }] = useForm({
+    schemas: schemas,
+    labelWidth: '150px',
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+  });
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 64 - 0
src/views/demo/document/form/BasicFormCustomSlots.vue

@@ -0,0 +1,64 @@
+<!-- 插槽 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" @submit="handleSubmit">
+      <!--  #name对应的是formSchemas对应slot(name)插槽    -->
+      <template #name="{ model, field }">
+        <JInput v-model:value="model[field]" />
+      </template>
+  </BasicForm>
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  //引入CustomDemo自定义组件
+  import JInput from "/@/components/Form/src/jeecg/components/JInput.vue";
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'name',
+      label: '姓名',
+      component: 'Input',
+      slot:'name'
+    },
+    {
+      field: 'phone',
+      label: '联系方式',
+      component: 'Input',
+    },
+    {
+      field: 'feedback',
+      label: '问题反馈',
+      component: 'InputTextArea',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+
+  /**
+   * 提交信息
+   */
+  function handleSubmit(values) {
+    console.log("values::",values);
+  }
+</script>
+
+<style scoped>
+  .font-color {
+    font-size: 13px;
+    color: #a1a1a1;
+    margin-bottom: 5px;
+  }
+</style>

+ 80 - 0
src/views/demo/document/form/BasicFormDynamicsRules.vue

@@ -0,0 +1,80 @@
+<!-- 动态表单验证 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" @submit="handleSubmit" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { duplicateCheck } from '/@/views/system/user/user.api';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'visitor',
+      label: '来访人员',
+      component: 'Input',
+      //自动触发检验,布尔类型
+      required: true,
+    },
+    {
+      field: 'accessed',
+      label: '来访日期',
+      component: 'DatePicker',
+      //支持获取当前值判断触发 values代表当前表单的值
+      required: ({ values }) => {
+        return !values.accessed;
+      },
+    },
+    {
+      field: 'phone',
+      label: '来访人手机号',
+      component: 'Input',
+      //动态自定义正则,values: 当前表单的所有值
+      dynamicRules: ({ values }) => {
+        //需要return
+        return [
+          {
+            //默认开启表单检验
+            required: true,
+            // value 当前手机号输入的值
+            validator: (_, value) => {
+              //需要return 一个Promise对象
+              return new Promise((resolve, reject) => {
+                if (!value) {
+                  reject('请输入手机号!');
+                }
+                //验证手机号是否正确
+                let reg = /^1[3456789]\d{9}$/;
+                if (!reg.test(value)) {
+                  reject('请输入正确手机号!');
+                }
+                resolve();
+              });
+            },
+          },
+        ];
+      },
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+
+  /**
+   * 提交事件
+   */
+  function handleSubmit(values: any) {}
+</script>
+
+<style scoped></style>

+ 70 - 0
src/views/demo/document/form/BasicFormFieldShow.vue

@@ -0,0 +1,70 @@
+<!-- 字段显示与隐藏 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'id',
+      label: '编号',
+      component: 'Input',
+      //隐藏id,css 控制,不会删除 dom(支持布尔类型 true和false。支持动态值判断,详情请见ifShow)
+      show: false,
+    },
+    {
+      field: 'evaluate',
+      label: '对公司整体评价',
+      component: 'RadioGroup',
+      componentProps: {
+        options: [
+          { label: '满意', value: '0' },
+          { label: '不满意', value: '1' },
+        ],
+      },
+      defaultValue: '0',
+    },
+    {
+      field: 'describe',
+      label: '不满意原因说明',
+      component: 'InputTextArea',
+      //ifShow和show属性一致,使用其中一个即可,values代表当前表单的值,js 控制,会删除 dom
+      ifShow: ({ values }) => {
+        return values.evaluate == '1';
+      },
+    },
+    {
+      field: 'satisfiedLevel',
+      label: '满意度',
+      component: 'Slider',
+      componentProps: {
+        tipFormatter: (value) => {
+          return value + '%';
+        },
+      },
+      //动态禁用,values代表当前表单的值,返回 true或false
+      dynamicDisabled: ({ values }) => {
+        return values.evaluate == '1';
+      },
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+</script>
+
+<style scoped></style>

+ 55 - 0
src/views/demo/document/form/BasicFormFieldTip.vue

@@ -0,0 +1,55 @@
+<!-- 字段标题提示及前缀 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'month',
+      label: '当前月份',
+      component: 'Input',
+      suffix: '月',
+    },
+    {
+      field: 'lateNumber',
+      label: '迟到次数',
+      component: 'InputNumber',
+      //帮助信息:可以直接返回String(helpMessage:"迟到次数"),也可以获取表单值,动态填写
+      helpMessage: ({ values }) => {
+        return '当前迟到次数' + values.lateNumber + ', 扣款' + values.lateNumber * 50 + '元';
+      },
+      defaultValue: 0,
+    },
+    {
+      field: 'lateReason',
+      label: '迟到原因',
+      component: 'Input',
+      helpMessage: '什么原因耽误上班迟到',
+      //自定义提示属性,需要结合helpMessage一起使用
+      helpComponentProps: {
+        maxWidth: '200px',
+        color: '#66CCFF',
+      },
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+</script>
+
+<style scoped></style>

+ 105 - 0
src/views/demo/document/form/BasicFormFooter.vue

@@ -0,0 +1,105 @@
+<!-- 自定义页脚 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px">
+    <template #formHeader>
+      <div style="margin: 0 auto 20px">
+        <span>我是自定义按钮</span>
+      </div>
+    </template>
+    <template #formFooter>
+      <div style="margin: 0 auto">
+        <a-button type="primary" @click="save" class="mr-2"> 保存 </a-button>
+        <a-button type="primary" @click="saveDraft" class="mr-2"> 保存草稿 </a-button>
+        <a-button type="error" @click="reset" class="mr-2"> 重置 </a-button>
+      </div>
+    </template>
+  </BasicForm>
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      label: '员工姓名',
+      field: 'name',
+      component: 'Input',
+    },
+    {
+      label: '性别',
+      field: 'sex',
+      component: 'Select',
+      //填写组件的属性
+      componentProps: {
+        options: [
+          { label: '男', value: 1 },
+          { label: '女', value: 2 },
+          { label: '未知', value: 3 },
+        ],
+      },
+      //默认值
+      defaultValue: 3,
+    },
+    {
+      label: '年龄',
+      field: 'age',
+      component: 'Input',
+    },
+    {
+      label: '入职时间',
+      subLabel: '( 选填 )',
+      field: 'entryTime',
+      component: 'TimePicker',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm, { validate, resetFields }] = useForm({
+    schemas: formSchemas,
+    labelWidth: '150px',
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+  });
+
+  /**
+   * 保存
+   */
+  async function save() {
+    //使用useForm方法获取表单值
+    let values = await validate();
+    console.log(values);
+  }
+
+  /**
+   * 保存草稿
+   */
+  async function saveDraft() {
+    //使用useForm方法validate获取表单值
+    let values = await validate();
+    console.log(values);
+  }
+
+  /**
+   * 重置
+   */
+  async function reset() {
+    //使用useForm方法resetFields清空值
+    await resetFields();
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 63 - 0
src/views/demo/document/form/BasicFormLayout.vue

@@ -0,0 +1,63 @@
+<!-- 表单布局 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      label: '会议名称',
+      field: 'name',
+      component: 'Input',
+    },
+    {
+      label: '参会地点',
+      field: 'meetingLocation',
+      component: 'Input',
+    },
+    {
+      label: '参与人数',
+      field: 'numberOfPart',
+      component: 'InputNumber',
+    },
+    {
+      label: '会议纪要',
+      field: 'meetingMinutes',
+      component: 'JUpload',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //表单布局属性,支持(vertical,inline),默认为inline
+    layout: 'inline',
+    //注册表单列
+    schemas: formSchemas,
+    //不显示查询和重置按钮
+    showActionButtonGroup: false,
+    //默认row行配置,当 layout 为 inline 生效
+    rowProps: { gutter: 24, justify: 'center', align: 'middle' },
+    //全局col列占比(每列显示多少位),和schemas中的colProps属性一致
+    baseColProps: { span: 12 },
+    //row行的样式
+    baseRowStyle: { width: '100%' },
+  });
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 84 - 0
src/views/demo/document/form/BasicFormModal.vue

@@ -0,0 +1,84 @@
+<!-- 弹出层表单 -->
+<template>
+  <div style="margin: 20px auto">
+    <a-button type="primary" @click="openPopup" class="mr-2"> 打开弹窗 </a-button>
+  </div>
+  <!-- 自定义弹窗组件 -->
+  <BasicModal @register="registerModal" title="弹出层表单">
+    <!-- 自定义表单 -->
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import { BasicModal } from '/@/components/Modal';
+  import { useModal } from '/@/components/Modal';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      label: '员工姓名',
+      field: 'name',
+      component: 'Input',
+    },
+    {
+      label: '性别',
+      field: 'sex',
+      component: 'Select',
+      //填写组件的属性
+      componentProps: {
+        options: [
+          { label: '男', value: 1 },
+          { label: '女', value: 2 },
+          { label: '未知', value: 3 },
+        ],
+      },
+      //默认值
+      defaultValue: 3,
+    },
+    {
+      label: '年龄',
+      field: 'age',
+      component: 'Input',
+    },
+    {
+      label: '入职时间',
+      subLabel: '( 选填 )',
+      field: 'entryTime',
+      component: 'TimePicker',
+    },
+  ];
+
+  //BasicModal绑定注册;
+  const [registerModal, { openModal }] = useModal();
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm, { validate, resetFields }] = useForm({
+    schemas: formSchemas,
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+  });
+
+  /**
+   * 打开弹窗
+   */
+  async function openPopup() {
+    //详见 BasicModal模块
+    openModal(true, {});
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 90 - 0
src/views/demo/document/form/BasicFormRander.vue

@@ -0,0 +1,90 @@
+<!-- 自定义渲染 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px">
+    <!--  #phone对应的是formSchemas对应slot(phone)插槽    -->
+    <template #phone="{ model, field }">
+      <!-- 如果是组件需要进行双向绑定,model当前表单对象,field当前字段名称  -->
+      <a-input v-model:value="model[field]" placeholder="请输入手机号" />
+      <span class="font-color">请输入您的手机号,方便我们联系您</span>
+    </template>
+    <template #feedback="{ model, field }">
+      <JEditor v-model:value="model[field]" placeholder="请输入问题反馈" />
+      <span class="font-color">请您图文并茂,方便我们了解问题并及时反馈</span>
+    </template>
+  </BasicForm>
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
+  import { h } from 'vue';
+  import { Input } from 'ant-design-vue';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'productName',
+      label: '商品名称',
+      component: 'Input',
+    },
+    {
+      field: 'price',
+      label: '价格',
+      component: 'InputNumber',
+    },
+    {
+      field: 'buyNums',
+      label: '购买个数',
+      component: 'InputNumber',
+      //model 单签表单对象,field 当前字段
+      render: ({ model, field }) => {
+        //渲染自定义组件,以Input为例
+        return h(Input, {
+          placeholder: '请输入购买个数',
+          value: model[field],
+          style: { width: '100%' },
+          type: 'number',
+          onChange: (e: ChangeEvent) => {
+            model[field] = e.target.value;
+          },
+        });
+      },
+    },
+    {
+      field: 'describe',
+      label: '描述',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+      //渲染 values当前表单所有值
+      render: ({ values }) => {
+        let productName = values.productName?values.productName:'';
+        let price = values.price ? values.price : 0;
+        let buyNums = values.buyNums ? values.buyNums : 0;
+        return '购买商品名称:' + productName + ', 总价格: ' + price * buyNums + '元';
+      },
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+</script>
+
+<style scoped>
+  /** 数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+</style>

+ 58 - 0
src/views/demo/document/form/BasicFormRules.vue

@@ -0,0 +1,58 @@
+<!-- 表单验证 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" @submit="handleSubmit" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'visitor',
+      label: '来访人员',
+      component: 'Input',
+      //自动触发检验,布尔类型
+      required: true,
+      //检验的时候不加上标题
+      rulesMessageJoinLabel: false,
+    },
+    {
+      field: 'accessed',
+      label: '来访日期',
+      component: 'DatePicker',
+      //支持获取当前值判断触发 values代表当前表单的值
+      required: ({ values }) => {
+        return !values.accessed;
+      },
+    },
+    {
+      field: 'phone',
+      label: '来访人手机号',
+      component: 'Input',
+      //支持正则表达式pattern 和 自定义提示信息 message
+      rules: [{ required: false, message: '请输入正确的手机号', pattern: /^1[3456789]\d{9}$/ }],
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+
+  /**
+   * 提交事件
+   */
+  function handleSubmit(values: any) {}
+</script>
+
+<style scoped></style>

+ 99 - 0
src/views/demo/document/form/BasicFormSchemas.vue

@@ -0,0 +1,99 @@
+<!-- 操作表单schemas配置 -->
+<template>
+  <div style="margin: 20px auto; text-align: center">
+    <a-button @click="updateFormSchemas" class="mr-2"> 更新字段属性 </a-button>
+    <a-button @click="resetFormSchemas" class="mr-2"> 重置字段属性 </a-button>
+  </div>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'visitor',
+      label: '来访人员',
+      component: 'Input',
+      componentProps: {
+        disabled: true,
+      },
+    },
+    {
+      field: 'accessed',
+      label: '来访日期',
+      component: 'DatePicker',
+      ifShow: false,
+    },
+    {
+      field: 'phone',
+      label: '来访人手机号',
+      component: 'Input',
+      required: true,
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   * updateSchema 更新字段属性,支持schemas里面所有的配置
+   * updateSchema([{ field: 'visitor', componentProps: { disabled: false },}, ... ]);
+   * resetSchema 重置字段属性,支持schemas里面所有的配置
+   * resetSchema([{ field: 'visitor',label: '来访人员',component: 'Input',},... ]);
+   */
+  const [registerForm, { updateSchema, resetSchema }] = useForm({
+    schemas: formSchemas,
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+    labelWidth: '150px',
+    //默认聚焦第一个,只支持input
+    autoFocusFirstItem: true,
+  });
+
+  /**
+   * 清除表单配置
+   */
+  async function resetFormSchemas() {
+    await resetSchema([
+      {
+        //字段必填
+        field: 'visitor',
+        label: '来访人员',
+        component: 'Input',
+      },
+    ]);
+  }
+
+  /**
+   * 更新表单配置
+   */
+  async function updateFormSchemas() {
+    //支持更新schemas里面所有的配置
+    await updateSchema([
+      {
+        //字段必填
+        field: 'visitor',
+        componentProps: {
+          disabled: false,
+        },
+      },
+      {
+        field: 'accessed',
+        ifShow: true,
+      },
+    ]);
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 116 - 0
src/views/demo/document/form/BasicFormSearch.vue

@@ -0,0 +1,116 @@
+<!-- 查询区域 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" @submit="handleSubmit" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'name',
+      label: '姓名',
+      component: 'Input',
+    },
+    {
+      field: 'hobby',
+      label: '爱好',
+      component: 'Input',
+    },
+    {
+      field: 'birthday',
+      label: '生日',
+      component: 'DatePicker',
+    },
+    {
+      field: 'joinTime',
+      label: '入职时间',
+      component: 'RangePicker',
+      componentProps: {
+        valueType: 'Date',
+      },
+    },
+    {
+      field: 'workYears',
+      label: '工作年份',
+      component: 'JRangeNumber',
+    },
+    {
+      field: 'sex',
+      label: '性别',
+      component: 'Select',
+      componentProps: {
+        options: [
+          {
+            label: '男',
+            value: '1',
+          },
+          {
+            label: '女',
+            value: '2',
+          },
+        ],
+      },
+    },
+    {
+      field: 'marital',
+      label: '婚姻状况',
+      component: 'RadioGroup',
+      componentProps: {
+        options: [
+          {
+            label: '未婚',
+            value: '1',
+          },
+          {
+            label: '已婚',
+            value: '2',
+          },
+        ],
+      },
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //将表单内时间区域的值映射成 2个字段, 'YYYY-MM-DD'日期格式化
+    fieldMapToTime: [['joinTime', ['joinTime_begin', 'joinTime_end'], 'YYYY-MM-DD']],
+    //注册表单列
+    schemas: formSchemas,
+    //是否显示展开收起按钮,默认false
+    showAdvancedButton: true,
+    //超过指定行数折叠,默认3行
+    autoAdvancedCol: 3,
+    //折叠时默认显示行数,默认1行
+    alwaysShowLines: 2,
+
+    //将表单内数值类型区域的值映射成 2个字段
+    fieldMapToNumber: [['workYears', ['workYears_begin', 'workYears_end']]],
+    //每列占比,默认一行为24
+    baseColProps: { span: 12 },
+  });
+
+  /**
+   * 点击提交按钮的value值
+   * @param values
+   */
+  function handleSubmit(values: any) {
+    console.log('提交按钮数据::::', values);
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 63 - 0
src/views/demo/document/form/BasicFormSlots.vue

@@ -0,0 +1,63 @@
+<!-- 插槽 -->
+<template>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px">
+    <!--  #phone对应的是formSchemas对应slot(phone)插槽    -->
+    <template #phone="{ model, field }">
+      <!-- 如果是组件需要进行双向绑定,model当前表单对象,field当前字段名称  -->
+      <a-input v-model:value="model[field]" placeholder="请输入手机号" />
+      <span class="font-color">请输入您的手机号,方便我们联系您</span>
+    </template>
+    <template #feedback="{ model, field }">
+      <JEditor v-model:value="model[field]" placeholder="请输入问题反馈" />
+      <span class="font-color">请您图文并茂,方便我们了解问题并及时反馈</span>
+    </template>
+  </BasicForm>
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+  import JEditor from '/@/components/Form/src/jeecg/components/JEditor.vue';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'name',
+      label: '姓名',
+      component: 'Input',
+    },
+    {
+      field: 'phone',
+      label: '联系方式',
+      component: 'Input',
+      slot: 'phone',
+    },
+    {
+      field: 'feedback',
+      label: '问题反馈',
+      component: 'InputTextArea',
+      slot: 'feedback',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    showResetButton: false,
+    labelWidth: '150px',
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    actionColOptions: { span: 17 },
+  });
+</script>
+
+<style scoped>
+  .font-color {
+    font-size: 13px;
+    color: #a1a1a1;
+    margin-bottom: 5px;
+  }
+</style>

+ 94 - 0
src/views/demo/document/form/BasicFormValue.vue

@@ -0,0 +1,94 @@
+<!-- 操作表单值 -->
+<template>
+  <div style="margin: 20px auto; text-align: center">
+    <a-button @click="getFormValue" class="mr-2"> 获取表单值 </a-button>
+    <a-button @click="clearFormValue" class="mr-2"> 清空表单值 </a-button>
+    <a-button @click="updateFormValue" class="mr-2"> 更新表单值 </a-button>
+  </div>
+  <!-- 自定义表单 -->
+  <BasicForm @register="registerForm" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      field: 'visitor',
+      label: '来访人员',
+      component: 'Input',
+      required: true,
+    },
+    {
+      field: 'accessed',
+      label: '来访日期',
+      component: 'DatePicker',
+      required: true,
+    },
+    {
+      field: 'phone',
+      label: '来访人手机号',
+      component: 'Input',
+      required: true,
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   * getFieldsValue 获取所有表单值
+   * validate 验证通过之后的表单值,支持验证其中几个字段,validate(['visitor',...])
+   * setFieldsValue 更新表单值,如 setFieldsValue({'visitor':'李四',...})
+   * resetFields 清除所有表单值
+   */
+  const [registerForm, { getFieldsValue, setFieldsValue, resetFields, validate }] = useForm({
+    schemas: formSchemas,
+    //隐藏操作按钮
+    showActionButtonGroup: false,
+    labelWidth: '150px',
+    //默认聚焦第一个,只支持input
+    autoFocusFirstItem: true,
+  });
+
+  /**
+   * 获取表单值
+   */
+  async function getFormValue() {
+    //获取所有值
+    let fieldsValue = await getFieldsValue();
+    console.log('fieldsValue:::', fieldsValue);
+    //表单验证通过后获取所有字段值
+    fieldsValue = await validate();
+    console.log('fieldsValue:::', fieldsValue);
+    //表单验`visitor来访人员`通过后获取的值
+    fieldsValue = await validate(['visitor']);
+    console.log('fieldsValue:::', fieldsValue);
+  }
+
+  /**
+   * 清空表单值
+   */
+  async function clearFormValue() {
+    await resetFields();
+  }
+
+  /**
+   * 更新表单值
+   */
+  async function updateFormValue() {
+    console.log('我进来了');
+    await setFieldsValue({ visitor: '李四' });
+  }
+</script>
+
+<style scoped>
+  /** 时间和数字输入框样式 */
+  :deep(.ant-input-number) {
+    width: 100%;
+  }
+
+  :deep(.ant-picker) {
+    width: 100%;
+  }
+</style>

+ 63 - 0
src/views/demo/document/form/BasicFunctionForm.vue

@@ -0,0 +1,63 @@
+<!-- 基本用法 -->
+<template>
+  <!-- 自定表单 -->
+  <BasicForm @register="registerForm" @submit="handleSubmit" style="margin-top: 20px" />
+</template>
+
+<script lang="ts" setup>
+  //引入依赖
+  import { useForm, BasicForm, FormSchema } from '/@/components/Form';
+
+  //自定义表单字段
+  const formSchemas: FormSchema[] = [
+    {
+      //标题名称
+      label: '用户名(后面根据labelLength定义的长度隐藏)',
+      //字段
+      field: 'username',
+      //组件
+      component: 'Input',
+      //标题宽度,支持数字和字符串
+      labelWidth: 150,
+      //标题长度,超过位数隐藏
+      labelLength: 3,
+    },
+    {
+      label: '密码',
+      field: 'password',
+      //子标题名称(在主标题后面)
+      subLabel: '(数字和字母组成)',
+      component: 'InputPassword',
+      labelWidth: '150px',
+    },
+  ];
+
+  /**
+   * BasicForm绑定注册;
+   * useForm 是整个框架的核心用于表单渲染,里边封装了很多公共方法;
+   * 支持(schemas: 渲染表单列,autoSubmitOnEnter:回车提交,submitButtonOptions:自定义按钮文本和图标等方法);
+   * 平台通过此封装,简化了代码,支持自定义扩展;
+   */
+  const [registerForm] = useForm({
+    //注册表单列
+    schemas: formSchemas,
+    //回车提交
+    autoSubmitOnEnter: true,
+    //不显示重置按钮
+    showResetButton: false,
+    //自定义提交按钮文本和图标
+    submitButtonOptions: { text: '提交', preIcon: '' },
+    //查询列占比 24代表一行 取值范围 0-24
+    actionColOptions: { span: 17 },
+  });
+
+  /**
+   * 点击提交按钮的value值
+   * @param values
+   */
+  function handleSubmit(values: any) {
+    console.log('提交按钮数据::::', values);
+  }
+</script>
+
+<style scoped></style>

+ 393 - 0
src/views/demo/document/form/example.data.ts

@@ -0,0 +1,393 @@
+import { FormSchema } from '/@/components/Form';
+
+import dayjs from 'dayjs';
+
+export const schemas: FormSchema[] = [
+  {
+    label: '文本框',
+    field: 'name',
+    component: 'Input',
+    componentProps: {
+      prefix: '中文',
+      showCount: true,
+    },
+    defaultValue: '张三',
+  },
+  {
+    label: '密码',
+    field: 'password',
+    component: 'InputPassword',
+    componentProps: {
+      //是否显示切换按钮或者控制密码显隐
+      visibilityToggle: true,
+      prefix: '密码',
+    },
+  },
+  {
+    label: '搜索框',
+    field: 'searchBox',
+    component: 'InputSearch',
+    componentProps: {
+      onSearch: (value) => {
+        console.log(value);
+      },
+    },
+  },
+  {
+    label: '文本域',
+    field: 'textArea',
+    component: 'InputTextArea',
+    componentProps: {
+      //可以点击清除图标删除内容
+      allowClear: true,
+      //是否展示字数
+      showCount: true,
+      //自适应内容高度,可设置为 true | false 或对象:{ minRows: 2, maxRows: 6 }
+      autoSize: {
+        //最小显示行数
+        minRows: 2,
+        //最大显示行数
+        maxRows: 3,
+      },
+    },
+  },
+  {
+    label: '数值输入框',
+    field: 'number',
+    component: 'InputNumber',
+    componentProps: {
+      //带标签的 input,设置后置标签
+      addonAfter: '保留两位小数',
+      //最大值
+      max: 100,
+      //数值经度
+      precision: 2,
+      //步数
+      step: 0.1,
+    },
+  },
+
+  {
+    label: '下拉框',
+    field: 'jinputtype',
+    component: 'Select',
+    componentProps: {
+      options: [
+        { value: 'like', label: '模糊(like)' },
+        { value: 'ne', label: '不等于(ne)' },
+        { value: 'ge', label: '大于等于(ge)' },
+        { value: 'le', label: '小于等于(le)' },
+      ],
+      //下拉多选
+      mode: 'multiple',
+      //配置是否可搜索
+      showSearch: true,
+    },
+  },
+  {
+    field: 'TreeSelect',
+    label: '下拉树',
+    component: 'TreeSelect',
+    componentProps: {
+      //是否显示下拉框,默认false
+      treeCheckable: true,
+      //标题
+      title: '下拉树',
+      //下拉树
+      treeData: [
+        {
+          label: '洗衣机',
+          value: '0',
+          children: [
+            {
+              label: '滚筒洗衣机',
+              value: '0-1',
+            },
+          ],
+        },
+        {
+          label: '电视机',
+          value: '1',
+          children: [
+            {
+              label: '平板电视',
+              value: '1-1',
+              disabled: true,
+            },
+            {
+              label: 'CRT电视机',
+              value: '1-2',
+            },
+            {
+              label: '投影电视',
+              value: '1-3',
+            },
+          ],
+        },
+      ],
+    },
+  },
+  {
+    label: 'RadioButtonGroup组件',
+    field: 'status',
+    component: 'RadioButtonGroup',
+    componentProps: {
+      options: [
+        { label: '有效', value: 1 },
+        { label: '无效', value: 0 },
+      ],
+    },
+  },
+  {
+    label: '单选框',
+    field: 'radioSex',
+    component: 'RadioGroup',
+    componentProps: {
+      //options里面由一个一个的radio组成,支持disabled禁用
+      options: [
+        { label: '男', value: 1, disabled: false },
+        { label: '女', value: 0 },
+      ],
+    },
+  },
+  {
+    label: '多选框',
+    field: 'checkbox',
+    component: 'Checkbox',
+    componentProps: {
+      //是否禁用,默认false
+      disabled: false,
+    },
+  },
+  {
+    label: '多选框组',
+    field: 'checkSex',
+    component: 'CheckboxGroup',
+    componentProps: {
+      //RadioGroup 下所有 input[type="radio"] 的 name 属性
+      name: '爱好',
+      //options支持disabled禁用
+      options: [
+        { label: '运动', value: 0, disabled: true },
+        { label: '听音乐', value: 1 },
+        { label: '看书', value: 2 },
+      ],
+    },
+    defaultValue: [2],
+  },
+  {
+    label: '自动完成组件',
+    field: 'AutoComplete',
+    component: 'AutoComplete',
+    componentProps: {
+      options: [{ value: 'Burns Bay Road' }, { value: 'Downing Street' }, { value: 'Wall Street' }],
+    },
+  },
+  {
+    label: '级联选择',
+    field: 'cascade',
+    component: 'Cascader',
+    componentProps: {
+      //最多显示多少个tag
+      maxTagCount: 2,
+      //浮层预设位置
+      placement: 'bottomRight',
+      //在选择框中显示搜索框,默认false
+      showSearch: true,
+      options: [
+        {
+          label: '北京',
+          value: 'BeiJin',
+          children: [
+            {
+              label: '海淀区',
+              value: 'HaiDian',
+            },
+          ],
+        },
+        {
+          label: '江苏省',
+          value: 'JiangSu',
+          children: [
+            {
+              label: '南京',
+              value: 'Nanjing',
+              children: [
+                {
+                  label: '中华门',
+                  value: 'ZhongHuaMen',
+                },
+              ],
+            },
+          ],
+        },
+      ],
+    },
+  },
+  {
+    label: '日期选择',
+    field: 'dateSelect',
+    component: 'DatePicker',
+    componentProps: {
+      //日期格式化,页面上显示的值
+      format: 'YYYY-MM-DD',
+      //返回值格式化(绑定值的格式)
+      valueFormat: 'YYYY-MM-DD',
+      //是否显示今天按钮
+      showToday: true,
+      //不可选择日期
+      disabledDate: (currentDate) => {
+        let date = dayjs(currentDate).format('YYYY-MM-DD');
+        let nowDate = dayjs(new Date()).format('YYYY-MM-DD');
+        //当天不可选择
+        if (date == nowDate) {
+          return true;
+        }
+        return false;
+      },
+    },
+  },
+  {
+    label: '月份选择',
+    field: 'monthSelect',
+    component: 'MonthPicker',
+    componentProps: {
+      //不可选择日期
+      disabledDate: (currentDate) => {
+        let date = dayjs(currentDate).format('YYYY-MM');
+        let nowDate = dayjs(new Date()).format('YYYY-MM');
+        //当天不可选择
+        if (date == nowDate) {
+          return true;
+        }
+        return false;
+      },
+    },
+  },
+  {
+    label: '周选择',
+    field: 'weekSelect',
+    component: 'WeekPicker',
+    componentProps: {
+      size: 'small',
+    },
+  },
+  {
+    label: '时间选择',
+    field: 'timeSelect',
+    component: 'TimePicker',
+    componentProps: {
+      size: 'default',
+      //日期时间或者时间模式下是否显示此刻,不支持日期时间范围和时间范围
+      showNow: true,
+    },
+  },
+  {
+    label: '日期时间范围',
+    field: 'dateTimeRangeSelect',
+    component: 'RangePicker',
+    componentProps: {
+      //是否显示时间
+      showTime: true,
+      //日期格式化
+      format: 'YYYY/MM/DD HH:mm:ss',
+      //范围文本描述用集合
+      placeholder: ['请选择开始日期时间', '请选择结束日期时间'],
+    },
+  },
+  {
+    label: '日期范围',
+    field: 'dateRangeSelect',
+    component: 'RangeDate',
+    componentProps: {
+      //日期格式化
+      format: 'YYYY/MM/DD',
+      //范围文本描述用集合
+      placeholder: ['请选择开始日期', '请选择结束日期'],
+    },
+  },
+  {
+    label: '时间范围',
+    field: 'timeRangeSelect',
+    component: 'RangeTime',
+    componentProps: {
+      //日期格式化
+      format: 'HH/mm/ss',
+      //范围文本描述用集合
+      placeholder: ['请选择开始时间', '请选择结束时间'],
+    },
+  },
+  {
+    label: '开关',
+    field: 'switch',
+    component: 'Switch',
+    componentProps: {
+      //开关大小,可选值:default small
+      size: 'default',
+      //非选中时的内容
+      unCheckedChildren: '开启',
+      //非选中时的值
+      unCheckedValue: '0',
+      //选中时的内容
+      checkedChildren: '关闭',
+      //选中时的值
+      checkedValue: '1',
+      //是否禁用
+      disabled: false,
+    },
+  },
+  {
+    label: '滑动输入条',
+    field: 'slider',
+    component: 'Slider',
+    componentProps: {
+      //最小值
+      min: -20,
+      //最大值
+      max: 100,
+      //是否为双滑块模式
+      range: true,
+      //刻度标记
+      marks: {
+        '-20': '-20°C',
+        0: '0°C',
+        26: '26°C',
+        37: '37°C',
+        100: {
+          style: {
+            color: '#f50',
+          },
+          label: '100°C',
+        },
+      },
+    },
+  },
+  {
+    label: '评分',
+    field: 'rate',
+    component: 'Rate',
+    componentProps: {
+      //是否允许半选
+      allowHalf: true,
+      //star 总数
+      count: 5,
+      //tooltip提示,有几颗星写几个
+      tooltips: ['非常差', '较差', '正常', '很好', '非很好'],
+    },
+  },
+  {
+    label: '分割线',
+    field: 'divisionLine',
+    component: 'Divider',
+    componentProps: {
+      //是否虚线
+      dashed: false,
+      //分割线标题的位置(left | right | center)
+      orientation: 'center',
+      //文字是否显示为普通正文样式
+      plain: true,
+      //水平还是垂直类型(horizontal | vertical)
+      type: 'horizontal',
+    },
+  },
+];

+ 452 - 0
src/views/demo/document/form/exampleCustom.data.ts

@@ -0,0 +1,452 @@
+import { FormSchema } from '/@/components/Form';
+import { defHttp } from '/@/utils/http/axios';
+
+export const schemas: FormSchema[] = [
+  {
+    label: '验证码',
+    field: 'code',
+    component: 'InputCountDown',
+    componentProps: {
+      //'default': 默认, 'large': 最大, 'small': 最小
+      size:'default',
+      //倒计时
+      count: 120,
+    },
+  },
+  {
+    label: 'Api下拉选择',
+    field: 'apiSelect',
+    component: 'ApiSelect',
+    componentProps: {
+      //multiple: 多选;不填写为单选
+      mode: 'multiple',
+      //请求api,返回结果{ result: { records:[{'id':'1',name:'scott'},{'id':'2',name:'小张'}] }}
+      api: () => defHttp.get({ url: '/test/jeecgDemo/list' }),
+      //数值转成String
+      numberToString: false,
+      //标题字段
+      labelField: 'name',
+      //值字段
+      valueField: 'id',
+      //请求参数
+      params: {},
+      //返回结果字段
+      resultField: 'records',
+    },
+  },
+  {
+    label: 'Api树选择',
+    field: 'apiSelect',
+    component: 'ApiTreeSelect',
+    componentProps: {
+      /* 请求api,返回结果
+         { result: { list: [{ title:'选项0',value:'0',key:'0',
+           children: [ {"title": "选项0-0","value": "0-0","key": "0-0"},...]
+           }, ...]
+         }} */
+      api: () => defHttp.get({ url: '/mock/tree/getDemoOptions' }),
+      //请求参数
+      params: {},
+      //返回结果字段
+      resultField: 'list',
+    },
+  },
+  {
+    label: '校验密码强度',
+    field: 'pwd',
+    component: 'StrengthMeter',
+    componentProps: {
+      //是否显示密码文本框
+      showInput: true,
+      //是否禁用
+      disabled: false,
+    },
+  },
+  {
+    label: '省市县联动',
+    field: 'province',
+    component: 'JAreaLinkage',
+    componentProps: {
+      //是否显示区县,默认true,否则只显示省
+      showArea: true,
+      //是否是全部文本,默认false
+      showAll: true,
+    },
+  },
+  {
+    label: '岗位选择',
+    field: 'post',
+    component: 'JSelectPosition',
+    componentProps: {
+      //是否右侧显示选中列表
+      showSelected: true,
+      //最大选择数量
+      maxSelectCount: 1,
+      //岗位标题
+      modalTitle: '岗位',
+    },
+  },
+  {
+    label: '角色选择',
+    field: 'role',
+    component: 'JSelectRole',
+    componentProps: {
+      //请求参数 如params:{"code":"001"}
+      params: {},
+      //是否单选,默认false
+      isRadioSelection: true,
+      //角色标题
+      modalTitle: '角色',
+    },
+  },
+  {
+    label: '用户选择',
+    field: 'user',
+    component: 'JSelectUser',
+    componentProps: {
+      //取值字段配置,一般为主键字段
+      rowKey: 'username',
+      //显示字段配置
+      labelKey: 'realname',
+      //是否显示选择按钮
+      showButton: false,
+      //用户标题
+      modalTitle: '用户',
+    },
+  },
+  {
+    label: '图片上传',
+    field: 'uploadImage',
+    component: 'JImageUpload',
+    componentProps: {
+      //按钮显示文字
+      text:'图片上传',
+      //支持两种基本样式picture和picture-card
+      listType:'picture-card',
+      //用于控制文件上传的业务路径,默认temp
+      bizPath:'temp',
+      //是否禁用
+      disabled:false,
+      //最大上传数量
+      fileMax:1,
+    },
+  },
+  {
+    label: '字典标签',
+    field: 'dictTags',
+    component: 'JDictSelectTag',
+    componentProps: {
+      //字典code配置,比如通过性别字典编码:sex,也可以使用demo,name,id 表名,名称,值的方式
+      dictCode:'sex',
+      //支持radio(单选按钮)、radioButton(单选按钮 btn风格)、select(下拉框)
+      type:'radioButton'
+    },
+  },
+  {
+    label: '部门选择',
+    field: 'dept',
+    component: 'JSelectDept',
+    componentProps: {
+      //是否开启异步加载
+      sync: false,
+      //是否显示复选框
+      checkable: true,
+      //是否显示选择按钮
+      showButton: false,
+      //父子节点选中状态不再关联
+      checkStrictly: true,
+      //选择框标题
+      modalTitle: '部门选择',
+    },
+  },
+  {
+    label: '省市县级联动',
+    field: 'provinceArea',
+    component: 'JAreaSelect',
+    componentProps: {
+      //级别 1 只显示省 2 省市 3 省市区
+      level:3
+    },
+  },
+  {
+    label: '富文本',
+    field: 'editor',
+    component: 'JEditor',
+    componentProps: {
+      //是否禁用
+      disabled: false
+    },
+  },
+  {
+    label: 'markdown',
+    field: 'markdown',
+    component: 'JMarkdownEditor',
+    componentProps: {
+      //是否禁用
+      disabled: false
+    },
+  },
+  {
+    label: '可输入下拉框',
+    field: 'inputSelect',
+    component: 'JSelectInput',
+    componentProps: {
+      options: [
+        { label: 'Default', value: 'default' },
+        { label: 'IFrame', value: 'iframe' },
+      ],
+      //是否为搜索模式
+      showSearch: true,
+      //是否禁用
+      disabled: false
+    },
+  },
+  {
+    label: '代码编辑器组件',
+    field: 'jCode',
+    component: 'JCodeEditor',
+    componentProps: {
+      //高度,默认auto
+      height:'150px',
+      //是否禁用
+      disabled:false,
+      //是否全屏
+      fullScreen:false,
+      //全屏之后的坐标
+      zIndex: 999,
+      //代码主题,目前只支持idea,可在组件自行扩展
+      theme:'idea',
+      //代码提示
+      keywords:['console'],
+      //语言如(javascript,vue,markdown)可在组件自行扩展
+      language:'javascript'
+    },
+  },
+  {
+    label: '分类字典树',
+    field: 'dictTree',
+    component: 'JCategorySelect',
+    componentProps: {
+      //占位内容
+      placeholder:'请选择分类字典树',
+      //查询条件,如“{'name':'笔记本'}”
+      condition:"",
+      //是否多选
+      multiple: false,
+      //起始选择code,见配置的分类字典的类型编码
+      pcode: 'A04',
+      //父级id
+      pid:'',
+      //返回key
+      back:'id',
+    },
+  },
+  {
+    label: '下拉多选',
+    field: 'selectMultiple',
+    component: 'JSelectMultiple',
+    componentProps: {
+      //字典code配置,比如通过性别字典编码:sex,也可以使用demo,name,id 表名,名称,值的方式
+      dictCode:'company_rank',
+      //是否只读
+      readOnly:false,
+    },
+  },
+  {
+    label: 'popup',
+    field: 'popup',
+    component: 'JPopup',
+    componentProps: ({ formActionType }) => {
+      const {setFieldsValue} = formActionType;
+      return{
+        setFieldsValue:setFieldsValue,
+        //online报表编码
+        code:"demo",
+        //是否为多选
+        multi:false,
+        //字段配置
+        fieldConfig: [
+          { source: 'name', target: 'popup' },
+        ],
+      }
+    },
+  },
+  {
+    label: '开关自定义',
+    field: 'switch',
+    component: 'JSwitch',
+    componentProps:{
+      //取值 options
+      options:['Y','N'],
+      //文本option
+      labelOptions:['是', '否'],
+      //是否启用下拉
+      query: false,
+      //是否禁用
+      disabled: false,
+    },
+  },
+  {
+    label: '定时表达式选择',
+    field: 'timing',
+    component: 'JEasyCron',
+    componentProps:{
+      //是否隐藏参数秒和年设置,如果隐藏,那么参数秒和年将会全部忽略掉。
+      hideSecond: false,
+      //是否隐藏参数年设置,如果隐藏,那么参数年将会全部忽略掉
+      hideYear: false,
+      //是否禁用
+      disabled: false,
+      //获取预览执行时间列表的函数,格式为:remote (cron值, time时间戳, cb回调函数)
+      remote:(cron,time,cb)=>{}
+    },
+  },
+  {
+    label: '分类字典树',
+    field: 'treeDict',
+    component: 'JTreeDict',
+    componentProps:{
+      //指定当前组件需要存储的字段 可选: id(主键)和code(编码)
+      field:'id',
+      //是否为异步
+      async: true,
+      //是否禁用
+      disabled: false,
+      //指定一个节点的编码,加载该节点下的所有字典数据,若不指定,默认加载所有数据
+      parentCode:'A04'
+    },
+  },
+  {
+    label: '多行输入窗口',
+    field: 'inputPop',
+    component: 'JInputPop',
+    componentProps:{
+      //标题
+      title:'多行输入窗口',
+      //弹窗显示位置
+      position:'bottom',
+    },
+  },
+  {
+    label: '多选',
+    field: 'multipleChoice',
+    component: 'JCheckbox',
+    componentProps:{
+      //字典code配置,比如通过职位字典编码:company_rank,也可以使用demo,name,id 表名,名称,值的方式
+      dictCode:'company_rank',
+      //是否禁用
+      disabled: false,
+      //没有字典code可以使用option来定义
+      // options:[
+      //   {label:'CE0',value:'1'}
+      // ]
+    },
+  },
+  {
+    label: '下拉树选择',
+    field: 'treeCusSelect',
+    component: 'JTreeSelect',
+    componentProps: {
+      //字典code配置,比如通过性别字典编码:sex,也可以使用sys_permission,name,id 表名,名称,值的方式
+      dict: 'sys_permission,name,id',
+      //父级id字段
+      pidField: 'parent_id',
+    },
+  },
+  {
+    label: '根据部门选择用户组件',
+    field: 'userByDept',
+    component: 'JSelectUserByDept',
+    componentProps: {
+      //是否显示选择按钮
+      showButton: true,
+      //选择框标题
+      modalTitle: '部门用户选择'
+    },
+  },
+  {
+    label: '文件上传',
+    field: 'uploadFile',
+    component: 'JUpload',
+    componentProps: {
+      //是否显示选择按钮
+      text: '文件上传',
+      //最大上传数
+      maxCount: 2,
+      //是否显示下载按钮
+      download: true,
+    },
+  },
+  {
+    label: '字典表搜索',
+    field: 'dictSearchSelect',
+    component: 'JSearchSelect',
+    componentProps: {
+      //字典code配置,通过 demo,name,id 表名,名称,值的方式
+      dict: 'demo,name,id',
+      //是否异步加载
+      async: true,
+      //当async设置为true时有效,表示异步查询时,每次获取数据的数量,默认10
+      pageSize:3
+    },
+  },
+  {
+    label: '动态创建input框',
+    field: 'jAddInput',
+    component: 'JAddInput',
+    componentProps: {
+      //自定义超过多少行才会显示删除按钮,默认为1
+      min:1
+    },
+  },
+  {
+    label: '用户选择组件',
+    field: 'userCusSelect',
+    component: 'UserSelect',
+    componentProps: {
+      //是否多选
+      multi: true,
+      //从用户表中选择一列,其值作为该控件的存储值,默认id列
+      store: 'id',
+      //是否排除我自己(当前登录用户)
+      izExcludeMy: false,
+      //是否禁用
+      disabled: false,
+    },
+  },  
+  {
+    label: '选择角色组件',
+    field: 'roleSelect',
+    component: 'RoleSelect',
+    componentProps: {
+      //最大选择数量  
+      maxSelectCount: 4,
+      //是否单选
+      multi: true
+    },
+  },  
+  {
+    label: '数值范围输入框',
+    field: 'rangeNumber',
+    component: 'JRangeNumber',
+  }, 
+  {
+    label: '远程Api单选框组',
+    field: 'apiRadioGroup',
+    component: 'ApiRadioGroup',
+    componentProps:{
+      //请求接口返回结果{ result:{ list: [ name: '选项0',id: '1' ] }}
+      api:()=> defHttp.get({ url: '/mock/select/getDemoOptions' }),
+      //请求参数
+      params:{},
+      //是否为按钮风格类型,默认false
+      isBtn: false,
+      //返回集合名称
+      resultField: 'list',
+      //标题字段名称
+      labelField: 'name',
+      //值字段名称
+      valueField: 'id',
+    }
+  },
+];

+ 24 - 0
src/views/demo/document/form/index.ts

@@ -0,0 +1,24 @@
+export { default as BasicFiledsLayotForm } from './BasicFiledsLayotForm.vue';
+export { default as BasicFixedWidthForm } from './BasicFixedWidthForm.vue';
+export { default as BasicFormAdd } from './BasicFormAdd.vue';
+export { default as BasicFormBtn } from './BasicFormBtn.vue';
+export { default as BasicFormCleanRule } from './BasicFormCleanRule.vue';
+export { default as BasicFormCompact } from './BasicFormCompact.vue';
+export { default as BasicFormComponent } from './BasicFormComponent.vue';
+export { default as BasicFormConAttribute } from './BasicFormConAttribute.vue';
+export { default as BasicFormCustom } from './BasicFormCustom.vue';
+export { default as BasicFormCustomComponent } from './BasicFormCustomComponent.vue';
+export { default as BasicFormCustomSlots } from './BasicFormCustomSlots.vue';
+export { default as BasicFormDynamicsRules } from './BasicFormDynamicsRules.vue';
+export { default as BasicFormFieldShow } from './BasicFormFieldShow.vue';
+export { default as BasicFormFieldTip } from './BasicFormFieldTip.vue';
+export { default as BasicFormFooter } from './BasicFormFooter.vue';
+export { default as BasicFormLayout } from './BasicFormLayout.vue';
+export { default as BasicFormModal } from './BasicFormModal.vue';
+export { default as BasicFormRander } from './BasicFormRander.vue';
+export { default as BasicFormRules } from './BasicFormRules.vue';
+export { default as BasicFormSchemas } from './BasicFormSchemas.vue';
+export { default as BasicFormSearch } from './BasicFormSearch.vue';
+export { default as BasicFormSlots } from './BasicFormSlots.vue';
+export { default as BasicFormValue } from './BasicFormValue.vue';
+export { default as BasicFunctionForm } from './BasicFunctionForm.vue';

+ 114 - 0
src/views/demo/document/form/tabIndex.vue

@@ -0,0 +1,114 @@
+<template>
+  <div class="p-4">
+    <a-card :bordered="false" style="height: 100%">
+      <a-tabs v-model:activeKey="activeKey" @change="tabChange">
+        <a-tab-pane :key="item.key" :tab="item.label" v-for="item in compList" />
+      </a-tabs>
+      <component :is="currentComponent" />
+    </a-card>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed } from 'vue';
+  import {
+    BasicFunctionForm,
+    BasicFormConAttribute,
+    BasicFormFieldShow,
+    BasicFormFieldTip,
+    BasicFormRules,
+    BasicFormDynamicsRules,
+    BasicFormSlots,
+    BasicFormCustomSlots,
+    BasicFormRander,
+    BasicFixedWidthForm,
+    BasicFiledsLayotForm,
+    BasicFormLayout,
+    BasicFormBtn,
+    BasicFormCompact,
+    BasicFormCleanRule,
+    BasicFormValue,
+    BasicFormSchemas,
+    BasicFormAdd,
+    BasicFormFooter,
+    BasicFormModal,
+    BasicFormCustom,
+    BasicFormSearch,
+    BasicFormComponent,
+    BasicFormCustomComponent,
+  } from './index';
+  export default defineComponent({
+    name: 'document-table-demo',
+    components: {
+      BasicFunctionForm,
+      BasicFormConAttribute,
+      BasicFormFieldShow,
+      BasicFormFieldTip,
+      BasicFormRules,
+      BasicFormDynamicsRules,
+      BasicFormSlots,
+      BasicFormCustomSlots,
+      BasicFormRander,
+      BasicFixedWidthForm,
+      BasicFiledsLayotForm,
+      BasicFormLayout,
+      BasicFormBtn,
+      BasicFormCompact,
+      BasicFormCleanRule,
+      BasicFormValue,
+      BasicFormSchemas,
+      BasicFormAdd,
+      BasicFormFooter,
+      BasicFormModal,
+      BasicFormCustom,
+      BasicFormSearch,
+      BasicFormComponent,
+      BasicFormCustomComponent,
+    },
+    setup() {
+      //当前选中key
+      const activeKey = ref('BasicFunctionForm');
+      //组件集合
+      const compList = ref([
+        { key: 'BasicFunctionForm', label: '基础表单' },
+        { key: 'BasicFormConAttribute', label: '字段控件属性' },
+        { key: 'BasicFormComponent', label: 'Ant Design Vue自带控件' },
+        { key: 'BasicFormCustomComponent', label: 'JEECG封装的控件' },
+        { key: 'BasicFormFieldShow', label: '字段显示和隐藏' },
+        { key: 'BasicFormFieldTip', label: '字段标题提示' },
+        { key: 'BasicFormRules', label: '表单检验' },
+        { key: 'BasicFormDynamicsRules', label: '自定义动态检验' },
+        { key: 'BasicFormSlots', label: '字段插槽' },
+        { key: 'BasicFormCustomSlots', label: '自定义组件(插槽)' },
+        { key: 'BasicFormCustom', label: '自定义组件(component)' },
+        { key: 'BasicFormRander', label: '自定义渲染' },
+        { key: 'BasicFixedWidthForm', label: '固定label宽度' },
+        { key: 'BasicFiledsLayotForm', label: '标题与字段布局' },
+        { key: 'BasicFormLayout', label: '表单布局' },
+        { key: 'BasicFormBtn', label: '操作按钮示例' },
+        { key: 'BasicFormCompact', label: '表单紧凑' },
+        { key: 'BasicFormCleanRule', label: '表单检验配置' },
+        { key: 'BasicFormValue', label: '获取value值' },
+        { key: 'BasicFormSchemas', label: '更新schemas表单配置' },
+        { key: 'BasicFormAdd', label: '动态增减表单' },
+        { key: 'BasicFormFooter', label: '自定义页脚' },
+        { key: 'BasicFormModal', label: '弹出层表单' },
+        { key: 'BasicFormSearch', label: '查询区域' },
+      ]);
+      //当前选中组件
+      const currentComponent = computed(() => {
+        return activeKey.value;
+      });
+
+      //使用component动态切换tab
+      function tabChange(key) {
+        activeKey.value = key;
+      }
+      return {
+        activeKey,
+        currentComponent,
+        tabChange,
+        compList,
+      };
+    },
+  });
+</script>

+ 135 - 0
src/views/demo/document/table/AuthColumnDemo.vue

@@ -0,0 +1,135 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '编辑',
+              onClick: handleEdit.bind(null, record),
+              auth: 'demo:btn:show', // 根据权限控制是否显示: 无权限,不显示
+            },
+            {
+              label: '删除',
+              icon: 'ic:outline-delete-outline',
+              onClick: handleDelete.bind(null, record),
+              auth: 'super', // 根据权限控制是否显示: 有权限,会显示
+            },
+          ]"
+          :dropDownActions="[
+            {
+              label: '启用',
+              popConfirm: {
+                title: '是否启用?',
+                confirm: handleOpen.bind(null, record),
+              },
+              ifShow: (_action) => {
+                return record.status !== 'enable'; // 根据业务控制是否显示: 非enable状态的不显示启用按钮
+              },
+            },
+            {
+              label: '禁用',
+              popConfirm: {
+                title: '是否禁用?',
+                confirm: handleOpen.bind(null, record),
+              },
+              ifShow: () => {
+                return record.status === 'enable'; // 根据业务控制是否显示: enable状态的显示禁用按钮
+              },
+            },
+            {
+              label: '同时控制',
+              popConfirm: {
+                title: '是否动态显示?',
+                confirm: handleOpen.bind(null, record),
+              },
+              auth: 'super', // 同时根据权限和业务控制是否显示
+              ifShow: () => {
+                return true;
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';
+
+  import { demoListApi } from '/@/api/demo/table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  const columns: BasicColumn[] = [
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 100,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      auth: 'demo:field:show', // 根据权限控制是否显示: 无权限,不显示
+    },
+    {
+      title: '状态',
+      dataIndex: 'status',
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      auth: 'super', // 同时根据权限和业务控制是否显示
+      ifShow: (_column) => {
+        return true;
+      },
+    },
+    {
+      title: '开始时间',
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+      width: 200,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableAction },
+    setup() {
+      const { tableContext } = useListPage({
+        tableProps: {
+          title: '权限列',
+          api: demoListApi,
+          columns: columns,
+          bordered: true,
+          useSearchForm: false,
+          actionColumn: {
+            width: 250,
+            title: 'Action',
+            dataIndex: 'action',
+            slots: { customRender: 'action' },
+          },
+        },
+      });
+
+      //注册table数据
+      const [registerTable] = tableContext;
+
+      function handleEdit(record: Recordable) {
+        console.log('点击了编辑', record);
+      }
+      function handleDelete(record: Recordable) {
+        console.log('点击了删除', record);
+      }
+      function handleOpen(record: Recordable) {
+        console.log('点击了启用', record);
+      }
+      return {
+        registerTable,
+        handleEdit,
+        handleDelete,
+        handleOpen,
+      };
+    },
+  });
+</script>

+ 94 - 0
src/views/demo/document/table/BasicTableBorder.vue

@@ -0,0 +1,94 @@
+<template>
+  <!--引用表格-->
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #bodyCell="{ column, text }">
+        <template v-if="column.dataIndex === 'name'">
+          <a>{{ text }}</a>
+        </template>
+      </template>
+      <template #footer>页脚</template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="basic-table-demo" setup>
+  import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  //定义表格列
+  const columns: BasicColumn[] = [
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      key: 'name',
+      width: 300,
+    },
+    {
+      title: '年龄',
+      dataIndex: 'age',
+      key: 'age',
+      width: 300,
+    },
+    {
+      title: '住址',
+      dataIndex: 'address',
+      key: 'address',
+      ellipsis: true,
+    },
+    {
+      title: '长内容列',
+      dataIndex: 'address',
+      key: 'address 2',
+      ellipsis: true,
+    },
+    {
+      title: '长内容列',
+      dataIndex: 'address',
+      key: 'address 3',
+      ellipsis: true,
+    },
+  ];
+  // 列表页面公共参数、方法
+  const { tableContext } = useListPage({
+    designScope: 'basic-table-demo',
+    tableProps: {
+      title: '边框表格',
+      dataSource: [
+        {
+          key: '1',
+          name: '张三',
+          age: 32,
+          address: '中国北京北京市朝阳区大屯路科学院南里1号楼3单元401',
+        },
+        {
+          key: '2',
+          name: '刘思',
+          age: 32,
+          address: '中国北京北京市昌平区顺沙路尚湖世家2号楼7单元503',
+        },
+      ],
+      columns: columns,
+      showActionColumn: false,
+      useSearchForm: false,
+    },
+  });
+  //注册table数据
+  const [registerTable] = tableContext;
+  /**
+   * 操作栏
+   */
+  function getTableAction(record): ActionItem[] {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+    ];
+  }
+
+  function handleEdit(record) {
+    console.log(record);
+  }
+</script>
+
+<style scoped></style>

+ 81 - 0
src/views/demo/document/table/BasicTableDemo.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="p-4">
+    <!--引用表格-->
+    <BasicTable @register="registerTable">
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="basic-table-demo" setup>
+  import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  //定义表格列
+  const columns: BasicColumn[] = [
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      key: 'name',
+      resizable: false,
+    },
+    {
+      title: '年龄',
+      dataIndex: 'age',
+      key: 'age',
+    },
+    {
+      title: '住址',
+      dataIndex: 'address',
+      key: 'address',
+    },
+  ];
+  // 列表页面公共参数、方法
+  const { tableContext } = useListPage({
+    designScope: 'basic-table-demo',
+    tableProps: {
+      title: '用户列表',
+      dataSource: [
+        {
+          key: '1',
+          name: '胡歌',
+          age: 32,
+          address: '朝阳区林萃路1号',
+        },
+        {
+          key: '2',
+          name: '刘诗诗',
+          age: 32,
+          address: '昌平区白沙路1号',
+        },
+      ],
+      columns: columns,
+      size: 'large', //紧凑型表格 large
+      striped: false, //斑马纹设置 false
+      showActionColumn: true,
+      useSearchForm: false,
+    },
+  });
+  //注册table数据
+  const [registerTable, methods] = tableContext;
+  console.log('methods', methods);
+  /**
+   * 操作栏
+   */
+  function getTableAction(record): ActionItem[] {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+    ];
+  }
+
+  function handleEdit(record) {
+    console.log(record);
+  }
+</script>
+
+<style scoped></style>

+ 157 - 0
src/views/demo/document/table/BasicTableDemoAjax.vue

@@ -0,0 +1,157 @@
+<template>
+  <div class="p-4">
+    <!--定义表格-->
+    <BasicTable @register="registerTable">
+      <!-- 搜索区域插槽自定义查询 -->
+      <template #form-email="{ model, field }">
+        <a-input placeholder="请输入邮箱" v-model:value="model[field]" addon-before="邮箱:" addon-after=".com"></a-input>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="basic-table-demo" setup>
+  import { ActionItem, BasicColumn, BasicTable, FormSchema, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  import { defHttp } from '/@/utils/http/axios';
+  //定义表格列
+  const columns: BasicColumn[] = [
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 170,
+      align: 'left',
+      resizable: true,
+      sorter: {
+        multiple: 1,
+      },
+    },
+    {
+      title: '关键词',
+      dataIndex: 'keyWord',
+      width: 130,
+      resizable: true,
+    },
+    {
+      title: '打卡时间',
+      dataIndex: 'punchTime',
+      width: 140,
+      resizable: true,
+    },
+    {
+      title: '工资',
+      dataIndex: 'salaryMoney',
+      width: 140,
+      resizable: true,
+      sorter: {
+        multiple: 2,
+      },
+    },
+    {
+      title: '奖金',
+      dataIndex: 'bonusMoney',
+      width: 140,
+      resizable: true,
+    },
+    {
+      title: '性别',
+      dataIndex: 'sex',
+      sorter: {
+        multiple: 3,
+      },
+      filters: [
+        { text: '男', value: '1' },
+        { text: '女', value: '2' },
+      ],
+      customRender: ({ record }) => {
+        return record.sex ? (record.sex == '1' ? '男' : '女') : '';
+      },
+      width: 120,
+      resizable: true,
+    },
+    {
+      title: '生日',
+      dataIndex: 'birthday',
+      width: 120,
+      resizable: true,
+    },
+    {
+      title: '邮箱',
+      dataIndex: 'email',
+      width: 120,
+      resizable: true,
+    },
+  ];
+  //表单搜索字段
+  const searchFormSchema: FormSchema[] = [
+    {
+      label: '姓名', //显示label
+      field: 'name', //查询字段
+      component: 'JInput', //渲染的组件
+      defaultValue: '苏榕润', //设置默认值
+    },
+    {
+      label: '性别',
+      field: 'sex',
+      component: 'JDictSelectTag',
+      componentProps: {
+        //渲染的组件的props
+        dictCode: 'sex',
+        placeholder: '请选择性别',
+      },
+    },
+    {
+      label: '邮箱',
+      field: 'email',
+      component: 'JInput',
+      slot: 'email',
+    },
+    {
+      label: '生日',
+      field: 'birthday',
+      component: 'DatePicker',
+    },
+  ];
+  //ajax请求api接口
+  const demoListApi = (params) => {
+    return defHttp.get({ url: '/test/jeecgDemo/list', params });
+  };
+  // 列表页面公共参数、方法
+  const { tableContext } = useListPage({
+    designScope: 'basic-table-demo-filter',
+    tableProps: {
+      title: '用户列表',
+      api: demoListApi,
+      columns: columns,
+      formConfig: {
+        schemas: searchFormSchema,
+      },
+      useSearchForm: true,
+    },
+  });
+  //BasicTable绑定注册
+  const [registerTable, { getForm }] = tableContext;
+  /**
+   * 操作栏
+   */
+  function getTableAction(record): ActionItem[] {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+    ];
+  }
+
+  function handleEdit(record) {
+    let { getFieldsValue } = getForm();
+    console.log('查询form的数据', getFieldsValue());
+    console.log(record);
+  }
+</script>
+
+<style scoped></style>

+ 106 - 0
src/views/demo/document/table/CustomerCellDemo.vue

@@ -0,0 +1,106 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #id="{ record }"> ID: {{ record.id }} </template>
+      <template #bodyCell="{ column, record }">
+        <Avatar v-if="column.key === 'avatar'" :size="60" :src="record.avatar" />
+        <Tag v-if="column.key === 'no'" color="green">
+          {{ record.no }}
+        </Tag>
+      </template>
+      <template #img="{ text }">
+        <TableImg :size="60" :simpleShow="true" :imgList="text" />
+      </template>
+      <template #imgs="{ text }"> <TableImg :size="60" :imgList="text" /> </template>
+
+      <template #category="{ record }">
+        <Tag color="green">
+          {{ record.no }}
+        </Tag>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableImg } from '/@/components/Table';
+  import { Tag, Avatar } from 'ant-design-vue';
+  import { demoListApi } from '/@/api/demo/table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  const columns: BasicColumn[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      slots: { customRender: 'id' },
+    },
+    {
+      title: '头像',
+      dataIndex: 'avatar',
+      width: 100,
+    },
+    {
+      title: '分类',
+      dataIndex: 'category',
+      width: 80,
+      align: 'center',
+      defaultHidden: true,
+      slots: { customRender: 'category' },
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 120,
+    },
+    {
+      title: '图片列表1',
+      dataIndex: 'imgArr',
+      helpMessage: ['这是简单模式的图片列表', '只会显示一张在表格中', '但点击可预览多张图片'],
+      width: 140,
+      slots: { customRender: 'img' },
+    },
+    {
+      title: '照片列表2',
+      dataIndex: 'imgs',
+      width: 160,
+      slots: { customRender: 'imgs' },
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+    },
+    {
+      title: '开始时间',
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableImg, Tag, Avatar },
+    setup() {
+      const { tableContext } = useListPage({
+        tableProps: {
+          title: '自定义列内容',
+          titleHelpMessage: '表格中所有头像、图片均为mock生成,仅用于演示图片占位',
+          api: demoListApi,
+          columns: columns,
+          bordered: true,
+          showTableSetting: false,
+          showActionColumn: false,
+          useSearchForm: false,
+        },
+      });
+      //注册table数据
+      const [registerTable] = tableContext;
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 217 - 0
src/views/demo/document/table/EditCellTableDemo.vue

@@ -0,0 +1,217 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" @edit-end="handleEditEnd" @edit-cancel="handleEditCancel" :beforeEditSubmit="beforeEditSubmit" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
+  import { optionsListApi } from '/@/api/demo/select';
+
+  import { demoListApi } from '/@/api/demo/table';
+  import { treeOptionsListApi } from '/@/api/demo/tree';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  const columns: BasicColumn[] = [
+    {
+      title: '输入框',
+      dataIndex: 'name',
+      edit: true,
+      editComponentProps: {
+        prefix: '$',
+      },
+      width: 200,
+    },
+    {
+      title: '默认输入状态',
+      dataIndex: 'name7',
+      edit: true,
+      editable: true,
+      width: 200,
+    },
+    {
+      title: '输入框校验',
+      dataIndex: 'name1',
+      edit: true,
+      // 默认必填校验
+      editRule: true,
+      width: 200,
+    },
+    {
+      title: '输入框函数校验',
+      dataIndex: 'name2',
+      edit: true,
+      editRule: async (text) => {
+        if (text === '2') {
+          return '不能输入该值';
+        }
+        return '';
+      },
+      width: 200,
+    },
+    {
+      title: '数字输入框',
+      dataIndex: 'id',
+      edit: true,
+      editRule: true,
+      editComponent: 'InputNumber',
+      width: 200,
+    },
+    {
+      title: '下拉框',
+      dataIndex: 'name3',
+      edit: true,
+      editComponent: 'Select',
+      editComponentProps: {
+        options: [
+          {
+            label: 'Option1',
+            value: '1',
+          },
+          {
+            label: 'Option2',
+            value: '2',
+          },
+        ],
+      },
+      width: 200,
+    },
+    {
+      title: '远程下拉',
+      dataIndex: 'name4',
+      edit: true,
+      editComponent: 'ApiSelect',
+      editComponentProps: {
+        api: optionsListApi,
+        resultField: 'list',
+        labelField: 'name',
+        valueField: 'id',
+      },
+      width: 200,
+    },
+    {
+      title: '远程下拉树',
+      dataIndex: 'name71',
+      edit: true,
+      editComponent: 'ApiTreeSelect',
+      editRule: false,
+      editComponentProps: {
+        api: treeOptionsListApi,
+        resultField: 'list',
+      },
+      width: 200,
+    },
+    {
+      title: '日期选择',
+      dataIndex: 'date',
+      edit: true,
+      editComponent: 'DatePicker',
+      editComponentProps: {
+        valueFormat: 'YYYY-MM-DD',
+        format: 'YYYY-MM-DD',
+      },
+      width: 200,
+    },
+    {
+      title: '时间选择',
+      dataIndex: 'time',
+      edit: true,
+      editComponent: 'TimePicker',
+      editComponentProps: {
+        valueFormat: 'HH:mm',
+        format: 'HH:mm',
+      },
+      width: 200,
+    },
+    {
+      title: '勾选框',
+      dataIndex: 'name5',
+      edit: true,
+      editComponent: 'Checkbox',
+      editValueMap: (value) => {
+        return value ? '是' : '否';
+      },
+      width: 200,
+    },
+    {
+      title: '开关',
+      dataIndex: 'name6',
+      edit: true,
+      editComponent: 'Switch',
+      editValueMap: (value) => {
+        return value ? '开' : '关';
+      },
+      width: 200,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      // 列表页面公共参数、方法
+      const { tableContext } = useListPage({
+        designScope: 'basic-table-demo',
+        tableProps: {
+          title: '可编辑单元格示例',
+          api: demoListApi,
+          columns: columns,
+          showIndexColumn: false,
+          bordered: true,
+          showActionColumn: false,
+          useSearchForm: false,
+        },
+      });
+      //注册table数据
+      const [registerTable] = tableContext;
+      const { createMessage } = useMessage();
+
+      function handleEditEnd({ record, index, key, value }: Recordable) {
+        console.log(record, index, key, value);
+        return false;
+      }
+
+      // 模拟将指定数据保存
+      function feakSave({ value, key, id }) {
+        createMessage.loading({
+          content: `正在模拟保存${key}`,
+          key: '_save_fake_data',
+          duration: 0,
+        });
+        return new Promise((resolve) => {
+          setTimeout(() => {
+            if (value === '') {
+              createMessage.error({
+                content: '保存失败:不能为空',
+                key: '_save_fake_data',
+                duration: 2,
+              });
+              resolve(false);
+            } else {
+              createMessage.success({
+                content: `记录${id}的${key}已保存`,
+                key: '_save_fake_data',
+                duration: 2,
+              });
+              resolve(true);
+            }
+          }, 2000);
+        });
+      }
+
+      async function beforeEditSubmit({ record, index, key, value }) {
+        console.log('单元格数据正在准备提交', { record, index, key, value });
+        return await feakSave({ id: record.id, key, value });
+      }
+
+      function handleEditCancel() {
+        console.log('cancel');
+      }
+
+      return {
+        registerTable,
+        handleEditEnd,
+        handleEditCancel,
+        beforeEditSubmit,
+      };
+    },
+  });
+</script>

+ 261 - 0
src/views/demo/document/table/EditRowTableDemo.vue

@@ -0,0 +1,261 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" @edit-change="onEditChange">
+      <template #action="{ record, column }">
+        <TableAction :actions="createActions(record, column)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { BasicTable, useTable, TableAction, BasicColumn, ActionItem, EditRecordRow } from '/@/components/Table';
+  import { optionsListApi } from '/@/api/demo/select';
+
+  import { demoListApi } from '/@/api/demo/table';
+  import { treeOptionsListApi } from '/@/api/demo/tree';
+  import { cloneDeep } from 'lodash-es';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { useListPage } from '/@/hooks/system/useListPage';
+
+  const columns: BasicColumn[] = [
+    {
+      title: '输入框',
+      dataIndex: 'name',
+      editRow: true,
+      editComponentProps: {
+        prefix: '$',
+      },
+      width: 150,
+    },
+    {
+      title: '默认输入状态',
+      dataIndex: 'name7',
+      editRow: true,
+      width: 150,
+    },
+    {
+      title: '输入框校验',
+      dataIndex: 'name1',
+      editRow: true,
+      align: 'left',
+      // 默认必填校验
+      editRule: true,
+      width: 150,
+    },
+    {
+      title: '输入框函数校验',
+      dataIndex: 'name2',
+      editRow: true,
+      align: 'right',
+      editRule: async (text) => {
+        if (text === '2') {
+          return '不能输入该值';
+        }
+        return '';
+      },
+    },
+    {
+      title: '数字输入框',
+      dataIndex: 'id',
+      editRow: true,
+      editRule: true,
+      editComponent: 'InputNumber',
+      width: 150,
+    },
+    {
+      title: '下拉框',
+      dataIndex: 'name3',
+      editRow: true,
+      editComponent: 'Select',
+      editComponentProps: {
+        options: [
+          {
+            label: 'Option1',
+            value: '1',
+          },
+          {
+            label: 'Option2',
+            value: '2',
+          },
+          {
+            label: 'Option3',
+            value: '3',
+          },
+        ],
+      },
+      width: 200,
+    },
+    {
+      title: '远程下拉',
+      dataIndex: 'name4',
+      editRow: true,
+      editComponent: 'ApiSelect',
+      editComponentProps: {
+        api: optionsListApi,
+        resultField: 'list',
+        labelField: 'name',
+        valueField: 'id',
+      },
+      width: 200,
+    },
+    {
+      title: '远程下拉树',
+      dataIndex: 'name8',
+      editRow: true,
+      editComponent: 'ApiTreeSelect',
+      editRule: false,
+      editComponentProps: {
+        api: treeOptionsListApi,
+        resultField: 'list',
+      },
+      width: 200,
+    },
+    {
+      title: '日期选择',
+      dataIndex: 'date',
+      editRow: true,
+      editComponent: 'DatePicker',
+      editComponentProps: {
+        valueFormat: 'YYYY-MM-DD',
+        format: 'YYYY-MM-DD',
+      },
+      width: 150,
+    },
+    {
+      title: '时间选择',
+      dataIndex: 'time',
+      editRow: true,
+      editComponent: 'TimePicker',
+      editComponentProps: {
+        valueFormat: 'HH:mm',
+        format: 'HH:mm',
+      },
+      width: 100,
+    },
+    {
+      title: '勾选框',
+      dataIndex: 'name5',
+      editRow: true,
+
+      editComponent: 'Checkbox',
+      editValueMap: (value) => {
+        return value ? '是' : '否';
+      },
+      width: 100,
+    },
+    {
+      title: '开关',
+      dataIndex: 'name6',
+      editRow: true,
+      editComponent: 'Switch',
+      editValueMap: (value) => {
+        return value ? '开' : '关';
+      },
+      width: 100,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableAction },
+    setup() {
+      const { createMessage: msg } = useMessage();
+      const currentEditKeyRef = ref('');
+
+      const { tableContext } = useListPage({
+        designScope: 'basic-table-demo',
+        tableProps: {
+          title: '可编辑行示例',
+          titleHelpMessage: ['本例中修改[数字输入框]这一列时,同一行的[远程下拉]列的当前编辑数据也会同步发生改变'],
+          api: demoListApi,
+          columns: columns,
+          showIndexColumn: false,
+          showTableSetting: true,
+          tableSetting: { fullScreen: true },
+          actionColumn: {
+            width: 160,
+            title: 'Action',
+            dataIndex: 'action',
+            slots: { customRender: 'action' },
+          },
+          useSearchForm: false,
+        },
+      });
+      //注册table数据
+      const [registerTable] = tableContext;
+
+      function handleEdit(record: EditRecordRow) {
+        currentEditKeyRef.value = record.key;
+        record.onEdit?.(true);
+      }
+
+      function handleCancel(record: EditRecordRow) {
+        currentEditKeyRef.value = '';
+        record.onEdit?.(false, false);
+      }
+
+      async function handleSave(record: EditRecordRow) {
+        // 校验
+        msg.loading({ content: '正在保存...', duration: 0, key: 'saving' });
+        const valid = await record.onValid?.();
+        if (valid) {
+          try {
+            const data = cloneDeep(record.editValueRefs);
+            console.log(data);
+            //TODO 此处将数据提交给服务器保存
+            // ...
+            // 保存之后提交编辑状态
+            const pass = await record.onEdit?.(false, true);
+            if (pass) {
+              currentEditKeyRef.value = '';
+            }
+            msg.success({ content: '数据已保存', key: 'saving' });
+          } catch (error) {
+            msg.error({ content: '保存失败', key: 'saving' });
+          }
+        } else {
+          msg.error({ content: '请填写正确的数据', key: 'saving' });
+        }
+      }
+
+      function createActions(record: EditRecordRow, column: BasicColumn): ActionItem[] {
+        if (!record.editable) {
+          return [
+            {
+              label: '编辑',
+              disabled: currentEditKeyRef.value ? currentEditKeyRef.value !== record.key : false,
+              onClick: handleEdit.bind(null, record),
+            },
+          ];
+        }
+        return [
+          {
+            label: '保存',
+            onClick: handleSave.bind(null, record, column),
+          },
+          {
+            label: '取消',
+            popConfirm: {
+              title: '是否取消编辑',
+              confirm: handleCancel.bind(null, record, column),
+            },
+          },
+        ];
+      }
+
+      function onEditChange({ column, value, record }) {
+        // 本例
+        if (column.dataIndex === 'id') {
+          record.editValueRefs.name4.value = `${value}`;
+        }
+        console.log(column, value, record);
+      }
+
+      return {
+        registerTable,
+        handleEdit,
+        createActions,
+        onEditChange,
+      };
+    },
+  });
+</script>

+ 119 - 0
src/views/demo/document/table/ExpandTableDemo.vue

@@ -0,0 +1,119 @@
+<template>
+  <PageWrapper
+    title="可展开表格"
+    content="不可与scroll共用。TableAction组件可配置stopButtonPropagation来阻止操作按钮的点击事件冒泡,以便配合Table组件的expandRowByClick"
+  >
+    <BasicTable @register="registerTable">
+      <template #expandedRowRender="{ record }">
+        <span>No: {{ record.no }} </span>
+      </template>
+      <template #action="{ record }">
+        <TableAction
+          stopButtonPropagation
+          :actions="[
+            {
+              label: '删除',
+              icon: 'ic:outline-delete-outline',
+              onClick: handleDelete.bind(null, record),
+            },
+          ]"
+          :dropDownActions="[
+            {
+              label: '启用',
+              popConfirm: {
+                title: '是否启用?',
+                confirm: handleOpen.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, TableAction, BasicColumn } from '/@/components/Table';
+  import { PageWrapper } from '/@/components/Page';
+  import { demoListApi } from '/@/api/demo/table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  const columns: BasicColumn[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      fixed: 'left',
+      width: 200,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 150,
+      filters: [
+        { text: 'Male', value: 'male' },
+        { text: 'Female', value: 'female' },
+      ],
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      width: 300,
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 150,
+      sorter: true,
+      defaultHidden: true,
+    },
+    {
+      title: '开始时间',
+      width: 150,
+      sorter: true,
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      width: 150,
+      sorter: true,
+      dataIndex: 'endTime',
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableAction, PageWrapper },
+    setup() {
+      const { tableContext } = useListPage({
+        designScope: 'basic-table-demo',
+        tableProps: {
+          api: demoListApi,
+          title: '可展开表格演示',
+          titleHelpMessage: ['已启用expandRowByClick', '已启用stopButtonPropagation'],
+          columns: columns,
+          rowKey: 'id',
+          canResize: false,
+          expandRowByClick: true,
+          actionColumn: {
+            width: 160,
+            title: 'Action',
+            dataIndex: 'action',
+          },
+          useSearchForm: false,
+        },
+      });
+      //注册table数据
+      const [registerTable] = tableContext;
+
+      function handleDelete(record: Recordable) {
+        console.log('点击了删除', record);
+      }
+      function handleOpen(record: Recordable) {
+        console.log('点击了启用', record);
+      }
+
+      return {
+        registerTable,
+        handleDelete,
+        handleOpen,
+      };
+    },
+  });
+</script>

+ 131 - 0
src/views/demo/document/table/ExportTableDemo.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="p-4">
+    <!--引用表格-->
+    <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <template #tableTitle>
+        <a-button type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
+        <j-upload-button type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
+      </template>
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="basic-table-demo" setup>
+  import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  import { defHttp } from '/@/utils/http/axios';
+  //定义表格列
+  const columns: BasicColumn[] = [
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 170,
+      align: 'left',
+      resizable: true,
+      sorter: {
+        multiple: 1,
+      },
+    },
+    {
+      title: '关键词',
+      dataIndex: 'keyWord',
+      width: 130,
+      resizable: true,
+    },
+    {
+      title: '打卡时间',
+      dataIndex: 'punchTime',
+      width: 140,
+      resizable: true,
+    },
+    {
+      title: '工资',
+      dataIndex: 'salaryMoney',
+      width: 140,
+      resizable: true,
+      sorter: {
+        multiple: 2,
+      },
+    },
+    {
+      title: '奖金',
+      dataIndex: 'bonusMoney',
+      width: 140,
+      resizable: true,
+    },
+    {
+      title: '性别',
+      dataIndex: 'sex',
+      sorter: {
+        multiple: 3,
+      },
+      filters: [
+        { text: '男', value: '1' },
+        { text: '女', value: '2' },
+      ],
+      customRender: ({ record }) => {
+        return record.sex ? (record.sex == '1' ? '男' : '女') : '';
+      },
+      width: 120,
+      resizable: true,
+    },
+    {
+      title: '生日',
+      dataIndex: 'birthday',
+      width: 120,
+      resizable: true,
+    },
+    {
+      title: '邮箱',
+      dataIndex: 'email',
+      width: 120,
+      resizable: true,
+    },
+  ];
+
+  //ajax请求api接口
+  const demoListApi = (params) => {
+    return defHttp.get({ url: '/test/jeecgDemo/list', params });
+  };
+  // 列表页面公共参数、方法
+  const { tableContext, onExportXls, onImportXls } = useListPage({
+    designScope: 'basic-table-demo-filter',
+    tableProps: {
+      title: '表单搜索',
+      api: demoListApi,
+      columns: columns,
+      showActionColumn: false,
+      useSearchForm: false,
+    },
+    exportConfig: {
+      name: '示例列表',
+      url: '/test/jeecgDemo/exportXls',
+    },
+    importConfig: {
+      url: '/test/jeecgDemo/importExcel',
+    },
+  });
+  //注册table数据
+  const [registerTable, { reload }, { rowSelection, selectedRows, selectedRowKeys }] = tableContext;
+  /**
+   * 操作栏
+   */
+  function getTableAction(record): ActionItem[] {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+    ];
+  }
+
+  function handleEdit(record) {
+    console.log(record);
+  }
+</script>
+
+<style scoped></style>

+ 98 - 0
src/views/demo/document/table/FixedHeaderColumn.vue

@@ -0,0 +1,98 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable">
+      <template #action="{ record }">
+        <TableAction
+          :actions="[
+            {
+              label: '删除',
+              icon: 'ic:outline-delete-outline',
+              onClick: handleDelete.bind(null, record),
+            },
+          ]"
+          :dropDownActions="[
+            {
+              label: '启用',
+              popConfirm: {
+                title: '是否启用?',
+                confirm: handleOpen.bind(null, record),
+              },
+            },
+          ]"
+        />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table';
+
+  import { demoListApi } from '/@/api/demo/table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  const columns: BasicColumn[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      fixed: 'left',
+      width: 280,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 260,
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 300,
+    },
+    {
+      title: '开始时间',
+      width: 200,
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      dataIndex: 'endTime',
+      width: 200,
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable, TableAction },
+    setup() {
+      const { tableContext } = useListPage({
+        tableProps: {
+          title: '固定头和列示例',
+          api: demoListApi,
+          columns: columns,
+          canResize: false,
+          scroll: { y: 200 },
+          actionColumn: {
+            width: 160,
+            title: 'Action',
+            dataIndex: 'action',
+          },
+          useSearchForm: false,
+        },
+      });
+
+      const [registerTable] = tableContext;
+      function handleDelete(record: Recordable) {
+        console.log('点击了删除', record);
+      }
+      function handleOpen(record: Recordable) {
+        console.log('点击了启用', record);
+      }
+      return {
+        registerTable,
+        handleDelete,
+        handleOpen,
+      };
+    },
+  });
+</script>

+ 131 - 0
src/views/demo/document/table/InnerTableDemo.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" class="components-table-demo-nested">
+      <template #bodyCell="{ column }">
+        <template v-if="column.key === 'operation'">
+          <a>Publish</a>
+        </template>
+      </template>
+      <template #expandedRowRender>
+        <a-table :columns="innerColumns" :data-source="innerData" :pagination="false">
+          <template #bodyCell="{ column }">
+            <template v-if="column.dataIndex === 'state'">
+              <span>
+                <a-badge status="success" />
+                Finished
+              </span>
+            </template>
+            <template v-if="column.dataIndex === 'operation'">
+              <span class="table-operation">
+                <a>Pause</a>
+                <a>Stop</a>
+                <a-dropdown>
+                  <template #overlay>
+                    <a-menu>
+                      <a-menu-item>Action 1</a-menu-item>
+                      <a-menu-item>Action 2</a-menu-item>
+                    </a-menu>
+                  </template>
+                  <a> More </a>
+                </a-dropdown>
+              </span>
+            </template>
+          </template>
+        </a-table>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicTable } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+
+  const columns = [
+    { title: 'Name', dataIndex: 'name', key: 'name' },
+    { title: 'Platform', dataIndex: 'platform', key: 'platform' },
+    { title: 'Version', dataIndex: 'version', key: 'version' },
+    { title: 'Upgraded', dataIndex: 'upgradeNum', key: 'upgradeNum' },
+    { title: 'Creator', dataIndex: 'creator', key: 'creator' },
+    { title: 'Date', dataIndex: 'createdAt', key: 'createdAt' },
+    { title: 'Action', key: 'operation' },
+  ];
+
+  interface DataItem {
+    key: number;
+    name: string;
+    platform: string;
+    version: string;
+    upgradeNum: number;
+    creator: string;
+    createdAt: string;
+  }
+
+  const data: DataItem[] = [];
+  for (let i = 0; i < 3; ++i) {
+    data.push({
+      key: i,
+      name: 'Screem',
+      platform: 'iOS',
+      version: '10.3.4.5654',
+      upgradeNum: 500,
+      creator: 'Jack',
+      createdAt: '2014-12-24 23:12:00',
+    });
+  }
+
+  const innerColumns = [
+    { title: 'Date', dataIndex: 'date', key: 'date' },
+    { title: 'Name', dataIndex: 'name', key: 'name' },
+    { title: 'Status', dataIndex: 'state', key: 'state' },
+    { title: 'Upgrade Status', dataIndex: 'upgradeNum', key: 'upgradeNum' },
+    {
+      title: 'Action',
+      dataIndex: 'operation',
+      key: 'operation',
+    },
+  ];
+
+  interface innerDataItem {
+    key: number;
+    date: string;
+    name: string;
+    upgradeNum: string;
+  }
+
+  const innerData: innerDataItem[] = [];
+  for (let i = 0; i < 3; ++i) {
+    innerData.push({
+      key: i,
+      date: '2014-12-24 23:12:00',
+      name: 'This is production name',
+      upgradeNum: 'Upgraded: 56',
+    });
+  }
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      // 列表页面公共参数、方法
+      const { tableContext } = useListPage({
+        tableProps: {
+          title: '内嵌表格',
+          dataSource: data,
+          columns: columns,
+          showActionColumn: false,
+          rowKey: 'key',
+          useSearchForm: false,
+        },
+      });
+      //注册table数据
+      const [registerTable] = tableContext;
+      return {
+        data,
+        columns,
+        innerColumns,
+        innerData,
+        registerTable,
+      };
+    },
+  });
+</script>

+ 70 - 0
src/views/demo/document/table/MergeHeaderDemo.vue

@@ -0,0 +1,70 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="registerTable" />
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicColumn, BasicTable, useTable } from '/@/components/Table';
+
+  import { demoListApi } from '/@/api/demo/table';
+  //计算合并表头
+
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const [registerTable] = useTable({
+        title: '分组表头示例',
+        api: demoListApi,
+        columns: getMergeHeaderColumns(),
+        bordered: true,
+        useSearchForm: false,
+      });
+
+      function getMergeHeaderColumns(): BasicColumn[] {
+        return [
+          {
+            title: 'ID',
+            dataIndex: 'id',
+            width: 300,
+          },
+          {
+            title: '姓名',
+            dataIndex: 'name',
+            width: 300,
+          },
+          {
+            title: '地址',
+            width: 120,
+            children: [
+              {
+                title: '地址',
+                dataIndex: 'address',
+                key: 'address',
+                width: 200,
+              },
+              {
+                title: '编号',
+                dataIndex: 'no',
+                key: 'no',
+              },
+            ],
+          },
+          {
+            title: '开始时间',
+            dataIndex: 'beginTime',
+            width: 200,
+          },
+          {
+            title: '结束时间',
+            dataIndex: 'endTime',
+            width: 200,
+          },
+        ];
+      }
+      return {
+        registerTable,
+      };
+    },
+  });
+</script>

+ 144 - 0
src/views/demo/document/table/MergeTableDemo.vue

@@ -0,0 +1,144 @@
+<template>
+  <div class="p-4">
+    <!--引用表格-->
+    <BasicTable @register="registerTable">
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="basic-table-demo" setup>
+  import { ActionItem, BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  //定义表格列
+  const columns: BasicColumn[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      customCell: (record, index, column) => ({
+        colSpan: index < 4 ? 1 : 5,
+      }),
+      customRender: ({ text, record, index, column }) => {
+        return index < 4 ? text : `${record.name}/${record.age}/${record.address}/${record.phone}`;
+      },
+    },
+    {
+      title: '年龄',
+      dataIndex: 'age',
+      customCell: (record, index, column) => {
+        if (index == 4) {
+          return { colSpan: 0 };
+        }
+      },
+    },
+    {
+      title: '家庭住址',
+      dataIndex: 'address',
+      customCell: (record, index, column) => {
+        if (index == 4) {
+          return { colSpan: 0 };
+        }
+      },
+    },
+    {
+      title: '联系电话',
+      colSpan: 2,
+      dataIndex: 'tel',
+      customCell: (record, index, column) => {
+        if (index === 2) {
+          return { rowSpan: 2 };
+        }
+        if (index === 3) {
+          return { rowSpan: 0 };
+        }
+        if (index === 4) {
+          return { colSpan: 0 };
+        }
+      },
+    },
+    {
+      title: 'Phone',
+      colSpan: 0,
+      dataIndex: 'phone',
+      customCell: (record, index, column) => {
+        if (index === 4) {
+          return { colSpan: 0 };
+        }
+      },
+    },
+  ];
+  // 列表页面公共参数、方法
+  const { tableContext } = useListPage({
+    designScope: 'basic-table-demo',
+    tableProps: {
+      title: '合并行列',
+      dataSource: [
+        {
+          key: '1',
+          name: '尹嘉乐',
+          age: 32,
+          tel: '0319-5972018',
+          phone: 17600080009,
+          address: '北京市昌平区',
+        },
+        {
+          key: '2',
+          name: '龙佳钰',
+          tel: '0319-5972018',
+          phone: 17600060007,
+          age: 42,
+          address: '北京市海淀区',
+        },
+        {
+          key: '3',
+          name: '贺泽惠',
+          age: 32,
+          tel: '0319-5972018',
+          phone: 17600040005,
+          address: '北京市门头沟区',
+        },
+        {
+          key: '4',
+          name: '沈勇',
+          age: 18,
+          tel: '0319-5972018',
+          phone: 17600010003,
+          address: '北京市朝阳区',
+        },
+        {
+          key: '5',
+          name: '白佳毅',
+          age: 18,
+          tel: '0319-5972018',
+          phone: 17600010002,
+          address: '北京市丰台区',
+        },
+      ],
+      columns: columns,
+      showActionColumn: false,
+      useSearchForm: false,
+    },
+  });
+  //注册table数据
+  const [registerTable] = tableContext;
+  /**
+   * 操作栏
+   */
+  function getTableAction(record): ActionItem[] {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+    ];
+  }
+
+  function handleEdit(record) {
+    console.log(record);
+  }
+</script>
+
+<style scoped></style>

+ 80 - 0
src/views/demo/document/table/SelectTableDemo.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="p-4">
+    <!--引用表格-->
+    <BasicTable @register="registerTable" :rowSelection="rowSelection">
+      <!--操作栏-->
+      <template #action="{ record }">
+        <TableAction :actions="getTableAction(record)" />
+      </template>
+    </BasicTable>
+  </div>
+</template>
+
+<script lang="ts" name="basic-table-demo" setup>
+  import { BasicColumn, BasicTable, TableAction } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  //定义表格列
+  const columns: BasicColumn[] = [
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      key: 'name',
+      resizable: true,
+    },
+    {
+      title: '年龄',
+      dataIndex: 'age',
+      key: 'age',
+    },
+    {
+      title: '住址',
+      dataIndex: 'address',
+      key: 'address',
+    },
+  ];
+  // 列表页面公共参数、方法
+  const { tableContext } = useListPage({
+    designScope: 'basic-table-demo',
+    tableProps: {
+      title: '可选择表格',
+      dataSource: [
+        {
+          id: '1',
+          name: '胡歌',
+          age: 32,
+          address: '朝阳区林萃路1号',
+        },
+        {
+          id: '2',
+          name: '刘诗诗',
+          age: 32,
+          address: '昌平区白沙路1号',
+        },
+      ],
+      columns: columns,
+      rowSelection: { type: 'checkbox' }, //默认是 checkbox 多选,可以设置成 radio 单选
+      useSearchForm: false,
+    },
+  });
+  //注册table数据
+  const [registerTable, { reload }, { rowSelection, selectedRows, selectedRowKeys }] = tableContext;
+  /**
+   * 操作栏
+   */
+  function getTableAction(record): ActionItem[] {
+    return [
+      {
+        label: '编辑',
+        onClick: handleEdit.bind(null, record),
+      },
+    ];
+  }
+
+  function handleEdit(record) {
+    console.log(record);
+    console.log(selectedRows.value);
+    console.log(selectedRowKeys.value);
+  }
+</script>
+
+<style scoped></style>

+ 124 - 0
src/views/demo/document/table/TreeTableDemo.vue

@@ -0,0 +1,124 @@
+<template>
+  <div class="p-4">
+    <BasicTable @register="register">
+      <template #toolbar>
+        <a-button type="primary" @click="expandAll">展开全部</a-button>
+        <a-button type="primary" @click="collapseAll">折叠全部</a-button>
+      </template>
+    </BasicTable>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { BasicColumn, BasicTable } from '/@/components/Table';
+  import { useListPage } from '/@/hooks/system/useListPage';
+  const columns: BasicColumn[] = [
+    {
+      title: 'ID',
+      dataIndex: 'id',
+      fixed: 'left',
+      width: 200,
+    },
+    {
+      title: '姓名',
+      dataIndex: 'name',
+      width: 150,
+      filters: [
+        { text: 'Male', value: 'male' },
+        { text: 'Female', value: 'female' },
+      ],
+    },
+    {
+      title: '地址',
+      dataIndex: 'address',
+      width: 300,
+    },
+    {
+      title: '编号',
+      dataIndex: 'no',
+      width: 150,
+      sorter: true,
+      defaultHidden: true,
+    },
+    {
+      title: '开始时间',
+      width: 150,
+      sorter: true,
+      dataIndex: 'beginTime',
+    },
+    {
+      title: '结束时间',
+      width: 150,
+      sorter: true,
+      dataIndex: 'endTime',
+    },
+  ];
+  export default defineComponent({
+    components: { BasicTable },
+    setup() {
+      const { tableContext } = useListPage({
+        tableProps: {
+          title: '树形表格',
+          isTreeTable: true,
+          rowSelection: {
+            type: 'checkbox',
+            getCheckboxProps(record: Recordable) {
+              // Demo: 第一行(id为0)的选择框禁用
+              if (record.id === '0') {
+                return { disabled: true };
+              } else {
+                return { disabled: false };
+              }
+            },
+          },
+          columns: columns,
+          dataSource: getTreeTableData(),
+          rowKey: 'id',
+          useSearchForm: false,
+        },
+      });
+      //注册table数据
+      const [register, { expandAll, collapseAll }] = tableContext;
+      function getTreeTableData() {
+        const data: any = (() => {
+          const arr: any = [];
+          for (let index = 0; index < 40; index++) {
+            arr.push({
+              id: `${index}`,
+              name: 'John Brown',
+              age: `1${index}`,
+              no: `${index + 10}`,
+              address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+              beginTime: new Date().toLocaleString(),
+              endTime: new Date().toLocaleString(),
+              children: [
+                {
+                  id: `l2-${index}`,
+                  name: 'John Brown',
+                  age: `1${index}`,
+                  no: `${index + 10}`,
+                  address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+                  beginTime: new Date().toLocaleString(),
+                  endTime: new Date().toLocaleString(),
+                },
+                {
+                  id: `l3-${index}`,
+                  name: 'John Mary',
+                  age: `1${index}`,
+                  no: `${index + 10}`,
+                  address: 'New York No. 1 Lake ParkNew York No. 1 Lake Park',
+                  beginTime: new Date().toLocaleString(),
+                  endTime: new Date().toLocaleString(),
+                },
+              ],
+            });
+          }
+          return arr;
+        })();
+
+        return data;
+      }
+      return { register, expandAll, collapseAll };
+    },
+  });
+</script>

+ 15 - 0
src/views/demo/document/table/index.ts

@@ -0,0 +1,15 @@
+export { default as AuthColumnDemo } from './AuthColumnDemo.vue';
+export { default as BasicTableBorder } from './BasicTableBorder.vue';
+export { default as BasicTableDemo } from './BasicTableDemo.vue';
+export { default as BasicTableDemoAjax } from './BasicTableDemoAjax.vue';
+export { default as CustomerCellDemo } from './CustomerCellDemo.vue';
+export { default as EditCellTableDemo } from './EditCellTableDemo.vue';
+export { default as EditRowTableDemo } from './EditRowTableDemo.vue';
+export { default as ExpandTableDemo } from './ExpandTableDemo.vue';
+export { default as ExportTableDemo } from './ExportTableDemo.vue';
+export { default as FixedHeaderColumn } from './FixedHeaderColumn.vue';
+export { default as InnerTableDemo } from './InnerTableDemo.vue';
+export { default as MergeHeaderDemo } from './MergeHeaderDemo.vue';
+export { default as MergeTableDemo } from './MergeTableDemo.vue';
+export { default as SelectTableDemo } from './SelectTableDemo.vue';
+export { default as TreeTableDemo } from './TreeTableDemo.vue';

+ 87 - 0
src/views/demo/document/table/tabIndex.vue

@@ -0,0 +1,87 @@
+<template>
+  <div class="p-4">
+    <a-card :bordered="false" style="height: 100%">
+      <a-tabs v-model:activeKey="activeKey" @change="tabChange">
+        <a-tab-pane :key="item.key" :tab="item.label" v-for="item in compList" />
+      </a-tabs>
+      <component :is="currentComponent" />
+    </a-card>
+  </div>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, computed } from 'vue';
+  import {
+    AuthColumnDemo,
+    BasicTableBorder,
+    BasicTableDemo,
+    BasicTableDemoAjax,
+    CustomerCellDemo,
+    EditCellTableDemo,
+    EditRowTableDemo,
+    ExpandTableDemo,
+    ExportTableDemo,
+    FixedHeaderColumn,
+    InnerTableDemo,
+    MergeHeaderDemo,
+    MergeTableDemo,
+    SelectTableDemo,
+    TreeTableDemo,
+  } from './index';
+  export default defineComponent({
+    name: 'document-table-demo',
+    components: {
+      AuthColumnDemo,
+      BasicTableBorder,
+      BasicTableDemo,
+      BasicTableDemoAjax,
+      CustomerCellDemo,
+      EditCellTableDemo,
+      EditRowTableDemo,
+      ExpandTableDemo,
+      ExportTableDemo,
+      FixedHeaderColumn,
+      InnerTableDemo,
+      MergeHeaderDemo,
+      MergeTableDemo,
+      SelectTableDemo,
+      TreeTableDemo,
+    },
+    setup() {
+      //当前选中key
+      const activeKey = ref('BasicTableDemo');
+      //组件集合
+      const compList = ref([
+        { key: 'BasicTableDemo', label: '基础静态表格' },
+        { key: 'BasicTableDemoAjax', label: '常规AJAX表格' },
+        { key: 'BasicTableBorder', label: '边框表格' },
+        { key: 'CustomerCellDemo', label: '自定义列内容' },
+        { key: 'EditCellTableDemo', label: '可编辑单元格' },
+        { key: 'EditRowTableDemo', label: '可编辑行' },
+        { key: 'ExpandTableDemo', label: '可展开表格' },
+        { key: 'ExportTableDemo', label: '导入导出' },
+        { key: 'FixedHeaderColumn', label: '固定头和列示例' },
+        { key: 'InnerTableDemo', label: '内嵌表格' },
+        { key: 'MergeHeaderDemo', label: '分组表头示例' },
+        { key: 'MergeTableDemo', label: '合并行列' },
+        { key: 'SelectTableDemo', label: '可选择表格' },
+        { key: 'TreeTableDemo', label: '树形表格' },
+        { key: 'AuthColumnDemo', label: '权限列设置' },
+      ]);
+      //当前选中组件
+      const currentComponent = computed(() => {
+        return activeKey.value;
+      });
+
+      //使用component动态切换tab
+      function tabChange(key) {
+        activeKey.value = key;
+      }
+      return {
+        activeKey,
+        currentComponent,
+        tabChange,
+        compList,
+      };
+    },
+  });
+</script>

+ 91 - 0
src/views/demo/editor/json/index.vue

@@ -0,0 +1,91 @@
+<template>
+  <PageWrapper title="代码编辑器组件示例" contentFullHeight fixedHeight contentBackground>
+    <template #extra>
+      <a-space size="middle">
+        <a-button @click="showData" type="primary">获取数据</a-button>
+        <RadioGroup button-style="solid" v-model:value="modeValue" @change="handleModeChange">
+          <RadioButton value="application/json"> json数据 </RadioButton>
+          <RadioButton value="htmlmixed"> html代码 </RadioButton>
+          <RadioButton value="javascript"> javascript代码 </RadioButton>
+        </RadioGroup>
+      </a-space>
+    </template>
+    <CodeEditor v-model:value="value" :mode="modeValue" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { CodeEditor } from '/@/components/CodeEditor';
+  import { PageWrapper } from '/@/components/Page';
+  import { Radio, Space, Modal } from 'ant-design-vue';
+
+  const jsonData =
+    '{"name":"BeJson","url":"http://www.xxx.com","page":88,"isNonProfit":true,"address":{"street":"科技园路.","city":"江苏苏州","country":"中国"},"links":[{"name":"Google","url":"http://www.xxx.com"},{"name":"Baidu","url":"http://www.xxx.com"},{"name":"SoSo","url":"http://www.xxx.com"}]}';
+
+  const jsData = `
+      (() => {
+        var htmlRoot = document.getElementById('htmlRoot');
+        var theme = window.localStorage.getItem('__APP__DARK__MODE__');
+        if (htmlRoot && theme) {
+          htmlRoot.setAttribute('data-theme', theme);
+          theme = htmlRoot = null;
+        }
+      })();
+  `;
+
+  const htmlData = `
+     <!DOCTYPE html>
+<html lang="en" id="htmlRoot">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta
+      name="viewport"
+      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
+    />
+    <title><%= title %></title>
+    <link rel="icon" href="/favicon.ico" />
+  </head>
+  <body>
+    <div id="app">
+    </div>
+  </body>
+</html>
+  `;
+  export default defineComponent({
+    components: {
+      CodeEditor,
+      PageWrapper,
+      RadioButton: Radio.Button,
+      RadioGroup: Radio.Group,
+      ASpace: Space,
+    },
+    setup() {
+      const modeValue = ref('application/json');
+      const value = ref(jsonData);
+
+      function handleModeChange(e: ChangeEvent) {
+        const mode = e.target.value;
+        if (mode === 'application/json') {
+          value.value = jsonData;
+          return;
+        }
+        if (mode === 'htmlmixed') {
+          value.value = htmlData;
+          return;
+        }
+        if (mode === 'javascript') {
+          value.value = jsData;
+          return;
+        }
+      }
+
+      function showData() {
+        Modal.info({ title: '编辑器当前值', content: value.value });
+      }
+
+      return { value, modeValue, handleModeChange, showData };
+    },
+  });
+</script>

+ 53 - 0
src/views/demo/editor/markdown/Editor.vue

@@ -0,0 +1,53 @@
+<template>
+  <PageWrapper title="MarkDown组件嵌入Form示例">
+    <CollapseContainer title="MarkDown表单">
+      <BasicForm :labelWidth="100" :schemas="schemas" :actionColOptions="{ span: 24 }" @submit="handleSubmit" />
+    </CollapseContainer>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, h } from 'vue';
+  import { BasicForm, FormSchema } from '/@/components/Form/index';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { MarkDown } from '/@/components/Markdown';
+  import { PageWrapper } from '/@/components/Page';
+
+  const schemas: FormSchema[] = [
+    {
+      field: 'title',
+      component: 'Input',
+      label: 'title',
+      defaultValue: '标题',
+      rules: [{ required: true }],
+    },
+    {
+      field: 'markdown',
+      component: 'Input',
+      label: 'markdown',
+      defaultValue: 'defaultValue',
+      rules: [{ required: true, trigger: 'blur' }],
+      render: ({ model, field }) => {
+        return h(MarkDown, {
+          value: model[field],
+          onChange: (value: string) => {
+            model[field] = value;
+          },
+        });
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicForm, CollapseContainer, PageWrapper },
+    setup() {
+      const { createMessage } = useMessage();
+
+      return {
+        schemas,
+        handleSubmit: (values: any) => {
+          createMessage.success('click search,values:' + JSON.stringify(values));
+        },
+      };
+    },
+  });
+</script>

+ 55 - 0
src/views/demo/editor/markdown/index.vue

@@ -0,0 +1,55 @@
+<template>
+  <PageWrapper title="MarkDown组件示例">
+    <div>
+      <a-button @click="toggleTheme" class="mb-2" type="primary"> 黑暗主题 </a-button>
+      <a-button @click="clearValue" class="mb-2" type="default"> 清空内容 </a-button>
+      <MarkDown v-model:value="value" @change="handleChange" ref="markDownRef" placeholder="这是占位文本" />
+    </div>
+    <div class="mt-2">
+      <a-card title="Markdown Viewer 组件演示">
+        <MarkdownViewer :value="value" />
+      </a-card>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref, unref } from 'vue';
+  import { MarkDown, MarkDownActionType, MarkdownViewer } from '/@/components/Markdown';
+  import { PageWrapper } from '/@/components/Page';
+  import { Card } from 'ant-design-vue';
+
+  export default defineComponent({
+    components: { MarkDown, PageWrapper, MarkdownViewer, ACard: Card },
+    setup() {
+      const markDownRef = ref<Nullable<MarkDownActionType>>(null);
+      const valueRef = ref(`
+# title
+
+# content
+`);
+
+      function toggleTheme() {
+        const markDown = unref(markDownRef);
+        if (!markDown) return;
+        const vditor = markDown.getVditor();
+        vditor.setTheme('dark');
+      }
+
+      function handleChange(v: string) {
+        valueRef.value = v;
+      }
+
+      function clearValue() {
+        valueRef.value = '';
+      }
+
+      return {
+        value: valueRef,
+        toggleTheme,
+        markDownRef,
+        handleChange,
+        clearValue,
+      };
+    },
+  });
+</script>

+ 53 - 0
src/views/demo/editor/tinymce/Editor.vue

@@ -0,0 +1,53 @@
+<template>
+  <PageWrapper title="富文本嵌入表单示例">
+    <CollapseContainer title="富文本表单">
+      <BasicForm :labelWidth="100" :schemas="schemas" :actionColOptions="{ span: 24 }" @submit="handleSubmit" />
+    </CollapseContainer>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, h } from 'vue';
+  import { BasicForm, FormSchema } from '/@/components/Form/index';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { Tinymce } from '/@/components/Tinymce/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  const schemas: FormSchema[] = [
+    {
+      field: 'title',
+      component: 'Input',
+      label: 'title',
+      defaultValue: 'defaultValue',
+      rules: [{ required: true }],
+    },
+    {
+      field: 'tinymce',
+      component: 'Input',
+      label: 'tinymce',
+      defaultValue: 'defaultValue',
+      rules: [{ required: true }],
+      render: ({ model, field }) => {
+        return h(Tinymce, {
+          value: model[field],
+          onChange: (value: string) => {
+            model[field] = value;
+          },
+        });
+      },
+    },
+  ];
+  export default defineComponent({
+    components: { BasicForm, CollapseContainer, PageWrapper },
+    setup() {
+      const { createMessage } = useMessage();
+
+      return {
+        schemas,
+        handleSubmit: (values: any) => {
+          createMessage.success('click search,values:' + JSON.stringify(values));
+        },
+      };
+    },
+  });
+</script>

+ 21 - 0
src/views/demo/editor/tinymce/index.vue

@@ -0,0 +1,21 @@
+<template>
+  <PageWrapper title="富文本组件示例">
+    <Tinymce v-model="value" @change="handleChange" width="100%" />
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { Tinymce } from '/@/components/Tinymce/index';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { Tinymce, PageWrapper },
+    setup() {
+      const value = ref('hello world!');
+      function handleChange(value: string) {
+        console.log(value);
+      }
+      return { handleChange, value };
+    },
+  });
+</script>

+ 13 - 0
src/views/demo/feat/breadcrumb/ChildrenList.vue

@@ -0,0 +1,13 @@
+<template>
+  <PageWrapper title="层级面包屑示例" content="子级页面面包屑会添加到当前层级后面">
+    <router-link to="/feat/breadcrumb/children/childrenDetail"> 进入子级详情页 </router-link>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { PageWrapper },
+  });
+</script>

+ 10 - 0
src/views/demo/feat/breadcrumb/ChildrenListDetail.vue

@@ -0,0 +1,10 @@
+<template>
+  <PageWrapper title="子级详情页">
+    <div> 子级详情页内容在此 </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+  export default defineComponent({ components: { PageWrapper } });
+</script>

+ 13 - 0
src/views/demo/feat/breadcrumb/FlatList.vue

@@ -0,0 +1,13 @@
+<template>
+  <PageWrapper title="平级面包屑示例" content="子级页面面包屑会覆盖当前层级">
+    <router-link to="/feat/breadcrumb/flatDetail"> 进入平级详情页 </router-link>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { PageWrapper },
+  });
+</script>

+ 8 - 0
src/views/demo/feat/breadcrumb/FlatListDetail.vue

@@ -0,0 +1,8 @@
+<template>
+  <div> 平级详情页 </div>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+
+  export default defineComponent({});
+</script>

+ 43 - 0
src/views/demo/feat/click-out-side/index.vue

@@ -0,0 +1,43 @@
+<template>
+  <PageWrapper title="点内外部触发事件">
+    <ClickOutSide @clickOutside="handleClickOutside" class="flex justify-center">
+      <div @click="innerClick" class="demo-box">
+        {{ text }}
+      </div>
+    </ClickOutSide>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { ClickOutSide } from '/@/components/ClickOutSide';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { ClickOutSide, PageWrapper },
+    setup() {
+      const text = ref('Click');
+      function handleClickOutside() {
+        text.value = 'Click Out Side';
+      }
+
+      function innerClick() {
+        text.value = 'Click Inner';
+      }
+      return { innerClick, handleClickOutside, text };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .demo-box {
+    display: flex;
+    width: 100%;
+    height: 300px;
+    font-size: 24px;
+    color: #fff;
+    background-color: #408ede;
+    border-radius: 10px;
+    justify-content: center;
+    align-items: center;
+  }
+</style>

+ 85 - 0
src/views/demo/feat/context-menu/index.vue

@@ -0,0 +1,85 @@
+<template>
+  <PageWrapper title="右键菜单示例">
+    <CollapseContainer title="Simple">
+      <a-button type="primary" @contextmenu="handleContext"> Right Click on me </a-button>
+    </CollapseContainer>
+
+    <CollapseContainer title="Multiple" class="mt-4">
+      <a-button type="primary" @contextmenu="handleMultipleContext"> Right Click on me </a-button>
+    </CollapseContainer>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { useContextMenu } from '/@/hooks/web/useContextMenu';
+  import { CollapseContainer } from '/@/components/Container';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { CollapseContainer, PageWrapper },
+    setup() {
+      const [createContextMenu] = useContextMenu();
+      const { createMessage } = useMessage();
+      function handleContext(e: MouseEvent) {
+        createContextMenu({
+          event: e,
+          items: [
+            {
+              label: 'New',
+              icon: 'bi:plus',
+              handler: () => {
+                createMessage.success('click new');
+              },
+            },
+            {
+              label: 'Open',
+              icon: 'bx:bxs-folder-open',
+              handler: () => {
+                createMessage.success('click open');
+              },
+            },
+          ],
+        });
+      }
+
+      function handleMultipleContext(e: MouseEvent) {
+        createContextMenu({
+          event: e,
+          items: [
+            {
+              label: 'New',
+              icon: 'bi:plus',
+
+              children: [
+                {
+                  label: 'New1-1',
+                  icon: 'bi:plus',
+                  divider: true,
+                  children: [
+                    {
+                      label: 'New1-1-1',
+                      handler: () => {
+                        createMessage.success('click new');
+                      },
+                    },
+                    {
+                      label: 'New1-2-1',
+                      disabled: true,
+                    },
+                  ],
+                },
+                {
+                  label: 'New1-2',
+                  icon: 'bi:plus',
+                },
+              ],
+            },
+          ],
+        });
+      }
+
+      return { handleContext, handleMultipleContext };
+    },
+  });
+</script>

+ 40 - 0
src/views/demo/feat/copy/index.vue

@@ -0,0 +1,40 @@
+<template>
+  <PageWrapper title="文本复制示例">
+    <CollapseContainer class="w-full h-32 bg-white rounded-md" title="Copy Example">
+      <div class="flex justify-center">
+        <a-input placeholder="请输入" v-model:value="value" />
+        <a-button type="primary" @click="handleCopy"> Copy </a-button>
+      </div>
+    </CollapseContainer>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, unref, ref } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    name: 'Copy',
+    components: { CollapseContainer, PageWrapper },
+    setup() {
+      const valueRef = ref('');
+      const { createMessage } = useMessage();
+      const { clipboardRef, copiedRef } = useCopyToClipboard();
+
+      function handleCopy() {
+        const value = unref(valueRef);
+        if (!value) {
+          createMessage.warning('请输入要拷贝的内容!');
+          return;
+        }
+        clipboardRef.value = value;
+        if (unref(copiedRef)) {
+          createMessage.warning('copy success!');
+        }
+      }
+      return { handleCopy, value: valueRef };
+    },
+  });
+</script>

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
src/views/demo/feat/download/imgBase64.ts


+ 59 - 0
src/views/demo/feat/download/index.vue

@@ -0,0 +1,59 @@
+<template>
+  <PageWrapper title="文件下载示例">
+    <a-alert message="根据后台接口文件流下载" />
+    <a-button type="primary" class="my-4" @click="handleDownByData"> 文件流下载 </a-button>
+
+    <a-alert message="根据文件地址下载文件" />
+    <a-button type="primary" class="my-4" @click="handleDownloadByUrl"> 文件地址下载 </a-button>
+
+    <a-alert message="base64流下载" />
+    <a-button type="primary" class="my-4" @click="handleDownloadByBase64"> base64流下载 </a-button>
+
+    <a-alert message="图片Url下载,如果有跨域问题,需要处理图片跨域" />
+    <a-button type="primary" class="my-4" @click="handleDownloadByOnlineUrl"> 图片Url下载 </a-button>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { downloadByUrl, downloadByData, downloadByBase64, downloadByOnlineUrl } from '/@/utils/file/download';
+  import imgBase64 from './imgBase64';
+  import { PageWrapper } from '/@/components/Page';
+  import { Alert } from 'ant-design-vue';
+
+  export default defineComponent({
+    components: { PageWrapper, [Alert.name]: Alert },
+    setup() {
+      function handleDownByData() {
+        downloadByData('text content', 'testName.txt');
+      }
+      function handleDownloadByUrl() {
+        downloadByUrl({
+          url: 'https://codeload.github.com/anncwb/vue-Jeecg-admin-doc/zip/master',
+          target: '_self',
+        });
+
+        downloadByUrl({
+          url: 'https://vebn.oss-cn-beijing.aliyuncs.com/Jeecg/logo.png',
+          target: '_self',
+        });
+      }
+
+      function handleDownloadByBase64() {
+        downloadByBase64(imgBase64, 'logo.png');
+      }
+
+      function handleDownloadByOnlineUrl() {
+        downloadByOnlineUrl(
+          'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5944817f47b8408e9f1442ece49d68ca~tplv-k3u1fbpfcp-watermark.image',
+          'logo.png'
+        );
+      }
+      return {
+        handleDownloadByUrl,
+        handleDownByData,
+        handleDownloadByBase64,
+        handleDownloadByOnlineUrl,
+      };
+    },
+  });
+</script>

+ 45 - 0
src/views/demo/feat/full-screen/index.vue

@@ -0,0 +1,45 @@
+<template>
+  <PageWrapper title="全屏示例">
+    <CollapseContainer class="w-full h-32 bg-white rounded-md" title="Window Full Screen">
+      <a-button type="primary" @click="enter" class="mr-2"> Enter Window Full Screen </a-button>
+      <a-button color="success" @click="toggle" class="mr-2"> Toggle Window Full Screen </a-button>
+
+      <a-button color="error" @click="exit" class="mr-2"> Exit Window Full Screen </a-button>
+
+      Current State: {{ isFullscreen }}
+    </CollapseContainer>
+
+    <CollapseContainer class="w-full mt-5 bg-white rounded-md" title="Dom Full Screen">
+      <a-button type="primary" @click="toggleDom" class="mr-2"> Enter Dom Full Screen </a-button>
+    </CollapseContainer>
+
+    <div ref="domRef" class="flex items-center justify-center w-1/2 h-64 mx-auto mt-10 bg-white rounded-md">
+      <a-button type="primary" @click="toggleDom" class="mr-2"> Exit Dom Full Screen </a-button>
+    </div>
+  </PageWrapper>
+</template>
+<script lang="ts">
+  import { defineComponent, ref } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import { useFullscreen } from '@vueuse/core';
+
+  import { PageWrapper } from '/@/components/Page';
+
+  export default defineComponent({
+    components: { CollapseContainer, PageWrapper },
+    setup() {
+      const domRef = ref<Nullable<HTMLElement>>(null);
+      const { enter, toggle, exit, isFullscreen } = useFullscreen();
+
+      const { toggle: toggleDom } = useFullscreen(domRef);
+      return {
+        enter,
+        toggleDom,
+        toggle,
+        isFullscreen,
+        exit,
+        domRef,
+      };
+    },
+  });
+</script>

Неке датотеке нису приказане због велике количине промена