v-uni-app-ui 1.0.0 → 1.0.4

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 (86) hide show
  1. package/README.md +147 -0
  2. package/components/config/css/basic.scss +19 -0
  3. package/components/config/interface/basic-type.js +16 -0
  4. package/components/config/interface/components-interface.ts +0 -0
  5. package/components/config/interface/monitor/components/input-monitor.js +0 -0
  6. package/components/config/interface/monitor/property-monitor.ts +136 -0
  7. package/components/config/interface/props/basic-props.ts +88 -0
  8. package/components/config/interface/props/components/button-props.ts +85 -0
  9. package/components/config/interface/props/components/input-props.ts +69 -0
  10. package/components/config/interface/props/props-tools.ts +64 -0
  11. package/components/config/style/basic.js +346 -0
  12. package/components/config/style/component-registry.js +142 -0
  13. package/components/config/style/components/button-style.js +160 -0
  14. package/components/config/style/components/input-style.js +98 -0
  15. package/components/config/style/components-style.js +622 -0
  16. package/components/config/style/property-mapper.js +377 -0
  17. package/components/config/style/pseudo-processor.js +213 -0
  18. package/components/config.js +123 -0
  19. package/components/icon/iconfont.css +87 -0
  20. package/components/icon/iconfont.js +1 -0
  21. package/components/icon/iconfont.json +135 -0
  22. package/components/icon/iconfont.ttf +0 -0
  23. package/components/icon/iconfont.woff +0 -0
  24. package/components/icon/iconfont.woff2 +0 -0
  25. package/components/layout/v-card/v-card.vue +108 -0
  26. package/components/layout/v-grid/v-grid.vue +162 -0
  27. package/components/layout/v-icon-grid/v-icon-grid.vue +195 -0
  28. package/components/layout/v-infinite-scroll/v-infinite-scroll.vue +172 -0
  29. package/components/layout/v-list/v-list.vue +43 -0
  30. package/components/layout/v-row/v-row.vue +142 -0
  31. package/components/layout/v-waterfall/v-waterfall.vue +79 -0
  32. package/components/model/compound/v-checkbox-group/v-checkbox-group.vue +96 -0
  33. package/components/model/compound/v-console/v-console.js +20 -0
  34. package/components/model/compound/v-console/v-console.vue +299 -0
  35. package/components/model/compound/v-date-time/v-date-time.vue +261 -0
  36. package/components/model/compound/v-dialog/v-dialog.vue +178 -0
  37. package/components/model/compound/v-drum-select-picker/v-drum-select-picker.vue +83 -0
  38. package/components/model/compound/v-form/v-form.vue +226 -0
  39. package/components/model/compound/v-form-item/v-form-item.vue +255 -0
  40. package/components/model/compound/v-image/v-image.vue +357 -0
  41. package/components/model/compound/v-input-desensitize/v-input-desensitize.vue +101 -0
  42. package/components/model/compound/v-page/v-page.vue +11 -0
  43. package/components/model/compound/v-pages/v-pages.vue +141 -0
  44. package/components/model/compound/v-picker-list/v-picker-list.vue +109 -0
  45. package/components/model/compound/v-popup/v-popup.vue +151 -0
  46. package/components/model/compound/v-radio-group/v-radio-group.vue +86 -0
  47. package/components/model/compound/v-select-picker/v-select-picker.vue +202 -0
  48. package/components/model/compound/v-series-picker-list/v-series-picker-list.vue +221 -0
  49. package/components/model/compound/v-series-select-picker/v-series-select-picker.vue +203 -0
  50. package/components/model/compound/v-switch/v-switch.vue +136 -0
  51. package/components/model/compound/v-tabs-page/v-tabs-page.vue +138 -0
  52. package/components/model/native/v-badge/v-badge.vue +143 -0
  53. package/components/model/native/v-button/v-button.vue +81 -0
  54. package/components/model/native/v-carousel/v-carousel.vue +138 -0
  55. package/components/model/native/v-checkbox/v-checkbox.vue +215 -0
  56. package/components/model/native/v-collapse/v-collapse.vue +190 -0
  57. package/components/model/native/v-header-navigation-bar/v-header-navigation-bar.vue +92 -0
  58. package/components/model/native/v-input/v-input.vue +163 -0
  59. package/components/model/native/v-input-code/v-input-code.vue +146 -0
  60. package/components/model/native/v-loading/v-loading.vue +206 -0
  61. package/components/model/native/v-menu/v-menu.vue +222 -0
  62. package/components/model/native/v-menu-slide/v-menu-slide.vue +364 -0
  63. package/components/model/native/v-min-loading/v-min-loading.vue +80 -0
  64. package/components/model/native/v-null/v-null.vue +97 -0
  65. package/components/model/native/v-overlay/v-overlay.vue +96 -0
  66. package/components/model/native/v-pull-up-refresh/v-pull-up-refresh.vue +157 -0
  67. package/components/model/native/v-radio/v-radio.vue +138 -0
  68. package/components/model/native/v-scroll-list/v-scroll-list.vue +169 -0
  69. package/components/model/native/v-steps/v-steps.vue +253 -0
  70. package/components/model/native/v-table/v-table.vue +203 -0
  71. package/components/model/native/v-tabs/v-tabs.vue +235 -0
  72. package/components/model/native/v-tag/v-tag.vue +206 -0
  73. package/components/model/native/v-text/v-text.vue +187 -0
  74. package/components/model/native/v-textarea/v-textarea.vue +178 -0
  75. package/components/model/native/v-title/v-title.vue +91 -0
  76. package/components/model/native/v-toast/info.png +0 -0
  77. package/components/model/native/v-toast/success.png +0 -0
  78. package/components/model/native/v-toast/v-toast.vue +198 -0
  79. package/components/model/native/v-toast/warn.png +0 -0
  80. package/components/model/native/v-upload-file-button/v-upload-file-button.vue +296 -0
  81. package/components/model/native/v-video/v-video.vue +175 -0
  82. package/components/model/native/v-window/v-window.vue +158 -0
  83. package/components/utils/event-modifiers.ts +139 -0
  84. package/components/utils/validator.ts +451 -0
  85. package/index.js +372 -0
  86. package/package.json +25 -93
@@ -0,0 +1,296 @@
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>
@@ -0,0 +1,175 @@
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>
@@ -0,0 +1,158 @@
1
+ <template>
2
+ <view v-show="show" :class="['v-window', `v-window--size-${size}`, { 'v-window--fullscreen': fullscreen }]" :style="windowStyle">
3
+ <view class="v-window-header" v-if="title">
4
+ <view class="v-window-title">{{ title }}</view>
5
+ <view class="v-window-actions" v-if="showActions">
6
+ <button class="v-window-action" @click="minimize">
7
+ <text class="icon-minimize">-</text>
8
+ </button>
9
+ <button class="v-window-action" @click="maximize">
10
+ <text class="icon-maximize">□</text>
11
+ </button>
12
+ <button class="v-window-action" @click="close">
13
+ <text class="icon-close">×</text>
14
+ </button>
15
+ </view>
16
+ </view>
17
+ <view class="v-window-content">
18
+ <slot></slot>
19
+ </view>
20
+ </view>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { ref, computed,inject,watch } from 'vue';
25
+
26
+ const props = defineProps({
27
+ title: {
28
+ type: String,
29
+ default: ''
30
+ },
31
+ fullscreen: {
32
+ type: Boolean,
33
+ default: false
34
+ },
35
+ showActions:{
36
+ type:Boolean,
37
+ default:true
38
+ },
39
+ show:{
40
+ type:Boolean,
41
+ default:true
42
+ }
43
+ });
44
+
45
+ const emit = defineEmits(['update:show','update:showActions','update:fullscreen','close', 'minimize', 'maximize']);
46
+ const config = inject<any>('config');
47
+ const windowStyle = computed(() => {
48
+ return {
49
+ width: props.fullscreen ? '100%' : undefined,
50
+ height: props.fullscreen ? '100%' : undefined,
51
+ '--window-title-color': '#333',
52
+ '--window-content-bg': '#fff',
53
+ '--window-border-color': '#e0e0e0'
54
+ };
55
+ });
56
+
57
+ const showActions = ref(props.showActions);
58
+ const show = ref(props.show);
59
+ const fullscreen = ref(props.fullscreen);
60
+
61
+ watch(()=>props.show,(newValue)=>{
62
+ show.value = newValue;
63
+ },{
64
+ immediate:true
65
+ })
66
+ watch(()=>props.showActions,(newValue)=>{
67
+ showActions.value = newValue;
68
+ },{
69
+ immediate:true
70
+ })
71
+ watch(()=>props.fullscreen,(newValue)=>{
72
+ fullscreen.value = newValue;
73
+ },{
74
+ immediate:true
75
+ })
76
+
77
+
78
+ const close = () => {
79
+ show.value = false;
80
+ emit('update:show',false)
81
+ emit('close');
82
+ };
83
+
84
+ const minimize = () => {
85
+ fullscreen.value = false;
86
+ emit('update:fullscreen',false);
87
+ emit('minimize');
88
+ };
89
+
90
+ const maximize = () => {
91
+ fullscreen.value = true;
92
+ emit('update:fullscreen',true);
93
+ emit('maximize');
94
+ };
95
+ </script>
96
+
97
+ <style lang="scss" scoped>
98
+ .v-window {
99
+ width: 80%;
100
+ height: 100%;
101
+ display: flex;
102
+ flex-direction: column;
103
+ border: 1rpx solid var(--window-border-color);
104
+ border-radius: 4rpx;
105
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
106
+ background-color: var(--window-content-bg);
107
+ overflow: hidden;
108
+ margin: auto;
109
+
110
+ &--fullscreen {
111
+ width: 100%;
112
+ height: 100%;
113
+ position: fixed;
114
+ top: 0;
115
+ left: 0;
116
+ right: 0;
117
+ bottom: 0;
118
+ z-index: 1000;
119
+ }
120
+
121
+ .v-window-header {
122
+ display: flex;
123
+ justify-content: space-between;
124
+ align-items: center;
125
+ padding: 8rpx 16rpx;
126
+ background-color: var(--window-content-bg);
127
+ border-bottom: 1rpx solid var(--window-border-color);
128
+
129
+ .v-window-title {
130
+ font-size: v-bind("config.fontSize.mediumTitle");
131
+ font-weight: bold;
132
+ color: var(--window-title-color);
133
+ }
134
+
135
+ .v-window-actions {
136
+ display: flex;
137
+ }
138
+
139
+ .v-window-action {
140
+ background: none;
141
+ border: none;
142
+ cursor: pointer;
143
+ font-size: v-bind("config.fontSize.smallText");
144
+ margin-left: 8rpx;
145
+
146
+ &:hover {
147
+ background-color: #f5f5f5;
148
+ }
149
+ }
150
+ }
151
+
152
+ .v-window-content {
153
+ flex: 1;
154
+ padding: 16rpx;
155
+ overflow: auto;
156
+ }
157
+ }
158
+ </style>