@aochuang/common 1.0.137 → 1.0.139

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,11 +9,11 @@ export default {
9
9
  name: {
10
10
  type: String,
11
11
  required: true
12
- },
12
+ },
13
13
  size: {
14
14
  type: Number
15
15
  },
16
- color: {
16
+ color: {
17
17
  type: String
18
18
  }
19
19
  },
@@ -1,5 +1,9 @@
1
1
  import Image from './image'
2
- import { setImageClickFn, getThumbnailSrc as _getThumbnailSrc, removeThumbnailSrc as _removeThumbnailSrc } from './util'
2
+ import {
3
+ setImageClickFn,
4
+ getThumbnailSrc as _getThumbnailSrc,
5
+ removeThumbnailSrc as _removeThumbnailSrc
6
+ } from './util'
3
7
 
4
8
  Image.install = (Vue, options) => {
5
9
  options = Object.assign({
@@ -106,6 +106,39 @@ export function getThumbnailSrc (url, width) {
106
106
  }
107
107
  }
108
108
 
109
+ function decodeUrlParam (value) {
110
+ try {
111
+ return decodeURIComponent(value.replace(/\+/g, ' '))
112
+ } catch (e) {
113
+ return value
114
+ }
115
+ }
116
+
117
+ function removeThumbnailQuery (url) {
118
+ const hashIndex = url.indexOf('#')
119
+ const hash = hashIndex > -1 ? url.substr(hashIndex) : ''
120
+ const urlWithoutHash = hashIndex > -1 ? url.substr(0, hashIndex) : url
121
+ const queryIndex = urlWithoutHash.indexOf('?')
122
+
123
+ if (queryIndex < 0) {
124
+ return url
125
+ }
126
+
127
+ const baseUrl = urlWithoutHash.substr(0, queryIndex)
128
+ const query = urlWithoutHash.substr(queryIndex + 1)
129
+ const nextQuery = query.split('&').filter(param => {
130
+ const equalIndex = param.indexOf('=')
131
+ const key = decodeUrlParam(equalIndex > -1 ? param.substr(0, equalIndex) : param)
132
+ const value = decodeUrlParam(equalIndex > -1 ? param.substr(equalIndex + 1) : '')
133
+ const isAliyunThumbnail = key === 'x-oss-process' && /^image\/resize,w_[^/]+$/.test(value)
134
+ const isTencentThumbnail = /^imageMogr2\/thumbnail\/[^/]+$/.test(key) && !value
135
+
136
+ return !isAliyunThumbnail && !isTencentThumbnail
137
+ }).join('&')
138
+
139
+ return baseUrl + (nextQuery ? '?' + nextQuery : '') + hash
140
+ }
141
+
109
142
  /**
110
143
  * 移除图片缩略图裁剪参数,保留其他查询参数及 hash
111
144
  */
@@ -115,27 +148,29 @@ export function removeThumbnailSrc (url) {
115
148
  }
116
149
 
117
150
  const hashIndex = url.indexOf('#')
118
- const hash = hashIndex >= 0 ? url.substr(hashIndex) : ''
119
- let source = hashIndex >= 0 ? url.substr(0, hashIndex) : url
120
-
121
- // /oss/view 和 /oss/ 地址的真实图片在 url 参数中,递归处理后再重新编码
122
- if (source.indexOf('/oss/view') > -1 || source.indexOf('/oss/') > -1) {
123
- const marker = source.indexOf('/oss/view') > -1 ? '/oss/view' : '/oss/'
124
- const markerIndex = source.indexOf(marker)
125
- const prefix = source.substr(0, markerIndex + marker.length)
126
- const params = getUrlParams(source)
127
- if (params.url) {
128
- params.url = removeThumbnailSrc(params.url)
129
- source = buildUrl(prefix, params)
130
- return source + hash
131
- }
151
+ const hash = hashIndex > -1 ? url.substr(hashIndex) : ''
152
+ const urlWithoutHash = hashIndex > -1 ? url.substr(0, hashIndex) : url
153
+ let proxyPath = ''
154
+
155
+ if (urlWithoutHash.indexOf('/oss/view') > -1) {
156
+ proxyPath = '/oss/view'
157
+ } else if (urlWithoutHash.indexOf('/oss/') > -1) {
158
+ proxyPath = '/oss/'
159
+ }
160
+
161
+ if (!proxyPath) {
162
+ return removeThumbnailQuery(url)
163
+ }
164
+
165
+ const urlParams = getUrlParams(urlWithoutHash)
166
+ if (!urlParams.url) {
167
+ return url
132
168
  }
133
169
 
134
- // 阿里云参数是普通 query 参数;腾讯云参数是无等号的 query path
135
- source = source
136
- .replace(/([?&])x-oss-process=image\/resize(?:,[^&#]*)?(?=&|$)/ig, '$1')
137
- .replace(/([?&])imageMogr2\/thumbnail\/[^&#]*(?=&|$)/ig, '$1')
138
- .replace('?&', '?')
139
- .replace(/[?&]$/, '')
140
- return source + hash
170
+ return buildUrl(
171
+ urlWithoutHash.substr(0, urlWithoutHash.indexOf(proxyPath) + proxyPath.length),
172
+ Object.assign({}, urlParams, {
173
+ url: removeThumbnailSrc(urlParams.url)
174
+ })
175
+ ) + hash
141
176
  }
@@ -1,23 +1,76 @@
1
1
  <template>
2
- <video
3
- class="ui-video"
4
- :autoplay="autoplay"
5
- :width="width"
6
- :poster="poster"
7
- :preload="preload"
8
- :height="height"
9
- :controls="controls"
10
- @error="handleError"
2
+ <div
3
+ class="ui-video"
4
+ :class="{'is-poster-play-mode': posterPlayMode}"
5
+ :style="wrapperStyle"
11
6
  >
12
- <source :src="innerSrc">
13
- 您的浏览器不支持 video 标签。
14
- </video>
7
+ <template v-if="posterPlayMode">
8
+ <div
9
+ v-if="innerSrc"
10
+ class="ui-video__cover"
11
+ :class="{'is-empty': !innerPoster}"
12
+ :style="coverStyle"
13
+ @click="handlePlayClick"
14
+ >
15
+ <div class="ui-video__cover-mask"></div>
16
+ <div class="ui-video__cover-play">
17
+ <ui-icon name="play-line1" :size="24"></ui-icon>
18
+ </div>
19
+ </div>
20
+ <div v-else class="ui-video__poster-only" :style="coverStyle"></div>
21
+ <div v-if="showFloatingPlayer" ref="floatingLayer" class="ui-video__floating-layer" @click.self="handleCloseClick">
22
+ <div class="ui-video__floating-panel">
23
+ <div class="ui-video__floating-head">
24
+ <ui-icon class="ui-video__floating-close" name="close-circle-line" :size="28" @click="handleCloseClick"></ui-icon>
25
+ </div>
26
+ <div class="ui-video__floating-body">
27
+ <video
28
+ ref="floatingVideo"
29
+ class="ui-video__native"
30
+ :autoplay="autoplay"
31
+ :poster="poster"
32
+ :preload="preload"
33
+ :controls="controls"
34
+ playsinline
35
+ webkit-playsinline
36
+ @error="handleError"
37
+ >
38
+ <source :src="innerSrc">
39
+ 您的浏览器不支持 video 标签。
40
+ </video>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+ <template v-else>
46
+ <video
47
+ ref="nativeVideo"
48
+ class="ui-video__native"
49
+ :autoplay="autoplay"
50
+ :width="width"
51
+ :poster="poster"
52
+ :preload="preload"
53
+ :height="height"
54
+ :controls="controls"
55
+ playsinline
56
+ webkit-playsinline
57
+ @error="handleError"
58
+ >
59
+ <source :src="innerSrc">
60
+ 您的浏览器不支持 video 标签。
61
+ </video>
62
+ </template>
63
+ </div>
15
64
  </template>
16
65
  <script>
66
+ import UiIcon from '../icon'
17
67
  import { formatOssPath } from '../../utils/oss'
18
68
 
19
69
  export default {
20
70
  name: 'UiVideo',
71
+ components: {
72
+ UiIcon
73
+ },
21
74
  props: {
22
75
  width: {
23
76
  type: String
@@ -39,6 +92,10 @@ export default {
39
92
  type: Boolean,
40
93
  default: false
41
94
  },
95
+ posterPlayMode: {
96
+ type: Boolean,
97
+ default: false
98
+ },
42
99
  src: {
43
100
  type: String
44
101
  }
@@ -46,36 +103,92 @@ export default {
46
103
  computed: {
47
104
  style () {
48
105
  return {
49
- width: this.width,
50
- height: this.height
106
+ width: this.formatSize(this.width),
107
+ height: this.formatSize(this.height)
51
108
  }
52
109
  },
110
+ wrapperStyle () {
111
+ if (!this.posterPlayMode) {
112
+ return null
113
+ }
114
+ return {
115
+ width: this.formatSize(this.width),
116
+ height: this.formatSize(this.height)
117
+ }
118
+ },
119
+ innerPoster () {
120
+ return formatOssPath(this.poster)
121
+ },
122
+ coverStyle () {
123
+ const style = {
124
+ width: this.formatSize(this.width),
125
+ height: this.formatSize(this.height)
126
+ }
127
+ if (this.innerPoster) {
128
+ style.backgroundImage = `url(${this.innerPoster})`
129
+ }
130
+ return style
131
+ },
53
132
  innerSrc () {
54
133
  return formatOssPath(this.src)
55
134
  }
56
135
  },
57
136
  data () {
58
137
  return {
59
- isError: false
138
+ isError: false,
139
+ showFloatingPlayer: false
60
140
  }
61
141
  },
62
142
  watch: {
63
143
  src () {
144
+ clearTimeout(this._checkErrorTimer)
145
+ this._checkErrorTimer = null
64
146
  this.isError = false
147
+ this.showFloatingPlayer = false
148
+ if (!this.posterPlayMode) {
149
+ this.$nextTick(() => {
150
+ this.loopCheckError()
151
+ })
152
+ }
65
153
  }
66
154
  },
67
155
  mounted () {
68
- this.loopCheckError()
156
+ if (!this.posterPlayMode) {
157
+ this.loopCheckError()
158
+ }
69
159
  },
70
160
  methods: {
161
+ formatSize (value) {
162
+ if (!value && value !== 0) {
163
+ return null
164
+ }
165
+ if (typeof value === 'number') {
166
+ return `${value}px`
167
+ }
168
+ if (/^\d+$/.test(String(value))) {
169
+ return `${value}px`
170
+ }
171
+ return value
172
+ },
173
+ getVideoElement () {
174
+ if (this.posterPlayMode) {
175
+ return this.$refs.floatingVideo
176
+ }
177
+ return this.$refs.nativeVideo
178
+ },
71
179
  loopCheckError () {
72
180
  if (this.isError) {
73
181
  return
74
182
  }
75
183
  this._checkErrorTimer = setTimeout(() => {
184
+ const videoEl = this.getVideoElement()
185
+ if (!videoEl) {
186
+ this.loopCheckError()
187
+ return
188
+ }
76
189
  // 0:没有关于媒体资源的可用信息,或者媒体资源尚未加载。
77
190
  // 3: 媒体元素尝试加载资源,但没有找到有效的媒体源(可能是资源 URL 错误、403/404 等网络错误,或所有<source>标签指定的资源都无法加载)。
78
- if (this.$el.readyState === 0 && this.$el.networkState === 3) {
191
+ if (videoEl.readyState === 0 && videoEl.networkState === 3) {
79
192
  this.isError = true
80
193
  this.$emit('error')
81
194
  } else {
@@ -86,10 +199,48 @@ export default {
86
199
  handleError () {
87
200
  this.isError = true
88
201
  this.$emit('error')
202
+ },
203
+ appendFloatingLayer () {
204
+ const layerEl = this.$refs.floatingLayer
205
+ if (!layerEl || !document.body || layerEl.parentNode === document.body) {
206
+ return
207
+ }
208
+ document.body.appendChild(layerEl)
209
+ },
210
+ removeFloatingLayer () {
211
+ const layerEl = this.$refs.floatingLayer
212
+ if (!layerEl || layerEl.parentNode !== document.body) {
213
+ return
214
+ }
215
+ document.body.removeChild(layerEl)
216
+ },
217
+ handlePlayClick () {
218
+ this.showFloatingPlayer = true
219
+ this.$nextTick(() => {
220
+ this.appendFloatingLayer()
221
+ const videoEl = this.$refs.floatingVideo
222
+ if (!videoEl) {
223
+ return
224
+ }
225
+ if (!this._checkErrorTimer && !this.isError) {
226
+ this.loopCheckError()
227
+ }
228
+ if (videoEl.play) {
229
+ videoEl.play().catch(() => {})
230
+ }
231
+ })
232
+ },
233
+ handleCloseClick () {
234
+ clearTimeout(this._checkErrorTimer)
235
+ this._checkErrorTimer = null
236
+ this.removeFloatingLayer()
237
+ this.showFloatingPlayer = false
89
238
  }
90
239
  },
91
240
  beforeDestroy () {
92
241
  clearTimeout(this._checkErrorTimer)
242
+ this._checkErrorTimer = null
243
+ this.removeFloatingLayer()
93
244
  }
94
245
  }
95
246
  </script>
@@ -97,5 +248,95 @@ export default {
97
248
  .ui-video {
98
249
  background-color: #000;
99
250
  object-fit: contain;
251
+ position: relative;
252
+ z-index: 0;
253
+ display: block;
254
+ -webkit-transform: translateZ(0);
255
+ transform: translateZ(0);
256
+ }
257
+ .ui-video__native{
258
+ display: block;
259
+ max-width: 100%;
260
+ max-height: 100%;
261
+ background-color: #000;
262
+ object-fit: contain;
263
+ }
264
+ .ui-video.is-poster-play-mode{
265
+ overflow: hidden;
266
+ display: block;
267
+ }
268
+ .ui-video__cover{
269
+ position: relative;
270
+ background-color: #000;
271
+ background-position: center;
272
+ background-repeat: no-repeat;
273
+ background-size: cover;
274
+ cursor: pointer;
275
+ display: block;
276
+ }
277
+ .ui-video__cover.is-empty{
278
+ background-image: none;
279
+ }
280
+ .ui-video__poster-only{
281
+ background-color: #000;
282
+ background-position: center;
283
+ background-repeat: no-repeat;
284
+ background-size: cover;
285
+ display: block;
286
+ }
287
+ .ui-video__floating-layer{
288
+ position: fixed;
289
+ inset: 0;
290
+ z-index: 9999;
291
+ background-color: rgba(0,0,0,.72);
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ padding: 24px 0;
296
+ box-sizing: border-box;
297
+ }
298
+ .ui-video__floating-panel{
299
+ width: 100%;
300
+ max-width: 960px;
301
+ border-radius: 4px;
302
+ overflow: hidden;
303
+ }
304
+ .ui-video__floating-head{
305
+ display: flex;
306
+ justify-content: flex-end;
307
+ padding: 8px 8px 0;
308
+ color: #fff;
309
+ }
310
+ .ui-video__floating-close{
311
+ cursor: pointer;
312
+ }
313
+ .ui-video__floating-body{
314
+ padding: 8px 0 12px;
315
+ }
316
+ .ui-video__floating-body .ui-video__native{
317
+ width: 100%;
318
+ height: auto;
319
+ max-width: 100%;
320
+ max-height: calc(100vh - 120px);
321
+ }
322
+ .ui-video__cover-mask{
323
+ position: absolute;
324
+ inset: 0;
325
+ background-color: rgba(0,0,0,.2);
326
+ }
327
+ .ui-video__cover-play{
328
+ position: absolute;
329
+ left: 50%;
330
+ top: 50%;
331
+ width: 44px;
332
+ height: 44px;
333
+ margin-left: -22px;
334
+ margin-top: -22px;
335
+ border-radius: 50%;
336
+ background-color: rgba(0,0,0,.55);
337
+ color: #fff;
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: center;
100
341
  }
101
342
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aochuang/common",
3
- "version": "1.0.137",
3
+ "version": "1.0.139",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {