From 47ae3ec09571d17cd88aca28a6bc3779101cb05c Mon Sep 17 00:00:00 2001 From: dayou <853094838@qq.com> Date: Thu, 8 Aug 2024 20:05:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=B7=B3=E8=BD=AC=E9=80=BB=E8=BE=91=20?= =?UTF-8?q?(#388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + web/components.d.ts | 78 --- web/package.json | 2 + web/src/common/logicEngine/RuleBuild.ts | 29 +- web/src/common/logicEngine/RulesMatch.ts | 81 +++- web/src/management/config/questionConfig.js | 2 +- web/src/management/hooks/useJumpLogicFlow.ts | 171 +++++++ web/src/management/hooks/useJumpLogicInfo.js | 42 ++ web/src/management/hooks/useQuestionInfo.js | 3 +- .../management/hooks/useShowLogicEngine.js | 7 - web/src/management/hooks/useShowLogicInfo.js | 7 +- .../pages/edit/components/MaterialGroup.vue | 6 +- .../pages/edit/components/ModuleNavbar.vue | 9 +- .../pages/edit/components/QuestionWrapper.vue | 14 +- web/src/management/pages/edit/index.vue | 5 +- .../edit/modules/logicModule/JumpLogic.vue | 449 ++++++++++++++++++ .../logicModule/ShowLogic.vue} | 6 +- .../logicModule/components/RuleNodeView.vue | 5 +- .../{ => components}/RulePanel.vue | 8 +- .../components/nodeExtension/edges/QEdge.js | 55 +++ .../components/nodeExtension/index.js | 17 + .../components/nodeExtension/nodes/EndNode.js | 41 ++ .../components/nodeExtension/nodes/QNode.js | 177 +++++++ .../nodeExtension/nodes/StartNode.js | 42 ++ .../components/nodeExtension/readme.md | 3 + .../components/nodeExtension/style.css | 23 + .../components/nodeExtension/util.js | 18 + .../questionModule/components/TypeList.vue | 5 +- .../pages/edit/pages/edit/LogicIndex.vue | 85 ++++ .../pages/edit/pages/edit/index.vue | 4 +- web/src/management/router/index.ts | 3 +- web/src/management/stores/edit.ts | 43 +- web/src/management/styles/icon.scss | 19 +- .../communals/widgets/SubmitButton/index.jsx | 4 +- web/src/render/components/MaterialGroup.vue | 30 +- web/src/render/components/QuestionWrapper.vue | 92 +++- web/src/render/hooks/useRuleEngine.js | 6 - web/src/render/pages/IndexPage.vue | 5 +- web/src/render/stores/survey.js | 21 +- web/src/render/utils/index.js | 20 + 40 files changed, 1453 insertions(+), 185 deletions(-) delete mode 100644 web/components.d.ts create mode 100644 web/src/management/hooks/useJumpLogicFlow.ts create mode 100644 web/src/management/hooks/useJumpLogicInfo.js delete mode 100644 web/src/management/hooks/useShowLogicEngine.js create mode 100644 web/src/management/pages/edit/modules/logicModule/JumpLogic.vue rename web/src/management/pages/edit/{pages/edit/LogicEditPage.vue => modules/logicModule/ShowLogic.vue} (86%) rename web/src/management/pages/edit/modules/logicModule/{ => components}/RulePanel.vue (88%) create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/edges/QEdge.js create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/index.js create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/nodes/EndNode.js create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/nodes/QNode.js create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/nodes/StartNode.js create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/readme.md create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/style.css create mode 100644 web/src/management/pages/edit/modules/logicModule/components/nodeExtension/util.js create mode 100644 web/src/management/pages/edit/pages/edit/LogicIndex.vue delete mode 100644 web/src/render/hooks/useRuleEngine.js diff --git a/.gitignore b/.gitignore index 4031f8b1..e0d3138e 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ pnpm-debug.log* *.sw? .history +components.d.ts # 默认的上传文件夹 userUpload diff --git a/web/components.d.ts b/web/components.d.ts deleted file mode 100644 index a7d55fc9..00000000 --- a/web/components.d.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 -export {} - -declare module 'vue' { - export interface GlobalComponents { - ElButton: typeof import('element-plus/es')['ElButton'] - ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] - ElCollapse: typeof import('element-plus/es')['ElCollapse'] - ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] - ElColorPicker: typeof import('element-plus/es')['ElColorPicker'] - ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'] - ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] - ElDialog: typeof import('element-plus/es')['ElDialog'] - ElForm: typeof import('element-plus/es')['ElForm'] - ElFormItem: typeof import('element-plus/es')['ElFormItem'] - ElIcon: typeof import('element-plus/es')['ElIcon'] - ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] - ElMenu: typeof import('element-plus/es')['ElMenu'] - ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] - ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup'] - ElOption: typeof import('element-plus/es')['ElOption'] - ElPagination: typeof import('element-plus/es')['ElPagination'] - ElPopover: typeof import('element-plus/es')['ElPopover'] - ElRadio: typeof import('element-plus/es')['ElRadio'] - ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] - ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] - ElRow: typeof import('element-plus/es')['ElRow'] - ElSegmented: typeof import('element-plus/es')['ElSegmented'] - ElSelect: typeof import('element-plus/es')['ElSelect'] - ElSelectV2: typeof import('element-plus/es')['ElSelectV2'] - ElSlider: typeof import('element-plus/es')['ElSlider'] - ElSwitch: typeof import('element-plus/es')['ElSwitch'] - ElTable: typeof import('element-plus/es')['ElTable'] - ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] - ElTabPane: typeof import('element-plus/es')['ElTabPane'] - ElTabs: typeof import('element-plus/es')['ElTabs'] - ElTag: typeof import('element-plus/es')['ElTag'] - ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] - ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTree: typeof import('element-plus/es')['ElTree'] - IEpArrowLeft: typeof import('~icons/ep/arrow-left')['default'] - IEpArrowRight: typeof import('~icons/ep/arrow-right')['default'] - IEpBottom: typeof import('~icons/ep/bottom')['default'] - IEpCheck: typeof import('~icons/ep/check')['default'] - IEpCirclePlus: typeof import('~icons/ep/circle-plus')['default'] - IEpClose: typeof import('~icons/ep/close')['default'] - IEpConnection: typeof import('~icons/ep/connection')['default'] - IEpCopyDocument: typeof import('~icons/ep/copy-document')['default'] - IEpDelete: typeof import('~icons/ep/delete')['default'] - IEpIphone: typeof import('~icons/ep/iphone')['default'] - IEpLoading: typeof import('~icons/ep/loading')['default'] - IEpMinus: typeof import('~icons/ep/minus')['default'] - IEpMonitor: typeof import('~icons/ep/monitor')['default'] - IEpMore: typeof import('~icons/ep/more')['default'] - IEpMoreFilled: typeof import('~icons/ep/more-filled')['default'] - IEpPlus: typeof import('~icons/ep/plus')['default'] - IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default'] - IEpRank: typeof import('~icons/ep/rank')['default'] - IEpRemove: typeof import('~icons/ep/remove')['default'] - IEpSearch: typeof import('~icons/ep/search')['default'] - IEpSort: typeof import('~icons/ep/sort')['default'] - IEpSortDown: typeof import('~icons/ep/sort-down')['default'] - IEpSortUp: typeof import('~icons/ep/sort-up')['default'] - IEpTop: typeof import('~icons/ep/top')['default'] - IEpView: typeof import('~icons/ep/view')['default'] - IEpWarningFilled: typeof import('~icons/ep/warning-filled')['default'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - } - export interface ComponentCustomProperties { - vLoading: typeof import('element-plus/es')['ElLoadingDirective'] - } -} diff --git a/web/package.json b/web/package.json index afc1e033..fcacfdeb 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,8 @@ "format": "prettier --write src/" }, "dependencies": { + "@logicflow/core": "2.0.0", + "@logicflow/extension": "2.0.0", "@wangeditor/editor": "^5.1.23", "@wangeditor/editor-for-vue": "^5.1.12", "async-validator": "^4.2.5", diff --git a/web/src/common/logicEngine/RuleBuild.ts b/web/src/common/logicEngine/RuleBuild.ts index d9b99ac0..eb5a05ec 100644 --- a/web/src/common/logicEngine/RuleBuild.ts +++ b/web/src/common/logicEngine/RuleBuild.ts @@ -33,8 +33,8 @@ export class RuleNode { conditions: ConditionNode[] = [] scope: string = Scope.Question target: string = '' - constructor(scope: string = Scope.Question, target: string = '') { - this.id = generateID(PrefixID.Rule) + constructor(target: string = '', scope: string = Scope.Question, id?: string) { + this.id = id || generateID(PrefixID.Rule) this.scope = scope this.target = target } @@ -54,14 +54,8 @@ export class RuleNode { export class RuleBuild { rules: RuleNode[] = [] - static instance: RuleBuild constructor() { this.rules = [] - if (!RuleBuild.instance) { - RuleBuild.instance = this - } - - return RuleBuild.instance } // 添加条件规则到规则引擎中 @@ -71,6 +65,9 @@ export class RuleBuild { removeRule(ruleId: string) { this.rules = this.rules.filter((rule) => rule.id !== ruleId) } + clear() { + this.rules = [] + } findRule(ruleId: string) { return this.rules.find((rule) => rule.id === ruleId) } @@ -94,7 +91,7 @@ export class RuleBuild { if (ruleConf instanceof Array) { ruleConf.forEach((rule: any) => { const { scope, target } = rule - const ruleNode = new RuleNode(scope, target) + const ruleNode = new RuleNode(target, scope) rule.conditions.forEach((condition: any) => { const { field, operator, value } = condition const conditionNode = new ConditionNode(field, operator, value) @@ -112,19 +109,19 @@ export class RuleBuild { findTargetsByScope(scope: string) { return this.rules.filter((rule) => rule.scope === scope).map((rule) => rule.target) } + findRulesByField(field: string) { + return this.rules.filter((rule) => { + return rule.conditions.filter((condition) => condition.field === field).length + }) + } // 实现前置题删除校验 findTargetsByFields(field: string) { - const nodes = this.rules.filter((rule: RuleNode) => { - const conditions = rule.conditions.filter((item: any) => { - return item.field === field - }) - return conditions.length > 0 - }) + const nodes = this.findRulesByField(field) return nodes.map((item: any) => { return item.target }) } - // 根据目标题获取显示逻辑 + // 根据目标题获取关联的逻辑条件 findConditionByTarget(target: string) { return this.rules.filter((rule) => rule.target === target).map((item) => item.conditions) } diff --git a/web/src/common/logicEngine/RulesMatch.ts b/web/src/common/logicEngine/RulesMatch.ts index fece3f90..9fa2f7a2 100644 --- a/web/src/common/logicEngine/RulesMatch.ts +++ b/web/src/common/logicEngine/RulesMatch.ts @@ -3,7 +3,7 @@ import { Operator, type FieldTypes, type Fact } from './BasicType' // 定义条件规则类 export class ConditionNode { // 默认显示 - public result: boolean = false + public result: boolean | undefined = undefined constructor( public field: F, public operator: O, @@ -16,7 +16,7 @@ export class ConditionNode { return this.field + this.operator + this.value } - match(facts: Fact): boolean { + match(facts: Fact): boolean | undefined { // console.log(this.calculateHash()) // 如果该特征在事实对象中不存在,则直接返回false if (!facts[this.field]) { @@ -45,7 +45,7 @@ export class ConditionNode { this.result = this.value.some((v) => !facts[this.field].includes(v)) return this.result } else { - this.result = facts[this.field].includes(this.value) + this.result = !facts[this.field].includes(this.value) return this.result } case Operator.NotEqual: @@ -53,7 +53,7 @@ export class ConditionNode { this.result = this.value.every((v) => !facts[this.field].includes(v)) return this.result } else { - this.result = facts[this.field].includes(this.value) + this.result = facts[this.field].toString() !== this.value return this.result } // 其他比较操作符的判断逻辑 @@ -69,7 +69,7 @@ export class ConditionNode { export class RuleNode { conditions: Map> // 使用哈希表存储条件规则对象 - public result: boolean = false + public result: boolean | undefined constructor( public target: string, public scope: string @@ -83,15 +83,28 @@ export class RuleNode { } // 匹配条件规则 - match(fact: Fact) { - const res = Array.from(this.conditions.entries()).every(([, value]) => { - const res = value.match(fact) - if (res) { - return true - } else { - return false - } - }) + match(fact: Fact, comparor?: any) { + let res: boolean | undefined = undefined + if (comparor === 'or') { + res = Array.from(this.conditions.entries()).some(([, value]) => { + const res = value.match(fact) + if (res) { + return true + } else { + return false + } + }) + } else { + res = Array.from(this.conditions.entries()).every(([, value]) => { + const res = value.match(fact) + if (res) { + return true + } else { + return false + } + }) + } + this.result = res return res } @@ -121,14 +134,14 @@ export class RuleNode { export class RuleMatch { rules: Map - static instance: any + // static instance: any constructor() { this.rules = new Map() - if (!RuleMatch.instance) { - RuleMatch.instance = this - } + // if (!RuleMatch.instance) { + // RuleMatch.instance = this + // } - return RuleMatch.instance + // return RuleMatch.instance } fromJson(ruleConf: any) { if (ruleConf instanceof Array) { @@ -145,6 +158,7 @@ export class RuleMatch { this.addRule(ruleNode) }) } + return this } // 添加条件规则到规则引擎中 @@ -161,12 +175,12 @@ export class RuleMatch { } // 匹配条件规则 - match(target: string, scope: string, fact: Fact) { + match(target: string, scope: string, fact: Fact, comparor?: any) { const hash = this.calculateHash(target, scope) const rule = this.rules.get(hash) if (rule) { - const result = rule.match(fact) + const result = rule.match(fact, comparor) // this.matchCache.set(hash, result); return result } else { @@ -196,11 +210,34 @@ export class RuleMatch { [...this.rules.entries()].filter(([, value]) => { return [...value.conditions.entries()].filter(([, value]) => { return value.field === field - }) + }).length }) ) return [...rules.values()].map((obj) => obj.target) } + findRulesByField(field: string) { + const list = [...this.rules.entries()] + const match = list.filter(([, ruleValue]) => { + const list = [...ruleValue.conditions.entries()] + const res = list.filter(([, conditionValue]) => { + const hit = conditionValue.field === field + return hit + }) + return res.length + }) + console.log({ match }) + return match + } + findFieldsByTarget(target: string) { + const rules = new Map( + [...this.rules.entries()].filter(([, value]) => { + return value.target === target + }) + ) + return [...rules.values()].map((obj) => + [...obj.conditions.entries()].map(([, value]) => value.field) + ) + } toJson() { return Array.from(this.rules.entries()).map(([, value]) => { return value.toJson() diff --git a/web/src/management/config/questionConfig.js b/web/src/management/config/questionConfig.js index 16604a35..5623e241 100644 --- a/web/src/management/config/questionConfig.js +++ b/web/src/management/config/questionConfig.js @@ -53,7 +53,7 @@ export const defaultQuestionConfig = { star: 5, optionOrigin: '', originType: 'selected', - innerType:'', + innerType: '', matrixOptionsRely: '', numberRange: { min: { diff --git a/web/src/management/hooks/useJumpLogicFlow.ts b/web/src/management/hooks/useJumpLogicFlow.ts new file mode 100644 index 00000000..9d32d260 --- /dev/null +++ b/web/src/management/hooks/useJumpLogicFlow.ts @@ -0,0 +1,171 @@ +import { useEditStore } from '../stores/edit' +import { Operator } from '@/common/logicEngine/BasicType' +import { cleanRichText } from '@/common/xss' +import { CHOICES } from '@/common/typeEnum' + +export const generateNodes = (questionDataList: [any]) => { + let x = 50 + const y = 300 + const startNode = [ + { + id: 'start', + type: 'start-node', + x: 50, + y, + text: '开始' + } + ] + const nodes: any[] = questionDataList.map((item) => { + x = x + 300 + let options = [] + if (CHOICES.includes(item.type)) { + options = item?.options.map((option: any) => { + return { + key: option?.hash, + type: cleanRichText(option?.text) + } + }) + } + return { + id: item?.field, + type: 'q-node', + x, + y, + properties: { + questionType: item?.type, + field: item.field, + title: cleanRichText(item?.title), + options + } + } + }) + const endNode = [ + { + id: 'end', + type: 'end-node', + x: x + 200, + y, + text: '结束' + } + ] + return startNode.concat(nodes).concat(endNode) +} + +/* 跳转逻辑的初始化 */ +export const generateLine = (models: Array) => { + const acc: Array = [] + const editStore = useEditStore() + const jumpLogicRule = editStore.jumpLogicEngine?.toJson() + + const edges = models.reduce((prev: any, point: any, index: number, array: any[]) => { + if (index === 0) { + return acc + } + const previousPoint: any = array[index - 1] + if (!previousPoint) { + return acc + } + let edge + if (previousPoint?.type === 'start-node') { + // 开始节点连接线 + edge = { + type: 'q-edge', + sourceNodeId: previousPoint?.id, + targetNodeId: point?.id, + sourceAnchorId: `${previousPoint.anchors[0].id}`, + targetAnchorId: `${point?.anchors[0].id}`, + // properties: { + draggable: false + // } + } + acc.push(edge) + } else if (previousPoint?.type === 'q-node') { + // 生成题目节点连接线 + // 方案1:以条件节点为主体 + const editStore = useEditStore() + const rules = editStore.jumpLogicEngine.findRulesByField(previousPoint.id) + if (!jumpLogicRule.length || !rules.length) { + edge = { + type: 'q-edge', + sourceNodeId: previousPoint?.id, + targetNodeId: point?.id, + sourceAnchorId: `${previousPoint.anchors[1].id}`, + targetAnchorId: `${point?.anchors[0].id}` + } + acc.push(edge) + } else { + const hasDefault = rules.filter((i: any) => { + return i.conditions.filter((item: any) => item.operator === Operator.NotEqual).length + }) + if (!hasDefault.length) { + // 如果规则中没有默认答题跳转则生成一条默认的题目答完链接线 + edge = { + type: 'q-edge', + sourceNodeId: previousPoint?.id, + targetNodeId: point?.id, + sourceAnchorId: `${previousPoint.anchors[1].id}`, + targetAnchorId: `${point?.anchors[0].id}` + } + acc.push(edge) + } + rules.forEach((rule: any) => { + const condition = rule.conditions[0] + let sourceAnchorId = `${condition.field}_right` + if (condition.operator === 'in') { + sourceAnchorId = `${condition.value}_right` + } + const targetAnchorId = `${rule.target}_left` + edge = { + type: 'q-edge', + sourceNodeId: previousPoint?.id, + targetNodeId: rule.target, + sourceAnchorId: `${sourceAnchorId}`, + targetAnchorId: `${targetAnchorId}`, + properties: { + ruleId: rule.id + } + } + acc.push(edge) + }) + } + } else { + edge = { + type: 'q-edge', + sourceNodeId: previousPoint?.id, + targetNodeId: point?.id, + sourceAnchorId: `${previousPoint.anchors[1].id}`, + targetAnchorId: `${point?.anchors[0].id}`, + draggable: false + } + acc.push(edge) + } + + return acc + }) + return edges +} + +export const getNodesStep = (source: string, target: string, questionDataList: any[]) => { + const sourceIndex = questionDataList.findIndex((item: any) => item.field === source) + const targetIndex = questionDataList.findIndex((item: any) => item.field === target) + return targetIndex - sourceIndex +} +export const getCondition = (sourceInfo: any): any => { + const { nodeId, anchorId } = sourceInfo + const anchorKey = anchorId.split('_right')[0] + if (nodeId === anchorKey) { + // 答完跳转 + return { + field: nodeId, + operator: Operator.NotEqual, + value: '' + } + } else { + // 选中optionhash跳转 + return { + field: nodeId, + operator: Operator.Include, + value: anchorKey + } + } +} diff --git a/web/src/management/hooks/useJumpLogicInfo.js b/web/src/management/hooks/useJumpLogicInfo.js new file mode 100644 index 00000000..62ee8cb1 --- /dev/null +++ b/web/src/management/hooks/useJumpLogicInfo.js @@ -0,0 +1,42 @@ +import { computed, unref } from 'vue' +import { useQuestionInfo } from './useQuestionInfo' +import { useEditStore } from '../stores/edit' +import { storeToRefs } from 'pinia' +const editStore = useEditStore() +const { jumpLogicEngine } = storeToRefs(editStore) + +// 目标题的显示逻辑提示文案 +export const useJumpLogicInfo = (field) => { + const hasJumpLogic = computed(() => { + const logicEngine = jumpLogicEngine.value + // 判断该题是否作为了跳转逻辑条件 + const isField = logicEngine?.findTargetsByFields(field)?.length > 0 + // 判断该题是否作为了跳转目标 + const isTarget = logicEngine?.findConditionByTarget(field)?.length > 0 + return isField || isTarget + }) + const getJumpLogicText = computed(() => { + const logicEngine = jumpLogicEngine.value + if (!logicEngine) return + // 获取跳转 + const rules = logicEngine?.findRulesByField(field) || [] + if (!rules) return + const ruleText = rules.map((rule) => { + const conditions = rule.conditions.map((condition) => { + const { getOptionTitle } = useQuestionInfo(condition.field) + if (condition.operator === 'in') { + return ` 选择了 【${getOptionTitle.value(unref(condition.value)).join('')}】` + } else if (condition.operator === 'neq') { + return ` 答完题目 ` + } + return '' + }) + const { getQuestionTitle } = useQuestionInfo(rule.target) + return ( + conditions.join('') + `  则跳转到 【${getQuestionTitle.value()}】
` + ) + }) + return ruleText.join('') + }) + return { hasJumpLogic, getJumpLogicText } +} diff --git a/web/src/management/hooks/useQuestionInfo.js b/web/src/management/hooks/useQuestionInfo.js index 19049008..05e9ab0a 100644 --- a/web/src/management/hooks/useQuestionInfo.js +++ b/web/src/management/hooks/useQuestionInfo.js @@ -8,7 +8,8 @@ export const useQuestionInfo = (field) => { const getQuestionTitle = computed(() => { return () => { - return questionDataList.value.find((item) => item.field === field)?.title + if (field === 'end') return '问卷末尾' + return cleanRichText(questionDataList.value.find((item) => item.field === field)?.title) } }) const getOptionTitle = computed(() => { diff --git a/web/src/management/hooks/useShowLogicEngine.js b/web/src/management/hooks/useShowLogicEngine.js deleted file mode 100644 index 8a02934a..00000000 --- a/web/src/management/hooks/useShowLogicEngine.js +++ /dev/null @@ -1,7 +0,0 @@ -import { ref } from 'vue' -import { RuleBuild } from '@/common/logicEngine/RuleBuild' - -export const showLogicEngine = ref() -export const initShowLogicEngine = (ruleConf) => { - showLogicEngine.value = new RuleBuild().fromJson(ruleConf) -} diff --git a/web/src/management/hooks/useShowLogicInfo.js b/web/src/management/hooks/useShowLogicInfo.js index a70bbded..d3d79828 100644 --- a/web/src/management/hooks/useShowLogicInfo.js +++ b/web/src/management/hooks/useShowLogicInfo.js @@ -2,7 +2,10 @@ import { computed, unref } from 'vue' import { useQuestionInfo } from './useQuestionInfo' import { flatten } from 'lodash-es' import { cleanRichText } from '@/common/xss' -import { showLogicEngine } from '@/management/hooks/useShowLogicEngine' +import { useEditStore } from '../stores/edit' +import { storeToRefs } from 'pinia' +const editStore = useEditStore() +const { showLogicEngine } = storeToRefs(editStore) // 目标题的显示逻辑提示文案 export const useShowLogicInfo = (field) => { @@ -11,7 +14,7 @@ export const useShowLogicInfo = (field) => { // 判断该题是否作为了显示逻辑前置题 const isField = logicEngine?.findTargetsByFields(field)?.length > 0 // 判断该题是否作为了显示逻辑目标题 - const isTarget = logicEngine?.findTargetsByScope(field)?.length > 0 + const isTarget = logicEngine?.findConditionByTarget(field)?.length > 0 return isField || isTarget }) const getShowLogicText = computed(() => { diff --git a/web/src/management/pages/edit/components/MaterialGroup.vue b/web/src/management/pages/edit/components/MaterialGroup.vue index ebdb9329..9456ee16 100644 --- a/web/src/management/pages/edit/components/MaterialGroup.vue +++ b/web/src/management/pages/edit/components/MaterialGroup.vue @@ -14,7 +14,7 @@ :ref="`questionWrapper-${element.field}`" :moduleConfig="element" :qIndex="element.qIndex" - :isFirst="index==0" + :isFirst="index == 0" :indexNumber="element.indexNumber" :isSelected="currentEditOne === element.qIndex" :isLast="index + 1 === questionDataList.length" @@ -60,12 +60,12 @@ export default defineComponent({ } } }, - emits: ['change', 'select', 'changeSeq','change'], + emits: ['change', 'select', 'changeSeq', 'change'], setup(props, { emit }) { const editStore = useEditStore() const renderData = computed({ get() { - return props.questionDataList; //filterQuestionPreviewData(props.questionDataList) + return props.questionDataList //filterQuestionPreviewData(props.questionDataList) }, set(value) { editStore.moveQuestionDataList(value) diff --git a/web/src/management/pages/edit/components/ModuleNavbar.vue b/web/src/management/pages/edit/components/ModuleNavbar.vue index 4b26886c..96cb7065 100644 --- a/web/src/management/pages/edit/components/ModuleNavbar.vue +++ b/web/src/management/pages/edit/components/ModuleNavbar.vue @@ -29,8 +29,7 @@ + + + + diff --git a/web/src/management/pages/edit/pages/edit/LogicEditPage.vue b/web/src/management/pages/edit/modules/logicModule/ShowLogic.vue similarity index 86% rename from web/src/management/pages/edit/pages/edit/LogicEditPage.vue rename to web/src/management/pages/edit/modules/logicModule/ShowLogic.vue index 93892858..8520b13e 100644 --- a/web/src/management/pages/edit/pages/edit/LogicEditPage.vue +++ b/web/src/management/pages/edit/modules/logicModule/ShowLogic.vue @@ -9,7 +9,7 @@ import { storeToRefs } from 'pinia' import { useEditStore } from '@/management/stores/edit' import { cloneDeep } from 'lodash-es' -import RulePanel from '../../modules/logicModule/RulePanel.vue' +import RulePanel from './components/RulePanel.vue' import { filterQuestionPreviewData } from '@/management/utils/index' const editStore = useEditStore() @@ -23,11 +23,11 @@ provide('renderData', renderData) diff --git a/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue b/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue index f24a9705..3a1c1d40 100644 --- a/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue +++ b/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue @@ -52,7 +52,10 @@ import { ElMessageBox } from 'element-plus' import 'element-plus/theme-chalk/src/message-box.scss' import { RuleNode } from '@/common/logicEngine/RuleBuild' import { cleanRichText } from '@/common/xss' -import { showLogicEngine } from '@/management/hooks/useShowLogicEngine' +import { useEditStore } from '@/management/stores/edit' +import { storeToRefs } from 'pinia' +const editStore = useEditStore() +const { showLogicEngine } = storeToRefs(editStore) import ConditionView from './ConditionView.vue' const renderData = inject>>('renderData') || ref([]) diff --git a/web/src/management/pages/edit/modules/logicModule/RulePanel.vue b/web/src/management/pages/edit/modules/logicModule/components/RulePanel.vue similarity index 88% rename from web/src/management/pages/edit/modules/logicModule/RulePanel.vue rename to web/src/management/pages/edit/modules/logicModule/components/RulePanel.vue index ddee0c26..b3afb01e 100644 --- a/web/src/management/pages/edit/modules/logicModule/RulePanel.vue +++ b/web/src/management/pages/edit/modules/logicModule/components/RulePanel.vue @@ -21,8 +21,12 @@ + diff --git a/web/src/management/pages/edit/pages/edit/index.vue b/web/src/management/pages/edit/pages/edit/index.vue index 51085729..d0b0cb7c 100644 --- a/web/src/management/pages/edit/pages/edit/index.vue +++ b/web/src/management/pages/edit/pages/edit/index.vue @@ -37,7 +37,9 @@ const activeRouter = ref(route.name) watch( activeRouter, (val: any) => { - router.push({ name: val }) + // 避免编辑页刷新丢失query + const query = route.query + router.push({ name: val, query }) }, { immediate: true diff --git a/web/src/management/router/index.ts b/web/src/management/router/index.ts index be4ef401..5dc1b65d 100644 --- a/web/src/management/router/index.ts +++ b/web/src/management/router/index.ts @@ -57,7 +57,8 @@ const routes: RouteRecordRaw[] = [ meta: { needLogin: true }, - component: () => import('../pages/edit/pages/edit/LogicEditPage.vue') + component: () => import('../pages/edit/pages/edit/LogicIndex.vue'), + props: (route) => ({ active: route.query.active }) } ] }, diff --git a/web/src/management/stores/edit.ts b/web/src/management/stores/edit.ts index a7c188c0..65552a72 100644 --- a/web/src/management/stores/edit.ts +++ b/web/src/management/stores/edit.ts @@ -20,6 +20,7 @@ import { SurveyPermissions } from '@/management/utils/types/workSpace' import { getBannerData } from '@/management/api/skin.js' import { getCollaboratorPermissions } from '@/management/api/space' import { CODE_MAP } from '../api/base' +import { RuleBuild } from '@/common/logicEngine/RuleBuild' const innerMetaConfig = { submit: { @@ -82,10 +83,12 @@ function useInitializeSchema(surveyId: Ref) { pageEditOne: 1, pageConf: [], // 分页逻辑 logicConf: { - showLogicConf: [] + showLogicConf: [], + jumpLogicConf: [] } }) - + const { showLogicEngine, initShowLogicEngine, jumpLogicEngine, initJumpLogicEngine } = + useLogicEngine(schema) function initSchema({ metaData, codeData }: { metaData: any; codeData: any }) { schema.metaData = metaData schema.bannerConf = _merge({}, schema.bannerConf, codeData.bannerConf) @@ -130,6 +133,9 @@ function useInitializeSchema(surveyId: Ref) { logicConf } }) + + initShowLogicEngine() + initJumpLogicEngine() } else { throw new Error(res.errmsg || '问卷不存在') } @@ -138,7 +144,9 @@ function useInitializeSchema(surveyId: Ref) { return { schema, initSchema, - getSchemaFromRemote + getSchemaFromRemote, + showLogicEngine, + jumpLogicEngine } } @@ -289,6 +297,7 @@ function useCurrentEdit({ changeCurrentEditStatus } } + function usePageEdit( { schema, @@ -421,6 +430,23 @@ function usePageEdit( } } +function useLogicEngine(schema: any) { + const logicConf = toRef(schema, 'logicConf') + const showLogicEngine = ref() + const jumpLogicEngine = ref() + function initShowLogicEngine() { + showLogicEngine.value = new RuleBuild().fromJson(logicConf.value?.showLogicConf) + } + function initJumpLogicEngine() { + jumpLogicEngine.value = new RuleBuild().fromJson(logicConf.value?.jumpLogicConf) + } + return { + showLogicEngine, + jumpLogicEngine, + initShowLogicEngine, + initJumpLogicEngine + } +} type IBannerItem = { name: string key: string @@ -432,9 +458,9 @@ export const useEditStore = defineStore('edit', () => { const bannerList: Ref = ref({}) const cooperPermissions = ref(Object.values(SurveyPermissions)) const schemaUpdateTime = ref(Date.now()) - const { schema, initSchema, getSchemaFromRemote } = useInitializeSchema(surveyId) + const { schema, initSchema, getSchemaFromRemote, showLogicEngine, jumpLogicEngine } = + useInitializeSchema(surveyId) const questionDataList = toRef(schema, 'questionDataList') - function setQuestionDataList(data: any) { schema.questionDataList = data } @@ -455,6 +481,7 @@ export const useEditStore = defineStore('edit', () => { cooperPermissions.value = res.data.permissions } } + // const { showLogicEngine, initShowLogicEngine, jumpLogicEngine, initJumpLogicEngine } = useLogicEngine(schema) const { currentEditOne, currentEditKey, @@ -469,7 +496,7 @@ export const useEditStore = defineStore('edit', () => { async function init() { const { metaData } = schema if (!metaData || (metaData as any)?._id !== surveyId.value) { - getSchemaFromRemote() + await getSchemaFromRemote() } currentEditOne.value = null currentEditStatus.value = 'Success' @@ -618,6 +645,8 @@ export const useEditStore = defineStore('edit', () => { createNewQuestion, changeSchema, changeThemePreset, - compareQuestionSeq + compareQuestionSeq, + showLogicEngine, + jumpLogicEngine } }) diff --git a/web/src/management/styles/icon.scss b/web/src/management/styles/icon.scss index 04e6df83..8cf5cff0 100644 --- a/web/src/management/styles/icon.scss +++ b/web/src/management/styles/icon.scss @@ -1,10 +1,9 @@ @font-face { - font-family: 'iconfont'; - /* Project id 4263849 */ + font-family: 'iconfont'; /* Project id 4263849 */ src: - url('//at.alicdn.com/t/c/font_4263849_tj4thybmhq.woff2?t=1717580126029') format('woff2'), - url('//at.alicdn.com/t/c/font_4263849_tj4thybmhq.woff?t=1717580126029') format('woff'), - url('//at.alicdn.com/t/c/font_4263849_tj4thybmhq.ttf?t=1717580126029') format('truetype'); + url('//at.alicdn.com/t/c/font_4263849_wh46krnnsv.woff2?t=1722515679936') format('woff2'), + url('//at.alicdn.com/t/c/font_4263849_wh46krnnsv.woff?t=1722515679936') format('woff'), + url('//at.alicdn.com/t/c/font_4263849_wh46krnnsv.ttf?t=1722515679936') format('truetype'); } .iconfont { @@ -162,3 +161,13 @@ .icon-gauge:before { content: '\e6db'; } + +.icon-suoxiao:before { + content: '\e6f4'; +} +.icon-fangda:before { + content: '\e6f5'; +} +.icon-shiying:before { + content: '\e6f6'; +} diff --git a/web/src/materials/communals/widgets/SubmitButton/index.jsx b/web/src/materials/communals/widgets/SubmitButton/index.jsx index 6e19b3f9..f40210bb 100644 --- a/web/src/materials/communals/widgets/SubmitButton/index.jsx +++ b/web/src/materials/communals/widgets/SubmitButton/index.jsx @@ -42,11 +42,11 @@ export default defineComponent({ } }, render() { - const { submitConf,isFinallyPage } = this.props + const { submitConf, isFinallyPage } = this.props return (
) diff --git a/web/src/render/components/MaterialGroup.vue b/web/src/render/components/MaterialGroup.vue index 78b3b6fb..dccf9f89 100644 --- a/web/src/render/components/MaterialGroup.vue +++ b/web/src/render/components/MaterialGroup.vue @@ -13,6 +13,7 @@