比较完美完成返回滚动实现

This commit is contained in:
zilong 2021-11-04 20:09:27 +08:00
parent e16176d6ea
commit f0e5ea4363
14 changed files with 535 additions and 95 deletions

View File

@ -22,6 +22,7 @@ import {
NMessageProvider,
} from "naive-ui";
import router from "./router";
// import { CloudCircleSharp } from "@vicons/ionicons5";
const store = useStore();
@ -44,6 +45,16 @@ store.commit("loadSettings");
store.state.settings.playing = false; //
store.commit("loadCaches");
// onMounted(() => {
// window.addEventListener('popstate', popstate, false);
// })
// const popstate = (event)=>{
// console.log('popstate--',event);
// }
// onUnmounted(() => {
// window.removeEventListener('popstate',pop)
// })
// router.replace('/discover/recommend')
const showPlaying = ref(false); //
@ -60,14 +71,15 @@ watch(
}
)
//route
const routeToken = pubsub.subscribe("router", (msg, data) => {
switch (msg) {
case "router.beforeEach":
case "router.afterEach":
store.commit("saveSettings", {
currentRoute: data.to.fullPath,
});
// store.commit("saveSettings", {
// currentRoute: data.to.fullPath,
// });
break;
}
});
@ -140,9 +152,9 @@ const hideWins = (e) => {
</div>
<div id="main">
<router-view v-slot="{ Component, route }">
<keep-alive exclude="FM,Friends">
<!-- <keep-alive exclude="FM,Friends"> -->
<component :is="Component" />
</keep-alive>
<!-- </keep-alive> -->
</router-view>
<!-- <router-view v-slot="{ Component, route }">
<keep-alive v-if="route.meta.keepAlive">

View File

@ -0,0 +1,16 @@
<script setup>
</script>
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@ -0,0 +1,55 @@
<script setup>
import { NAvatar } from 'naive-ui';
const props = defineProps({
artists: Array,
})
</script>
<template>
<table class="arList">
<tr v-for="(ar) in artists" :key="ar.idx" @click="$router.push('/singer/'+ar.id)">
<td class="img" >
<NAvatar :size="60" :src="ar.picUrl" object-fit="cover"></NAvatar>
<!-- <img :src="ar.picUrl" alt="歌手头像" width="100"> -->
</td>
<td class="content">
<span class="name">{{ar.name}}</span>
<span class="alias">
{{ar.alias.length > 0 ? '' + ar.alias.join(' ') + '' : ''}}
</span>
</td>
<td class="others"></td>
</tr>
</table>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
@import "@/assets/css/common.less";
.arList{
margin-top: 6px;
border-spacing: 0;
tr{
cursor: pointer;
.content{
.name{
padding: 6px;
}
.alias{
font-size: 14px;
color: #999;
}
}
}
}
</style>

View File

@ -1,31 +1,14 @@
<script setup>
import {
ref,
reactive,
h,
watch,
toRaw,
onMounted,
onUnmounted,
nextTick,
} from "vue";
import { RouterLink, useRoute, useRouter } from "vue-router";
import { useRouter } from "vue-router";
import { useStore } from "vuex";
import {
NButton,
NButtonGroup,
NSpace,
NIcon,
NDropdown,
NMenu,
NLayout,
NLayoutHeader,
NLayoutFooter,
NLayoutContent,
NLayoutSider,
NTag,
NDataTable,
useMessage,
} from "naive-ui";
import Play from "@/assets/svgs/Play_.svg";
import Pause from "@/assets/svgs/Pause.svg";
@ -42,8 +25,24 @@ const router = useRouter();
dayjs.extend(duration);
const props = defineProps({
songs: Array,
songs: Array, //
ctxMenu: Array, //
showHead: {
type: Boolean,
default: true,
}
});
const emit = defineEmits({
itemDbclick: null, //
ctxMenuSelect: null, //
})
const ctxMenuOptions = ()=>{
return (props.ctxMenu ? props.ctxMenu : [])
}
const onCtxMenuSelect = (key, id) => {
props.ctxMenuSelect?.(key, id)
}
// const s = ref(props.songs)
// console.log(s.value.value);
@ -107,7 +106,7 @@ const props = defineProps({
</div>
</div>
<table class="tbList">
<tr class="tr trh">
<tr class="tr trh" v-show="showHead">
<td class="icon"></td>
<td class="name">音乐标题</td>
<td class="ar">歌手</td>
@ -120,7 +119,7 @@ const props = defineProps({
v-for="p in songs"
:key="p.id"
draggable="false"
@dblclick="pubsub.publish('zp.play', { id: p.id, im: true })"
@dblclick="$emit('itemDbclick', p.id)"
>
<td class="icon">
<NButton
@ -139,10 +138,12 @@ const props = defineProps({
<span class="ntext">{{ p.name }}</span>
<span class="nm">
<n-dropdown
v-if="ctxMenuOptions().length > 0"
placement="right-start"
@select="handleSelect($event, p.id)"
@select="$emit('ctxMenuSelect', $event, p.id)"
trigger="click"
:show-arrow="true"
:options="(ctxMenuOptions())"
>
<NButton
class="mn"
@ -207,7 +208,7 @@ export default {
}
&:hover {
background-color: #eee;
// background-color: #eee;
.name .mn {
display: block;

5
src/lib/eventBus.js Normal file
View File

@ -0,0 +1,5 @@
import { EventEmitter } from "events";
const eventBus = new EventEmitter();
export default eventBus;

122
src/lib/useBackSnaps.js Normal file
View File

@ -0,0 +1,122 @@
import { onMounted, onActivated, nextTick } from "vue";
import { useStore } from "vuex";
//设置
let config = {
elName: "mainContent", //#元素名称
log: false, //是否显示log
};
const backSnaps = []; //返回快照数组
let lashHistoryLength = 0; //上次History的长度
let lastHistoryPos = -1; //上次History的位置
const tsArr = [];
export const test = () => {
tsArr.push("tsArr");
console.log(tsArr);
};
export function cfg(cfg){
config = {...config, ...cfg}
}
export function useBackSnaps() {
onActivated(() => {
restore();
});
onMounted(() => {
restore();
});
return {};
}
export const saveBackSnaps = () => {
// console.log("路由afterEach: 当前页面 ", window.location);
// console.log("路由afterEach: ", window.history);
// console.log("路由afterEach: ", from.fullPath, " to ", to.fullPath);
// 判断动作是?
const moveOn =
lashHistoryLength == 0 ||
lastHistoryPos == -1 ||
window.history.length != lashHistoryLength ||
window.history.state.position - lastHistoryPos > 0;
//查找元素...
const $content = document.querySelector(`#${config.elName}`);
// if ($content)
// console.log("路由afterEach: ", $content, $content.scrollTop);
const idx = window.history.state.position + (moveOn ? -1 : 1);
if(window.history.length != lashHistoryLength){ //新路由更新backSnaps数组长度
backSnaps.splice(idx + 1, backSnaps.length - idx)
}
//保存原来的
lashHistoryLength = window.history.length;
lastHistoryPos = window.history.state.position;
//保存
if (idx > -1) {
const backSnap = {
idx,
scrollTop: $content ? $content.scrollTop : 0,
};
// console.log("路由afterEach: ", backSnap);
if (backSnaps.length < idx + 1) backSnaps.length = idx + 1;
backSnaps[idx] = backSnaps[idx] ? {...backSnaps[idx], ...backSnap} : backSnap
// backSnaps[idx] = backSnap;
if (config.log)
console.log("useBackSnaps: 保存上个页面", backSnaps);
}
};
export const saveSnap = (snap)=>{
const idx = window.history.state.position;
//保存
if (idx > -1) {
// const backSnap = {
// idx,
// url: window.location.href,
// };
// // console.log("路由afterEach: ", backSnap);
if (backSnaps.length < idx + 1) backSnaps.length = idx + 1;
backSnaps[idx] = backSnaps[idx] ? {...backSnaps[idx], ...{other: snap}} : {other: snap}
// backSnaps[idx] = backSnap;
if (config.log)
console.log("useBackSnaps: 保存当前页面", backSnaps);
}
}
export const getScrollTop = () => {
return backSnaps[window.history.state.position]
? backSnaps[window.history.state.position].scrollTop
: 0;
};
export const getSnap=()=>{
return backSnaps[window.history.state.position]
? backSnaps[window.history.state.position]
: null;
}
// 恢复scrollTop设置
const restore = () => {
if (config.log)
console.log(
`useBackSnaps: 查找上次"#${config.elName}"保存的设置...`
);
if (backSnaps[window.history.state.position]) {
nextTick(() => {
document.getElementById(config.elName).scrollTop =
backSnaps[window.history.state.position].scrollTop;
});
if (config.log)
console.log(
`useBackSnaps: 恢复${config.elName}的scrollTop为${
backSnaps[window.history.state.position].scrollTop
}`
);
}
};

View File

@ -1,6 +1,9 @@
import { createRouter, createWebHashHistory } from "vue-router";
import pubsub from 'pubsub-js'
import Home from "../views/Home.vue";
import pubsub from "pubsub-js";
import store from "@/store";
import { saveBackSnaps } from "../lib/useBackSnaps";
// import eventBus from '@/eventBus'
// import Home from "../views/Home.vue";
const routes = [
{
@ -15,7 +18,7 @@ const routes = [
{
path: "/discover",
name: "discover",
component: ()=> import('../views/discover/Discover.vue'),
component: () => import("../views/discover/Discover.vue"),
meta: {
keepAlive: true,
},
@ -23,7 +26,7 @@ const routes = [
{
path: "recommend",
name: "discover.recommend",
component: ()=> import('@/views/discover/Recommend.vue'),
component: () => import("@/views/discover/Recommend.vue"),
meta: {
keepAlive: true,
},
@ -31,39 +34,39 @@ const routes = [
{
path: "songlist",
name: "discover.songlist",
component: ()=> import('../views/discover/Songlist.vue'),
component: () => import("../views/discover/Songlist.vue"),
},
{
path: "anchor",
name: "discover.anchor",
component: ()=> import('../views/discover/Anchor.vue'),
component: () => import("../views/discover/Anchor.vue"),
},
{
path: "ranking",
name: "discover.ranking",
component: ()=> import('../views/discover/Ranking.vue'),
component: () => import("../views/discover/Ranking.vue"),
},
{
path: "singer",
name: "discover.singer",
component: ()=> import('../views/discover/Singer.vue'),
component: () => import("../views/discover/Singer.vue"),
},
{
path: "latest",
name: "discover.latest",
component: ()=> import('../views/discover/Latest.vue'),
component: () => import("../views/discover/Latest.vue"),
},
],
},
{
path: "/fm",
name: "fm",
component: ()=> import('../views/fm/FM.vue'),
component: () => import("../views/fm/FM.vue"),
},
{
path: "/videos",
name: "videos",
component: ()=> import('../views/videos/Videos.vue'),
component: () => import("../views/videos/Videos.vue"),
meta: {
keepAlive: true,
},
@ -71,36 +74,37 @@ const routes = [
{
path: "v",
name: "videos.v",
component: ()=> import('../views/videos/V.vue'),
component: () => import("../views/videos/V.vue"),
},
{
path: "mv",
name: "videos.mv",
component: ()=> import('../views/videos/MV.vue'),
component: () => import("../views/videos/MV.vue"),
},
],
},
{
path: "/played",
name: "played",
component: ()=> import('@/views/Played.vue'),
component: () => import("@/views/Played.vue"),
meta: {
keepAlive: true,
},
},
{
path: "/search/:type/:keywords",
path: "/search/:keywords",
name: "search",
component: ()=> import('@/views/SearchResult.vue'),
component: () => import("@/views/SearchResult.vue"),
meta: {
keepAlive: false,
scrollTop: 0,
},
props: true,
},
{
path: "/friends",
name: "friends",
component: ()=> import('../views/friends/Friends.vue'),
component: () => import("../views/friends/Friends.vue"),
},
{
path: "/about",
@ -120,19 +124,39 @@ const router = createRouter({
router.beforeEach((to, from, next) => {
//发布router.beforeEach
pubsub.publish('router.beforeEach', {to: to, frmo: from})
store.commit("saveSettings", {
currentRoute: to.fullPath,
});
// const scrollTop = $content ? $content.scrollTop : 0;
// from.meta.scrollTop = scrollTop;
// }
// to and from are both route objects. must call `next`.
next();
});
// window.addEventListener(
// "popstate",
// (e) => {
// console.log("popstate ", e);
// },
// false
// );
router.afterEach((to, from) => {
// to and from are both route objects.
saveBackSnaps() //保存返回设置
//发布router.afterEach订阅
pubsub.publish('router.afterEach', {to: to, frmo: from})
// pubsub.publish('router.afterEach', {to: to, frmo: from})
// eventBus.emit('router.afterEach', {to: to, frmo: from})
// console.log('router----',to.path)
store.commit("saveSettings", {
currentRoute: to.fullPath,
});
if (to.meta.title) document.title = to.meta.title;
});
export default router;

View File

@ -6,6 +6,9 @@ export default createStore({
// debugStr: "测试debug字符",
showSongDetail: false, //是否显示歌曲详情
keywords: "", //查询关键字
backSnaps: [], //返回快照
lastHistoryPos: -1,
lashHistoryLength: 0,
settings: {
currentRoute: "/discover/recommend", //当前路由
songId: 0, //歌曲id
@ -80,6 +83,21 @@ export default createStore({
saveLoaclSettings(state.settings);
},
//载入backSnaps设置
loadBackSnaps(state) {
const l = localStorage.getItem("zmusic.backSnaps");
if (l) state.backSnaps = { ...state.backSnaps, ...JSON.parse(l) };
},
//保存backSnaps设置
saveBackSnaps(state, backSnaps) {
state.backSnaps = { ...state.backSnaps, ...backSnaps };
saveLoaclTheme(state.backSnaps);
},
saveSnaps(state, snap)
{
// state.backSnaps[snap.idx] =
},
//载入theme设置
loadTheme(state) {
const l = localStorage.getItem("zmusic.theme");
@ -158,3 +176,6 @@ function saveLoaclCaches(s) {
function saveLoaclTheme(s) {
localStorage.setItem("zmusic.theme", JSON.stringify(s));
}
function saveLoaclBackSnaps(s) {
localStorage.setItem("zmusic.backSnaps", JSON.stringify(s));
}

View File

@ -27,10 +27,9 @@ import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import pubsub from "pubsub-js";
console.log('Played 初始化');
onUnmounted(() => {
console.log('Played 卸载');
})
//使useBackSnaps
import { useBackSnaps } from "@/lib/useBackSnaps";
useBackSnaps()
const menuOptions = [
{
@ -149,7 +148,7 @@ const route = useRoute();
:value="route.path"
/>
</div>
<div class="main-content">
<div id="mainContent" class="main-content">
<div class="ld-width">
<div class="lmt-width">
<n-layout>

View File

@ -1,32 +1,76 @@
<script setup>
import { ref, onActivated, watch, onMounted } from "vue";
import {
ref,
toRefs,
onActivated,
watch,
onMounted,
nextTick,
defineAsyncComponent,
onDeactivated,
onUnmounted,
} from "vue";
import { searchResult } from "../network/search";
import Songlist from "../components/Songlist.vue";
import SongsList from "../components/SongsList.vue";
import { useStore } from "vuex";
import { NPagination } from "naive-ui";
import { c, NPagination } from "naive-ui";
import pubsub from "pubsub-js";
import Artistlist from "../components/Artistlist.vue";
import { get } from "lodash";
import { useRoute } from "vue-router";
//使useBackSnaps
import {
useBackSnaps,
getScrollTop,
saveSnap,
getSnap,
} from "@/lib/useBackSnaps";
useBackSnaps();
const snap = {
...{
type: 1,
page: 1,
pageSize: 50,
},
...getSnap()?.other,
};
console.log(snap);
const Songlist = defineAsyncComponent(() =>
import("@/components/Songlist.vue")
);
const props = defineProps({
type: String,
keywords: String,
});
const store = useStore();
const route = useRoute();
onActivated(() => {});
const type = ref(props.type);
// const type = ref(getSnap('type', 1));
const type = ref(snap.type);
const page = ref(snap.page);
const pageSize = ref(snap.pageSize);
const keywords = ref(props.keywords);
// type.value=snap.type
// page.value=snap.page
// pageSize.value=snap.pageSize
const count = ref(0);
const page = ref(1);
const pageSize = ref(50);
const things = ref("单曲");
const elWp = ref(null);
const songs = ref([]);
const artists = ref([]);
const albums = ref([]);
const playlists = ref([]);
const djRadios = ref([]);
const mvs = ref([]);
const videos = ref([]);
watch(
() => [props.type, props.keywords, page.value, pageSize.value],
([t, k]) => {
type.value = t;
() => [props.keywords, type.value, page.value, pageSize.value],
([k, t]) => {
keywords.value = k;
search();
}
@ -37,11 +81,30 @@ onMounted(() => {
search();
});
let firstTime = true;
const search = () => {
switch (type.value) {
case "1":
case 10:
things.value = "专辑";
break;
case 100:
things.value = "歌手";
break;
case 1000:
things.value = "歌单";
break;
case 1009:
things.value = "电台";
break;
case 1004:
things.value = "MV";
break;
case 1014:
things.value = "视频";
break;
case 1:
default:
type.value = "1";
things.value = "单曲";
break;
}
@ -58,8 +121,45 @@ const search = () => {
if (type.value == 1) {
count.value = res.data.result.songCount;
songs.value = res.data.result.songs;
console.log(songs.value);
} else if (type.value == 100) {
count.value = res.data.result.artistCount;
artists.value = res.data.result.artists;
} else if (type.value == 10) {
count.value = res.data.result.albumCount;
albums.value = res.data.result.albums;
} else if (type.value == 1000) {
count.value = res.data.result.playlistCount;
playlists.value = res.data.result.playlists;
} else if (type.value == 1009) {
count.value = res.data.result.djRadiosCount;
djRadios.value = res.data.result.djRadios;
} else if (type.value == 1004) {
count.value = res.data.result.mvCount;
mvs.value = res.data.result.mvs;
} else if (type.value == 1014) {
count.value = res.data.result.videoCount;
videos.value = res.data.result.videos;
}
//
const maxPage = Math.ceil(count.value / pageSize.value);
// console.log('maxPage:' ,count.value, maxPage);
if (page.value > maxPage) page.value = maxPage;
if (page.value < 1) page.value = 1;
if (firstTime) {
setTimeout(() => {
console.log(getScrollTop());
elWp.value.scrollTop = getScrollTop();
}, 100);
firstTime = false;
} else elWp.value.scrollTop = 0;
saveSnap({
type: type.value,
page: page.value,
pageSize: pageSize.value,
});
}
})
.catch((err) => {
@ -77,47 +177,125 @@ const selStyle = (t) => {
};
}
};
const songMenu = [
{
label: "播放",
key: "play",
},
// {
// label: "",
// key: "nextToPlay",
// },
];
const click = () => {
// console.log("ctxMenuClick");
console.log(elWp.value.scrollTop);
window.history.pushState(null, null);
};
</script>
<template>
<div class="main-content">
<div id="mainContent" class="main-content" ref="elWp">
<div class="lmt-width">
<div class="title">
{{ keywords }}
<span class="result">找到 {{ count }} {{ things }}</span>
</div>
<!-- <button @click="click" style="z-index: 1000">btn</button> -->
<div class="tabs">
<div class="tab">
<div class="btns">
<span class="caption" :style="selStyle('1')"> 单曲 </span>
<span class="caption" :style="selStyle('10')">
专辑
<span
class="caption"
:style="selStyle('1')"
@click="
type = 1;
page = 1;
"
>
单曲
</span>
<span class="caption" :style="selStyle('100')">
<span
class="caption"
:style="selStyle('100')"
@click="
type = 100;
page = 1;
"
>
歌手
</span>
<span class="caption" :style="selStyle('1000')">
<span
class="caption"
:style="selStyle('10')"
@click="type = 10;page=1;"
>
专辑
</span>
<span
class="caption"
:style="selStyle('1000')"
@click="type = 1000;page=1;"
>
歌单
</span>
<span class="caption" :style="selStyle('1009')">
<span
class="caption"
:style="selStyle('1009')"
@click="type = 1009;page=1;"
>
电台
</span>
<span class="caption" :style="selStyle('1004')">
<span
class="caption"
:style="selStyle('1004')"
@click="type = 1004;page=1;"
>
MV
</span>
<span class="caption" :style="selStyle('1014')">
<span
class="caption"
:style="selStyle('1014')"
@click="type = 1014;page=1;"
>
视频
</span>
</div>
<div class="bt"></div>
</div>
<div class="panel" v-show="type == '1'">
<Songlist :songs="songs"></Songlist>
<div class="panel" v-show="type == 1">
<Songlist
:songs="songs"
:ctxMenu="songMenu"
@ctxMenuSelect="
(key, id) => {
switch (key) {
case 'play':
pubsub.publish('zp.play', {
id,
im: true,
});
break;
case 'nextToPlay':
break;
}
}
"
@itemDbclick="
(id) => {
pubsub.publish('zp.play', { id, im: true });
}
"
></Songlist>
</div>
<div class="panel" v-show="type == '10'"></div>
<div class="panel" v-show="type == '100'"></div>
<div class="panel" v-show="type == '1000'"></div>
<div class="panel" v-show="type == 10"></div>
<div class="panel" v-show="type == 100">
<Artistlist :artists="artists" />
</div>
<div class="panel" v-show="type == 1000"></div>
</div>
<div class="pager">
<NPagination
@ -130,7 +308,9 @@ const selStyle = (t) => {
</template>
<script>
export default {};
export default {
name: "SearchResult",
};
</script>
<style lang="less" scoped>
@ -156,6 +336,7 @@ export default {};
margin-right: 16px;
color: #666;
border-bottom: solid 2px #f0f0f0;
cursor: pointer;
}
// .sel {
// color: red;

View File

@ -18,7 +18,7 @@ const search = () => {
pubsub.publish("zp.toggleSearch");
// elSearch.value.blur()
store.commit('addSearchHistory', keywords.value)
router.push(`/search/1/${keywords.value}`);
router.push(`/search/${keywords.value}`);
}
};

View File

@ -86,7 +86,7 @@ onUnmounted(() => {
@close.stop="store.commit('removeSearchHistory', h)"
@click="()=>{
pubsub.publish('zp.toggleSearch');
router.push(`/search/1/${h}`);
router.push(`/search/${h}`);
}"
>{{ h }}</NTag
>
@ -101,7 +101,7 @@ onUnmounted(() => {
@click="()=>{
pubsub.publish('zp.toggleSearch');
store.commit('addSearchHistory', item.searchWord)
router.push(`/search/1/${item.searchWord}`);
router.push(`/search/${item.searchWord}`);
}"
>
<div class="idx" :class="{ idxHot: item.iconType == 1 }">

View File

@ -116,7 +116,7 @@ const route = useRoute();
:value="route.path"
/>
</div>
<div class="main-content">
<div id="mainContent" class="main-content">
<!-- <input type="text"> -->
<!-- <NScrollbar > -->
<div class="ld-width">

View File

@ -217,6 +217,10 @@ import {
import pubsub from "pubsub-js";
import ArtistsSpan from "@/components/ArtistsSpan.vue";
//使useBackSnaps
import { useBackSnaps } from "@/lib/useBackSnaps";
useBackSnaps()
// console.log("recommend ");
// onUnmounted(() => {
// console.log('recommend ');