فهرست منبع

Merge branch 'dev' of https://git.yingcaibx.com/tl/web into dev

DESKTOP-USV654P\pc 1 هفته پیش
والد
کامیت
79df7080d1

+ 1 - 2
.env.development

@@ -16,8 +16,7 @@ VITE_DROP_CONSOLE = false
 # 接口地址
 # 如果没有跨域问题,直接在这里配置即可
 # VITE_GLOB_API_URL=http://42.247.8.136:8007/tlapipre
-VITE_GLOB_API_URL=http://10.150.10.139:8888/api
-
+VITE_GLOB_API_URL=http://10.150.10.139:8888/api/
 #VITE_GLOB_API_URL=http://172.21.92.28:8080
 # 文件上传接口  可选
 VITE_GLOB_UPLOAD_URL =/system/oss/upload

+ 6 - 2
src/api/form/execute/index.ts

@@ -131,10 +131,14 @@ export async function updateFormExecute(data: Recordable, mode: ErrorMessageMode
 /**
  * @description: 导出
  */
-export async function exportForm(params: Recordable, mode: ErrorMessageMode = 'modal') {
+export async function exportForm(
+  params: Recordable,
+  url?: string,
+  mode: ErrorMessageMode = 'modal',
+) {
   return defHttp.download(
     {
-      url: Api.Export,
+      url: url || Api.Export,
       method: 'POST',
       params,
       responseType: 'blob',

+ 326 - 11
src/components/CreateCodeStep/src/ViewDesignStep.vue

@@ -382,6 +382,91 @@
           </div>
         </div>
       </a-tab-pane>
+      <a-tab-pane key="4" :tab="t('导入配置')">
+        <div class="right-contarin">
+          <div class="right-bottom">
+            <CollapseContainer :title="t('列表配置')">
+              <div class="right-top">
+                <div class="right-top-list">
+                  <span>{{ t('模板名称:') }}</span>
+                  <a-input
+                    v-model:value="generatorConfig.listConfig.importTemplateName"
+                    :placeholder="t('可填写列表标题,非必填')"
+                    style="width: 250px"
+                  />
+                </div>
+              </div>
+              <div>
+                <a-table
+                  size="middle"
+                  :columns="importListColumns"
+                  :pagination="false"
+                  :data-source="generatorConfig.listConfig.importConfigs"
+                  :key="importColumnTableKey"
+                  class="list-config"
+                >
+                  <template #headerCell="{ column }">
+                    <template v-if="column.key === 'sort'">
+                      <svg class="icon" aria-hidden="true">
+                        <use xlink:href="#icon-fangxiang1" />
+                      </svg>
+                    </template>
+                  </template>
+                  <template #bodyCell="{ column, record, index }">
+                    <template v-if="column.key !== 'action'">
+                      <template v-if="column.key === 'sort'">
+                        <svg
+                          class="icon importDraggable-icon"
+                          aria-hidden="true"
+                          style="cursor: move"
+                        >
+                          <use xlink:href="#icon-paixu" />
+                        </svg>
+                      </template>
+                      <!---如果是checked一类的组件-->
+                      <template v-if="column.key === 'columnName'">
+                        <a-select
+                          v-model:value="record[column.dataIndex]"
+                          style="width: 100%"
+                          :options="selectOption"
+                          @change="(_, option) => handleColumnNameChange(option, index)"
+                        />
+                      </template>
+                      <template v-if="column.key === 'alignType'">
+                        <a-select v-model:value="record[column.dataIndex]" style="width: 100%">
+                          <a-select-option value="left"> {{ t('左对齐') }} </a-select-option>
+                          <a-select-option value="center"> {{ t('居中') }} </a-select-option>
+                          <a-select-option value="right"> {{ t('右对齐') }} </a-select-option>
+                        </a-select>
+                      </template>
+                      <template v-if="column.key === 'width'">
+                        <a-input type="number" v-model:value="record[column.dataIndex]" />
+                      </template>
+                      <template v-if="column.key === 'required'">
+                        <Switch
+                          v-model:checked="record[column.dataIndex]"
+                          :disabled="record.componentProps?.required"
+                        />
+                      </template>
+                      <template v-if="column.key === 'isFilter'">
+                        <Switch v-model:checked="record[column.dataIndex]" />
+                      </template>
+                    </template>
+                    <template v-if="column.key === 'action' && !record.required">
+                      <DeleteTwoTone two-tone-color="#ff8080" @click="ImportColumnRemove(index)" />
+                    </template>
+                  </template>
+                </a-table>
+                <a-button type="dashed" block @click="importColumnAdd">
+                  <PlusOutlined />
+                  {{ t('新增') }}
+                </a-button>
+              </div>
+            </CollapseContainer>
+          </div>
+        </div>
+      </a-tab-pane>
+
       <a-tab-pane class="tab-list3" key="3" :tab="t('按钮设置')">
         <div style="padding-top: 10px">
           <p class="tab-title">{{ t('按钮列表') }}</p>
@@ -418,8 +503,8 @@
                   <a-input v-model:value="record[column.dataIndex]" disabled />
                 </template>
               </template>
-              <template v-if="column.key === 'action' && !record.isDefault">
-                <DeleteTwoTone two-tone-color="#ff8080" @click="buttonRemove(index)" />
+              <template v-if="column.key === 'redirectUrl' && record.isUse">
+                <a-input placeholder="重定向地址" v-model:value="record[column.dataIndex]" />
               </template>
             </template>
           </a-table>
@@ -498,7 +583,6 @@
   });
   const { notification } = useMessage();
   const generatorConfig = inject<GeneratorConfig>('generatorConfig') as GeneratorConfig;
-  console.log('generatorConfig', generatorConfig);
   // generatorConfig!.formJson!.list.push()
   const current = inject<Ref<number>>('current') as Ref<number>;
   const designType = inject<string>('designType', '');
@@ -584,6 +668,40 @@
       width: '80px',
     },
   ]);
+  const importListColumns = ref([
+    {
+      dataIndex: 'sort',
+      key: 'sort',
+      width: '50px',
+      align: 'center',
+    },
+    {
+      title: t('列表字段'),
+      dataIndex: 'columnName',
+      key: 'columnName',
+      width: '35%',
+    },
+    {
+      title: t('宽度'),
+      dataIndex: 'width',
+      key: 'width',
+      width: '15%',
+    },
+    {
+      title: t('是否必填'),
+      dataIndex: 'required',
+      width: '15%',
+      key: 'required',
+      align: 'center',
+    },
+    {
+      title: t('操作'),
+      key: 'action',
+      fixed: 'right',
+      align: 'center',
+      width: '80px',
+    },
+  ]);
 
   const buttonColumns = ref([
     {
@@ -612,8 +730,9 @@
       width: '20%',
     },
     {
-      title: t('操作'),
-      key: 'action',
+      title: t('配置'),
+      dataIndex: 'redirectUrl',
+      key: 'redirectUrl',
       fixed: 'right',
       align: 'center',
     },
@@ -622,6 +741,7 @@
   const activeKey = ref<string>('1');
   const queryTableKey = ref<number>(0);
   const columnTableKey = ref<number>(0);
+  const importColumnTableKey = ref<number>(0);
 
   const apiConfigDialog = ref<boolean>(false);
 
@@ -651,6 +771,7 @@
           code: 'refresh',
           icon: 'ant-design:reload-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: true,
@@ -658,6 +779,7 @@
           code: 'view',
           icon: 'ant-design:eye-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: true,
@@ -665,6 +787,7 @@
           code: 'add',
           icon: 'ant-design:plus-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: true,
@@ -672,6 +795,7 @@
           code: 'edit',
           icon: 'ant-design:form-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: true,
@@ -679,6 +803,7 @@
           code: 'delete',
           icon: 'ant-design:delete-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -686,6 +811,7 @@
           code: 'batchdelete',
           icon: 'ant-design:delete-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -693,6 +819,7 @@
           code: 'copyData',
           icon: 'ant-design:copy-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -700,6 +827,7 @@
           code: 'batchSetUserId',
           icon: 'ant-design:setting-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -707,6 +835,7 @@
           code: 'import',
           icon: 'ant-design:import-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -721,6 +850,7 @@
           code: 'startwork',
           icon: 'ant-design:form-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -728,6 +858,7 @@
           code: 'print',
           icon: 'ant-design:printer-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
         {
           isUse: false,
@@ -735,6 +866,7 @@
           code: PrintButton.CODE,
           icon: 'ant-design:printer-outlined',
           isDefault: true,
+          redirectUrl: '',
         },
       ];
     }
@@ -853,8 +985,130 @@
 
     return mainComponents;
   };
+
+  //过滤出 所有主表数据字段  子表的不需要  不在列表页展示的不需要 (只给导入配置使用)
+  const filterImportMainComponent = (
+    list: ComponentOptionModel[] | null,
+    isSearch = false,
+  ): Recordable[] => {
+    if (!list) return [];
+    let mainComponents = [] as Recordable[];
+    let mainTableFieldConfigs = [] as TableFieldConfig[];
+
+    const remoteComponents = [
+      'select',
+      'associate-select',
+      'associate-popup',
+      'multiple-popup',
+      'checkbox',
+      'radio',
+    ];
+    generatorConfig.tableStructureConfigs?.forEach((x) => {
+      if (x.isMain) mainTableFieldConfigs = x.tableFieldConfigs;
+    });
+    for (const item of list) {
+      //去除子表单组件  和 不在列表页展示的组件
+      if (noShowList.includes(item.type)) {
+        if (['tab', 'grid', 'card'].includes(item.type)) {
+          for (const child of item.layout!) {
+            mainComponents = unionWith(mainComponents, filterMainComponent(child.list, isSearch));
+          }
+        }
+      } else if (item.type.indexOf('range') !== -1) {
+        //查询配置不要时间范围和日期范围
+        if (!isSearch) {
+          mainComponents.push({
+            key: item.key,
+            value: item.bindStartTime,
+            label: t(`{name}开始时间`, { name: item.label }),
+            type: item.type,
+            componentProps: item.options,
+
+            format: item.options?.format,
+          });
+          mainComponents.push({
+            key: item.key,
+            value: item.bindEndTime,
+            label: t(`{name}结束时间`, { name: item.label }),
+            type: item.type,
+            componentProps: item.options,
+
+            format: item.options?.format,
+          });
+        }
+      } else {
+        let notInQuery = false;
+        //组件的数据是使用api并且引用组件值作为参数的组件 不进入查询列表
+        if (
+          ((remoteComponents.includes(item.type) && item.options?.datasourceType === 'api') ||
+            item.type === 'cascader') &&
+          isSearch
+        ) {
+          notInQuery = item.options?.apiConfig?.apiParams?.some((para) => {
+            const hasFormData = para.tableInfo?.some((info) => {
+              if (!info.value) return false;
+              return info.bindType === 'data' && info.value?.slice(0, 2) !== '3-';
+            });
+            return hasFormData;
+          });
+        }
+
+        if (!notInQuery) {
+          if (item.type === 'time' || item.type === 'date') {
+            //查询时 时间选择和日期选择需要保存format
+            mainComponents.push({
+              value: item.bindField,
+              label: item.label,
+              type: item.type,
+              format: item.options?.format,
+            });
+          } else if (isSearch && item.type === 'info' && item.options?.infoType === 2) {
+            //查询时 信息体为当前时间 则查询为时间范围
+            mainComponents.push({
+              value: item.bindField,
+              label: item.label,
+              type: item.type,
+              componentProps: item.options,
+
+              // format: 'YYYY-MM-DD HH:mm:ss',
+              isDate: true,
+            });
+          } else {
+            let isNumber;
+            if (unref(designType) !== 'data') {
+              let fieldType;
+              mainTableFieldConfigs?.map((x) => {
+                if (x.fieldName === item.bindField) {
+                  fieldType = x.fieldType;
+                }
+              });
+              isNumber = [2, 3, 7].includes(fieldType);
+            } else {
+              const table = tableInfo?.value.find((x) => x.isMain); //找到主表信息
+              const field = table?.fields.find((x) => x.name === item.bindField); //找到当前字段信息
+              isNumber = field?.type === ColumnType.NUMBER;
+            }
+            if ((item.type == 'input' && !item.options?.isSave) || item.type !== 'input') {
+              mainComponents.push({
+                key: item.key,
+                value: item.bindField,
+                label: item.label,
+                type: item.type,
+                isNumber,
+                componentProps: item.options,
+              });
+            }
+          }
+        }
+      }
+    }
+
+    return mainComponents;
+  };
+
   const getConfigInfo = () => {
     let listComponent = filterMainComponent(generatorConfig!.formJson!.list);
+    let importComponent = filterImportMainComponent(generatorConfig!.formJson!.list);
     let queryComponent = filterMainComponent(generatorConfig!.formJson!.list, true);
     let columnConfigs: ColumnConfig[] = [];
     let queryConfigs: QueryConfig[] = [];
@@ -894,7 +1148,23 @@
       //除去查询配置新增时已经设置好的组件
       queryComponent = queryComponent.filter((x) => !addQueryNames.includes(x.value));
     }
-
+    generatorConfig.listConfig.importConfigs = importComponent.map((component) => {
+      const columnConfig: ColumnConfig = {
+        key: component.key, //数据优先存在编辑时 绑定字段相同的情况 所以需要唯一标识
+        columnName: component.value,
+        label: component.label,
+        width: 0,
+        alignType: '',
+        autoWidth: true,
+        isNumber: component.isNumber,
+        componentType: component.type,
+        componentProps: component.componentProps,
+        required: component.componentProps?.required || false,
+      };
+      return columnConfig;
+    });
+    generatorConfig.listConfig.importTemplateName =
+      generatorConfig.listConfig.importTemplateName || generatorConfig.menuConfig.name + '导出模板';
     generatorConfig.listConfig.columnConfigs = listComponent.map((component) => {
       const columnConfig: ColumnConfig = {
         key: component.key, //数据优先存在编辑时 绑定字段相同的情况 所以需要唯一标识
@@ -1009,7 +1279,6 @@
                 columns.splice(oldIndex, 1);
               }
               generatorConfig!.listConfig!.columnConfigs = cloneDeep(columns);
-              console.log(generatorConfig!.listConfig!.columnConfigs);
               columnTableKey.value++;
             },
           });
@@ -1021,6 +1290,39 @@
       immediate: true,
     },
   );
+  // 监听导入模板事件
+  watch(
+    () => [generatorConfig?.listConfig?.importConfigs, activeKey.value],
+    (val) => {
+      if (val[0] && val[0].length && val[1] === '4') {
+        nextTick(() => {
+          const tbody: any = document.querySelector('.list-config .ant-table-tbody');
+          Sortable.create(tbody, {
+            handle: '.importDraggable-icon',
+            onEnd: ({ oldIndex, newIndex }) => {
+              if (isNullAndUnDef(oldIndex) || isNullAndUnDef(newIndex) || newIndex === oldIndex) {
+                return;
+              }
+              const columns = cloneDeep(generatorConfig?.listConfig?.importConfigs);
+              if (oldIndex > newIndex) {
+                columns.splice(newIndex, 0, columns[oldIndex]);
+                columns.splice(oldIndex + 1, 1);
+              } else {
+                columns.splice(newIndex + 1, 0, columns[oldIndex]);
+                columns.splice(oldIndex, 1);
+              }
+              generatorConfig!.listConfig!.importConfigs = cloneDeep(columns);
+              importColumnTableKey.value++;
+            },
+          });
+        });
+      }
+    },
+    {
+      deep: true,
+      immediate: true,
+    },
+  );
 
   watch(
     apiConfig,
@@ -1075,10 +1377,27 @@
     generatorConfig?.listConfig?.columnConfigs.push(pushObj);
     // queryOptionData.value.push(pushObj);
   };
+  const importColumnAdd = () => {
+    const pushObj: ColumnConfig = {
+      key: '',
+      label: '',
+      columnName: '',
+      width: 100,
+      alignType: '',
+      autoWidth: true,
+      isTotal: false,
+      isFilter: false,
+      componentType: '',
+    };
+    generatorConfig.listConfig.importConfigs.push(pushObj);
+  };
 
   const columnRemove = (index) => {
     generatorConfig?.listConfig?.columnConfigs.splice(index, 1);
   };
+  const ImportColumnRemove = (index) => {
+    generatorConfig.listConfig.importConfigs.splice(index, 1);
+  };
 
   const buttonAdd = () => {
     //给各个组件赋默认值
@@ -1092,10 +1411,6 @@
     generatorConfig?.listConfig?.buttonConfigs.push(pushObj);
   };
 
-  const buttonRemove = (index) => {
-    generatorConfig?.listConfig?.buttonConfigs.splice(index, 1);
-  };
-
   const closeModal = () => {
     formRef.value.resetFields();
   };

+ 4 - 2
src/components/Import/src/ImportModal.vue

@@ -28,6 +28,7 @@
   import { useMessage } from '/@/hooks/web/useMessage';
   import { downloadFile } from '/@/api/sys/download';
   import { downloadByData } from '/@/utils/file/download';
+  import { formatToDateTime } from '/@/utils/dateUtil';
 
   const props = defineProps({
     importUrl: String,
@@ -45,11 +46,12 @@
   let templateTitle;
 
   const [registerModal, { setModalProps }] = useModalInner(async (data) => {
+    console.log(data);
     title.value = data.title;
     downLoadUrl = data.downLoadUrl;
     importType.value = data.type;
     templateApi = data.api;
-    templateTitle = data.templateTitle;
+    templateTitle = data.templateTitle || data.templateName;
     fileList.value = [];
     setModalProps({
       destroyOnClose: true,
@@ -100,7 +102,7 @@
     }
     downloadByData(
       res.data,
-      `${templateTitle || '模板'}.xlsx`,
+      `${templateTitle || '模板'}${formatToDateTime(new Date(), 'YYYY-MM-DD')}.xlsx`,
       'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
     );
   };

+ 4 - 0
src/model/generator/generatorConfig.ts

@@ -24,6 +24,8 @@ export interface GeneratorConfig {
   outputConfig: OutputConfig;
   //菜单配置
   menuConfig?: MenuConfig;
+  // 导出列配置
+  importConfig?: ListConfig;
   //表结构配置
   tableStructureConfigs?: TableStructureConfig[];
   //表单事件配置
@@ -79,4 +81,6 @@ export interface FormReleaseConfig {
   listConfig?: ListConfig;
   //菜单配置
   menuConfig: MenuConfig;
+  // 导入配置
+  importConfig?: ListConfig;
 }

+ 9 - 0
src/model/generator/listConfig.ts

@@ -16,6 +16,7 @@ export interface ListConfig {
   columnConfigs: ColumnConfig[];
   //按钮配置
   buttonConfigs: ButtonConfig[];
+  importConfigs?: any;
   // //合计配置
   // totalConfigs?: TotalConfig[];
   //列表标题
@@ -26,6 +27,8 @@ export interface ListConfig {
   orderBy?: string;
   //排序类型 (默认 倒叙)
   orderType?: 'desc' | 'asc';
+  width?: number;
+  importTemplateName: string;
 }
 
 /**
@@ -110,6 +113,10 @@ export interface ColumnConfig {
   mainField?: boolean;
   //是否列头筛选
   isFilter?: boolean;
+  // 是否必填
+  required?: boolean;
+  // 宽度
+  width?: number;
 }
 
 /**
@@ -126,6 +133,8 @@ export interface ButtonConfig {
   icon: string;
   //是否新增
   isDefault: boolean;
+  //重定向地址
+  redirectUrl?: string;
 }
 
 /**

+ 17 - 0
src/views/form/release/components/ReleaseModal.vue

@@ -66,6 +66,7 @@
 
   let generatorConfig = reactive<GeneratorConfig>({
     listConfig: {
+      importTemplateName: '',
       listTitle: '',
       isLeftMenu: false,
       queryConfigs: [],
@@ -84,6 +85,13 @@
       defaultOrder: true,
       orderBy: '',
       isPage: true,
+      importConfigs: [] as {
+        fieldName: string;
+        label: string;
+        required: boolean;
+        sortCode: number;
+        width: number;
+      }[],
     },
     menuConfig: {},
   });
@@ -206,6 +214,15 @@
       return;
     }
     formReleaseConfig.listConfig = generatorConfig.listConfig;
+    if (
+      Array.isArray(formReleaseConfig.listConfig.importConfigs) &&
+      formReleaseConfig.listConfig.importConfigs.length
+    ) {
+      formReleaseConfig.listConfig.importConfigs.forEach((item, index) => {
+        item.fieldName = item.columnName;
+        item.sortCode = index;
+      });
+    }
     const params = {
       ...toRaw(formReleaseConfig),
       id: releaseId.value,

+ 14 - 7
src/views/form/template/index.vue

@@ -37,7 +37,11 @@
         </template>
         <template #toolbar>
           <template v-for="button in tableButtonConfig" :key="button.code">
-            <a-button v-if="button.isDefault" type="primary" @click="buttonClick(button.code)">
+            <a-button
+              v-if="button.isDefault"
+              type="primary"
+              @click="buttonClick(button.code, button.redirectUrl)"
+            >
               <template #icon><Icon :icon="button.icon" /></template>
               {{ button.name }}
             </a-button>
@@ -189,6 +193,7 @@
   const { path } = unref(currentRoute);
   const printMenuId = computed(() => currentRoute.value.meta.menuId as string);
   const { filterColumnAuth, filterButtonAuth } = usePermission();
+  const templateName = ref('');
   const expandedKeys: Ref<UnwrapRef<any[]>> = ref([]);
   let columns: BasicColumn[] = [], //列表配置
     searchFormSchema: FormSchema[] = [], //搜索框配置
@@ -420,11 +425,11 @@
       },
     };
   }
-  function buttonClick(code) {
+  function buttonClick(code, redirectUrl) {
     if (code.includes(PrintButton.CODE)) {
       printTemplate(code);
     } else {
-      btnEvent[code]();
+      btnEvent[code](redirectUrl);
     }
   }
   // 模板打印
@@ -656,9 +661,9 @@
     });
   }
 
-  async function handleExport() {
+  async function handleExport(redirectUrl) {
     const fileName = listTitle.value;
-    const res = await exportForm({ ...pageParamsInfo.value });
+    const res = await exportForm({ ...pageParamsInfo.value }, redirectUrl);
     downloadByData(
       res.data,
       `${fileName}.xlsx`,
@@ -666,11 +671,12 @@
     );
   }
 
-  function handleImport() {
+  function handleImport(redirectUrl) {
     openImportModal(true, {
       title: t('快速导入'),
-      downLoadUrl: '/form/execute/export',
+      downLoadUrl: redirectUrl || '/form/execute/export',
       type: 'POST',
+      templateName: templateName.value,
     });
   }
 
@@ -746,6 +752,7 @@
     useTitle(` ${configJson.menuConfig.name} - ${title} `);
     //设置是否需要左侧菜单
     listConfig.value = configJson.listConfig!;
+    templateName.value = listConfig.value.importTemplateName;
     listTitle.value = listConfig.value.listTitle || configJson.menuConfig.name;
     const { queryConfigs, columnConfigs } = configJson.listConfig!;
     //如果设置了左侧菜单  需要请求菜单数据