Bladeren bron

fix: BsUi-组件修改

hanxiaohui 2 maanden geleden
bovenliggende
commit
25028b40ac

+ 164 - 128
src/components/BsUi/Form/Form.vue

@@ -1,17 +1,19 @@
 <template>
   <div class="form">
-    <a-form ref="formRef" :model="props.formData" v-bind="props.formExtraProps" layout="vertical" :id="props.formId"
-      :validateTrigger="['submit']">
+    <a-form ref="formRef" :model="props.formData" v-bind="props.formExtraProps" layout="vertical" :id="props.formId" :validateTrigger="['submit']">
       <a-row v-for="(key, index) in Object.keys(newFormTemp)" :key="index" :id="key" :gutter="16">
         <a-col :span="24" v-if="formGroups.length > 0">
-          <BsCatalog :title="formGroups.find((v) => v.id === key)?.name"
-            v-if="formGroups.findIndex((item) => item.type === 'dot') > -1" />
-          <div class="group-title" v-else>{{formGroups.find((v) => v.id === key)?.name}}</div>
+          <BsCatalog :title="formGroups.find((v) => v.id === key)?.name" v-if="formGroups.findIndex((item) => item.type === 'dot') > -1" />
+          <div class="group-title" v-else>{{ formGroups.find((v) => v.id === key)?.name }}</div>
         </a-col>
         <a-col :span="item.span ?? 24" v-for="(item, idx) in newFormTemp[key]">
-          <a-form-item v-if="item.visible === DISPLAY_STATE.VISIBLE" :name="item.field"
-            :required="item.required === REQUIRED_STATE.REQUIRED" :label="item.label" v-bind="item.formItemExtraProps">
-
+          <a-form-item
+            v-if="item.visible === DISPLAY_STATE.VISIBLE"
+            :name="item.field"
+            :required="item.required === REQUIRED_STATE.REQUIRED"
+            :label="item.label"
+            v-bind="item.formItemExtraProps"
+          >
             <template #label v-if="item.tooltip">
               {{ item.label }}
               <a-tooltip class="tooltip">
@@ -23,14 +25,18 @@
             </template>
             
             <template #label v-else>{{ item.label }}</template>
-            <component :is="item.component"
-                       v-model:value="props.formData[item.field]"
-                       v-model:selected-data="props.formData[item.field]"
-                       v-model:checked="props.formData[item.field]" v-bind="item.componentProps"
-                       @change="(value, extraProps) => handleFieldChange(item, value, extraProps)">
+            <component
+              :is="item.component"
+              v-model:value="props.formData[item.field]"
+              v-model:selected-data="props.formData[item.field]"
+              v-model:checked="props.formData[item.field]"
+              v-bind="item.componentProps"
+              @change="(value, extraProps) => handleFieldChange(item, value, extraProps)"
+              ref="formItemRef"
+            >
               <slot>{{ item.componentProps?.buttonText || '' }}</slot>
               <template v-for="slot in item.componentProps.slots" #[slot.slotName]="slotVal">
-                <component :is="slot.customRender(slotVal)" v-bind="slot.slotProps"></component>
+                <component :is="slot.customRender(slotVal)" v-bind="slot.slotProps" />
               </template>
             </component>
           </a-form-item>
@@ -51,7 +57,7 @@
 
         <a-col :span="24" v-if="slots.footerRender">
           <a-space style="display: flex; justify-content: flex-end; padding: 10px 0">
-            <slot name="footerRender" :props="props" />
+            <slot name="footerRender" :props="props"></slot>
           </a-space>
         </a-col>
       </a-row>
@@ -60,128 +66,158 @@
 </template>
 
 <script setup lang="jsx">
-import { computed, ref, useSlots } from 'vue';
-import RenderVNode from '/@/components/BsUi/RenderVNode/index.js';
-import { groupBy, mapValues, sortBy } from 'lodash';
-import { DISPLAY_STATE, REQUIRED_STATE } from '/@/components/BsUi/constant.js'
-import BsCatalog from '/@/components/BsUi/Catalog/index.vue';
-const formRef = ref(null);
-const confirmLoading = ref(false);
-
-const props = defineProps({
-  formFields: {
-    required: true,
-    default: [],
-  },
-  formData: {
-    required: true,
-    default: {},
-  },
-  formExtraProps: {
-    required: false,
-    default: null,
-  },
-  footerRender: {
-    required: false,
-    default: null,
-  },
-  formGroups: {
-    required: false,
-    default: [],
-  },
-  formId: {
-    required: false,
-    default: 'AchorContentId',
-  },
-  isShowGroupTitle: {
-    required: false,
-    default: true,
-  },
-});
-const emits = defineEmits(['cancel', 'ok', 'change']);
-const slots = useSlots();
-
-const handleFieldChange = (field, value, extraValue = {}) => {
-  formRef.value.validateFields([field.field])
-  field?.onChange && field?.onChange(value)
-  emits('change', value, extraValue)
-}
-
-const ValidatorSingalField =(field)=>{
-  return new Promise((resolve,reject)=>{
-    formRef.value.validateFields([field])
-    .then((res)=>{
-      resolve(res)
-    })
-    .catch((error)=>{
-      reject(error)
-    })
-  })
-}
-
-const handlerFormValidator = () => {
-  return new Promise((resolve, reject) => {
-    formRef.value
-      .validateFields()
-      .then(() => {
-        resolve();
-      })
-      .catch((error) => {
-        reject(error);
-      });
+  import { computed, ref, useSlots } from 'vue';
+  import RenderVNode from '/@/components/BsUi/RenderVNode/index.js';
+  import { groupBy, isEmpty, mapValues, sortBy } from 'lodash';
+  import { DISPLAY_STATE, REQUIRED_STATE } from '/@/components/BsUi/constant.js';
+  import BsCatalog from '/@/components/BsUi/Catalog/index.vue';
+  const formRef = ref(null);
+  const confirmLoading = ref(false);
+
+  const formItemRef = ref(null);
+
+  const props = defineProps({
+    formFields: {
+      required: true,
+      default: [],
+    },
+    formData: {
+      required: true,
+      default: {},
+    },
+    formExtraProps: {
+      required: false,
+      default: null,
+    },
+    footerRender: {
+      required: false,
+      default: null,
+    },
+    formGroups: {
+      required: false,
+      default: [],
+    },
+    formId: {
+      required: false,
+      default: 'AchorContentId',
+    },
+    isShowGroupTitle: {
+      required: false,
+      default: true,
+    },
   });
-};
-
-const cancelHandle = () => {
-  emits('cancel');
-};
-const setLoading = (loading) => {
-  confirmLoading.value = loading;
-};
-
-const confirmHandle = () => {
-  handlerFormValidator.then((res) => {
-    emits('ok', props.formData, formRef.value, setLoading);
+  const emits = defineEmits(['cancel', 'ok', 'change']);
+  const slots = useSlots();
+
+  const handleFieldChange = (field, value, extraValue = {}) => {
+    formRef.value.validateFields([field.field]);
+    field?.onChange && field?.onChange(value);
+    emits('change', value, extraValue);
+  };
+
+  const ValidatorSingalField = (field) => {
+    return new Promise((resolve, reject) => {
+      formRef.value
+        .validateFields([field])
+        .then((res) => {
+          resolve(res);
+        })
+        .catch((error) => {
+          reject(error);
+        });
+    });
+  };
+
+  const handlerFormValidator = () => {
+    const pItem = new Promise((resolve, reject) => {
+      formRef.value
+        .validateFields()
+        .then(() => {
+          resolve();
+        })
+        .catch((error) => {
+          reject(error);
+        });
+    });
+    const pSubItem = new Promise((resolve, reject) => {
+      console.log('formItemRef', formItemRef.value);
+      const subsRef = formItemRef.value.filter((v) => v.validator);
+      if (isEmpty(subsRef)) {
+        resolve();
+      } else {
+        Promise.all(subsRef.map((v) => v.validator())).then(
+          (res) => {
+            resolve(res);
+          },
+          (error) => {
+            reject(error);
+          }
+        );
+      }
+    });
+
+    return new Promise((resolve, reject) => {
+      Promise.all([pItem, pSubItem]).then(
+        (res) => {
+          resolve(res);
+        },
+        (reason) => {
+          reject(reason);
+        }
+      );
+    });
+  };
+
+  const cancelHandle = () => {
+    emits('cancel');
+  };
+  const setLoading = (loading) => {
+    confirmLoading.value = loading;
+  };
+
+  const confirmHandle = () => {
+    handlerFormValidator.then((res) => {
+      emits('ok', props.formData, formRef.value, setLoading);
+    });
+  };
+
+  const newFormTemp = computed(() => {
+    const groupByIds = groupBy(props.formFields, 'groupId');
+    return mapValues(groupByIds, (group) => sortBy(group, 'sort'));
   });
-};
 
-const newFormTemp = computed(() => {
-  const groupByIds = groupBy(props.formFields, 'groupId');
-  return mapValues(groupByIds, (group) => sortBy(group, 'sort'));
-});
-
-defineExpose({ handlerFormValidator ,ValidatorSingalField})
+  defineExpose({ handlerFormValidator, ValidatorSingalField });
 </script>
 
 <style lang="scss" scoped>
-.form {
-  width: 100%;
+  .form {
+    width: 100%;
 
-  :deep(.ant-form-item) {
-    margin: 10px 0;
-  }
+    :deep(.ant-form-item) {
+      margin: 10px 0;
+    }
 
-  .group-title {
-    width: 100%;
-    height: 40px;
-    background: #e0e8f5;
-    line-height: 40px;
-    color: #4285f4;
-    font-size: 16px;
-    padding-left: 15px;
-
-    &::before {
-      content: '';
-      width: 2px;
-      height: 100%;
-      position: absolute;
-      left: 0;
-      background: #4285f4;
+    .group-title {
+      width: 100%;
+      height: 40px;
+      background: #e0e8f5;
+      line-height: 40px;
+      color: #4285f4;
+      font-size: 16px;
+      padding-left: 15px;
+
+      &::before {
+        content: '';
+        width: 2px;
+        height: 100%;
+        position: absolute;
+        left: 0;
+        background: #4285f4;
+      }
     }
-  }
 
-  .tooltip {
-    padding-left: 8px;
+    .tooltip {
+      padding-left: 8px;
+    }
   }
-}
 </style>

+ 171 - 59
src/components/BsUi/SubTableInput/index.vue

@@ -1,23 +1,26 @@
 <template>
   <div class="sub-table-input">
-    <bs-table v-bind="tableOptions" ref="bsTableRef">
+    <bs-table v-bind="options" ref="bsTableRef">
       <template #bottom>
         <div class="table-bottom-btns">
           <div class="custom-table-btn">
-            <div class="ct-btn">
-              <slot v-if="slots.tableLeftBtn" name="tableLeftBtn"></slot>
+            <div class="ct-btn" v-if="isShowAddBtn">
               <a-button type="text" style="width: 100%" @click="handleAddBtn">
                 <template #icon> <PlusOutlined /></template>
                 <span>新增</span>
               </a-button>
             </div>
 
-            <div class="ct-btn">
+            <div class="ct-btn" v-if="isShowBatchDeleteBtn">
               <a-button type="text" @click="handleBatchDelete" style="width: 100%" :disabled="selectedData.length === 0">
                 <template #icon> <DeleteOutlined /></template>
                 <span>批量删除</span>
               </a-button>
             </div>
+
+            <div class="ct-btn" v-for="(btnSlot, index) in optBtnSlots" :key="index">
+              <slot :name="btnSlot"></slot>
+            </div>
           </div>
         </div>
       </template>
@@ -26,25 +29,44 @@
 </template>
 
 <script setup lang="jsx">
-  import { useSlots, h, ref, toRefs, onMounted, nextTick, watch } from 'vue';
+  import { useSlots, h, ref, toRefs, onMounted, nextTick, watch, computed } from 'vue';
   import BsTable, { useBsTable } from '/@/components/BsUi/Table';
   import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
-  import { cloneDeep, isEmpty } from 'lodash';
-  import { DISPLAY_STATE } from '/@/components/BsUi/constant.js';
+  import { cloneDeep, has, isEmpty } from 'lodash';
+  import { DISPLAY_STATE, ROW_KEY_FIELD } from '/@/components/BsUi/constant.js';
+  import { getUUID } from 'ant-design-vue/es/vc-dialog/util.js';
 
   const props = defineProps({
-    customColumns: {
-      required: false,
-      default: [],
-    },
     value: {
       required: true,
       default: undefined,
     },
-    rowKey: {
-      required: false,
-      default: 'id',
+    tableOptions: {
+      type: Object,
+      default: {
+        gridOptions: {},
+      },
+    },
+    isShowAddBtn: {
+      type: Boolean,
+      default: true,
+    },
+    isShowBatchDeleteBtn: {
+      type: Boolean,
+      default: true,
+    },
+    optBtnSlots: {
+      type: Array,
+      default: () => [],
     },
+    isEditRow: {
+      type: Boolean,
+      default: true,
+    },
+  });
+
+  const rowKey = computed(() => {
+    return props.tableOptions.rowKey;
   });
 
   const emits = defineEmits('change', 'update:value', 'add');
@@ -53,37 +75,6 @@
   const selectedData = ref([]);
   const bsTableRef = ref(null);
 
-  const bsTableAdapter = {
-    toolbarConfig: {
-      enable: false,
-    },
-    searchConfig: {
-      enable: false,
-    },
-    gridOptions: {
-      border: true,
-      minHeight: '0',
-      data: [],
-      columns: [],
-      onCheckboxChange(props) {
-        const gridRef = getGridRef();
-        selectedData.value = gridRef.getCheckboxRecords(true);
-      },
-      onCheckboxAll(props) {
-        const gridRef = getGridRef();
-        selectedData.value = gridRef.getCheckboxRecords(true);
-      },
-    },
-    pagerConfig: {
-      enable: false,
-      isFixed: false,
-    },
-  };
-
-  const bsTableBean = useBsTable({ tableOptions: bsTableAdapter });
-
-  const { tableOptions, setTablePropsValue: setValue, getTablePropsValue: getValue, getGridRef } = bsTableBean;
-
   const selectAndSeqCols = [
     { type: 'checkbox', width: 60, align: 'center', fixed: 'left' },
     { type: 'seq', width: 50, align: 'center', fixed: 'left' },
@@ -106,12 +97,16 @@
                   icon={h(DeleteOutlined)}
                   type='text'
                   onClick={() => {
-                    const tableData = getValue('gridOptions.data');
+                    const tableData = props.value;
                     const deepCloneData = cloneDeep(tableData);
                     deepCloneData.splice(rowIndex, 1);
-                    // setValue('gridOptions.data', deepCloneData);
                     emits('update:value', deepCloneData);
                     emits('change', deepCloneData);
+                    emits('delete-row', {
+                      gridRef: getGridRef(),
+                      bsTable: bsTableBean,
+                      tableData: deepCloneData,
+                    });
                   }}
                 ></a-button>
               </a-tooltip>
@@ -128,35 +123,152 @@
       },
     },
   ];
-  const gridOptions = getValue('gridOptions');
-  gridOptions.columns = [...selectAndSeqCols, ...optionsCols];
-  if (!isEmpty(props.customColumns)) {
-    gridOptions.columns = [...selectAndSeqCols, ...props.customColumns, ...optionsCols];
-  }
+
+  const bsTableAdapter = computed(() => {
+    const customOpt = {
+      toolbarConfig: {
+        enable: false,
+      },
+      searchConfig: {
+        enable: false,
+      },
+      pagerConfig: {
+        pageNum: 1,
+        pageSize: 100,
+        pageSizeOptions: ['100'],
+      },
+      async request({ pageNum, pageSize }) {
+        const sourceData = isEmpty(props.value) ? [] : cloneDeep(props.value);
+        const startIndex = (pageNum - 1) * pageSize;
+        const endIndex = startIndex + pageSize;
+        return {
+          list: sourceData.slice(startIndex, endIndex),
+          total: sourceData.length,
+        };
+      },
+      gridOptions: {
+        loading: false,
+        border: true,
+        minHeight: '100px',
+        data: [],
+        columns: [],
+        rowKey: 'id',
+        height: '400px',
+        onCheckboxChange(props) {
+          const gridRef = getGridRef();
+          selectedData.value = gridRef.getCheckboxRecords(true);
+        },
+        onCheckboxAll(props) {
+          const gridRef = getGridRef();
+          selectedData.value = gridRef.getCheckboxRecords(true);
+        },
+      },
+    };
+    const editOpt = {
+      keepSource: true,
+      editConfig: {
+        trigger: 'click',
+        mode: 'row',
+        showStatus: true,
+      },
+      onEditActivated(params) {
+        const { $grid, row, column } = params;
+      },
+      onEditClosed(params) {
+        const { $grid, row, column } = params;
+        const tableData = cloneDeep(props.value);
+        tableData.forEach((item) => {
+          if ((has(item, rowKey.value) && item[rowKey.value] === row[rowKey.value]) || item[ROW_KEY_FIELD] === row[ROW_KEY_FIELD]) {
+            item[column.field] = row[column.field];
+          }
+        });
+        emits('update:value', tableData);
+        emits('change', tableData);
+      },
+    };
+    if (props.isEditRow) {
+      return {
+        ...customOpt,
+        gridOptions: {
+          ...customOpt.gridOptions,
+          ...editOpt,
+          ...props.tableOptions.gridOptions,
+          columns: [...selectAndSeqCols, ...props.tableOptions.gridOptions?.columns, ...optionsCols],
+        },
+      };
+    } else {
+      return {
+        ...customOpt,
+        gridOptions: {
+          ...customOpt.gridOptions,
+          ...props.tableOptions.gridOptions,
+          columns: [...selectAndSeqCols, ...props.tableOptions.gridOptions?.columns],
+        },
+      };
+    }
+  });
+
+  const bsTableBean = useBsTable({ tableOptions: bsTableAdapter.value });
+  const { tableOptions: options, setTablePropsValue: setValue, getTablePropsValue: getValue, getGridRef, refreshTable } = bsTableBean;
+
   const handleAddBtn = () => {
-    const tableData = getValue('gridOptions.data');
+    const newData = isEmpty(props.value) ? [] : [...props.value];
+    newData.push({
+      [ROW_KEY_FIELD]: getUUID(),
+    });
+    emits('update:value', newData);
+    emits('change', newData);
     emits('add', {
       gridRef: getGridRef(),
       bsTable: bsTableBean,
-      tableData,
+      tableData: newData,
     });
   };
 
   const handleBatchDelete = () => {
-    const tableData = getValue('gridOptions.data');
-    const newTableData = tableData.filter((v) => selectedData.value.findIndex((v1) => v[props.rowKey] === v1[props.rowKey]) === -1);
-    // setValue('gridOptions.data', newTableData);
+    const tableData = props.value;
+    const newTableData = tableData.filter(
+      (v) =>
+        selectedData.value.findIndex((v1) => {
+          if (has(v, rowKey.value)) {
+            return v[rowKey.value] === v1[rowKey.value];
+          } else {
+            return v[ROW_KEY_FIELD] === v1[ROW_KEY_FIELD];
+          }
+        }) === -1
+    );
     emits('update:value', newTableData);
     emits('change', newTableData);
+    selectedData.value = [];
+    emits('delete-rows', {
+      gridRef: getGridRef(),
+      bsTable: bsTableBean,
+      tableData: newTableData,
+    });
   };
 
   watch(
     () => props.value,
     (val) => {
-      setValue('gridOptions.data', val);
+      // setValue('gridOptions.data', val);
+      refreshTable();
     },
     { immediate: true }
   );
+
+  defineExpose({
+    validator() {
+      return new Promise(async (resolve, reject) => {
+        const gridRef = getGridRef();
+        const errMap = await gridRef.validate(true);
+        if (errMap) {
+          reject(errMap);
+        } else {
+          resolve(true);
+        }
+      });
+    },
+  });
 </script>
 
 <style scoped lang="scss">
@@ -173,7 +285,7 @@
       padding: 0;
     }
     :deep(.vxe-grid--layout-body-wrapper) {
-      padding: 10px;
+      padding: 0;
     }
 
     :deep(.top-bottom) {

+ 8 - 6
src/components/BsUi/Table/index.js

@@ -2,8 +2,8 @@ import BsTable from './Table.vue';
 import { reactive, onMounted, onBeforeMount, toRaw, nextTick } from 'vue';
 import { set, get, isEmpty, isUndefined, forEach, has } from 'lodash';
 import { getTableDataApi } from '/@/api/system/table-api.js';
-import {PAGE_NUM, PAGE_SIZE_OPTIONS} from "/@/components/BsUi/constant.js";
-import {PAGE_SIZE} from "/@/constants/common-const.js";
+import { PAGE_NUM, PAGE_SIZE_OPTIONS } from '/@/components/BsUi/constant.js';
+import { PAGE_SIZE } from '/@/constants/common-const.js';
 
 export const useBsTable = (options, tableRef) => {
   let tableOptions = reactive(options.tableOptions);
@@ -139,7 +139,7 @@ export const useBsTable = (options, tableRef) => {
 
   const setPageSizeOptions = (value) => {
     set(tableOptions, 'pagerConfig.pageSizeOptions', value);
-  }
+  };
 
   const setTableData = (data) => {
     set(tableOptions, 'gridOptions.data', data);
@@ -171,7 +171,7 @@ export const useBsTable = (options, tableRef) => {
       } else {
         if (isEmpty(tableOptions?.url)) {
           setLoading(false);
-          setPageTotal(tableOptions.gridOptions.data.length)
+          setPageTotal(tableOptions.gridOptions.data.length);
           setTableData(tableOptions.gridOptions.data);
           nextTick(() => {
             tableOptions?.tableSearchAfterBiz && tableOptions.tableSearchAfterBiz({ gridRef });
@@ -257,12 +257,14 @@ export const useBsTable = (options, tableRef) => {
   };
 
   const refreshTable = async () => {
-    setPageNum(1);
+    setPageNum(tableOptions.pagerConfig.pageNum || PAGE_NUM);
+    setPageSize(tableOptions.pagerConfig.pageSize || PAGE_SIZE);
     await getTableData();
   };
 
   const fetchTableData = async (searchParams) => {
-    setPageNum(1);
+    setPageNum(searchParams?.pageNum || PAGE_NUM);
+    setPageSize(searchParams?.pageSize || PAGE_SIZE);
     await getTableData(searchParams);
   };
 

+ 6 - 4
src/components/BsUi/constant.js

@@ -18,10 +18,12 @@ export const SCENE_TYPE = Object.freeze({
   USER: 'USER', // 人员
   GROUP: 'GROUP', // 群组
   JOB: 'JOB', // 岗位
-})
+});
+
+export const PAGE_SIZE_OPTIONS = ['10', '20', '50', '100'];
 
-export const PAGE_SIZE_OPTIONS = ['10', '20', '50', '100']
+export const PAGE_NUM = 1;
 
-export const PAGE_NUM = 1
+export const PAGE_SIZE = 10;
 
-export const PAGE_SIZE = 10
+export const ROW_KEY_FIELD = 'ROW_KEY';