Lighthouse 和 Performance 面板 都是 Chrome 开发者工具中用于分析页面性能的工具,但它们的功能和定位有所不同。以下是对两者的详细对比,以及为什么需要同时使用它们。
1. 功能定位
(1)Lighthouse
-
定位:
Lighthouse 是一个 自动化审计工具,专注于提供全面的性能分析报告和优化建议。 -
功能:
分析页面的性能、可访问性、最佳实践、SEO 和 PWA 等方面,生成详细的报告。 -
目标用户:
开发者、SEO 专家、产品经理等,希望快速了解页面整体表现并获取优化建议。
(2)Performance 面板
-
定位:
Performance 面板是一个 性能分析工具,专注于深入分析页面加载和运行时的性能瓶颈。 -
功能:
记录页面加载过程,展示主线程活动、网络请求、渲染时间等详细信息。 -
目标用户:
开发者,尤其是前端工程师,希望深入分析性能问题并优化代码。
2. 功能对比
功能 | Lighthouse | Performance 面板 |
---|---|---|
性能分析 | 提供关键性能指标(如 LCP、FID、CLS) | 展示详细的性能时间线(如 FCP、LCP) |
优化建议 | 提供具体的优化建议(如减少未使用的 JS) | 无直接优化建议,需手动分析 |
可访问性分析 | 支持 | 不支持 |
SEO 分析 | 支持 | 不支持 |
最佳实践分析 | 支持 | 不支持 |
PWA 分析 | 支持 | 不支持 |
实时记录 | 不支持 | 支持 |
主线程活动分析 | 不支持 | 支持 |
网络请求分析 | 不支持 | 支持 |
渲染时间分析 | 不支持 | 支持 |
3. 使用场景
(1)Lighthouse 的使用场景
-
快速获取优化建议:
当你需要快速了解页面的性能问题并获取具体的优化建议时,可以使用 Lighthouse。 -
全面审计:
当你需要分析页面的可访问性、SEO、最佳实践等方面时,Lighthouse 是更好的选择。 -
报告生成:
当你需要生成一份详细的性能报告(如给团队或客户)时,Lighthouse 提供了更友好的报告格式。
(2)Performance 面板的使用场景
-
深入分析性能瓶颈:
当你需要深入分析页面加载过程中的性能瓶颈(如长任务、渲染时间)时,Performance 面板更合适。 -
实时调试:
当你需要实时记录页面加载过程并分析具体问题时,Performance 面板是必备工具。 -
代码优化:
当你需要优化 JavaScript 执行、减少主线程阻塞时,Performance 面板提供了更详细的数据。
4. 为什么需要同时使用?
-
Lighthouse 提供了 全面的审计报告和优化建议,适合快速定位问题和制定优化策略。
-
Performance 面板 提供了 详细的性能数据和实时调试能力,适合深入分析和优化代码。
示例场景
-
使用 Lighthouse 发现问题:
-
运行 Lighthouse,发现 LCP 时间过长。
-
报告建议优化图片加载和减少 JavaScript 阻塞。
-
-
使用 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>