hw-cus-ui 1.0.24 → 1.0.25

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.
package/HwBtn/HwBtn.vue CHANGED
@@ -1,16 +1,16 @@
1
- <template>
2
- <div class="btn">我的按钮</div>
3
- </template>
4
-
5
- <script setup lang="ts">
6
-
7
- </script>
8
-
9
- <style scoped>
10
- .btn {
11
- width: 100px;
12
- height: 100px;
13
- background-color: red;
14
- color: #ffffff;
15
- }
16
- </style>
1
+ <template>
2
+ <div class="btn">我的按钮</div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+
7
+ </script>
8
+
9
+ <style scoped>
10
+ .btn {
11
+ width: 100px;
12
+ height: 100px;
13
+ background-color: red;
14
+ color: #ffffff;
15
+ }
16
+ </style>
package/HwBtn/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- // index.ts
2
- import type { App } from 'vue'
3
- import HwBtn from "./HwBtn.vue"
4
-
5
- // 使用install方法,在app.use挂载
6
- HwBtn.install = (app: App) => {
7
- app.component(HwBtn.__name as string, HwBtn) //注册组件
8
- }
9
-
10
- export default HwBtn
1
+ // index.ts
2
+ import type { App } from 'vue'
3
+ import HwBtn from "./HwBtn.vue"
4
+
5
+ // 使用install方法,在app.use挂载
6
+ HwBtn.install = (app: App) => {
7
+ app.component(HwBtn.__name as string, HwBtn) //注册组件
8
+ }
9
+
10
+ export default HwBtn
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <view class="bn-select" :style="{ marginTop: marginTop }">
3
+ <view class="select-item">
4
+ <view>
5
+ <!-- 标签区域:显示字段标签和必填标识 -->
6
+ <view class="label" :style="{ color: types === 'detail' ? 'rgba(23, 26, 29, 0.6)' : 'rgba(23, 26, 29, 1)' }">
7
+ <text style="color: red" v-if="required">*</text>
8
+ {{ label }}
9
+ </view>
10
+ <!-- 编辑模式下显示复选框组件 -->
11
+ <slot>
12
+ <uni-data-checkbox
13
+ class="value"
14
+ v-if="pageType !== 'detail'"
15
+ v-model="value"
16
+ :localdata="localdata"
17
+ v-bind="$attrs"
18
+ />
19
+ </slot>
20
+
21
+ <!-- 详情模式下显示选中项的文本 -->
22
+ <view
23
+ class="value"
24
+ v-if="pageType === 'detail'"
25
+ :style="{ color: types === 'detail' ? 'rgba(23, 26, 29, 0.6)' : 'rgba(23, 26, 29, 1)' }"
26
+ >
27
+ {{ getValue(value) }}
28
+ </view>
29
+ </view>
30
+ </view>
31
+ </view>
32
+ </template>
33
+
34
+ <script lang="ts" setup>
35
+ /**
36
+ * 双向绑定的值,可以是字符串或数字类型
37
+ */
38
+ const value = defineModel<string | number>();
39
+
40
+ /**
41
+ * 组件属性接口定义
42
+ */
43
+ interface Props {
44
+ /**
45
+ * 上边距,默认为 20px
46
+ */
47
+ marginTop?: string;
48
+ /**
49
+ * 页面类型,用于区分编辑页面还是详情页面
50
+ */
51
+ pageType?: string;
52
+ /**
53
+ * 字段标签名称(必填)
54
+ */
55
+ label: string;
56
+ /**
57
+ * 类型标识,用于控制样式显示
58
+ */
59
+ types?: string;
60
+ /**
61
+ * 是否必填项,true时会显示红色星号
62
+ */
63
+ required?: boolean;
64
+ /**
65
+ * 下拉选项数据列表,每项应包含value和text属性
66
+ */
67
+ localdata?: any[];
68
+ }
69
+
70
+ /**
71
+ * 定义组件接收的属性
72
+ */
73
+ const props = defineProps<Props>();
74
+
75
+ /**
76
+ * 解构赋值设置默认值
77
+ */
78
+ const { label = '', marginTop = '20px', pageType = 'checkbox', types = 'add' } = props;
79
+
80
+ /**
81
+ * 根据选中的值获取对应的文本显示
82
+ * @param val 当前选中的值
83
+ * @returns 对应的文本内容,未找到则返回空字符串
84
+ */
85
+ const getValue = (val: any) => {
86
+ if (val) {
87
+ return props.localdata?.find((item) => item.value === val)?.text;
88
+ }
89
+ return '';
90
+ };
91
+ </script>
92
+
93
+ <style lang="scss" scoped>
94
+ .bn-select {
95
+ width: 100%;
96
+ border-bottom: 1rpx solid rgba(17, 31, 44, 0.12);
97
+ .select-item {
98
+ .label {
99
+ font-size: 34rpx;
100
+ letter-spacing: 0px;
101
+ line-height: 44rpx;
102
+ height: 44rpx;
103
+ color: rgba(23, 26, 29, 1);
104
+ }
105
+ .value {
106
+ font-size: 34rpx;
107
+ padding: 20rpx 0;
108
+ }
109
+ }
110
+ ::v-deep .checklist-text {
111
+ font-size: 34rpx !important;
112
+ }
113
+ ::v-deep .uni-data-checklist .checklist-group .checklist-box {
114
+ margin: 10rpx 40rpx 10rpx 0rpx !important;
115
+ }
116
+ }
117
+ </style>
@@ -0,0 +1,10 @@
1
+ // index.ts
2
+ import type { App } from 'vue';
3
+ import HwCheckbox from './HwCheckbox.vue';
4
+
5
+ // 使用install方法,在app.use挂载
6
+ HwCheckbox.install = (app: App) => {
7
+ app.component(HwCheckbox.__name as string, HwCheckbox); //注册组件
8
+ };
9
+
10
+ export default HwCheckbox;
@@ -0,0 +1,10 @@
1
+ // index.ts
2
+ import type { App } from 'vue';
3
+ import HwDraggableBottomPopup from './HwDraggableBottomPopup.vue';
4
+
5
+ // 使用install方法,在app.use挂载
6
+ HwDraggableBottomPopup.install = (app: App) => {
7
+ app.component(HwDraggableBottomPopup.__name as string, HwDraggableBottomPopup); //注册组件
8
+ };
9
+
10
+ export default HwDraggableBottomPopup;
@@ -0,0 +1,412 @@
1
+ <!--
2
+ 文件上传组件
3
+ 支持图片和文件上传、预览、下载和删除功能
4
+ 可通过isImg属性控制是图片模式还是文件模式展示
5
+ 支持多种文件类型上传限制
6
+ -->
7
+ <template>
8
+ <view
9
+ class="bn-select"
10
+ :style="{ marginTop: marginTop, borderBottom: border ? `1rpx solid rgba(17, 31, 44, 0.12);` : 'none' }"
11
+ >
12
+ <view class="select-item" v-if="label">
13
+ <view class="label" :style="{ color: types === 'detail' ? 'rgba(23, 26, 29, 0.6)' : 'rgba(23, 26, 29, 1)' }">
14
+ <text style="color: red" v-if="required">*</text>
15
+ {{ label }}
16
+ </view>
17
+ <uni-file-picker
18
+ v-if="types !== 'detail' && !isImg"
19
+ file-mediatype="all"
20
+ :file-extname="fileType.join(',')"
21
+ :auto-upload="false"
22
+ v-bind="$attrs"
23
+ :image-styles="{
24
+ height: 34,
25
+ width: 34,
26
+ border: {
27
+ width: '0'
28
+ }
29
+ }"
30
+ @select="handleSelect"
31
+ >
32
+ <template v-if="!isImg">
33
+ <image class="icon-add" src="@/static/image/add.png" />
34
+ </template>
35
+ </uni-file-picker>
36
+ </view>
37
+
38
+ <view class="col-img" v-if="isImg">
39
+ <view class="col-img-item" v-for="(item, index) in fileList" :key="index">
40
+ <image
41
+ class="col-img-item-img"
42
+ :src="isLink ? `${baseUrl}${item.url}` : item.url"
43
+ mode="aspectFill"
44
+ @click="handlePreview(item)"
45
+ />
46
+
47
+ <view class="col-img-item-del" @click="handleDelete(index)" v-if="types !== 'detail'">
48
+ <image class="col-img-item-del-img" src="@/static/image/del-img.png" />
49
+ </view>
50
+ </view>
51
+
52
+ <uni-file-picker
53
+ v-if="types !== 'detail'"
54
+ file-mediatype="all"
55
+ :file-extname="fileType.join(',')"
56
+ :auto-upload="false"
57
+ v-bind="$attrs"
58
+ @select="handleSelect"
59
+ >
60
+ <template v-if="isImg">
61
+ <image class="icon-add" src="@/static/image/add-img.png" />
62
+ </template>
63
+ </uni-file-picker>
64
+ </view>
65
+ <view class="col-file" v-if="!isImg">
66
+ <view class="col-file-item" v-for="(item, index) in fileList" :key="index">
67
+ <image class="col-file-item-img" src="@/static/image/file.png" />
68
+ <view class="name">
69
+ <view class="name-label">{{ item.name || item.originalName }}</view>
70
+ <view>
71
+ <text v-if="item.fileSize">{{ item.fileSize }}KB</text>
72
+ <text style="color: rgba(49, 126, 208, 1)" @click="handleDownload(item)">下载</text>
73
+ </view>
74
+ </view>
75
+ <view class="col-file-item-del" @click="handleDelete(index)" v-if="types !== 'detail'">
76
+ <image class="col-file-item-del-img" src="@/static/image/del.png" />
77
+ </view>
78
+ </view>
79
+ </view>
80
+ <view v-if="fileList.length === 0 && !isImg" class="no-more">
81
+ {{ types === 'detail' ? '暂无数据' : '请上传文件' }}
82
+ </view>
83
+ </view>
84
+ </template>
85
+
86
+ <script lang="ts" setup>
87
+ import { ref, watch } from 'vue';
88
+ //@ts-ignore
89
+ import FileSaver from 'file-saver';
90
+
91
+ // 导入uni-app的类型定义
92
+ declare const uni: any;
93
+
94
+ /**
95
+ * 组件属性定义
96
+ * @property {any[]} fileType - 允许上传的文件类型列表,默认为 ['pdf', 'xlsx', 'docx', 'doc', 'zip', 'rar', 'exe']
97
+ * @property {any[]} modelValue - 绑定值,文件列表
98
+ * @property {boolean} isImg - 是否为图片模式,默认为 false
99
+ * @property {boolean} border - 是否显示下边框,默认为 true
100
+ * @property {string} marginTop - 上边距,默认为 '20px'
101
+ * @property {string} types - 组件类型,'add' 为添加模式,'detail' 为详情模式
102
+ * @property {boolean} required - 是否必填,默认为 false
103
+ * @property {string} label - 标签文本
104
+ */
105
+ interface Props {
106
+ fileType?: any[];
107
+ modelValue?: any[];
108
+ isImg?: boolean;
109
+ border?: boolean;
110
+ marginTop?: string;
111
+ types: string;
112
+ required?: boolean;
113
+ isLink?: boolean;
114
+ baseUrl?: string;
115
+ apiUrl: string;
116
+ label: string;
117
+ headers?: any;
118
+ }
119
+
120
+ const props = defineProps<Props>();
121
+
122
+ const {
123
+ label = '',
124
+ marginTop = '20px',
125
+ fileType = ['pdf', 'xlsx', 'docx', 'doc', 'zip', 'rar', 'exe'],
126
+ isImg = false,
127
+ isLink = false,
128
+ baseUrl = '',
129
+ apiUrl = '',
130
+ border = true,
131
+ types = 'add',
132
+ headers = {}
133
+ } = props;
134
+
135
+ const emit = defineEmits(['update:modelValue']);
136
+
137
+ const fileList = ref<any[]>([]);
138
+
139
+ /**
140
+ * 监听modelValue变化,同步更新fileList
141
+ * 当传入的值为字符串数组时,会自动转换为对象数组格式
142
+ */
143
+ watch(
144
+ () => props.modelValue,
145
+ (val: any) => {
146
+ if (val && val.length) {
147
+ // 首先将值转为数组
148
+ let list: any[] = [];
149
+ if (Array.isArray(val)) {
150
+ list = val as any[];
151
+ }
152
+ // 然后将数组转为对象数组
153
+ fileList.value = list.map((item) => {
154
+ let itemData;
155
+ if (typeof item === 'string') {
156
+ itemData = { name: item, url: item };
157
+ } else {
158
+ itemData = { ...item };
159
+ }
160
+ return itemData;
161
+ });
162
+ } else {
163
+ fileList.value = [];
164
+ }
165
+ },
166
+ { deep: true, immediate: true }
167
+ );
168
+
169
+ /**
170
+ * 删除指定索引的文件
171
+ * @param {number} index - 要删除的文件在列表中的索引
172
+ */
173
+ const handleDelete = (index: number) => {
174
+ fileList.value.splice(index, 1);
175
+ emit('update:modelValue', [...fileList.value]);
176
+ };
177
+
178
+ /**
179
+ * 预览图片
180
+ * @param {Object} item - 文件对象,包含name和url属性
181
+ */
182
+ const handlePreview = (item: { name: string; url: string }) => {
183
+ const urls = [isLink ? `${baseUrl}${item.url}` : item.url];
184
+ uni.previewImage({
185
+ urls
186
+ });
187
+ };
188
+
189
+ /**
190
+ * 下载文件
191
+ * @param {Object} file - 文件对象
192
+ */
193
+ const handleDownload = (file: any) => {
194
+ convertUrlToBlob(file.url, file.name);
195
+ };
196
+
197
+ /**
198
+ * 将URL转换为Blob并下载
199
+ * @param {string} url - 文件URL地址
200
+ * @param {string} name - 文件名
201
+ */
202
+ async function convertUrlToBlob(url: string, name: string) {
203
+ try {
204
+ const response = await fetch(url);
205
+ if (!response.ok) {
206
+ throw new Error(`HTTP错误: ${response.status}`);
207
+ }
208
+ const blob = await response.blob();
209
+ FileSaver.saveAs(blob, name);
210
+ } catch (error) {
211
+ console.log(error);
212
+ }
213
+ }
214
+
215
+ /**
216
+ * 处理文件选择事件
217
+ * @param {Object} e - 选择文件事件对象
218
+ */
219
+ const handleSelect = (e: any) => {
220
+ const filePath = e.tempFilePaths[0];
221
+ const tempFile = e.tempFiles[0];
222
+ if (!tempFile) {
223
+ return;
224
+ }
225
+ if (!fileType.includes(`${tempFile.extname}`)) {
226
+ uni.showToast({
227
+ title: '暂不支持当前文件类型',
228
+ icon: 'none'
229
+ });
230
+ emit('update:modelValue', [...fileList.value]);
231
+ return;
232
+ }
233
+ uni.showLoading({
234
+ title: '上传中...'
235
+ });
236
+
237
+ uni.uploadFile({
238
+ url: `${baseUrl}${apiUrl}`,
239
+ header: headers,
240
+ filePath,
241
+ name: 'file',
242
+ success: (uploadFileRes: any) => {
243
+ const res = JSON.parse(uploadFileRes.data);
244
+ uni.hideLoading();
245
+ if (res.code === 200) {
246
+ if (isLink) {
247
+ emit('update:modelValue', [...fileList.value, { ...res.data }]);
248
+ } else {
249
+ emit('update:modelValue', [...fileList.value, { ...res.data, name: res.data.originalName }]);
250
+ }
251
+ } else {
252
+ uni.showToast({
253
+ title: res.msg,
254
+ icon: 'none'
255
+ });
256
+ }
257
+ },
258
+ fail: () => {
259
+ uni.hideLoading();
260
+ }
261
+ });
262
+ };
263
+ </script>
264
+ <style scoped lang="scss">
265
+ .bn-select {
266
+ width: 100%;
267
+ border-bottom: 1rpx solid rgba(17, 31, 44, 0.12);
268
+ padding-bottom: 24rpx;
269
+ .no-more {
270
+ color: rgba(23, 26, 29, 0.6);
271
+ font-size: 34rpx;
272
+ }
273
+ .select-item {
274
+ display: flex;
275
+ justify-content: space-between;
276
+ align-items: center;
277
+ .label {
278
+ font-size: 34rpx;
279
+ letter-spacing: 0px;
280
+ line-height: 44rpx;
281
+ height: 44rpx;
282
+ color: rgba(23, 26, 29, 1);
283
+ }
284
+ .icon-add {
285
+ width: 44rpx;
286
+ height: 44rpx;
287
+ }
288
+
289
+ ::v-deep .uni-file-picker {
290
+ width: auto;
291
+ flex: none;
292
+ }
293
+ ::v-deep .uni-file-picker__container > :not(:last-child) {
294
+ display: none !important;
295
+ }
296
+ ::v-deep .file-picker__box-content {
297
+ margin: 0;
298
+ }
299
+ ::v-deep .uni-file-picker__lists {
300
+ display: none;
301
+ }
302
+ }
303
+ .col-img {
304
+ margin-top: 24rpx;
305
+ display: grid;
306
+ grid-template-columns: repeat(3, auto);
307
+ grid-gap: 24rpx;
308
+ .col-img-item {
309
+ width: 100%;
310
+ height: 136rpx;
311
+ border-radius: 12rpx;
312
+ position: relative;
313
+ .col-img-item-img {
314
+ width: 100%;
315
+ height: 100%;
316
+ border-radius: 12rpx;
317
+ }
318
+ .col-img-item-del {
319
+ position: absolute;
320
+ right: 12rpx;
321
+ bottom: 12rpx;
322
+ width: 48rpx;
323
+ height: 48rpx;
324
+ .col-img-item-del-img {
325
+ width: 100%;
326
+ height: 100%;
327
+ display: flex;
328
+ justify-content: center;
329
+ align-items: center;
330
+ .col-img-item-del-img-icon {
331
+ width: 100%;
332
+ height: 100%;
333
+ }
334
+ }
335
+ }
336
+ }
337
+ .icon-add {
338
+ width: 44rpx;
339
+ height: 44rpx;
340
+ }
341
+
342
+ ::v-deep .uni-file-picker {
343
+ width: auto;
344
+ flex: none;
345
+ }
346
+ ::v-deep .file-picker__box {
347
+ width: 100% !important;
348
+ height: 136rpx !important;
349
+ // border: 4rpx dashed rgba(0, 127, 255, 0.48) !important;
350
+ // border-radius: 12rpx !important;
351
+ }
352
+ ::v-deep .uni-file-picker__container {
353
+ margin: 0;
354
+ }
355
+ ::v-deep .uni-file-picker__container > :not(:last-child) {
356
+ display: none !important;
357
+ }
358
+ ::v-deep .file-picker__box-content {
359
+ margin: 0;
360
+ background: rgba(0, 127, 255, 0.12);
361
+ border: 4rpx dashed rgba(0, 127, 255, 0.48) !important;
362
+ border-radius: 12rpx !important;
363
+ }
364
+ ::v-deep .uni-file-picker__lists {
365
+ display: none;
366
+ }
367
+ }
368
+ .col-file {
369
+ margin-top: 24rpx;
370
+
371
+ .col-file-item {
372
+ width: 100%;
373
+ display: grid;
374
+ grid-template-columns: 96rpx auto 50rpx;
375
+ grid-gap: 24rpx;
376
+ margin-bottom: 20rpx;
377
+ align-items: center;
378
+ .col-file-item-img {
379
+ width: 100%;
380
+ height: 96rpx;
381
+ }
382
+ .name-label {
383
+ flex: 1;
384
+ font-size: 34rpx;
385
+ color: rgba(23, 26, 29, 1);
386
+ overflow: hidden;
387
+ text-overflow: ellipsis;
388
+ display: -webkit-box;
389
+ -webkit-line-clamp: 2;
390
+ line-clamp: 2;
391
+ -webkit-box-orient: vertical;
392
+ word-break: break-all;
393
+ }
394
+ .col-file-item-del {
395
+ width: 36rpx;
396
+ height: 36rpx;
397
+ .col-file-item-del-img {
398
+ width: 100%;
399
+ height: 100%;
400
+ display: flex;
401
+ justify-content: center;
402
+ align-items: center;
403
+ .col-file-item-del-img-icon {
404
+ width: 36rpx;
405
+ height: 36rpx;
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+ </style>
@@ -0,0 +1,10 @@
1
+ // index.ts
2
+ import type { App } from 'vue';
3
+ import HwFileUpload from './HwFileUpload.vue';
4
+
5
+ // 使用install方法,在app.use挂载
6
+ HwFileUpload.install = (app: App) => {
7
+ app.component(HwFileUpload.__name as string, HwFileUpload); //注册组件
8
+ };
9
+
10
+ export default HwFileUpload;