Lighthouse 和 Performance 面板

Lighthouse 和 Performance 面板 都是 Chrome 开发者工具中用于分析页面性能的工具,但它们的功能和定位有所不同。以下是对两者的详细对比,以及为什么需要同时使用它们。


1. 功能定位

(1)Lighthouse
  • 定位
    Lighthouse 是一个 自动化审计工具,专注于提供全面的性能分析报告和优化建议。

  • 功能
    分析页面的性能、可访问性、最佳实践、SEO 和 PWA 等方面,生成详细的报告。

  • 目标用户
    开发者、SEO 专家、产品经理等,希望快速了解页面整体表现并获取优化建议。

(2)Performance 面板
  • 定位
    Performance 面板是一个 性能分析工具,专注于深入分析页面加载和运行时的性能瓶颈。

  • 功能
    记录页面加载过程,展示主线程活动、网络请求、渲染时间等详细信息。

  • 目标用户
    开发者,尤其是前端工程师,希望深入分析性能问题并优化代码。


2. 功能对比

功能LighthousePerformance 面板
性能分析提供关键性能指标(如 LCP、FID、CLS)展示详细的性能时间线(如 FCP、LCP)
优化建议提供具体的优化建议(如减少未使用的 JS)无直接优化建议,需手动分析
可访问性分析支持不支持
SEO 分析支持不支持
最佳实践分析支持不支持
PWA 分析支持不支持
实时记录不支持支持
主线程活动分析不支持支持
网络请求分析不支持支持
渲染时间分析不支持支持

3. 使用场景

(1)Lighthouse 的使用场景
  • 快速获取优化建议
    当你需要快速了解页面的性能问题并获取具体的优化建议时,可以使用 Lighthouse。

  • 全面审计
    当你需要分析页面的可访问性、SEO、最佳实践等方面时,Lighthouse 是更好的选择。

  • 报告生成
    当你需要生成一份详细的性能报告(如给团队或客户)时,Lighthouse 提供了更友好的报告格式。

(2)Performance 面板的使用场景
  • 深入分析性能瓶颈
    当你需要深入分析页面加载过程中的性能瓶颈(如长任务、渲染时间)时,Performance 面板更合适。

  • 实时调试
    当你需要实时记录页面加载过程并分析具体问题时,Performance 面板是必备工具。

  • 代码优化
    当你需要优化 JavaScript 执行、减少主线程阻塞时,Performance 面板提供了更详细的数据。


4. 为什么需要同时使用?

  • Lighthouse 提供了 全面的审计报告和优化建议,适合快速定位问题和制定优化策略。

  • Performance 面板 提供了 详细的性能数据和实时调试能力,适合深入分析和优化代码。

示例场景
  1. 使用 Lighthouse 发现问题

    • 运行 Lighthouse,发现 LCP 时间过长。

    • 报告建议优化图片加载和减少 JavaScript 阻塞。

  2. 使用 Performance 面板深入分析

    • 打开 Performance 面板,记录页面加载过程。

    • 发现某个 JavaScript 文件执行时间过长,导致主线程阻塞。

    • 优化该 JavaScript 代码,减少执行时间。


5. 总结

  • Lighthouse 是一个 自动化审计工具,适合快速获取优化建议和生成报告。

  • Performance 面板 是一个 性能分析工具,适合深入分析性能瓶颈和优化代码。

  • 两者结合使用,可以更全面地分析和优化页面性能。

如果你只需要快速了解页面的性能问题,Lighthouse 可能已经足够;但如果你需要深入分析和优化代码,Performance 面板是必不可少的工具。

以下是一个示例:

从性能面板上记录渲染页面,发现有一个cloneDeep函数执行1700ms , 再去检查代码,通过console.time和console.timeEnd,发现复制了后端返回的数据,因为在联动获取其它重新整理数据格式,导致初始化要保留历史数据,clone的数据包含config:{options:[20000多条数据]},发现cloneDeep都可以删除,其中一个加上联动的判断条件,code如下:

<template>

  <div>

    <template v-if="collapseData[currentStep]?.length > 0 && !isExportPdf && showCollapse">

      <div

        ref="anchorRef"

        :style="`position: sticky; top: ${top}px; z-index: 1000; border-bottom: 2px solid #c9d1d9;`"

      >

        <Anchor

          :wrap="resId['actionCode'] === 'detail' ? '.check-component' : ''"

          :data="

            collapseData[currentStep].filter(

              (v) => v.linkageConditions !== 3 && v.linkageConditions !== 6,

            )

          "

        />

      </div>

    </template>

    <div style="width: 100%" v-if="oldList && oldList.length > 0">

      <a-form

        label-align="right"

        layout="horizontal"

        :label-col="{ style: { width: '140px' } }"

        :wrapper-col="{ span: 24, offset: 0 }"

        ref="formCRef"

        :model="formState"

        :validate-messages="validateMessages"

        :class="getFormClass"

      >

        <template v-if="pageList.length">

          <template v-for="(item, index) in pageList">

            <c-form

              v-if="currentStep === index"

              :key="item.id"

              :attach-ref="attachRef"

              :table-ref="tableRef"

              :comp-ref="compRef"

              :list="item.children"

              :form-state-p="formState"

              :record-hide="recordHide"

              :record-hide-save="recordHideSave"

              :current-linkage-field="currentLinkageField"

              :changeInputNumber="changeInputNumber"

              :changeDatePicker="changeDatePicker"

              :action="resId['actionCode']"

              @linkage-change="selectChangeComponent"

              :resId="resId"

              :isMounted="isMounted"

            />

          </template>

        </template>

      </a-form>

    </div>

  </div>

</template>

<script lang="ts">

  // 流程对象页面

  import {

    defineComponent,

    unref,

    ref,

    toRefs,

    reactive,

    nextTick,

    computed,

    onMounted,

    onBeforeUnmount,

    inject,

  } from 'vue';

  import { cloneDeep } from 'lodash-es';

  import { getDictionaryInfoByCode } from '/@/api/sys/dictionary';

  import { useDataChange } from '/@/hooks/component/useDataChange';

  import { useDataSave } from '/@/hooks/component/useDataEdit';

  import { useMessage } from '/@/hooks/web/useMessage';

  import { useI18n } from '/@/hooks/web/useI18n';

  import CForm from '/@/views/entityObject/components/form.vue';

  import type { FormInstance } from 'ant-design-vue';

  import { isArray, isEmpty, isString, isJSON } from '/@/utils/is';

  import {

    saveOrUpdateWorkflowObject,

    temporarySaveWorkflow,

    rebut,

    updateWorkflowObject,

    parallelHandle,

  } from '/@/api/workflowObject';

  import { multiUpload } from '/@/api/entityObject/attachment';

  import scrollIntoView from 'scroll-into-view-if-needed';

  import { BasicHelp } from '/@/components/Basic';

  import { findIndex } from 'lodash-es';

  import { useDataArray, useDataT } from '/@/hooks/component/useDataArray';

  import { useUserStore } from '/@/store/modules/user';

  import { useRootSetting } from '/@/hooks/setting/useRootSetting';

  import { useMatchRule } from '/@/hooks/web/useMathRule';

  import { recursionGroup, recursionGroupNew, recursionLink } from '/@/utils/recursion';

  import Anchor from './anchor.vue';

  import { fieldLinkageAopExecute } from '/@/api/publicConfig/fieldManage';

  import { flattenTree } from '/@/components/CasesTreeV2/treeSonversion';

  import { useEntityObjectStoreWithOut } from '/@/store/modules/entityObject';

  import { formatToDateTime } from '/@/utils/dateUtil';

  import useSelectChange from '/@/views/workflowObject/hooks/useSelectChange';

  const { currentLinkageField, selectChange, generateFormState } = useSelectChange();

  export default defineComponent({

    components: {

      CForm,

      Anchor,

    },

    props: {

      list: {

        type: Array,

        default: () => [],

      },

      showSure: {

        type: Boolean,

        default: false,

      },

      resId: {

        type: Object,

        default: () => ({}),

      },

      closeLoading: {

        type: Function,

        default: null,

      },

      isExportPdf: {

        type: Boolean,

        default: false,

      },

      topValue: {

        type: Number,

        default: 12,

      },

      showCollapse: {

        type: Boolean,

        default: true,

      },

      currentStep: {

        type: Number,

        default: 0,

      },

    },

    setup(props) {

      const calculateData: any = {

        cacheRule: {},

        cacheField: [],

      }; // 用来缓存计算规则数据,给数字和日期类型更改值触发计算属性

      let changeInputNumber: any = ref(() => {});

      let changeDatePicker: any = ref(() => {});

      const { createMessage: message } = useMessage();

      const { t } = useI18n();

      const { list: oldList } = toRefs(props);

      let pageList = ref<Recordable[]>([]);

      let inputRule: any = {};

      const formState = reactive<any>({});

      const recordHide = reactive<any>({});

      const recordHideSave = reactive<any>({});

      const collapseForm = reactive<any>({});

      const collapseData: any = ref<any>([]);

      const collapseOld: any = new Set([]);

      let keyObj: any = {};

      let topVal = ref(0);

      const formCRef = ref<FormInstance>();

      let tableObject = ref({}); // 记录table ref对象

      let developObject = ref({}); // 记录自定义组件 ref对象

      let attachArr: any = {}; // 记录attachment ref对象

      const pageCode = ref(props.resId['actionCode']);

      const fieldMap = new Map();

      const fieldOldMap = new Map();

      let isDetail = inject('isDetail', '');

      const linkageMap = new Map();

      const initValue = new Map();

      const userStore = useUserStore();

      const { getFormSpace } = useRootSetting();

      const getFormClass = computed(() => {

        return [

          {

            ['andform-compact']: unref(getFormSpace) === 'compact',

          },

        ];

      });

      const top = ref(0);

      const anchorRef = ref();

      const entityObjectStore = useEntityObjectStoreWithOut();

      let isMounted = ref(false);

      //  左侧页面滚动与右侧导航标签联动

      function debounce(fn, delay) {

        let timer = null; //借助闭包

        return function () {

          if (timer) {

            clearTimeout(timer);

          }

          timer = setTimeout(fn, delay); // 简化写法

        };

      }

      function scrollFn() {

        let scrollContainerDom = document?.getElementById('scroll-contaier');

        let scrollbarWrapDom: any =

          scrollContainerDom?.getElementsByClassName('scrollbar__wrap')[0];

        let collapseRenderDom: any = scrollbarWrapDom?.getElementsByClassName('collapse-render');

        let antAnchorLinkDom: any = scrollContainerDom?.getElementsByClassName('ant-anchor-link');

        // scrollbarWrapDom.addEventListener('scroll', () => {

        try {

          if (collapseRenderDom.length > 0) {

            for (let i = 0; i < collapseRenderDom.length; i++) {

              let h = Number(collapseRenderDom[i].offsetHeight + collapseRenderDom[i].offsetTop);

              if (

                collapseRenderDom[i].offsetTop - 8 < scrollbarWrapDom.scrollTop &&

                scrollbarWrapDom.scrollTop < h

              ) {

                antAnchorLinkDom[i].classList.add('ant-anchor-link-active');

                antAnchorLinkDom[i]

                  .getElementsByClassName('ant-anchor-link-title')[0]

                  .classList.add('ant-anchor-link-title-active');

                let antAnchoInkBall: any =

                  scrollContainerDom?.getElementsByClassName('ant-anchor-ink-ball')[0];

                antAnchoInkBall.style.top = `${30 * i + 10.5}px`;

                antAnchoInkBall.classList.add('visible');

              } else {

                antAnchorLinkDom[i].classList.remove('ant-anchor-link-active');

                antAnchorLinkDom[i]

                  .getElementsByClassName('ant-anchor-link-title')[0]

                  .classList.remove('ant-anchor-link-title-active');

              }

            }

          }

        } catch {}

        // });

      }

      nextTick(() => {

        // scrollFn();

        try {

          let scrollContainerDom = document?.getElementById('scroll-contaier');

          let scrollbarWrapDom: any =

            scrollContainerDom?.getElementsByClassName('scrollbar__wrap')[0];

          scrollbarWrapDom.addEventListener('scroll', debounce(scrollFn, 400));

        } catch {}

      });

      onBeforeUnmount(() => {

        try {

          let scrollContainerDom = document?.getElementById('scroll-contaier');

          let scrollbarWrapDom: any =

            scrollContainerDom?.getElementsByClassName('scrollbar__wrap')[0];

          scrollbarWrapDom.removeEventListener('scroll', () => {});

        } catch {}

      });

      // 动态设置表格ref

      function tableRef(element, item) {

        if (element && item && element.setDynamicTable && !tableObject.value[item.fieldName]) {

          tableObject.value[item.fieldName] = element;

          item['refElement'] = element;

          element.setDynamicTable({

            head: item.config?.head ?? [],

            data: item.config?.data ?? [],

            childHead: item.config?.childHead ?? [],

            isAddChildRow: item.config?.isAddChildRow ?? false,

          });

        }

      }

      function compRef(element, item) {

        if (element && item) {

          developObject.value[item.fieldName] = element;

          item['refElement'] = element;

        }

      }

      function attachRef(element, item) {

        attachArr[item.fieldName] = element;

        item['refElement'] = element;

      }

      function filterOption(input: string, option: any) {

        return option.label.indexOf(input) >= 0;

      }

      onMounted(async () => {

        tableObject.value = {}; // 初始化tableRef存储值

        developObject.value = {};

        attachArr = {};

        isMounted.value = false;

        // oldList 需要分Collapse处理

        if (unref(oldList) && unref(oldList).length > 0) {

          if (!inputRule.hasOwnProperty('data')) {

            inputRule = await getDictionaryInfoByCode('formValidatedRule');

          }

          unref(oldList).forEach((v: Recordable, i) => {

            const collapseList: Recordable = [];

            const collapseOldList: Recordable = [];

            recursionGroup(v.children, (item: any) => {

              const newItem = useDataChange(item, inputRule, 'entity');

              keyObj[newItem.fieldName] = item.fieldTypeInnerKey;

              // 多行文件的追加特殊处理, 需求变更,不是跟旧数据对比,而是跟最终填写的数据对比,同一用户和节点仍然是不追加的默认填入

              if (item.fieldTypeInnerKey === 'textarea') {

                if (

                  newItem.isBrandNew &&

                  newItem.isAppend &&

                  newItem.isUser &&

                  props.resId['oldData'] &&

                  props.resId['oldData'][`${newItem.fieldName}`]

                ) {

                  // 再判断是否需要添加用户名

                  const date = new Date();

                  const userName = userStore.getUserInfo.trueName;

                  let compareString = `【${userName}-${props.resId['nodeName']}-${formatToDateTime(

                    date,

                  )}】`;

                  let oldData = props.resId['oldData'][`${newItem.fieldName}`].trim();

                  let mat = oldData.length - compareString.length;

                  const lastInfo = oldData.substr(mat, oldData.length);

                  if (lastInfo.includes(userName) && lastInfo.includes(props.resId['nodeName'])) {

                    newItem[`${newItem.fieldName}_r`] =

                      props.resId['oldData'][`${newItem.fieldName}`];

                    newItem.isSpecailAppend = true;

                  }

                }

              }

              // 初始化form表单值

              formState[`${newItem.fieldName}_r`] = newItem[`${newItem.fieldName}_r`];

              initValue.set(`${newItem.fieldName}_r`, newItem[`${newItem.fieldName}_r`]);

              if (item.fieldTypeInnerKey === 'department' || item.fieldTypeInnerKey === 'user') {

                formState[`${newItem.fieldName}_modal`] = newItem[`${newItem.fieldName}_modal`];

                initValue.set(`${newItem.fieldName}_modal`, newItem[`${newItem.fieldName}_modal`]);

              }

              // 将collapse数据提取出来

              if (item.fieldTypeInnerKey === 'collapse') {

                collapseList.push(newItem);

                collapseOldList.push(cloneDeep(newItem));

              }

              // 存储每个字段的原始信息,包含isBrandNew, isRequired, isRemark, config等信息

              fieldMap.set(newItem.fieldName, {

                oldConfig: cloneDeep(newItem.config),

                ...newItem,

              });

              fieldOldMap.set(newItem.fieldName, newItem);

              let itemId: any = [];

              if (unref(isDetail)) {

                if (newItem.config?.linkUse && isString(newItem.config?.linkUse)) {

                  if (newItem.fieldTypeInnerKey === 'related') {

                    itemId = newItem.config?.linkUse;

                  } else {

                    itemId = newItem.config?.linkUse?.split(',') || [];

                  }

                } else {

                  itemId = newItem.config?.linkUse || [];

                }

              } else {

                if (

                  newItem.config?.defaultDictionaryItemId &&

                  isString(newItem.config?.defaultDictionaryItemId)

                ) {

                  itemId = newItem.config?.defaultDictionaryItemId?.split(',') || [];

                } else if (newItem.config?.defaultDictionaryItemId) {

                  itemId = newItem.config?.defaultDictionaryItemId || [];

                } else if (newItem.config?.defaultDictionaryItemIds) {

                  itemId = newItem.config?.defaultDictionaryItemIds || [];

                } else if (newItem.config?.defaultValue) {

                  itemId = newItem.config?.defaultValue || [];

                } else {

                  itemId = [];

                }

              }

              if (newItem.fieldLinkageItemList?.length) {

                // 以上联动配置会走下面的初始化onchange事件

                linkageMap.set(newItem.fieldName, {

                  list: newItem.fieldLinkageItemList,

                  defaultId: itemId,

                  config: newItem.config,

                  fieldName: newItem.fieldName,

                  fieldInnerKey: newItem.fieldInnerKey,

                });

              }

              // else if (itemId && newItem.config?.relateExtendClass) {

              //   // 如果有默认值、有联动扩展类的也需要手动触发联动, add会默认触发, edit不会

              //   // 需求扩展联动不会手动触发

              //   selectChange(

              //     newItem.fieldName,

              //     itemId,

              //     {

              //       list: newItem.fieldLinkageItemList,

              //       defaultId: itemId,

              //       config: newItem.config,

              //     },

              //     2,

              //   );

              // }

            });

            collapseData.value[i] = collapseList;

            collapseOld.add(collapseOldList);

          });

          // 先添加数字,日期计算属性规则

          recursionGroupNew(oldList.value, (iv: any) => {

            const rule = (iv.config?.calculateRule?.numberRule || iv.config?.numberRule) ?? '';

            const ruleFirst = iv.config?.calculateRule?.first || iv.config?.first;

            const ruleLast = iv.config?.calculateRule?.last || iv.config?.last;

            if (rule || ruleFirst) {

              // 需要拿结果内容的fieldName,所以下面循环是把规则数组填充对应数据

              for (let [key, fv] of fieldOldMap) {

                // 是否存在日期规则或者数字规则

                if (

                  rule.indexOf(`${fv.fieldName}`) > -1 ||

                  fv.fieldId === ruleFirst ||

                  fv.fieldId === ruleLast

                ) {

                  fv.ruleField = `${iv.fieldName}`;

                  // 保存哪些字段被设置到计算属性字段里了,在改变值的时候需要用到

                  calculateData.cacheField.push({

                    ruleField: fv.ruleField,

                    fieldName: fv.fieldName,

                    fieldId: fv.fieldId,

                  });

                }

              }

              // 保存设置计算规则的字段, 以及规则

              calculateData.cacheRule[`${iv.fieldName}`] = rule || {

                first: ruleFirst,

                last: ruleLast,

              };

            }

          });

          // 向子元素传递改变数值和改变日期触发的方法

          const resObj = useMatchRule(calculateData, formState);

          changeInputNumber.value = resObj.changeInputNumber;

          changeDatePicker.value = resObj.changeDatePicker;

          const newList = oldList.value.map((item: any) => {

            return cloneDeep(item);

          });

          pageList.value = cloneDeep(newList);

          // 如果linkageMap,size大于0 先将部分联动信息赋到对应的字段

          if (linkageMap.size > 0) {

            for (let [key, value] of linkageMap.entries()) {

              // 如果有默认值的情况,联动扩展不会手动触发

              if (value.defaultId.length > 0) {

                selectChangeComponent(key, value.defaultId, value, 1, { isFirst: true });

              }

            }

          }

          if (collapseData.value[props.currentStep].length > 0) {

            collapseData.value[props.currentStep][0].isCurrent = true;

          }

          nextTick(() => {

            props.closeLoading ? props.closeLoading() : '';

            // const topNum = unref(anchorRef)?.offsetTop - props.topValue;

            // top.value = topNum < 0 ? 0 : topNum;

          });

          if (props.resId['actionCode'] === 'detail') {

            topVal.value = 60;

          } else {

            topVal.value = 0;

          }

          // 清除gird等容器的字段遍历,返回字段名null

          if (formState.hasOwnProperty('null_r')) {

            Reflect.deleteProperty(formState, 'null_r');

          } else if (formState.hasOwnProperty('_r')) {

            Reflect.deleteProperty(formState, '_r');

          }

        }

      });

      // 提交表单事件

      async function submitForm() {

        // 添加暂存功能 actionCode, tempSave

        if (props.resId['addTemp'] && props.resId['addTemp'] === 'tempSave') {

          let obj = {

            objectId: props.resId['objectId'] ?? '',

            objectType: props.resId['objectType'] ?? '',

            exampleId: props.resId['exampleId'] ?? '',

            deriveSourceId: props.resId['deriveSourceId'] || undefined,

            deriveSourceObjectType: props.resId['deriveSourceObjectType'] || undefined,

            isOtherObjectDerive: props.resId['isOtherObjectDerive'] || undefined,

            pageId: props.resId['pageId'],

            typeId: props.resId['typeId'] || undefined,

          };

          let objectInfo = generateFormState({

            formState,

            recordHideSave,

            keyObj,

            fieldMap,

            recordHide,

            resId: props.resId,

            tableObject,

            attachArr,

            developObject,

          });

          // 重新过滤formState

          let temp = {

            data: {

              oldData: {

                ...obj,

                objectInfo: props.resId['oldData'] ?? {},

              },

              newData: {

                ...obj,

                objectInfo,

              },

            },

          };

          // 开始做attachment 附件上传

          let attachPromise: any = [];

          if (!isEmpty(unref(attachArr))) {

            for (let i in unref(attachArr)) {

              if (recordHide[i] === '@@linkageHide') {

                continue;

              }

              let c = unref(attachArr)[i].handleUpload();

              attachPromise.push(c);

            }

            await Promise.all(attachPromise);

          }

          // 开始developComponent 自定义组件接口调用

          let devSubmitPromise: any = [];

          if (!isEmpty(unref(developObject))) {

            for (let i in unref(developObject)) {

              if (recordHide[i] === '@@linkageHide') {

                continue;

              }

              let c = unref(developObject)[i].handleSubmit

                ? unref(developObject)[i].handleSubmit(null, 'tempSave')

                : true;

              devSubmitPromise.push(c);

              let res = unref(developObject)[i].getData

                ? await unref(developObject)[i].getData()

                : null;

              if (res) {

                objectInfo[res.name] =

                  res.value && typeof res.value !== 'string'

                    ? JSON.stringify(res.value)

                    : res.value || '';

              }

            }

            await Promise.all(devSubmitPromise);

          }

          if (props.resId['isHasExample'] && temp['data']['oldData']) {

            // 带有生命周期的调用接口需要多加两个参数, 如果没有配置页面置空

            if (props.resId['noPage']) {

              temp['data'].newData['objectInfo'] = {};

            }

            temp['data'].oldData['entitySaveOrUpdateInfoDTO'] = {

              ...temp['data'].oldData,

            };

            temp['data'].newData['entitySaveOrUpdateInfoDTO'] = {

              ...temp['data'].newData,

              relationObjectType: props.resId?.oldRevData?.parentRevData?.objectType,

              relationObjectId: props.resId?.oldRevData?.parentRevData?.id,

              parentId: props.resId?.oldRevData?.clickData?.id,

            };

            if (props.resId['actionCode'] === 'derive') {

              temp['data'].newData['isEntityExample'] = false;

              temp['data'].newData['isOtherObjectDerive'] = true; //有派生转到的派生操作不会有暂存功能

            } else {

              temp['data'].newData['isEntityExample'] = true;

            }

          }

          const res = await temporarySaveWorkflow(temp);

          if (res.code === 0) {

            return true;

          } else {

            return false;

          }

        }

        // 遍历所有表格的验证,表单验证-----------暂存功能结束

        const validRes = await validForm();

        const { tableValidate, formValidte, objectInfo } = validRes;

        try {

          if (tableValidate && formValidte && !isEmpty(props.resId)) {

            let res: any = null;

            let obj = {

              objectId: props.resId['objectId'],

              objectType: props.resId['objectType'],

              pageId: props.resId['pageId'],

              exampleId: props.resId['exampleId'],

            };

            // 开始做attachment 附件上传

            let attachPromise: any = [];

            let attachValidate = false;

            if (!isEmpty(unref(attachArr))) {

              for (let i in unref(attachArr)) {

                if (recordHide[i] === '@@linkageHide') {

                  continue;

                }

                let c = unref(attachArr)[i].handleUpload();

                attachPromise.push(c);

              }

              const data = await Promise.all(attachPromise);

              attachValidate = data.every((v) => v);

            } else {

              attachValidate = true;

            }

            // 开始developComponent 自定义组件接口调用

            let devSubmitPromise: any = [];

            let developValidate = false;

            console.log('developObject', developObject);

            if (!isEmpty(unref(developObject))) {

              try {

                for (let i in unref(developObject)) {

                  if (recordHide[i] === '@@linkageHide') {

                    continue;

                  }

                  let c = unref(developObject)[i].handleSubmit

                    ? unref(developObject)[i].handleSubmit()

                    : true;

                  devSubmitPromise.push(c);

                  let res = unref(developObject)[i].getData

                    ? await unref(developObject)[i].getData()

                    : null;

                  if (res) {

                    objectInfo[res.name] =

                      res.value && typeof res.value !== 'string'

                        ? JSON.stringify(res.value)

                        : res.value || '';

                  }

                }

              } catch (e) {

                console.log('自定义组件异常', e);

              }

              console.log('devSubmitPromise', devSubmitPromise);

              const data = await Promise.all(devSubmitPromise);

              developValidate = data.every((v) => v);

            } else {

              developValidate = true;

            }

            // 自定义组件调用完成

            if (attachValidate && developValidate) {

              let temp = {};

              if (

                props.resId['actionCode'] === 'add' &&

                props.resId['operationPlace'] === 'operation'

              ) {

                temp = {

                  oldData: {

                    ...obj,

                    objectInfo: {},

                  },

                  newData: {

                    ...obj,

                    operationId: props.resId['operationId'],

                    objectInfo,

                  },

                };

              } else if (props.resId['actionCode'] === 'detail') {

                temp = {

                  oldData: {

                    ...obj,

                    objectInfo: props.resId['oldData'],

                  },

                  newData: {

                    ...obj,

                    operationId: props.resId['operationId'],

                    objectInfo,

                  },

                };

              } else if (props.resId['actionCode'] === 'parallel') {

                temp = {

                  parallelNodeId: props.resId['nodeId'],

                  objectId: obj.objectId,

                  objectInfo,

                  objectType: obj.objectType,

                  oldObjectInfo: props.resId['oldData'],

                  parallelPageId: obj.pageId,

                };

              } else if (props.resId['operationPlace'] === 'edit') {

                temp = {

                  oldData: {

                    ...obj,

                    objectInfo: props.resId['oldData'],

                  },

                  newData: {

                    ...obj,

                    objectInfo,

                  },

                };

              } else if (props.resId['actionCode'] === 'rebut') {

                // 驳回调用新的接口,传递的参数格式也不一样

                temp = {

                  objectId: obj['objectId'],

                  objectInfo,

                  objectType: obj['objectType'],

                  oldObjectInfo: props.resId['oldData'],

                  pageId: obj['pageId'],

                };

              } else if (props.resId['actionCode'] === 'derive') {

                const params: any = cloneDeep(props['resId']);

                const sendData: any = {

                  deriveSourceId: params.record['id'],

                  deriveSourceObjectType: params.deriveSourceObjectType,

                  isOtherObjectDerive: params.isOtherObjectDerive,

                  objectId: params.objectId,

                  objectType: params.objectType,

                  pageId: params.pageId,

                };

                if (props.resId['operationPlace'] === 'sure') {

                  // 这一段逻辑不知道什么时候会出现,派生exampleId是原数据exmapleId,不是deriveExampleId, 后端会根据操作id去查询

                  sendData.exampleId = params.btnInfo?.deriveExampleId;

                  sendData.operationId = params.btnInfo?.id;

                }

                if (params['hasDeriveNode']) {

                  sendData.fromNodeId = params['fromNodeId'];

                  sendData.exampleId = params['exampleId'];

                  sendData.operationId = params['operationId'];

                  sendData.isDeriveAndNotHasDeriveTo =

                    params['isDeriveAndNotHasDeriveTo'] ?? undefined;

                  sendData.deriveOperationId = params['deriveOperationId'] ?? undefined;

                }

                temp = {

                  oldData: {

                    ...obj,

                    objectInfo: props.resId['oldData'],

                    exampleId: sendData.exampleId,

                  },

                  newData: Object.assign({ objectInfo }, sendData),

                };

              }

              if (props.resId['actionCode'] === 'rebut') {

                res = await rebut(temp);

              } else if (props.resId['operationPlace'] === 'edit') {

                res = await updateWorkflowObject(temp);

              } else if (props.resId['actionCode'] === 'parallel') {

                res = await parallelHandle({ data: temp });

              } else {

                if (props.resId['isHasExample'] && temp['oldData']) {

                  // 带有生命周期的调用接口需要多加两个参数, 如果没有配置页面置空

                  if (props.resId['noPage']) {

                    temp.newData['objectInfo'] = {};

                  }

                  temp.oldData['entitySaveOrUpdateInfoDTO'] = {

                    ...temp.oldData,

                  };

                  temp.newData['entitySaveOrUpdateInfoDTO'] = {

                    ...temp.newData,

                    relationObjectType: props.resId?.oldRevData?.parentRevData?.objectType,

                    relationObjectId: props.resId?.oldRevData?.parentRevData?.id,

                    parentId: props.resId?.oldRevData?.clickData?.id,

                  };

                  if (props.resId['actionCode'] === 'derive') {

                    temp.newData['isEntityExample'] = false;

                    temp.newData['isOtherObjectDerive'] = props.resId['isOtherObjectDerive'];

                  } else {

                    temp.newData['isEntityExample'] = true;

                  }

                }

                entityObjectStore.setRequirementWorkflow(temp);

                res = await saveOrUpdateWorkflowObject(temp);

              }

              if (res.code === 0) {

                return true;

              } else {

                return false;

              }

            }

          } else {

            if (tableValidate) {

              message.error('请填写必填项');

            }

            props.closeLoading ? props.closeLoading() : '';

            return false;

          }

        } catch (e: any) {

          console.log(e);

          if (e?.message) {

            if (isJSON(e.message)) {

              let res = JSON.parse(e.message);

              if (res.handleType === 'LOCATE_TIP') {

                message.error(res.msg);

                const isError = document.querySelector(`div[data-field=${res.fieldName}]`);

                isError?.scrollIntoView({

                  block: 'center',

                  behavior: 'smooth',

                });

              }

            }

          }

        }

      }

      const handleClick = (e, link) => {

        e.preventDefault();

        const wraps: HTMLElement = document.querySelector(link.href);

        scrollIntoView(wraps, {

          block: 'start',

          inline: 'start',

          behavior: (actions) => {

            actions.forEach(({ el, top }) => {

              if (props.resId['actionCode'] === 'detail') {

                el.scrollTo({

                  top: top - unref(topVal),

                  behavior: 'smooth',

                });

              } else {

                el.scrollTo({

                  top,

                  behavior: 'smooth',

                });

              }

            });

          },

        });

      };

      const validateMessages = {

        required: '${label}是必填!',

        types: {

          email: '${label}不是有效的电子邮件!',

          number: '${label}不是一个数字!',

        },

        number: {

          range: '${label}必须在${min}和${max}之间',

        },

      };

      async function selectChangeComponent(fieldName, g, changeItem, type, other = {}) {

        const fullOther = {

          isFirst: false,

          linkageMap,

          isMounted,

          fieldMap,

          pageList,

          collapseData,

          formState,

          initValue,

          recordHide,

          recordHideSave,

          resId: props.resId,

          keyObj,

          tableObject,

          attachArr,

          developObject,

          ...other,

        };

        selectChange(fieldName, g, changeItem, type, fullOther);

      }

      async function validForm() {

        let tablePromise: any = [];

        if (!isEmpty(unref(tableObject))) {

          for (let i in unref(tableObject)) {

            if (recordHide[i] === '@@linkageHide' || recordHideSave[i] === '@@linkageHideSave') {

              continue;

            }

            let a = unref(tableObject)[i].validateTable();

            tablePromise.push(a);

          }

        }

        const res = await Promise.all(tablePromise);

        const tableValidate = res.every((v) => v);

        if (!tableValidate) {

          message.error('请填写表格必填项');

          return false;

        }

        // 遍历所有自定义组件验证

        let devPromise: any = [];

        if (!isEmpty(unref(developObject))) {

          for (let i in unref(developObject)) {

            if (recordHide[i] === '@@linkageHide' || recordHideSave[i] === '@@linkageHideSave') {

              continue;

            }

            let a = unref(developObject)[i].validate ? unref(developObject)[i].validate() : true;

            devPromise.push(a);

          }

        }

        const resD = await Promise.all(devPromise);

        const devValidate = resD.every((v) => v);

        if (!devValidate) {

          // const hasMessage = document.querySelector('.ant-message .ant-message-notice'); // 存在其他message

          // 当自定义组件内部存在message则不显示此error信息

          const hasMessage = document.querySelector('.ant-message .develop-message');

          !hasMessage && message.error('存在必填信息未填,请维护后重试!');

          return;

        }

        // 开始其它表单验证

        try {

          const validateFieldsList = [];

          recursionGroup(pageList.value[props.currentStep].children, (item) => {

            // item.isRequired && validateFieldsList.push(`${item.fieldName}_r`);

            validateFieldsList.push(`${item.fieldName}_r`);

          });

          const formValidte = await formCRef.value.validateFields(validateFieldsList);

          // const formValidte = await formCRef.value?.validate();

          let objectInfo = {};

          // 重新过滤formState

          for (let i in formState) {

            // 不可见组件跳出循环

            if (/\_r$/.test(i)) {

              let key = i.split('_r');

              if (recordHide[key[0]] === '@@linkageHide') {

                delete objectInfo[key[0]];

              } else {

                let formVal = cloneDeep(formState[i]);

                // 特殊处理多行文本的追加

                if (

                  keyObj[key[0]] === 'textarea' &&

                  recordHideSave[key[0]] !== '@@linkageHideSave'

                ) {

                  const textItem = fieldMap.get(key[0]);

                  let val = textItem[`${key[0]}_append`];

                  if (textItem.isBrandNew && textItem.isAppend) {

                    // 再判断是否需要添加用户名

                    const userName = userStore.getUserInfo.trueName;

                    const date = new Date();

                    if (formVal.trim() && textItem.isUser) {

                      // 再判断是否需要添加用户名

                      const userName = userStore.getUserInfo.trueName;

                      let compareString = `【${userName}-${

                        props.resId['nodeName']

                      }-${formatToDateTime(date)}】`;

                      let string = formVal.trim();

                      let mat = string.length - compareString.length;

                      const lastInfo = string.substr(mat, string.length);

                      if (

                        !lastInfo.includes(userName) ||

                        !lastInfo.includes(props.resId['nodeName'])

                      ) {

                        formVal = `${formVal}【${userName}-${

                          props.resId['nodeName']

                        }-${formatToDateTime(date)}】`;

                      }

                    }

                    if (val && !textItem['isSpecailAppend']) {

                      formVal =

                        formVal.trim() && textItem.isUser

                          ? `${val}\n${formVal}`

                          : formVal

                          ? `${val}\n${formVal}【${formatToDateTime(date)}】`

                          : val;

                    } else {

                      formVal =

                        formVal.trim() && textItem.isUser

                          ? formVal

                          : formVal

                          ? `${formVal}【${formatToDateTime(date)}】`

                          : formVal;

                    }

                  }

                }

                let refObject: any = null;

                if (keyObj[key[0]] === 'table') {

                  refObject = tableObject;

                } else if (keyObj[key[0]] === 'upload') {

                  refObject = attachArr;

                } else if (keyObj[key[0]] === 'related' || keyObj[key[0]] === 'develop') {

                  refObject = developObject;

                }

                const res = useDataSave(

                  keyObj,

                  key[0],

                  formVal,

                  refObject,

                  recordHideSave[key[0]] === '@@linkageHideSave',

                );

                objectInfo[key[0]] = res;

              }

            }

          }

          return {

            tableValidate,

            formValidte,

            objectInfo,

          };

        } catch (e: any) {

          if (e?.errorFields?.length > 0) {

            nextTick(() => {

              setTimeout(() => {

                const isError = document.getElementsByClassName('ant-form-item-explain-error');

                isError[0].scrollIntoView({

                  block: 'center',

                  behavior: 'smooth',

                });

              }, 10);

            });

          }

          console.error('提交报错', e);

          return {};

        }

      }

      function getFormStateData() {

        return generateFormState({

          formState,

          recordHideSave,

          keyObj,

          fieldMap,

          recordHide,

          resId: props.resId,

          tableObject,

          attachArr,

          developObject,

        });

      }

      return {

        pageList,

        filterOption,

        formState,

        collapseForm,

        tableRef,

        attachRef,

        compRef,

        t,

        collapseData,

        oldList,

        submitForm,

        formCRef,

        multiUpload,

        handleClick,

        topVal,

        pageCode,

        validateMessages,

        selectChangeComponent,

        recordHide,

        getFormClass,

        changeInputNumber,

        changeDatePicker,

        anchorRef,

        top,

        currentLinkageField,

        validForm,

        fieldMap,

        recordHideSave,

        isMounted,

        getFormStateData,

      };

    },

  });

</script>

<style lang="less" scoped>

  .render-form {

    .ant-form-item {

      margin-bottom: 15px;

    }

  }

  ::v-deep(.ant-anchor-ink) {

    left: 9px;

  }

</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值