This commit is contained in:
itcsx committed 2026-09-23 07:25:47 +08:00
1 parent adcf94df4a
commit 428c94951b
9 files changed
+622 -263

No files matched your search

@@ -19,13 +19,19 @@
</div>
<div v-else class="vap-body">
<!-- 收起 -->
<button class="vap-collapse" type="button" title="收起播放器" @click="emit('close')">
<!-- 收起(控制区收起时换成下面的"显示控制区",右上角同一位置只放一个) -->
<button v-if="controlsVisible" class="vap-collapse" type="button" title="收起播放器" @click="emit('close')">
<DownOutlined />
</button>
<!-- 控制区收起后的恢复入口:整页用法没有弹窗右上角按钮,否则收起就点不回来了 -->
<button v-if="!controlsVisible" class="vap-restore" type="button" title="显示控制区"
@click.stop="toggleControls">
<EyeOutlined />
</button>
<!-- 唱片 + 唱臂 -->
<div class="vap-stage">
<!-- 唱片 + 唱臂:整片区域单击即可暂停/播放,
这样页内全屏(控制区已收起)时也还能控制播放 -->
<div class="vap-stage" :title="isPlaying ? '单击暂停' : '单击播放'" @click="toggle">
<div class="vap-turntable">
<div class="vap-disc" :class="{ 'is-spinning': isPlaying }">
<img v-if="current.cover && !coverError" class="vap-disc-cover" :src="current.cover" alt=""
@@ -66,10 +72,12 @@
</div>
<!-- 功能图标行 -->
<div class="vap-actions">
<button class="vap-act" :class="{ 'is-fav': isFaved }" type="button" title="喜欢" @click="onFav">
<HeartFilled v-if="isFaved" />
<HeartOutlined v-else />
<div v-if="controlsVisible" class="vap-actions">
<!-- 隐藏/显示控制区:整页用法时父组件会顺带藏掉 layout 顶栏,做到整页全屏 -->
<button class="vap-act" type="button" :class="{ 'is-on': !controlsVisible }"
:title="controlsVisible ? '隐藏控制区' : '显示控制区'" @click.stop="toggleControls">
<EyeInvisibleOutlined v-if="controlsVisible" />
<EyeOutlined v-else />
</button>
<button class="vap-act" type="button" title="下载" @click="onDownload">
<DownloadOutlined />
@@ -117,8 +125,8 @@
</div>
<!-- 进度 -->
<div class="vap-progress" ref="barRef" @pointerdown="onBarDown" @pointermove="onBarMove" @pointerup="onBarUp"
@pointercancel="onBarUp">
<div v-if="controlsVisible" class="vap-progress" ref="barRef" @pointerdown="onBarDown"
@pointermove="onBarMove" @pointerup="onBarUp" @pointercancel="onBarUp">
<div class="vap-bar">
<span class="vap-bar-fill" :style="{ width: displayRatio * 100 + '%' }"></span>
<span class="vap-bar-thumb" :style="{ left: displayRatio * 100 + '%' }"></span>
@@ -130,7 +138,7 @@
</div>
<!-- 主控制 -->
<div class="vap-controls">
<div v-if="controlsVisible" class="vap-controls">
<button class="vap-ctl" type="button" :title="modeTitle" @click="cycleMode">
<RetweetOutlined v-if="mode !== 'random'" />
<SwapOutlined v-else />
@@ -180,12 +188,13 @@
import { ref, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
import { message } from "ant-design-vue";
import { request } from "@/utils/request";
import { useVModel } from "@vueuse/core";
import {
DownOutlined, HeartOutlined, HeartFilled, DownloadOutlined, FieldTimeOutlined,
DownOutlined, DownloadOutlined, FieldTimeOutlined,
AudioOutlined, AudioMutedOutlined, EllipsisOutlined, InfoCircleOutlined,
RetweetOutlined, SwapOutlined, StepBackwardFilled, StepForwardFilled,
CaretRightFilled, PauseOutlined, UnorderedListOutlined, CloseOutlined,
CustomerServiceOutlined,
CustomerServiceOutlined, EyeOutlined, EyeInvisibleOutlined,
} from "@ant-design/icons-vue";
/** 一首歌 */
@@ -202,8 +211,31 @@ export interface VinylTrack {
lrc?: string;
}
const props = withDefaults(defineProps<{ tracks?: VinylTrack[] }>(), { tracks: () => [] });
const emit = defineEmits<{ (e: "close"): void }>();
const props = withDefaults(
defineProps<{
tracks?: VinylTrack[];
/** 控制区(功能图标行 / 进度 / 主控制)是否显示;页面可用它做「整页全屏」 */
controls?: boolean;
}>(),
{ tracks: () => [], controls: true }
);
const emit = defineEmits<{
(e: "close"): void;
(e: "update:controls", visible: boolean): void;
}>();
/**
* 控制区是否显示:由功能图标行的眼睛图标切换。
* 收起后唱片与歌词吃掉这部分空间;父组件可用 v-model:controls 拿到状态做整页全屏。
* passive: true(与 VideoPlayer / PhotoSlidePlayer 一致):父组件不绑 v-model 时
* 按钮也不会"点了没反应"。
*/
const controlsVisible = useVModel(props, "controls", emit, { passive: true });
const toggleControls = () => {
menu.value = ""; // 顺手收起可能打开的弹层(定时 / 音量 / 倍速)
controlsVisible.value = !controlsVisible.value;
};
/* ---------------- 基础状态 ---------------- */
const audioARef = ref<HTMLAudioElement | null>(null);
@@ -580,25 +612,7 @@ const onSleep = (v: number) => {
menu.value = "";
};
/* ---------------- 喜欢 / 下载 / 信息 ---------------- */
const FAV_KEY = "vap-favs";
const loadFavs = (): Set<string> => {
try {
return new Set<string>(JSON.parse(localStorage.getItem(FAV_KEY) || "[]"));
} catch {
return new Set<string>();
}
};
const favs = ref<Set<string>>(loadFavs());
const isFaved = computed(() => !!current.value && favs.value.has(current.value.url));
const onFav = () => {
if (!current.value) return;
const s = new Set(favs.value);
if (s.has(current.value.url)) s.delete(current.value.url);
else s.add(current.value.url);
favs.value = s;
localStorage.setItem(FAV_KEY, JSON.stringify([...s]));
};
/* ---------------- 下载 / 信息 ---------------- */
const onDownload = async () => {
if (!current.value) return;
// 参考 DmsFileEnquiry.vue:先取回字节流,再用 Blob + <a download> 落盘
@@ -781,7 +795,33 @@ onBeforeUnmount(() => {
padding: 6px;
}
/* 控制区收起后的恢复入口:与"收起"按钮同一位置(右上角),整页用法下没有弹窗右上角按钮 */
.vap-restore {
position: absolute;
top: 14px;
right: 14px;
z-index: 30;
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
font-size: 18px;
color: #fff;
background: rgba(0, 0, 0, 0.38);
border: 0;
border-radius: 10px;
cursor: pointer;
opacity: 0.55;
transition: opacity 0.2s;
}
.vap-restore:hover {
opacity: 1;
}
/* ---------- 唱片区 ---------- */
/* 单击整片区域即可暂停/播放(全屏收起控制区后唯一的播放开关),所以给可点的光标 */
.vap-stage {
position: relative;
flex: 1 1 auto;
@@ -789,6 +829,7 @@ onBeforeUnmount(() => {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
/* 唱盘方框:唱片与唱臂共用同一坐标系(尺寸即唱片直径) */
@@ -1040,10 +1081,6 @@ onBeforeUnmount(() => {
color: #7ff3e8;
}
.vap-act.is-fav {
color: #ff5b7f;
}
.vap-act.is-muted {
opacity: 0.55;
}