diff --git a/jeecgboot-vue3/src/views/applet/course-page/AppletCoursePageList.vue b/jeecgboot-vue3/src/views/applet/course-page/AppletCoursePageList.vue index 4ef457e..b7defd9 100644 --- a/jeecgboot-vue3/src/views/applet/course-page/AppletCoursePageList.vue +++ b/jeecgboot-vue3/src/views/applet/course-page/AppletCoursePageList.vue @@ -24,33 +24,42 @@
-
-
- -
-
{{ page.title || `${index + 1}` }}
-
- {{ getPageTypeName(page.type) }} - + + + + +
+ +

暂无页面,点击右上角"新增页面"开始创建

@@ -85,65 +94,98 @@
-
- -
-
- - 文本内容 - - - -
- -
- - 中文 - 英文 - -
- -
+ + +
@@ -171,9 +213,6 @@ - - -
- -
视频预览
+
+ +
+
+
+ 视频封面 +
+
+
视频预览
+
@@ -249,6 +296,7 @@ import { Icon } from '/@/components/Icon'; import { JImageUpload, JUpload } from '/@/components/Form'; import { saveOrUpdate, getById, list, deleteOne, add, edit } from './AppletCoursePage.api'; import { initDictOptions } from '/@/utils/dict/JDictSelectUtil'; +import draggable from 'vuedraggable'; const route = useRoute(); const router = useRouter(); @@ -439,7 +487,7 @@ async function handleAddPage() { courseId: currentPage.courseId, title: `新页面 ${pageList.value.length + 1}`, type: '0', - sort: pageList.value.length, + sort: pageList.value.length + 1, // 自动设置为最后一个位置 pay: 'N', status: 'N', content: '[]' // 默认空内容 @@ -454,6 +502,9 @@ async function handleAddPage() { // 将新页面添加到页面列表中 pageList.value.push(newPage); + // 重新设置所有页面的排序值 + updatePageSortOrder(); + // 设置为当前编辑页面 Object.assign(currentPage, newPage); contentComponents.value = []; @@ -485,6 +536,8 @@ async function deletePage(page: any) { const index = pageList.value.findIndex(p => p === page); if (index > -1) { pageList.value.splice(index, 1); + // 重新设置排序值 + updatePageSortOrder(); // 如果删除的是当前页面,选择其他页面 if (currentPageId.value === page.id || currentPageId.value === '') { if (pageList.value.length > 0) { @@ -521,6 +574,53 @@ async function deletePage(page: any) { } } +/** + * 拖拽开始事件 + */ +function onPageDragStart(evt: any) { + console.log('开始拖拽页面:', evt); +} + +/** + * 拖拽结束事件 + */ +function onPageDragEnd(evt: any) { + console.log('拖拽结束:', evt); + // 更新排序值 + updatePageSortOrder(); + // 保存排序更改 + savePageOrder(); + createMessage.success('页面排序已更新'); +} + +/** + * 更新页面排序值 + */ +function updatePageSortOrder() { + pageList.value.forEach((page, index) => { + page.sort = index + 1; + }); +} + +/** + * 保存页面排序 + */ +async function savePageOrder() { + try { + // 批量更新页面排序 + const updatePromises = pageList.value.map(page => { + if (page.id) { + return edit({ id: page.id, sort: page.sort }); + } + }).filter(Boolean); + + await Promise.all(updatePromises); + } catch (error) { + console.error('保存页面排序失败:', error); + createMessage.error('保存排序失败'); + } +} + /** * 保存页面数据 */ @@ -580,6 +680,7 @@ function refreshPreview() { */ function addTextContent() { contentComponents.value.push({ + id: Date.now() + Math.random(), // 生成唯一ID type: 'text', content: '', language: 'zh' // 默认选择中文 @@ -591,6 +692,7 @@ function addTextContent() { */ function addImageContent() { contentComponents.value.push({ + id: Date.now() + Math.random(), // 生成唯一ID type: 'image', imageUrl: '', alt: '' @@ -602,8 +704,10 @@ function addImageContent() { */ function addVideoContent() { contentComponents.value.push({ + id: Date.now() + Math.random(), // 生成唯一ID type: 'video', - url: '' + url: '', + coverUrl: '' }); } @@ -629,6 +733,30 @@ function handleVideoChange(value: string, component: any) { component.url = value; } +/** + * 视频封面上传变化处理 + */ +function handleVideoCoverChange(value: string, component: any) { + component.coverUrl = value; +} + +/** + * 拖拽开始事件 + */ +function onDragStart(evt: any) { + console.log('拖拽开始:', evt); +} + +/** + * 拖拽结束事件 + */ +function onDragEnd(evt: any) { + console.log('拖拽结束:', evt); + // Vue Draggable 会自动更新 v-model 绑定的数组 + // 这里可以添加额外的处理逻辑,比如保存排序状态 + createMessage.success('组件顺序已更新'); +} + /** * 获取页面类型图标 */ @@ -746,9 +874,51 @@ function getPageTypeName(type: string) { height: 6px; } +/* 拖拽手柄样式 */ +.drag-handle { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + margin-right: 8px; + cursor: grab; + color: #8c8c8c; + border-radius: 2px; + transition: all 0.2s; +} + +.drag-handle:hover { + color: #1890ff; + background: #f0f0f0; +} + +.drag-handle:active { + cursor: grabbing; +} + +/* 拖拽状态样式 */ +.sortable-ghost { + opacity: 0.5; + background: #f0f7ff; + border: 2px dashed #1890ff; +} + +.sortable-chosen { + transform: scale(1.02); + box-shadow: 0 6px 16px rgba(24, 144, 255, 0.3); +} + +.sortable-drag { + opacity: 0.8; + transform: rotate(2deg); +} + .page-item { - min-width: 140px; - width: 140px; + display: flex; + align-items: center; + min-width: 160px; + width: 160px; padding: 8px; background: #fff; border: 1px solid #d9d9d9; @@ -1002,6 +1172,34 @@ function getPageTypeName(type: string) { border-radius: 6px; } +.mobile-screen .video-cover-preview { + position: relative; + display: inline-block; + width: 100%; +} + +.mobile-screen .video-cover-preview img { + width: 100%; + height: auto; + border-radius: 6px; +} + +.mobile-screen .video-cover-preview .play-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; + background: rgba(0, 0, 0, 0.6); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 16px; +} + .settings-panel, .preview-panel { display: flex; @@ -1084,6 +1282,73 @@ function getPageTypeName(type: string) { flex: 1; } +/* 拖拽手柄样式 */ +.drag-handle { + cursor: move; + padding: 4px; + color: #8c8c8c; + transition: color 0.3s; + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 4px; +} + +.drag-handle:hover { + color: #1890ff; + background-color: #f0f8ff; +} + +/* 拖拽列表样式 */ +.draggable-list { + display: flex; + flex-direction: column; + gap: 16px; +} + +/* 拖拽时的样式 */ +.sortable-ghost { + opacity: 0.5; + background-color: #f5f5f5; + border: 2px dashed #d9d9d9; +} + +.sortable-chosen { + transform: scale(1.02); + box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2); + border: 2px solid #1890ff; + background-color: #f0f8ff; +} + +.sortable-drag { + opacity: 0.8; + transform: rotate(5deg); +} + +/* 组件头部样式增强 */ +.component-header { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background-color: #fafafa; + border-radius: 6px 6px 0 0; + border-bottom: 1px solid #f0f0f0; + transition: all 0.3s; +} + +.content-component:hover .component-header { + background-color: #f0f8ff; +} + +.content-component { + transition: all 0.3s; + border-radius: 6px; + overflow: hidden; +} + /* 语言选择器样式 */ .language-selector { margin-bottom: 12px;