张延森 hace 2 años
padre
commit
2656b397d5

+ 1
- 0
package.json Ver fichero

@@ -9,6 +9,7 @@
9 9
     "preview": "vite preview"
10 10
   },
11 11
   "dependencies": {
12
+    "@zjxpcyc/vue-tiny-store": "^1.0.1",
12 13
     "vue": "^3.2.37"
13 14
   },
14 15
   "devDependencies": {

+ 8
- 0
pnpm-lock.yaml Ver fichero

@@ -2,11 +2,13 @@ lockfileVersion: 5.4
2 2
 
3 3
 specifiers:
4 4
   '@vitejs/plugin-vue': ^3.0.3
5
+  '@zjxpcyc/vue-tiny-store': ^1.0.1
5 6
   less: ^4.1.3
6 7
   vite: ^3.0.7
7 8
   vue: ^3.2.37
8 9
 
9 10
 dependencies:
11
+  '@zjxpcyc/vue-tiny-store': registry.npmmirror.com/@zjxpcyc/vue-tiny-store/1.0.1
10 12
   vue: registry.npmmirror.com/vue/3.2.37
11 13
 
12 14
 devDependencies:
@@ -166,6 +168,12 @@ packages:
166 168
     name: '@vue/shared'
167 169
     version: 3.2.37
168 170
 
171
+  registry.npmmirror.com/@zjxpcyc/vue-tiny-store/1.0.1:
172
+    resolution: {integrity: sha512-2r/E2bxGygn859ntWLHPDI+pLUR+MuIEPGSN82Y6xPdbQqcsmX1/+L+Pivh/LwFeEY51YcEDhPH8wkhzmtuD8g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@zjxpcyc/vue-tiny-store/-/vue-tiny-store-1.0.1.tgz}
173
+    name: '@zjxpcyc/vue-tiny-store'
174
+    version: 1.0.1
175
+    dev: false
176
+
169 177
   registry.npmmirror.com/copy-anything/2.0.6:
170 178
     resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz}
171 179
     name: copy-anything

+ 2
- 0
src/App.vue Ver fichero

@@ -1,6 +1,7 @@
1 1
 <script setup>
2 2
 import { computed, onMounted, ref } from 'vue';
3 3
 import Loader from '@/components/Loader.vue';
4
+import ScrollDown from '@/components/ScrollDown.vue';
4 5
 import Image from '@/components/Image.vue';
5 6
 import { preload } from '@/utils/preload';
6 7
 
@@ -22,6 +23,7 @@ onMounted(() => {
22 23
 
23 24
 <template>
24 25
   <Loader :loading="loading" :percent="percent" />
26
+  <ScrollDown />
25 27
   <Image v-for="res in resources" :key="res.image" :resource="res"></Image>
26 28
 </template>
27 29
 

BIN
src/assets/audios/10台城.mp3 Ver fichero


BIN
src/assets/audios/11十里堤.mp3 Ver fichero


BIN
src/assets/audios/12石头城.mp3 Ver fichero


BIN
src/assets/audios/13莫愁湖公园.mp3 Ver fichero


BIN
src/assets/audios/14赏心亭.mp3 Ver fichero


BIN
src/assets/audios/15长干里.mp3 Ver fichero


BIN
src/assets/audios/16大报恩寺.mp3 Ver fichero


BIN
src/assets/audios/17凤凰台.mp3 Ver fichero


BIN
src/assets/audios/18桃叶渡.mp3 Ver fichero


BIN
src/assets/audios/19雨花台.mp3 Ver fichero


BIN
src/assets/audios/1南京长江大桥.mp3 Ver fichero


BIN
src/assets/audios/20牛首山.mp3 Ver fichero


BIN
src/assets/audios/21石臼湖.mp3 Ver fichero


BIN
src/assets/audios/22东山.mp3 Ver fichero


BIN
src/assets/audios/23瞻园.mp3 Ver fichero


BIN
src/assets/audios/24白鹭洲.mp3 Ver fichero


BIN
src/assets/audios/25随园.mp3 Ver fichero


BIN
src/assets/audios/26世界文学客厅.mp3 Ver fichero


BIN
src/assets/audios/2阅江楼.mp3 Ver fichero


BIN
src/assets/audios/3秦淮河.mp3 Ver fichero


BIN
src/assets/audios/4乌衣巷.mp3 Ver fichero


BIN
src/assets/audios/5燕子矶.mp3 Ver fichero


BIN
src/assets/audios/6栖霞山.mp3 Ver fichero


BIN
src/assets/audios/7朝天宫.mp3 Ver fichero


BIN
src/assets/audios/8梁洲.mp3 Ver fichero


BIN
src/assets/audios/9后湖映月.mp3 Ver fichero


+ 82
- 6
src/components/Image.vue Ver fichero

@@ -1,21 +1,97 @@
1 1
 <template>
2
-  <img :src="resource.img.src" alt="">
2
+  <div class="img-wrapper">
3
+    <img :src="resource.img.src" @load="onLoad" alt="">
4
+    <div v-if="resource.playBtn" class="paybtn" :style="btnStyle" @click="onPlayBtn">
5
+      <audio :src="resource.audio" preload="auto" ref="audioRef" @ended="onEnded"></audio>
6
+    </div>
7
+  </div>
3 8
 </template>
4 9
 
5 10
 <script setup>
11
+import { ref, watch } from 'vue';
12
+import { useModel } from '@zjxpcyc/vue-tiny-store';
13
+
14
+const [current, updateCurrent] = useModel('audio');
15
+
6 16
 const props = defineProps({
7 17
   resource: Object,
8 18
   default: () => ({})
19
+});
20
+
21
+const btnStyle = ref();
22
+const audioRef = ref();
23
+const playState = ref(0);
24
+
25
+const playAudio = () => {
26
+    audioRef.value.play();
27
+    playState.value = 1;
28
+    updateCurrent(props.resource.audio);
29
+}
30
+
31
+const stopAudio = () => {
32
+    audioRef.value.pause();
33
+    playState.value = 0;
34
+}
35
+
36
+// 监控全局, 如果非当前播放器, 则停止播放
37
+watch(current, (nw) => {
38
+  if (nw !== props.resource.audio && playState.value === 1) {
39
+    stopAudio();
40
+  }
9 41
 })
10 42
 
43
+// 点击播放
44
+const onPlayBtn = () => {
45
+  if (playState.value) {
46
+    stopAudio();
47
+  } else {
48
+    playAudio();
49
+  }
50
+}
51
+
52
+// 播放完成
53
+const onEnded = () => {
54
+  stopAudio();
55
+}
56
+
57
+// 图片加载完成, 设置播放器
58
+const onLoad = (e) => {
59
+  if (!props.resource.playBtn) return;
60
+
61
+  const { pos } = props.resource.playBtn;
62
+  const { width, height, naturalHeight, naturalWidth } = e.target;
63
+  const d = 32; // 宽度一半
64
+  const x = width * pos[0] / naturalWidth - d;
65
+  const y = height * pos[1] / naturalHeight - d;
66
+
67
+  btnStyle.value = { left: `${x}px`, top: `${y}px` }
68
+}
69
+
11 70
 </script>
12 71
 
13 72
 <style lang="less" scoped>
14
-img {
15
-  display: block;
16
-  width: 100vw;
17
-  height: auto;
18
-  object-fit: contain;
73
+.img-wrapper {
74
+  position: relative;
75
+
76
+  .paybtn {
77
+    position: absolute;
78
+    width: 64px;
79
+    height: 64px;
80
+    // background: rgba(255, 0, 0, .5);
81
+    z-index: 10;
82
+
83
+    & > audio {
84
+      max-width: 1px;
85
+      max-height: 1px;
86
+    }
87
+  }
88
+
89
+  img {
90
+    display: block;
91
+    width: 100vw;
92
+    height: auto;
93
+    object-fit: contain;
94
+  }
19 95
 }
20 96
 </style>
21 97
 

+ 1
- 1
src/components/Loader.vue Ver fichero

@@ -1,7 +1,7 @@
1 1
 <template>
2 2
   <div class="loader-wrapper" :class="{ loaded: !loading }">
3 3
     <div class="loader">
4
-      <P class="tip">{{tip}}</P>
4
+      <p class="tip">{{tip}}</p>
5 5
       <p class="percent">{{percent}}%</p>
6 6
       <p class="progress">
7 7
         <div :style="progressStyle"></div>

+ 85
- 0
src/components/ScrollDown.vue Ver fichero

@@ -0,0 +1,85 @@
1
+<template>
2
+  <div class="scroll-down" :style="style">
3
+    <span></span><span></span><span></span>
4
+  </div>
5
+</template>
6
+
7
+<script setup>
8
+import { onBeforeUnmount, onMounted, ref } from "vue"
9
+
10
+const style = ref({ opacity: 1 })
11
+
12
+const onScroll = (e) => {
13
+  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
14
+  const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
15
+  const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
16
+	if (clientHeight + scrollTop === scrollHeight) {
17
+		style.value = { opacity: 0 }
18
+    console.log('已到底部');
19
+	} else {
20
+    if (style.value.opacity === 0) {
21
+      style.value = { opacity: 1 }
22
+    }
23
+  }
24
+}
25
+
26
+onMounted(() => {
27
+  document.addEventListener('scroll', onScroll);
28
+})
29
+
30
+onBeforeUnmount(() => {
31
+  document.removeEventListener('scroll', onScroll);
32
+})
33
+
34
+</script>
35
+
36
+<style lang="less" scoped>
37
+@arrow-color: #5C9495;
38
+
39
+.scroll-down {
40
+  position: fixed;
41
+  bottom: 100px;
42
+  right: 60px;
43
+  z-index: 2;
44
+
45
+  & > span {
46
+    position: absolute;
47
+    display: block;
48
+    top: 0;
49
+    left: 50%;
50
+    width: 24px;
51
+    height: 24px;
52
+    margin-left: -12px;
53
+    border-left: 2px solid @arrow-color;
54
+    border-bottom: 2px solid @arrow-color;
55
+    transform: rotate(-45deg);
56
+    animation: sdb07 2s infinite;
57
+    opacity: 0;
58
+    box-sizing: border-box;
59
+
60
+    &:nth-of-type(1) {
61
+      animation-delay: 0s;
62
+    }
63
+    &:nth-of-type(2) {
64
+      animation-delay: .15s;
65
+      top: 16px;
66
+    }
67
+    &:nth-of-type(3) {
68
+      animation-delay: .3s;
69
+      top: 32px;
70
+    }
71
+  }
72
+
73
+  @keyframes sdb07 {
74
+    0% {
75
+      opacity: 0;
76
+    }
77
+    50% {
78
+      opacity: 1;
79
+    }
80
+    100% {
81
+      opacity: 0;
82
+    }
83
+  }
84
+}
85
+</style>

+ 4
- 1
src/main.js Ver fichero

@@ -1,5 +1,8 @@
1 1
 import { createApp } from 'vue'
2 2
 import './style.css'
3 3
 import App from './App.vue'
4
+import store from './store'
4 5
 
5
-createApp(App).mount('#app')
6
+const app = createApp(App);
7
+app.use(store);
8
+app.mount('#app');

+ 19
- 0
src/store/index.js Ver fichero

@@ -0,0 +1,19 @@
1
+import { ref } from 'vue';
2
+import createStore from '@zjxpcyc/vue-tiny-store';
3
+
4
+const useAudio = () => {
5
+  const current = ref()
6
+
7
+  const changeAudio = (aduRef) => {
8
+    current.value = aduRef;
9
+  }
10
+
11
+  return [
12
+    current,
13
+    changeAudio,
14
+  ]
15
+};
16
+
17
+const store = createStore({ audio: useAudio });
18
+
19
+export default store;

+ 3
- 3
src/utils/preload.js Ver fichero

@@ -5,7 +5,7 @@ export function preload(onProgress) {
5 5
     const total = resources.length;
6 6
     let cursor = 0;
7 7
 
8
-    const callback = () => {
8
+    const callback = (e, isError) => {
9 9
       cursor += 1;
10 10
       onProgress(cursor / total);
11 11
       if (cursor >= total) {
@@ -15,8 +15,8 @@ export function preload(onProgress) {
15 15
 
16 16
     for (let res of resources) {
17 17
       const img = document.createElement('img');
18
-      img.onload = callback;
19
-      img.onerror = callback;
18
+      img.onload = e => callback(e, false);
19
+      img.onerror = e => callback(e, true);
20 20
       img.src = res.image;
21 21
       res.img = img;
22 22
     }

+ 146
- 15
src/utils/resources.js Ver fichero

@@ -33,6 +33,33 @@ import img21 from '@/assets/images/21.jpg';
33 33
 import img22 from '@/assets/images/22.jpg';
34 34
 import img23 from '@/assets/images/23.jpg';
35 35
 
36
+import audio1 from '@/assets/audios/1南京长江大桥.mp3';
37
+import audio2 from '@/assets/audios/2阅江楼.mp3';
38
+import audio3 from '@/assets/audios/3秦淮河.mp3';
39
+import audio4 from '@/assets/audios/4乌衣巷.mp3';
40
+import audio5 from '@/assets/audios/5燕子矶.mp3';
41
+import audio6 from '@/assets/audios/6栖霞山.mp3';
42
+import audio7 from '@/assets/audios/7朝天宫.mp3';
43
+import audio8 from '@/assets/audios/8梁洲.mp3';
44
+import audio9 from '@/assets/audios/9后湖映月.mp3';
45
+import audio10 from '@/assets/audios/10台城.mp3';
46
+import audio11 from '@/assets/audios/11十里堤.mp3';
47
+import audio12 from '@/assets/audios/12石头城.mp3';
48
+import audio13 from '@/assets/audios/13莫愁湖公园.mp3';
49
+import audio14 from '@/assets/audios/14赏心亭.mp3';
50
+import audio15 from '@/assets/audios/15长干里.mp3';
51
+import audio16 from '@/assets/audios/16大报恩寺.mp3';
52
+import audio17 from '@/assets/audios/17凤凰台.mp3';
53
+import audio18 from '@/assets/audios/18桃叶渡.mp3';
54
+import audio19 from '@/assets/audios/19雨花台.mp3';
55
+import audio20 from '@/assets/audios/20牛首山.mp3';
56
+import audio21 from '@/assets/audios/21石臼湖.mp3';
57
+import audio22 from '@/assets/audios/22东山.mp3';
58
+import audio23 from '@/assets/audios/23瞻园.mp3';
59
+import audio24 from '@/assets/audios/24白鹭洲.mp3';
60
+import audio25 from '@/assets/audios/25随园.mp3';
61
+import audio26 from '@/assets/audios/26世界文学客厅.mp3';
62
+
36 63
 // 预加载资源
37 64
 export const resources = [
38 65
   { 
@@ -43,95 +70,199 @@ export const resources = [
43 70
   },
44 71
   { 
45 72
     image: img3,
73
+    playBtn: {
74
+      pos: [616, 932],
75
+    },
76
+    audio: audio1,
46 77
   },
47 78
   { 
48 79
     image: img4,
80
+    playBtn: {
81
+      pos: [100, 406],
82
+    },
83
+    audio: audio2,
49 84
   },
50 85
   { 
51 86
     image: img5_1,
87
+    playBtn: {
88
+      pos: [650, 172],
89
+    },
90
+    audio: audio3,
52 91
   },
53 92
   { 
54 93
     image: img5_2,
94
+    playBtn: {
95
+      pos: [652, 773],
96
+    },
97
+    audio: audio4,
55 98
   },
56
-  { 
57
-    image: img5,
58
-  },
99
+  // { 
100
+  //   image: img5,
101
+  // },
59 102
   { 
60 103
     image: img6,
104
+    playBtn: {
105
+      pos: [624, 890],
106
+    },
107
+    audio: audio5,
61 108
   },
62 109
   { 
63 110
     image: img7,
111
+    playBtn: {
112
+      pos: [612, 486],
113
+    },
114
+    audio: audio6,
64 115
   },
65 116
   { 
66 117
     image: img8_1,
118
+    playBtn: {
119
+      pos: [93, 557],
120
+    },
121
+    audio: audio7,
67 122
   },
68 123
   { 
69 124
     image: img8_2,
125
+    playBtn: {
126
+      pos: [163, 224],
127
+    },
128
+    audio: audio8,
70 129
   },
71
-  { 
72
-    image: img8,
73
-  },
130
+  // { 
131
+  //   image: img8,
132
+  // },
74 133
   { 
75 134
     image: img9,
135
+    playBtn: {
136
+      pos: [152, 782],
137
+    },
138
+    audio: audio9,
76 139
   },
77 140
   { 
78 141
     image: img10_1,
142
+    playBtn: {
143
+      pos: [659, 368],
144
+    },
145
+    audio: audio10,
79 146
   },
80 147
   { 
81 148
     image: img10_2,
149
+    playBtn: {
150
+      pos: [646, 217],
151
+    },
152
+    audio: audio11,
82 153
   },
83
-  { 
84
-    image: img10,
85
-  },
154
+  // { 
155
+  //   image: img10,
156
+  // },
86 157
   { 
87 158
     image: img11,
159
+    playBtn: {
160
+      pos: [574, 1074],
161
+    },
162
+    audio: audio12,
88 163
   },
89 164
   { 
90 165
     image: img12,
166
+    playBtn: {
167
+      pos: [132, 550],
168
+    },
169
+    audio: audio13,
91 170
   },
92 171
   { 
93 172
     image: img13,
173
+    playBtn: {
174
+      pos: [642, 310],
175
+    },
176
+    audio: audio14,
94 177
   },
95 178
   { 
96 179
     image: img14,
180
+    playBtn: {
181
+      pos: [92, 80],
182
+    },
183
+    audio: audio15,
97 184
   },
98 185
   { 
99 186
     image: img15_1,
187
+    playBtn: {
188
+      pos: [652, 31],
189
+    },
190
+    audio: audio16,
100 191
   },
101 192
   { 
102 193
     image: img15_2,
194
+    playBtn: {
195
+      pos: [652, 353],
196
+    },
197
+    audio: audio17,
103 198
   },
104
-  { 
105
-    image: img15,
106
-  },
199
+  // { 
200
+  //   image: img15,
201
+  // },
107 202
   { 
108 203
     image: img16,
204
+    playBtn: {
205
+      pos: [166, 600],
206
+    },
207
+    audio: audio18,
109 208
   },
110 209
   { 
111 210
     image: img17,
211
+    playBtn: {
212
+      pos: [364, 606],
213
+    },
214
+    audio: audio19,
112 215
   },
113 216
   { 
114 217
     image: img18,
218
+    playBtn: {
219
+      pos: [386, 472],
220
+    },
221
+    audio: audio20,
115 222
   },
116 223
   { 
117 224
     image: img19_1,
225
+    playBtn: {
226
+      pos: [132, 367],
227
+    },
228
+    audio: audio21,
118 229
   },
119 230
   { 
120 231
     image: img19_2,
232
+    playBtn: {
233
+      pos: [250, 699],
234
+    },
235
+    audio: audio22,
121 236
   },
122
-  { 
123
-    image: img19,
124
-  },
237
+  // { 
238
+  //   image: img19,
239
+  // },
125 240
   { 
126 241
     image: img20,
242
+    playBtn: {
243
+      pos: [152, 1040],
244
+    },
245
+    audio: audio23,
127 246
   },
128 247
   { 
129 248
     image: img21,
249
+    playBtn: {
250
+      pos: [654, 886],
251
+    },
252
+    audio: audio24,
130 253
   },
131 254
   { 
132 255
     image: img22,
256
+    playBtn: {
257
+      pos: [98, 196],
258
+    },
259
+    audio: audio25,
133 260
   },
134 261
   { 
135 262
     image: img23,
263
+    playBtn: {
264
+      pos: [160, 64],
265
+    },
266
+    audio: audio26,
136 267
   },
137 268
 ]