Przeglądaj źródła

bug-376 添加 [完成检测] 节点

cc12458 6 miesięcy temu
rodzic
commit
379d7295e4

+ 18 - 0
@types/global.d.ts

@@ -0,0 +1,18 @@
+export {};
+
+declare global {
+  /**
+   * Promise 扩展
+   */
+  interface PromiseConstructor {
+    withResolvers<T>(): {
+      promise: Promise<T>;
+      resolve: (value: T | PromiseLike<T>) => void;
+      reject: (reason?: any) => void;
+    };
+  }
+
+  interface Array<T> {
+    at(index: number): T;
+  }
+}

+ 3 - 0
src/libs/logic-flow/constant.ts

@@ -1,2 +1,5 @@
 export const START_ID = `start`;
 export const START_ID = `start`;
 export const END_ID = `end`;
 export const END_ID = `end`;
+
+export const START_NODE = `StartNode`
+export const END_NODE = `EndNode`

+ 1 - 1
src/main.ts

@@ -1,6 +1,6 @@
 import 'virtual:uno.css';
 import 'virtual:uno.css';
 import '@/themes/index.scss';
 import '@/themes/index.scss';
-
+import './polyfill'
 import vxe    from '@/libs/vxe';
 import vxe    from '@/libs/vxe';
 import router from '@/router';
 import router from '@/router';
 import pinia  from '@/stores';
 import pinia  from '@/stores';

Plik diff jest za duży
+ 0 - 0
src/pages/aio/flow-config/assets/finish.svg


+ 7 - 4
src/pages/aio/flow-config/index.vue

@@ -282,10 +282,10 @@ const validate = (tips = true) => {
 
 
   const start = instance.lf.getNodeModelById(Node.ID_Start);
   const start = instance.lf.getNodeModelById(Node.ID_Start);
 
 
-  let gather;
+  let gather: Gather;
   const { promise, resolve, reject } = withResolvers<{ gather: Gather; data?: FlowRequestData; message?: string }>();
   const { promise, resolve, reject } = withResolvers<{ gather: Gather; data?: FlowRequestData; message?: string }>();
   try {
   try {
-    gather = map(start);
+    gather = map(start).sort((g1, g2) => g1.level - g2.level || +(g1.targetNodeId === Node.ID_Back) - +(g2.targetNodeId === Node.ID_Back));
 
 
     const data: Record<string, any> = {
     const data: Record<string, any> = {
       [Node.ID_Start]: instance.lf.getNodeModelById(Node.ID_Start)?.getProperties().requestData,
       [Node.ID_Start]: instance.lf.getNodeModelById(Node.ID_Start)?.getProperties().requestData,
@@ -294,7 +294,7 @@ const validate = (tips = true) => {
     oldValue = toFlowRequestData(gather, data);
     oldValue = toFlowRequestData(gather, data);
     requestData.value = oldValue;
     requestData.value = oldValue;
     resolve({ gather, data: oldValue });
     resolve({ gather, data: oldValue });
-    console.log('[AioFlowConfig] 更新 request-data 数据: ', );
+    console.log('[AioFlowConfig] 更新 request-data 数据: ', oldValue);
   } catch (error: any) {
   } catch (error: any) {
     if (tips) {
     if (tips) {
       notification.error({
       notification.error({
@@ -310,9 +310,12 @@ const validate = (tips = true) => {
   }
   }
 
 
   if (Array.isArray(gather) && gather.length) {
   if (Array.isArray(gather) && gather.length) {
+    const last = gather.at(-1);
+    const notOpenEdgeId = last.targetNodeId === Node.ID_Back ? last.edgeId : void 0;
     for (const { edgeId } of gather) {
     for (const { edgeId } of gather) {
       instance.lf.setProperties(edgeId, { isAnimation: true });
       instance.lf.setProperties(edgeId, { isAnimation: true });
-      instance.lf.openEdgeAnimation(edgeId);
+      if (notOpenEdgeId === edgeId) instance.lf.closeEdgeAnimation(notOpenEdgeId);
+      else instance.lf.openEdgeAnimation(edgeId);
     }
     }
   } else {
   } else {
     for (const edge of instance.lf.getGraphRawData().edges) {
     for (const edge of instance.lf.getGraphRawData().edges) {

+ 26 - 14
src/pages/aio/flow-config/nodes/FlowNode.model.ts

@@ -1,5 +1,5 @@
 import { RectNodeModel } from '@logicflow/core';
 import { RectNodeModel } from '@logicflow/core';
-import { END_ID } from '@/libs/logic-flow/constant';
+import { END_ID, END_NODE, START_ID } from '@/libs/logic-flow/constant';
 import type { FlowNodeAnchor, FlowNodeConnectRuleResult, FlowNodeProperties } from './index';
 import type { FlowNodeAnchor, FlowNodeConnectRuleResult, FlowNodeProperties } from './index';
 
 
 export default class FlowNodeModel extends RectNodeModel<FlowNodeProperties> {
 export default class FlowNodeModel extends RectNodeModel<FlowNodeProperties> {
@@ -20,6 +20,13 @@ export default class FlowNodeModel extends RectNodeModel<FlowNodeProperties> {
         isAllPass: false,
         isAllPass: false,
         msg: `当前 [${source.text.value}] 节点不能直接连接 [${forbidDirectTarget.map((id) => this.graphModel.getNodeModelById(id)?.text.value).join(' | ')}] 节点`,
         msg: `当前 [${source.text.value}] 节点不能直接连接 [${forbidDirectTarget.map((id) => this.graphModel.getNodeModelById(id)?.text.value).join(' | ')}] 节点`,
       };
       };
+    // 获取目标节点禁止直接连接
+    const forbidDirectSource = getProperties(target).forbidDirectSource ?? [];
+    if (forbidDirectSource.includes(source.id))
+      return {
+        isAllPass: false,
+        msg: `目标 [${target.text.value}] 节点不能直接连接 [${forbidDirectSource.map((id) => this.graphModel.getNodeModelById(id)?.text.value).join(' | ')}] 节点`,
+      };
 
 
     // 获取目标节点 forbidSource
     // 获取目标节点 forbidSource
     let forbidSource = getProperties(target).forbidSource ?? [];
     let forbidSource = getProperties(target).forbidSource ?? [];
@@ -63,6 +70,12 @@ export default class FlowNodeModel extends RectNodeModel<FlowNodeProperties> {
   }
   }
 
 
   override isAllowConnectedAsTarget(...args: any[]): FlowNodeConnectRuleResult {
   override isAllowConnectedAsTarget(...args: any[]): FlowNodeConnectRuleResult {
+    // 当前节点是否开始
+    if (this.id === START_ID)
+      return {
+        isAllPass: false,
+        msg: `当前 [${this.text.value}] 为开始节点不允许输入`,
+      };
     // 获取目标节点连接到的所有起始节点
     // 获取目标节点连接到的所有起始节点
     const directNodes = this.graphModel.getNodeIncomingNode(this.id);
     const directNodes = this.graphModel.getNodeIncomingNode(this.id);
     if (directNodes.length >= 1)
     if (directNodes.length >= 1)
@@ -79,31 +92,30 @@ export default class FlowNodeModel extends RectNodeModel<FlowNodeProperties> {
         msg: `目标 [${this.text.value}] 节点仅允许 [${onlySource.map((id) => this.graphModel.getNodeModelById(id)?.text.value).join(' | ')}] 节点作为输入`,
         msg: `目标 [${this.text.value}] 节点仅允许 [${onlySource.map((id) => this.graphModel.getNodeModelById(id)?.text.value).join(' | ')}] 节点作为输入`,
       };
       };
 
 
-    // 当前节点是否开始
-    if (getProperties(this).start)
-      return {
-        isAllPass: false,
-        msg: `当前 [${this.text.value}] 为开始节点不允许输入`,
-      };
-
-
     return this.isAllowConnected(args[0], this, args[1], args[2], args[3]);
     return this.isAllowConnected(args[0], this, args[1], args[2], args[3]);
   }
   }
 
 
   override isAllowConnectedAsSource(...args: any[]): FlowNodeConnectRuleResult {
   override isAllowConnectedAsSource(...args: any[]): FlowNodeConnectRuleResult {
+    // 当前节点是否结束
+    if (this.id === END_ID)
+      return {
+        isAllPass: false,
+        msg: `当前 [${this.text.value}] 为结束节点不允许输出`,
+      };
     // 获取当前节点所有的下一级节点
     // 获取当前节点所有的下一级节点
-    const directNodes = this.graphModel.getNodeOutgoingNode(this.id).filter((node) => node.id !== END_ID);
-    if (directNodes.length >= 1 && args[0].id !== END_ID)
+    const directNodes = this.graphModel.getNodeOutgoingNode(this.id).filter((node) => (node.type as string) !== END_NODE);
+    if (directNodes.length >= 1 && args[0].type !== END_NODE)
       return {
       return {
         isAllPass: false,
         isAllPass: false,
         msg: `当前 [${this.text.value}] 节点已存在 ${directNodes.length}个输出 (${directNodes.map((node) => node.text.value)})`,
         msg: `当前 [${this.text.value}] 节点已存在 ${directNodes.length}个输出 (${directNodes.map((node) => node.text.value)})`,
       };
       };
 
 
-    // 当前节点是否结束
-    if (getProperties(this).finish && args[0].id !== END_ID)
+    // 获取目标节点 onlyTarget
+    const onlyTarget = (getProperties(this).onlyTarget as string[]) ?? [];
+    if (onlyTarget.length && !onlyTarget.includes(args[0].id))
       return {
       return {
         isAllPass: false,
         isAllPass: false,
-        msg: `当前 [${this.text.value}] 为结束节点不允许输出`,
+        msg: `当前 [${this.text.value}] 节点仅允许 [${onlyTarget.map((id) => this.graphModel.getNodeModelById(id)?.text.value).join(' | ')}] 节点作为输出`,
       };
       };
 
 
     return this.isAllowConnected(this, args[0], args[1], args[2], args[3]);
     return this.isAllowConnected(this, args[0], args[1], args[2], args[3]);

+ 2 - 0
src/pages/aio/flow-config/nodes/FlowNodeInlay.vue

@@ -3,6 +3,7 @@ import { h } from 'vue';
 import type { FlowNodeProperties } from './index';
 import type { FlowNodeProperties } from './index';
 
 
 import IconStart from '@/pages/aio/flow-config/assets/start.svg';
 import IconStart from '@/pages/aio/flow-config/assets/start.svg';
+import IconFinish from '@/pages/aio/flow-config/assets/finish.svg';
 import IconConfig from '@/pages/aio/flow-config/assets/config.svg';
 import IconConfig from '@/pages/aio/flow-config/assets/config.svg';
 import IconReport from '@/pages/aio/flow-config/assets/report.svg';
 import IconReport from '@/pages/aio/flow-config/assets/report.svg';
 import IconPulse from '@/pages/aio/flow-config/assets/pulse.svg';
 import IconPulse from '@/pages/aio/flow-config/assets/pulse.svg';
@@ -20,6 +21,7 @@ defineOptions({
 
 
 const Icon = {
 const Icon = {
   start: IconStart,
   start: IconStart,
+  finish: IconFinish,
   config: IconConfig,
   config: IconConfig,
   report: IconReport,
   report: IconReport,
   pulse: IconPulse,
   pulse: IconPulse,

+ 19 - 8
src/pages/aio/flow-config/nodes/config.ts

@@ -1,4 +1,4 @@
-import { END_ID, START_ID } from '@/libs/logic-flow/constant';
+import { END_ID, END_NODE, START_ID } from '@/libs/logic-flow/constant';
 import type { FlowNodeProperties } from './index';
 import type { FlowNodeProperties } from './index';
 
 
 interface CreateNodeOptions extends FlowNodeProperties {
 interface CreateNodeOptions extends FlowNodeProperties {
@@ -18,6 +18,7 @@ export const DEFAULT_NODE_HEIGHT = 40;
 
 
 export const ID_Start = START_ID;
 export const ID_Start = START_ID;
 export const ID_End = END_ID;
 export const ID_End = END_ID;
+export const ID_Back = 'back';
 export const ID_Register = 'register';
 export const ID_Register = 'register';
 export const ID_Analysis_Pulse = 'pulseAnalysis';
 export const ID_Analysis_Pulse = 'pulseAnalysis';
 export const ID_Analysis_TongueAndFace = 'tongueAndFaceAnalysis';
 export const ID_Analysis_TongueAndFace = 'tongueAndFaceAnalysis';
@@ -48,7 +49,8 @@ const factory = (type: 'StartNode' | 'EndNode' | 'FlowNode', id: string, text: s
 
 
 const textRef = {
 const textRef = {
   [ID_Start]: '开始检测',
   [ID_Start]: '开始检测',
-  [ID_End]: '返回首页',
+  [ID_End]: '完成检测',
+  [ID_Back]: '返回首页',
   [ID_Register]: '建档',
   [ID_Register]: '建档',
   [ID_Analysis_Pulse]: '脉象分析',
   [ID_Analysis_Pulse]: '脉象分析',
   [ID_Analysis_TongueAndFace]: '舌面象分析',
   [ID_Analysis_TongueAndFace]: '舌面象分析',
@@ -73,14 +75,23 @@ export function start(options?: CreateNodeOptions) {
     icon: 'start',
     icon: 'start',
     iconBackground: '#cf1322',
     iconBackground: '#cf1322',
     iconColor: '#fff',
     iconColor: '#fff',
-    start: true,
     configurable: true,
     configurable: true,
     ...options,
     ...options,
   });
   });
 }
 }
 
 
 export function end(options?: CreateNodeOptions) {
 export function end(options?: CreateNodeOptions) {
-  return factory('EndNode', ID_End, textRef[ID_End], {
+  return factory('FlowNode', ID_End, textRef[ID_End], {
+    icon: 'finish',
+    iconBackground: '#0958d9',
+    iconColor: '#fff',
+    forbidDirectSource: [ID_Start],
+    ...options,
+  });
+}
+
+export function back(options?: CreateNodeOptions) {
+  return factory(END_NODE, ID_Back, textRef[ID_Back], {
     radius: 20,
     radius: 20,
     ...options,
     ...options,
   });
   });
@@ -131,7 +142,7 @@ export function pulseAnalysisReport(options?: CreateNodeOptions) {
     icon: 'report',
     icon: 'report',
     iconBackground: '#08979c',
     iconBackground: '#08979c',
     iconColor: '#fff',
     iconColor: '#fff',
-    forbidDirectTarget: [ID_End],
+    forbidDirectTarget: [ID_Back],
     ...options,
     ...options,
   });
   });
 }
 }
@@ -141,7 +152,7 @@ export function tongueAndFaceAnalysisReport(options?: CreateNodeOptions) {
     icon: 'report',
     icon: 'report',
     iconBackground: '#08979c',
     iconBackground: '#08979c',
     iconColor: '#fff',
     iconColor: '#fff',
-    forbidDirectTarget: [ID_End],
+    forbidDirectTarget: [ID_Back],
     ...options,
     ...options,
   });
   });
 }
 }
@@ -151,8 +162,8 @@ export function healthAnalysisReport(options?: CreateNodeOptions) {
     icon: 'report',
     icon: 'report',
     iconBackground: '#08979c',
     iconBackground: '#08979c',
     iconColor: '#fff',
     iconColor: '#fff',
-    finish: true,
     configurable: true,
     configurable: true,
+    onlyTarget: [ID_End],
     ...options,
     ...options,
   });
   });
 }
 }
@@ -162,8 +173,8 @@ export function healthAnalysisScheme(options?: CreateNodeOptions) {
     icon: 'report',
     icon: 'report',
     iconBackground: '#08979c',
     iconBackground: '#08979c',
     iconColor: '#fff',
     iconColor: '#fff',
-    finish: true,
     configurable: true,
     configurable: true,
+    onlyTarget: [ID_End],
     ...options,
     ...options,
   });
   });
 }
 }

+ 3 - 4
src/pages/aio/flow-config/nodes/index.ts

@@ -14,16 +14,15 @@ export {
 export * as Node from './config';
 export * as Node from './config';
 
 
 export interface FlowNodeProperties extends IRectNodeProperties {
 export interface FlowNodeProperties extends IRectNodeProperties {
-  icon?: 'start' | 'config' | 'report' | 'pulse' | 'tongue' | 'questionnaire';
+  icon?: 'start' | 'finish' | 'config' | 'report' | 'pulse' | 'tongue' | 'questionnaire';
   iconBackground?: string;
   iconBackground?: string;
   iconColor?: string;
   iconColor?: string;
 
 
+  forbidDirectSource?: string[];
   forbidDirectTarget?: string[];
   forbidDirectTarget?: string[];
   forbidSource?: string[];
   forbidSource?: string[];
   onlySource?: string[];
   onlySource?: string[];
-
-  start?: boolean;
-  finish?: boolean;
+  onlyTarget?: string[];
 
 
   configurable?: boolean;
   configurable?: boolean;
   requestData?: Record<string, any>;
   requestData?: Record<string, any>;

+ 10 - 6
src/pages/aio/flow-config/tool.ts

@@ -1,5 +1,6 @@
 import {
 import {
   alcoholAnalysisReport,
   alcoholAnalysisReport,
+  back,
   edge,
   edge,
   end,
   end,
   healthAnalysis,
   healthAnalysis,
@@ -8,6 +9,7 @@ import {
   ID_Analysis_Health,
   ID_Analysis_Health,
   ID_Analysis_Pulse,
   ID_Analysis_Pulse,
   ID_Analysis_TongueAndFace,
   ID_Analysis_TongueAndFace,
+  ID_Back,
   ID_End,
   ID_End,
   ID_Register,
   ID_Register,
   ID_Report_Alcohol,
   ID_Report_Alcohol,
@@ -33,24 +35,24 @@ const nodeRef = {
   [ID_Analysis_TongueAndFace]: /*拍照页*/ 'tongueface_upload',
   [ID_Analysis_TongueAndFace]: /*拍照页*/ 'tongueface_upload',
   [ID_Analysis_Health]: /* 问诊页 */ 'tongueface_analysis',
   [ID_Analysis_Health]: /* 问诊页 */ 'tongueface_analysis',
   [ID_Report_Pulse]: /* 脉诊结果页 */ 'pulse_upload_result',
   [ID_Report_Pulse]: /* 脉诊结果页 */ 'pulse_upload_result',
-  [ID_Report_TongueAndFace]:  /* 舌面分析报告页 */'tongueface_analysis_result',
+  [ID_Report_TongueAndFace]:  /* 舌面分析报告页 */'tongueface_analysis_result',
   [ID_Report_Health]: /* 健康报告页 */ 'health_analysis',
   [ID_Report_Health]: /* 健康报告页 */ 'health_analysis',
   [ID_Scheme_Health]: /* 调理方案页 */ 'health_analysis_scheme',
   [ID_Scheme_Health]: /* 调理方案页 */ 'health_analysis_scheme',
   [ID_Report_Alcohol]: /* 酒精结果页 */ 'alcohol_upload_result',
   [ID_Report_Alcohol]: /* 酒精结果页 */ 'alcohol_upload_result',
 } as const;
 } as const;
 
 
 export type NodeId = keyof typeof nodeRef;
 export type NodeId = keyof typeof nodeRef;
-export type Gather = { level: number; sourceNodeId: NodeId; targetNodeId: NodeId; edgeId?: string }[];
+export type Gather = { level: number; sourceNodeId: NodeId; targetNodeId: NodeId | typeof ID_Back; edgeId: string }[];
 
 
 const refNode = ((ref) => Object.keys(ref).reduce((record, key) => ((record[ref[<NodeId>key]] = <NodeId>key), record), {} as Record<string, NodeId>))(nodeRef);
 const refNode = ((ref) => Object.keys(ref).reduce((record, key) => ((record[ref[<NodeId>key]] = <NodeId>key), record), {} as Record<string, NodeId>))(nodeRef);
 
 
 export function toFlowRequestData(gather: Gather, data: Record<string, any>) {
 export function toFlowRequestData(gather: Gather, data: Record<string, any>) {
   const value: string[] = [];
   const value: string[] = [];
-  const _gather = [...gather].sort((g1, g2) => g1.level - g2.level || +(g1.targetNodeId === ID_End) - +(g2.targetNodeId === ID_End));
-  for (const { targetNodeId } of _gather) {
-    if (targetNodeId === ID_End) {
+  for (let i = 0; i < gather.length; i++) {
+    const { targetNodeId } = gather[i];
+    if (targetNodeId === ID_Back && i !== gather.length - 1) {
       value.push(value.pop()!.replace(/\?*$/, '?'));
       value.push(value.pop()!.replace(/\?*$/, '?'));
-    } else value.push(nodeRef[targetNodeId]);
+    } else if (targetNodeId !== ID_Back && targetNodeId !== ID_End) value.push(nodeRef[targetNodeId]);
   }
   }
   const getReportRequestData = (key: string) => data[key]?.elements ?? [];
   const getReportRequestData = (key: string) => data[key]?.elements ?? [];
   return {
   return {
@@ -90,6 +92,7 @@ export function fromFlowRequestData(data?: FlowRequestData) {
       healthAnalysisReport({ requestData: getReportRequestData('health_analysis_report_page') }),
       healthAnalysisReport({ requestData: getReportRequestData('health_analysis_report_page') }),
       healthAnalysisScheme({ requestData: getReportRequestData('health_analysis_scheme_page') }),
       healthAnalysisScheme({ requestData: getReportRequestData('health_analysis_scheme_page') }),
     ],
     ],
+    [back()],
   ];
   ];
 
 
   const nodes = new Set<string>([ID_Start, ID_End]);
   const nodes = new Set<string>([ID_Start, ID_End]);
@@ -98,6 +101,7 @@ export function fromFlowRequestData(data?: FlowRequestData) {
   if (flow.length && flow[0] === nodeRef[ID_Start]) flow.shift();
   if (flow.length && flow[0] === nodeRef[ID_Start]) flow.shift();
   if (flow.length && flow[flow.length - 1] === nodeRef[ID_End]) flow.pop();
   if (flow.length && flow[flow.length - 1] === nodeRef[ID_End]) flow.pop();
   flow.unshift(ID_Start);
   flow.unshift(ID_Start);
+  flow.push(ID_End);
   for (let i = 1; i < flow.length; i++) {
   for (let i = 1; i < flow.length; i++) {
     const [source] = flow[i - 1].split(/[?:]/).filter(Boolean);
     const [source] = flow[i - 1].split(/[?:]/).filter(Boolean);
     const [target, title, countDown] = flow[i].split(/[?:]/).filter(Boolean);
     const [target, title, countDown] = flow[i].split(/[?:]/).filter(Boolean);

+ 21 - 0
src/polyfill.ts

@@ -0,0 +1,21 @@
+if (typeof Promise.withResolvers !== 'function') {
+  Promise.withResolvers = function <T>() {
+    let resolve!: (value: T | PromiseLike<T>) => void;
+    let reject!: (reason?: any) => void;
+
+    const promise = new Promise<T>((res, rej) => {
+      resolve = res;
+      reject = rej;
+    });
+
+    return { promise, resolve, reject };
+  };
+}
+
+if (typeof Array.prototype.at !== 'function') {
+  Array.prototype.at = function (index: number) {
+    return index < 0 ? this[this.length + index] : this[index];
+  };
+}
+
+export {};

+ 2 - 1
src/tools/promise.ts

@@ -1,3 +1,4 @@
+// @ts-nocheck
 export function withResolvers<T, R = any>(): {
 export function withResolvers<T, R = any>(): {
   promise: Promise<T>;
   promise: Promise<T>;
   resolve: (value: T | PromiseLike<T>) => void;
   resolve: (value: T | PromiseLike<T>) => void;
@@ -16,6 +17,6 @@ export function withResolvers<T, R = any>(): {
       return { promise, resolve, reject };
       return { promise, resolve, reject };
     };
     };
     withResolvers = fn;
     withResolvers = fn;
+    return fn();
   }
   }
-  return fn();
 }
 }

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików