@hlw-uni/mp-vue 2.1.14 → 2.1.15

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": "2.1.14",
3
+ "version": "2.1.15",
4
4
  "description": "hlw-uni 小程序运行时 — Vue 组件 + composables + theme + http + 工具集(合并自原 mp-core)",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -3,35 +3,21 @@
3
3
  class="hlw-card"
4
4
  :class="[
5
5
  `hlw-card--radius-${radius}`,
6
- border ? 'hlw-card--bordered' : '',
6
+ ...borderClasses,
7
7
  ]"
8
+ :style="rootStyle"
8
9
  >
9
- <!-- 头部 -->
10
+ <!-- 头部 — 用 #header slot 自定义;常规场景请用 <hlw-card-header> -->
10
11
  <view v-if="hasHeader" class="hlw-card-header">
11
- <slot name="header">
12
- <view class="hlw-card-header-inner">
13
- <!-- 头部左侧 -->
14
- <view class="hlw-card-header-left">
15
- <slot name="header-left">
16
- <text v-if="title" class="hlw-card-title">{{ title }}</text>
17
- </slot>
18
- </view>
19
- <!-- 头部右侧 -->
20
- <view v-if="$slots['header-right'] || extra" class="hlw-card-header-right">
21
- <slot name="header-right">
22
- <text v-if="extra" class="hlw-card-extra">{{ extra }}</text>
23
- </slot>
24
- </view>
25
- </view>
26
- </slot>
12
+ <slot name="header" />
27
13
  </view>
28
14
 
29
- <!-- 头部虚线分隔 -->
30
- <view v-if="showDivider" class="hlw-card-divider"></view>
15
+ <!-- 头部虚线分隔(有 #header slot 且 divider != false 时显示) -->
16
+ <view v-if="showDivider" class="hlw-card-divider" />
31
17
 
32
18
  <!-- 内容区 -->
33
19
  <view class="hlw-card-body" :class="{ 'hlw-card-body--padded': padding }">
34
- <slot></slot>
20
+ <slot />
35
21
  </view>
36
22
 
37
23
  <!-- 底部 -->
@@ -39,10 +25,10 @@
39
25
  <slot name="footer">
40
26
  <view class="hlw-card-footer-inner">
41
27
  <view class="hlw-card-footer-left">
42
- <slot name="footer-left"></slot>
28
+ <slot name="footer-left" />
43
29
  </view>
44
30
  <view v-if="$slots['footer-right']" class="hlw-card-footer-right">
45
- <slot name="footer-right"></slot>
31
+ <slot name="footer-right" />
46
32
  </view>
47
33
  </view>
48
34
  </slot>
@@ -56,68 +42,106 @@ import { computed, useSlots } from "vue";
56
42
  /**
57
43
  * hlw-card 卡片容器
58
44
  *
59
- * @example 基础用法
45
+ * 头部用法已迁出:常规标题 + 副标题请用 <hlw-card-header>,写在 default slot 里
46
+ * (记得 :padding="false" 避免 body padding 把 header 顶出来)。
47
+ *
48
+ * @example 标准头部 + body
60
49
  * ```vue
61
- * <hlw-card title="标题" extra="更多">
62
- * <text>内容</text>
50
+ * <hlw-card :padding="false">
51
+ * <hlw-card-header title="标题" icon="i-fa6-solid-star" extra="副标题" />
52
+ * <view style="padding: 24rpx 28rpx">content</view>
63
53
  * </hlw-card>
64
54
  * ```
65
55
  *
66
- * @example 自定义头部左右
56
+ * @example 完全自定义头部
67
57
  * ```vue
68
58
  * <hlw-card>
69
- * <template #header-left>
70
- * <text>自定义左侧</text>
71
- * </template>
72
- * <template #header-right>
73
- * <button>操作</button>
74
- * </template>
59
+ * <template #header>...</template>
75
60
  * <text>内容</text>
76
61
  * </hlw-card>
77
62
  * ```
78
63
  *
79
64
  * @example 自定义底部
80
65
  * ```vue
81
- * <hlw-card title="标题">
66
+ * <hlw-card>
82
67
  * <text>内容</text>
83
- * <template #footer-left>
84
- * <text>左侧说明</text>
85
- * </template>
86
- * <template #footer-right>
87
- * <button>确认</button>
88
- * </template>
68
+ * <template #footer-left><text>左侧说明</text></template>
69
+ * <template #footer-right><button>确认</button></template>
89
70
  * </hlw-card>
90
71
  * ```
91
72
  */
73
+ type BorderValue = boolean | string | string[];
74
+
92
75
  interface Props {
93
- /** 卡片标题 */
94
- title?: string;
95
- /** 头部右侧文字(无 header-right slot 时显示) */
96
- extra?: string;
97
- /** 是否显示边框,默认 true */
98
- border?: boolean;
76
+ /**
77
+ * 边框,支持三种形式:
78
+ * - `true`(默认)/`false` —— 四边全开 / 全关
79
+ * - 字符串:"t r b l" 或 "top right bottom left",空格分隔,如 `"t b"` = 仅上下
80
+ * - 数组:`['t','l']` 同上
81
+ */
82
+ border?: BorderValue;
83
+ /** 边框颜色,任意 CSS color:`#f00`、`rgb(...)`、`var(--xxx)`;默认走主题 var(--border-color) */
84
+ borderColor?: string;
85
+ /** 边框线型:solid(默认)/ dashed / dotted / double */
86
+ borderStyle?: "solid" | "dashed" | "dotted" | "double";
87
+ /** 边框宽度,CSS 长度值,默认 `1rpx` */
88
+ borderWidth?: string;
99
89
  /** 圆角大小,对应 CSS 变量体系 */
100
90
  radius?: "none" | "sm" | "md" | "lg" | "xl";
101
- /** 头部与内容之间是否显示虚线分隔,有头部时默认 true */
91
+ /** 头部与内容之间是否显示虚线分隔,有 #header slot 时默认 true */
102
92
  divider?: boolean;
103
93
  /** body 是否有内边距,默认 true */
104
94
  padding?: boolean;
105
95
  }
106
96
 
107
97
  const props = withDefaults(defineProps<Props>(), {
108
- title: "",
109
- extra: "",
110
98
  border: true,
99
+ borderColor: "",
100
+ borderStyle: "solid",
101
+ borderWidth: "",
111
102
  radius: "xl",
112
103
  divider: undefined,
113
104
  padding: true,
114
105
  });
115
106
 
107
+ const rootStyle = computed<Record<string, string>>(() => {
108
+ const s: Record<string, string> = {};
109
+ if (props.borderColor) s["--card-border-color"] = props.borderColor;
110
+ if (props.borderStyle && props.borderStyle !== "solid") s["--card-border-style"] = props.borderStyle;
111
+ if (props.borderWidth) s["--card-border-width"] = props.borderWidth;
112
+ return s;
113
+ });
114
+
115
+ const SIDE_MAP: Record<string, string> = {
116
+ t: "top", top: "top",
117
+ r: "right", right: "right",
118
+ b: "bottom", bottom: "bottom",
119
+ l: "left", left: "left",
120
+ };
121
+
122
+ const borderClasses = computed<string[]>(() => {
123
+ if (props.border === false) return [];
124
+ if (props.border === true) return ["hlw-card--bordered"];
125
+
126
+ const sides = Array.isArray(props.border)
127
+ ? props.border
128
+ : String(props.border).trim().split(/\s+/).filter(Boolean);
129
+
130
+ const seen = new Set<string>();
131
+ const classes: string[] = [];
132
+ for (const s of sides) {
133
+ const side = SIDE_MAP[s.toLowerCase()];
134
+ if (side && !seen.has(side)) {
135
+ seen.add(side);
136
+ classes.push(`hlw-card--border-${side}`);
137
+ }
138
+ }
139
+ return classes.length === 4 ? ["hlw-card--bordered"] : classes;
140
+ });
141
+
116
142
  const slots = useSlots();
117
143
 
118
- const hasHeader = computed(
119
- () => !!(props.title || props.extra || slots.header || slots["header-left"] || slots["header-right"]),
120
- );
144
+ const hasHeader = computed(() => !!slots.header);
121
145
 
122
146
  const hasFooter = computed(
123
147
  () => !!(slots.footer || slots["footer-left"] || slots["footer-right"]),
@@ -142,46 +166,46 @@ const showDivider = computed(() => {
142
166
  &--radius-lg { border-radius: var(--radius-lg, 24rpx); }
143
167
  &--radius-xl { border-radius: var(--radius-xl, 32rpx); }
144
168
 
145
- /* 边框 */
169
+ /* 边框 — width / style / color 全部走 CSS 变量,未设置时回落 */
146
170
  &--bordered {
147
- border: 1rpx solid var(--border-color, #e2e8f0);
171
+ border:
172
+ var(--card-border-width, 1rpx)
173
+ var(--card-border-style, solid)
174
+ var(--card-border-color, var(--border-color, #e2e8f0));
175
+ }
176
+
177
+ /* 边框 — 单边 */
178
+ &--border-top {
179
+ border-top:
180
+ var(--card-border-width, 1rpx)
181
+ var(--card-border-style, solid)
182
+ var(--card-border-color, var(--border-color, #e2e8f0));
183
+ }
184
+ &--border-right {
185
+ border-right:
186
+ var(--card-border-width, 1rpx)
187
+ var(--card-border-style, solid)
188
+ var(--card-border-color, var(--border-color, #e2e8f0));
189
+ }
190
+ &--border-bottom {
191
+ border-bottom:
192
+ var(--card-border-width, 1rpx)
193
+ var(--card-border-style, solid)
194
+ var(--card-border-color, var(--border-color, #e2e8f0));
195
+ }
196
+ &--border-left {
197
+ border-left:
198
+ var(--card-border-width, 1rpx)
199
+ var(--card-border-style, solid)
200
+ var(--card-border-color, var(--border-color, #e2e8f0));
148
201
  }
149
202
  }
150
203
 
151
- /* 头部 */
204
+ /* 头部 wrapper(#header slot) */
152
205
  .hlw-card-header {
153
206
  width: 100%;
154
207
  }
155
208
 
156
- .hlw-card-header-inner {
157
- display: flex;
158
- align-items: center;
159
- justify-content: space-between;
160
- padding: 24rpx 28rpx;
161
- }
162
-
163
- .hlw-card-header-left {
164
- flex: 1;
165
- min-width: 0;
166
- }
167
-
168
- .hlw-card-header-right {
169
- flex-shrink: 0;
170
- margin-left: 16rpx;
171
- }
172
-
173
- .hlw-card-title {
174
- font-size: var(--font-sm, 24rpx);
175
- font-weight: 700;
176
- color: #1e293b;
177
- letter-spacing: 0.02em;
178
- }
179
-
180
- .hlw-card-extra {
181
- font-size: var(--font-xs, 20rpx);
182
- color: #94a3b8;
183
- }
184
-
185
209
  /* 虚线分隔 */
186
210
  .hlw-card-divider {
187
211
  width: 100%;
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <view class="hlw-card-header">
3
+ <view class="hlw-card-header__left">
4
+ <slot name="left">
5
+ <text v-if="icon" class="hlw-card-header__icon" :class="icon" />
6
+ <text v-if="title" class="hlw-card-header__title">{{ title }}</text>
7
+ </slot>
8
+ </view>
9
+ <view v-if="hasRight" class="hlw-card-header__right">
10
+ <slot name="right">
11
+ <text v-if="extra" class="hlw-card-header__extra">{{ extra }}</text>
12
+ </slot>
13
+ </view>
14
+ </view>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { computed, useSlots } from "vue";
19
+
20
+ /**
21
+ * hlw-card-header — 卡片头部独立组件
22
+ *
23
+ * 三种用法:
24
+ *
25
+ * @example A. 直接当 <hlw-card> 的 default slot 子元素(注意把 hlw-card 的 padding 关掉,否则会双重 padding)
26
+ * ```vue
27
+ * <hlw-card :padding="false">
28
+ * <hlw-card-header title="标题" icon="i-fa6-solid-heart-pulse text-rose-500" extra="副标题" />
29
+ * <view style="padding: 24rpx 28rpx">body</view>
30
+ * </hlw-card>
31
+ * ```
32
+ *
33
+ * @example B. 放进 <hlw-card> 的 #header slot(保留 body 默认 padding)
34
+ * ```vue
35
+ * <hlw-card>
36
+ * <template #header>
37
+ * <hlw-card-header title="标题" icon="i-fa6-solid-star" extra="副标题" />
38
+ * </template>
39
+ * body
40
+ * </hlw-card>
41
+ * ```
42
+ *
43
+ * @example C. 完全独立(不必嵌在 hlw-card 里)
44
+ * ```vue
45
+ * <hlw-card-header title="独立标题">
46
+ * <template #right><button>操作</button></template>
47
+ * </hlw-card-header>
48
+ * ```
49
+ */
50
+ interface Props {
51
+ /** 标题文字 */
52
+ title?: string;
53
+ /** 图标 class(iconify 或自定义),如 `"i-fa6-solid-heart-pulse text-rose-500"` */
54
+ icon?: string;
55
+ /** 右侧附加文字(无 right slot 时显示) */
56
+ extra?: string;
57
+ }
58
+
59
+ const props = withDefaults(defineProps<Props>(), {
60
+ title: "",
61
+ icon: "",
62
+ extra: "",
63
+ });
64
+
65
+ const slots = useSlots();
66
+
67
+ const hasRight = computed(() => !!(slots.right || props.extra));
68
+
69
+ defineOptions({
70
+ name: "HlwCardHeader",
71
+ });
72
+ </script>
73
+
74
+ <style lang="scss" scoped>
75
+ .hlw-card-header {
76
+ width: 100%;
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: space-between;
80
+ padding: 24rpx 28rpx;
81
+ box-sizing: border-box;
82
+ }
83
+
84
+ .hlw-card-header__left {
85
+ flex: 1;
86
+ min-width: 0;
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 12rpx;
90
+ }
91
+
92
+ .hlw-card-header__right {
93
+ flex-shrink: 0;
94
+ margin-left: 16rpx;
95
+ }
96
+
97
+ .hlw-card-header__icon {
98
+ font-size: var(--font-base, 28rpx);
99
+ }
100
+
101
+ .hlw-card-header__title {
102
+ font-size: var(--font-sm, 24rpx);
103
+ font-weight: 700;
104
+ color: var(--text-primary, #1e293b);
105
+ letter-spacing: 0.02em;
106
+ }
107
+
108
+ .hlw-card-header__extra {
109
+ font-size: var(--font-xs, 20rpx);
110
+ color: var(--text-subtle, #94a3b8);
111
+ }
112
+ </style>
@@ -13,6 +13,8 @@
13
13
 
14
14
  <scroll-view
15
15
  class="hlw-page-content"
16
+ :class="bodyClass"
17
+ :style="bodyStyle"
16
18
  :scroll-y="true"
17
19
  :enable-flex="true"
18
20
  :enhanced="true"
@@ -39,16 +41,25 @@ defineOptions({
39
41
  inheritAttrs: false,
40
42
  });
41
43
 
44
+ type ClassValue = string | Record<string, boolean> | Array<string | Record<string, boolean>>;
45
+ type StyleValue = string | Record<string, string | number>;
46
+
42
47
  interface Props {
43
48
  title?: string;
44
49
  isBack?: boolean;
45
50
  bgClass?: string;
51
+ /** 透传到 scroll-view 内容区的 class,常用于直接挂 .container */
52
+ bodyClass?: ClassValue;
53
+ /** 透传到 scroll-view 内容区的 style */
54
+ bodyStyle?: StyleValue;
46
55
  }
47
56
 
48
57
  const props = withDefaults(defineProps<Props>(), {
49
58
  title: "",
50
59
  isBack: false,
51
60
  bgClass: "",
61
+ bodyClass: "",
62
+ bodyStyle: "",
52
63
  });
53
64
  </script>
54
65