diff --git a/README.md b/README.md index 8fca4e16..88524ea7 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ **3、能力增强** -  在线平台:建设中、智能化问卷:规划中 +  智能化问卷:规划中 # 项目优势 @@ -128,17 +128,35 @@ # 快速使用 -_(在线平台建设中)_ +快速试用:https://xiaojuwenjuan.com/render/LWpBOxRx + +在线平台:https://xiaojuwenjuan.com # 本地开发 请查看 [本地安装手册](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B) 来启动项目。 +``` +// 服务启动 +cd server +npm install +npm run local + +// 页面启动 +cd web +npm install +npm run serve + +// B端 http://localhost:8080/management + +// C端 http://localhost:8080/render/:surveyPath +``` + # 快速部署 ### 服务部署 -请查看 [快速部署指导](https://xiaojusurvey.didi.cn/docs/next/document/%E5%B7%A5%E7%A8%8B%E9%83%A8%E7%BD%B2/Docker%E9%83%A8%E7%BD%B2) 。 +请查看 [部署指导](https://xiaojusurvey.didi.cn/docs/next/document/%E5%B7%A5%E7%A8%8B%E9%83%A8%E7%BD%B2/Docker%E9%83%A8%E7%BD%B2) 。 ### 一键部署 diff --git a/docker-compose.yaml b/docker-compose.yaml index e19bc04f..68c8cef2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,7 +15,7 @@ services: - xiaoju-survey xiaoju-survey: - image: "xiaojusurvey/xiaoju-survey:1.3.0-slim" # 最新版本:https://hub.docker.com/r/xiaojusurvey/xiaoju-survey/tags + image: "xiaojusurvey/xiaoju-survey:1.3.1-slim" # 最新版本:https://hub.docker.com/r/xiaojusurvey/xiaoju-survey/tags container_name: xiaoju-survey restart: always ports: diff --git a/server/package.json b/server/package.json index 57b81d0f..31a098fc 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "xiaoju-survey-server", - "version": "1.3.0", + "version": "1.3.1", "description": "XIAOJUSURVEY的server端", "author": "", "scripts": { diff --git a/server/src/enums/question.ts b/server/src/enums/question.ts index e55b0c46..09959099 100644 --- a/server/src/enums/question.ts +++ b/server/src/enums/question.ts @@ -34,4 +34,8 @@ export enum QUESTION_TYPE { * 投票 */ VOTE = 'vote', + /** + * 多级联动 + */ + CASCADER = 'cascader', } diff --git a/server/src/interfaces/survey.ts b/server/src/interfaces/survey.ts index b7368e26..290540ab 100644 --- a/server/src/interfaces/survey.ts +++ b/server/src/interfaces/survey.ts @@ -22,6 +22,20 @@ export interface NPS { rightText: string; } +export interface CascaderItem { + hash: string; + text: string; + children?: CascaderItem[]; +} + +export interface CascaderDate { + placeholder: Array<{ + hash: string; + text: string; + }>; + children: Array; +} + export interface TextRange { min: { placeholder: string; @@ -60,6 +74,7 @@ export interface DataItem { rangeConfig?: any; starStyle?: string; innerType?: string; + cascaderData: CascaderDate; } export interface Option { diff --git a/server/src/modules/survey/controllers/dataStatistic.controller.ts b/server/src/modules/survey/controllers/dataStatistic.controller.ts index 185e6354..cbcee75f 100644 --- a/server/src/modules/survey/controllers/dataStatistic.controller.ts +++ b/server/src/modules/survey/controllers/dataStatistic.controller.ts @@ -108,6 +108,7 @@ export class DataStatisticController { QUESTION_TYPE.RADIO_STAR, QUESTION_TYPE.RADIO_NPS, QUESTION_TYPE.VOTE, + QUESTION_TYPE.CASCADER, ]; const fieldList = responseSchema.code.dataConf.dataList .filter((item) => allowQuestionType.includes(item.type as QUESTION_TYPE)) diff --git a/server/src/modules/survey/controllers/surveyGroup.controller.ts b/server/src/modules/survey/controllers/surveyGroup.controller.ts index 4852f82e..bb150c6d 100644 --- a/server/src/modules/survey/controllers/surveyGroup.controller.ts +++ b/server/src/modules/survey/controllers/surveyGroup.controller.ts @@ -85,6 +85,7 @@ export class SurveyGroupController { const surveyTotalList = await Promise.all( groupIdList.map((item) => { return this.surveyMetaService.countSurveyMetaByGroupId({ + userId, groupId: item, }); }), @@ -95,6 +96,7 @@ export class SurveyGroupController { return pre; }, {}); const notTotal = await this.surveyMetaService.countSurveyMetaByGroupId({ + userId, groupId: null, }); return { diff --git a/server/src/modules/survey/services/dataStatistic.service.ts b/server/src/modules/survey/services/dataStatistic.service.ts index fbb29ec5..8026a3b5 100644 --- a/server/src/modules/survey/services/dataStatistic.service.ts +++ b/server/src/modules/survey/services/dataStatistic.service.ts @@ -87,6 +87,21 @@ export class DataStatisticService { .join(',') : optionTextMap[data[itemKey]]?.text || data[itemKey]; } + // 将多级联动id还原成选项文案 + if ( + itemConfig.cascaderData && + itemConfig.type === QUESTION_TYPE.CASCADER + ) { + let optionTextMap = keyBy(itemConfig.cascaderData.children, 'hash'); + data[itemKey] = data[itemKey] + ?.split(',') + .map((v) => { + const text = optionTextMap[v]?.text || v; + optionTextMap = keyBy(optionTextMap[v].children, 'hash'); + return text; + }) + .join('-'); + } } return { ...data, diff --git a/server/src/modules/survey/services/surveyMeta.service.ts b/server/src/modules/survey/services/surveyMeta.service.ts index dbbb4d4b..c18f4cdb 100644 --- a/server/src/modules/survey/services/surveyMeta.service.ts +++ b/server/src/modules/survey/services/surveyMeta.service.ts @@ -173,7 +173,7 @@ export class SurveyMetaService { } if (groupId && groupId !== 'all') { query.groupId = - groupId === 'nogrouped' + groupId === 'unclassified' ? { $exists: true, $eq: null, @@ -248,8 +248,9 @@ export class SurveyMetaService { }); return total; } - async countSurveyMetaByGroupId({ groupId }) { + async countSurveyMetaByGroupId({ groupId, userId = undefined }) { const total = await this.surveyRepository.count({ + ownerId: userId, groupId, $or: [ { workspaceId: { $exists: false } }, diff --git a/server/src/modules/survey/utils/index.ts b/server/src/modules/survey/utils/index.ts index 286f0430..eb270d87 100644 --- a/server/src/modules/survey/utils/index.ts +++ b/server/src/modules/survey/utils/index.ts @@ -161,6 +161,7 @@ export function handleAggretionData({ dataMap, item }) { title: dataMap[item.field].title, type: dataMap[item.field].type, data: { + ...item.data, aggregation: arr.map((item) => { const num = item.toString(); return { @@ -173,6 +174,27 @@ export function handleAggretionData({ dataMap, item }) { summary, }, }; + } else if (type == QUESTION_TYPE.CASCADER) { + const aggregation = getTextPaths( + dataMap[item.field].cascaderData.children, + ); + return { + ...item, + title: dataMap[item.field].title, + type: dataMap[item.field].type, + data: { + ...item.data, + aggregation: aggregation + .map((item) => { + return { + id: item.id, + text: item.text, + count: aggregationMap[item.id]?.count || 0, + }; + }) + .filter((v) => v.count > 0), + }, + }; } else { return { ...item, @@ -182,6 +204,27 @@ export function handleAggretionData({ dataMap, item }) { } } +const getTextPaths = (arr, textPrefix = '', idPrefix = '') => { + let paths = []; + + arr.forEach((item) => { + const currentTextPath = textPrefix + ? `${textPrefix}-${item.text}` + : item.text; + const currentIdPath = idPrefix ? `${idPrefix},${item.hash}` : item.hash; + + if (item.children && item.children.length > 0) { + paths = paths.concat( + getTextPaths(item.children, currentTextPath, currentIdPath), + ); + } else { + paths.push({ id: currentIdPath, text: currentTextPath }); + } + }); + + return paths; +}; + function getAverage({ aggregation }) { const { sum, count } = aggregation.reduce( (pre, cur) => { diff --git a/web/package.json b/web/package.json index c606e495..d9cc82f6 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "xiaoju-survey-web", - "version": "1.3.0", + "version": "1.3.1", "description": "XIAOJUSURVEY的web端,包含B端和C端应用", "type": "module", "scripts": { @@ -22,13 +22,14 @@ "axios": "^1.4.0", "clipboard": "^2.0.11", "crypto-js": "^4.2.0", + "default-passive-events": "^2.0.0", "echarts": "^5.5.0", "element-plus": "^2.8.5", "lodash-es": "^4.17.21", "moment": "^2.29.4", "nanoid": "^5.0.7", "node-forge": "^1.3.1", - "pinia": "^2.1.7", + "pinia": "2.2.7", "qrcode": "^1.5.3", "uuid": "^10.0.0", "vue": "^3.4.15", diff --git a/web/public/imgs/question-type-snapshot/cascader.webp b/web/public/imgs/question-type-snapshot/cascader.webp new file mode 100644 index 00000000..ab849863 Binary files /dev/null and b/web/public/imgs/question-type-snapshot/cascader.webp differ diff --git a/web/public/imgs/question-type-snapshot/multilevel.webp b/web/public/imgs/question-type-snapshot/multilevel.webp new file mode 100644 index 00000000..ab849863 Binary files /dev/null and b/web/public/imgs/question-type-snapshot/multilevel.webp differ diff --git a/web/src/common/typeEnum.ts b/web/src/common/typeEnum.ts index 14087a02..65ca3c06 100644 --- a/web/src/common/typeEnum.ts +++ b/web/src/common/typeEnum.ts @@ -7,7 +7,8 @@ export enum QUESTION_TYPE { BINARY_CHOICE = 'binary-choice', RADIO_STAR = 'radio-star', RADIO_NPS = 'radio-nps', - VOTE = 'vote' + VOTE = 'vote', + CASCADER = 'cascader', } // 题目类型标签映射对象 @@ -19,7 +20,8 @@ export const typeTagLabels: Record = { [QUESTION_TYPE.BINARY_CHOICE]: '判断', [QUESTION_TYPE.RADIO_STAR]: '评分', [QUESTION_TYPE.RADIO_NPS]: 'NPS评分', - [QUESTION_TYPE.VOTE]: '投票' + [QUESTION_TYPE.VOTE]: '投票', + [QUESTION_TYPE.CASCADER]: '多级联动' } // 输入类题型 @@ -38,3 +40,6 @@ export const CHOICES = [ // 评分题题型分类 export const RATES = [QUESTION_TYPE.RADIO_STAR, QUESTION_TYPE.RADIO_NPS] + +// 高级题型分类 +export const ADVANCED = [QUESTION_TYPE.CASCADER] \ No newline at end of file diff --git a/web/src/management/config/questionMenuConfig.js b/web/src/management/config/questionMenuConfig.js index 66784bc5..0e363f94 100644 --- a/web/src/management/config/questionMenuConfig.js +++ b/web/src/management/config/questionMenuConfig.js @@ -54,6 +54,13 @@ export const menuItems = { snapshot: '/imgs/question-type-snapshot/nGTscsZlwn1657702222857.webp', icon: 'tixing-toupiao', title: '投票' + }, + cascader: { + type: 'cascader', + path: 'CascaderModule', + snapshot: '/imgs/question-type-snapshot/cascader.webp', + icon: 'cascader-select', + title: '多级联动' } } @@ -65,6 +72,9 @@ const menuGroup = [ { title: '选择类题型', questionList: ['radio', 'checkbox', 'binary-choice', 'radio-star', 'radio-nps', 'vote'] + }, { + title: '高级题型', + questionList: ['cascader'] } ] diff --git a/web/src/management/hooks/useCascaderPull.ts b/web/src/management/hooks/useCascaderPull.ts new file mode 100644 index 00000000..e4ee8c56 --- /dev/null +++ b/web/src/management/hooks/useCascaderPull.ts @@ -0,0 +1,138 @@ +import { ElMessageBox } from 'element-plus' +import { ref } from 'vue' +import { cloneDeep } from 'lodash-es' + +interface NodeItem { + hash: string, + text: string, + children?: NodeItem[], +} + +type CascaderDate = { + placeholder: Array<{ + hash: string, + text: string, + }>, + children: Array, +} + +export const useCascaderPull = () => { + const maxCount = 3; + const optionsCount = 50; + const cascaderVal = ref>([]); + const cascaderData = ref(null) + let hashArr: Array = []; + + + const extractHash = (obj: CascaderDate): Array => { + const hashes: Array = []; + + function recurse(currentObj: any) { + if (Array.isArray(currentObj)) { + currentObj.forEach(item => recurse(item)); + } else if (typeof currentObj === 'object' && currentObj !== null) { + if (currentObj.hash) { + hashes.push(currentObj.hash); + } + for (const key in currentObj) { + // eslint-disable-next-line no-prototype-builtins + if (currentObj.hasOwnProperty(key as any)) { + recurse(currentObj[key]); + } + } + } + } + + recurse(obj); + return hashes; + } + + const getRandom = () => { + return Math.random().toString().slice(-6) + } + + const getNewHash = () => { + let random = getRandom() + while (random in hashArr) { + random = getRandom() + } + hashArr.push(random) + return random + } + + const addCascaderNode = (key: number) => { + const nodeItem: NodeItem = (key == 0 ? cascaderData.value : cascaderVal.value[key - 1]) as NodeItem + if (nodeItem.children && nodeItem.children.length > optionsCount) { + ElMessageBox.alert(`当前最多添加${optionsCount}个选项`, '提示', { + confirmButtonText: '确定', + type: 'warning' + }) + return + } + const optionStr = `选项${nodeItem?.children ? nodeItem?.children?.length + 1 : 1}` + nodeItem.children?.push({ + hash: getNewHash(), + text: optionStr, + children: [] + }) + } + + const resetCascaderVal = (index: number) => { + for (let i = cascaderVal.value.length; index < i; i--) { + cascaderVal.value[i - 1] = null; + } + } + + const removeCascaderNode = (nodeItem: NodeItem, index: number, key: number) => { + try { + if (key == 0 && cascaderData.value?.children && cascaderData.value?.children?.length<=1) { + ElMessageBox.alert('至少保留一个选项', '提示', { + confirmButtonText: '确定', + type: 'warning' + }) + return + } + if (nodeItem.children) { + nodeItem.children[index].children = []; + } + nodeItem.children?.splice(index, 1) + resetCascaderVal(key) + } catch (error) { + console.log(error) + } + } + + const editCascaderNode = (nodeItem: NodeItem, index: number, text: string) => { + nodeItem.children && (nodeItem.children[index].text = text) + } + + + const setCascaderVal = (data: NodeItem, index: number) => { + if (cascaderVal.value[index]?.hash == data.hash) return + resetCascaderVal(index) + cascaderVal.value[index] = data + } + + + const loadInitData = (data: CascaderDate) => { + cascaderData.value = cloneDeep(data); + cascaderVal.value = []; + for (let index = 0; index < maxCount; index++) { + cascaderVal.value.push(null) + } + hashArr = extractHash(cascaderData.value); + } + + + + return { + addCascaderNode, + removeCascaderNode, + editCascaderNode, + loadInitData, + setCascaderVal, + + cascaderVal, + cascaderData + } +} \ No newline at end of file diff --git a/web/src/management/hooks/useMultilevelPull.ts b/web/src/management/hooks/useMultilevelPull.ts new file mode 100644 index 00000000..e4ee8c56 --- /dev/null +++ b/web/src/management/hooks/useMultilevelPull.ts @@ -0,0 +1,138 @@ +import { ElMessageBox } from 'element-plus' +import { ref } from 'vue' +import { cloneDeep } from 'lodash-es' + +interface NodeItem { + hash: string, + text: string, + children?: NodeItem[], +} + +type CascaderDate = { + placeholder: Array<{ + hash: string, + text: string, + }>, + children: Array, +} + +export const useCascaderPull = () => { + const maxCount = 3; + const optionsCount = 50; + const cascaderVal = ref>([]); + const cascaderData = ref(null) + let hashArr: Array = []; + + + const extractHash = (obj: CascaderDate): Array => { + const hashes: Array = []; + + function recurse(currentObj: any) { + if (Array.isArray(currentObj)) { + currentObj.forEach(item => recurse(item)); + } else if (typeof currentObj === 'object' && currentObj !== null) { + if (currentObj.hash) { + hashes.push(currentObj.hash); + } + for (const key in currentObj) { + // eslint-disable-next-line no-prototype-builtins + if (currentObj.hasOwnProperty(key as any)) { + recurse(currentObj[key]); + } + } + } + } + + recurse(obj); + return hashes; + } + + const getRandom = () => { + return Math.random().toString().slice(-6) + } + + const getNewHash = () => { + let random = getRandom() + while (random in hashArr) { + random = getRandom() + } + hashArr.push(random) + return random + } + + const addCascaderNode = (key: number) => { + const nodeItem: NodeItem = (key == 0 ? cascaderData.value : cascaderVal.value[key - 1]) as NodeItem + if (nodeItem.children && nodeItem.children.length > optionsCount) { + ElMessageBox.alert(`当前最多添加${optionsCount}个选项`, '提示', { + confirmButtonText: '确定', + type: 'warning' + }) + return + } + const optionStr = `选项${nodeItem?.children ? nodeItem?.children?.length + 1 : 1}` + nodeItem.children?.push({ + hash: getNewHash(), + text: optionStr, + children: [] + }) + } + + const resetCascaderVal = (index: number) => { + for (let i = cascaderVal.value.length; index < i; i--) { + cascaderVal.value[i - 1] = null; + } + } + + const removeCascaderNode = (nodeItem: NodeItem, index: number, key: number) => { + try { + if (key == 0 && cascaderData.value?.children && cascaderData.value?.children?.length<=1) { + ElMessageBox.alert('至少保留一个选项', '提示', { + confirmButtonText: '确定', + type: 'warning' + }) + return + } + if (nodeItem.children) { + nodeItem.children[index].children = []; + } + nodeItem.children?.splice(index, 1) + resetCascaderVal(key) + } catch (error) { + console.log(error) + } + } + + const editCascaderNode = (nodeItem: NodeItem, index: number, text: string) => { + nodeItem.children && (nodeItem.children[index].text = text) + } + + + const setCascaderVal = (data: NodeItem, index: number) => { + if (cascaderVal.value[index]?.hash == data.hash) return + resetCascaderVal(index) + cascaderVal.value[index] = data + } + + + const loadInitData = (data: CascaderDate) => { + cascaderData.value = cloneDeep(data); + cascaderVal.value = []; + for (let index = 0; index < maxCount; index++) { + cascaderVal.value.push(null) + } + hashArr = extractHash(cascaderData.value); + } + + + + return { + addCascaderNode, + removeCascaderNode, + editCascaderNode, + loadInitData, + setCascaderVal, + + cascaderVal, + cascaderData + } +} \ No newline at end of file diff --git a/web/src/management/pages/edit/components/Picker/event.ts b/web/src/management/pages/edit/components/Picker/event.ts new file mode 100644 index 00000000..b9a041a5 --- /dev/null +++ b/web/src/management/pages/edit/components/Picker/event.ts @@ -0,0 +1,24 @@ + +interface IEvent { + handleConfirm: () => void, + handleCancel: () => void +} + +const useEvent = ({ emit, ctx }: any): IEvent => { + const handleConfirm = () => { + emit('confirm', ctx.list[ctx.index]) + emit('update:modelValue', false) + } + + const handleCancel = () => { + emit('update:modelValue', false) + emit('cancel', false) + } + + return { + handleConfirm, + handleCancel + } +} + +export default useEvent diff --git a/web/src/management/pages/edit/components/Picker/index.vue b/web/src/management/pages/edit/components/Picker/index.vue new file mode 100644 index 00000000..9766eefa --- /dev/null +++ b/web/src/management/pages/edit/components/Picker/index.vue @@ -0,0 +1,206 @@ + + + + + + diff --git a/web/src/management/pages/edit/components/Picker/list.ts b/web/src/management/pages/edit/components/Picker/list.ts new file mode 100644 index 00000000..0d7b0289 --- /dev/null +++ b/web/src/management/pages/edit/components/Picker/list.ts @@ -0,0 +1,145 @@ +import { ref, computed } from 'vue' +import type { Ref } from 'vue' + +interface IList { + box: Ref, + list: Ref>, + getOffsetY: any, + getStyle: any, + handleMove: (e: TouchEvent) => void, + handleStart: (e: TouchEvent) => void, + handleEnd: (e: TouchEvent) => void, + goItem: (idx: number) => void, + resetData: () => void, + index: Ref, + isTouch:boolean +} + +const useList = (props: any): IList => { + const colors = ['gray', '#ccc', '#ddd', '#eee'] + const scales = [.96, .9, .88, .84] + const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 + let startY: number, activeIndex = 0, isDragging = false + const box = ref() + const offY = ref() + const index = ref(0) + const list = ref(props.list) + const getStyle = (idx: number) => { + let color = '#000', scale = 1 + const len = colors.length - 1 + if (idx > activeIndex) { + const _idx = idx - activeIndex > len ? len : idx - activeIndex - 1 + color = colors[_idx] + scale = scales[_idx] + } else if (idx < activeIndex) { + const _idx = activeIndex - idx > len ? len : activeIndex - idx - 1 + color = colors[_idx] + scale = scales[_idx] + } + return { color, transform: `scale(${scale})` } + } + + // 节流 + const throttle = function (callback: any, delay = 20) { + let timer: any = null + return function (args: any) { + if (timer) { + return + } + timer = setTimeout(() => { + callback(args) + timer = null + }, delay) + } + } + + // 移动的实现 + const move = throttle((e: any) => { + const clientY = e.touches ? e.touches[0].clientY : e.clientY; + offY.value = clientY - startY + if (offY.value > 40) { + offY.value = 40 + } else if (offY.value < -box.value.offsetHeight - 40) { + offY.value = -box.value.offsetHeight - 40 + } + // 计算当前位置的就近下标 + index.value = Math.abs(Math.ceil(offY.value / 40)) + // 判断顶部和底部的一个界限,然后做一个位置的重置 + if (index.value <= 0 || offY.value > 0) { + index.value = 0 + } else if (index.value > list.value.length - 1 || offY.value < -box.value.offsetHeight - 18) { + index.value = list.value.length - 1 + } + activeIndex = index.value + }) + + const goItem = (idx: number) => { + index.value = idx; + activeIndex = idx; + } + + const resetData = () => { + startY = 0; + activeIndex = 0 + index.value = 0 + box.value = null; + offY.value = null; + } + + const handleStart = (e: MouseEvent | TouchEvent) => { + isDragging = true + const transform = box.value.style.transform + transform.match(/,(.*)px/) + const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY + startY = clientY - Number(RegExp.$1) + + document.addEventListener('mousemove', handleMove) + document.addEventListener('mouseup', handleEnd) + + } + + const handleMove = (e: MouseEvent | TouchEvent) => { + if (isDragging) { + move(e) + } + } + + const handleEnd = () => { + // 重置当前位置,加setTimeout避免出现Bug + isDragging = false + setTimeout(() => { + offY.value = -index.value * 40 - 18 + }, 100) + document.removeEventListener('mousemove', handleMove) + document.removeEventListener('mouseup', handleEnd) + } + + const getOffsetY = computed(() => { + if (typeof offY.value === 'number') { + return { + transform: `translate(-50%, ${offY.value}px)` + } + } else { + return { + transform: 'translate(-50%, -18px)' + } + } + }) + + return { + box, + list, + getOffsetY, + getStyle, + handleMove, + handleStart, + handleEnd, + goItem, + resetData, + index, + isTouch + } +} + +export default useList + diff --git a/web/src/management/pages/edit/components/QuestionWrapper.vue b/web/src/management/pages/edit/components/QuestionWrapper.vue index ca0afd06..3fb8444c 100644 --- a/web/src/management/pages/edit/components/QuestionWrapper.vue +++ b/web/src/management/pages/edit/components/QuestionWrapper.vue @@ -85,7 +85,7 @@ const itemClass = computed(() => { } }) const showHover = computed(() => { - return isHover.value || props.isSelected + return isHover.value }) const showUp = computed(() => { return !props.isFirst diff --git a/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/CascaderConfig.vue b/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/CascaderConfig.vue new file mode 100644 index 00000000..98d5437f --- /dev/null +++ b/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/CascaderConfig.vue @@ -0,0 +1,194 @@ + + + \ No newline at end of file diff --git a/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/MultiLevelConfig.vue b/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/MultiLevelConfig.vue new file mode 100644 index 00000000..7f896aef --- /dev/null +++ b/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/MultiLevelConfig.vue @@ -0,0 +1,194 @@ + + + \ No newline at end of file diff --git a/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/index.vue b/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/index.vue index ba18f6c2..aa243087 100644 --- a/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/index.vue +++ b/web/src/management/pages/edit/modules/questionModule/components/AdvancedConfig/index.vue @@ -40,6 +40,9 @@ switch (props.moduleConfig.type) { case QUESTION_TYPE.RADIO_NPS: advancedComponent.value = defineAsyncComponent(() => import('./RateConfig.vue')) break + case QUESTION_TYPE.CASCADER: + advancedComponent.value = defineAsyncComponent(() => import('./CascaderConfig.vue')) + break default: break } diff --git a/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue b/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue index 204f6866..ceeefd27 100644 --- a/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue +++ b/web/src/management/pages/edit/modules/questionModule/components/TypeList.vue @@ -11,7 +11,6 @@ :list="item.questionList" :group="{ name: DND_GROUP, pull: 'clone', put: false }" :clone="createNewQuestion" - @end="onDragEnd" item-key="path" >