@hlw-uni/mp-vue 1.0.35 → 1.1.1

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.0.35",
3
+ "version": "1.1.1",
4
4
  "description": "hlw-uni Vue 组件库 — 供小程序业务方使用的 UI 组件集合",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -14,6 +14,21 @@
14
14
  </template>
15
15
 
16
16
  <script setup lang="ts">
17
+ /**
18
+ * HlwAvatar — 头像组件
19
+ *
20
+ * 显示用户头像,图片加载失败时自动回退到姓名首字母占位。
21
+ *
22
+ * @props
23
+ * src - 头像图片地址
24
+ * name - 用户名称,用于提取首字母(图片缺失时显示)
25
+ * size - 尺寸:small(56rpx) / medium(80rpx) / large(120rpx),默认 medium
26
+ *
27
+ * @example
28
+ * ```vue
29
+ * <HlwAvatar src="/avatar.png" name="张三" size="large" />
30
+ * ```
31
+ */
17
32
  import { ref, computed } from 'vue';
18
33
 
19
34
  const props = defineProps<{
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <button
3
+ class="hlw-btn"
4
+ :class="[
5
+ `hlw-btn--${type}`,
6
+ `hlw-btn--${size}`,
7
+ { 'hlw-btn--block': block, 'hlw-btn--round': round, 'hlw-btn--disabled': disabled, 'hlw-btn--loading': loading },
8
+ ]"
9
+ :disabled="disabled || loading"
10
+ :open-type="openType"
11
+ @tap="$emit('click')"
12
+ >
13
+ <view v-if="loading" class="hlw-btn-spinner" />
14
+ <view v-else-if="icon" :class="icon" class="hlw-btn-icon" />
15
+ <slot />
16
+ </button>
17
+ </template>
18
+
19
+ <script setup lang="ts">
20
+ /**
21
+ * HlwButton — 主题按钮
22
+ *
23
+ * 跟随 --primary-color 主题色,支持多种类型、尺寸和状态。
24
+ *
25
+ * @props
26
+ * type - 按钮类型:primary / outline / text / ghost,默认 primary
27
+ * size - 尺寸:small / medium / large,默认 medium
28
+ * loading - 加载状态(显示 spinner 并禁止点击),默认 false
29
+ * disabled - 禁用状态,默认 false
30
+ * block - 块级按钮(占满父容器宽度),默认 false
31
+ * round - 圆角药丸形状,默认 false
32
+ * icon - 左侧图标 class(如 i-fa6-solid-plus)
33
+ * openType - 微信原生 open-type(如 share、getPhoneNumber)
34
+ *
35
+ * @events
36
+ * click - 点击事件
37
+ *
38
+ * @example
39
+ * ```vue
40
+ * <HlwButton type="primary" @click="submit">提交</HlwButton>
41
+ * <HlwButton type="outline" loading>加载中</HlwButton>
42
+ * <HlwButton type="text" icon="i-fa6-solid-plus">新增</HlwButton>
43
+ * ```
44
+ */
45
+ interface Props {
46
+ type?: "primary" | "outline" | "text" | "ghost";
47
+ size?: "small" | "medium" | "large";
48
+ loading?: boolean;
49
+ disabled?: boolean;
50
+ block?: boolean;
51
+ round?: boolean;
52
+ icon?: string;
53
+ openType?: string;
54
+ }
55
+
56
+ withDefaults(defineProps<Props>(), {
57
+ type: "primary",
58
+ size: "medium",
59
+ loading: false,
60
+ disabled: false,
61
+ block: false,
62
+ round: false,
63
+ icon: "",
64
+ openType: "",
65
+ });
66
+
67
+ defineEmits<{ click: [] }>();
68
+ </script>
69
+
70
+ <style lang="scss" scoped>
71
+ .hlw-btn {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ gap: 8rpx;
76
+ border: none;
77
+ font-weight: 500;
78
+ line-height: 1;
79
+ transition: opacity 0.2s;
80
+
81
+ &::after { display: none; }
82
+ &:active { opacity: 0.7; }
83
+
84
+ &--primary {
85
+ background: var(--primary-color);
86
+ color: #fff;
87
+ }
88
+ &--outline {
89
+ background: transparent;
90
+ color: var(--primary-color);
91
+ border: 2rpx solid var(--primary-color);
92
+ }
93
+ &--text {
94
+ background: transparent;
95
+ color: var(--primary-color);
96
+ }
97
+ &--ghost {
98
+ background: transparent;
99
+ color: #fff;
100
+ border: 2rpx solid rgba(255, 255, 255, 0.6);
101
+ }
102
+
103
+ &--small {
104
+ padding: 8rpx 20rpx;
105
+ font-size: var(--font-xs, 20rpx);
106
+ border-radius: var(--radius-sm, 8rpx);
107
+ }
108
+ &--medium {
109
+ padding: 16rpx 32rpx;
110
+ font-size: var(--font-sm, 24rpx);
111
+ border-radius: var(--radius-md, 16rpx);
112
+ }
113
+ &--large {
114
+ padding: 24rpx 48rpx;
115
+ font-size: var(--font-base, 28rpx);
116
+ border-radius: var(--radius-md, 16rpx);
117
+ }
118
+
119
+ &--block { display: flex; width: 100%; }
120
+ &--round { border-radius: 999rpx; }
121
+ &--disabled { opacity: 0.4; &:active { opacity: 0.4; } }
122
+ &--loading { opacity: 0.7; }
123
+ }
124
+
125
+ .hlw-btn-icon {
126
+ font-size: 1.1em;
127
+ }
128
+
129
+ .hlw-btn-spinner {
130
+ width: 28rpx;
131
+ height: 28rpx;
132
+ border: 3rpx solid currentColor;
133
+ border-top-color: transparent;
134
+ border-radius: 50%;
135
+ animation: hlw-spin 0.6s linear infinite;
136
+ }
137
+
138
+ @keyframes hlw-spin {
139
+ to { transform: rotate(360deg); }
140
+ }
141
+ </style>
@@ -0,0 +1,143 @@
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
+ /**
37
+ * HlwCell — 列表单元格
38
+ *
39
+ * 通用列表项:左侧 icon + 标题/描述 + 右侧内容/箭头。
40
+ * 传入 url 自动渲染为 navigator 组件。
41
+ *
42
+ * @props
43
+ * title - 标题文字
44
+ * label - 描述文字(标题下方灰色小字)
45
+ * value - 右侧内容文字
46
+ * icon - 左侧图标 class(如 i-fa6-solid-gear)
47
+ * isLink - 是否显示右箭头,默认 false
48
+ * url - 跳转地址(有值则渲染为 navigator)
49
+ * border - 是否显示底部分割线,默认 true
50
+ *
51
+ * @events
52
+ * click - 点击事件(url 模式下由 navigator 处理跳转)
53
+ *
54
+ * @slots
55
+ * icon - 自定义左侧图标
56
+ * title - 自定义标题区域
57
+ * value - 自定义右侧内容
58
+ * right - 自定义最右侧区域
59
+ *
60
+ * @example
61
+ * ```vue
62
+ * <HlwCell title="设置" icon="i-fa6-solid-gear" is-link url="/pages/settings/index" />
63
+ * <HlwCell title="版本号" value="1.0.0" />
64
+ * ```
65
+ */
66
+ interface Props {
67
+ title?: string;
68
+ label?: string;
69
+ value?: string;
70
+ icon?: string;
71
+ isLink?: boolean;
72
+ url?: string;
73
+ border?: boolean;
74
+ }
75
+
76
+ withDefaults(defineProps<Props>(), {
77
+ title: "",
78
+ label: "",
79
+ value: "",
80
+ icon: "",
81
+ isLink: false,
82
+ url: "",
83
+ border: true,
84
+ });
85
+
86
+ defineEmits<{ click: [] }>();
87
+ </script>
88
+
89
+ <style lang="scss" scoped>
90
+ .hlw-cell {
91
+ display: flex;
92
+ align-items: center;
93
+ padding: 24rpx 32rpx;
94
+ background: #fff;
95
+ gap: 20rpx;
96
+
97
+ &--border {
98
+ border-bottom: 1rpx solid var(--border-color-light, #f1f5f9);
99
+ &:last-child { border-bottom: none; }
100
+ }
101
+ &--hover { background: #f8fafc; }
102
+ }
103
+
104
+ .hlw-cell-icon {
105
+ font-size: var(--font-lg, 36rpx);
106
+ color: var(--primary-color, #3b82f6);
107
+ flex-shrink: 0;
108
+ }
109
+
110
+ .hlw-cell-body {
111
+ flex: 1;
112
+ min-width: 0;
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: space-between;
116
+ }
117
+
118
+ .hlw-cell-title {
119
+ font-size: var(--font-base, 28rpx);
120
+ color: #1e293b;
121
+ }
122
+
123
+ .hlw-cell-label {
124
+ font-size: var(--font-xs, 20rpx);
125
+ color: #94a3b8;
126
+ margin-top: 4rpx;
127
+ }
128
+
129
+ .hlw-cell-value {
130
+ font-size: var(--font-sm, 24rpx);
131
+ color: #94a3b8;
132
+ flex-shrink: 0;
133
+ }
134
+
135
+ .hlw-cell-arrow {
136
+ width: 16rpx;
137
+ height: 16rpx;
138
+ border-top: 3rpx solid #c0c4cc;
139
+ border-right: 3rpx solid #c0c4cc;
140
+ transform: rotate(45deg);
141
+ flex-shrink: 0;
142
+ }
143
+ </style>
@@ -0,0 +1,72 @@
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
+ /**
13
+ * HlwDivider — 分割线
14
+ *
15
+ * 水平分割线,可带文字说明,支持虚线和文字位置调整。
16
+ *
17
+ * @props
18
+ * text - 分割线中间文字
19
+ * position - 文字位置:left / center / right,默认 center
20
+ * dashed - 是否虚线,默认 false
21
+ *
22
+ * @slots
23
+ * default - 自定义分割线中间内容(覆盖 text)
24
+ *
25
+ * @example
26
+ * ```vue
27
+ * <HlwDivider />
28
+ * <HlwDivider text="或" />
29
+ * <HlwDivider text="更多" position="left" dashed />
30
+ * ```
31
+ */
32
+ interface Props {
33
+ text?: string;
34
+ position?: "left" | "center" | "right";
35
+ dashed?: boolean;
36
+ }
37
+
38
+ withDefaults(defineProps<Props>(), {
39
+ text: "",
40
+ position: "center",
41
+ dashed: false,
42
+ });
43
+ </script>
44
+
45
+ <style lang="scss" scoped>
46
+ .hlw-divider {
47
+ display: flex;
48
+ align-items: center;
49
+ padding: 24rpx 0;
50
+ }
51
+
52
+ .hlw-divider-line {
53
+ flex: 1;
54
+ height: 1rpx;
55
+ background: var(--border-color, #e2e8f0);
56
+
57
+ .hlw-divider--dashed & {
58
+ background: none;
59
+ border-top: 1rpx dashed var(--border-color, #e2e8f0);
60
+ }
61
+ }
62
+
63
+ .hlw-divider-text {
64
+ padding: 0 24rpx;
65
+ font-size: var(--font-xs, 20rpx);
66
+ color: #94a3b8;
67
+ white-space: nowrap;
68
+ }
69
+
70
+ .hlw-divider--left .hlw-divider-line:first-child { max-width: 60rpx; }
71
+ .hlw-divider--right .hlw-divider-line:last-child { max-width: 60rpx; }
72
+ </style>
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <view class="hlw-empty">
3
3
  <image v-if="image" class="hlw-empty__image" :src="image" mode="aspectFit" />
4
- <text v-else class="hlw-empty__icon">📦</text>
5
- <text class="hlw-empty__text">{{ text || '暂无数据' }}</text>
4
+ <view v-else class="hlw-empty__icon i-fa6-solid-box-open" />
5
+ <text class="hlw-empty__text">{{ text || "暂无数据" }}</text>
6
6
  <slot />
7
7
  </view>
8
8
  </template>
@@ -32,6 +32,7 @@ defineProps<{
32
32
  .hlw-empty__icon {
33
33
  font-size: 100rpx;
34
34
  margin-bottom: 20rpx;
35
+ color: #cbd5e1;
35
36
  }
36
37
 
37
38
  .hlw-empty__text {
@@ -34,6 +34,34 @@
34
34
  </template>
35
35
 
36
36
  <script setup lang="ts">
37
+ /**
38
+ * HlwHeader — 页面顶部导航栏
39
+ *
40
+ * 自动适配状态栏高度和胶囊按钮位置(微信小程序),支持返回按钮、自定义标题和背景。
41
+ *
42
+ * @props
43
+ * title - 标题文字
44
+ * titleAlign - 标题对齐:center / left,默认 center
45
+ * titleSize - 标题字号,默认 var(--font-base)
46
+ * titleWeight - 标题字重,默认 500
47
+ * titleColor - 标题颜色
48
+ * isBack - 是否显示返回按钮,默认 false
49
+ * bgClass - 自定义背景 CSS class
50
+ * extraHeight - 额外高度(rpx),默认 0
51
+ *
52
+ * @events
53
+ * back - 点击返回按钮
54
+ *
55
+ * @slots
56
+ * bg - 自定义背景层
57
+ * title - 自定义标题区域
58
+ * back-icon - 自定义返回图标
59
+ *
60
+ * @example
61
+ * ```vue
62
+ * <HlwHeader title="我的" is-back @back="goBack" />
63
+ * ```
64
+ */
37
65
  import { ref, computed, useSlots } from "vue";
38
66
 
39
67
  const getNavBarContentHeight = (): number => {
@@ -6,6 +6,19 @@
6
6
  </template>
7
7
 
8
8
  <script setup lang="ts">
9
+ /**
10
+ * HlwLoading — 加载动画组件
11
+ *
12
+ * 圆形旋转 spinner,可附带文字提示。
13
+ *
14
+ * @props
15
+ * text - 加载提示文字(可选)
16
+ *
17
+ * @example
18
+ * ```vue
19
+ * <HlwLoading text="加载中..." />
20
+ * ```
21
+ */
9
22
  defineProps<{
10
23
  text?: string;
11
24
  }>();
@@ -0,0 +1,157 @@
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
+ /**
20
+ * HlwModal — 模态弹窗
21
+ *
22
+ * 比 uni.showModal 更灵活:支持插槽自定义内容和底部按钮。
23
+ * 使用 v-model:show 控制显隐。
24
+ *
25
+ * @props
26
+ * show - 是否显示,支持 v-model:show
27
+ * title - 标题文字
28
+ * showCancel - 是否显示取消按钮,默认 true
29
+ * confirmText - 确认按钮文字,默认 "确定"
30
+ * cancelText - 取消按钮文字,默认 "取消"
31
+ * closeOnMask - 点击蒙层是否关闭,默认 true
32
+ *
33
+ * @events
34
+ * update:show - 显隐状态变更
35
+ * confirm - 点击确认
36
+ * cancel - 点击取消
37
+ *
38
+ * @slots
39
+ * default - 弹窗内容
40
+ * footer - 自定义底部按钮区域(覆盖默认确认/取消)
41
+ *
42
+ * @example
43
+ * ```vue
44
+ * <HlwModal v-model:show="visible" title="提示" @confirm="onOk">
45
+ * <text>确定要删除吗?</text>
46
+ * </HlwModal>
47
+ * ```
48
+ */
49
+ interface Props {
50
+ show?: boolean;
51
+ title?: string;
52
+ showCancel?: boolean;
53
+ confirmText?: string;
54
+ cancelText?: string;
55
+ closeOnMask?: boolean;
56
+ }
57
+
58
+ withDefaults(defineProps<Props>(), {
59
+ show: false,
60
+ title: "",
61
+ showCancel: true,
62
+ confirmText: "确定",
63
+ cancelText: "取消",
64
+ closeOnMask: true,
65
+ });
66
+
67
+ const emit = defineEmits<{ "update:show": [value: boolean]; confirm: []; cancel: [] }>();
68
+
69
+ function close() {
70
+ emit("update:show", false);
71
+ }
72
+
73
+ function onMask() {
74
+ close();
75
+ }
76
+
77
+ function onConfirm() {
78
+ emit("confirm");
79
+ close();
80
+ }
81
+
82
+ function onCancel() {
83
+ emit("cancel");
84
+ close();
85
+ }
86
+ </script>
87
+
88
+ <style lang="scss" scoped>
89
+ .hlw-modal-mask {
90
+ position: fixed;
91
+ inset: 0;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ background: rgba(0, 0, 0, 0.5);
96
+ z-index: 1000;
97
+ animation: hlw-fade-in 0.2s;
98
+ }
99
+
100
+ .hlw-modal {
101
+ width: 80%;
102
+ max-width: 600rpx;
103
+ background: #fff;
104
+ border-radius: var(--radius-xl, 32rpx);
105
+ overflow: hidden;
106
+ animation: hlw-scale-in 0.25s ease;
107
+ }
108
+
109
+ .hlw-modal-title {
110
+ padding: 40rpx 32rpx 0;
111
+ text-align: center;
112
+ font-size: var(--font-md, 32rpx);
113
+ font-weight: 600;
114
+ color: #1e293b;
115
+ }
116
+
117
+ .hlw-modal-body {
118
+ padding: 32rpx;
119
+ font-size: var(--font-base, 28rpx);
120
+ color: #475569;
121
+ text-align: center;
122
+ line-height: 1.6;
123
+ }
124
+
125
+ .hlw-modal-footer {
126
+ display: flex;
127
+ border-top: 1rpx solid var(--border-color-light, #f1f5f9);
128
+ }
129
+
130
+ .hlw-modal-btn {
131
+ flex: 1;
132
+ padding: 24rpx 0;
133
+ text-align: center;
134
+ font-size: var(--font-base, 28rpx);
135
+ font-weight: 500;
136
+
137
+ &:active { background: #f8fafc; }
138
+
139
+ &--cancel {
140
+ color: #64748b;
141
+ border-right: 1rpx solid var(--border-color-light, #f1f5f9);
142
+ }
143
+ &--confirm {
144
+ color: var(--primary-color, #3b82f6);
145
+ }
146
+ }
147
+
148
+ @keyframes hlw-fade-in {
149
+ from { opacity: 0; }
150
+ to { opacity: 1; }
151
+ }
152
+
153
+ @keyframes hlw-scale-in {
154
+ from { transform: scale(0.9); opacity: 0; }
155
+ to { transform: scale(1); opacity: 1; }
156
+ }
157
+ </style>