@hlw-uni/mp-vue 1.1.11 → 1.1.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hlw-uni/mp-vue",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "hlw-uni Vue 组件库 — 供小程序业务方使用的 UI 组件集合",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -0,0 +1,264 @@
1
+ <template>
2
+ <view v-if="visible" class="hlw-sheet-root" :class="{ show: shown }">
3
+ <view class="hlw-sheet-overlay" @tap="handleMaskTap" />
4
+ <view class="hlw-sheet-panel" :style="{ height: maxHeight, maxHeight }" @tap.stop>
5
+ <view class="hlw-sheet-header">
6
+ <view v-if="showHandle" class="hlw-sheet-handle" />
7
+ <text v-if="title" class="hlw-sheet-title">{{ title }}</text>
8
+ <view v-if="showClose" class="hlw-sheet-close" @tap="handleClose">
9
+ <text class="i-fa6-solid-xmark" />
10
+ </view>
11
+ </view>
12
+
13
+ <scroll-view class="hlw-sheet-body" scroll-y :show-scrollbar="false">
14
+ <view class="hlw-sheet-body-inner">
15
+ <slot />
16
+ <view v-if="showCta" class="hlw-sheet-cta" @tap="handleCta">
17
+ <text class="hlw-sheet-cta-text">{{ ctaText }}</text>
18
+ </view>
19
+ <slot name="footer" />
20
+ </view>
21
+ </scroll-view>
22
+ </view>
23
+ </view>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ /**
28
+ * HlwSheet — 通用底部弹窗(Bottom Sheet)
29
+ *
30
+ * 单根模板 + position:fixed 覆盖视口,内部 overlay 与 panel 使用 absolute 定位。
31
+ * 自管入场/出场动画时序(16ms 入场,300ms 出场),父组件只需 v-model 控制显隐。
32
+ *
33
+ * @props
34
+ * modelValue - 是否显示(v-model)
35
+ * title - 标题文字
36
+ * showHandle - 是否显示顶部拖拽条,默认 true
37
+ * showClose - 是否显示关闭按钮,默认 true
38
+ * closeOnMask - 点击遮罩是否关闭,默认 true
39
+ * showCta - 是否显示底部主按钮,默认 false
40
+ * ctaText - 底部按钮文字,默认 "确定"
41
+ * maxHeight - 最大高度,默认 "85vh"
42
+ *
43
+ * @events
44
+ * update:modelValue - v-model 更新
45
+ * close - 关闭触发
46
+ * cta - 点击底部按钮
47
+ *
48
+ * @slots
49
+ * default - 主内容(自动在 scroll-view 中滚动)
50
+ * footer - 自定义底部区(紧跟 CTA 按钮之后)
51
+ *
52
+ * @example
53
+ * ```vue
54
+ * <hlw-sheet v-model="visible" title="标题" show-cta cta-text="确定" @cta="handleOk">
55
+ * <view>内容区</view>
56
+ * </hlw-sheet>
57
+ * ```
58
+ */
59
+ import { ref, watch, onBeforeUnmount } from "vue";
60
+
61
+ interface Props {
62
+ modelValue: boolean;
63
+ title?: string;
64
+ showHandle?: boolean;
65
+ showClose?: boolean;
66
+ closeOnMask?: boolean;
67
+ showCta?: boolean;
68
+ ctaText?: string;
69
+ maxHeight?: string;
70
+ }
71
+
72
+ const props = withDefaults(defineProps<Props>(), {
73
+ modelValue: false,
74
+ title: "",
75
+ showHandle: true,
76
+ showClose: true,
77
+ closeOnMask: true,
78
+ showCta: false,
79
+ ctaText: "确定",
80
+ maxHeight: "85vh",
81
+ });
82
+
83
+ const emit = defineEmits<{
84
+ (e: "update:modelValue", value: boolean): void;
85
+ (e: "close"): void;
86
+ (e: "cta"): void;
87
+ }>();
88
+
89
+ const visible = ref(false);
90
+ const shown = ref(false);
91
+
92
+ let closeTimer: ReturnType<typeof setTimeout> | null = null;
93
+ let openTimer: ReturnType<typeof setTimeout> | null = null;
94
+
95
+ const clearTimers = () => {
96
+ if (closeTimer) {
97
+ clearTimeout(closeTimer);
98
+ closeTimer = null;
99
+ }
100
+ if (openTimer) {
101
+ clearTimeout(openTimer);
102
+ openTimer = null;
103
+ }
104
+ };
105
+
106
+ watch(
107
+ () => props.modelValue,
108
+ (val) => {
109
+ clearTimers();
110
+ if (val) {
111
+ visible.value = true;
112
+ openTimer = setTimeout(() => {
113
+ shown.value = true;
114
+ }, 16);
115
+ } else if (visible.value) {
116
+ shown.value = false;
117
+ closeTimer = setTimeout(() => {
118
+ visible.value = false;
119
+ }, 300);
120
+ }
121
+ },
122
+ { immediate: true }
123
+ );
124
+
125
+ const handleClose = () => {
126
+ emit("update:modelValue", false);
127
+ emit("close");
128
+ };
129
+
130
+ const handleMaskTap = () => {
131
+ if (props.closeOnMask) handleClose();
132
+ };
133
+
134
+ const handleCta = () => {
135
+ emit("cta");
136
+ emit("update:modelValue", false);
137
+ };
138
+
139
+ onBeforeUnmount(() => {
140
+ clearTimers();
141
+ });
142
+ </script>
143
+
144
+ <style lang="scss" scoped>
145
+ .hlw-sheet-root {
146
+ position: fixed;
147
+ top: 0;
148
+ left: 0;
149
+ right: 0;
150
+ bottom: 0;
151
+ z-index: 1000;
152
+ }
153
+
154
+ .hlw-sheet-overlay {
155
+ position: absolute;
156
+ top: 0;
157
+ left: 0;
158
+ right: 0;
159
+ bottom: 0;
160
+ background: rgba(0, 0, 0, 0.55);
161
+ opacity: 0;
162
+ transition: opacity 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
163
+ }
164
+
165
+ .hlw-sheet-root.show .hlw-sheet-overlay {
166
+ opacity: 1;
167
+ }
168
+
169
+ .hlw-sheet-panel {
170
+ position: absolute;
171
+ left: 0;
172
+ right: 0;
173
+ bottom: 0;
174
+ background: #ffffff;
175
+ border-radius: var(--radius-xl, 32rpx) var(--radius-xl, 32rpx) 0 0;
176
+ display: flex;
177
+ flex-direction: column;
178
+ overflow: hidden;
179
+ transform: translateY(100%);
180
+ transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
181
+ box-shadow: 0 -12rpx 48rpx rgba(15, 23, 42, 0.15);
182
+ }
183
+
184
+ .hlw-sheet-root.show .hlw-sheet-panel {
185
+ transform: translateY(0);
186
+ }
187
+
188
+ .hlw-sheet-header {
189
+ position: relative;
190
+ display: flex;
191
+ flex-direction: column;
192
+ align-items: center;
193
+ padding: 20rpx 0 24rpx;
194
+ }
195
+
196
+ .hlw-sheet-handle {
197
+ width: 72rpx;
198
+ height: 8rpx;
199
+ border-radius: 999rpx;
200
+ background: var(--border-color, #e2e8f0);
201
+ margin-bottom: 20rpx;
202
+ }
203
+
204
+ .hlw-sheet-title {
205
+ font-size: var(--font-md, 32rpx);
206
+ font-weight: 700;
207
+ color: #0f172a;
208
+ }
209
+
210
+ .hlw-sheet-close {
211
+ position: absolute;
212
+ right: 24rpx;
213
+ top: 24rpx;
214
+ width: 56rpx;
215
+ height: 56rpx;
216
+ border-radius: 50%;
217
+ background: var(--border-color-light, #f1f5f9);
218
+ color: #64748b;
219
+ display: flex;
220
+ align-items: center;
221
+ justify-content: center;
222
+ font-size: var(--font-sm, 24rpx);
223
+ transition: background 0.2s ease;
224
+
225
+ &:active {
226
+ background: var(--border-color, #e2e8f0);
227
+ }
228
+ }
229
+
230
+ .hlw-sheet-body {
231
+ flex: 1 1 auto;
232
+ min-height: 0;
233
+ width: 100%;
234
+ }
235
+
236
+ .hlw-sheet-body-inner {
237
+ padding: 16rpx 36rpx 48rpx;
238
+ display: flex;
239
+ flex-direction: column;
240
+ gap: 32rpx;
241
+ }
242
+
243
+ .hlw-sheet-cta {
244
+ padding: 28rpx;
245
+ background: #0f172a;
246
+ border-radius: var(--radius-md, 16rpx);
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.2);
251
+ transition: transform 0.15s ease;
252
+
253
+ &:active {
254
+ transform: scale(0.98);
255
+ }
256
+ }
257
+
258
+ .hlw-sheet-cta-text {
259
+ font-size: var(--font-base, 28rpx);
260
+ font-weight: 600;
261
+ color: #ffffff;
262
+ letter-spacing: 1rpx;
263
+ }
264
+ </style>