-
-
-
+
+
+
+
+
@@ -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;