diff --git a/server/package.json b/server/package.json index 8ab12443..44164d74 100644 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,7 @@ "author": "", "scripts": { "build": "nest build", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "format": "prettier --write \"src/**/*.ts\" \"src/**/__test/*.ts\"", "local": "ts-node ./scripts/run-local.ts", "start": "nest start", "dev": "npm run start:dev", diff --git a/server/src/modules/survey/template/surveyTemplate/templateBase.json b/server/src/modules/survey/template/surveyTemplate/templateBase.json index de6debc5..56574dcc 100644 --- a/server/src/modules/survey/template/surveyTemplate/templateBase.json +++ b/server/src/modules/survey/template/surveyTemplate/templateBase.json @@ -48,5 +48,8 @@ "contentConf": { "opacity": 100 } + }, + "logicConf": { + "showLogicConf": [] } } diff --git a/web/.gitignore b/web/.gitignore index 6bf0eb84..94556503 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -15,6 +15,7 @@ yarn-error.log* pnpm-debug.log* package-lock.json pnpm-lock.yaml +yarn.lock # Editor directories and files .idea diff --git a/web/components.d.ts b/web/components.d.ts index 411d01a0..a6b11b23 100644 --- a/web/components.d.ts +++ b/web/components.d.ts @@ -11,15 +11,13 @@ declare module 'vue' { 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'] ElOption: typeof import('element-plus/es')['ElOption'] ElPagination: typeof import('element-plus/es')['ElPagination'] ElPopover: typeof import('element-plus/es')['ElPopover'] @@ -27,24 +25,22 @@ declare module 'vue' { ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElSelect: typeof import('element-plus/es')['ElSelect'] - ElSlider: typeof import('element-plus/es')['ElSlider'] + ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] 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'] - 'IEp-[]': typeof import('~icons/ep/[]')['default'] - 'IEp-[test]': typeof import('~icons/ep/[test]')['default'] - 'IEp-]': typeof import('~icons/ep/]')['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'] IEpCopyDocument: typeof import('~icons/ep/copy-document')['default'] + IEpDelete: typeof import('~icons/ep/delete')['default'] IEpLoading: typeof import('~icons/ep/loading')['default'] + IEpMinus: typeof import('~icons/ep/minus')['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'] @@ -58,6 +54,5 @@ declare module 'vue' { } export interface ComponentCustomProperties { vLoading: typeof import('element-plus/es')['ElLoadingDirective'] - vPopover: typeof import('element-plus/es')['ElPopoverDirective'] } } diff --git a/web/package.json b/web/package.json index b53d9fba..1b879af2 100644 --- a/web/package.json +++ b/web/package.json @@ -23,13 +23,15 @@ "element-plus": "^2.7.0", "lodash-es": "^4.17.21", "moment": "^2.29.4", + "nanoid": "^5.0.7", "node-forge": "^1.3.1", "qrcode": "^1.5.3", "vue": "^3.4.15", "vue-router": "^4.2.5", "vuedraggable": "^4.1.0", "vuex": "^4.0.2", - "xss": "^1.0.14" + "xss": "^1.0.14", + "yup": "^1.4.0" }, "devDependencies": { "@iconify-json/ep": "^1.1.15", diff --git a/web/src/common/Editor/EditorV2.vue b/web/src/common/Editor/EditorV2.vue deleted file mode 100644 index 38a4ff07..00000000 --- a/web/src/common/Editor/EditorV2.vue +++ /dev/null @@ -1,68 +0,0 @@ - - - diff --git a/web/src/common/Editor/ReadOnly.vue b/web/src/common/Editor/ReadOnly.vue deleted file mode 100644 index 1719dc3e..00000000 --- a/web/src/common/Editor/ReadOnly.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/web/src/common/logicEngine/BasicType.ts b/web/src/common/logicEngine/BasicType.ts new file mode 100644 index 00000000..810e1f78 --- /dev/null +++ b/web/src/common/logicEngine/BasicType.ts @@ -0,0 +1,32 @@ +/** + * in:包含, 选择了,任一 + * eq: 等于,选择了,全部 + * nin: 不包含,不选择,任一 + * neq:不等于,不选择,全部,可以实现“填写了” + */ +export enum Operator { + Include = 'in', + Equal = 'eq', + NotEqual = 'neq', + NotInclude = 'nin', +} + + +export enum PrefixID { + Rule = 'r', + Condition = 'c' +} + +export enum Scope { + Question = 'question', + Option = 'option' +} + + +export type FieldTypes = string | string[]; + +// 定义事实对象类型 +export type Fact = { + [key: string]: any; +}; + diff --git a/web/src/common/logicEngine/RuleBuild.ts b/web/src/common/logicEngine/RuleBuild.ts new file mode 100644 index 00000000..b358e7b7 --- /dev/null +++ b/web/src/common/logicEngine/RuleBuild.ts @@ -0,0 +1,146 @@ +import { nanoid } from 'nanoid'; +import * as yup from 'yup' +import { type FieldTypes, PrefixID, Operator, Scope } from './BasicType' + +export function generateID(prefix = PrefixID.Rule) { + return `${prefix}-${nanoid(5)}` +} +// 定义条件规则类 +export class ConditionNode { + id: string = ''; + public field: string = ''; + public operator: Operator = Operator.Include; + public value: FieldTypes = [] + constructor(field: string = '', operator: Operator = Operator.Include, value: FieldTypes = []) { + this.field = field; + this.operator = operator; + this.value = value; + this.id = generateID(PrefixID.Condition) + } + setField(field: string) { + this.field = field; + } + setOperator(operator: Operator) { + this.operator = operator; + } + setValue(value: FieldTypes) { + this.value = value; + } +} + +export class RuleNode { + id: string = ''; + conditions: ConditionNode[] = [] + scope: string = Scope.Question + target: string = '' + constructor(scope:string = Scope.Question, target: string = '') { + this.id = generateID(PrefixID.Rule) + this.scope = scope + this.target = target + } + setTarget(value: string) { + this.target = value + } + addCondition(condition: ConditionNode) { + this.conditions.push(condition); + } + removeCondition(id: string) { + this.conditions = this.conditions.filter(v => v.id !== id); + } + findCondition(conditionId: string) { + return this.conditions.find(condition => condition.id === conditionId); + } +} + +export class RuleBuild { + rules: RuleNode[] = []; + static instance: RuleBuild; + constructor() { + this.rules = []; + if (!RuleBuild.instance) { + RuleBuild.instance = this; + } + + return RuleBuild.instance; + } + + // 添加条件规则到规则引擎中 + addRule(rule: RuleNode) { + this.rules.push(rule); + } + removeRule(ruleId: string) { + this.rules = this.rules.filter(rule => rule.id !== ruleId); + } + findRule(ruleId: string) { + return this.rules.find(rule => rule.id === ruleId); + } + toJson() { + return this.rules.map(rule => { + return { + target: rule.target, + scope: rule.scope, + conditions: rule.conditions.map(condition => { + return { + field: condition.field, + operator: condition.operator, + value: condition.value + } + }) + } + }) + } + fromJson(ruleConf: any) { + this.rules = [] + if(ruleConf instanceof Array) { + ruleConf.forEach((rule: any) => { + const { scope, target } = rule + const ruleNode = new RuleNode(scope, target); + rule.conditions.forEach((condition: any) => { + const { field, operator, value } = condition + const conditionNode = new ConditionNode(field, operator, value); + ruleNode.addCondition(conditionNode) + }) + this.addRule(ruleNode) + }) + } + return this + } + validateSchema() { + return ruleSchema.validateSync(this.toJson()) + } + // 实现目标选择了下拉框置灰效果 + 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 + }) + return nodes.map((item: any) => { + return item.target + }) + } + // 根据目标题获取显示逻辑 + findConditionByTarget(target: string) { + return this.rules.filter(rule=> rule.target === target).map(item => item.conditions) + } +} + + +export const ruleSchema = yup.array().of( + yup.object({ + target: yup.string().required(), + scope: yup.string().required(), + conditions: yup.array().of( + yup.object({ + field: yup.string().required(), + operator: yup.string().required(), + value: yup.array().of(yup.string().required()) + }) + ) + }) +) \ No newline at end of file diff --git a/web/src/common/logicEngine/RulesMatch.ts b/web/src/common/logicEngine/RulesMatch.ts new file mode 100644 index 00000000..3b1ce657 --- /dev/null +++ b/web/src/common/logicEngine/RulesMatch.ts @@ -0,0 +1,201 @@ + +import { Operator, type FieldTypes, type Fact } from "./BasicType"; + +// 定义条件规则类 +export class ConditionNode { + // 默认显示 + public result: boolean = false; + constructor(public field: F, public operator: O, public value: FieldTypes) { + } + + // 计算条件规则的哈希值 + calculateHash(): string { + // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 + return this.field + this.operator + this.value; + } + + match(facts: Fact): boolean { + // console.log(this.calculateHash()) + // 如果该特征在事实对象中不存在,则直接返回false + if(!facts[this.field]) { + this.result = false + return this.result + } + switch (this.operator) { + case Operator.Equal: + if(this.value instanceof Array) { + this.result = this.value.every(v => facts[this.field].includes(v)) + return this.result + } else { + this.result = facts[this.field].includes(this.value); + return this.result + } + case Operator.Include: + if(this.value instanceof Array) { + this.result = this.value.some(v => facts[this.field].includes(v)) + return this.result + } else { + this.result = facts[this.field].includes(this.value); + return this.result + } + case Operator.NotInclude: + if(this.value instanceof Array) { + this.result = this.value.some(v => !facts[this.field].includes(v)) + return this.result + } else { + this.result = facts[this.field].includes(this.value); + return this.result + } + case Operator.NotEqual: + if(this.value instanceof Array) { + this.result = this.value.every(v => !facts[this.field].includes(v)) + return this.result + } else { + this.result = facts[this.field].includes(this.value); + return this.result + } + // 其他比较操作符的判断逻辑 + default: + return this.result + } + + } + + getResult() { + return this.result + } +} + +export class RuleNode { + conditions: Map>; // 使用哈希表存储条件规则对象 + public result: boolean = false; + constructor(public target: string, public scope: string) { + this.conditions = new Map(); + } + // 添加条件规则到规则引擎中 + addCondition(condition: ConditionNode) { + const hash = condition.calculateHash(); + this.conditions.set(hash, condition); + } + + // 匹配条件规则 + match(fact: Fact) { + const 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 + } + getResult() { + const res = Array.from(this.conditions.entries()).every(([, value]) => { + const res = value.getResult() + return res + }) + return res + } + + // 计算条件规则的哈希值 + calculateHash(): string { + // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 + return this.target + this.scope; + } + toJson() { + return { + target: this.target, + scope: this.scope, + conditions: Object.fromEntries( + Array.from(this.conditions, ([key, value]) => [key, value.getResult()]) + ) + }; + } + +} + +export class RuleMatch { + rules: Map; + static instance: any; + constructor() { + this.rules = new Map(); + if (!RuleMatch.instance) { + RuleMatch.instance = this; + } + + return RuleMatch.instance; + } + fromJson(ruleConf:any) { + if(ruleConf instanceof Array) { + ruleConf.forEach((rule: any) => { + const ruleNode = new RuleNode(rule.target, rule.scope); + rule.conditions.forEach((condition: any) => { + const conditionNode = new ConditionNode(condition.field, condition.operator, condition.value); + ruleNode.addCondition(conditionNode) + }); + this.addRule(ruleNode) + }) + } + } + + // 添加条件规则到规则引擎中 + addRule(rule: RuleNode) { + const hash = rule.calculateHash(); + if (this.rules.has(hash)) { + const existRule: any = this.rules.get(hash); + existRule.conditions.forEach((item: ConditionNode) => { + rule.addCondition(item) + }) + } + + this.rules.set(hash, rule); + } + + + // 匹配条件规则 + match(target: string, scope: string, fact: Fact) { + const hash = this.calculateHash(target, scope); + + const rule = this.rules.get(hash); + if (rule) { + const result = rule.match(fact) + // this.matchCache.set(hash, result); + return result + } else { + // 默认显示 + return true + } + } + + getResult(target: string, scope: string) { + const hash = this.calculateHash(target, scope); + const rule = this.rules.get(hash); + if (rule) { + const result = rule.getResult() + return result + } else { + // 默认显示 + return true + } + } + // 计算哈希值的方法 + calculateHash(target: string, scope: string): string { + // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 + 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 + }) + })) + return [...rules.values()].map(obj => obj.target); + } + toJson() { + return Array.from(this.rules.entries()).map(([, value]) => { + return value.toJson() + }) + } +} diff --git a/web/src/common/logicEngine/ruleConf.ts b/web/src/common/logicEngine/ruleConf.ts new file mode 100644 index 00000000..4f979283 --- /dev/null +++ b/web/src/common/logicEngine/ruleConf.ts @@ -0,0 +1,36 @@ +// 静态数据 +export const ruleConf = [ + { + conditions: [ + { + field: 'data515', // 题目2 + operator: 'in', + value: ['115019'] + } + ], + scope: 'question', + target: 'data648' // 题目3 + }, + { + conditions: [ + { + field: 'data648', // 题目3 + operator: 'in', + value: ['106374'] + } + ], + scope: 'question', + target: 'data517' // 题目4 + }, + { + conditions: [ + { + field: 'data648', // 题目3 + operator: 'in', + value: ['106374'] + } + ], + scope: 'option', + target: 'data517-106374' // 题目4 + } +] diff --git a/web/src/management/hooks/useQuestionInfo.js b/web/src/management/hooks/useQuestionInfo.js new file mode 100644 index 00000000..1612493a --- /dev/null +++ b/web/src/management/hooks/useQuestionInfo.js @@ -0,0 +1,23 @@ +import { computed } from 'vue'; +import store from '@/management/store' +import { cleanRichText } from '@/common/xss' +export const useQuestionInfo = (field) => { + const getQuestionTitle = computed(() => { + const questionDataList = store.state.edit.schema.questionDataList + return () => { + return questionDataList.find((item) => item.field === field)?.title + } + }) + const getOptionTitle = computed(() => { + const questionDataList = store.state.edit.schema.questionDataList + return (value) => { + const options = questionDataList.find((item) => item.field === field)?.options || [] + if(value instanceof Array) { + return options.filter((item) => value.includes(item.hash)).map((item) => cleanRichText(item.text)) + } else { + return options.filter((item) => item.hash === value).map((item) => cleanRichText(item.text)) + } + } + }) + return { getQuestionTitle, getOptionTitle } +} diff --git a/web/src/management/hooks/useShowLogicEngine.js b/web/src/management/hooks/useShowLogicEngine.js new file mode 100644 index 00000000..50c8d5d7 --- /dev/null +++ b/web/src/management/hooks/useShowLogicEngine.js @@ -0,0 +1,7 @@ +import { ref } from 'vue' +import { RuleBuild } from '@/common/logicEngine/RuleBuild' + +export const showLogicEngine = ref() +export const initShowLogicEngine = (ruleConf) => { + showLogicEngine.value = new RuleBuild().fromJson(ruleConf) +} \ No newline at end of file diff --git a/web/src/management/hooks/useShowLogicInfo.js b/web/src/management/hooks/useShowLogicInfo.js new file mode 100644 index 00000000..15b89bf4 --- /dev/null +++ b/web/src/management/hooks/useShowLogicInfo.js @@ -0,0 +1,29 @@ +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' + +// 目标题的显示逻辑提示文案 +export const useShowLogicInfo = (field) => { + const hasShowLogic = computed(() => { + const logicEngine = showLogicEngine.value + // 判断该题是否作为了显示逻辑前置题 + const isField = logicEngine?.findTargetsByFields(field)?.length > 0 + // 判断该题是否作为了显示逻辑目标题 + const isTarget = logicEngine?.findTargetsByScope(field)?.length > 0 + return isField || isTarget + }) + const getShowLogicText = computed(() => { + const logicEngine = showLogicEngine.value + // 获取目标题的规则 + const rules = logicEngine?.findConditionByTarget(field) || [] + + const conditions = flatten(rules).map((item) => { + const { getQuestionTitle, getOptionTitle } = useQuestionInfo(item.field) + return `【 ${cleanRichText(getQuestionTitle.value())}】 选择了 【${getOptionTitle.value(unref(item.value)).join('、')}】
` + }) + return conditions.length ? conditions.join('') + '  满足以上全部,则显示本题' :'' + }) + return { hasShowLogic, getShowLogicText } +} \ No newline at end of file diff --git a/web/src/management/pages/edit/components/QuestionWrapper.vue b/web/src/management/pages/edit/components/QuestionWrapper.vue index a334c75b..763200f4 100644 --- a/web/src/management/pages/edit/components/QuestionWrapper.vue +++ b/web/src/management/pages/edit/components/QuestionWrapper.vue @@ -6,7 +6,7 @@ @click="clickFormItem" >
- +
@@ -24,12 +24,14 @@
+
+ diff --git a/web/src/management/pages/edit/modules/logicModule/components/ConditionView.vue b/web/src/management/pages/edit/modules/logicModule/components/ConditionView.vue new file mode 100644 index 00000000..d45494ed --- /dev/null +++ b/web/src/management/pages/edit/modules/logicModule/components/ConditionView.vue @@ -0,0 +1,195 @@ + + + \ No newline at end of file diff --git a/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue b/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue new file mode 100644 index 00000000..577216fc --- /dev/null +++ b/web/src/management/pages/edit/modules/logicModule/components/RuleNodeView.vue @@ -0,0 +1,147 @@ + + + \ No newline at end of file diff --git a/web/src/management/pages/edit/modules/questionModule/PreviewPanel.vue b/web/src/management/pages/edit/modules/questionModule/PreviewPanel.vue index 6479d35f..e4dfa24d 100644 --- a/web/src/management/pages/edit/modules/questionModule/PreviewPanel.vue +++ b/web/src/management/pages/edit/modules/questionModule/PreviewPanel.vue @@ -184,7 +184,6 @@ export default { .operation-wrapper { margin-top: 50px; margin-bottom: 45px; - // min-height: 812px; overflow-x: hidden; overflow-y: auto; padding-right: 30px; diff --git a/web/src/management/pages/edit/modules/settingModule/skin/SetterPanel.vue b/web/src/management/pages/edit/modules/settingModule/skin/SetterPanel.vue index a740ca64..d5314353 100644 --- a/web/src/management/pages/edit/modules/settingModule/skin/SetterPanel.vue +++ b/web/src/management/pages/edit/modules/settingModule/skin/SetterPanel.vue @@ -95,25 +95,4 @@ export default { padding: 0 !important; } } -.no-select-question { - padding-top: 125px; - display: flex; - flex-direction: column; - align-items: center; - - img { - width: 160px; - padding: 25px; - } - - .tip { - font-size: 14px; - color: $normal-color; - letter-spacing: 0; - } -} - -.question-config-form { - padding: 30px 20px 50px 20px; -} diff --git a/web/src/management/pages/edit/pages/EditPage.vue b/web/src/management/pages/edit/pages/EditPage.vue deleted file mode 100644 index 9c7849e0..00000000 --- a/web/src/management/pages/edit/pages/EditPage.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/web/src/management/pages/edit/pages/edit/LogicEditPage.vue b/web/src/management/pages/edit/pages/edit/LogicEditPage.vue new file mode 100644 index 00000000..966ebc2c --- /dev/null +++ b/web/src/management/pages/edit/pages/edit/LogicEditPage.vue @@ -0,0 +1,32 @@ + + + diff --git a/web/src/management/pages/edit/pages/edit/QuestionEditPage.vue b/web/src/management/pages/edit/pages/edit/QuestionEditPage.vue new file mode 100644 index 00000000..af072310 --- /dev/null +++ b/web/src/management/pages/edit/pages/edit/QuestionEditPage.vue @@ -0,0 +1,33 @@ + + + \ No newline at end of file diff --git a/web/src/management/pages/edit/pages/edit/index.vue b/web/src/management/pages/edit/pages/edit/index.vue new file mode 100644 index 00000000..61915934 --- /dev/null +++ b/web/src/management/pages/edit/pages/edit/index.vue @@ -0,0 +1,68 @@ + + + diff --git a/web/src/management/pages/edit/pages/SettingPage.vue b/web/src/management/pages/edit/pages/setting/index.vue similarity index 79% rename from web/src/management/pages/edit/pages/SettingPage.vue rename to web/src/management/pages/edit/pages/setting/index.vue index eceba5e5..9212f087 100644 --- a/web/src/management/pages/edit/pages/SettingPage.vue +++ b/web/src/management/pages/edit/pages/setting/index.vue @@ -5,7 +5,7 @@ diff --git a/web/src/render/constant/index.js b/web/src/render/constant/index.js new file mode 100644 index 00000000..f50b6d80 --- /dev/null +++ b/web/src/render/constant/index.js @@ -0,0 +1,12 @@ +export const QUESTION_TYPE = { + VOTE: 'vote', + CHECKBOX: 'checkbox', + CHOICES: [ // 选择类题型分类 + 'radio', + 'checkbox', + ], + RATES: [ // 评分题题型分类 + 'radio-star', + 'radio-nps' + ] +} \ No newline at end of file diff --git a/web/src/render/hooks/useRuleEngine.js b/web/src/render/hooks/useRuleEngine.js new file mode 100644 index 00000000..00dcee4f --- /dev/null +++ b/web/src/render/hooks/useRuleEngine.js @@ -0,0 +1,6 @@ +import { RuleMatch } from '@/common/logicEngine/RulesMatch' + +export const ruleEngine = new RuleMatch() +export const initRuleEngine = (ruleConf) => { + ruleEngine.fromJson(ruleConf) +} \ No newline at end of file diff --git a/web/src/render/hooks/useShowInput.js b/web/src/render/hooks/useShowInput.js new file mode 100644 index 00000000..2dc2df82 --- /dev/null +++ b/web/src/render/hooks/useShowInput.js @@ -0,0 +1,30 @@ +import store from '../store/index' +export const useShowInput = (questionKey) => { + const formValues = store.state.formValues + const questionVal = formValues[questionKey] + let rangeConfig = store.state.questionData[questionKey].rangeConfig + let othersValue = {} + if (rangeConfig && Object.keys(rangeConfig).length > 0) { + for(let key in rangeConfig) { + const curRange = rangeConfig[key] + if (curRange.isShowInput) { + const rangeKey = `${questionKey}_${key}` + othersValue[rangeKey] = formValues[rangeKey] + + curRange.othersKey = rangeKey, + curRange.othersValue = formValues[rangeKey] + if(!questionVal.toString().includes(key) && formValues[rangeKey]) { + // 如果分值被未被选中且对应的填写更多有值,则清空填写更多 + const data = { + key: rangeKey, + value: '' + } + store.commit('changeFormData', data) + } + } + } + } + + + return { rangeConfig, othersValue } +} \ No newline at end of file diff --git a/web/src/render/hooks/useShowOthers.js b/web/src/render/hooks/useShowOthers.js new file mode 100644 index 00000000..9323326f --- /dev/null +++ b/web/src/render/hooks/useShowOthers.js @@ -0,0 +1,29 @@ +import store from '../store/index' +export const useShowOthers = (questionKey) => { + const formValues = store.state.formValues + const questionVal = formValues[questionKey] + let othersValue = {} + let options = store.state.questionData[questionKey].options.map(optionItem => { + if (optionItem.others) { + const opKey = `${questionKey}_${optionItem.hash}` + othersValue[opKey] = formValues[opKey] + if(!questionVal.includes(optionItem.hash) && formValues[opKey]) { + // 如果选项被未被选中且对应的填写更多有值,则清空填写更多 + const data = { + key: opKey, + value: '' + } + store.commit('changeFormData', data) + } + return { + ...optionItem, + othersKey: opKey, + othersValue: formValues[opKey] + } + } else { + return optionItem + } + }) + + return { options, othersValue } +} \ No newline at end of file diff --git a/web/src/render/hooks/useVoteMap.js b/web/src/render/hooks/useVoteMap.js new file mode 100644 index 00000000..3edac314 --- /dev/null +++ b/web/src/render/hooks/useVoteMap.js @@ -0,0 +1,17 @@ +import store from '../store/index' +export const useVoteMap = (questionKey) => { + + let voteTotal = store.state.voteMap?.[questionKey]?.total || 0 + + const options = store.state.questionData[questionKey].options.map(option => { + const optionHash = option.hash + const voteCount = store.state.voteMap?.[questionKey]?.[optionHash] || 0 + + return { + ...option, + voteCount + } + }) + + return { options, voteTotal } +} \ No newline at end of file diff --git a/web/src/render/pages/IndexPage.vue b/web/src/render/pages/IndexPage.vue index 784f10f5..4bbf0ca8 100644 --- a/web/src/render/pages/IndexPage.vue +++ b/web/src/render/pages/IndexPage.vue @@ -1,12 +1,12 @@