Feature/dnd (#132)
* feat: 增加拖拽添加题目效果 * feat: 手动实现题型的预览效果 * feat: 优化预览体验
This commit is contained in:
parent
ee4049b947
commit
6350c95536
1
web/src/management/config/dnd.ts
Normal file
1
web/src/management/config/dnd.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const DND_GROUP = 'question'
|
@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<draggable
|
<draggable
|
||||||
:list="renderData"
|
v-model="renderData"
|
||||||
handle=".question-wrapper.isSelected"
|
handle=".question-wrapper.isSelected"
|
||||||
filter=".question-wrapper.isSelected .question.isSelected"
|
filter=".question-wrapper.isSelected .question.isSelected"
|
||||||
|
:group="DND_GROUP"
|
||||||
:onEnd="checkEnd"
|
:onEnd="checkEnd"
|
||||||
:move="checkMove"
|
:move="checkMove"
|
||||||
itemKey="field"
|
itemKey="field"
|
||||||
@ -34,10 +35,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { computed, defineComponent, ref, getCurrentInstance } from 'vue'
|
import { computed, defineComponent, ref, getCurrentInstance } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import QuestionContainerB from '@/materials/questions/QuestionContainerB'
|
import QuestionContainerB from '@/materials/questions/QuestionContainerB'
|
||||||
import QuestionWrapper from '@/management/pages/edit/components/QuestionWrapper.vue'
|
import QuestionWrapper from '@/management/pages/edit/components/QuestionWrapper.vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import { filterQuestionPreviewData } from '@/management/utils/index'
|
import { filterQuestionPreviewData } from '@/management/utils/index'
|
||||||
|
import { DND_GROUP } from '@/management/config/dnd'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -58,8 +61,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const renderData = computed(() => {
|
const store = useStore()
|
||||||
|
|
||||||
|
const renderData = computed({
|
||||||
|
get () {
|
||||||
return filterQuestionPreviewData(props.questionDataList)
|
return filterQuestionPreviewData(props.questionDataList)
|
||||||
|
},
|
||||||
|
set (questionDataList) {
|
||||||
|
store.commit('edit/setQuestionDataList', questionDataList)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const handleSelect = (index) => {
|
const handleSelect = (index) => {
|
||||||
emit('select', index)
|
emit('select', index)
|
||||||
@ -89,6 +99,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
DND_GROUP,
|
||||||
renderData,
|
renderData,
|
||||||
handleSelect,
|
handleSelect,
|
||||||
handleChangeSeq,
|
handleChangeSeq,
|
||||||
|
@ -6,58 +6,91 @@
|
|||||||
:name="index"
|
:name="index"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
>
|
||||||
<div class="questiontype-list">
|
<draggable
|
||||||
<el-popover
|
class="questiontype-list"
|
||||||
v-for="(item, index) in item.questionList"
|
:list="item.questionList"
|
||||||
:key="item.type"
|
:group="{ name: DND_GROUP, pull: 'clone', put: false }"
|
||||||
placement="right"
|
:clone="getNewQuestion"
|
||||||
trigger="hover"
|
item-key="path"
|
||||||
:popper-class="'qtype-popper-' + (index % 3)"
|
|
||||||
:popper-style="{ width: '369px' }"
|
|
||||||
>
|
>
|
||||||
<img :src="item.snapshot" width="345px" />
|
<template #item="{ element }">
|
||||||
<template #reference>
|
<div
|
||||||
<div :key="item.type" class="qtopic-item" @click="onQuestionType({ type: item.type })">
|
:key="element.type"
|
||||||
<i class="iconfont" :class="['icon-' + item.icon]"></i>
|
class="qtopic-item"
|
||||||
<p class="text">{{ item.title }}</p>
|
:id="'qtopic' + element.type"
|
||||||
|
@click="onQuestionType({ type: element.type })"
|
||||||
|
@mouseenter="showPreview(element, 'qtopic' + element.type)"
|
||||||
|
@mouseleave="isShowPreviewImage = false"
|
||||||
|
@mousedown="isShowPreviewImage = false"
|
||||||
|
>
|
||||||
|
<i class="iconfont" :class="['icon-' + element.icon]"></i>
|
||||||
|
<p class="text">{{ element.title }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
|
||||||
</div>
|
</draggable>
|
||||||
</el-collapse-item>
|
</el-collapse-item>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div class="preview-popover" v-show="isShowPreviewImage" :style="{ top: previewTop + 'px'}">
|
||||||
|
<img :src="previewImg" class="preview-image"/>
|
||||||
|
<span class="preview-arrow"></span>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</el-collapse>
|
</el-collapse>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import questionLoader from '@/materials/questions/questionLoader'
|
import questionLoader from '@/materials/questions/questionLoader'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
|
import { DND_GROUP } from '@/management/config/dnd'
|
||||||
|
|
||||||
import questionMenuConfig, { questionTypeList } from '@/management/config/questionMenuConfig'
|
import questionMenuConfig, { questionTypeList } from '@/management/config/questionMenuConfig'
|
||||||
import { getQuestionByType } from '@/management/utils/index'
|
import { getQuestionByType } from '@/management/utils/index'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { get as _get } from 'lodash-es'
|
import { get as _get, isNumber as _isNumber } from 'lodash-es'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const activeNames = ref([0, 1])
|
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
|
const activeNames = ref([0, 1])
|
||||||
|
const previewImg = ref('')
|
||||||
|
const isShowPreviewImage = ref(false)
|
||||||
|
const previewTop = ref(0)
|
||||||
const questionDataList = computed(() => _get(store, 'state.edit.schema.questionDataList'))
|
const questionDataList = computed(() => _get(store, 'state.edit.schema.questionDataList'))
|
||||||
|
const newQuestionIndex = computed(() => {
|
||||||
|
const currentEditOne = _get(store, 'state.edit.currentEditOne')
|
||||||
|
const index = _isNumber(currentEditOne) ? currentEditOne + 1 : questionDataList.value.length
|
||||||
|
return index
|
||||||
|
})
|
||||||
|
|
||||||
questionLoader.init({
|
questionLoader.init({
|
||||||
typeList: questionTypeList.map((item) => item.type)
|
typeList: questionTypeList.map((item) => item.type)
|
||||||
})
|
})
|
||||||
|
|
||||||
const onQuestionType = ({ type }) => {
|
const getNewQuestion = ({ type }) => {
|
||||||
const fields = questionDataList.value.map((item) => item.field)
|
const fields = questionDataList.value.map((item) => item.field)
|
||||||
const currentEditOne = _get(store, 'state.edit.currentEditOne')
|
|
||||||
const index =
|
|
||||||
typeof currentEditOne === 'number' ? currentEditOne + 1 : questionDataList.value.length
|
|
||||||
const newQuestion = getQuestionByType(type, fields)
|
const newQuestion = getQuestionByType(type, fields)
|
||||||
newQuestion.title = newQuestion.title = `标题${index + 1}`
|
newQuestion.title = newQuestion.title = `标题${newQuestionIndex.value + 1}`
|
||||||
if (type === 'vote') {
|
if (type === 'vote') {
|
||||||
newQuestion.innerType = 'radio'
|
newQuestion.innerType = 'radio'
|
||||||
}
|
}
|
||||||
store.dispatch('edit/addQuestion', { question: newQuestion, index })
|
return newQuestion
|
||||||
store.commit('edit/setCurrentEditOne', index)
|
}
|
||||||
|
|
||||||
|
const onQuestionType = ({ type }) => {
|
||||||
|
const newQuestion = getNewQuestion({ type })
|
||||||
|
store.dispatch('edit/addQuestion', { question: newQuestion, index: newQuestionIndex.value })
|
||||||
|
store.commit('edit/setCurrentEditOne', newQuestionIndex.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const showPreview = ({ snapshot }, id) => {
|
||||||
|
previewImg.value = snapshot
|
||||||
|
|
||||||
|
const dragEl = document.getElementById(id)
|
||||||
|
const { top, height } = dragEl.getBoundingClientRect()
|
||||||
|
previewTop.value = top + height / 2
|
||||||
|
|
||||||
|
isShowPreviewImage.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -106,6 +139,7 @@ const onQuestionType = ({ type }) => {
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,4 +162,64 @@ const onQuestionType = ({ type }) => {
|
|||||||
.qtype-popper-2 {
|
.qtype-popper-2 {
|
||||||
transform: translateX(30px);
|
transform: translateX(30px);
|
||||||
}
|
}
|
||||||
|
// 设置拖拽到编辑区的样式
|
||||||
|
.box .qtopic-item {
|
||||||
|
height: 2px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
* {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-popover {
|
||||||
|
position: fixed;
|
||||||
|
left: 390px;
|
||||||
|
z-index: 9;
|
||||||
|
width: 371px;
|
||||||
|
padding: 12px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--el-border-color-light);
|
||||||
|
box-shadow: var(--el-box-shadow-light);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
animation: fadeIn 100ms linear forwards;
|
||||||
|
|
||||||
|
.preview-image {
|
||||||
|
width: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -6px;
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: var(--el-border-color-light);
|
||||||
|
z-index: -1;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 10px;
|
||||||
|
width: 10px;
|
||||||
|
border: 1px solid var(--el-border-color-light);
|
||||||
|
background: #ffffff;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -60,5 +60,8 @@ export default {
|
|||||||
Object.keys(presets).forEach((key) => {
|
Object.keys(presets).forEach((key) => {
|
||||||
_set(state.schema, key, presets[key])
|
_set(state.schema, key, presets[key])
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
setQuestionDataList(state, data) {
|
||||||
|
state.schema.questionDataList = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user