@hlw-uni/mp-vue 1.0.35 → 1.1.0

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.
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <navigator v-if="url" :url="url" class="hlw-cell" :class="{ 'hlw-cell--border': border }" hover-class="hlw-cell--hover">
3
+ <view v-if="icon || $slots.icon" class="hlw-cell-icon">
4
+ <slot name="icon"><view :class="icon" /></slot>
5
+ </view>
6
+ <view class="hlw-cell-body">
7
+ <view class="hlw-cell-title">
8
+ <slot name="title">{{ title }}</slot>
9
+ <view v-if="label" class="hlw-cell-label">{{ label }}</view>
10
+ </view>
11
+ <view class="hlw-cell-value">
12
+ <slot name="value">{{ value }}</slot>
13
+ </view>
14
+ </view>
15
+ <view class="hlw-cell-arrow" />
16
+ </navigator>
17
+ <view v-else class="hlw-cell" :class="{ 'hlw-cell--border': border, 'hlw-cell--link': isLink }" :hover-class="isLink ? 'hlw-cell--hover' : ''" @tap="$emit('click')">
18
+ <view v-if="icon || $slots.icon" class="hlw-cell-icon">
19
+ <slot name="icon"><view :class="icon" /></slot>
20
+ </view>
21
+ <view class="hlw-cell-body">
22
+ <view class="hlw-cell-title">
23
+ <slot name="title">{{ title }}</slot>
24
+ <view v-if="label" class="hlw-cell-label">{{ label }}</view>
25
+ </view>
26
+ <view class="hlw-cell-value">
27
+ <slot name="value">{{ value }}</slot>
28
+ </view>
29
+ </view>
30
+ <view v-if="isLink" class="hlw-cell-arrow" />
31
+ <slot name="right" />
32
+ </view>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ interface Props {
37
+ title?: string;
38
+ label?: string;
39
+ value?: string;
40
+ icon?: string;
41
+ isLink?: boolean;
42
+ url?: string;
43
+ border?: boolean;
44
+ }
45
+
46
+ withDefaults(defineProps<Props>(), {
47
+ title: "",
48
+ label: "",
49
+ value: "",
50
+ icon: "",
51
+ isLink: false,
52
+ url: "",
53
+ border: true,
54
+ });
55
+
56
+ defineEmits<{ click: [] }>();
57
+ </script>
58
+
59
+ <style lang="scss" scoped>
60
+ .hlw-cell {
61
+ display: flex;
62
+ align-items: center;
63
+ padding: 24rpx 32rpx;
64
+ background: #fff;
65
+ gap: 20rpx;
66
+
67
+ &--border {
68
+ border-bottom: 1rpx solid var(--border-color-light, #f1f5f9);
69
+ &:last-child { border-bottom: none; }
70
+ }
71
+ &--hover { background: #f8fafc; }
72
+ }
73
+
74
+ .hlw-cell-icon {
75
+ font-size: var(--font-lg, 36rpx);
76
+ color: var(--primary-color, #3b82f6);
77
+ flex-shrink: 0;
78
+ }
79
+
80
+ .hlw-cell-body {
81
+ flex: 1;
82
+ min-width: 0;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: space-between;
86
+ }
87
+
88
+ .hlw-cell-title {
89
+ font-size: var(--font-base, 28rpx);
90
+ color: #1e293b;
91
+ }
92
+
93
+ .hlw-cell-label {
94
+ font-size: var(--font-xs, 20rpx);
95
+ color: #94a3b8;
96
+ margin-top: 4rpx;
97
+ }
98
+
99
+ .hlw-cell-value {
100
+ font-size: var(--font-sm, 24rpx);
101
+ color: #94a3b8;
102
+ flex-shrink: 0;
103
+ }
104
+
105
+ .hlw-cell-arrow {
106
+ width: 16rpx;
107
+ height: 16rpx;
108
+ border-top: 3rpx solid #c0c4cc;
109
+ border-right: 3rpx solid #c0c4cc;
110
+ transform: rotate(45deg);
111
+ flex-shrink: 0;
112
+ }
113
+ </style>
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <view class="hlw-divider" :class="[`hlw-divider--${position}`, { 'hlw-divider--dashed': dashed }]">
3
+ <view class="hlw-divider-line" />
4
+ <view v-if="text || $slots.default" class="hlw-divider-text">
5
+ <slot>{{ text }}</slot>
6
+ </view>
7
+ <view class="hlw-divider-line" />
8
+ </view>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ interface Props {
13
+ text?: string;
14
+ position?: "left" | "center" | "right";
15
+ dashed?: boolean;
16
+ }
17
+
18
+ withDefaults(defineProps<Props>(), {
19
+ text: "",
20
+ position: "center",
21
+ dashed: false,
22
+ });
23
+ </script>
24
+
25
+ <style lang="scss" scoped>
26
+ .hlw-divider {
27
+ display: flex;
28
+ align-items: center;
29
+ padding: 24rpx 0;
30
+ }
31
+
32
+ .hlw-divider-line {
33
+ flex: 1;
34
+ height: 1rpx;
35
+ background: var(--border-color, #e2e8f0);
36
+
37
+ .hlw-divider--dashed & {
38
+ background: none;
39
+ border-top: 1rpx dashed var(--border-color, #e2e8f0);
40
+ }
41
+ }
42
+
43
+ .hlw-divider-text {
44
+ padding: 0 24rpx;
45
+ font-size: var(--font-xs, 20rpx);
46
+ color: #94a3b8;
47
+ white-space: nowrap;
48
+ }
49
+
50
+ .hlw-divider--left .hlw-divider-line:first-child { max-width: 60rpx; }
51
+ .hlw-divider--right .hlw-divider-line:last-child { max-width: 60rpx; }
52
+ </style>
@@ -0,0 +1,127 @@
1
+ <template>
2
+ <view v-if="show" class="hlw-modal-mask" @tap.self="onMask">
3
+ <view class="hlw-modal" :class="{ 'hlw-modal--show': show }">
4
+ <view v-if="title" class="hlw-modal-title">{{ title }}</view>
5
+ <view class="hlw-modal-body">
6
+ <slot />
7
+ </view>
8
+ <slot name="footer">
9
+ <view class="hlw-modal-footer">
10
+ <view v-if="showCancel" class="hlw-modal-btn hlw-modal-btn--cancel" @tap="onCancel">{{ cancelText }}</view>
11
+ <view class="hlw-modal-btn hlw-modal-btn--confirm" @tap="onConfirm">{{ confirmText }}</view>
12
+ </view>
13
+ </slot>
14
+ </view>
15
+ </view>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ interface Props {
20
+ show?: boolean;
21
+ title?: string;
22
+ showCancel?: boolean;
23
+ confirmText?: string;
24
+ cancelText?: string;
25
+ closeOnMask?: boolean;
26
+ }
27
+
28
+ withDefaults(defineProps<Props>(), {
29
+ show: false,
30
+ title: "",
31
+ showCancel: true,
32
+ confirmText: "确定",
33
+ cancelText: "取消",
34
+ closeOnMask: true,
35
+ });
36
+
37
+ const emit = defineEmits<{ "update:show": [value: boolean]; confirm: []; cancel: [] }>();
38
+
39
+ function close() {
40
+ emit("update:show", false);
41
+ }
42
+
43
+ function onMask() {
44
+ close();
45
+ }
46
+
47
+ function onConfirm() {
48
+ emit("confirm");
49
+ close();
50
+ }
51
+
52
+ function onCancel() {
53
+ emit("cancel");
54
+ close();
55
+ }
56
+ </script>
57
+
58
+ <style lang="scss" scoped>
59
+ .hlw-modal-mask {
60
+ position: fixed;
61
+ inset: 0;
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ background: rgba(0, 0, 0, 0.5);
66
+ z-index: 1000;
67
+ animation: hlw-fade-in 0.2s;
68
+ }
69
+
70
+ .hlw-modal {
71
+ width: 80%;
72
+ max-width: 600rpx;
73
+ background: #fff;
74
+ border-radius: var(--radius-xl, 32rpx);
75
+ overflow: hidden;
76
+ animation: hlw-scale-in 0.25s ease;
77
+ }
78
+
79
+ .hlw-modal-title {
80
+ padding: 40rpx 32rpx 0;
81
+ text-align: center;
82
+ font-size: var(--font-md, 32rpx);
83
+ font-weight: 600;
84
+ color: #1e293b;
85
+ }
86
+
87
+ .hlw-modal-body {
88
+ padding: 32rpx;
89
+ font-size: var(--font-base, 28rpx);
90
+ color: #475569;
91
+ text-align: center;
92
+ line-height: 1.6;
93
+ }
94
+
95
+ .hlw-modal-footer {
96
+ display: flex;
97
+ border-top: 1rpx solid var(--border-color-light, #f1f5f9);
98
+ }
99
+
100
+ .hlw-modal-btn {
101
+ flex: 1;
102
+ padding: 24rpx 0;
103
+ text-align: center;
104
+ font-size: var(--font-base, 28rpx);
105
+ font-weight: 500;
106
+
107
+ &:active { background: #f8fafc; }
108
+
109
+ &--cancel {
110
+ color: #64748b;
111
+ border-right: 1rpx solid var(--border-color-light, #f1f5f9);
112
+ }
113
+ &--confirm {
114
+ color: var(--primary-color, #3b82f6);
115
+ }
116
+ }
117
+
118
+ @keyframes hlw-fade-in {
119
+ from { opacity: 0; }
120
+ to { opacity: 1; }
121
+ }
122
+
123
+ @keyframes hlw-scale-in {
124
+ from { transform: scale(0.9); opacity: 0; }
125
+ to { transform: scale(1); opacity: 1; }
126
+ }
127
+ </style>
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <view v-if="!closed" class="hlw-notice" :style="{ color, background }">
3
+ <view v-if="leftIcon" :class="leftIcon" class="hlw-notice-left-icon" />
4
+ <view v-else class="hlw-notice-left-icon">&#128227;</view>
5
+ <view class="hlw-notice-wrap" @tap="$emit('click')">
6
+ <view v-if="scrollable" class="hlw-notice-scroll" :style="animStyle">
7
+ <text class="hlw-notice-text">{{ text }}</text>
8
+ </view>
9
+ <text v-else class="hlw-notice-text hlw-notice-text--ellipsis">{{ text }}</text>
10
+ </view>
11
+ <view v-if="closable" class="hlw-notice-close" @tap="onClose">&#215;</view>
12
+ </view>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { ref, computed, onMounted } from "vue";
17
+
18
+ interface Props {
19
+ text?: string;
20
+ scrollable?: boolean;
21
+ closable?: boolean;
22
+ color?: string;
23
+ background?: string;
24
+ speed?: number;
25
+ leftIcon?: string;
26
+ }
27
+
28
+ const props = withDefaults(defineProps<Props>(), {
29
+ text: "",
30
+ scrollable: true,
31
+ closable: false,
32
+ color: "#ed6a0c",
33
+ background: "#fffbe8",
34
+ speed: 60,
35
+ leftIcon: "",
36
+ });
37
+
38
+ defineEmits<{ close: []; click: [] }>();
39
+
40
+ const closed = ref(false);
41
+ const duration = computed(() => Math.max(3, (props.text.length * 20) / props.speed));
42
+ const animStyle = computed(() => ({ animationDuration: `${duration.value}s` }));
43
+
44
+ function onClose() {
45
+ closed.value = true;
46
+ }
47
+ </script>
48
+
49
+ <style lang="scss" scoped>
50
+ .hlw-notice {
51
+ display: flex;
52
+ align-items: center;
53
+ padding: 16rpx 24rpx;
54
+ gap: 12rpx;
55
+ font-size: var(--font-sm, 24rpx);
56
+ }
57
+
58
+ .hlw-notice-left-icon {
59
+ flex-shrink: 0;
60
+ font-size: var(--font-base, 28rpx);
61
+ }
62
+
63
+ .hlw-notice-wrap {
64
+ flex: 1;
65
+ overflow: hidden;
66
+ white-space: nowrap;
67
+ }
68
+
69
+ .hlw-notice-scroll {
70
+ display: inline-block;
71
+ white-space: nowrap;
72
+ animation: hlw-notice-scroll linear infinite;
73
+ padding-left: 100%;
74
+ }
75
+
76
+ .hlw-notice-text {
77
+ display: inline;
78
+ }
79
+
80
+ .hlw-notice-text--ellipsis {
81
+ display: block;
82
+ overflow: hidden;
83
+ text-overflow: ellipsis;
84
+ white-space: nowrap;
85
+ }
86
+
87
+ .hlw-notice-close {
88
+ flex-shrink: 0;
89
+ font-size: 32rpx;
90
+ line-height: 1;
91
+ opacity: 0.6;
92
+ padding: 0 4rpx;
93
+ }
94
+
95
+ @keyframes hlw-notice-scroll {
96
+ 0% { transform: translateX(0); }
97
+ 100% { transform: translateX(-100%); }
98
+ }
99
+ </style>
@@ -0,0 +1,108 @@
1
+ <template>
2
+ <view v-if="show" class="hlw-popup-mask" @tap.self="onClose" />
3
+ <view class="hlw-popup" :class="[`hlw-popup--${position}`, { 'hlw-popup--show': show, 'hlw-popup--round': round }]">
4
+ <view v-if="title || closable" class="hlw-popup-header">
5
+ <text class="hlw-popup-title">{{ title }}</text>
6
+ <view v-if="closable" class="hlw-popup-close" @tap="onClose">&#215;</view>
7
+ </view>
8
+ <slot />
9
+ </view>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ interface Props {
14
+ show?: boolean;
15
+ position?: "bottom" | "center" | "top";
16
+ round?: boolean;
17
+ closable?: boolean;
18
+ title?: string;
19
+ }
20
+
21
+ withDefaults(defineProps<Props>(), {
22
+ show: false,
23
+ position: "bottom",
24
+ round: true,
25
+ closable: true,
26
+ title: "",
27
+ });
28
+
29
+ const emit = defineEmits<{ "update:show": [value: boolean]; close: [] }>();
30
+
31
+ function onClose() {
32
+ emit("update:show", false);
33
+ emit("close");
34
+ }
35
+ </script>
36
+
37
+ <style lang="scss" scoped>
38
+ .hlw-popup-mask {
39
+ position: fixed;
40
+ inset: 0;
41
+ background: rgba(0, 0, 0, 0.5);
42
+ z-index: 1000;
43
+ animation: hlw-fade-in 0.25s;
44
+ }
45
+
46
+ .hlw-popup {
47
+ position: fixed;
48
+ z-index: 1001;
49
+ background: #fff;
50
+ transition: transform 0.3s ease, opacity 0.3s ease;
51
+
52
+ &--bottom {
53
+ left: 0;
54
+ right: 0;
55
+ bottom: 0;
56
+ max-height: 80vh;
57
+ transform: translateY(100%);
58
+ &.hlw-popup--show { transform: translateY(0); }
59
+ &.hlw-popup--round { border-radius: var(--radius-xl, 32rpx) var(--radius-xl, 32rpx) 0 0; }
60
+ }
61
+
62
+ &--top {
63
+ left: 0;
64
+ right: 0;
65
+ top: 0;
66
+ max-height: 80vh;
67
+ transform: translateY(-100%);
68
+ &.hlw-popup--show { transform: translateY(0); }
69
+ &.hlw-popup--round { border-radius: 0 0 var(--radius-xl, 32rpx) var(--radius-xl, 32rpx); }
70
+ }
71
+
72
+ &--center {
73
+ left: 50%;
74
+ top: 50%;
75
+ width: 80%;
76
+ transform: translate(-50%, -50%) scale(0.9);
77
+ opacity: 0;
78
+ border-radius: var(--radius-xl, 32rpx);
79
+ &.hlw-popup--show { transform: translate(-50%, -50%) scale(1); opacity: 1; }
80
+ }
81
+ }
82
+
83
+ .hlw-popup-header {
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ padding: 28rpx 32rpx;
88
+ border-bottom: 1rpx solid var(--border-color-light, #f1f5f9);
89
+ }
90
+
91
+ .hlw-popup-title {
92
+ font-size: var(--font-md, 32rpx);
93
+ font-weight: 600;
94
+ color: #1e293b;
95
+ }
96
+
97
+ .hlw-popup-close {
98
+ font-size: 40rpx;
99
+ color: #94a3b8;
100
+ line-height: 1;
101
+ padding: 0 8rpx;
102
+ }
103
+
104
+ @keyframes hlw-fade-in {
105
+ from { opacity: 0; }
106
+ to { opacity: 1; }
107
+ }
108
+ </style>
@@ -0,0 +1,95 @@
1
+ <template>
2
+ <view class="hlw-search" :style="background ? { background } : {}">
3
+ <view class="hlw-search-box" :class="{ 'hlw-search-box--round': shape === 'round' }">
4
+ <view class="hlw-search-icon">&#128269;</view>
5
+ <input
6
+ class="hlw-search-input"
7
+ type="text"
8
+ :value="modelValue"
9
+ :placeholder="placeholder"
10
+ :disabled="disabled"
11
+ confirm-type="search"
12
+ @input="onInput"
13
+ @confirm="$emit('search', modelValue)"
14
+ @focus="$emit('focus')"
15
+ @blur="$emit('blur')"
16
+ />
17
+ <view v-if="clearable && modelValue" class="hlw-search-clear" @tap="onClear">&#215;</view>
18
+ </view>
19
+ </view>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ interface Props {
24
+ modelValue?: string;
25
+ placeholder?: string;
26
+ disabled?: boolean;
27
+ clearable?: boolean;
28
+ shape?: "square" | "round";
29
+ background?: string;
30
+ }
31
+
32
+ const props = withDefaults(defineProps<Props>(), {
33
+ modelValue: "",
34
+ placeholder: "搜索",
35
+ disabled: false,
36
+ clearable: true,
37
+ shape: "round",
38
+ background: "",
39
+ });
40
+
41
+ const emit = defineEmits<{
42
+ "update:modelValue": [value: string];
43
+ search: [value: string];
44
+ clear: [];
45
+ focus: [];
46
+ blur: [];
47
+ }>();
48
+
49
+ function onInput(e: { detail: { value: string } }) {
50
+ emit("update:modelValue", e.detail.value);
51
+ }
52
+
53
+ function onClear() {
54
+ emit("update:modelValue", "");
55
+ emit("clear");
56
+ }
57
+ </script>
58
+
59
+ <style lang="scss" scoped>
60
+ .hlw-search {
61
+ padding: 16rpx 24rpx;
62
+ }
63
+
64
+ .hlw-search-box {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 12rpx;
68
+ padding: 12rpx 24rpx;
69
+ background: #f1f5f9;
70
+ border-radius: var(--radius-md, 16rpx);
71
+
72
+ &--round { border-radius: 999rpx; }
73
+ }
74
+
75
+ .hlw-search-icon {
76
+ font-size: var(--font-sm, 24rpx);
77
+ flex-shrink: 0;
78
+ opacity: 0.5;
79
+ }
80
+
81
+ .hlw-search-input {
82
+ flex: 1;
83
+ font-size: var(--font-sm, 24rpx);
84
+ color: #1e293b;
85
+ min-height: 40rpx;
86
+ }
87
+
88
+ .hlw-search-clear {
89
+ font-size: 32rpx;
90
+ color: #94a3b8;
91
+ line-height: 1;
92
+ flex-shrink: 0;
93
+ padding: 0 4rpx;
94
+ }
95
+ </style>
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <view v-if="loading" class="hlw-skeleton" :class="{ 'hlw-skeleton--animate': animate }">
3
+ <view v-if="avatar" class="hlw-skeleton-avatar" :class="`hlw-skeleton-avatar--${avatarSize}`" />
4
+ <view class="hlw-skeleton-content">
5
+ <view v-if="title" class="hlw-skeleton-title" />
6
+ <view v-for="i in rows" :key="i" class="hlw-skeleton-row" :style="{ width: i === rows ? '60%' : '100%' }" />
7
+ </view>
8
+ </view>
9
+ <slot v-else />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ interface Props {
14
+ loading?: boolean;
15
+ rows?: number;
16
+ avatar?: boolean;
17
+ title?: boolean;
18
+ animate?: boolean;
19
+ avatarSize?: "small" | "medium" | "large";
20
+ }
21
+
22
+ withDefaults(defineProps<Props>(), {
23
+ loading: true,
24
+ rows: 3,
25
+ avatar: false,
26
+ title: true,
27
+ animate: true,
28
+ avatarSize: "medium",
29
+ });
30
+ </script>
31
+
32
+ <style lang="scss" scoped>
33
+ .hlw-skeleton {
34
+ display: flex;
35
+ gap: 24rpx;
36
+ padding: 24rpx;
37
+ }
38
+
39
+ .hlw-skeleton-avatar {
40
+ flex-shrink: 0;
41
+ border-radius: 50%;
42
+ background: #e2e8f0;
43
+
44
+ &--small { width: 56rpx; height: 56rpx; }
45
+ &--medium { width: 80rpx; height: 80rpx; }
46
+ &--large { width: 120rpx; height: 120rpx; }
47
+ }
48
+
49
+ .hlw-skeleton-content {
50
+ flex: 1;
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 20rpx;
54
+ }
55
+
56
+ .hlw-skeleton-title {
57
+ width: 40%;
58
+ height: 32rpx;
59
+ border-radius: 6rpx;
60
+ background: #e2e8f0;
61
+ }
62
+
63
+ .hlw-skeleton-row {
64
+ height: 24rpx;
65
+ border-radius: 6rpx;
66
+ background: #e2e8f0;
67
+ }
68
+
69
+ .hlw-skeleton--animate {
70
+ .hlw-skeleton-avatar,
71
+ .hlw-skeleton-title,
72
+ .hlw-skeleton-row {
73
+ animation: hlw-skeleton-pulse 1.5s ease-in-out infinite;
74
+ }
75
+ }
76
+
77
+ @keyframes hlw-skeleton-pulse {
78
+ 0% { opacity: 1; }
79
+ 50% { opacity: 0.4; }
80
+ 100% { opacity: 1; }
81
+ }
82
+ </style>