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..c87dbeaf 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) } - // 实现前置题删除校验 - 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 + findRulesByField(field: string) { + return this.rules.filter((rule) => { + return rule.conditions.filter((condition) => condition.field === field).length }) + } + // 实现前置题删除校验 + findTargetsByField(field: string) { + 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..d879ae82 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 } // 添加条件规则到规则引擎中 @@ -160,22 +174,31 @@ export class RuleMatch { this.rules.set(hash, rule) } - // 匹配条件规则 - 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) - // this.matchCache.set(hash, result); + const result = rule.match(fact, comparor) return result } else { // 默认显示 return true } } - - getResult(target: string, scope: string) { + /* 获取条件题关联的多个目标题匹配情况 */ + getResultsByField(field: string, fact: Fact) { + const rules = this.findRulesByField(field) + return rules.map(([, rule]) => { + return { + target: rule.target, + result: this.match(rule.target, 'question', fact, 'or') + } + }) + } + /* 获取目标题的规则是否匹配 */ + getResultByTarget(target: string, scope: string) { const hash = this.calculateHash(target, scope) const rule = this.rules.get(hash) if (rule) { @@ -191,15 +214,18 @@ export class RuleMatch { // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 return target + scope } - findTargetsByField(field: string) { - const rules = new Map( - [...this.rules.entries()].filter(([, value]) => { - return [...value.conditions.entries()].filter(([, value]) => { - return value.field === field - }) + // 查找条件题的规则 + 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 [...rules.values()].map((obj) => obj.target) + return res.length + }) + return match } toJson() { return Array.from(this.rules.entries()).map(([, value]) => { 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..5fc90fb7 --- /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?.findTargetsByField(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..5f46eb30 100644 --- a/web/src/management/hooks/useShowLogicInfo.js +++ b/web/src/management/hooks/useShowLogicInfo.js @@ -2,16 +2,19 @@ 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) => { const hasShowLogic = computed(() => { const logicEngine = showLogicEngine.value // 判断该题是否作为了显示逻辑前置题 - const isField = logicEngine?.findTargetsByFields(field)?.length > 0 + const isField = logicEngine?.findTargetsByField(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/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 70c34514..1def51af 100644 --- a/web/src/management/stores/edit.ts +++ b/web/src/management/stores/edit.ts @@ -21,6 +21,7 @@ import { getBannerData } from '@/management/api/skin.js' import { getCollaboratorPermissions } from '@/management/api/space' import useEditGlobalBaseConf, { type TypeMethod } from './composables/useEditGlobalBaseConf' import { CODE_MAP } from '../api/base' +import { RuleBuild } from '@/common/logicEngine/RuleBuild' const innerMetaConfig = { submit: { @@ -83,10 +84,12 @@ function useInitializeSchema(surveyId: Ref, initializeSchemaCallBack: () 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) @@ -132,6 +135,9 @@ function useInitializeSchema(surveyId: Ref, initializeSchemaCallBack: () } }) initializeSchemaCallBack() + + initShowLogicEngine() + initJumpLogicEngine() } else { throw new Error(res.errmsg || '问卷不存在') } @@ -140,7 +146,9 @@ function useInitializeSchema(surveyId: Ref, initializeSchemaCallBack: () return { schema, initSchema, - getSchemaFromRemote + getSchemaFromRemote, + showLogicEngine, + jumpLogicEngine } } @@ -299,6 +307,7 @@ function useCurrentEdit({ changeCurrentEditStatus } } + function usePageEdit( { schema, @@ -431,6 +440,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 @@ -442,13 +468,13 @@ 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, () => { editGlobalBaseConf.initCounts() }) const questionDataList = toRef(schema, 'questionDataList') const editGlobalBaseConf = useEditGlobalBaseConf(questionDataList, updateTime) - function setQuestionDataList(data: any) { schema.questionDataList = data } @@ -469,6 +495,7 @@ export const useEditStore = defineStore('edit', () => { cooperPermissions.value = res.data.permissions } } + // const { showLogicEngine, initShowLogicEngine, jumpLogicEngine, initJumpLogicEngine } = useLogicEngine(schema) const { currentEditOne, currentEditKey, @@ -483,7 +510,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' @@ -636,6 +663,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 23fd2418..31326626 100644 --- a/web/src/management/styles/icon.scss +++ b/web/src/management/styles/icon.scss @@ -160,3 +160,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/adapter/index.js b/web/src/render/adapter/index.js index 3f6b279f..4f3f0c84 100644 --- a/web/src/render/adapter/index.js +++ b/web/src/render/adapter/index.js @@ -7,7 +7,7 @@ const adapter = (() => { const list = [] const exec = (questionData) => { - return list.reduce((pre, next) => ({ ...pre, ...next(questionData, pre) }), {}) + return list.reduce((pre, next, index) => ({ ...pre, index, ...next(questionData, pre) }), {}) } return { diff --git a/web/src/render/adapter/question.js b/web/src/render/adapter/question.js index 04dd519c..e8287cde 100644 --- a/web/src/render/adapter/question.js +++ b/web/src/render/adapter/question.js @@ -7,11 +7,12 @@ import { get as _get } from 'lodash-es' export default function (questionConfig) { let dataList = _get(questionConfig, 'dataConf.dataList') // 将题目列表转成对象,并且对题目类型、题目的选项做一些字段的增加和转换 - const questionData = dataList.reduce((pre, item) => { + const questionData = dataList.reduce((pre, item, index) => { Object.assign(pre, { [item.field]: { indexNumber: '', voteTotal: 0, + index, ...item } }) diff --git a/web/src/render/components/MaterialGroup.vue b/web/src/render/components/MaterialGroup.vue index 78b3b6fb..35852283 100644 --- a/web/src/render/components/MaterialGroup.vue +++ b/web/src/render/components/MaterialGroup.vue @@ -13,6 +13,7 @@ diff --git a/web/src/render/hooks/useRuleEngine.js b/web/src/render/hooks/useRuleEngine.js deleted file mode 100644 index 23f4980d..00000000 --- a/web/src/render/hooks/useRuleEngine.js +++ /dev/null @@ -1,6 +0,0 @@ -import { RuleMatch } from '@/common/logicEngine/RulesMatch' - -export const ruleEngine = new RuleMatch() -export const initRuleEngine = (ruleConf) => { - ruleEngine.fromJson(ruleConf) -} diff --git a/web/src/render/pages/IndexPage.vue b/web/src/render/pages/IndexPage.vue index c0418ac8..b54dfa68 100644 --- a/web/src/render/pages/IndexPage.vue +++ b/web/src/render/pages/IndexPage.vue @@ -10,7 +10,7 @@ import useCommandComponent from '../hooks/useCommandComponent' import { useSurveyStore } from '../stores/survey' import AlertDialog from '../components/AlertDialog.vue' -import { initRuleEngine } from '@/render/hooks/useRuleEngine.js' + const route = useRoute() const surveyStore = useSurveyStore() const loadData = (res: any, surveyPath: string) => { @@ -44,7 +44,8 @@ const loadData = (res: any, surveyPath: string) => { surveyStore.setSurveyPath(surveyPath) surveyStore.initSurvey(questionData) - initRuleEngine(logicConf?.showLogicConf) + surveyStore.initShowLogicEngine(logicConf?.showLogicConf) + surveyStore.initJumpLogicEngine(logicConf.jumpLogicConf) } else { throw new Error(res.errmsg) } diff --git a/web/src/render/stores/question.js b/web/src/render/stores/question.js index 45aff9cf..0f810353 100644 --- a/web/src/render/stores/question.js +++ b/web/src/render/stores/question.js @@ -11,6 +11,11 @@ export const useQuestionStore = defineStore('question', () => { const questionData = ref(null) const questionSeq = ref([]) // 题目的顺序,因为可能会有分页的情况,所以是一个二维数组[[qid1, qid2], [qid3,qid4]] const pageIndex = ref(1) // 当前分页的索引 + const changeField = ref(null) + const changeIndex = computed(() => { + return questionData.value[changeField.value].index + }) + const needHideFields = ref([]) // 题目列表 const questionList = computed(() => { @@ -177,6 +182,22 @@ export const useQuestionStore = defineStore('question', () => { }) } + const setChangeField = (field) => { + changeField.value = field + } + const getQuestionIndexByField = (field) => { + return questionData.value[field].index + } + const addNeedHideFields = (fields) => { + fields.forEach(field => { + if(!needHideFields.value.includes(field)) { + needHideFields.value.push(field) + } + }) + } + const removeNeedHideFields = (fields) => { + needHideFields.value = needHideFields.value.filter(field => !fields.includes(field)) + } return { voteMap, questionData, @@ -191,6 +212,13 @@ export const useQuestionStore = defineStore('question', () => { setVoteMap, updateVoteMapByKey, initVoteData, - updateVoteData + updateVoteData, + changeField, + changeIndex, + setChangeField, + needHideFields, + addNeedHideFields, + removeNeedHideFields, + getQuestionIndexByField } }) diff --git a/web/src/render/stores/survey.js b/web/src/render/stores/survey.js index 977dd293..c0cc3c09 100644 --- a/web/src/render/stores/survey.js +++ b/web/src/render/stores/survey.js @@ -15,6 +15,8 @@ import 'moment/locale/zh-cn' moment.locale('zh-cn') import adapter from '../adapter' +import { RuleMatch } from '@/common/logicEngine/RulesMatch' +// import { jumpLogicRule } from '@/common/logicEngine/jumpLogicRule' /** * CODE_MAP不从management引入,在dev阶段,会导致B端 router被加载,进而导致C端路由被添加 baseUrl: /management @@ -39,6 +41,7 @@ export const useSurveyStore = defineStore('survey', () => { const formValues = ref({}) const whiteData = ref({}) const pageConf = ref([]) + const router = useRouter() const questionStore = useQuestionStore() @@ -156,6 +159,16 @@ export const useSurveyStore = defineStore('survey', () => { if (key in formValues.value) { formValues.value[key] = value } + questionStore.setChangeField(key) + } + + const showLogicEngine = ref() + const initShowLogicEngine = (showLogicConf) => { + showLogicEngine.value = new RuleMatch().fromJson(showLogicConf) + } + const jumpLogicEngine = ref() + const initJumpLogicEngine = (jumpLogicConf) => { + jumpLogicEngine.value = new RuleMatch().fromJson(jumpLogicConf) } return { @@ -173,12 +186,15 @@ export const useSurveyStore = defineStore('survey', () => { formValues, whiteData, pageConf, - initSurvey, changeData, setWhiteData, setSurveyPath, setEnterTime, - getEncryptInfo + getEncryptInfo, + showLogicEngine, + initShowLogicEngine, + jumpLogicEngine, + initJumpLogicEngine } }) diff --git a/web/src/render/utils/index.js b/web/src/render/utils/index.js index 610e506f..7f9cf2b2 100644 --- a/web/src/render/utils/index.js +++ b/web/src/render/utils/index.js @@ -29,4 +29,4 @@ export const formatLink = (url) => { return url } return `http://${url}` -} +} \ No newline at end of file