import { ButtonPosition } from '@/type/button-position'; import uniq from 'lodash/uniq'; import qs from 'query-string'; import * as cnUniApi from '@alife/cn-uni-api'; import { calculateTextExprValue, executeFunction, executeObjectExpr, getArrayTableFieldList, getBizComponent, getBizComponentNameList, getDataSourceList, getDataSourceListWithAllPath, getNodeById, getRealizeValue, getRealRequestUrl, getRealResponse, getRunTimeBizComponent, handleI18nLabel, handlePrototypeCondition, isArrayNotEmpty, isRecursionComponent, uuid, } from '../util/util'; import { createServiceSelectPrototype, handleRequestParams, makeRequest, } from './request'; import { createTableSelectSetters, getFormHandleTypeSetterSnippet, getSelectDialogSnippet, getSetterSnippet, } from './setter-snippet'; import { getJSExpressionPrototype, getStyleListByPosition, } from '@/common/manager/common-style'; import CheckboxSetter from '@/common/setter/checkbox-setter'; import ExprSetter from '@/common/setter/expr-setter'; import { __arrayTableCurrentRow__, __dataSource__, __flowData__, getCommonBoolDataSource, } from '@/common/util/expr-const'; import isPlainObject from 'lodash/isPlainObject'; import { ActionRunEnv } from '@/type/action-run-env'; import { ParamSelectSetter } from '@/common/setter/param-select-setter'; import cloneDeep from 'lodash/cloneDeep'; import { pageDataSource } from '@/common/util/const'; import { CnDataSourceChange, emitEvent } from '@/common/util/event-name'; import { createMessageSetters } from '@/common/manager/message'; import isNumber from 'lodash/isNumber'; import { CnMessage } from '@cainiaofe/cn-ui-m'; import findLastIndex from 'lodash/findLastIndex'; const CnUniApi = window.cnUniApi || cnUniApi; // 兼容:优先用户script注入的cnUniApi const { ActionSetter } = window.VisualEngineUtils || {}; const ALL_BUTTON_POSITION_LIST = Object.values(ButtonPosition); const jsActionMap = { [ButtonPosition.navBarEvent]: `/** * onBack * */ function onBack(state) { }`, [ButtonPosition.navBarRightButton]: `/** * onClick * */ function onClick(state) { }`, [ButtonPosition.form]: `/** * onClick * */ function onClick({ form, state}) { }`, [ButtonPosition.formDialog]: `/** * onClick * */ function onClick({ form, state}) { }`, }; const jsAction = { title: 'js脚本', value: 'jsAction', position: ALL_BUTTON_POSITION_LIST, getPrototypeList: (position, config) => { const { __field } = config || {}; if ( [ ButtonPosition.navBarEvent, ButtonPosition.navBarRightButton, ButtonPosition.form, ButtonPosition.formDialog, ].includes(position) ) { let defaultActionName = 'onBack'; if ( [ ButtonPosition.form, ButtonPosition.formDialog, ButtonPosition.navBarRightButton, ].includes(position) ) { defaultActionName = 'onClick'; } return [ { title: '自定义脚本', name: 'jsFunction', display: 'inline', className: 'cn-action-setter', setter: { componentName: , props: () => { const defaultCode = jsActionMap[position]; return { supportTypes: ['page'], defaultCode, defaultActionName, }; }, }, }, ]; }else { return [ { title: '自定义脚本', name: 'jsFunction', // isRequired: true, // editable:true, display: 'inline', // condition(){ // return true; // }, className: 'cn-action-setter', // setter: , setter: { componentName: ( ), props: () => { const defaultCode = jsActionMap[position]; const defaultActionName = 'jsAction'; return { supportTypes: ['page'], defaultCode, defaultActionName, }; }, }, }, ]; } }, action: (config) => { const { buttonConfig, _context, position, jsParamList = [] } = config; const { name, options } = buttonConfig; const { jsFunction } = options || {}; if (typeof jsFunction === 'function') { if ( [ ButtonPosition.navBarEvent, ButtonPosition.filterItemEvent, ButtonPosition.formItemEvent, ButtonPosition.formDialogItemEvent, ].includes(position) ) { if (name === 'onBack') { return jsFunction(...jsParamList); } } else if ( [ ButtonPosition.navBarRightButton, ButtonPosition.form, ButtonPosition.formDialog, ].includes(position) ) { return jsFunction(...jsParamList); }else { async function executeJsFunction() { await jsFunction(); } return executeJsFunction(); } } }, }; const link = { title: '跳转链接', value: 'link', position: ALL_BUTTON_POSITION_LIST, getDefaultProps: () => { return { redirectType: 'open', routerType: 'location', }; }, getPrototypeList: (position, config) => { let paramSetter = 'StringSetter'; let urlSetter = 'StringSetter'; const setterSnippet = getSetterSnippet({ position, optType: 'link', ...config, }); const { urlSetter: newUrlSetter, requestParamSetter } = setterSnippet || {}; if (requestParamSetter) { paramSetter = requestParamSetter; } if (newUrlSetter) { urlSetter = newUrlSetter; } return [ { title: '路由类型', name: 'routerType', condition() { return false; }, originalCondition() { return false; }, description: '路由类型', setter: 'StringSetter', }, { title: '链接地址', name: 'url', // condition: { // "type": "JSFunction", // "value": "() => false" // }, description: '链接地址', className: 'cn-link-url-setter', setter: urlSetter, }, { title: '打开方式', name: 'redirectType', description: '打开方式', setter: { componentName: 'RadioGroupSetter', initialValue: 'current', props: { options: [ { title: '当前页面', value: 'current', }, { title: '新开页面', value: 'open', }, ], }, }, }, { title: 'url参数', name: 'urlParams', description: 'url参数', // extraProps: { // }, className: 'cn-button-url-param-setter', setter: { componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', // initialValue: { // "type": "JSFunction", // "value": "() => {return {primaryKey: String(Math.floor(Math.random() * 10000)),children: \"Title\",optType:\"link\" };}" // }, initialValue: { label: 'param', }, props: { config: { items: [ { title: '参数名', name: 'label', isRequired: true, description: '参数名', setter: 'StringSetter', }, // { // title: '参数值', // name: 'value', // isRequired: true, // description: '参数值', // supportVariable: true, // setter: 'StringSetter', // }, { title: '参数值', name: 'value', isRequired: true, description: '参数值', // supportVariable: true, className: 'cn-param-select-setter', setter: paramSetter, }, ], }, }, }, }, }, }, ]; }, action: (config, extra) => { const { buttonConfig, urlParamsDataSource, recordDataSource: originalRecordDataSource, state, position, arrayTableConfig, selectedRowKeys = [], selectedRowRecords = [], } = config; const needDelay = position === ButtonPosition.formDialogSubmitAfterRequestSuccess || position === ButtonPosition.formSubmitAfterRequestSuccess; const { options } = buttonConfig || {}; let extraParamList = []; let recordDataSource = getRealizeValue(originalRecordDataSource); if (position === ButtonPosition.arrayTableOperate) { if (extra?.field) { const { field } = extra; const { formItemConfig } = arrayTableConfig || {}; if (formItemConfig?.name && field) { const list = field?.query?.(formItemConfig?.name)?.value?.(); if (Array.isArray(list) && list[field?.index]) { recordDataSource = list[field.index]; extraParamList = [field.index]; } } } } else { extraParamList.push(selectedRowKeys); extraParamList.push(selectedRowRecords); } if (options) { const { url, redirectType, urlParams = [] } = options; let search = ''; if (urlParams && urlParams.length > 0) { const temp = handleRequestParams(urlParams, { urlParamsDataSource, recordDataSource, state, extraParamList, }); search = qs.stringify(temp); } if (url) { let realUrl = calculateTextExprValue(url, { urlParamsDataSource, recordDataSource, state, extraParamList, }); if (typeof realUrl === 'string' && realUrl.length > 0) { if (realUrl.indexOf('?') !== -1) { realUrl += `&${search}`; } else if (search) { realUrl += `?${search}`; } const temp = () => { if (redirectType === 'current') { if (typeof window.jumpTo === 'function') { const tempList = realUrl?.split('?'); window.jumpTo({ pathname: tempList?.[0], search: tempList?.[1], }); } else { location.href = realUrl; } } else if (redirectType === 'open') { if (typeof window.jumpTo === 'function') { const tempList = realUrl?.split('?'); window.jumpTo({ pathname: tempList?.[0], search: tempList?.[1], type: '_blank', }); } else { window.open(realUrl); } } }; if (needDelay) { setTimeout(temp, 1500); } else { temp(); } } } } }, // getButtonNode: (arg) => { // const { buttonConfig, record, index } = arg; // const { optType, options = {}, children } = buttonConfig; // if (children) { // let events = {}; // if (optType) { // const button = componentMap[optType]; // if (typeof button?.action === 'function') { // events = { // onClick: button.action.bind(this, arg), // }; // } // } // return {children}; // } // return null; // }, }; const message = { title: '提示信息(toast)', value: 'message', position: ALL_BUTTON_POSITION_LIST, getPrototypeList: (position, config) => { return createMessageSetters({ position, isToast: true, config }); }, action: (config) => { const { buttonConfig, urlParamsDataSource, state, _context, recordDataSource, actionRunEnv, getFlowActionInfo, position, } = config || {}; let { response = {} } = config || {}; if (ActionRunEnv.flowAction === actionRunEnv) { const flowInfo = getFlowActionInfo?.(); const { flowList, currentFlowIndex } = flowInfo || {}; if (isArrayNotEmpty(flowList) && currentFlowIndex) { const realIndex = +currentFlowIndex; if (isNumber(realIndex) && !isNaN(realIndex)) { const beforeList = flowList.slice(0, realIndex); // const lastRequestIndex = beforeList.findLastIndex(item=>item?.optType === 'request'); const lastRequestIndex = findLastIndex( beforeList, (item) => item?.optType === 'request', ); let path = 'request'; if (lastRequestIndex !== 0) { path += lastRequestIndex; } const realResponse = state?.valueOf?.[path]; if (isPlainObject(realResponse)) { response = realResponse; } } } } const msg = response?.msg; const { options = {} } = buttonConfig || {}; const { type, title, content, ...rest } = options; let realContent = content; const realType = calculateTextExprValue(type, { urlParamsDataSource, recordDataSource: getRealizeValue(recordDataSource), state, }) || 'success'; if (typeof content === 'function') { let realFirstParam = response; if ( [ ButtonPosition.formItemEvent, ButtonPosition.formDialogItemEvent, ].includes(position) ) { realFirstParam = getRealizeValue(recordDataSource); } realContent = content(realFirstParam, state); } if (!realContent && !title) { realContent = msg; } CnMessage?.[realType]?.({ title, content: realContent, ...rest }); }, }; const submit = { title: '提交(先校验表单,再发请求)', value: 'submit', position: [ButtonPosition.formDialog, ButtonPosition.form], getPrototypeList: (position) => { let paramSetter = 'StringSetter'; let paramSelectSetter; let jsExpressionSetter; const setterSnippet = getSetterSnippet({ position, optType: 'submit', }); const { requestParamSetter } = setterSnippet || {}; if (requestParamSetter) { paramSetter = requestParamSetter; if (Array.isArray(requestParamSetter?.props?.setters)) { paramSelectSetter = requestParamSetter?.props?.setters?.find( (item) => item?.componentName === 'ParamSelectSetter', ); jsExpressionSetter = requestParamSetter?.props?.setters?.find( (item) => item?.componentName === 'CnRowDataSetter', ); } } const result = [ ...createServiceSelectPrototype({ optType: 'submit', paramSetter, position, paramSelectSetter, jsExpressionSetter, paramTitleDom: (
请求参数配置{' '} 提示:提交时会默认带上当前表单的全部数据作为参数,无需手动配置
), }), ]; if ([ButtonPosition.formDialog].includes(position)) { result.push({ name: 'afterRequestNotCloseDialog', title: '请求成功后不关闭弹窗', setter: 'BoolSetter', }); } return result; }, action: (config) => { const { buttonConfig, formInstance, position, close, urlParamsDataSource, recordDataSource, state, _context, dialogRef, needSuccessToast: paramNeedSuccessToast, getExtraParam, } = config; const realFormInstance = getRealizeValue(formInstance); if (buttonConfig && realFormInstance) { if (realFormInstance?.submitting) { } else { let afterRequestPosition; if (ButtonPosition.formDialog === position) { afterRequestPosition = ButtonPosition.formDialogSubmitAfterRequestSuccess; } else if (ButtonPosition.form === position) { afterRequestPosition = ButtonPosition.formSubmitAfterRequestSuccess; } const { options } = buttonConfig || {}; const { afterRequest = {}, requestConfig = {}, afterRequestNotCloseDialog, } = options || {}; const realUrl = getRealRequestUrl({ requestConfig, state, extraParam: getExtraParam?.(), }); if (realUrl) { requestConfig.url = realUrl; requestConfig.dynamicUrl = undefined; requestConfig.useDynamicUrl = undefined; return realFormInstance?.submit((value) => { if (value) { let needSuccessToast = true; if (paramNeedSuccessToast !== undefined) { needSuccessToast = paramNeedSuccessToast; } if (afterRequestPosition && afterRequest?.optType === 'message') { needSuccessToast = false; } return makeRequest({ buttonConfig, urlParamsDataSource, recordDataSource: value, state, needSuccessToast, getExtraParam, handleParams: () => { return value; }, onCancel: () => { if (realFormInstance?.setSubmitting) { realFormInstance.setSubmitting(false); } }, extraParamList: [], }) .then((res) => { if (afterRequestNotCloseDialog !== true) { close && close(); } return res; }) .finally((res) => { if (realFormInstance?.setSubmitting) { realFormInstance.setSubmitting(false); } }); } }); } else { return realFormInstance?.validate?.(); } } } }, }; const reset = { title: '重置', value: 'reset', position: [ButtonPosition.formDialog, ButtonPosition.form], action: (config) => { const { buttonConfig, formInstance, position } = config; const realFormInstance = getRealizeValue(formInstance); if (buttonConfig && realFormInstance) { realFormInstance.reset('*'); } }, }; const setFormValue = { title: '给表单设置数据', value: 'setFormValue', position: ALL_BUTTON_POSITION_LIST, getDefaultProps: () => { return { mode: 'merge', }; }, getPrototypeList: (position, config) => { const { flowList, currentFlowIndex } = config || {}; return [ ...createFormSelectSetters({ mode: 'single', }), getFormHandleTypeSetterSnippet(), // { // name:'mode', // title:'新数据填入表单的方式', // setter: { // componentName: 'RadioGroupSetter', // props: { // options: [ // { // title: '和原表单值合并', // value: 'merge', // }, // { // title: '清空表单重新设值', // value: 'overwrite', // }, // ], // }, // }, // }, { name: 'formValues', title: '要填入表单的数据', className: 'cn-button-url-param-setter', condition(prop) { return ( prop?.parent?.getPropValue?.('handleType') === 'setFormFieldValue' ); }, originalCondition(prop) { return ( prop?.parent?.getPropValue?.('handleType') === 'setFormFieldValue' ); }, setter: { title: '给表单每个字段设置数据', componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', // initialValue: { // }, props: { config: { items: [ { name: 'label', display: 'inline', title: '选择字段', isRequired: true, setter: { componentName: 'CnTreeSelectSetter', props: (field) => { const dataSource = []; const currentData = field?.parent?.parent?.parent?.getValue?.(); const { _bindForm } = currentData || {}; if (_bindForm && field) { const node = getNodeById({ field, id: _bindForm, }); if (node?.id) { const config = node?.getPropValue?.('config'); if (Array.isArray(config) && config.length > 0) { config.forEach((item) => { if (item?.name) { const temp = { label: handleI18nLabel(item?.label) || item.name, value: item.name, }; dataSource.push(temp); if ( isRecursionComponent(item?.componentName) ) { const children = getArrayTableFieldList({ config: item?.options?.config, arrayTableName: item.name, }); if (isArrayNotEmpty(children)) { temp.children = children; } } } }); } } } return { dataSource, popupClassName: 'cn-tree-select-setter-pupup', }; }, }, }, { name: 'value', display: 'inline', title: '数据', isRequired: true, className: 'cn-param-select-setter', setter: { componentName: 'MixedSetter', props: { setters: [ { componentName: 'ParamSelectSetter', props: { ParamSelectSetterComponent: ParamSelectSetter, configList: [ { groupName: '其他数据', groupExprName: __dataSource__, needSecondParam: true, }, { groupName: '动作编排的数据', groupExprName: __flowData__, needSecondParam: true, flowList, currentFlowIndex, }, ], }, title: '从数据源选择', }, getJSExpressionPrototype({ type: 'formRequest', prototypeConfig: config, }), ], }, }, }, ], }, }, }, }, }, }, { name: 'allFormValues', display: 'inline', condition(prop) { return ( prop?.parent?.getPropValue?.('handleType') === 'setAllFormValue' ); }, originalCondition(prop) { return ( prop?.parent?.getPropValue?.('handleType') === 'setAllFormValue' ); }, title: '新的表单数据', className: 'cn-param-select-setter', // className: 'cn-button-url-param-setter', setter: { componentName: 'MixedSetter', props: { setters: [ { componentName: 'ParamSelectSetter', props: { configList: [ { groupName: '其他数据', groupExprName: __dataSource__, needSecondParam: true, }, { groupName: '动作编排的数据', groupExprName: __flowData__, needSecondParam: true, flowList, currentFlowIndex, }, ], }, title: '从数据源选择', }, { componentName: 'StringSetter', title: '字符串', }, { componentName: 'NumberSetter', title: '数字', }, getJSExpressionPrototype({ type: 'formRequest', prototypeConfig: config, }), ], }, }, }, ]; }, action: (config) => { const { buttonConfig, urlParamsDataSource, state, _context, field, position, arrayBaseFieldIndex, } = config || {}; const { options } = buttonConfig || {}; const { formValues, _bindForm, handleType, allFormValues } = options || {}; if (_bindForm) { const formNode = _context?.$(_bindForm); const formInstance = formNode?.getFormInstance?.(); if (formInstance) { const oldFormValue = cloneDeep(formInstance?.values); if (handleType === 'clearFormValue') { formInstance.setValues?.({}, 'overwrite'); } else if (handleType === 'setAllFormValue') { const newFormValue = calculateTextExprValue(allFormValues, { urlParamsDataSource, recordDataSource: oldFormValue, state, }); if (isPlainObject(newFormValue)) { formInstance.setValues?.(newFormValue, 'overwrite'); } } else { let newFormValue; if (Array.isArray(formValues)) { if (formValues.length > 0) { const arrayTableValues = []; const notArrayTableValues = []; formValues.forEach((item) => { if (item?.label?.includes(__arrayTableCurrentRow__)) { arrayTableValues.push(item); } else { notArrayTableValues.push(item); } }); const temp = handleRequestParams(notArrayTableValues, { urlParamsDataSource, recordDataSource: oldFormValue || {}, state, }); if (isArrayNotEmpty(arrayTableValues)) { let fieldIndex; if (position === ButtonPosition.arraySubAreaCardOperate) { fieldIndex = arrayBaseFieldIndex; } else if (field) { fieldIndex = field?.path?.segments?.[1]; } if (typeof fieldIndex === 'number') { const tempFormValue = { ...oldFormValue, ...temp }; arrayTableValues.forEach((aitem) => { const { label, value } = aitem || {}; const arr = label?.split?.(`.${__arrayTableCurrentRow__}.`); if (arr.length === 2) { const fieldName = arr[0]; const arrayTableFieldName = arr[1]; const temp2 = handleRequestParams([aitem], { urlParamsDataSource, recordDataSource: tempFormValue, state, }); if ( fieldName && arrayTableFieldName && tempFormValue[fieldName]?.[fieldIndex] ) { tempFormValue[fieldName][fieldIndex][ arrayTableFieldName ] = temp2[label]; temp[fieldName] = tempFormValue[fieldName]; } } }); } } if (isPlainObject(temp)) { newFormValue = temp; } } } formInstance.setValues?.(newFormValue); } } } return Promise.resolve(); }, }; const arrayTableAdd = { title: '添加一行', value: 'arrayTableAdd', position: [ButtonPosition.arrayTableOperate], getPrototypeList: (position) => { return []; }, action: ({ buttonConfig, formData, open, close }) => {}, }; const flowAction = { title: '动作编排', value: 'flowAction', position: ALL_BUTTON_POSITION_LIST, getPrototypeList: (position) => { return [ { name: 'flowList', title: '动作编排', display: 'plain', setter: createFlowListSetters({ position, activeSetter: { componentName: 'MixedSetter', props: { setters: [ { componentName: , title: '启用/禁用', }, { componentName: ( ), props(field) { const flowList = field?.parent?.parent?.getValue?.(); const index = field?.parent?.name; return { configList: [ { groupName: '动作编排的数据', groupExprName: __flowData__, needSecondParam: true, flowList, currentFlowIndex: index, }, { groupName: '其他数据', groupExprName: __dataSource__, needSecondParam: true, }, ], }; }, title: '简单表达式', }, { title: '写js表达式', componentName: 'CnRowDataSetter', props(field) { const stateList = getDataSourceListWithAllPath({ needChildren: true, prototypeConfig: { flowList: field?.parent?.parent?.getValue?.(), currentFlowIndex: field?.parent?.name, }, }); return { list: stateList, initialCode: `(arg, state) => { // return state.urlParams.xxx; }`, tip: ` state:全部的数据,在左侧列表中选择使用。 `, }; }, }, ], }, }, }), }, ]; }, action: (config, ...otherArgs) => { const { buttonConfig, position, _context } = config || {}; const { options } = buttonConfig || {}; const { flowList } = options; if (Array.isArray(flowList) && flowList.length > 0) { async function executeFlowList(list) { const result = []; for (const index in list) { const item = list[index]; const { active, optType, options: itemOptions } = item || {}; const isActive = executeObjectExpr( active, { [__dataSource__]: _context?.state, }, {}, _context?.state, ); if (isActive === true) { let action = getButtonAction({ ...item, position, }); if (!action) { const componentDefine = getRunTimeItem(optType); const component = getRealizeValue(componentDefine?.component); if (typeof component?.action === 'function') { action = component.action; } } let flowResult; if (typeof action === 'function') { flowResult = await action( { needSuccessToast: false, ...config, buttonConfig: { ...item }, // 动作执行的环境 actionRunEnv: 'flowAction', getFlowActionInfo: () => { return { flowList, currentFlowIndex: index, }; }, }, ...otherArgs, ); } const handleFlowResult = getItem( position, optType, 'handleFlowResult', ); if (typeof handleFlowResult === 'function') { flowResult = handleFlowResult(flowResult); } if (flowResult && _context) { let realIndex = +index; if (realIndex === 0) { realIndex = ''; } _context.state.valueOf[optType + realIndex] = flowResult; } result.push(flowResult); } } return result; } if (_context?.state) { const flowDataKeys = Object.keys(_context.state.valueOf); if (Array.isArray(flowDataKeys) && flowDataKeys.length > 0) { flowDataKeys.forEach((key) => { if (key) { if (key.includes('$$$') || key.startsWith('Cn')) { } else { _context.state.valueOf[key] = undefined; } } }); } } executeFlowList(flowList); // try { // }catch (e){ // console.log('flow action run error', e); // } } }, }; const request = { title: '发请求', value: 'request', position: ALL_BUTTON_POSITION_LIST, getPrototypeList: (position, config) => { let paramSetter = 'StringSetter'; let paramSelectSetter; let jsExpressionSetter; let wholeParamSetter; const setterSnippet = getSetterSnippet({ position, optType: 'request', ...config, }); const { requestParamSetter } = setterSnippet || {}; if (requestParamSetter) { paramSetter = requestParamSetter; if (Array.isArray(requestParamSetter?.props?.setters)) { paramSelectSetter = requestParamSetter?.props?.setters?.find( (item) => item?.componentName === 'ParamSelectSetter', ); jsExpressionSetter = requestParamSetter?.props?.setters?.find( (item) => item?.componentName === 'CnRowDataSetter', ); } } if ([ButtonPosition.tableOperate].includes(position)) { wholeParamSetter = { name: 'params', title: '请求参数配置', className: 'params-list', setter: { componentName: 'MixedSetter', props: { setters: [ { title: '设置每个字段的值', componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', initialValue: {}, props: { config: { items: [ { name: 'label', isRequired: true, title: '参数名', setter: 'StringSetter', }, { name: 'value', isRequired: true, title: '参数值', setter: paramSetter, }, ], }, }, }, }, }, jsExpressionSetter, ], }, }, }; } return createServiceSelectPrototype({ optType: 'request', paramSetter, position, paramSelectSetter, jsExpressionSetter, wholeParamSetter, }); }, action: (config, extra) => { let { buttonConfig = {}, record, componentProps = {}, position, tableRef, state, urlParamsDataSource, recordDataSource, needSuccessToast, extraParamList, arrayTableConfig, noNeedHandleResult, isDesign, _context, field, formInstance, actionRunEnv, getExtraParam, arrayTableCurrentRow, arrayBaseFieldIndex, } = config; const { options = {} } = buttonConfig; const { keepPagination } = options; if (position === ButtonPosition.arraySubAreaCardOperate) { recordDataSource = arrayTableCurrentRow; extraParamList = [arrayBaseFieldIndex]; } else if (position === ButtonPosition.arrayTableOperate) { if (extra?.field) { const { field } = extra; const { formItemConfig } = arrayTableConfig || {}; if (formItemConfig?.name && field) { const list = field?.query?.('..')?.value?.(); if (Array.isArray(list) && list[field?.index]) { recordDataSource = list[field.index]; if (!extraParamList) { extraParamList = [field.index]; } } } } } const realField = field || extra?.field; return makeRequest({ buttonConfig, urlParamsDataSource, recordDataSource: getRealizeValue(recordDataSource), state, needSuccessToast, noNeedHandleResult, extraParamList, position, isDesign, _context, getExtraParam: () => { const tempExtraParam = executeFunction(getExtraParam); let extraParam = {}; if (isPlainObject(tempExtraParam)) { extraParam = { ...tempExtraParam, }; } // const realFormInstance = getRealizeValue(formInstance); // if(realFormInstance && field && position === ButtonPosition.formItemEvent) { // const tempFormValue = realFormInstance.values; // if(tempFormValue) { // const segments = field?.path?.segments; // if(isArrayNotEmpty(segments) && segments.length >= 3) { // const needSegments = segments.slice(0,2) // const tempCurrentRow = get(tempFormValue, needSegments) // if(isPlainObject(tempCurrentRow)) { // extraParam[__arrayTableCurrentRow__] = tempCurrentRow; // } // } // // } // } const realFormInstance = getRealizeValue(formInstance); if ( realFormInstance && realField && [ ButtonPosition.formItemEvent, ButtonPosition.arraySubAreaCardOperate, ].includes(position) ) { const realPath = field?.path?.parent?.()?.segments; if (ButtonPosition.arraySubAreaCardOperate === position) { } const tempCurrentRow = realFormInstance?.getValuesIn?.( field?.path?.parent?.()?.segments, ); if (isPlainObject(tempCurrentRow)) { extraParam[__arrayTableCurrentRow__] = tempCurrentRow; } } return extraParam; }, handleParams: () => { const params = {}; if (position === ButtonPosition.tableOperate) { const { primaryKey } = componentProps; if (primaryKey && record[primaryKey] !== undefined) { params[primaryKey] = record[primaryKey]; } } return params; }, }).then((res) => { if (position !== ButtonPosition.tableToolArea) { if (actionRunEnv === ActionRunEnv.flowAction) { } else if (position === ButtonPosition.tableOperate) { if (keepPagination === true) { tableRef?.tableLoad()?.(); } else { tableRef?.load?.(); } } else { tableRef?.load?.(); } } return res; }); }, handleFlowResult: (res) => { return getRealResponse(res); }, // getButtonNode: (arg) => { // const { buttonConfig, record, index } = arg; // const { optType, options = {}, children } = buttonConfig; // if (children) { // let events = {}; // if (optType) { // const { needConfirm, confirmInfo = {} } = options; // const button = componentMap[optType]; // if (typeof button?.action === 'function') { // const action = button.action.bind(this, arg); // events = { // onClick: needConfirm ? () => { // Dialog.confirm({ // v2: true, // title: confirmInfo.title || '通知', // content: confirmInfo.content || '是否确认', // onOk: action, // cancelProps: { // children: '关闭弹框', // }, // }); // } : action, // }; // } // } // return {children}; // } // return null; // }, }; const arrayTableRemove = { title: '删除', value: 'arrayTableRemove', position: [ButtonPosition.arrayTableOperate], action: (config) => {}, }; const setDataSourceValue = { title: '给页面的数据源设置数据', value: 'setDataSourceValue', position: ALL_BUTTON_POSITION_LIST, getDefaultProps: () => { return {}; }, getPrototypeList: (position, config) => { const { flowList, currentFlowIndex } = config || {}; return [ { name: 'dataSourceValues', title: '给数据源设置数据', className: 'cn-button-url-param-setter', setter: { componentName: 'MixedSetter', props: { setters: [ { title: '给数据源设置数据', componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', // initialValue: { // }, props: { config: { items: [ { name: 'label', display: 'inline', title: '选择页面数据源', isRequired: true, setter: { componentName: 'SelectSetter', props: (field) => { const options = getDataSourceList({ typeList: ['VALUE'], })?.filter( (item) => item?.dataSourceId && item.dataSourceId?.startsWith?.( pageDataSource, ), ); return { options: options || [], }; }, }, }, { name: 'value', display: 'inline', title: '数据', isRequired: true, className: 'cn-param-select-setter', setter: { componentName: 'MixedSetter', props: { setters: [ { componentName: 'ParamSelectSetter', props: { configList: [ { groupName: '其他数据', groupExprName: __dataSource__, needSecondParam: true, }, { groupName: '动作编排的数据', groupExprName: __flowData__, needSecondParam: true, flowList, currentFlowIndex, }, ], }, title: '从数据源选择', }, { componentName: 'StringSetter', title: '字符串', }, { componentName: 'NumberSetter', title: '数字', }, getJSExpressionPrototype({ type: 'formRequest', prototypeConfig: config, }), ], }, }, }, ], }, }, }, }, }, // getJSExpressionPrototype({type:'base'}) ], }, }, }, ]; }, action: (config) => { const { buttonConfig, urlParamsDataSource, state, _context, recordDataSource, } = config || {}; const { options } = buttonConfig || {}; const { dataSourceValues } = options || {}; if (Array.isArray(dataSourceValues) && dataSourceValues.length > 0) { const temp = handleRequestParams(dataSourceValues, { urlParamsDataSource, recordDataSource: getRealizeValue(recordDataSource) || {}, state, }); if (isPlainObject(temp) && Object.keys(temp).length > 0) { _context?.setState?.(temp); emitEvent(CnDataSourceChange); } } return Promise.resolve(); }, }; const dialog = { title: '弹窗', value: 'dialog', position: ALL_BUTTON_POSITION_LIST, action: (config, extra) => { const { arrayTableConfig, buttonConfig, _context, position, componentProps, record, index, selectedRowKeys, selectedRowRecords, response, arrayTableCurrentRow, } = config || {}; let recordDataSource = record; if (position === ButtonPosition.arraySubAreaCardOperate) { recordDataSource = arrayTableCurrentRow; } else if (!record && extra?.field) { const { field } = extra; const { formItemConfig } = arrayTableConfig || {}; if (formItemConfig?.name && field) { if (field.path.segments.length === 5) { const path = field.path.segments.slice(0, 4); const tempRow = field?.form?.getValuesIn?.(path); if (isPlainObject(tempRow)) { recordDataSource = cloneDeep(tempRow); } } else { const list = field?.query?.(formItemConfig?.name)?.value?.(); if (Array.isArray(list) && list[field?.index]) { recordDataSource = cloneDeep(list[field.index]); } } } } if (position === ButtonPosition.tableCellAfterRequestSuccess) { recordDataSource = response?.data || {}; } if (buttonConfig && _context) { const { options = {} } = buttonConfig; const { _bindDialog, setCurrentRowToDialog } = options; if (_bindDialog) { const dialogNode = _context.$(_bindDialog); let realRecordDataSource = recordDataSource; if (setCurrentRowToDialog === false) { realRecordDataSource = undefined; } return new Promise((resolve) => { setTimeout(() => { const p = dialogNode?.open?.(realRecordDataSource, { buttonConfig, position, selectedRowKeys, selectedRowRecords, }); if (typeof p?.then === 'function') { p.then((res) => { setTimeout(() => { resolve(true); }, 8); }); } else { setTimeout(() => { resolve(true); }, 8); } }); }); // table?.load?.(); } } }, getPrototypeList: (position) => { const result = [ getSelectDialogSnippet(), // { // name:'openDialogMode', // title:'当一个弹窗复用于新增和编辑多个场景时,你希望本次打开的弹窗用途', // setter:{ // componentName:'CnSelectSetter', // props:{ // options: getOpenDialogModeEnum({ // prefix:'用于' // }), // selectProps:{ // hasClear: true, // } // } // } // } ]; if ( [ ButtonPosition.tableOperate, ButtonPosition.arrayTableOperate, ButtonPosition.tableCell, ].includes(position) ) { result.splice(1, 0, { title: '是否将表格当前行数据默认带入到弹窗表单中', name: 'setCurrentRowToDialog', setter: { componentName: 'RadioGroupSetter', props: { options: getCommonBoolDataSource(), }, }, defaultValue: true, }); } return result; }, }; const cancel = { title: '取消(关闭当前弹窗)', value: 'cancel', position: [ ButtonPosition.dialog, ButtonPosition.formDialog, ButtonPosition.form, ], action: ({ buttonConfig, formData, open, close }) => { if (buttonConfig) { close && close(); } }, }; const tableReload = { title: '刷新表格', value: 'tableReload', position: ALL_BUTTON_POSITION_LIST, getPrototypeList: () => { return [...createTableSelectSetters()]; }, action: (arg) => { const { buttonConfig, _context } = arg || {}; const { options = {} } = buttonConfig; const { _bindTable, keepPagination } = options; function reloadOneTable(id) { if (typeof id === 'string') { const table = _context.$(id); if (keepPagination === true) { table?.tableLoad()?.(); } else { table?.load?.(); } } } if (_bindTable && _context) { if (Array.isArray(_bindTable)) { _bindTable.forEach((item) => { reloadOneTable(item); }); } else { reloadOneTable(_bindTable); } } }, }; /// --------- jsbridge start --------- const jsBridgeTextSetter = ({ position, config }) => { const { urlSetter = 'StringSetter' } = getSetterSnippet({ position, optType: 'jsbridge', ...config, }) || {}; return urlSetter; }; const getJsBridgeToastMessageField = ({ initialValue = '' }) => ({ successToastMessage: { title: '成功toast文案', setter: { componentName: 'StringSetter', initialValue, }, }, }); const toastResultCallback = ({ response, inputParams }) => { const { successToastMessage } = inputParams || {}; if (successToastMessage) { CnMessage.success?.({ content: successToastMessage }); } }; /** api配置 */ const BRIDGE_API_CONFIGS = { callPhone: { title: '拨打电话', props: { phoneNumber: { title: '电话号码', setter: jsBridgeTextSetter, }, }, }, scan: { title: '扫一扫', props: { scanType: { title: '扫码类型', setter: { componentName: 'RadioGroupSetter', initialValue: 'qrCode', props: { options: [ { title: '二维码', value: 'qrCode', }, { title: '条形码', value: 'barCode', }, ], }, }, }, scanResult: { title: '扫码结果', setter: { componentName: 'RadioGroupSetter', initialValue: 'blank', props: { options: [ { title: '新开窗口', value: 'blank', }, { title: '复制到剪贴板', value: 'copy', }, ], }, }, }, }, /** * 端执行回调 * response: 端执行的结果 * inputParams: 参数,保存用户做的选择 */ resultCallback: ({ response, inputParams }) => { const code = response?.code || response?.qrCode || response?.barCode; const { scanResult } = inputParams || {}; if (scanResult === 'blank') { CnUniApi?.navigateTo?.({ url: code }); } else if (scanResult === 'copy') { CnUniApi?.setClipboard?.({ text: code }) ?.then(() => CnMessage.success({ content: '已复制到剪贴板' })) ?.catch((err) => CnMessage.error({ content: err?.errMsg || '复制失败' }), ); } }, }, showSharePanel: { title: '分享', props: { title: { title: '分享标题', setter: jsBridgeTextSetter, }, content: { title: '分享文案', setter: jsBridgeTextSetter, }, url: { title: '分享网址', setter: jsBridgeTextSetter, }, image: { title: 'logo主图', setter: jsBridgeTextSetter, }, }, }, saveImage: { title: '保存到相册', props: { url: { title: '图片地址', setter: jsBridgeTextSetter, }, ...getJsBridgeToastMessageField({ initialValue: '保存成功' }), }, resultCallback: toastResultCallback, }, setClipboard: { title: '复制到剪贴板', props: { text: { title: '复制文案', setter: jsBridgeTextSetter, }, ...getJsBridgeToastMessageField({ initialValue: '复制成功' }), }, resultCallback: toastResultCallback, }, choosePhoneContact: { title: '通讯录', props: { needCallPhone: { title: '选择后拨打', setter: { componentName: 'BoolSetter', initialValue: true, }, }, }, resultCallback: ({ response, inputParams }) => { const { needCallPhone } = inputParams || {}; const mobile = response?.mobile || ''; const phoneNumber = mobile.split(',')?.[0] || mobile; if (needCallPhone) { CnUniApi?.callPhone?.({ phoneNumber }); } }, }, navigateTo: { title: '新开浏览器窗口', props: { url: { title: '新窗口地址', setter: jsBridgeTextSetter, }, }, }, navigateBack: { title: '关闭浏览器窗口', }, setNavigationBarTitle: { title: '设置标题', props: { title: { title: '标题名称', setter: jsBridgeTextSetter, }, }, }, getLocation: { title: '获取位置', }, getNetworkType: { title: '获取当前网络状态', }, pay: { title: '支付', props: { payStr: { title: '订单信息', setter: jsBridgeTextSetter, }, }, }, vibrate: { title: '手机震动', }, snapshot: { title: '截屏', }, call: { title: '自定义能力', props: { data: { title: '自定义入参', setter: { componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', initialValue: { label: 'param', }, props: { config: { items: [ { title: '参数名', name: 'label', isRequired: true, description: '参数名', setter: 'StringSetter', }, { title: '参数值', name: 'value', isRequired: true, description: '参数值', className: 'cn-param-select-setter', setter: { componentName: 'MixedSetter', props: { setters: [ { componentName: 'StringSetter', title: '字符串', }, { componentName: 'NumberSetter', title: '数字', }, { componentName: 'BoolSetter', title: '布尔(true/false)', }, ], }, }, }, ], }, }, }, }, }, }, }, }, }; const jsbridge = { title: '跨端JSBridge', value: 'jsbridge', position: ALL_BUTTON_POSITION_LIST, getDefaultProps: () => { return {}; }, /** * 二级面板配置 * @param position 位置,如:btn_tableOperate * @param config TODO: 配置,默认undefined | {__field: Node} * @returns */ getPrototypeList: (position, config) => { const jsbridgeApis = { name: 'apiName', title: '跨端能力', // setter: https://lowcode-engine.cn/site/docs/guide/appendix/setters setter(node) { const options = Object.keys(BRIDGE_API_CONFIGS).map((key) => ({ title: BRIDGE_API_CONFIGS[key]?.title, value: key, })); return { componentName: 'SelectSetter', initialValue: 'callPhone', // 初始化值 props: { options, }, }; }, }; const apiPropsList = Object.keys(BRIDGE_API_CONFIGS) .map((apiName) => { const { props = {} } = BRIDGE_API_CONFIGS[apiName]; return Object.keys(props).map((fieldName) => { const fieldConfig = props[fieldName]; const { title = '', setter = 'StringSetter' } = fieldConfig || {}; return { name: fieldName, title, condition: (node) => { return node?.parent?.getPropValue?.('apiName') === apiName; }, setter: typeof setter === 'function' ? setter({ position, config }) : setter, }; }); }) .flat(); return [ jsbridgeApis, // api选择 ...apiPropsList, // 配置选择 ]; }, action: async (context) => { const { buttonConfig: { options } = {}, // urlParamsDataSource, // recordDataSource, // state, // getExtraParam, // position, // arrayTableConfig, // selectedRowKeys = [], // selectedRowRecords = [], } = context; if (!options) { return; } /** * 根据参数表达式,获得真实的值 * originalParams: 输入参数。用户的原始选择(包含表达式) * context: action上下文环境 */ const getApiParams = (originalParams, context) => { const { urlParamsDataSource, recordDataSource, state, getExtraParam } = context; const params = Object.keys(originalParams) .map((key) => { if (key.startsWith('_unsafe_')) { return null; } const originalValue = originalParams[key]; const newValue = calculateTextExprValue(originalValue, { urlParamsDataSource, recordDataSource, state, extraParamList: [], getExtraParam, }); return { [key]: newValue }; }) .filter((item) => !!item) .reduce((prev, current) => { return { ...prev, ...current }; }, {}); return params; }; /** * 根据apiName配置,执行对应的回调函数(如有) * apiName: api命名 * inputParams: 参数,保存用户做的选择 * response: 端执行的结果 */ const excuteApiResultCallback = ({ apiName, inputParams, response }) => { const apiNameConfig = Object.keys(BRIDGE_API_CONFIGS) .filter((key) => key === apiName) .map((key) => BRIDGE_API_CONFIGS[key])?.[0]; const { resultCallback } = apiNameConfig || {}; if (!apiNameConfig) { return; } if (!resultCallback) { return; } resultCallback?.({ response, inputParams }); }; // cn-uni-api处理 // 1. 获取真实的参数值 const { apiName, ...rest } = options; const inputParams = getApiParams(rest, context); const { log, warn } = console; // TODO: 真机调试不方便,先打点调试,后续删除。 log('--before jsbridge--:', apiName, inputParams); // 2. 执行 return CnUniApi?.[apiName]?.({ ...inputParams }) ?.then((response) => { // 3. 回调处理 log('--success jsbridge--:', response); excuteApiResultCallback({ apiName, inputParams, response }); // 处理回调函数,用“动作编排”能力。 return { success: true, data: response, error: null, }; }) ?.catch((err) => { warn('--fail jsbridge--:', err); return { success: true, data: null, error: err, }; }); }, }; /// --------- jsbridge end --------- const componentMap = { request, link, submit, reset, setFormValue, tableReload, dialog, cancel, arrayTableAdd, arrayTableRemove, setDataSourceValue, message, flowAction, jsAction, jsbridge, }; export const createFlowListSetters = (config) => { const { position, activeSetter } = config || {}; return { componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', initialValue: { active: true, }, props: { config: { items: [ { name: 'optType', display: 'inline', title: '动作类型', isRequired: true, setter: { componentName: 'SelectSetter', props: () => { return { options: getButtonListByPosition(position, { excludeButtonList: ['flowAction'], }), }; }, }, }, { name: 'active', display: 'inline', title: '启用', isRequired: true, setter: activeSetter || { componentName: , title: '启用/禁用', }, }, { name: 'options', // display: 'accordion', display: 'plain', title: '动作配置项', extraProps: { defaultCollapsed: false, }, setter: { componentName: 'ObjectSetter', props: (field) => { const flowList = field?.parent?.parent?.getValue?.(); const index = field?.parent?.name; return { config: { items: [ ...getButtonPrototypeListByPosition(position, { flowList, currentFlowIndex: index, }), ], }, }; }, }, }, ], }, }, }, }, }; }; export function getItem(position, componentName, propKey) { let result; if (componentName) { let item = componentMap[componentName]; if (!item) { item = getBizComponent(componentName, position); } if (item && item.position && item.position.includes(position)) { if (!item.label) { item.label = item.title; } if (!item.value) { item.value = item.componentName; } if (propKey) { result = item[propKey]; } else { result = item; } } } return result; } export function getButtonAction(buttonConfig) { const { optType, position } = buttonConfig || {}; if (optType) { const action = getItem(position, optType, 'action'); if (typeof action === 'function') { return action; } } } // 获取按钮列表 export function getButtonListByPosition(position, config) { const { excludeButtonList = [] } = config || {}; const defaultList = Object.keys(componentMap); const bizComponentNameList = getBizComponentNameList(); const allComponentList = uniq([...defaultList, ...bizComponentNameList]); const result = []; if (position !== undefined) { allComponentList.forEach((name) => { if (excludeButtonList?.includes(name)) { } else { const component = getItem(position, name); if (component) { const { bizInfo = [] } = component; if (bizInfo.length > 0) { bizInfo.forEach((item) => { const { label, value } = item; const existGroup = result.find((item2) => item2.value === value); if (existGroup) { existGroup?.children.push(component); } else { result.push({ title: label, value, children: [component], }); } }); return; } result.push(component); } } }); } return result; } // 获取按钮prototype列表by position export function getButtonPrototypeListByPosition(position, config) { const defaultList = Object.keys(componentMap); let prototypeList = []; const bizComponentNameList = getBizComponentNameList(); const allComponentList = uniq([...defaultList, ...bizComponentNameList]); if (position !== undefined) { allComponentList.forEach((name) => { const item = getItem(position, name) || {}; const { getPrototypeList, configure = [] } = item; if (typeof getPrototypeList === 'function') { const temp = getPrototypeList(position, config); if (temp && temp.length > 0) { prototypeList = [ ...prototypeList, ...handlePrototypeCondition(temp, name, 'optType'), ]; } } else if (configure?.length > 0) { prototypeList = [ ...prototypeList, ...handlePrototypeCondition(configure, name, 'optType'), ]; } }); } return prototypeList; } export const createButtonListSetters = (config) => { const { position, childrenTitle } = config || {}; const tempButtonList = getButtonListByPosition(position, config); const titleSetter = 'CnI18nSetter'; const configure = [ { name: 'primaryKey', title: '唯一标识', condition: { type: 'JSFunction', value: '() => false', }, initialValue: { type: 'JSFunction', value: 'val => {\n if (val) return val;\n return String(Math.floor(Math.random() * 10000));\n }', }, setter: 'StringSetter', }, { name: 'children', isRequired: true, title: childrenTitle || '标题', setter: titleSetter, }, { name: 'optType', title: '按钮功能', isRequired: true, display: 'inline', // extraProps: { // setValue(target, value) { // const currentRowValue = target?.parent?.getValue(); // const newButtonInitialValue = getItemDefaultProps(position, value); // target?.parent.setValue({ // ...currentRowValue, // optType: value, // options: newButtonInitialValue, // }); // }, // }, editable: true, setter: { componentName: 'SelectSetter', props: () => { return { options: getButtonListByPosition(position, config), }; }, }, }, ]; configure.push({ name: 'options', // display: 'accordion', display: 'block', title: '按钮配置项', extraProps: { defaultCollapsed: false, }, setter: { componentName: 'ObjectSetter', props: () => { return { config: { items: [...getButtonPrototypeListByPosition(position)], }, }; }, }, }); const styleList = getStyleListByPosition(position); if (styleList?.length > 0) { configure.push({ title: '样式', type: 'group', display: 'accordion', collapsed: true, items: styleList, }); } return { componentName: 'ArraySetter', props: { mode: 'list', itemSetter: { componentName: 'ObjectSetter', initialValue() { return { primaryKey: uuid(6), children: '按钮', optType: tempButtonList[0]?.value, }; }, props: { config: { items: configure, }, }, }, }, }; }; export function createFormSelectSetters(config) { const { mode } = config || {}; return [ { name: '_bindForm', title: '选择表单', setter(prop) { const options = []; prop?.getNode?.()?.document?.nodesMap?.forEach((item) => { if ( item.getPropValue('isCnForm') || item.getPropValue('isCnFormDialog') ) { let prefix = '表单'; if (item.getPropValue('isCnFormDialog')) { prefix = '弹窗表单'; } const id = item.id || ''; const title = `${prefix}_${ handleI18nLabel(item?.propsData?.title) || '' }`; options.push({ title, value: id, }); } }); return { componentName: 'SelectSetter', props: { mode, // hasClear: true, options, }, }; }, }, ]; } export function getRunTimeItem(componentName) { let result; if (componentName) { result = componentMap[componentName]; if (!result) { result = getRunTimeBizComponent(componentName); } } return result; }