rrj-astra-ui 1.0.2

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 (38) hide show
  1. package/README.en.md +36 -0
  2. package/README.md +37 -0
  3. package/components/AuiBadge.vue +50 -0
  4. package/components/AuiBlockBox.vue +85 -0
  5. package/components/AuiButton.vue +210 -0
  6. package/components/AuiCustomerForm.vue +304 -0
  7. package/components/AuiDivider.vue +66 -0
  8. package/components/AuiFold.vue +40 -0
  9. package/components/AuiFoldItem.vue +173 -0
  10. package/components/AuiForm.vue +76 -0
  11. package/components/AuiFormItem.vue +88 -0
  12. package/components/AuiGrid.vue +26 -0
  13. package/components/AuiGridItem.vue +20 -0
  14. package/components/AuiIcon.vue +145 -0
  15. package/components/AuiImage.vue +152 -0
  16. package/components/AuiInput.vue +176 -0
  17. package/components/AuiLamp.vue +254 -0
  18. package/components/AuiLineProgress.vue +169 -0
  19. package/components/AuiList.vue +18 -0
  20. package/components/AuiListItem.vue +142 -0
  21. package/components/AuiMultiSelect.vue +303 -0
  22. package/components/AuiNoticeBar.vue +62 -0
  23. package/components/AuiNumberBox.vue +282 -0
  24. package/components/AuiPicker.vue +619 -0
  25. package/components/AuiPopup.vue +57 -0
  26. package/components/AuiSelectGroup.vue +312 -0
  27. package/components/AuiTab.vue +173 -0
  28. package/components/AuiTabItem.vue +43 -0
  29. package/components/AuiTable.vue +357 -0
  30. package/components/AuiTag.vue +112 -0
  31. package/components/AuiText.vue +81 -0
  32. package/components/AuiTextarea.vue +203 -0
  33. package/components/AuiToast.vue +96 -0
  34. package/components/AuiUpdate.vue +271 -0
  35. package/components/AuiUpload.vue +524 -0
  36. package/index.js +93 -0
  37. package/package.json +36 -0
  38. package/style.scss +30 -0
@@ -0,0 +1,203 @@
1
+ <template>
2
+ <view :class="['aui-textarea-item',`aui-theme-${theme}`]">
3
+ <view class="aui-textarea-befor">
4
+ <slot name="befor"></slot>
5
+ </view>
6
+ <view class="aui-textarea-content">
7
+ <textarea :placeholder="placeholder" v-model="textareaValue"
8
+ :class="['aui-textarea', `aui-textarea-${size}`,`aui-textarea-align-${textAlign}`]"
9
+ :readonly="readonly" :disabled="disabled" @input="handleInput"
10
+ :style="`min-height:${minHeight} !important;max-height:${maxHeight};height:100px`"
11
+ :auto-height="autoHeight" :show-confirm-bar="showConfirmBar"
12
+ :confirm-type="confirmType" :cursor-spacing="cursorSpacing"
13
+ :adjust-position="adjustPosition" :hold-keyboard="holdKeyboard"/>
14
+ <view v-if="showCount && textareaValue" class="aui-textarea-count">
15
+ {{textareaValue.length}}/{{maxlength}}
16
+ </view>
17
+ </view>
18
+ <view class="aui-textarea-after">
19
+ <slot name="after"></slot>
20
+ </view>
21
+ </view>
22
+ </template>
23
+
24
+ <script setup>
25
+ import { defineProps, defineEmits, ref, watch } from 'vue';
26
+ const __name = 'AuiTextarea';
27
+ defineOptions({ name: __name });
28
+
29
+ const props = defineProps({
30
+ placeholder: {
31
+ type: String,
32
+ default: ''
33
+ },
34
+ size: {
35
+ type: String,
36
+ default: 'normal',
37
+ validator: (value) => ['normal', 'small', 'large'].includes(value)
38
+ },
39
+ disabled: {
40
+ type: Boolean,
41
+ default: false
42
+ },
43
+ modelValue: {
44
+ type: [String, Number],
45
+ default: ''
46
+ },
47
+ theme: {
48
+ type: String,
49
+ default: 'normal',
50
+ validator: (value) => ['normal', 'flat', 'grey'].includes(value)
51
+ },
52
+ minHeight: {
53
+ type: String,
54
+ default: '50px',
55
+ },
56
+ maxHeight: {
57
+ type: String,
58
+ default: '200px',
59
+ },
60
+ textAlign: {
61
+ type: String,
62
+ default: 'left',
63
+ validator: (value) => ['left', 'center', 'right'].includes(value)
64
+ },
65
+ readonly: {
66
+ type: Boolean,
67
+ default: false,
68
+ },
69
+ maxlength: {
70
+ type: Number,
71
+ default: 200,
72
+ },
73
+ showCount: {
74
+ type: Boolean,
75
+ default: true,
76
+ },
77
+ autoHeight: {
78
+ type: Boolean,
79
+ default: false,
80
+ },
81
+ showConfirmBar: {
82
+ type: Boolean,
83
+ default: true,
84
+ },
85
+ confirmType: {
86
+ type: String,
87
+ default: 'done',
88
+ validator: (value) => ['send', 'search', 'next', 'go', 'done'].includes(value)
89
+ },
90
+ cursorSpacing: {
91
+ type: Number,
92
+ default: 50,
93
+ },
94
+ adjustPosition: {
95
+ type: Boolean,
96
+ default: true,
97
+ },
98
+ holdKeyboard: {
99
+ type: Boolean,
100
+ default: false,
101
+ }
102
+ });
103
+
104
+ const emits = defineEmits(['input', 'update:modelValue']);
105
+
106
+ const textareaValue = ref(props.modelValue);
107
+
108
+ // 监听 props.modelValue 的变化
109
+ watch(() => props.modelValue, (newValue) => {
110
+ console.log('props.modelValue 变化,新值:', newValue);
111
+ textareaValue.value = newValue;
112
+ });
113
+
114
+ const handleInput = (e) => {
115
+ let newValue;
116
+ if (e.detail && typeof e.detail.value !== 'undefined') {
117
+ newValue = e.detail.value;
118
+ } else {
119
+ newValue = e.target ? e.target.value : undefined;
120
+ }
121
+ if (newValue !== undefined) {
122
+ // 限制最大长度
123
+ if (newValue.length > props.maxlength) {
124
+ newValue = newValue.substring(0, props.maxlength);
125
+ }
126
+ textareaValue.value = newValue;
127
+ console.log('输入事件触发,新值:', newValue);
128
+ emits('input', newValue);
129
+ emits('update:modelValue', newValue);
130
+ }
131
+ };
132
+ </script>
133
+
134
+ <style scoped lang="scss">
135
+ @import '../style.scss';
136
+
137
+ .aui-textarea-item {
138
+ display: flex;
139
+ align-items: flex-start;
140
+ border: 1px solid $aui-border-color;
141
+ border-radius: 8px;
142
+ padding: 0 10px;
143
+ position: relative;
144
+ flex: 1;
145
+ }
146
+ .aui-textarea-befor {
147
+ margin-right: 10px;
148
+ margin-top: 10px;
149
+ }
150
+ .aui-textarea-after {
151
+ margin-left: 10px;
152
+ margin-top: 10px;
153
+ }
154
+ .aui-textarea-content {
155
+ flex: 1;
156
+ position: relative;
157
+ }
158
+ .aui-textarea {
159
+ font-size: $aui-font-size;
160
+ outline: none;
161
+ transition: border-color 0.3s;
162
+ width: 100%;
163
+ padding: 10px 0;
164
+ &:focus {
165
+ border-color: $aui-primary-color;
166
+ }
167
+ }
168
+ .aui-theme-flat{
169
+ background-color: #F5F5F5;
170
+ border: none;
171
+ border-radius: 10px;
172
+ }
173
+ .aui-theme-flat textarea{border:none;}
174
+ .aui-theme-grey{
175
+ background-color: #F5F5F5;
176
+ border: none;
177
+ border-radius: 10px;
178
+ }
179
+ .aui-theme-grey textarea{border:none;}
180
+ .aui-textarea-small {
181
+ font-size: 12px;
182
+ }
183
+
184
+ .aui-textarea-large {
185
+ font-size: 18px;
186
+ }
187
+ .aui-textarea-align-left {
188
+ text-align: left;
189
+ }
190
+ .aui-textarea-align-center {
191
+ text-align: center;
192
+ }
193
+ .aui-textarea-align-right {
194
+ text-align: right;
195
+ }
196
+ .aui-textarea-count {
197
+ position: absolute;
198
+ right: 0;
199
+ bottom: 5px;
200
+ font-size: 12px;
201
+ color: #999;
202
+ }
203
+ </style>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <view v-if="visible" class="toast" :class="typeClass" :style="{ bottom: position + 'px' }">
3
+ <i v-if="showIcon" :class="iconClass"></i>
4
+ <span>{{ message }}</span>
5
+ </view>
6
+ </template>
7
+
8
+ <script setup>
9
+ import { ref, onMounted, onUnmounted, computed } from 'vue';
10
+ const __name = 'AuiToast';
11
+
12
+ defineOptions({
13
+ name: __name
14
+ })
15
+
16
+ const props = defineProps({
17
+ message: {
18
+ type: String,
19
+ default: ''
20
+ },
21
+ duration: {
22
+ type: Number,
23
+ default: 2000
24
+ },
25
+ position: {
26
+ type: Number,
27
+ default: 50
28
+ },
29
+ type: {
30
+ type: String,
31
+ default: 'default',
32
+ validator: (value) => ['default', 'success', 'error', 'warning'].includes(value)
33
+ },
34
+ showIcon: {
35
+ type: Boolean,
36
+ default: true
37
+ }
38
+ });
39
+
40
+ const visible = ref(false);
41
+
42
+ const typeClass = computed(() => `toast-${props.type}`);
43
+ const iconClass = computed(() => {
44
+ switch (props.type) {
45
+ case 'success':
46
+ return 'fa fa-check-circle';
47
+ case 'error':
48
+ return 'fa fa-times-circle';
49
+ case 'warning':
50
+ return 'fa fa-exclamation-circle';
51
+ default:
52
+ return '';
53
+ }
54
+ });
55
+
56
+ const showToast = () => {
57
+ visible.value = true;
58
+ const timer = setTimeout(() => {
59
+ visible.value = false;
60
+ clearTimeout(timer);
61
+ }, props.duration);
62
+ };
63
+
64
+ onMounted(() => {
65
+ showToast();
66
+ });
67
+ </script>
68
+
69
+ <style scoped>
70
+ .toast {
71
+ position: fixed;
72
+ left: 50%;
73
+ transform: translateX(-50%);
74
+ background-color: rgba(0, 0, 0, 0.7);
75
+ color: white;
76
+ padding: 10px 20px;
77
+ border-radius: 5px;
78
+ z-index: 9999;
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 10px;
82
+ transition: opacity 0.3s ease;
83
+ }
84
+
85
+ .toast-success {
86
+ background-color: #5cb85c;
87
+ }
88
+
89
+ .toast-error {
90
+ background-color: #d9534f;
91
+ }
92
+
93
+ .toast-warning {
94
+ background-color: #f0ad4e;
95
+ }
96
+ </style>
@@ -0,0 +1,271 @@
1
+ <template>
2
+ <view :class="[`aui-update`, `aui-update-theme-${theme}`]">
3
+ <!-- 使用时间戳确保唯一性 -->
4
+ <input
5
+ :id="`aui-file-input-${timestamp}`"
6
+ type="file"
7
+ @change="handleFileChange"
8
+ :accept="accept"
9
+ :multiple="multiple"
10
+ ref="fileInput"
11
+ class="aui-update-input"
12
+ />
13
+ <view class="aui-update-data">
14
+ <view v-if="previewUrl" class="preview-container">
15
+ <template v-if="isImage">
16
+ <img :src="previewUrl" class="preview-media" alt="文件预览" />
17
+ </template>
18
+ <template v-else-if="isVideo">
19
+ <video :src="previewUrl" class="preview-media" controls />
20
+ </template>
21
+ <view class="preview-overlay">
22
+ <button @click="removeMedia" class="remove-button">
23
+ <AuiIcon name="close" size="16px" />
24
+ </button>
25
+ </view>
26
+ </view>
27
+ </view>
28
+ <label
29
+ class="aui-update-button"
30
+ :for="`aui-file-input-${timestamp}`"
31
+ @click="debugClick"
32
+ >
33
+ <view :class="[`aui-update-button-theme-${theme}`, { 'has-preview': previewUrl }]">
34
+ <slot name="button">
35
+ <div class="button-content">
36
+ <AuiIcon :name="uploadIcon" size="30px" />
37
+ <AuiText class="button-text">{{ buttonText }}</AuiText>
38
+ <AuiText v-if="fileLimit" class="file-limit">最大 {{ fileLimit }}MB</AuiText>
39
+ </div>
40
+ </slot>
41
+ </view>
42
+ </label>
43
+ </view>
44
+ </template>
45
+
46
+ <script setup>
47
+ import { ref, computed, defineProps, defineEmits, onMounted } from 'vue';
48
+ import AuiIcon from './AuiIcon.vue';
49
+ import AuiText from './AuiText.vue';
50
+
51
+ const fileInput=ref(null)
52
+
53
+ const _name='AuiUpdate';
54
+ defineOptions({
55
+ name: _name,
56
+ components: { AuiIcon, AuiText }
57
+ })
58
+
59
+ const props = defineProps({
60
+ theme: {
61
+ type: String,
62
+ default: 'default',
63
+ validator: v => ['default', 'primary', 'dashed'].includes(v)
64
+ },
65
+ accept: {
66
+ type: String,
67
+ default: 'image/*',
68
+ validator: v => ['image/*', 'video/*', 'image/*,video/*'].includes(v)
69
+ },
70
+ multiple: Boolean,
71
+ fileLimit: {
72
+ type: Number,
73
+ default: 5
74
+ },
75
+ buttonText: {
76
+ type: String,
77
+ default: '点击上传'
78
+ }
79
+ });
80
+
81
+ const emit = defineEmits(['update:modelValue', 'file-change', 'error']);
82
+
83
+ // 使用时间戳生成唯一id
84
+ const timestamp = ref(Date.now());
85
+
86
+ const previewUrl = ref('');
87
+ const selectedFile = ref(null);
88
+
89
+ const isImage = computed(() => selectedFile.value?.type.startsWith('image/'));
90
+ const isVideo = computed(() => selectedFile.value?.type.startsWith('video/'));
91
+ const uploadIcon = computed(() => isVideo.value? 'video' : 'camera');
92
+
93
+ // 调试点击事件
94
+ const debugClick = () => {
95
+ console.log('Label 被点击,ID:', `aui-file-input-${timestamp.value}`);
96
+ if (fileInput.value) {
97
+ fileInput.value.click();
98
+ }
99
+ };
100
+
101
+ const handleFileChange = (event) => {
102
+ console.log('文件选择事件触发');
103
+ const files = event.target.files;
104
+ console.log('选中的文件:', files);
105
+ if (!files.length) return;
106
+
107
+ const file = files[0];
108
+ if (!validateFile(file)) return;
109
+
110
+ selectedFile.value = file;
111
+ previewUrl.value = URL.createObjectURL(file);
112
+ console.log('预览URL:', previewUrl.value);
113
+
114
+ emit('update:modelValue', file);
115
+ emit('file-change', file);
116
+ };
117
+
118
+ const validateFile = (file) => {
119
+ const validTypes = props.accept.split(',').map(t => t.trim());
120
+ const isTypeValid = validTypes.some(type => {
121
+ if (type === '*/*') return true;
122
+ const [main, sub] = type.split('/');
123
+ return file.type.startsWith(`${main}/`) && (sub === '*' || file.type.endsWith(sub));
124
+ });
125
+
126
+ if (!isTypeValid) {
127
+ console.log('文件类型无效:', file.type);
128
+ emit('error', 'invalidType');
129
+ return false;
130
+ }
131
+
132
+ if (file.size > props.fileLimit * 1024 * 1024) {
133
+ console.log('文件大小超过限制:', file.size);
134
+ emit('error','sizeExceeded');
135
+ return false;
136
+ }
137
+
138
+ return true;
139
+ };
140
+
141
+ const removeMedia = () => {
142
+ console.log('移除媒体');
143
+ URL.revokeObjectURL(previewUrl.value);
144
+ previewUrl.value = '';
145
+ selectedFile.value = null;
146
+ const input = document.getElementById(`aui-file-input-${timestamp.value}`);
147
+ if (input) {
148
+ input.value = '';
149
+ }
150
+ emit('update:modelValue', null);
151
+ };
152
+
153
+ onMounted(() => {
154
+ // 在这里可以进行一些初始化操作
155
+ });
156
+ </script>
157
+
158
+ <style scoped lang="scss">
159
+ .aui-update {
160
+ position: relative;
161
+ width: 100%;
162
+ max-width: 600px;
163
+ margin: 0 auto;
164
+ }
165
+
166
+ .aui-update-input {
167
+ position: absolute;
168
+ opacity: 0;
169
+ width: 0;
170
+ height: 0;
171
+ z-index: 11;
172
+ }
173
+
174
+ .preview-container {
175
+ position: relative;
176
+ width: 100%;
177
+ max-width: 400px;
178
+ margin: 16px 0;
179
+ border-radius: 8px;
180
+ overflow: hidden;
181
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
182
+
183
+ .preview-media {
184
+ display: block;
185
+ width: 100%;
186
+ height: auto;
187
+ aspect-ratio: 16/9;
188
+ object-fit: cover;
189
+ }
190
+ }
191
+
192
+ .preview-overlay {
193
+ position: absolute;
194
+ top: 0;
195
+ right: 0;
196
+ padding: 8px;
197
+ }
198
+
199
+ .remove-button {
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ width: 32px;
204
+ height: 32px;
205
+ background: rgba(0,0,0,0.6);
206
+ border: none;
207
+ border-radius: 50%;
208
+ cursor: pointer;
209
+ transition: all 0.2s;
210
+
211
+ &:hover {
212
+ background: rgba(255,0,0,0.8);
213
+ transform: scale(1.1);
214
+ }
215
+ }
216
+
217
+ .aui-update-button {
218
+ display: block;
219
+ width: 100%;
220
+ cursor: pointer;
221
+ outline: none; /* 移除聚焦时的轮廓 */
222
+
223
+ &-theme-default {
224
+ border: 2px dashed #ddd;
225
+ border-radius: 8px;
226
+ padding: 24px;
227
+ background: #f8f9fa;
228
+ transition: all 0.3s ease;
229
+
230
+ &:hover {
231
+ border-color: #409eff;
232
+ background: rgba(64,158,255,0.05);
233
+ }
234
+
235
+ &.has-preview {
236
+ margin-top: 16px;
237
+ }
238
+ }
239
+
240
+ .button-content {
241
+ display: flex;
242
+ flex-direction: column;
243
+ align-items: center;
244
+ gap: 8px;
245
+ }
246
+
247
+ .button-text {
248
+ color: #666;
249
+ font-weight: 500;
250
+ }
251
+
252
+ .file-limit {
253
+ color: #999;
254
+ font-size: 12px;
255
+ }
256
+ }
257
+
258
+ // 主题扩展样式
259
+ .aui-update-button-theme-primary {
260
+ border: 2px solid #409eff;
261
+ background: rgba(64,158,255,0.1);
262
+
263
+ &:hover {
264
+ background: rgba(64,158,255,0.2);
265
+ }
266
+ }
267
+
268
+ .aui-update-button-theme-dashed {
269
+ border-style: dashed;
270
+ }
271
+ </style>