feat: 抽离题型枚举 (#272)

* feat: 抽离题型枚举

* fix: 投放的链接加时间戳去掉ifream缓存

* feat: serve端的node engines
This commit is contained in:
dayou 2024-06-14 18:54:11 +08:00
parent 4eda73e669
commit dc7275aeee
19 changed files with 88 additions and 62 deletions

View File

@ -97,5 +97,9 @@
"moduleNameMapper": { "moduleNameMapper": {
"^src/(.*)$": "<rootDir>/$1" "^src/(.*)$": "<rootDir>/$1"
} }
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.6.0"
} }
} }

View File

@ -0,0 +1,50 @@
// 题型枚举
export enum QUESTION_TYPE {
TEXT = 'text',
TEXTAREA = 'textarea',
RADIO = 'radio',
CHECKBOX = 'checkbox',
BINARY_CHOICE = 'binary-choice',
RADIO_STAR = 'radio-star',
RADIO_NPS = 'radio-nps',
VOTE = 'vote',
}
// 题目类型标签映射对象
export const typeTagLabels: Record<QUESTION_TYPE, string> = {
[QUESTION_TYPE.TEXT]: '单行输入框',
[QUESTION_TYPE.TEXTAREA]: '多行输入框',
[QUESTION_TYPE.RADIO]: '单选',
[QUESTION_TYPE.CHECKBOX]: '多选',
[QUESTION_TYPE.BINARY_CHOICE]: '判断',
[QUESTION_TYPE.RADIO_STAR]: '评分',
[QUESTION_TYPE.RADIO_NPS]: 'NPS评分',
[QUESTION_TYPE.VOTE]: '投票'
}
// 输入类题型
export const INPUT = [
QUESTION_TYPE.TEXT,
QUESTION_TYPE.TEXTAREA
]
// 选择类题型分类
export const NORMAL_CHOICES = [
QUESTION_TYPE.RADIO,
QUESTION_TYPE.CHECKBOX
]
// 选择类题型分类
export const CHOICES = [
QUESTION_TYPE.RADIO,
QUESTION_TYPE.CHECKBOX,
QUESTION_TYPE.BINARY_CHOICE,
QUESTION_TYPE.VOTE
]
// 评分题题型分类
export const RATES = [
QUESTION_TYPE.RADIO_STAR,
QUESTION_TYPE.RADIO_NPS
]

View File

@ -65,7 +65,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, inject, ref, type ComputedRef } from 'vue' import { computed, inject, ref, type ComputedRef } from 'vue'
import { ConditionNode, RuleNode } from '@/common/logicEngine/RuleBuild' import { ConditionNode, RuleNode } from '@/common/logicEngine/RuleBuild'
import { qAbleList } from '@/management/utils/constant.js' import { CHOICES } from '@/common/typeEnum'
import { cleanRichText } from '@/common/xss' import { cleanRichText } from '@/common/xss'
const renderData = inject<ComputedRef<Array<any>>>('renderData') || ref([]) const renderData = inject<ComputedRef<Array<any>>>('renderData') || ref([])
const props = defineProps({ const props = defineProps({
@ -94,7 +94,7 @@ 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 return renderData.value
.slice(0, currentIndex) .slice(0, currentIndex)
.filter((question: any) => qAbleList.includes(question.type)) .filter((question: any) => CHOICES.includes(question.type))
.map((item: any) => { .map((item: any) => {
return { return {
label: `${item.showIndex ? item.indexNumber + '.' : ''} ${cleanRichText(item.title)}`, label: `${item.showIndex ? item.indexNumber + '.' : ''} ${cleanRichText(item.title)}`,

View File

@ -48,7 +48,7 @@ import { getQuestionByType } from '@/management/utils/index'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { get as _get, isNumber as _isNumber } from 'lodash-es' import { get as _get, isNumber as _isNumber } from 'lodash-es'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { QUESTION_TYPE } from '@/common/typeEnum.ts'
const store = useStore() const store = useStore()
const activeNames = ref([0, 1]) const activeNames = ref([0, 1])
@ -70,8 +70,8 @@ const getNewQuestion = ({ type }) => {
const fields = questionDataList.value.map((item) => item.field) const fields = questionDataList.value.map((item) => item.field)
const newQuestion = getQuestionByType(type, fields) const newQuestion = getQuestionByType(type, fields)
newQuestion.title = newQuestion.title = `标题${newQuestionIndex.value + 1}` newQuestion.title = newQuestion.title = `标题${newQuestionIndex.value + 1}`
if (type === 'vote') { if (type === QUESTION_TYPE.VOTE) {
newQuestion.innerType = 'radio' newQuestion.innerType = QUESTION_TYPE.RADIO
} }
return newQuestion return newQuestion
} }

View File

@ -121,7 +121,7 @@ moment.locale('zh-cn')
import EmptyIndex from '@/management/components/EmptyIndex.vue' import EmptyIndex from '@/management/components/EmptyIndex.vue'
import { CODE_MAP } from '@/management/api/base' import { CODE_MAP } from '@/management/api/base'
import { QOP_MAP } from '@/management/utils/constant' import { QOP_MAP } from '@/management/utils/constant.ts'
import { deleteSurvey } from '@/management/api/survey' import { deleteSurvey } from '@/management/api/survey'
import ModifyDialog from './ModifyDialog.vue' import ModifyDialog from './ModifyDialog.vue'
import TagModule from './TagModule.vue' import TagModule from './TagModule.vue'

View File

@ -48,7 +48,6 @@ import { useStore } from 'vuex'
import { pick as _pick } from 'lodash-es' import { pick as _pick } from 'lodash-es'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss' import 'element-plus/theme-chalk/src/message.scss'
import { QOP_MAP } from '@/management/utils/constant' import { QOP_MAP } from '@/management/utils/constant'
import MemberSelect from './MemberSelect.vue' import MemberSelect from './MemberSelect.vue'
import { type IMember, type IWorkspace, UserRole } from '@/management/utils/types/workSpace' import { type IMember, type IWorkspace, UserRole } from '@/management/utils/types/workSpace'

View File

@ -51,7 +51,7 @@ const mainChannel = computed(() => {
let fullUrl = '' let fullUrl = ''
if (metaData.value) { if (metaData.value) {
fullUrl = `${location.origin}/render/${metaData.value.surveyPath}` fullUrl = `${location.origin}/render/${metaData.value.surveyPath}?t=${Date.now()}`
} }
return { fullUrl } return { fullUrl }

View File

@ -1,11 +1,10 @@
// 问卷操作枚举 // 问卷操作枚举
export const QOP_MAP = { export enum QOP_MAP {
ADD: 'add', ADD = 'add',
COPY: 'copy', COPY = 'copy',
EDIT: 'edit' EDIT = 'edit'
} }
export const qAbleList = ['radio', 'checkbox', 'binary-choice', 'vote']
export const operatorOptions = [ export const operatorOptions = [
{ {
label: '选择了', label: '选择了',

View File

@ -1,5 +1,6 @@
import { defaultQuestionConfig } from '../config/questionConfig' import { defaultQuestionConfig } from '../config/questionConfig'
import { cloneDeep as _cloneDeep, map as _map } from 'lodash-es' import { cloneDeep as _cloneDeep, map as _map } from 'lodash-es'
import { QUESTION_TYPE } from '@/common/typeEnum.ts'
const generateQuestionField = () => { const generateQuestionField = () => {
const num = Math.floor(Math.random() * 1000) const num = Math.floor(Math.random() * 1000)
return `data${num}` return `data${num}`
@ -24,7 +25,7 @@ const generateHash = (hashList) => {
function getOptions(type) { function getOptions(type) {
const options = [].concat({ ..._cloneDeep(defaultQuestionConfig) }.options) const options = [].concat({ ..._cloneDeep(defaultQuestionConfig) }.options)
if (type === 'binary-choice') { if (type === QUESTION_TYPE.BINARY_CHOICE) {
options[0].text = '对' options[0].text = '对'
options[1].text = '错' options[1].text = '错'
} }

View File

@ -1,15 +0,0 @@
const config = {
mob: '手机号',
text: '单行输入框',
textarea: '多行输入框',
radio: '单选',
checkbox: '多选',
'radio-star': '评分',
'radio-nps': 'NPS评分',
city: '城市选择',
vote: '投票',
'binary-choice': '判断',
fillin: '填空'
}
export default config

View File

@ -54,6 +54,7 @@ import { ref, computed, inject } from 'vue'
import OptionConfig from '../AdvancedConfig/OptionConfig.vue' import OptionConfig from '../AdvancedConfig/OptionConfig.vue'
import RateConfig from '../AdvancedConfig/RateConfig.vue' import RateConfig from '../AdvancedConfig/RateConfig.vue'
import ExtraIcon from '../ExtraIcon/index.vue' import ExtraIcon from '../ExtraIcon/index.vue'
import { QUESTION_TYPE } from '@/common/typeEnum'
defineProps({ defineProps({
optionList: { optionList: {
@ -101,7 +102,7 @@ const onVisibleChange = (val) => {
} }
const isNps = computed(() => { const isNps = computed(() => {
return moduleConfig.value.type === 'radio-nps' return moduleConfig.value.type === QUESTION_TYPE.RADIO_NPS
}) })
const min = computed(() => { const min = computed(() => {

View File

@ -1,6 +1,6 @@
import { defineComponent, watch, ref, computed } from 'vue' import { defineComponent, watch, ref, computed } from 'vue'
import { filterXSS } from '@/common/xss' import { filterXSS } from '@/common/xss'
import tagList from '@materials/questions/common/config/tagList' import { typeTagLabels } from '@/common/typeEnum.ts'
import './style.scss' import './style.scss'
@ -58,7 +58,7 @@ export default defineComponent({
let ret = '' let ret = ''
types.forEach((t) => { types.forEach((t) => {
if (ret) return if (ret) return
const tv = tagList && tagList[t] const tv = typeTagLabels && typeTagLabels[t]
if (tv && typeof tv === 'string') { if (tv && typeof tv === 'string') {
ret = tv.trim() ret = tv.trim()
} }

View File

@ -1,6 +1,6 @@
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import { includes } from 'lodash-es' import { includes } from 'lodash-es'
import { QUESTION_TYPE } from '@/common/typeEnum'
import AnswerProcess from './AnswerProcess/index.vue' import AnswerProcess from './AnswerProcess/index.vue'
import BaseChoice from '../BaseChoice' import BaseChoice from '../BaseChoice'
@ -56,7 +56,7 @@ export default defineComponent({
} }
const myOptions = computed(() => { const myOptions = computed(() => {
const { options } = props const { options } = props
if (props.innerType === 'checkbox') { if (props.innerType === QUESTION_TYPE.CHECKBOX) {
return options.map((item) => { return options.map((item) => {
return { return {
...item, ...item,

View File

@ -11,7 +11,7 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss' import 'element-plus/theme-chalk/src/message.scss'
import { QUESTION_TYPE } from '@/common/typeEnum'
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant' import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
interface Props { interface Props {
@ -25,7 +25,7 @@ interface Emit {
const emit = defineEmits<Emit>() const emit = defineEmits<Emit>()
const props = defineProps<Props>() const props = defineProps<Props>()
const setterTypes = ['checkbox', 'vote'] const setterTypes = [QUESTION_TYPE.CHECKBOX, QUESTION_TYPE.VOTE]
const modelValue = ref(props.formConfig.value || 0) const modelValue = ref(props.formConfig.value || 0)
const minModelValue = computed(() => { const minModelValue = computed(() => {
const { min } = props.formConfig const { min } = props.formConfig

View File

@ -1,4 +1,5 @@
// 定义提交的数据结构:{ field1: '', field2: [], field1_hash1: '', } // 定义提交的数据结构:{ field1: '', field2: [], field1_hash1: '', }
import { QUESTION_TYPE } from '@/common/typeEnum.ts'
export default function ({ dataConf }) { export default function ({ dataConf }) {
const dataList = dataConf.dataList const dataList = dataConf.dataList
const formValues = {} const formValues = {}
@ -17,7 +18,7 @@ export default function ({ dataConf }) {
// } // }
// 题型是多选或者子题型是多选innerType是用于投票 // 题型是多选或者子题型是多选innerType是用于投票
if (/checkbox/.test(type) || innerType === 'checkbox') { if (/checkbox/.test(type) || innerType === QUESTION_TYPE.CHECKBOX) {
value = value ? [value] : [] value = value ? [value] : []
} }
formValues[key] = value formValues[key] = value

View File

@ -3,7 +3,7 @@
*/ */
import { get as _get, map as _map } from 'lodash-es' import { get as _get, map as _map } from 'lodash-es'
import { QUESTION_TYPE } from '@/common/typeEnum.ts'
// 处理选择题的options // 处理选择题的options
function handleOptions(item) { function handleOptions(item) {
const { type } = item const { type } = item
@ -13,7 +13,7 @@ function handleOptions(item) {
const cleanOption = {} const cleanOption = {}
// 投票逻辑处理 // 投票逻辑处理
if (type.indexOf('vote') > -1) { if (type.indexOf(QUESTION_TYPE.VOTE) > -1) {
cleanOption.voteCount = 0 cleanOption.voteCount = 0
} }

View File

@ -5,6 +5,7 @@ import {
keys as _keys, keys as _keys,
set as _set set as _set
} from 'lodash-es' } from 'lodash-es'
import { INPUT, RATES, QUESTION_TYPE } from '@/common/typeEnum.ts'
const regexpMap = { const regexpMap = {
nd: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/, nd: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,
@ -28,13 +29,11 @@ const msgMap = {
e: '请输入邮箱', e: '请输入邮箱',
licensePlate: '请输入车牌号' licensePlate: '请输入车牌号'
} }
const inputType = ['text', 'textarea']
const checkBoxTip = '至少选择#min#项,少选择了#less#项' const checkBoxTip = '至少选择#min#项,少选择了#less#项'
const checkBoxTipSame = '请选择#min#项,少选择了#less#项' const checkBoxTipSame = '请选择#min#项,少选择了#less#项'
const textRangeMinTip = '至少输入#min#字' const textRangeMinTip = '至少输入#min#字'
const numberRangeMinTip = '数字最小为#min#' const numberRangeMinTip = '数字最小为#min#'
const numberRangeMaxTip = '数字最大为#max#' const numberRangeMaxTip = '数字最大为#max#'
const radioType = ['radio-star', 'radio-nps']
// 多选题的选项数目限制 // 多选题的选项数目限制
export function optionValidator(value, minNum, maxNum) { export function optionValidator(value, minNum, maxNum) {
@ -88,7 +87,7 @@ export function generateValidArr(
numberRangeMax numberRangeMax
) { ) {
const validArr = [] const validArr = []
const isInput = inputType.indexOf(type) !== -1 const isInput = INPUT.indexOf(type) !== -1
if (isRequired || valid === '*') { if (isRequired || valid === '*') {
// 输入框的必填校验做trim // 输入框的必填校验做trim
if (!isInput) { if (!isInput) {
@ -199,14 +198,14 @@ const generateOthersKeyMap = (question) => {
const { type, field, options, rangeConfig } = question const { type, field, options, rangeConfig } = question
let othersKeyMap = undefined let othersKeyMap = undefined
if (radioType.includes(type)) { if (RATES.includes(type)) {
othersKeyMap = {} othersKeyMap = {}
for (const key in rangeConfig) { for (const key in rangeConfig) {
if (rangeConfig[key].isShowInput) { if (rangeConfig[key].isShowInput) {
othersKeyMap[`${field}_${key}`] = key othersKeyMap[`${field}_${key}`] = key
} }
} }
} else if (type.includes('radio') || type.includes('checkbox')) { } else if (type.includes(QUESTION_TYPE.RADIO) || type.includes(QUESTION_TYPE.CHECKBOX)) {
othersKeyMap = {} othersKeyMap = {}
options options
.filter((op) => op.others) .filter((op) => op.others)
@ -258,7 +257,7 @@ export default function (questionConfig) {
// 对于选择题支持填写更多信息的,需要做是否必填的校验 // 对于选择题支持填写更多信息的,需要做是否必填的校验
if (_keys(othersKeyMap).length) { if (_keys(othersKeyMap).length) {
if (radioType.includes(type)) { if (RATES.includes(type)) {
if (rangeConfig) { if (rangeConfig) {
for (const key in rangeConfig) { for (const key in rangeConfig) {
if (rangeConfig[key].isShowInput && rangeConfig[key].required) { if (rangeConfig[key].isShowInput && rangeConfig[key].required) {

View File

@ -17,7 +17,8 @@ import { useShowInput } from '@/render/hooks/useShowInput'
import store from '@/render/store' import store from '@/render/store'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { ruleEngine } from '@/render/hooks/useRuleEngine.js' import { ruleEngine } from '@/render/hooks/useRuleEngine.js'
import { QUESTION_TYPE } from '@/render/constant/index'
import { NORMAL_CHOICES, RATES, QUESTION_TYPE } from '@/common/typeEnum.ts'
const props = defineProps({ const props = defineProps({
indexNumber: { indexNumber: {
@ -48,7 +49,7 @@ const questionConfig = computed(() => {
moduleConfig.voteTotal = unref(voteTotal) moduleConfig.voteTotal = unref(voteTotal)
} }
if ( if (
QUESTION_TYPE.CHOICES.includes(type) && NORMAL_CHOICES.includes(type) &&
options.filter((optionItem) => optionItem.others).length > 0 options.filter((optionItem) => optionItem.others).length > 0
) { ) {
let { options, othersValue } = useShowOthers(field) let { options, othersValue } = useShowOthers(field)
@ -57,7 +58,7 @@ const questionConfig = computed(() => {
moduleConfig.othersValue = unref(othersValue) moduleConfig.othersValue = unref(othersValue)
} }
if ( if (
QUESTION_TYPE.RATES.includes(type) && RATES.includes(type) &&
Object.keys(rest.rangeConfig).filter((index) => rest.rangeConfig[index].isShowInput).length > 0 Object.keys(rest.rangeConfig).filter((index) => rest.rangeConfig[index].isShowInput).length > 0
) { ) {
let { rangeConfig, othersValue } = useShowInput(field) let { rangeConfig, othersValue } = useShowInput(field)

View File

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