uview-plus 3.8.127 → 3.8.129

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.
Files changed (70) hide show
  1. package/README.md +13 -0
  2. package/changelog.md +38 -0
  3. package/components/u-barcode/u-barcode.vue +1 -1
  4. package/components/u-calendar/month.vue +71 -35
  5. package/components/u-calendar/u-calendar.vue +67 -31
  6. package/components/u-car-keyboard/u-car-keyboard.vue +21 -8
  7. package/components/u-cate-tab/u-cate-tab.vue +56 -11
  8. package/components/u-cell/u-cell.vue +20 -16
  9. package/components/u-collapse-item/u-collapse-item.vue +34 -11
  10. package/components/u-datetime-picker/u-datetime-picker.vue +16 -0
  11. package/components/u-dropdown/u-dropdown.vue +15 -0
  12. package/components/u-grid/u-grid.vue +9 -0
  13. package/components/u-grid-item/u-grid-item.vue +8 -0
  14. package/components/u-icon/util.js +16 -1
  15. package/components/u-list/u-list.vue +59 -8
  16. package/components/u-list-item/u-list-item.vue +9 -3
  17. package/components/u-navbar/navbar.js +2 -1
  18. package/components/u-navbar/props.js +6 -1
  19. package/components/u-navbar/u-navbar.vue +20 -3
  20. package/components/u-number-keyboard/u-number-keyboard.vue +18 -6
  21. package/components/u-parse/parser.js +4 -3
  22. package/components/u-picker/u-picker.vue +6 -3
  23. package/components/u-search/u-search.vue +15 -5
  24. package/components/u-select/u-select.vue +206 -31
  25. package/components/u-slider/u-slider.vue +21 -12
  26. package/components/u-tabs/u-tabs.vue +5 -1
  27. package/components/u-text/u-text.vue +34 -32
  28. package/components/u-textarea/props.js +5 -0
  29. package/components/u-textarea/textarea.js +4 -1
  30. package/components/u-textarea/u-textarea.vue +3 -1
  31. package/components/u-upload/u-upload.vue +15 -4
  32. package/components/u-upload/utils.js +4 -1
  33. package/components/u-video/danmaku.js +97 -0
  34. package/components/u-video/props.js +205 -0
  35. package/components/u-video/u-video.vue +1427 -0
  36. package/components/u-video/video-danmaku.vue +273 -0
  37. package/components/u-video/video-slider.vue +179 -0
  38. package/components/u-video/video-utils.js +98 -0
  39. package/components/u-video/video.js +72 -0
  40. package/libs/config/props.js +2 -1
  41. package/libs/function/test.js +2 -0
  42. package/libs/i18n/locales/de.js +14 -1
  43. package/libs/i18n/locales/en.js +14 -1
  44. package/libs/i18n/locales/es.js +14 -1
  45. package/libs/i18n/locales/fr.js +14 -1
  46. package/libs/i18n/locales/ja.js +14 -1
  47. package/libs/i18n/locales/ko.js +14 -1
  48. package/libs/i18n/locales/ru.js +14 -1
  49. package/libs/i18n/locales/th.js +14 -1
  50. package/libs/i18n/locales/zh-Hans.js +14 -1
  51. package/libs/i18n/locales/zh-Hant.js +14 -1
  52. package/libs/mixin/mixin.js +11 -6
  53. package/package.json +1 -1
  54. package/skills/README.md +66 -0
  55. package/skills/uview-plus/SKILL.md +95 -0
  56. package/skills/uview-plus/references/api.md +105 -0
  57. package/skills/uview-plus/references/components.md +156 -0
  58. package/skills/uview-plus/references/icons.md +223 -0
  59. package/skills/uview-plus/references/pitfalls.md +77 -0
  60. package/skills/uview-plus/references/setup.md +168 -0
  61. package/types/comps/form.d.ts +44 -8
  62. package/types/comps/list.d.ts +1 -1
  63. package/types/comps/listItem.d.ts +1 -1
  64. package/types/comps/navbar.d.ts +6 -0
  65. package/types/comps/numberBox.d.ts +85 -21
  66. package/types/comps/pagination.d.ts +96 -0
  67. package/types/comps/swipeActionItem.d.ts +3 -4
  68. package/types/comps/textarea.d.ts +9 -3
  69. package/types/comps.d.ts +1 -0
  70. package/types/index.d.ts +5 -0
@@ -0,0 +1,273 @@
1
+ <template>
2
+ <view class="up-video-danmaku" :style="{ opacity: opacity }">
3
+ <view
4
+ v-for="item in items"
5
+ :key="item.id"
6
+ class="up-video-danmaku__item"
7
+ :style="itemStyle(item)"
8
+ >
9
+ <text class="up-video-danmaku__text" :style="{ color: item.color, fontSize: fontSizePx }">{{ item.text }}</text>
10
+ </view>
11
+ </view>
12
+ </template>
13
+
14
+ <script>
15
+ import { upGetRect } from '../../libs/function/index'
16
+ import {
17
+ acquireDanmuTrack,
18
+ createDanmuState,
19
+ estimateDanmuWidth,
20
+ findDanmuCursor,
21
+ normalizeDanmuItem,
22
+ normalizeDanmuList,
23
+ resetDanmuState
24
+ } from './danmaku'
25
+
26
+ // 顶部/底部固定弹幕的停留毫秒数
27
+ const STATIC_HOLD = 4000
28
+
29
+ /**
30
+ * u-video 的弹幕层。父组件在 timeupdate 里调用 sync(currentTime) 投放弹幕,
31
+ * 发送弹幕走 push()。文件名不以 u- 开头,避免被 easycom 当成公开组件。
32
+ */
33
+ export default {
34
+ name: 'video-danmaku',
35
+ props: {
36
+ list: {
37
+ type: Array,
38
+ default: () => []
39
+ },
40
+ open: {
41
+ type: Boolean,
42
+ default: true
43
+ },
44
+ color: {
45
+ type: String,
46
+ default: '#ffffff'
47
+ },
48
+ fontSize: {
49
+ type: [String, Number],
50
+ default: 14
51
+ },
52
+ duration: {
53
+ type: [String, Number],
54
+ default: 8
55
+ },
56
+ // full 全屏 / half 上半屏 / top 顶部三行
57
+ area: {
58
+ type: String,
59
+ default: 'full'
60
+ },
61
+ opacity: {
62
+ type: [String, Number],
63
+ default: 1
64
+ },
65
+ max: {
66
+ type: [String, Number],
67
+ default: 30
68
+ }
69
+ },
70
+ data() {
71
+ return {
72
+ items: [],
73
+ containerWidth: 0,
74
+ containerHeight: 0,
75
+ cursor: 0,
76
+ lastTime: 0,
77
+ seq: 0,
78
+ timers: []
79
+ }
80
+ },
81
+ computed: {
82
+ fontSizePx() {
83
+ return `${Number(this.fontSize) || 14}px`
84
+ },
85
+ trackHeight() {
86
+ return Math.round((Number(this.fontSize) || 14) * 1.6)
87
+ },
88
+ sortedList() {
89
+ return normalizeDanmuList(this.list)
90
+ }
91
+ },
92
+ watch: {
93
+ list() {
94
+ // 换集/换弹幕源后重新对齐游标
95
+ this.reset(this.lastTime)
96
+ },
97
+ open(value) {
98
+ if (!value) this.clear()
99
+ }
100
+ },
101
+ created() {
102
+ this.state = createDanmuState()
103
+ },
104
+ mounted() {
105
+ this.measure()
106
+ },
107
+ beforeUnmount() {
108
+ this.clear()
109
+ },
110
+ // #ifdef VUE2
111
+ beforeDestroy() {
112
+ this.clear()
113
+ },
114
+ // #endif
115
+ methods: {
116
+ async measure() {
117
+ const rect = await upGetRect('.up-video-danmaku', false, this)
118
+ if (rect && rect.width) {
119
+ this.containerWidth = rect.width
120
+ this.containerHeight = rect.height
121
+ }
122
+ return rect
123
+ },
124
+ trackCount() {
125
+ const height = this.containerHeight || 0
126
+ let usable = height
127
+ if (this.area === 'half') usable = height / 2
128
+ if (this.area === 'top') usable = this.trackHeight * 3
129
+ return Math.max(1, Math.floor(usable / this.trackHeight) || 1)
130
+ },
131
+ // 拖动进度条、切集后调用,清空在屏弹幕并把游标移到新时间点
132
+ reset(time = 0) {
133
+ this.clear()
134
+ this.lastTime = Number(time) || 0
135
+ this.cursor = findDanmuCursor(this.sortedList, this.lastTime)
136
+ },
137
+ clear() {
138
+ this.timers.forEach((timer) => clearTimeout(timer))
139
+ this.timers = []
140
+ this.items = []
141
+ resetDanmuState(this.state)
142
+ },
143
+ // 播放进度推进时投放到点的弹幕,跳转超过 2 秒视为 seek
144
+ sync(currentTime) {
145
+ const time = Number(currentTime) || 0
146
+ if (!this.open) {
147
+ this.lastTime = time
148
+ return
149
+ }
150
+ if (time < this.lastTime || time - this.lastTime > 2) {
151
+ this.reset(time)
152
+ }
153
+ this.lastTime = time
154
+ const list = this.sortedList
155
+ // 宽度还没量到(首帧或隐藏中)时先不投放,避免弹幕从左边冒出来
156
+ if (!this.containerWidth) {
157
+ this.measure()
158
+ return
159
+ }
160
+ while (this.cursor < list.length && list[this.cursor].time <= time) {
161
+ this.spawn(list[this.cursor])
162
+ this.cursor += 1
163
+ }
164
+ },
165
+ // 主动发送一条弹幕(输入框、外部调用)
166
+ push(danmu) {
167
+ const item = normalizeDanmuItem(danmu)
168
+ if (!item.text) return null
169
+ item.time = this.lastTime
170
+ this.spawn(item)
171
+ return item
172
+ },
173
+ spawn(danmu) {
174
+ if (!this.open || !danmu || !danmu.text) return
175
+ if (this.items.length >= Number(this.max)) return
176
+ if (!this.containerWidth) this.measure()
177
+ const width = estimateDanmuWidth(danmu.text, this.fontSize)
178
+ const isStatic = danmu.type === 'top' || danmu.type === 'bottom'
179
+ const track = acquireDanmuTrack(this.state, {
180
+ now: Date.now(),
181
+ itemWidth: width,
182
+ containerWidth: this.containerWidth,
183
+ duration: Number(this.duration) || 8,
184
+ trackCount: this.trackCount(),
185
+ hold: isStatic ? STATIC_HOLD : 0
186
+ })
187
+ if (track < 0) return
188
+ this.mount(danmu, track, width, isStatic)
189
+ },
190
+ mount(danmu, track, width, isStatic) {
191
+ this.seq += 1
192
+ const life = isStatic ? STATIC_HOLD : (Number(this.duration) || 8) * 1000
193
+ const item = {
194
+ id: this.seq,
195
+ text: danmu.text,
196
+ color: danmu.color || this.color,
197
+ width,
198
+ type: danmu.type,
199
+ offset: track * this.trackHeight,
200
+ x: isStatic ? Math.max(0, (this.containerWidth - width) / 2) : this.containerWidth,
201
+ moving: false,
202
+ life
203
+ }
204
+ this.items.push(item)
205
+ if (!isStatic) {
206
+ // 必须等节点带着初始 transform 上屏,再改终点值,transition 才会跑起来
207
+ this.$nextTick(() => {
208
+ this.timers.push(setTimeout(() => {
209
+ const target = this.items.find((current) => current.id === item.id)
210
+ if (target) {
211
+ target.x = -width
212
+ target.moving = true
213
+ }
214
+ }, 20))
215
+ })
216
+ }
217
+ this.timers.push(setTimeout(() => this.remove(item.id), life + 200))
218
+ },
219
+ remove(id) {
220
+ const index = this.items.findIndex((item) => item.id === id)
221
+ if (index > -1) this.items.splice(index, 1)
222
+ },
223
+ itemStyle(item) {
224
+ const style = {
225
+ transform: `translateX(${item.x}px)`
226
+ }
227
+ if (item.type === 'bottom') {
228
+ style.bottom = `${item.offset}px`
229
+ } else {
230
+ style.top = `${item.offset}px`
231
+ }
232
+ if (item.moving) {
233
+ style.transitionProperty = 'transform'
234
+ style.transitionDuration = `${item.life}ms`
235
+ style.transitionTimingFunction = 'linear'
236
+ }
237
+ return style
238
+ }
239
+ }
240
+ }
241
+ </script>
242
+
243
+ <style lang="scss" scoped>
244
+ .up-video-danmaku {
245
+ position: absolute;
246
+ top: 0;
247
+ left: 0;
248
+ right: 0;
249
+ bottom: 0;
250
+ overflow: hidden;
251
+ /* #ifndef APP-NVUE */
252
+ pointer-events: none;
253
+ /* #endif */
254
+ }
255
+
256
+ .up-video-danmaku__item {
257
+ position: absolute;
258
+ left: 0;
259
+ flex-direction: row;
260
+ /* #ifndef APP-NVUE */
261
+ white-space: nowrap;
262
+ will-change: transform;
263
+ /* #endif */
264
+ }
265
+
266
+ .up-video-danmaku__text {
267
+ /* #ifndef APP-NVUE */
268
+ white-space: nowrap;
269
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
270
+ /* #endif */
271
+ line-height: 1.6;
272
+ }
273
+ </style>
@@ -0,0 +1,179 @@
1
+ <template>
2
+ <view
3
+ class="up-video-slider"
4
+ :class="[disabled ? 'up-video-slider--disabled' : '']"
5
+ @touchstart.stop.prevent="onTouchStart"
6
+ @touchmove.stop.prevent="onTouchMove"
7
+ @touchend.stop="onTouchEnd"
8
+ @touchcancel.stop="onTouchEnd"
9
+ @click.stop="onTrackClick"
10
+ >
11
+ <view class="up-video-slider__track" :style="{ height: barHeight }">
12
+ <view
13
+ v-if="buffered > 0"
14
+ class="up-video-slider__buffered"
15
+ :style="{ width: buffered + '%' }"
16
+ ></view>
17
+ <view
18
+ class="up-video-slider__active"
19
+ :style="{ width: innerPercent + '%', backgroundColor: activeColor }"
20
+ ></view>
21
+ <view
22
+ class="up-video-slider__block"
23
+ :style="{
24
+ left: innerPercent + '%',
25
+ width: blockSize,
26
+ height: blockSize,
27
+ marginLeft: '-' + halfBlock,
28
+ marginTop: '-' + halfBlock,
29
+ backgroundColor: activeColor
30
+ }"
31
+ ></view>
32
+ </view>
33
+ </view>
34
+ </template>
35
+
36
+ <script>
37
+ import { upGetRect } from '../../libs/function/index'
38
+ import { clampNumber } from './video-utils'
39
+
40
+ /**
41
+ * u-video 内部使用的拖动条,进度与音量共用。
42
+ * 独立成组件是为了让触摸计算只写一遍,文件名不以 u- 开头以免被 easycom 收录。
43
+ */
44
+ export default {
45
+ name: 'video-slider',
46
+ props: {
47
+ percent: {
48
+ type: [String, Number],
49
+ default: 0
50
+ },
51
+ // 已缓冲百分比,仅进度条使用
52
+ buffered: {
53
+ type: [String, Number],
54
+ default: 0
55
+ },
56
+ activeColor: {
57
+ type: String,
58
+ default: '#2979ff'
59
+ },
60
+ barHeight: {
61
+ type: String,
62
+ default: '2px'
63
+ },
64
+ blockSize: {
65
+ type: String,
66
+ default: '10px'
67
+ },
68
+ disabled: {
69
+ type: Boolean,
70
+ default: false
71
+ }
72
+ },
73
+ emits: ['start', 'changing', 'change'],
74
+ data() {
75
+ return {
76
+ dragging: false,
77
+ innerPercent: 0,
78
+ rect: null,
79
+ touchedAt: 0
80
+ }
81
+ },
82
+ computed: {
83
+ halfBlock() {
84
+ const size = parseFloat(this.blockSize)
85
+ const unit = String(this.blockSize).replace(/[\d.]/g, '') || 'px'
86
+ return `${size / 2}${unit}`
87
+ }
88
+ },
89
+ watch: {
90
+ percent(value) {
91
+ // 拖动过程中不接受外部回写,否则手指会被播放进度拽走
92
+ if (!this.dragging) this.innerPercent = clampNumber(value, 0, 100)
93
+ }
94
+ },
95
+ mounted() {
96
+ this.innerPercent = clampNumber(this.percent, 0, 100)
97
+ },
98
+ methods: {
99
+ async ensureRect() {
100
+ const rect = await upGetRect('.up-video-slider__track', false, this)
101
+ if (rect && rect.width) this.rect = rect
102
+ return this.rect
103
+ },
104
+ percentFromPageX(pageX) {
105
+ if (!this.rect || !this.rect.width) return this.innerPercent
106
+ return clampNumber(((pageX - this.rect.left) / this.rect.width) * 100, 0, 100)
107
+ },
108
+ async onTouchStart(event) {
109
+ if (this.disabled) return
110
+ this.dragging = true
111
+ await this.ensureRect()
112
+ this.$emit('start')
113
+ this.onTouchMove(event)
114
+ },
115
+ onTouchMove(event) {
116
+ if (this.disabled || !this.dragging) return
117
+ const touch = event.touches && event.touches[0] ? event.touches[0] : event.changedTouches && event.changedTouches[0]
118
+ if (!touch) return
119
+ this.innerPercent = this.percentFromPageX(touch.pageX)
120
+ this.$emit('changing', this.innerPercent)
121
+ },
122
+ onTouchEnd() {
123
+ if (this.disabled || !this.dragging) return
124
+ this.dragging = false
125
+ this.touchedAt = Date.now()
126
+ this.$emit('change', this.innerPercent)
127
+ },
128
+ async onTrackClick(event) {
129
+ // 触摸设备上 touchend 后浏览器还会补一个 click,这里只服务鼠标点击
130
+ if (this.disabled || this.dragging) return
131
+ if (this.touchedAt && Date.now() - this.touchedAt < 400) return
132
+ const pageX = event.detail && event.detail.x !== undefined ? event.detail.x : event.pageX
133
+ if (pageX === undefined) return
134
+ await this.ensureRect()
135
+ this.innerPercent = this.percentFromPageX(pageX)
136
+ this.$emit('change', this.innerPercent)
137
+ }
138
+ }
139
+ }
140
+ </script>
141
+
142
+ <style lang="scss" scoped>
143
+ .up-video-slider {
144
+ flex: 1;
145
+ padding-top: 8px;
146
+ padding-bottom: 8px;
147
+ justify-content: center;
148
+ }
149
+
150
+ .up-video-slider--disabled {
151
+ opacity: 0.5;
152
+ }
153
+
154
+ .up-video-slider__track {
155
+ position: relative;
156
+ width: 100%;
157
+ border-radius: 4px;
158
+ background-color: rgba(255, 255, 255, 0.3);
159
+ }
160
+
161
+ .up-video-slider__buffered,
162
+ .up-video-slider__active {
163
+ position: absolute;
164
+ top: 0;
165
+ bottom: 0;
166
+ left: 0;
167
+ border-radius: 4px;
168
+ }
169
+
170
+ .up-video-slider__buffered {
171
+ background-color: rgba(255, 255, 255, 0.45);
172
+ }
173
+
174
+ .up-video-slider__block {
175
+ position: absolute;
176
+ top: 50%;
177
+ border-radius: 50%;
178
+ }
179
+ </style>
@@ -0,0 +1,98 @@
1
+ /**
2
+ * u-video 的纯逻辑辅助方法。
3
+ * 这里不允许引入 uni / vue,Node 端的校验脚本会直接 import 本文件做行为断言。
4
+ */
5
+
6
+ // 把数值限制在 [min, max] 之间
7
+ export function clampNumber(value, min, max) {
8
+ const number = Number(value)
9
+ if (!Number.isFinite(number)) return min
10
+ if (number < min) return min
11
+ if (number > max) return max
12
+ return number
13
+ }
14
+
15
+ // 秒数格式化为 mm:ss,超过一小时输出 hh:mm:ss
16
+ export function formatVideoTime(seconds) {
17
+ const total = Math.floor(Number(seconds))
18
+ if (!Number.isFinite(total) || total <= 0) return '00:00'
19
+ const pad = (value) => (value < 10 ? `0${value}` : `${value}`)
20
+ const hour = Math.floor(total / 3600)
21
+ const minute = Math.floor((total % 3600) / 60)
22
+ const second = total % 60
23
+ if (hour > 0) return `${pad(hour)}:${pad(minute)}:${pad(second)}`
24
+ return `${pad(minute)}:${pad(second)}`
25
+ }
26
+
27
+ // 倍速列表去重、去掉非法值并升序排列,兜底为 [1]
28
+ export function normalizeRateList(list) {
29
+ if (!Array.isArray(list)) return [1]
30
+ const rates = []
31
+ list.forEach((item) => {
32
+ const rate = Number(item)
33
+ if (Number.isFinite(rate) && rate > 0 && !rates.includes(rate)) {
34
+ rates.push(rate)
35
+ }
36
+ })
37
+ if (!rates.length) return [1]
38
+ return rates.sort((a, b) => a - b)
39
+ }
40
+
41
+ // 选集支持直接传地址字符串,默认标题为集数序号
42
+ export function normalizeEpisodes(list) {
43
+ if (!Array.isArray(list)) return []
44
+ return list.map((item, index) => {
45
+ const episode = typeof item === 'string' ? { src: item } : (item || {})
46
+ return {
47
+ src: episode.src || '',
48
+ title: episode.title === undefined || episode.title === '' ? String(index + 1) : episode.title,
49
+ poster: episode.poster || '',
50
+ danmuList: Array.isArray(episode.danmuList) ? episode.danmuList : null
51
+ }
52
+ })
53
+ }
54
+
55
+ // 广告归一化:type 默认前置贴片,图片广告没有时长时给 5 秒
56
+ export function normalizeAds(list) {
57
+ if (!Array.isArray(list)) return []
58
+ return list.map((item) => {
59
+ const ad = item || {}
60
+ const type = ['preroll', 'pause', 'postroll'].includes(ad.type) ? ad.type : 'preroll'
61
+ const src = ad.src || ''
62
+ const duration = Number(ad.duration) > 0 ? Number(ad.duration) : (src ? 0 : 5)
63
+ return {
64
+ type,
65
+ src,
66
+ image: ad.image || '',
67
+ link: ad.link || '',
68
+ text: ad.text || '',
69
+ duration,
70
+ skipAfter: Number.isFinite(Number(ad.skipAfter)) ? Number(ad.skipAfter) : -1
71
+ }
72
+ })
73
+ }
74
+
75
+ export function pickAds(ads, type) {
76
+ if (!Array.isArray(ads)) return []
77
+ return ads.filter((ad) => ad && ad.type === type)
78
+ }
79
+
80
+ // skipAfter: -1 不可跳过,0 立即可跳过,n 播放 n 秒后可跳过
81
+ export function adSkippable(ad, elapsed) {
82
+ if (!ad) return false
83
+ const skipAfter = Number(ad.skipAfter)
84
+ if (!Number.isFinite(skipAfter) || skipAfter < 0) return false
85
+ return Number(elapsed) >= skipAfter
86
+ }
87
+
88
+ export function timeToProgress(time, duration) {
89
+ const total = Number(duration)
90
+ if (!Number.isFinite(total) || total <= 0) return 0
91
+ return clampNumber((Number(time) / total) * 100, 0, 100)
92
+ }
93
+
94
+ export function progressToTime(percent, duration) {
95
+ const total = Number(duration)
96
+ if (!Number.isFinite(total) || total <= 0) return 0
97
+ return clampNumber((clampNumber(percent, 0, 100) / 100) * total, 0, total)
98
+ }
@@ -0,0 +1,72 @@
1
+ export default {
2
+ video: {
3
+ // 视频地址
4
+ src: '',
5
+ // 封面图,首次播放前展示
6
+ poster: '',
7
+ // 标题,显示在顶部信息栏
8
+ title: '',
9
+ width: '100%',
10
+ height: '211px',
11
+ radius: '',
12
+ objectFit: 'contain',
13
+ autoplay: false,
14
+ loop: false,
15
+ muted: false,
16
+ // 指定开始播放的位置,单位秒
17
+ initialTime: 0,
18
+ // 是否显示自绘控制层
19
+ controls: true,
20
+ // 未播放时是否显示中间的大播放按钮
21
+ showCenterPlayBtn: true,
22
+ showFullscreenBtn: true,
23
+ // 顶部返回按钮,只在全屏时有意义
24
+ showBack: false,
25
+ backIcon: 'arrow-left',
26
+ // 播放中控制层自动隐藏的毫秒数,0 表示不自动隐藏
27
+ autoHide: 4000,
28
+ // 是否显示锁屏按钮
29
+ showLock: true,
30
+ // 倍速
31
+ showRate: true,
32
+ rate: 1,
33
+ rateList: [0.5, 0.75, 1, 1.25, 1.5, 2],
34
+ // 音量,取值 0-1
35
+ showVolume: true,
36
+ volume: 1,
37
+ // 弹幕
38
+ enableDanmu: false,
39
+ danmuList: [],
40
+ danmuBtn: true,
41
+ danmuOpen: true,
42
+ danmuColor: '#ffffff',
43
+ danmuFontSize: 14,
44
+ // 一条滚动弹幕走完全屏所需秒数
45
+ danmuDuration: 8,
46
+ // full 全屏 / half 上半屏 / top 顶部三行
47
+ danmuArea: 'full',
48
+ danmuOpacity: 1,
49
+ // 同屏最大弹幕数
50
+ danmuMax: 30,
51
+ // 选集
52
+ episodes: [],
53
+ episodeIndex: 0,
54
+ episodeColumns: 5,
55
+ // 播完自动跳下一集
56
+ autoNext: true,
57
+ // 广告:[{ type: 'preroll'|'pause'|'postroll', src, image, duration, skipAfter, link, text }]
58
+ ads: [],
59
+ // 原生手势,自绘进度条时默认关闭,避免与拖动冲突
60
+ enableProgressGesture: false,
61
+ pageGesture: false,
62
+ vslideGesture: false,
63
+ // 全屏方向,-1 表示由系统根据视频宽高决定
64
+ direction: -1,
65
+ autoPauseIfNavigate: true,
66
+ autoPauseIfOpenNative: true,
67
+ // 自定义 video 节点 id,留空则自动生成
68
+ videoId: '',
69
+ // 进度条与选中态颜色
70
+ activeColor: '#2979ff'
71
+ }
72
+ }
@@ -117,7 +117,8 @@ const componentKeys = [
117
117
  'tooltip',
118
118
  'tr',
119
119
  'transition',
120
- 'upload'
120
+ 'upload',
121
+ 'video'
121
122
  ]
122
123
 
123
124
  const props = {}
@@ -103,6 +103,8 @@ export function idCard(value) {
103
103
  * 是否车牌号
104
104
  */
105
105
  export function carNo(value) {
106
+ // 空值(如表单字段默认为null/undefined)直接返回false,避免读取length时报错
107
+ if (!value) return false
106
108
  // 新能源车牌
107
109
  const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
108
110
  // 旧车牌
@@ -82,5 +82,18 @@ export default {
82
82
  "up.goodsSku.price": "Preis",
83
83
  "up.goodsSku.amount": "Stück",
84
84
  "up.goodsSku.choosed": "Ausgewählt",
85
- "up.goodsSku.buyAmount": "Anzahl"
85
+ "up.goodsSku.buyAmount": "Anzahl",
86
+ "up.video.rate": "Geschwindigkeit",
87
+ "up.video.volume": "Lautstärke",
88
+ "up.video.danmu": "Kommentare",
89
+ "up.video.danmuPlaceholder": "Kommentar senden",
90
+ "up.video.send": "Senden",
91
+ "up.video.episodes": "Folgen",
92
+ "up.video.lock": "Gesperrt",
93
+ "up.video.unlock": "Entsperrt",
94
+ "up.video.ad": "Werbung",
95
+ "up.video.adCountdown": "Werbung {seconds}s",
96
+ "up.video.skipAd": "Werbung überspringen",
97
+ "up.video.adDetail": "Mehr erfahren",
98
+ "up.video.error": "Wiedergabe fehlgeschlagen"
86
99
  }
@@ -82,5 +82,18 @@ export default {
82
82
  "up.goodsSku.price": "Price",
83
83
  "up.goodsSku.amount": "Items",
84
84
  "up.goodsSku.choosed": "Selected",
85
- "up.goodsSku.buyAmount": "Quantity"
85
+ "up.goodsSku.buyAmount": "Quantity",
86
+ "up.video.rate": "Speed",
87
+ "up.video.volume": "Volume",
88
+ "up.video.danmu": "Danmaku",
89
+ "up.video.danmuPlaceholder": "Send a danmaku",
90
+ "up.video.send": "Send",
91
+ "up.video.episodes": "Episodes",
92
+ "up.video.lock": "Locked",
93
+ "up.video.unlock": "Unlocked",
94
+ "up.video.ad": "Ad",
95
+ "up.video.adCountdown": "Ad {seconds}s",
96
+ "up.video.skipAd": "Skip ad",
97
+ "up.video.adDetail": "Learn more",
98
+ "up.video.error": "Playback failed"
86
99
  }