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,187 @@
1
+ <template>
2
+ <!-- <text :class="['v-text', `v-text--${type}`, `v-text--${size}`]" :style="textStyle">
3
+ <slot v-if="!processedText">{{ processedText }}</slot>
4
+ <view v-html="processedText">
5
+
6
+ </view>
7
+ </text> -->
8
+ <view :class="['v-text', `v-text--${type}`, `v-text--${size}`]" :style="textStyle">
9
+ <slot v-if="!processedText">{{ processedText }}</slot>
10
+ <rich-text :nodes="processedText"></rich-text>
11
+ </view>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { computed, inject } from 'vue';
16
+
17
+ const props = defineProps({
18
+ text: {
19
+ type: String,
20
+ default: ''
21
+ },
22
+ type: {
23
+ type: String,
24
+ default: 'default',
25
+ validator: (value: string) => {
26
+ return ['default', 'primary', 'succeed', 'warning', 'delete', 'info'].includes(value);
27
+ }
28
+ },
29
+ size: {
30
+ type: String,
31
+ default: 'medium',
32
+ validator: (value: string) => {
33
+ return ['small', 'medium', 'large'].includes(value);
34
+ }
35
+ },
36
+ color: {
37
+ type: String,
38
+ default: ''
39
+ },
40
+ lineHeight: {
41
+ type: [Number, String],
42
+ default: 1.5
43
+ },
44
+ mode: {
45
+ type: String,
46
+ default: '',
47
+ validator: (value: string) => {
48
+ return ['', 'timestamp', 'amount'].includes(value);
49
+ }
50
+ },
51
+ encrypt: {
52
+ type: String,
53
+ default: '',
54
+ validator: (value: string) => {
55
+ return ['', 'phone', 'name'].includes(value);
56
+ }
57
+ },
58
+ lines: {
59
+ type: [Number, String],
60
+ default: 1
61
+ },
62
+ format: {
63
+ type: String,
64
+ default: ''
65
+ },
66
+ keyword: { type: String, default: '' },
67
+ keywordColor: { type: String, default: '#ff5e5e' }
68
+ });
69
+
70
+ const config = inject<any>('config');
71
+
72
+ const processedText = computed(() => {
73
+ let result = props.text;
74
+
75
+ if (props.mode === 'timestamp' && props.format) {
76
+ // 尝试将文本转换为日期对象
77
+ let date: Date;
78
+ if (/^\d+$/.test(props.text)) {
79
+ // 如果是时间戳(数字)
80
+ date = new Date(parseInt(props.text));
81
+ } else {
82
+ // 如果是日期字符串
83
+ date = new Date(props.text);
84
+ }
85
+
86
+ if (!isNaN(date.getTime())) {
87
+ const year = date.getFullYear();
88
+ const month = date.getMonth() + 1;
89
+ const day = date.getDate();
90
+ const hours = date.getHours();
91
+ const minutes = date.getMinutes();
92
+ const seconds = date.getSeconds();
93
+
94
+ result = props.format
95
+ .replace('yyyy', year.toString())
96
+ .replace('MM', month.toString().padStart(2, '0'))
97
+ .replace('dd', day.toString().padStart(2, '0'))
98
+ .replace('HH', hours.toString().padStart(2, '0'))
99
+ .replace('mm', minutes.toString().padStart(2, '0'))
100
+ .replace('ss', seconds.toString().padStart(2, '0'));
101
+ }
102
+ } else if (props.mode === 'amount' && props.format) {
103
+ const amount = parseFloat(props.text);
104
+ if (!isNaN(amount)) {
105
+ if (props.format === '$') {
106
+ result = `$${amount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
107
+ } else if (props.format === '¥') {
108
+ result = `¥${amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
109
+ }
110
+ }
111
+ }
112
+
113
+ if (props.encrypt === 'phone' && result.length === 11) {
114
+ result = result.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
115
+ } else if (props.encrypt === 'name' && result.length > 1) {
116
+ const firstChar = result.charAt(0);
117
+ result = firstChar + '****' + result.slice(-1);
118
+ }
119
+
120
+ // 关键字高亮(最后一步)
121
+ if (props.keyword) {
122
+ const reg = new RegExp(`(${props.keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
123
+ // 先转义再替换
124
+ const escape = (str: string) => str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;');
125
+ result = escape(result).replace(reg, `<span style="color:${props.keywordColor}" class="v-text__keyword">$1</span>`);
126
+ }
127
+
128
+ return result;
129
+ });
130
+
131
+ const textStyle = computed(() => {
132
+ const style: Record<string, string | number> = {};
133
+ if (props.color) style.color = props.color;
134
+ if (props.lineHeight) style.lineHeight = typeof props.lineHeight === 'number' ? `${props.lineHeight}` : props.lineHeight;
135
+ if (props.lines) {
136
+ style.webkitLineClamp = typeof props.lines === 'number' ? `${props.lines}` : props.lines;
137
+ style.overflow = 'hidden';
138
+ style.textOverflow = 'ellipsis';
139
+ style.display = '-webkit-box';
140
+ style.webkitBoxOrient = 'vertical';
141
+ }
142
+ return style;
143
+ });
144
+ </script>
145
+
146
+ <style lang="scss" scoped>
147
+ .v-text {
148
+ font-size: v-bind('config.fontSize.mediumText');
149
+ line-height: 1.5;
150
+
151
+ &--default {
152
+ color: v-bind('config.fontColor.text');
153
+ }
154
+
155
+ &--primary {
156
+ color: v-bind('config.fontColor.default');
157
+ }
158
+
159
+ &--succeed {
160
+ color: v-bind('config.fontColor.succeed');
161
+ }
162
+
163
+ &--warn {
164
+ color: v-bind('config.fontColor.warn');
165
+ }
166
+
167
+ &--delete {
168
+ color: v-bind('config.fontColor.delete');
169
+ }
170
+
171
+ &--info {
172
+ color: v-bind('config.fontColor.info');
173
+ }
174
+
175
+ &--small {
176
+ font-size: v-bind('config.fontSize.smallText');
177
+ }
178
+
179
+ &--medium {
180
+ font-size: v-bind('config.fontSize.mediumText');
181
+ }
182
+
183
+ &--large {
184
+ font-size: v-bind('config.fontSize.largeText');
185
+ }
186
+ }
187
+ </style>
@@ -0,0 +1,178 @@
1
+ <template>
2
+ <view class="v-textarea-container">
3
+ <textarea
4
+ :value="inputValue"
5
+ :class="['v-textarea', { 'v-textarea--disabled': disabled, 'v-textarea--focused': focused }, `v-textarea--border--${boderModel}`]"
6
+ :placeholder="placeholder"
7
+ :maxlength="maxlength || undefined"
8
+ :disabled="disabled"
9
+ :auto-height="autoHeight"
10
+ @input="handleInput"
11
+ @focus="handleFocus"
12
+ @blur="handleBlur"
13
+ @confirm="handleConfirm"
14
+ ></textarea>
15
+ <view v-if="showCounter" class="v-textarea-counter">{{ currentLength }}/{{ maxlength }}</view>
16
+ </view>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ import { ref, watch,inject } from 'vue';
21
+
22
+ /**
23
+ * v-textarea 文本框
24
+ * value 双向绑定
25
+ * placeholder 提示词
26
+ * maxlength 最大输入限制
27
+ * disabled 是否禁用 默认值:false 可选值:true禁用 false不禁用
28
+ * autoHeight 是否自动延长高度 默认值:true 可选值:true禁用 false不禁用
29
+ * boderModel 边框模式 默认值:all 可选值:all普通边框 nont无边框模式 bottom底部边框模式 top上边框模式 left左边框模式 right右边框模式 ends左右两端边框模式 up-down上下边框模式
30
+ * showCounter 是否显示字数
31
+ * inputTextPosition 输入框文本位置 默认值:left 可选值:left居左 right居右 center居中
32
+ * 相关事件:focus、blur、confirm
33
+ */
34
+ const props = defineProps({
35
+ value: {
36
+ type: String,
37
+ default: '',
38
+ required: true
39
+ },
40
+ placeholder: {
41
+ type: String,
42
+ default: ''
43
+ },
44
+ maxlength: {
45
+ type: Number,
46
+ default: null
47
+ },
48
+ disabled: {
49
+ type: Boolean,
50
+ default: false
51
+ },
52
+ autoHeight: {
53
+ type: Boolean,
54
+ default: true
55
+ },
56
+ boderModel: {
57
+ type: String,
58
+ default: 'all'
59
+ },
60
+ showCounter: {
61
+ type: Boolean,
62
+ default: false
63
+ },
64
+ inputTextPosition:{
65
+ type:String,
66
+ default:"left"
67
+ }
68
+ });
69
+
70
+ const emit = defineEmits(['update:value', 'input', 'focus', 'blur', 'confirm']);
71
+
72
+ const config = inject<any>('config');
73
+ const inputValue = ref(props.value);
74
+ const focused = ref(false);
75
+ const currentLength = ref(props.value.length);
76
+
77
+ watch(
78
+ () => props.value,
79
+ (newVal) => {
80
+ inputValue.value = newVal;
81
+ currentLength.value = newVal.length;
82
+ }
83
+ );
84
+
85
+ const handleInput = (e: any) => {
86
+ const value = e.detail.value;
87
+ inputValue.value = value;
88
+ currentLength.value = value.length;
89
+ emit('update:value', value);
90
+ emit('input', value);
91
+ };
92
+
93
+ const handleFocus = (e: any) => {
94
+ focused.value = true;
95
+ emit('focus', e);
96
+ };
97
+
98
+ const handleBlur = (e: any) => {
99
+ focused.value = false;
100
+ emit('blur', e);
101
+ };
102
+
103
+ const handleConfirm = (e: any) => {
104
+ emit('confirm', e);
105
+ };
106
+ </script>
107
+
108
+ <style lang="scss" scoped>
109
+ .v-textarea-container {
110
+ position: relative;
111
+ }
112
+
113
+ .v-textarea {
114
+ width: 100%;
115
+ padding: 24rpx;
116
+ box-sizing: border-box;
117
+ background-color: #fff;
118
+ font-size: v-bind("config.fontSize.mediumText");
119
+ line-height: 1.5;
120
+ transition: border-color 0.3s;
121
+ resize: none;
122
+ text-align: v-bind("props.inputTextPosition");
123
+
124
+ &--border--all {
125
+ border-radius: 6rpx;
126
+ border: 1px solid v-bind("config.border.color");
127
+
128
+ &.v-textarea--focused {
129
+ border-color: v-bind("config.border.default");
130
+ box-shadow: v-bind("config.VInput.boxShadow");
131
+ }
132
+ }
133
+
134
+ &--border--none {
135
+ border: none;
136
+ }
137
+
138
+ &--border--bottom {
139
+ border-bottom: 1rpx solid v-bind("config.border.color");
140
+ }
141
+
142
+ &--border--top {
143
+ border-top: 1rpx solid v-bind("config.border.color");
144
+ }
145
+
146
+ &--border--left {
147
+ border-left: 1rpx solid v-bind("config.border.color");
148
+ }
149
+
150
+ &--border--right {
151
+ border-right: 1rpx solid v-bind("config.border.color");
152
+ }
153
+
154
+ &--border--ends {
155
+ border-left: 1rpx solid v-bind("config.border.color");
156
+ border-right: 1rpx solid v-bind("config.border.color");
157
+ }
158
+
159
+ &--border--up-down {
160
+ border-top: 1rpx solid v-bind("config.border.color");
161
+ border-bottom: 1rpx solid v-bind("config.border.color");
162
+ }
163
+
164
+ &--disabled{
165
+ opacity: v-bind('config.opacity.disabled');
166
+ background-color: v-bind('config.backgroundColor.disabled');
167
+ color: v-bind('config.fontColor.text');
168
+ }
169
+ }
170
+
171
+ .v-textarea-counter {
172
+ position: absolute;
173
+ bottom: 5rpx;
174
+ right: 5rpx;
175
+ font-size: v-bind("config.fontSize.mediumText");
176
+ color: #999;
177
+ }
178
+ </style>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <view :class="['v-title', `v-title--level-${level}`]" :style="titleStyle">
3
+ <slot></slot>
4
+ </view>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import { computed,inject } from 'vue';
9
+
10
+ const props = defineProps({
11
+ level: {
12
+ type: Number,
13
+ default: 1,
14
+ validator: (val: number) => val >= 1 && val <= 6
15
+ },
16
+ fontSize: {
17
+ type: String,
18
+ default: ''
19
+ },
20
+ color: {
21
+ type: String,
22
+ default: ''
23
+ }
24
+ });
25
+
26
+ const config = inject<any>('config');
27
+ const titleStyle = computed(() => {
28
+ const style: Record<string, string> = {};
29
+ if (props.fontSize) {
30
+ style['font-size'] = props.fontSize;
31
+ }
32
+ if (props.color) {
33
+ style['color'] = props.color;
34
+ }
35
+ return style;
36
+ });
37
+ </script>
38
+
39
+ <style lang="scss" scoped>
40
+ .v-title {
41
+ font-weight: bold;
42
+
43
+ &--level-1 {
44
+ font-size: v-bind("config.fontSize.largeTitle");
45
+ color: v-bind("config.fontColor.mianTitle");
46
+ }
47
+
48
+ &--level-2 {
49
+ font-size: v-bind("config.fontSize.mediumTitle");
50
+ color: v-bind("config.fontColor.mianTitle");
51
+ }
52
+
53
+ &--level-3 {
54
+ font-size: v-bind("config.fontSize.smallTitle");
55
+ color: v-bind("config.fontColor.mianTitle");
56
+ }
57
+
58
+ &--level-4 {
59
+ font-size: v-bind("config.fontSize.largeText");
60
+ color: v-bind("config.fontColor.subTitle");
61
+ }
62
+
63
+ &--level-5 {
64
+ font-size: v-bind("config.fontSize.mediumText");
65
+ color: v-bind("config.fontColor.subTitle");
66
+ }
67
+
68
+ &--level-6 {
69
+ font-size: v-bind("config.fontSize.smallText");
70
+ color: v-bind("config.fontColor.subTitle");
71
+ }
72
+
73
+ @media (prefers-color-scheme: dark) {
74
+ &--level-1,
75
+ &--level-2,
76
+ &--level-3 {
77
+ color: #e0e0e0;
78
+ }
79
+
80
+ &--level-4,
81
+ &--level-5,
82
+ &--level-6 {
83
+ color: #b0b0b0;
84
+ }
85
+
86
+ :deep(.v-title) {
87
+ color: #e0e0e0;
88
+ }
89
+ }
90
+ }
91
+ </style>
@@ -0,0 +1,198 @@
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>