format: 代码格式化 (#160)

This commit is contained in:
dayou 2024-05-23 21:52:57 +08:00 committed by sudoooooo
parent 38566f1f60
commit 4c85fcc47e
30 changed files with 291 additions and 274 deletions

10
web/components.d.ts vendored
View File

@ -11,26 +11,30 @@ declare module 'vue' {
ElCheckbox: typeof import('element-plus/es')['ElCheckbox'] ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] 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'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElForm: typeof import('element-plus/es')['ElForm'] ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElInput: typeof import('element-plus/es')['ElInput'] ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] 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'] ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination'] ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu'] ElSlider: typeof import('element-plus/es')['ElSlider']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable'] ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs'] 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'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
IEpBottom: typeof import('~icons/ep/bottom')['default'] IEpBottom: typeof import('~icons/ep/bottom')['default']
IEpCheck: typeof import('~icons/ep/check')['default'] IEpCheck: typeof import('~icons/ep/check')['default']

View File

@ -8,25 +8,22 @@ export enum Operator {
Include = 'in', Include = 'in',
Equal = 'eq', Equal = 'eq',
NotEqual = 'neq', NotEqual = 'neq',
NotInclude = 'nin', NotInclude = 'nin'
} }
export enum PrefixID { export enum PrefixID {
Rule = 'r', Rule = 'r',
Condition = 'c' Condition = 'c'
} }
export enum Scope { export enum Scope {
Question = 'question', Question = 'question',
Option = 'option' Option = 'option'
} }
export type FieldTypes = string | string[]
export type FieldTypes = string | string[];
// 定义事实对象类型 // 定义事实对象类型
export type Fact = { export type Fact = {
[key: string]: any; [key: string]: any
}; }

View File

@ -1,4 +1,4 @@
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid'
import * as yup from 'yup' import * as yup from 'yup'
import { type FieldTypes, PrefixID, Operator, Scope } from './BasicType' import { type FieldTypes, PrefixID, Operator, Scope } from './BasicType'
@ -7,33 +7,33 @@ export function generateID(prefix = PrefixID.Rule) {
} }
// 定义条件规则类 // 定义条件规则类
export class ConditionNode { export class ConditionNode {
id: string = ''; id: string = ''
public field: string = ''; public field: string = ''
public operator: Operator = Operator.Include; public operator: Operator = Operator.Include
public value: FieldTypes = [] public value: FieldTypes = []
constructor(field: string = '', operator: Operator = Operator.Include, value: FieldTypes = []) { constructor(field: string = '', operator: Operator = Operator.Include, value: FieldTypes = []) {
this.field = field; this.field = field
this.operator = operator; this.operator = operator
this.value = value; this.value = value
this.id = generateID(PrefixID.Condition) this.id = generateID(PrefixID.Condition)
} }
setField(field: string) { setField(field: string) {
this.field = field; this.field = field
} }
setOperator(operator: Operator) { setOperator(operator: Operator) {
this.operator = operator; this.operator = operator
} }
setValue(value: FieldTypes) { setValue(value: FieldTypes) {
this.value = value; this.value = value
} }
} }
export class RuleNode { export class RuleNode {
id: string = ''; id: string = ''
conditions: ConditionNode[] = [] conditions: ConditionNode[] = []
scope: string = Scope.Question scope: string = Scope.Question
target: string = '' target: string = ''
constructor(scope:string = Scope.Question, target: string = '') { constructor(scope: string = Scope.Question, target: string = '') {
this.id = generateID(PrefixID.Rule) this.id = generateID(PrefixID.Rule)
this.scope = scope this.scope = scope
this.target = target this.target = target
@ -42,44 +42,44 @@ export class RuleNode {
this.target = value this.target = value
} }
addCondition(condition: ConditionNode) { addCondition(condition: ConditionNode) {
this.conditions.push(condition); this.conditions.push(condition)
} }
removeCondition(id: string) { removeCondition(id: string) {
this.conditions = this.conditions.filter(v => v.id !== id); this.conditions = this.conditions.filter((v) => v.id !== id)
} }
findCondition(conditionId: string) { findCondition(conditionId: string) {
return this.conditions.find(condition => condition.id === conditionId); return this.conditions.find((condition) => condition.id === conditionId)
} }
} }
export class RuleBuild { export class RuleBuild {
rules: RuleNode[] = []; rules: RuleNode[] = []
static instance: RuleBuild; static instance: RuleBuild
constructor() { constructor() {
this.rules = []; this.rules = []
if (!RuleBuild.instance) { if (!RuleBuild.instance) {
RuleBuild.instance = this; RuleBuild.instance = this
} }
return RuleBuild.instance; return RuleBuild.instance
} }
// 添加条件规则到规则引擎中 // 添加条件规则到规则引擎中
addRule(rule: RuleNode) { addRule(rule: RuleNode) {
this.rules.push(rule); this.rules.push(rule)
} }
removeRule(ruleId: string) { removeRule(ruleId: string) {
this.rules = this.rules.filter(rule => rule.id !== ruleId); this.rules = this.rules.filter((rule) => rule.id !== ruleId)
} }
findRule(ruleId: string) { findRule(ruleId: string) {
return this.rules.find(rule => rule.id === ruleId); return this.rules.find((rule) => rule.id === ruleId)
} }
toJson() { toJson() {
return this.rules.map(rule => { return this.rules.map((rule) => {
return { return {
target: rule.target, target: rule.target,
scope: rule.scope, scope: rule.scope,
conditions: rule.conditions.map(condition => { conditions: rule.conditions.map((condition) => {
return { return {
field: condition.field, field: condition.field,
operator: condition.operator, operator: condition.operator,
@ -91,13 +91,13 @@ export class RuleBuild {
} }
fromJson(ruleConf: any) { fromJson(ruleConf: any) {
this.rules = [] this.rules = []
if(ruleConf instanceof Array) { if (ruleConf instanceof Array) {
ruleConf.forEach((rule: any) => { ruleConf.forEach((rule: any) => {
const { scope, target } = rule const { scope, target } = rule
const ruleNode = new RuleNode(scope, target); const ruleNode = new RuleNode(scope, target)
rule.conditions.forEach((condition: any) => { rule.conditions.forEach((condition: any) => {
const { field, operator, value } = condition const { field, operator, value } = condition
const conditionNode = new ConditionNode(field, operator, value); const conditionNode = new ConditionNode(field, operator, value)
ruleNode.addCondition(conditionNode) ruleNode.addCondition(conditionNode)
}) })
this.addRule(ruleNode) this.addRule(ruleNode)
@ -109,16 +109,16 @@ export class RuleBuild {
return ruleSchema.validateSync(this.toJson()) return ruleSchema.validateSync(this.toJson())
} }
// 实现目标选择了下拉框置灰效果 // 实现目标选择了下拉框置灰效果
findTargetsByScope(scope: string){ findTargetsByScope(scope: string) {
return this.rules.filter(rule => rule.scope === scope).map(rule => rule.target) return this.rules.filter((rule) => rule.scope === scope).map((rule) => rule.target)
} }
// 实现前置题删除校验 // 实现前置题删除校验
findTargetsByFields(field: string) { findTargetsByFields(field: string) {
const nodes = this.rules.filter((rule: RuleNode) => { const nodes = this.rules.filter((rule: RuleNode) => {
const conditions = rule.conditions.filter((item: any) => { const conditions = rule.conditions.filter((item: any) => {
return item.field === field return item.field === field
}) })
return conditions.length > 0 return conditions.length > 0
}) })
return nodes.map((item: any) => { return nodes.map((item: any) => {
return item.target return item.target
@ -126,11 +126,10 @@ export class RuleBuild {
} }
// 根据目标题获取显示逻辑 // 根据目标题获取显示逻辑
findConditionByTarget(target: string) { findConditionByTarget(target: string) {
return this.rules.filter(rule=> rule.target === target).map(item => item.conditions) return this.rules.filter((rule) => rule.target === target).map((item) => item.conditions)
} }
} }
export const ruleSchema = yup.array().of( export const ruleSchema = yup.array().of(
yup.object({ yup.object({
target: yup.string().required(), target: yup.string().required(),
@ -143,4 +142,4 @@ export const ruleSchema = yup.array().of(
}) })
) )
}) })
) )

View File

@ -1,64 +1,65 @@
import { Operator, type FieldTypes, type Fact } from './BasicType'
import { Operator, type FieldTypes, type Fact } from "./BasicType";
// 定义条件规则类 // 定义条件规则类
export class ConditionNode<F extends string, O extends Operator> { export class ConditionNode<F extends string, O extends Operator> {
// 默认显示 // 默认显示
public result: boolean = false; public result: boolean = false
constructor(public field: F, public operator: O, public value: FieldTypes) { constructor(
} public field: F,
public operator: O,
public value: FieldTypes
) {}
// 计算条件规则的哈希值 // 计算条件规则的哈希值
calculateHash(): string { calculateHash(): string {
// 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法
return this.field + this.operator + this.value; return this.field + this.operator + this.value
} }
match(facts: Fact): boolean { match(facts: Fact): boolean {
// console.log(this.calculateHash()) // console.log(this.calculateHash())
// 如果该特征在事实对象中不存在则直接返回false // 如果该特征在事实对象中不存在则直接返回false
if(!facts[this.field]) { if (!facts[this.field]) {
this.result = false this.result = false
return this.result return this.result
} }
switch (this.operator) { switch (this.operator) {
case Operator.Equal: case Operator.Equal:
if(this.value instanceof Array) { if (this.value instanceof Array) {
this.result = this.value.every(v => facts[this.field].includes(v)) this.result = this.value.every((v) => facts[this.field].includes(v))
return this.result return this.result
} else { } else {
this.result = facts[this.field].includes(this.value); this.result = facts[this.field].includes(this.value)
return this.result return this.result
} }
case Operator.Include: case Operator.Include:
if(this.value instanceof Array) { if (this.value instanceof Array) {
this.result = this.value.some(v => facts[this.field].includes(v)) this.result = this.value.some((v) => facts[this.field].includes(v))
return this.result return this.result
} else { } else {
this.result = facts[this.field].includes(this.value); this.result = facts[this.field].includes(this.value)
return this.result return this.result
} }
case Operator.NotInclude: case Operator.NotInclude:
if(this.value instanceof Array) { if (this.value instanceof Array) {
this.result = this.value.some(v => !facts[this.field].includes(v)) this.result = this.value.some((v) => !facts[this.field].includes(v))
return this.result return this.result
} else { } else {
this.result = facts[this.field].includes(this.value); this.result = facts[this.field].includes(this.value)
return this.result return this.result
} }
case Operator.NotEqual: case Operator.NotEqual:
if(this.value instanceof Array) { if (this.value instanceof Array) {
this.result = this.value.every(v => !facts[this.field].includes(v)) this.result = this.value.every((v) => !facts[this.field].includes(v))
return this.result return this.result
} else { } else {
this.result = facts[this.field].includes(this.value); this.result = facts[this.field].includes(this.value)
return this.result return this.result
} }
// 其他比较操作符的判断逻辑 // 其他比较操作符的判断逻辑
default: default:
return this.result return this.result
} }
} }
getResult() { getResult() {
@ -67,27 +68,30 @@ export class ConditionNode<F extends string, O extends Operator> {
} }
export class RuleNode { export class RuleNode {
conditions: Map<string, ConditionNode<string, Operator>>; // 使用哈希表存储条件规则对象 conditions: Map<string, ConditionNode<string, Operator>> // 使用哈希表存储条件规则对象
public result: boolean = false; public result: boolean = false
constructor(public target: string, public scope: string) { constructor(
this.conditions = new Map(); public target: string,
public scope: string
) {
this.conditions = new Map()
} }
// 添加条件规则到规则引擎中 // 添加条件规则到规则引擎中
addCondition(condition: ConditionNode<string, Operator>) { addCondition(condition: ConditionNode<string, Operator>) {
const hash = condition.calculateHash(); const hash = condition.calculateHash()
this.conditions.set(hash, condition); this.conditions.set(hash, condition)
} }
// 匹配条件规则 // 匹配条件规则
match(fact: Fact) { match(fact: Fact) {
const res = Array.from(this.conditions.entries()).every(([, value]) => { const res = Array.from(this.conditions.entries()).every(([, value]) => {
const res = value.match(fact) const res = value.match(fact)
if (res) { if (res) {
return true; return true
} else { } else {
return false return false
} }
}); })
this.result = res this.result = res
return res return res
} }
@ -102,7 +106,7 @@ export class RuleNode {
// 计算条件规则的哈希值 // 计算条件规则的哈希值
calculateHash(): string { calculateHash(): string {
// 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法
return this.target + this.scope; return this.target + this.scope
} }
toJson() { toJson() {
return { return {
@ -111,30 +115,33 @@ export class RuleNode {
conditions: Object.fromEntries( conditions: Object.fromEntries(
Array.from(this.conditions, ([key, value]) => [key, value.getResult()]) Array.from(this.conditions, ([key, value]) => [key, value.getResult()])
) )
}; }
} }
} }
export class RuleMatch { export class RuleMatch {
rules: Map<string, RuleNode>; rules: Map<string, RuleNode>
static instance: any; static instance: any
constructor() { constructor() {
this.rules = new Map(); this.rules = new Map()
if (!RuleMatch.instance) { if (!RuleMatch.instance) {
RuleMatch.instance = this; RuleMatch.instance = this
} }
return RuleMatch.instance; return RuleMatch.instance
} }
fromJson(ruleConf:any) { fromJson(ruleConf: any) {
if(ruleConf instanceof Array) { if (ruleConf instanceof Array) {
ruleConf.forEach((rule: any) => { ruleConf.forEach((rule: any) => {
const ruleNode = new RuleNode(rule.target, rule.scope); const ruleNode = new RuleNode(rule.target, rule.scope)
rule.conditions.forEach((condition: any) => { rule.conditions.forEach((condition: any) => {
const conditionNode = new ConditionNode(condition.field, condition.operator, condition.value); const conditionNode = new ConditionNode(
ruleNode.addCondition(conditionNode) condition.field,
}); condition.operator,
condition.value
)
ruleNode.addCondition(conditionNode)
})
this.addRule(ruleNode) this.addRule(ruleNode)
}) })
} }
@ -142,23 +149,22 @@ export class RuleMatch {
// 添加条件规则到规则引擎中 // 添加条件规则到规则引擎中
addRule(rule: RuleNode) { addRule(rule: RuleNode) {
const hash = rule.calculateHash(); const hash = rule.calculateHash()
if (this.rules.has(hash)) { if (this.rules.has(hash)) {
const existRule: any = this.rules.get(hash); const existRule: any = this.rules.get(hash)
existRule.conditions.forEach((item: ConditionNode<string, Operator>) => { existRule.conditions.forEach((item: ConditionNode<string, Operator>) => {
rule.addCondition(item) rule.addCondition(item)
}) })
} }
this.rules.set(hash, rule);
}
this.rules.set(hash, rule)
}
// 匹配条件规则 // 匹配条件规则
match(target: string, scope: string, fact: Fact) { match(target: string, scope: string, fact: Fact) {
const hash = this.calculateHash(target, scope); const hash = this.calculateHash(target, scope)
const rule = this.rules.get(hash); const rule = this.rules.get(hash)
if (rule) { if (rule) {
const result = rule.match(fact) const result = rule.match(fact)
// this.matchCache.set(hash, result); // this.matchCache.set(hash, result);
@ -168,10 +174,10 @@ export class RuleMatch {
return true return true
} }
} }
getResult(target: string, scope: string) { getResult(target: string, scope: string) {
const hash = this.calculateHash(target, scope); const hash = this.calculateHash(target, scope)
const rule = this.rules.get(hash); const rule = this.rules.get(hash)
if (rule) { if (rule) {
const result = rule.getResult() const result = rule.getResult()
return result return result
@ -183,15 +189,17 @@ export class RuleMatch {
// 计算哈希值的方法 // 计算哈希值的方法
calculateHash(target: string, scope: string): string { calculateHash(target: string, scope: string): string {
// 假设哈希值计算方法为简单的字符串拼接或其他哈希算法 // 假设哈希值计算方法为简单的字符串拼接或其他哈希算法
return target + scope; return target + scope
} }
findTargetsByField(field: string) { findTargetsByField(field: string) {
const rules = new Map([...this.rules.entries()].filter(([, value]) => { const rules = new Map(
return [...value.conditions.entries()].filter(([, value]) => { [...this.rules.entries()].filter(([, value]) => {
return value.field === field return [...value.conditions.entries()].filter(([, value]) => {
return value.field === field
})
}) })
})) )
return [...rules.values()].map(obj => obj.target); return [...rules.values()].map((obj) => obj.target)
} }
toJson() { toJson() {
return Array.from(this.rules.entries()).map(([, value]) => { return Array.from(this.rules.entries()).map(([, value]) => {

View File

@ -1 +1 @@
export const DND_GROUP = 'question' export const DND_GROUP = 'question'

View File

@ -1,4 +1,4 @@
import { computed } from 'vue'; import { computed } from 'vue'
import store from '@/management/store' import store from '@/management/store'
import { cleanRichText } from '@/common/xss' import { cleanRichText } from '@/common/xss'
export const useQuestionInfo = (field) => { export const useQuestionInfo = (field) => {
@ -12,9 +12,11 @@ export const useQuestionInfo = (field) => {
const questionDataList = store.state.edit.schema.questionDataList const questionDataList = store.state.edit.schema.questionDataList
return (value) => { return (value) => {
const options = questionDataList.find((item) => item.field === field)?.options || [] const options = questionDataList.find((item) => item.field === field)?.options || []
if(value instanceof Array) { if (value instanceof Array) {
return options.filter((item) => value.includes(item.hash)).map((item) => cleanRichText(item.text)) return options
} else { .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 options.filter((item) => item.hash === value).map((item) => cleanRichText(item.text))
} }
} }

View File

@ -4,4 +4,4 @@ import { RuleBuild } from '@/common/logicEngine/RuleBuild'
export const showLogicEngine = ref() export const showLogicEngine = ref()
export const initShowLogicEngine = (ruleConf) => { export const initShowLogicEngine = (ruleConf) => {
showLogicEngine.value = new RuleBuild().fromJson(ruleConf) showLogicEngine.value = new RuleBuild().fromJson(ruleConf)
} }

View File

@ -1,4 +1,4 @@
import { computed, unref } from 'vue'; import { computed, unref } from 'vue'
import { useQuestionInfo } from './useQuestionInfo' import { useQuestionInfo } from './useQuestionInfo'
import { flatten } from 'lodash-es' import { flatten } from 'lodash-es'
import { cleanRichText } from '@/common/xss' import { cleanRichText } from '@/common/xss'
@ -16,14 +16,16 @@ export const useShowLogicInfo = (field) => {
}) })
const getShowLogicText = computed(() => { const getShowLogicText = computed(() => {
const logicEngine = showLogicEngine.value const logicEngine = showLogicEngine.value
// 获取目标题的规则 // 获取目标题的规则
const rules = logicEngine?.findConditionByTarget(field) || [] const rules = logicEngine?.findConditionByTarget(field) || []
const conditions = flatten(rules).map((item) => { const conditions = flatten(rules).map((item) => {
const { getQuestionTitle, getOptionTitle } = useQuestionInfo(item.field) const { getQuestionTitle, getOptionTitle } = useQuestionInfo(item.field)
return `<span>【 ${cleanRichText(getQuestionTitle.value())}】 选择了 【${getOptionTitle.value(unref(item.value)).join('、')}】</span> <br/>` return `<span>【 ${cleanRichText(getQuestionTitle.value())}】 选择了 【${getOptionTitle.value(unref(item.value)).join('、')}】</span> <br/>`
}) })
return conditions.length ? conditions.join('') + '<span> &nbsp;满足以上全部,则显示本题</span>' :'' return conditions.length
? conditions.join('') + '<span> &nbsp;满足以上全部,则显示本题</span>'
: ''
}) })
return { hasShowLogic, getShowLogicText } return { hasShowLogic, getShowLogicText }
} }

View File

@ -41,7 +41,7 @@ export default {
if (this.isPublishing) { if (this.isPublishing) {
return return
} }
try { try {
this.isPublishing = true this.isPublishing = true
const saveRes = await saveSurvey(saveData) const saveRes = await saveSurvey(saveData)
@ -66,7 +66,7 @@ export default {
} }
}, },
updateLogicConf() { updateLogicConf() {
if(showLogicEngine.value) { if (showLogicEngine.value) {
showLogicEngine.value.validateSchema() showLogicEngine.value.validateSchema()
const showLogicConf = showLogicEngine.value.toJson() const showLogicConf = showLogicEngine.value.toJson()
// //

View File

@ -90,7 +90,7 @@ export default {
} }
}, },
updateLogicConf() { updateLogicConf() {
if(showLogicEngine.value) { if (showLogicEngine.value) {
showLogicEngine.value.validateSchema() showLogicEngine.value.validateSchema()
const showLogicConf = showLogicEngine.value.toJson() const showLogicConf = showLogicEngine.value.toJson()
// //
@ -118,7 +118,7 @@ export default {
ElMessage.error('请检查逻辑配置是否有误') ElMessage.error('请检查逻辑配置是否有误')
return return
} }
try { try {
this.isSaving = true this.isSaving = true
const res = await this.saveData() const res = await this.saveData()

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="rule-list"> <div class="rule-list">
<RuleNodeView <RuleNodeView
v-for="(item) in list" v-for="item in list"
ref="ruleWrappers" ref="ruleWrappers"
:key="item.id" :key="item.id"
:ruleNode="item" :ruleNode="item"

View File

@ -78,7 +78,8 @@ const props = defineProps({
}) })
const fieldList = computed(() => { const fieldList = computed(() => {
const currentIndex = renderData.value.findIndex((item) => item.field === props.ruleNode.target) const currentIndex = renderData.value.findIndex((item) => item.field === props.ruleNode.target)
return renderData.value.slice(0, currentIndex) return renderData.value
.slice(0, currentIndex)
.filter((question: any) => qAbleList.includes(question.type)) .filter((question: any) => qAbleList.includes(question.type))
.map((item: any) => { .map((item: any) => {
return { return {
@ -140,14 +141,14 @@ const handleDelete = (id: any) => {
position: relative; position: relative;
display: flex; display: flex;
padding: 24px 0; padding: 24px 0;
&:not(:last-child)::before{ &:not(:last-child)::before {
content: attr(data-content-before); content: attr(data-content-before);
bottom: 0px; bottom: 0px;
width: 20px; width: 20px;
height: 20px; height: 20px;
background: #FEF6E6; background: #fef6e6;
border-radius: 2px; border-radius: 2px;
color: #FAA600; color: #faa600;
font-size: 12px; font-size: 12px;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -155,14 +156,14 @@ const handleDelete = (id: any) => {
position: absolute; position: absolute;
bottom: -8px; bottom: -8px;
} }
&:not(:last-child)::after{ &:not(:last-child)::after {
content: ""; content: '';
display: block; display: block;
width: calc(100% - 32px); width: calc(100% - 32px);
border-top: 1px dashed #e3e4e8; border-top: 1px dashed #e3e4e8;
position: absolute; position: absolute;
left: 32px; left: 32px;
bottom: 0; bottom: 0;
} }
.desc { .desc {
display: inline-block; display: inline-block;
@ -192,4 +193,4 @@ const handleDelete = (id: any) => {
.select { .select {
width: 200px; width: 200px;
} }
</style> </style>

View File

@ -32,7 +32,7 @@
v-for="{ label, value, disabled } in targetQuestionList" v-for="{ label, value, disabled } in targetQuestionList"
:key="value" :key="value"
:label="label" :label="label"
:disabled="disabled && ruleNode.target !== value " :disabled="disabled && ruleNode.target !== value"
:value="value" :value="value"
> >
</el-option> </el-option>
@ -97,7 +97,9 @@ const submitForm = () => {
const targetQuestionList = computed(() => { const targetQuestionList = computed(() => {
const currntIndexs: number[] = [] const currntIndexs: number[] = []
props.ruleNode.conditions.forEach((el) => { props.ruleNode.conditions.forEach((el) => {
currntIndexs.push(renderData.value.findIndex((item: { field: string }) => item.field === el.field)) currntIndexs.push(
renderData.value.findIndex((item: { field: string }) => item.field === el.field)
)
}) })
const currntIndex = Math.max(...currntIndexs) const currntIndex = Math.max(...currntIndexs)
let questionList = cloneDeep(renderData.value.slice(currntIndex + 1)) let questionList = cloneDeep(renderData.value.slice(currntIndex + 1))
@ -105,9 +107,7 @@ const targetQuestionList = computed(() => {
return { return {
label: `${item.showIndex ? item.indexNumber + '.' : ''} ${cleanRichText(item.title)}`, label: `${item.showIndex ? item.indexNumber + '.' : ''} ${cleanRichText(item.title)}`,
value: item.field, value: item.field,
disabled: showLogicEngine.value disabled: showLogicEngine.value.findTargetsByScope('question').includes(item.field)
.findTargetsByScope('question')
.includes(item.field)
} }
}) })
}) })
@ -144,4 +144,4 @@ defineExpose({
.select { .select {
width: 200px; width: 200px;
} }
</style> </style>

View File

@ -6,34 +6,33 @@
:name="index" :name="index"
:key="index" :key="index"
> >
<draggable <draggable
class="questiontype-list" class="questiontype-list"
:list="item.questionList" :list="item.questionList"
:group="{ name: DND_GROUP, pull: 'clone', put: false }" :group="{ name: DND_GROUP, pull: 'clone', put: false }"
:clone="getNewQuestion" :clone="getNewQuestion"
item-key="path" item-key="path"
> >
<template #item="{ element }"> <template #item="{ element }">
<div <div
:key="element.type" :key="element.type"
class="qtopic-item" class="qtopic-item"
:id="'qtopic' + element.type" :id="'qtopic' + element.type"
@click="onQuestionType({ type: element.type })" @click="onQuestionType({ type: element.type })"
@mouseenter="showPreview(element, 'qtopic' + element.type)" @mouseenter="showPreview(element, 'qtopic' + element.type)"
@mouseleave="isShowPreviewImage = false" @mouseleave="isShowPreviewImage = false"
@mousedown="isShowPreviewImage = false" @mousedown="isShowPreviewImage = false"
> >
<i class="iconfont" :class="['icon-' + element.icon]"></i> <i class="iconfont" :class="['icon-' + element.icon]"></i>
<p class="text">{{ element.title }}</p> <p class="text">{{ element.title }}</p>
</div> </div>
</template> </template>
</draggable>
</draggable>
</el-collapse-item> </el-collapse-item>
<Teleport to="body"> <Teleport to="body">
<div class="preview-popover" v-show="isShowPreviewImage" :style="{ top: previewTop + 'px'}"> <div class="preview-popover" v-show="isShowPreviewImage" :style="{ top: previewTop + 'px' }">
<img :src="previewImg" class="preview-image"/> <img :src="previewImg" class="preview-image" />
<span class="preview-arrow"></span> <span class="preview-arrow"></span>
</div> </div>
</Teleport> </Teleport>
</el-collapse> </el-collapse>
@ -78,14 +77,14 @@ const getNewQuestion = ({ type }) => {
} }
const onQuestionType = ({ type }) => { const onQuestionType = ({ type }) => {
const newQuestion = getNewQuestion({ type }) const newQuestion = getNewQuestion({ type })
store.dispatch('edit/addQuestion', { question: newQuestion, index: newQuestionIndex.value }) store.dispatch('edit/addQuestion', { question: newQuestion, index: newQuestionIndex.value })
store.commit('edit/setCurrentEditOne', newQuestionIndex.value) store.commit('edit/setCurrentEditOne', newQuestionIndex.value)
} }
const showPreview = ({ snapshot }, id) => { const showPreview = ({ snapshot }, id) => {
previewImg.value = snapshot previewImg.value = snapshot
const dragEl = document.getElementById(id) const dragEl = document.getElementById(id)
const { top, height } = dragEl.getBoundingClientRect() const { top, height } = dragEl.getBoundingClientRect()
previewTop.value = top + height / 2 previewTop.value = top + height / 2
@ -136,7 +135,7 @@ const showPreview = ({ snapshot }, id) => {
background-color: $primary-color-light; background-color: $primary-color-light;
border: 1px solid $primary-color; border: 1px solid $primary-color;
} }
.text { .text {
font-size: 12px; font-size: 12px;
user-select: none; user-select: none;
@ -211,7 +210,7 @@ const showPreview = ({ snapshot }, id) => {
&::before { &::before {
position: absolute; position: absolute;
content: ""; content: '';
height: 10px; height: 10px;
width: 10px; width: 10px;
border: 1px solid var(--el-border-color-light); border: 1px solid var(--el-border-color-light);
@ -219,7 +218,6 @@ const showPreview = ({ snapshot }, id) => {
border-bottom-color: transparent; border-bottom-color: transparent;
border-right-color: transparent; border-right-color: transparent;
} }
} }
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<CommonTemplate> <CommonTemplate>
<template #left> <template #left>
<CatalogPanel /> <CatalogPanel />
</template> </template>
<template #center> <template #center>
<PreviewPanel /> <PreviewPanel />
@ -12,22 +12,22 @@
</CommonTemplate> </CommonTemplate>
</template> </template>
<script> <script>
import CommonTemplate from '../../components/CommonTemplate.vue'; import CommonTemplate from '../../components/CommonTemplate.vue'
import CatalogPanel from '../../modules/questionModule/CatalogPanel.vue'; import CatalogPanel from '../../modules/questionModule/CatalogPanel.vue'
import PreviewPanel from '../../modules/questionModule/PreviewPanel.vue'; import PreviewPanel from '../../modules/questionModule/PreviewPanel.vue'
import SetterPanel from '../../modules/questionModule/SetterPanel.vue'; import SetterPanel from '../../modules/questionModule/SetterPanel.vue'
export default { export default {
name: 'editIndex', name: 'editIndex',
components: { components: {
CommonTemplate, CommonTemplate,
CatalogPanel, CatalogPanel,
PreviewPanel, PreviewPanel,
SetterPanel, SetterPanel
}, }
}; }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.navbar { .navbar {
border-bottom: 1px solid #e7e9eb; border-bottom: 1px solid #e7e9eb;
} }
</style> </style>

View File

@ -30,8 +30,7 @@ const routes: RouteRecordRaw[] = [
needLogin: true needLogin: true
}, },
name: 'QuestionEditPage', name: 'QuestionEditPage',
component: () => component: () => import('../pages/edit/pages/edit/index.vue'),
import('../pages/edit/pages/edit/index.vue'),
children: [ children: [
{ {
path: '', path: '',
@ -39,8 +38,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
needLogin: true needLogin: true
}, },
component: () => component: () => import('../pages/edit/pages/edit/QuestionEditPage.vue')
import('../pages/edit/pages/edit/QuestionEditPage.vue')
}, },
{ {
path: 'logic', path: 'logic',
@ -48,8 +46,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
needLogin: true needLogin: true
}, },
component: () => component: () => import('../pages/edit/pages/edit/LogicEditPage.vue')
import('../pages/edit/pages/edit/LogicEditPage.vue')
} }
] ]
}, },

View File

@ -15,8 +15,15 @@ export default {
if (res.code === 200) { if (res.code === 200) {
const metaData = res.data.surveyMetaRes const metaData = res.data.surveyMetaRes
document.title = metaData.title document.title = metaData.title
const { bannerConf, bottomConf, skinConf, baseConf, submitConf, dataConf, logicConf = {} } = const {
res.data.surveyConfRes.code bannerConf,
bottomConf,
skinConf,
baseConf,
submitConf,
dataConf,
logicConf = {}
} = res.data.surveyConfRes.code
commit('initSchema', { commit('initSchema', {
metaData, metaData,
codeData: { codeData: {
@ -29,7 +36,6 @@ export default {
logicConf logicConf
} }
}) })
} else { } else {
throw new Error(res.errmsg || '问卷不存在') throw new Error(res.errmsg || '问卷不存在')
} }

View File

@ -1,5 +1,5 @@
export default { export default {
setBannerList(state, data) { setBannerList(state, data) {
state.bannerList = data state.bannerList = data
}, }
} }

View File

@ -3,19 +3,14 @@ export const QOP_MAP = {
COPY: 'copy', COPY: 'copy',
EDIT: 'edit' EDIT: 'edit'
} }
export const qAbleList = [ export const qAbleList = ['radio', 'checkbox', 'binary-choice', 'vote']
'radio',
'checkbox',
'binary-choice',
'vote',
]
export const operatorOptions = [ export const operatorOptions = [
{ {
label: '选择了', label: '选择了',
value: 'in', value: 'in'
}, },
{ {
label: '不选择', label: '不选择',
value: 'nin', value: 'nin'
}, }
] ]

View File

@ -32,7 +32,7 @@ interface Props {
} }
interface Emit { interface Emit {
(ev: typeof FORM_CHANGE_EVENT_KEY, arg: { key: string; value: string }): void (ev: typeof FORM_CHANGE_EVENT_KEY, arg: { key: string; value: string | null }): void
} }
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
@ -58,8 +58,8 @@ const handleSelectChange = (value: string) => {
emit(FORM_CHANGE_EVENT_KEY, { key: formdataBackfillKey, value }) emit(FORM_CHANGE_EVENT_KEY, { key: formdataBackfillKey, value })
} }
const handleSwitchChange = (value: string) => { const handleSwitchChange = (value: string | null) => {
emit(FORM_CHANGE_EVENT_KEY, { key: formdataBackfillHourKey, value: value ? 24 : null }) emit(FORM_CHANGE_EVENT_KEY, { key: formdataBackfillHourKey, value: value ? '24' : null })
} }
const watchValue = computed(() => props.formConfig.value) const watchValue = computed(() => props.formConfig.value)

View File

@ -77,15 +77,15 @@ onMounted(async () => {
if (res.code === 200) { if (res.code === 200) {
const data = res.data const data = res.data
const { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf, logicConf } = data.code const { bannerConf, baseConf, bottomConf, dataConf, skinConf, submitConf, logicConf } =
data.code
const questionData = { const questionData = {
bannerConf, bannerConf,
baseConf, baseConf,
bottomConf, bottomConf,
dataConf, dataConf,
skinConf, skinConf,
submitConf, submitConf
} }
document.title = data.title document.title = data.title
@ -126,4 +126,4 @@ html {
flex: 1; flex: 1;
background-color: #fff; background-color: #fff;
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<form ref="ruleForm" :model="formValues" :rules="rules"> <form ref="ruleForm" :model="formValues" :rules="rules">
<div v-for="(item) in renderData" :key="item.field"> <div v-for="item in renderData" :key="item.field">
<QuestionWrapper <QuestionWrapper
class="gap" class="gap"
v-bind="$attrs" v-bind="$attrs"

View File

@ -29,36 +29,42 @@ const props = defineProps({
default: () => { default: () => {
return {} return {}
} }
}, }
}) })
const emit = defineEmits(['change']) const emit = defineEmits(['change'])
const formValues = computed(() => { const formValues = computed(() => {
return store.state.formValues return store.state.formValues
}) })
const questionConfig = computed(() =>{ const questionConfig = computed(() => {
let moduleConfig = props.moduleConfig let moduleConfig = props.moduleConfig
const { type, field, options, ...rest } = cloneDeep(moduleConfig) const { type, field, options, ...rest } = cloneDeep(moduleConfig)
// console.log(field,'formValuechange') // console.log(field,'formValuechange')
let alloptions = options let alloptions = options
if(type === QUESTION_TYPE.VOTE) { if (type === QUESTION_TYPE.VOTE) {
const { options, voteTotal } = useVoteMap(field) const { options, voteTotal } = useVoteMap(field)
const voteOptions = unref(options) const voteOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index])) alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index]))
moduleConfig.voteTotal = unref(voteTotal) moduleConfig.voteTotal = unref(voteTotal)
} }
if(QUESTION_TYPE.CHOICES.includes(type) && options.filter(optionItem => optionItem.others).length > 0) { if (
QUESTION_TYPE.CHOICES.includes(type) &&
options.filter((optionItem) => optionItem.others).length > 0
) {
let { options, othersValue } = useShowOthers(field) let { options, othersValue } = useShowOthers(field)
const othersOptions = unref(options) const othersOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index])) alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index]))
moduleConfig.othersValue = unref(othersValue) moduleConfig.othersValue = unref(othersValue)
} }
if(QUESTION_TYPE.RATES.includes(type) && Object.keys(rest.rangeConfig).filter(index => rest.rangeConfig[index].isShowInput).length > 0) { if (
QUESTION_TYPE.RATES.includes(type) &&
Object.keys(rest.rangeConfig).filter((index) => rest.rangeConfig[index].isShowInput).length > 0
) {
let { rangeConfig, othersValue } = useShowInput(field) let { rangeConfig, othersValue } = useShowInput(field)
moduleConfig.rangeConfig = unref(rangeConfig) moduleConfig.rangeConfig = unref(rangeConfig)
moduleConfig.othersValue = unref(othersValue) moduleConfig.othersValue = unref(othersValue)
} }
return { return {
...moduleConfig, ...moduleConfig,
options: alloptions, options: alloptions,
@ -73,27 +79,30 @@ const visible = computed(() => {
return ruleEngine.match(field, 'question', formValues.value) return ruleEngine.match(field, 'question', formValues.value)
}) })
watch(() => visible.value, (newVal, oldVal) => { watch(
// () => visible.value,
const { field, type, innerType } = props.moduleConfig (newVal, oldVal) => {
if(!newVal && oldVal) { //
let value = '' const { field, type, innerType } = props.moduleConfig
// innerType if (!newVal && oldVal) {
if (type === QUESTION_TYPE.CHECKBOX || innerType === QUESTION_TYPE.CHECKBOX) { let value = ''
value = value ? [value] : [] // innerType
if (type === QUESTION_TYPE.CHECKBOX || innerType === QUESTION_TYPE.CHECKBOX) {
value = value ? [value] : []
}
const data = {
key: field,
value: value
}
store.commit('changeFormData', data)
} }
const data = {
key: field,
value: value
}
store.commit('changeFormData', data)
} }
}) )
const handleChange = (data) => { const handleChange = (data) => {
emit('change', data) emit('change', data)
// //
if(props.moduleConfig.type === QUESTION_TYPE.VOTE) { if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
store.dispatch('updateVoteData', data) store.dispatch('updateVoteData', data)
} }
} }

View File

@ -1,12 +1,14 @@
export const QUESTION_TYPE = { export const QUESTION_TYPE = {
VOTE: 'vote', VOTE: 'vote',
CHECKBOX: 'checkbox', CHECKBOX: 'checkbox',
CHOICES: [ // 选择类题型分类 CHOICES: [
// 选择类题型分类
'radio', 'radio',
'checkbox', 'checkbox'
], ],
RATES: [ // 评分题题型分类 RATES: [
// 评分题题型分类
'radio-star', 'radio-star',
'radio-nps' 'radio-nps'
] ]
} }

View File

@ -3,4 +3,4 @@ import { RuleMatch } from '@/common/logicEngine/RulesMatch'
export const ruleEngine = new RuleMatch() export const ruleEngine = new RuleMatch()
export const initRuleEngine = (ruleConf) => { export const initRuleEngine = (ruleConf) => {
ruleEngine.fromJson(ruleConf) ruleEngine.fromJson(ruleConf)
} }

View File

@ -5,15 +5,14 @@ export const useShowInput = (questionKey) => {
let rangeConfig = store.state.questionData[questionKey].rangeConfig let rangeConfig = store.state.questionData[questionKey].rangeConfig
let othersValue = {} let othersValue = {}
if (rangeConfig && Object.keys(rangeConfig).length > 0) { if (rangeConfig && Object.keys(rangeConfig).length > 0) {
for(let key in rangeConfig) { for (let key in rangeConfig) {
const curRange = rangeConfig[key] const curRange = rangeConfig[key]
if (curRange.isShowInput) { if (curRange.isShowInput) {
const rangeKey = `${questionKey}_${key}` const rangeKey = `${questionKey}_${key}`
othersValue[rangeKey] = formValues[rangeKey] othersValue[rangeKey] = formValues[rangeKey]
curRange.othersKey = rangeKey, ;(curRange.othersKey = rangeKey), (curRange.othersValue = formValues[rangeKey])
curRange.othersValue = formValues[rangeKey] if (!questionVal.toString().includes(key) && formValues[rangeKey]) {
if(!questionVal.toString().includes(key) && formValues[rangeKey]) {
// 如果分值被未被选中且对应的填写更多有值,则清空填写更多 // 如果分值被未被选中且对应的填写更多有值,则清空填写更多
const data = { const data = {
key: rangeKey, key: rangeKey,
@ -24,7 +23,6 @@ export const useShowInput = (questionKey) => {
} }
} }
} }
return { rangeConfig, othersValue }
return { rangeConfig, othersValue } }
}

View File

@ -3,11 +3,11 @@ export const useShowOthers = (questionKey) => {
const formValues = store.state.formValues const formValues = store.state.formValues
const questionVal = formValues[questionKey] const questionVal = formValues[questionKey]
let othersValue = {} let othersValue = {}
let options = store.state.questionData[questionKey].options.map(optionItem => { let options = store.state.questionData[questionKey].options.map((optionItem) => {
if (optionItem.others) { if (optionItem.others) {
const opKey = `${questionKey}_${optionItem.hash}` const opKey = `${questionKey}_${optionItem.hash}`
othersValue[opKey] = formValues[opKey] othersValue[opKey] = formValues[opKey]
if(!questionVal.includes(optionItem.hash) && formValues[opKey]) { if (!questionVal.includes(optionItem.hash) && formValues[opKey]) {
// 如果选项被未被选中且对应的填写更多有值,则清空填写更多 // 如果选项被未被选中且对应的填写更多有值,则清空填写更多
const data = { const data = {
key: opKey, key: opKey,
@ -24,6 +24,6 @@ export const useShowOthers = (questionKey) => {
return optionItem return optionItem
} }
}) })
return { options, othersValue } return { options, othersValue }
} }

View File

@ -1,17 +1,16 @@
import store from '../store/index' import store from '../store/index'
export const useVoteMap = (questionKey) => { export const useVoteMap = (questionKey) => {
let voteTotal = store.state.voteMap?.[questionKey]?.total || 0
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
}
})
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 } return { options, voteTotal }
} }

View File

@ -2,28 +2,28 @@ export default {
// 题目列表 // 题目列表
renderData: (state) => { renderData: (state) => {
const { questionSeq, questionData } = state const { questionSeq, questionData } = state
let index = 1 let index = 1
return ( return (
questionSeq && questionSeq &&
questionSeq.reduce((pre, item) => { questionSeq.reduce((pre, item) => {
const questionArr = [] const questionArr = []
item.forEach(questionKey => { item.forEach((questionKey) => {
console.log('题目重新计算') console.log('题目重新计算')
const question = { ...questionData[questionKey] } const question = { ...questionData[questionKey] }
// 开启显示序号 // 开启显示序号
if (question.showIndex) { if (question.showIndex) {
question.indexNumber = index++ question.indexNumber = index++
} }
questionArr.push(question) questionArr.push(question)
}) })
if (questionArr && questionArr.length) { if (questionArr && questionArr.length) {
pre.push(questionArr) pre.push(questionArr)
} }
return pre return pre
}, []) }, [])
) )

View File

@ -39,7 +39,7 @@ export default {
updateVoteMapByKey(state, data) { updateVoteMapByKey(state, data) {
const { questionKey, voteKey, voteValue } = data const { questionKey, voteKey, voteValue } = data
// 兼容为空的情况 // 兼容为空的情况
if(!state.voteMap[questionKey]){ if (!state.voteMap[questionKey]) {
state.voteMap[questionKey] = {} state.voteMap[questionKey] = {}
} }
state.voteMap[questionKey][voteKey] = voteValue state.voteMap[questionKey][voteKey] = voteValue
@ -52,5 +52,5 @@ export default {
}, },
setRuleEgine(state, ruleEngine) { setRuleEgine(state, ruleEngine) {
state.ruleEngine = ruleEngine state.ruleEngine = ruleEngine
}, }
} }