Commit ac99d10f authored by 黄同智's avatar 黄同智

实现拖拽功能

parent c03e0d22
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="208" fill="none" viewBox="0 0 12 208"><path fill="#000" d="M1 0h10v10.951a2 2 0 0 1-.731 1.547l-3 2.461a2 2 0 0 1-2.538 0l-3-2.461A2 2 0 0 1 1 10.95z"/><path stroke="#000" d="M6 13v204"/></svg>
\ No newline at end of file
<template>
<draggable v-model="list" item-key="id" class="list-group" @end="onDragEnd" handle=".tuoz">
<template #item="{ element, index }">
<section class="box rounded-16 p-10 my-10" style="min-width: 1110px;">
<div class="flex-align-center justify-between">
<div class="flex-align-center gap-4">
<div class="tuoz flex-align-center" id="anzhu">
<div class="relative">
<div class="tuoz flex-align-center" id="anzhu" @mousedown="isHidden = true" @mouseup="isHidden = false"
@mouseleave="isHidden = false">
<img src="@/static/imgs/tuoz.png" alt="" class="h-16">
</div>
<span class="fs-12">分镜1</span>
</div>
<span class="fs-12">分镜{{ index+1 }}</span>
<span class="fs-10 py-2 px-4 rounded-4 bg-e3e9ff">ID</span>
<div class="flex-align-center ml-10 bg-f4f6ff py-6 px-14 rounded-8 btnsborder gap-4">
<el-icon color="#5252ff" size="14">
......@@ -20,7 +25,7 @@
<el-button text :icon="Delete" circle />
</div>
</div>
<div class="flex gap-10 h-b100 content">
<div class="flex gap-10 h-b100" :style="{ display: isHidden ? 'none' : 'flex' }">
<section class="w-400 p-14 bg-fff rounded-16 mt-10">
<div>分镜信息</div>
<div class="h-548 hide-scrollbar">
......@@ -110,7 +115,8 @@
</el-upload>
</div>
<div class="flex-1 overflow-y-auto my-8 hide-scrollbar" style="max-height: 412px">
<div contenteditable="true" class="bg-f5 p-10 text-333 rounded-8 custom" style="min-height: 412px"></div>
<div contenteditable="true" class="bg-f5 p-10 text-333 rounded-8 custom" style="min-height: 412px">
</div>
</div>
<div class="">
<div v-if="isShowtip" class="hint flex-between-center px-10">
......@@ -131,8 +137,7 @@
<el-select v-model="secondVal" placeholder="Select" style="width: 80px;">
<el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button size="small" class="h-32 rounded-8"
@click="isShowtip = false">@</el-button>
<el-button size="small" class="h-32 rounded-8" @click="isShowtip = false">@</el-button>
</div>
<div class="flex-align-center h-32 px-10 rounded-4 jinbi">
<svg t="1779335788481" class="icon" viewBox="0 0 1024 1024" version="1.1"
......@@ -160,16 +165,20 @@
</section>
</div>
</section>
</template>
</draggable>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Plus, DocumentCopy, CopyDocument, CircleCloseFilled, MoreFilled, Delete, HelpFilled, Warning } from '@element-plus/icons-vue'
import Empty from './Empty.vue';
import draggable from 'vuedraggable'
const fenjDesc = ref('')
const aiTypeVal = ref('1')
const isShowtip = ref(true)
const secondVal = ref(5)
const isHidden = ref(false)
const options = [
{
......@@ -257,6 +266,17 @@ const videoIndex = ref(0)
const changeVideo = (item: any, index: number) => {
videoIndex.value = index
}
const list = ref([
{ id: 1, name: '第1集 凌晨的咖啡与雨', time: '00:00:00' },
{ id: 2, name: '第2集 早餐的阳光与微笑', time: '00:00:00' },
{ id: 3, name: '第3集 午餐的温暖与关怀', time: '00:00:00' },
])
const onDragEnd = (evt: any) => {
console.log('拖拽结束', list.value)
}
</script>
<style lang="scss" scoped>
.box {
......@@ -340,20 +360,22 @@ const changeVideo = (item: any, index: number) => {
background-color: #4747e7;
}
}
.custom {
outline: none;
box-shadow: none;
border-color: transparent;
}
.tuoz {
cursor: all-scroll;
}
.content {
display: block;
}
// .content1 {
// // display: inline-block;
// }
#anzhu:active ~ .content {
display: none;
}
// section.box:has(#anzhu:active) .content1 {
// display: none !important;
// }
</style>
\ No newline at end of file
<template>
<div class="video-wrapper" @mouseenter="showControls = true" @mouseleave="showControls = false">
<!-- 1. 视频本体 (隐藏自带控件) -->
<video
ref="videoRef"
src="https://www.w3schools.com/html/mov_bbb.mp4"
@timeupdate="handleTimeUpdate"
@loadedmetadata="handleLoadedMetadata"
@ended="handleEnded"
></video>
<!-- 2. 自定义控制层 (只在悬停时显示) -->
<div class="custom-controls" :class="{ 'visible': showControls || isPlaying }">
<!-- 底部区域:包含时间和进度条 -->
<div class="control-bottom">
<!-- 时间显示 (左) -->
<div class="time-display">
{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
</div>
<!-- 进度条轨道 (中) -->
<div
class="progress-track"
ref="progressTrack"
@click="seekVideo"
@mousemove="handleHoverMove"
@mouseleave="hoverTime = null"
>
<!-- 进度填充条 -->
<div class="progress-fill" :style="{ width: progressPercentage + '%' }"></div>
<!-- 拖动滑块 (圆点) -->
<div class="progress-thumb" :style="{ left: progressPercentage + '%' }"></div>
<!-- 悬停预览气泡 (Tooltip) -->
<div
class="hover-tooltip"
v-if="hoverTime !== null"
:style="{ left: hoverPercent + '%' }"
>
{{ formatTime(hoverTime) }}
</div>
</div>
<!-- 播放/暂停按钮 (右) -->
<button class="play-btn" @click="togglePlay">
{{ isPlaying ? '⏸' : '▶' }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
// ----- 状态 -----
const videoRef = ref(null)
const isPlaying = ref(false)
const currentTime = ref(0)
const duration = ref(0)
const showControls = ref(false)
// 悬停状态
const hoverTime = ref(null) // 悬停时的秒数
const hoverPercent = ref(0) // 悬停时的百分比
// ----- 计算属性 -----
const progressPercentage = computed(() => {
if (duration.value === 0) return 0
return (currentTime.value / duration.value) * 100
})
// ----- 核心逻辑 -----
// 1. 格式化时间 (00:00)
const formatTime = (seconds) => {
if (!seconds && seconds !== 0) return '00:00'
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
}
// 2. 视频加载完成,获取总时长
const handleLoadedMetadata = () => {
if (videoRef.value) duration.value = videoRef.value.duration
}
// 3. 播放过程中更新进度
const handleTimeUpdate = () => {
if (videoRef.value) currentTime.value = videoRef.value.currentTime
}
// 4. 播放结束重置
const handleEnded = () => {
isPlaying.value = false
}
// 5. 切换播放/暂停
const togglePlay = () => {
if (!videoRef.value) return
if (videoRef.value.paused) {
videoRef.value.play()
isPlaying.value = true
} else {
videoRef.value.pause()
isPlaying.value = false
}
}
// 6. 点击进度条跳转
const seekVideo = (event) => {
const track = event.currentTarget
const rect = track.getBoundingClientRect()
const x = event.clientX - rect.left
const percent = Math.min(100, Math.max(0, (x / rect.width) * 100))
if (duration.value > 0) {
const newTime = (percent / 100) * duration.value
videoRef.value.currentTime = newTime
currentTime.value = newTime
}
}
// 7. 鼠标悬停计算位置和时间
const handleHoverMove = (event) => {
const track = event.currentTarget
const rect = track.getBoundingClientRect()
const x = event.clientX - rect.left
const percent = Math.min(100, Math.max(0, (x / rect.width) * 100))
hoverPercent.value = percent
if (duration.value > 0) {
hoverTime.value = (percent / 100) * duration.value
}
}
</script>
<style scoped>
/* 容器 */
.video-wrapper {
position: relative;
width: 100%;
max-width: 800px;
background: #000;
aspect-ratio: 16/9;
/* overflow: hidden; */
border-radius: 8px;
}
video {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
/* 控制层 (初始透明,悬停显示) */
.custom-controls {
/* position: absolute;
bottom: 0;
left: 0;
right: 0; */
padding: 20px;
background: linear-gradient(to top, rgba(0,0,0,0.7), transparent);
/* opacity: 0; */
transition: opacity 0.3s ease;
pointer-events: none; /* 鼠标穿透 */
}
.custom-controls.visible {
opacity: 1;
pointer-events: auto;
}
/* 底部布局 */
.control-bottom {
display: flex;
align-items: center;
gap: 15px;
}
/* 时间文本 */
.time-display {
color: rgba(255,255,255,0.9);
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
min-width: 70px;
}
/* 进度条轨道 */
.progress-track {
flex-grow: 1;
height: 6px; /* 更细的轨道 */
background: rgba(1, 255, 56, 0.3); /* 半透明灰 */
border-radius: 4px;
position: relative;
cursor: pointer;
height: 100px;
}
/* 填充条 */
.progress-fill {
height: 100%;
background: #ff0000; /* 纯白填充 */
border-radius: 4px;
width: 0%;
transition: width 0.1s linear;
}
/* 滑块 (圆点) - 核心视觉 */
.progress-thumb {
position: absolute;
top: 50%;
width: 12px;
height: 12px;
background: #fff;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 0 6px rgba(0,0,0,0.5);
opacity: 0;
transition: opacity 0.2s;
}
/* 悬停或播放时显示圆点 */
.progress-track:hover .progress-thumb,
.custom-controls.visible .progress-thumb {
opacity: 1;
}
/* 悬停气泡 (Tooltip) */
.hover-tooltip {
position: absolute;
bottom: 25px;
transform: translateX(-50%);
background: rgba(0,0,0,0.75);
color: #fff;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-family: sans-serif;
pointer-events: none;
}
/* 播放按钮 */
.play-btn {
background: none;
border: none;
color: #fff;
font-size: 20px;
cursor: pointer;
padding: 5px;
transition: transform 0.2s;
}
.play-btn:hover {
transform: scale(1.1);
}
</style>
\ No newline at end of file
This diff is collapsed.
......@@ -34,15 +34,8 @@
</div>
</el-popover>
</div>
<div v-for="(item, index) in 1" :key="index" class="pb-20">
<Episode />
</div>
<!-- <draggable v-model="list" item-key="id" class="list-group" @end="onDragEnd" handle=".tuoz">
<template #item="{ element, index }">
<Episode />
</template>
</draggable> -->
</div>
</section>
</section>
</template>
......@@ -50,7 +43,6 @@
import { ref } from 'vue';
import { DocumentCopy } from '@element-plus/icons-vue'
import Episode from './Episode.vue';
import draggable from 'vuedraggable'
const indexVal = ref(0)
const aiTypeVal = ref('')
......@@ -66,15 +58,6 @@ const options = [
]
const list = ref([
{ id: 1, name: '第1集 凌晨的咖啡与雨', time: '00:00:00' },
{ id: 2, name: '第2集 早餐的阳光与微笑', time: '00:00:00' },
{ id: 3, name: '第3集 午餐的温暖与关怀', time: '00:00:00' },
])
const onDragEnd = (evt:any) => {
console.log('拖拽结束', list.value)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment