Feature/dnd (#132)

* feat: 增加拖拽添加题目效果

* feat: 手动实现题型的预览效果

* feat: 优化预览体验
This commit is contained in:
nil 2024-05-16 21:12:59 +08:00 committed by sudoooooo
parent ee4049b947
commit 6350c95536
4 changed files with 139 additions and 30 deletions

View File

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

View File

@ -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()
return filterQuestionPreviewData(props.questionDataList)
const renderData = computed({
get () {
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,

View File

@ -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"
</div> @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>
</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>
@ -103,9 +136,10 @@ const onQuestionType = ({ type }) => {
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;
} }
} }
@ -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>

View File

@ -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
} }
} }