v-uni-app-ui 1.0.2 → 1.0.6

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 (65) hide show
  1. package/README.md +127 -0
  2. package/dist/v-uni-app-ui.css +1 -0
  3. package/dist/v-uni-app-ui.es.js +6569 -0
  4. package/dist/v-uni-app-ui.umd.js +7 -0
  5. package/package.json +28 -8
  6. package/components/config.js +0 -123
  7. package/components/layout/v-card/v-card.vue +0 -108
  8. package/components/layout/v-grid/v-grid.vue +0 -162
  9. package/components/layout/v-icon-grid/v-icon-grid.vue +0 -195
  10. package/components/layout/v-infinite-scroll/v-infinite-scroll.vue +0 -172
  11. package/components/layout/v-list/v-list.vue +0 -43
  12. package/components/layout/v-row/v-row.vue +0 -142
  13. package/components/layout/v-waterfall/v-waterfall.vue +0 -79
  14. package/components/model/compound/v-checkbox-group/v-checkbox-group.vue +0 -96
  15. package/components/model/compound/v-console/v-console.js +0 -20
  16. package/components/model/compound/v-console/v-console.vue +0 -299
  17. package/components/model/compound/v-date-time/v-date-time.vue +0 -261
  18. package/components/model/compound/v-dialog/v-dialog.vue +0 -178
  19. package/components/model/compound/v-drum-select-picker/v-drum-select-picker.vue +0 -83
  20. package/components/model/compound/v-form/v-form.vue +0 -226
  21. package/components/model/compound/v-form-item/v-form-item.vue +0 -255
  22. package/components/model/compound/v-image/v-image.vue +0 -357
  23. package/components/model/compound/v-input-desensitize/v-input-desensitize.vue +0 -101
  24. package/components/model/compound/v-page/v-page.vue +0 -11
  25. package/components/model/compound/v-pages/v-pages.vue +0 -141
  26. package/components/model/compound/v-picker-list/v-picker-list.vue +0 -109
  27. package/components/model/compound/v-popup/v-popup.vue +0 -151
  28. package/components/model/compound/v-radio-group/v-radio-group.vue +0 -86
  29. package/components/model/compound/v-select-picker/v-select-picker.vue +0 -202
  30. package/components/model/compound/v-series-picker-list/v-series-picker-list.vue +0 -221
  31. package/components/model/compound/v-series-select-picker/v-series-select-picker.vue +0 -203
  32. package/components/model/compound/v-switch/v-switch.vue +0 -136
  33. package/components/model/compound/v-tabs-page/v-tabs-page.vue +0 -138
  34. package/components/model/native/v-badge/v-badge.vue +0 -143
  35. package/components/model/native/v-button/v-button.vue +0 -273
  36. package/components/model/native/v-carousel/v-carousel.vue +0 -138
  37. package/components/model/native/v-checkbox/v-checkbox.vue +0 -215
  38. package/components/model/native/v-collapse/v-collapse.vue +0 -190
  39. package/components/model/native/v-header-navigation-bar/v-header-navigation-bar.vue +0 -92
  40. package/components/model/native/v-input/v-input.vue +0 -352
  41. package/components/model/native/v-input-code/v-input-code.vue +0 -146
  42. package/components/model/native/v-loading/v-loading.vue +0 -206
  43. package/components/model/native/v-menu/v-menu.vue +0 -222
  44. package/components/model/native/v-menu-slide/v-menu-slide.vue +0 -364
  45. package/components/model/native/v-min-loading/v-min-loading.vue +0 -80
  46. package/components/model/native/v-null/v-null.vue +0 -97
  47. package/components/model/native/v-overlay/v-overlay.vue +0 -96
  48. package/components/model/native/v-pull-up-refresh/v-pull-up-refresh.vue +0 -157
  49. package/components/model/native/v-radio/v-radio.vue +0 -138
  50. package/components/model/native/v-scroll-list/v-scroll-list.vue +0 -169
  51. package/components/model/native/v-steps/v-steps.vue +0 -253
  52. package/components/model/native/v-table/v-table.vue +0 -203
  53. package/components/model/native/v-tabs/v-tabs.vue +0 -235
  54. package/components/model/native/v-tag/v-tag.vue +0 -206
  55. package/components/model/native/v-text/v-text.vue +0 -187
  56. package/components/model/native/v-text-button/v-text-button.vue +0 -139
  57. package/components/model/native/v-textarea/v-textarea.vue +0 -178
  58. package/components/model/native/v-title/v-title.vue +0 -91
  59. package/components/model/native/v-toast/info.png +0 -0
  60. package/components/model/native/v-toast/success.png +0 -0
  61. package/components/model/native/v-toast/v-toast.vue +0 -198
  62. package/components/model/native/v-toast/warn.png +0 -0
  63. package/components/model/native/v-upload-file-button/v-upload-file-button.vue +0 -296
  64. package/components/model/native/v-video/v-video.vue +0 -175
  65. package/components/model/native/v-window/v-window.vue +0 -158
@@ -1,198 +0,0 @@
1
- <template>
2
- <view v-if="toastList.length > 0" class="toast-container">
3
- <view v-for="(toast, index) in toastList" :key="index" class="toast-content-wrapper" :style="{ top: `${20 + index * 10}%` }">
4
- <view class="toast-content" :class="[toast.typeClass, toast.customClass]" v-if="toast.isVisible">
5
- <!-- 信息图标 -->
6
- <text v-if="toast.type === 'info'" class="icon">
7
- <image src="./info.png" />
8
- </text>
9
- <!-- 警告图标 -->
10
- <text v-else-if="toast.type === 'warn'" class="icon"><image src="./warn.png" /></text>
11
- <!-- 加载中图标 -->
12
- <view v-else-if="toast.type === 'loading'" class="loading-icon"></view>
13
- <!-- 成功图标 -->
14
- <text v-else-if="toast.type === 'success'" class="icon"><image src="./success.png" /></text>
15
- <!-- 错误图标 -->
16
- <text v-else-if="toast.type === 'error'" class="icon"><view class="text-icon">✕</view></text>
17
- <!-- 消息内容 -->
18
- <text class="toast-message">{{ toast.message }}</text>
19
- </view>
20
- </view>
21
- </view>
22
- </template>
23
-
24
- <script lang="ts" setup>
25
- import { ref, computed, onUnmounted } from 'vue';
26
- import { config } from '../../../config';
27
-
28
- type ToastType = 'info' | 'success' | 'error' | 'loading' | 'warn';
29
- type ToastPosition = 'top' | 'center' | 'bottom';
30
-
31
- interface ToastItem {
32
- message: string;
33
- type: ToastType;
34
- position: ToastPosition;
35
- duration: number;
36
- customClass: string;
37
- typeClass: string;
38
- isVisible: boolean;
39
- isLoaded: boolean;
40
- timer?: NodeJS.Timeout;
41
- }
42
-
43
- // Toast 列表
44
- const toastList = ref<ToastItem[]>([]);
45
-
46
- const show = (options: { message: string; type?: ToastType; duration?: number; position?: ToastPosition; customClass?: string }) => {
47
- // 如果超过3个,则清空全部
48
- if (toastList.value.length >= 3) {
49
- clearAll();
50
- }
51
-
52
- // 添加新的 Toast
53
- const newToast: ToastItem = {
54
- message: options.message,
55
- type: options.type || 'info',
56
- duration: options.duration || 2000,
57
- position: options.position || 'top',
58
- customClass: options.customClass || '',
59
- typeClass: `toast-${options.type || 'info'}`,
60
- isVisible: true,
61
- isLoaded: false
62
- };
63
-
64
- toastList.value.push(newToast);
65
- if (newToast.duration > 0) {
66
- newToast.timer = setTimeout(() => {
67
- removeToast(newToast);
68
- }, newToast.duration);
69
- }
70
- };
71
-
72
- const removeToast = (toast: ToastItem) => {
73
- if (!toast.timer) {
74
- return;
75
- }
76
- clearTimeout(toast.timer);
77
- toast.timer = undefined;
78
- toast.isVisible = false;
79
- console.log(JSON.stringify(toast));
80
- toastList.value = toastList.value.filter((t) => t !== toast);
81
- };
82
-
83
- const clearAll = () => {
84
- // 清除所有定时器
85
- toastList.value.forEach((toast) => {
86
- if (toast.timer) {
87
- clearTimeout(toast.timer);
88
- toast.timer = undefined;
89
- }
90
- });
91
- toastList.value = [];
92
- };
93
-
94
- onUnmounted(() => {
95
- clearAll();
96
- });
97
-
98
- defineExpose({ show, clearAll });
99
- </script>
100
-
101
- <style lang="scss">
102
- .toast-container {
103
- position: fixed;
104
- left: 0;
105
- right: 0;
106
- top: 10%;
107
- bottom: 0;
108
- display: flex;
109
- flex-direction: column;
110
- justify-content: flex-start;
111
- pointer-events: none;
112
- z-index: 9999;
113
- }
114
-
115
- .toast-content-wrapper {
116
- margin-top: 2.5%;
117
- display: flex;
118
- justify-content: center;
119
- }
120
-
121
- .toast-content {
122
- min-width: 65%;
123
- padding: 15rpx 25rpx;
124
- background-color: #000000;
125
- opacity: v-bind('config.opacity.toast');
126
- border-radius: 8rpx;
127
- color: v-bind('config.fontColor.reversal');
128
- font-size: 28rpx;
129
- display: flex;
130
- align-items: center;
131
- max-width: 70%;
132
- box-sizing: border-box;
133
- line-height: 1.5;
134
-
135
- .icon {
136
- margin-right: 16rpx;
137
- font-size: 36rpx;
138
- image {
139
- margin: auto;
140
- width: 32rpx;
141
- height: 32rpx;
142
- margin-top: 10rpx;
143
- }
144
- .text-icon {
145
- display: block;
146
- margin-top: -8rpx;
147
- }
148
- }
149
- .toast-message {
150
- margin-left: 20rpx;
151
- text-align: left;
152
- display: flex;
153
- align-items: center;
154
- }
155
-
156
- .loading-icon {
157
- width: 36rpx;
158
- height: 36rpx;
159
- margin-right: 16rpx;
160
- border: 4rpx solid #fff;
161
- border-top-color: transparent;
162
- border-radius: 50%;
163
- animation: rotating 1s linear infinite;
164
- display: flex;
165
- align-items: center;
166
- }
167
-
168
- &.toast-info {
169
- background-color: v-bind('config.backgroundColor.info');
170
- }
171
-
172
- &.toast-success {
173
- background-color: v-bind('config.backgroundColor.succeed');
174
- }
175
-
176
- &.toast-error {
177
- background-color: v-bind('config.backgroundColor.delete');
178
- }
179
-
180
- &.toast-warn {
181
- background-color: v-bind('config.backgroundColor.warn');
182
- }
183
-
184
- &.toast-loading {
185
- background-color: #000000;
186
- opacity: v-bind('config.opacity.toast');
187
- }
188
- }
189
-
190
- @keyframes rotating {
191
- from {
192
- transform: rotate(0deg);
193
- }
194
- to {
195
- transform: rotate(360deg);
196
- }
197
- }
198
- </style>
@@ -1,296 +0,0 @@
1
- <template>
2
- <button @click="handleFileChange" :class="['upload-button', { uploading: uploading }]" :disabled="disabled">
3
- <template v-if="!uploading && !fileSelected">
4
- <slot>
5
- <view class="upload-icon">
6
- <text class="icon-upload"></text>
7
- </view>
8
- <view class="upload-text">
9
- {{ buttonText }}
10
- </view>
11
- </slot>
12
- </template>
13
- <template v-else>
14
- <view v-if="uploading" class="upload-progress">
15
- <slot name="loading" :progress="progress">
16
- <view class="progress-bar">
17
- <view class="progress-bar-filled" :style="{ width: `${progress}%` }"></view>
18
- </view>
19
- <view class="progress-text">{{ progress }}%</view>
20
- </slot>
21
- </view>
22
- <view v-else-if="fileSelected" class="selected-file">
23
- <slot name="selected-file" :file="selectedFile" :file-type="fileFormat" :file-size="fileSize">
24
- <view class="file-name">{{ selectedFile.name }}</view>
25
- <view class="file-info">{{ fileFormat }} {{ fileSize }}</view>
26
- </slot>
27
- </view>
28
- </template>
29
- </button>
30
- </template>
31
-
32
- <script setup lang="ts">
33
- import { ref, computed, inject, watchEffect } from 'vue';
34
-
35
- const props = defineProps({
36
- buttonText: {
37
- type: String,
38
- default: '上传文件'
39
- },
40
- accept: {
41
- type: String,
42
- default: '*'
43
- },
44
- disabled: {
45
- type: Boolean,
46
- default: false
47
- },
48
- uploadUrl: {
49
- type: String,
50
- required: true
51
- },
52
- toKen: {
53
- type: String,
54
- default: null
55
- },
56
- requestMode: {
57
- type: String,
58
- default: 'POST'
59
- }
60
- });
61
-
62
- const emit = defineEmits(['success', 'error', 'cancel']);
63
-
64
- const config = inject<any>('config');
65
- const selectedFile = ref(null);
66
- const progress = ref<number>(0);
67
- const uploading = ref<boolean>(false);
68
- const fileSelected = ref<boolean>(false);
69
-
70
- const fileFormat = computed(() => {
71
- if (selectedFile.value) {
72
- const name = selectedFile.value.name;
73
- const extension = name.split('.').pop().toLowerCase(); // 获取文件扩展名
74
-
75
- if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(extension)) {
76
- return '图片';
77
- }
78
- if (extension === 'pdf') {
79
- return 'PDF';
80
- }
81
- return '文件';
82
- }
83
- return '';
84
- });
85
-
86
- const fileSize = computed(() => {
87
- if (selectedFile.value) {
88
- const size = selectedFile.value.size;
89
- if (size < 1024) return `${size} B`;
90
- if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;
91
- return `${(size / 1024 / 1024).toFixed(2)} MB`;
92
- }
93
- return '';
94
- });
95
-
96
- watchEffect(() => {
97
- if (props.disabled) {
98
- uploading.value = false;
99
- }
100
- });
101
- const handleFileChange = () => {
102
- if (typeof uni !== 'undefined') {
103
- // 使用 uni.chooseImage 选择图片
104
- uni.chooseImage({
105
- count: 1,
106
- sizeType: ['original', 'compressed'],
107
- sourceType: ['album', 'camera'],
108
- success: (res) => {
109
- const tempFilePath = res.tempFilePaths[0];
110
- selectedFile.value = { path: tempFilePath, name: tempFilePath.split('/').pop() };
111
- fileSelected.value = true;
112
- uploadFile(tempFilePath); // 直接传递文件路径
113
- },
114
- fail: (err) => {
115
- console.error('选择图片失败:', err);
116
- }
117
- });
118
- } else {
119
- // 浏览器环境使用 HTML File API
120
- const input = document.createElement('input');
121
- input.type = 'file';
122
- input.accept = props.accept;
123
- input.onchange = (e) => {
124
- const files = e.target.files;
125
- if (files && files.length > 0) {
126
- selectedFile.value = files[0];
127
- fileSelected.value = true;
128
- uploadFile(files[0]);
129
- }
130
- };
131
- input.click();
132
- }
133
- };
134
-
135
- const uploadFile = (file) => {
136
- if (!file) return;
137
- uploading.value = true;
138
- progress.value = 0;
139
-
140
- if (typeof uni !== 'undefined') {
141
- // uni-app 环境
142
- const uploadTask = uni.uploadFile({
143
- url: props.uploadUrl,
144
- filePath: file,
145
- name: 'file',
146
- header: props.toKen
147
- ? {
148
- Authorization: `Bearer ${props.toKen}`
149
- }
150
- : {},
151
- formData: {},
152
- timeout: 60000, // 设置超时时间为60秒
153
- success: (res) => {
154
- emit('success', JSON.parse(res.data));
155
- },
156
- fail: (err) => {
157
- console.error('上传失败:', err);
158
- emit('error', err);
159
- },
160
- complete: () => {
161
- uploading.value = false;
162
- }
163
- });
164
-
165
- // 监听上传进度
166
- uploadTask.onProgressUpdate((res) => {
167
- progress.value = res.progress;
168
- });
169
- } else {
170
- // 浏览器环境
171
- const formData = new FormData();
172
- formData.append('file', file);
173
-
174
- const xhr = new XMLHttpRequest();
175
- xhr.open(props.requestMode, props.uploadUrl, true);
176
- if (props.toKen) {
177
- xhr.setRequestHeader('Authorization', `Bearer ${props.toKen}`);
178
- }
179
-
180
- xhr.upload.onprogress = (event) => {
181
- if (event.lengthComputable) {
182
- progress.value = (event.loaded / event.total) * 100;
183
- }
184
- };
185
-
186
- xhr.onload = () => {
187
- if (xhr.status >= 200 && xhr.status < 300) {
188
- emit('success', JSON.parse(xhr.responseText));
189
- } else {
190
- console.error('上传失败:', xhr.statusText);
191
- emit('error', { errMsg: '上传失败' });
192
- }
193
- uploading.value = false;
194
- };
195
-
196
- xhr.onerror = () => {
197
- console.error('上传出错:', xhr.statusText);
198
- emit('error', { errMsg: '上传出错' });
199
- uploading.value = false;
200
- };
201
-
202
- xhr.send(formData);
203
- }
204
- };
205
- </script>
206
-
207
- <style lang="scss" scoped>
208
- .upload-button {
209
- position: relative;
210
- display: flex;
211
- flex-direction: column;
212
- align-items: center;
213
- justify-content: center;
214
- width: 100%;
215
- min-height: 140rpx;
216
- border: 4rpx dashed;
217
- border-color: v-bind('config.border.color');
218
- border-radius: v-bind('config.borderRadius.semicircle');
219
- background-color: v-bind('config.VUploadFileButton.backgroundColor');
220
- color: v-bind('config.fontColor.mainText');
221
- cursor: pointer;
222
- transition: all 0.3s;
223
-
224
- &:hover {
225
- border-color: v-bind('config.border.default');
226
- background-color: v-bind('config.VUploadFileButton.backgroundColor');
227
- color: v-bind('config.fontColor.default');
228
- }
229
-
230
- &:disabled {
231
- cursor: not-allowed;
232
- opacity: 0.6;
233
- }
234
-
235
- &.uploading {
236
- border-color: v-bind('config.border.default');
237
- background-color: v-bind('config.VUploadFileButton.backgroundColor');
238
- color: v-bind('config.fontColor.default');
239
- }
240
-
241
- .upload-icon {
242
- margin-bottom: 8rpx;
243
- .icon-upload {
244
- font-size: v-bind('config.fontSize.mediumText');
245
- }
246
- }
247
-
248
- .upload-text {
249
- font-size: v-bind('config.fontSize.mediumText');
250
- }
251
-
252
- .upload-progress {
253
- width: 100%;
254
- text-align: center;
255
- }
256
-
257
- .progress-bar {
258
- width: 100%;
259
- height: 16rpx;
260
- background-color: v-bind('config.VUploadFileButton.backgroundColor');
261
- border-radius: 4rpx;
262
- overflow: hidden;
263
- margin-bottom: 8rpx;
264
- }
265
-
266
- .progress-bar-filled {
267
- height: 100%;
268
- background-color: v-bind('config.backgroundColor.default');
269
- width: 0;
270
- transition: width 0.3s;
271
- }
272
-
273
- .progress-text {
274
- font-size: v-bind('config.fontSize.mediumText');
275
- color: v-bind('config.fontColor.default');
276
- }
277
-
278
- .selected-file {
279
- width: 100%;
280
- text-align: center;
281
- }
282
-
283
- .file-name {
284
- font-size: v-bind('config.fontSize.mediumText');
285
- margin-bottom: 6rpx;
286
- white-space: nowrap;
287
- overflow: hidden;
288
- text-overflow: ellipsis;
289
- }
290
-
291
- .file-info {
292
- font-size: v-bind('config.fontSize.mediumText');
293
- color: v-bind('config.fontColor.info');
294
- }
295
- }
296
- </style>
@@ -1,175 +0,0 @@
1
- <template>
2
- <view class="v-video-container">
3
- <video
4
- :id="videoId"
5
- :src="videoContext.src"
6
- :poster="poster"
7
- :autoplay="autoplay"
8
- :controls="controls"
9
- :loop="loop"
10
- :muted="muted"
11
- :object-fit="objectFit"
12
- :class="videoClass"
13
- :style="videoStyle"
14
- @play="handlePlay"
15
- @pause="handlePause"
16
- @ended="handleEnded"
17
- @click="handleVideoClick"
18
- @touchstart="handleTouchStart"
19
- @touchmove="handleTouchMove"
20
- @touchend="handleTouchEnd"
21
- :error="handleError"
22
- style="width: 100%; height: 100%"
23
- ></video>
24
- <!-- 加载插槽 -->
25
- <slot name="loading" v-if="isLoading" />
26
-
27
- <!-- 错误插槽 -->
28
- <slot name="error" v-if="isError" />
29
-
30
- <!-- 新增:暂停插槽(非 loading / 非 error / 已暂停 / 未结束) -->
31
- <slot name="paused" v-if="!isLoading && !isError && isPaused && !hasEnded" />
32
- </view>
33
- </template>
34
-
35
- <script setup lang="ts">
36
- import { ref, onMounted, watch, reactive } from 'vue';
37
-
38
- interface Props {
39
- src: string;
40
- poster?: string;
41
- autoplay?: boolean;
42
- controls?: boolean;
43
- loop?: boolean;
44
- muted?: boolean;
45
- objectFit?: string;
46
- width?: string;
47
- height?: string;
48
- videoClass?: string;
49
- videoStyle?: string;
50
- }
51
-
52
- const props = withDefaults(defineProps<Props>(), {
53
- poster: '',
54
- autoplay: false,
55
- controls: true,
56
- loop: false,
57
- muted: false,
58
- objectFit: 'contain',
59
- width: '100%',
60
- height: '100%',
61
- videoClass: '',
62
- videoStyle: ''
63
- });
64
-
65
- const emit = defineEmits(['play', 'pause', 'ended', 'error']);
66
-
67
- const videoId = ref('video_' + Date.now());
68
- const isLoading = ref(true);
69
- const isError = ref(false);
70
- const videoContext = reactive({
71
- src: props.src
72
- });
73
- const videoElement = ref(null);
74
- const touchStartX = ref(0);
75
- const touchStartTime = ref(0);
76
- const isLongPress = ref(false);
77
- const clickNumber = ref(0);
78
- const isPaused = ref(true);
79
- const hasEnded = ref(false);
80
-
81
- onMounted(() => {
82
- videoElement.value = uni.createVideoContext(videoId.value);
83
- });
84
-
85
- watch(
86
- () => props.src,
87
- (newValue) => {
88
- isLoading.value = true;
89
- if (newValue) {
90
- videoContext.src = newValue;
91
- }
92
- },
93
- { immediate: true }
94
- );
95
- watch(
96
- () => props.src,
97
- (newValue) => {
98
- isLoading.value = true;
99
- if (newValue) {
100
- videoContext.src = newValue;
101
- }
102
- },
103
- { immediate: true }
104
- );
105
-
106
- const handlePlay = () => {
107
- isLoading.value = false;
108
- isError.value = false;
109
- isPaused.value = false;
110
- hasEnded.value = false;
111
- emit('play');
112
- };
113
-
114
- const handlePause = () => {
115
- isPaused.value = true;
116
- emit('pause');
117
- };
118
-
119
- const handleEnded = () => {
120
- hasEnded.value = true;
121
- isPaused.value = true;
122
- emit('ended');
123
- };
124
-
125
- const handleError = (event: Event) => {
126
- isLoading.value = false;
127
- isError.value = true;
128
- emit('error', event);
129
- };
130
-
131
- const handleVideoClick = () => {
132
- if (!props.controls && videoElement.value) {
133
- clickNumber.value++;
134
- if (clickNumber.value % 2 == 0) {
135
- videoElement.value.play();
136
- } else {
137
- videoElement.value.pause();
138
- }
139
- }
140
- };
141
-
142
- const handleTouchStart = (event) => {
143
- touchStartX.value = event.touches[0].clientX;
144
- touchStartTime.value = Date.now();
145
- setTimeout(() => {
146
- if (Date.now() - touchStartTime.value > 500 && !isLongPress.value) {
147
- isLongPress.value = true;
148
- }
149
- }, 500);
150
- };
151
-
152
- const handleTouchMove = (event) => {
153
- if (!props.controls && isLongPress.value && videoElement.value) {
154
- const touchMoveX = event.touches[0].clientX;
155
- const videoWidth = videoElement.value.videoWidth;
156
- const seekTime = videoElement.value.currentTime;
157
- const duration = videoElement.value.duration;
158
- const moveDistance = touchMoveX - touchStartX.value;
159
- const seekPercentage = moveDistance / videoWidth;
160
- const newTime = Math.max(0, Math.min(duration, seekTime + seekPercentage * duration));
161
- videoElement.value.currentTime = newTime;
162
- }
163
- };
164
-
165
- const handleTouchEnd = () => {
166
- isLongPress.value = false;
167
- };
168
- </script>
169
-
170
- <style scoped>
171
- .v-video-container {
172
- width: 100%;
173
- height: 100%;
174
- }
175
- </style>